Import Cobalt 23.master.0.306649
diff --git a/.codespellignorelines b/.codespellignorelines
index 1a26ffa..e2b2905 100644
--- a/.codespellignorelines
+++ b/.codespellignorelines
@@ -1,2 +1,3 @@
vp9AllowList.get("Technicolor").add("STING");
return SkSurface::MakeRenderTarget(gr_context_.get(), SkBudgeted::kNo,
+ texture_infos.push_back(TextureInfo("uv", "ba"));
diff --git a/.gn b/.gn
index 4d67c09..5fb6ec2 100644
--- a/.gn
+++ b/.gn
@@ -14,3 +14,8 @@
# The location of the build configuration file.
buildconfig = "//starboard/build/config/BUILDCONFIG.gn"
+
+# We have `python3` if we're in a container or on buildbot, so use that.
+if (getenv("IS_DOCKER") == "1" || getenv("BUILDBOT_PROJECT") != "") {
+ script_executable = "python3"
+}
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 033e9b1..e80ac24 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -10,6 +10,8 @@
(
base|
build|
+ buildtools|
+ crypto|
net|
testing|
third_party|
@@ -68,17 +70,8 @@
exclude: |
(?x)(
^cobalt/bindings/(templates|generated)/|
- ^starboard/shared/uikit/.*\.h$|
- tests?\.(cc|h)$
+ ^starboard/shared/uikit/.*\.h$
)
- - id: cpplint_test
- name: cpplint_test
- entry: cpplint
- language: system
- types: [c++]
- args: [--verbose=5, --quiet]
- files: '.*tests?.(cc|h)$'
- exclude: '^starboard/shared/uikit/.*\.h$'
- id: yapf
name: yapf
description: Run yapf (the python formatter) in-place on changed files.
@@ -114,10 +107,12 @@
stages: [push]
exclude: |
(?x)^(
+ cobalt/media/|
cobalt/layout_tests/testdata/|
starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java$
)
exclude_types: [markdown]
+ verbose: true
- id: check-if-starboard-interface-changed
name: check if starboard interface changed
entry: python ./precommit_hooks/warn_that_starboard_interface_changed_wrapper.py
@@ -174,7 +169,7 @@
types: [text]
# TODO: Remove docker-compose-windows.yml after internal check evaluates
# properly on it.
- exclude: '(^docker-compose-windows.yml|EXCLUDE\.FILES(\.RECURSIVE)?)$'
+ exclude: '(^docker-compose-windows.yml|EXCLUDE\.FILES(\.RECURSIVE)?|codereview\.settings)$'
- id: gn-format
name: GN format
entry: gn format
diff --git a/BUILD.gn b/BUILD.gn
index fa1f55e..d2d4ccd 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -27,7 +27,7 @@
if (is_qa || is_gold) {
deps = [
"//cobalt:default",
- "//starboard",
+ "//starboard:default",
]
} else {
deps = [ ":gn_all" ]
diff --git a/base/BUILD.gn b/base/BUILD.gn
index d4c5d7c..c0af895 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -94,10 +94,10 @@
}
config("base_public_defines") {
- defines = [
- "BASE_DONT_ENFORCE_THREAD_NAME_LENGTH",
- "ENABLE_TEST_DATA",
- ]
+ defines = [ "BASE_DONT_ENFORCE_THREAD_NAME_LENGTH" ]
+ if (!is_gold) {
+ defines += [ "ENABLE_TEST_DATA" ]
+ }
}
if (is_win) {
@@ -1107,6 +1107,10 @@
"debug/proc_maps_linux.h",
"debug/stack_trace_android.cc",
"debug/stack_trace_win.cc",
+
+ # No usage found for Cobalt yet and they introduce API leaks.
+ "debug/thread_heap_usage_tracker.cc",
+ "debug/thread_heap_usage_tracker.h",
"file_descriptor_store.cc",
"file_descriptor_store.h",
"file_version_info_mac.h",
@@ -1583,7 +1587,12 @@
# more robust check for this.
if (!use_sysroot && (is_android || (is_linux && !is_chromecast)) &&
host_toolchain != "//build/toolchain/cros:host") {
- libs += [ "atomic" ]
+ # TODO(b/206642994): see if we can remove this condition. It's added for now
+ # because linking atomic leads to linker errors for evergreen on arm-hardfp,
+ # and it's apparently not really needed (i.e., we can build without it).
+ if (!is_starboard || !sb_is_evergreen) {
+ libs += [ "atomic" ]
+ }
}
if (!is_starboard && use_allocator_shim) {
@@ -2557,7 +2566,7 @@
}
}
-if (is_win || is_mac) {
+if (!is_starboard && (is_win || is_mac)) {
if (current_cpu == "x64") {
# Must be a shared library so that it can be unloaded during testing.
shared_library("base_profiler_test_support_library") {
@@ -2568,7 +2577,7 @@
}
}
-bundle_data("base_unittests_bundle_data") {
+copy("base_unittests_bundle_data") {
testonly = true
sources = [
"//tools/metrics/histograms/enums.xml",
@@ -2594,12 +2603,14 @@
"test/data/serializer_test.json",
"test/data/serializer_test_nowhitespace.json",
]
- outputs = [
- "{{bundle_resources_dir}}/" +
- "{{source_root_relative_dir}}/{{source_file_part}}",
- ]
if (is_starboard) {
sources -= [ "//tools/metrics/histograms/enums.xml" ]
+ outputs = [ "$sb_static_contents_output_data_dir/test/base/{{source_target_relative}}" ]
+ } else {
+ outputs = [
+ "{{bundle_resources_dir}}/" +
+ "{{source_root_relative_dir}}/{{source_file_part}}",
+ ]
}
}
@@ -2668,6 +2679,8 @@
"bit_cast_unittest.cc",
"bits_unittest.cc",
"build_time_unittest.cc",
+ # TODO(b/216774170): Fix this test cases, then move to right order.
+ "metrics/persistent_histogram_storage_unittest.cc",
"callback_helpers_unittest.cc",
"callback_list_unittest.cc",
"callback_unittest.cc",
@@ -2790,7 +2803,6 @@
"metrics/histogram_unittest.cc",
"metrics/metrics_hashes_unittest.cc",
"metrics/persistent_histogram_allocator_unittest.cc",
- "metrics/persistent_histogram_storage_unittest.cc",
"metrics/persistent_memory_allocator_unittest.cc",
"metrics/persistent_sample_map_unittest.cc",
"metrics/sample_map_unittest.cc",
diff --git a/base/DEPS b/base/DEPS
new file mode 100644
index 0000000..208d51a
--- /dev/null
+++ b/base/DEPS
@@ -0,0 +1,17 @@
+include_rules = [
+ "+jni",
+ "+third_party/apple_apsl",
+ "+third_party/ashmem",
+ "+third_party/ced",
+ "+third_party/libxml",
+ "+third_party/lss",
+ "+third_party/modp_b64",
+ "+third_party/tcmalloc",
+
+ # These are implicitly brought in from the root, and we don't want them.
+ "-ipc",
+ "-url",
+
+ # ICU dependendencies must be separate from the rest of base.
+ "-i18n",
+]
diff --git a/base/OWNERS b/base/OWNERS
new file mode 100644
index 0000000..5b6a6b5
--- /dev/null
+++ b/base/OWNERS
@@ -0,0 +1,55 @@
+# About src/base:
+#
+# Chromium is a very mature project, most things that are generally useful are
+# already here, and that things not here aren't generally useful.
+#
+# Base is pulled into many projects. For example, various ChromeOS daemons. So
+# the bar for adding stuff is that it must have demonstrated wide
+# applicability. Prefer to add things closer to where they're used (i.e. "not
+# base"), and pull into base only when needed. In a project our size,
+# sometimes even duplication is OK and inevitable.
+#
+# Adding a new logging macro DPVELOG_NE is not more clear than just
+# writing the stuff you want to log in a regular logging statement, even
+# if it makes your calling code longer. Just add it to your own code.
+#
+# If the code in question does not need to be used inside base, but will have
+# multiple consumers across the codebase, consider placing it in a new directory
+# under components/ instead.
+
+ajwong@chromium.org
+danakj@chromium.org
+dcheng@chromium.org
+fdoray@chromium.org
+gab@chromium.org
+kylechar@chromium.org
+mark@chromium.org
+thakis@chromium.org
+thestig@chromium.org
+
+# For Bind/Callback:
+per-file bind*=tzik@chromium.org
+per-file callback*=tzik@chromium.org
+
+# For Android-specific changes:
+per-file *android*=file://base/android/OWNERS
+per-file BUILD.gn=file://base/android/OWNERS
+
+# For Fuchsia-specific changes:
+per-file *_fuchsia*=file://build/fuchsia/OWNERS
+
+# For FeatureList API:
+per-file feature_list*=asvitkine@chromium.org
+per-file feature_list*=isherman@chromium.org
+
+# Restricted since rand_util.h also backs the cryptographically secure RNG.
+per-file rand_util*=set noparent
+per-file rand_util*=file://ipc/SECURITY_OWNERS
+
+# For TCMalloc tests:
+per-file security_unittest.cc=jln@chromium.org
+
+# For Value:
+per-file values*=jdoerrie@chromium.org
+
+# COMPONENT: Internals>Core
diff --git a/base/PRESUBMIT.py b/base/PRESUBMIT.py
new file mode 100644
index 0000000..7fc8107
--- /dev/null
+++ b/base/PRESUBMIT.py
@@ -0,0 +1,49 @@
+# Copyright (c) 2012 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.
+
+"""Chromium presubmit script for src/base.
+
+See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
+for more details on the presubmit API built into depot_tools.
+"""
+
+def _CheckNoInterfacesInBase(input_api, output_api):
+ """Checks to make sure no files in libbase.a have |@interface|."""
+ pattern = input_api.re.compile(r'^\s*@interface', input_api.re.MULTILINE)
+ files = []
+ for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
+ if (f.LocalPath().startswith('base/') and
+ not "/ios/" in f.LocalPath() and
+ not "/test/" in f.LocalPath() and
+ not f.LocalPath().endswith('_unittest.mm') and
+ not f.LocalPath().endswith('mac/sdk_forward_declarations.h')):
+ contents = input_api.ReadFile(f)
+ if pattern.search(contents):
+ files.append(f)
+
+ if len(files):
+ return [ output_api.PresubmitError(
+ 'Objective-C interfaces or categories are forbidden in libbase. ' +
+ 'See http://groups.google.com/a/chromium.org/group/chromium-dev/' +
+ 'browse_thread/thread/efb28c10435987fd',
+ files) ]
+ return []
+
+
+def _CommonChecks(input_api, output_api):
+ """Checks common to both upload and commit."""
+ results = []
+ results.extend(_CheckNoInterfacesInBase(input_api, output_api))
+ return results
+
+def CheckChangeOnUpload(input_api, output_api):
+ results = []
+ results.extend(_CommonChecks(input_api, output_api))
+ return results
+
+
+def CheckChangeOnCommit(input_api, output_api):
+ results = []
+ results.extend(_CommonChecks(input_api, output_api))
+ return results
diff --git a/base/allocator/OWNERS b/base/allocator/OWNERS
new file mode 100644
index 0000000..de658d0
--- /dev/null
+++ b/base/allocator/OWNERS
@@ -0,0 +1,4 @@
+primiano@chromium.org
+wfh@chromium.org
+
+# COMPONENT: Internals
diff --git a/base/allocator/partition_allocator/OWNERS b/base/allocator/partition_allocator/OWNERS
new file mode 100644
index 0000000..b0a2a85
--- /dev/null
+++ b/base/allocator/partition_allocator/OWNERS
@@ -0,0 +1,8 @@
+ajwong@chromium.org
+haraken@chromium.org
+palmer@chromium.org
+tsepez@chromium.org
+
+# TEAM: platform-architecture-dev@chromium.org
+# Also: security-dev@chromium.org
+# COMPONENT: Blink>MemoryAllocator>Partition
diff --git a/base/android/OWNERS b/base/android/OWNERS
new file mode 100644
index 0000000..5c40958
--- /dev/null
+++ b/base/android/OWNERS
@@ -0,0 +1,8 @@
+agrieve@chromium.org
+nyquist@chromium.org
+rmcilroy@chromium.org
+torne@chromium.org
+yfriedman@chromium.org
+
+per-file *.aidl=set noparent
+per-file *.aidl=file://ipc/SECURITY_OWNERS
diff --git a/base/android/java/src/org/chromium/base/process_launcher/OWNERS b/base/android/java/src/org/chromium/base/process_launcher/OWNERS
new file mode 100644
index 0000000..c2edc66
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/process_launcher/OWNERS
@@ -0,0 +1,5 @@
+boliu@chromium.org
+jcivelli@chromium.org
+
+per-file *.aidl=set noparent
+per-file *.aidl=file://ipc/SECURITY_OWNERS
diff --git a/base/android/java/src/org/chromium/base/task/OWNERS b/base/android/java/src/org/chromium/base/task/OWNERS
new file mode 100644
index 0000000..1169d15
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/task/OWNERS
@@ -0,0 +1,2 @@
+alexclarke@chromium.org
+smaier@chromium.org
diff --git a/base/android/jni_generator/PRESUBMIT.py b/base/android/jni_generator/PRESUBMIT.py
new file mode 100644
index 0000000..bc76d5b
--- /dev/null
+++ b/base/android/jni_generator/PRESUBMIT.py
@@ -0,0 +1,37 @@
+# Copyright 2018 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.
+
+"""Presubmit script for android buildbot.
+
+See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts for
+details on the presubmit API built into depot_tools.
+"""
+
+
+def CommonChecks(input_api, output_api):
+ base_android_jni_generator_dir = input_api.PresubmitLocalPath()
+
+ env = dict(input_api.environ)
+ env.update({
+ 'PYTHONPATH': base_android_jni_generator_dir,
+ 'PYTHONDONTWRITEBYTECODE': '1',
+ })
+
+ return input_api.canned_checks.RunUnitTests(
+ input_api,
+ output_api,
+ unit_tests=[
+ input_api.os_path.join(
+ base_android_jni_generator_dir, 'jni_generator_tests.py')
+ ],
+ env=env,
+ )
+
+
+def CheckChangeOnUpload(input_api, output_api):
+ return CommonChecks(input_api, output_api)
+
+
+def CheckChangeOnCommit(input_api, output_api):
+ return CommonChecks(input_api, output_api)
diff --git a/base/android/linker/DEPS b/base/android/linker/DEPS
new file mode 100644
index 0000000..15c3afb
--- /dev/null
+++ b/base/android/linker/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+ # This code cannot depend on anything from base/
+ "-base",
+]
diff --git a/base/android/orderfile/OWNERS b/base/android/orderfile/OWNERS
new file mode 100644
index 0000000..d45b803
--- /dev/null
+++ b/base/android/orderfile/OWNERS
@@ -0,0 +1,3 @@
+lizeb@chromium.org
+mattcary@chromium.org
+pasko@chromium.org
diff --git a/base/containers/OWNERS b/base/containers/OWNERS
new file mode 100644
index 0000000..cc39b28
--- /dev/null
+++ b/base/containers/OWNERS
@@ -0,0 +1,3 @@
+danakj@chromium.org
+dcheng@chromium.org
+vmpstr@chromium.org
diff --git a/base/debug/OWNERS b/base/debug/OWNERS
new file mode 100644
index 0000000..6150257
--- /dev/null
+++ b/base/debug/OWNERS
@@ -0,0 +1,2 @@
+# For activity tracking:
+per-file activity_*=bcwhite@chromium.org
diff --git a/base/fuchsia/OWNERS b/base/fuchsia/OWNERS
new file mode 100644
index 0000000..e7034ea
--- /dev/null
+++ b/base/fuchsia/OWNERS
@@ -0,0 +1 @@
+file://build/fuchsia/OWNERS
diff --git a/base/i18n/OWNERS b/base/i18n/OWNERS
new file mode 100644
index 0000000..d717b8d
--- /dev/null
+++ b/base/i18n/OWNERS
@@ -0,0 +1 @@
+jshin@chromium.org
diff --git a/base/ios/OWNERS b/base/ios/OWNERS
new file mode 100644
index 0000000..bdb59ec
--- /dev/null
+++ b/base/ios/OWNERS
@@ -0,0 +1,3 @@
+eugenebut@chromium.org
+rohitrao@chromium.org
+sdefresne@chromium.org
diff --git a/base/json/OWNERS b/base/json/OWNERS
new file mode 100644
index 0000000..e44d995
--- /dev/null
+++ b/base/json/OWNERS
@@ -0,0 +1 @@
+file://base/SECURITY_OWNERS
diff --git a/base/mac/OWNERS b/base/mac/OWNERS
new file mode 100644
index 0000000..93e90e0
--- /dev/null
+++ b/base/mac/OWNERS
@@ -0,0 +1,8 @@
+mark@chromium.org
+thakis@chromium.org
+
+# sdk_forward_declarations.[h|mm] will likely need to be modified by Cocoa
+# developers in general.
+per-file sdk_forward_declarations.*=file://chrome/browser/ui/cocoa/OWNERS
+
+# COMPONENT: Internals
diff --git a/base/memory/OWNERS b/base/memory/OWNERS
new file mode 100644
index 0000000..9b7cbb1
--- /dev/null
+++ b/base/memory/OWNERS
@@ -0,0 +1,4 @@
+per-file *chromeos*=skuhne@chromium.org
+per-file *chromeos*=oshima@chromium.org
+per-file *shared_memory*=set noparent
+per-file *shared_memory*=file://ipc/SECURITY_OWNERS
diff --git a/base/metrics/OWNERS b/base/metrics/OWNERS
new file mode 100644
index 0000000..4cc69ff
--- /dev/null
+++ b/base/metrics/OWNERS
@@ -0,0 +1,10 @@
+asvitkine@chromium.org
+bcwhite@chromium.org
+gayane@chromium.org
+holte@chromium.org
+isherman@chromium.org
+jwd@chromium.org
+mpearson@chromium.org
+rkaplow@chromium.org
+
+# COMPONENT: Internals>Metrics
diff --git a/base/nix/OWNERS b/base/nix/OWNERS
new file mode 100644
index 0000000..280ba47
--- /dev/null
+++ b/base/nix/OWNERS
@@ -0,0 +1 @@
+thomasanderson@chromium.org
diff --git a/base/numerics/DEPS b/base/numerics/DEPS
new file mode 100644
index 0000000..d95bf13
--- /dev/null
+++ b/base/numerics/DEPS
@@ -0,0 +1,7 @@
+# This is a dependency-free, header-only, library, and it needs to stay that
+# way to facilitate pulling it into various third-party projects. So, this
+# file is here to protect against accidentally introducing dependencies.
+include_rules = [
+ "-base",
+ "+base/numerics",
+]
diff --git a/base/numerics/OWNERS b/base/numerics/OWNERS
new file mode 100644
index 0000000..5493fba
--- /dev/null
+++ b/base/numerics/OWNERS
@@ -0,0 +1,5 @@
+jschuh@chromium.org
+tsepez@chromium.org
+
+
+# COMPONENT: Internals
diff --git a/base/profiler/OWNERS b/base/profiler/OWNERS
new file mode 100644
index 0000000..81ff9fa
--- /dev/null
+++ b/base/profiler/OWNERS
@@ -0,0 +1,5 @@
+# Stack sampling profiler
+per-file native_stack_sampler*=wittman@chromium.org
+per-file stack_sampling_profiler*=wittman@chromium.org
+per-file test_support_library*=wittman@chromium.org
+per-file win32_stack_frame_unwinder*=wittman@chromium.org
diff --git a/base/sampling_heap_profiler/OWNERS b/base/sampling_heap_profiler/OWNERS
new file mode 100644
index 0000000..87c9661
--- /dev/null
+++ b/base/sampling_heap_profiler/OWNERS
@@ -0,0 +1 @@
+alph@chromium.org
diff --git a/base/strings/OWNERS b/base/strings/OWNERS
new file mode 100644
index 0000000..5381872
--- /dev/null
+++ b/base/strings/OWNERS
@@ -0,0 +1,2 @@
+per-file safe_sprintf*=jln@chromium.org
+per-file safe_sprintf*=mdempsky@chromium.org
diff --git a/base/task/OWNERS b/base/task/OWNERS
new file mode 100644
index 0000000..0f3ad5e
--- /dev/null
+++ b/base/task/OWNERS
@@ -0,0 +1,6 @@
+fdoray@chromium.org
+gab@chromium.org
+robliao@chromium.org
+
+# TEAM: scheduler-dev@chromium.org
+# COMPONENT: Internals>TaskScheduler
diff --git a/base/task/sequence_manager/OWNERS b/base/task/sequence_manager/OWNERS
new file mode 100644
index 0000000..ac6eae8
--- /dev/null
+++ b/base/task/sequence_manager/OWNERS
@@ -0,0 +1,6 @@
+altimin@chromium.org
+alexclarke@chromium.org
+skyostil@chromium.org
+
+# TEAM: scheduler-dev@chromium.org
+# Component: Blink>Scheduling
diff --git a/base/task/task_scheduler/OWNERS b/base/task/task_scheduler/OWNERS
new file mode 100644
index 0000000..0f3ad5e
--- /dev/null
+++ b/base/task/task_scheduler/OWNERS
@@ -0,0 +1,6 @@
+fdoray@chromium.org
+gab@chromium.org
+robliao@chromium.org
+
+# TEAM: scheduler-dev@chromium.org
+# COMPONENT: Internals>TaskScheduler
diff --git a/base/test/BUILD.gn b/base/test/BUILD.gn
index 756adf0..a4e3b91 100644
--- a/base/test/BUILD.gn
+++ b/base/test/BUILD.gn
@@ -378,7 +378,7 @@
}
}
-source_set("run_all_unittests") {
+static_library("run_all_unittests") {
testonly = true
sources = [
"run_all_unittests.cc",
diff --git a/base/test/DEPS b/base/test/DEPS
new file mode 100644
index 0000000..5827c26
--- /dev/null
+++ b/base/test/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+third_party/libxml",
+]
diff --git a/base/test/OWNERS b/base/test/OWNERS
new file mode 100644
index 0000000..08d2b4c
--- /dev/null
+++ b/base/test/OWNERS
@@ -0,0 +1,15 @@
+# Metrics-related test utilites:
+per-file *scoped_feature_list*=file://base/metrics/OWNERS
+
+# Tracing test utilities:
+per-file trace_*=file://base/trace_event/OWNERS
+
+#For Windows-specific test utilities:
+per-file *_win*=file://base/win/OWNERS
+
+# For Android-specific changes:
+per-file *android*=file://base/test/android/OWNERS
+per-file BUILD.gn=file://base/test/android/OWNERS
+
+# Linux fontconfig changes
+per-file *fontconfig*=file://base/nix/OWNERS
diff --git a/base/test/android/OWNERS b/base/test/android/OWNERS
new file mode 100644
index 0000000..2b0078b
--- /dev/null
+++ b/base/test/android/OWNERS
@@ -0,0 +1,4 @@
+jbudorick@chromium.org
+file://base/android/OWNERS
+
+# COMPONENT: Test>Android
diff --git a/base/test/android/java/src/org/chromium/base/OWNERS b/base/test/android/java/src/org/chromium/base/OWNERS
new file mode 100644
index 0000000..89442ab
--- /dev/null
+++ b/base/test/android/java/src/org/chromium/base/OWNERS
@@ -0,0 +1,2 @@
+per-file *.aidl=set noparent
+per-file *.aidl=file://ipc/SECURITY_OWNERS
\ No newline at end of file
diff --git a/base/test/data/file_util/.gitattributes b/base/test/data/file_util/.gitattributes
new file mode 100644
index 0000000..07998b9
--- /dev/null
+++ b/base/test/data/file_util/.gitattributes
@@ -0,0 +1,2 @@
+/blank_line_crlf.txt -text
+/crlf.txt -text
\ No newline at end of file
diff --git a/base/test/ios/OWNERS b/base/test/ios/OWNERS
new file mode 100644
index 0000000..40a68c7
--- /dev/null
+++ b/base/test/ios/OWNERS
@@ -0,0 +1 @@
+rohitrao@chromium.org
diff --git a/base/test/metrics/OWNERS b/base/test/metrics/OWNERS
new file mode 100644
index 0000000..f117f82
--- /dev/null
+++ b/base/test/metrics/OWNERS
@@ -0,0 +1,5 @@
+#TODO(https://crbug.com/846421): Move other metrics test files into here.
+
+file://base/metrics/OWNERS
+
+# COMPONENT: Internals>Metrics
diff --git a/base/third_party/nspr/OWNERS b/base/third_party/nspr/OWNERS
new file mode 100644
index 0000000..20ba660
--- /dev/null
+++ b/base/third_party/nspr/OWNERS
@@ -0,0 +1,2 @@
+rsleevi@chromium.org
+wtc@chromium.org
diff --git a/base/third_party/superfasthash/OWNERS b/base/third_party/superfasthash/OWNERS
new file mode 100644
index 0000000..633cc35
--- /dev/null
+++ b/base/third_party/superfasthash/OWNERS
@@ -0,0 +1 @@
+mgiuca@chromium.org
diff --git a/base/third_party/symbolize/DEPS b/base/third_party/symbolize/DEPS
new file mode 100644
index 0000000..73eab50
--- /dev/null
+++ b/base/third_party/symbolize/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+glog",
+]
diff --git a/base/time/OWNERS b/base/time/OWNERS
new file mode 100644
index 0000000..ff0520a
--- /dev/null
+++ b/base/time/OWNERS
@@ -0,0 +1,3 @@
+miu@chromium.org
+
+# COMPONENT: Internals>Core
diff --git a/base/trace_event/OWNERS b/base/trace_event/OWNERS
new file mode 100644
index 0000000..24a0bd2
--- /dev/null
+++ b/base/trace_event/OWNERS
@@ -0,0 +1,15 @@
+chiniforooshan@chromium.org
+oysteine@chromium.org
+primiano@chromium.org
+per-file trace_event_android.cc=wangxianzhu@chromium.org
+
+# For memory-infra related changes
+ssid@chromium.org
+
+# Emeritus:
+dsinclair@chromium.org
+nduca@chromium.org
+simonhatch@chromium.org
+
+# TEAM: tracing@chromium.org
+# COMPONENT: Speed>Tracing
diff --git a/base/win/OWNERS b/base/win/OWNERS
new file mode 100644
index 0000000..4593b2c
--- /dev/null
+++ b/base/win/OWNERS
@@ -0,0 +1,7 @@
+brucedawson@chromium.org
+grt@chromium.org
+jschuh@chromium.org
+robliao@chromium.org
+scottmg@chromium.org
+
+# COMPONENT: Internals>PlatformIntegration
diff --git a/build/OWNERS b/build/OWNERS
new file mode 100644
index 0000000..405ccdc
--- /dev/null
+++ b/build/OWNERS
@@ -0,0 +1,26 @@
+set noparent
+# NOTE: keep this in sync with lsc-owners-override@chromium.org owners
+agrieve@chromium.org
+brucedawson@chromium.org
+dpranke@google.com
+jochen@chromium.org
+thakis@chromium.org
+thomasanderson@chromium.org
+tikuta@chromium.org
+
+# Clang build config changes:
+hans@chromium.org
+
+# For java build changes:
+wnwen@chromium.org
+
+# NOTE: keep this in sync with lsc-owners-override@chromium.org owners
+
+per-file .gitignore=*
+per-file check_gn_headers_whitelist.txt=*
+per-file mac_toolchain.py=erikchen@chromium.org
+per-file mac_toolchain.py=justincohen@chromium.org
+per-file whitespace_file.txt=*
+per-file OWNERS.status=*
+per-file OWNERS.setnoparent=set noparent
+per-file OWNERS.setnoparent=file://ENG_REVIEW_OWNERS
diff --git a/build/android/OWNERS b/build/android/OWNERS
new file mode 100644
index 0000000..0b64bda
--- /dev/null
+++ b/build/android/OWNERS
@@ -0,0 +1,7 @@
+bjoyce@chromium.org
+jbudorick@chromium.org
+mheikal@chromium.org
+pasko@chromium.org
+skyostil@chromium.org
+tiborg@chromium.org
+wnwen@chromium.org
diff --git a/build/android/PRESUBMIT.py b/build/android/PRESUBMIT.py
new file mode 100644
index 0000000..2cf0602
--- /dev/null
+++ b/build/android/PRESUBMIT.py
@@ -0,0 +1,119 @@
+# Copyright (c) 2013 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.
+
+"""Presubmit script for android buildbot.
+
+See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts for
+details on the presubmit API built into depot_tools.
+"""
+
+
+def CommonChecks(input_api, output_api):
+ build_android_dir = input_api.PresubmitLocalPath()
+
+ def J(*dirs):
+ """Returns a path relative to presubmit directory."""
+ return input_api.os_path.join(build_android_dir, *dirs)
+
+ build_pys = [
+ r'gn/.*\.py$',
+ r'gyp/.*\.py$',
+ ]
+ tests = []
+ # yapf likes formatting the extra_paths_list to be less readable.
+ # yapf: disable
+ tests.extend(
+ input_api.canned_checks.GetPylint(
+ input_api,
+ output_api,
+ pylintrc='pylintrc',
+ files_to_skip=[
+ r'.*_pb2\.py',
+ r'.*list_java_targets\.py', # crbug.com/1100664
+ r'.*fast_local_dev_server\.py', # crbug.com/1100664
+ ] + build_pys,
+ extra_paths_list=[
+ J(),
+ J('gyp'),
+ J('buildbot'),
+ J('..', 'util', 'lib', 'common'),
+ J('..', '..', 'third_party', 'catapult', 'common',
+ 'py_trace_event'),
+ J('..', '..', 'third_party', 'catapult', 'common', 'py_utils'),
+ J('..', '..', 'third_party', 'catapult', 'devil'),
+ J('..', '..', 'third_party', 'catapult', 'tracing'),
+ J('..', '..', 'third_party', 'depot_tools'),
+ J('..', '..', 'third_party', 'colorama', 'src'),
+ J('..', '..', 'build'),
+ ]))
+ tests.extend(
+ input_api.canned_checks.GetPylint(
+ input_api,
+ output_api,
+ files_to_check=build_pys,
+ files_to_skip=[
+ r'.*_pb2\.py',
+ r'.*_pb2\.py',
+ ],
+ extra_paths_list=[J('gyp'), J('gn')]))
+ # yapf: enable
+
+ # Disabled due to http://crbug.com/410936
+ #output.extend(input_api.canned_checks.RunUnitTestsInDirectory(
+ #input_api, output_api, J('buildbot', 'tests')))
+
+ pylib_test_env = dict(input_api.environ)
+ pylib_test_env.update({
+ 'PYTHONPATH': build_android_dir,
+ 'PYTHONDONTWRITEBYTECODE': '1',
+ })
+ tests.extend(
+ input_api.canned_checks.GetUnitTests(
+ input_api,
+ output_api,
+ unit_tests=[
+ J('.', 'emma_coverage_stats_test.py'),
+ J('.', 'list_class_verification_failures_test.py'),
+ J('gyp', 'util', 'build_utils_test.py'),
+ J('gyp', 'util', 'manifest_utils_test.py'),
+ J('gyp', 'util', 'md5_check_test.py'),
+ J('gyp', 'util', 'resource_utils_test.py'),
+ J('pylib', 'constants', 'host_paths_unittest.py'),
+ J('pylib', 'gtest', 'gtest_test_instance_test.py'),
+ J('pylib', 'instrumentation',
+ 'instrumentation_test_instance_test.py'),
+ J('pylib', 'local', 'device', 'local_device_gtest_run_test.py'),
+ J('pylib', 'local', 'device',
+ 'local_device_instrumentation_test_run_test.py'),
+ J('pylib', 'local', 'device', 'local_device_test_run_test.py'),
+ J('pylib', 'local', 'machine',
+ 'local_machine_junit_test_run_test.py'),
+ J('pylib', 'output', 'local_output_manager_test.py'),
+ J('pylib', 'output', 'noop_output_manager_test.py'),
+ J('pylib', 'output', 'remote_output_manager_test.py'),
+ J('pylib', 'results', 'json_results_test.py'),
+ J('pylib', 'symbols', 'apk_native_libs_unittest.py'),
+ J('pylib', 'symbols', 'elf_symbolizer_unittest.py'),
+ J('pylib', 'symbols', 'symbol_utils_unittest.py'),
+ J('pylib', 'utils', 'chrome_proxy_utils_test.py'),
+ J('pylib', 'utils', 'decorators_test.py'),
+ J('pylib', 'utils', 'device_dependencies_test.py'),
+ J('pylib', 'utils', 'dexdump_test.py'),
+ J('pylib', 'utils', 'gold_utils_test.py'),
+ J('pylib', 'utils', 'proguard_test.py'),
+ J('pylib', 'utils', 'test_filter_test.py'),
+ J('.', 'convert_dex_profile_tests.py'),
+ ],
+ env=pylib_test_env,
+ run_on_python2=False))
+
+ return input_api.RunTests(tests)
+
+
+def CheckChangeOnUpload(input_api, output_api):
+ return CommonChecks(input_api, output_api)
+
+
+def CheckChangeOnCommit(input_api, output_api):
+ return CommonChecks(input_api, output_api)
diff --git a/build/android/gradle/OWNERS b/build/android/gradle/OWNERS
new file mode 100644
index 0000000..a0e0826
--- /dev/null
+++ b/build/android/gradle/OWNERS
@@ -0,0 +1,2 @@
+agrieve@chromium.org
+wnwen@chromium.org
diff --git a/build/android/gyp/OWNERS b/build/android/gyp/OWNERS
new file mode 100644
index 0000000..25557e1
--- /dev/null
+++ b/build/android/gyp/OWNERS
@@ -0,0 +1,4 @@
+agrieve@chromium.org
+digit@chromium.org
+smaier@chromium.org
+wnwen@chromium.org
diff --git a/build/android/pylib/gtest/filter/OWNERS b/build/android/pylib/gtest/filter/OWNERS
new file mode 100644
index 0000000..72e8ffc
--- /dev/null
+++ b/build/android/pylib/gtest/filter/OWNERS
@@ -0,0 +1 @@
+*
diff --git a/build/android/pylib/local/emulator/OWNERS b/build/android/pylib/local/emulator/OWNERS
new file mode 100644
index 0000000..0853590
--- /dev/null
+++ b/build/android/pylib/local/emulator/OWNERS
@@ -0,0 +1,4 @@
+bpastene@chromium.org
+hypan@google.com
+jbudorick@chromium.org
+liaoyuke@chromium.org
diff --git a/build/apple/OWNERS b/build/apple/OWNERS
new file mode 100644
index 0000000..07d900e
--- /dev/null
+++ b/build/apple/OWNERS
@@ -0,0 +1,4 @@
+mark@chromium.org
+rohitrao@chromium.org
+rsesek@chromium.org
+sdefresne@chromium.org
diff --git a/build/args/OWNERS b/build/args/OWNERS
new file mode 100644
index 0000000..d218b6b
--- /dev/null
+++ b/build/args/OWNERS
@@ -0,0 +1 @@
+per-file headless.gn=file://headless/OWNERS
diff --git a/build/chromeos/OWNERS b/build/chromeos/OWNERS
new file mode 100644
index 0000000..e1058c8
--- /dev/null
+++ b/build/chromeos/OWNERS
@@ -0,0 +1 @@
+bpastene@chromium.org
diff --git a/build/chromeos/PRESUBMIT.py b/build/chromeos/PRESUBMIT.py
new file mode 100644
index 0000000..312faf0
--- /dev/null
+++ b/build/chromeos/PRESUBMIT.py
@@ -0,0 +1,26 @@
+# Copyright (c) 2013 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.
+"""Presubmit script for build/chromeos/.
+
+See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts for
+details on the presubmit API built into depot_tools.
+"""
+
+
+def CommonChecks(input_api, output_api):
+ results = []
+ results += input_api.canned_checks.RunPylint(
+ input_api, output_api, pylintrc='pylintrc')
+ tests = input_api.canned_checks.GetUnitTestsInDirectory(
+ input_api, output_api, '.', [r'^.+_test\.py$'], run_on_python3=True)
+ results += input_api.RunTests(tests)
+ return results
+
+
+def CheckChangeOnUpload(input_api, output_api):
+ return CommonChecks(input_api, output_api)
+
+
+def CheckChangeOnCommit(input_api, output_api):
+ return CommonChecks(input_api, output_api)
diff --git a/build/config/OWNERS b/build/config/OWNERS
new file mode 100644
index 0000000..eeb6706
--- /dev/null
+++ b/build/config/OWNERS
@@ -0,0 +1,5 @@
+dpranke@google.com
+scottmg@chromium.org
+
+per-file ozone.gni=file://ui/ozone/OWNERS
+per-file ozone_extra.gni=file://ui/ozone/OWNERS
diff --git a/build/config/android/OWNERS b/build/config/android/OWNERS
new file mode 100644
index 0000000..a74cfbe
--- /dev/null
+++ b/build/config/android/OWNERS
@@ -0,0 +1 @@
+file://build/android/OWNERS
diff --git a/build/config/apple/OWNERS b/build/config/apple/OWNERS
new file mode 100644
index 0000000..6f3324f
--- /dev/null
+++ b/build/config/apple/OWNERS
@@ -0,0 +1 @@
+file://build/apple/OWNERS
diff --git a/build/config/clang/clang.gni b/build/config/clang/clang.gni
index 5888645..e8f794c 100644
--- a/build/config/clang/clang.gni
+++ b/build/config/clang/clang.gni
@@ -4,7 +4,17 @@
import("//build/toolchain/toolchain.gni")
-default_clang_base_path = "//third_party/llvm-build/Release+Asserts"
+if (is_starboard) {
+ import("//starboard/build/toolchain/starboard_toolchains.gni")
+
+ declare_args() {
+ clang_revision = "365097-f7e52fbd-8"
+ }
+
+ default_clang_base_path = "$starboard_toolchains_path/x86_64-linux-gnu-clang-chromium-${clang_revision}"
+} else {
+ default_clang_base_path = "//third_party/llvm-build/Release+Asserts"
+}
declare_args() {
# Indicates if the build should use the Chrome-specific plugins for enforcing
diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn
index eafd9e9..22b5a42 100644
--- a/build/config/compiler/BUILD.gn
+++ b/build/config/compiler/BUILD.gn
@@ -1988,11 +1988,15 @@
# Shared settings for both "optimize" and "optimize_max" configs.
# IMPORTANT: On Windows "/O1" and "/O2" must go before the common flags.
if (is_win) {
- common_optimize_on_cflags = [
- "/Ob2", # Both explicit and auto inlining.
- "/Oy-", # Disable omitting frame pointers, must be after /O2.
- "/Zc:inline", # Remove unreferenced COMDAT (faster links).
- ]
+ if (is_starboard) {
+ common_optimize_on_cflags = []
+ } else {
+ common_optimize_on_cflags = [
+ "/Ob2", # Both explicit and auto inlining.
+ "/Oy-", # Disable omitting frame pointers, must be after /O2.
+ "/Zc:inline", # Remove unreferenced COMDAT (faster links).
+ ]
+ }
if (!is_asan) {
common_optimize_on_cflags += [
# Put data in separate COMDATs. This allows the linker
diff --git a/build/config/coverage/OWNERS b/build/config/coverage/OWNERS
new file mode 100644
index 0000000..0fc481f
--- /dev/null
+++ b/build/config/coverage/OWNERS
@@ -0,0 +1,3 @@
+inferno@chromium.org
+liaoyuke@chromium.org
+ochang@chromium.org
diff --git a/build/config/freetype/OWNERS b/build/config/freetype/OWNERS
new file mode 100644
index 0000000..3277f87
--- /dev/null
+++ b/build/config/freetype/OWNERS
@@ -0,0 +1,2 @@
+bungeman@chromium.org
+drott@chromium.org
diff --git a/build/config/fuchsia/OWNERS b/build/config/fuchsia/OWNERS
new file mode 100644
index 0000000..3a1056b
--- /dev/null
+++ b/build/config/fuchsia/OWNERS
@@ -0,0 +1,4 @@
+file://build/fuchsia/OWNERS
+
+per-file *.cmx=set noparent
+per-file *.cmx=file://fuchsia/SECURITY_OWNERS
diff --git a/build/config/fuchsia/test/OWNERS b/build/config/fuchsia/test/OWNERS
new file mode 100644
index 0000000..3be17de
--- /dev/null
+++ b/build/config/fuchsia/test/OWNERS
@@ -0,0 +1,7 @@
+file://build/fuchsia/OWNERS
+
+per-file *.test-cmx=set noparent
+per-file *.test-cmx=ddorwin@chromium.org
+per-file *.test-cmx=wez@chromium.org
+# Please prefer the above when possible.
+per-file *.test-cmx=file://fuchsia/SECURITY_OWNERS
diff --git a/build/config/ios/OWNERS b/build/config/ios/OWNERS
new file mode 100644
index 0000000..6f3324f
--- /dev/null
+++ b/build/config/ios/OWNERS
@@ -0,0 +1 @@
+file://build/apple/OWNERS
diff --git a/build/config/linux/OWNERS b/build/config/linux/OWNERS
new file mode 100644
index 0000000..280ba47
--- /dev/null
+++ b/build/config/linux/OWNERS
@@ -0,0 +1 @@
+thomasanderson@chromium.org
diff --git a/build/config/mac/OWNERS b/build/config/mac/OWNERS
new file mode 100644
index 0000000..6f3324f
--- /dev/null
+++ b/build/config/mac/OWNERS
@@ -0,0 +1 @@
+file://build/apple/OWNERS
diff --git a/build/config/profiling/OWNERS b/build/config/profiling/OWNERS
new file mode 100644
index 0000000..225ce18
--- /dev/null
+++ b/build/config/profiling/OWNERS
@@ -0,0 +1,3 @@
+liaoyuke@chromium.org
+sajjadm@chromium.org
+sebmarchand@chromium.org
diff --git a/build/config/sanitizers/OWNERS b/build/config/sanitizers/OWNERS
new file mode 100644
index 0000000..f6a122b
--- /dev/null
+++ b/build/config/sanitizers/OWNERS
@@ -0,0 +1,3 @@
+inferno@chromium.org
+metzman@chromium.org
+ochang@chromium.org
diff --git a/build/config/win/visual_studio_version.gni b/build/config/win/visual_studio_version.gni
index 48ecc5b..b46c531 100644
--- a/build/config/win/visual_studio_version.gni
+++ b/build/config/win/visual_studio_version.gni
@@ -16,14 +16,24 @@
wdk_version = "10.0.17763.0"
}
+ if (is_docker_build) {
+ _default_visual_studio_path = "C:/BuildTools"
+ } else {
+ _default_visual_studio_path = "C:/Program Files (x86)/Microsoft Visual Studio/2017/Professional"
+ }
+
declare_args() {
# Path to Visual Studio.
- visual_studio_path = "C:/Program Files (x86)/Microsoft Visual Studio/2017/Professional/VC/Tools/MSVC/$visual_studio_version"
+ visual_studio_path = _default_visual_studio_path
- wdk_include_path = "$windows_sdk_path/Include/$wdk_version"
+ wdk_include_path = "$windows_sdk_path/include/$wdk_version"
wdk_lib_path = "$windows_sdk_path/lib/$wdk_version"
}
+
+ declare_args() {
+ msvc_path = "$visual_studio_path/VC/Tools/MSVC/$visual_studio_version"
+ }
} else {
declare_args() {
# Path to Visual Studio. If empty, the default is used which is to use the
diff --git a/build/fuchsia/OWNERS b/build/fuchsia/OWNERS
new file mode 100644
index 0000000..3dcaf8a
--- /dev/null
+++ b/build/fuchsia/OWNERS
@@ -0,0 +1,8 @@
+ddorwin@chromium.org
+fdegans@chromium.org
+kmarshall@chromium.org
+sergeyu@chromium.org
+wez@chromium.org
+
+per-file linux.sdk.sha1=chromium-autoroll@skia-public.iam.gserviceaccount.com
+per-file mac.sdk.sha1=chromium-autoroll@skia-public.iam.gserviceaccount.com
diff --git a/build/ios/OWNERS b/build/ios/OWNERS
new file mode 100644
index 0000000..6f3324f
--- /dev/null
+++ b/build/ios/OWNERS
@@ -0,0 +1 @@
+file://build/apple/OWNERS
diff --git a/build/lacros/OWNERS b/build/lacros/OWNERS
new file mode 100644
index 0000000..aae4f73
--- /dev/null
+++ b/build/lacros/OWNERS
@@ -0,0 +1,3 @@
+svenzheng@chromium.org
+liaoyuke@chromium.org
+erikchen@chromium.org
diff --git a/build/lacros/PRESUBMIT.py b/build/lacros/PRESUBMIT.py
new file mode 100644
index 0000000..1394a42
--- /dev/null
+++ b/build/lacros/PRESUBMIT.py
@@ -0,0 +1,18 @@
+# Copyright (c) 2020 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.
+"""Presubmit script for changes affecting //build/lacros"""
+
+
+def _CommonChecks(input_api, output_api):
+ tests = input_api.canned_checks.GetUnitTestsInDirectory(
+ input_api, output_api, '.', [r'^.+_test\.py$'])
+ return input_api.RunTests(tests)
+
+
+def CheckChangeOnUpload(input_api, output_api):
+ return _CommonChecks(input_api, output_api)
+
+
+def CheckChangeOnCommit(input_api, output_api):
+ return _CommonChecks(input_api, output_api)
diff --git a/build/linux/OWNERS b/build/linux/OWNERS
new file mode 100644
index 0000000..8e1cb55
--- /dev/null
+++ b/build/linux/OWNERS
@@ -0,0 +1,3 @@
+mmoss@chromium.org
+thestig@chromium.org
+thomasanderson@chromium.org
diff --git a/build/linux/libncursesw/OWNERS b/build/linux/libncursesw/OWNERS
new file mode 100644
index 0000000..976b955
--- /dev/null
+++ b/build/linux/libncursesw/OWNERS
@@ -0,0 +1 @@
+file://ui/accessibility/OWNERS
diff --git a/build/mac/OWNERS b/build/mac/OWNERS
new file mode 100644
index 0000000..6f3324f
--- /dev/null
+++ b/build/mac/OWNERS
@@ -0,0 +1 @@
+file://build/apple/OWNERS
diff --git a/build/sanitizers/OWNERS b/build/sanitizers/OWNERS
new file mode 100644
index 0000000..b893bc8
--- /dev/null
+++ b/build/sanitizers/OWNERS
@@ -0,0 +1,8 @@
+ochang@chromium.org
+eugenis@chromium.org
+glider@chromium.org
+inferno@chromium.org
+metzman@chromium.org
+rnk@chromium.org
+per-file tsan_suppressions.cc=*
+per-file lsan_suppressions.cc=*
diff --git a/build/skia_gold_common/OWNERS b/build/skia_gold_common/OWNERS
new file mode 100644
index 0000000..428f610
--- /dev/null
+++ b/build/skia_gold_common/OWNERS
@@ -0,0 +1 @@
+bsheedy@chromium.org
diff --git a/build/skia_gold_common/PRESUBMIT.py b/build/skia_gold_common/PRESUBMIT.py
new file mode 100644
index 0000000..41e1bb2
--- /dev/null
+++ b/build/skia_gold_common/PRESUBMIT.py
@@ -0,0 +1,34 @@
+# Copyright 2020 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.
+"""Presubmit script for //build/skia_gold_common/.
+
+See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
+for more details on the presubmit API built into depot_tools.
+"""
+
+
+def CommonChecks(input_api, output_api):
+ output = []
+ build_path = input_api.os_path.join(input_api.PresubmitLocalPath(), '..')
+ skia_gold_env = dict(input_api.environ)
+ skia_gold_env.update({
+ 'PYTHONPATH': build_path,
+ 'PYTHONDONTWRITEBYTECODE': '1',
+ })
+ output.extend(
+ input_api.canned_checks.RunUnitTestsInDirectory(
+ input_api,
+ output_api,
+ input_api.PresubmitLocalPath(), [r'^.+_unittest\.py$'],
+ env=skia_gold_env))
+ output.extend(input_api.canned_checks.RunPylint(input_api, output_api))
+ return output
+
+
+def CheckChangeOnUpload(input_api, output_api):
+ return CommonChecks(input_api, output_api)
+
+
+def CheckChangeOnCommit(input_api, output_api):
+ return CommonChecks(input_api, output_api)
diff --git a/build/toolchain/OWNERS b/build/toolchain/OWNERS
new file mode 100644
index 0000000..d7012d3
--- /dev/null
+++ b/build/toolchain/OWNERS
@@ -0,0 +1,5 @@
+dpranke@google.com
+scottmg@chromium.org
+
+# Code Coverage.
+per-file *code_coverage*=liaoyuke@chromium.org
diff --git a/build/toolchain/android/OWNERS b/build/toolchain/android/OWNERS
new file mode 100644
index 0000000..a74cfbe
--- /dev/null
+++ b/build/toolchain/android/OWNERS
@@ -0,0 +1 @@
+file://build/android/OWNERS
diff --git a/build/toolchain/apple/OWNERS b/build/toolchain/apple/OWNERS
new file mode 100644
index 0000000..6f3324f
--- /dev/null
+++ b/build/toolchain/apple/OWNERS
@@ -0,0 +1 @@
+file://build/apple/OWNERS
diff --git a/build/toolchain/cc_wrapper.gni b/build/toolchain/cc_wrapper.gni
index 6d6cfcf..070461f 100644
--- a/build/toolchain/cc_wrapper.gni
+++ b/build/toolchain/cc_wrapper.gni
@@ -35,7 +35,13 @@
declare_args() {
# Set to "ccache", "icecc" or "distcc". Probably doesn't work on windows.
cc_wrapper = ""
- if (is_starboard) {
+}
+
+if (is_starboard && cc_wrapper == "") {
+ # TODO(https://crbug.com/gn/273): Use sccache locally as well.
+ if (host_os == "win" && cobalt_fastbuild) {
+ cc_wrapper = "sccache.exe"
+ } else if (host_os != "win") {
cc_wrapper = "ccache"
}
}
diff --git a/build/toolchain/fuchsia/OWNERS b/build/toolchain/fuchsia/OWNERS
new file mode 100644
index 0000000..3f809e8
--- /dev/null
+++ b/build/toolchain/fuchsia/OWNERS
@@ -0,0 +1 @@
+scottmg@chromium.org
diff --git a/build/toolchain/gcc_toolchain.gni b/build/toolchain/gcc_toolchain.gni
index 42e2b80..b432d20 100644
--- a/build/toolchain/gcc_toolchain.gni
+++ b/build/toolchain/gcc_toolchain.gni
@@ -105,6 +105,15 @@
# Location of the strip executable. When specified, strip will be run on
# all shared libraries and executables as they are built. The pre-stripped
# artifacts will be put in lib.unstripped/ and exe.unstripped/.
+#
+# Optional parameters added with the Starboard platform:
+#
+# - tail_lib_dependencies
+# If defined, this string will be added to the compilation line after all
+# other libs are specified.
+# - using_snarl_linker
+# Defining this string will ensure static linker flags are passed in a way
+# that the snarl tool will accept.
template("gcc_toolchain") {
toolchain(target_name) {
assert(defined(invoker.ar), "gcc_toolchain() must specify a \"ar\" value")
@@ -266,9 +275,9 @@
compiler_prefix = "$python_path ${_coverage_wrapper} " + compiler_prefix
}
- cc = compiler_prefix + invoker.cc
- cxx = compiler_prefix + invoker.cxx
- asm = asm_prefix + invoker.cc
+ cc = "$compiler_prefix\"${invoker.cc}\""
+ cxx = "$compiler_prefix\"${invoker.cxx}\""
+ asm = "$asm_prefix\"${invoker.cc}\""
ar = invoker.ar
ld = "$goma_ld${invoker.ld}"
if (defined(invoker.readelf)) {
@@ -346,6 +355,12 @@
# Object files go in this directory.
object_subdir = "{{target_out_dir}}/{{label_name}}"
+ if (defined(invoker.tail_lib_dependencies)) {
+ tail_lib_dependencies = " " + invoker.tail_lib_dependencies
+ } else {
+ tail_lib_dependencies = ""
+ }
+
tool("cc") {
depfile = "{{output}}.d"
precompiled_header_type = "gcc"
@@ -367,14 +382,27 @@
tool("asm") {
# For GCC we can just use the C compiler to compile assembly.
depfile = "{{output}}.d"
- command = "$asm -MMD -MF $depfile ${rebuild_string}{{defines}} {{include_dirs}} {{asmflags}}${extra_asmflags} -c {{source}} -o {{output}}"
+
+ # TODO(b/206642994): see if we can remove this condition. It's needed for
+ # now to add cflags for evergreen-arm* platforms but we haven't yet
+ # decided whether cflags should be added here for all platforms.
+ if (is_starboard && sb_is_evergreen && target_cpu == "arm") {
+ command = "$asm -MMD -MF $depfile ${rebuild_string}{{defines}} {{include_dirs}} {{cflags}} {{asmflags}}${extra_asmflags} -c {{source}} -o {{output}}"
+ } else {
+ command = "$asm -MMD -MF $depfile ${rebuild_string}{{defines}} {{include_dirs}} {{asmflags}}${extra_asmflags} -c {{source}} -o {{output}}"
+ }
+
depsformat = "gcc"
description = "ASM {{output}}"
outputs = [ "$object_subdir/{{source_name_part}}.o" ]
}
tool("alink") {
- if (current_os == "aix") {
+ if (defined(invoker.using_snarl_linker) && invoker.using_snarl_linker) {
+ rspfile = "{{output}}.rsp"
+ rspfile_content = "{{inputs_newline}}"
+ command = "\"$ar\" {{arflags}} rcsD {{output}} @\"$rspfile\""
+ } else if (current_os == "aix") {
# AIX does not support either -D (deterministic output) or response
# files.
command = "$ar -X64 {{arflags}} -r -c -s {{output}} {{inputs}}"
@@ -424,7 +452,14 @@
# .TOC file, overwrite it, otherwise, don't change it.
tocfile = sofile + ".TOC"
- link_command = "$ld -shared -Wl,-soname=\"$soname\" {{ldflags}}${extra_ldflags} -o \"$unstripped_sofile\" @\"$rspfile\""
+ # TODO(b/206642994): see if we can remove this condition. It's needed for
+ # now because we use the ld.lld linker for evergreen-arm* and need to pass
+ # options as `option` instead of `-Wl,option`.
+ if (is_starboard && sb_is_evergreen && target_cpu == "arm") {
+ link_command = "$ld -shared -soname=\"$soname\" {{ldflags}}${extra_ldflags} -o \"$unstripped_sofile\" @\"$rspfile\""
+ } else {
+ link_command = "$ld -shared -Wl,-soname=\"$soname\" {{ldflags}}${extra_ldflags} -o \"$unstripped_sofile\" @\"$rspfile\""
+ }
# Generate a map file to be used for binary size analysis.
# Map file adds ~10% to the link time on a z620.
@@ -450,9 +485,14 @@
command = "$python_path \"$solink_wrapper\" --readelf=\"$readelf\" --nm=\"$nm\" $strip_switch$dwp_switch --sofile=\"$unstripped_sofile\" --tocfile=\"$tocfile\"$map_switch --output=\"$sofile\" -- $link_command"
if (target_cpu == "mipsel" && is_component_build && is_android) {
- rspfile_content = "-Wl,--start-group -Wl,--whole-archive {{inputs}} {{solibs}} -Wl,--no-whole-archive {{libs}} -Wl,--end-group"
+ rspfile_content = "-Wl,--start-group -Wl,--whole-archive {{inputs}} {{solibs}} -Wl,--no-whole-archive {{libs}} -Wl,--end-group$tail_lib_dependencies"
+ # TODO(b/206642994): see if we can remove this condition. It's needed for
+ # now because we use the ld.lld linker for evergreen-arm* and need to
+ # pass options as `option` instead of `-Wl,option`.
+ } else if (is_starboard && sb_is_evergreen && target_cpu == "arm") {
+ rspfile_content = "--whole-archive {{inputs}} {{solibs}} --no-whole-archive {{libs}}$tail_lib_dependencies"
} else {
- rspfile_content = "-Wl,--whole-archive {{inputs}} {{solibs}} -Wl,--no-whole-archive {{libs}}"
+ rspfile_content = "-Wl,--whole-archive {{inputs}} {{solibs}} -Wl,--no-whole-archive {{libs}}$tail_lib_dependencies"
}
description = "SOLINK $sofile"
@@ -520,7 +560,7 @@
strip_command = "${invoker.strip} -o \"$sofile\" \"$unstripped_sofile\""
command += " && " + strip_command
}
- rspfile_content = "-Wl,--whole-archive {{inputs}} {{solibs}} -Wl,--no-whole-archive {{libs}}"
+ rspfile_content = "-Wl,--whole-archive {{inputs}} {{solibs}} -Wl,--no-whole-archive {{libs}}$tail_lib_dependencies"
description = "SOLINK_MODULE $sofile"
@@ -572,7 +612,7 @@
start_group_flag = "-Wl,--start-group"
end_group_flag = "-Wl,--end-group "
}
- link_command = "$ld {{ldflags}}${extra_ldflags} -o \"$unstripped_outfile\" $start_group_flag @\"$rspfile\" {{solibs}} $end_group_flag {{libs}}"
+ link_command = "$ld {{ldflags}}${extra_ldflags} {{libs}} -o \"$unstripped_outfile\" $start_group_flag @\"$rspfile\" {{solibs}} $end_group_flag$tail_lib_dependencies"
# Generate a map file to be used for binary size analysis.
# Map file adds ~10% to the link time on a z620.
diff --git a/build/toolchain/ios/OWNERS b/build/toolchain/ios/OWNERS
new file mode 100644
index 0000000..6f3324f
--- /dev/null
+++ b/build/toolchain/ios/OWNERS
@@ -0,0 +1 @@
+file://build/apple/OWNERS
diff --git a/build/toolchain/mac/OWNERS b/build/toolchain/mac/OWNERS
new file mode 100644
index 0000000..6f3324f
--- /dev/null
+++ b/build/toolchain/mac/OWNERS
@@ -0,0 +1 @@
+file://build/apple/OWNERS
diff --git a/build/toolchain/win/msvc_toolchain.gni b/build/toolchain/win/msvc_toolchain.gni
index 77b60f9..2755047 100644
--- a/build/toolchain/win/msvc_toolchain.gni
+++ b/build/toolchain/win/msvc_toolchain.gni
@@ -78,6 +78,14 @@
asm = invoker.asm
env_wrapper = "ninja -t msvc -e $env -- " # Note trailing space.
+
+ # TODO(https://crbug.com/gn/273): Always use sccache for cc and cxx tools.
+ if (cc_wrapper != "") {
+ cl_prefix = "${cc_wrapper} " # Note trailing space.
+ } else {
+ cl_prefix = env_wrapper
+ }
+
sys_include_flags = ""
tool("cc") {
@@ -91,7 +99,7 @@
description = "CC {{output}}"
outputs = [ "$object_subdir/{{source_name_part}}.obj" ]
- command = "$env_wrapper$cl /nologo /showIncludes $sys_include_flags{{defines}} {{include_dirs}} {{cflags}} {{cflags_c}} /c {{source}} /Fo{{output}} /Fd\"$pdbname\""
+ command = "$cl_prefix\"$cl\" /nologo /showIncludes $sys_include_flags{{defines}} {{include_dirs}} {{cflags}} {{cflags_c}} /c {{source}} /Fo{{output}} /Fd\"$pdbname\""
}
tool("cxx") {
@@ -105,7 +113,7 @@
description = "CXX {{output}}"
outputs = [ "$object_subdir/{{source_name_part}}.obj" ]
- command = "$env_wrapper$cl /nologo /showIncludes $sys_include_flags{{defines}} {{include_dirs}} {{cflags}} {{cflags_cc}} /c {{source}} /Fo{{output}} /Fd\"$pdbname\""
+ command = "$cl_prefix\"$cl\" /nologo /showIncludes $sys_include_flags{{defines}} {{include_dirs}} {{cflags}} {{cflags_cc}} /c {{source}} /Fo{{output}} /Fd\"$pdbname\""
}
tool("asm") {
@@ -114,12 +122,11 @@
command ="$env_wrapper$asm /nologo /Fo{{output}} /c {{defines}} {{include_dirs}} {{asmflags}} {{source}}"
}
- linker_wrapper = "ninja -t msvc -e $env -- " # Note trailing space.
sys_lib_flags = "${invoker.sys_lib_flags} " # Note trailing space.
tool("alink") {
rspfile = "{{output}}.rsp"
- command = "$linker_wrapper$lib /OUT:{{output}} /nologo ${sys_lib_flags}{{arflags}} @$rspfile"
+ command = "$env_wrapper$lib /OUT:{{output}} /nologo ${sys_lib_flags}{{arflags}} @$rspfile"
description = "LIB {{output}}"
outputs = [
# Ignore {{output_extension}} and always use .lib, there's no reason to
@@ -142,7 +149,7 @@
rspfile = "${dllname}.rsp"
pool = "//build/toolchain:link_pool($default_toolchain)"
- command = "$linker_wrapper$link /OUT:$dllname /nologo ${sys_lib_flags}/IMPLIB:$libname /DLL /PDB:$pdbname @$rspfile"
+ command = "$env_wrapper$link /OUT:$dllname /nologo ${sys_lib_flags}/IMPLIB:$libname /DLL /PDB:$pdbname @$rspfile"
default_output_extension = ".dll"
default_output_dir = "{{root_out_dir}}"
@@ -176,7 +183,7 @@
rspfile = "${dllname}.rsp"
pool = "//build/toolchain:link_pool($default_toolchain)"
- command = "$linker_wrapper$link /OUT:$dllname /nologo ${sys_lib_flags}/DLL /PDB:$pdbname @$rspfile"
+ command = "$env_wrapper$link /OUT:$dllname /nologo ${sys_lib_flags}/DLL /PDB:$pdbname @$rspfile"
default_output_extension = ".dll"
default_output_dir = "{{root_out_dir}}"
@@ -198,7 +205,7 @@
rspfile = "$exename.rsp"
pool = "//build/toolchain:link_pool($default_toolchain)"
- command = "$linker_wrapper$link /OUT:$exename /nologo ${sys_lib_flags} /PDB:$pdbname @$rspfile"
+ command = "$env_wrapper$link /OUT:$exename /nologo ${sys_lib_flags} /PDB:$pdbname @$rspfile"
default_output_extension = ".exe"
default_output_dir = "{{root_out_dir}}"
diff --git a/build/util/PRESUBMIT.py b/build/util/PRESUBMIT.py
new file mode 100644
index 0000000..1e0fc8c
--- /dev/null
+++ b/build/util/PRESUBMIT.py
@@ -0,0 +1,58 @@
+# Copyright 2019 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.
+
+import re
+"""Presubmit for build/util"""
+
+
+def _GetFilesToSkip(input_api):
+ files_to_skip = []
+ affected_files = input_api.change.AffectedFiles()
+ version_script_change = next(
+ (f for f in affected_files
+ if re.search('\\/version\\.py$|\\/version_test\\.py$', f.LocalPath())),
+ None)
+
+ if version_script_change is None:
+ files_to_skip.append('version_test\\.py$')
+
+ android_chrome_version_script_change = next(
+ (f for f in affected_files if re.search(
+ '\\/android_chrome_version\\.py$|'
+ '\\/android_chrome_version_test\\.py$', f.LocalPath())), None)
+
+ if android_chrome_version_script_change is None:
+ files_to_skip.append('android_chrome_version_test\\.py$')
+
+ return files_to_skip
+
+
+def _GetPythonUnitTests(input_api, output_api):
+ # No need to test if files are unchanged
+ files_to_skip = _GetFilesToSkip(input_api)
+
+ return input_api.canned_checks.GetUnitTestsRecursively(
+ input_api,
+ output_api,
+ input_api.PresubmitLocalPath(),
+ files_to_check=['.*_test\\.py$'],
+ files_to_skip=files_to_skip)
+
+
+def CommonChecks(input_api, output_api):
+ """Presubmit checks run on both upload and commit.
+ """
+ checks = []
+ checks.extend(_GetPythonUnitTests(input_api, output_api))
+ return input_api.RunTests(checks, False)
+
+
+def CheckChangeOnUpload(input_api, output_api):
+ """Presubmit checks on CL upload."""
+ return CommonChecks(input_api, output_api)
+
+
+def CheckChangeOnCommit(input_api, output_api):
+ """Presubmit checks on commit."""
+ return CommonChecks(input_api, output_api)
diff --git a/build/util/lib/common/PRESUBMIT.py b/build/util/lib/common/PRESUBMIT.py
new file mode 100644
index 0000000..53984fd
--- /dev/null
+++ b/build/util/lib/common/PRESUBMIT.py
@@ -0,0 +1,16 @@
+# Copyright 2015 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.
+
+
+def _RunTests(input_api, output_api):
+ return (input_api.canned_checks.RunUnitTestsInDirectory(
+ input_api, output_api, '.', files_to_check=[r'.+_test.py$']))
+
+
+def CheckChangeOnUpload(input_api, output_api):
+ return _RunTests(input_api, output_api)
+
+
+def CheckChangeOnCommit(input_api, output_api):
+ return _RunTests(input_api, output_api)
diff --git a/buildtools/DEPS b/buildtools/DEPS
new file mode 100644
index 0000000..fde9a08
--- /dev/null
+++ b/buildtools/DEPS
@@ -0,0 +1,25 @@
+use_relative_paths = True
+
+vars = {
+ "chromium_url": "https://chromium.googlesource.com",
+
+ "clang_format_rev": "0653eee0c81ea04715c635dd0885e8096ff6ba6d", # r302580
+ "libcxx_revision": "3a07dd740be63878167a0ea19fe81869954badd7", # r303878
+ "libcxxabi_revision": "4072e8fd76febee37f60aeda76d6d9f5e3791daa", # r303806
+ "libunwind_revision": "41f982e5887185b904a456e20dfcd58e6be6cc19", # r306442
+}
+
+deps = {
+ "clang_format/script":
+ Var("chromium_url") + "/chromium/llvm-project/cfe/tools/clang-format.git@" +
+ Var("clang_format_rev"),
+ "third_party/libc++/trunk":
+ Var("chromium_url") + "/chromium/llvm-project/libcxx.git" + "@" +
+ Var("libcxx_revision"),
+ "third_party/libc++abi/trunk":
+ Var("chromium_url") + "/chromium/llvm-project/libcxxabi.git" + "@" +
+ Var("libcxxabi_revision"),
+ "third_party/libunwind/trunk":
+ Var("chromium_url") + "/external/llvm.org/libunwind.git" + "@" +
+ Var("libunwind_revision"),
+}
diff --git a/buildtools/README.txt b/buildtools/README.txt
new file mode 100644
index 0000000..1db97b4
--- /dev/null
+++ b/buildtools/README.txt
@@ -0,0 +1,56 @@
+This repository contains hashes of build tools used by Chromium and related
+projects. The actual binaries are pulled from Google Storage, normally as part
+of a gclient hook.
+
+The repository is separate so that the shared build tools can be shared between
+the various Chromium-related projects without each one needing to maintain
+their own versionining of each binary.
+
+To update the GN binary, run (from the Chromium repo) tools/gn/bin/roll_gn.py
+which will automatically upload the binaries and roll build tools.
+
+________________________________________
+UPDATING AND ROLLING BUILDTOOLS MANUALLY
+
+When you update buildtools, you should roll the new version into the Chromium
+repository right away. Otherwise, the next person who makes a change will end
+up rolling (and testing) your change. If there are any unresolved problems with
+your change, the next person will be blocked.
+
+ - From the buildtools directory, make a branch, edit and upload normally.
+
+ - Get your change reviewed and landed. There are no trybots so landing will
+ be very fast.
+
+ - Get the hash for the commit that commit-bot made. Make a new branch in
+ the Chromium repository and paste the hash into the line in //DEPS
+ labeled "buildtools_revision".
+
+ - You can TBR changes to the DEPS file since the git hashes can't be reviewed
+ in any practical way. Submit that patch to the commit queue.
+
+ - If this roll identifies a problem with your patch, fix it promptly. If you
+ are unable to fix it promptly, it's best to revert your buildtools patch
+ to avoid blocking other people that want to make changes.
+
+________________________
+ADDING BINARIES MANUALLY
+
+One uploads new versions of the tools using the 'gsutil' binary from the
+Google Storage SDK:
+
+ https://developers.google.com/storage/docs/gsutil
+
+There is a checked-in version of gsutil as part of depot_tools.
+
+To initialize gsutil's credentials:
+
+ python ~/depot_tools/third_party/gsutil/gsutil config
+
+ That will give a URL which you should log into with your web browser. For
+ rolling GN, the username should be the one that is on the ACL for the
+ "chromium-gn" bucket (probably your @google.com address). Contact the build
+ team for help getting access if necessary.
+
+ Copy the code back to the command line util. Ignore the project ID (it's OK
+ to just leave blank when prompted).
diff --git a/cobalt/.gitattributes b/cobalt/.gitattributes
new file mode 100644
index 0000000..0f70607
--- /dev/null
+++ b/cobalt/.gitattributes
@@ -0,0 +1,30 @@
+# These files are text and should be normalized (convert crlf > lf).
+*.bat text eol=lf
+*.cc text eol=lf
+*.cg text eol=lf
+*.cpp text eol=lf
+*.css text eol=lf
+*.gyp text eol=lf
+*.gypi text eol=lf
+*.h text eol=lf
+*.html text eol=lf
+*.idl text eol=lf
+*.js text eol=lf
+*.pump text eol=lf
+*.py text eol=lf
+*.sublime-project text eol=lf
+*.sublime-workspace text eol=lf
+*.template text eol=lf
+*.txt text eol=lf
+*.y text eol=lf
+.clang-format text eol=lf
+codereview.settings text eol=lf
+gyp_cobalt text eol=lf
+
+# Images should be treated as binary files.
+*.exe binary
+*.jpg binary
+*.mp4 binary
+*.png binary
+*.pyc binary
+*.ttf binary
diff --git a/cobalt/BUILD.gn b/cobalt/BUILD.gn
index 25ae8bc..7fbccdc 100644
--- a/cobalt/BUILD.gn
+++ b/cobalt/BUILD.gn
@@ -16,72 +16,58 @@
testonly = true
deps = [
":default",
+ "//cobalt/bindings/testing:bindings_test",
+ "//cobalt/browser:browser_test",
+ "//cobalt/dom:dom_test",
+ "//cobalt/dom/testing:dom_testing",
+ "//cobalt/dom_parser:dom_parser_test",
+ "//cobalt/encoding:text_encoding_test",
+ "//cobalt/extension:extension_test",
"//cobalt/layout:layout_test",
+ "//cobalt/layout_tests",
+ "//cobalt/layout_tests:web_platform_tests",
+ "//cobalt/loader:loader_test",
+ "//cobalt/loader/image/sandbox:image_decoder_sandbox",
+ "//cobalt/media/sandbox:media_sandbox",
+ "//cobalt/media/sandbox:web_media_player_sandbox",
+ "//cobalt/media_capture:media_capture_test",
+ "//cobalt/media_session:media_session_test",
+ "//cobalt/media_stream:media_stream_test",
+ "//cobalt/renderer:renderer_test",
+ "//cobalt/renderer/sandbox:renderer_sandbox",
+ "//cobalt/renderer/sandbox:scaling_text_sandbox",
+ "//cobalt/speech/sandbox:speech_sandbox",
"//cobalt/web_animations:web_animations_test",
+ "//cobalt/webdriver:webdriver_test",
"//cobalt/websocket:websocket_test",
"//cobalt/xhr:xhr_test",
]
+
+ if (sb_is_evergreen) {
+ deps += [
+ "//components/update_client:cobalt_slot_management_test",
+ "//components/update_client:update_client_test",
+ ]
+ }
}
group("default") {
deps = [
- "//cobalt/account",
- "//cobalt/audio",
- "//cobalt/base",
- "//cobalt/browser",
- "//cobalt/css_parser",
- "//cobalt/dom",
- "//cobalt/dom_parser",
- "//cobalt/encoding:text_encoding",
- "//cobalt/fetch",
- "//cobalt/h5vcc",
- "//cobalt/input",
- "//cobalt/layout",
- "//cobalt/math",
- "//cobalt/media",
- "//cobalt/media_capture",
- "//cobalt/media_session",
- "//cobalt/network",
- "//cobalt/network_bridge",
- "//cobalt/overlay_info",
- "//cobalt/render_tree",
+ "//cobalt/browser:cobalt",
+ "//cobalt/browser:cobalt_install",
"//cobalt/renderer:default_options",
- "//cobalt/renderer/backend:renderer_backend",
- "//cobalt/renderer/rasterizer",
- "//cobalt/renderer/rasterizer/egl/shaders",
- "//cobalt/renderer/test/png_utils",
- "//cobalt/script",
- "//cobalt/script:engine",
"//cobalt/script:engine_shell",
- "//cobalt/sso",
- "//cobalt/storage",
- "//cobalt/storage:storage_constants",
- "//cobalt/subtlecrypto",
- "//cobalt/trace_event",
- "//cobalt/ui_navigation",
- "//cobalt/webdriver",
- "//cobalt/websocket",
- "//cobalt/xhr",
- "//content/browser/speech",
- "//crypto",
- "//nb",
- "//third_party/brotli:dec",
"//third_party/brotli:dec_no_dictionary_data",
- "//third_party/flac",
- "//third_party/freetype2",
- "//third_party/harfbuzz-ng",
- "//third_party/libpng",
- "//third_party/libwebp",
- "//third_party/ots",
- "//third_party/skia/third_party/skcms",
- "//third_party/v8",
- "//third_party/woff2:woff2_dec",
]
if (sb_is_evergreen) {
deps += [
"//base/util/values:values_util",
+ "//cobalt/updater",
+ "//components/client_update_protocol",
+ "//components/crx_file",
"//components/prefs",
+ "//components/update_client",
"//third_party/llvm-project/compiler-rt:compiler_rt",
"//third_party/llvm-project/libcxx:cxx",
"//third_party/llvm-project/libcxxabi:cxxabi",
diff --git a/cobalt/audio/BUILD.gn b/cobalt/audio/BUILD.gn
index d24bd06..88bfeb1 100644
--- a/cobalt/audio/BUILD.gn
+++ b/cobalt/audio/BUILD.gn
@@ -74,10 +74,13 @@
deps = [
":audio",
"//cobalt/dom",
+ "//cobalt/dom/testing:dom_testing",
"//cobalt/media",
"//cobalt/script",
"//cobalt/test:run_all_unittests",
"//testing/gtest",
]
deps += cobalt_platform_dependencies
+
+ content_deps = [ "//third_party/icu:icudata" ]
}
diff --git a/cobalt/base/BUILD.gn b/cobalt/base/BUILD.gn
index 8cae034..302256c 100644
--- a/cobalt/base/BUILD.gn
+++ b/cobalt/base/BUILD.gn
@@ -115,7 +115,6 @@
"fixed_size_lru_cache_test.cc",
"token_test.cc",
]
-
deps = [
":base",
"//cobalt/test:run_all_unittests",
@@ -123,4 +122,5 @@
"//testing/gmock",
"//testing/gtest",
]
+ content_deps = [ "//third_party/icu:icudata" ]
}
diff --git a/cobalt/bindings/bindings_templates.gni b/cobalt/bindings/bindings_templates.gni
index c2d4daf..12f18ad 100644
--- a/cobalt/bindings/bindings_templates.gni
+++ b/cobalt/bindings/bindings_templates.gni
@@ -12,12 +12,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import("//cobalt/bindings/bindings_variables.gni")
import("//cobalt/bindings/blink_variables.gni")
import("//cobalt/script/v8c/v8c_variables.gni")
+# The allowlist of what extended attributes we support. If an attribute
+# not in this list is encountered, it will cause an error in the
+# pipeline.
+_extended_attributes_file = "//cobalt/bindings/IDLExtendedAttributes.txt"
+
# Additional files used by the idl compilation scripts.
-bindings_extra_inputs = [
+_bindings_extra_inputs = [
"//cobalt/bindings/code_generator_cobalt.py",
"//cobalt/bindings/expression_generator.py",
"//cobalt/bindings/contexts.py",
@@ -26,11 +30,9 @@
"//cobalt/bindings/name_conversion.py",
"//cobalt/bindings/overload_context.py",
]
-bindings_extra_inputs += code_generator_template_files
-bindings_extra_inputs += engine_bindings_scripts
-bindings_extra_inputs += idl_lexer_parser_files
-bindings_extra_inputs += idl_cache_files
-bindings_extra_inputs += idl_compiler_files
+_bindings_extra_inputs += engine_bindings_scripts
+_bindings_extra_inputs += idl_lexer_parser_files
+_bindings_extra_inputs += idl_compiler_files
# Template definitions.
template("idl_compile") {
@@ -38,30 +40,36 @@
script = "//starboard/build/run_bash.py"
py_script = engine_idl_compiler
- public_configs = invoker.public_configs
-
sources = invoker.sources
- deps = invoker.deps
-
- if (defined(invoker.public_deps)) {
- public_deps = invoker.public_deps
- }
+ forward_variables_from(invoker,
+ [
+ "testonly",
+ "deps",
+ "public_deps",
+ "public_configs",
+ ])
inputs = [
- invoker.interfaces_info,
- invoker.extended_attributes,
+ _extended_attributes_file,
invoker.component_info,
+ invoker.interfaces_info,
py_script,
]
- inputs += invoker.bindings_extra_inputs
+ inputs += _bindings_extra_inputs
generated_file_path =
- "$generated_source_output_dir/{{source_root_relative_dir}}"
- generated_file_name = "${generated_bindings_prefix}_{{source_name_part}}"
+ invoker.output_directory + "/{{source_root_relative_dir}}"
+
+ if (defined(invoker.header_prefix)) {
+ header_prefix = invoker.header_prefix + "_"
+ } else {
+ header_prefix = ""
+ }
+
outputs = [
- "$generated_file_path/$generated_file_name.h",
- "$generated_file_path/$generated_file_name.cc",
+ "$generated_file_path/${header_prefix}{{source_name_part}}.h",
+ "$generated_file_path/${generated_bindings_prefix}_{{source_name_part}}.cc",
]
args = [
@@ -76,12 +84,49 @@
"--component-info",
rebase_path(invoker.component_info),
"--extended-attributes",
- rebase_path(invoker.extended_attributes),
+ rebase_path(_extended_attributes_file),
"{{source}}",
]
}
}
+template("generate_type_conversion") {
+ action(target_name) {
+ script = "//starboard/build/run_bash.py"
+ py_script = engine_conversion_header_generator_script
+
+ deps = invoker.deps
+
+ public_deps = engine_dependencies
+
+ inputs = [
+ py_script,
+ _extended_attributes_file,
+ invoker.global_names_idl_file,
+ "//cobalt/bindings/shared/idl_conditional_macros.h",
+ ]
+ inputs += _bindings_extra_inputs
+ inputs += invoker.inputs
+
+ # Generated header file that contains forward declarations for converting
+ # to/from JS value and generated types.
+ outputs = [ "${invoker.output_dir}/${generated_bindings_prefix}_gen_type_conversion.h" ]
+
+ args = [
+ "python2",
+ rebase_path(py_script, root_build_dir),
+ "--cache-directory",
+ rebase_path(invoker.cache_directory, root_build_dir),
+ "--output-dir",
+ rebase_path(invoker.output_dir, root_build_dir),
+ "--interfaces-info",
+ rebase_path(invoker.interfaces_info, root_build_dir),
+ "--component-info",
+ rebase_path(invoker.component_info, root_build_dir),
+ ]
+ }
+}
+
template("compute_global_objects") {
action(target_name) {
script = "//starboard/build/run_bash.py"
@@ -157,7 +202,7 @@
inputs = [
py_script,
"$bindings_scripts_dir/utilities.py",
- invoker.extended_attributes,
+ _extended_attributes_file,
invoker.generated_idl_files,
]
inputs += invoker.idl_files
@@ -189,7 +234,7 @@
"--component-info-file",
rebase_path(invoker.component_info_file, root_build_dir),
"--extended-attributes",
- rebase_path(invoker.extended_attributes, root_build_dir),
+ rebase_path(_extended_attributes_file, root_build_dir),
"--dependency-idl-files",
dependency_idl_files_list,
"--",
@@ -199,3 +244,101 @@
]
}
}
+
+template("generate_interfaces_info_overall") {
+ action(target_name) {
+ script = "//starboard/build/run_bash.py"
+ py_script = "$bindings_scripts_dir/compute_interfaces_info_overall.py"
+
+ deps = invoker.deps
+
+ inputs = [
+ py_script,
+ invoker.individual_interfaces_file,
+ ]
+
+ outputs = [ invoker.combined_interfaces_file ]
+
+ args = [
+ "python2",
+ rebase_path(py_script, root_build_dir),
+ "--",
+ rebase_path(invoker.individual_interfaces_file, root_build_dir),
+ rebase_path(invoker.combined_interfaces_file, root_build_dir),
+ ]
+ }
+}
+
+template("cache_lex_tables") {
+ action(target_name) {
+ script = "//starboard/build/run_bash.py"
+ py_script = "$bindings_scripts_dir/blink_idl_parser.py"
+
+ inputs = [ py_script ]
+ inputs += idl_lexer_parser_files
+
+ outputs = [
+ "${invoker.output_dir}/lextab.py",
+ "${invoker.output_dir}/parsetab.pickle",
+ ]
+
+ args = [
+ "python2",
+ rebase_path(py_script, root_build_dir),
+ rebase_path(invoker.output_dir, root_build_dir),
+ ]
+ }
+}
+
+template("cache_templates") {
+ action(target_name) {
+ script = "//starboard/build/run_bash.py"
+ py_script = "//cobalt/bindings/code_generator_cobalt.py"
+
+ inputs = [
+ py_script,
+ "//cobalt/bindings/path_generator.py",
+ "//third_party/jinja2/__init__.py",
+ "//third_party/markupsafe/__init__.py", # jinja2 dep
+ ]
+
+ # Cobalt's Jinja templates.
+ inputs += engine_template_files
+
+ # Templates that are shared by the code generation for multiple engines.
+ inputs += [
+ "//cobalt/bindings/templates/dictionary.h.template",
+ "//cobalt/bindings/templates/interface-base.cc.template",
+ "//cobalt/bindings/templates/interface-base.h.template",
+ "//cobalt/bindings/templates/callback-interface-base.cc.template",
+ "//cobalt/bindings/templates/callback-interface-base.h.template",
+ ]
+
+ # The filenames are hashes of the template file path.
+ outputs = [
+ "${invoker.output_dir}/__jinja2_0458bc0cdbf0dcc5ede1938c55c599dc57dcae71.cache",
+ "${invoker.output_dir}/__jinja2_303dad9caff7b8c046562177d5460bb9f7b159df.cache",
+ "${invoker.output_dir}/__jinja2_4038c00836c6764d385cf4c46462430f9aaf17be.cache",
+ "${invoker.output_dir}/__jinja2_435670c71610a56f1cef61f2d38133c78acac78f.cache",
+ "${invoker.output_dir}/__jinja2_4a2a64213caba2133a0b71aada741965e14fc1bb.cache",
+ "${invoker.output_dir}/__jinja2_4c7f97150281049711d4ce10a328cc061253b390.cache",
+ "${invoker.output_dir}/__jinja2_5d45ad6b9cacef6d9d179dc9099ec37cf6ee1724.cache",
+ "${invoker.output_dir}/__jinja2_65e7aef6627bae7400833440f6e4ae091c710908.cache",
+ "${invoker.output_dir}/__jinja2_74db451681d577fcb9c39eae42a296cbbcf752c7.cache",
+ "${invoker.output_dir}/__jinja2_8e238dc5f6ff498c4406817387ddb7ece64b45bd.cache",
+ "${invoker.output_dir}/__jinja2_b0197cf1f0bbd6fc7944725aec13712883090f68.cache",
+ "${invoker.output_dir}/__jinja2_d049954a9d5a3dc383d37b7673da54b661ec34b0.cache",
+ "${invoker.output_dir}/__jinja2_d09b22d687fad0727f239c3feb23cadd73ac6bc0.cache",
+ "${invoker.output_dir}/__jinja2_df2da080300d5d4b19c5c7b8b87f6a5521d00c89.cache",
+ ]
+
+ args = [
+ "python2",
+ rebase_path(py_script, root_build_dir),
+ rebase_path("${invoker.output_dir}", root_build_dir),
+ rebase_path("$engine_templates_dir", root_build_dir),
+ rebase_path("${invoker.output_dir}/cached_jinja_templates.stamp",
+ root_build_dir),
+ ]
+ }
+}
diff --git a/cobalt/bindings/bindings_variables.gni b/cobalt/bindings/bindings_variables.gni
deleted file mode 100644
index 04e9b59..0000000
--- a/cobalt/bindings/bindings_variables.gni
+++ /dev/null
@@ -1,57 +0,0 @@
-# Copyright 2021 The Cobalt Authors. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import("//cobalt/browser/browser_bindings_variables.gni")
-import("//cobalt/script/v8c/v8c_variables.gni")
-
-# Migrated from cobalt/bindings/bindings.gypi
-#############################################
-# Blink interface info is calculated in two stages. First at a per-component level
-# (in Blink this is core or modules) and then these are combined. While Cobalt
-# currently does not and may not need to distinguish between components, we adhere to
-# Blink's process for simplicity.
-component_info_pickle = "$bindings_scripts_output_dir/component_info.pickle"
-interfaces_info_individual_pickle =
- "$bindings_scripts_output_dir/interfaces_info_individual.pickle"
-interfaces_info_combined_pickle =
- "$bindings_scripts_output_dir/interfaces_info_overall.pickle"
-
-# The allowlist of what extended attributes we support. If an attribute
-# not in this list is encountered, it will cause an error in the
-# pipeline.
-extended_attributes_file = "//cobalt/bindings/IDLExtendedAttributes.txt"
-
-# Base directory into which generated bindings source files will be
-# generated. Directory structure will mirror the directory structure
-# that the .idl files came from.
-generated_source_output_dir = "$bindings_output_dir/source"
-
-# Generated header file that contains forward declarations for converting
-# to/from JS value and generated types.
-generated_type_conversion_header_file = "$generated_source_output_dir/${generated_bindings_prefix}_gen_type_conversion.h"
-
-# Directory containing generated IDL files.
-generated_idls_output_dir = "$bindings_output_dir/idl"
-
-# Templates that are shared by the code generation for multiple engines.
-shared_template_files = [
- "//cobalt/bindings/templates/dictionary.h.template",
- "//cobalt/bindings/templates/interface-base.cc.template",
- "//cobalt/bindings/templates/interface-base.h.template",
- "//cobalt/bindings/templates/callback-interface-base.cc.template",
- "//cobalt/bindings/templates/callback-interface-base.h.template",
-]
-
-# Cobalt's Jinja templates.
-code_generator_template_files = engine_template_files + shared_template_files
diff --git a/cobalt/bindings/blink_variables.gni b/cobalt/bindings/blink_variables.gni
index b90a7d5..d19986d 100644
--- a/cobalt/bindings/blink_variables.gni
+++ b/cobalt/bindings/blink_variables.gni
@@ -12,8 +12,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import("//cobalt/browser/browser_bindings_variables.gni")
-
# Migrated from third_party/blink/Source/bindings/scripts/scripts.gypi
######################################################################
bindings_scripts_dir = "//third_party/blink/Source/bindings/scripts"
@@ -46,8 +44,6 @@
"//third_party/blink/Source/bindings/scripts/v8_utilities.py",
]
-idl_cache_files = [ "$bindings_scripts_output_dir/lextab.py" ]
-
idl_lexer_parser_files =
get_path_info([
# PLY (Python Lex-Yacc)
diff --git a/cobalt/bindings/testing/BUILD.gn b/cobalt/bindings/testing/BUILD.gn
new file mode 100644
index 0000000..ca902da
--- /dev/null
+++ b/cobalt/bindings/testing/BUILD.gn
@@ -0,0 +1,389 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import("//cobalt/bindings/bindings_templates.gni")
+import("//cobalt/script/v8c/v8c_variables.gni")
+
+##########################################################
+# Configuration variables for bindings generation scripts.
+##########################################################
+
+_bindings_output_dir = "$root_gen_dir/bindings/testing"
+_bindings_scripts_output_dir = "$_bindings_output_dir/scripts"
+
+# Blink interface info is calculated in two stages. First at a per-component level
+# (in Blink this is core or modules) and then these are combined. While Cobalt
+# currently does not and may not need to distinguish between components, we adhere to
+# Blink's process for simplicity.
+_component_info_pickle = "$_bindings_scripts_output_dir/component_info.pickle"
+_interfaces_info_individual_pickle =
+ "$_bindings_scripts_output_dir/interfaces_info_individual.pickle"
+_interfaces_info_combined_pickle =
+ "$_bindings_scripts_output_dir/interfaces_info_overall.pickle"
+_global_objects_pickle = "$_bindings_scripts_output_dir/GlobalObjects.pickle"
+
+# Base directory into which generated bindings source files will be
+# generated. Directory structure will mirror the directory structure
+# that the .idl files came from.
+_generated_source_output_dir = "$_bindings_output_dir/source"
+
+# Directory containing generated IDL files.
+_generated_idls_output_dir = "$_bindings_output_dir/idl"
+
+###################################################
+# IDL files to be compiled to bindings source code.
+###################################################
+
+# Testing IDL files.
+_source_idl_files = [
+ "anonymous_indexed_getter_interface.idl",
+ "anonymous_named_getter_interface.idl",
+ "anonymous_named_indexed_getter_interface.idl",
+ "arbitrary_interface.idl",
+ "base_interface.idl",
+ "boolean_type_test_interface.idl",
+ "callback_function_interface.idl",
+ "callback_interface_interface.idl",
+ "conditional_interface.idl",
+ "constants_interface.idl",
+ "constructor_interface.idl",
+ "constructor_with_arguments_interface.idl",
+ "convert_simple_object_interface.idl",
+ "derived_getter_setter_interface.idl",
+ "derived_interface.idl",
+ "dictionary_interface.idl",
+ "disabled_interface.idl",
+ "dom_string_test_interface.idl",
+ "enumeration_interface.idl",
+ "exception_object_interface.idl",
+ "exceptions_interface.idl",
+ "extended_idl_attributes_interface.idl",
+ "garbage_collection_test_interface.idl",
+ "global_interface_parent.idl",
+ "indexed_getter_interface.idl",
+ "interface_with_any.idl",
+ "interface_with_any_dictionary.idl",
+ "interface_with_date.idl",
+ "interface_with_unsupported_properties.idl",
+ "named_constructor_interface.idl",
+ "named_getter_interface.idl",
+ "named_indexed_getter_interface.idl",
+ "nested_put_forwards_interface.idl",
+ "no_constructor_interface.idl",
+ "no_interface_object_interface.idl",
+ "nullable_types_test_interface.idl",
+ "numeric_types_test_interface.idl",
+ "object_type_bindings_interface.idl",
+ "operations_test_interface.idl",
+ "promise_interface.idl",
+ "put_forwards_interface.idl",
+ "sequence_user.idl",
+ "single_operation_interface.idl",
+ "static_properties_interface.idl",
+ "stringifier_anonymous_operation_interface.idl",
+ "stringifier_attribute_interface.idl",
+ "stringifier_operation_interface.idl",
+ "target_interface.idl",
+ "union_types_interface.idl",
+ "window.idl",
+]
+
+_generated_header_idl_files = [
+ "derived_dictionary.idl",
+ "dictionary_with_dictionary_member.idl",
+ "test_dictionary.idl",
+ "test_enum.idl",
+]
+
+# Partial interfaces and the right-side of "implements"
+# Code will not get generated for these interfaces; they are used to add
+# functionality to other interfaces.
+_dependency_idl_files = [
+ "implemented_interface.idl",
+ "partial_interface.idl",
+ "interface_with_unsupported_properties_partial.idl",
+]
+
+_unsupported_interface_idl_files = [ "unsupported_interface.idl" ]
+
+#########################
+# Bindings tests targets.
+#########################
+
+static_library("bindings_test_implementation") {
+ testonly = true
+
+ sources = [
+ "constants_interface.cc",
+ "constructor_interface.cc",
+ "exceptions_interface.cc",
+ "garbage_collection_test_interface.cc",
+ "named_constructor_interface.cc",
+ "operations_test_interface.cc",
+ "put_forwards_interface.cc",
+ "static_properties_interface.cc",
+ ]
+
+ deps = [
+ ":generated_types_test_support",
+ "//cobalt/base",
+ "//testing/gmock",
+ "//testing/gtest",
+ ]
+}
+
+target(gtest_target_type, "bindings_test") {
+ testonly = true
+
+ sources = [
+ "any_bindings_test.cc",
+ "any_dictionary_bindings_test.cc",
+ "array_buffers_test.cc",
+ "boolean_type_bindings_test.cc",
+ "callback_function_test.cc",
+ "callback_interface_test.cc",
+ "conditional_attribute_test.cc",
+ "constants_bindings_test.cc",
+ "constructor_bindings_test.cc",
+ "convert_simple_object_test.cc",
+ "date_bindings_test.cc",
+ "dependent_interface_test.cc",
+ "dictionary_test.cc",
+ "dom_string_bindings_test.cc",
+ "enumeration_bindings_test.cc",
+ "evaluate_script_test.cc",
+ "exceptions_bindings_test.cc",
+ "extended_attributes_test.cc",
+ "garbage_collection_test.cc",
+ "get_own_property_descriptor.cc",
+ "getter_setter_test.cc",
+ "global_interface_bindings_test.cc",
+ "interface_object_test.cc",
+ "nullable_types_bindings_test.cc",
+ "numeric_type_bindings_test.cc",
+ "object_type_bindings_test.cc",
+ "operations_bindings_test.cc",
+ "optional_arguments_bindings_test.cc",
+ "promise_test.cc",
+ "put_forwards_test.cc",
+ "sequence_bindings_test.cc",
+ "stack_trace_test.cc",
+ "static_properties_bindings_test.cc",
+ "stringifier_bindings_test.cc",
+ "union_type_bindings_test.cc",
+ "unsupported_test.cc",
+ "variadic_arguments_bindings_test.cc",
+ ]
+
+ configs += [ ":enable_conditional_interface" ]
+
+ deps = [
+ ":bindings_test_implementation",
+ ":bindings_test_support",
+ "//cobalt/base",
+ "//cobalt/script",
+ "//cobalt/script/v8c:engine",
+ "//cobalt/test:run_all_unittests",
+ "//testing/gmock",
+ "//testing/gtest",
+ ]
+}
+
+###################
+# Bindings sandbox.
+###################
+
+target(final_executable_type, "bindings_sandbox") {
+ testonly = true
+ sources = [ "bindings_sandbox_main.cc" ]
+ deps = [
+ ":bindings_test_implementation",
+ ":bindings_test_support",
+ "//cobalt/base",
+ "//cobalt/script:engine",
+ "//cobalt/script:standalone_javascript_runner",
+ ]
+}
+
+##############################
+# Bindings generation targets.
+##############################
+
+group("bindings_test_support") {
+ testonly = true
+ public_deps = [
+ ":generated_bindings_sources_test_support",
+ ":generated_types_sources_test_support",
+ ]
+}
+
+config("enable_conditional_interface") {
+ defines = [
+ "ENABLE_CONDITIONAL_INTERFACE",
+ "ENABLE_CONDITIONAL_PROPERTY",
+ ]
+}
+
+config("generated_test_support_sources_includes") {
+ include_dirs = [ _generated_source_output_dir ]
+}
+
+idl_compile("generated_bindings_test_support") {
+ testonly = true
+ sources = _source_idl_files
+
+ cache_directory = _bindings_scripts_output_dir
+ component_info = _component_info_pickle
+ interfaces_info = _interfaces_info_combined_pickle
+ output_directory = _generated_source_output_dir
+ header_prefix = "${generated_bindings_prefix}"
+
+ # TODO(b/211055528): Missing deps from generated sources.
+ deps = [
+ ":cached_jinja_templates_test_support",
+ ":cached_lex_yacc_tables_test_support",
+ ":generated_type_conversion_test_support",
+ ":generated_types_test_support",
+ ":global_constructors_idls_test_support",
+ ":interfaces_info_individual_test_support",
+ ":interfaces_info_overall_test_support",
+ "//cobalt/script:engine",
+ ]
+ public_deps = engine_dependencies + [ "//testing/gmock" ]
+
+ public_configs = [ ":generated_test_support_sources_includes" ]
+}
+
+source_set("generated_bindings_sources_test_support") {
+ testonly = true
+ sources = get_target_outputs(":generated_bindings_test_support")
+ configs += [ ":enable_conditional_interface" ]
+ public_deps = [ ":generated_bindings_test_support" ]
+ deps = [
+ # Ensure that all the files have been generated before trying to compile.
+ ":generated_types_test_support",
+ ]
+}
+
+idl_compile("generated_types_test_support") {
+ sources = _generated_header_idl_files
+
+ cache_directory = _bindings_scripts_output_dir
+ component_info = _component_info_pickle
+ interfaces_info = _interfaces_info_combined_pickle
+ output_directory = _generated_source_output_dir
+
+ # TODO(b/211055528): Missing deps from generated sources.
+ deps = [
+ ":cached_jinja_templates_test_support",
+ ":cached_lex_yacc_tables_test_support",
+ ":global_constructors_idls_test_support",
+ ":interfaces_info_individual_test_support",
+ ":interfaces_info_overall_test_support",
+ ]
+ public_deps = engine_dependencies
+
+ public_configs = [ ":generated_test_support_sources_includes" ]
+}
+
+source_set("generated_types_sources_test_support") {
+ testonly = true
+ sources = get_target_outputs(":generated_types_test_support")
+ public_deps = [ ":generated_types_test_support" ]
+ deps = [
+ # Ensure that all the files have been generated before trying to compile.
+ ":generated_bindings_test_support",
+ ]
+}
+
+generate_type_conversion("generated_type_conversion_test_support") {
+ # TODO(b/211055528): Missing deps from generated sources.
+ deps = [
+ ":cached_jinja_templates_test_support",
+ ":cached_lex_yacc_tables_test_support",
+ ":global_constructors_idls_test_support",
+ ":interfaces_info_overall_test_support",
+ ]
+
+ # Generated IDL file that will define all the constructors that should be
+ # on the Window object.
+ global_names_idl_file =
+ "$_generated_idls_output_dir/testing/window_constructors.idl"
+
+ inputs = [ _interfaces_info_combined_pickle ]
+ inputs += _source_idl_files
+ inputs += _generated_header_idl_files
+
+ cache_directory = _bindings_scripts_output_dir
+ output_dir = _generated_source_output_dir
+ interfaces_info = _interfaces_info_combined_pickle
+ component_info = _component_info_pickle
+}
+
+compute_global_objects("global_objects_test_support") {
+ idl_files = _source_idl_files + _generated_header_idl_files
+
+ global_objects_file = _global_objects_pickle
+}
+
+compute_global_constructors_idls("global_constructors_idls_test_support") {
+ idl_files = _source_idl_files + _unsupported_interface_idl_files
+
+ global_objects_file = _global_objects_pickle
+
+ # Generated IDL file that will define all the constructors that should be
+ # on the Window object.
+ global_names_idl_file =
+ "$_generated_idls_output_dir/testing/window_constructors.idl"
+
+ # Dummy header file which is generated because the idl compiler assumes
+ # there is a header for each IDL.
+ global_constructors_generated_header_file =
+ "$_generated_idls_output_dir/testing/window_constructors.h"
+
+ deps = [ ":global_objects_test_support" ]
+}
+
+compute_interfaces_info_individual("interfaces_info_individual_test_support") {
+ idl_files = _source_idl_files + _generated_header_idl_files +
+ _dependency_idl_files + _unsupported_interface_idl_files
+
+ # Generated IDL file that will define all the constructors that should be
+ # on the Window object.
+ generated_idl_files =
+ "$_generated_idls_output_dir/testing/window_constructors.idl"
+ component_info_file = _component_info_pickle
+ interfaces_info_file = _interfaces_info_individual_pickle
+ cache_directory = _bindings_scripts_output_dir
+ dependency_idl_files = _dependency_idl_files
+
+ deps = [
+ ":cached_lex_yacc_tables_test_support",
+ ":global_constructors_idls_test_support",
+ ]
+}
+
+generate_interfaces_info_overall("interfaces_info_overall_test_support") {
+ individual_interfaces_file = _interfaces_info_individual_pickle
+ combined_interfaces_file = _interfaces_info_combined_pickle
+ deps = [ ":interfaces_info_individual_test_support" ]
+}
+
+cache_lex_tables("cached_lex_yacc_tables_test_support") {
+ output_dir = _bindings_scripts_output_dir
+}
+
+cache_templates("cached_jinja_templates_test_support") {
+ output_dir = _bindings_scripts_output_dir
+}
diff --git a/cobalt/black_box_tests/README.md b/cobalt/black_box_tests/README.md
index 6a696ea..2fd70f4 100644
--- a/cobalt/black_box_tests/README.md
+++ b/cobalt/black_box_tests/README.md
@@ -60,22 +60,22 @@
work with runner.JSTestsSucceeded() in the python test scripts. Together,
they allow for test logic to exist in either the python test scripts or
JavaScript test data.
-e.g. Call OnEndTest() to signal test completion in the JavaScripts,
+e.g. Call OnEndTest() to signal test completion on the JavaScript side,
JSTestsSucceeded() will react to the signal and return the test status of
-JavaScript test logic; another example is that when python script wants to wait
-for some setup steps on JavaScript, call runner.WaitForJSTestsSetup(). Calling
-setupFinished() in JavaScript whenever ready will unblock the wait.
+JavaScript test logic; another example is that when the python script wants to
+wait for some setup steps on JavaScript, call runner.WaitForJSTestsSetup().
+Calling setupFinished() in JavaScript whenever ready will unblock the wait.
## Test Data
-A default local test server will be launcher before any unit test starts to
+A default local test server will be launched before any unit test starts to
serve the test data in black_box_tests/testdata/. The server's port will be
passed to the app launcher to fetch test data from.
-Test data can include target web page for Cobalt to open and any additional
-resource(font file, JavaScripts...).
+Test data can include the target web page for Cobalt to open and any additional
+resources (font file, JavaScripts...).
Tests are free to start their own HTTP servers if the default test server is
-inadequate(e.g. The test is testing that Cobalt handles receipt of a specific
+inadequate (e.g. The test is testing that Cobalt handles receipt of a specific
HTTP server generated error code properly).
diff --git a/cobalt/black_box_tests/black_box_tests.py b/cobalt/black_box_tests/black_box_tests.py
index cae01ff..bfb73d8 100644
--- a/cobalt/black_box_tests/black_box_tests.py
+++ b/cobalt/black_box_tests/black_box_tests.py
@@ -65,6 +65,7 @@
'compression_test',
'disable_eval_with_csp',
'persistent_cookie',
+ 'soft_mic_platform_service_test',
'web_debugger',
'web_platform_tests',
]
diff --git a/cobalt/black_box_tests/testdata/soft_mic_platform_service_test.html b/cobalt/black_box_tests/testdata/soft_mic_platform_service_test.html
new file mode 100644
index 0000000..2a451b9
--- /dev/null
+++ b/cobalt/black_box_tests/testdata/soft_mic_platform_service_test.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+
+<html>
+ <head></head>
+ <body>
+ <script src='black_box_js_test_utils.js'></script>
+ <script src='soft_mic_platform_service_test.js'></script>
+ </body>
+</html>
diff --git a/cobalt/black_box_tests/testdata/soft_mic_platform_service_test.js b/cobalt/black_box_tests/testdata/soft_mic_platform_service_test.js
new file mode 100644
index 0000000..39f0947
--- /dev/null
+++ b/cobalt/black_box_tests/testdata/soft_mic_platform_service_test.js
@@ -0,0 +1,296 @@
+// Copyright 2021 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+'use strict';
+
+const SOFT_MIC_SERVICE_NAME = "com.google.youtube.tv.SoftMic";
+
+function failTest() {
+ notReached();
+ onEndTest();
+}
+
+/**
+* @param {ArrayBuffer} data to be converted to a String.
+*/
+function ab2str(data) {
+ try {
+ return String.fromCharCode.apply(null, new Uint8Array(data));
+ } catch(error) {
+ console.error(`ab2str() error: ${error}, decoding data: ${data}`);
+ }
+}
+
+/**
+* @param {String} data to be converted to an ArrayBuffer.
+*/
+function str2ab(data) {
+ try {
+ return Uint8Array.from(data.split(''), (s) => {return s.charCodeAt(0)}).buffer;
+ } catch(error) {
+ console.error(`str2ab() error: ${error}, decoding data: ${data}`);
+ }
+}
+
+function bothUndefined(hard_mic, soft_mic) {
+ assertFalse(hard_mic);
+ assertTrue(soft_mic);
+}
+
+function hardMicUndefinedSoftMicTrue(hard_mic, soft_mic) {
+ assertFalse(hard_mic);
+ assertTrue(soft_mic);
+}
+
+function hardMicUndefinedSoftMicFalse(hard_mic, soft_mic) {
+ assertFalse(hard_mic);
+ assertFalse(soft_mic);
+}
+
+function hardMicTrueSoftMicUndefined(hard_mic, soft_mic) {
+ assertTrue(hard_mic);
+ assertTrue(soft_mic);
+}
+
+function hardMicTrueSoftMicTrue(hard_mic, soft_mic) {
+ assertTrue(hard_mic);
+ assertTrue(soft_mic);
+}
+
+function hardMicTrueSoftMicFalse(hard_mic, soft_mic) {
+ assertTrue(hard_mic);
+ assertFalse(soft_mic);
+}
+
+function hardMicFalseSoftMicUndefined(hard_mic, soft_mic) {
+ assertFalse(hard_mic);
+ assertTrue(soft_mic);
+}
+
+function hardMicFalseSoftMicTrue(hard_mic, soft_mic) {
+ assertFalse(hard_mic);
+ assertTrue(soft_mic);
+}
+
+function hardMicFalseSoftMicFalse(hard_mic, soft_mic) {
+ assertFalse(hard_mic);
+ assertFalse(soft_mic);
+}
+
+function micGestureNull(micGesture) {
+ assertFalse(micGesture);
+ assertTrue(micGesture == null);
+}
+
+function micGestureHold(micGesture) {
+ assertTrue(micGesture);
+ assertTrue(micGesture == "HOLD");
+}
+
+function micGestureTap(micGesture) {
+ assertTrue(micGesture);
+ assertTrue(micGesture == "TAP");
+}
+
+/**
+* @param {function} assertCallback
+* @param {boolean} testMicGestureOnly
+*/
+function testService(assertCallback, testMicGestureOnly = false) {
+ var service_send_done = false;
+ var service_response_received = false;
+
+ /**
+ * @param {ArrayBuffer} data
+ */
+ function testResponseIsTrue(data) {
+ try {
+ assertTrue(new Int8Array(data)[0]);
+ } catch (error) {
+ console.log(`Error in testResponseIsTrue: ${error}`);
+ notReached();
+ }
+ }
+
+ /**
+ * @param {ArrayBuffer} data
+ */
+ function receiveCallback(service, data) {
+ var str_response = ab2str(data);
+
+ try {
+ var response = JSON.parse(str_response);
+ var has_hard_mic = response["hasHardMicSupport"];
+ var has_soft_mic = response["hasSoftMicSupport"];
+ var mic_gesture = response["micGesture"];
+
+ console.log(`receiveCallback() response:
+ has_hard_mic: ${has_hard_mic},
+ has_soft_mic: ${has_soft_mic},
+ micGesture: ${mic_gesture}`);
+
+ if (testMicGestureOnly)
+ assertCallback(mic_gesture);
+ else
+ assertCallback(has_hard_mic, has_soft_mic);
+ } catch (error) {
+ console.log(`receiveCallback() error: ${error}`);
+ failTest();
+ }
+
+ service_response_received = true;
+
+ if (service_send_done) {
+ soft_mic_service.close();
+ onEndTest();
+ }
+ }
+
+ if (!H5vccPlatformService) {
+ console.log("H5vccPlatformService is not implemented");
+ onEndTest();
+ return;
+ }
+
+ if (!H5vccPlatformService.has(SOFT_MIC_SERVICE_NAME)) {
+ console.log(`H5vccPlatformService.Has(${SOFT_MIC_SERVICE_NAME}) returned false.`);
+ onEndTest();
+ return;
+ }
+
+ // Open the service and pass the receive_callback.
+ var soft_mic_service = H5vccPlatformService.open(SOFT_MIC_SERVICE_NAME,
+ receiveCallback);
+
+ if (soft_mic_service === null) {
+ console.log("H5vccPlatformService.open() returned null");
+ failTest();
+ return;
+ }
+
+ // Send "getMicSupport" message and test the sync response here and the async platform
+ // response in the receiveCallback()
+ testResponseIsTrue(soft_mic_service.send(str2ab(JSON.stringify("getMicSupport"))));
+
+ // Test the sync response for "notifySearchActive".
+ testResponseIsTrue(soft_mic_service.send(str2ab(JSON.stringify("notifySearchActive"))));
+
+ // Test the sync response for "notifySearchInactive".
+ testResponseIsTrue(soft_mic_service.send(str2ab(JSON.stringify("notifySearchInactive"))));
+
+ service_send_done = true;
+
+ if (service_response_received) {
+ soft_mic_service.close();
+ onEndTest();
+ }
+}
+
+function testIncorrectRequests() {
+ /**
+ * @param {ArrayBuffer} data
+ */
+ function testResponseIsFalse(data) {
+ try {
+ assertFalse(new Int8Array(data)[0]);
+ } catch (error) {
+ console.log(`Error in testResponseIsFalse: ${error}`);
+ notReached();
+ }
+ }
+
+ if (!H5vccPlatformService) {
+ console.log("H5vccPlatformService is not implemented");
+ onEndTest();
+ return;
+ }
+
+ if (!H5vccPlatformService.has(SOFT_MIC_SERVICE_NAME)) {
+ console.log(`H5vccPlatformService.Has(${SOFT_MIC_SERVICE_NAME}) returned false.`);
+ onEndTest();
+ return;
+ }
+
+ // Open the service and pass the receive_callback.
+ var soft_mic_service = H5vccPlatformService.open(SOFT_MIC_SERVICE_NAME, (service, data) => { });
+
+ if (soft_mic_service === null) {
+ console.log("H5vccPlatformService.open() returned null");
+ failTest();
+ return;
+ }
+
+ // Send "getMicSupport" without JSON.stringify.
+ testResponseIsFalse(soft_mic_service.send(str2ab("getMicSupport")));
+
+ // Send "notifySearchActive" without JSON.stringify.
+ testResponseIsFalse(soft_mic_service.send(str2ab("notifySearchActive")));
+
+ // Send "notifySearchInactive" without JSON.stringify.
+ testResponseIsFalse(soft_mic_service.send(str2ab("notifySearchInactive")));
+
+ // Send "" empty string.
+ testResponseIsFalse(soft_mic_service.send(str2ab("")));
+
+ // Send "foo" invalid message string.
+ testResponseIsFalse(soft_mic_service.send(str2ab("foo")));
+
+ // Complete the test.
+ soft_mic_service.close();
+ onEndTest();
+}
+
+/**
+* @param {KeyboardEvent} event
+*/
+window.onkeydown = function(event) {
+ if (event.shiftKey) {
+ testMicGesture(event)
+ return;
+ }
+
+ if (event.key == 0) {
+ testIncorrectRequests();
+ } else if (event.key == 1) {
+ testService(bothUndefined);
+ } else if (event.key == 2) {
+ testService(hardMicUndefinedSoftMicTrue);
+ } else if (event.key == 3) {
+ testService(hardMicUndefinedSoftMicFalse);
+ } else if (event.key == 4) {
+ testService(hardMicTrueSoftMicUndefined);
+ } else if (event.key == 5) {
+ testService(hardMicTrueSoftMicTrue);
+ } else if (event.key == 6) {
+ testService(hardMicTrueSoftMicFalse);
+ } else if (event.key == 7) {
+ testService(hardMicFalseSoftMicUndefined);
+ } else if (event.key == 8) {
+ testService(hardMicFalseSoftMicTrue);
+ } else if (event.key == 9) {
+ testService(hardMicFalseSoftMicFalse);
+ }
+}
+
+/**
+* @param {KeyboardEvent} event
+*/
+function testMicGesture(event) {
+ if (event.key == 0) {
+ testService(micGestureNull, true);
+ } else if (event.key == 1) {
+ testService(micGestureHold, true);
+ } else if (event.key == 2) {
+ testService(micGestureTap, true);
+ }
+}
diff --git a/cobalt/black_box_tests/tests/soft_mic_platform_service_test.py b/cobalt/black_box_tests/tests/soft_mic_platform_service_test.py
new file mode 100644
index 0000000..d68322a
--- /dev/null
+++ b/cobalt/black_box_tests/tests/soft_mic_platform_service_test.py
@@ -0,0 +1,131 @@
+# Copyright 2021 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License
+"""Test SoftMicPlatformService messages match between web app and platform"""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import _env # pylint: disable=unused-import
+
+from cobalt.black_box_tests import black_box_tests
+from cobalt.black_box_tests.threaded_web_server import ThreadedWebServer
+from cobalt.tools.automated_testing import webdriver_utils
+
+keys = webdriver_utils.import_selenium_module('webdriver.common.keys')
+
+
+class SoftMicPlatformServiceTest(black_box_tests.BlackBoxTestCase):
+
+ def test_soft_mic_platform_service(self):
+ with ThreadedWebServer(binding_address=self.GetBindingAddress()) as server:
+ url = server.GetURL(
+ file_name='testdata/soft_mic_platform_service_test.html')
+
+ # The webpage listens for NUMPAD0 through NUMPAD9 at opening
+ # to test hasHardMicSupport and hasSoftMicSupport switch values.
+ with self.CreateCobaltRunner(url=url) as runner:
+ # Press NUMPAD0 to test testIncorrectRequests
+ runner.SendKeys(keys.Keys.NUMPAD0)
+ self.assertTrue(runner.JSTestsSucceeded())
+
+ with self.CreateCobaltRunner(url=url) as runner:
+ # Press NUMPAD1 to test bothUndefined
+ runner.SendKeys(keys.Keys.NUMPAD1)
+ self.assertTrue(runner.JSTestsSucceeded())
+
+ with self.CreateCobaltRunner(
+ url=url, target_params=['--has_soft_mic_support=true']) as runner:
+ # Press NUMPAD2 to test hardMicUndefinedSoftMicTrue
+ runner.SendKeys(keys.Keys.NUMPAD2)
+ self.assertTrue(runner.JSTestsSucceeded())
+
+ with self.CreateCobaltRunner(
+ url=url, target_params=['--has_soft_mic_support=false']) as runner:
+ # Press NUMPAD3 to test hardMicUndefinedSoftMicFalse
+ runner.SendKeys(keys.Keys.NUMPAD3)
+ self.assertTrue(runner.JSTestsSucceeded())
+
+ with self.CreateCobaltRunner(
+ url=url, target_params=['--has_hard_mic_support=true']) as runner:
+ # Press NUMPAD4 to test hardMicTrueSoftMicUndefined
+ runner.SendKeys(keys.Keys.NUMPAD4)
+ self.assertTrue(runner.JSTestsSucceeded())
+
+ with self.CreateCobaltRunner(
+ url=url,
+ target_params=([
+ '--has_hard_mic_support=true', '--has_soft_mic_support=true'
+ ])) as runner:
+ # Press NUMPAD5 to test hardMicTrueSoftMicTrue
+ runner.SendKeys(keys.Keys.NUMPAD5)
+ self.assertTrue(runner.JSTestsSucceeded())
+
+ with self.CreateCobaltRunner(
+ url=url,
+ target_params=([
+ '--has_hard_mic_support=true', '--has_soft_mic_support=false'
+ ])) as runner:
+ # Press NUMPAD6 to test hardMicTrueSoftMicFalse
+ runner.SendKeys(keys.Keys.NUMPAD6)
+ self.assertTrue(runner.JSTestsSucceeded())
+
+ with self.CreateCobaltRunner(
+ url=url, target_params=['--has_hard_mic_support=false']) as runner:
+ # Press NUMPAD7 to test hardMicFalseSoftMicUndefined
+ runner.SendKeys(keys.Keys.NUMPAD7)
+ self.assertTrue(runner.JSTestsSucceeded())
+
+ with self.CreateCobaltRunner(
+ url=url,
+ target_params=([
+ '--has_hard_mic_support=false', '--has_soft_mic_support=true'
+ ])) as runner:
+ # Press NUMPAD8 to test hardMicFalseSoftMicTrue
+ runner.SendKeys(keys.Keys.NUMPAD8)
+ self.assertTrue(runner.JSTestsSucceeded())
+
+ with self.CreateCobaltRunner(
+ url=url,
+ target_params=([
+ '--has_hard_mic_support=false', '--has_soft_mic_support=false'
+ ])) as runner:
+ # Press NUMPAD9 to test hardMicFalseSoftMicFalse
+ runner.SendKeys(keys.Keys.NUMPAD9)
+ self.assertTrue(runner.JSTestsSucceeded())
+
+ # The webpage listens for NUMPAD0 through NUMPAD9 at opening with SHIFT
+ # to test micGesture tap and hold switch values.
+ with self.CreateCobaltRunner(url=url) as runner:
+ # Press SHIFT, NUMPAD0 to test micGestureNull
+ runner.SendKeys([keys.Keys.SHIFT, keys.Keys.NUMPAD0])
+ self.assertTrue(runner.JSTestsSucceeded())
+
+ with self.CreateCobaltRunner(
+ url=url, target_params=['--mic_gesture=foo']) as runner:
+ # Press SHIFT, NUMPAD0 to test micGestureNull
+ runner.SendKeys([keys.Keys.SHIFT, keys.Keys.NUMPAD0])
+ self.assertTrue(runner.JSTestsSucceeded())
+
+ with self.CreateCobaltRunner(
+ url=url, target_params=['--mic_gesture=hold']) as runner:
+ # Press SHIFT, NUMPAD1 to test micGestureHold
+ runner.SendKeys([keys.Keys.SHIFT, keys.Keys.NUMPAD1])
+ self.assertTrue(runner.JSTestsSucceeded())
+
+ with self.CreateCobaltRunner(
+ url=url, target_params=['--mic_gesture=tap']) as runner:
+ # Press SHIFT, NUMPAD2 to test micGestureTap
+ runner.SendKeys([keys.Keys.SHIFT, keys.Keys.NUMPAD2])
+ self.assertTrue(runner.JSTestsSucceeded())
diff --git a/cobalt/browser/BUILD.gn b/cobalt/browser/BUILD.gn
index cd760a5..6c9ae83 100644
--- a/cobalt/browser/BUILD.gn
+++ b/cobalt/browser/BUILD.gn
@@ -13,8 +13,34 @@
# limitations under the License.
import("//cobalt/bindings/bindings_templates.gni")
-import("//cobalt/browser/browser_bindings_variables.gni")
import("//cobalt/browser/idl_files.gni")
+import("//cobalt/script/v8c/v8c_variables.gni")
+
+##########################################################
+# Configuration variables for bindings generation scripts.
+##########################################################
+
+_bindings_output_dir = "$root_gen_dir/bindings/browser"
+_bindings_scripts_output_dir = "$_bindings_output_dir/scripts"
+
+# Blink interface info is calculated in two stages. First at a per-component level
+# (in Blink this is core or modules) and then these are combined. While Cobalt
+# currently does not and may not need to distinguish between components, we adhere to
+# Blink's process for simplicity.
+_component_info_pickle = "$_bindings_scripts_output_dir/component_info.pickle"
+_interfaces_info_individual_pickle =
+ "$_bindings_scripts_output_dir/interfaces_info_individual.pickle"
+_interfaces_info_combined_pickle =
+ "$_bindings_scripts_output_dir/interfaces_info_overall.pickle"
+_global_objects_pickle = "$_bindings_scripts_output_dir/GlobalObjects.pickle"
+
+# Base directory into which generated bindings source files will be
+# generated. Directory structure will mirror the directory structure
+# that the .idl files came from.
+_generated_source_output_dir = "$_bindings_output_dir/source"
+
+# Directory containing generated IDL files.
+_generated_idls_output_dir = "$_bindings_output_dir/idl"
target(final_executable_type, "cobalt") {
sources = [ "main.cc" ]
@@ -24,18 +50,40 @@
"//cobalt/base",
"//net",
]
+ content_deps = [
+ "//cobalt/dom:licenses",
+ "//cobalt/network:copy_ssl_certificates",
+ "//cobalt/speech:speech_testdata",
+ "//cobalt/webdriver:copy_webdriver_data",
+ "//third_party/icu:icudata",
+ ]
+ if (cobalt_font_package == "empty") {
+ content_deps += [ "//cobalt/content/fonts:copy_font_data" ]
+ } else {
+ content_deps += [
+ "//cobalt/content/fonts:copy_fonts",
+ "//cobalt/content/fonts:fonts_xml",
+ ]
+ }
+ if (!is_gold) {
+ content_deps += [ "//cobalt/debug:copy_backend_web_files" ]
+ }
}
+##############################
+# Bindings generation targets.
+##############################
+
config("bindings_includes") {
- include_dirs = [ generated_source_output_dir ]
+ include_dirs = [ _generated_source_output_dir ]
}
source_set("browser_switches") {
+ has_pedantic_warnings = true
sources = [
"switches.cc",
"switches.h",
]
-
public_deps = [ "//starboard:starboard_headers_only" ]
}
@@ -117,6 +165,7 @@
"//cobalt/ui_navigation",
"//cobalt/webdriver",
"//cobalt/websocket",
+ "//cobalt/worker",
"//cobalt/xhr",
"//crypto",
"//nb",
@@ -142,13 +191,49 @@
}
if (sb_is_evergreen) {
- # TODO(b/206642994): Migrate //cobalt/updater
- # deps += [ "//cobalt/updater" ]
+ deps += [ "//cobalt/updater" ]
} else {
deps += cobalt_platform_dependencies
}
}
+target(gtest_target_type, "browser_test") {
+ testonly = true
+ has_pedantic_warnings = true
+
+ sources = [
+ "device_authentication_test.cc",
+ "memory_settings/auto_mem_settings_test.cc",
+ "memory_settings/auto_mem_test.cc",
+ "memory_settings/calculations_test.cc",
+ "memory_settings/memory_settings_test.cc",
+ "memory_settings/pretty_print_test.cc",
+ "memory_settings/table_printer_test.cc",
+ "memory_settings/test_common.h",
+ "memory_tracker/tool/tool_impl_test.cc",
+ "memory_tracker/tool/util_test.cc",
+ "user_agent_string_test.cc",
+ ]
+
+ deps = [
+ ":browser",
+ ":browser_switches",
+ "//cobalt/base",
+ "//cobalt/browser/memory_settings:browser_memory_settings",
+ "//cobalt/browser/memory_tracker:memory_tracker_tool",
+ "//cobalt/dom",
+ "//cobalt/loader",
+ "//cobalt/math",
+ "//cobalt/network",
+ "//cobalt/speech",
+ "//cobalt/storage",
+ "//cobalt/test:run_all_unittests",
+ "//nb",
+ "//testing/gmock",
+ "//testing/gtest",
+ ]
+}
+
group("bindings") {
public_configs = [ ":bindings_includes" ]
@@ -163,11 +248,11 @@
idl_compile("generated_bindings") {
sources = source_idl_files
- cache_directory = bindings_scripts_output_dir
- component_info = component_info_pickle
- extended_attributes = extended_attributes_file
- interfaces_info = interfaces_info_combined_pickle
- output_directory = generated_source_output_dir
+ cache_directory = _bindings_scripts_output_dir
+ component_info = _component_info_pickle
+ interfaces_info = _interfaces_info_combined_pickle
+ output_directory = _generated_source_output_dir
+ header_prefix = "${generated_bindings_prefix}"
# TODO(b/211055528): Missing deps from generated sources.
deps = [
@@ -191,17 +276,17 @@
deps = [
# Ensure that all the files have been generated before trying to compile.
":generated_types",
+ "//cobalt/webdriver",
]
}
idl_compile("generated_types") {
sources = generated_header_idl_files
- cache_directory = bindings_scripts_output_dir
- component_info = component_info_pickle
- extended_attributes = extended_attributes_file
- interfaces_info = interfaces_info_combined_pickle
- output_directory = generated_source_output_dir
+ cache_directory = _bindings_scripts_output_dir
+ component_info = _component_info_pickle
+ interfaces_info = _interfaces_info_combined_pickle
+ output_directory = _generated_source_output_dir
# TODO(b/211055528): Missing deps from generated sources.
deps = [
@@ -225,10 +310,7 @@
]
}
-action("generated_type_conversion") {
- script = "//starboard/build/run_bash.py"
- py_script = engine_conversion_header_generator_script
-
+generate_type_conversion("generated_type_conversion") {
# TODO(b/211055528): Missing deps from generated sources.
deps = [
":cached_jinja_templates",
@@ -237,59 +319,41 @@
":interfaces_info_overall",
]
- public_deps = engine_dependencies
-
# Generated IDL file that will define all the constructors that should be
# on the Window object.
global_names_idl_file =
- "$generated_idls_output_dir/dom/window_constructors.idl"
- inputs = [
- py_script,
- interfaces_info_combined_pickle,
- extended_attributes_file,
- global_names_idl_file,
- "//cobalt/bindings/shared/idl_conditional_macros.h",
- ]
- inputs += bindings_extra_inputs
+ "$_generated_idls_output_dir/dom/window_constructors.idl"
+
+ inputs = [ _interfaces_info_combined_pickle ]
inputs += source_idl_files
inputs += generated_header_idl_files
- outputs = [ generated_type_conversion_header_file ]
-
- args = [
- "python2",
- rebase_path(py_script, root_build_dir),
- "--cache-directory",
- rebase_path(bindings_scripts_output_dir, root_build_dir),
- "--output-dir",
- rebase_path(generated_source_output_dir, root_build_dir),
- "--interfaces-info",
- rebase_path(interfaces_info_combined_pickle, root_build_dir),
- "--component-info",
- rebase_path(component_info_pickle, root_build_dir),
- ]
+ cache_directory = _bindings_scripts_output_dir
+ output_dir = _generated_source_output_dir
+ interfaces_info = _interfaces_info_combined_pickle
+ component_info = _component_info_pickle
}
compute_global_objects("global_objects") {
idl_files = source_idl_files + generated_header_idl_files
- global_objects_file = global_objects_pickle
+ global_objects_file = _global_objects_pickle
}
compute_global_constructors_idls("global_constructors_idls") {
idl_files = source_idl_files
- global_objects_file = global_objects_pickle
+ global_objects_file = _global_objects_pickle
# Generated IDL file that will define all the constructors that should be
# on the Window object.
global_names_idl_file =
- "$generated_idls_output_dir/dom/window_constructors.idl"
+ "$_generated_idls_output_dir/dom/window_constructors.idl"
# Dummy header file which is generated because the idl compiler assumes
# there is a header for each IDL.
global_constructors_generated_header_file =
- "$generated_idls_output_dir/dom/window_constructors.h"
+ "$_generated_idls_output_dir/dom/window_constructors.h"
deps = [ ":global_objects" ]
}
@@ -300,11 +364,11 @@
# Generated IDL file that will define all the constructors that should be
# on the Window object.
- generated_idl_files = "$generated_idls_output_dir/dom/window_constructors.idl"
- component_info_file = component_info_pickle
- interfaces_info_file = interfaces_info_individual_pickle
- cache_directory = bindings_scripts_output_dir
- extended_attributes = extended_attributes_file
+ generated_idl_files =
+ "$_generated_idls_output_dir/dom/window_constructors.idl"
+ component_info_file = _component_info_pickle
+ interfaces_info_file = _interfaces_info_individual_pickle
+ cache_directory = _bindings_scripts_output_dir
deps = [
":cached_lex_yacc_tables",
@@ -312,69 +376,16 @@
]
}
-action("interfaces_info_overall") {
- script = "//starboard/build/run_bash.py"
- py_script = "$bindings_scripts_dir/compute_interfaces_info_overall.py"
-
+generate_interfaces_info_overall("interfaces_info_overall") {
+ individual_interfaces_file = _interfaces_info_individual_pickle
+ combined_interfaces_file = _interfaces_info_combined_pickle
deps = [ ":interfaces_info_individual" ]
-
- inputs = [
- py_script,
- interfaces_info_individual_pickle,
- ]
-
- outputs = [ interfaces_info_combined_pickle ]
-
- args = [
- "python2",
- rebase_path(py_script, root_build_dir),
- "--",
- rebase_path(interfaces_info_individual_pickle, root_build_dir),
- rebase_path(interfaces_info_combined_pickle, root_build_dir),
- ]
}
-action("cached_lex_yacc_tables") {
- script = "//starboard/build/run_bash.py"
- py_script = "$bindings_scripts_dir/blink_idl_parser.py"
-
- inputs = [ py_script ]
- inputs += idl_lexer_parser_files
-
- outputs = [
- "$bindings_scripts_output_dir/lextab.py",
- "$bindings_scripts_output_dir/parsetab.pickle",
- ]
-
- args = [
- "python2",
- rebase_path(py_script, root_build_dir),
- rebase_path(bindings_scripts_output_dir, root_build_dir),
- ]
+cache_lex_tables("cached_lex_yacc_tables") {
+ output_dir = _bindings_scripts_output_dir
}
-action("cached_jinja_templates") {
- script = "//starboard/build/run_bash.py"
- py_script = "//cobalt/bindings/code_generator_cobalt.py"
-
- inputs = [
- py_script,
- "//cobalt/bindings/path_generator.py",
- "//third_party/jinja2/__init__.py",
- "//third_party/markupsafe/__init__.py", # jinja2 dep
- ]
- inputs += code_generator_template_files
-
- # TODO: Figure out a way to list the actual outputs instead
- # Dummy to track dependency
- outputs = [ "$bindings_scripts_output_dir/cached_jinja_templates.stamp" ]
-
- args = [
- "python2",
- rebase_path(py_script, root_build_dir),
- rebase_path("$bindings_scripts_output_dir", root_build_dir),
- rebase_path("$engine_templates_dir", root_build_dir),
- rebase_path("$bindings_scripts_output_dir/cached_jinja_templates.stamp",
- root_build_dir),
- ]
+cache_templates("cached_jinja_templates") {
+ output_dir = _bindings_scripts_output_dir
}
diff --git a/cobalt/browser/browser.gyp b/cobalt/browser/browser.gyp
index 9c7c7f5..6091c14 100644
--- a/cobalt/browser/browser.gyp
+++ b/cobalt/browser/browser.gyp
@@ -134,6 +134,7 @@
'<(DEPTH)/cobalt/ui_navigation/ui_navigation.gyp:ui_navigation',
'<(DEPTH)/cobalt/webdriver/webdriver.gyp:webdriver',
'<(DEPTH)/cobalt/websocket/websocket.gyp:websocket',
+ '<(DEPTH)/cobalt/worker/worker.gyp:*',
'<(DEPTH)/cobalt/xhr/xhr.gyp:xhr',
'<(DEPTH)/net/net.gyp:net',
'<(DEPTH)/nb/nb.gyp:nb',
diff --git a/cobalt/browser/browser_bindings_gen.gyp b/cobalt/browser/browser_bindings_gen.gyp
index bce6391..fc7c2d7 100644
--- a/cobalt/browser/browser_bindings_gen.gyp
+++ b/cobalt/browser/browser_bindings_gen.gyp
@@ -237,6 +237,8 @@
'../websocket/close_event.idl',
'../websocket/web_socket.idl',
+ '../worker/service_worker_container.idl',
+
'../xhr/xml_http_request.idl',
'../xhr/xml_http_request_event_target.idl',
'../xhr/xml_http_request_upload.idl',
@@ -376,6 +378,7 @@
'../dom/window_timers.idl',
'../media_capture/navigator.idl',
'../media_session/navigator_media_session.idl',
+ '../worker/navigator.idl',
],
'conditions': [
diff --git a/cobalt/browser/browser_bindings_variables.gni b/cobalt/browser/browser_bindings_variables.gni
deleted file mode 100644
index b5b7768..0000000
--- a/cobalt/browser/browser_bindings_variables.gni
+++ /dev/null
@@ -1,20 +0,0 @@
-# Copyright 2021 The Cobalt Authors. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Migrated from cobalt/browser/browser_bindings_gen.gyp
-#######################################################
-bindings_output_dir = "$root_gen_dir/bindings/browser"
-bindings_scripts_output_dir = "$bindings_output_dir/scripts"
-
-global_objects_pickle = "$bindings_scripts_output_dir/GlobalObjects.pickle"
diff --git a/cobalt/browser/browser_module.cc b/cobalt/browser/browser_module.cc
index f749090..faf23cf 100644
--- a/cobalt/browser/browser_module.cc
+++ b/cobalt/browser/browser_module.cc
@@ -1766,22 +1766,31 @@
ResetResources();
+ // Suspend media module and update system window and resource provider.
+ if (media_module_) {
+ DCHECK(system_window_);
+ window_size_ = system_window_->GetWindowSize();
+#if SB_API_VERSION >= 13
+ // This needs to be done before destroying the renderer module as it
+ // may use the renderer module to release assets during the update.
+ media_module_->UpdateSystemWindowAndResourceProvider(NULL,
+ GetResourceProvider());
+#endif // SB_API_VERSION >= 13
+ }
+
if (renderer_module_) {
// Destroy the renderer module into so that it releases all its graphical
// resources.
DestroyRendererModule();
}
- if (media_module_) {
- DCHECK(system_window_);
- window_size_ = system_window_->GetWindowSize();
#if SB_API_VERSION >= 13
+ // Reset system window after renderer module destroyed.
+ if (media_module_) {
input_device_manager_.reset();
system_window_.reset();
- media_module_->UpdateSystemWindowAndResourceProvider(NULL,
- GetResourceProvider());
-#endif // SB_API_VERSION >= 13
}
+#endif // SB_API_VERSION >= 13
}
void BrowserModule::FreezeInternal(SbTimeMonotonic timestamp) {
diff --git a/cobalt/browser/browser_module.h b/cobalt/browser/browser_module.h
index cec410e..dc79f84 100644
--- a/cobalt/browser/browser_module.h
+++ b/cobalt/browser/browser_module.h
@@ -73,11 +73,11 @@
#if defined(ENABLE_DEBUGGER)
#include "cobalt/browser/debug_console.h"
#include "cobalt/browser/lifecycle_console_commands.h"
-#include "cobalt/debug/backend/debug_dispatcher.h"
+#include "cobalt/debug/backend/debug_dispatcher.h" // nogncheck
#include "cobalt/debug/backend/debugger_state.h"
#include "cobalt/debug/console/command_manager.h"
-#include "cobalt/debug/debug_client.h"
-#endif // ENABLE_DEBUGGER
+#include "cobalt/debug/debug_client.h" // nogncheck
+#endif // ENABLE_DEBUGGER
#if SB_IS(EVERGREEN)
#include "cobalt/updater/updater_module.h"
@@ -234,6 +234,7 @@
SbTimeMonotonic timestamp);
// Pass the deeplink timestamp from Starboard.
void SetDeepLinkTimestamp(SbTimeMonotonic timestamp);
+
private:
#if SB_HAS(CORE_DUMP_HANDLER_SUPPORT)
static void CoreDumpHandler(void* browser_module_as_void);
diff --git a/cobalt/browser/idl_files.gni b/cobalt/browser/idl_files.gni
index 5a3203a..d86cff1 100644
--- a/cobalt/browser/idl_files.gni
+++ b/cobalt/browser/idl_files.gni
@@ -228,6 +228,8 @@
"//cobalt/websocket/close_event.idl",
"//cobalt/websocket/web_socket.idl",
+ "//cobalt/worker/service_worker_container.idl",
+
"//cobalt/xhr/xml_http_request.idl",
"//cobalt/xhr/xml_http_request_event_target.idl",
"//cobalt/xhr/xml_http_request_upload.idl",
@@ -373,4 +375,5 @@
"//cobalt/dom/window_timers.idl",
"//cobalt/media_capture/navigator.idl",
"//cobalt/media_session/navigator_media_session.idl",
+ "//cobalt/worker/navigator.idl",
]
diff --git a/cobalt/browser/memory_settings/BUILD.gn b/cobalt/browser/memory_settings/BUILD.gn
index a99e9c2..83bcad6 100644
--- a/cobalt/browser/memory_settings/BUILD.gn
+++ b/cobalt/browser/memory_settings/BUILD.gn
@@ -13,6 +13,8 @@
# limitations under the License.
static_library("browser_memory_settings") {
+ has_pedantic_warnings = true
+
sources = [
"auto_mem.cc",
"auto_mem.h",
diff --git a/cobalt/browser/memory_tracker/BUILD.gn b/cobalt/browser/memory_tracker/BUILD.gn
index 4131d62..f2ecb49 100644
--- a/cobalt/browser/memory_tracker/BUILD.gn
+++ b/cobalt/browser/memory_tracker/BUILD.gn
@@ -13,6 +13,7 @@
# limitations under the License.
static_library("memory_tracker_tool") {
+ has_pedantic_warnings = true
sources = [
"tool.cc",
"tool.h",
diff --git a/cobalt/build/all.gyp b/cobalt/build/all.gyp
index b514602..080b70a 100644
--- a/cobalt/build/all.gyp
+++ b/cobalt/build/all.gyp
@@ -83,6 +83,7 @@
'<(DEPTH)/cobalt/webdriver/webdriver.gyp:*',
'<(DEPTH)/cobalt/webdriver/webdriver_test.gyp:*',
'<(DEPTH)/cobalt/websocket/websocket.gyp:*',
+ '<(DEPTH)/cobalt/worker/worker.gyp:*',
'<(DEPTH)/cobalt/xhr/xhr.gyp:*',
'<(DEPTH)/crypto/crypto.gyp:crypto_unittests_deploy',
'<(DEPTH)/third_party/boringssl/boringssl_tool.gyp:*',
diff --git a/cobalt/build/cobalt_configuration.gypi b/cobalt/build/cobalt_configuration.gypi
index 26085f3..d75f498 100644
--- a/cobalt/build/cobalt_configuration.gypi
+++ b/cobalt/build/cobalt_configuration.gypi
@@ -504,6 +504,8 @@
# further reduced on systems with extremely low memory.
'cobalt_media_source_garbage_collection_duration_threshold_in_seconds%': -1,
+ # TODO(b/212641065): Evaluate if any flags in defines_debug, defines_devel
+ # defines_qa need to be added to GN.
'defines_debug': [
'ALLOCATOR_STATS_TRACKING',
'COBALT_BOX_DUMP_ENABLED',
diff --git a/cobalt/build/gn.py b/cobalt/build/gn.py
new file mode 100644
index 0000000..93af0bb
--- /dev/null
+++ b/cobalt/build/gn.py
@@ -0,0 +1,105 @@
+# Copyright 2021 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Thin wrapper for building Starboard platforms with GN."""
+
+import argparse
+import os
+import shutil
+import subprocess
+import sys
+from pathlib import Path
+
+_REPOSITORY_ROOT = os.path.abspath(
+ os.path.join(os.path.dirname(__file__), os.pardir, os.pardir))
+sys.path.append(_REPOSITORY_ROOT)
+from starboard.build.platforms import PLATFORMS # pylint:disable=wrong-import-position
+
+_BUILD_TYPES = ['debug', 'devel', 'qa', 'gold']
+
+
+def main(out_directory: str, platform: str, build_type: str,
+ overwrite_args: bool, check_dependencies: bool):
+ platform_path = PLATFORMS[platform]
+ dst_args_gn_file = os.path.join(out_directory, 'args.gn')
+ src_args_gn_file = os.path.join(platform_path, 'args.gn')
+
+ Path(out_directory).mkdir(parents=True, exist_ok=True)
+
+ if overwrite_args or not os.path.exists(dst_args_gn_file):
+ shutil.copy(src_args_gn_file, dst_args_gn_file)
+
+ with open(dst_args_gn_file, 'a') as f:
+ f.write(f'build_type = "{build_type}"\n')
+ extra_args = []
+ if check_dependencies:
+ extra_args += ['--check']
+ gn_command = ['gn', 'gen', out_directory] + extra_args
+ print(' '.join(gn_command))
+ subprocess.check_call(gn_command)
+
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser()
+
+ builds_directory_group = parser.add_mutually_exclusive_group()
+ builds_directory_group.add_argument(
+ 'out_directory',
+ type=str,
+ nargs='?',
+ help='Path to the directory to build in.')
+ builds_directory_group.add_argument(
+ '-b',
+ '--builds_directory',
+ type=str,
+ help='Path to the directory a named build folder, <platform>_<config>/, '
+ 'will be created in.')
+
+ parser.add_argument(
+ '-p',
+ '--platform',
+ required=True,
+ choices=list(PLATFORMS),
+ help='The platform to build.')
+ parser.add_argument(
+ '-c',
+ '-C',
+ '--build_type',
+ default='devel',
+ choices=_BUILD_TYPES,
+ help='The build_type (configuration) to build with.')
+ parser.add_argument(
+ '--overwrite_args',
+ default=False,
+ action='store_true',
+ help='Whether or not to overwrite an existing args.gn file if one exists '
+ 'in the out directory. In general, if the file exists, you should run '
+ '`gn args <out_directory>` to edit it instead.')
+ parser.add_argument(
+ '--check',
+ default=False,
+ action='store_true',
+ help='Whether or not to generate the ninja files with the gn --check '
+ 'option.')
+ args = parser.parse_args()
+
+ if args.out_directory:
+ builds_out_directory = args.out_directory
+ else:
+ builds_directory = os.getenv('COBALT_BUILDS_DIRECTORY',
+ args.builds_directory or 'out')
+ builds_out_directory = os.path.join(builds_directory,
+ f'{args.platform}_{args.build_type}')
+
+ main(builds_out_directory, args.platform, args.build_type,
+ args.overwrite_args, args.check)
diff --git a/cobalt/content/fonts/BUILD.gn b/cobalt/content/fonts/BUILD.gn
index 8b99258..899c2df 100644
--- a/cobalt/content/fonts/BUILD.gn
+++ b/cobalt/content/fonts/BUILD.gn
@@ -16,12 +16,14 @@
if (cobalt_font_package == "empty") {
copy("copy_font_data") {
+ install_content = true
sources = [ "$source_font_config_dir/fonts.xml" ]
outputs =
[ "$sb_static_contents_output_data_dir/fonts/{{source_file_part}}" ]
}
} else {
action("fonts_xml") {
+ install_content = true
script = "scripts/filter_fonts.py"
font_xml = "$source_font_config_dir/fonts.xml"
sources = [ font_xml ]
@@ -35,6 +37,7 @@
}
copy("copy_fonts") {
+ install_content = true
if (copy_font_files) {
fonts = exec_script("scripts/filter_fonts.py",
[
diff --git a/cobalt/content/fonts/config/android/fonts.xml b/cobalt/content/fonts/config/android/fonts.xml
index d26ab07..1d10ac1 100644
--- a/cobalt/content/fonts/config/android/fonts.xml
+++ b/cobalt/content/fonts/config/android/fonts.xml
@@ -170,7 +170,7 @@
<font weight="400" style="normal">NotoSansHebrew-Regular.ttf</font>
<font weight="700" style="normal">NotoSansHebrew-Bold.ttf</font>
</family>
- <family>
+ <family lang="und-Thai">
<font weight="400" style="normal">NotoSansThaiUI-Regular.ttf</font>
<font weight="700" style="normal">NotoSansThaiUI-Bold.ttf</font>
</family>
diff --git a/cobalt/content/ssl/certs/4a6481c9.0 b/cobalt/content/ssl/certs/4a6481c9.0
deleted file mode 100644
index 6f0f8db..0000000
--- a/cobalt/content/ssl/certs/4a6481c9.0
+++ /dev/null
@@ -1,22 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4G
-A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp
-Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1
-MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEG
-A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI
-hvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6ErPL
-v4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8
-eoLrvozps6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklq
-tTleiDTsvHgMCJiEbKjNS7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzd
-C9XZzPnqJworc5HGnRusyMvo4KD0L5CLTfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pa
-zq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6CygPCm48CAwEAAaOBnDCB
-mTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm+IH
-V2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5n
-bG9iYWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG
-3lm0mi3f3BmGLjANBgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4Gs
-J0/WwbgcQ3izDJr86iw8bmEbTUsp9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO
-291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu01yiPqFbQfXf5WRDLenVOavS
-ot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG79G+dwfCMNYxd
-AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7
-TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg==
------END CERTIFICATE-----
diff --git a/cobalt/content/ssl/certs/76cb8f92.0 b/cobalt/content/ssl/certs/76cb8f92.0
deleted file mode 100644
index edbeb27..0000000
--- a/cobalt/content/ssl/certs/76cb8f92.0
+++ /dev/null
@@ -1,22 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYG
-A1UEChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2Jh
-bCBSb290MB4XDTA2MTIxNTA4MDAwMFoXDTIxMTIxNTA4MDAwMFowOzEYMBYGA1UE
-ChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2JhbCBS
-b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+Mi8vRRQZhP/8NN5
-7CPytxrHjoXxEnOmGaoQ25yiZXRadz5RfVb23CO21O1fWLE3TdVJDm71aofW0ozS
-J8bi/zafmGWgE07GKmSb1ZASzxQG9Dvj1Ci+6A74q05IlG2OlTEQXO2iLb3VOm2y
-HLtgwEZLAfVJrn5GitB0jaEMAs7u/OePuGtm839EAL9mJRQr3RAwHQeWP032a7iP
-t3sMpTjr3kfb1V05/Iin89cqdPHoWqI7n1C6poxFNcJQZZXcY4Lv3b93TZxiyWNz
-FtApD0mpSPCzqrdsxacwOUBdrsTiXSZT8M4cIwhhqJQZugRiQOwfOHB3EgZxpzAY
-XSUnpQIDAQABo4GlMIGiMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/
-MB0GA1UdDgQWBBS2CHsNesysIEyGVjJez6tuhS1wVzA/BgNVHR8EODA2MDSgMqAw
-hi5odHRwOi8vd3d3Mi5wdWJsaWMtdHJ1c3QuY29tL2NybC9jdC9jdHJvb3QuY3Js
-MB8GA1UdIwQYMBaAFLYIew16zKwgTIZWMl7Pq26FLXBXMA0GCSqGSIb3DQEBBQUA
-A4IBAQBW7wojoFROlZfJ+InaRcHUowAl9B8Tq7ejhVhpwjCt2BWKLePJzYFa+HMj
-Wqd8BfP9IjsO0QbE2zZMcwSO5bAi5MXzLqXZI+O4Tkogp24CJJ8iYGd7ix1yCcUx
-XOl5n4BHPa2hCwcUPUf/A2kaDAtE52Mlp3+yybh2hO0j9n0Hq0V+09+zv+mKts2o
-omcrUtW3ZfA5TGOgkXmTUg9U3YO7n9GPp1Nzw8v/MOx8BLjYRB+TX3EJIrduPuoc
-A06dGiBh+4E37F78CkWr1+cXVdCg6mCbpvbjjFspwgZgFJ0tl0ypkxWdYcQBX0jW
-WL1WMRJOEcgh4LMRkWXbtKaIOM5V
------END CERTIFICATE-----
diff --git a/cobalt/css_parser/BUILD.gn b/cobalt/css_parser/BUILD.gn
index 08114c0..93c7458 100644
--- a/cobalt/css_parser/BUILD.gn
+++ b/cobalt/css_parser/BUILD.gn
@@ -22,7 +22,7 @@
script = "//starboard/build/run_bash.py"
# Define the platform specific Bison binary.
- if (is_win) {
+ if (host_os == "win") {
bison_executable = "win_bison"
} else {
bison_executable = "bison"
diff --git a/cobalt/debug/BUILD.gn b/cobalt/debug/BUILD.gn
index 0f97f00..567027e 100644
--- a/cobalt/debug/BUILD.gn
+++ b/cobalt/debug/BUILD.gn
@@ -93,6 +93,8 @@
}
copy("copy_backend_web_files") {
+ install_content = true
+
sources = [
"backend/content/css_agent.js",
"backend/content/dom_agent.js",
diff --git a/cobalt/demos/content/BUILD.gn b/cobalt/demos/content/BUILD.gn
new file mode 100644
index 0000000..e87713e
--- /dev/null
+++ b/cobalt/demos/content/BUILD.gn
@@ -0,0 +1,216 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+copy("demos_testdata") {
+ sources = [
+ "animations-demo/index.html",
+ "animations-demo/layer_fern.css",
+ "animations-demo/layer_fern.js",
+ "animations-demo/layer_intro.css",
+ "animations-demo/layer_intro.js",
+ "animations-demo/layer_sun.css",
+ "animations-demo/layer_sun.js",
+ "background-mode-demo/background-mode-demo.html",
+ "background-mode-demo/background-mode-demo.js",
+ "cobalt-oxide/cobalt-oxide.css",
+ "cobalt-oxide/cobalt-oxide.html",
+ "cobalt-oxide/cobalt-oxide.js",
+ "color-transitions-demo/color-transitions-demo.html",
+ "crash-demo/crash-demo.html",
+ "deep-link-demo/deep-link-demo.html",
+ "deviceorientation-demo/deviceorientation-demo.html",
+ "disable-jit/index.html",
+ "dom-gc-demo/dom-gc-demo.html",
+ "dual-playback-demo/bear.mp4",
+ "dual-playback-demo/dual-playback-demo.html",
+ "eme-demo/eme-demo.html",
+ "eme-demo/eme-demo.js",
+ "focus-demo/focus-demo.html",
+ "hybrid-navigation/hybrid-navigation-grid.html",
+ "lottie-player-demo/lottie-player-demo.html",
+ "lottie-player-demo/lottie-player-raw-json-demo.html",
+ "lottie-player-demo/white_material_wave_loading.json",
+ "material-design-spinner-demo/index.html",
+ "media-capture/media-devices-test.html",
+ "media-element-demo/.gitignore",
+ "media-element-demo/README.md",
+ "media-element-demo/legacy/key-systems.html",
+ "media-element-demo/legacy/key-systems.js",
+ "media-element-demo/package-lock.json",
+ "media-element-demo/package.json",
+ "media-element-demo/public/assets/vp9_720p.webm",
+ "media-element-demo/public/index.html",
+ "media-element-demo/public/styles/app.css",
+ "media-element-demo/src/components/component.ts",
+ "media-element-demo/src/components/download_buffer_info.ts",
+ "media-element-demo/src/components/error_logger.ts",
+ "media-element-demo/src/components/player.ts",
+ "media-element-demo/src/components/router.ts",
+ "media-element-demo/src/components/source_buffer_info.ts",
+ "media-element-demo/src/components/video_info.ts",
+ "media-element-demo/src/components/watch.ts",
+ "media-element-demo/src/index.ts",
+ "media-element-demo/src/utils/download_buffer.ts",
+ "media-element-demo/src/utils/downloader.ts",
+ "media-element-demo/src/utils/enums.ts",
+ "media-element-demo/src/utils/limited_source_buffer.ts",
+ "media-element-demo/src/utils/media.ts",
+ "media-element-demo/src/utils/observable.ts",
+ "media-element-demo/src/utils/shared_values.ts",
+ "media-element-demo/tsconfig.json",
+ "media-element-demo/webpack.config.js",
+ "media-query/media-query-test.html",
+ "mtm-demo/mtm.html",
+ "mtm-demo/normal.html",
+ "opacity-transitions-demo/opacity-transitions-demo.html",
+ "page-visibility-demo/page-visibility-demo.html",
+ "performance-api-demo/performance-lifecycle-timing-demo.html",
+ "performance-api-demo/performance-resource-timing-demo.html",
+ "performance-api-demo/resources/square.png",
+ "pointer-events-demo/pointer-events-demo.html",
+ "screen_diagonal/screen_diagonal.html",
+ "script-debugger-test/script-debugger-test.html",
+ "script-tag-demo/increment-and-print-i.js",
+ "script-tag-demo/script-tag-demo.html",
+ "selector-tester/selector-tester.html",
+ "simple-xhr/simple-xhr.html",
+ "simple-xhr/simple-xhr.js",
+ "smooth-animations-demo/index.html",
+ "smooth-key-scroll/index.html",
+ "specificity-demo/specificity-demo.html",
+ "speech-synthesis-demo/index.html",
+ "splash_screen/beforeunload.html",
+ "splash_screen/block_render_tree_html_display_none.html",
+ "splash_screen/link_splash_screen.html",
+ "splash_screen/link_splash_screen_network.html",
+ "splash_screen/redirect_server.py",
+ "splash_screen/redirected.html",
+ "splash_screen/render_postponed.html",
+ "system-caption-settings/index.html",
+ "timer-demo/timer-demo.html",
+ "transitions-demo/transitions-demo.html",
+ "transparent-animated-webp-demo/bottleflip_loader.webp",
+ "transparent-animated-webp-demo/index.html",
+ "transparent-animated-webp-demo/loading-spinner-opaque.webp",
+ "transparent-animated-webp-demo/webp-animated-semitransparent4.webp",
+ "unload-demo/unload-demo.html",
+ "user-agent-client-hints-demo/user-agent-client-hints-demo.html",
+ "web-audio-demo/web-audio-demo.html",
+ ]
+
+ if (is_internal_build) {
+ sources += [
+ "javascript-fuzzer/index.html",
+ "kabuki/runtime-dump.html",
+ "media-element-demo/public/assets/ac3.mp4",
+ "media-element-demo/public/assets/dash-audio.mp4",
+ "media-element-demo/public/assets/dash-video-1080p.mp4",
+ "media-element-demo/public/assets/dash-video-240p.mp4",
+ "media-element-demo/public/assets/eac3.mp4",
+ "media-element-demo/public/assets/hvc1_480p.mp4",
+ "media-element-demo/public/assets/hvc1_480p_720p.mp4",
+ "media-element-demo/public/assets/hvc1_720p.mp4",
+ "media-element-demo/public/assets/hvc1_hdr_480p.mp4",
+ "media-element-demo/public/assets/progressive.mp4",
+ "mtm-demo/README.txt",
+ "mtm-demo/progressive.mp4",
+ "performance-spike/assets/Roboto-Regular.ttf",
+ "performance-spike/assets/banner.jpg",
+ "performance-spike/assets/banner1080.jpg",
+ "performance-spike/assets/banner1080baked.jpg",
+ "performance-spike/assets/banner1080withLinearGradient.jpg",
+ "performance-spike/assets/banner720.jpg",
+ "performance-spike/assets/banner720baked.jpg",
+ "performance-spike/assets/icons.ttf",
+ "performance-spike/assets/profile-alecmce.jpg",
+ "performance-spike/css/default.css",
+ "performance-spike/css/icons-content.css",
+ "performance-spike/css/icons.css",
+ "performance-spike/di/injector.js",
+ "performance-spike/di/mapping.js",
+ "performance-spike/di/resolver.js",
+ "performance-spike/index.html",
+ "performance-spike/namespace.js",
+ "performance-spike/runtime-dump.html",
+ "performance-spike/spike/anim/_config.js",
+ "performance-spike/spike/anim/alignment.js",
+ "performance-spike/spike/anim/centering.js",
+ "performance-spike/spike/anim/cssanimations/_config.js",
+ "performance-spike/spike/anim/cssanimations/animationbuilder.js",
+ "performance-spike/spike/anim/cssanimations/centering.js",
+ "performance-spike/spike/anim/cssanimations/rows.js",
+ "performance-spike/spike/anim/csstransitions/_config.js",
+ "performance-spike/spike/anim/csstransitions/centering.js",
+ "performance-spike/spike/anim/csstransitions/rows.js",
+ "performance-spike/spike/anim/csstransitions/transitionbuilder.js",
+ "performance-spike/spike/anim/rows.js",
+ "performance-spike/spike/anim/transformer/_config.js",
+ "performance-spike/spike/anim/transformer/centering.js",
+ "performance-spike/spike/anim/transformer/ease.js",
+ "performance-spike/spike/anim/transformer/rows.js",
+ "performance-spike/spike/anim/transformer/transformer.js",
+ "performance-spike/spike/anim/tweens/_config.js",
+ "performance-spike/spike/anim/tweens/centering.js",
+ "performance-spike/spike/anim/tweens/rows.js",
+ "performance-spike/spike/anim/tweens/tweens.js",
+ "performance-spike/spike/behavior/_config.js",
+ "performance-spike/spike/behavior/body.js",
+ "performance-spike/spike/behavior/buttons.js",
+ "performance-spike/spike/behavior/column.js",
+ "performance-spike/spike/behavior/content.js",
+ "performance-spike/spike/behavior/cssfocused.js",
+ "performance-spike/spike/behavior/factory.js",
+ "performance-spike/spike/behavior/header.js",
+ "performance-spike/spike/behavior/headerbuttons.js",
+ "performance-spike/spike/behavior/item.js",
+ "performance-spike/spike/behavior/main.js",
+ "performance-spike/spike/behavior/menu.js",
+ "performance-spike/spike/behavior/navigate.js",
+ "performance-spike/spike/behavior/row.js",
+ "performance-spike/spike/core/_config.js",
+ "performance-spike/spike/core/environment.js",
+ "performance-spike/spike/core/experiments.js",
+ "performance-spike/spike/core/fps.js",
+ "performance-spike/spike/core/location.js",
+ "performance-spike/spike/core/math.js",
+ "performance-spike/spike/core/rollingmean.js",
+ "performance-spike/spike/core/styles.js",
+ "performance-spike/spike/core/throttlefactory.js",
+ "performance-spike/spike/core/ticker.js",
+ "performance-spike/spike/ctrl/_config.js",
+ "performance-spike/spike/ctrl/behaviors.js",
+ "performance-spike/spike/ctrl/factory.js",
+ "performance-spike/spike/ctrl/focuser.js",
+ "performance-spike/spike/ctrl/hbox.js",
+ "performance-spike/spike/ctrl/keyhandler.js",
+ "performance-spike/spike/ctrl/leaf.js",
+ "performance-spike/spike/ctrl/vbox.js",
+ "performance-spike/spike/data/_config.js",
+ "performance-spike/spike/data/channel_crash_course.js",
+ "performance-spike/spike/data/menu.js",
+ "performance-spike/spike/data/menu_content.js",
+ "performance-spike/spike/data/update.js",
+ "performance-spike/spike/factory/_config.js",
+ "performance-spike/spike/factory/itemfactory.js",
+ "performance-spike/spike/factory/menufactory.js",
+ "performance-spike/spike/factory/rowfactory.js",
+ "performance-spike/spike/main.js",
+ "text-encoding-workaround/text-encoding-workaround.html",
+ ]
+ }
+
+ outputs = [
+ "$sb_static_contents_output_data_dir/test/demos/{{source_target_relative}}",
+ ]
+}
diff --git a/cobalt/demos/content/mse-eme-conformance-tests/0.5.html b/cobalt/demos/content/mse-eme-conformance-tests/0.5.html
deleted file mode 100644
index 26d8d0e..0000000
--- a/cobalt/demos/content/mse-eme-conformance-tests/0.5.html
+++ /dev/null
@@ -1,6299 +0,0 @@
-<!DOCTYPE html>
-<!--
-Copyright 2014 The Cobalt Authors. All Rights Reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
--->
-<html>
- <head>
- <title>Legacy Media Source and Encrypted Media Conformance Tests (2013)</title>
- <link rel="stylesheet" href="style-20150612143746.css" type="text/css"></link>
- <script type="text/javascript">
-function Alert(msg) {
- console.log(msg);
- throw msg;
-}
-
-// util-20150612143746.js begin
-(function() {
-
-if (!Function.prototype.bind) {
- Function.prototype.bind = function(oThis) {
- if (typeof this !== 'function') {
- throw new TypeError('What is trying to be bound is not a function');
- }
-
- var aArgs = Array.prototype.slice.call(arguments, 1);
- var fToBind = this;
- var fNOP = function() {};
- var fBound = function() {
- return fToBind.apply(
- this instanceof fNOP && oThis ? this : oThis,
- aArgs.concat(Array.prototype.slice.call(arguments)));
- };
-
- fNOP.prototype = this.prototype;
- fBound.prototype = new fNOP();
-
- return fBound;
- };
-}
-
-var util = {};
-
-util.createElement = function(tag, id, class_, innerHTML) {
- var element = document.createElement(tag);
- if (id != null)
- element.id = id;
- if (innerHTML != null)
- element.innerHTML = innerHTML;
- if (class_ != null)
- element.classList.add(class_);
- return element;
-};
-
-util.getClosestElement = function(refElement) {
- if (arguments.length === 1)
- return null;
-
- var bestElement = arguments[1];
- var bestDistance =
- Math.abs((bestElement.offsetLeft + bestElement.offsetWidth / 2) -
- (refElement.offsetLeft + refElement.offsetWidth / 2));
- for (var i = 2; i < arguments.length; ++i) {
- var currElement = arguments[i];
- var currDistance =
- Math.abs((currElement.offsetLeft + currElement.offsetWidth / 2) -
- (refElement.offsetLeft + refElement.offsetWidth / 2));
- if (currDistance < bestDistance) {
- bestDistance = currDistance;
- bestElement = currElement;
- }
- }
-
- return bestElement;
-};
-
-util.fireEvent = function(obj, eventName) {
- if (document.createEvent) {
- var event = document.createEvent('MouseEvents');
- event.initEvent(eventName, true, false);
- obj.dispatchEvent(event);
- } else if (document.createEventObject) {
- obj.fireEvent('on' + eventName);
- }
-};
-
-util.getElementWidth = function(element) {
- var style = window.getComputedStyle(element);
- var width = 0;
-
- if (!isNaN(parseInt(style.width))) width += parseInt(style.width);
- if (!isNaN(parseInt(style.marginLeft))) width += parseInt(style.marginLeft);
- if (!isNaN(parseInt(style.marginRight))) width += parseInt(style.marginRight);
-
- return width;
-};
-
-util.isValidArgument = function(arg) {
- return typeof(arg) != 'undefined' && arg != null;
-};
-
-util.MakeCapitalName = function(name) {
- name = name.substr(0, 1).toUpperCase() + name.substr(1);
- var offset = 0;
- for (;;) {
- var space = name.indexOf(' ', offset);
- if (space === -1)
- break;
- name = name.substr(0, space + 1) +
- name.substr(space + 1, 1).toUpperCase() + name.substr(space + 2);
- offset = space + 1;
- }
- return name;
-};
-
-util.Round = function(value, digits) {
- return Math.round(value * Math.pow(10, digits)) / Math.pow(10, digits);
-};
-
-util.SizeToText = function(size, unitType) {
- var unit = 'B';
- if (!!unitType && (unitType == 'B' || unitType == 'b')) {
- unit = unitType;
- }
- if (size >= 1024 * 1024) {
- size /= 1024 * 1024;
- unit = 'M';
- } else if (size >= 1024) {
- size /= 1024;
- unit = 'K';
- }
- if ((size - Math.floor(size)) * 10 <
- Math.floor(size))
- size = Math.floor(size);
- else
- size = util.Round(size, 3);
- return size + unit;
-};
-
-util.formatStatus = function(status) {
- if (typeof status === 'undefined')
- return 'undefined';
- else if (typeof status === 'string')
- return '"' + status + '"';
- else if (typeof status === 'number')
- return status.toString();
- else if (typeof status === 'boolean')
- return status ? 'true' : 'false';
- throw 'unknown status type';
-};
-
-util.getAttr = function(obj, attr) {
- attr = attr.split('.');
- if (!obj || attr.length === 0)
- return undefined;
- while (attr.length) {
- if (!obj)
- return undefined;
- obj = obj[attr.shift()];
- }
- return obj;
-};
-
-util.resize = function(str, newLength, fillValue) {
- if (typeof str != 'string')
- throw 'Only string is supported';
- if (str.length > newLength) {
- str.substr(0, newLength);
- } else {
- while (str.length < newLength)
- str += fillValue;
- }
-
- return str;
-};
-
-window.util = util;
-
-})();
-// util-20150612143746.js end
-
-// streamDef-20150612143746.js begin
-function getStreamDef(index) {
- var d = {};
- index = index || 0;
-
- var streamDefinitions = [
- {
- AudioType: 'audio/mp4; codecs="mp4a.40.2"',
- VideoType: 'video/mp4; codecs="avc1.640028"',
- AudioTiny: ['media/car-20120827-8b.mp4', 717502, 181.62],
- AudioNormal: ['media/car-20120827-8c.mp4', 2884572, 181.58, {
- 200000: 12.42}],
- AudioNormalAdv: ['media/car-20120827-8c.mp4', 2884572, 181.58, {
- 200000: 12.42}],
- AudioHuge: ['media/car-20120827-8d.mp4', 5789853, 181.58, {
- 'appendAudioOffset': 17.42}],
- VideoTiny: ['media/car-20120827-85.mp4', 6015001, 181.44, {
- 'videoChangeRate': 11.47}],
- VideoNormal: ['media/car-20120827-86.mp4', 15593225, 181.44, {
- 'mediaSourceDuration': Infinity}],
- VideoHuge: ['media/car-20120827-89.mp4', 95286345, 181.44],
- AudioTinyClearKey: ['media/car_cenc-20120827-8b.mp4', 783470, 181.62],
- AudioNormalClearKey: ['media/car_cenc-20120827-8c.mp4', 3013084, 181.58],
- AudioHugeClearKey: ['media/car_cenc-20120827-8d.mp4', 5918365, 181.58],
- VideoTinyClearKey: ['media/car_cenc-20120827-85.mp4', 6217017, 181.44],
- VideoNormalClearKey: ['media/car_cenc-20120827-86.mp4', 15795193, 181.44],
- VideoHugeClearKey: ['media/car_cenc-20120827-89.mp4', 95488313, 181.44],
- VideoStreamYTCenc: ['media/oops_cenc-20121114-145-no-clear-start.mp4', 39980507, 242.71],
- VideoTinyStreamYTCenc: ['media/oops_cenc-20121114-145-143.mp4', 7229257, 30.03],
- VideoSmallStreamYTCenc: ['media/oops_cenc-20121114-143-no-clear-start.mp4', 12045546, 242.71],
- Audio1MB: ['media/car-audio-1MB-trunc.mp4', 1048576, 65.875],
- Video1MB: ['media/test-video-1MB.mp4', 1053406, 1.04],
- ProgressiveLow: ['media/car_20130125_18.mp4', 15477531, 181.55],
- ProgressiveNormal: ['media/car_20130125_22.mp4', 55163609, 181.55],
- ProgressiveHigh: [],
- }, {
- AudioType: 'audio/mp4; codecs="mp4a.40.2"',
- VideoType: 'video/webm; codecs="vp9"',
- AudioTiny: ['media/car-20120827-8b.mp4', 717502, 181.62],
- AudioNormal: ['media/car-20120827-8c.mp4', 2884572, 181.58, {
- 200000: 12.42}],
- AudioNormalAdv: ['media/car-20120827-8c.mp4', 2884572, 181.58, {
- 200000: 12.42}],
- AudioHuge: ['media/car-20120827-8d.mp4', 5789853, 181.58, {
- 'appendAudioOffset': 17.42}],
- VideoTiny: ['media/feelings_vp9-20130806-242.webm', 4478156, 135.46, {
- 'videoChangeRate': 15.35}],
- VideoNormal: ['media/feelings_vp9-20130806-243.webm', 7902885, 135.46, {
- 'mediaSourceDuration': 135.469}],
- VideoHuge: ['media/feelings_vp9-20130806-247.webm', 27757852, 135.46],
- AudioTinyClearKey: ['media/car_cenc-20120827-8b.mp4', 783470, 181.62],
- AudioNormalClearKey: ['media/car_cenc-20120827-8c.mp4', 3013084, 181.58],
- AudioHugeClearKey: ['media/car_cenc-20120827-8d.mp4', 5918365, 181.58],
- VideoTinyClearKey: [],
- VideoNormalClearKey: [],
- VideoHugeClearKey: [],
- VideoStreamYTCenc: [],
- VideoTinyStreamYTCenc: [],
- VideoSmallStreamYTCenc: [],
- Audio1MB: ['media/car-audio-1MB-trunc.mp4', 1048576, 65.875],
- Video1MB: ['media/vp9-video-1mb.webm', 1103716, 1.00],
- ProgressiveLow: [],
- ProgressiveNormal: [],
- ProgressiveHigh: [],
- }
- ];
-
- d.AudioType = streamDefinitions[index]['AudioType'];
- d.VideoType = streamDefinitions[index]['VideoType'];
-
- var CreateAudioDef = function(src, size, duration, customMap) {
- return {name: 'audio', type: d.AudioType, size: size, src: src,
- duration: duration, bps: Math.floor(size / duration),
- customMap: customMap};
- };
-
- var CreateVideoDef = function(src, size, duration, customMap) {
- return {name: 'video', type: d.VideoType, size: size, src: src,
- duration: duration, bps: Math.floor(size / duration),
- customMap: customMap};
- };
-
- d.AudioTiny = CreateAudioDef.apply(this, streamDefinitions[index]['AudioTiny']);
- d.AudioNormal = CreateAudioDef.apply(this, streamDefinitions[index]['AudioNormal']);
- d.AudioNormalAdv = CreateAudioDef.apply(this, streamDefinitions[index]['AudioNormalAdv']);
- d.AudioHuge = CreateAudioDef.apply(this,streamDefinitions[index]['AudioHuge']);
-
- d.VideoTiny = CreateVideoDef.apply(this, streamDefinitions[index]['VideoTiny']);
- d.VideoNormal = CreateVideoDef.apply(this, streamDefinitions[index]['VideoNormal']);
- d.VideoHuge = CreateVideoDef.apply(this, streamDefinitions[index]['VideoHuge']);
-
- d.AudioTinyClearKey = CreateAudioDef.apply(this, streamDefinitions[index]['AudioTinyClearKey']);
- d.AudioNormalClearKey = CreateAudioDef.apply(this, streamDefinitions[index]['AudioNormalClearKey']);
- d.AudioHugeClearKey = CreateAudioDef.apply(this, streamDefinitions[index]['AudioHugeClearKey']);
-
- d.VideoTinyClearKey = CreateVideoDef.apply(this, streamDefinitions[index]['VideoTinyClearKey']);
- d.VideoNormalClearKey = CreateVideoDef.apply(this, streamDefinitions[index]['VideoNormalClearKey']);
- d.VideoHugeClearKey = CreateVideoDef.apply(this, streamDefinitions[index]['VideoHugeClearKey']);
-
- d.VideoStreamYTCenc = CreateVideoDef.apply(this, streamDefinitions[index]['VideoStreamYTCenc']);
- d.VideoTinyStreamYTCenc = CreateVideoDef.apply(this, streamDefinitions[index]['VideoTinyStreamYTCenc']);
- d.VideoSmallStreamYTCenc = CreateVideoDef.apply(this, streamDefinitions[index]['VideoSmallStreamYTCenc']);
-
- d.Audio1MB = CreateAudioDef.apply(this, streamDefinitions[index]['Audio1MB']);
- d.Video1MB = CreateVideoDef.apply(this, streamDefinitions[index]['Video1MB']);
-
- d.ProgressiveLow = CreateVideoDef.apply(this, streamDefinitions[index]['ProgressiveLow']);
- d.ProgressiveNormal = CreateVideoDef.apply(this, streamDefinitions[index]['ProgressiveNormal']);
- d.ProgressiveHigh = CreateVideoDef.apply(this, streamDefinitions[index]['ProgressiveHigh']);
-
- d.isWebM = function() {
- return index === 1;
- }
-
- return d;
-}
-
-var StreamDef = getStreamDef();
-
-function UpdateStreamDef(index) {
- StreamDef = getStreamDef(index);
-}
-
-// streamDef-20150612143746.js end
-
-// focusManager-20150612143746.js begin
-
-(function() {
-
-var INFINITY = 100000;
-var CLOSE = 50;
-var MAX_FUDGE = INFINITY;
-var DIRECTION_WEIGHT = 0.5;
-
-var LEFT = new Pair(-1, 0);
-var UP = new Pair(0, -1);
-var RIGHT = new Pair(1, 0);
-var DOWN = new Pair(0, 1);
-
-function Pair(x, y) {
- this.x = x;
- this.y = y;
-
- this.add = function(operand) {
- return new Pair(this.x + operand.x, this.y + operand.y);
- };
-
- this.sub = function(operand) {
- return new Pair(this.x - operand.x, this.y - operand.y);
- };
-
- this.dot = function(operand) {
- return this.x * operand.x + this.y * operand.y;
- };
-
- this.dotRelative = function(ref, operand) {
- return this.x * (operand.x - ref.x) + this.y * (operand.y - ref.y);
- };
-
- this.distTo = function(operand) {
- return Math.sqrt(this.x * operand.x + this.y * operand.y);
- };
-
- this.distTo2 = function(operand) {
- return this.x * operand.x + this.y * operand.y;
- };
-
- this.cross = function(operand) {
- return this.x * operand.y - this.y * operand.x;
- };
-}
-
-function Rect(left, top, width, height) {
- this.left = left;
- this.top = top;
- this.width = width;
- this.height = height;
- this.right = this.left + this.width;
- this.bottom = this.top + this.height;
-
- var rangeDist = function(start, end, startRef, endRef) {
- if (start < startRef) {
- if (end < startRef)
- return startRef - end;
- return 0;
- }
- if (start <= endRef)
- return 0;
- return start - endRef;
- };
-
- this.valid = function() {
- return this.width !== 0 && this.height !== 0;
- };
-
- this.inside = function(x, y) {
- // Technically speaking, this is not correct. However, this works out for
- // our usage.
- return x >= this.left && x < this.left + this.width &&
- y >= this.top && y < this.top + this.height;
- };
-
- this.intersect = function(that) {
- return this.inside(that.left, that.top) ||
- this.inside(that.right, that.top) ||
- this.inside(that.left, that.bottom) ||
- this.inside(that.right, that.bottom) ||
- that.inside(this.left, this.top) ||
- that.inside(this.right, this.top) ||
- that.inside(this.left, this.bottom) ||
- that.inside(this.right, this.bottom);
- };
-
- this.intersectComplete = function(that) {
- var centerXThat = (that.left + that.right) * 0.5;
- var centerYThat = (that.top + that.bottom) * 0.5;
- var halfThatWidth = that.width * 0.5;
- var halfThatHeight = that.height * 0.5;
- var expandedRect = new Rect(
- this.left - halfThatWidth, this.top - halfThatHeight,
- this.width + that.width, this.height + that.height);
- return expandedRect.inside(centerXThat, centerYThat);
- };
-
- this.distanceSquared = function(ref, dir) {
- var x, y;
- if (dir.x === -1) {
- x = Math.max((ref.left - this.right) * DIRECTION_WEIGHT, 0);
- y = rangeDist(this.top, this.bottom, ref.top, ref.bottom);
- } else if (dir.x === 1) {
- x = Math.max((this.left - ref.right) * DIRECTION_WEIGHT, 0);
- y = rangeDist(this.top, this.bottom, ref.top, ref.bottom);
- } else if (dir.y === -1) {
- x = rangeDist(this.left, this.right, ref.left, ref.right);
- y = Math.max((ref.top - this.bottom) * DIRECTION_WEIGHT, 0);
- } else {
- x = rangeDist(this.left, this.right, ref.left, ref.right);
- y = Math.max((this.top - ref.bottom) * DIRECTION_WEIGHT, 0);
- }
-
- return x * x + y * y;
- };
-
- this.generateSideSliver = function(dir) {
- var left, right, top, bottom;
-
- if (dir === LEFT) {
- left = this.left - CLOSE;
- right = this.left - 1;
- top = (this.top + this.bottom) * 0.5;
- bottom = top;
- } else if (dir === RIGHT) {
- left = this.right + 1;
- right = this.right + CLOSE;
- top = (this.top + this.bottom) * 0.5;
- bottom = top;
- } else if (dir === UP) {
- left = (this.left + this.right) * 0.5;
- right = (this.left + this.right) * 0.5;
- top = this.top - CLOSE;
- bottom = this.top - 1;
- } else {
- left = (this.left + this.right) * 0.5;
- right = (this.left + this.right) * 0.5;
- top = this.bottom + 1;
- bottom = this.bottom + CLOSE;
- }
-
- return new Rect(left, top, right - left, bottom - top);
- };
-
- // Generates a rectangle to check if there are any other rectangles strictly
- // to one side (defined by dir) of 'this'.
- this.generateSideRect = function(dir, fudge) {
- if (!fudge) {
- return this.generateSideSliver(dir);
- }
-
- var left, right, top, bottom;
-
- if (dir === LEFT) {
- left = -INFINITY;
- right = this.left - 1;
- top = this.top - fudge;
- bottom = this.bottom + fudge;
- } else if (dir === RIGHT) {
- left = this.right + 1;
- right = INFINITY;
- top = this.top - fudge;
- bottom = this.bottom + fudge;
- } else if (dir === UP) {
- left = this.left - fudge;
- right = this.right + fudge;
- top = -INFINITY;
- bottom = this.top - 1;
- } else {
- left = this.left - fudge;
- right = this.right + fudge;
- top = this.bottom + 1;
- bottom = INFINITY;
- }
-
- return new Rect(left, top, right - left, bottom - top);
- };
-
- this.toSideOf = function(target, dir) {
- var testX = [0, this.right - this.center.x, this.left - this.center.x];
- var testY = [0, this.bottom - this.center.y, this.top - this.center.y];
-
- var testLineSegRel0 = new Pair(
- testX[dir.x - (dir.x != 0)], testY[dir.y - (dir.y != 0)]);
- var testLineSegRel1 = new Pair(
- testY[dir.x + (dir.x != 0)], testY[dir.y + (dir.y != 0)]);
-
- return dir.cross(testLineSegRel0) * dir.cross(testLineSegRel1) <= 0 &&
- this.intersect(target);
- };
-
- this.toString = function() {
- return '(' + this.left + ', ' + this.top + ', ' + this.right + ', ' +
- this.bottom + ')';
- };
-};
-
-function createRect(element) {
- var offsetLeft = element.offsetLeft;
- var offsetTop = element.offsetTop;
- var e = element.offsetParent;
- while (e && e !== document.body) {
- offsetLeft += e.offsetLeft;
- offsetTop += e.offsetTop;
- e = e.offsetParent;
- }
- return new Rect(offsetLeft, offsetTop,
- element.offsetWidth, element.offsetHeight);
-};
-
-function FocusManager() {
- var elements = [];
- var handlers = [];
-
- var pickElement_ = function(currElem, dir, fudge) {
- var rect = createRect(currElem);
- var rectSide = rect.generateSideRect(dir, fudge);
- var bestDistanceSquared = INFINITY * INFINITY;
- var bestElement = null;
-
- for (var i = 0; i < elements.length; ++i) {
- if (elements[i] !== currElem) {
- var r = createRect(elements[i]);
-
- if (r.valid() && r.intersectComplete(rectSide)) {
- var distanceSquared = r.distanceSquared(rect, dir);
- if (!bestElement || distanceSquared < bestDistanceSquared) {
- bestElement = elements[i];
- bestDistanceSquared = distanceSquared;
- }
- }
- }
- }
-
- return bestElement;
- };
-
- var pickElement = function(currElem, dir) {
- return pickElement_(currElem, dir) ||
- pickElement_(currElem, dir, 2) ||
- pickElement_(currElem, dir, MAX_FUDGE);
- };
-
- var onkeydown = function(e) {
- if (elements.indexOf(e.target) !== -1) {
- var dir;
- if (e.keyCode === 37) { // left
- dir = LEFT;
- } else if (e.keyCode === 38) { // up
- dir = UP;
- } else if (e.keyCode === 39) { // right
- dir = RIGHT;
- } else if (e.keyCode === 40) { // down
- dir = DOWN;
- } else {
- return true;
- }
- var element = pickElement(e.target, dir);
- if (element) {
- element.focus();
- e.stopPropagation();
- e.preventDefault();
- }
- }
-
- return true;
- };
-
- this.add = function(element) {
- if (elements.indexOf(element) === -1) {
- elements.push(element);
- handlers.push(element.addEventListener('keydown', onkeydown));
- }
- };
-};
-
-window.addEventListener('load', function() {
- var focusManager = new FocusManager;
- var elements = document.getElementsByClassName('focusable');
- for (var i = 0; i < elements.length; ++i)
- focusManager.add(elements[i]);
-
- /*var links = document.getElementsByTagName('A');
- for (var i = 0; i < links.length; ++i)
- focusManager.add(links[i]);*/
-});
-
-})();
-
-// focusManager-20150612143746.js end
-
-// logger-20150612143746.js begin
-
-(function() {
-
-var Logger = function(log) {
- this.throwError = true;
- this.log = log;
- this.assert = function(cond, msg) {
- if (!cond) {
- this.log('Assert failed: ' + msg);
- try {
- var x = y.z.u.v.w;
- } catch (e) {
- this.log(e.stack);
- }
- if (this.throwError) throw 'Assert: ' + msg;
- }
- };
-
- this.check = function(condition, passMsg, failMsg) {
- if (condition)
- this.log(passMsg);
- else
- this.assert(false, failMsg);
- };
-
- this.checkEq = function(x, y, name) {
- var result = (x == y) ||
- (typeof(x) === 'number' && typeof(y) === 'number' &&
- isNaN(x) && isNaN(y));
- this.check(result, 'checkEq passed: ' + name + ' is (' + x + ').',
- name + ' is (' + x + ') which should be (' + y + ')');
- };
-
- this.checkNE = function(x, y, name) {
- var result = (x != y) &&
- !((typeof(x) === 'number' && typeof(y) === 'number' &&
- isNaN(x) && isNaN(y)));
- this.check(result, 'checkNE passed: ' + name + ' is (' + x + ').',
- name + ' is (' + x + ') which shouldn\'t.');
- };
-};
-
-window.createLogger = function(log) {
- return new Logger(log || console.log.bind(console));
-};
-
-})();
-
-// logger-20150612143746.js end
-
-// js/harness/xhr-20150612143746.js begin
-
-(function() {
-
-var BYPASS_CACHE = false;
-
-// Hook the onload event for request that is finished successfully
-var Request = function(manager, logger, file, onload, postLength,
- start, length) {
- var self = this;
-
- this.open = function() {
- this.xhr = new XMLHttpRequest();
-
- this.onload = onload;
- this.type = util.isValidArgument(postLength) ? 'POST' : 'GET';
-
- this.xhr.open(this.type,
- file + (BYPASS_CACHE ? '?' + (new Date()).getTime() : ''));
- this.xhr.responseType = 'arraybuffer';
-
- this.startTime = new Date().getTime();
- this.lastUpdate = this.startTime;
-
- if (start != null && length != null)
- this.xhr.setRequestHeader(
- 'Range', 'bytes=' + start + '-' + (start + length - 1));
-
- this.xhr.addEventListener('error', function(e) {
- if (self.xhr.status === 404)
- Alert('Failed to find "' + file +
- '" with error 404. Is it on the server?');
- manager.requestFinished(self);
- logger.log('XHR error with code', self.xhr.status);
- self.open();
- self.send();
- });
-
- this.xhr.addEventListener('timeout', function(e) {
- manager.requestFinished(self);
- logger.log('XHR timeout');
- self.open();
- self.send();
- });
-
- this.xhr.addEventListener('load', function(e) {
- manager.requestFinished(self);
- return self.onload(e);
- });
-
- this.xhr.addEventListener('progress', function onProgress(e) {
- if (e.lengthComputable && (e.loaded === e.total)) {
- self.xhr.removeEventListener('progress', onProgress);
- }
- self.lastUpdate = new Date().getTime();
- });
- };
-
- this.getRawResponse = function() {
- if (this.xhr.status === 404)
- Alert('Failed to find "' + file +
- '" with error 404. Is it on the server?');
- logger.assert(this.xhr.status >= 200 && this.xhr.status < 300,
- 'XHR bad status: ' + this.xhr.status);
- return this.xhr.response;
- };
-
- this.getResponseData = function() {
- if (this.xhr.status === 404)
- Alert('Failed to find "' + file +
- '" with error 404. Is it on the server?');
- logger.assert(this.xhr.status >= 200 && this.xhr.status < 300,
- 'XHR bad status: ' + this.xhr.status);
- var result = new Uint8Array(this.xhr.response);
- if (length != null) {
- var rangeHeader = this.xhr.getResponseHeader('Content-Range');
- var lengthHeader = this.xhr.getResponseHeader('Content-Length');
- if (!rangeHeader && lengthHeader) {
- logger.assert(length <= lengthHeader,
- 'Length of response is smaller than request');
- result = result.subarray(start, start + length);
- logger.checkEq(result.length, length, 'XHR length');
- return result;
- }
- logger.checkEq(result.length, length, 'XHR length');
- }
- return result;
- };
-
- this.send = function(postData) {
- manager.addRequest(this);
- if (postData) {
- logger.checkEq(this.type, 'POST', 'XHR requestType');
- this.xhr.send(postData);
- } else {
- logger.checkEq(this.type, 'GET', 'XHR requestType');
- this.xhr.send();
- }
- };
-
- this.abort = function() {
- this.xhr.abort();
- };
-
- this.open();
-};
-
-var XHRManager = function(logger) {
- var requests = [];
- this.totalRequestDuration = 0;
-
- this.addRequest = function(request) {
- logger.checkEq(requests.indexOf(request), -1, 'request index');
- requests.push(request);
- };
-
- this.requestFinished = function(request) {
- var currentTime = new Date().getTime();
- this.totalRequestDuration += currentTime - request.startTime;
- logger.checkNE(requests.indexOf(request), -1, 'request index');
- requests.splice(requests.indexOf(request), 1);
- };
-
- this.abortAll = function() {
- for (var i = 0; i < requests.length; ++i)
- requests[i].abort();
- requests = [];
- };
-
- this.createRequest = function(file, onload, start, length) {
- return new Request(this, logger, file, onload, null, start, length);
- };
-
- this.createPostRequest = function(file, onload, postLength, start, length) {
- return new Request(this, logger, file, onload, postLength, start, length);
- };
-
- this.hasActiveRequests = function() {
- if (requests.length > 0) {
- return true;
- }
- return false;
- }
-
- this.getLastUpdate = function() {
- if (requests.length == 0) {
- return null;
- }
-
- var latestUpdate = 0;
- for (var i in requests) {
- latestUpdate = Math.max(requests[i].lastUpdate, latestUpdate);
- }
- return latestUpdate;
- };
-};
-
-window.createXHRManager = function(logger) {
- return new XHRManager(logger);
-};
-
-})();
-
-// js/harness/xhr-20150612143746.js end
-
-// js/harness/timeout-20150612143746.js begin
-
-(function() {
-
-var TimeoutManager = function(logger) {
- var timers = [];
- var intervals = [];
-
- var getUniqueItem = function(container) {
- var id = 0;
- while (typeof(container[id]) != 'undefined')
- ++id;
- container[id] = {id: id};
- return container[id];
- };
-
- var timeoutHandler = function(id) {
- if (timers[id]) {
- var func = timers[id].func;
- delete timers[id];
- func();
- }
- };
-
- var intervalHandler = function(id) {
- var func = intervals[id].func;
- func();
- };
-
- this.setTimeout = function(func, timeout) {
- var timer = getUniqueItem(timers);
- timer.func = func;
- var id = window.setTimeout(timeoutHandler.bind(this, timer.id), timeout);
- };
-
- this.setInterval = function(func, timeout) {
- var interval = getUniqueItem(intervals);
- interval.func = func;
- interval.id = window.setInterval(intervalHandler, timeout, interval.id);
- };
-
- this.clearAll = function() {
- for (var id = 0; id < timers.length; ++id)
- if (typeof(timers[id]) != 'undefined')
- window.clearTimeout(timers[id].id);
- timers = [];
-
- for (var id = 0; id < intervals.length; ++id)
- if (typeof(intervals[id]) != 'undefined')
- window.clearInterval(intervals[id].id);
- intervals = [];
- };
-};
-
-window.createTimeoutManager = function(logger) {
- return new TimeoutManager(logger);
-};
-
-})();
-
-// js/harness/timeout-20150612143746.js end
-
-// js/harness/testView-20150612143746.js begin
-var TestView = (function() {
-
-var createElement = util.createElement;
-
-var createAnchor = function(text, id) {
- return util.createElement('span', id, 'rightmargin20', text);
-};
-
-var createOption = function(text, value) {
- var option = document.createElement('option');
- option.text = text;
- option.value = value;
- return option;
-};
-
-function TestView(mseSpec) {
- this.mseSpec = mseSpec;
- this.testList = null;
-
- var selectors = [];
- var switches = [];
- var commands = [];
- var testSuites = [];
- var links = [];
-
- this.addSelector = function(text, optionTexts, values, callback) {
- optionTexts = optionTexts instanceof Array ? optionTexts : [optionTexts];
- values = values instanceof Array ? values : [values];
-
- if (optionTexts.length !== values.length)
- throw "text length and value length don't match!";
-
- selectors.push({
- 'text': text,
- 'optionTexts': optionTexts,
- 'values': values,
- 'cb': callback
- })
- };
-
- this.addSwitch = function(text, id) {
- switches.push({text: text, id: id});
- };
-
- this.addCommand = function(text, id, title, onclick) {
- commands.push({text: text, id: id, title: title, onclick: onclick});
- };
-
- this.addTestSuite = function(text, href) {
- testSuites.push({text: text, href: href});
- };
-
- this.addTestSuites = function(testTypes) {
- var isTestTypeSupported = function(testType) {
- var supported = testTypes[testType].supported;
- if (typeof supported === 'string' && supported == 'all') {
- return true;
- } else if (typeof supported === 'object') {
- for (var index in supported) {
- if (supported[index] == mseSpec) {
- return true;
- }
- }
- }
- return false;
- };
-
- for (var testType in testTypes) {
- if (testType !== currentTestType && isTestTypeSupported(testType)) {
- this.addTestSuite(testTypes[testType].name, '?test_type=' + testType);
- }
- }
- }
-
- this.addLink = function(text, href) {
- links.push({text: text, href: href});
- };
-
- this.generate = function() {
- var heading = '[' + this.mseSpec + '] ' +
- testTypes[currentTestType].heading + ' (v 20150612143746-4K5xqupUzgiRyTYP)';
- document.title = testTypes[currentTestType].title;
- document.body.appendChild(createElement('div', 'title', null, heading));
- document.body.appendChild(createElement('div', 'info'));
- document.body.appendChild(createElement('div', 'usage'));
- document.body.appendChild(createElement('div', 'testview'));
-
- var div = document.getElementById(this.divId);
- div.innerHTML = '';
- div.appendChild(createElement('div', 'testsuites', 'container'));
- div.appendChild(createElement('div', 'switches', 'container'));
- div.appendChild(createElement('div', 'controls', 'container'));
-
- var testContainer = createElement('div', null, 'container');
- testContainer.appendChild(createElement('div', 'testlist', 'box-left'));
- testContainer.appendChild(createElement('div', 'testarea'));
- div.appendChild(testContainer);
-
- var outputArea = createElement('div', 'outputarea');
- var textArea = createElement('div', 'output');
- textArea.rows = 10;
- textArea.cols = 80;
- outputArea.appendChild(textArea);
- div.appendChild(outputArea);
-
- var switchDiv = document.getElementById('switches');
- for (var i = 0; i < switches.length; ++i) {
- var id = switches[i].id;
- switchDiv.appendChild(document.createTextNode(switches[i].text));
- switchDiv.appendChild(createAnchor(window[id] ? 'on' : 'off', id));
- switchDiv.lastChild.href = 'javascript:;';
- switchDiv.lastChild.onclick = (function(id) {
- return function(e) {
- var wasOff = e.target.innerHTML === 'off';
- e.target.innerHTML = wasOff ? 'on' : 'off';
- window[id] = wasOff;
- };
- })(id);
- }
- for (var i = 0; i < selectors.length; ++i) {
- switchDiv.appendChild(document.createTextNode(selectors[i].text));
- var select = document.createElement('select');
- for (var j = 0; j < selectors[i].optionTexts.length; ++j) {
- select.appendChild(createOption(selectors[i].optionTexts[j],
- selectors[i].values[j]));
- }
- select.onchange = selectors[i].cb;
- switchDiv.appendChild(select);
- }
-
- switchDiv.appendChild(
- createElement('span', 'finish-count', null, '0 tests finished'));
-
- var controlsDiv = document.getElementById('controls');
- for (var i = 0; i < commands.length; ++i) {
- controlsDiv.appendChild(createAnchor(commands[i].text, commands[i].id));
- controlsDiv.lastChild.href = 'javascript:;';
- controlsDiv.lastChild.onclick = commands[i].onclick;
- controlsDiv.lastChild.title = commands[i].title;
- }
-
- for (var i = 0; i < links.length; ++i) {
- controlsDiv.appendChild(createAnchor(links[i].text));
- controlsDiv.lastChild.href = links[i].href;
- }
-
- var testSuitesDiv = document.getElementById('testsuites');
- for (var i = 0; i < testSuites.length; ++i) {
- testSuitesDiv.appendChild(createAnchor(testSuites[i].text));
- testSuitesDiv.lastChild.href = testSuites[i].href;
- }
-
- this.testList.generate(document.getElementById('testlist'));
- };
-
- this.addTest = function(desc) {
- return this.testList.addTest(desc);
- };
-
- this.anySelected = function() {
- return this.testList.anySelected();
- };
-};
-
-return {
- create: function(mseSpec) {
- return new TestView(mseSpec);
- }};
-
-})();
-
-// js/harness/testView-20150612143746.js end
-
-// fullTestList-20150612143746.js begin
-
-(function() {
-
-var createElement = util.createElement;
-
-function Test(desc, fields) {
- var INDEX = 0;
- var STATUS = INDEX + 1;
- var DESC = STATUS + 1;
- var FIELD = DESC + 1;
- this.index = desc.index;
- this.id = 'test-row' + this.index;
- this.desc = desc;
- this.steps = [];
-
- this.createElement = function(element) {
- element.id = this.id;
- element.appendChild(createElement('td', null, 'index',
- this.index + 1 + '.'));
- element.appendChild(
- createElement('td', null, 'status',
- '<input type="checkbox" checked="yes"></input> >'));
- element.appendChild(createElement('td', null, 'desc'));
-
- for (var field = 0; field < fields.length; ++field)
- element.appendChild(createElement('td', null, 'state', 0));
-
- var link = createElement('span', null, null, desc.desc);
- link.href = 'javascript:;';
- link.onclick = desc.onclick;
- link.title = desc.title;
- element.childNodes[DESC].appendChild(link);
- var explanationPoint = createElement('span', null, 'desc-expl-point', '?');
- var explanation = createElement(
- 'span', null, 'desc-expl-popup', desc.title);
- explanationPoint.appendChild(explanation);
- element.childNodes[DESC].appendChild(explanationPoint);
- };
-
- this.addStep = function(name) {
- var tr = createElement('tr');
- tr.appendChild(createElement('td', null, 'small'));
- tr.appendChild(createElement('td', null, 'small'));
- tr.appendChild(createElement('td', null, 'small',
- this.steps.length + 1 + '. ' + name));
- for (var field = 0; field < fields.length; ++field)
- tr.appendChild(createElement('td', null, 'small', 0));
-
- var element = document.getElementById(this.id);
- if (this.steps.length !== 0)
- element = this.steps[this.steps.length - 1];
- if (element.nextSibling)
- element.parentNode.insertBefore(tr, element.nextSibling);
- else
- element.parentNode.appendChild(tr);
- this.steps.push(tr);
- };
-
- this.updateStatus = function() {
- var element = document.getElementById(this.id);
- element.childNodes[STATUS].className =
- this.desc.running ? 'status_current' : 'status';
- for (var field = 0; field < fields.length; ++field)
- element.childNodes[FIELD + field].innerHTML =
- this.desc[fields[field].replace(' ', '_')];
- };
-
- this.selected = function() {
- var element = document.getElementById(this.id);
- return element.childNodes[STATUS].childNodes[0].checked;
- };
-
- this.select = function() {
- var element = document.getElementById(this.id);
- element.childNodes[STATUS].childNodes[0].checked = true;
- };
-
- this.deselect = function() {
- var element = document.getElementById(this.id);
- element.childNodes[STATUS].childNodes[0].checked = false;
- };
-}
-
-function TestList(fields) {
- var tableId = 'test-list-table';
- var headId = tableId + '-head';
- var bodyId = tableId + '-body';
- var tests = [];
-
- if (!fields || !fields.length)
- throw 'No test fields';
-
- this.addColumnHeader = function(class_, text) {
- var head = document.getElementById(headId);
- var th = createElement('th', null, class_, text);
- th.scope = 'col';
- head.appendChild(th);
- };
-
- this.addTest = function(desc) {
- var test = new Test(desc, fields);
- tests.push(test);
- return test;
- };
-
- this.generate = function(div) {
- var table = document.createElement('table');
- table.id = tableId;
- div.appendChild(table);
-
- var thead = createElement('thead');
- table.classList.add('test-table');
- table.innerHTML = '';
- var head = createElement('tr');
- var body = createElement('tbody');
-
- head.id = headId;
- body.id = bodyId;
- thead.appendChild(head);
- table.appendChild(thead);
- table.appendChild(body);
-
- this.addColumnHeader('index');
- this.addColumnHeader('status');
- this.addColumnHeader('desc', 'Test');
-
- for (var i = 0; i < fields.length; ++i)
- this.addColumnHeader('state', util.MakeCapitalName(fields[i]));
-
- for (var i = 0; i < tests.length; ++i) {
- var tr = createElement('tr');
- body.appendChild(tr);
- tests[i].createElement(tr);
- tests[i].updateStatus();
- }
- };
-
- this.getTest = function(index) {
- return tests[index];
- };
-
- this.anySelected = function() {
- for (var i = 0; i < tests.length; ++i)
- if (tests[i].selected())
- return true;
- return false;
- };
-
- this.selectAll = function() {
- for (var i = 0; i < tests.length; ++i)
- tests[i].select();
- };
-
- this.deselectAll = function() {
- for (var i = 0; i < tests.length; ++i)
- tests[i].deselect();
- };
-};
-
-window.createFullTestList = function(fields) {
- return new TestList(fields);
-};
-
-})();
-// fullTestList-20150612143746.js end
-
-// js/harness/fullTestView-20150612143746.js begin
-
-var fullTestView = (function() {
-
-function FullTestView(fields) {
- var self = this;
- this.divId = 'testview';
- this.testCount = 0;
-
- this.initialize = function() {
- this.testList = createFullTestList(fields);
-
- this.addSwitch('Loop: ', 'loop');
- this.addSwitch('Stop on failure: ', 'stoponfailure');
- this.addSwitch('Log: ', 'logging');
- this.addSwitch('WebM/VP9 (2015/tip only): ', 'enablewebm');
-
- this.addCommand('Select All', 'select-all', 'Select all tests.',
- this.testList.selectAll.bind(this.testList));
- this.addCommand('Deselect All', 'deselect-all', 'Deselect all tests.',
- this.testList.deselectAll.bind(this.testList));
- this.addCommand('Run Selected', 'run-selected',
- 'Run all selected tests in order.',
- function(e) {
- if (self.onrunselected)
- self.onrunselected.call(self, e);
- });
-
- this.addLink('Links', 'links.html');
- this.addLink('Instructions', 'instructions.html');
- this.addLink('Changelog', 'main.html');
- this.addLink('Download', 'download-20150612143746.tar.gz');
-
- this.addTestSuites(testTypes);
- };
-
- this.addTest = function(desc) {
- return this.testList.addTest(desc);
- };
-
- this.generate = function() {
- FullTestView.prototype.generate.call(this);
- // document.getElementById('run-selected').focus();
- };
-
- this.getTest = function(index) {
- return this.testList.getTest(index);
- };
-
- this.finishedOneTest = function() {
- ++this.testCount;
- document.getElementById('finish-count').innerHTML =
- this.testCount === 1 ? this.testCount + ' test finished' :
- this.testCount + ' tests finished';
- };
-
- this.anySelected = function() {
- return this.testList.anySelected();
- };
-
- this.initialize();
-};
-
-//FullTestView.prototype = TestView.create();
-//FullTestView.prototype.constructor = FullTestView;
-
-return {
- create: function(mseSpec, fields) {
- FullTestView.prototype = TestView.create(mseSpec);
- FullTestView.prototype.constructor = FullTestView;
- return new FullTestView(fields);
- }
-};
-
-})();
-
-// js/harness/fullTestView-20150612143746.js end
-
-// js/harness/compactTestList-20150612143746.js begin
-
-(function() {
-
-var ITEM_IN_COLUMN = 25; // Test item count in a column
-var CATEGORY_SPACE = 1; // Row between the end of the last category and the
- // beginning of the next category
-var MIN_ROW_AT_THE_BOTTOM = 2; // If at the bottom of the table and the row
- // count is less than this, start a new column.
-
-var createElement = util.createElement;
-
-function Category(categoryName) {
- this.setElement = function(nameCell, statusCell) {
- nameCell.className = 'cell-category';
- nameCell.innerText = categoryName;
- };
-
- this.setDoubleElement = function(cellElem) {
- cellElem.className = 'cell-category';
- cellElem.setAttribute('colspan', 2);
- cellElem.innerText = categoryName;
- };
-}
-
-function Test(desc, style) {
- var self = this;
- this.index = desc.index;
- this.nameId = 'test-item-name-' + this.index;
- this.statusId = 'test-item-status-' + this.index;
- this.desc = desc;
- this.steps = [];
- this.style = style;
-
- this.createElement = function(name, status) {
- name.id = this.nameId;
- status.id = this.statusId;
- var link = createElement('span', null, null,
- this.index + 1 + '. ' + this.desc.desc);
- link.href = 'javascript:;';
- link.onclick = desc.onclick;
- link.title = desc.title;
- name.appendChild(link);
- this.updateStatus(status);
- };
-
- this.updateStatus = function(status) {
- var text = this.desc.status;
- var failureStatus = '';
- if (text && text.length > 5) text = '';
- status = status ? status : document.getElementById(this.statusId);
-
- if (this.style === 'extra compact') {
- failureStatus = this.desc.mandatory ? 'test-status-fail' :
- 'test-status-optional-fail';
- if (this.desc.running) {
- status.className = 'test-status-running';
- } else if (this.desc.failures) {
- status.className = failureStatus;
- } else if (this.desc.timeouts) {
- status.className = failureStatus;
- } else if (this.desc.passes) {
- status.className = 'test-status-pass';
- } else {
- status.className = 'test-status-none';
- }
- } else {
- failureStatus = this.desc.mandatory ? 'cell-status-fail' :
- 'cell-status-normal';
- if (this.desc.running) {
- status.innerHTML = ' ... ';
- status.className = 'cell-status-running';
- } else if (this.desc.failures) {
- status.innerHTML = text || ' Fail ';
- status.className = failureStatus;
- } else if (this.desc.timeouts) {
- status.innerHTML = text || ' Fail ';
- status.className = failureStatus;
- } else if (this.desc.passes) {
- status.innerHTML = text || ' Pass ';
- status.className = 'cell-status-pass';
- } else {
- status.innerHTML = ' ';
- status.className = 'cell-status-normal';
- }
- }
- };
-
- this.selected = function() {
- return true;
- };
-
- this.getElement = function() {
- return document.getElementById(this.nameId).childNodes[0];
- };
-}
-
-function TestList(style) {
- var self = this;
- var tests = [];
-
- var SINGLE_WIDTH_CELL = 1;
- var DOUBLE_WIDTH_CELL = 2;
-
- this.style = style || '';
-
- // returns array [row, column]
- var getTableDimension = function() {
- var lastCategory = '';
- var cells = 0;
- var rowLeft;
-
- for (var i = 0; i < tests.length; ++i) {
- if (lastCategory !== tests[i].desc.category) {
- rowLeft = ITEM_IN_COLUMN - cells % ITEM_IN_COLUMN;
- if (rowLeft < MIN_ROW_AT_THE_BOTTOM)
- cells += rowLeft;
- if (cells % ITEM_IN_COLUMN !== 0)
- cells += CATEGORY_SPACE;
- cells++;
- lastCategory = tests[i].desc.category;
- } else if (cells % ITEM_IN_COLUMN === 0) {
- cells++; // category (continued)
- }
- cells++;
- }
-
- return [Math.min(cells, ITEM_IN_COLUMN),
- Math.floor((cells + ITEM_IN_COLUMN - 1) / ITEM_IN_COLUMN)];
- };
-
- var createExtraCompactTable = function(div, table) {
- var lastCategory = null;
- var totalCells = 0;
- var totalTests = 0;
- var rowsRemaining = 0;
- var layoutColumnSpan = [];
- var rows = [];
- var j = 0;
-
- var createEmptyCells = function(row) {
- if (table.childNodes.length <= row) {
- table.appendChild(createElement('tr'));
- }
-
- var tr = table.childNodes[row];
- var elems = [
- createElement('td', null, 'test-status-none'),
- createElement('td', null, 'cell-name', ' ')
- ];
- tr.appendChild(elems[0]);
- tr.appendChild(elems[1]);
- return elems;
- };
-
- var createTestCells = function(testIndex, row, test) {
- var cells = createEmptyCells(row);
- tests[testIndex].createElement(cells[1], cells[0]);
- };
-
- var createCategoryCell = function(row, categoryName) {
- if (table.childNodes.length <= row) {
- table.appendChild(createElement('tr'));
- }
-
- var tr = table.childNodes[row];
- var elem = createElement('td', null, 'cell-name', ' ');
- tr.appendChild(elem);
-
- (new Category(categoryName)).setDoubleElement(elem);
- };
-
- for (var i = 0; i < tests.length; ++i) {
- var currCategory = tests[i].desc.category;
-
- if (lastCategory !== currCategory) {
- rowsRemaining = ITEM_IN_COLUMN - totalCells % ITEM_IN_COLUMN;
-
- if (rowsRemaining < MIN_ROW_AT_THE_BOTTOM) {
- // Add a row for heading.
- for (j = 0; j < rowsRemaining; ++j) {
- createEmptyCells(totalCells % ITEM_IN_COLUMN);
- totalCells += 1;
- }
- }
-
- if (totalCells % ITEM_IN_COLUMN !== 0) {
- // Add a row for extra space before heading, if in middle of column.
- for (j = 0; j < CATEGORY_SPACE; ++j) {
- createEmptyCells(totalCells % ITEM_IN_COLUMN);
- totalCells += 1;
- }
- }
-
- lastCategory = currCategory;
- createCategoryCell(totalCells % ITEM_IN_COLUMN, lastCategory);
- totalCells++;
- } else if (totalCells % ITEM_IN_COLUMN === 0) {
- // category (continued)
- createCategoryCell(totalCells % ITEM_IN_COLUMN, lastCategory);
- totalCells++;
- }
-
- createTestCells(totalTests, totalCells % ITEM_IN_COLUMN, lastCategory);
- totalCells++;
- totalTests++;
- }
-
- div.innerHTML = '';
- div.appendChild(table);
- };
-
- this.addTest = function(desc) {
- var test = new Test(desc, this.style);
- tests.push(test);
- return test;
- };
-
- this.generate = function(div) {
- var table = createElement('div', null, 'compact-list');
- var tr;
- var dim = getTableDimension();
- var lastCategory = '';
- var row;
- var column;
-
- if (self.style === 'extra compact') {
- createExtraCompactTable(div, table);
- } else {
- for (row = 0; row < dim[0]; ++row) {
- tr = createElement('div');
- table.appendChild(tr);
- for (column = 0; column < dim[1]; ++column) {
- tr.appendChild(createElement('span', null, 'cell-name', ' '));
- tr.appendChild(createElement('span', null, 'cell-divider'));
- tr.appendChild(createElement('span', null, 'cell-status-normal'));
- }
- }
-
- div.innerHTML = '';
- div.appendChild(table);
-
- row = column = 0;
-
- for (var i = 0; i < tests.length; ++i) {
- if (lastCategory !== tests[i].desc.category) {
- if (ITEM_IN_COLUMN - row <= MIN_ROW_AT_THE_BOTTOM) {
- row = 0;
- column++;
- }
-
- if (row % ITEM_IN_COLUMN !== 0)
- row += CATEGORY_SPACE;
-
- lastCategory = tests[i].desc.category;
- (new Category(lastCategory)).setElement(
- table.childNodes[row].childNodes[column * 3],
- table.childNodes[row].childNodes[column * 3 + 2]);
- row++;
- } else if (row === 0) {
- (new Category(lastCategory)).setElement(
- table.childNodes[row].childNodes[column * 3],
- table.childNodes[row].childNodes[column * 3 + 2]);
- row++;
- }
-
- tests[i].createElement(
- table.childNodes[row].childNodes[column * 3],
- table.childNodes[row].childNodes[column * 3 + 2]);
- row++;
-
- if (row === ITEM_IN_COLUMN) {
- row = 0;
- column++;
- }
- }
- }
- };
-
- this.getTest = function(index) {
- return tests[index];
- };
-
- this.anySelected = function() {
- return tests.length !== 0;
- };
-};
-
-window.createCompactTestList = function(style) {
- return new TestList(style);
-};
-
-})();
-
-// js/harness/compactTestList-20150612143746.js end
-
-// js/harness/compactTestView-20150612143746.js begin
-
-var compactTestView = (function() {
-
-function CompactTestView(fields, style) {
- var self = this;
- this.divId = 'testview';
- this.testCount = 0;
-
- this.initialize = function() {
- this.testList = createCompactTestList(style);
-
- this.addSwitch('Loop: ', 'loop');
- this.addSwitch('Stop on failure: ', 'stoponfailure');
- this.addSwitch('Log: ', 'logging');
- this.addSwitch('WebM/VP9 (2015/tip only): ', 'enablewebm');
-
- this.addCommand('Run All', 'run-selected', 'Run all tests in order.',
- function(e) {
- if (self.onrunselected)
- self.onrunselected.call(self, e);
- });
-
- this.addLink('Links', 'links.html');
- this.addLink('Instructions', 'instructions.html');
- this.addLink('Changelog', 'main.html');
- this.addLink('Download', 'download-20150612143746.tar.gz');
-
- this.addTestSuites(testTypes);
- };
-
- this.addTest = function(desc) {
- return this.testList.addTest(desc);
- };
-
- this.generate = function() {
- CompactTestView.prototype.generate.call(this);
- // document.getElementById('run-selected').focus();
-
- var USAGE = 'Use ↑↓→← to move around, ' +
- 'use ENTER to select.';
- document.getElementById('usage').innerHTML = USAGE;
- // document.getElementById('run-selected').focus();
- };
-
- this.getTest = function(index) {
- return this.testList.getTest(index);
- };
-
- this.finishedOneTest = function() {
- ++this.testCount;
- document.getElementById('finish-count').innerHTML =
- this.testCount === 1 ? this.testCount + ' test finished' :
- this.testCount + ' tests finished';
- };
-
- this.anySelected = function() {
- return this.testList.anySelected();
- };
-
- this.initialize();
-};
-
-CompactTestView.prototype = TestView.create();
-CompactTestView.prototype.constructor = CompactTestView;
-
-return {
- create: function(mseSpec, fields, style) {
- CompactTestView.prototype = TestView.create(mseSpec);
- CompactTestView.prototype.constructor = CompactTestView;
- return new CompactTestView(fields, style);
- }
-};
-
-})();
-
-// js/harness/compactTestView-20150612143746.js end
-
-// js/lib/mse/2013/mediaSourcePortability-20150612143746.js begin
-
-/*
- * without Webkit prefix and MediaSource ver 0.5 with or without Webkit prefix.
- */
-function setupMsePortability() {
- var dlog = function() {
- var forward = window.dlog || console.log.bind(console);
- forward.apply(this, arguments);
- };
-
- // Check if we have MSE 0.6 WITHOUT webkit prefix
- if (window.MediaSource) {
- window.MediaSource.prototype.version = 'MSE-live';
- return;
- }
-
- // Check if we have MSE 0.6 WITH webkit prefix
- if (window.WebKitMediaSource) {
- window.MediaSource = window.WebKitMediaSource;
- window.MediaSource.prototype.version = 'MSE-live-webkit';
-
- var cou = window.URL.createObjectURL;
- var creatingURL = false;
- window.URL.createObjectURL = function(obj) {
- if (!creatingURL) {
- creatingURL = true;
- var url = window.URL.createObjectURL(obj);
- creatingURL = false;
- return url;
- }
- return cou.call(this, obj);
- };
-
- var ael = window.MediaSource.prototype.addEventListener;
- window.MediaSource.prototype.addEventListener = function(
- type, listener, useCaptures) {
- var re = /^source(open|close|ended)$/;
- var match = re.exec(type);
- if (match) {
- ael.call(this, 'webkit' + type, listener, useCaptures);
- } else {
- ael.call(this, type, listener, useCaptures);
- }
- };
-
- return;
- }
-
- var v = document.createElement('video');
-
- // Do we have any forms of MSE 0.5?
- // NOTE: We will only support MSE 0.5 with webkit prefix.
- if (!v.webkitSourceAddId)
- return;
-
- function MediaSource() {
- this.sourceBuffers = [];
- this.activeSourceBuffers = this.sourceBuffers;
- this.readyState = 'closed';
-
- this.msWrapperVideo = null;
- this.msWrapperDuration = NaN;
- this.msWrapperSourceIdCount = 1;
- this.msWrapperHandlers = {};
- this.msWrapperAppended = false;
-
- this.isWrapper = true;
- }
-
- MediaSource.prototype.version = 'MSE-v0.5-wrapped-webkit';
- var missingFeature = 'Missing:';
- if (!v.webkitSourceSetDuration)
- missingFeature += ' webkitSourceSetDuration';
- if (!v.webkitSourceTimestampOffset)
- missingFeature += ' webkitSourceTimestampOffset';
- if (missingFeature !== 'Missing:')
- MediaSource.prototype.version += '(' + missingFeature + ')';
-
- MediaSource.prototype.msWrapperHandler = function(name, evt) {
- var handlers = this.msWrapperHandlers[name] || [];
- dlog(4, 'In msWrapperHandler');
- if (name === 'close') {
- this.readyState = 'closed';
- this.msWrapperDuration = NaN;
- } else {
- this.readyState = name;
- }
- for (var i = 0; i < handlers.length; i++) {
- handlers[i].call(evt, evt);
- }
- };
-
- MediaSource.prototype.attachTo = function(video) {
- dlog(4, 'In msWrapperAttach');
- var names = ['open', 'close', 'ended'];
- for (var i = 0; i < names.length; i++) {
- var h = this.msWrapperHandler.bind(this, names[i]);
- video.addEventListener('webkitsource' + names[i], h);
- }
- this.msWrapperVideo = video;
- var self = this;
- video.addEventListener('durationchange', function() {
- LOG(video.duration);
- self.msWrapperDuration = video.duration;
- });
- video.src = video.webkitMediaSourceURL;
- };
-
- MediaSource.prototype.addSourceBuffer = function(type) {
- if (!this.msWrapperVideo) throw 'Unattached';
- var id = '' + this.msWrapperSourceIdCount;
- this.msWrapperSourceIdCount += 1;
- this.msWrapperVideo.webkitSourceAddId(id, type);
-
- var buf = new SourceBuffer(this.msWrapperVideo, id);
- this.sourceBuffers.push(buf);
- return buf;
- };
-
- MediaSource.prototype.removeSourceBuffer = function(buf) {
- for (var i = 0; i < this.sourceBuffers.length; ++i) {
- if (buf === this.sourceBuffers[i]) {
- this.msWrapperVideo.webkitSourceRemoveId(buf.msWrapperSourceId);
- delete this.sourceBuffers.splice(i, 1)[0];
- break;
- }
- }
- };
-
- MediaSource.prototype.endOfStream = function(opt_error) {
- var v = this.msWrapperVideo;
-
- // TODO: are these prefixed in M21?
- var err;
- if (opt_error === 'network') {
- err = v.EOS_NETWORK_ERR;
- } else if (opt_error === 'decode') {
- err = v.EOS_DECODE_ERR;
- } else if (!opt_error) {
- err = v.EOS_NO_ERROR;
- } else {
- throw 'Unrecognized endOfStream error type: ' + opt_error;
- }
-
- v.webkitSourceEndOfStream(err);
- };
-
- // The 'setDuration' method of the media element is an extension to the
- // MSE-v0.5 spec, which will be implemented on some devices.
- // Calling this method is defined to have the same semantics as setting
- // the duration property of the Media Source object in the current spec.
- // Getting it is undefined, although clearly here we just return the last
- // value that we set.
- Object.defineProperty(MediaSource.prototype, 'duration', {
- get: function() {
- if (this.readyState === 'closed')
- return NaN;
- return this.msWrapperDuration;
- },
- set: function(duration) {
- this.msWrapperDuration = duration;
- if (this.msWrapperVideo.webkitSourceSetDuration) {
- this.msWrapperVideo.webkitSourceSetDuration(duration);
- } else {
- dlog(1, 'webkitSourceSetDuration() missing (ignored)');
- }
- }
- });
-
- MediaSource.prototype.addEventListener = function(name, handler) {
- var re = /^source(open|close|ended)$/;
- var match = re.exec(name);
- if (match && match[1]) {
- name = match[1];
- var l = this.msWrapperHandlers[name] || [];
- l.push(handler);
- this.msWrapperHandlers[name] = l;
- } else {
- throw 'Unrecognized event name: ' + name;
- }
- };
-
- function SourceBuffer(video, id) {
- this.msWrapperVideo = video;
- this.msWrapperSourceId = id;
- this.msWrapperTimestampOffset = 0;
- }
-
- function FakeSourceBufferedRanges() {
- this.length = 0;
- }
-
- SourceBuffer.prototype.msWrapperGetBuffered = function() {
- dlog(4, 'In msWrapperGetBuffered');
-
- // Chrome 22 doesn't like calling sourceBuffered() before initialization
- // segment gets appended.
- if (!this.msWrapperAppended) return new FakeSourceBufferedRanges();
-
- var v = this.msWrapperVideo;
- var id = this.msWrapperSourceId;
- return v.webkitSourceBuffered(id);
- };
-
- SourceBuffer.prototype.append = function(bytes) {
- dlog(4, 'In append');
- var v = this.msWrapperVideo;
- var id = this.msWrapperSourceId;
- v.webkitSourceAppend(id, bytes);
- this.msWrapperAppended = true;
- };
-
- SourceBuffer.prototype.abort = function() {
- dlog(4, 'In abort');
- var v = this.msWrapperVideo;
- var id = this.msWrapperSourceId;
- v.webkitSourceAbort(id);
- };
-
- // The 'setTimestampOffset' method of the media element is an extension to the
- // MSE-v0.5 spec, which will be implemented on some devices.
- // Calling this method is defined to have the same semantics as setting the
- // timestampOffset property of the Media Source object in the current spec.
- Object.defineProperty(SourceBuffer.prototype, 'timestampOffset', {
- get: function() { return this.msWrapperTimestampOffset; },
- set: function(o) {
- this.msWrapperTimestampOffset = o;
- if (this.msWrapperVideo.webkitSourceTimestampOffset) {
- this.msWrapperVideo.webkitSourceTimestampOffset(
- this.msWrapperSourceId, o);
- } else {
- dlog(1, 'webkitSourceTimestampOffset() missing (ignored)');
- }
- }
- });
-
- Object.defineProperty(SourceBuffer.prototype, 'buffered', {
- get: SourceBuffer.prototype.msWrapperGetBuffered
- });
-
- window.MediaSource = MediaSource;
-}
-
-// js/lib/mse/2013/mediaSourcePortability-20150612143746.js end
-
-// js/lib/eme/encryptedMediaPortability-20150612143746.js begin
-/* The code tries to wrapper the EME versions with or without webkit prefix */
-
-
-function prefixedAttributeName(obj, suffix, opt_preprefix) {
- suffix = suffix.toLowerCase();
- if (opt_preprefix === undefined) {
- opt_preprefix = '';
- }
- for (var attr in obj) {
- var lattr = attr.toLowerCase();
- if (lattr.indexOf(opt_preprefix) == 0 &&
- lattr.indexOf(suffix, lattr.length - suffix.length) != -1) {
- return attr;
- }
- }
- return null;
-}
-
-function normalizeAttribute(obj, suffix, opt_preprefix) {
- if (opt_preprefix === undefined) {
- opt_preprefix = '';
- }
-
- var attr = prefixedAttributeName(obj, suffix, opt_preprefix);
- if (attr) {
- obj[opt_preprefix + suffix] = obj[attr];
- }
-}
-
-function stringToArray(s) {
- var array = new Uint8Array(new ArrayBuffer(s.length));
- for (var i = 0; i < s.length; i++) {
- array[i] = s.charCodeAt(i);
- }
- return array;
-}
-
-
-function arrayToString(a) {
- return String.fromCharCode.apply(String, a);
-}
-
-function parsePlayReadyKeyMessage(message) {
- // message is UTF-16LE, let's throw out every second element.
- var message_ascii = new Array();
- for (var i = 0; i < message.length; i += 2) {
- message_ascii.push(message[i]);
- }
-
- var str = String.fromCharCode.apply(String, message_ascii);
- var doc = (new DOMParser()).parseFromString(str, 'text/xml');
- return stringToArray(
- atob(doc.childNodes[0].childNodes[0].childNodes[0].childNodes[0].data));
-}
-
-/**
- * Extracts the BMFF Clear Key ID from the init data of the segment.
- * @param {ArrayBuffer} initData Init data for the media segment.
- * @return {ArrayBuffer} Returns the BMFF ClearKey ID if found, otherwise the
- * initData itself.
- */
-function extractBMFFClearKeyID(initData) {
- // Accessing the Uint8Array's underlying ArrayBuffer is impossible, so we
- // copy it to a new one for parsing.
- var abuf = new ArrayBuffer(initData.length);
- var view = new Uint8Array(abuf);
- view.set(initData);
-
- var dv = new DataView(abuf);
- var pos = 0;
- while (pos < abuf.byteLength) {
- var box_size = dv.getUint32(pos, false);
- var type = dv.getUint32(pos + 4, false);
-
- if (type != 0x70737368)
- throw 'Box type ' + type.toString(16) + ' not equal to "pssh"';
-
- // Scan for Clear Key header
- if ((dv.getUint32(pos + 12, false) == 0x58147ec8) &&
- (dv.getUint32(pos + 16, false) == 0x04234659) &&
- (dv.getUint32(pos + 20, false) == 0x92e6f52c) &&
- (dv.getUint32(pos + 24, false) == 0x5ce8c3cc)) {
- var size = dv.getUint32(pos + 28, false);
- if (size != 16) throw 'Unexpected KID size ' + size;
- return new Uint8Array(abuf.slice(pos + 32, pos + 32 + size));
- }
-
- // Failing that, scan for Widevine protobuf header
- if ((dv.getUint32(pos + 12, false) == 0xedef8ba9) &&
- (dv.getUint32(pos + 16, false) == 0x79d64ace) &&
- (dv.getUint32(pos + 20, false) == 0xa3c827dc) &&
- (dv.getUint32(pos + 24, false) == 0xd51d21ed)) {
- return new Uint8Array(abuf.slice(pos + 36, pos + 52));
- }
- pos += box_size;
- }
- // Couldn't find it, give up hope.
- return initData;
-}
-
-function base64_encode(arr) {
- var b64dict = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
- var b64pad = "=";
-
- var i;
- var hexStr = "";
- for (i = 0; i < arr.length; i++) {
- var hex = arr[i].toString(16);
- hexStr += hex.length == 1 ? "0" + hex : hex;
- }
-
- var dest = "";
- var c;
- for (i = 0; i+3 <= hexStr.length; i+=3) {
- c = parseInt(hexStr.substring(i, i+3), 16);
- dest += b64dict.charAt(c >> 6) + b64dict.charAt(c & 63);
- }
- if (i+1 == hexStr.length) {
- c = parseInt(hexStr.substring(i, i+1), 16);
- dest += b64dict.charAt(c << 2);
- } else if (i+2 == hexStr.length) {
- c = parseInt(hexStr.substring(i, i+2), 16);
- dest += b64dict.charAt(c >> 2) + b64dict.charAt((c & 3) << 4);
- }
-
- while ((dest.length & 3) > 0) {
- dest += b64pad;
- }
-
- return dest;
-}
-
-function b64tob64url(s) {
- var b64urlStr = removePadding(s);
- b64urlStr = b64urlStr.replace(/\+/g, "-");
- b64urlStr = b64urlStr.replace(/\//g, "_");
- return b64urlStr;
-}
-
-function removePadding(s) {
- return s.replace(/\=/g, "");
-}
-
-function base64NoPadding_encode(arr) {
- return removePadding(base64_encode(arr));
-}
-
-function base64url_encode(arr) {
- return b64tob64url(base64_encode(arr));
-}
-
-(function() {
- var dlog = function() {
- var forward = window.dlog || console.log.bind(console);
- forward.apply(this, arguments);
- };
-
- var proto = window.HTMLMediaElement.prototype;
-
- if (proto.addKey || proto.setMediaKeys) {
- return; // Non-prefix version, needn't wrap.
- }
-
- if (!proto.webkitAddKey) {
- dlog(1, 'EME is not available');
- return; // EME is not available at all.
- }
-
- proto.generateKeyRequest = function(keySystem, initData) {
- return proto.webkitGenerateKeyRequest.call(this, keySystem, initData);
- };
-
- proto.addKey = function(keySystem, key, initData, sessionId) {
- return proto.webkitAddKey.call(this, keySystem, key, initData, sessionId);
- };
-
- proto.cancelKeyRequest = function(keySystem, sessionId) {
- return proto.webkitCancelKeyRequest.call(this, keySystem, sessionId);
- };
-
- var ael = proto.addEventListener;
- var eventWrapper = function(listener, e) {
- listener.call(this, e);
- };
-
- proto.addEventListener = function(type, listener, useCaptures) {
- var re = /^(keyadded|keyerror|keymessage|needkey)$/;
- var match = re.exec(type);
- if (match) {
- ael.call(this, 'webkit' + type, eventWrapper.bind(this, listener),
- useCaptures);
- } else {
- ael.call(this, type, listener, useCaptures);
- }
- };
-})();
-
-// js/lib/eme/encryptedMediaPortability-20150612143746.js end
-
-// js/harness/test-20150612143746.js begin
-
-
-var BYPASS_CACHE = false;
-var XHR_TIMEOUT_LIMIT = 5000;
-
-(function() {
-
-window.testTypes = {
- 'conformance-test': {
- name: 'MSE Conformance Tests',
- supported: 'all',
- title: 'Media Source and Encrypted Media Conformance Tests',
- heading: 'MSE Conformance Tests'
- },
- 'encryptedmedia-test': {
- name: 'EME Conformance Tests',
- supported: ['2015', 'tip'],
- title: 'Encrypted Media Extensions Conformance Tests',
- heading: 'EME Conformance Tests'
- },
- 'performance-test': {
- name: 'MSE Performance Tests',
- supported: 'all',
- title: 'Media Source and Encrypted Media Conformance Tests',
- heading: 'MSE Performance Tests'
- },
- 'endurance-test': {
- name: 'MSE Endurance Tests',
- supported: 'all',
- title: 'Media Source and Encrypted Media Conformance Tests',
- heading: 'MSE Endurance Tests'
- },
- 'progressive-test': {
- name: 'Progressive Tests',
- supported: 'all',
- title: 'HTML Media Element Conformance Tests',
- heading: 'HTML Media Element Conformance Tests'
- }
-};
-
-window.kDefaultTestType = 'conformance-test';
-
-window.currentTestType = null;
-window.recycleVideoTag = true;
-
-if (!window.LOG) {
- window.LOG = console.log.bind(console);
-}
-
-var TestBase = {};
-
-TestBase.onsourceopen = function() {
- this.log('default onsourceopen()');
-};
-
-TestBase.start = function(runner, video) {
- this.log('Test started');
-};
-
-TestBase.teardown = function(mseSpec) {
- if (this.video != null) {
- this.video.removeAllEventListeners();
- this.video.pause();
- if (mseSpec && mseSpec !== '0.5') // For backwards compatibility.
- window.URL.revokeObjectURL(this.video.src);
- this.video.src = '';
- if (recycleVideoTag)
- this.video.parentNode.removeChild(this.video);
- }
- this.ms = null;
- this.video = null;
- this.runner = null;
-};
-
-TestBase.log = function() {
- var args = Array.prototype.slice.call(arguments, 0);
- args.splice(0, 0, this.desc + ': ');
- LOG.apply(this, args);
-};
-
-TestBase.dump = function() {
- if (this.video) {
- this.log('video.currentTime =', this.video.currentTime);
- this.log('video.readyState =', this.video.readyState);
- this.log('video.networkState =', this.video.networkState);
- }
- if (this.ms) {
- this.log('ms.sb count =', this.ms.sourceBuffers.length);
- for (var i = 0; i < this.ms.sourceBuffers.length; ++i) {
- if (this.ms.sourceBuffers[i].buffered) {
- var buffered = this.ms.sourceBuffers[i].buffered;
- this.log('sb' + i + '.buffered.length', buffered.length);
- for (var j = 0; j < buffered.length; ++j) {
- this.log(' ' + j + ': (' + buffered.start(j) + ', ' +
- buffered.end(j) + ')');
- }
- } else {
- this.log('sb', i, 'invalid buffered range');
- }
- }
- }
-};
-
-TestBase.timeout = 30000;
-
-window.createTest = function(name) {
- var t = function() {};
- t.prototype.__proto__ = TestBase;
- t.prototype.desc = name;
- t.prototype.running = false;
- t.prototype.category = '';
- t.prototype.mandatory = true;
-
- return t;
-};
-
-window.createMSTest = function(name) {
- var t = createTest(name);
- t.prototype.start = function(runner, video) {
- this.ms = new MediaSource();
- this.ms.addEventListener('sourceopen', this.onsourceopen.bind(this));
- if (this.ms.isWrapper)
- this.ms.attachTo(video);
- else
- this.video.src = window.URL.createObjectURL(this.ms);
- this.log('MS test started');
- };
- return t;
-};
-
-var ConformanceTestRunner = function(testSuite, testsMask, mseSpec) {
- this.testView = null;
- this.currentTest = null;
- this.currentTestIdx = 0;
- this.assertThrowsError = true;
- this.XHRManager = createXHRManager(createLogger(this.log.bind(this)));
- this.timeouts = createTimeoutManager(createLogger(this.log.bind(this)));
- this.lastResult = 'pass';
- this.mseSpec = mseSpec || '0.5';
-
- if (testsMask) {
- this.testList = [];
- testsMask = util.resize(testsMask, testSuite.tests.length,
- testsMask.substr(-1));
- for (var i = 0; i < testSuite.tests.length; ++i)
- if (testsMask[i] === '1')
- this.testList.push(testSuite.tests[i]);
- } else {
- this.testList = testSuite.tests;
- }
- this.fields = testSuite.fields;
- this.info = testSuite.info;
- this.viewType = testSuite.viewType;
-};
-
-ConformanceTestRunner.prototype.log = function() {
- var args = Array.prototype.slice.call(arguments, 0);
- args.splice(0, 0, 'ConformanceTestRunner: ');
- LOG.apply(this, args);
-};
-
-ConformanceTestRunner.prototype.assert = function(cond, msg) {
- if (!cond) {
- ++this.testList[this.currentTestIdx].prototype.failures;
- this.updateStatus();
- this.error('Assert failed: ' + msg, false);
- }
-};
-
-ConformanceTestRunner.prototype.checkException = function(testFunc, exceptionCode) {
- try {
- testFunc();
- this.fail('Expect exception ' + exceptionCode);
- } catch (err) {
- this.checkEq(err.code, exceptionCode, 'Exception');
- }
-};
-
-ConformanceTestRunner.prototype.check = function(condition, passMsg, failMsg) {
- if (condition)
- this.log(passMsg);
- else
- this.assert(false, failMsg);
-};
-
-ConformanceTestRunner.prototype.checkType = function(x, y, name) {
- var t = typeof(x);
- var result = t === y;
- this.check(result, 'checkType passed: type of ' + name + ' is (' + t + ').',
- 'Type of ' + name + ' is (' + t + ') which should be (' + y + ')');
-};
-
-ConformanceTestRunner.prototype.checkEq = function(x, y, name) {
- var result = (x == y) ||
- (typeof(x) === 'number' && typeof(y) === 'number' &&
- isNaN(x) && isNaN(y));
- this.check(result, 'checkEq passed: ' + name + ' is (' + x + ').',
- name + ' is (' + x + ') which should be (' + y + ')');
-};
-
-ConformanceTestRunner.prototype.checkNE = function(x, y, name) {
- var result = (x != y) &&
- !(typeof(x) === 'number' && typeof(y) === 'number' &&
- isNaN(x) && isNaN(y));
- this.check(result, 'checkNE passed: ' + name + ' is (' + x + ').',
- name + ' is (' + x + ') which shouldn\'t.');
-};
-
-ConformanceTestRunner.prototype.checkApproxEq = function(x, y, name) {
- var diff = Math.abs(x - y);
- var eps = 0.5;
- this.check(diff < eps, 'checkApproxEq passed: ' + name + ' is (' + x + ').',
- name + ' is (' + x + ') which should between [' +
- (y - eps) + ', ' + (y + eps) + ']');
-};
-
-ConformanceTestRunner.prototype.checkGr = function(x, y, name) {
- this.check(x > y, 'checkGr passed: ' + name + ' is (' + x + ').',
- name + ' is (' + x +
- ') which should be greater than (' + y + ')');
-};
-
-ConformanceTestRunner.prototype.checkGE = function(x, y, name) {
- this.check(x >= y, 'checkGE passed: ' + name + ' is (' + x + ').',
- name + ' is (' + x +
- ') which should be greater than or equal to (' + y + ')');
-};
-
-ConformanceTestRunner.prototype.checkLE = function(x, y, name) {
- this.check(x <= y, 'checkLE passed: ' + name + ' is (' + x + ').',
- name + ' is (' + x +
- ') which should be less than or equal to (' + y + ')');
-};
-
-ConformanceTestRunner.prototype.getControlContainer = function() {
- // Override this function to anchor one to the DOM.
- return document.createElement('div');
-};
-
-ConformanceTestRunner.prototype.getNewVideoTag = function() {
- // Override this function to anchor one to the DOM.
- return document.createElement('video');
-};
-
-ConformanceTestRunner.prototype.getOutputArea = function() {
- // Override this function to anchor one to the DOM.
- return document.createElement('div');
-};
-
-ConformanceTestRunner.prototype.updateStatus = function() {
- this.testView.getTest(this.currentTestIdx).updateStatus();
-};
-
-ConformanceTestRunner.prototype.initialize = function() {
- var self = this;
- if (this.viewType === 'extra compact')
- this.testView = compactTestView.create(this.mseSpec, this.fields,
- this.viewType);
- else if (this.viewType === 'compact')
- this.testView = compactTestView.create(this.mseSpec, this.fields);
- else
- this.testView = fullTestView.create(this.mseSpec, this.fields);
-
- this.testView.onrunselected = function() {
- self.startTest(0, self.testList.length);
- };
-
- for (var i = 0; i < this.testList.length; ++i) {
- this.testList[i].prototype.onclick = this.startTest.bind(this, i, 1);
- this.testView.addTest(this.testList[i].prototype);
- }
-
- this.testView.generate(this.mseSpec);
-
- document.getElementById('info').innerText = this.info;
- this.log('Media Source and Encrypted Media Conformance Tests ' +
- '(version 20150612143746-4K5xqupUzgiRyTYP)');
-
- this.longestTimeRatio = -1;
- this.longestTest = null;
-};
-
-ConformanceTestRunner.prototype.onfinished = function() {
- this.log('Finished!');
- if (this.longestTest && this.longestTimeRatio > 0) {
- this.log('Longest test is ' + this.longestTest + ', it takes ' +
- this.longestTimeRatio + ' of its timeout.');
- }
-
- var keepRunning = (!stoponfailure || this.lastResult === 'pass') &&
- loop && (this.testView.anySelected() || this.numOfTestToRun === 1);
- if (keepRunning) {
- this.testToRun = this.numOfTestToRun;
- this.currentTestIdx = this.startIndex;
- this.startNextTest();
- } else {
- this.lastResult = 'pass';
- this.getNewVideoTag();
- this.log('All tests are completed');
- }
-
- this.sendTestReport(GetTestResults());
-};
-
-ConformanceTestRunner.prototype.sendTestReport = function(results) {
- if (this.clientName) {
- var xhr = new XMLHttpRequest();
- var resultsURL = 'http://qual-e.appspot.com/api?command=save_result';
- resultsURL += '&source=mse_eme_conformance';
- resultsURL += '&testid=' + this.clientName + '_' + this.runStartTime;
- xhr.open('POST', resultsURL);
- xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
- xhr.send(JSON.stringify(results));
- }
-};
-
-ConformanceTestRunner.prototype.startTest = function(startIndex, numOfTestToRun) {
- if (!this.currentTest) {
- this.startIndex = startIndex;
- this.numOfTestToRun = numOfTestToRun;
- this.testToRun = numOfTestToRun;
- this.currentTestIdx = startIndex;
- this.startNextTest();
- this.runStartTime = Date.now();
- }
-};
-
-ConformanceTestRunner.prototype.startNextTest = function() {
- UpdateStreamDef(Number(enablewebm));
- if (this.numOfTestToRun != 1) {
- while (this.testToRun > 0 &&
- !this.testView.getTest(this.currentTestIdx).selected()) {
- this.testToRun--;
- this.currentTestIdx++;
- }
- }
-
- if (this.testToRun <= 0 || (stoponfailure && this.lastResult != 'pass')) {
- this.onfinished();
- return;
- }
-
- this.currentTest = new this.testList[this.currentTestIdx];
-
- this.log('**** Starting test ' + (this.currentTest.index + 1) + ':' +
- this.currentTest.desc + ' with timeout ' +
- this.currentTest.timeout);
- this.timeouts.setTimeout(this.timeout.bind(this), this.currentTest.timeout);
-
- this.testList[this.currentTestIdx].prototype.testName = this.currentTest.desc;
- this.testList[this.currentTestIdx].prototype.running = true;
-
- this.updateStatus();
-
- this.startTime = Date.now();
- this.currentTest.runner = this;
- this.currentTest.video = this.getNewVideoTag();
-
- var addEventListener = this.currentTest.video.addEventListener;
- this.currentTest.video.eventsAdded = [];
- this.currentTest.video.addEventListener =
- function(type, listener, useCapture) {
- addEventListener.call(this, type, listener, useCapture);
- this.eventsAdded.push([type, listener]);
- };
- this.currentTest.video.removeAllEventListeners = function() {
- for (var i = 0; i < this.eventsAdded.length; ++i) {
- this.removeEventListener(this.eventsAdded[i][0],
- this.eventsAdded[i][1]);
- }
- };
-
- this.currentTest.start(this, this.currentTest.video);
-};
-
-ConformanceTestRunner.prototype.succeed = function() {
- this.lastResult = 'pass';
- ++this.testList[this.currentTestIdx].prototype.passes;
- this.updateStatus();
- this.log('**** Test ' + this.currentTest.desc + ' succeeded.');
- this.teardownCurrentTest(false);
-};
-
-ConformanceTestRunner.prototype.error = function(msg, isTimeout) {
- this.lastResult = isTimeout ? 'timeout' : 'failure';
- var test = this.currentTest;
- this.log(msg);
- var stack = '';
-
- try {
- test.dump();
- } catch (e) {
- }
-
- try {
- var x = y.z.u.v.w;
- } catch (e) {
- if (e && e.stack)
- {
- this.log(e.stack);
- stack = e.stack;
- }
- }
-
- this.testList[this.currentTestIdx].prototype.lastError = {
- message: msg,
- callStack: stack
- };
-
- this.teardownCurrentTest(isTimeout);
- if (this.assertThrowsError) throw msg;
-};
-
-ConformanceTestRunner.prototype.fail = function(msg) {
- ++this.testList[this.currentTestIdx].prototype.failures;
- this.updateStatus();
- this.error('Test ' + this.currentTest.desc + ' FAILED: ' + msg, false);
-};
-
-ConformanceTestRunner.prototype.timeout = function() {
- var isTestTimedOut = false;
- var currentTime = new Date().getTime();
- var testTime = currentTime - this.startTime;
-
- // Wait longer if we have still running xhr requests.
- // Don't consider data transfer time as part of the timeout.
- if (this.XHRManager) {
- if (this.XHRManager.hasActiveRequests()) {
- var timeSinceLastUpdate = currentTime - this.XHRManager.getLastUpdate();
- if (timeSinceLastUpdate < XHR_TIMEOUT_LIMIT) {
- this.timeouts.setTimeout(this.timeout.bind(this), 1000);
- } else {
- isTestTimedOut = true;
- }
- } else if (this.XHRManager.totalRequestDuration > 0) {
- var testTimeLimit = this.currentTest.timeout + this.XHRManager.totalRequestDuration;
- if (testTime < testTimeLimit) {
- this.timeouts.setTimeout(this.timeout.bind(this),
- this.XHRManager.totalRequestDuration);
- } else {
- isTestTimedOut = true;
- }
- }
- } else {
- isTestTimedOut = true;
- }
-
- if (isTestTimedOut) {
- ++this.testList[this.currentTestIdx].prototype.timeouts;
- this.updateStatus();
- this.error('Test ' + this.currentTest.desc + ' TIMED OUT!', true);
- }
-};
-
-ConformanceTestRunner.prototype.teardownCurrentTest = function(isTimeout) {
- if (!isTimeout) {
- var time = Date.now() - this.startTime;
- var ratio = time / this.currentTest.timeout;
- if (ratio >= this.longestTimeRatio) {
- this.longestTimeRatio = ratio;
- this.longestTest = this.currentTest.desc;
- this.log('New longest test ' + this.currentTest.desc +
- ' with timeout ' + this.currentTest.timeout + ' takes ' + time);
- }
- }
-
- this.testList[this.currentTestIdx].prototype.running = false;
- this.updateStatus();
-
- this.timeouts.clearAll();
- this.XHRManager.abortAll();
- this.testView.finishedOneTest();
- this.currentTest.teardown(this.mseSpec);
- if (this.currentTest.ms &&
- !this.currentTest.ms.isWrapper &&
- this.currentTest &&
- this.currentTest.video &&
- this.currentTest.video.src) {
- if (this.mseSpec !== '0.5')
- window.URL.revokeObjectURL(this.currentTest.video.src);
- this.currentTest.video.src = '';
- }
- this.currentTest = null;
- this.testToRun--;
- this.currentTestIdx++;
- window.setTimeout(this.startNextTest.bind(this), 1);
-};
-
-window.TestBase = TestBase;
-window.ConformanceTestRunner = ConformanceTestRunner;
-
-window.GetTestResults = function(testStartId, testEndId) {
- testStartId = testStartId || 0;
- testEndId = testEndId || window.globalRunner.testList.length;
- var results = [];
-
- for (var i = testStartId; i < testEndId; ++i) {
- if (window.globalRunner.testList[i]) {
- var newResult = {
- id: i,
- name: window.globalRunner.testList[i].prototype.testName,
- passed: false,
- passes: 0,
- error: ''
- };
-
- if (window.globalRunner.testList[i].prototype.passes > 0) {
- newResult.passed = true;
- newResult.passes = window.globalRunner.testList[i].prototype.passes;
- }
- else {
- newResult.passed = false;
- newResult.error = window.globalRunner.testList[i].prototype.lastError;
- }
- results.push(newResult);
- }
- }
-
- return results;
-};
-
-
-window.createSimpleTest = function() {
- window.ms = new MediaSource;
- ms.addEventListener('sourceopen', function() {
- window.vsrc = ms.addSourceBuffer(StreamDef.VideoType);
- window.asrc = ms.addSourceBuffer(StreamDef.AudioType);
- console.log('Objects has been created:\n' +
- 'They are video, ms, logger, XHMManager, timeouts, ' +
- 'vchain, vsrc, achain, asrc');
- });
- window.video = document.createElement('video');
- window.logger = createLogger();
- window.XHRManager = createXHRManager(logger);
- window.timeouts = createTimeoutManager(logger);
- video.src = window.URL.createObjectURL(ms);
- window.vchain = new ResetInit(new FileSource(
- 'media/car-20120827-85.mp4', XHRManager, timeouts));
- window.achain = new ResetInit(new FileSource(
- 'media/car-20120827-8b.mp4', XHRManager, timeouts));
-};
-
-})();
-
-// js/harness/test-20150612143746.js end
-
-// js/lib/mse/2013/msutil-20150612143746.js begin
-
-(function() {
-
-var DLOG_LEVEL = 3;
-
-// Log a debug message. Only logs if the given level is less than the current
-// value of the global variable DLOG_LEVEL.
-window.dlog = function(level) {
- if (typeof(level) !== 'number')
- throw 'level has to be an non-negative integer!';
- // Comment this to prevent debug output
- if (arguments.length > 1 && level <= DLOG_LEVEL) {
- var args = [];
- for (var i = 1; i < arguments.length; ++i)
- args.push(arguments[i]);
- if (window.LOG)
- window.LOG.apply(null, args);
- else
- console.log(args);
- }
-};
-
-var ensureUID = (function() {
- var uid = 0;
-
- return function(sb) {
- if (!sb.uid) sb.uid = uid++;
- };
-})();
-
-var elementInBody = function(element) {
- while (element && element !== document.body)
- element = element.parentNode;
- return Boolean(element);
-};
-
-// A version of 'SourceBuffer.append()' that automatically handles EOS
-// (indicated by the 'null' values. Returns true if append succeeded,
-// false if EOS.
-window.safeAppend = function(sb, buf) {
- ensureUID(sb);
-
- if (!buf)
- dlog(2, 'EOS appended to ' + sb.uid);
- else
- sb.append(buf);
-
- return Boolean(buf);
-};
-
-// Convert a 4-byte array into a signed 32-bit int.
-function btoi(data, offset) {
- offset = offset || 0;
- var result = data[offset] >>> 0;
- result = (result << 8) + (data[offset + 1] >>> 0);
- result = (result << 8) + (data[offset + 2] >>> 0);
- result = (result << 8) + (data[offset + 3] >>> 0);
- return result;
-}
-
-// Convert a 4-byte array into a fourcc.
-function btofourcc(data, offset) {
- offset = offset || 0;
- return String.fromCharCode(data[offset], data[offset + 1],
- data[offset + 2], data[offset + 3]);
-}
-
-// Convert a signed 32-bit int into a 4-byte array.
-function itob(value) {
- return [value >>> 24, (value >>> 16) & 0xff, (value >>> 8) & 0xff,
- value & 0xff];
-}
-
-// Return the offset of sidx box
-function getSIDXOffset(data) {
- var length = data.length;
- var pos = 0;
-
- while (pos + 8 <= length) {
- var size = [];
-
- for (var i = 0; i < 4; ++i)
- size.push(data[pos + i]);
-
- size = btoi(size);
- if (size < 8) throw 'Unexpectedly small size';
- if (pos + size >= data.length) break;
-
- if (btofourcc(data, pos + 4) === 'sidx')
- return pos;
-
- pos += size;
- }
-
- throw 'Cannot find sidx box in first ' + data.length + ' bytes of file';
-}
-
-// Given a buffer contains the first 32k of a file, return a list of tables
-// containing 'time', 'duration', 'offset', and 'size' properties for each
-// subsegment.
-function parseSIDX(data) {
- var sidxStartBytes = getSIDXOffset(data);
- var currPos = sidxStartBytes;
-
- function read(bytes) {
- if (currPos + bytes > data.length) throw 'sidx box is incomplete.';
- var result = [];
- for (var i = 0; i < bytes; ++i) result.push(data[currPos + i]);
- currPos += bytes;
- return result;
- }
-
- var size = btoi(read(4));
- var sidxEnd = sidxStartBytes + size;
- var boxType = read(4);
- boxType = btofourcc(boxType);
- if (boxType !== 'sidx') throw 'Unrecognized box type ' + boxType;
-
- var verFlags = btoi(read(4));
- var refId = read(4);
- var timescale = btoi(read(4));
-
- var earliestPts, offset;
- if (verFlags === 0) {
- earliestPts = btoi(read(4));
- offset = btoi(read(4));
- } else {
- dlog(2, 'Warning: may be truncating sidx values');
- read(4);
- earliestPts = btoi(read(4));
- read(4);
- offset = btoi(read(4));
- }
- offset = offset + sidxEnd;
-
- var count = btoi(read(4));
- var time = earliestPts;
-
- var res = [];
- for (var i = 0; i < count; ++i) {
- var size = btoi(read(4));
- var duration = btoi(read(4));
- var sapStuff = read(4);
- res.push({
- time: time / timescale,
- duration: duration / timescale,
- offset: offset,
- size: size
- });
- time = time + duration;
- offset = offset + size;
- }
- if (currPos !== sidxEnd) throw 'Bad end point' + currPos + sidxEnd;
- return res;
-}
-
-// Given a BufferedRange object, find the one that contains the given time
-// 't'. Returns the end time of the buffered range. If a suitable buffered
-// range is not found, returns 'null'.
-function findBufferedRangeEndForTime(sb, t) {
- var buf = sb.buffered;
- ensureUID(sb);
- for (var i = 0; i < buf.length; ++i) {
- var s = buf.start(i), e = buf.end(i);
- dlog(4, 'findBuf: uid=' + sb.uid + ' index=' + i + ' time=' + t +
- ' start=' + s + ' end=' + e);
- if (t >= s && t <= e)
- return e;
- }
-
- return null;
-}
-
-// This part defines the... source, for the, erm... media. But it's not the
-// Media Source. No. No way.
-//
-// Let's call it "source chain" instead.
-//
-// At the end of a source chain is a file source. File sources implement the
-// following methods:
-//
-// init(t, cb): Gets the (cached) initialization segment buffer for t.
-// Current position is not affected. If cb is null, it will return the init
-// segment, otherwise it will call cb with the asynchronously received init
-// segment. If will throw is init segment is not ready and cb is null.
-//
-// seek(t): Sets the maximum time of the next segment to be appended. Will
-// likely round down to the nearest segment start time. (To reset a source
-// after EOF, seek to 0.)
-//
-// pull(cb): Call the cb with the next media segment.
-// return value of EOS('null') indicates that the chain has been exhausted.
-//
-// Most source chain elements will return entire media segments, and many will
-// expect incoming data to begin on a media segment boundary. Those elements
-// that either do not require this property, or return output that doesn't
-// follow it, will be noted.
-//
-// All source chain elements will forward messages that are not handled to the
-// upstream element until they reach the file source.
-
-// Produces a FileSource table.
-window.FileSource = function(path, xhrManager, timeoutManager,
- startIndex, endIndex) {
- this.path = path;
- this.startIndex = startIndex;
- this.endIndex = endIndex;
- this.segs = null;
- this.segIdx = 0;
- this.initBuf = null;
-
- this.init = function(t, cb) {
- if (!cb) {
- if (!this.initBuf)
- throw 'Calling init synchronusly when the init seg is not ready';
- return this.initBuf;
- }
- self = this;
- if (this.initBuf) {
- timeoutManager.setTimeout(cb.bind(this, this.initBuf), 1);
- } else {
- var self = this;
- var xhr = xhrManager.createRequest(this.path, function(e) {
- self.segs = parseSIDX(this.getResponseData());
-
- self.startIndex = self.startIndex || 0;
- self.endIndex = self.endIndex || self.segs.length - 1;
- self.endIndex = Math.min(self.endIndex, self.segs.length - 1);
- self.startIndex = Math.min(self.startIndex, self.endIndex);
- self.segIdx = self.startIndex;
-
- xhr = xhrManager.createRequest(self.path, function(e) {
- self.initBuf = this.getResponseData();
- cb.call(self, self.initBuf);
- }, 0, self.segs[0].offset);
- xhr.send();
- }, 0, 32 * 1024);
- xhr.send();
- }
- };
-
- this.seek = function(t, sb) {
- if (!this.initBuf)
- throw 'Seek must be called after init';
-
- if (sb)
- sb.abort();
- else if (t !== 0)
- throw 'You can only seek to the beginning without providing a sb';
-
- t += this.segs[this.startIndex].time;
- var i = this.startIndex;
- while (i <= this.endIndex && this.segs[i].time <= t)
- ++i;
- this.segIdx = i - 1;
- dlog(2, 'Seeking to segment index=' + this.segIdx + ' time=' + t +
- ' start=' + this.segs[this.segIdx].time +
- ' length=' + this.segs[this.segIdx].duration);
- };
-
- this.pull = function(cb) {
- if (this.segIdx > this.endIndex) {
- timeoutManager.setTimeout(cb.bind(this, null), 1);
- return;
- }
- var seg = this.segs[this.segIdx];
- ++this.segIdx;
- var self = this;
- var xhr = xhrManager.createRequest(this.path, function(e) {
- cb.call(self, this.getResponseData());
- }, seg.offset, seg.size);
- xhr.send();
- };
- this.duration = function() {
- var last = this.segs[this.segs.length - 1];
- return last.time + last.duration;
- };
- this.currSegDuration = function() {
- if (!this.segs || !this.segs[this.segIdx])
- return 0;
- return this.segs[this.segIdx].duration;
- };
-};
-
-function attachChain(downstream, upstream) {
- downstream.upstream = upstream;
- downstream.init = function(t, cb) {
- return upstream.init(t, cb);
- };
- downstream.seek = function(t, sb) {
- return upstream.seek(t, sb);
- };
- downstream.pull = function(cb) {
- return upstream.pull(cb);
- };
- downstream.duration = function() {
- return upstream.duration();
- };
- downstream.currSegDuration = function() {
- return upstream.currSegDuration();
- };
-}
-
-window.ResetInit = function(upstream) {
- this.initSent = false;
- attachChain(this, upstream);
-
- this.init = function(t, cb) {
- this.initSent = true;
- return this.upstream.init(t, cb);
- };
-
- this.seek = function(t, sb) {
- this.initSent = false;
- return this.upstream.seek(t, sb);
- };
-
- this.pull = function(cb) {
- if (!this.initSent) {
- this.initSent = true;
- this.upstream.init(0, function(initSeg) {
- cb(initSeg);
- });
- return;
- }
- var self = this;
- this.upstream.pull(function(rsp) {
- if (!rsp)
- self.initSent = false;
- cb(rsp);
- });
- };
-};
-
-// This function _blindly_ parses the mdhd header in the segment to find the
-// timescale. It doesn't take any box hierarchy into account.
-function parseTimeScale(data) {
- for (var i = 0; i < data.length - 3; ++i) {
- if (btofourcc(data, i) !== 'mdhd')
- continue;
- var off = i + 16;
- if (data[i + 4] != 0)
- off = i + 28;
-
- return btoi(data, off);
- }
-
- throw 'Failed to find mdhd box in the segment provided';
-}
-
-function replaceTFDT(data, tfdt) {
- for (var i = 0; i < data.length - 3; ++i) {
- if (btofourcc(data, i) !== 'tfdt')
- continue;
- tfdt = itob(tfdt); // convert it into array
- var off = i + 8;
- if (data[i + 4] === 0) {
- data[off] = tfdt[0];
- data[off + 1] = tfdt[1];
- data[off + 2] = tfdt[2];
- data[off + 3] = tfdt[3];
- } else {
- data[off] = 0;
- data[off + 1] = 0;
- data[off + 2] = 0;
- data[off + 3] = 0;
- data[off + 4] = tfdt[0];
- data[off + 5] = tfdt[1];
- data[off + 6] = tfdt[2];
- data[off + 7] = tfdt[3];
- }
-
- return true;
- }
- // the init segment doesn't have tfdt box.
- return false;
-}
-
-// It will repeat a normal stream to turn it into an infinite stream.
-// This type of stream cannot be seeked.
-window.InfiniteStream = function(upstream) {
- this.upstream = upstream;
- this.timescale = null;
- this.elapsed = 0;
- attachChain(this, upstream);
-
- this.seek = function(t, sb) {
- throw 'InfiniteStream cannot be seeked';
- };
-
- this.pull = function(cb) {
- var self = this;
- var currSegDuration = self.upstream.currSegDuration();
- function onPull(buf) {
- if (!buf) {
- self.upstream.seek(0, null);
- self.upstream.pull(onPull);
- return;
- }
- if (!self.timescale) {
- var initBuf = self.upstream.init(0);
- self.timescale = parseTimeScale(initBuf);
- }
- var tfdt = Math.floor(self.timescale * self.elapsed);
- if (tfdt === 1) tfdt = 0;
- dlog(3, 'TFDT: time=' + self.elapsed + ' timescale=' + self.timescale +
- ' tfdt=' + tfdt);
- if (replaceTFDT(buf, tfdt))
- self.elapsed = self.elapsed + currSegDuration;
- cb(buf);
- }
- this.upstream.pull(onPull);
- };
-
- return this;
-};
-
-// Pull 'len' bytes from upstream chain element 'elem'. 'cache'
-// is a temporary buffer of bytes left over from the last pull.
-//
-// This function will send exactly 0 or 1 pull messages upstream. If 'len' is
-// greater than the number of bytes in the combined values of 'cache' and the
-// pulled buffer, it will be capped to the available bytes. This avoids a
-// number of nasty edge cases.
-//
-// Returns 'rsp, newCache'. 'newCache' should be passed as 'cache' to the
-// next invocation.
-function pullBytes(elem, len, cache, cb) {
- if (!cache) {
- // Always return EOS if cache is EOS, the caller should call seek before
- // reusing the source chain.
- cb(cache, null);
- return;
- }
-
- if (len <= cache.length) {
- var buf = cache.subarray(0, len);
- cache = cache.subarray(len);
- cb(buf, cache);
- return;
- }
-
- elem.pull(function(buf) {
- if (!buf) { // EOS
- cb(cache, buf);
- return;
- }
- var newCache = new Uint8Array(new ArrayBuffer(cache.length + buf.length));
- newCache.set(cache);
- newCache.set(buf, cache.length);
- cache = newCache;
-
- if (cache.length <= len) {
- cb(cache, new Uint8Array(new ArrayBuffer(0)));
- } else {
- buf = cache.subarray(0, len);
- cache = cache.subarray(len);
- cb(buf, cache);
- }
- });
-}
-
-window.FixedAppendSize = function(upstream, size) {
- this.cache = new Uint8Array(new ArrayBuffer(0));
- attachChain(this, upstream);
- this.appendSize = function() {
- return size || 512 * 1024;
- };
- this.seek = function(t, sb) {
- this.cache = new Uint8Array(new ArrayBuffer(0));
- return this.upstream.seek(t, sb);
- };
- this.pull = function(cb) {
- var len = this.appendSize();
- var self = this;
- pullBytes(this.upstream, len, this.cache, function(buf, cache) {
- self.cache = cache;
- cb(buf);
- });
- };
-};
-
-window.RandomAppendSize = function(upstream, min, max) {
- FixedAppendSize.apply(this, arguments);
- this.appendSize = function() {
- min = min || 100;
- max = max || 10000;
- return Math.floor(Math.random() * (max - min + 1) + min);
- };
-};
-
-window.RandomAppendSize.prototype = new window.FixedAppendSize;
-window.RandomAppendSize.prototype.constructor = window.RandomAppendSize;
-
-// This function appends the init segment to media source
-window.appendInit = function(mp, sb, chain, t, cb) {
- chain.init(t, function(initSeg) {
- sb.append(initSeg);
- cb();
- });
-};
-
-// This is a simple append loop. It pulls data from 'chain' and appends it to
-// 'sb' until the end of the buffered range contains time 't'.
-// It starts from the current playback location.
-window.appendUntil = function(timeoutManager, mp, sb, chain, t, cb) {
- if (!elementInBody(mp)) {
- cb();
- return;
- }
-
- var started = sb.buffered.length !== 0;
- var current = mp.currentTime;
- var bufferedEnd = findBufferedRangeEndForTime(sb, current);
-
- if (bufferedEnd) {
- bufferedEnd = bufferedEnd + 0.1;
- } else {
- bufferedEnd = 0;
- if (started) {
- chain.seek(0, sb);
- }
- }
-
- (function loop(buffer) {
- if (!elementInBody(mp)) {
- cb();
- return;
- }
- if (buffer) {
- if (!safeAppend(sb, buffer)) {
- cb();
- return;
- }
- bufferedEnd = findBufferedRangeEndForTime(sb, bufferedEnd);
- if (bufferedEnd) {
- bufferedEnd = bufferedEnd + 0.1;
- } else {
- bufferedEnd = 0;
- }
- timeoutManager.setTimeout(loop, 0);
- } else {
- if (t >= bufferedEnd && !mp.error)
- chain.pull(loop);
- else
- cb();
- }
- })();
-};
-
-// This is a simple append loop. It pulls data from 'chain' and appends it to
-// 'sb' until the end of the buffered range that contains time 't' is at
-// least 'gap' seconds beyond 't'. If 't' is not currently in a buffered
-// range, it first seeks to a time before 't' and appends until 't' is
-// covered.
-window.appendAt = function(timeoutManager, mp, sb, chain, t, gap, cb) {
- if (!elementInBody(mp)) {
- cb();
- return;
- }
-
- gap = gap || 3;
-
- var bufferedEnd = findBufferedRangeEndForTime(sb, t);
-
- (function loop(buffer) {
- if (!elementInBody(mp)) {
- cb();
- return;
- }
- if (buffer) {
- if (!safeAppend(sb, buffer))
- return;
- bufferedEnd = findBufferedRangeEndForTime(sb, t);
- timeoutManager.setTimeout(loop, 0);
- } else {
- if (t + gap >= (bufferedEnd || 0) && !mp.error) {
- chain.pull(loop);
- } else {
- cb();
- }
- }
- })();
-};
-
-// Append data from chains 'f1' and 'f2' to source buffers 's1' and 's2',
-// maintaining 'lead' seconds of time between current playback time and end of
-// current buffered range. Continue to do this until the current playback time
-// reaches 'endTime'.
-// It supports play one stream, where 's2' and 'f2' are null.
-//
-// 'lead' may be small or negative, which usually triggers some interesting
-// fireworks with regard to the network buffer level state machine.
-//
-// TODO: catch transition to HAVE_CURRENT_DATA or lower and append enough to
-// resume in that case
-window.playThrough = function(timeoutManager, mp, lead, endTime, s1, f1, s2,
- f2, cb) {
- var yieldTime = 0.03;
-
- function loop() {
- if (!elementInBody(mp))
- return;
- if (mp.currentTime <= endTime && !mp.error)
- timeoutManager.setTimeout(playThrough.bind(
- null, timeoutManager, mp, lead, endTime, s1, f1, s2, f2, cb),
- yieldTime * 1000);
- else
- cb();
- };
- appendAt(timeoutManager, mp, s1, f1, mp.currentTime, yieldTime + lead,
- function() {
- if (s2)
- appendAt(timeoutManager, mp, s2, f2, mp.currentTime,
- yieldTime + lead, loop);
- else
- loop();
- });
-};
-
-window.waitUntil = function(timeouts, media, target, cb) {
- var initTime = media.currentTime;
- var lastTime = lastTime;
- var check = function() {
- if (media.currentTime === initTime) {
- timeouts.setTimeout(check, 500);
- } else if (media.currentTime === lastTime || media.currentTime > target) {
- cb();
- } else {
- lastTime = media.currentTime;
- timeouts.setTimeout(check, 500);
- }
- };
-
- timeouts.setTimeout(check, 500);
-};
-
-window.callAfterLoadedMetaData = function(media, testFunc) {
- var onLoadedMetadata = function() {
- LOG('onLoadedMetadata called');
- media.removeEventListener('loadedmetadata', onLoadedMetadata);
- testFunc();
- };
-
- if (media.readyState >= media.HAVE_METADATA) {
- LOG('onLoadedMetadata bypassed');
- testFunc();
- } else {
- media.addEventListener('loadedmetadata', onLoadedMetadata);
- }
-};
-
-})();
-
-// js/lib/mse/2013/msutil-20150612143746.js end
-
-// js/tests/2013/conformanceTest-20150612143746.js begin
-
-var ConformanceTest = function() {
-
-var tests = [];
-var info = 'No MSE Support!';
-if (window.MediaSource)
- info = 'MSE Version: ' + MediaSource.prototype.version;
-info += ' / Default Timeout: ' + TestBase.timeout + 'ms';
-
-var fields = ['passes', 'failures', 'timeouts'];
-
-var createConformanceTest = function(name) {
- var t = createMSTest(name);
- t.prototype.index = tests.length;
- t.prototype.passes = 0;
- t.prototype.failures = 0;
- t.prototype.timeouts = 0;
- tests.push(t);
- return t;
-};
-
-
-var testPresence = createConformanceTest('Presence');
-testPresence.prototype.title = 'Test if MediaSource object is present.';
-testPresence.prototype.start = function(runner, video) {
- if (!window.MediaSource)
- return runner.fail('No MediaSource object available.');
-
- var ms = new MediaSource();
- if (!ms)
- return runner.fail('Found MediaSource but could not create one');
-
- if (ms.version)
- this.log('Media source version reported as ' + ms.version);
- else
- this.log('No media source version reported');
-
- runner.succeed();
-};
-testPresence.prototype.teardown = function() {};
-
-
-var testAttach = createConformanceTest('Attach');
-testAttach.prototype.timeout = 2000;
-testAttach.prototype.title =
- 'Test if MediaSource object can be attached to video.';
-testAttach.prototype.start = function(runner, video) {
- this.ms = new MediaSource();
- this.ms.addEventListener('sourceopen', function() {
- runner.succeed();
- });
- if (this.ms.isWrapper)
- this.ms.attachTo(video);
- else
- video.src = window.URL.createObjectURL(this.ms);
- video.load();
-};
-testAttach.prototype.teardown = function() {};
-
-
-var testAddSourceBuffer = createConformanceTest('addSourceBuffer');
-testAddSourceBuffer.prototype.title =
- 'Test if we can add source buffer';
-testAddSourceBuffer.prototype.onsourceopen = function() {
- this.runner.checkEq(this.ms.sourceBuffers.length, 0, 'Source buffer number');
- this.ms.addSourceBuffer(StreamDef.AudioType);
- this.runner.checkEq(this.ms.sourceBuffers.length, 1, 'Source buffer number');
- this.ms.addSourceBuffer(StreamDef.VideoType);
- this.runner.checkEq(this.ms.sourceBuffers.length, 2, 'Source buffer number');
- this.runner.succeed();
-};
-
-
-var testSupportedFormats = createConformanceTest('SupportedFormats');
-testSupportedFormats.prototype.title =
- 'Test if we support mp4 video (video/mp4; codecs="avc1.640008") and ' +
- 'audio (audio/mp4; codecs="mp4a.40.5") formats, or webm video' +
- '(video/webm; codecs="vorbis,vp9") and audio (audio/webm; codecs="vorbis").';
-testSupportedFormats.prototype.onsourceopen = function() {
- try {
- this.log('Trying format ' + StreamDef.AudioType);
- var src = this.ms.addSourceBuffer(StreamDef.AudioType);
- this.log('Trying format ' + StreamDef.VideoType);
- var src = this.ms.addSourceBuffer(StreamDef.VideoType);
- } catch (e) {
- return this.runner.fail(e);
- }
- this.runner.succeed();
-};
-
-
-var testAddSourceBufferException = createConformanceTest('AddSBException');
-testAddSourceBufferException.prototype.title =
- 'Test if add incorrect source buffer type will fire the correct ' +
- 'exceptions.';
-testAddSourceBufferException.prototype.onsourceopen = function() {
- var runner = this.runner;
- var self = this;
- runner.checkException(function() {
- self.ms.addSourceBuffer('^^^');
- }, DOMException.NOT_SUPPORTED_ERR);
- if (this.ms.isWrapper) {
- runner.checkException(function() {
- var video = document.createElement('video');
- video.webkitSourceAddId('id', StreamDef.AudioType);
- }, DOMException.INVALID_STATE_ERR);
- } else {
- runner.checkException(function() {
- var ms = new MediaSource;
- ms.addSourceBuffer(StreamDef.AudioType);
- }, DOMException.INVALID_STATE_ERR);
- }
- runner.succeed();
-};
-
-
-var createInitialMediaStateTest = function(state, value, check) {
- var test = createConformanceTest('InitialMedia' +
- util.MakeCapitalName(state));
-
- check = typeof(check) === 'undefined' ? 'checkEq' : check;
- test.prototype.title = 'Test if the state ' + state +
- ' is correct when onsourceopen is called';
- test.prototype.onsourceopen = function() {
- this.runner[check](this.video[state], value, state);
- this.runner.succeed();
- };
-};
-
-createInitialMediaStateTest('duration', NaN);
-createInitialMediaStateTest('videoWidth', 0);
-createInitialMediaStateTest('videoHeight', 0);
-createInitialMediaStateTest('readyState', HTMLMediaElement.HAVE_NOTHING);
-createInitialMediaStateTest('src', '', 'checkNE');
-createInitialMediaStateTest('currentSrc', '', 'checkNE');
-
-
-var createInitialMSStateTest = function(state, value, check) {
- var test = createConformanceTest('InitialMS' + util.MakeCapitalName(state));
-
- check = typeof(check) === 'undefined' ? 'checkEq' : check;
- test.prototype.title = 'Test if the state ' + state +
- ' is correct when onsourceopen is called';
- test.prototype.onsourceopen = function() {
- this.runner[check](this.ms[state], value, state);
- this.runner.succeed();
- };
-};
-
-createInitialMSStateTest('duration', NaN);
-createInitialMSStateTest('readyState', 'open');
-
-
-var createAppendTest = function(stream) {
- var test = createConformanceTest('Append' +
- util.MakeCapitalName(stream.name));
- test.prototype.title = 'Test if we can append a whole ' + stream.name +
- ' file whose size is 1MB.';
- test.prototype.onsourceopen = function() {
- var runner = this.runner;
- var sb = this.ms.addSourceBuffer(stream.type);
- var xhr = runner.XHRManager.createRequest(stream.src,
- function(e) {
- sb.append(xhr.getResponseData());
- runner.checkEq(sb.buffered.length, 1, 'Source buffer number');
- runner.checkEq(sb.buffered.start(0), 0, 'Range start');
- runner.checkApproxEq(sb.buffered.end(0), stream.duration, 'Range end');
- runner.succeed();
- });
- xhr.send();
- };
-};
-
-createAppendTest(StreamDef.Audio1MB);
-createAppendTest(StreamDef.Video1MB);
-
-
-var createAbortTest = function(stream) {
- var test = createConformanceTest('Abort' + util.MakeCapitalName(stream.name));
- test.prototype.title = 'Test if we can abort the current segment.';
- test.prototype.onsourceopen = function() {
- var runner = this.runner;
- var sb = this.ms.addSourceBuffer(stream.type);
- var xhr = runner.XHRManager.createRequest(stream.src,
- function(e) {
- sb.append(xhr.getResponseData());
- sb.abort();
- sb.append(xhr.getResponseData());
- runner.checkEq(sb.buffered.length, 1, 'Source buffer number');
- runner.checkEq(sb.buffered.start(0), 0, 'Range start');
- runner.checkGr(sb.buffered.end(0), 0, 'Range end');
- runner.succeed();
- }, 0, 200000);
- xhr.send();
- };
-};
-
-createAbortTest(StreamDef.Audio1MB);
-createAbortTest(StreamDef.Video1MB);
-
-
-var createTimestampOffsetTest = function(stream) {
- var test = createConformanceTest('TimestampOffset' +
- util.MakeCapitalName(stream.name));
- test.prototype.title = 'Test if we can set timestamp offset.';
- test.prototype.onsourceopen = function() {
- var runner = this.runner;
- var sb = this.ms.addSourceBuffer(stream.type);
- var xhr = runner.XHRManager.createRequest(stream.src,
- function(e) {
- sb.timestampOffset = 5;
- sb.append(xhr.getResponseData());
- runner.checkEq(sb.buffered.length, 1, 'Source buffer number');
- runner.checkEq(sb.buffered.start(0), 5, 'Range start');
- runner.checkApproxEq(sb.buffered.end(0), stream.duration + 5,
- 'Range end');
- runner.succeed();
- });
- xhr.send();
- };
-};
-
-createTimestampOffsetTest(StreamDef.Audio1MB);
-createTimestampOffsetTest(StreamDef.Video1MB);
-
-
-var testDuration = createConformanceTest('Duration');
-testDuration.prototype.title =
- 'Test if we can set duration.';
-testDuration.prototype.onsourceopen = function() {
- this.ms.duration = 10;
- this.runner.checkEq(this.ms.duration, 10, 'ms.duration');
- this.runner.succeed();
-};
-
-
-var testSourceRemove = createConformanceTest('SourceRemove');
-testSourceRemove.prototype.title =
- 'Test if we can add/remove source buffer and do it for more than once';
-testSourceRemove.prototype.onsourceopen = function() {
- var sb = this.ms.addSourceBuffer(StreamDef.AudioType);
- this.ms.removeSourceBuffer(sb);
- this.runner.checkEq(this.ms.sourceBuffers.length, 0, 'Source buffer number');
- this.ms.addSourceBuffer(StreamDef.AudioType);
- this.runner.checkEq(this.ms.sourceBuffers.length, 1, 'Source buffer number');
- for (var i = 0; i < 10; ++i) {
- try {
- sb = this.ms.addSourceBuffer(StreamDef.VideoType);
- this.runner.checkEq(this.ms.sourceBuffers.length, 2,
- 'Source buffer number');
- this.ms.removeSourceBuffer(sb);
- this.runner.checkEq(this.ms.sourceBuffers.length, 1,
- 'Source buffer number');
- } catch (e) {
- return this.runner.fail(e);
- }
- }
- this.runner.succeed();
-};
-
-
-var createDurationAfterAppendTest = function(type, stream) {
- var test = createConformanceTest('DurationAfterAppend' +
- util.MakeCapitalName(type));
- test.prototype.title = 'Test if the duration expands after appending data.';
- test.prototype.onsourceopen = function() {
- var runner = this.runner;
- var media = this.video;
- var ms = this.ms;
- var sb = ms.addSourceBuffer(stream.type);
- var self = this;
- var ondurationchange = function() {
- self.log('ondurationchange called');
- media.removeEventListener('durationchange', ondurationchange);
- runner.checkApproxEq(ms.duration, sb.buffered.end(0), 'ms.duration');
- runner.succeed();
- };
- var xhr = runner.XHRManager.createRequest(stream.src,
- function(e) {
- var data = xhr.getResponseData();
- sb.append(data);
- sb.abort();
- ms.duration = sb.buffered.end(0) / 2;
- media.addEventListener('durationchange', ondurationchange);
- sb.append(data);
- });
- xhr.send();
- };
-};
-
-createDurationAfterAppendTest('audio', StreamDef.Audio1MB);
-createDurationAfterAppendTest('video', StreamDef.Video1MB);
-
-
-var createPausedTest = function(type, stream) {
- var test = createConformanceTest('PausedStateWith' +
- util.MakeCapitalName(type));
- test.prototype.title = 'Test if the paused state is correct before or ' +
- ' after appending data.';
- test.prototype.onsourceopen = function() {
- var runner = this.runner;
- var media = this.video;
- var ms = this.ms;
- var sb = ms.addSourceBuffer(stream.type);
-
- runner.checkEq(media.paused, true, 'media.paused');
-
- var xhr = runner.XHRManager.createRequest(stream.src,
- function(e) {
- runner.checkEq(media.paused, true, 'media.paused');
- sb.append(xhr.getResponseData());
- runner.checkEq(media.paused, true, 'media.paused');
- runner.succeed();
- });
- xhr.send();
- };
-};
-
-createPausedTest('audio', StreamDef.Audio1MB);
-createPausedTest('video', StreamDef.Video1MB);
-
-
-var createMediaElementEventsTest = function() {
- var test = createConformanceTest('MediaElementEvents');
- test.prototype.title = 'Test if the events on MediaSource are correct.';
- test.prototype.onsourceopen = function() {
- var runner = this.runner;
- var media = this.video;
- var ms = this.ms;
- var audioSb = this.ms.addSourceBuffer(StreamDef.AudioType);
- var videoSb = this.ms.addSourceBuffer(StreamDef.VideoType);
- var lastState = 'open';
- var self = this;
- var videoXhr = runner.XHRManager.createRequest(StreamDef.Video1MB.src,
- function(e) {
- self.log('onload called');
- videoSb.append(videoXhr.getResponseData());
- videoSb.abort();
- ms.duration = 1;
- ms.endOfStream();
- media.play();
- });
- var audioXhr = runner.XHRManager.createRequest(StreamDef.Audio1MB.src,
- function(e) {
- self.log('onload called');
- audioSb.append(audioXhr.getResponseData());
- audioSb.abort();
- videoXhr.send();
- });
-
- media.addEventListener('ended', function() {
- self.log('onended called');
- runner.succeed();
- });
-
- audioXhr.send();
- };
-};
-
-createMediaElementEventsTest();
-
-
-var createMediaSourceEventsTest = function() {
- var test = createConformanceTest('MediaSourceEvents');
- test.prototype.title = 'Test if the events on MediaSource are correct.';
- test.prototype.onsourceopen = function() {
- var runner = this.runner;
- var media = this.video;
- var ms = this.ms;
- var audioSb = this.ms.addSourceBuffer(StreamDef.AudioType);
- var videoSb = this.ms.addSourceBuffer(StreamDef.VideoType);
- var lastState = 'open';
- var self = this;
- var videoXhr = runner.XHRManager.createRequest(StreamDef.Video1MB.src,
- function(e) {
- self.log('onload called');
- videoSb.append(videoXhr.getResponseData());
- videoSb.abort();
- ms.endOfStream();
- });
- var audioXhr = runner.XHRManager.createRequest(StreamDef.Audio1MB.src,
- function(e) {
- self.log('onload called');
- audioSb.append(audioXhr.getResponseData());
- audioSb.abort();
- videoXhr.send();
- });
-
- ms.addEventListener('sourceclose', function() {
- self.log('onsourceclose called');
- runner.checkEq(lastState, 'ended', 'The previous state');
- runner.succeed();
- });
-
- ms.addEventListener('sourceended', function() {
- self.log('onsourceended called');
- runner.checkEq(lastState, 'open', 'The previous state');
- lastState = 'ended';
- media.removeAttribute('src');
- media.load();
- });
-
- audioXhr.send();
- };
-};
-
-createMediaSourceEventsTest();
-
-
-var testBufferSize = createConformanceTest('VideoBufferSize');
-testBufferSize.prototype.title = 'Determines video buffer sizes by ' +
- 'appending incrementally until discard occurs, and tests that it meets ' +
- 'the minimum requirements for streaming.';
-testBufferSize.prototype.onsourceopen = function() {
- var runner = this.runner;
- var sb = this.ms.addSourceBuffer(StreamDef.VideoType);
- var self = this;
- var xhr = runner.XHRManager.createRequest('media/test-video-1MB.mp4',
- function(e) {
- // The test clip has a bitrate which is nearly exactly 1MB/sec, and
- // lasts 1s. We start appending it repeatedly until we get eviction.
- var expectedTime = 0;
- while (true) {
- sb.append(xhr.getResponseData());
- runner.checkEq(sb.buffered.start(0), 0, 'Range start');
- if (expectedTime > sb.buffered.end(0) + 0.1) break;
- expectedTime++;
- sb.timestampOffset = expectedTime;
- }
- var MIN_SIZE = 12;
- runner.checkGE(expectedTime, MIN_SIZE, 'Estimated source buffer size');
- runner.succeed();
- });
- xhr.send();
-};
-
-
-var testSourceChain = createConformanceTest('SourceChain');
-testSourceChain.prototype.title =
- 'Test if Source Chain works properly. Source Chain is a stack of ' +
- 'classes that help with common tasks like appending init segment or ' +
- 'append data in random size.';
-testSourceChain.prototype.onsourceopen = function() {
- var runner = this.runner;
- var media = this.video;
- var videoChain = new RandomAppendSize(new ResetInit(
- new FileSource('media/car-20120827-85.mp4', runner.XHRManager,
- runner.timeouts)));
- var videoSb = this.ms.addSourceBuffer(StreamDef.VideoType);
- var audioChain = new FixedAppendSize(new ResetInit(
- new FileSource('media/car-20120827-8b.mp4', runner.XHRManager,
- runner.timeouts)));
- var audioSb = this.ms.addSourceBuffer(StreamDef.AudioType);
-
- appendUntil(runner.timeouts, media, videoSb, videoChain, 5, function() {
- appendUntil(runner.timeouts, media, audioSb, audioChain, 5, function() {
- media.play();
- playThrough(
- runner.timeouts, media, 1, 2,
- videoSb, videoChain, audioSb, audioChain,
- function() {
- runner.checkGE(media.currentTime, 2, 'currentTime');
- runner.succeed();
- });
- });
- });
-};
-
-
-var testVideoDimension = createConformanceTest('VideoDimension');
-testVideoDimension.prototype.title =
- 'Test if the readyState transition is correct.';
-testVideoDimension.prototype.onsourceopen = function() {
- var runner = this.runner;
- var media = this.video;
- var videoChain = new ResetInit(new FixedAppendSize(
- new FileSource('media/car-20120827-86.mp4', runner.XHRManager,
- runner.timeouts), 65536));
- var videoSb = this.ms.addSourceBuffer(StreamDef.VideoType);
- var self = this;
-
- runner.checkEq(media.videoWidth, 0, 'video width');
- runner.checkEq(media.videoHeight, 0, 'video height');
-
- media.addEventListener('loadedmetadata', function(e) {
- self.log('loadedmetadata called');
- runner.checkEq(media.videoWidth, 640, 'video width');
- runner.checkEq(media.videoHeight, 360, 'video height');
- runner.succeed();
- });
-
- runner.checkEq(media.readyState, media.HAVE_NOTHING, 'readyState');
- appendInit(media, videoSb, videoChain, 0, function() {});
-};
-
-
-var testPlaybackState = createConformanceTest('PlaybackState');
-testPlaybackState.prototype.title =
- 'Test if the playback state transition is correct.';
-testPlaybackState.prototype.onsourceopen = function() {
- var runner = this.runner;
- var media = this.video;
- var videoChain = new ResetInit(new FixedAppendSize(
- new FileSource('media/car-20120827-86.mp4', runner.XHRManager,
- runner.timeouts), 65536));
- var videoSb = this.ms.addSourceBuffer(StreamDef.VideoType);
- var audioChain = new ResetInit(new FixedAppendSize(
- new FileSource('media/car-20120827-8b.mp4', runner.XHRManager,
- runner.timeouts), 65536));
- var audioSb = this.ms.addSourceBuffer(StreamDef.AudioType);
- var self = this;
-
- media.play();
- runner.checkEq(media.currentTime, 0, 'media.currentTime');
- media.pause();
- runner.checkEq(media.currentTime, 0, 'media.currentTime');
-
- appendInit(media, audioSb, audioChain, 0, function() {});
- appendInit(media, videoSb, videoChain, 0, function() {});
- callAfterLoadedMetaData(media, function() {
- media.play();
- runner.checkEq(media.currentTime, 0, 'media.currentTime');
- media.pause();
- runner.checkEq(media.currentTime, 0, 'media.currentTime');
- media.play();
- appendUntil(runner.timeouts, media, audioSb, audioChain, 5, function() {
- appendUntil(runner.timeouts, media, videoSb, videoChain, 5, function() {
- playThrough(runner.timeouts, media, 1, 2, audioSb, audioChain,
- videoSb, videoChain, function() {
- var time = media.currentTime;
- media.pause();
- runner.checkApproxEq(media.currentTime, time, 'media.currentTime');
- runner.succeed();
- });
- });
- });
- });
-};
-
-
-var testStartPlayWithoutData = createConformanceTest('StartPlayWithoutData');
-testStartPlayWithoutData.prototype.title =
- 'Test if we can start play before feeding any data. The play should ' +
- 'start automatically after data is appended';
-testStartPlayWithoutData.prototype.onsourceopen = function() {
- var runner = this.runner;
- var media = this.video;
- var videoChain = new ResetInit(
- new FileSource('media/car-20120827-86.mp4', runner.XHRManager,
- runner.timeouts));
- var videoSb = this.ms.addSourceBuffer(StreamDef.VideoType);
- var audioChain = new ResetInit(
- new FileSource('media/car-20120827-8d.mp4', runner.XHRManager,
- runner.timeouts));
- var audioSb = this.ms.addSourceBuffer(StreamDef.AudioType);
-
- media.play();
- appendUntil(runner.timeouts, media, videoSb, videoChain, 1, function() {
- appendUntil(runner.timeouts, media, audioSb, audioChain, 1, function() {
- playThrough(
- runner.timeouts, media, 1, 2,
- videoSb, videoChain, audioSb, audioChain,
- function() {
- runner.checkGE(media.currentTime, 2, 'currentTime');
- runner.succeed();
- });
- });
- });
-};
-
-
-var testPlayPartialSegment = createConformanceTest('PlayPartialSegment');
-testPlayPartialSegment.prototype.title =
- 'Test if we can play a partially appended video segment.';
-testPlayPartialSegment.prototype.onsourceopen = function() {
- var runner = this.runner;
- var video = this.video;
- var videoSb = this.ms.addSourceBuffer(StreamDef.VideoType);
- var audioSb = this.ms.addSourceBuffer(StreamDef.AudioType);
- var videoXhr = runner.XHRManager.createRequest('media/car-20120827-85.mp4',
- function(e) {
- videoSb.append(this.getResponseData());
- video.addEventListener('timeupdate', function(e) {
- if (!video.paused && video.currentTime >= 2) {
- runner.succeed();
- }
- });
- video.play();
- }, 0, 1500000);
- var audioXhr = runner.XHRManager.createRequest('media/car-20120827-8b.mp4',
- function(e) {
- audioSb.append(this.getResponseData());
- videoXhr.send();
- }, 0, 500000);
- audioXhr.send();
-};
-
-
-var testIncrementalAudio = createConformanceTest('IncrementalAudio');
-testIncrementalAudio.prototype.title =
- 'Test if we can append audio not in the unit of segment.';
-testIncrementalAudio.prototype.onsourceopen = function() {
- var runner = this.runner;
- var sb = this.ms.addSourceBuffer(StreamDef.AudioType);
- var xhr = runner.XHRManager.createRequest('media/car-20120827-8c.mp4',
- function(e) {
- sb.append(xhr.getResponseData());
- runner.checkEq(sb.buffered.length, 1, 'Source buffer number');
- runner.checkEq(sb.buffered.start(0), 0, 'Range start');
- runner.checkApproxEq(sb.buffered.end(0), 12.42, 'Range end');
- runner.succeed();
- }, 0, 200000);
- xhr.send();
-};
-
-
-var testAppendAudioOffset = createConformanceTest('AppendAudioOffset');
-testAppendAudioOffset.prototype.title =
- 'Test if we can append audio data with an explicit offset.';
-testAppendAudioOffset.prototype.onsourceopen = function() {
- var runner = this.runner;
- var video = this.video;
- var sb = this.ms.addSourceBuffer(StreamDef.AudioType);
- var xhr = runner.XHRManager.createRequest('media/car-20120827-8c.mp4',
- function(e) {
- sb.timestampOffset = 5;
- sb.append(this.getResponseData());
- xhr2.send();
- }, 0, 200000);
- var xhr2 = runner.XHRManager.createRequest('media/car-20120827-8d.mp4',
- function(e) {
- sb.abort();
- sb.timestampOffset = 0;
- sb.append(this.getResponseData());
- runner.checkEq(sb.buffered.length, 1, 'Source buffer number');
- runner.checkEq(sb.buffered.start(0), 0, 'Range start');
- runner.checkApproxEq(sb.buffered.end(0), 17.42, 'Range end');
- runner.succeed();
- }, 0, 200000);
- xhr.send();
-};
-
-
-var testVideoChangeRate = createConformanceTest('VideoChangeRate');
-testVideoChangeRate.prototype.title =
- 'Test if we can change the format of video on the fly.';
-testVideoChangeRate.prototype.onsourceopen = function() {
- var self = this;
- var runner = this.runner;
- var video = this.video;
- var sb = this.ms.addSourceBuffer(StreamDef.VideoType);
- var xhr = runner.XHRManager.createRequest('media/car-20120827-86.mp4',
- function(e) {
- sb.timestampOffset = 5;
- sb.append(this.getResponseData());
- xhr2.send();
- }, 0, 200000);
- var xhr2 = runner.XHRManager.createRequest('media/car-20120827-85.mp4',
- function(e) {
- sb.abort();
- sb.timestampOffset = 0;
- sb.append(this.getResponseData());
- runner.checkEq(sb.buffered.length, 1, 'Source buffer number');
- runner.checkEq(sb.buffered.start(0), 0, 'Range start');
- runner.checkApproxEq(sb.buffered.end(0), 11.47, 'Range end');
- callAfterLoadedMetaData(video, function() {
- video.currentTime = 3;
- video.addEventListener('seeked', function(e) {
- self.log('seeked called');
- video.addEventListener('timeupdate', function(e) {
- self.log('timeupdate called with ' + video.currentTime);
- if (!video.paused && video.currentTime >= 2) {
- runner.succeed();
- }
- });
- });
- });
- video.play();
- }, 0, 400000);
- this.ms.duration = 100000000; // Ensure that we can seek to any position.
- xhr.send();
-};
-
-
-var createAppendMultipleInitTest = function(type, stream) {
- var test = createConformanceTest('AppendMultipleInit' +
- util.MakeCapitalName(type));
- test.prototype.title = 'Test if we can append multiple init segments.';
- test.prototype.onsourceopen = function() {
- var runner = this.runner;
- var media = this.video;
- var chain = new FileSource(stream.src, runner.XHRManager, runner.timeouts);
- var src = this.ms.addSourceBuffer(stream.type);
- var init;
-
- chain.init(0, function(buf) {
- init = buf;
- chain.pull(function(buf) {
- for (var i = 0; i < 10; ++i)
- src.append(init);
- src.append(buf);
- src.abort();
- var end = src.buffered.end(0);
- for (var i = 0; i < 10; ++i)
- src.append(init);
- runner.checkEq(src.buffered.end(0), end, 'Range end');
- runner.succeed();
- });
- });
- };
-};
-
-createAppendMultipleInitTest('audio', StreamDef.Audio1MB);
-createAppendMultipleInitTest('video', StreamDef.Video1MB);
-
-
-var testAppendOutOfOrder = createConformanceTest('AppendOutOfOrder');
-testAppendOutOfOrder.prototype.title =
- 'Test if we can append segments out of order. This is valid according' +
- ' to MSE v0.6 section 2.3.';
-testAppendOutOfOrder.prototype.onsourceopen = function() {
- var runner = this.runner;
- var media = this.video;
- var audioChain = new FileSource('media/car-20120827-8c.mp4',
- runner.XHRManager,
- runner.timeouts);
- var audioSb = this.ms.addSourceBuffer(StreamDef.AudioType);
- var bufs = [];
-
- audioChain.init(0, function(buf) {
- bufs.push(buf);
- audioChain.pull(function(buf) {
- bufs.push(buf);
- audioChain.pull(function(buf) {
- bufs.push(buf);
- audioChain.pull(function(buf) {
- bufs.push(buf);
- audioChain.pull(function(buf) {
- bufs.push(buf);
- audioSb.append(bufs[0]);
- runner.checkEq(audioSb.buffered.length, 0, 'Source buffer number');
- audioSb.append(bufs[2]);
- runner.checkEq(audioSb.buffered.length, 1, 'Source buffer number');
- runner.checkGr(audioSb.buffered.start(0), 0, 'Range start');
- audioSb.append(bufs[1]);
- runner.checkEq(audioSb.buffered.length, 1, 'Source buffer number');
- runner.checkEq(audioSb.buffered.start(0), 0, 'Range start');
- audioSb.append(bufs[4]);
- runner.checkEq(audioSb.buffered.length, 2, 'Source buffer number');
- runner.checkEq(audioSb.buffered.start(0), 0, 'Range start');
- audioSb.append(bufs[3]);
- runner.checkEq(audioSb.buffered.length, 1, 'Source buffer number');
- runner.checkEq(audioSb.buffered.start(0), 0, 'Range start');
- runner.succeed();
- });
- });
- });
- });
- });
-};
-
-
-var testBufferedRange = createConformanceTest('BufferedRange');
-testBufferedRange.prototype.title =
- 'Test if SourceBuffer.buffered get updated correctly after feeding data.';
-testBufferedRange.prototype.onsourceopen = function() {
- var runner = this.runner;
- var media = this.video;
- var videoChain = new ResetInit(
- new FileSource('media/car-20120827-86.mp4', runner.XHRManager,
- runner.timeouts));
- var videoSb = this.ms.addSourceBuffer(StreamDef.VideoType);
- var audioChain = new ResetInit(
- new FileSource('media/car-20120827-8c.mp4', runner.XHRManager,
- runner.timeouts));
- var audioSb = this.ms.addSourceBuffer(StreamDef.AudioType);
-
- runner.checkEq(videoSb.buffered.length, 0, 'Source buffer number');
- runner.checkEq(audioSb.buffered.length, 0, 'Source buffer number');
- appendInit(media, videoSb, videoChain, 0, function() {
- appendInit(media, audioSb, audioChain, 0, function() {
- runner.checkEq(videoSb.buffered.length, 0, 'Source buffer number');
- runner.checkEq(audioSb.buffered.length, 0, 'Source buffer number');
- appendUntil(runner.timeouts, media, videoSb, videoChain, 5, function() {
- runner.checkEq(videoSb.buffered.length, 1, 'Source buffer number');
- runner.checkEq(videoSb.buffered.start(0), 0, 'Source buffer number');
- runner.checkGE(videoSb.buffered.end(0), 5, 'Range end');
- appendUntil(runner.timeouts, media, audioSb, audioChain, 5, function() {
- runner.checkEq(audioSb.buffered.length, 1, 'Source buffer number');
- runner.checkEq(audioSb.buffered.start(0), 0, 'Source buffer number');
- runner.checkGE(audioSb.buffered.end(0), 5, 'Range end');
- runner.succeed();
- });
- });
- });
- });
-};
-
-
-var testMediaSourceDuration = createConformanceTest('MediaSourceDuration');
-testMediaSourceDuration.prototype.title =
- 'Test if the duration on MediaSource can be set and got sucessfully.';
-testMediaSourceDuration.prototype.onsourceopen = function() {
- var runner = this.runner;
- var media = this.video;
- var ms = this.ms;
- var videoChain = new ResetInit(
- new FileSource('media/car-20120827-86.mp4', runner.XHRManager,
- runner.timeouts));
- var videoSb = this.ms.addSourceBuffer(StreamDef.VideoType);
- var self = this;
- var onsourceclose = function() {
- self.log('onsourceclose called');
- runner.assert(isNaN(ms.duration));
- runner.succeed();
- };
-
- runner.assert(isNaN(media.duration), 'Initial media duration not NaN');
- media.play();
- appendInit(media, videoSb, videoChain, 0, function() {
- appendUntil(runner.timeouts, media, videoSb, videoChain, 10, function() {
- runner.checkEq(ms.duration, Infinity, 'ms.duration');
- ms.duration = 5;
- runner.checkEq(ms.duration, 5, 'ms.duration');
- runner.checkEq(media.duration, 5, 'media.duration');
- runner.checkLE(videoSb.buffered.end(0), 5.1, 'Range end');
- videoSb.abort();
- videoChain.seek(0);
- appendInit(media, videoSb, videoChain, 0, function() {
- appendUntil(runner.timeouts, media,
- videoSb, videoChain, 10, function() {
- runner.checkApproxEq(ms.duration, 10, 'ms.duration');
- ms.duration = 5;
- var duration = videoSb.buffered.end(0);
- ms.endOfStream();
- runner.checkEq(ms.duration, duration, 'ms.duration');
- media.play();
- ms.addEventListener('sourceended', function() {
- runner.checkEq(ms.duration, duration, 'ms.duration');
- runner.checkEq(media.duration, duration, 'media.duration');
- ms.addEventListener('sourceclose', onsourceclose);
- media.removeAttribute('src');
- media.load();
- });
- });
- });
- });
- });
-};
-
-
-var testAudioWithOverlap = createConformanceTest('AudioWithOverlap');
-testAudioWithOverlap.prototype.title =
- 'Test if audio data with overlap will be merged into one range.';
-testAudioWithOverlap.prototype.onsourceopen = function() {
- var runner = this.runner;
- var media = this.video;
- var audioChain = new ResetInit(
- new FileSource('media/car-20120827-8c.mp4', runner.XHRManager,
- runner.timeouts));
- var audioSb = this.ms.addSourceBuffer(StreamDef.AudioType);
- var GAP = 0.1;
-
- appendInit(media, audioSb, audioChain, 0, function() {
- audioChain.pull(function(buf) {
- runner.assert(safeAppend(audioSb, buf), 'safeAppend failed');
- runner.checkEq(audioSb.buffered.length, 1, 'Source buffer number');
- var segmentDuration = audioSb.buffered.end(0);
- audioSb.timestampOffset = segmentDuration - GAP;
- audioChain.seek(0);
- audioChain.pull(function(buf) {
- runner.assert(safeAppend(audioSb, buf), 'safeAppend failed');
- audioChain.pull(function(buf) {
- runner.assert(safeAppend(audioSb, buf), 'safeAppend failed');
- runner.checkEq(audioSb.buffered.length, 1, 'Source buffer number');
- runner.checkApproxEq(audioSb.buffered.end(0),
- segmentDuration * 2 - GAP, 'Range end');
- runner.succeed();
- });
- });
- });
- });
-};
-
-
-var testAudioWithSmallGap = createConformanceTest('AudioWithSmallGap');
-testAudioWithSmallGap.prototype.title =
- 'Test if audio data with a gap smaller than an audio frame size ' +
- 'will be merged into one buffered range.';
-testAudioWithSmallGap.prototype.onsourceopen = function() {
- var runner = this.runner;
- var media = this.video;
- var audioChain = new ResetInit(
- new FileSource('media/car-20120827-8c.mp4', runner.XHRManager,
- runner.timeouts));
- var audioSb = this.ms.addSourceBuffer(StreamDef.AudioType);
- var GAP = 0.01; // The audio frame size of this file is 0.0232
-
- appendInit(media, audioSb, audioChain, 0, function() {
- audioChain.pull(function(buf) {
- runner.assert(safeAppend(audioSb, buf), 'safeAppend failed');
- runner.checkEq(audioSb.buffered.length, 1, 'Source buffer number');
- var segmentDuration = audioSb.buffered.end(0);
- audioSb.timestampOffset = segmentDuration + GAP;
- audioChain.seek(0);
- audioChain.pull(function(buf) {
- runner.assert(safeAppend(audioSb, buf, 'safeAppend failed'));
- audioChain.pull(function(buf) {
- runner.assert(safeAppend(audioSb, buf), 'safeAppend failed');
- runner.checkEq(audioSb.buffered.length, 1, 'Source buffer number');
- runner.checkApproxEq(audioSb.buffered.end(0),
- segmentDuration * 2 + GAP, 'Range end');
- runner.succeed();
- });
- });
- });
- });
-};
-
-
-var testAudioWithLargeGap = createConformanceTest('AudioWithLargeGap');
-testAudioWithLargeGap.prototype.title =
- 'Test if audio data with a gap larger than an audio frame size ' +
- 'will not be merged into one buffered range.';
-testAudioWithLargeGap.prototype.onsourceopen = function() {
- var runner = this.runner;
- var media = this.video;
- var audioChain = new ResetInit(
- new FileSource('media/car-20120827-8c.mp4', runner.XHRManager,
- runner.timeouts));
- var audioSb = this.ms.addSourceBuffer(StreamDef.AudioType);
- var GAP = 0.03; // The audio frame size of this file is 0.0232
-
- appendInit(media, audioSb, audioChain, 0, function() {
- audioChain.pull(function(buf) {
- runner.assert(safeAppend(audioSb, buf), 'safeAppend failed');
- runner.checkEq(audioSb.buffered.length, 1, 'Source buffer number');
- var segmentDuration = audioSb.buffered.end(0);
- audioSb.timestampOffset = segmentDuration + GAP;
- audioChain.seek(0);
- audioChain.pull(function(buf) {
- runner.assert(safeAppend(audioSb, buf), 'safeAppend failed');
- audioChain.pull(function(buf) {
- runner.assert(safeAppend(audioSb, buf), 'safeAppend failed');
- runner.checkEq(audioSb.buffered.length, 2, 'Source buffer number');
- runner.succeed();
- });
- });
- });
- });
-};
-
-
-var testCanPlayClearKey = createConformanceTest('CanPlayClearKey');
-testCanPlayClearKey.prototype.title =
- 'Test if canPlay return is correct for clear key.';
-testCanPlayClearKey.prototype.onsourceopen = function() {
- var video = this.video;
- this.runner.assert(
- video.canPlayType(
- StreamDef.VideoType, 'org.w3.clearkey') === 'probably' ||
- video.canPlayType(
- StreamDef.VideoType, 'webkit-org.w3.clearkey') === 'probably',
- "canPlay doesn't support video and clearkey properly");
- this.runner.assert(
- video.canPlayType(
- StreamDef.AudioType, 'org.w3.clearkey') === 'probably' ||
- video.canPlayType(
- StreamDef.AudioType, 'webkit-org.w3.clearkey') === 'probably',
- "canPlay doesn't support audio and clearkey properly");
- this.runner.succeed();
-};
-
-
-var testCanPlayPlayReady = createConformanceTest('CanPlayPlayReady');
-testCanPlayPlayReady.prototype.title =
- 'Test if canPlay return is correct for PlayReady.';
-testCanPlayPlayReady.prototype.onsourceopen = function() {
- var video = this.video;
- this.runner.checkEq(
- video.canPlayType(StreamDef.VideoType, 'com.youtube.playready'),
- 'probably', 'canPlayType result');
- this.runner.checkEq(
- video.canPlayType(StreamDef.AudioType, 'com.youtube.playready'),
- 'probably', 'canPlayType result');
- this.runner.succeed();
-};
-
-
-var testCannotPlayWidevine = createConformanceTest('CannotPlayWidevine');
-testCannotPlayWidevine.prototype.title =
- 'Test if canPlay return is correct for Widevine.';
-testCannotPlayWidevine.prototype.onsourceopen = function() {
- var video = this.video;
- this.runner.checkEq(
- video.canPlayType(StreamDef.VideoType, 'com.widevine.alpha'), '',
- 'canPlayType result');
- this.runner.checkEq(
- video.canPlayType(StreamDef.AudioType, 'com.widevine.alpha'), '',
- 'canPlayType result');
- this.runner.succeed();
-};
-
-
-var testWebM = createConformanceTest('WebMHandling');
-testWebM.prototype.title = 'Ensure that WebM is either supported or ' +
- 'that attempting to add a WebM SourceBuffer results in an error.';
-testWebM.prototype.onsourceopen = function() {
- var mime = 'video/webm; codecs="vorbis,vp8"';
- var runner = this.runner;
- try {
- this.log('Add sourceBuffer typed webm');
- var webmSb = this.ms.addSourceBuffer(mime);
- } catch (e) {
- runner.checkEq(e.code, DOMException.NOT_SUPPORTED_ERR,
- 'exception code');
- this.log('Add sourceBuffer typed webm to closed MediaSource');
- try {
- (new MediaSource).addSourceBuffer(mime);
- } catch (e) {
- LOG("WebM with mime '" + mime + "' not supported. (This is okay.)");
- runner.succeed();
- return;
- }
- runner.fail('Add sourceBuffer typed webm to closed MediaSource hasn\'t' +
- ' thrown any exception.');
- return;
- }
- var xhr = runner.XHRManager.createRequest('media/test.webm',
- function(e) {
- try {
- webmSb.append(xhr.getResponseData());
- } catch (e) {
- LOG('WebM support claimed but error on appending data!');
- runner.fail();
- return;
- }
- runner.checkEq(webmSb.buffered.length, 1, 'buffered.length');
- runner.checkApproxEq(webmSb.buffered.end(0), 6.04, 'buffered.end(0)');
- runner.succeed();
- });
- xhr.send();
-};
-
-
-var testClearKeyAudio = createConformanceTest('ClearKeyAudio');
-testClearKeyAudio.prototype.title =
- 'Test if we can play audio encrypted with ClearKey encryption.';
-testClearKeyAudio.prototype.onsourceopen = function() {
- var runner = this.runner;
- var media = this.video;
- var videoChain = new ResetInit(
- new FileSource('media/car-20120827-86.mp4', runner.XHRManager,
- runner.timeouts));
- var videoSb = this.ms.addSourceBuffer(StreamDef.VideoType);
- var audioChain = new ResetInit(
- new FileSource('media/car_cenc-20120827-8c.mp4', runner.XHRManager,
- runner.timeouts));
- var audioSb = this.ms.addSourceBuffer(StreamDef.AudioType);
-
- media.addEventListener('needkey', function(e) {
- e.target.generateKeyRequest('org.w3.clearkey', e.initData);
- });
-
- media.addEventListener('keymessage', function(e) {
- var key = new Uint8Array([
- 0x1a, 0x8a, 0x20, 0x95, 0xe4, 0xde, 0xb2, 0xd2,
- 0x9e, 0xc8, 0x16, 0xac, 0x7b, 0xae, 0x20, 0x82]);
- var keyId = new Uint8Array([
- 0x60, 0x06, 0x1e, 0x01, 0x7e, 0x47, 0x7e, 0x87,
- 0x7e, 0x57, 0xd0, 0x0d, 0x1e, 0xd0, 0x0d, 0x1e]);
- e.target.addKey('org.w3.clearkey', key, keyId, e.sessionId);
- });
-
- appendUntil(runner.timeouts, media, videoSb, videoChain, 5, function() {
- appendUntil(runner.timeouts, media, audioSb, audioChain, 5, function() {
- media.play();
- playThrough(
- runner.timeouts, media, 10, 5,
- videoSb, videoChain, audioSb, audioChain, function() {
- runner.checkGE(media.currentTime, 5, 'currentTime');
- runner.succeed();
- });
- });
- });
-};
-
-
-var testClearKeyVideo = createConformanceTest('ClearKeyVideo');
-testClearKeyVideo.prototype.title =
- 'Test if we can play video encrypted with ClearKey encryption.';
-testClearKeyVideo.prototype.onsourceopen = function() {
- var runner = this.runner;
- var media = this.video;
- var videoChain = new ResetInit(
- new FileSource('media/car_cenc-20120827-86.mp4', runner.XHRManager,
- runner.timeouts));
- var videoSb = this.ms.addSourceBuffer(StreamDef.VideoType);
- var audioChain = new ResetInit(
- new FileSource('media/car-20120827-8c.mp4', runner.XHRManager,
- runner.timeouts));
- var audioSb = this.ms.addSourceBuffer(StreamDef.AudioType);
-
- media.addEventListener('needkey', function(e) {
- e.target.generateKeyRequest('org.w3.clearkey', e.initData);
- });
-
- media.addEventListener('keymessage', function(e) {
- var key = new Uint8Array([
- 0x1a, 0x8a, 0x20, 0x95, 0xe4, 0xde, 0xb2, 0xd2,
- 0x9e, 0xc8, 0x16, 0xac, 0x7b, 0xae, 0x20, 0x82]);
- var keyId = new Uint8Array([
- 0x60, 0x06, 0x1e, 0x01, 0x7e, 0x47, 0x7e, 0x87,
- 0x7e, 0x57, 0xd0, 0x0d, 0x1e, 0xd0, 0x0d, 0x1e]);
- e.target.addKey('org.w3.clearkey', key, keyId, e.sessionId);
- });
-
- appendUntil(runner.timeouts, media, videoSb, videoChain, 5, function() {
- appendUntil(runner.timeouts, media, audioSb, audioChain, 5, function() {
- media.play();
- playThrough(
- runner.timeouts, media, 10, 5,
- videoSb, videoChain, audioSb, audioChain, function() {
- runner.checkGE(media.currentTime, 5, 'currentTime');
- runner.succeed();
- });
- });
- });
-};
-
-
-var testSeekTimeUpdate = createConformanceTest('SeekTimeUpdate');
-testSeekTimeUpdate.prototype.title =
- 'Timeupdate event fired with correct currentTime after seeking.';
-testSeekTimeUpdate.prototype.onsourceopen = function() {
- var runner = this.runner;
- var media = this.video;
- var videoSb = this.ms.addSourceBuffer(StreamDef.VideoType);
- var audioSb = this.ms.addSourceBuffer(StreamDef.AudioType);
- var lastTime = 0;
- var updateCount = 0;
- var xhr = runner.XHRManager.createRequest('media/car-20120827-86.mp4',
- function() {
- videoSb.append(xhr.getResponseData());
- var xhr2 = runner.XHRManager.createRequest('media/car-20120827-8c.mp4',
- function() {
- audioSb.append(xhr2.getResponseData());
- callAfterLoadedMetaData(media, function() {
- media.addEventListener('timeupdate', function(e) {
- if (!media.paused) {
- ++updateCount;
- runner.checkGE(media.currentTime, lastTime,
- 'media.currentTime');
- if (updateCount > 3) {
- updateCount = 0;
- lastTime += 10;
- if (lastTime >= 35)
- runner.succeed();
- else
- media.currentTime = lastTime + 6;
- }
- }
- });
- media.play();
- });
- }, 0, 1000000);
- xhr2.send();
- }, 0, 5000000);
- this.ms.duration = 100000000; // Ensure that we can seek to any position.
- xhr.send();
-};
-
-
-var testSourceSeek = createConformanceTest('Seek');
-testSourceSeek.prototype.title = 'Test if we can seek during playing. It' +
- ' also tests if the implementation properly supports seek operation' +
- ' fired immediately after another seek that hasn\'t been completed.';
-testSourceSeek.prototype.onsourceopen = function() {
- var runner = this.runner;
- var media = this.video;
- var videoChain = new ResetInit(new FileSource(
- 'media/car-20120827-86.mp4', runner.XHRManager, runner.timeouts));
- var videoSb = this.ms.addSourceBuffer(StreamDef.VideoType);
- var audioChain = new ResetInit(new FileSource(
- 'media/car-20120827-8c.mp4', runner.XHRManager, runner.timeouts));
- var audioSb = this.ms.addSourceBuffer(StreamDef.AudioType);
- var self = this;
-
- this.ms.duration = 100000000; // Ensure that we can seek to any position.
-
- appendUntil(runner.timeouts, media, videoSb, videoChain, 20, function() {
- appendUntil(runner.timeouts, media, audioSb, audioChain, 20, function() {
- self.log('Seek to 17s');
- callAfterLoadedMetaData(media, function() {
- media.currentTime = 17;
- media.play();
- playThrough(
- runner.timeouts, media, 10, 19,
- videoSb, videoChain, audioSb, audioChain, function() {
- runner.checkGE(media.currentTime, 19, 'currentTime');
- self.log('Seek to 28s');
- media.currentTime = 53;
- media.currentTime = 58;
- playThrough(
- runner.timeouts, media, 10, 60,
- videoSb, videoChain, audioSb, audioChain, function() {
- runner.checkGE(media.currentTime, 60, 'currentTime');
- self.log('Seek to 7s');
- media.currentTime = 0;
- media.currentTime = 7;
- videoChain.seek(7, videoSb);
- audioChain.seek(7, audioSb);
- playThrough(runner.timeouts, media, 10, 9, videoSb, videoChain,
- audioSb, audioChain, function() {
- runner.checkGE(media.currentTime, 9, 'currentTime');
- runner.succeed();
- });
- });
- });
- });
- });
- });
-};
-
-
-var testBufUnbufSeek = createConformanceTest('BufUnbufSeek');
-testBufUnbufSeek.prototype.title = 'Seek into and out of a buffered region.';
-testBufUnbufSeek.prototype.onsourceopen = function() {
- var runner = this.runner;
- var media = this.video;
- var videoSb = this.ms.addSourceBuffer(StreamDef.VideoType);
- var audioSb = this.ms.addSourceBuffer(StreamDef.AudioType);
- var xhr = runner.XHRManager.createRequest('media/car-20120827-86.mp4',
- function() {
- videoSb.append(xhr.getResponseData());
- var xhr2 = runner.XHRManager.createRequest('media/car-20120827-8c.mp4',
- function() {
- audioSb.append(xhr2.getResponseData());
- callAfterLoadedMetaData(media, function() {
- var N = 30;
- function loop(i) {
- if (i > N) {
- media.currentTime = 1.005;
- media.addEventListener('timeupdate', function(e) {
- if (!media.paused && media.currentTime > 3)
- runner.succeed();
- });
- return;
- }
- // bored of shitty test scripts now => test scripts get shittier
- media.currentTime = (i++ % 2) * 1.0e6 + 1;
- runner.timeouts.setTimeout(loop.bind(null, i), 50);
- }
- media.play();
- media.addEventListener('play', loop.bind(null, 0));
- });
- }, 0, 100000);
- xhr2.send();
- }, 0, 1000000);
- this.ms.duration = 100000000; // Ensure that we can seek to any position.
- xhr.send();
-};
-
-
-var createDelayedTest = function(delayed, nonDelayed) {
- var test = createConformanceTest('Delayed' +
- util.MakeCapitalName(delayed.name));
- test.prototype.title = 'Test if we can play properly when there' +
- ' is not enough ' + delayed.name + ' data. The play should resume once ' +
- delayed.name + ' data is appended.';
- test.prototype.onsourceopen = function() {
- var runner = this.runner;
- var media = this.video;
- var chain = new FixedAppendSize(new ResetInit(
- new FileSource(nonDelayed.src, runner.XHRManager, runner.timeouts)),
- 65536);
- var src = this.ms.addSourceBuffer(nonDelayed.type);
- var delayedChain = new FixedAppendSize(new ResetInit(
- new FileSource(delayed.src, runner.XHRManager, runner.timeouts)),
- 65536);
- var delayedSrc = this.ms.addSourceBuffer(delayed.type);
- var self = this;
- var ontimeupdate = function(e) {
- if (!media.paused) {
- var end = delayedSrc.buffered.end(0);
- runner.checkLE(media.currentTime, end + 1.0, 'media.currentTime');
- }
- };
- appendUntil(runner.timeouts, media, src, chain, 15, function() {
- appendUntil(runner.timeouts, media, delayedSrc, delayedChain, 8,
- function() {
- var end = delayedSrc.buffered.end(0);
- self.log('Start play when there is only ' + end + ' seconds of ' +
- delayed.name + ' data.');
- media.play();
- media.addEventListener('timeupdate', ontimeupdate);
- waitUntil(runner.timeouts, media, delayedSrc.buffered.end(0) + 3,
- function() {
- runner.checkLE(media.currentTime, end + 1.0, 'media.currentTime');
- runner.checkGr(media.currentTime, end - 1.0, 'media.currentTime');
- runner.succeed();
- });
- });
- });
- };
-};
-
-createDelayedTest(StreamDef.AudioNormal, StreamDef.VideoNormal);
-createDelayedTest(StreamDef.VideoNormal, StreamDef.AudioNormal);
-
-
-var testXHRUint8Array = createConformanceTest('XHRUint8Array');
-testXHRUint8Array.prototype.title = 'Ensure that XHR can send an Uint8Array';
-testXHRUint8Array.prototype.timeout = 10000;
-testXHRUint8Array.prototype.start = function(runner, video) {
- var s = 'XHR DATA';
- var buf = new ArrayBuffer(s.length);
- var view = new Uint8Array(buf);
- for (var i = 0; i < s.length; i++) {
- view[i] = s.charCodeAt(i);
- }
-
- var xhr = runner.XHRManager.createPostRequest(
- 'https://drmproxy.appspot.com/echo',
- function(e) {
- runner.checkEq(String.fromCharCode.apply(null, xhr.getResponseData()),
- s, 'XHR response');
- runner.succeed();
- },
- view.length);
- xhr.send(view);
-};
-
-
-var testXHRAbort = createConformanceTest('XHRAbort');
-testXHRAbort.prototype.title = 'Ensure that XHR aborts actually abort by ' +
- 'issuing an absurd number of them and then aborting all but one.';
-testXHRAbort.prototype.start = function(runner, video) {
- var N = 100;
- var startTime = Date.now();
- var lastAbortTime;
- function startXHR(i) {
- var xhr = runner.XHRManager.createRequest(
- 'media/car-20120827-85.mp4?x=' + Date.now() + '.' + i,
- function() {
- if (i >= N) {
- xhr.getResponseData(); // This will verify status internally.
- runner.succeed();
- }
- });
- if (i < N) {
- runner.timeouts.setTimeout(xhr.abort.bind(xhr), 10);
- runner.timeouts.setTimeout(startXHR.bind(null, i + 1), 1);
- lastAbortTime = Date.now();
- }
- xhr.send();
- };
- startXHR(0);
-};
-
-
-var testXHROpenState = createConformanceTest('XHROpenState');
-testXHROpenState.prototype.title = 'Ensure XMLHttpRequest.open does not ' +
- 'reset XMLHttpRequest.responseType';
-testXHROpenState.prototype.start = function(runner, video) {
- var xhr = new XMLHttpRequest;
- // It should not be an error to set responseType before calling open
- xhr.responseType = 'arraybuffer';
- xhr.open('GET', 'http://google.com', true);
- runner.checkEq(xhr.responseType, 'arraybuffer', 'XHR responseType');
- runner.succeed();
-};
-
-
-var testFrameGaps = createConformanceTest('FrameGaps');
-testFrameGaps.prototype.title = 'Test media with frame durations of 24FPS ' +
- 'but segment timing corresponding to 23.976FPS';
-testFrameGaps.prototype.filename = 'media/nq-frames24-tfdt23.mp4';
-testFrameGaps.prototype.onsourceopen = function() {
- var runner = this.runner;
- var media = this.video;
- var videoChain = new FixedAppendSize(new ResetInit(
- new FileSource(this.filename, runner.XHRManager,
- runner.timeouts)));
- var videoSb = this.ms.addSourceBuffer(StreamDef.VideoType);
- var audioChain = new FixedAppendSize(new ResetInit(
- new FileSource('media/car-20120827-8c.mp4', runner.XHRManager,
- runner.timeouts)));
- var audioSb = this.ms.addSourceBuffer(StreamDef.AudioType);
- media.play();
- playThrough(runner.timeouts, media, 5, 18, videoSb, videoChain,
- audioSb, audioChain, runner.succeed.bind(runner));
-};
-
-
-var testFrameOverlaps = createConformanceTest('FrameOverlaps');
-testFrameOverlaps.prototype.title = 'Test media with frame durations of ' +
- '23.976FPS but segment timing corresponding to 24FPS';
-testFrameOverlaps.prototype.filename = 'media/nq-frames23-tfdt24.mp4';
-testFrameOverlaps.prototype.onsourceopen = testFrameGaps.prototype.onsourceopen;
-
-
-var testAAC51 = createConformanceTest('AAC51');
-testAAC51.prototype.title = 'Test 5.1-channel AAC';
-testAAC51.prototype.audioFilename = 'media/sintel-trunc.mp4';
-testAAC51.prototype.onsourceopen = function() {
- var runner = this.runner;
- var media = this.video;
- var audioSb = this.ms.addSourceBuffer(StreamDef.AudioType);
- var videoSb = this.ms.addSourceBuffer(StreamDef.VideoType);
- var xhr = runner.XHRManager.createRequest(this.audioFilename,
- function(e) {
- audioSb.append(xhr.getResponseData());
- var xhr2 = runner.XHRManager.createRequest('media/car-20120827-86.mp4',
- function(e) {
- videoSb.append(xhr2.getResponseData());
- media.play();
- media.addEventListener('timeupdate', function(e) {
- if (!media.paused && media.currentTime > 2)
- runner.succeed();
- });
- }, 0, 3000000);
- xhr2.send();
- });
- xhr.send();
-};
-
-
-var testEventTimestamp = createConformanceTest('EventTimestamp');
-testEventTimestamp.prototype.title = 'Test Event Timestamp is relative to ' +
- 'the epoch';
-testEventTimestamp.prototype.onsourceopen = function() {
- var runner = this.runner;
- var video = this.video;
- var videoSb = this.ms.addSourceBuffer(StreamDef.VideoType);
- var audioSb = this.ms.addSourceBuffer(StreamDef.AudioType);
- var last = Date.now();
- runner.checkGr(last, 1360000000000, 'Date.now()');
-
- var audioXhr = runner.XHRManager.createRequest('media/car-20120827-8b.mp4',
- function(e) {
- audioSb.append(this.getResponseData());
- video.addEventListener('timeupdate', function(e) {
- runner.checkGE(e.timeStamp, last, 'event.timeStamp');
- last = e.timeStamp;
- if (!video.paused && video.currentTime >= 2) {
- runner.succeed();
- }
- });
- video.play();
- }, 0, 500000);
-
- var videoXhr = runner.XHRManager.createRequest('media/car-20120827-85.mp4',
- function(e) {
- videoSb.append(this.getResponseData());
- audioXhr.send();
- }, 0, 1500000);
- videoXhr.send();
-};
-
-
-var testDualKey = createConformanceTest('[OPTIONAL/NEW]DualKey');
-testDualKey.prototype.title = 'Tests multiple video keys';
-testDualKey.prototype.start = function(runner, video) {
- var ms = new MediaSource();
- var testEmeHandler = new EMEHandler();
-
- var firstLicense = null;
- var licenseTestPass = false;
- testEmeHandler['_onLoad'] = testEmeHandler['onLoad'];
- testEmeHandler['onLoad'] = function(initData, session, e) {
- try {
- testEmeHandler._onLoad(initData, session, e);
- } catch (exp) {
- if (firstLicense)
- runner.fail('Adding second key failed. Perhaps the system does not ' +
- 'support more than one video key?');
- else
- runner.fail('Failed to add first key.');
- }
-
- var licenseString = arrayToString(
- new Uint8Array(e.target.response)).split('\r\n').pop();
- if (!firstLicense)
- firstLicense = licenseString;
- else if (firstLicense !== licenseString)
- licenseTestPass = true;
- else
- runner.fail('Somehow, the same key was used. This is a failure of the ' +
- 'test video selection.');
- };
-
- testEmeHandler.init(video);
-
- var kFlavorMap = {
- playready: 'http://dash-mse-test.appspot.com/api/drm/playready?' +
- 'drm_system=playready&source=YOUTUBE&' +
- 'video_id=03681262dc412c06&ip=0.0.0.0&ipbits=0&' +
- 'expire=19000000000&' +
- 'sparams=ip,ipbits,expire,drm_system,source,video_id&' +
- 'signature=3BB038322E72D0B027F7233A733CD67D518AF675.' +
- '2B7C39053DA46498D23F3BCB87596EF8FD8B1669&key=test_key1',
- clearkey: 'http://dash-mse-test.appspot.com/api/drm/clearkey?' +
- 'drm_system=clearkey&source=YOUTUBE&video_id=03681262dc412c06&' +
- 'ip=0.0.0.0&ipbits=0&expire=19000000000&' +
- 'sparams=ip,ipbits,expire,drm_system,source,video_id&' +
- 'signature=065297462DF2ACB0EFC28506C5BA5E2E509864D3.' +
- '1FEC674BBB2420DE6B0C7FE3ECD8740C58A43420&key=test_key1'
- };
-
- var kFlavorFiles = {
- playready: [
- 'media/oops_cenc-20121114-145-no-clear-start.mp4',
- 'media/oops_cenc-20121114-145-143.mp4'],
- clearkey: [
- 'media/oops_cenc-20121114-145-no-clear-start.mp4',
- 'media/oops_cenc-20121114-143-no-clear-start.mp4']
- };
-
- var keySystem = 'clearkey';
- var keySystemQuery = /keysystem=([^&]*)/.exec(document.location.search);
- if (keySystemQuery && kFlavorMap[keySystemQuery[1]]) {
- keySystem = keySystemQuery[1];
- }
- try {
- testEmeHandler.setFlavor(kFlavorMap, keySystem);
- } catch (e) {
- runner.fail('Browser does not support the requested key system: ' +
- keySystem);
- return;
- }
-
- function onError(e) {
- runner.fail('Error reported in TestClearKeyNeedKey');
- }
-
- // Open two sources. When the second source finishes, it should also call
- // onLoad above. onLoad will then check if the two keys are dissimilar.
- function onSourceOpen(e) {
- var sb = ms.addSourceBuffer('video/mp4; codecs="avc1.640028"');
-
- var firstFile = new ResetInit(new FileSource(
- kFlavorFiles[keySystem][0],
- runner.XHRManager, runner.timeouts));
-
- appendUntil(runner.timeouts, video, sb, firstFile, 5, function() {
- sb.abort();
-
- var secondFile = new ResetInit(new FileSource(
- kFlavorFiles[keySystem][1],
- runner.XHRManager, runner.timeouts));
-
- appendInit(video, sb, secondFile, 0, function() {
- sb.timestampOffset = video.buffered.end(0);
- appendAt(runner.timeouts, video, sb, secondFile, 5, 5, function() {
- video.play();
- });
- });
- });
-
- video.addEventListener('timeupdate', function onTimeUpdate() {
- if (video.currentTime >= 10 - 1) {
- video.removeEventListener('timeupdate', onTimeUpdate);
- runner.succeed();
- }
- });
- }
-
- ms.addEventListener('sourceopen', onSourceOpen);
- ms.addEventListener('webkitsourceopen', onSourceOpen);
- video.addEventListener('error', onError);
- video.src = window.URL.createObjectURL(ms);
- video.load();
-};
-testDualKey.prototype.teardown = function() {};
-
-
-return {tests: tests, info: info, fields: fields, viewType: 'compact'};
-
-};
-
-// js/tests/2013/conformanceTest-20150612143746.js end
-
-// js/tests/2013/enduranceTest-20150612143746.js begin
-var EnduranceTest = function() {
-
-var tests = [];
-var info = 'Please use these tests to check for resource leaks or ' +
- 'accumulating issues.';
-var fields = ['elapsed'];
-
-var createEnduranceTest = function(name) {
- var t = createMSTest(name);
- t.prototype.index = tests.length;
- t.prototype.elapsed = 0;
- t.prototype.timeout = 2147483647;
- tests.push(t);
- return t;
-};
-
-var enableProgressUpdate = function(test, runner, media) {
- test.prototype.elapsed = 0;
- runner.updateStatus();
-
- runner.timeouts.setInterval(function() {
- test.prototype.elapsed = util.Round(media.currentTime, 3);
- runner.updateStatus();
- }, 1000);
-};
-
-var createOneShotTest = function(stream) {
- var test = createEnduranceTest(util.MakeCapitalName(stream.name) + 'OneShot');
- test.prototype.title = 'XHR and Play media once.';
- test.prototype.onsourceopen = function() {
- var runner = this.runner;
- var media = this.video;
- var sb = this.ms.addSourceBuffer(stream.type);
-
- enableProgressUpdate(test, runner, media);
-
- var xhr = runner.XHRManager.createRequest(stream.src,
- function(e) {
- sb.append(xhr.getResponseData());
- var end = util.Round(sb.buffered.end(0), 2);
- media.addEventListener('timeupdate', function(e) {
- if (!media.paused && media.currentTime > end - 1) {
- media.pause();
- runner.succeed();
- }
- });
- media.play();
- });
- xhr.send();
- };
-};
-
-createOneShotTest(StreamDef.AudioNormal);
-createOneShotTest(StreamDef.VideoNormal);
-
-
-var createInfiniteLoopTest = function(stream) {
- var test = createEnduranceTest('Infinite' +
- util.MakeCapitalName(stream.name) + 'Loop');
- test.prototype.title = 'Play in an infinite loop, good way to see if ' +
- 'there is any resource leak.';
- test.prototype.onsourceopen = function() {
- var runner = this.runner;
- var media = this.video;
- var chain = new InfiniteStream(new ResetInit(
- new FileSource(stream.src, runner.XHRManager, runner.timeouts)));
- var src = this.ms.addSourceBuffer(stream.type);
-
- enableProgressUpdate(test, runner, media);
-
- appendUntil(runner.timeouts, media, src, chain, 1, function() {
- media.play();
- playThrough(
- runner.timeouts, media, 20, Infinity, src, chain, null, null,
- function() {}
- );
- });
- };
-};
-
-createInfiniteLoopTest(StreamDef.AudioNormal);
-createInfiniteLoopTest(StreamDef.VideoNormal);
-
-
-var createInfiniteAVLoopTest = function(audio, video, desc) {
- var test = createEnduranceTest('InfiniteAVLoop' + desc);
- test.prototype.times = 'n/a';
- test.prototype.length = 'n/a';
- test.prototype.title =
- 'Play in an infinite loop, good way to see if there is any resource leak.';
- test.prototype.onsourceopen = function() {
- var runner = this.runner;
- var timeouts = runner.timeouts;
- var media = this.video;
- var video_chain = new InfiniteStream(new ResetInit(
- new FileSource(video.src, runner.XHRManager, runner.timeouts)));
- var video_src = this.ms.addSourceBuffer(StreamDef.VideoType);
- var audio_chain = new InfiniteStream(new ResetInit(
- new FileSource(audio.src, runner.XHRManager, runner.timeouts)));
- var audio_src = this.ms.addSourceBuffer(StreamDef.AudioType);
-
- enableProgressUpdate(test, runner, media);
-
- media.addEventListener('needkey', function(e) {
- e.target.generateKeyRequest('org.w3.clearkey', e.initData);
- });
-
- media.addEventListener('keymessage', function(e) {
- var key = new Uint8Array([
- 0x1a, 0x8a, 0x20, 0x95, 0xe4, 0xde, 0xb2, 0xd2,
- 0x9e, 0xc8, 0x16, 0xac, 0x7b, 0xae, 0x20, 0x82]);
- var key_id = new Uint8Array([
- 0x60, 0x06, 0x1e, 0x01, 0x7e, 0x47, 0x7e, 0x87,
- 0x7e, 0x57, 0xd0, 0x0d, 0x1e, 0xd0, 0x0d, 0x1e]);
- e.target.addKey('org.w3.clearkey', key, key_id, e.sessionId);
- });
- appendUntil(timeouts, media, video_src, video_chain, 1, function() {
- appendUntil(timeouts, media, audio_src, audio_chain, 1, function() {
- media.play();
- playThrough(
- timeouts, media, 5, Infinity, video_src, video_chain,
- audio_src, audio_chain, function() {}
- );
- });
- });
- };
-};
-
-createInfiniteAVLoopTest(StreamDef.AudioTiny, StreamDef.VideoTiny, 'Tiny');
-createInfiniteAVLoopTest(StreamDef.AudioNormal, StreamDef.VideoNormal,
- 'Normal');
-createInfiniteAVLoopTest(StreamDef.AudioHuge, StreamDef.VideoHuge, 'Huge');
-
-createInfiniteAVLoopTest(StreamDef.AudioTinyClearKey,
- StreamDef.VideoTinyClearKey, 'TinyWithClearKey');
-createInfiniteAVLoopTest(StreamDef.AudioNormalClearKey,
- StreamDef.VideoNormalClearKey, 'NormalWithClearKey');
-createInfiniteAVLoopTest(StreamDef.AudioHugeClearKey,
- StreamDef.VideoHugeClearKey, 'HugeWithClearKey');
-
-var createSourceAbortTest = function(stream) {
- var test = createEnduranceTest('Source Abort Test');
- test.prototype.title = 'Source Abort Test.';
- test.prototype.onsourceopen = function() {
- var runner = this.runner;
- var media = this.video;
- var chain = new ResetInit(new FileSource(stream.src, runner.XHRManager,
- runner.timeouts));
- var src = this.ms.addSourceBuffer(stream.type);
-
- test.prototype.times = 0;
- test.prototype.min = 0;
- test.prototype.max = 0;
- test.prototype.average = 0;
- runner.updateStatus();
-
- var segs = [];
- var i = 0;
- var j = 0;
- var k = 0;
-
- function doTest() {
- src.append(segs[0]);
- if (i < segs[1].length) {
- if (j < segs[2].length) {
- if (k < segs[3].length) {
- src.append(segs[1].subarray(0, i));
- src.abort();
- src.append(segs[2].subarray(0, j));
- src.abort();
- src.append(segs[3].subarray(0, k));
- src.abort();
- test.prototype.elapsed++;
- runner.updateStatus();
- k++;
- if (k == segs[3].length) {
- k = 0;
- j++;
- if (j == segs[2].length) {
- j = 0;
- i++;
- if (i == segs[1].length) {
- runner.succeed();
- return;
- }
- }
- }
- runner.timeouts.setTimeout(doTest, 0);
- }
- }
- }
- }
-
- chain.pull(function(data) {
- segs.push(data);
- chain.pull(function(data) {
- segs.push(data);
- chain.pull(function(data) {
- segs.push(data);
- chain.pull(function(data) {
- segs.push(data);
- doTest();
- });
- });
- });
- });
- };
-};
-
-createSourceAbortTest(StreamDef.VideoHuge);
-
-/*
-var createInfiniteLoopYTCencTest = function(stream, keysystem, desc) {
- var test = createEnduranceTest(
- 'Infinite' + util.MakeCapitalName(stream.name) + 'LoopWith' + desc);
- test.prototype.times = 'âˆÅ¾';
- test.prototype.length = 'âˆÅ¾';
- test.prototype.title =
- 'Play in an infinite loop, good way to see if there is any resource leak.';
- var extractBMFFClearKeyID = function(initData) {
- // Accessing the Uint8Array's underlying ArrayBuffer is impossible, so we
- // copy it to a new one for parsing.
- var abuf = new ArrayBuffer(initData.length);
- var view = new Uint8Array(abuf);
- view.set(initData);
-
- var dv = new DataView(abuf);
- var pos = 0;
- while (pos < abuf.byteLength) {
- var box_size = dv.getUint32(pos, false);
- var type = dv.getUint32(pos + 4, false);
-
- if (type !== 0x70737368)
- throw 'Box type ' + type.toString(16) + ' not equal to "pssh"';
-
- if ((dv.getUint32(pos + 12, false) === 0x58147ec8) &&
- (dv.getUint32(pos + 16, false) === 0x04234659) &&
- (dv.getUint32(pos + 20, false) === 0x92e6f52c) &&
- (dv.getUint32(pos + 24, false) === 0x5ce8c3cc)) {
- var size = dv.getUint32(pos + 28, false);
- if (size !== 16) throw 'Unexpected KID size ' + size;
- return new Uint8Array(abuf.slice(pos + 32, pos + 32 + size));
- }
- pos += box_size;
- }
- // Couldn't find it, give up hope.
- return initData;
- };
-
- test.prototype.onsourceopen = function() {
- var runner = this.runner;
- var timeouts = runner.timeouts;
- var media = this.video;
- var chain = new InfiniteStream(new ResetInit(
- new FileSource(stream.src, runner.XHRManager, runner.timeouts)));
- var src = this.ms.addSourceBuffer(stream.type);
- var self = this;
-
- media.addEventListener('needkey', function(e) {
- if (keysystem.indexOf('clearkey') !== -1) {
- self.initData = extractBMFFClearKeyID(e.initData);
- console.log(e.initData);
- console.log(self.initData);
- } else {
- self.initData = e.initData;
- }
- e.target.generateKeyRequest(keysystem, self.initData);
- });
-
- media.addEventListener('keymessage', function(e) {
- var xhr = runner.XHRManager.createPostRequest(
- // TODO: make this universal
- 'http://dash-mse-test.appspot.com/api/drm/clearkey?' +
- 'source=YOUTUBE&video_id=03681262dc412c06',
- function() {
- e.target.addKey('org.w3.clearkey', xhr.getResponseData(),
- self.initData, e.sessionId);
- }, e.message.length);
- xhr.send(e.message);
- });
-
- appendUntil(timeouts, media, src, chain, 1, function() {
- media.play();
- playThrough(
- timeouts, media, 5, Infinity, src, chain, null, null, function() {}
- );
- });
- };
-};
-
-createInfiniteLoopYTCencTest(StreamDef.VideoNormalYTCenc,
- 'webkit-org.w3.clearkey', 'ClearKey');
-createInfiniteLoopYTCencTest(StreamDef.VideoNormalYTCenc,
- 'com.youtube.playready', 'PlayReady');
-*/
-
-return {tests: tests, info: info, fields: fields, viewType: 'full'};
-
-};
-
-// js/tests/2013/enduranceTest-20150612143746.js end
-
-// js/tests/2013/performanceTest-20150612143746.js begin
-
-var PerformanceTest = function() {
-
-var tests = [];
-var info = 'These tests can evaluate the quality of the implementation.';
-var fields = ['times', 'min', 'max', 'average', 'baseline PC',
- 'baseline device'];
-
-function Profiler() {
- var start = Date.now();
- var last = Date.now();
- var times = 0;
-
- this.min = Infinity;
- this.max = -Infinity;
- this.average = 0;
-
- this.tick = function() {
- var curr = Date.now();
- var elapsed = (curr - last) / 1000.;
- last = curr;
- ++times;
- if (elapsed > this.max) this.max = elapsed;
- if (elapsed < this.min) this.min = elapsed;
- this.average = (curr - start) / times / 1000.;
- };
-};
-
-var createPerformanceTest = function(name) {
- var t = createMSTest(name);
- t.prototype.index = tests.length;
- t.prototype.times = 0;
- t.prototype.min = 0;
- t.prototype.max = 0;
- t.prototype.average = 0;
- t.prototype.baseline_PC = 'N/A';
- t.prototype.baseline_device = 'N/A';
- t.prototype.timeout = 2147483647;
- tests.push(t);
- return t;
-};
-
-
-var createCreateUint8ArrayTest = function(size, times, refPC, refDevice) {
- var test = createPerformanceTest(
- 'create Uint8Array in ' + util.SizeToText(size));
- test.prototype.baseline_PC = refPC;
- test.prototype.baseline_device = refDevice;
- test.prototype.title = 'Measure Uint8Array creation performance.';
- test.prototype.start = function(runner, video) {
- var profiler = new Profiler;
- test.prototype.times = 0;
- var array;
- for (var i = 0; i < times; ++i) {
- array = new Uint8Array(new ArrayBuffer(size));
- array = new Uint8Array(array);
- profiler.tick();
- ++test.prototype.times;
- test.prototype.min = profiler.min;
- test.prototype.max = profiler.max;
- test.prototype.average = util.Round(profiler.average, 3);
- runner.updateStatus();
- }
- runner.succeed();
- };
-};
-
-createCreateUint8ArrayTest(1024 * 1024, 1, 0.001, 0.002);
-
-
-var createXHRRequestTest = function(size, times) {
- var test = createPerformanceTest('XHR Request in ' + util.SizeToText(size));
- test.prototype.title = 'Measure XHR request performance.';
- test.prototype.start = function(runner, video) {
- var startTime = Date.now();
- var profiler = new Profiler;
- test.prototype.times = 0;
- function startXHR(i) {
- var xhr = runner.XHRManager.createRequest(
- 'media/car-20120827-85.mp4?x=' + Date.now() + '.' + i,
- function() {
- xhr.getResponseData();
- profiler.tick();
- ++test.prototype.times;
- test.prototype.min = profiler.min;
- test.prototype.max = profiler.max;
- test.prototype.average = util.Round(profiler.average, 3);
- runner.updateStatus();
- if (i < times)
- runner.timeouts.setTimeout(startXHR.bind(null, i + 1), 10);
- else
- runner.succeed();
- }, 0, size);
- xhr.send();
- };
- startXHR(1);
- };
-};
-
-createXHRRequestTest(4096, 32);
-createXHRRequestTest(1024 * 1024, 16);
-createXHRRequestTest(4 * 1024 * 1024, 16);
-
-
-var createXHRAbortTest = function(size, times, refPC, refDevice) {
- var test = createPerformanceTest('Abort XHR Request in ' +
- util.SizeToText(size));
- test.prototype.baseline_PC = refPC;
- test.prototype.baseline_device = refDevice;
- test.prototype.title = 'Measure how fast to abort XHR request.';
- test.prototype.start = function(runner, video) {
- var startTime = Date.now();
- var profiler = new Profiler;
- test.prototype.times = 0;
- function startXHR(i) {
- var xhr = runner.XHRManager.createRequest(
- 'media/car-20120827-85.mp4?x=' + Date.now() + '.' + i,
- function() {});
- xhr.send();
- runner.timeouts.setTimeout(function() {
- xhr.abort();
- profiler.tick();
- ++test.prototype.times;
- test.prototype.min = profiler.min;
- test.prototype.max = profiler.max;
- test.prototype.average = util.Round(profiler.average, 3);
- runner.updateStatus();
- if (i < times)
- startXHR(i + 1);
- else
- runner.succeed();
- }, 0, size);
- };
- startXHR(1);
- };
-};
-
-createXHRAbortTest(4096, 64, 0.098, 0.125);
-createXHRAbortTest(1024 * 1024, 64, 0.116, 0.14);
-createXHRAbortTest(4 * 1024 * 1024, 64, 0.126, 0.15);
-
-
-var createAppendTest = function(stream, size, times, refPC, refDevice) {
- var test = createPerformanceTest('Append ' + util.SizeToText(size) +
- ' to ' + stream.name + ' source buffer');
- test.prototype.baseline_PC = refPC;
- test.prototype.baseline_device = refDevice;
- test.prototype.title = 'Measure source buffer append performance.';
- test.prototype.onsourceopen = function() {
- var runner = this.runner;
- var sb = this.ms.addSourceBuffer(stream.type);
- var xhr = runner.XHRManager.createRequest(stream.src,
- function(e) {
- var profiler = new Profiler;
- var responseData = xhr.getResponseData();
- test.prototype.times = 0;
- for (var i = 0; i < times; ++i) {
- sb.append(responseData);
- sb.abort();
- sb.timestampOffset = sb.buffered.end(sb.buffered.length - 1);
- profiler.tick();
- ++test.prototype.times;
- test.prototype.min = profiler.min;
- test.prototype.max = profiler.max;
- test.prototype.average = util.Round(profiler.average, 3);
- runner.updateStatus();
- }
- runner.succeed();
- }, 0, size);
- xhr.send();
- };
-};
-
-createAppendTest(StreamDef.AudioNormal, 16384, 1024, 0.002, 0.12);
-createAppendTest(StreamDef.AudioNormal, 2 * 1024 * 1024, 128, 0.098, 0.19);
-createAppendTest(StreamDef.VideoNormal, 16384, 1024, 0.002, 0.1);
-createAppendTest(StreamDef.VideoNormal, 4 * 1024 * 1024, 64, 0.015, 0.15);
-
-
-var createSeekAccuracyTest = function(stream, size, times, step) {
- var test = createPerformanceTest('Video Seek Accuracy Test');
- test.prototype.baseline_PC = 0;
- test.prototype.baseline_device = 0;
- test.prototype.title = 'Measure video seeking accuracy.';
- test.prototype.onsourceopen = function() {
- var runner = this.runner;
- var media = this.video;
- var sb = this.ms.addSourceBuffer(stream.type);
- var seekTime = 0;
- var minimumTimeAfterSeek = Infinity;
- var totalDiff = 0;
- var xhr = runner.XHRManager.createRequest(stream.src,
- function(e) {
- test.prototype.times = 0;
- test.prototype.min = Infinity;
- test.prototype.max = 0;
- sb.append(xhr.getResponseData());
- sb.abort();
- media.addEventListener('timeupdate', function(e) {
- if (media.currentTime < minimumTimeAfterSeek)
- minimumTimeAfterSeek = media.currentTime;
- });
- media.addEventListener('seeked', function(e) {
- if (media.currentTime < minimumTimeAfterSeek)
- minimumTimeAfterSeek = media.currentTime;
- var diff = minimumTimeAfterSeek - seekTime;
- totalDiff += diff;
- ++test.prototype.times;
- if (diff < test.prototype.min) test.prototype.min = diff;
- if (diff > test.prototype.max) test.prototype.max = diff;
- test.prototype.average =
- util.Round(totalDiff / test.prototype.times, 3);
- seekTime += step;
- minimumTimeAfterSeek = Infinity;
- runner.updateStatus();
- if (seekTime < times)
- media.currentTime = seekTime;
- else
- runner.succeed();
- });
- callAfterLoadedMetaData(media, function() {
- media.play();
- media.currentTime = seekTime;
- });
- }, 0, size);
- xhr.send();
- };
-};
-
-createSeekAccuracyTest(StreamDef.VideoNormal, 12 * 1024 * 1024, 100, 1);
-
-
-var createSeekBackwardsTest = function(audio, video) {
- var test = createPerformanceTest('Seek Backwards Test');
- test.prototype.baseline_PC = 0;
- test.prototype.baseline_device = 0;
- test.prototype.title = 'Measure seeking accuracy while seeking backwards.';
- test.prototype.onsourceopen = function() {
- var runner = this.runner;
- var media = this.video;
- var audio_chain = new ResetInit(
- new FileSource(audio.src, runner.XHRManager, runner.timeouts));
- var video_chain = new ResetInit(
- new FileSource(video.src, runner.XHRManager, runner.timeouts));
- var audio_src = this.ms.addSourceBuffer(audio.type);
- var video_src = this.ms.addSourceBuffer(video.type);
- var seekTime = video.duration - 5;
- var minimumTimeAfterSeek = Infinity;
- var totalDiff = 0;
- var doingSeek = false;
-
- test.prototype.times = 0;
- test.prototype.min = 0;
- test.prototype.max = 0;
- runner.updateStatus();
-
- var ontimeupdate = function() {
- media.removeEventListener('timeupdate', ontimeupdate);
- if (seekTime > 5) {
- seekTime -= 1;
- doSeek();
- } else {
- runner.succeed();
- }
- };
-
- var onseeked = function() {
- media.removeEventListener('seeked', onseeked);
- media.addEventListener('timeupdate', ontimeupdate);
- };
-
- var doSeek = function() {
- if (doingSeek) {
- runner.timeouts.setTimeout(doSeek, 100);
- return;
- }
- doingSeek = true;
- media.addEventListener('seeked', onseeked);
- audio_chain.seek(Math.max(seekTime, 0), audio_src);
- video_chain.seek(seekTime, video_src);
- media.currentTime = seekTime;
-
- audio_chain.pull(function(data) {
- audio_src.append(data);
- audio_chain.pull(function(data) {
- audio_src.append(data);
- video_chain.pull(function(data) {
- video_src.append(data);
- video_chain.pull(function(data) {
- video_src.append(data);
- video_chain.pull(function(data) {
- video_src.append(data);
- doingSeek = false;
- });
- });
- });
- });
- });
- };
-
- this.ms.duration = 100000000; // Ensure that we can seek to any position.
- audio_chain.init(0, function(data) {
- audio_src.append(data);
- video_chain.init(0, function(data) {
- video_src.append(data);
- media.play();
- callAfterLoadedMetaData(media, doSeek);
- });
- });
- };
-};
-
-createSeekBackwardsTest(StreamDef.AudioNormal, StreamDef.VideoNormal);
-
-
-var createBufferSizeTest = function(stream, refPC, refDevice) {
- var test = createPerformanceTest(
- 'Buffer Size for ' + stream.name + ' in ' +
- util.SizeToText(stream.bps) + ' bps');
- test.prototype.baseline_PC = refPC;
- test.prototype.baseline_device = refDevice;
- test.prototype.title = 'Determines buffer sizes for different stream ' +
- 'types and qualites.';
- test.prototype.onsourceopen = function() {
- var runner = this.runner;
- var sb = this.ms.addSourceBuffer(stream.type);
- function startXHR() {
- var size = Math.min(stream.size, 1024 * 1024);
- var xhr = runner.XHRManager.createRequest(
- stream.src,
- function() {
- var buf = xhr.getResponseData();
- while (true) {
- var old_end = sb.buffered.length ? sb.buffered.end(0) : 0;
- sb.timestampOffset = old_end;
- sb.append(buf);
- sb.abort();
- var new_end = sb.buffered.length ? sb.buffered.end(0) : 0;
- test.prototype.min = Math.floor(new_end);
- test.prototype.max = Math.floor(new_end);
- test.prototype.average = Math.floor(new_end);
- runner.updateStatus();
- if (new_end <= old_end && new_end !== 0)
- break;
- }
- runner.succeed();
- }, 0, size);
- xhr.send();
- };
- startXHR();
- };
-};
-
-createBufferSizeTest(StreamDef.AudioTiny, 3147, 512);
-createBufferSizeTest(StreamDef.AudioNormal, 786, 128);
-createBufferSizeTest(StreamDef.AudioHuge, 393, 64);
-
-createBufferSizeTest(StreamDef.VideoTiny, 4610, 784);
-createBufferSizeTest(StreamDef.VideoNormal, 1062, 182);
-createBufferSizeTest(StreamDef.VideoHuge, 281, 47);
-
-
-var createPrerollSizeTest = function(stream, refPC, refDevice) {
- var test = createPerformanceTest(
- 'Preroll Size for ' + stream.name + ' in ' +
- util.SizeToText(stream.bps) + ' bps');
- test.prototype.baseline_PC = refPC;
- test.prototype.baseline_device = refDevice;
- test.prototype.title = 'Determines preroll sizes for different stream ' +
- 'types and qualites.';
- test.prototype.onsourceopen = function() {
- var runner = this.runner;
- var sb = this.ms.addSourceBuffer(stream.type);
- var end = 0;
-
- test.prototype.times = 0;
- test.prototype.min = 0;
- test.prototype.max = 0;
- test.prototype.average = 0;
- runner.updateStatus();
-
- function timeupdate(e) {
- if (this.currentTime) {
- runner.succeed();
- }
- };
-
- function append(buf) {
- var size = buf.length;
- while (buf.length) {
- var appendSize = Math.min(1, buf.length);
- sb.append(buf.subarray(0, appendSize));
- buf = buf.subarray(appendSize);
- ++test.prototype.times;
- if (sb.buffered.length && sb.buffered.end(0) - end > 0.1) {
- end = sb.buffered.end(0);
- break;
- }
- }
-
- test.prototype.min = util.Round(end, 3);
- test.prototype.max = util.Round(end, 3);
- test.prototype.average = util.Round(end, 3);
- runner.updateStatus();
- runner.timeouts.setTimeout(append.bind(null, buf), 500);
- };
-
- function startXHR() {
- var size = Math.min(stream.size, 5 * 1024 * 1024);
- var xhr = runner.XHRManager.createRequest(
- stream.src,
- function() {
- var buf = new Uint8Array(new ArrayBuffer(size));
- buf.set(xhr.getResponseData());
- append(buf);
- }, 0, size);
- xhr.send();
- };
-
- this.video.addEventListener('timeupdate', timeupdate);
- this.video.play();
- startXHR();
- };
-};
-
-createPrerollSizeTest(StreamDef.AudioTiny, 1.486, 0.557);
-createPrerollSizeTest(StreamDef.AudioNormal, 0.418, 0.209);
-createPrerollSizeTest(StreamDef.AudioHuge, 0.418, 0.209);
-
-createPrerollSizeTest(StreamDef.VideoTiny, 0.25, 0.751);
-createPrerollSizeTest(StreamDef.VideoNormal, 0.25, 0.667);
-createPrerollSizeTest(StreamDef.VideoHuge, 0.25, 0.584);
-
-
-var createSizeToPauseTest = function(stream, refPC, refDevice) {
- var test = createPerformanceTest(
- 'Buffer Size Before Pausing ' + stream.name + ' in ' +
- util.SizeToText(stream.bps) + ' bps');
- test.prototype.baseline_PC = refPC;
- test.prototype.baseline_device = refDevice;
- test.prototype.title = 'Determines preroll sizes for different stream ' +
- 'types and qualites.';
- test.prototype.onsourceopen = function() {
- var runner = this.runner;
- var media = this.video;
- var chain = new ResetInit(new FileSource(stream.src, runner.XHRManager,
- runner.timeouts));
- var src = this.ms.addSourceBuffer(stream.type);
-
- test.prototype.times = 0;
- test.prototype.min = 0;
- test.prototype.max = 0;
- test.prototype.average = 0;
- runner.updateStatus();
-
- appendUntil(runner.timeouts, media, src, chain, 10, function() {
- function timeupdate(e) {
- if (this.currentTime) {
- runner.timeouts.setTimeout(function() {
- var gap = src.buffered.end(0) - media.currentTime;
- gap = util.Round(gap, 3);
- test.prototype.times = 1;
- test.prototype.min = gap;
- test.prototype.max = gap;
- test.prototype.average = gap;
- runner.updateStatus();
- runner.succeed();
- }, (src.buffered.end(0) + 3) * 1000);
- }
- };
- media.addEventListener('timeupdate', timeupdate);
- media.play();
- });
- };
-};
-
-createSizeToPauseTest(StreamDef.AudioTiny, 0, 0.094);
-createSizeToPauseTest(StreamDef.AudioNormal, 0, 0.047);
-createSizeToPauseTest(StreamDef.AudioHuge, 0, 0.047);
-
-createSizeToPauseTest(StreamDef.VideoTiny, 0.083, 0.043);
-createSizeToPauseTest(StreamDef.VideoNormal, 0.125, 0.084);
-createSizeToPauseTest(StreamDef.VideoHuge, 0.083, 0.043);
-
-return {tests: tests, info: info, fields: fields, viewType: 'full'};
-
-};
-
-// js/tests/2013/performanceTest-20150612143746.js end
-
-// js/tests/progressiveTest-20150612143746.js begin
-
-var ProgressiveTest = function() {
-
-var tests = [];
-var info = 'Default Timeout: ' + TestBase.timeout + 'ms';
-
-var fields = ['passes', 'failures', 'timeouts'];
-
-var createProgressiveTest = function(category, name, mandatory) {
- var t = createTest(name);
- t.prototype.category = category;
- t.prototype.index = tests.length;
- t.prototype.passes = 0;
- t.prototype.failures = 0;
- t.prototype.timeouts = 0;
- t.prototype.mandatory = true;
- if (typeof mandatory == 'boolean' && !mandatory)
- t.prototype.mandatory = false;
- tests.push(t);
- return t;
-};
-
-
-var createInitialMediaStateTest = function(state, value, check) {
- var test = createProgressiveTest('state before initial', state);
-
- check = typeof(check) === 'undefined' ? 'checkEq' : check;
- test.prototype.title = 'Test if the state ' + state +
- ' is correct when media element is just created';
- test.prototype.start = function(runner, video) {
- test.prototype.status = util.formatStatus(util.getAttr(video, state));
- runner[check](util.getAttr(video, state), value, state);
- runner.succeed();
- };
-};
-
-createInitialMediaStateTest('src', ''); // can actually be undefined
-createInitialMediaStateTest('currentSrc', '');
-createInitialMediaStateTest('defaultPlaybackRate', 1);
-createInitialMediaStateTest('playbackRate', 1);
-createInitialMediaStateTest('duration', NaN);
-createInitialMediaStateTest('paused', true);
-createInitialMediaStateTest('seeking', false);
-createInitialMediaStateTest('ended', false);
-createInitialMediaStateTest('videoWidth', 0);
-createInitialMediaStateTest('videoHeight', 0);
-createInitialMediaStateTest('buffered.length', 0);
-createInitialMediaStateTest('played.length', 0);
-createInitialMediaStateTest('seekable.length', 0);
-createInitialMediaStateTest('networkState', HTMLMediaElement.NETWORK_EMPTY);
-createInitialMediaStateTest('readyState', HTMLMediaElement.HAVE_NOTHING);
-
-
-var createMediaStateAfterSrcAssignedTest = function(state, value, check) {
- var test = createProgressiveTest('state after src assigned', state);
-
- check = typeof(check) === 'undefined' ? 'checkEq' : check;
- test.prototype.title = 'Test if the state ' + state +
- ' is correct when media element is a src has been assigned';
- test.prototype.start = function(runner, video) {
- video.src = StreamDef.ProgressiveLow.src;
- test.prototype.status = util.formatStatus(util.getAttr(video, state));
- runner[check](util.getAttr(video, state), value, state);
- runner.succeed();
- };
-};
-
-createMediaStateAfterSrcAssignedTest('networkState',
- HTMLMediaElement.NETWORK_NO_SOURCE);
-createMediaStateAfterSrcAssignedTest('readyState',
- HTMLMediaElement.HAVE_NOTHING);
-createMediaStateAfterSrcAssignedTest('src', '', 'checkNE');
-
-
-var createMediaStateInLoadStart = function(state, value, check) {
- var test = createProgressiveTest('state in loadstart', state);
-
- check = typeof(check) === 'undefined' ? 'checkEq' : check;
- test.prototype.title = 'Test if the state ' + state +
- ' is correct when media element is a src has been assigned';
- test.prototype.start = function(runner, video) {
- video.addEventListener('loadstart', function() {
- test.prototype.status = util.formatStatus(util.getAttr(video, state));
- runner[check](util.getAttr(video, state), value, state);
- runner.succeed();
- });
- video.src = StreamDef.ProgressiveLow.src;
- };
-};
-
-createMediaStateInLoadStart('networkState', HTMLMediaElement.NETWORK_LOADING);
-createMediaStateInLoadStart('readyState', HTMLMediaElement.HAVE_NOTHING);
-createMediaStateInLoadStart('currentSrc', '', 'checkNE');
-
-
-var createProgressTest = function() {
- var test = createProgressiveTest('event', 'onprogress');
-
- test.prototype.title = 'Test if there is progress event.';
- test.prototype.start = function(runner, video) {
- var self = this;
- video.src = StreamDef.ProgressiveLow.src + '?' + Date.now();
- video.addEventListener('progress', function() {
- self.log('onprogress called');
- runner.succeed();
- });
- };
-};
-
-createProgressTest();
-
-
-var createTimeUpdateTest = function() {
- var test = createProgressiveTest('event', 'ontimeupdate');
-
- test.prototype.title = 'Test if there is timeupdate event.';
- test.prototype.start = function(runner, video) {
- var self = this;
- video.src = StreamDef.ProgressiveLow.src;
- video.addEventListener('timeupdate', function() {
- self.log('ontimeupdate called');
- runner.succeed();
- });
- video.play();
- };
-};
-
-createTimeUpdateTest();
-
-
-var createCanPlayTest = function() {
- var test = createProgressiveTest('event', 'canplay');
-
- test.prototype.title = 'Test if there is canplay event.';
- test.prototype.start = function(runner, video) {
- var self = this;
- video.src = StreamDef.ProgressiveLow.src;
- video.addEventListener('canplay', function() {
- self.log('canplay called');
- runner.succeed();
- });
- };
-};
-
-createCanPlayTest();
-
-
-var createAutoPlayTest = function() {
- var test = createProgressiveTest('control', 'autoplay');
-
- test.prototype.title = 'Test if autoplay works';
- test.prototype.start = function(runner, video) {
- var self = this;
- video.autoplay = true;
- video.src = StreamDef.ProgressiveLow.src;
- video.addEventListener('timeupdate', function() {
- self.log('ontimeupdate called');
- runner.succeed();
- });
- };
-};
-
-createAutoPlayTest();
-
-
-var createNetworkStateTest = function() {
- var test = createProgressiveTest('state', 'networkState', false);
-
- test.prototype.title = 'Test if the network state is correct';
- test.prototype.start = function(runner, video) {
- var self = this;
- video.src = StreamDef.ProgressiveLow.src;
- video.addEventListener('suspend', function() {
- self.log('onsuspend called');
- runner.checkEq(video.networkState, HTMLMediaElement.NETWORK_IDLE,
- 'networkState');
- runner.succeed();
- });
- };
-};
-
-createNetworkStateTest();
-
-
-var createOnLoadedMetadataTest = function() {
- var test = createProgressiveTest('event', 'onloadedmetadata');
-
- test.prototype.title = 'Test if the onloadedmetadata is called correctly';
- test.prototype.start = function(runner, video) {
- video.addEventListener('loadedmetadata', function() {
- runner.succeed();
- });
- video.src = 'getvideo.py';
- };
-};
-
-
-// getvideo.py is not supported by AppEngine.
-// createOnLoadedMetadataTest();
-
-
-var createPlayingWithoutDataPaused = function() {
- var test = createProgressiveTest('play without data', 'paused',
- false);
-
- test.prototype.title = 'Test if we can play without any data';
- test.prototype.start = function(runner, video) {
- video.src = 'hang.py';
- video.play();
- test.prototype.status = util.formatStatus(video.paused);
- runner.checkEq(video.paused, false, 'video.paused');
- runner.succeed();
- };
-};
-
-createPlayingWithoutDataPaused();
-
-
-var createPlayingWithoutDataWaiting = function() {
- var test = createProgressiveTest('play without data', 'onwaiting',
- false);
-
- test.prototype.title = 'Test if we can play without any data';
- test.prototype.start = function(runner, video) {
- video.addEventListener('waiting', function() {
- runner.checkEq(video.currentTime, 0, 'video.currentTime');
- runner.succeed();
- });
- video.src = 'hang.py';
- video.play();
- };
-};
-
-createPlayingWithoutDataWaiting();
-
-
-var createTimeUpdateMaxGranularity = function(suffix, playbackRatio) {
- var test = createProgressiveTest(
- 'timeupdate', 'max granularity' + suffix, false);
-
- test.prototype.title = 'Test the time update granularity.';
- test.prototype.start = function(runner, video) {
- var maxGranularity = 0;
- var times = 0;
- var last = 0;
- video.addEventListener('suspend', function() {
- video.playbackRate = playbackRatio;
- video.play();
- video.addEventListener('timeupdate', function() {
- if (times !== 0) {
- var interval = Date.now() - last;
- if (interval > maxGranularity)
- maxGranularity = interval;
- }
- if (times === 50) {
- maxGranularity = maxGranularity / 1000.0;
- test.prototype.status = util.Round(maxGranularity, 2);
- runner.checkLE(maxGranularity, 0.26, 'maxGranularity');
- runner.succeed();
- }
- last = Date.now();
- ++times;
- });
- });
- video.src = StreamDef.ProgressiveLow.src;
- };
-};
-
-createTimeUpdateMaxGranularity('', 1.0);
-createTimeUpdateMaxGranularity(' slow motion', 0.2);
-createTimeUpdateMaxGranularity(' fast motion', 2.0);
-
-
-var createTimeUpdateMinGranularity = function(suffix, playbackRatio) {
- var test = createProgressiveTest(
- 'timeupdate', 'min granularity' + suffix, false);
-
- test.prototype.title = 'Test the time update granularity.';
- test.prototype.start = function(runner, video) {
- var minGranularity = Infinity;
- var times = 0;
- var last = 0;
- video.addEventListener('suspend', function() {
- video.playbackRate = playbackRatio;
- video.play();
- video.addEventListener('timeupdate', function() {
- if (times !== 0) {
- var interval = Date.now() - last;
- if (interval > 1 && interval < minGranularity)
- minGranularity = interval;
- }
- if (times === 50) {
- minGranularity = minGranularity / 1000.0;
- test.prototype.status = util.Round(minGranularity, 2);
- runner.checkGE(minGranularity, 0.015, 'minGranularity');
- runner.succeed();
- }
- last = Date.now();
- ++times;
- });
- });
- video.src = StreamDef.ProgressiveLow.src;
- };
-};
-
-createTimeUpdateMinGranularity('', 1.0);
-createTimeUpdateMinGranularity(' slow motion', 0.2);
-createTimeUpdateMinGranularity(' fast motion', 2.0);
-
-
-var createTimeUpdateAccuracy = function() {
- var test = createProgressiveTest('timeupdate', 'accuracy', false);
-
- test.prototype.title = 'Test the time update granularity.';
- test.prototype.start = function(runner, video) {
- var maxTimeDiff = 0;
- var baseTimeDiff = 0;
- var times = 0;
- video.addEventListener('suspend', function() {
- video.play();
- video.addEventListener('timeupdate', function() {
- if (times === 0) {
- baseTimeDiff = Date.now() / 1000.0 - video.currentTime;
- } else {
- var timeDiff = Date.now() / 1000.0 - video.currentTime;
- maxTimeDiff = Math.max(Math.abs(timeDiff - baseTimeDiff),
- maxTimeDiff);
- }
-
- if (times > 500 || video.currentTime > 10) {
- test.prototype.status = util.Round(maxTimeDiff, 2);
- runner.checkLE(maxTimeDiff, 0.5, 'maxTimeDiff');
- runner.succeed();
- }
- ++times;
- });
- });
- video.src = StreamDef.ProgressiveLow.src;
- };
-};
-createTimeUpdateAccuracy();
-
-
-var createTimeUpdateProgressing = function() {
- var test = createProgressiveTest('timeupdate', 'progressing', false);
-
- test.prototype.title = 'Test if the time updates progress.';
- test.prototype.start = function(runner, video) {
- var last = 0;
- var times = 0;
- video.addEventListener('timeupdate', function() {
- if (times === 0) {
- last = video.currentTime;
- } else {
- runner.checkGE(video.currentTime, last, 'video.currentTime');
- last = video.currentTime;
- }
-
- if (video.currentTime > 10) {
- test.prototype.status = util.Round(video.currentTime, 2);
- runner.succeed();
- }
- ++times;
- });
- video.src = StreamDef.ProgressiveLow.src;
- video.play();
- };
-};
-
-createTimeUpdateProgressing();
-
-
-var createTimeUpdateProgressingWithInitialSeek = function() {
- var test = createProgressiveTest(
- 'timeupdate', 'progressing after seek', false);
-
- test.prototype.title = 'Test if the time updates progress.';
- test.prototype.start = function(runner, video) {
- var last = 0;
- var times = 0;
- video.addEventListener('canplay', function() {
- if (times == 0) {
- video.currentTime = 0.001;
- video.play();
- video.addEventListener('timeupdate', function() {
- if (times === 0) {
- last = video.currentTime;
- } else {
- runner.checkGE(video.currentTime, last, 'video.currentTime');
- last = video.currentTime;
- }
-
- if (video.currentTime > 10) {
- test.prototype.status = util.Round(video.currentTime, 2);
- runner.succeed();
- }
- ++times;
- });
- }
- });
- video.src = StreamDef.ProgressiveLow.src;
- };
-};
-
-createTimeUpdateProgressingWithInitialSeek();
-
-
-var createTimeUpdateProgressingWithDurationCheck = function() {
- var test = createProgressiveTest(
- 'timeupdate', 'duration on timeupdate', true);
-
- test.prototype.title = 'Test if the duration is non-negative when time ' +
- 'updates.';
- test.prototype.start = function(runner, video) {
- video.addEventListener('timeupdate', function() {
- runner.checkGE(video.duration, 0, 'video.duration');
- if (video.currentTime > 1) {
- runner.succeed();
- }
- });
- video.src = StreamDef.ProgressiveLow.src;
- video.play();
- };
-};
-
-createTimeUpdateProgressingWithDurationCheck();
-
-return {tests: tests, info: info, fields: fields, viewType: 'compact'};
-
-};
-
-// js/tests/progressiveTest-20150612143746.js end
-
-// js/harness/main-20150612143746.js begin
-(function() {
-
-var timestamp;
-var command;
-var viewType;
-var timeout;
-var testsMask;
-
-var loadTests = function(testType) {
- currentTestType = testType;
-
- // We have to make it compatible to the legacy url format.
- var testName = testType.substr(0, testType.indexOf('-'));
- testName = util.MakeCapitalName(testName) + 'Test';
- console.log(currentTestType);
- console.log(testName);
- return window[testName]();
-};
-
-var parseParam = function(param, defaultValue) {
- var regex = new RegExp('(\\?|\\&)' + param + '=([-,\\w]+)', 'g');
- var value = regex.exec(document.URL);
- return value ? value[2] : defaultValue;
-};
-
-var parseParams = function() {
- var testType = parseParam('test_type', kDefaultTestType);
-
- if (!testTypes[testType]) {
- Alert('Cannot find test type ' + testType);
- throw 'Cannot find test type ' + testType;
- }
-
- timestamp = parseParam('timestamp');
- // if (!timestamp) return;
-
- command = parseParam('command');
- viewType = parseParam('view_type');
- TestBase.timeout = parseParam('timeout', TestBase.timeout);
-
- var disableLog = parseParam('disable_log', 'false');
- window.logging = disableLog !== 'true';
- var loop = parseParam('loop', 'false');
- window.loop = loop === 'true';
- var stoponfailure = parseParam('stoponfailure', 'false');
- window.stoponfailure = stoponfailure === 'true';
- var enablewebm = parseParam('enablewebm', 'false');
- window.enablewebm = enablewebm === 'true';
-
- var tests = parseParam('tests');
- var exclude = parseParam('exclude');
-
- if (tests) {
- testsMask = '';
- tests = tests.split(',').map(function(x) {return parseInt(x);}).
- sort(function(a, b) {return a - b;});
- for (var i = 0; i < tests.length; ++i) {
- var index = tests[i] * 1 - 1;
- if (index < 0)
- continue;
- testsMask = util.resize(testsMask, index, '0');
- testsMask += '1';
- }
- testsMask += '0';
- } else if (exclude) {
- exclude = exclude.split(',').map(function(x) {return parseInt(x);}).
- sort(function(a, b) {return a - b;});
- testsMask = '';
- for (var i = 0; i < exclude.length; ++i) {
- var index = exclude[i] * 1 - 1;
- if (index < 0)
- continue;
- testsMask = util.resize(testsMask, index, '1');
- testsMask += '0';
- }
- testsMask += '1';
- } else {
- testsMask = parseParam('tests_mask');
- if (!testsMask)
- testsMask = '1';
- }
-
- var testSuite = loadTests(testType);
- if (viewType)
- testSuite.viewType = viewType;
- return testSuite;
-};
-
-window.globalRunner = null;
-
-var startRunner = function(testSuite, mseSpec) {
- var id = 0;
- var runner = new ConformanceTestRunner(testSuite, testsMask, mseSpec);
-
- // Expose the runner so outside/injected scripts can read it.
- window.globalRunner = runner;
-
- runner.getNewVideoTag = function() {
- var testarea = document.getElementById('testarea');
- var vid = 'v' + id;
- if (recycleVideoTag)
- ++id;
- if (!document.getElementById(vid)) {
- testarea.innerHTML = '';
- testarea.appendChild(util.createElement('video', vid, 'box-right'));
- document.getElementById(vid).controls = true;
- }
- return document.getElementById(vid);
- };
-
- runner.getControlContainer = function() {
- return document.getElementById('control');
- };
-
- window.LOG = function() {
- if (!window.logging)
- return;
- var output = document.getElementById('output');
- var text = '';
-
- for (var i = 0; i < arguments.length; ++i)
- text += arguments[i].toString() + ' ';
-
- console.log(text);
- output.value = text + '\n' + output.value;
- };
- runner.initialize();
- if (command === 'run')
- runner.startTest(0, runner.testList.length);
-};
-
-window.startMseTest = function(mseSpec) {
- setupMsePortability(mseSpec);
-
- var testSuite = parseParams();
- if (!timestamp) {
-/* if (!/\?/.test(document.URL))
- window.location = document.URL + '?timestamp=' + (new Date()).getTime();
- else
- window.location = document.URL + '×tamp=' + (new Date()).getTime();
- return;*/
- }
- startRunner(testSuite, mseSpec);
-};
-
-})();
-
-// js/harness/main-20150612143746.js end
- </script>
- </head>
- <body>
- <script type="text/javascript">
- window.setTimeout(function() { startMseTest(); }, 1);
- </script>
- </body>
-</html>
diff --git a/cobalt/demos/content/mse-eme-conformance-tests/README.txt b/cobalt/demos/content/mse-eme-conformance-tests/README.txt
deleted file mode 100644
index 36fe3ee..0000000
--- a/cobalt/demos/content/mse-eme-conformance-tests/README.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-The content of this folder will not work as local files as the media files
-have to be served in a web server in order to make it work with XMLHttpRequest.
-The content of this folder will be synced to
-/
-and it can be accessed via the following url:
-https://storage.googleapis.com/yt-dash-mse-eme-test/0.5.html
diff --git a/cobalt/demos/content/mse-eme-conformance-tests/media/.gitignore b/cobalt/demos/content/mse-eme-conformance-tests/media/.gitignore
deleted file mode 100644
index cc2ac4e..0000000
--- a/cobalt/demos/content/mse-eme-conformance-tests/media/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-# Ignore raw media files as they are not tracked inside git repository. They
-# are synced via gclient hook "mse_eme_conformance_tests".
-*.mp4
diff --git a/cobalt/demos/content/mse-eme-conformance-tests/media/car-20120827-85.mp4.sha1 b/cobalt/demos/content/mse-eme-conformance-tests/media/car-20120827-85.mp4.sha1
deleted file mode 100644
index 0b7d073..0000000
--- a/cobalt/demos/content/mse-eme-conformance-tests/media/car-20120827-85.mp4.sha1
+++ /dev/null
@@ -1 +0,0 @@
-525f4499f58ddd838c3290caccc8a9526dd3ce3b
\ No newline at end of file
diff --git a/cobalt/demos/content/mse-eme-conformance-tests/media/car-20120827-86.mp4.sha1 b/cobalt/demos/content/mse-eme-conformance-tests/media/car-20120827-86.mp4.sha1
deleted file mode 100644
index a3ed38f..0000000
--- a/cobalt/demos/content/mse-eme-conformance-tests/media/car-20120827-86.mp4.sha1
+++ /dev/null
@@ -1 +0,0 @@
-0dccf2486fa7d35503c574b31a0c9377aea2501b
\ No newline at end of file
diff --git a/cobalt/demos/content/mse-eme-conformance-tests/media/car-20120827-8b.mp4.sha1 b/cobalt/demos/content/mse-eme-conformance-tests/media/car-20120827-8b.mp4.sha1
deleted file mode 100644
index e89e1e8..0000000
--- a/cobalt/demos/content/mse-eme-conformance-tests/media/car-20120827-8b.mp4.sha1
+++ /dev/null
@@ -1 +0,0 @@
-6dbf8f08a19b54fb03e314d06514875ae7e935fd
\ No newline at end of file
diff --git a/cobalt/demos/content/mse-eme-conformance-tests/media/car-20120827-8c.mp4.sha1 b/cobalt/demos/content/mse-eme-conformance-tests/media/car-20120827-8c.mp4.sha1
deleted file mode 100644
index 96d89c6..0000000
--- a/cobalt/demos/content/mse-eme-conformance-tests/media/car-20120827-8c.mp4.sha1
+++ /dev/null
@@ -1 +0,0 @@
-2d0fc9b7d1db36c94eadad0c231bdae7a11b0d0d
\ No newline at end of file
diff --git a/cobalt/demos/content/mse-eme-conformance-tests/media/car-20120827-8d.mp4.sha1 b/cobalt/demos/content/mse-eme-conformance-tests/media/car-20120827-8d.mp4.sha1
deleted file mode 100644
index b92e419..0000000
--- a/cobalt/demos/content/mse-eme-conformance-tests/media/car-20120827-8d.mp4.sha1
+++ /dev/null
@@ -1 +0,0 @@
-e3d0abec59fc5b136faffcd052e351c16c147922
\ No newline at end of file
diff --git a/cobalt/demos/content/mse-eme-conformance-tests/media/car-audio-1MB-trunc.mp4.sha1 b/cobalt/demos/content/mse-eme-conformance-tests/media/car-audio-1MB-trunc.mp4.sha1
deleted file mode 100644
index 920fd78..0000000
--- a/cobalt/demos/content/mse-eme-conformance-tests/media/car-audio-1MB-trunc.mp4.sha1
+++ /dev/null
@@ -1 +0,0 @@
-1b24172d028d213e7d9133db3b52de52028736c0
\ No newline at end of file
diff --git a/cobalt/demos/content/mse-eme-conformance-tests/media/car_20130125_18.mp4.sha1 b/cobalt/demos/content/mse-eme-conformance-tests/media/car_20130125_18.mp4.sha1
deleted file mode 100644
index 9ab541e..0000000
--- a/cobalt/demos/content/mse-eme-conformance-tests/media/car_20130125_18.mp4.sha1
+++ /dev/null
@@ -1 +0,0 @@
-24fc78ce49cafb91b58fa60c35fe22a1d5b2cd2b
\ No newline at end of file
diff --git a/cobalt/demos/content/mse-eme-conformance-tests/media/car_cenc-20120827-85.mp4.sha1 b/cobalt/demos/content/mse-eme-conformance-tests/media/car_cenc-20120827-85.mp4.sha1
deleted file mode 100644
index e957caf..0000000
--- a/cobalt/demos/content/mse-eme-conformance-tests/media/car_cenc-20120827-85.mp4.sha1
+++ /dev/null
@@ -1 +0,0 @@
-978ef66d6cdf83a2474567d90402d25bf44b1582
\ No newline at end of file
diff --git a/cobalt/demos/content/mse-eme-conformance-tests/media/car_cenc-20120827-8b.mp4.sha1 b/cobalt/demos/content/mse-eme-conformance-tests/media/car_cenc-20120827-8b.mp4.sha1
deleted file mode 100644
index 509d199..0000000
--- a/cobalt/demos/content/mse-eme-conformance-tests/media/car_cenc-20120827-8b.mp4.sha1
+++ /dev/null
@@ -1 +0,0 @@
-f7c06a431837c2d1c323e04697369a8af0f797a9
\ No newline at end of file
diff --git a/cobalt/demos/content/mse-eme-conformance-tests/media/car_cenc-20120827-8c.mp4.sha1 b/cobalt/demos/content/mse-eme-conformance-tests/media/car_cenc-20120827-8c.mp4.sha1
deleted file mode 100644
index ae579ac..0000000
--- a/cobalt/demos/content/mse-eme-conformance-tests/media/car_cenc-20120827-8c.mp4.sha1
+++ /dev/null
@@ -1 +0,0 @@
-edc4888006af3d734cfc803976380f15b7c55d08
\ No newline at end of file
diff --git a/cobalt/demos/content/mse-eme-conformance-tests/media/car_cenc-20120827-8d.mp4.sha1 b/cobalt/demos/content/mse-eme-conformance-tests/media/car_cenc-20120827-8d.mp4.sha1
deleted file mode 100644
index fe20489..0000000
--- a/cobalt/demos/content/mse-eme-conformance-tests/media/car_cenc-20120827-8d.mp4.sha1
+++ /dev/null
@@ -1 +0,0 @@
-e26b56453f20e03cf114b9cba27c9c52a36bb451
\ No newline at end of file
diff --git a/cobalt/demos/content/mse-eme-conformance-tests/media/nq-frames23-tfdt24.mp4.sha1 b/cobalt/demos/content/mse-eme-conformance-tests/media/nq-frames23-tfdt24.mp4.sha1
deleted file mode 100644
index 77df480..0000000
--- a/cobalt/demos/content/mse-eme-conformance-tests/media/nq-frames23-tfdt24.mp4.sha1
+++ /dev/null
@@ -1 +0,0 @@
-0d35423701c8c864cf54af36182e517f68a783ec
\ No newline at end of file
diff --git a/cobalt/demos/content/mse-eme-conformance-tests/media/nq-frames24-tfdt23.mp4.sha1 b/cobalt/demos/content/mse-eme-conformance-tests/media/nq-frames24-tfdt23.mp4.sha1
deleted file mode 100644
index 1124439..0000000
--- a/cobalt/demos/content/mse-eme-conformance-tests/media/nq-frames24-tfdt23.mp4.sha1
+++ /dev/null
@@ -1 +0,0 @@
-a146da2c2d755cca39f1df4cecdc7afd4e3323f0
\ No newline at end of file
diff --git a/cobalt/demos/content/mse-eme-conformance-tests/media/oops_cenc-20121114-145-143.mp4.sha1 b/cobalt/demos/content/mse-eme-conformance-tests/media/oops_cenc-20121114-145-143.mp4.sha1
deleted file mode 100644
index c01deb9..0000000
--- a/cobalt/demos/content/mse-eme-conformance-tests/media/oops_cenc-20121114-145-143.mp4.sha1
+++ /dev/null
@@ -1 +0,0 @@
-422d426dd9ec7367054f7c84a75a4b54b7562693
\ No newline at end of file
diff --git a/cobalt/demos/content/mse-eme-conformance-tests/media/sintel-trunc.mp4.sha1 b/cobalt/demos/content/mse-eme-conformance-tests/media/sintel-trunc.mp4.sha1
deleted file mode 100644
index 0a539dd..0000000
--- a/cobalt/demos/content/mse-eme-conformance-tests/media/sintel-trunc.mp4.sha1
+++ /dev/null
@@ -1 +0,0 @@
-18b46d6d3b6b7815aa262d3868d5a33c5e8c6207
\ No newline at end of file
diff --git a/cobalt/demos/content/mse-eme-conformance-tests/media/test-video-1MB.mp4.sha1 b/cobalt/demos/content/mse-eme-conformance-tests/media/test-video-1MB.mp4.sha1
deleted file mode 100644
index 8a99c26..0000000
--- a/cobalt/demos/content/mse-eme-conformance-tests/media/test-video-1MB.mp4.sha1
+++ /dev/null
@@ -1 +0,0 @@
-d0389c8f3342147a515d8ea29ec7ceacf8a60a97
\ No newline at end of file
diff --git a/cobalt/demos/content/mse-eme-conformance-tests/style-20150612143746.css b/cobalt/demos/content/mse-eme-conformance-tests/style-20150612143746.css
deleted file mode 100644
index 31c3b52..0000000
--- a/cobalt/demos/content/mse-eme-conformance-tests/style-20150612143746.css
+++ /dev/null
@@ -1,191 +0,0 @@
-/*
-Copyright 2014 The Cobalt Authors. All Rights Reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-body {
- background: #fff;
- font-size: 10px;
- margin: 30px;
- width: 1280px;
-}
-
-textarea { color: #369; }
-
-h2 {
- margin: 10px 0 0 0;
-}
-
-h3 {
- margin: 10px 0 0 0;
-}
-
-.test-table .index { width: 30px; text-align: right; }
-.test-table .status {
- width: 20px;
- color: #fff;
- text-align: right;
-}
-
-.test-table .status_current {
- width: 20px;
- text-align: right;
-}
-
-.test-table .desc { width: 200px; }
-.test-table .state { width: 30px; text-align: center; }
-.test-table .failstate { width: 30px; text-align: center; }
-
-.test-table {
- width: 380px;
- text-align: left;
- margin: 0px;
- display: inline-block;
-}
-
-.test-table th {
- font-weight: bold;
- color: #039;
- padding: 10px 8px 4px;
-}
-
-.test-table td {
- font-size: 9px;
- color: #669;
- padding: 9px 8px 0;
-}
-
-.test-table td.failstate {
- font-size: 9px;
- color: #f00;
- padding: 9px 8px 0;
-}
-
-.test-table td.small {
- font-size: 6px;
- color: #0a0;
- padding: 9px 8px 0;
-}
-
-.compact-list .cell-category {
- font-weight: bold;
- color: #039;
-}
-
-.compact-list .cell-divider {
- width: 5px;
-}
-
-.compact-list .cell-status-normal {
- text-align: center;
-}
-
-.compact-list .cell-status-running {
- text-align: center;
-}
-
-.compact-list .cell-status-fail {
- background-color: #800;
- color: #FFF;
- text-align: center;
-}
-
-.compact-list .cell-status-pass {
- background-color: #080;
- color: #FFF;
- text-align: center;
-}
-
-.compact-list .test-status-none {
- width: 5px;
-}
-
-.compact-list .test-status-running {
- width: 5px;
- background-color: #880;
-}
-
-.compact-list .test-status-fail {
- width: 5px;
- background-color: #800;
-}
-
-.compact-list .test-status-pass {
- width: 5px;
- background-color: #080;
-}
-
-.compact-list .test-status-optional-fail {
- width: 3px;
- padding: 0px;
-}
-
-ul {
- padding-left: 20px;
- margin-top: 5px;
-}
-
-div.container {
- width:100%;
- margin:5px 0 5px 0;
- padding:5px 0 5px 0;
- overflow:hidden;
-}
-
-div.container_hidden {
- width:100%;
- margin:10px 0 10px 0;
- overflow:hidden;
- display:none;
-}
-
-.box-left {
- width:740px;
-}
-
-.box-right {
- width:420px;
-}
-
-.desc-expl-popup {
- position: absolute;
- padding: 0.5em;
- z-index: 100;
- display: none;
-}
-
-span.nowrap {
- display: inline-block;
- margin: 0px 10px 0px 0px;
- width: 200px;
-}
-
-span.code {
-}
-
-.rightmargin20 {
- margin-left: 0px;
- margin-right: 20px;
-}
-
-.rightmargin20new {
- margin-left: 0px;
- margin-right: 20px;
- color: #f00;
- font-weight: bold;
-}
-
-a.title_link {
- color: #000;
-}
diff --git a/cobalt/demos/content/soft-mic-platform-service-demo/soft_mic_platform_service_demo.html b/cobalt/demos/content/soft-mic-platform-service-demo/soft_mic_platform_service_demo.html
new file mode 100644
index 0000000..31bc511
--- /dev/null
+++ b/cobalt/demos/content/soft-mic-platform-service-demo/soft_mic_platform_service_demo.html
@@ -0,0 +1,136 @@
+<!--
+This is a light weighted demo page used to verify SoftMicPlatformService.
+Start a http server by running this python3 command in the directory
+cobalt/demos/content/soft-mic-platform-service/:
+python3 -m http.server 8000
+Then run in Cobalt using this command:
+out/linux-x64x11_debug/cobalt --url=http://localhost:8000/soft_mic_platform_service_demo.html
+-->
+<!DOCTYPE html>
+<meta charset="utf-8">
+<body>
+ <script>
+ 'use strict';
+ /**
+ * @param {ArrayBuffer} data to be converted to a String.
+ */
+ function ab2str(data) {
+ try {
+ return String.fromCharCode.apply(null, new Uint8Array(data));
+ } catch(error) {
+ console.error(`ab2str() error: ${error}, decoding data: ${data}`);
+ }
+ }
+
+ /**
+ * @param {String} data to be converted to an ArrayBuffer.
+ */
+ function str2ab(data) {
+ try {
+ return Uint8Array.from(data.split(''), (s) => {return s.charCodeAt(0)}).buffer;
+ } catch(error) {
+ console.error(`str2ab() error: ${error}, decoding data: ${data}`);
+ }
+ }
+
+ async function testSoftMicPlatformService() {
+ // Set to true once the service.send() calls are complete.
+ var service_send_done = false;
+
+ // These default boolean values represent the default assumption for
+ // platforms that do not implement the extension.
+ var has_soft_mic = true;
+ var has_hard_mic = false;
+
+ if (!H5vccPlatformService) {
+ // H5vccPlatformService is not implemented. Fallback to current Soft Mic
+ // implementation.
+ console.error("H5vccPlatformService is not implemented");
+ return;
+ }
+
+ var SOFT_MIC_SERVICE_NAME = "com.google.youtube.tv.SoftMic";
+
+ if (!H5vccPlatformService.has(SOFT_MIC_SERVICE_NAME)) {
+ // SOFT_MIC_SERVICE_NAME is not implemented. Fallback to current
+ // Soft Mic implementation.
+ console.error(`H5vccPlatformService.Has(${SOFT_MIC_SERVICE_NAME}) returned false.`);
+ return;
+ }
+
+ /**
+ * @param {ArrayBuffer} data
+ */
+ function receiveCallback(service, data) {
+ var str_response = ab2str(data);
+
+ try {
+ var response = JSON.parse(str_response);
+ has_hard_mic = response["hasHardMicSupport"];
+ has_soft_mic = response["hasSoftMicSupport"];
+ var mic_gesture = response["micGesture"];
+ console.log(`receiveCallback() response:\n
+ has_hard_mic: ${has_hard_mic}\n
+ has_soft_mic: ${has_soft_mic}\n
+ micGesture: ${mic_gesture}`);
+
+ // It is now safe to close the platform service.
+ if (service_send_done)
+ soft_mic_service.close();
+ } catch (error) {
+ console.error(`receiveCallback() error: ${error}, str_response: ${str_response}`);
+ }
+ }
+
+ // Open the service and pass the receive_callback.
+ var soft_mic_service = H5vccPlatformService.open(SOFT_MIC_SERVICE_NAME,
+ receiveCallback);
+
+ // Async web app message for "getMicSupport".
+ var get_mic_support_sync_response = soft_mic_service.send(str2ab(JSON.stringify("getMicSupport")));
+ try {
+ if (new Int8Array(get_mic_support_sync_response)[0])
+ console.log("getMicSupport send() platform response success.");
+ else
+ console.log("getMicSupport send() platform response failure.");
+ } catch (error) {
+ console.log(`Error in response from platform for getMicSupport: ${error}`);
+ }
+
+ // Test notifySearchActive send() and response from platform.
+ var notify_search_active_message = str2ab(JSON.stringify("notifySearchActive"));
+ var notify_search_active_response = soft_mic_service.send(notify_search_active_message);
+ try {
+ if (new Int8Array(notify_search_active_response)[0])
+ console.log("notifySearchActive send() platform response success.");
+ else
+ console.log("notifySearchActive send() platform response failure.");
+ } catch (error) {
+ console.log(`Error in response from platform for notifySearchActive: ${error}`);
+ }
+
+ // Test notifySearchInactive send() and response from platform.
+ var notify_search_inactive_message = str2ab(JSON.stringify("notifySearchInactive"));
+ var notify_search_inactive_response = soft_mic_service.send(notify_search_inactive_message);
+ try {
+ if (new Int8Array(notify_search_inactive_response)[0])
+ console.log("notifySearchInactive send() platform response success.");
+ else
+ console.log("notifySearchInactive send() platform response failure.");
+ } catch (error) {
+ console.log(`Error in response from platform for notifySearchInactive: ${error}`);
+ }
+
+ service_send_done = true;
+
+ // Close the service after a timeout. This is in case there is an error on
+ // the platform and a response is not received in the receiveCallback().
+ var TIME_BEFORE_CLOSE = 10000;
+ await new Promise(r => setTimeout(r, TIME_BEFORE_CLOSE));
+ soft_mic_service.close();
+ }
+
+ testSoftMicPlatformService();
+
+ </script>
+</body>
diff --git a/cobalt/dom/BUILD.gn b/cobalt/dom/BUILD.gn
index 04e1ce0..c594de0 100644
--- a/cobalt/dom/BUILD.gn
+++ b/cobalt/dom/BUILD.gn
@@ -358,6 +358,7 @@
"//cobalt/system_window",
"//cobalt/ui_navigation",
"//cobalt/web_animations",
+ "//cobalt/worker",
"//crypto",
"//nb",
"//net",
@@ -381,9 +382,97 @@
}
copy("licenses") {
+ install_content = true
license_path =
"licenses/platform/$cobalt_licenses_platform/licenses_cobalt.txt"
sources = [ "$static_contents_source_dir/$license_path" ]
outputs =
[ "$sb_static_contents_output_data_dir/licenses/licenses_cobalt.txt" ]
}
+
+target(gtest_target_type, "dom_test") {
+ testonly = true
+ has_pedantic_warnings = true
+
+ sources = [
+ "application_lifecycle_state_test.cc",
+ "blob_test.cc",
+ "comment_test.cc",
+ "crypto_test.cc",
+ "csp_delegate_test.cc",
+ "custom_event_test.cc",
+ "document_test.cc",
+ "document_type_test.cc",
+ "dom_implementation_test.cc",
+ "dom_parser_test.cc",
+ "dom_rect_list_test.cc",
+ "dom_string_map_test.cc",
+ "dom_token_list_test.cc",
+ "element_test.cc",
+ "error_event_test.cc",
+ "event_queue_test.cc",
+ "event_target_test.cc",
+ "event_test.cc",
+ "font_cache_test.cc",
+ "html_element_factory_test.cc",
+ "html_element_test.cc",
+ "html_link_element_test.cc",
+ "intersection_observer_test.cc",
+ "keyboard_event_test.cc",
+ "local_storage_database_test.cc",
+ "location_test.cc",
+ "media_query_list_test.cc",
+ "mutation_observer_test.cc",
+ "named_node_map_test.cc",
+ "navigator_licenses_test.cc",
+ "node_dispatch_event_test.cc",
+ "node_list_live_test.cc",
+ "node_list_test.cc",
+ "node_test.cc",
+ "on_screen_keyboard_test.cc",
+ "performance_observer_test.cc",
+ "performance_test.cc",
+ "rule_matching_test.cc",
+ "screen_test.cc",
+ "serializer_test.cc",
+ "storage_area_test.cc",
+ "text_test.cc",
+ "time_ranges_test.cc",
+ "url_utils_test.cc",
+ "user_agent_data_test.cc",
+ "window_test.cc",
+ "window_timers_test.cc",
+ "xml_document_test.cc",
+ ]
+
+ deps = [
+ ":dom",
+ ":dom_exception",
+ "//cobalt/base",
+ "//cobalt/browser:browser",
+ "//cobalt/browser:generated_bindings",
+ "//cobalt/browser:generated_types",
+ "//cobalt/csp",
+ "//cobalt/css_parser",
+ "//cobalt/cssom",
+ "//cobalt/cssom:cssom_test",
+ "//cobalt/dom/testing:dom_testing",
+ "//cobalt/dom_parser",
+ "//cobalt/h5vcc",
+ "//cobalt/loader",
+ "//cobalt/media_session",
+ "//cobalt/network_bridge",
+ "//cobalt/network_bridge",
+ "//cobalt/render_tree",
+ "//cobalt/script",
+ "//cobalt/script/v8c:engine",
+ "//cobalt/storage",
+ "//cobalt/storage/store:memory_store",
+ "//cobalt/test:run_all_unittests",
+ "//nb",
+ "//net:test_support",
+ "//testing/gmock",
+ "//testing/gtest",
+ "//url",
+ ]
+}
diff --git a/cobalt/dom/dom.gyp b/cobalt/dom/dom.gyp
index 4039e2c..c02e614 100644
--- a/cobalt/dom/dom.gyp
+++ b/cobalt/dom/dom.gyp
@@ -365,6 +365,7 @@
'<(DEPTH)/cobalt/system_window/system_window.gyp:system_window',
'<(DEPTH)/cobalt/ui_navigation/ui_navigation.gyp:ui_navigation',
'<(DEPTH)/cobalt/web_animations/web_animations.gyp:web_animations',
+ '<(DEPTH)/cobalt/worker/worker.gyp:worker',
'<(DEPTH)/nb/nb.gyp:nb',
'<(DEPTH)/net/net.gyp:net',
'<(DEPTH)/third_party/icu/icu.gyp:icuuc',
diff --git a/cobalt/dom/eme/media_key_session.cc b/cobalt/dom/eme/media_key_session.cc
index 71a1894..3eca031 100644
--- a/cobalt/dom/eme/media_key_session.cc
+++ b/cobalt/dom/eme/media_key_session.cc
@@ -17,7 +17,7 @@
#include <memory>
#include <type_traits>
-#include "base/polymorphic_downcast.h"
+#include "cobalt/base/polymorphic_downcast.h"
#include "cobalt/dom/dom_exception.h"
#include "cobalt/dom/dom_settings.h"
#include "cobalt/dom/eme/eme_helpers.h"
diff --git a/cobalt/dom/eme/media_key_status_map.cc b/cobalt/dom/eme/media_key_status_map.cc
index bbaa8db..88b21c2 100644
--- a/cobalt/dom/eme/media_key_status_map.cc
+++ b/cobalt/dom/eme/media_key_status_map.cc
@@ -15,7 +15,7 @@
#include "cobalt/dom/eme/media_key_status_map.h"
#include "base/logging.h"
-#include "base/polymorphic_downcast.h"
+#include "cobalt/base/polymorphic_downcast.h"
#include "cobalt/dom/dom_settings.h"
#include "cobalt/script/array_buffer.h"
#include "cobalt/script/array_buffer_view.h"
diff --git a/cobalt/dom/navigator.cc b/cobalt/dom/navigator.cc
index 55fbf20..6e1e73d 100644
--- a/cobalt/dom/navigator.cc
+++ b/cobalt/dom/navigator.cc
@@ -25,6 +25,7 @@
#include "cobalt/media_capture/media_devices.h"
#include "cobalt/media_session/media_session_client.h"
#include "cobalt/script/script_value_factory.h"
+#include "cobalt/worker/service_worker_container.h"
#include "starboard/configuration_constants.h"
#include "starboard/file.h"
#include "starboard/media.h"
@@ -156,6 +157,7 @@
plugins_(new PluginArray()),
media_devices_(
new media_capture::MediaDevices(settings, script_value_factory)),
+ service_worker_(new worker::ServiceWorkerContainer(script_value_factory)),
system_caption_settings_(captions),
script_value_factory_(script_value_factory) {}
@@ -234,6 +236,10 @@
return media_devices_;
}
+scoped_refptr<worker::ServiceWorkerContainer> Navigator::service_worker() {
+ return service_worker_;
+}
+
const scoped_refptr<MimeTypeArray>& Navigator::mime_types() const {
return mime_types_;
}
diff --git a/cobalt/dom/navigator.h b/cobalt/dom/navigator.h
index 29e68cd..3fdb68e 100644
--- a/cobalt/dom/navigator.h
+++ b/cobalt/dom/navigator.h
@@ -31,6 +31,7 @@
#include "cobalt/script/script_value_factory.h"
#include "cobalt/script/sequence.h"
#include "cobalt/script/wrappable.h"
+#include "cobalt/worker/service_worker_container.h"
namespace cobalt {
namespace dom {
@@ -71,6 +72,9 @@
// Web API: MediaDevices
scoped_refptr<media_capture::MediaDevices> media_devices();
+ // Web API: ServiceWorker
+ scoped_refptr<worker::ServiceWorkerContainer> service_worker();
+
const scoped_refptr<MimeTypeArray>& mime_types() const;
const scoped_refptr<PluginArray>& plugins() const;
@@ -135,6 +139,7 @@
scoped_refptr<PluginArray> plugins_;
scoped_refptr<cobalt::media_session::MediaSession> media_session_;
scoped_refptr<cobalt::media_capture::MediaDevices> media_devices_;
+ scoped_refptr<cobalt::worker::ServiceWorkerContainer> service_worker_;
scoped_refptr<cobalt::dom::captions::SystemCaptionSettings>
system_caption_settings_;
script::ScriptValueFactory* script_value_factory_;
diff --git a/cobalt/dom/testing/BUILD.gn b/cobalt/dom/testing/BUILD.gn
new file mode 100644
index 0000000..6cc8596
--- /dev/null
+++ b/cobalt/dom/testing/BUILD.gn
@@ -0,0 +1,49 @@
+# Copyright 2021 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+static_library("dom_testing") {
+ testonly = true
+ has_pedantic_warnings = true
+
+ sources = [
+ "gtest_workarounds.h",
+ "html_collection_testing.h",
+ "mock_event_listener.h",
+ "mock_layout_boxes.h",
+ "stub_css_parser.cc",
+ "stub_css_parser.h",
+ "stub_environment_settings.h",
+ "stub_script_runner.cc",
+ "stub_script_runner.h",
+ "stub_window.h",
+ ]
+
+ deps = [
+ "//base",
+ "//base/test:test_support",
+ "//cobalt/base",
+ "//cobalt/browser",
+ "//cobalt/browser:bindings",
+ "//cobalt/css_parser",
+ "//cobalt/cssom",
+ "//cobalt/dom",
+ "//cobalt/dom:dom_exception",
+ "//cobalt/dom_parser",
+ "//cobalt/loader",
+ "//cobalt/script",
+ "//testing/gmock",
+ "//testing/gtest",
+ "//url",
+ ]
+}
diff --git a/cobalt/dom_parser/BUILD.gn b/cobalt/dom_parser/BUILD.gn
index f4f4c20..e4e4256 100644
--- a/cobalt/dom_parser/BUILD.gn
+++ b/cobalt/dom_parser/BUILD.gn
@@ -40,3 +40,23 @@
"//third_party/protobuf:protobuf_lite",
]
}
+
+target(gtest_target_type, "dom_parser_test") {
+ testonly = true
+ has_pedantic_warnings = true
+
+ sources = [
+ "html_decoder_test.cc",
+ "xml_decoder_test.cc",
+ ]
+
+ deps = [
+ ":dom_parser",
+ "//cobalt/dom",
+ "//cobalt/dom/testing:dom_testing",
+ "//cobalt/loader",
+ "//cobalt/test:run_all_unittests",
+ "//testing/gmock",
+ "//testing/gtest",
+ ]
+}
diff --git a/cobalt/encoding/BUILD.gn b/cobalt/encoding/BUILD.gn
index adb6e89..708a621 100644
--- a/cobalt/encoding/BUILD.gn
+++ b/cobalt/encoding/BUILD.gn
@@ -30,3 +30,24 @@
"//third_party/icu:icuuc",
]
}
+
+target(gtest_target_type, "text_encoding_test") {
+ testonly = true
+ has_pedantic_warnings = true
+
+ sources = [
+ "text_decoder_test.cc",
+ "text_encoder_test.cc",
+ ]
+
+ deps = [
+ ":text_encoding",
+ "//cobalt/base",
+ "//cobalt/dom",
+ "//cobalt/dom/testing:dom_testing",
+ "//cobalt/script",
+ "//cobalt/test:run_all_unittests",
+ "//testing/gmock",
+ "//testing/gtest",
+ ]
+}
diff --git a/cobalt/encoding/text_decoder.cc b/cobalt/encoding/text_decoder.cc
index b6abd56..1118c0f 100644
--- a/cobalt/encoding/text_decoder.cc
+++ b/cobalt/encoding/text_decoder.cc
@@ -143,7 +143,7 @@
if (!do_not_flush_) {
bom_seen_ = false;
}
- if (!ignore_bom_ && !bom_seen_) {
+ if (!ignore_bom_ && !bom_seen_ && length) {
bom_seen_ = true;
if (!RemoveBOM(start, length, exception_state)) {
return;
diff --git a/cobalt/encoding/text_decoder_test.cc b/cobalt/encoding/text_decoder_test.cc
index 282a8d7..956a277 100644
--- a/cobalt/encoding/text_decoder_test.cc
+++ b/cobalt/encoding/text_decoder_test.cc
@@ -89,6 +89,7 @@
.Times(0);
scoped_refptr<TextDecoder> text_decoder_ = new TextDecoder(&exception_state_);
std::vector<std::pair<std::vector<uint8>, std::string>> tests = {
+ {{}, ""},
{{72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33},
"Hello world!"},
{{72, 101, 106, 33, 32, 208, 159, 209, 128, 208, 184, 208, 178, 208,
diff --git a/cobalt/extension/BUILD.gn b/cobalt/extension/BUILD.gn
new file mode 100644
index 0000000..2e1723b
--- /dev/null
+++ b/cobalt/extension/BUILD.gn
@@ -0,0 +1,30 @@
+# Copyright 2021 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+target(gtest_target_type, "extension_test") {
+ testonly = true
+ has_pedantic_warnings = true
+ sources = [ "extension_test.cc" ]
+ deps = [
+ "//cobalt/base",
+ "//cobalt/test:run_all_unittests",
+ "//starboard",
+ "//testing/gmock",
+ "//testing/gtest",
+ ]
+ if (sb_is_evergreen) {
+ deps += cobalt_platform_dependencies
+ }
+ content_deps = [ "//third_party/icu:icudata" ]
+}
diff --git a/cobalt/extension/extension_test.cc b/cobalt/extension/extension_test.cc
index 43b4925..b7c0602 100644
--- a/cobalt/extension/extension_test.cc
+++ b/cobalt/extension/extension_test.cc
@@ -18,6 +18,7 @@
#include "cobalt/extension/crash_handler.h"
#include "cobalt/extension/cwrappers.h"
#include "cobalt/extension/font.h"
+#include "cobalt/extension/free_space.h"
#include "cobalt/extension/graphics.h"
#include "cobalt/extension/installation_manager.h"
#include "cobalt/extension/javascript_cache.h"
@@ -349,5 +350,24 @@
<< "Extension struct should be a singleton";
}
+TEST(ExtensionTest, FreeSpace) {
+ typedef CobaltExtensionFreeSpaceApi ExtensionApi;
+ const char* kExtensionName = kCobaltExtensionFreeSpaceName;
+
+ const ExtensionApi* extension_api =
+ static_cast<const ExtensionApi*>(SbSystemGetExtension(kExtensionName));
+ if (!extension_api) {
+ return;
+ }
+
+ EXPECT_STREQ(extension_api->name, kExtensionName);
+ EXPECT_EQ(extension_api->version, 1u);
+ EXPECT_NE(extension_api->MeasureFreeSpace, nullptr);
+
+ const ExtensionApi* second_extension_api =
+ static_cast<const ExtensionApi*>(SbSystemGetExtension(kExtensionName));
+ EXPECT_EQ(second_extension_api, extension_api)
+ << "Extension struct should be a singleton";
+}
} // namespace extension
} // namespace cobalt
diff --git a/cobalt/extension/free_space.h b/cobalt/extension/free_space.h
new file mode 100644
index 0000000..53d466b
--- /dev/null
+++ b/cobalt/extension/free_space.h
@@ -0,0 +1,50 @@
+// Copyright 2022 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+#ifndef COBALT_EXTENSION_FREE_SPACE_H_
+#define COBALT_EXTENSION_FREE_SPACE_H_
+
+#include <stdint.h>
+
+#include "starboard/system.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+#define kCobaltExtensionFreeSpaceName "dev.cobalt.extension.FreeSpace"
+
+typedef struct CobaltExtensionFreeSpaceApi {
+ // Name should be the string |kCobaltExtensionFreeSpaceName|.
+ // This helps to validate that the extension API is correct.
+ const char* name;
+
+ // This specifies the version of the API that is implemented.
+ uint32_t version;
+
+ // The fields below this point were added in version 1 or later.
+
+ // Returns the free space in bytes for the provided |system_path_id|.
+ // If there is no implementation for the that |system_path_id| or
+ // if there was an error -1 is returned.
+ int64_t (*MeasureFreeSpace)(SbSystemPathId system_path_id);
+} CobaltExtensionFreeSpaceApi;
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // COBALT_EXTENSION_FREE_SPACE_H_
diff --git a/cobalt/h5vcc/BUILD.gn b/cobalt/h5vcc/BUILD.gn
index b914711..96de253 100644
--- a/cobalt/h5vcc/BUILD.gn
+++ b/cobalt/h5vcc/BUILD.gn
@@ -111,7 +111,6 @@
"h5vcc_updater.cc",
"h5vcc_updater.h",
]
- # TODO(b/211447021): Migrate //cobalt/updater
- # deps += [ "//cobalt/updater" ]
+ deps += [ "//cobalt/updater" ]
}
}
diff --git a/cobalt/h5vcc/h5vcc.cc b/cobalt/h5vcc/h5vcc.cc
index 196c708..e1e9e04 100644
--- a/cobalt/h5vcc/h5vcc.cc
+++ b/cobalt/h5vcc/h5vcc.cc
@@ -28,6 +28,9 @@
runtime_ = new H5vccRuntime(settings.event_dispatcher);
settings_ =
new H5vccSettings(settings.media_module, settings.network_module,
+#if SB_IS(EVERGREEN)
+ settings.updater_module,
+#endif
settings.user_agent_data, settings.global_environment);
#if defined(COBALT_ENABLE_SSO)
sso_ = new H5vccSso();
diff --git a/cobalt/h5vcc/h5vcc_settings.cc b/cobalt/h5vcc/h5vcc_settings.cc
index 2607bed..83bf08b 100644
--- a/cobalt/h5vcc/h5vcc_settings.cc
+++ b/cobalt/h5vcc/h5vcc_settings.cc
@@ -21,18 +21,29 @@
H5vccSettings::H5vccSettings(media::MediaModule* media_module,
cobalt::network::NetworkModule* network_module,
+#if SB_IS(EVERGREEN)
+ cobalt::updater::UpdaterModule* updater_module,
+#endif
dom::NavigatorUAData* user_agent_data,
script::GlobalEnvironment* global_environment)
: media_module_(media_module),
network_module_(network_module),
+#if SB_IS(EVERGREEN)
+ updater_module_(updater_module),
+#endif
user_agent_data_(user_agent_data),
- global_environment_(global_environment) {}
+ global_environment_(global_environment) {
+}
bool H5vccSettings::Set(const std::string& name, int32 value) const {
const char kMediaPrefix[] = "Media.";
const char kNavigatorUAData[] = "NavigatorUAData";
const char kQUIC[] = "QUIC";
+#if SB_IS(EVERGREEN)
+ const char kUpdaterMinFreeSpaceBytes[] = "Updater.MinFreeSpaceBytes";
+#endif
+
if (name.compare(kMediaPrefix) == 0) {
return media_module_ ? media_module_->SetConfiguration(name, value) : false;
}
@@ -51,6 +62,12 @@
}
}
+#if SB_IS(EVERGREEN)
+ if (name.compare(kUpdaterMinFreeSpaceBytes) == 0) {
+ updater_module_->SetMinFreeSpaceBytes(value);
+ return true;
+ }
+#endif
return false;
}
diff --git a/cobalt/h5vcc/h5vcc_settings.h b/cobalt/h5vcc/h5vcc_settings.h
index ec4d4c1..086a524 100644
--- a/cobalt/h5vcc/h5vcc_settings.h
+++ b/cobalt/h5vcc/h5vcc_settings.h
@@ -23,6 +23,10 @@
#include "cobalt/script/global_environment.h"
#include "cobalt/script/wrappable.h"
+#if SB_IS(EVERGREEN)
+#include "cobalt/updater/updater_module.h"
+#endif
+
namespace cobalt {
namespace h5vcc {
@@ -33,6 +37,9 @@
public:
explicit H5vccSettings(media::MediaModule* media_module,
cobalt::network::NetworkModule* network_module,
+#if SB_IS(EVERGREEN)
+ cobalt::updater::UpdaterModule* updater_module,
+#endif
dom::NavigatorUAData* user_agent_data,
script::GlobalEnvironment* global_environment);
@@ -46,6 +53,9 @@
private:
media::MediaModule* media_module_;
cobalt::network::NetworkModule* network_module_ = nullptr;
+#if SB_IS(EVERGREEN)
+ cobalt::updater::UpdaterModule* updater_module_ = nullptr;
+#endif
dom::NavigatorUAData* user_agent_data_;
script::GlobalEnvironment* global_environment_;
diff --git a/cobalt/h5vcc/h5vcc_trace_event.cc b/cobalt/h5vcc/h5vcc_trace_event.cc
index 5e27555..a7c509e 100644
--- a/cobalt/h5vcc/h5vcc_trace_event.cc
+++ b/cobalt/h5vcc/h5vcc_trace_event.cc
@@ -14,6 +14,8 @@
#include "cobalt/h5vcc/h5vcc_trace_event.h"
+#include "base/files/file_util.h"
+
namespace cobalt {
namespace h5vcc {
@@ -29,17 +31,31 @@
} else {
base::FilePath output_filepath(
output_filename.empty() ? kOutputTraceFilename : output_filename);
+ last_absolute_path_.clear();
trace_to_file_.reset(new trace_event::ScopedTraceToFile(output_filepath));
}
}
void H5vccTraceEvent::Stop() {
if (trace_to_file_) {
+ last_absolute_path_ = trace_to_file_->absolute_output_path();
trace_to_file_.reset();
} else {
DLOG(WARNING) << "H5vccTraceEvent is already stopped.";
}
}
+std::string H5vccTraceEvent::Read() {
+ if (trace_to_file_) {
+ Stop();
+ }
+ std::string trace;
+ if (!last_absolute_path_.empty()) {
+ ReadFileToString(last_absolute_path_, &trace);
+ }
+ return trace;
+}
+
+
} // namespace h5vcc
} // namespace cobalt
diff --git a/cobalt/h5vcc/h5vcc_trace_event.h b/cobalt/h5vcc/h5vcc_trace_event.h
index c1f2f8f..9621448 100644
--- a/cobalt/h5vcc/h5vcc_trace_event.h
+++ b/cobalt/h5vcc/h5vcc_trace_event.h
@@ -51,6 +51,7 @@
void Start(const std::string& output_filename);
void Stop();
+ std::string Read();
TRACE_EVENT0_FOR_EACH(DEFINE_H5VCC_TRACE_EVENT0)
TRACE_EVENT1_FOR_EACH(DEFINE_H5VCC_TRACE_EVENT1)
@@ -62,6 +63,8 @@
// While initialized, it means that a trace is on-going.
std::unique_ptr<trace_event::ScopedTraceToFile> trace_to_file_;
+ base::FilePath last_absolute_path_;
+
DISALLOW_COPY_AND_ASSIGN(H5vccTraceEvent);
};
diff --git a/cobalt/h5vcc/h5vcc_trace_event.idl b/cobalt/h5vcc/h5vcc_trace_event.idl
index 9fca122..3e1a23c 100644
--- a/cobalt/h5vcc/h5vcc_trace_event.idl
+++ b/cobalt/h5vcc/h5vcc_trace_event.idl
@@ -19,6 +19,7 @@
// will be used.
void start(optional DOMString output_filename = "");
void stop();
+ DOMString read();
void traceBegin(DOMString category, DOMString name);
void traceEnd(DOMString category, DOMString name);
diff --git a/cobalt/layout_tests/BUILD.gn b/cobalt/layout_tests/BUILD.gn
new file mode 100644
index 0000000..01eb9e0
--- /dev/null
+++ b/cobalt/layout_tests/BUILD.gn
@@ -0,0 +1,87 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+static_library("layout_test_utils") {
+ testonly = true
+ has_pedantic_warnings = true
+
+ sources = [
+ "layout_snapshot.cc",
+ "layout_snapshot.h",
+ "test_parser.cc",
+ "test_parser.h",
+ "test_utils.cc",
+ "test_utils.h",
+ "web_platform_test_parser.cc",
+ "web_platform_test_parser.h",
+ ]
+
+ deps = [
+ "//base/test:test_support",
+ "//cobalt/base",
+ "//cobalt/browser",
+ "//cobalt/cssom",
+ "//cobalt/layout_tests/testdata:layout_copy_test_data",
+ "//cobalt/math",
+ "//cobalt/network",
+ "//cobalt/render_tree",
+ "//cobalt/script",
+ "//net",
+ "//starboard:starboard_headers_only",
+ "//url",
+ ]
+}
+
+target(gtest_target_type, "layout_tests") {
+ testonly = true
+ has_pedantic_warnings = true
+
+ sources = [ "layout_tests.cc" ]
+
+ deps = [
+ ":layout_test_utils",
+ "//cobalt/base",
+ "//cobalt/browser",
+ "//cobalt/cssom",
+ "//cobalt/math",
+ "//cobalt/render_tree:animations",
+ "//cobalt/renderer:render_tree_pixel_tester",
+ "//cobalt/renderer/backend:renderer_backend",
+ "//cobalt/script",
+ "//cobalt/test:run_all_unittests",
+ "//testing/gtest",
+ "//url",
+ ]
+}
+
+target(gtest_target_type, "web_platform_tests") {
+ testonly = true
+ has_pedantic_warnings = true
+
+ sources = [ "web_platform_tests.cc" ]
+
+ deps = [
+ ":layout_test_utils",
+ "//cobalt/base",
+ "//cobalt/browser",
+ "//cobalt/cssom",
+ "//cobalt/math",
+ "//cobalt/media",
+ "//cobalt/network",
+ "//cobalt/render_tree",
+ "//cobalt/test:run_all_unittests",
+ "//testing/gtest",
+ "//url",
+ ]
+}
diff --git a/cobalt/layout_tests/testdata/BUILD.gn b/cobalt/layout_tests/testdata/BUILD.gn
new file mode 100644
index 0000000..1c8dbad
--- /dev/null
+++ b/cobalt/layout_tests/testdata/BUILD.gn
@@ -0,0 +1,1779 @@
+# Copyright 2021 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+copy("layout_copy_test_data") {
+ sources = [
+ "animation-timing/5-animation-frame-callback-is-called-before-layout-occurs-expected.png",
+ "animation-timing/5-animation-frame-callback-is-called-before-layout-occurs.html",
+ "animation-timing/layout_tests.txt",
+ "bidi/bidi-paragraphs-should-maintain-proper-ordering-when-split-across-multiple-lines-expected.png",
+ "bidi/bidi-paragraphs-should-maintain-proper-ordering-when-split-across-multiple-lines.html",
+ "bidi/containing-block-should-not-inherit-directionality-from-nested-block-box-expected.png",
+ "bidi/containing-block-should-not-inherit-directionality-from-nested-block-box.html",
+ "bidi/directional-stack-should-be-restored-following-nested-paragraph-expected.png",
+ "bidi/directional-stack-should-be-restored-following-nested-paragraph.html",
+ "bidi/inline-blocks-should-have-a-neutral-direction-within-containing-paragraph-and-open-nested-paragraph-expected.png",
+ "bidi/inline-blocks-should-have-a-neutral-direction-within-containing-paragraph-and-open-nested-paragraph.html",
+ "bidi/inline-blocks-should-not-impact-directionality-of-containing-block-expected.png",
+ "bidi/inline-blocks-should-not-impact-directionality-of-containing-block.html",
+ "bidi/inline-container-blocks-should-not-impact-directionality-of-containing-block-expected.png",
+ "bidi/inline-container-blocks-should-not-impact-directionality-of-containing-block.html",
+ "bidi/inline-container-boxes-should-continue-active-paragraph-expected.png",
+ "bidi/inline-container-boxes-should-continue-active-paragraph.html",
+ "bidi/layout_tests.txt",
+ "bidi/line-boxes-should-use-containing-block-direction-for-inline-base-direction-expected.png",
+ "bidi/line-boxes-should-use-containing-block-direction-for-inline-base-direction.html",
+ "bidi/mirror-characters-should-be-reversed-in-rtl-expected.png",
+ "bidi/mirror-characters-should-be-reversed-in-rtl.html",
+ "bidi/nested-block-boxes-should-close-active-paragraph-expected.png",
+ "bidi/nested-block-boxes-should-close-active-paragraph.html",
+ "bidi/nested-block-boxes-should-inherit-directionality-from-containing-block-expected.png",
+ "bidi/nested-block-boxes-should-inherit-directionality-from-containing-block.html",
+ "bidi/numbers-should-have-weak-directionality-1-expected.png",
+ "bidi/numbers-should-have-weak-directionality-1.html",
+ "bidi/numbers-should-have-weak-directionality-2-expected.png",
+ "bidi/numbers-should-have-weak-directionality-2.html",
+ "bidi/numbers-should-have-weak-directionality-3-expected.png",
+ "bidi/numbers-should-have-weak-directionality-3.html",
+ "bidi/paragraph-should-maintain-directional-stack-with-nested-inline-container-blocks-expected.png",
+ "bidi/paragraph-should-maintain-directional-stack-with-nested-inline-container-blocks.html",
+ "bidi/whitespace-should-be-handled-properly-with-bidirectional-text-expected.png",
+ "bidi/whitespace-should-be-handled-properly-with-bidirectional-text.html",
+ "cluster-fuzz/fuzz-222-expected.png",
+ "cluster-fuzz/fuzz-222.html",
+ "cluster-fuzz/layout_tests.txt",
+ "cobalt-pixel/aliasing-solid-borders-expected.png",
+ "cobalt-pixel/aliasing-solid-borders.html",
+ "cobalt-pixel/aliasing-solid-color-expected.png",
+ "cobalt-pixel/aliasing-solid-color.html",
+ "cobalt-pixel/aliasing-texture-expected.png",
+ "cobalt-pixel/aliasing-texture.html",
+ "cobalt-pixel/layout_tests.txt",
+ "cobalt/100-dynamically-created-nested-elements-expected.png",
+ "cobalt/100-dynamically-created-nested-elements.html",
+ "cobalt/100-nested-elements-expected.png",
+ "cobalt/100-nested-elements.html",
+ "cobalt/README.txt",
+ "cobalt/block-and-inline-block-display-expected.png",
+ "cobalt/block-and-inline-block-display.html",
+ "cobalt/btoa-with-null-char-expected.png",
+ "cobalt/btoa-with-null-char.html",
+ "cobalt/changing-css-text-triggers-layout-expected.png",
+ "cobalt/changing-css-text-triggers-layout.html",
+ "cobalt/cobalt-oxide-expected.png",
+ "cobalt/console-trace-should-not-crash-expected.png",
+ "cobalt/console-trace-should-not-crash.html",
+ "cobalt/console-trace-should-not-crash.js",
+ "cobalt/div-with-border-of-non-integer-size-expected.png",
+ "cobalt/div-with-border-of-non-integer-size.html",
+ "cobalt/divs-with-background-color-and-text-expected.png",
+ "cobalt/divs-with-background-color-and-text.html",
+ "cobalt/fixed-width-divs-with-background-color-expected.png",
+ "cobalt/fixed-width-divs-with-background-color.html",
+ "cobalt/font-weight-expected.png",
+ "cobalt/font-weight.html",
+ "cobalt/image-cleanup-expected.png",
+ "cobalt/image-cleanup.html",
+ "cobalt/image-from-blob-expected.png",
+ "cobalt/image-from-blob.html",
+ "cobalt/image-onerror-expected.png",
+ "cobalt/image-onerror.html",
+ "cobalt/inline-box-with-overflow-words-expected.png",
+ "cobalt/inline-box-with-overflow-words.html",
+ "cobalt/inline-style-allowed-while-cloning-objects-expected.png",
+ "cobalt/inline-style-allowed-while-cloning-objects.html",
+ "cobalt/interface-object-types-are-correct-expected.png",
+ "cobalt/interface-object-types-are-correct.html",
+ "cobalt/layout_tests.txt",
+ "cobalt/onload_event_fired_even_though_link_file_does_not_exist-expected.png",
+ "cobalt/onload_event_fired_even_though_link_file_does_not_exist.html",
+ "cobalt/performance-spike-header-buttons.html",
+ "cobalt/platform-object-user-properties-survive-gc-expected.png",
+ "cobalt/platform-object-user-properties-survive-gc.html",
+ "cobalt/positioned-boxes-with-same-z-index-are-processed-in-insertion-order-expected.png",
+ "cobalt/positioned-boxes-with-same-z-index-are-processed-in-insertion-order.html",
+ "cobalt/relative-font-size-expected.png",
+ "cobalt/relative-font-size.html",
+ "cobalt/repro-27290784-expected.png",
+ "cobalt/repro-27290784.html",
+ "cobalt/screenshot-expected.png",
+ "cobalt/screenshot-with-animation-expected.png",
+ "cobalt/screenshot-with-animation.html",
+ "cobalt/screenshot.html",
+ "cobalt/simple-transform-stacked-expected.png",
+ "cobalt/simple-transform-stacked.html",
+ "cobalt/simple-transform-text-expected.png",
+ "cobalt/simple-transform-text.html",
+ "cobalt/support/MEgalopolisExtra.woff2",
+ "cobalt/support/tcu-font.woff",
+ "cobalt/transform-translate-with-em-units-expected.png",
+ "cobalt/transform-translate-with-em-units.html",
+ "cobalt/transform-with-background-color-expected.png",
+ "cobalt/transform-with-background-color.html",
+ "cobalt/url-utils-interfaces-expected.png",
+ "cobalt/url-utils-interfaces.html",
+ "cobalt/user-agent-style-sheet-display-expected.png",
+ "cobalt/user-agent-style-sheet-display.html",
+ "cobalt/user-agent-test-expected.png",
+ "cobalt/user-agent-test.html",
+ "cobalt/window-onerror-expected.png",
+ "cobalt/window-onerror.html",
+ "cobalt/woff2-decoding-expected.png",
+ "cobalt/woff2-decoding.html",
+ "csp/img-src-expected.png",
+ "csp/img-src.html",
+ "csp/layout_tests.txt",
+ "css-2-1/10-1-absolute-elements-inherit-style-from-direct-parent-expected.png",
+ "css-2-1/10-1-absolute-elements-inherit-style-from-direct-parent.html",
+ "css-2-1/10-1-absolute-positioned-elements-are-positioned-relative-to-parent-element-flow-expected.png",
+ "css-2-1/10-1-absolute-positioned-elements-are-positioned-relative-to-parent-element-flow.html",
+ "css-2-1/10-1-absolute-positioned-elements-are-positioned-relative-to-parent-inline-box-expected.png",
+ "css-2-1/10-1-absolute-positioned-elements-are-positioned-relative-to-parent-inline-box.html",
+ "css-2-1/10-1-absolute-positioned-elements-compute-used-values-from-containing-block-expected.png",
+ "css-2-1/10-1-absolute-positioned-elements-compute-used-values-from-containing-block.html",
+ "css-2-1/10-1-absolute-positioned-elements-container-block-is-absolute-positioned-ancestor-expected.png",
+ "css-2-1/10-1-absolute-positioned-elements-container-block-is-absolute-positioned-ancestor.html",
+ "css-2-1/10-1-absolute-positioned-elements-do-not-effect-containing-block-size-expected.png",
+ "css-2-1/10-1-absolute-positioned-elements-do-not-effect-containing-block-size.html",
+ "css-2-1/10-1-containing-block-above-stacking-context-should-be-padding-edge-for-absolute-positioned-elements-expected.png",
+ "css-2-1/10-1-containing-block-above-stacking-context-should-be-padding-edge-for-absolute-positioned-elements.html",
+ "css-2-1/10-1-containing-block-should-be-ancestor-padding-edge-for-absolutely-positioned-elements-expected.png",
+ "css-2-1/10-1-containing-block-should-be-ancestor-padding-edge-for-absolutely-positioned-elements.html",
+ "css-2-1/10-1-containing-block-should-be-ancestor-padding-edge-for-fixed-positioned-elements-expected.png",
+ "css-2-1/10-1-containing-block-should-be-ancestor-padding-edge-for-fixed-positioned-elements.html",
+ "css-2-1/10-1-containing-block-should-be-ancestor-padding-edge-for-percentage-of-absolutely-positioned-elements-expected.png",
+ "css-2-1/10-1-containing-block-should-be-ancestor-padding-edge-for-percentage-of-absolutely-positioned-elements.html",
+ "css-2-1/10-1-non-positioned-stacking-context-above-containing-block-should-apply-proper-offset-to-children-expected.png",
+ "css-2-1/10-1-non-positioned-stacking-context-above-containing-block-should-apply-proper-offset-to-children.html",
+ "css-2-1/10-1-positioned-stacking-context-above-fixed-containing-block-should-apply-proper-offset-to-children-expected.png",
+ "css-2-1/10-1-positioned-stacking-context-above-fixed-containing-block-should-apply-proper-offset-to-children.html",
+ "css-2-1/10-3-1-auto-margin-should-become-zero-in-inline-non-replaced-elements-expected.png",
+ "css-2-1/10-3-1-auto-margin-should-become-zero-in-inline-non-replaced-elements.html",
+ "css-2-1/10-3-1-width-should-not-apply-to-inline-non-replaced-elements-expected.png",
+ "css-2-1/10-3-1-width-should-not-apply-to-inline-non-replaced-elements.html",
+ "css-2-1/10-3-2-auto-margin-should-become-zero-in-inline-replaced-elements-expected.png",
+ "css-2-1/10-3-2-auto-margin-should-become-zero-in-inline-replaced-elements.html",
+ "css-2-1/10-3-2-replaced-box-width-expected.png",
+ "css-2-1/10-3-2-replaced-box-width.html",
+ "css-2-1/10-3-3-auto-margin-left-and-right-should-center-element-horizontally-expected.png",
+ "css-2-1/10-3-3-auto-margin-left-and-right-should-center-element-horizontally.html",
+ "css-2-1/10-3-3-auto-width-should-zero-other-autos-expected.png",
+ "css-2-1/10-3-3-auto-width-should-zero-other-autos.html",
+ "css-2-1/10-3-3-correct-margin-is-ignored-if-overconstrained-expected.png",
+ "css-2-1/10-3-3-correct-margin-is-ignored-if-overconstrained-rtl-expected.png",
+ "css-2-1/10-3-3-correct-margin-is-ignored-if-overconstrained-rtl.html",
+ "css-2-1/10-3-3-correct-margin-is-ignored-if-overconstrained.html",
+ "css-2-1/10-3-3-margin-auto-should-be-treated-as-zero-if-sum-is-greater-than-containing-block-width-expected.png",
+ "css-2-1/10-3-3-margin-auto-should-be-treated-as-zero-if-sum-is-greater-than-containing-block-width.html",
+ "css-2-1/10-3-3-one-auto-should-follow-from-equality-expected.png",
+ "css-2-1/10-3-3-one-auto-should-follow-from-equality.html",
+ "css-2-1/10-3-3-overconstrained-should-respect-direction-expected.png",
+ "css-2-1/10-3-3-overconstrained-should-respect-direction.html",
+ "css-2-1/10-3-4-block-level-replaced-box-margins-should-be-calculated-as-for-non-replaced-box-expected.png",
+ "css-2-1/10-3-4-block-level-replaced-box-margins-should-be-calculated-as-for-non-replaced-box.html",
+ "css-2-1/10-3-4-block-level-replaced-box-width-should-be-calculated-as-for-inline-replaced-box-expected.png",
+ "css-2-1/10-3-4-block-level-replaced-box-width-should-be-calculated-as-for-inline-replaced-box.html",
+ "css-2-1/10-3-7-absolute-element-children-should-shrink-to-fit-expected.png",
+ "css-2-1/10-3-7-absolute-element-children-should-shrink-to-fit-rtl-expected.png",
+ "css-2-1/10-3-7-absolute-element-children-should-shrink-to-fit-rtl.html",
+ "css-2-1/10-3-7-absolute-element-children-should-shrink-to-fit.html",
+ "css-2-1/10-3-7-absolute-element-children-with-br-elements-should-shrink-to-fit-expected.png",
+ "css-2-1/10-3-7-absolute-element-children-with-br-elements-should-shrink-to-fit-rtl-expected.png",
+ "css-2-1/10-3-7-absolute-element-children-with-br-elements-should-shrink-to-fit-rtl.html",
+ "css-2-1/10-3-7-absolute-element-children-with-br-elements-should-shrink-to-fit.html",
+ "css-2-1/10-3-7-absolute-position-elements-solve-for-height-when-top-and-bottom-are-specified-expected.png",
+ "css-2-1/10-3-7-absolute-position-elements-solve-for-height-when-top-and-bottom-are-specified.html",
+ "css-2-1/10-3-7-absolute-position-elements-solve-for-width-when-left-and-right-are-specified-expected.png",
+ "css-2-1/10-3-7-absolute-position-elements-solve-for-width-when-left-and-right-are-specified.html",
+ "css-2-1/10-3-7-blockification-should-not-affect-static-position-expected.png",
+ "css-2-1/10-3-7-blockification-should-not-affect-static-position-rtl-expected.png",
+ "css-2-1/10-3-7-blockification-should-not-affect-static-position-rtl.html",
+ "css-2-1/10-3-7-blockification-should-not-affect-static-position.html",
+ "css-2-1/10-3-7-left-and-top-position-absolute-elements-relative-to-their-containing-block-expected.png",
+ "css-2-1/10-3-7-left-and-top-position-absolute-elements-relative-to-their-containing-block-rtl-expected.png",
+ "css-2-1/10-3-7-left-and-top-position-absolute-elements-relative-to-their-containing-block-rtl.html",
+ "css-2-1/10-3-7-left-and-top-position-absolute-elements-relative-to-their-containing-block.html",
+ "css-2-1/10-3-7-left-and-width-and-right-are-auto-expected.png",
+ "css-2-1/10-3-7-left-and-width-and-right-are-auto-in-inline-element-expected.png",
+ "css-2-1/10-3-7-left-and-width-and-right-are-auto-in-inline-element-rtl-expected.png",
+ "css-2-1/10-3-7-left-and-width-and-right-are-auto-in-inline-element-rtl.html",
+ "css-2-1/10-3-7-left-and-width-and-right-are-auto-in-inline-element.html",
+ "css-2-1/10-3-7-left-and-width-and-right-are-auto-rtl-expected.png",
+ "css-2-1/10-3-7-left-and-width-and-right-are-auto-rtl.html",
+ "css-2-1/10-3-7-left-and-width-and-right-are-auto.html",
+ "css-2-1/10-3-7-multiple-absolute-positioned-elements-share-same-static-position-expected.png",
+ "css-2-1/10-3-7-multiple-absolute-positioned-elements-share-same-static-position.html",
+ "css-2-1/10-3-7-none-of-left-and-width-and-right-is-auto-expected.png",
+ "css-2-1/10-3-7-none-of-left-and-width-and-right-is-auto-rtl-expected.png",
+ "css-2-1/10-3-7-none-of-left-and-width-and-right-is-auto-rtl.html",
+ "css-2-1/10-3-7-none-of-left-and-width-and-right-is-auto.html",
+ "css-2-1/10-3-7-position-absolute-should-apply-rules-that-dictate-shrink-to-fit.html",
+ "css-2-1/10-3-7-some-of-left-and-width-and-right-are-auto-expected.png",
+ "css-2-1/10-3-7-some-of-left-and-width-and-right-are-auto-rtl-expected.png",
+ "css-2-1/10-3-7-some-of-left-and-width-and-right-are-auto-rtl.html",
+ "css-2-1/10-3-7-some-of-left-and-width-and-right-are-auto.html",
+ "css-2-1/10-3-7-static-position-for-block-level-elements-expected.png",
+ "css-2-1/10-3-7-static-position-for-block-level-elements-rtl-expected.png",
+ "css-2-1/10-3-7-static-position-for-block-level-elements-rtl.html",
+ "css-2-1/10-3-7-static-position-for-block-level-elements.html",
+ "css-2-1/10-3-9-auto-margin-should-become-zero-in-inline-block-non-replaced-elements-expected.png",
+ "css-2-1/10-3-9-auto-margin-should-become-zero-in-inline-block-non-replaced-elements.html",
+ "css-2-1/10-3-9-child-width-should-be-calculated-after-width-of-inline-block-parent-expected.png",
+ "css-2-1/10-3-9-child-width-should-be-calculated-after-width-of-inline-block-parent.html",
+ "css-2-1/10-3-9-inline-block-non-replaced-and-br-elements-with-auto-width-should-shrink-to-fit-expected.png",
+ "css-2-1/10-3-9-inline-block-non-replaced-and-br-elements-with-auto-width-should-shrink-to-fit.html",
+ "css-2-1/10-3-9-inline-block-non-replaced-elements-with-auto-width-should-shrink-to-fit-expected.png",
+ "css-2-1/10-3-9-inline-block-non-replaced-elements-with-auto-width-should-shrink-to-fit.html",
+ "css-2-1/10-3-9-shrink-to-fit-width-should-not-depend-on-text-align-expected.png",
+ "css-2-1/10-3-9-shrink-to-fit-width-should-not-depend-on-text-align.html",
+ "css-2-1/10-4-min-height-and-max-height-limit-box-size-expected.png",
+ "css-2-1/10-4-min-height-and-max-height-limit-box-size.html",
+ "css-2-1/10-4-min-height-and-max-height-percentage-should-refer-containing-block-height-expected.png",
+ "css-2-1/10-4-min-height-and-max-height-percentage-should-refer-containing-block-height.html",
+ "css-2-1/10-4-min-width-and-max-width-constraints-for-replaced_elements-expected.png",
+ "css-2-1/10-4-min-width-and-max-width-constraints-for-replaced_elements.html",
+ "css-2-1/10-4-min-width-and-max-width-limit-box-size-expected.png",
+ "css-2-1/10-4-min-width-and-max-width-limit-box-size.html",
+ "css-2-1/10-4-min-width-and-max-width-percentage-should-refer-containing-block-width-expected.png",
+ "css-2-1/10-4-min-width-and-max-width-percentage-should-refer-containing-block-width.html",
+ "css-2-1/10-5-percentage-of-auto-height-on-absolute-element-should-compute-to-percentage-expected.png",
+ "css-2-1/10-5-percentage-of-auto-height-on-absolute-element-should-compute-to-percentage.html",
+ "css-2-1/10-5-percentage-of-auto-height-should-compute-to-auto-expected.png",
+ "css-2-1/10-5-percentage-of-auto-height-should-compute-to-auto.html",
+ "css-2-1/10-5-percentage-of-height-should-work-properly-with-top-and-bottom-expected.png",
+ "css-2-1/10-5-percentage-of-height-should-work-properly-with-top-and-bottom.html",
+ "css-2-1/10-6-1-content-height-of-inline-boxes-matches-font-size-expected.png",
+ "css-2-1/10-6-1-content-height-of-inline-boxes-matches-font-size.html",
+ "css-2-1/10-6-2-replaced-box-height-expected.png",
+ "css-2-1/10-6-2-replaced-box-height.html",
+ "css-2-1/10-8-1-inline-box-with-non-normal-line-height-should-use-first-available-font-for-font-metrics-expected.png",
+ "css-2-1/10-8-1-inline-box-with-non-normal-line-height-should-use-first-available-font-for-font-metrics.html",
+ "css-2-1/10-8-1-inline-box-with-normal-line-height-should-combine-all-used-fonts-in-font-metrics-expected.png",
+ "css-2-1/10-8-1-inline-box-with-normal-line-height-should-combine-all-used-fonts-in-font-metrics.html",
+ "css-2-1/10-8-1-unitless-line-height-expected.png",
+ "css-2-1/10-8-1-unitless-line-height.html",
+ "css-2-1/10-8-1-vertical-align-baseline-after-middle-expected.png",
+ "css-2-1/10-8-1-vertical-align-baseline-after-middle-text-expected.png",
+ "css-2-1/10-8-1-vertical-align-baseline-after-middle-text.html",
+ "css-2-1/10-8-1-vertical-align-baseline-after-middle.html",
+ "css-2-1/10-8-1-vertical-align-expected.png",
+ "css-2-1/10-8-1-vertical-align-fixed-size-boxes-and-text-expected.png",
+ "css-2-1/10-8-1-vertical-align-fixed-size-boxes-and-text.html",
+ "css-2-1/10-8-1-vertical-align-larger-font-middle-after-baseline-expected.png",
+ "css-2-1/10-8-1-vertical-align-larger-font-middle-after-baseline.html",
+ "css-2-1/10-8-1-vertical-align-middle-top-baseline-simple-expected.png",
+ "css-2-1/10-8-1-vertical-align-middle-top-baseline-simple.html",
+ "css-2-1/10-8-1-vertical-align-potential-baseline-vs-bottom-confusion-expected.png",
+ "css-2-1/10-8-1-vertical-align-potential-baseline-vs-bottom-confusion.html",
+ "css-2-1/10-8-1-vertical-align.html",
+ "css-2-1/10-8-baseline-of-inline-block-should-be-baseline-of-last-line-box-expected.png",
+ "css-2-1/10-8-baseline-of-inline-block-should-be-baseline-of-last-line-box.html",
+ "css-2-1/10-8-large-elements-can-increase-the-distance-between-succesive-baselines-expected.png",
+ "css-2-1/10-8-large-elements-can-increase-the-distance-between-succesive-baselines.html",
+ "css-2-1/10-8-line-height-and-font-size-and-borders-in-nested-inline-boxes-do-no-affect-alignment-expected.png",
+ "css-2-1/10-8-line-height-and-font-size-and-borders-in-nested-inline-boxes-do-no-affect-alignment.html",
+ "css-2-1/10-8-line-height-should-be-used-for-vertical-align-with-inline-non-replaced-boxes-expected.png",
+ "css-2-1/10-8-line-height-should-be-used-for-vertical-align-with-inline-non-replaced-boxes.html",
+ "css-2-1/10-8-line-height-should-not-affect-relative-vertical-placement-of-child-boxes-expected.png",
+ "css-2-1/10-8-line-height-should-not-affect-relative-vertical-placement-of-child-boxes.html",
+ "css-2-1/10-8-line-height-should-specify-minimum-height-of-line-boxes-expected.png",
+ "css-2-1/10-8-line-height-should-specify-minimum-height-of-line-boxes.html",
+ "css-2-1/10-8-margin-box-should-be-used-for-vertical-align-with-most-boxes-expected.png",
+ "css-2-1/10-8-margin-box-should-be-used-for-vertical-align-with-most-boxes.html",
+ "css-2-1/10-8-vertical-align-top-and-bottom-affect-height-and-baseline-expected.png",
+ "css-2-1/10-8-vertical-align-top-and-bottom-affect-height-and-baseline.html",
+ "css-2-1/11-1-1-overflow-auto-div-with-position-absolute-children-expected.png",
+ "css-2-1/11-1-1-overflow-auto-div-with-position-absolute-children.html",
+ "css-2-1/11-1-1-overflow-auto-expected.png",
+ "css-2-1/11-1-1-overflow-auto-from-non-positioned-containing-block-should-affect-relative-positioned-child-expected.png",
+ "css-2-1/11-1-1-overflow-auto-from-non-positioned-containing-block-should-affect-relative-positioned-child.html",
+ "css-2-1/11-1-1-overflow-auto-transform-expected.png",
+ "css-2-1/11-1-1-overflow-auto-transform.html",
+ "css-2-1/11-1-1-overflow-auto.html",
+ "css-2-1/11-1-1-overflow-from-non-positioned-containing-block-should-affect-relative-positioned-child-expected.png",
+ "css-2-1/11-1-1-overflow-from-non-positioned-containing-block-should-affect-relative-positioned-child.html",
+ "css-2-1/11-1-1-overflow-hidden-and-opacity-expected.png",
+ "css-2-1/11-1-1-overflow-hidden-and-opacity.html",
+ "css-2-1/11-1-1-overflow-hidden-and-position-absolute-div-with-position-absolute-children-expected.png",
+ "css-2-1/11-1-1-overflow-hidden-and-position-absolute-div-with-position-absolute-children.html",
+ "css-2-1/11-1-1-overflow-hidden-applied-to-elements-with-display-set-to-block-expected.png",
+ "css-2-1/11-1-1-overflow-hidden-applied-to-elements-with-display-set-to-block.html",
+ "css-2-1/11-1-1-overflow-hidden-applied-to-elements-with-display-set-to-inline-block-expected.png",
+ "css-2-1/11-1-1-overflow-hidden-applied-to-elements-with-display-set-to-inline-block.html",
+ "css-2-1/11-1-1-overflow-hidden-applied-to-elements-with-display-set-to-inline-expected.png",
+ "css-2-1/11-1-1-overflow-hidden-applied-to-elements-with-display-set-to-inline.html",
+ "css-2-1/11-1-1-overflow-hidden-div-with-position-absolute-children-expected.png",
+ "css-2-1/11-1-1-overflow-hidden-div-with-position-absolute-children.html",
+ "css-2-1/11-1-1-overflow-hidden-expected.png",
+ "css-2-1/11-1-1-overflow-hidden-masks-padding-box-only-expected.png",
+ "css-2-1/11-1-1-overflow-hidden-masks-padding-box-only.html",
+ "css-2-1/11-1-1-overflow-hidden-transform-expected.png",
+ "css-2-1/11-1-1-overflow-hidden-transform.html",
+ "css-2-1/11-1-1-overflow-hidden-translate-expected.png",
+ "css-2-1/11-1-1-overflow-hidden-translate.html",
+ "css-2-1/11-1-1-overflow-hidden.html",
+ "css-2-1/11-1-1-overflow-scroll-absolute-positioned-elements-positioned-scroller-expected.png",
+ "css-2-1/11-1-1-overflow-scroll-absolute-positioned-elements-positioned-scroller.html",
+ "css-2-1/11-1-1-overflow-scroll-absolute-positioned-elements-unpositioned-scroller-expected.png",
+ "css-2-1/11-1-1-overflow-scroll-absolute-positioned-elements-unpositioned-scroller.html",
+ "css-2-1/11-1-1-overflow-scroll-container-scrolled-expected.png",
+ "css-2-1/11-1-1-overflow-scroll-container-scrolled-rtl-expected.png",
+ "css-2-1/11-1-1-overflow-scroll-container-scrolled-rtl.html",
+ "css-2-1/11-1-1-overflow-scroll-container-scrolled.html",
+ "css-2-1/11-1-1-overflow-scroll-expected.png",
+ "css-2-1/11-1-1-overflow-scroll-fixed-positioned-elements-transformed-scroller-expected.png",
+ "css-2-1/11-1-1-overflow-scroll-fixed-positioned-elements-transformed-scroller.html",
+ "css-2-1/11-1-1-overflow-scroll-fixed-positioned-elements-untransformed-scroller-expected.png",
+ "css-2-1/11-1-1-overflow-scroll-fixed-positioned-elements-untransformed-scroller.html",
+ "css-2-1/11-1-1-overflow-scroll-should-affect-descendants-with-z-index-expected.png",
+ "css-2-1/11-1-1-overflow-scroll-should-affect-descendants-with-z-index.html",
+ "css-2-1/11-1-1-overflow-scroll-transform-expected.png",
+ "css-2-1/11-1-1-overflow-scroll-transform.html",
+ "css-2-1/11-1-1-overflow-scroll-unpositioned-elements-unpositioned-scroller-expected.png",
+ "css-2-1/11-1-1-overflow-scroll-unpositioned-elements-unpositioned-scroller.html",
+ "css-2-1/11-1-1-overflow-scroll.html",
+ "css-2-1/11-1-1-overflow-should-affect-descendants-with-z-index-expected.png",
+ "css-2-1/11-1-1-overflow-should-affect-descendants-with-z-index.html",
+ "css-2-1/11-1-1-overflow-should-not-affect-descendants-contained-in-another-block-expected.png",
+ "css-2-1/11-1-1-overflow-should-not-affect-descendants-contained-in-another-block.html",
+ "css-2-1/11-1-1-overflow-visible-expected.png",
+ "css-2-1/11-1-1-overflow-visible.html",
+ "css-2-1/11-1-absolutely-positioned-children-of-overflow-hidden-expected.png",
+ "css-2-1/11-1-absolutely-positioned-children-of-overflow-hidden-to-left-expected.png",
+ "css-2-1/11-1-absolutely-positioned-children-of-overflow-hidden-to-left.html",
+ "css-2-1/11-1-absolutely-positioned-children-of-overflow-hidden-to-right-expected.png",
+ "css-2-1/11-1-absolutely-positioned-children-of-overflow-hidden-to-right.html",
+ "css-2-1/11-1-absolutely-positioned-children-of-overflow-hidden.html",
+ "css-2-1/11-1-absolutely-positioned-children-of-overflow-scroll-expected.png",
+ "css-2-1/11-1-absolutely-positioned-children-of-overflow-scroll.html",
+ "css-2-1/11-2-visibility-hidden-renders-generated-box-invisible-expected.png",
+ "css-2-1/11-2-visibility-hidden-renders-generated-box-invisible.html",
+ "css-2-1/11-2-visibility-hidden-should-be-overridable-expected.png",
+ "css-2-1/11-2-visibility-hidden-should-be-overridable.html",
+ "css-2-1/11-2-visibility-hidden-still-affects-layout-expected.png",
+ "css-2-1/11-2-visibility-hidden-still-affects-layout.html",
+ "css-2-1/12-1-after-pseudoelement-expected.png",
+ "css-2-1/12-1-after-pseudoelement-simple-expected.png",
+ "css-2-1/12-1-after-pseudoelement-simple.html",
+ "css-2-1/12-1-after-pseudoelement.html",
+ "css-2-1/12-1-before-pseudoelement-does-not-inherit-inline-style-expected.png",
+ "css-2-1/12-1-before-pseudoelement-does-not-inherit-inline-style.html",
+ "css-2-1/12-1-before-pseudoelement-expected.png",
+ "css-2-1/12-1-before-pseudoelement-responds-to-style-change-expected.png",
+ "css-2-1/12-1-before-pseudoelement-responds-to-style-change.html",
+ "css-2-1/12-1-before-pseudoelement.html",
+ "css-2-1/16-2-text-align-can-be-left-center-right-expected.png",
+ "css-2-1/16-2-text-align-can-be-left-center-right.html",
+ "css-2-1/16-2-text-align-should-not-apply-when-child-boxes-overflow-expected.png",
+ "css-2-1/16-2-text-align-should-not-apply-when-child-boxes-overflow.html",
+ "css-2-1/18-4-outline-animation-expected.png",
+ "css-2-1/18-4-outline-animation.html",
+ "css-2-1/18-4-outline-expected.png",
+ "css-2-1/18-4-outline-overflow-hidden-expected.png",
+ "css-2-1/18-4-outline-overflow-hidden.html",
+ "css-2-1/18-4-outline.html",
+ "css-2-1/8-1-margin-should-be-transparent-expected.png",
+ "css-2-1/8-1-margin-should-be-transparent.html",
+ "css-2-1/8-3-1-box-with-cropped-overflow-should-form-collapsed-margin-with-parent-but-not-child-expected.png",
+ "css-2-1/8-3-1-box-with-cropped-overflow-should-form-collapsed-margin-with-parent-but-not-child.html",
+ "css-2-1/8-3-1-box-with-in-flow-child-should-not-form-collapsed-margin-expected.png",
+ "css-2-1/8-3-1-box-with-in-flow-child-should-not-form-collapsed-margin.html",
+ "css-2-1/8-3-1-box-with-inline-child-should-not-form-collapsed-margin-expected.png",
+ "css-2-1/8-3-1-box-with-inline-child-should-not-form-collapsed-margin.html",
+ "css-2-1/8-3-1-box-with-min-height-should-not-form-collapsed-margin-expected.png",
+ "css-2-1/8-3-1-box-with-min-height-should-not-form-collapsed-margin.html",
+ "css-2-1/8-3-1-box-with-no-in-flow-or-inline-children-should-form-collapsed-margin-expected.png",
+ "css-2-1/8-3-1-box-with-no-in-flow-or-inline-children-should-form-collapsed-margin.html",
+ "css-2-1/8-3-1-empty-box-margin-collapses-itself-then-collapses-with-parent-bottom-margin-expected.png",
+ "css-2-1/8-3-1-empty-box-margin-collapses-itself-then-collapses-with-parent-bottom-margin.html",
+ "css-2-1/8-3-1-empty-box-margin-collapses-itself-then-collapses-with-parent-top-but-not-bottom-margin-expected.png",
+ "css-2-1/8-3-1-empty-box-margin-collapses-itself-then-collapses-with-parent-top-but-not-bottom-margin.html",
+ "css-2-1/8-3-1-in-flow-siblings-separated-by-absolute-box-should-form-collapsed-margin-expected.png",
+ "css-2-1/8-3-1-in-flow-siblings-separated-by-absolute-box-should-form-collapsed-margin.html",
+ "css-2-1/8-3-1-in-flow-siblings-separated-by-inline-box-should-not-form-collapsed-margin-expected.png",
+ "css-2-1/8-3-1-in-flow-siblings-separated-by-inline-box-should-not-form-collapsed-margin.html",
+ "css-2-1/8-3-1-in-flow-siblings-should-form-collapsed-margin-expected.png",
+ "css-2-1/8-3-1-in-flow-siblings-should-form-collapsed-margin.html",
+ "css-2-1/8-3-1-inline-level-boxes-should-not-form-collapsed-margin-expected.png",
+ "css-2-1/8-3-1-inline-level-boxes-should-not-form-collapsed-margin.html",
+ "css-2-1/8-3-1-parent-and-first-in-flow-child-separated-by-absolute-box-should-form-collapsed-margin-expected.png",
+ "css-2-1/8-3-1-parent-and-first-in-flow-child-separated-by-absolute-box-should-form-collapsed-margin.html",
+ "css-2-1/8-3-1-parent-and-first-in-flow-child-separated-by-border-should-not-form-collapsed-margin-expected.png",
+ "css-2-1/8-3-1-parent-and-first-in-flow-child-separated-by-border-should-not-form-collapsed-margin.html",
+ "css-2-1/8-3-1-parent-and-first-in-flow-child-separated-by-inline-box-should-not-form-collapsed-margin-expected.png",
+ "css-2-1/8-3-1-parent-and-first-in-flow-child-separated-by-inline-box-should-not-form-collapsed-margin.html",
+ "css-2-1/8-3-1-parent-and-first-in-flow-child-separated-by-padding-should-not-form-collapsed-margin-expected.png",
+ "css-2-1/8-3-1-parent-and-first-in-flow-child-separated-by-padding-should-not-form-collapsed-margin.html",
+ "css-2-1/8-3-1-parent-and-first-in-flow-child-should-form-collapsed-margin-expected.png",
+ "css-2-1/8-3-1-parent-and-first-in-flow-child-should-form-collapsed-margin.html",
+ "css-2-1/8-3-1-parent-and-last-in-flow-child-separated-by-absolute-box-should-form-collapsed-margin-expected.png",
+ "css-2-1/8-3-1-parent-and-last-in-flow-child-separated-by-absolute-box-should-form-collapsed-margin.html",
+ "css-2-1/8-3-1-parent-and-last-in-flow-child-separated-by-border-should-not-form-collapsed-margin-expected.png",
+ "css-2-1/8-3-1-parent-and-last-in-flow-child-separated-by-border-should-not-form-collapsed-margin.html",
+ "css-2-1/8-3-1-parent-and-last-in-flow-child-separated-by-inline-box-should-not-form-collapsed-margin-expected.png",
+ "css-2-1/8-3-1-parent-and-last-in-flow-child-separated-by-inline-box-should-not-form-collapsed-margin.html",
+ "css-2-1/8-3-1-parent-and-last-in-flow-child-separated-by-padding-should-not-form-collapsed-margin-expected.png",
+ "css-2-1/8-3-1-parent-and-last-in-flow-child-separated-by-padding-should-not-form-collapsed-margin.html",
+ "css-2-1/8-3-1-parent-and-last-in-flow-child-should-form-collapsed-margin-expected.png",
+ "css-2-1/8-3-1-parent-and-last-in-flow-child-should-form-collapsed-margin.html",
+ "css-2-1/8-3-1-parent-with-non-auto-height-should-not-form-collapsed-margin-expected.png",
+ "css-2-1/8-3-1-parent-with-non-auto-height-should-not-form-collapsed-margin.html",
+ "css-2-1/8-3-1-statically-positioned-absolute-box-should-not-form-collapsed-margin-expected.png",
+ "css-2-1/8-3-1-statically-positioned-absolute-box-should-not-form-collapsed-margin.html",
+ "css-2-1/8-3-margin-percentage-should-refer-containing-block-width-expected.png",
+ "css-2-1/8-3-margin-percentage-should-refer-containing-block-width.html",
+ "css-2-1/8-3-negative-margins-should-be-allowed-expected.png",
+ "css-2-1/8-3-negative-margins-should-be-allowed.html",
+ "css-2-1/8-3-negative_margins-should-be-allowed-to-produce_negative-box-widths-expected.png",
+ "css-2-1/8-3-negative_margins-should-be-allowed-to-produce_negative-box-widths.html",
+ "css-2-1/8-3-vertical-margins-should-not-apply-to-non-replaced-inline-elements-expected.png",
+ "css-2-1/8-3-vertical-margins-should-not-apply-to-non-replaced-inline-elements.html",
+ "css-2-1/8-4-padding-color-should-be-specified-by-background-expected.png",
+ "css-2-1/8-4-padding-color-should-be-specified-by-background.html",
+ "css-2-1/8-4-padding-image-should-be-specified-by-background-expected.png",
+ "css-2-1/8-4-padding-image-should-be-specified-by-background.html",
+ "css-2-1/8-4-padding-percentage-should-refer-containing-block-width-expected.png",
+ "css-2-1/8-4-padding-percentage-should-refer-containing-block-width.html",
+ "css-2-1/9-2-1-1-anonymous-block-boxes-should-be-ignored-when-resolving-percentages-expected.png",
+ "css-2-1/9-2-1-1-anonymous-block-boxes-should-be-ignored-when-resolving-percentages.html",
+ "css-2-1/9-2-1-1-inline-level-boxes-should-be-broken-around-block-level-box-expected.png",
+ "css-2-1/9-2-1-1-inline-level-boxes-should-be-broken-around-block-level-box.html",
+ "css-2-1/9-2-1-1-inline-level-boxes-should-be-wrapped-in-anonymous-block-boxes-in-block-formatting-context-expected.png",
+ "css-2-1/9-2-1-1-inline-level-boxes-should-be-wrapped-in-anonymous-block-boxes-in-block-formatting-context.html",
+ "css-2-1/9-2-1-block-level-boxes-should-participate-in-block-formatting-context-expected.png",
+ "css-2-1/9-2-1-block-level-boxes-should-participate-in-block-formatting-context.html",
+ "css-2-1/9-2-1-descendant-boxes-should-be-nested-in-ascendant-box-expected.png",
+ "css-2-1/9-2-1-descendant-boxes-should-be-nested-in-ascendant-box.html",
+ "css-2-1/9-2-2-inline-level-boxes-should-participate-in-inline-formatting-context-expected.png",
+ "css-2-1/9-2-2-inline-level-boxes-should-participate-in-inline-formatting-context.html",
+ "css-2-1/9-2-4-display-none-on-br-element-should-not-generate-line-break-expected.png",
+ "css-2-1/9-2-4-display-none-on-br-element-should-not-generate-line-break.html",
+ "css-2-1/9-2-4-display-none-should-not-be-overridable-expected.png",
+ "css-2-1/9-2-4-display-none-should-not-be-overridable.html",
+ "css-2-1/9-2-4-display-none-should-not-generate-box-expected.png",
+ "css-2-1/9-2-4-display-none-should-not-generate-box.html",
+ "css-2-1/9-3-1-fixed-position-elements-are-contained-by-transformed-elements-expected.png",
+ "css-2-1/9-3-1-fixed-position-elements-are-contained-by-transformed-elements.html",
+ "css-2-1/9-3-1-fixed-position-elements-are-contained-by-viewport-despite-absolute-containing-block-expected.png",
+ "css-2-1/9-3-1-fixed-position-elements-are-contained-by-viewport-despite-absolute-containing-block.html",
+ "css-2-1/9-3-1-fixed-position-elements-compute-em-units-from-parent-expected.png",
+ "css-2-1/9-3-1-fixed-position-elements-compute-em-units-from-parent.html",
+ "css-2-1/9-3-1-fixed-position-elements-follow-normal-stacking-context-rules-expected.png",
+ "css-2-1/9-3-1-fixed-position-elements-follow-normal-stacking-context-rules.html",
+ "css-2-1/9-3-1-fixed-position-elements-resolve-percentages-relative-to-viewport-expected.png",
+ "css-2-1/9-3-1-fixed-position-elements-resolve-percentages-relative-to-viewport.html",
+ "css-2-1/9-3-1-fixed-position-elements-right-and-bottom-position-relative-to-viewport-expected.png",
+ "css-2-1/9-3-1-fixed-position-elements-right-and-bottom-position-relative-to-viewport.html",
+ "css-2-1/9-3-2-absolute-position-elements-can-be-positioned-with-bottom-length-expected.png",
+ "css-2-1/9-3-2-absolute-position-elements-can-be-positioned-with-bottom-length.html",
+ "css-2-1/9-3-2-absolute-position-elements-can-be-positioned-with-bottom-percentage-expected.png",
+ "css-2-1/9-3-2-absolute-position-elements-can-be-positioned-with-bottom-percentage.html",
+ "css-2-1/9-3-2-absolute-position-elements-can-be-positioned-with-left-length-expected.png",
+ "css-2-1/9-3-2-absolute-position-elements-can-be-positioned-with-left-length.html",
+ "css-2-1/9-3-2-absolute-position-elements-can-be-positioned-with-left-percentage-expected.png",
+ "css-2-1/9-3-2-absolute-position-elements-can-be-positioned-with-left-percentage.html",
+ "css-2-1/9-3-2-absolute-position-elements-can-be-positioned-with-right-length-expected.png",
+ "css-2-1/9-3-2-absolute-position-elements-can-be-positioned-with-right-length.html",
+ "css-2-1/9-3-2-absolute-position-elements-can-be-positioned-with-right-percentage-expected.png",
+ "css-2-1/9-3-2-absolute-position-elements-can-be-positioned-with-right-percentage.html",
+ "css-2-1/9-3-2-absolute-position-elements-can-be-positioned-with-top-length-expected.png",
+ "css-2-1/9-3-2-absolute-position-elements-can-be-positioned-with-top-length.html",
+ "css-2-1/9-3-2-absolute-position-elements-can-be-positioned-with-top-percentage-expected.png",
+ "css-2-1/9-3-2-absolute-position-elements-can-be-positioned-with-top-percentage.html",
+ "css-2-1/9-3-2-absolute-position-elements-with-implicit-width-can-be-positioned-with-bottom-length-expected.png",
+ "css-2-1/9-3-2-absolute-position-elements-with-implicit-width-can-be-positioned-with-bottom-length.html",
+ "css-2-1/9-3-2-absolute-position-elements-with-implicit-width-can-be-positioned-with-right-length-expected.png",
+ "css-2-1/9-3-2-absolute-position-elements-with-implicit-width-can-be-positioned-with-right-length.html",
+ "css-2-1/9-4-1-vertical-margins-between-adjacent-block-level-boxes-in-a-block-formatting-context-collapse-expected.png",
+ "css-2-1/9-4-1-vertical-margins-between-adjacent-block-level-boxes-in-a-block-formatting-context-collapse.html",
+ "css-2-1/9-4-2-collapsed-elements-should-reduce-available-width-of-line-box-expected.png",
+ "css-2-1/9-4-2-collapsed-elements-should-reduce-available-width-of-line-box.html",
+ "css-2-1/9-4-2-inline-boxes-should-split-at-br-elements-expected.png",
+ "css-2-1/9-4-2-inline-boxes-should-split-at-br-elements.html",
+ "css-2-1/9-4-2-long-inline-boxes-should-be-split-expected.png",
+ "css-2-1/9-4-2-long-inline-boxes-should-be-split.html",
+ "css-2-1/9-4-2-margin-should-not-overflow-line-box-expected.png",
+ "css-2-1/9-4-2-margin-should-not-overflow-line-box.html",
+ "css-2-1/9-4-2-margins-borders-paddings-should-have-no-visual-effect-when-bidi-split-occurs-expected.png",
+ "css-2-1/9-4-2-margins-borders-paddings-should-have-no-visual-effect-when-bidi-split-occurs.html",
+ "css-2-1/9-4-2-margins-borders-paddings-should-have-no-visual-effect-when-split-occurs-2-expected.png",
+ "css-2-1/9-4-2-margins-borders-paddings-should-have-no-visual-effect-when-split-occurs-2.html",
+ "css-2-1/9-4-2-margins-borders-paddings-should-have-no-visual-effect-when-split-occurs-expected.png",
+ "css-2-1/9-4-2-margins-borders-paddings-should-have-no-visual-effect-when-split-occurs.html",
+ "css-2-1/9-4-2-multiple-inline-boxes-exceeding-width-of-line-box-should-be-split-expected.png",
+ "css-2-1/9-4-2-multiple-inline-boxes-exceeding-width-of-line-box-should-be-split.html",
+ "css-2-1/9-4-2-nested-inline-boxes-should-split-at-br-elements-expected.png",
+ "css-2-1/9-4-2-nested-inline-boxes-should-split-at-br-elements.html",
+ "css-2-1/9-4-2-splitting-of-boxes-can-affect-containing-box-of-children-expected.png",
+ "css-2-1/9-4-2-splitting-of-boxes-can-affect-containing-box-of-children.html",
+ "css-2-1/9-4-2-splitting-of-boxes-can-affect-stacking-context-of-children-expected.png",
+ "css-2-1/9-4-2-splitting-of-boxes-can-affect-stacking-context-of-children.html",
+ "css-2-1/9-4-2-splitting-of-relatively-positioned-box-does-not-affect-stacking-context-of-siblings-expected.png",
+ "css-2-1/9-4-2-splitting-of-relatively-positioned-box-does-not-affect-stacking-context-of-siblings.html",
+ "css-2-1/9-4-2-unsplittable-inline-box-should-overflow-expected.png",
+ "css-2-1/9-4-2-unsplittable-inline-box-should-overflow.html",
+ "css-2-1/9-4-3-relative-positioned-element-percentages-resolved-from-parent-expected.png",
+ "css-2-1/9-4-3-relative-positioned-element-percentages-resolved-from-parent.html",
+ "css-2-1/9-4-3-relative-positioned-elements-are-offset-from-inline-box-ancestor-expected.png",
+ "css-2-1/9-4-3-relative-positioned-elements-are-offset-from-inline-box-ancestor.html",
+ "css-2-1/9-4-3-relative-positioned-elements-are-offset-from-normal-flow-expected.png",
+ "css-2-1/9-4-3-relative-positioned-elements-are-offset-from-normal-flow-via-right-and-bottom-expected.png",
+ "css-2-1/9-4-3-relative-positioned-elements-are-offset-from-normal-flow-via-right-and-bottom.html",
+ "css-2-1/9-4-3-relative-positioned-elements-are-offset-from-normal-flow.html",
+ "css-2-1/9-4-3-relative-positioned-elements-participate-in-stacking-context-expected.png",
+ "css-2-1/9-4-3-relative-positioned-elements-participate-in-stacking-context.html",
+ "css-2-1/9-4-3-relative-positioned-elements-use-left-and-top-versus-right-and-bottom-expected.png",
+ "css-2-1/9-4-3-relative-positioned-elements-use-left-and-top-versus-right-and-bottom-rtl-expected.png",
+ "css-2-1/9-4-3-relative-positioned-elements-use-left-and-top-versus-right-and-bottom-rtl.html",
+ "css-2-1/9-4-3-relative-positioned-elements-use-left-and-top-versus-right-and-bottom.html",
+ "css-2-1/9-4-3-relative-positioned-elements-with-inline-block-display-are-offset-from-normal-flow-expected.png",
+ "css-2-1/9-4-3-relative-positioned-elements-with-inline-block-display-are-offset-from-normal-flow.html",
+ "css-2-1/9-4-3-relative-positioned-elements-with-inline-display-are-offset-from-normal-flow-expected.png",
+ "css-2-1/9-4-3-relative-positioned-elements-with-inline-display-are-offset-from-normal-flow.html",
+ "css-2-1/9-7-display-should-resolve-to-block-if-position-is-absolute-expected.png",
+ "css-2-1/9-7-display-should-resolve-to-block-if-position-is-absolute.html",
+ "css-2-1/9-9-1-absolute-positioned-elements-should-render-on-top-of-static-elements-expected.png",
+ "css-2-1/9-9-1-absolute-positioned-elements-should-render-on-top-of-static-elements.html",
+ "css-2-1/9-9-1-containing-block-may-be-farther-than-stacking-context-expected.png",
+ "css-2-1/9-9-1-containing-block-may-be-farther-than-stacking-context.html",
+ "css-2-1/9-9-1-elements-with-transform-should-appear-over-normal-flow-elements-expected.png",
+ "css-2-1/9-9-1-elements-with-transform-should-appear-over-normal-flow-elements.html",
+ "css-2-1/9-9-1-fixed-position-containing-block-should-form-stacking-context-expected.png",
+ "css-2-1/9-9-1-fixed-position-containing-block-should-form-stacking-context.html",
+ "css-2-1/9-9-1-fixed-position-element-should-not-appear-on-top-of-later-siblings-expected.png",
+ "css-2-1/9-9-1-fixed-position-element-should-not-appear-on-top-of-later-siblings.html",
+ "css-2-1/9-9-1-nearest-ancestor-stacking-context-should-contain-element-expected.png",
+ "css-2-1/9-9-1-nearest-ancestor-stacking-context-should-contain-element.html",
+ "css-2-1/9-9-1-negative-z-indices-expected.png",
+ "css-2-1/9-9-1-negative-z-indices.html",
+ "css-2-1/9-9-1-relative-positioned-element-should-be-included-in-containing-stacking-context-expected.png",
+ "css-2-1/9-9-1-relative-positioned-element-should-be-included-in-containing-stacking-context.html",
+ "css-2-1/9-9-1-relative-positioned-element-should-not-appear-on-top-of-later-sibling-expected.png",
+ "css-2-1/9-9-1-relative-positioned-element-should-not-appear-on-top-of-later-sibling.html",
+ "css-2-1/9-9-1-simple-positive-z-indices-expected.png",
+ "css-2-1/9-9-1-simple-positive-z-indices.html",
+ "css-2-1/9-9-1-stacking-contexts-and-containing-blocks-in-separate-subtrees-expected.png",
+ "css-2-1/9-9-1-stacking-contexts-and-containing-blocks-in-separate-subtrees.html",
+ "css-2-1/9-9-1-stacking-contexts-and-containing-blocks-with-transforms-expected.png",
+ "css-2-1/9-9-1-stacking-contexts-and-containing-blocks-with-transforms.html",
+ "css-2-1/9-9-1-stacking-contexts-differ-from-containing-blocks-expected.png",
+ "css-2-1/9-9-1-stacking-contexts-differ-from-containing-blocks.html",
+ "css-2-1/9-9-1-stacking-contexts-should-take-into-account-intermediate-containing-blocks-expected.png",
+ "css-2-1/9-9-1-stacking-contexts-should-take-into-account-intermediate-containing-blocks.html",
+ "css-2-1/9-9-1-z-index-should-only-be-applied-to-positioned-elements-expected.png",
+ "css-2-1/9-9-1-z-index-should-only-be-applied-to-positioned-elements.html",
+ "css-2-1/DISABLED-10-3-2-auto-width-should-resolve-to-intrinsic-width.html",
+ "css-2-1/DISABLED-10-3-7-absolute-elements-do-not-break-inline-boxes.html",
+ "css-2-1/DISABLED-10-3-7-inline-absolute-elements-do-not-break-inline-boxes-but-appear-at-inline-position.html",
+ "css-2-1/DISABLED-10-3-7-inline-box-should-not-split-on-absolutely-positioned-child.html",
+ "css-2-1/DISABLED-10-8-1-inline-box-without-glyphs-should-contain-strut.html",
+ "css-2-1/DISABLED-10-8-1-vertical-align-notext.html",
+ "css-2-1/DISABLED-10-8-line-height-can-be-modified-by-non-ancestor-non-sibling-boxes.html",
+ "css-2-1/DISABLED-12-1-after-pseudoelement-linebreak.html",
+ "css-2-1/DISABLED-8-3-1-box-without-in-flow-children-should-form-collapsed-margin.html",
+ "css-2-1/DISABLED-9-2-1-1-inline-level-box-border-should-be-split-around-block-level-box.html",
+ "css-2-1/DISABLED-9-3-2-bottom-should-work-with-positive-and-negative-offsets.html",
+ "css-2-1/DISABLED-9-3-2-left-should-work-with-positive-and-negative-offsets.html",
+ "css-2-1/DISABLED-9-3-2-right-should-work-with-positive-and-negative-offsets.html",
+ "css-2-1/DISABLED-9-3-2-top-should-work-with-positive-and-negative-offsets.html",
+ "css-2-1/DISABLED-9-4-2-horizontal-margins-borders-paddings-should-be-respected-in-inline-formatting-context.html",
+ "css-2-1/DISABLED-9-4-2-splitting-of-boxes-may-not-affect-containing-box-of-positioned-children.html",
+ "css-2-1/DISABLED-9-4-3-relative-positioned-inline-level-elements-participate-in-stacking-context.html",
+ "css-2-1/DISABLED-9-9-1-background-and-borders-should-be-painted-before-children-with-negative-z-index.html",
+ "css-2-1/cobalt.png",
+ "css-2-1/layout_tests.txt",
+ "css-text-3/2-1-letters-should-all-appear-in-uppercase-with-text-transform-uppercase-expected.png",
+ "css-text-3/2-1-letters-should-all-appear-in-uppercase-with-text-transform-uppercase.html",
+ "css-text-3/3-bidirectional-content-should-overflow-the-line-with-white-space-nowrap-expected.png",
+ "css-text-3/3-bidirectional-content-should-overflow-the-line-with-white-space-nowrap.html",
+ "css-text-3/3-br-elements-within-white-space-nowrap-block-should-generate-new-lines-expected.png",
+ "css-text-3/3-br-elements-within-white-space-nowrap-block-should-generate-new-lines.html",
+ "css-text-3/3-br-elements-within-white-space-pre-block-should-generate-new-lines-expected.png",
+ "css-text-3/3-br-elements-within-white-space-pre-block-should-generate-new-lines.html",
+ "css-text-3/3-br-elements-within-white-space-pre-line-block-should-generate-new-lines-expected.png",
+ "css-text-3/3-br-elements-within-white-space-pre-line-block-should-generate-new-lines.html",
+ "css-text-3/3-br-elements-within-white-space-pre-wrap-block-should-generate-new-lines-expected.png",
+ "css-text-3/3-br-elements-within-white-space-pre-wrap-block-should-generate-new-lines.html",
+ "css-text-3/3-content-should-overflow-the-line-with-white-space-nowrap-expected.png",
+ "css-text-3/3-content-should-overflow-the-line-with-white-space-nowrap.html",
+ "css-text-3/3-content-should-overflow-the-line-with-white-space-pre-expected.png",
+ "css-text-3/3-content-should-overflow-the-line-with-white-space-pre.html",
+ "css-text-3/3-content-should-wrap-the-line-with-white-space-pre-line-expected.png",
+ "css-text-3/3-content-should-wrap-the-line-with-white-space-pre-line.html",
+ "css-text-3/3-content-should-wrap-the-line-with-white-space-pre-wrap-expected.png",
+ "css-text-3/3-content-should-wrap-the-line-with-white-space-pre-wrap.html",
+ "css-text-3/3-lines-and-spaces-should-be-collapsed-with-white-space-nowrap-containing-block-expected.png",
+ "css-text-3/3-lines-and-spaces-should-be-collapsed-with-white-space-nowrap-containing-block.html",
+ "css-text-3/3-lines-and-spaces-should-be-collapsed-with-white-space-nowrap-expected.png",
+ "css-text-3/3-lines-and-spaces-should-be-collapsed-with-white-space-nowrap.html",
+ "css-text-3/3-lines-and-spaces-should-be-retained-with-white-space-pre-containing-block-expected.png",
+ "css-text-3/3-lines-and-spaces-should-be-retained-with-white-space-pre-containing-block.html",
+ "css-text-3/3-lines-and-spaces-should-be-retained-with-white-space-pre-expected.png",
+ "css-text-3/3-lines-and-spaces-should-be-retained-with-white-space-pre-wrap-containing-block-expected.png",
+ "css-text-3/3-lines-and-spaces-should-be-retained-with-white-space-pre-wrap-containing-block.html",
+ "css-text-3/3-lines-and-spaces-should-be-retained-with-white-space-pre-wrap-expected.png",
+ "css-text-3/3-lines-and-spaces-should-be-retained-with-white-space-pre-wrap.html",
+ "css-text-3/3-lines-and-spaces-should-be-retained-with-white-space-pre.html",
+ "css-text-3/3-lines-should-be-retained-and-spaces-collapsed-with-white-space-pre-line-containing-block-expected.png",
+ "css-text-3/3-lines-should-be-retained-and-spaces-collapsed-with-white-space-pre-line-containing-block.html",
+ "css-text-3/3-lines-should-be-retained-and-spaces-collapsed-with-white-space-pre-line-expected.png",
+ "css-text-3/3-lines-should-be-retained-and-spaces-collapsed-with-white-space-pre-line.html",
+ "css-text-3/3-multiple-boxes-with-a-mixture-of-white-space-nowrap-and-normal-should-wrap-properly-expected.png",
+ "css-text-3/3-multiple-boxes-with-a-mixture-of-white-space-nowrap-and-normal-should-wrap-properly.html",
+ "css-text-3/3-multiple-boxes-with-a-mixture-of-white-space-pre-and-normal-should-wrap-properly-expected.png",
+ "css-text-3/3-multiple-boxes-with-a-mixture-of-white-space-pre-and-normal-should-wrap-properly.html",
+ "css-text-3/3-multiple-boxes-within-white-space-nowrap-block-should-not-wrap-expected.png",
+ "css-text-3/3-multiple-boxes-within-white-space-nowrap-block-should-not-wrap.html",
+ "css-text-3/3-multiple-boxes-within-white-space-pre-block-should-not-wrap-expected.png",
+ "css-text-3/3-multiple-boxes-within-white-space-pre-block-should-not-wrap.html",
+ "css-text-3/3-multiple-boxes-within-white-space-pre-line-block-should-wrap-expected.png",
+ "css-text-3/3-multiple-boxes-within-white-space-pre-line-block-should-wrap.html",
+ "css-text-3/3-multiple-boxes-within-white-space-pre-wrap-block-should-wrap-expected.png",
+ "css-text-3/3-multiple-boxes-within-white-space-pre-wrap-block-should-wrap.html",
+ "css-text-3/3-non-space-ending-white-space-pre-box-should-obey-wrapping-rules-expected.png",
+ "css-text-3/3-non-space-ending-white-space-pre-box-should-obey-wrapping-rules.html",
+ "css-text-3/3-non-space-preceding-white-space-pre-box-should-not-be-wrappable-expected.png",
+ "css-text-3/3-non-space-preceding-white-space-pre-box-should-not-be-wrappable.html",
+ "css-text-3/3-space-ending-white-space-pre-box-should-be-treated-as-non-breaking-space-expected.png",
+ "css-text-3/3-space-ending-white-space-pre-box-should-be-treated-as-non-breaking-space.html",
+ "css-text-3/3-space-preceding-white-space-pre-box-should-be-wrappable-expected.png",
+ "css-text-3/3-space-preceding-white-space-pre-box-should-be-wrappable.html",
+ "css-text-3/4-1-1-space-following-collapsible-space-in-another-box-should-be-collapsed-expected.png",
+ "css-text-3/4-1-1-space-following-collapsible-space-in-another-box-should-be-collapsed.html",
+ "css-text-3/4-1-1-space-preceding-br-element-should-not-be-collapsed-expected.png",
+ "css-text-3/4-1-1-space-preceding-br-element-should-not-be-collapsed.html",
+ "css-text-3/4-1-3-spaces-at-beginning-and-end-of-line-should-be-collapsed-expected.png",
+ "css-text-3/4-1-3-spaces-at-beginning-and-end-of-line-should-be-collapsed.html",
+ "css-text-3/4-1-empty-inline-block-should-be-treated-as-non-empty-text-expected.png",
+ "css-text-3/4-1-empty-inline-block-should-be-treated-as-non-empty-text.html",
+ "css-text-3/5-1-inline-blocks-should-be-treated-as-object-replacement-characters-for-line-wrapping-expected.png",
+ "css-text-3/5-1-inline-blocks-should-be-treated-as-object-replacement-characters-for-line-wrapping.html",
+ "css-text-3/5-1-replaced-elements-should-be-treated-as-object-replacement-characters-for-line-wrapping-expected.png",
+ "css-text-3/5-1-replaced-elements-should-be-treated-as-object-replacement-characters-for-line-wrapping.html",
+ "css-text-3/5-absolute-boxes-in-span-should-not-impact-line-wrapping-expected.png",
+ "css-text-3/5-absolute-boxes-in-span-should-not-impact-line-wrapping-rtl-expected.png",
+ "css-text-3/5-absolute-boxes-in-span-should-not-impact-line-wrapping-rtl.html",
+ "css-text-3/5-absolute-boxes-in-span-should-not-impact-line-wrapping.html",
+ "css-text-3/5-collapsible-leading-white-space-should-not-prevent-soft-wrap-opportunity-expected.png",
+ "css-text-3/5-collapsible-leading-white-space-should-not-prevent-soft-wrap-opportunity.html",
+ "css-text-3/5-collapsible-trailing-white-space-should-not-prevent-soft-wrap-opportunity-expected.png",
+ "css-text-3/5-collapsible-trailing-white-space-should-not-prevent-soft-wrap-opportunity.html",
+ "css-text-3/5-collapsible-trailing-white-space-that-overflows-line-prior-to-collapse-should-not-cause-wrap-expected.png",
+ "css-text-3/5-collapsible-trailing-white-space-that-overflows-line-prior-to-collapse-should-not-cause-wrap.html",
+ "css-text-3/5-element-edge-should-not-introduce-soft-wrap-opportunity-expected.png",
+ "css-text-3/5-element-edge-should-not-introduce-soft-wrap-opportunity.html",
+ "css-text-3/5-end-edge-but-not-start-edge-of-non-zero-padding-span-should-justify-line-existence-expected.png",
+ "css-text-3/5-end-edge-but-not-start-edge-of-non-zero-padding-span-should-justify-line-existence.html",
+ "css-text-3/5-multiple-boxes-in-span-should-wrap-at-first-breakable-location-on-first-box-overflow-break-word-expected.png",
+ "css-text-3/5-multiple-boxes-in-span-should-wrap-at-first-breakable-location-on-first-box-overflow-break-word.html",
+ "css-text-3/5-multiple-boxes-in-span-should-wrap-at-first-breakable-location-on-first-box-overflow-normal-expected.png",
+ "css-text-3/5-multiple-boxes-in-span-should-wrap-at-first-breakable-location-on-first-box-overflow-normal.html",
+ "css-text-3/5-multiple-boxes-in-span-should-wrap-at-first-breakable-location-on-second-box-overflow-break-word-expected.png",
+ "css-text-3/5-multiple-boxes-in-span-should-wrap-at-first-breakable-location-on-second-box-overflow-break-word.html",
+ "css-text-3/5-multiple-boxes-in-span-should-wrap-at-first-breakable-location-on-second-box-overflow-normal-expected.png",
+ "css-text-3/5-multiple-boxes-in-span-should-wrap-at-first-breakable-location-on-second-box-overflow-normal.html",
+ "css-text-3/5-multiple-boxes-in-span-should-wrap-at-last-breakable-location-within-width-to-prevent-overflow-expected.png",
+ "css-text-3/5-multiple-boxes-in-span-should-wrap-at-last-breakable-location-within-width-to-prevent-overflow.html",
+ "css-text-3/5-multiple-boxes-in-span-should-wrap-at-last-soft-wrap-location-within-width-expected.png",
+ "css-text-3/5-multiple-boxes-in-span-should-wrap-at-last-soft-wrap-location-within-width.html",
+ "css-text-3/5-multiple-boxes-should-wrap-at-first-breakable-location-on-first-box-overflow-break-word-expected.png",
+ "css-text-3/5-multiple-boxes-should-wrap-at-first-breakable-location-on-first-box-overflow-break-word.html",
+ "css-text-3/5-multiple-boxes-should-wrap-at-first-breakable-location-on-first-box-overflow-normal-expected.png",
+ "css-text-3/5-multiple-boxes-should-wrap-at-first-breakable-location-on-first-box-overflow-normal.html",
+ "css-text-3/5-multiple-boxes-should-wrap-at-first-breakable-location-on-second-box-overflow-break-word-expected.png",
+ "css-text-3/5-multiple-boxes-should-wrap-at-first-breakable-location-on-second-box-overflow-break-word.html",
+ "css-text-3/5-multiple-boxes-should-wrap-at-first-breakable-location-on-second-box-overflow-normal-expected.png",
+ "css-text-3/5-multiple-boxes-should-wrap-at-first-breakable-location-on-second-box-overflow-normal.html",
+ "css-text-3/5-multiple-boxes-should-wrap-at-last-breakable-location-within-width-to-prevent-overflow-expected.png",
+ "css-text-3/5-multiple-boxes-should-wrap-at-last-breakable-location-within-width-to-prevent-overflow.html",
+ "css-text-3/5-multiple-boxes-should-wrap-at-last-soft-wrap-location-within-width-expected.png",
+ "css-text-3/5-multiple-boxes-should-wrap-at-last-soft-wrap-location-within-width.html",
+ "css-text-3/5-span-with-overflowing-padding-should-attempt-to-wrap-line-before-expected.png",
+ "css-text-3/5-span-with-overflowing-padding-should-attempt-to-wrap-line-before.html",
+ "css-text-3/5-span-within-span-with-overflowing-padding-should-attempt-to-wrap-line-before-expected.png",
+ "css-text-3/5-span-within-span-with-overflowing-padding-should-attempt-to-wrap-line-before.html",
+ "css-text-3/5-unwrappable-collapsible-leading-white-space-should-not-count-against-available-width-expected.png",
+ "css-text-3/5-unwrappable-collapsible-leading-white-space-should-not-count-against-available-width.html",
+ "css-text-3/5-wrappable-collapsible-leading-white-space-should-not-count-against-available-width-expected.png",
+ "css-text-3/5-wrappable-collapsible-leading-white-space-should-not-count-against-available-width.html",
+ "css-text-3/6-2-gracefully-handle-unbreakable-overflow-word-with-overflow-wrap-break-word-expected.png",
+ "css-text-3/6-2-gracefully-handle-unbreakable-overflow-word-with-overflow-wrap-break-word.html",
+ "css-text-3/6-2-words-should-be-breakable-with-overflow-wrap-break-word-if-first-on-line-expected.png",
+ "css-text-3/6-2-words-should-be-breakable-with-overflow-wrap-break-word-if-first-on-line.html",
+ "css-text-3/6-2-words-should-be-breakable-with-word-wrap-break-word-if-first-on-line-expected.png",
+ "css-text-3/6-2-words-should-be-breakable-with-word-wrap-break-word-if-first-on-line.html",
+ "css-text-3/6-2-words-should-not-be-breakable-with-overflow-wrap-break-word-if-not-first-on-line-expected.png",
+ "css-text-3/6-2-words-should-not-be-breakable-with-overflow-wrap-break-word-if-not-first-on-line.html",
+ "css-text-3/6-2-words-should-not-be-breakable-with-word-wrap-break-word-if-not-first-on-line-expected.png",
+ "css-text-3/6-2-words-should-not-be-breakable-with-word-wrap-break-word-if-not-first-on-line.html",
+ "css-text-3/7-1-text-align-end-value-should-align-with-end-edge-of-line-box-expected.png",
+ "css-text-3/7-1-text-align-end-value-should-align-with-end-edge-of-line-box.html",
+ "css-text-3/7-1-text-align-should-not-double-shift-contents-of-inline-container-box-expected.png",
+ "css-text-3/7-1-text-align-should-not-double-shift-contents-of-inline-container-box.html",
+ "css-text-3/7-1-text-align-should-not-impact-spacing-between-boxes-expected.png",
+ "css-text-3/7-1-text-align-should-not-impact-spacing-between-boxes.html",
+ "css-text-3/7-1-text-align-start-value-should-align-with-start-edge-of-line-box-expected.png",
+ "css-text-3/7-1-text-align-start-value-should-align-with-start-edge-of-line-box.html",
+ "css-text-3/7-1-text-align-values-left-center-right-should-not-base-alignment-on-line-box-direction-expected.png",
+ "css-text-3/7-1-text-align-values-left-center-right-should-not-base-alignment-on-line-box-direction.html",
+ "css-text-3/9-1-negative-text-indent-should-indent-first-line-in-paragraph-to-the-left-expected.png",
+ "css-text-3/9-1-negative-text-indent-should-indent-first-line-in-paragraph-to-the-left.html",
+ "css-text-3/9-1-negative-text-indent-should-indent-first-line-in-rtl-paragraph-to-the-right-expected.png",
+ "css-text-3/9-1-negative-text-indent-should-indent-first-line-in-rtl-paragraph-to-the-right.html",
+ "css-text-3/9-1-negative-text-indent-should-indent-nested-inline-block-to-the-left-expected.png",
+ "css-text-3/9-1-negative-text-indent-should-indent-nested-inline-block-to-the-left.html",
+ "css-text-3/9-1-negative-text-indent-should-indent-nested-inline-rtl-block-to-the-right-expected.png",
+ "css-text-3/9-1-negative-text-indent-should-indent-nested-inline-rtl-block-to-the-right.html",
+ "css-text-3/9-1-text-indent-should-indent-first-line-in-paragraph-expected.png",
+ "css-text-3/9-1-text-indent-should-indent-first-line-in-paragraph.html",
+ "css-text-3/9-1-text-indent-should-indent-first-line-in-rtl-paragraph-on-right-expected.png",
+ "css-text-3/9-1-text-indent-should-indent-first-line-in-rtl-paragraph-on-right.html",
+ "css-text-3/9-1-text-indent-should-indent-nested-inline-block-expected.png",
+ "css-text-3/9-1-text-indent-should-indent-nested-inline-block.html",
+ "css-text-3/9-1-text-indent-should-indent-nested-inline-rtl-block-expected.png",
+ "css-text-3/9-1-text-indent-should-indent-nested-inline-rtl-block.html",
+ "css-text-3/9-1-text-indent-should-not-indent-nested-span-expected.png",
+ "css-text-3/9-1-text-indent-should-not-indent-nested-span.html",
+ "css-text-3/layout_tests.txt",
+ "css-transforms/15-1-matrix-transform-function-expected.png",
+ "css-transforms/15-1-matrix-transform-function-with-transform-origin-expected.png",
+ "css-transforms/15-1-matrix-transform-function-with-transform-origin.html",
+ "css-transforms/15-1-matrix-transform-function.html",
+ "css-transforms/15-1-rotate-transform-function-expected.png",
+ "css-transforms/15-1-rotate-transform-function-with-transform-origin-expected.png",
+ "css-transforms/15-1-rotate-transform-function-with-transform-origin.html",
+ "css-transforms/15-1-rotate-transform-function.html",
+ "css-transforms/15-1-scale-transform-function-expected.png",
+ "css-transforms/15-1-scale-transform-function-with-transform-origin-expected.png",
+ "css-transforms/15-1-scale-transform-function-with-transform-origin.html",
+ "css-transforms/15-1-scale-transform-function.html",
+ "css-transforms/4-bounding-box-should-be-border-box-expected.png",
+ "css-transforms/4-bounding-box-should-be-border-box.html",
+ "css-transforms/7-replaced-boxes-transform-expected.png",
+ "css-transforms/7-replaced-boxes-transform.html",
+ "css-transforms/7-transform-should-establish-stacking-context-expected.png",
+ "css-transforms/7-transform-should-establish-stacking-context.html",
+ "css-transforms/7-translation-transform-supports-percentages-expected.png",
+ "css-transforms/7-translation-transform-supports-percentages.html",
+ "css-transforms/layout_tests.txt",
+ "css3-animations/3-animations-during-display-none-waits-expected.png",
+ "css3-animations/3-animations-during-display-none-waits-from-ancestor-expected.png",
+ "css3-animations/3-animations-during-display-none-waits-from-ancestor.html",
+ "css3-animations/3-animations-during-display-none-waits.html",
+ "css3-animations/3-animations-reset-on-display-change-not-computed-style-update-expected.png",
+ "css3-animations/3-animations-reset-on-display-change-not-computed-style-update.html",
+ "css3-animations/3-animations-restart-after-display-none-expected.png",
+ "css3-animations/3-animations-restart-after-display-none-from-ancestor-expected.png",
+ "css3-animations/3-animations-restart-after-display-none-from-ancestor.html",
+ "css3-animations/3-animations-restart-after-display-none.html",
+ "css3-animations/3-simple-animation-expected.png",
+ "css3-animations/3-simple-animation.html",
+ "css3-animations/canceled_animation_should_not_get_animationend_event-expected.png",
+ "css3-animations/canceled_animation_should_not_get_animationend_event.html",
+ "css3-animations/layout_tests.txt",
+ "css3-animations/removed_animation_from_other_animationend_event_handler_should_still_get_animationend_event-expected.png",
+ "css3-animations/removed_animation_from_other_animationend_event_handler_should_still_get_animationend_event.html",
+ "css3-background/14-2-1-background-positioning-area-is-smaller-than-image-size-expected.png",
+ "css3-background/14-2-1-background-positioning-area-is-smaller-than-image-size.html",
+ "css3-background/14-2-1-background-with-color-expected.png",
+ "css3-background/14-2-1-background-with-color-image-expected.png",
+ "css3-background/14-2-1-background-with-color-image-position-expected.png",
+ "css3-background/14-2-1-background-with-color-image-position-repeat-expected.png",
+ "css3-background/14-2-1-background-with-color-image-position-repeat.html",
+ "css3-background/14-2-1-background-with-color-image-position.html",
+ "css3-background/14-2-1-background-with-color-image-repeat-expected.png",
+ "css3-background/14-2-1-background-with-color-image-repeat-position-expected.png",
+ "css3-background/14-2-1-background-with-color-image-repeat-position.html",
+ "css3-background/14-2-1-background-with-color-image-repeat.html",
+ "css3-background/14-2-1-background-with-color-image.html",
+ "css3-background/14-2-1-background-with-color-position-expected.png",
+ "css3-background/14-2-1-background-with-color-position-image-expected.png",
+ "css3-background/14-2-1-background-with-color-position-image.html",
+ "css3-background/14-2-1-background-with-color-position-repeat-expected.png",
+ "css3-background/14-2-1-background-with-color-position-repeat.html",
+ "css3-background/14-2-1-background-with-color-position.html",
+ "css3-background/14-2-1-background-with-color-repeat-expected.png",
+ "css3-background/14-2-1-background-with-color-repeat-image-expected.png",
+ "css3-background/14-2-1-background-with-color-repeat-image-position-expected.png",
+ "css3-background/14-2-1-background-with-color-repeat-image-position.html",
+ "css3-background/14-2-1-background-with-color-repeat-image.html",
+ "css3-background/14-2-1-background-with-color-repeat-position-expected.png",
+ "css3-background/14-2-1-background-with-color-repeat-position-image-expected.png",
+ "css3-background/14-2-1-background-with-color-repeat-position-image.html",
+ "css3-background/14-2-1-background-with-color-repeat-position.html",
+ "css3-background/14-2-1-background-with-color-repeat.html",
+ "css3-background/14-2-1-background-with-color.html",
+ "css3-background/14-2-1-background-with-image-color-expected.png",
+ "css3-background/14-2-1-background-with-image-color-position-expected.png",
+ "css3-background/14-2-1-background-with-image-color-position-repeat-expected.png",
+ "css3-background/14-2-1-background-with-image-color-position-repeat.html",
+ "css3-background/14-2-1-background-with-image-color-position.html",
+ "css3-background/14-2-1-background-with-image-color-repeat-expected.png",
+ "css3-background/14-2-1-background-with-image-color-repeat-position-expected.png",
+ "css3-background/14-2-1-background-with-image-color-repeat-position.html",
+ "css3-background/14-2-1-background-with-image-color-repeat.html",
+ "css3-background/14-2-1-background-with-image-color.html",
+ "css3-background/14-2-1-background-with-image-position-color-expected.png",
+ "css3-background/14-2-1-background-with-image-position-color-repeat-expected.png",
+ "css3-background/14-2-1-background-with-image-position-color-repeat.html",
+ "css3-background/14-2-1-background-with-image-position-color.html",
+ "css3-background/14-2-1-background-with-image-position-expected.png",
+ "css3-background/14-2-1-background-with-image-position-repeat-color-expected.png",
+ "css3-background/14-2-1-background-with-image-position-repeat-color.html",
+ "css3-background/14-2-1-background-with-image-position-repeat-expected.png",
+ "css3-background/14-2-1-background-with-image-position-repeat.html",
+ "css3-background/14-2-1-background-with-image-position.html",
+ "css3-background/14-2-1-background-with-image-repeat-color-expected.png",
+ "css3-background/14-2-1-background-with-image-repeat-color-position-expected.png",
+ "css3-background/14-2-1-background-with-image-repeat-color-position.html",
+ "css3-background/14-2-1-background-with-image-repeat-color.html",
+ "css3-background/14-2-1-background-with-image-repeat-position-color-expected.png",
+ "css3-background/14-2-1-background-with-image-repeat-position-color.html",
+ "css3-background/14-2-1-background-with-image-repeat-position-expected.png",
+ "css3-background/14-2-1-background-with-image-repeat-position.html",
+ "css3-background/14-2-1-background-with-opaque-image-color-expected.png",
+ "css3-background/14-2-1-background-with-opaque-image-color.html",
+ "css3-background/14-2-1-background-with-opaque-image-transform-expected.png",
+ "css3-background/14-2-1-background-with-opaque-image-transform.html",
+ "css3-background/14-2-1-background-with-position-color-expected.png",
+ "css3-background/14-2-1-background-with-position-color-image-expected.png",
+ "css3-background/14-2-1-background-with-position-color-image-repeat-expected.png",
+ "css3-background/14-2-1-background-with-position-color-image-repeat.html",
+ "css3-background/14-2-1-background-with-position-color-image.html",
+ "css3-background/14-2-1-background-with-position-color-repeat-expected.png",
+ "css3-background/14-2-1-background-with-position-color-repeat-image-expected.png",
+ "css3-background/14-2-1-background-with-position-color-repeat-image.html",
+ "css3-background/14-2-1-background-with-position-color-repeat.html",
+ "css3-background/14-2-1-background-with-position-color.html",
+ "css3-background/14-2-1-background-with-position-image-color-expected.png",
+ "css3-background/14-2-1-background-with-position-image-color-repeat-expected.png",
+ "css3-background/14-2-1-background-with-position-image-color-repeat.html",
+ "css3-background/14-2-1-background-with-position-image-color.html",
+ "css3-background/14-2-1-background-with-position-image-expected.png",
+ "css3-background/14-2-1-background-with-position-image-repeat-color-expected.png",
+ "css3-background/14-2-1-background-with-position-image-repeat-color.html",
+ "css3-background/14-2-1-background-with-position-image-repeat-expected.png",
+ "css3-background/14-2-1-background-with-position-image-repeat.html",
+ "css3-background/14-2-1-background-with-position-image.html",
+ "css3-background/14-2-1-background-with-position-repeat-color-expected.png",
+ "css3-background/14-2-1-background-with-position-repeat-color-image-expected.png",
+ "css3-background/14-2-1-background-with-position-repeat-color-image.html",
+ "css3-background/14-2-1-background-with-position-repeat-color.html",
+ "css3-background/14-2-1-background-with-position-repeat-expected.png",
+ "css3-background/14-2-1-background-with-position-repeat-image-color-expected.png",
+ "css3-background/14-2-1-background-with-position-repeat-image-color.html",
+ "css3-background/14-2-1-background-with-position-repeat-image-expected.png",
+ "css3-background/14-2-1-background-with-position-repeat-image.html",
+ "css3-background/14-2-1-background-with-position-repeat.html",
+ "css3-background/14-2-1-background-with-repeat-color-expected.png",
+ "css3-background/14-2-1-background-with-repeat-color-image-expected.png",
+ "css3-background/14-2-1-background-with-repeat-color-image-position-expected.png",
+ "css3-background/14-2-1-background-with-repeat-color-image-position.html",
+ "css3-background/14-2-1-background-with-repeat-color-image.html",
+ "css3-background/14-2-1-background-with-repeat-color-position-expected.png",
+ "css3-background/14-2-1-background-with-repeat-color-position-image-expected.png",
+ "css3-background/14-2-1-background-with-repeat-color-position-image.html",
+ "css3-background/14-2-1-background-with-repeat-color-position.html",
+ "css3-background/14-2-1-background-with-repeat-color.html",
+ "css3-background/14-2-1-background-with-repeat-image-color-expected.png",
+ "css3-background/14-2-1-background-with-repeat-image-color-position-expected.png",
+ "css3-background/14-2-1-background-with-repeat-image-color-position.html",
+ "css3-background/14-2-1-background-with-repeat-image-color.html",
+ "css3-background/14-2-1-background-with-repeat-image-expected.png",
+ "css3-background/14-2-1-background-with-repeat-image-position-color-expected.png",
+ "css3-background/14-2-1-background-with-repeat-image-position-color.html",
+ "css3-background/14-2-1-background-with-repeat-image-position-expected.png",
+ "css3-background/14-2-1-background-with-repeat-image-position.html",
+ "css3-background/14-2-1-background-with-repeat-image.html",
+ "css3-background/14-2-1-background-with-repeat-position-color-expected.png",
+ "css3-background/14-2-1-background-with-repeat-position-color-image-expected.png",
+ "css3-background/14-2-1-background-with-repeat-position-color-image.html",
+ "css3-background/14-2-1-background-with-repeat-position-color.html",
+ "css3-background/14-2-1-background-with-repeat-position-expected.png",
+ "css3-background/14-2-1-background-with-repeat-position-image-color-expected.png",
+ "css3-background/14-2-1-background-with-repeat-position-image-color.html",
+ "css3-background/14-2-1-background-with-repeat-position-image-expected.png",
+ "css3-background/14-2-1-background-with-repeat-position-image.html",
+ "css3-background/14-2-1-background-with-repeat-position.html",
+ "css3-background/14-2-1-multiple-layers-background-image-expected.png",
+ "css3-background/14-2-1-multiple-layers-background-image.html",
+ "css3-background/3-10-background-none-declares-initial-value-for-background-color-expected.png",
+ "css3-background/3-10-background-none-declares-initial-value-for-background-color.html",
+ "css3-background/3-11-2-body-background-can-be-set-to-transparent-later-expected.png",
+ "css3-background/3-11-2-body-background-can-be-set-to-transparent-later.html",
+ "css3-background/3-11-2-propagate-computed-value-of-background-from-body-to-root-expected.png",
+ "css3-background/3-11-2-propagate-computed-value-of-background-from-body-to-root.html",
+ "css3-background/3-11-2-propagate-computed-value-of-background-from-html-to-root-expected.png",
+ "css3-background/3-11-2-propagate-computed-value-of-background-from-html-to-root.html",
+ "css3-background/4-0-border-1-individual-edge-expected.png",
+ "css3-background/4-0-border-1-individual-edge-with-rounded-corners-expected.png",
+ "css3-background/4-0-border-1-individual-edge-with-rounded-corners.html",
+ "css3-background/4-0-border-1-individual-edge.html",
+ "css3-background/4-0-border-2-individual-edges-expected.png",
+ "css3-background/4-0-border-2-individual-edges-with-rounded-corners-expected.png",
+ "css3-background/4-0-border-2-individual-edges-with-rounded-corners.html",
+ "css3-background/4-0-border-2-individual-edges.html",
+ "css3-background/4-0-border-color-with-solid-border-style-and-background-color-expected.png",
+ "css3-background/4-0-border-color-with-solid-border-style-and-background-color.html",
+ "css3-background/4-0-border-color-with-solid-border-style-expected.png",
+ "css3-background/4-0-border-color-with-solid-border-style.html",
+ "css3-background/4-0-border-set-using-border-color-and-border-style-expected.png",
+ "css3-background/4-0-border-set-using-border-color-and-border-style.html",
+ "css3-background/4-0-border-set-using-border-color-border-width-and-border-style-expected.png",
+ "css3-background/4-0-border-set-using-border-color-border-width-and-border-style.html",
+ "css3-background/4-0-border-set-using-border-width-and-border-style-expected.png",
+ "css3-background/4-0-border-set-using-border-width-and-border-style.html",
+ "css3-background/4-0-border-set-using-border-width-border-color-and-border-style-with-empty-content-expected.png",
+ "css3-background/4-0-border-set-using-border-width-border-color-and-border-style-with-empty-content.html",
+ "css3-background/4-0-border-style-hidden-with-only-background-color-expected.png",
+ "css3-background/4-0-border-style-hidden-with-only-background-color.html",
+ "css3-background/4-0-border-width-with-solid-border-style-expected.png",
+ "css3-background/4-0-border-width-with-solid-border-style.html",
+ "css3-background/4-0-different-border-styles-with-different-border-widths-expected.png",
+ "css3-background/4-0-different-border-styles-with-different-border-widths.html",
+ "css3-background/4-0-different-border-widths-with-solid-border-style-expected.png",
+ "css3-background/4-0-different-border-widths-with-solid-border-style.html",
+ "css3-background/4-1-border-color-2-values-with-rounded-corners-expected.png",
+ "css3-background/4-1-border-color-2-values-with-rounded-corners.html",
+ "css3-background/4-1-border-color-3-values-with-rounded-corners-expected.png",
+ "css3-background/4-1-border-color-3-values-with-rounded-corners.html",
+ "css3-background/4-1-border-color-4-values-with-different-rounded-corners-and-different-border-widths-expected.png",
+ "css3-background/4-1-border-color-4-values-with-different-rounded-corners-and-different-border-widths.html",
+ "css3-background/4-1-border-color-4-values-with-different-rounded-corners-expected.png",
+ "css3-background/4-1-border-color-4-values-with-different-rounded-corners.html",
+ "css3-background/4-1-border-color-4-values-with-rounded-corners-and-different-border-widths-expected.png",
+ "css3-background/4-1-border-color-4-values-with-rounded-corners-and-different-border-widths.html",
+ "css3-background/4-1-border-color-4-values-with-rounded-corners-expected.png",
+ "css3-background/4-1-border-color-4-values-with-rounded-corners.html",
+ "css3-background/4-3-border-width-2-values-with-rounded-corners-expected.png",
+ "css3-background/4-3-border-width-2-values-with-rounded-corners.html",
+ "css3-background/4-3-border-width-3-values-with-rounded-corners-expected.png",
+ "css3-background/4-3-border-width-3-values-with-rounded-corners.html",
+ "css3-background/4-3-border-width-4-values-with-different-rounded-corners-expected.png",
+ "css3-background/4-3-border-width-4-values-with-different-rounded-corners.html",
+ "css3-background/4-3-border-width-4-values-with-rounded-corners-expected.png",
+ "css3-background/4-3-border-width-4-values-with-rounded-corners.html",
+ "css3-background/5-0-border-radius-2-values-with-background-color-and-border-and-inset-shadow.html",
+ "css3-background/5-0-border-radius-2-values-with-background-color-and-border-and-outset-shadow.html",
+ "css3-background/5-0-border-radius-2-values-with-background-color-and-border.html",
+ "css3-background/5-0-border-radius-2-values-with-background-color-and-inset-shadow.html",
+ "css3-background/5-0-border-radius-2-values-with-background-color-and-outset-shadow.html",
+ "css3-background/5-0-border-radius-2-values-with-background-color-expected.png",
+ "css3-background/5-0-border-radius-2-values-with-background-color.html",
+ "css3-background/5-0-border-radius-2-values-with-border-only-expected.png",
+ "css3-background/5-0-border-radius-2-values-with-border-only.html",
+ "css3-background/5-0-border-radius-3-values-with-background-color-and-border-and-inset-shadow.html",
+ "css3-background/5-0-border-radius-3-values-with-background-color-and-border-and-outset-shadow.html",
+ "css3-background/5-0-border-radius-3-values-with-background-color-and-border.html",
+ "css3-background/5-0-border-radius-3-values-with-background-color-and-inset-shadow.html",
+ "css3-background/5-0-border-radius-3-values-with-background-color-and-outset-shadow.html",
+ "css3-background/5-0-border-radius-3-values-with-background-color-expected.png",
+ "css3-background/5-0-border-radius-3-values-with-background-color.html",
+ "css3-background/5-0-border-radius-3-values-with-border-only-expected.png",
+ "css3-background/5-0-border-radius-3-values-with-border-only.html",
+ "css3-background/5-0-border-radius-4-values-with-background-color-and-border-and-inset-shadow.html",
+ "css3-background/5-0-border-radius-4-values-with-background-color-and-border-and-outset-shadow.html",
+ "css3-background/5-0-border-radius-4-values-with-background-color-and-border.html",
+ "css3-background/5-0-border-radius-4-values-with-background-color-and-inset-shadow.html",
+ "css3-background/5-0-border-radius-4-values-with-background-color-and-outset-shadow.html",
+ "css3-background/5-0-border-radius-4-values-with-background-color-expected.png",
+ "css3-background/5-0-border-radius-4-values-with-background-color.html",
+ "css3-background/5-0-border-radius-4-values-with-border-only-expected.png",
+ "css3-background/5-0-border-radius-4-values-with-border-only.html",
+ "css3-background/5-0-border-radius-circle-with-background-color-and-border-expected.png",
+ "css3-background/5-0-border-radius-circle-with-background-color-and-border.html",
+ "css3-background/5-0-border-radius-circle-with-background-image-and-border-expected.png",
+ "css3-background/5-0-border-radius-circle-with-background-image-and-border.html",
+ "css3-background/5-0-border-radius-circle-with-irrational-out-of-bounds-radius-expected.png",
+ "css3-background/5-0-border-radius-circle-with-irrational-out-of-bounds-radius.html",
+ "css3-background/5-0-border-radius-circle-with-out-of-bounds-radius-expected.png",
+ "css3-background/5-0-border-radius-circle-with-out-of-bounds-radius.html",
+ "css3-background/5-0-border-radius-with-background-color-and-border-expected.png",
+ "css3-background/5-0-border-radius-with-background-color-and-border.html",
+ "css3-background/5-0-border-radius-with-background-color-expected.png",
+ "css3-background/5-0-border-radius-with-background-color.html",
+ "css3-background/5-0-border-radius-with-background-image-and-border-expected.png",
+ "css3-background/5-0-border-radius-with-background-image-and-border.html",
+ "css3-background/5-0-border-radius-with-background-image-expected.png",
+ "css3-background/5-0-border-radius-with-background-image.html",
+ "css3-background/5-0-border-radius-with-border-only-expected.png",
+ "css3-background/5-0-border-radius-with-border-only.html",
+ "css3-background/5-0-border-radius-with-zero-value-and-background-color-and-border-and-inset-shadow.html",
+ "css3-background/5-0-border-radius-with-zero-value-and-background-color-and-border-and-outset-shadow.html",
+ "css3-background/5-0-border-radius-with-zero-value-and-background-color-and-border.html",
+ "css3-background/5-0-border-radius-with-zero-value-and-background-color-and-inset-shadow.html",
+ "css3-background/5-0-border-radius-with-zero-value-and-background-color-and-outset-shadow.html",
+ "css3-background/5-0-border-radius-with-zero-value-and-background-color-expected.png",
+ "css3-background/5-0-border-radius-with-zero-value-and-background-color.html",
+ "css3-background/5-0-border-radius-with-zero-value-and-border-only-expected.png",
+ "css3-background/5-0-border-radius-with-zero-value-and-border-only.html",
+ "css3-background/7-1-1-box-shadow-with-inset-and-blur-and-spread-expected.png",
+ "css3-background/7-1-1-box-shadow-with-inset-and-blur-and-spread.html",
+ "css3-background/7-1-1-box-shadow-with-inset-expected.png",
+ "css3-background/7-1-1-box-shadow-with-inset.html",
+ "css3-background/7-1-1-box-shadow-with-spread-expected.png",
+ "css3-background/7-1-1-box-shadow-with-spread.html",
+ "css3-background/7-1-1-inset-box-shadow-applies-to-padding-box-expected.png",
+ "css3-background/7-1-1-inset-box-shadow-applies-to-padding-box.html",
+ "css3-background/7-1-1-outset-box-shadow-applies-to-border-box-expected.png",
+ "css3-background/7-1-1-outset-box-shadow-applies-to-border-box.html",
+ "css3-background/7-1-2-box-shadow-with-blur-expected.png",
+ "css3-background/7-1-2-box-shadow-with-blur.html",
+ "css3-background/7-1-box-shadow-appears-even-with-overflow-hidden-expected.png",
+ "css3-background/7-1-box-shadow-appears-even-with-overflow-hidden.html",
+ "css3-background/7-1-box-shadow-circle-with-inset-negative-spread-blur-and-rounded-corners-expected.png",
+ "css3-background/7-1-box-shadow-circle-with-inset-negative-spread-blur-and-rounded-corners.html",
+ "css3-background/7-1-box-shadow-circle-with-inset-spread-blur-and-rounded-corners-expected.png",
+ "css3-background/7-1-box-shadow-circle-with-inset-spread-blur-and-rounded-corners.html",
+ "css3-background/7-1-box-shadow-circle-with-negative-spread-blur-and-rounded-corners-expected.png",
+ "css3-background/7-1-box-shadow-circle-with-negative-spread-blur-and-rounded-corners.html",
+ "css3-background/7-1-box-shadow-circle-with-spread-blur-and-rounded-corners-expected.png",
+ "css3-background/7-1-box-shadow-circle-with-spread-blur-and-rounded-corners.html",
+ "css3-background/7-1-box-shadow-offset-top-left-expected.png",
+ "css3-background/7-1-box-shadow-offset-top-left.html",
+ "css3-background/7-1-box-shadow-with-inset-and-outset-blur-expected.png",
+ "css3-background/7-1-box-shadow-with-inset-and-outset-blur.html",
+ "css3-background/7-1-box-shadow-with-inset-negative-spread-and-rounded-corners-expected.png",
+ "css3-background/7-1-box-shadow-with-inset-negative-spread-and-rounded-corners.html",
+ "css3-background/7-1-box-shadow-with-inset-spread-and-rounded-corners-expected.png",
+ "css3-background/7-1-box-shadow-with-inset-spread-and-rounded-corners.html",
+ "css3-background/7-1-box-shadow-with-inset-spread-blur-and-rounded-corners-expected.png",
+ "css3-background/7-1-box-shadow-with-inset-spread-blur-and-rounded-corners.html",
+ "css3-background/7-1-box-shadow-with-negative-spread-and-rounded-corners-expected.png",
+ "css3-background/7-1-box-shadow-with-negative-spread-and-rounded-corners.html",
+ "css3-background/7-1-box-shadow-with-spread-and-blur-expected.png",
+ "css3-background/7-1-box-shadow-with-spread-and-blur.html",
+ "css3-background/7-1-box-shadow-with-spread-and-rounded-corners-expected.png",
+ "css3-background/7-1-box-shadow-with-spread-and-rounded-corners.html",
+ "css3-background/7-1-box-shadow-with-spread-blur-and-rounded-corners-expected.png",
+ "css3-background/7-1-box-shadow-with-spread-blur-and-rounded-corners.html",
+ "css3-background/7-1-circle-box-shadow-offset-top-left-expected.png",
+ "css3-background/7-1-circle-box-shadow-offset-top-left.html",
+ "css3-background/7-1-circle-box-shadow-with-inset-expected.png",
+ "css3-background/7-1-circle-box-shadow-with-inset.html",
+ "css3-background/7-1-circle-box-shadow-with-spread-expected.png",
+ "css3-background/7-1-circle-box-shadow-with-spread.html",
+ "css3-background/7-1-simple-black-box-shadow-expected.png",
+ "css3-background/7-1-simple-black-box-shadow.html",
+ "css3-background/7-1-simple-red-box-shadow-expected.png",
+ "css3-background/7-1-simple-red-box-shadow.html",
+ "css3-background/cobalt.png",
+ "css3-background/cobalt_logo.jpg",
+ "css3-background/cobalt_opaque.jpg",
+ "css3-background/layout_tests.txt",
+ "css3-background/legend-sprite.png",
+ "css3-background/test.png",
+ "css3-color/3-2-nested-opacity-compounds-expected.png",
+ "css3-color/3-2-nested-opacity-compounds.html",
+ "css3-color/3-2-non-positioned-element-with-opacity-forms-stacking-context-expected.png",
+ "css3-color/3-2-non-positioned-element-with-opacity-forms-stacking-context.html",
+ "css3-color/3-2-non-positioned-element-with-opacity-impacts-positioned-descendants-expected.png",
+ "css3-color/3-2-non-positioned-element-with-opacity-impacts-positioned-descendants.html",
+ "css3-color/3-2-opacity-applies-to-children-expected.png",
+ "css3-color/3-2-opacity-applies-to-children.html",
+ "css3-color/3-2-opacity-applies-to-text-expected.png",
+ "css3-color/3-2-opacity-applies-to-text.html",
+ "css3-color/3-2-opacity-does-apply-to-background-expected.png",
+ "css3-color/3-2-opacity-does-apply-to-background.html",
+ "css3-color/layout_tests.txt",
+ "css3-conditional/7-4-cssmediarule-expected.png",
+ "css3-conditional/7-4-cssmediarule.html",
+ "css3-conditional/layout_tests.txt",
+ "css3-flexbox/absolutely-positioned-children-expected.png",
+ "css3-flexbox/absolutely-positioned-children.html",
+ "css3-flexbox/combined-baseline-expected.png",
+ "css3-flexbox/combined-baseline.html",
+ "css3-flexbox/combined-container-sizing-edge-cases-expected.png",
+ "css3-flexbox/combined-container-sizing-edge-cases.html",
+ "css3-flexbox/combined-order-and-multiline-expected.png",
+ "css3-flexbox/combined-order-and-multiline.html",
+ "css3-flexbox/combined-positioning-tests-expected.png",
+ "css3-flexbox/combined-positioning-tests.html",
+ "css3-flexbox/combined-shrinking-and-justify-content-expected.png",
+ "css3-flexbox/combined-shrinking-and-justify-content.html",
+ "css3-flexbox/combined-with-baselines-percentages-expected.png",
+ "css3-flexbox/combined-with-baselines-percentages.html",
+ "css3-flexbox/content-based-minimum-size-expected.png",
+ "css3-flexbox/content-based-minimum-size.html",
+ "css3-flexbox/csswg_flex-001-expected.png",
+ "css3-flexbox/csswg_flex-001.html",
+ "css3-flexbox/csswg_flex-002-expected.png",
+ "css3-flexbox/csswg_flex-002.html",
+ "css3-flexbox/csswg_flex-003-expected.png",
+ "css3-flexbox/csswg_flex-003.html",
+ "css3-flexbox/csswg_flex-004-expected.png",
+ "css3-flexbox/csswg_flex-004.html",
+ "css3-flexbox/csswg_flex-basis-001-expected.png",
+ "css3-flexbox/csswg_flex-basis-001.html",
+ "css3-flexbox/csswg_flex-basis-002-expected.png",
+ "css3-flexbox/csswg_flex-basis-002.html",
+ "css3-flexbox/csswg_flex-basis-003-expected.png",
+ "css3-flexbox/csswg_flex-basis-003.html",
+ "css3-flexbox/csswg_flex-basis-004-expected.png",
+ "css3-flexbox/csswg_flex-basis-004.html",
+ "css3-flexbox/csswg_flex-basis-005-expected.png",
+ "css3-flexbox/csswg_flex-basis-005.html",
+ "css3-flexbox/csswg_flex-basis-006-expected.png",
+ "css3-flexbox/csswg_flex-basis-006.html",
+ "css3-flexbox/csswg_flex-basis-007-expected.png",
+ "css3-flexbox/csswg_flex-basis-007.html",
+ "css3-flexbox/csswg_flex-basis-008-expected.png",
+ "css3-flexbox/csswg_flex-basis-008.html",
+ "css3-flexbox/csswg_flex-basis-010-expected.png",
+ "css3-flexbox/csswg_flex-basis-010.html",
+ "css3-flexbox/csswg_flex-grow-001-expected.png",
+ "css3-flexbox/csswg_flex-grow-001.html",
+ "css3-flexbox/csswg_flex-grow-002-expected.png",
+ "css3-flexbox/csswg_flex-grow-002.html",
+ "css3-flexbox/csswg_flex-grow-003-expected.png",
+ "css3-flexbox/csswg_flex-grow-003.html",
+ "css3-flexbox/csswg_flex-grow-004-expected.png",
+ "css3-flexbox/csswg_flex-grow-004.html",
+ "css3-flexbox/csswg_flex-grow-005-expected.png",
+ "css3-flexbox/csswg_flex-grow-005.html",
+ "css3-flexbox/csswg_flex-grow-006-expected.png",
+ "css3-flexbox/csswg_flex-grow-006.html",
+ "css3-flexbox/csswg_flex-grow-007-expected.png",
+ "css3-flexbox/csswg_flex-grow-007.html",
+ "css3-flexbox/csswg_flex-shrink-001-expected.png",
+ "css3-flexbox/csswg_flex-shrink-001.html",
+ "css3-flexbox/csswg_flex-shrink-002-expected.png",
+ "css3-flexbox/csswg_flex-shrink-002.html",
+ "css3-flexbox/csswg_flex-shrink-003-expected.png",
+ "css3-flexbox/csswg_flex-shrink-003.html",
+ "css3-flexbox/csswg_flex-shrink-004-expected.png",
+ "css3-flexbox/csswg_flex-shrink-004.html",
+ "css3-flexbox/csswg_flex-shrink-005-expected.png",
+ "css3-flexbox/csswg_flex-shrink-005.html",
+ "css3-flexbox/csswg_flex-shrink-006-expected.png",
+ "css3-flexbox/csswg_flex-shrink-006.html",
+ "css3-flexbox/csswg_flex-shrink-007-expected.png",
+ "css3-flexbox/csswg_flex-shrink-007.html",
+ "css3-flexbox/csswg_flex-shrink-008-expected.png",
+ "css3-flexbox/csswg_flex-shrink-008.html",
+ "css3-flexbox/csswg_flexbox-flex-basis-content-001a-expected.png",
+ "css3-flexbox/csswg_flexbox-flex-basis-content-001a.html",
+ "css3-flexbox/csswg_flexbox-flex-basis-content-001b-expected.png",
+ "css3-flexbox/csswg_flexbox-flex-basis-content-001b.html",
+ "css3-flexbox/csswg_flexbox-flex-basis-content-002a-expected.png",
+ "css3-flexbox/csswg_flexbox-flex-basis-content-002a.html",
+ "css3-flexbox/csswg_flexbox-flex-basis-content-002b-expected.png",
+ "css3-flexbox/csswg_flexbox-flex-basis-content-002b.html",
+ "css3-flexbox/csswg_flexbox_flex-0-0-0-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-0-0-0-unitless-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-0-0-0-unitless.html",
+ "css3-flexbox/csswg_flexbox_flex-0-0-0.html",
+ "css3-flexbox/csswg_flexbox_flex-0-0-N-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-0-0-N-shrink-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-0-0-N-shrink.html",
+ "css3-flexbox/csswg_flexbox_flex-0-0-N-unitless-basis-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-0-0-N-unitless-basis.html",
+ "css3-flexbox/csswg_flexbox_flex-0-0-N.html",
+ "css3-flexbox/csswg_flexbox_flex-0-0-Npercent-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-0-0-Npercent-shrink-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-0-0-Npercent-shrink.html",
+ "css3-flexbox/csswg_flexbox_flex-0-0-Npercent.html",
+ "css3-flexbox/csswg_flexbox_flex-0-0-auto-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-0-0-auto-shrink-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-0-0-auto-shrink.html",
+ "css3-flexbox/csswg_flexbox_flex-0-0-auto.html",
+ "css3-flexbox/csswg_flexbox_flex-0-0-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-0-0.html",
+ "css3-flexbox/csswg_flexbox_flex-0-1-0-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-0-1-0-unitless-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-0-1-0-unitless.html",
+ "css3-flexbox/csswg_flexbox_flex-0-1-0.html",
+ "css3-flexbox/csswg_flexbox_flex-0-1-1-unitless-basis-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-0-1-1-unitless-basis.html",
+ "css3-flexbox/csswg_flexbox_flex-0-1-N-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-0-1-N-shrink-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-0-1-N-shrink.html",
+ "css3-flexbox/csswg_flexbox_flex-0-1-N-unitless-basis-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-0-1-N-unitless-basis.html",
+ "css3-flexbox/csswg_flexbox_flex-0-1-N.html",
+ "css3-flexbox/csswg_flexbox_flex-0-1-Npercent-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-0-1-Npercent-shrink-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-0-1-Npercent-shrink.html",
+ "css3-flexbox/csswg_flexbox_flex-0-1-Npercent.html",
+ "css3-flexbox/csswg_flexbox_flex-0-1-auto-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-0-1-auto-shrink-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-0-1-auto-shrink.html",
+ "css3-flexbox/csswg_flexbox_flex-0-1-auto.html",
+ "css3-flexbox/csswg_flexbox_flex-0-1-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-0-1.html",
+ "css3-flexbox/csswg_flexbox_flex-0-N-0-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-0-N-0-unitless-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-0-N-0-unitless.html",
+ "css3-flexbox/csswg_flexbox_flex-0-N-0.html",
+ "css3-flexbox/csswg_flexbox_flex-0-N-N-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-0-N-N-shrink-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-0-N-N-shrink.html",
+ "css3-flexbox/csswg_flexbox_flex-0-N-N.html",
+ "css3-flexbox/csswg_flexbox_flex-0-N-Npercent-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-0-N-Npercent-shrink-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-0-N-Npercent-shrink.html",
+ "css3-flexbox/csswg_flexbox_flex-0-N-Npercent.html",
+ "css3-flexbox/csswg_flexbox_flex-0-N-auto-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-0-N-auto-shrink-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-0-N-auto-shrink.html",
+ "css3-flexbox/csswg_flexbox_flex-0-N-auto.html",
+ "css3-flexbox/csswg_flexbox_flex-0-N-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-0-N.html",
+ "css3-flexbox/csswg_flexbox_flex-0-auto-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-0-auto.html",
+ "css3-flexbox/csswg_flexbox_flex-1-0-0-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-1-0-0-unitless-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-1-0-0-unitless.html",
+ "css3-flexbox/csswg_flexbox_flex-1-0-0.html",
+ "css3-flexbox/csswg_flexbox_flex-1-0-N-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-1-0-N-shrink-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-1-0-N-shrink.html",
+ "css3-flexbox/csswg_flexbox_flex-1-0-N.html",
+ "css3-flexbox/csswg_flexbox_flex-1-0-Npercent-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-1-0-Npercent-shrink-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-1-0-Npercent-shrink.html",
+ "css3-flexbox/csswg_flexbox_flex-1-0-Npercent.html",
+ "css3-flexbox/csswg_flexbox_flex-1-0-auto-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-1-0-auto-shrink-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-1-0-auto-shrink.html",
+ "css3-flexbox/csswg_flexbox_flex-1-0-auto.html",
+ "css3-flexbox/csswg_flexbox_flex-1-0-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-1-0.html",
+ "css3-flexbox/csswg_flexbox_flex-1-1-0-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-1-1-0-unitless-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-1-1-0-unitless.html",
+ "css3-flexbox/csswg_flexbox_flex-1-1-0.html",
+ "css3-flexbox/csswg_flexbox_flex-1-1-N-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-1-1-N-shrink-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-1-1-N-shrink.html",
+ "css3-flexbox/csswg_flexbox_flex-1-1-N.html",
+ "css3-flexbox/csswg_flexbox_flex-1-1-Npercent-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-1-1-Npercent-shrink-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-1-1-Npercent-shrink.html",
+ "css3-flexbox/csswg_flexbox_flex-1-1-Npercent.html",
+ "css3-flexbox/csswg_flexbox_flex-1-1-auto-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-1-1-auto-shrink-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-1-1-auto-shrink.html",
+ "css3-flexbox/csswg_flexbox_flex-1-1-auto.html",
+ "css3-flexbox/csswg_flexbox_flex-1-1-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-1-1.html",
+ "css3-flexbox/csswg_flexbox_flex-1-N-0-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-1-N-0-unitless-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-1-N-0-unitless.html",
+ "css3-flexbox/csswg_flexbox_flex-1-N-0.html",
+ "css3-flexbox/csswg_flexbox_flex-1-N-N-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-1-N-N-shrink-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-1-N-N-shrink.html",
+ "css3-flexbox/csswg_flexbox_flex-1-N-N.html",
+ "css3-flexbox/csswg_flexbox_flex-1-N-Npercent-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-1-N-Npercent-shrink-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-1-N-Npercent-shrink.html",
+ "css3-flexbox/csswg_flexbox_flex-1-N-Npercent.html",
+ "css3-flexbox/csswg_flexbox_flex-1-N-auto-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-1-N-auto-shrink-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-1-N-auto-shrink.html",
+ "css3-flexbox/csswg_flexbox_flex-1-N-auto.html",
+ "css3-flexbox/csswg_flexbox_flex-1-N-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-1-N.html",
+ "css3-flexbox/csswg_flexbox_flex-N-0-0-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-N-0-0-unitless-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-N-0-0-unitless.html",
+ "css3-flexbox/csswg_flexbox_flex-N-0-0.html",
+ "css3-flexbox/csswg_flexbox_flex-N-0-N-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-N-0-N-shrink-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-N-0-N-shrink.html",
+ "css3-flexbox/csswg_flexbox_flex-N-0-N.html",
+ "css3-flexbox/csswg_flexbox_flex-N-0-Npercent-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-N-0-Npercent-shrink-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-N-0-Npercent-shrink.html",
+ "css3-flexbox/csswg_flexbox_flex-N-0-Npercent.html",
+ "css3-flexbox/csswg_flexbox_flex-N-0-auto-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-N-0-auto-shrink-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-N-0-auto-shrink.html",
+ "css3-flexbox/csswg_flexbox_flex-N-0-auto.html",
+ "css3-flexbox/csswg_flexbox_flex-N-0-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-N-0.html",
+ "css3-flexbox/csswg_flexbox_flex-N-1-0-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-N-1-0-unitless-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-N-1-0-unitless.html",
+ "css3-flexbox/csswg_flexbox_flex-N-1-0.html",
+ "css3-flexbox/csswg_flexbox_flex-N-1-N-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-N-1-N-shrink-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-N-1-N-shrink.html",
+ "css3-flexbox/csswg_flexbox_flex-N-1-N.html",
+ "css3-flexbox/csswg_flexbox_flex-N-1-Npercent-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-N-1-Npercent-shrink-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-N-1-Npercent-shrink.html",
+ "css3-flexbox/csswg_flexbox_flex-N-1-Npercent.html",
+ "css3-flexbox/csswg_flexbox_flex-N-1-auto-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-N-1-auto-shrink-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-N-1-auto-shrink.html",
+ "css3-flexbox/csswg_flexbox_flex-N-1-auto.html",
+ "css3-flexbox/csswg_flexbox_flex-N-1-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-N-1.html",
+ "css3-flexbox/csswg_flexbox_flex-N-N-0-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-N-N-0-unitless-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-N-N-0-unitless.html",
+ "css3-flexbox/csswg_flexbox_flex-N-N-0.html",
+ "css3-flexbox/csswg_flexbox_flex-N-N-N-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-N-N-N-shrink-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-N-N-N-shrink.html",
+ "css3-flexbox/csswg_flexbox_flex-N-N-N.html",
+ "css3-flexbox/csswg_flexbox_flex-N-N-Npercent-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-N-N-Npercent-shrink-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-N-N-Npercent-shrink.html",
+ "css3-flexbox/csswg_flexbox_flex-N-N-Npercent.html",
+ "css3-flexbox/csswg_flexbox_flex-N-N-auto-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-N-N-auto-shrink-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-N-N-auto-shrink.html",
+ "css3-flexbox/csswg_flexbox_flex-N-N-auto.html",
+ "css3-flexbox/csswg_flexbox_flex-N-N-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-N-N.html",
+ "css3-flexbox/csswg_flexbox_flex-auto-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-auto.html",
+ "css3-flexbox/csswg_flexbox_flex-basis-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-basis-shrink-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-basis-shrink.html",
+ "css3-flexbox/csswg_flexbox_flex-basis.html",
+ "css3-flexbox/csswg_flexbox_flex-initial-2-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-initial-2.html",
+ "css3-flexbox/csswg_flexbox_flex-initial-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-initial.html",
+ "css3-flexbox/csswg_flexbox_flex-natural-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-natural-mixed-basis-auto-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-natural-mixed-basis-auto.html",
+ "css3-flexbox/csswg_flexbox_flex-natural-mixed-basis-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-natural-mixed-basis.html",
+ "css3-flexbox/csswg_flexbox_flex-natural-variable-auto-basis-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-natural-variable-auto-basis.html",
+ "css3-flexbox/csswg_flexbox_flex-natural-variable-zero-basis-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-natural-variable-zero-basis.html",
+ "css3-flexbox/csswg_flexbox_flex-natural.html",
+ "css3-flexbox/csswg_flexbox_flex-none-expected.png",
+ "css3-flexbox/csswg_flexbox_flex-none.html",
+ "css3-flexbox/empty_container_baseline-expected.png",
+ "css3-flexbox/empty_container_baseline.html",
+ "css3-flexbox/flex-container-auto-margins-expected.png",
+ "css3-flexbox/flex-container-auto-margins.html",
+ "css3-flexbox/flex-items-flexibility-expected.png",
+ "css3-flexbox/flex-items-flexibility.html",
+ "css3-flexbox/layout_tests.txt",
+ "css3-flexbox/positioned-containers-expected.png",
+ "css3-flexbox/positioned-containers.html",
+ "css3-fonts/4-2-font-face-font-family-hides-system-font-family-expected.png",
+ "css3-fonts/4-2-font-face-font-family-hides-system-font-family.html",
+ "css3-fonts/4-3-src-local-can-match-font-postscript-name-expected.png",
+ "css3-fonts/4-3-src-local-can-match-font-postscript-name.html",
+ "css3-fonts/4-3-src-local-can-match-full-font-name-expected.png",
+ "css3-fonts/4-3-src-local-can-match-full-font-name.html",
+ "css3-fonts/4-3-use-first-available-local-font-face-expected.png",
+ "css3-fonts/4-3-use-first-available-local-font-face.html",
+ "css3-fonts/4-3-use-next-font-family-if-font-face-sources-unavailable-expected.png",
+ "css3-fonts/4-3-use-next-font-family-if-font-face-sources-unavailable.html",
+ "css3-fonts/4-4-use-correct-style-in-font-face-set-expected.png",
+ "css3-fonts/4-4-use-correct-style-in-font-face-set.html",
+ "css3-fonts/4-5-prefer-later-face-with-close-style-over-earlier-face-with-unicode-range-expected.png",
+ "css3-fonts/4-5-prefer-later-face-with-close-style-over-earlier-face-with-unicode-range.html",
+ "css3-fonts/4-5-use-correct-font-with-unicode-range-expected.png",
+ "css3-fonts/4-5-use-correct-font-with-unicode-range.html",
+ "css3-fonts/5-2-use-correct-style-in-font-family-expected.png",
+ "css3-fonts/5-2-use-correct-style-in-font-family.html",
+ "css3-fonts/5-2-use-first-available-listed-font-family-expected.png",
+ "css3-fonts/5-2-use-first-available-listed-font-family.html",
+ "css3-fonts/5-2-use-numerical-font-weights-in-family-face-matching-expected.png",
+ "css3-fonts/5-2-use-numerical-font-weights-in-family-face-matching.html",
+ "css3-fonts/5-2-use-specified-font-family-if-available-expected.png",
+ "css3-fonts/5-2-use-specified-font-family-if-available.html",
+ "css3-fonts/5-2-use-system-fallback-if-no-matching-family-is-found-expected.png",
+ "css3-fonts/5-2-use-system-fallback-if-no-matching-family-is-found.html",
+ "css3-fonts/color-emojis-should-render-properly-expected.png",
+ "css3-fonts/color-emojis-should-render-properly.html",
+ "css3-fonts/layout_tests.txt",
+ "css3-fonts/synthetic-bolding-should-not-occur-on-bold-font-expected.png",
+ "css3-fonts/synthetic-bolding-should-not-occur-on-bold-font.html",
+ "css3-fonts/synthetic-bolding-should-occur-on-non-bold-font-expected.png",
+ "css3-fonts/synthetic-bolding-should-occur-on-non-bold-font.html",
+ "css3-fonts/text-bounds-should-be-calculated-properly-when-containing-zero-height-font-runs-expected.png",
+ "css3-fonts/text-bounds-should-be-calculated-properly-when-containing-zero-height-font-runs.html",
+ "css3-images/4-1-2-corner-to-corner-linear-gradient-expected.png",
+ "css3-images/4-1-2-corner-to-corner-linear-gradient.html",
+ "css3-images/4-1-2-gradient-in-middle-expected.png",
+ "css3-images/4-1-2-gradient-in-middle.html",
+ "css3-images/4-1-2-linear-gradient-with-same-stop-positions-expected.png",
+ "css3-images/4-1-2-linear-gradient-with-same-stop-positions.html",
+ "css3-images/4-1-2-linear-gradient-with-transparency-expected.png",
+ "css3-images/4-1-2-linear-gradient-with-transparency.html",
+ "css3-images/4-1-2-multistop-one-dimensional-expected.png",
+ "css3-images/4-1-2-multistop-one-dimensional-gradient-expected.png",
+ "css3-images/4-1-2-multistop-one-dimensional-gradient.html",
+ "css3-images/4-1-2-simple-yellow-to-blue-arbitrary-angle-linear-gradient-expected.png",
+ "css3-images/4-1-2-simple-yellow-to-blue-arbitrary-angle-linear-gradient.html",
+ "css3-images/4-1-2-simple-yellow-to-blue-vertical-linear-gradient-expected.png",
+ "css3-images/4-1-2-simple-yellow-to-blue-vertical-linear-gradient.html",
+ "css3-images/4-1-2-three-color-linear-gradient-expected.png",
+ "css3-images/4-1-2-three-color-linear-gradient.html",
+ "css3-images/4-1-2-to-bottom-linear-gradient-expected.png",
+ "css3-images/4-1-2-to-bottom-linear-gradient.html",
+ "css3-images/4-1-2-to-left-linear-gradient-expected.png",
+ "css3-images/4-1-2-to-left-linear-gradient.html",
+ "css3-images/4-1-2-to-right-linear-gradient-expected.png",
+ "css3-images/4-1-2-to-right-linear-gradient.html",
+ "css3-images/4-1-2-to-top-linear-gradient-expected.png",
+ "css3-images/4-1-2-to-top-linear-gradient.html",
+ "css3-images/4-2-4-closest-side-radial-gradient-expected.png",
+ "css3-images/4-2-4-closest-side-radial-gradient.html",
+ "css3-images/4-2-4-radial-gradient-non-centered-expected.png",
+ "css3-images/4-2-4-radial-gradient-non-centered.html",
+ "css3-images/4-2-4-radial-gradient-with-transparency-expected.png",
+ "css3-images/4-2-4-radial-gradient-with-transparency.html",
+ "css3-images/4-2-4-simple-radial-gradients-expected.png",
+ "css3-images/4-2-4-simple-radial-gradients.html",
+ "css3-images/layout_tests.txt",
+ "css3-mediaqueries/bug_style.css",
+ "css3-mediaqueries/layout_tests.txt",
+ "css3-mediaqueries/media_query_rule_precedence_200-expected.png",
+ "css3-mediaqueries/media_query_rule_precedence_200.html",
+ "css3-mediaqueries/media_query_rule_precedence_300-expected.png",
+ "css3-mediaqueries/media_query_rule_precedence_300.html",
+ "css3-mediaqueries/media_query_rule_precedence_400-expected.png",
+ "css3-mediaqueries/media_query_rule_precedence_400.html",
+ "css3-text-decor/4-red-text-shadow-expected.png",
+ "css3-text-decor/4-red-text-shadow.html",
+ "css3-text-decor/4-simple-text-shadow-expected.png",
+ "css3-text-decor/4-simple-text-shadow.html",
+ "css3-text-decor/4-text-shadow-appears-when-text-color-is-transparent-expected.png",
+ "css3-text-decor/4-text-shadow-appears-when-text-color-is-transparent.html",
+ "css3-text-decor/4-text-shadow-glow-expected.png",
+ "css3-text-decor/4-text-shadow-glow.html",
+ "css3-text-decor/4-text-shadow-multiple-layers-expected.png",
+ "css3-text-decor/4-text-shadow-multiple-layers.html",
+ "css3-text-decor/4-text-shadow-multiple-overlapping-blurred-layers-expected.png",
+ "css3-text-decor/4-text-shadow-multiple-overlapping-blurred-layers.html",
+ "css3-text-decor/4-text-shadow-multiple-overlapping-layers-expected.png",
+ "css3-text-decor/4-text-shadow-multiple-overlapping-layers.html",
+ "css3-text-decor/4-text-shadow-with-blur-expected.png",
+ "css3-text-decor/4-text-shadow-with-blur.html",
+ "css3-text-decor/4-text-shadow-with-large-blur-expected.png",
+ "css3-text-decor/4-text-shadow-with-large-blur.html",
+ "css3-text-decor/layout_tests.txt",
+ "css3-transitions/2-transition-ends-on-display-none-expected.png",
+ "css3-transitions/2-transition-ends-on-display-none-for-pseudoelement-expected.png",
+ "css3-transitions/2-transition-ends-on-display-none-for-pseudoelement.html",
+ "css3-transitions/2-transition-ends-on-display-none-from-ancestor-expected.png",
+ "css3-transitions/2-transition-ends-on-display-none-from-ancestor.html",
+ "css3-transitions/2-transition-ends-on-display-none.html",
+ "css3-transitions/2-transition-for-inheritable-property-from-ancestor-should-occur-expected.png",
+ "css3-transitions/2-transition-for-inheritable-property-from-ancestor-should-occur.html",
+ "css3-transitions/2-transition-for-not-inheritable-property-from-ancestor-should-not-occur-expected.png",
+ "css3-transitions/2-transition-for-not-inheritable-property-from-ancestor-should-not-occur.html",
+ "css3-transitions/2-transition-start-during-display-none-does-not-transition-expected.png",
+ "css3-transitions/2-transition-start-during-display-none-does-not-transition-from-ancestor-expected.png",
+ "css3-transitions/2-transition-start-during-display-none-does-not-transition-from-ancestor.html",
+ "css3-transitions/2-transition-start-during-display-none-does-not-transition.html",
+ "css3-transitions/2-transition-started-on-display-none-pseudoelement-does-not-dcheck-expected.png",
+ "css3-transitions/2-transition-started-on-display-none-pseudoelement-does-not-dcheck.html",
+ "css3-transitions/3-transition-not-started-on-reattach-expected.png",
+ "css3-transitions/3-transition-not-started-on-reattach.html",
+ "css3-transitions/5-multiple-transitions-should-fire-in-correct-order-expected.png",
+ "css3-transitions/5-multiple-transitions-should-fire-in-correct-order.html",
+ "css3-transitions/5-simple-transition-expected.png",
+ "css3-transitions/5-simple-transition-for-pseudoelement-expected.png",
+ "css3-transitions/5-simple-transition-for-pseudoelement.html",
+ "css3-transitions/5-simple-transition.html",
+ "css3-transitions/canceled_transition_property_should_not_get_transitionend_event-expected.png",
+ "css3-transitions/canceled_transition_property_should_not_get_transitionend_event.html",
+ "css3-transitions/layout_tests.txt",
+ "css3-transitions/modified_transition_property_from_other_transitionend_event_handler_should_still_get_transitionend_event-expected.png",
+ "css3-transitions/modified_transition_property_from_other_transitionend_event_handler_should_still_get_transitionend_event.html",
+ "css3-transitions/removed_transition_property_from_other_transitionend_event_handler_should_still_get_transitionend_event-expected.png",
+ "css3-transitions/removed_transition_property_from_other_transitionend_event_handler_should_still_get_transitionend_event.html",
+ "css3-ui/5-2-ellipsis-should-clip-when-it-overflows-expected.png",
+ "css3-ui/5-2-ellipsis-should-clip-when-it-overflows.html",
+ "css3-ui/5-2-ellipsis-should-ellipsize-each-line-that-overflows-expected.png",
+ "css3-ui/5-2-ellipsis-should-ellipsize-each-line-that-overflows.html",
+ "css3-ui/5-2-ellipsis-should-ellipsize-overflowing-atomic-inline-element-after-text-expected.png",
+ "css3-ui/5-2-ellipsis-should-ellipsize-overflowing-atomic-inline-element-after-text.html",
+ "css3-ui/5-2-ellipsis-should-ellipsize-overflowing-first-word-expected.png",
+ "css3-ui/5-2-ellipsis-should-ellipsize-overflowing-first-word.html",
+ "css3-ui/5-2-ellipsis-should-ellipsize-overflowing-second-atomic-inline-element-on-line-expected.png",
+ "css3-ui/5-2-ellipsis-should-ellipsize-overflowing-second-atomic-inline-element-on-line.html",
+ "css3-ui/5-2-ellipsis-should-ellipsize-second-atomic-inline-element-with-overflowing-end-margin-at-start-edge-expected.png",
+ "css3-ui/5-2-ellipsis-should-ellipsize-second-atomic-inline-element-with-overflowing-end-margin-at-start-edge.html",
+ "css3-ui/5-2-ellipsis-should-ellipsize-second-atomic-inline-element-with-overflowing-start-margin-at-start-edge-expected.png",
+ "css3-ui/5-2-ellipsis-should-ellipsize-second-atomic-inline-element-with-overflowing-start-margin-at-start-edge.html",
+ "css3-ui/5-2-ellipsis-should-ellipsize-span-with-overflowing-end-margin-at-end-line-edge-expected.png",
+ "css3-ui/5-2-ellipsis-should-ellipsize-span-with-overflowing-end-margin-at-end-line-edge.html",
+ "css3-ui/5-2-ellipsis-should-ellipsize-span-with-overflowing-start-margin-at-end-line-edge-expected.png",
+ "css3-ui/5-2-ellipsis-should-ellipsize-span-with-overflowing-start-margin-at-end-line-edge.html",
+ "css3-ui/5-2-ellipsis-should-handle-transform-functions-via-matrix-expected.png",
+ "css3-ui/5-2-ellipsis-should-handle-transform-functions-via-matrix.html",
+ "css3-ui/5-2-ellipsis-should-handle-transform-functions-via-rotation-expected.png",
+ "css3-ui/5-2-ellipsis-should-handle-transform-functions-via-rotation.html",
+ "css3-ui/5-2-ellipsis-should-not-display-ellipsis-when-overflow-is-visible-expected.png",
+ "css3-ui/5-2-ellipsis-should-not-display-ellipsis-when-overflow-is-visible.html",
+ "css3-ui/5-2-ellipsis-should-not-ellipsize-first-atomic-inline-element-on-line-expected.png",
+ "css3-ui/5-2-ellipsis-should-not-ellipsize-first-atomic-inline-element-on-line.html",
+ "css3-ui/5-2-ellipsis-should-not-ellipsize-first-atomic-inline-element-with-overflowing-end-margin-expected.png",
+ "css3-ui/5-2-ellipsis-should-not-ellipsize-first-atomic-inline-element-with-overflowing-end-margin.html",
+ "css3-ui/5-2-ellipsis-should-not-ellipsize-first-character-on-line-expected.png",
+ "css3-ui/5-2-ellipsis-should-not-ellipsize-first-character-on-line.html",
+ "css3-ui/5-2-ellipsis-should-not-place-ellipsis-before-first-character-is-encountered-on-line-expected.png",
+ "css3-ui/5-2-ellipsis-should-not-place-ellipsis-before-first-character-is-encountered-on-line.html",
+ "css3-ui/5-2-ellipsis-should-properly-position-ellipsis-in-spans-with-margins-expected.png",
+ "css3-ui/5-2-ellipsis-should-properly-position-ellipsis-in-spans-with-margins.html",
+ "css3-ui/5-2-ellipsis-should-retain-background-color-of-ellipsized-span-expected.png",
+ "css3-ui/5-2-ellipsis-should-retain-background-color-of-ellipsized-span.html",
+ "css3-ui/5-2-ellipsis-should-use-style-of-containing-block-expected.png",
+ "css3-ui/5-2-ellipsis-should-use-style-of-containing-block.html",
+ "css3-ui/5-2-shaped-text-should-accurately-calculate-width-before-ellipsis-expected.png",
+ "css3-ui/5-2-shaped-text-should-accurately-calculate-width-before-ellipsis.html",
+ "css3-ui/5-2-text-overflow-clip-should-not-display-ellipsis-on-overflow-expected.png",
+ "css3-ui/5-2-text-overflow-clip-should-not-display-ellipsis-on-overflow.html",
+ "css3-ui/5-2-text-overflow-ellipsis-should-display-ellipsis-on-overflow-when-overflow-is-hidden-expected.png",
+ "css3-ui/5-2-text-overflow-ellipsis-should-display-ellipsis-on-overflow-when-overflow-is-hidden.html",
+ "css3-ui/5-2-text-overflow-ellipsis-should-not-be-inherited-expected.png",
+ "css3-ui/5-2-text-overflow-ellipsis-should-not-be-inherited.html",
+ "css3-ui/layout_tests.txt",
+ "css3-values/4-2-numbers-can-be-represented-in-scientific-notation-expected.png",
+ "css3-values/4-2-numbers-can-be-represented-in-scientific-notation.html",
+ "css3-values/5-1-1-em-should-be-relative-to-parent-expected.png",
+ "css3-values/5-1-1-em-should-be-relative-to-parent.html",
+ "css3-values/5-1-1-rem-should-be-relative-to-root-expected.png",
+ "css3-values/5-1-1-rem-should-be-relative-to-root.html",
+ "css3-values/5-1-1-vw-vh-should-be-relative-to-viewport-size-expected.png",
+ "css3-values/5-1-1-vw-vh-should-be-relative-to-viewport-size.html",
+ "css3-values/layout_tests.txt",
+ "cssom-view/6-scrolling-area-absolute-positioned-children-expected.png",
+ "cssom-view/6-scrolling-area-absolute-positioned-children-rtl-expected.png",
+ "cssom-view/6-scrolling-area-absolute-positioned-children-rtl.html",
+ "cssom-view/6-scrolling-area-absolute-positioned-children.html",
+ "cssom-view/6-scrolling-area-absolute-positioned-grandchildren-expected.png",
+ "cssom-view/6-scrolling-area-absolute-positioned-grandchildren-rtl-expected.png",
+ "cssom-view/6-scrolling-area-absolute-positioned-grandchildren-rtl.html",
+ "cssom-view/6-scrolling-area-absolute-positioned-grandchildren.html",
+ "cssom-view/6-scrolling-area-fix-positioned-children-excluded-expected.png",
+ "cssom-view/6-scrolling-area-fix-positioned-children-excluded.html",
+ "cssom-view/6-scrolling-area-fix-positioned-children-included-expected.png",
+ "cssom-view/6-scrolling-area-fix-positioned-children-included.html",
+ "cssom-view/6-scrolling-area-not-visible-grandchildren-excluded-expected.png",
+ "cssom-view/6-scrolling-area-not-visible-grandchildren-excluded.html",
+ "cssom-view/6-scrolling-area-scaled-content-expected.png",
+ "cssom-view/6-scrolling-area-scaled-content.html",
+ "cssom-view/6-scrolling-area-visible-and-scaled-grandchildren-included-expected.png",
+ "cssom-view/6-scrolling-area-visible-and-scaled-grandchildren-included.html",
+ "cssom-view/extensions_to_the_element_interface_client_top_left_width_height-expected.png",
+ "cssom-view/extensions_to_the_element_interface_client_top_left_width_height.html",
+ "cssom-view/extensions_to_the_element_interface_get_bounding_client_rect_with_box_splitting-expected.png",
+ "cssom-view/extensions_to_the_element_interface_get_bounding_client_rect_with_box_splitting.html",
+ "cssom-view/extensions_to_the_element_interface_get_bounding_client_rect_with_nested_transforms-expected.png",
+ "cssom-view/extensions_to_the_element_interface_get_bounding_client_rect_with_nested_transforms.html",
+ "cssom-view/extensions_to_the_element_interface_get_bounding_client_rect_with_scale_transform-expected.png",
+ "cssom-view/extensions_to_the_element_interface_get_bounding_client_rect_with_scale_transform.html",
+ "cssom-view/extensions_to_the_element_interface_get_bounding_client_rect_with_scroll-expected.png",
+ "cssom-view/extensions_to_the_element_interface_get_bounding_client_rect_with_scroll.html",
+ "cssom-view/extensions_to_the_element_interface_get_bounding_client_rect_with_scroll_rtl-expected.png",
+ "cssom-view/extensions_to_the_element_interface_get_bounding_client_rect_with_scroll_rtl.html",
+ "cssom-view/extensions_to_the_element_interface_get_bounding_client_rect_with_translate_transform-expected.png",
+ "cssom-view/extensions_to_the_element_interface_get_bounding_client_rect_with_translate_transform.html",
+ "cssom-view/extensions_to_the_html_element_interface_offset_top_left_width_height-expected.png",
+ "cssom-view/extensions_to_the_html_element_interface_offset_top_left_width_height.html",
+ "cssom-view/extensions_to_the_html_element_interface_offset_width_height_with_box_splitting-expected.png",
+ "cssom-view/extensions_to_the_html_element_interface_offset_width_height_with_box_splitting.html",
+ "cssom-view/layout_tests.txt",
+ "dom/2-7-event-target-should-be-constructible-expected.png",
+ "dom/2-7-event-target-should-be-constructible.html",
+ "dom/layout_tests.txt",
+ "incremental-layout/DISABLED-size-related-style-change-should-trigger-size-update-of-absolute-positioned-siblings.html",
+ "incremental-layout/added_inline_child_of_inline_box_gets_rendered-expected.png",
+ "incremental-layout/added_inline_child_of_inline_box_gets_rendered.html",
+ "incremental-layout/child-being-moved-to-new-parent-should-trigger-inherited-style-update-expected.png",
+ "incremental-layout/child-being-moved-to-new-parent-should-trigger-inherited-style-update.html",
+ "incremental-layout/computed_style_change_gets_rendered-expected.png",
+ "incremental-layout/computed_style_change_gets_rendered.html",
+ "incremental-layout/cross_references_style_change_should_trigger_containing_block_update-expected.png",
+ "incremental-layout/cross_references_style_change_should_trigger_containing_block_update.html",
+ "incremental-layout/cross_references_style_change_should_trigger_stacking_context_update-expected.png",
+ "incremental-layout/cross_references_style_change_should_trigger_stacking_context_update.html",
+ "incremental-layout/dir_attribute_change_gets_rendered-expected.png",
+ "incremental-layout/dir_attribute_change_gets_rendered.html",
+ "incremental-layout/layout_tests.txt",
+ "incremental-layout/removed_inline_child_of_inline_box_does_not_get_rendered-expected.png",
+ "incremental-layout/removed_inline_child_of_inline_box_does_not_get_rendered.html",
+ "incremental-layout/size-related-style-change-should-trigger-size-update-of-absolute-positioned-children-expected.png",
+ "incremental-layout/size-related-style-change-should-trigger-size-update-of-absolute-positioned-children.html",
+ "incremental-layout/size-related-style-change-should-trigger-size-update-of-absolute-positioned-descendants-expected.png",
+ "incremental-layout/size-related-style-change-should-trigger-size-update-of-absolute-positioned-descendants.html",
+ "incremental-layout/size-related-style-change-should-trigger-size-update-of-static-descendants-expected.png",
+ "incremental-layout/size-related-style-change-should-trigger-size-update-of-static-descendants.html",
+ "incremental-layout/size-related-style-change-should-trigger-size-update-of-static-siblings-expected.png",
+ "incremental-layout/size-related-style-change-should-trigger-size-update-of-static-siblings.html",
+ "incremental-layout/text-node-text-content-mutation-expected.png",
+ "incremental-layout/text-node-text-content-mutation.html",
+ "incremental-layout/transform-style-change-should-trigger-size-update-expected.png",
+ "incremental-layout/transform-style-change-should-trigger-size-update.html",
+ "intersection-observer/containing-block-has-rotate-transform-expected.png",
+ "intersection-observer/containing-block-has-rotate-transform.html",
+ "intersection-observer/containing-block-undergoes-transition-expected.png",
+ "intersection-observer/containing-block-undergoes-transition.html",
+ "intersection-observer/element-in-containing-block-chain-has-overflow-clip-with-padding-and-border-expected.png",
+ "intersection-observer/element-in-containing-block-chain-has-overflow-clip-with-padding-and-border.html",
+ "intersection-observer/element-in-containing-block-chain-has-overflow-clip-without-padding-or-border-expected.png",
+ "intersection-observer/element-in-containing-block-chain-has-overflow-clip-without-padding-or-border.html",
+ "intersection-observer/intersection-ratio-is-nonzero-but-is-intersecting-is-false-expected.png",
+ "intersection-observer/intersection-ratio-is-nonzero-but-is-intersecting-is-false.html",
+ "intersection-observer/layout_tests.txt",
+ "intersection-observer/multiple-observers-with-different-roots-and-targets-expected.png",
+ "intersection-observer/multiple-observers-with-different-roots-and-targets.html",
+ "intersection-observer/no-intersection-when-root-is-not-in-containing-block-chain-of-target-expected.png",
+ "intersection-observer/no-intersection-when-root-is-not-in-containing-block-chain-of-target.html",
+ "intersection-observer/observers-should-update-when-elements-move-with-threshold-expected.png",
+ "intersection-observer/observers-should-update-when-elements-move-with-threshold.html",
+ "intersection-observer/observers-should-update-when-elements-move-without-threshold-expected.png",
+ "intersection-observer/observers-should-update-when-elements-move-without-threshold.html",
+ "intersection-observer/previous-threshold-index-and-is-intersecting-fields-should-be-updated-expected.png",
+ "intersection-observer/previous-threshold-index-and-is-intersecting-fields-should-be-updated.html",
+ "intersection-observer/root-has-nonzero-padding-and-border-and-overflow-clip-expected.png",
+ "intersection-observer/root-has-nonzero-padding-and-border-and-overflow-clip.html",
+ "intersection-observer/root-has-nonzero-padding-and-border-expected.png",
+ "intersection-observer/root-has-nonzero-padding-and-border.html",
+ "intersection-observer/root-has-nonzero-root-margin-expected.png",
+ "intersection-observer/root-has-nonzero-root-margin.html",
+ "intersection-observer/root-intersects-with-multiple-targets-expected.png",
+ "intersection-observer/root-intersects-with-multiple-targets.html",
+ "intersection-observer/root-is-viewport-and-target-is-partially-visible-expected.png",
+ "intersection-observer/root-is-viewport-and-target-is-partially-visible.html",
+ "intersection-observer/target-has-nonzero-padding-and-border-expected.png",
+ "intersection-observer/target-has-nonzero-padding-and-border.html",
+ "intersection-observer/target-undergoes-transition-expected.png",
+ "intersection-observer/target-undergoes-transition.html",
+ "intersection-observer/target-with-nonzero-area-is-edge-adjacent-to-root-expected.png",
+ "intersection-observer/target-with-nonzero-area-is-edge-adjacent-to-root.html",
+ "intersection-observer/target-with-zero-area-is-edge-adjacent-to-root-expected.png",
+ "intersection-observer/target-with-zero-area-is-edge-adjacent-to-root.html",
+ "intersection-observer/unobserved-targets-do-not-get-included-in-next-update-expected.png",
+ "intersection-observer/unobserved-targets-do-not-get-included-in-next-update.html",
+ "intersection-observer/unobserving-elements-without-calling-observe-should-not-crash-expected.png",
+ "intersection-observer/unobserving-elements-without-calling-observe-should-not-crash.html",
+ "lottie-player/layout_tests.txt",
+ "lottie-player/lottie-background-attribute-expected.png",
+ "lottie-player/lottie-background-attribute.html",
+ "lottie-player/lottie-playback-events-expected.png",
+ "lottie-player/lottie-playback-events.html",
+ "text-shaping/combining-character-sequences-should-be-handled-properly-expected.png",
+ "text-shaping/combining-character-sequences-should-be-handled-properly.html",
+ "text-shaping/layout_tests.txt",
+ "the-dir-attribute/layout_tests.txt",
+ "the-dir-attribute/the-dir-attribute-001-expected.png",
+ "the-dir-attribute/the-dir-attribute-001.html",
+ "the-dir-attribute/the-dir-attribute-002-expected.png",
+ "the-dir-attribute/the-dir-attribute-002.html",
+ "the-dir-attribute/the-dir-attribute-003.html",
+ "the-dir-attribute/the-dir-attribute-004.html",
+ "the-dir-attribute/the-dir-attribute-005.html",
+ "the-dir-attribute/the-dir-attribute-006.html",
+ "the-dir-attribute/the-dir-attribute-007-expected.png",
+ "the-dir-attribute/the-dir-attribute-007.html",
+ "the-dir-attribute/the-dir-attribute-008-expected.png",
+ "the-dir-attribute/the-dir-attribute-008.html",
+ "the-dir-attribute/the-dir-attribute-009-expected.png",
+ "the-dir-attribute/the-dir-attribute-009.html",
+ "the-dir-attribute/the-dir-attribute-010-expected.png",
+ "the-dir-attribute/the-dir-attribute-010.html",
+ "the-dir-attribute/the-dir-attribute-011-expected.png",
+ "the-dir-attribute/the-dir-attribute-011.html",
+ "the-dir-attribute/the-dir-attribute-012-expected.png",
+ "the-dir-attribute/the-dir-attribute-012.html",
+ "the-dir-attribute/the-dir-attribute-046-expected.png",
+ "the-dir-attribute/the-dir-attribute-046.html",
+ "the-dir-attribute/the-dir-attribute-047-expected.png",
+ "the-dir-attribute/the-dir-attribute-047.html",
+ "the-dir-attribute/the-dir-attribute-048-expected.png",
+ "the-dir-attribute/the-dir-attribute-048.html",
+ "the-dir-attribute/the-dir-attribute-049-expected.png",
+ "the-dir-attribute/the-dir-attribute-049.html",
+ "the-dir-attribute/the-dir-attribute-050-expected.png",
+ "the-dir-attribute/the-dir-attribute-050.html",
+ "the-dir-attribute/the-dir-attribute-051.html",
+ "the-dir-attribute/the-dir-attribute-052.html",
+ "the-dir-attribute/the-dir-attribute-053-expected.png",
+ "the-dir-attribute/the-dir-attribute-053.html",
+ "the-dir-attribute/the-dir-attribute-054-expected.png",
+ "the-dir-attribute/the-dir-attribute-054.html",
+ "the-dir-attribute/the-dir-attribute-055-expected.png",
+ "the-dir-attribute/the-dir-attribute-055.html",
+ "the-dir-attribute/the-dir-attribute-056-expected.png",
+ "the-dir-attribute/the-dir-attribute-056.html",
+ "the-dir-attribute/the-dir-attribute-057-expected.png",
+ "the-dir-attribute/the-dir-attribute-057.html",
+ "the-dir-attribute/the-dir-attribute-058-expected.png",
+ "the-dir-attribute/the-dir-attribute-058.html",
+ "the-dir-attribute/the-dir-attribute-059-expected.png",
+ "the-dir-attribute/the-dir-attribute-059.html",
+ "the-dir-attribute/the-dir-attribute-060-expected.png",
+ "the-dir-attribute/the-dir-attribute-060.html",
+ "the-dir-attribute/the-dir-attribute-061-expected.png",
+ "the-dir-attribute/the-dir-attribute-061.html",
+ "the-dir-attribute/the-dir-attribute-062-expected.png",
+ "the-dir-attribute/the-dir-attribute-062.html",
+ "the-dir-attribute/the-dir-attribute-063.html",
+ "the-dir-attribute/the-dir-attribute-064.html",
+ "the-dir-attribute/the-dir-attribute-065-expected.png",
+ "the-dir-attribute/the-dir-attribute-065.html",
+ "the-dir-attribute/the-dir-attribute-066-expected.png",
+ "the-dir-attribute/the-dir-attribute-066.html",
+ "the-dir-attribute/the-dir-attribute-067-expected.png",
+ "the-dir-attribute/the-dir-attribute-067.html",
+ "the-dir-attribute/the-dir-attribute-068-expected.png",
+ "the-dir-attribute/the-dir-attribute-068.html",
+ "the-dir-attribute/the-dir-attribute-069-expected.png",
+ "the-dir-attribute/the-dir-attribute-069.html",
+ "the-dir-attribute/the-dir-attribute-070-expected.png",
+ "the-dir-attribute/the-dir-attribute-070.html",
+ "the-dir-attribute/the-dir-attribute-071-expected.png",
+ "the-dir-attribute/the-dir-attribute-071.html",
+ "the-dir-attribute/writing-modes-principal-flow-body-propagation-expected.png",
+ "the-dir-attribute/writing-modes-principal-flow-body-propagation.html",
+ "the-dir-attribute/writing-modes-principal-flow-root-propagation-expected.png",
+ "the-dir-attribute/writing-modes-principal-flow-root-propagation.html",
+ "web-platform-tests/WebCryptoAPI/web_platform_tests.txt",
+ "web-platform-tests/WebIDL/web_platform_tests.txt",
+ "web-platform-tests/XMLHttpRequest/web_platform_tests.txt",
+ "web-platform-tests/cobalt_special/web_platform_tests.txt",
+ "web-platform-tests/content-security-policy/web_platform_tests.txt",
+ "web-platform-tests/cors/web_platform_tests.txt",
+ "web-platform-tests/dom/web_platform_tests.txt",
+ "web-platform-tests/encoding/web_platform_tests.txt",
+ "web-platform-tests/fetch/web_platform_tests.txt",
+ "web-platform-tests/html/web_platform_tests.txt",
+ "web-platform-tests/intersection-observer/web_platform_tests.txt",
+ "web-platform-tests/mediasession/web_platform_tests.txt",
+ "web-platform-tests/performance-timeline/web_platform_tests.txt",
+ "web-platform-tests/resource-timing/web_platform_tests.txt",
+ "web-platform-tests/streams/web_platform_tests.txt",
+ "web-platform-tests/websockets/web_platform_tests.txt",
+ "webappapis/6-1-5-2-onload-event-fires-on-window-expected.png",
+ "webappapis/6-1-5-2-onload-event-fires-on-window-when-set-via-attribute-expected.png",
+ "webappapis/6-1-5-2-onload-event-fires-on-window-when-set-via-attribute.html",
+ "webappapis/6-1-5-2-onload-event-fires-on-window.html",
+ "webappapis/layout_tests.txt",
+ "webp/layout_tests.txt",
+ "webp/static-webp-background-image-expected.png",
+ "webp/static-webp-background-image.html",
+ "webp/static-webp-image.webp",
+ ]
+
+ subdir = "cobalt/layout_tests"
+
+ outputs = [ "$sb_static_contents_output_data_dir/test/$subdir/{{source_target_relative}}" ]
+}
diff --git a/cobalt/loader/BUILD.gn b/cobalt/loader/BUILD.gn
index a94ee44..2892cc9 100644
--- a/cobalt/loader/BUILD.gn
+++ b/cobalt/loader/BUILD.gn
@@ -168,3 +168,96 @@
rebase_path("embedded_resources", root_build_dir),
]
}
+
+target(gtest_target_type, "loader_test") {
+ testonly = true
+ has_pedantic_warnings = true
+
+ sources = [
+ "blob_fetcher_test.cc",
+ "fetcher_factory_test.cc",
+ "fetcher_test.h",
+ "file_fetcher_test.cc",
+ "font/typeface_decoder_test.cc",
+ "image/image_decoder_test.cc",
+ "image/image_decoder_unit_test.cc",
+ "loader_test.cc",
+ "mesh/mesh_decoder_test.cc",
+ "text_decoder_test.cc",
+ ]
+
+ configs -= [ "//starboard/build/config:size" ]
+ configs += [ "//starboard/build/config:speed" ]
+
+ deps = [
+ ":copy_loader_test_data",
+ ":loader",
+ "//cobalt/base:base",
+ "//cobalt/dom",
+ "//cobalt/dom_parser",
+ "//cobalt/math:math",
+ "//cobalt/render_tree",
+ "//cobalt/test:run_all_unittests",
+ "//testing/gmock",
+ "//testing/gtest",
+ "//third_party/ots:ots",
+ ]
+
+ deps += cobalt_platform_dependencies
+}
+
+copy("copy_loader_test_data") {
+ sources = [
+ "testdata/baseline_jpeg.jpg",
+ "testdata/empty.txt",
+ "testdata/icons.ttf",
+ "testdata/icons.woff",
+ "testdata/icons.woff2",
+ "testdata/interlaced_png.png",
+ "testdata/non_interlaced_png.png",
+ "testdata/performance-spike.html",
+ "testdata/progressive_jpeg.jpg",
+ "testdata/projection.box",
+ "testdata/sandbox01.jpg",
+ "testdata/sandbox01.png",
+ "testdata/sandbox01.webp",
+ "testdata/sandbox02.jpg",
+ "testdata/sandbox02.png",
+ "testdata/sandbox02.webp",
+ "testdata/sandbox03.jpg",
+ "testdata/sandbox03.png",
+ "testdata/sandbox03.webp",
+ "testdata/sandbox04.jpg",
+ "testdata/sandbox04.webp",
+ "testdata/sandbox05.jpg",
+ "testdata/sandbox05.webp",
+ "testdata/sandbox06.jpg",
+ "testdata/sandbox06.webp",
+ "testdata/sandbox07.jpg",
+ "testdata/sandbox07.webp",
+ "testdata/sandbox08.jpg",
+ "testdata/sandbox08.webp",
+ "testdata/sandbox09.jpg",
+ "testdata/sandbox09.webp",
+ "testdata/sandbox10.jpg",
+ "testdata/sandbox10.webp",
+ "testdata/sandbox11.jpg",
+ "testdata/sandbox11.webp",
+ "testdata/sandbox12.jpg",
+ "testdata/sandbox12.webp",
+ "testdata/sandbox13.jpg",
+ "testdata/sandbox14.jpg",
+ "testdata/sandbox14.webp",
+ "testdata/sandbox15.jpg",
+ "testdata/sandbox15.webp",
+ "testdata/sandbox16.jpg",
+ "testdata/sandbox16.webp",
+ "testdata/sandbox17.jpg",
+ "testdata/sandbox18.jpg",
+ "testdata/vsauce_sm.webp",
+ "testdata/webp_image.webp",
+ ]
+
+ file_path = "{{source_root_relative_dir}}/{{source_file_part}}"
+ outputs = [ "$sb_static_contents_output_data_dir/test/$file_path" ]
+}
diff --git a/cobalt/loader/image/sandbox/BUILD.gn b/cobalt/loader/image/sandbox/BUILD.gn
new file mode 100644
index 0000000..adf6ab1
--- /dev/null
+++ b/cobalt/loader/image/sandbox/BUILD.gn
@@ -0,0 +1,33 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This is a sample sandbox application for experimenting with the Cobalt
+# ImageDecoder.
+
+# This target will build a sandbox application that allows for easy
+# experimentation with the ImageDecoder on any platform.
+target(final_executable_type, "image_decoder_sandbox") {
+ sources = [ "image_decoder_sandbox.cc" ]
+
+ deps = [
+ "//cobalt/base",
+ "//cobalt/loader",
+ "//cobalt/loader:copy_loader_test_data",
+ "//cobalt/math",
+ "//cobalt/renderer",
+ "//cobalt/system_window",
+ "//cobalt/trace_event",
+ "//url",
+ ]
+}
diff --git a/cobalt/math/BUILD.gn b/cobalt/math/BUILD.gn
index 59c4494..48c2943 100644
--- a/cobalt/math/BUILD.gn
+++ b/cobalt/math/BUILD.gn
@@ -100,4 +100,5 @@
"//cobalt/test:run_all_unittests",
"//testing/gtest",
]
+ content_deps = [ "//third_party/icu:icudata" ]
}
diff --git a/cobalt/media/BUILD.gn b/cobalt/media/BUILD.gn
index 2194a4d..5428451 100644
--- a/cobalt/media/BUILD.gn
+++ b/cobalt/media/BUILD.gn
@@ -24,10 +24,6 @@
defines = [ "MEDIA_IMPLEMENTATION" ]
}
-config("media_config_public") {
- include_dirs = [ ".." ]
-}
-
component("media") {
sources = [
"base/audio_bus.cc",
@@ -232,8 +228,6 @@
":media_config",
]
- public_configs = [ ":media_config_public" ]
-
deps = [
"//base",
"//cobalt/base",
diff --git a/cobalt/media/OWNERS b/cobalt/media/OWNERS
new file mode 100644
index 0000000..8a329a4
--- /dev/null
+++ b/cobalt/media/OWNERS
@@ -0,0 +1,24 @@
+# NOTE: Do not use these owners when you're in a subdirectory that has
+# OWNERS file. For example:
+# - cast
+# - midi
+# - ozone
+# - capture/{content,video}
+# Instead prefer the OWNERS in the subdirectory as they will be more familiar,
+# and to load balance. Only use OWNERS in this file for these subdirectories
+# when doing refactorings and general cleanups.
+
+chcunningham@chromium.org
+dalecurtis@chromium.org
+ddorwin@chromium.org
+hubbe@chromium.org
+jrummell@chromium.org
+liberato@chromium.org
+sandersd@chromium.org
+watk@chromium.org
+wolenetz@chromium.org
+xhwang@chromium.org
+
+per-file *.isolate=maruel@chromium.org
+per-file *.isolate=tandrii@chromium.org
+per-file *.isolate=vadimsh@chromium.org
diff --git a/cobalt/media/base/encryption_pattern.cc b/cobalt/media/base/encryption_pattern.cc
index 20608ca..4349bc0 100644
--- a/cobalt/media/base/encryption_pattern.cc
+++ b/cobalt/media/base/encryption_pattern.cc
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "media/base/encryption_pattern.h"
+#include "cobalt/media/base/encryption_pattern.h"
namespace cobalt {
namespace media {
diff --git a/cobalt/media/base/playback_statistics.cc b/cobalt/media/base/playback_statistics.cc
index ca6263d..ff49d71 100644
--- a/cobalt/media/base/playback_statistics.cc
+++ b/cobalt/media/base/playback_statistics.cc
@@ -37,7 +37,7 @@
volatile SbAtomic32 s_max_video_height = 0;
volatile SbAtomic32 s_last_working_codec = kUnknownVideoCodec;
-int64_t RoundValues(int64_t value) {
+int64_t RoundValue(int64_t value) {
if (value < 10) {
return value;
}
@@ -225,10 +225,10 @@
(current_video_config.is_encrypted() ? "Y" : "N"), video_width_.value(),
video_height_.value(), SbAtomicNoBarrier_Load(&s_active_instances),
SbAtomicNoBarrier_Load(&s_max_active_instances),
- RoundValues(SbAtomicNoBarrier_Load(&s_av1_played)),
- RoundValues(SbAtomicNoBarrier_Load(&s_h264_played)),
- RoundValues(SbAtomicNoBarrier_Load(&s_hevc_played)),
- RoundValues(SbAtomicNoBarrier_Load(&s_vp9_played)),
+ RoundValue(SbAtomicNoBarrier_Load(&s_av1_played)),
+ RoundValue(SbAtomicNoBarrier_Load(&s_h264_played)),
+ RoundValue(SbAtomicNoBarrier_Load(&s_hevc_played)),
+ RoundValue(SbAtomicNoBarrier_Load(&s_vp9_played)),
SbAtomicNoBarrier_Load(&s_min_video_width),
SbAtomicNoBarrier_Load(&s_min_video_height),
SbAtomicNoBarrier_Load(&s_max_video_width),
@@ -238,16 +238,16 @@
.c_str(),
ValToString(seek_time_).c_str(),
is_first_audio_buffer_written_
- ? RoundValues(first_written_audio_timestamp_.value().InSeconds())
+ ? RoundValue(first_written_audio_timestamp_.value().InSeconds())
: -1,
is_first_video_buffer_written_
- ? RoundValues(first_written_video_timestamp_.value().InSeconds())
+ ? RoundValue(first_written_video_timestamp_.value().InSeconds())
: -1,
is_first_audio_buffer_written_
- ? RoundValues(last_written_audio_timestamp_.value().InSeconds())
+ ? RoundValue(last_written_audio_timestamp_.value().InSeconds())
: -1,
is_first_video_buffer_written_
- ? RoundValues(last_written_video_timestamp_.value().InSeconds())
+ ? RoundValue(last_written_video_timestamp_.value().InSeconds())
: -1);
}
diff --git a/cobalt/media/base/simd/convert_rgb_to_yuv.h b/cobalt/media/base/simd/convert_rgb_to_yuv.h
index a180549..7eaa2b1 100644
--- a/cobalt/media/base/simd/convert_rgb_to_yuv.h
+++ b/cobalt/media/base/simd/convert_rgb_to_yuv.h
@@ -5,7 +5,7 @@
#ifndef COBALT_MEDIA_BASE_SIMD_CONVERT_RGB_TO_YUV_H_
#define COBALT_MEDIA_BASE_SIMD_CONVERT_RGB_TO_YUV_H_
-#include "media/base/yuv_convert.h"
+#include "cobalt/media/base/yuv_convert.h"
#include "starboard/types.h"
namespace cobalt {
diff --git a/cobalt/media/base/simd/convert_rgb_to_yuv_c.cc b/cobalt/media/base/simd/convert_rgb_to_yuv_c.cc
index 70f28e1..752e57b 100644
--- a/cobalt/media/base/simd/convert_rgb_to_yuv_c.cc
+++ b/cobalt/media/base/simd/convert_rgb_to_yuv_c.cc
@@ -3,7 +3,7 @@
// found in the LICENSE file.
#include "build/build_config.h"
-#include "media/base/simd/convert_rgb_to_yuv.h"
+#include "cobalt/media/base/simd/convert_rgb_to_yuv.h"
#include "starboard/types.h"
namespace cobalt {
diff --git a/cobalt/media/base/simd/convert_rgb_to_yuv_sse2.cc b/cobalt/media/base/simd/convert_rgb_to_yuv_sse2.cc
index 32fb4b7..43f0365 100644
--- a/cobalt/media/base/simd/convert_rgb_to_yuv_sse2.cc
+++ b/cobalt/media/base/simd/convert_rgb_to_yuv_sse2.cc
@@ -3,13 +3,15 @@
// found in the LICENSE file.
#include "build/build_config.h"
-#include "media/base/simd/convert_rgb_to_yuv.h"
+#include "cobalt/media/base/simd/convert_rgb_to_yuv.h"
#if defined(COMPILER_MSVC)
#include <intrin.h>
#else
+// clang-format off
#include <mmintrin.h>
#include <emmintrin.h>
+// clang-format on
#include "starboard/types.h"
#endif
diff --git a/cobalt/media/base/simd/convert_rgb_to_yuv_ssse3.cc b/cobalt/media/base/simd/convert_rgb_to_yuv_ssse3.cc
index dbb27c6..0e025a5 100644
--- a/cobalt/media/base/simd/convert_rgb_to_yuv_ssse3.cc
+++ b/cobalt/media/base/simd/convert_rgb_to_yuv_ssse3.cc
@@ -2,10 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "media/base/simd/convert_rgb_to_yuv.h"
+#include "cobalt/media/base/simd/convert_rgb_to_yuv.h"
#include "build/build_config.h"
-#include "media/base/simd/convert_rgb_to_yuv_ssse3.h"
+#include "cobalt/media/base/simd/convert_rgb_to_yuv_ssse3.h"
namespace cobalt {
namespace media {
diff --git a/cobalt/media/base/simd/convert_rgb_to_yuv_unittest.cc b/cobalt/media/base/simd/convert_rgb_to_yuv_unittest.cc
index 23063ec..b218f00 100644
--- a/cobalt/media/base/simd/convert_rgb_to_yuv_unittest.cc
+++ b/cobalt/media/base/simd/convert_rgb_to_yuv_unittest.cc
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "media/base/simd/convert_rgb_to_yuv.h"
+#include "cobalt/media/base/simd/convert_rgb_to_yuv.h"
#include <algorithm>
#include <memory>
diff --git a/cobalt/media/base/simd/convert_yuv_to_rgb.h b/cobalt/media/base/simd/convert_yuv_to_rgb.h
index dbb47bc..08fcd58 100644
--- a/cobalt/media/base/simd/convert_yuv_to_rgb.h
+++ b/cobalt/media/base/simd/convert_yuv_to_rgb.h
@@ -5,7 +5,7 @@
#ifndef COBALT_MEDIA_BASE_SIMD_CONVERT_YUV_TO_RGB_H_
#define COBALT_MEDIA_BASE_SIMD_CONVERT_YUV_TO_RGB_H_
-#include "media/base/yuv_convert.h"
+#include "cobalt/media/base/yuv_convert.h"
#include "starboard/types.h"
namespace cobalt {
diff --git a/cobalt/media/base/simd/convert_yuv_to_rgb_c.cc b/cobalt/media/base/simd/convert_yuv_to_rgb_c.cc
index 6025492..bac0ba3 100644
--- a/cobalt/media/base/simd/convert_yuv_to_rgb_c.cc
+++ b/cobalt/media/base/simd/convert_yuv_to_rgb_c.cc
@@ -3,7 +3,7 @@
// found in the LICENSE file.
#include "build/build_config.h"
-#include "media/base/simd/convert_yuv_to_rgb.h"
+#include "cobalt/media/base/simd/convert_yuv_to_rgb.h"
#include "starboard/types.h"
namespace cobalt {
diff --git a/cobalt/media/base/simd/convert_yuv_to_rgb_x86.cc b/cobalt/media/base/simd/convert_yuv_to_rgb_x86.cc
index 72cfec3..ffa75b7 100644
--- a/cobalt/media/base/simd/convert_yuv_to_rgb_x86.cc
+++ b/cobalt/media/base/simd/convert_yuv_to_rgb_x86.cc
@@ -8,8 +8,8 @@
#include <mmintrin.h>
#endif
-#include "media/base/simd/convert_yuv_to_rgb.h"
-#include "media/base/yuv_convert.h"
+#include "cobalt/media/base/simd/convert_yuv_to_rgb.h"
+#include "cobalt/media/base/yuv_convert.h"
#include "starboard/types.h"
namespace cobalt {
diff --git a/cobalt/media/base/simd/filter_yuv.h b/cobalt/media/base/simd/filter_yuv.h
index ec0a770..7dc3104 100644
--- a/cobalt/media/base/simd/filter_yuv.h
+++ b/cobalt/media/base/simd/filter_yuv.h
@@ -5,7 +5,7 @@
#ifndef COBALT_MEDIA_BASE_SIMD_FILTER_YUV_H_
#define COBALT_MEDIA_BASE_SIMD_FILTER_YUV_H_
-#include "media/base/media_export.h"
+#include "cobalt/media/base/media_export.h"
#include "starboard/types.h"
namespace cobalt {
diff --git a/cobalt/media/base/simd/filter_yuv_c.cc b/cobalt/media/base/simd/filter_yuv_c.cc
index 7e50d4e..e83ec00 100644
--- a/cobalt/media/base/simd/filter_yuv_c.cc
+++ b/cobalt/media/base/simd/filter_yuv_c.cc
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "media/base/simd/filter_yuv.h"
+#include "cobalt/media/base/simd/filter_yuv.h"
#include "starboard/types.h"
namespace cobalt {
diff --git a/cobalt/media/base/simd/filter_yuv_sse2.cc b/cobalt/media/base/simd/filter_yuv_sse2.cc
index 9fc24c1..e11a9e0 100644
--- a/cobalt/media/base/simd/filter_yuv_sse2.cc
+++ b/cobalt/media/base/simd/filter_yuv_sse2.cc
@@ -9,7 +9,7 @@
#include <mmintrin.h>
#endif
-#include "media/base/simd/filter_yuv.h"
+#include "cobalt/media/base/simd/filter_yuv.h"
#include "starboard/types.h"
namespace cobalt {
diff --git a/cobalt/media/decoder_buffer_allocator.cc b/cobalt/media/decoder_buffer_allocator.cc
index 9515da1..76ba23c 100644
--- a/cobalt/media/decoder_buffer_allocator.cc
+++ b/cobalt/media/decoder_buffer_allocator.cc
@@ -60,13 +60,8 @@
// We cannot call SbMediaGetMaxBufferCapacity because |video_codec_| is not
// set yet. Use 0 (unbounded) until |video_codec_| is updated in
// UpdateVideoConfig().
- int max_capacity = 0;
- reuse_allocator_.reset(new nb::BidirectionalFitReuseAllocator(
- &fallback_allocator_, initial_capacity_, kSmallAllocationThreshold,
- allocation_unit_, max_capacity));
- DLOG(INFO) << "Allocated " << initial_capacity_
- << " bytes for media buffer pool as its initial buffer, with max"
- << " capacity set to " << max_capacity;
+ starboard::ScopedLock scoped_lock(mutex_);
+ CreateReuseAllocator(0);
}
DecoderBufferAllocator::~DecoderBufferAllocator() {
@@ -84,6 +79,36 @@
}
}
+void DecoderBufferAllocator::Suspend() {
+ if (!using_memory_pool_ || is_memory_pool_allocated_on_demand_) {
+ return;
+ }
+
+ TRACK_MEMORY_SCOPE("Media");
+
+ starboard::ScopedLock scoped_lock(mutex_);
+
+ if (reuse_allocator_ && reuse_allocator_->GetAllocated() == 0) {
+ DLOG(INFO) << "Freed " << reuse_allocator_->GetCapacity()
+ << " bytes of media buffer pool `on suspend`.";
+ reuse_allocator_.reset();
+ }
+}
+
+void DecoderBufferAllocator::Resume() {
+ if (!using_memory_pool_ || is_memory_pool_allocated_on_demand_) {
+ return;
+ }
+
+ TRACK_MEMORY_SCOPE("Media");
+
+ starboard::ScopedLock scoped_lock(mutex_);
+
+ if (!reuse_allocator_) {
+ CreateReuseAllocator(0);
+ }
+}
+
DecoderBuffer::Allocator::Allocations DecoderBufferAllocator::Allocate(
size_t size, size_t alignment, intptr_t context) {
TRACK_MEMORY_SCOPE("Media");
@@ -106,12 +131,7 @@
max_capacity = SbMediaGetMaxBufferCapacity(
video_codec_, resolution_width_, resolution_height_, bits_per_pixel_);
}
- reuse_allocator_.reset(new nb::BidirectionalFitReuseAllocator(
- &fallback_allocator_, initial_capacity_, kSmallAllocationThreshold,
- allocation_unit_, max_capacity));
- DLOG(INFO) << "Allocated " << initial_capacity_
- << " bytes for media buffer pool, with max capacity set to "
- << max_capacity;
+ CreateReuseAllocator(max_capacity);
}
void* p = reuse_allocator_->Allocate(size, alignment);
@@ -217,6 +237,17 @@
resolution_height_, bits_per_pixel_);
}
+void DecoderBufferAllocator::CreateReuseAllocator(int max_capacity) {
+ mutex_.DCheckAcquired();
+
+ reuse_allocator_.reset(new nb::BidirectionalFitReuseAllocator(
+ &fallback_allocator_, initial_capacity_, kSmallAllocationThreshold,
+ allocation_unit_, max_capacity));
+ DLOG(INFO) << "Allocated " << initial_capacity_
+ << " bytes for media buffer pool, with max capacity set to "
+ << max_capacity;
+}
+
bool DecoderBufferAllocator::UpdateAllocationRecord() const {
#if !defined(COBALT_BUILD_TYPE_GOLD)
// This code is not quite multi-thread safe but is safe enough for tracking
diff --git a/cobalt/media/decoder_buffer_allocator.h b/cobalt/media/decoder_buffer_allocator.h
index e78e6af..67e5352 100644
--- a/cobalt/media/decoder_buffer_allocator.h
+++ b/cobalt/media/decoder_buffer_allocator.h
@@ -37,6 +37,9 @@
DecoderBufferAllocator();
~DecoderBufferAllocator() override;
+ void Suspend();
+ void Resume();
+
Allocations Allocate(size_t size, size_t alignment,
intptr_t context) override;
void Free(Allocations allocations) override;
@@ -46,6 +49,8 @@
size_t GetMaximumMemoryCapacity() const override;
private:
+ void CreateReuseAllocator(int max_capacity);
+
// Update the Allocation record, and return false if allocation exceeds the
// max buffer capacity, or true otherwise.
bool UpdateAllocationRecord() const;
diff --git a/cobalt/media/formats/mp2t/es_adapter_video.cc b/cobalt/media/formats/mp2t/es_adapter_video.cc
index 7b3e64b..92a72f3 100644
--- a/cobalt/media/formats/mp2t/es_adapter_video.cc
+++ b/cobalt/media/formats/mp2t/es_adapter_video.cc
@@ -2,11 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "media/formats/mp2t/es_adapter_video.h"
+#include "cobalt/media/formats/mp2t/es_adapter_video.h"
-#include "media/base/timestamp_constants.h"
-#include "media/base/video_decoder_config.h"
-#include "media/formats/mp2t/mp2t_common.h"
+#include "cobalt/media/base/timestamp_constants.h"
+#include "cobalt/media/base/video_decoder_config.h"
+#include "cobalt/media/formats/mp2t/mp2t_common.h"
#include "starboard/types.h"
namespace cobalt {
diff --git a/cobalt/media/formats/mp2t/es_adapter_video.h b/cobalt/media/formats/mp2t/es_adapter_video.h
index bdb23ee..e8e5ddd 100644
--- a/cobalt/media/formats/mp2t/es_adapter_video.h
+++ b/cobalt/media/formats/mp2t/es_adapter_video.h
@@ -13,8 +13,8 @@
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/time/time.h"
-#include "media/base/media_export.h"
-#include "media/base/stream_parser_buffer.h"
+#include "cobalt/media/base/media_export.h"
+#include "cobalt/media/base/stream_parser_buffer.h"
#include "starboard/types.h"
namespace cobalt {
diff --git a/cobalt/media/formats/mp2t/es_adapter_video_unittest.cc b/cobalt/media/formats/mp2t/es_adapter_video_unittest.cc
index 81951e0..65e5882 100644
--- a/cobalt/media/formats/mp2t/es_adapter_video_unittest.cc
+++ b/cobalt/media/formats/mp2t/es_adapter_video_unittest.cc
@@ -11,11 +11,11 @@
#include "base/macros.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
-#include "media/base/media_util.h"
-#include "media/base/stream_parser_buffer.h"
-#include "media/base/timestamp_constants.h"
-#include "media/base/video_decoder_config.h"
-#include "media/formats/mp2t/es_adapter_video.h"
+#include "cobalt/media/base/media_util.h"
+#include "cobalt/media/base/stream_parser_buffer.h"
+#include "cobalt/media/base/timestamp_constants.h"
+#include "cobalt/media/base/video_decoder_config.h"
+#include "cobalt/media/formats/mp2t/es_adapter_video.h"
#include "starboard/types.h"
#include "testing/gtest/include/gtest/gtest.h"
diff --git a/cobalt/media/formats/mp2t/es_parser.cc b/cobalt/media/formats/mp2t/es_parser.cc
index 502c148..d20c634 100644
--- a/cobalt/media/formats/mp2t/es_parser.cc
+++ b/cobalt/media/formats/mp2t/es_parser.cc
@@ -2,10 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "media/formats/mp2t/es_parser.h"
+#include "cobalt/media/formats/mp2t/es_parser.h"
-#include "media/base/timestamp_constants.h"
-#include "media/formats/common/offset_byte_queue.h"
+#include "cobalt/media/base/timestamp_constants.h"
+#include "cobalt/media/formats/common/offset_byte_queue.h"
namespace cobalt {
namespace media {
diff --git a/cobalt/media/formats/mp2t/es_parser.h b/cobalt/media/formats/mp2t/es_parser.h
index da35fa7..6b2a989 100644
--- a/cobalt/media/formats/mp2t/es_parser.h
+++ b/cobalt/media/formats/mp2t/es_parser.h
@@ -13,8 +13,8 @@
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/time/time.h"
-#include "media/base/media_export.h"
-#include "media/base/stream_parser_buffer.h"
+#include "cobalt/media/base/media_export.h"
+#include "cobalt/media/base/stream_parser_buffer.h"
#include "starboard/types.h"
namespace cobalt {
diff --git a/cobalt/media/formats/mp2t/es_parser_adts.cc b/cobalt/media/formats/mp2t/es_parser_adts.cc
index db1d3d3..6773610 100644
--- a/cobalt/media/formats/mp2t/es_parser_adts.cc
+++ b/cobalt/media/formats/mp2t/es_parser_adts.cc
@@ -2,22 +2,22 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "media/formats/mp2t/es_parser_adts.h"
+#include "cobalt/media/formats/mp2t/es_parser_adts.h"
#include <algorithm>
#include <vector>
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
-#include "media/base/audio_timestamp_helper.h"
-#include "media/base/bit_reader.h"
-#include "media/base/channel_layout.h"
-#include "media/base/media_util.h"
-#include "media/base/stream_parser_buffer.h"
-#include "media/base/timestamp_constants.h"
-#include "media/formats/common/offset_byte_queue.h"
-#include "media/formats/mp2t/mp2t_common.h"
-#include "media/formats/mpeg/adts_constants.h"
+#include "cobalt/media/base/audio_timestamp_helper.h"
+#include "cobalt/media/base/bit_reader.h"
+#include "cobalt/media/base/channel_layout.h"
+#include "cobalt/media/base/media_util.h"
+#include "cobalt/media/base/stream_parser_buffer.h"
+#include "cobalt/media/base/timestamp_constants.h"
+#include "cobalt/media/formats/common/offset_byte_queue.h"
+#include "cobalt/media/formats/mp2t/mp2t_common.h"
+#include "cobalt/media/formats/mpeg/adts_constants.h"
#include "starboard/types.h"
namespace cobalt {
diff --git a/cobalt/media/formats/mp2t/es_parser_adts.h b/cobalt/media/formats/mp2t/es_parser_adts.h
index f52e1dc..7630ff7 100644
--- a/cobalt/media/formats/mp2t/es_parser_adts.h
+++ b/cobalt/media/formats/mp2t/es_parser_adts.h
@@ -13,10 +13,10 @@
#include "base/compiler_specific.h"
#include "base/macros.h"
#include "base/time/time.h"
-#include "media/base/audio_decoder_config.h"
-#include "media/base/media_export.h"
-#include "media/formats/mp2t/es_parser.h"
-#include "media/formats/mpeg/adts_stream_parser.h"
+#include "cobalt/media/base/audio_decoder_config.h"
+#include "cobalt/media/base/media_export.h"
+#include "cobalt/media/formats/mp2t/es_parser.h"
+#include "cobalt/media/formats/mpeg/adts_stream_parser.h"
#include "starboard/types.h"
namespace cobalt {
@@ -26,6 +26,7 @@
class OffsetByteQueue;
class StreamParserBuffer;
} // namespace media
+} // namespace cobalt
namespace cobalt {
namespace media {
diff --git a/cobalt/media/formats/mp2t/es_parser_adts_unittest.cc b/cobalt/media/formats/mp2t/es_parser_adts_unittest.cc
index 4c86a32..92b63fc 100644
--- a/cobalt/media/formats/mp2t/es_parser_adts_unittest.cc
+++ b/cobalt/media/formats/mp2t/es_parser_adts_unittest.cc
@@ -8,9 +8,9 @@
#include "base/logging.h"
#include "base/macros.h"
#include "base/time/time.h"
-#include "media/base/stream_parser_buffer.h"
-#include "media/formats/mp2t/es_parser_adts.h"
-#include "media/formats/mp2t/es_parser_test_base.h"
+#include "cobalt/media/base/stream_parser_buffer.h"
+#include "cobalt/media/formats/mp2t/es_parser_adts.h"
+#include "cobalt/media/formats/mp2t/es_parser_test_base.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cobalt {
diff --git a/cobalt/media/formats/mp2t/es_parser_h264.cc b/cobalt/media/formats/mp2t/es_parser_h264.cc
index ede1283..35e534a 100644
--- a/cobalt/media/formats/mp2t/es_parser_h264.cc
+++ b/cobalt/media/formats/mp2t/es_parser_h264.cc
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "media/formats/mp2t/es_parser_h264.h"
+#include "cobalt/media/formats/mp2t/es_parser_h264.h"
#include <limits>
@@ -11,14 +11,14 @@
#include "base/optional.h"
#include "cobalt/math/geometry/rect.h"
#include "cobalt/math/geometry/size.h"
-#include "media/base/encryption_scheme.h"
-#include "media/base/media_util.h"
-#include "media/base/stream_parser_buffer.h"
-#include "media/base/timestamp_constants.h"
-#include "media/base/video_frame.h"
-#include "media/filters/h264_parser.h"
-#include "media/formats/common/offset_byte_queue.h"
-#include "media/formats/mp2t/mp2t_common.h"
+#include "cobalt/media/base/encryption_scheme.h"
+#include "cobalt/media/base/media_util.h"
+#include "cobalt/media/base/stream_parser_buffer.h"
+#include "cobalt/media/base/timestamp_constants.h"
+#include "cobalt/media/base/video_frame.h"
+#include "cobalt/media/filters/h264_parser.h"
+#include "cobalt/media/formats/common/offset_byte_queue.h"
+#include "cobalt/media/formats/mp2t/mp2t_common.h"
namespace cobalt {
namespace media {
diff --git a/cobalt/media/formats/mp2t/es_parser_h264.h b/cobalt/media/formats/mp2t/es_parser_h264.h
index fa4219a..84a2262 100644
--- a/cobalt/media/formats/mp2t/es_parser_h264.h
+++ b/cobalt/media/formats/mp2t/es_parser_h264.h
@@ -12,10 +12,10 @@
#include "base/compiler_specific.h"
#include "base/macros.h"
#include "base/time/time.h"
-#include "media/base/media_export.h"
-#include "media/base/video_decoder_config.h"
-#include "media/formats/mp2t/es_adapter_video.h"
-#include "media/formats/mp2t/es_parser.h"
+#include "cobalt/media/base/media_export.h"
+#include "cobalt/media/base/video_decoder_config.h"
+#include "cobalt/media/formats/mp2t/es_adapter_video.h"
+#include "cobalt/media/formats/mp2t/es_parser.h"
#include "starboard/types.h"
namespace cobalt {
@@ -25,6 +25,7 @@
struct H264SPS;
class OffsetByteQueue;
} // namespace media
+} // namespace cobalt
namespace cobalt {
namespace media {
diff --git a/cobalt/media/formats/mp2t/es_parser_h264_unittest.cc b/cobalt/media/formats/mp2t/es_parser_h264_unittest.cc
index c1149d0..5e237f3 100644
--- a/cobalt/media/formats/mp2t/es_parser_h264_unittest.cc
+++ b/cobalt/media/formats/mp2t/es_parser_h264_unittest.cc
@@ -12,10 +12,10 @@
#include "base/macros.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
-#include "media/base/stream_parser_buffer.h"
-#include "media/filters/h264_parser.h"
-#include "media/formats/mp2t/es_parser_h264.h"
-#include "media/formats/mp2t/es_parser_test_base.h"
+#include "cobalt/media/base/stream_parser_buffer.h"
+#include "cobalt/media/filters/h264_parser.h"
+#include "cobalt/media/formats/mp2t/es_parser_h264.h"
+#include "cobalt/media/formats/mp2t/es_parser_test_base.h"
#include "starboard/memory.h"
#include "starboard/types.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -122,7 +122,7 @@
offset += sizeof(aud);
memcpy(&stream_with_aud[offset], &stream_[access_units_[k].offset],
- access_units_[k].size);
+ access_units_[k].size);
offset += access_units_[k].size;
}
diff --git a/cobalt/media/formats/mp2t/es_parser_mpeg1audio.cc b/cobalt/media/formats/mp2t/es_parser_mpeg1audio.cc
index a97a0e2..5b47aa9 100644
--- a/cobalt/media/formats/mp2t/es_parser_mpeg1audio.cc
+++ b/cobalt/media/formats/mp2t/es_parser_mpeg1audio.cc
@@ -2,22 +2,22 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "media/formats/mp2t/es_parser_mpeg1audio.h"
+#include "cobalt/media/formats/mp2t/es_parser_mpeg1audio.h"
#include <vector>
#include "base/bind.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
-#include "media/base/audio_timestamp_helper.h"
-#include "media/base/bit_reader.h"
-#include "media/base/channel_layout.h"
-#include "media/base/media_util.h"
-#include "media/base/stream_parser_buffer.h"
-#include "media/base/timestamp_constants.h"
-#include "media/formats/common/offset_byte_queue.h"
-#include "media/formats/mp2t/mp2t_common.h"
-#include "media/formats/mpeg/mpeg1_audio_stream_parser.h"
+#include "cobalt/media/base/audio_timestamp_helper.h"
+#include "cobalt/media/base/bit_reader.h"
+#include "cobalt/media/base/channel_layout.h"
+#include "cobalt/media/base/media_util.h"
+#include "cobalt/media/base/stream_parser_buffer.h"
+#include "cobalt/media/base/timestamp_constants.h"
+#include "cobalt/media/formats/common/offset_byte_queue.h"
+#include "cobalt/media/formats/mp2t/mp2t_common.h"
+#include "cobalt/media/formats/mpeg/mpeg1_audio_stream_parser.h"
namespace cobalt {
namespace media {
diff --git a/cobalt/media/formats/mp2t/es_parser_mpeg1audio.h b/cobalt/media/formats/mp2t/es_parser_mpeg1audio.h
index 4966b46..5ac0343 100644
--- a/cobalt/media/formats/mp2t/es_parser_mpeg1audio.h
+++ b/cobalt/media/formats/mp2t/es_parser_mpeg1audio.h
@@ -13,10 +13,10 @@
#include "base/compiler_specific.h"
#include "base/macros.h"
#include "base/time/time.h"
-#include "media/base/audio_decoder_config.h"
-#include "media/base/media_export.h"
-#include "media/base/media_log.h"
-#include "media/formats/mp2t/es_parser.h"
+#include "cobalt/media/base/audio_decoder_config.h"
+#include "cobalt/media/base/media_export.h"
+#include "cobalt/media/base/media_log.h"
+#include "cobalt/media/formats/mp2t/es_parser.h"
#include "starboard/types.h"
namespace cobalt {
@@ -26,6 +26,7 @@
class OffsetByteQueue;
class StreamParserBuffer;
} // namespace media
+} // namespace cobalt
namespace cobalt {
namespace media {
diff --git a/cobalt/media/formats/mp2t/es_parser_mpeg1audio_unittest.cc b/cobalt/media/formats/mp2t/es_parser_mpeg1audio_unittest.cc
index 13aa58a..a92c318 100644
--- a/cobalt/media/formats/mp2t/es_parser_mpeg1audio_unittest.cc
+++ b/cobalt/media/formats/mp2t/es_parser_mpeg1audio_unittest.cc
@@ -8,10 +8,10 @@
#include "base/logging.h"
#include "base/macros.h"
#include "base/time/time.h"
-#include "media/base/media_log.h"
-#include "media/base/stream_parser_buffer.h"
-#include "media/formats/mp2t/es_parser_mpeg1audio.h"
-#include "media/formats/mp2t/es_parser_test_base.h"
+#include "cobalt/media/base/media_log.h"
+#include "cobalt/media/base/stream_parser_buffer.h"
+#include "cobalt/media/formats/mp2t/es_parser_mpeg1audio.h"
+#include "cobalt/media/formats/mp2t/es_parser_test_base.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cobalt {
diff --git a/cobalt/media/formats/mp2t/es_parser_test_base.cc b/cobalt/media/formats/mp2t/es_parser_test_base.cc
index 7315320..fe73bac 100644
--- a/cobalt/media/formats/mp2t/es_parser_test_base.cc
+++ b/cobalt/media/formats/mp2t/es_parser_test_base.cc
@@ -2,16 +2,16 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "media/formats/mp2t/es_parser_test_base.h"
+#include "cobalt/media/formats/mp2t/es_parser_test_base.h"
#include "base/files/memory_mapped_file.h"
#include "base/logging.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
-#include "media/base/stream_parser_buffer.h"
-#include "media/base/test_data_util.h"
-#include "media/base/timestamp_constants.h"
-#include "media/formats/mp2t/es_parser.h"
+#include "cobalt/media/base/stream_parser_buffer.h"
+#include "cobalt/media/base/test_data_util.h"
+#include "cobalt/media/base/timestamp_constants.h"
+#include "cobalt/media/formats/mp2t/es_parser.h"
#include "starboard/memory.h"
#include "testing/gtest/include/gtest/gtest.h"
diff --git a/cobalt/media/formats/mp2t/mp2t_stream_parser.cc b/cobalt/media/formats/mp2t/mp2t_stream_parser.cc
index 09513a4..f9f0e4f 100644
--- a/cobalt/media/formats/mp2t/mp2t_stream_parser.cc
+++ b/cobalt/media/formats/mp2t/mp2t_stream_parser.cc
@@ -2,27 +2,27 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "media/formats/mp2t/mp2t_stream_parser.h"
+#include "cobalt/media/formats/mp2t/mp2t_stream_parser.h"
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/callback_helpers.h"
-#include "media/base/media_tracks.h"
-#include "media/base/stream_parser_buffer.h"
-#include "media/base/text_track_config.h"
-#include "media/base/timestamp_constants.h"
-#include "media/formats/mp2t/es_parser.h"
-#include "media/formats/mp2t/es_parser_adts.h"
-#include "media/formats/mp2t/es_parser_h264.h"
-#include "media/formats/mp2t/es_parser_mpeg1audio.h"
-#include "media/formats/mp2t/mp2t_common.h"
-#include "media/formats/mp2t/ts_packet.h"
-#include "media/formats/mp2t/ts_section.h"
-#include "media/formats/mp2t/ts_section_pat.h"
-#include "media/formats/mp2t/ts_section_pes.h"
-#include "media/formats/mp2t/ts_section_pmt.h"
+#include "cobalt/media/base/media_tracks.h"
+#include "cobalt/media/base/stream_parser_buffer.h"
+#include "cobalt/media/base/text_track_config.h"
+#include "cobalt/media/base/timestamp_constants.h"
+#include "cobalt/media/formats/mp2t/es_parser.h"
+#include "cobalt/media/formats/mp2t/es_parser_adts.h"
+#include "cobalt/media/formats/mp2t/es_parser_h264.h"
+#include "cobalt/media/formats/mp2t/es_parser_mpeg1audio.h"
+#include "cobalt/media/formats/mp2t/mp2t_common.h"
+#include "cobalt/media/formats/mp2t/ts_packet.h"
+#include "cobalt/media/formats/mp2t/ts_section.h"
+#include "cobalt/media/formats/mp2t/ts_section_pat.h"
+#include "cobalt/media/formats/mp2t/ts_section_pes.h"
+#include "cobalt/media/formats/mp2t/ts_section_pmt.h"
namespace cobalt {
namespace media {
diff --git a/cobalt/media/formats/mp2t/mp2t_stream_parser.h b/cobalt/media/formats/mp2t/mp2t_stream_parser.h
index 6554bee..e37a8a3 100644
--- a/cobalt/media/formats/mp2t/mp2t_stream_parser.h
+++ b/cobalt/media/formats/mp2t/mp2t_stream_parser.h
@@ -11,12 +11,12 @@
#include "base/macros.h"
#include "base/memory/ref_counted.h"
-#include "media/base/audio_decoder_config.h"
-#include "media/base/byte_queue.h"
-#include "media/base/media_export.h"
-#include "media/base/stream_parser.h"
-#include "media/base/video_decoder_config.h"
-#include "media/formats/mp2t/timestamp_unroller.h"
+#include "cobalt/media/base/audio_decoder_config.h"
+#include "cobalt/media/base/byte_queue.h"
+#include "cobalt/media/base/media_export.h"
+#include "cobalt/media/base/stream_parser.h"
+#include "cobalt/media/base/video_decoder_config.h"
+#include "cobalt/media/formats/mp2t/timestamp_unroller.h"
#include "starboard/types.h"
namespace cobalt {
@@ -64,7 +64,7 @@
// Callback invoked to register a PES pid.
// Possible values for |stream_type| are defined in:
// ISO-13818.1 / ITU H.222 Table 2.34 "Stream type assignments".
- // |pes_pid| is part of the Program Map Table refered by |pmt_pid|.
+ // |pes_pid| is part of the Program Map Table referred by |pmt_pid|.
void RegisterPes(int pmt_pid, int pes_pid, int stream_type);
// Since the StreamParser interface allows only one audio & video streams,
diff --git a/cobalt/media/formats/mp2t/mp2t_stream_parser_unittest.cc b/cobalt/media/formats/mp2t/mp2t_stream_parser_unittest.cc
index a273cc4..8bf38a6 100644
--- a/cobalt/media/formats/mp2t/mp2t_stream_parser_unittest.cc
+++ b/cobalt/media/formats/mp2t/mp2t_stream_parser_unittest.cc
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "media/formats/mp2t/mp2t_stream_parser.h"
+#include "cobalt/media/formats/mp2t/mp2t_stream_parser.h"
#include <algorithm>
#include <memory>
@@ -14,15 +14,15 @@
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/time/time.h"
-#include "media/base/audio_decoder_config.h"
-#include "media/base/decoder_buffer.h"
-#include "media/base/media_log.h"
-#include "media/base/media_track.h"
-#include "media/base/media_tracks.h"
-#include "media/base/stream_parser_buffer.h"
-#include "media/base/test_data_util.h"
-#include "media/base/text_track_config.h"
-#include "media/base/video_decoder_config.h"
+#include "cobalt/media/base/audio_decoder_config.h"
+#include "cobalt/media/base/decoder_buffer.h"
+#include "cobalt/media/base/media_log.h"
+#include "cobalt/media/base/media_track.h"
+#include "cobalt/media/base/media_tracks.h"
+#include "cobalt/media/base/stream_parser_buffer.h"
+#include "cobalt/media/base/test_data_util.h"
+#include "cobalt/media/base/text_track_config.h"
+#include "cobalt/media/base/video_decoder_config.h"
#include "starboard/types.h"
#include "testing/gtest/include/gtest/gtest.h"
diff --git a/cobalt/media/formats/mp2t/timestamp_unroller.cc b/cobalt/media/formats/mp2t/timestamp_unroller.cc
index b88c6a6..3ff20f8 100644
--- a/cobalt/media/formats/mp2t/timestamp_unroller.cc
+++ b/cobalt/media/formats/mp2t/timestamp_unroller.cc
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "media/formats/mp2t/timestamp_unroller.h"
+#include "cobalt/media/formats/mp2t/timestamp_unroller.h"
#include "base/logging.h"
diff --git a/cobalt/media/formats/mp2t/timestamp_unroller.h b/cobalt/media/formats/mp2t/timestamp_unroller.h
index 0ea65b5..28e171b 100644
--- a/cobalt/media/formats/mp2t/timestamp_unroller.h
+++ b/cobalt/media/formats/mp2t/timestamp_unroller.h
@@ -6,7 +6,7 @@
#define COBALT_MEDIA_FORMATS_MP2T_TIMESTAMP_UNROLLER_H_
#include "base/macros.h"
-#include "media/base/media_export.h"
+#include "cobalt/media/base/media_export.h"
#include "starboard/types.h"
namespace cobalt {
diff --git a/cobalt/media/formats/mp2t/timestamp_unroller_unittest.cc b/cobalt/media/formats/mp2t/timestamp_unroller_unittest.cc
index 7da57bc..10e68d5 100644
--- a/cobalt/media/formats/mp2t/timestamp_unroller_unittest.cc
+++ b/cobalt/media/formats/mp2t/timestamp_unroller_unittest.cc
@@ -7,7 +7,7 @@
#include "base/logging.h"
#include "base/macros.h"
#include "base/test/perf_test_suite.h"
-#include "media/formats/mp2t/timestamp_unroller.h"
+#include "cobalt/media/formats/mp2t/timestamp_unroller.h"
#include "starboard/types.h"
#include "testing/gtest/include/gtest/gtest.h"
diff --git a/cobalt/media/formats/mp2t/ts_packet.cc b/cobalt/media/formats/mp2t/ts_packet.cc
index 098aad9..94bf3b3 100644
--- a/cobalt/media/formats/mp2t/ts_packet.cc
+++ b/cobalt/media/formats/mp2t/ts_packet.cc
@@ -2,12 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "media/formats/mp2t/ts_packet.h"
+#include "cobalt/media/formats/mp2t/ts_packet.h"
#include <memory>
-#include "media/base/bit_reader.h"
-#include "media/formats/mp2t/mp2t_common.h"
+#include "cobalt/media/base/bit_reader.h"
+#include "cobalt/media/formats/mp2t/mp2t_common.h"
namespace cobalt {
namespace media {
diff --git a/cobalt/media/formats/mp2t/ts_section_pat.cc b/cobalt/media/formats/mp2t/ts_section_pat.cc
index 76f4e62..1aa3449 100644
--- a/cobalt/media/formats/mp2t/ts_section_pat.cc
+++ b/cobalt/media/formats/mp2t/ts_section_pat.cc
@@ -2,13 +2,13 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "media/formats/mp2t/ts_section_pat.h"
+#include "cobalt/media/formats/mp2t/ts_section_pat.h"
#include <vector>
#include "base/logging.h"
-#include "media/base/bit_reader.h"
-#include "media/formats/mp2t/mp2t_common.h"
+#include "cobalt/media/base/bit_reader.h"
+#include "cobalt/media/formats/mp2t/mp2t_common.h"
namespace cobalt {
namespace media {
diff --git a/cobalt/media/formats/mp2t/ts_section_pat.h b/cobalt/media/formats/mp2t/ts_section_pat.h
index 0b102e2..64cca26 100644
--- a/cobalt/media/formats/mp2t/ts_section_pat.h
+++ b/cobalt/media/formats/mp2t/ts_section_pat.h
@@ -8,7 +8,7 @@
#include "base/callback.h"
#include "base/compiler_specific.h"
#include "base/macros.h"
-#include "media/formats/mp2t/ts_section_psi.h"
+#include "cobalt/media/formats/mp2t/ts_section_psi.h"
namespace cobalt {
namespace media {
diff --git a/cobalt/media/formats/mp2t/ts_section_pes.cc b/cobalt/media/formats/mp2t/ts_section_pes.cc
index 6cb2312..a381a3b 100644
--- a/cobalt/media/formats/mp2t/ts_section_pes.cc
+++ b/cobalt/media/formats/mp2t/ts_section_pes.cc
@@ -2,17 +2,17 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "media/formats/mp2t/ts_section_pes.h"
+#include "cobalt/media/formats/mp2t/ts_section_pes.h"
#include <memory>
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
-#include "media/base/bit_reader.h"
-#include "media/base/timestamp_constants.h"
-#include "media/formats/mp2t/es_parser.h"
-#include "media/formats/mp2t/mp2t_common.h"
-#include "media/formats/mp2t/timestamp_unroller.h"
+#include "cobalt/media/base/bit_reader.h"
+#include "cobalt/media/base/timestamp_constants.h"
+#include "cobalt/media/formats/mp2t/es_parser.h"
+#include "cobalt/media/formats/mp2t/mp2t_common.h"
+#include "cobalt/media/formats/mp2t/timestamp_unroller.h"
static const int kPesStartCode = 0x000001;
diff --git a/cobalt/media/formats/mp2t/ts_section_pes.h b/cobalt/media/formats/mp2t/ts_section_pes.h
index 553d520..675d774 100644
--- a/cobalt/media/formats/mp2t/ts_section_pes.h
+++ b/cobalt/media/formats/mp2t/ts_section_pes.h
@@ -9,8 +9,8 @@
#include "base/compiler_specific.h"
#include "base/macros.h"
-#include "media/base/byte_queue.h"
-#include "media/formats/mp2t/ts_section.h"
+#include "cobalt/media/base/byte_queue.h"
+#include "cobalt/media/formats/mp2t/ts_section.h"
#include "starboard/types.h"
namespace cobalt {
diff --git a/cobalt/media/formats/mp2t/ts_section_pmt.cc b/cobalt/media/formats/mp2t/ts_section_pmt.cc
index 98fe82b..071b7a6 100644
--- a/cobalt/media/formats/mp2t/ts_section_pmt.cc
+++ b/cobalt/media/formats/mp2t/ts_section_pmt.cc
@@ -2,14 +2,14 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "media/formats/mp2t/ts_section_pmt.h"
+#include "cobalt/media/formats/mp2t/ts_section_pmt.h"
#include <map>
#include <utility>
#include "base/logging.h"
-#include "media/base/bit_reader.h"
-#include "media/formats/mp2t/mp2t_common.h"
+#include "cobalt/media/base/bit_reader.h"
+#include "cobalt/media/formats/mp2t/mp2t_common.h"
namespace cobalt {
namespace media {
@@ -71,7 +71,7 @@
RCHECK(program_info_length < 1024);
// Read the program info descriptor.
- // TODO(damienv): check wether any of the descriptors could be useful.
+ // TODO(damienv): check whether any of the descriptors could be useful.
// Defined in section 2.6 of ISO-13818.
RCHECK(bit_reader->SkipBits(8 * program_info_length));
@@ -97,7 +97,7 @@
pid_map.insert(std::pair<int, int>(pid_es, stream_type));
// Read the ES info descriptors.
- // TODO(damienv): check wether any of the descriptors could be useful.
+ // TODO(damienv): check whether any of the descriptors could be useful.
// Defined in section 2.6 of ISO-13818.
RCHECK(bit_reader->SkipBits(8 * es_info_length));
}
diff --git a/cobalt/media/formats/mp2t/ts_section_pmt.h b/cobalt/media/formats/mp2t/ts_section_pmt.h
index f6ed1d9..5362da5 100644
--- a/cobalt/media/formats/mp2t/ts_section_pmt.h
+++ b/cobalt/media/formats/mp2t/ts_section_pmt.h
@@ -8,7 +8,7 @@
#include "base/callback.h"
#include "base/compiler_specific.h"
#include "base/macros.h"
-#include "media/formats/mp2t/ts_section_psi.h"
+#include "cobalt/media/formats/mp2t/ts_section_psi.h"
namespace cobalt {
namespace media {
diff --git a/cobalt/media/formats/mp2t/ts_section_psi.cc b/cobalt/media/formats/mp2t/ts_section_psi.cc
index fb0b3ba..0f809ae 100644
--- a/cobalt/media/formats/mp2t/ts_section_psi.cc
+++ b/cobalt/media/formats/mp2t/ts_section_psi.cc
@@ -2,13 +2,13 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "media/formats/mp2t/ts_section_psi.h"
+#include "cobalt/media/formats/mp2t/ts_section_psi.h"
#include <algorithm>
#include "base/logging.h"
-#include "media/base/bit_reader.h"
-#include "media/formats/mp2t/mp2t_common.h"
+#include "cobalt/media/base/bit_reader.h"
+#include "cobalt/media/formats/mp2t/mp2t_common.h"
static bool IsCrcValid(const uint8_t* buf, int size) {
uint32_t crc = 0xffffffffu;
diff --git a/cobalt/media/formats/mp2t/ts_section_psi.h b/cobalt/media/formats/mp2t/ts_section_psi.h
index 69ebb93..b840dd2 100644
--- a/cobalt/media/formats/mp2t/ts_section_psi.h
+++ b/cobalt/media/formats/mp2t/ts_section_psi.h
@@ -7,8 +7,8 @@
#include "base/compiler_specific.h"
#include "base/macros.h"
-#include "media/base/byte_queue.h"
-#include "media/formats/mp2t/ts_section.h"
+#include "cobalt/media/base/byte_queue.h"
+#include "cobalt/media/formats/mp2t/ts_section.h"
#include "starboard/types.h"
namespace cobalt {
diff --git a/cobalt/media/media_module.cc b/cobalt/media/media_module.cc
index 3f6671d..8d4e195 100644
--- a/cobalt/media/media_module.cc
+++ b/cobalt/media/media_module.cc
@@ -203,6 +203,8 @@
}
}
+ decoder_buffer_allocator_.Suspend();
+
resource_provider_ = NULL;
}
@@ -216,6 +218,8 @@
window = system_window_->GetSbWindow();
}
+ decoder_buffer_allocator_.Resume();
+
for (Players::iterator iter = players_.begin(); iter != players_.end();
++iter) {
DCHECK(!iter->second);
diff --git a/cobalt/media/progressive/demuxer_fuzzer.cc b/cobalt/media/progressive/demuxer_fuzzer.cc
index ebd2766..d7be774 100644
--- a/cobalt/media/progressive/demuxer_fuzzer.cc
+++ b/cobalt/media/progressive/demuxer_fuzzer.cc
@@ -20,12 +20,12 @@
#include "base/files/file_path.h"
#include "base/memory/ref_counted.h"
#include "cobalt/base/wrap_main.h"
+#include "cobalt/media/base/bind_to_loop.h"
+#include "cobalt/media/base/pipeline_status.h"
+#include "cobalt/media/progressive/progressive_demuxer.h"
#include "cobalt/media/sandbox/fuzzer_app.h"
#include "cobalt/media/sandbox/in_memory_data_source.h"
#include "cobalt/media/sandbox/media_sandbox.h"
-#include "media/base/bind_to_loop.h"
-#include "media/base/pipeline_status.h"
-#include "media/progressive/progressive_demuxer.h"
namespace cobalt {
namespace media {
@@ -69,22 +69,16 @@
private:
// DataSourceHost methods (parent class of DemuxerHost)
- void SetTotalBytes(int64 total_bytes) override {
- }
+ void SetTotalBytes(int64 total_bytes) override {}
- void AddBufferedByteRange(int64 start, int64 end) override {
- }
+ void AddBufferedByteRange(int64 start, int64 end) override {}
void AddBufferedTimeRange(base::TimeDelta start,
- base::TimeDelta end) override {
- }
+ base::TimeDelta end) override {}
// DemuxerHost methods
- void SetDuration(base::TimeDelta duration) override {
- }
- void OnDemuxerError(PipelineStatus error) override {
- error_occurred_ = true;
- }
+ void SetDuration(base::TimeDelta duration) override {}
+ void OnDemuxerError(PipelineStatus error) override { error_occurred_ = true; }
void InitializeCB(PipelineStatus status) {
DCHECK(!error_occurred_);
diff --git a/cobalt/media/progressive/mock_data_source_reader.h b/cobalt/media/progressive/mock_data_source_reader.h
index cf3ddab..1ec8ba7 100644
--- a/cobalt/media/progressive/mock_data_source_reader.h
+++ b/cobalt/media/progressive/mock_data_source_reader.h
@@ -17,7 +17,7 @@
#ifndef COBALT_MEDIA_PROGRESSIVE_MOCK_DATA_SOURCE_READER_H_
#define COBALT_MEDIA_PROGRESSIVE_MOCK_DATA_SOURCE_READER_H_
-#include "media/progressive/data_source_reader.h"
+#include "cobalt/media/progressive/data_source_reader.h"
#include "testing/gmock/include/gmock/gmock.h"
namespace cobalt {
diff --git a/cobalt/media/sandbox/BUILD.gn b/cobalt/media/sandbox/BUILD.gn
new file mode 100644
index 0000000..8ff9b34
--- /dev/null
+++ b/cobalt/media/sandbox/BUILD.gn
@@ -0,0 +1,64 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This is a sample sandbox application for experimenting with the Cobalt
+# media/renderer interface.
+
+target(final_executable_type, "media_sandbox") {
+ sources = [ "media2_sandbox.cc" ]
+
+ deps = [
+ "//cobalt/base",
+ "//cobalt/math",
+ "//cobalt/media",
+ "//starboard",
+ ]
+
+ content_deps = [ "//third_party/icu:icudata" ]
+}
+
+target(final_executable_type, "web_media_player_sandbox") {
+ sources = [
+ "format_guesstimator.cc",
+ "format_guesstimator.h",
+ "media_sandbox.cc",
+ "media_sandbox.h",
+ "web_media_player_helper.cc",
+ "web_media_player_helper.h",
+ "web_media_player_sandbox.cc",
+ ]
+
+ deps = [
+ "//cobalt/base",
+
+ # Use test data from demos to avoid keeping two copies of video files.
+ "//cobalt/demos/content:demos_testdata",
+ "//cobalt/loader",
+ "//cobalt/math",
+ "//cobalt/media",
+ "//cobalt/network",
+ "//cobalt/render_tree",
+ "//cobalt/render_tree:animations",
+ "//cobalt/renderer",
+ "//cobalt/storage",
+ "//cobalt/system_window",
+ "//cobalt/trace_event",
+ "//starboard",
+ "//url",
+ ]
+
+ if (!sb_is_evergreen) {
+ deps += cobalt_platform_dependencies
+ }
+}
diff --git a/cobalt/media/sandbox/demuxer_helper.cc b/cobalt/media/sandbox/demuxer_helper.cc
index c5b2991..6828de6 100644
--- a/cobalt/media/sandbox/demuxer_helper.cc
+++ b/cobalt/media/sandbox/demuxer_helper.cc
@@ -19,12 +19,12 @@
#include "base/bind_helpers.h"
#include "base/callback_helpers.h"
#include "base/logging.h"
+#include "cobalt/media/base/audio_decoder_config.h"
+#include "cobalt/media/base/bind_to_loop.h"
+#include "cobalt/media/base/decoder_buffer.h"
+#include "cobalt/media/base/video_decoder_config.h"
#include "cobalt/media/fetcher_buffered_data_source.h"
-#include "media/base/audio_decoder_config.h"
-#include "media/base/bind_to_loop.h"
-#include "media/base/decoder_buffer.h"
-#include "media/base/video_decoder_config.h"
-#include "media/progressive/progressive_demuxer.h"
+#include "cobalt/media/progressive/progressive_demuxer.h"
namespace cobalt {
namespace media {
@@ -212,19 +212,14 @@
class DemuxerHelper::DemuxerHostStub : public ::media::DemuxerHost {
private:
// DataSourceHost methods
- void SetTotalBytes(int64 total_bytes) override {
- }
- void AddBufferedByteRange(int64 start, int64 end) override {
- }
+ void SetTotalBytes(int64 total_bytes) override {}
+ void AddBufferedByteRange(int64 start, int64 end) override {}
void AddBufferedTimeRange(base::TimeDelta start,
- base::TimeDelta end) override {
- }
+ base::TimeDelta end) override {}
// DemuxerHost methods
- void SetDuration(base::TimeDelta duration) override {
- }
- void OnDemuxerError(::media::PipelineStatus error) override {
- }
+ void SetDuration(base::TimeDelta duration) override {}
+ void OnDemuxerError(::media::PipelineStatus error) override {}
};
DemuxerHelper::DemuxerHelper(
diff --git a/cobalt/media/sandbox/demuxer_helper.h b/cobalt/media/sandbox/demuxer_helper.h
index 69e100b..83051d8 100644
--- a/cobalt/media/sandbox/demuxer_helper.h
+++ b/cobalt/media/sandbox/demuxer_helper.h
@@ -19,7 +19,7 @@
#include "base/memory/ref_counted.h"
#include "base/message_loop/message_loop.h"
#include "cobalt/loader/fetcher_factory.h"
-#include "media/base/demuxer.h"
+#include "cobalt/media/base/demuxer.h"
#include "url/gurl.h"
namespace cobalt {
diff --git a/cobalt/media/sandbox/in_memory_data_source.h b/cobalt/media/sandbox/in_memory_data_source.h
index fe39f57..ba22db7 100644
--- a/cobalt/media/sandbox/in_memory_data_source.h
+++ b/cobalt/media/sandbox/in_memory_data_source.h
@@ -18,7 +18,7 @@
#include <vector>
#include "base/compiler_specific.h"
-#include "media/base/data_source.h"
+#include "cobalt/media/base/data_source.h"
namespace cobalt {
namespace media {
diff --git a/cobalt/media/sandbox/raw_video_decoder_fuzzer.cc b/cobalt/media/sandbox/raw_video_decoder_fuzzer.cc
index 1030a81..5a45130 100644
--- a/cobalt/media/sandbox/raw_video_decoder_fuzzer.cc
+++ b/cobalt/media/sandbox/raw_video_decoder_fuzzer.cc
@@ -21,11 +21,11 @@
#include "base/memory/ref_counted.h"
#include "base/time/time.h"
#include "cobalt/base/wrap_main.h"
+#include "cobalt/media/base/bind_to_loop.h"
#include "cobalt/media/sandbox/fuzzer_app.h"
#include "cobalt/media/sandbox/media_sandbox.h"
#include "cobalt/media/sandbox/media_source_demuxer.h"
#include "cobalt/media/sandbox/zzuf_fuzzer.h"
-#include "media/base/bind_to_loop.h"
#include "starboard/memory.h"
namespace cobalt {
@@ -66,8 +66,8 @@
current_au_buffer_ =
::media::ShellBufferFactory::Instance()->AllocateBufferNow(
desc.size, desc.is_keyframe);
- memcpy(current_au_buffer_->GetWritableData(),
- &au_data_[0] + desc.offset, desc.size);
+ memcpy(current_au_buffer_->GetWritableData(), &au_data_[0] + desc.offset,
+ desc.size);
++au_index_;
} else if (!current_au_buffer_->IsEndOfStream()) {
current_au_buffer_ =
diff --git a/cobalt/media_capture/BUILD.gn b/cobalt/media_capture/BUILD.gn
index f20b541..5334dbc 100644
--- a/cobalt/media_capture/BUILD.gn
+++ b/cobalt/media_capture/BUILD.gn
@@ -52,3 +52,26 @@
"//starboard",
]
}
+
+target(gtest_target_type, "media_capture_test") {
+ testonly = true
+ has_pedantic_warnings = true
+
+ sources = [
+ "get_user_media_test.cc",
+ "media_recorder_test.cc",
+ ]
+
+ deps = [
+ ":media_capture",
+ "//cobalt/dom",
+ "//cobalt/dom:dom_exception",
+ "//cobalt/dom/testing:dom_testing",
+ "//cobalt/media_stream",
+ "//cobalt/media_stream:media_stream_test_headers",
+ "//cobalt/script",
+ "//cobalt/test:run_all_unittests",
+ "//testing/gmock",
+ "//testing/gtest",
+ ]
+}
diff --git a/cobalt/media_integration_tests/_env.py b/cobalt/media_integration_tests/_env.py
index a9d83bf..a289cbb 100644
--- a/cobalt/media_integration_tests/_env.py
+++ b/cobalt/media_integration_tests/_env.py
@@ -15,12 +15,12 @@
#
"""Ask the parent directory to load the project environment."""
-from imp import load_source
+from imp import load_source # pylint: disable=deprecated-module
from os import path
import sys
_ENV = path.abspath(path.join(path.dirname(__file__), path.pardir, '_env.py'))
if not path.exists(_ENV):
- print '%s: Can\'t find repo root.\nMissing parent: %s' % (__file__, _ENV)
+ print('%s: Can\'t find repo root.\nMissing parent: %s' % (__file__, _ENV))
sys.exit(1)
load_source('', _ENV)
diff --git a/cobalt/media_session/BUILD.gn b/cobalt/media_session/BUILD.gn
index 56f00c9..2ef1cea 100644
--- a/cobalt/media_session/BUILD.gn
+++ b/cobalt/media_session/BUILD.gn
@@ -39,3 +39,19 @@
"//starboard:starboard_headers_only",
]
}
+
+target(gtest_target_type, "media_session_test") {
+ testonly = true
+
+ sources = [ "media_session_test.cc" ]
+
+ deps = [
+ ":media_session",
+ "//cobalt/base",
+ "//cobalt/browser",
+ "//cobalt/script",
+ "//cobalt/test:run_all_unittests",
+ "//testing/gmock",
+ "//testing/gtest",
+ ]
+}
diff --git a/cobalt/media_stream/BUILD.gn b/cobalt/media_stream/BUILD.gn
index 934eec4..b10e1fc 100644
--- a/cobalt/media_stream/BUILD.gn
+++ b/cobalt/media_stream/BUILD.gn
@@ -50,3 +50,44 @@
"//starboard",
]
}
+
+target(gtest_target_type, "media_stream_test") {
+ testonly = true
+ has_pedantic_warnings = true
+
+ sources = [
+ "audio_parameters_test.cc",
+ "media_stream_audio_source_test.cc",
+ "media_stream_audio_track_test.cc",
+ "media_stream_test.cc",
+ ]
+
+ deps = [
+ ":media_stream",
+ ":media_stream_test_headers",
+ "//cobalt/dom/testing:dom_testing",
+ "//cobalt/media",
+ "//cobalt/script",
+ "//cobalt/test:run_all_unittests",
+ "//testing/gmock",
+ "//testing/gtest",
+ ]
+
+ deps += cobalt_platform_dependencies
+}
+
+source_set("media_stream_test_headers") {
+ testonly = true
+
+ sources = [
+ "testing/mock_media_stream_audio_sink.h",
+ "testing/mock_media_stream_audio_source.h",
+ "testing/mock_media_stream_audio_track.h",
+ ]
+
+ deps = [
+ ":media_stream",
+ "//cobalt/script",
+ "//testing/gmock",
+ ]
+}
diff --git a/cobalt/network/BUILD.gn b/cobalt/network/BUILD.gn
index 44bccfc..4658ad4 100644
--- a/cobalt/network/BUILD.gn
+++ b/cobalt/network/BUILD.gn
@@ -81,6 +81,7 @@
}
copy("copy_ssl_certificates") {
+ install_content = true
sources = [
"//cobalt/content/ssl/certs/002c0b4f.0",
"//cobalt/content/ssl/certs/02265526.0",
@@ -119,7 +120,6 @@
"//cobalt/content/ssl/certs/406c9bb1.0",
"//cobalt/content/ssl/certs/4304c5e5.0",
"//cobalt/content/ssl/certs/48bec511.0",
- "//cobalt/content/ssl/certs/4a6481c9.0",
"//cobalt/content/ssl/certs/4b718d9b.0",
"//cobalt/content/ssl/certs/4bfab552.0",
"//cobalt/content/ssl/certs/4f316efb.0",
@@ -143,7 +143,6 @@
"//cobalt/content/ssl/certs/706f604c.0",
"//cobalt/content/ssl/certs/749e9e03.0",
"//cobalt/content/ssl/certs/75d1b2ed.0",
- "//cobalt/content/ssl/certs/76cb8f92.0",
"//cobalt/content/ssl/certs/76faf6c0.0",
"//cobalt/content/ssl/certs/7719f463.0",
"//cobalt/content/ssl/certs/773e07ad.0",
@@ -213,12 +212,11 @@
"//cobalt/content/ssl/certs/feffd413.0",
"//cobalt/content/ssl/certs/ff34af3f.0",
]
-
outputs =
[ "$sb_static_contents_output_data_dir/ssl/certs/{{source_file_part}}" ]
}
-target(gtest_target_type, "network_base_test") {
+target(gtest_target_type, "network_test") {
testonly = true
has_pedantic_warnings = true
diff --git a/cobalt/network/persistent_cookie_store_test.cc b/cobalt/network/persistent_cookie_store_test.cc
index 7f3325e..a05cde9 100644
--- a/cobalt/network/persistent_cookie_store_test.cc
+++ b/cobalt/network/persistent_cookie_store_test.cc
@@ -15,6 +15,7 @@
#include "cobalt/network/persistent_cookie_store.h"
#include <memory>
+#include <utility>
#include <vector>
#include "base/files/file_path.h"
diff --git a/cobalt/overlay_info/BUILD.gn b/cobalt/overlay_info/BUILD.gn
index d2209fe..b445d56 100644
--- a/cobalt/overlay_info/BUILD.gn
+++ b/cobalt/overlay_info/BUILD.gn
@@ -44,4 +44,5 @@
"//cobalt/test:run_all_unittests",
"//testing/gtest",
]
+ content_deps = [ "//third_party/icu:icudata" ]
}
diff --git a/cobalt/render_tree/image.h b/cobalt/render_tree/image.h
index 357052f..668f4f0 100644
--- a/cobalt/render_tree/image.h
+++ b/cobalt/render_tree/image.h
@@ -137,6 +137,9 @@
// A YUV image where each channel, Y, U and V, is stored as a separate
// single-channel 10bit unnormalized image plane.
kMultiPlaneImageFormatYUV3Plane10BitBT2020,
+ // A compacted YUV image where Y, U and V, are stored in 32-bit and each
+ // 32-bit represents 3 10-bit pixels with 2 bits of gap.
+ kMultiPlaneImageFormatYUV3Plane10BitCompactedBT2020,
};
// Like the ImageDataDescriptor object, a MultiPlaneImageDataDescriptor
diff --git a/cobalt/renderer/BUILD.gn b/cobalt/renderer/BUILD.gn
index c834841..8cf5f18 100644
--- a/cobalt/renderer/BUILD.gn
+++ b/cobalt/renderer/BUILD.gn
@@ -12,6 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import("//cobalt/renderer/rasterizer/skia/skia/skia_next.gni")
+
source_set("renderer_headers_only") {
sources = [
"egl_and_gles.h",
@@ -84,6 +86,10 @@
configs -= [ "//starboard/build/config:size" ]
configs += [ "//starboard/build/config:speed" ]
+ if (use_skia_next) {
+ include_dirs = [ skia_include_dir ]
+ }
+
deps = [
":renderer",
"//cobalt/base",
@@ -97,6 +103,449 @@
]
}
+target(gtest_target_type, "renderer_test") {
+ testonly = true
+
+ sources = [
+ "animations_test.cc",
+ "pipeline_test.cc",
+ "rasterizer/lottie_coverage_pixel_test.cc",
+ "rasterizer/pixel_test.cc",
+ "rasterizer/pixel_test_fixture.cc",
+ "rasterizer/pixel_test_fixture.h",
+ "rasterizer/stress_test.cc",
+ "resource_provider_test.cc",
+ "smoothed_value_test.cc",
+ "submission_queue_test.cc",
+ ]
+
+ configs -= [ "//starboard/build/config:size" ]
+ configs += [ "//starboard/build/config:speed" ]
+
+ deps = [
+ ":render_tree_pixel_tester",
+ ":renderer",
+ ":renderer_copy_lottie_test_data",
+ ":renderer_copy_test_data",
+ ":renderer_download_lottie_test_data",
+ ":renderer_headers_only",
+ "//base:i18n",
+ "//cobalt/base",
+ "//cobalt/loader",
+ "//cobalt/math",
+ "//cobalt/render_tree",
+ "//cobalt/render_tree:animations",
+ "//cobalt/renderer/backend:renderer_backend",
+ "//cobalt/test:run_all_unittests",
+ "//testing/gmock",
+ "//testing/gtest",
+ "//third_party/icu:icuuc",
+ ]
+}
+
+copy("renderer_copy_test_data") {
+ sources = [
+ "rasterizer/testdata/2xSpeedLottieAnimationTest-expected.png",
+ "rasterizer/testdata/AlmostCircleViaRoundedCorners-expected.png",
+ "rasterizer/testdata/Area1Image-expected.png",
+ "rasterizer/testdata/Area1Opacity-expected.png",
+ "rasterizer/testdata/BeginningOfPlayingLottieAnimationTest-expected.png",
+ "rasterizer/testdata/BlueFillRectOnEntireSurface-expected.png",
+ "rasterizer/testdata/BlueFillRectOnTopLeftQuarterOfSurface-expected.png",
+ "rasterizer/testdata/BlueRoundedCornersRectangularSubPixelBorder-expected.png",
+ "rasterizer/testdata/BounceModeLottieAnimationTest-expected.png",
+ "rasterizer/testdata/BoxShadowBigCircleWithInset25pxSpread50pxBlurAndRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowBigEllipseWithInset25pxSpread50pxBlurAndRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowBlur100pxCentered-expected.png",
+ "rasterizer/testdata/BoxShadowBlur1PxCentered-expected.png",
+ "rasterizer/testdata/BoxShadowBlur2PxCentered-expected.png",
+ "rasterizer/testdata/BoxShadowBlur3PxCentered-expected.png",
+ "rasterizer/testdata/BoxShadowBlur4PxCentered-expected.png",
+ "rasterizer/testdata/BoxShadowBlur5PxCentered-expected.png",
+ "rasterizer/testdata/BoxShadowBlur6PxCentered-expected.png",
+ "rasterizer/testdata/BoxShadowBlur8PxCentered-expected.png",
+ "rasterizer/testdata/BoxShadowBlurBottomLeft-expected.png",
+ "rasterizer/testdata/BoxShadowBlurBottomRight-expected.png",
+ "rasterizer/testdata/BoxShadowBlurCentered-expected.png",
+ "rasterizer/testdata/BoxShadowBlurCenteredOffscreenBottomRight-expected.png",
+ "rasterizer/testdata/BoxShadowBlurCenteredOffscreenTopLeft-expected.png",
+ "rasterizer/testdata/BoxShadowBlurTopLeft-expected.png",
+ "rasterizer/testdata/BoxShadowBlurTopRight-expected.png",
+ "rasterizer/testdata/BoxShadowCircleBottomRight-expected.png",
+ "rasterizer/testdata/BoxShadowCircleSpread-expected.png",
+ "rasterizer/testdata/BoxShadowCircleWithInset25pxSpread1pxBlurAndRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowCircleWithInset25pxSpread1pxBlurRoundedCornersAndNoOffset-expected.png",
+ "rasterizer/testdata/BoxShadowCircleWithInset25pxSpread50pxBlurAndRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowCircleWithInset25pxSpread8pxBlurAndRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowCircleWithInset25pxSpreadAndRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowCircleWithOutset25pxSpread1pxBlurAndRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowCircleWithOutset25pxSpread50pxBlurAndRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowCircleWithOutset25pxSpread8pxBlurAndRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowCircleWithOutset25pxSpreadAndRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowEllipseWithInset25pxSpread1pxBlurAndRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowEllipseWithInset25pxSpread50pxBlurAndRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowEllipseWithInset25pxSpread8pxBlurAndRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowEllipseWithInset25pxSpreadAndRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowEllipseWithOutset25pxSpread1pxBlurAndRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowEllipseWithOutset25pxSpread50pxBlurAndRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowEllipseWithOutset25pxSpread8pxBlurAndRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowEllipseWithOutset25pxSpreadAndRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowInsetCircleBottomRight-expected.png",
+ "rasterizer/testdata/BoxShadowInsetCircleSpread-expected.png",
+ "rasterizer/testdata/BoxShadowInsetUnderTransparentCircleBottomRightBlueBackground-expected.png",
+ "rasterizer/testdata/BoxShadowUnderTransparentCircleBottomRightBlueBackground-expected.png",
+ "rasterizer/testdata/BoxShadowWithInset-expected.png",
+ "rasterizer/testdata/BoxShadowWithInset25pxSpread1pxBlurAndIsometricRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowWithInset25pxSpread1pxBlurAndRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowWithInset25pxSpread50pxBlurAndIsometricRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowWithInset25pxSpread50pxBlurAndRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowWithInset25pxSpread8pxBlurAndIsometricRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowWithInset25pxSpread8pxBlurAndRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowWithInset25pxSpreadAndBlur-expected.png",
+ "rasterizer/testdata/BoxShadowWithInset25pxSpreadAndIsometricRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowWithInset25pxSpreadAndRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowWithInset25pxSpreadAndSameRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowWithInset5pxSpread25pxBlurAndDifferentRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowWithInset5pxSpread25pxBlurAndSameRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowWithInset5pxSpread5pxBlurAndSameRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowWithInset5pxSpreadAndDifferentRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowWithInset5pxSpreadAndSameRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowWithInsetNeg10pxSpreadAnd10pxBlurAndRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowWithInsetNeg10pxSpreadAnd2pxBlurAndRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowWithOutset25pxSpread1pxBlurAndIsometricRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowWithOutset25pxSpread1pxBlurAndRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowWithOutset25pxSpread50pxBlurAndIsometricRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowWithOutset25pxSpread50pxBlurAndRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowWithOutset25pxSpread8pxBlurAndIsometricRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowWithOutset25pxSpread8pxBlurAndRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowWithOutset25pxSpreadAndIsometricRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowWithOutset25pxSpreadAndRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowWithOutset25pxSpreadAndSameRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowWithOutset5pxSpread25pxBlurAndDifferentRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowWithOutset5pxSpread25pxBlurAndSameRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowWithOutset5pxSpread5pxBlurAndSameRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowWithOutset5pxSpreadAndDifferentRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowWithOutset5pxSpreadAndSameRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowWithOutsetNeg10pxSpreadAnd10pxBlurAndRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowWithOutsetNeg10pxSpreadAnd2pxBlurAndRoundedCorners-expected.png",
+ "rasterizer/testdata/BoxShadowWithSpread-expected.png",
+ "rasterizer/testdata/ChildrenOfCompositionThatStartsOffscreenAppear-expected.png",
+ "rasterizer/testdata/CircleViaRoundedCorners-expected.png",
+ "rasterizer/testdata/CircularSubPixelBorder-expected.png",
+ "rasterizer/testdata/CircularThickBorder-expected.png",
+ "rasterizer/testdata/CircularViewportOverCascadeOfRects-expected.png",
+ "rasterizer/testdata/CircularViewportOverImage-expected.png",
+ "rasterizer/testdata/CircularViewportOverWrappingImage-expected.png",
+ "rasterizer/testdata/CircularViewportOverZoomedInImage-expected.png",
+ "rasterizer/testdata/ClearRectNodeTest-expected.png",
+ "rasterizer/testdata/ColoredDropShadowBlurredText-expected.png",
+ "rasterizer/testdata/ColoredDropShadowText-expected.png",
+ "rasterizer/testdata/CompositionOfCascadedRectsOfDifferentColors-expected.png",
+ "rasterizer/testdata/CompositionOfSingleSolidColorRectWithAnisoScale-expected.png",
+ "rasterizer/testdata/CompositionOfSingleSolidColorRectWithIsoScale-expected.png",
+ "rasterizer/testdata/CompositionOfSingleSolidColorRectWithNoTransform-expected.png",
+ "rasterizer/testdata/CompositionOfSingleSolidColorRectWithRotation-expected.png",
+ "rasterizer/testdata/CompositionOfSingleSolidColorRectWithTranslation-expected.png",
+ "rasterizer/testdata/CompositionOfSingleSolidColorRectWithTranslationRotationAndAnisoScale-expected.png",
+ "rasterizer/testdata/ConcurrentTrimPathsLottieAnimationTest-expected.png",
+ "rasterizer/testdata/DebugAnimatedWebPFrame-expected.png",
+ "rasterizer/testdata/DownwardPointingTriangle-expected.png",
+ "rasterizer/testdata/DrawNullImage-expected.png",
+ "rasterizer/testdata/DrawNullImageInRoundedFilter-expected.png",
+ "rasterizer/testdata/DrawOffscreenImage-expected.png",
+ "rasterizer/testdata/DrawOffscreenYUVImage-expected.png",
+ "rasterizer/testdata/DropShadowBlurred0Point1PxText-expected.png",
+ "rasterizer/testdata/DropShadowBlurred1Px8PxText-expected.png",
+ "rasterizer/testdata/DropShadowBlurred1PxText-expected.png",
+ "rasterizer/testdata/DropShadowBlurred20PxText-expected.png",
+ "rasterizer/testdata/DropShadowBlurred2PxText-expected.png",
+ "rasterizer/testdata/DropShadowBlurred3PxText-expected.png",
+ "rasterizer/testdata/DropShadowBlurred4PxText-expected.png",
+ "rasterizer/testdata/DropShadowBlurred5PxText-expected.png",
+ "rasterizer/testdata/DropShadowBlurred6PxText-expected.png",
+ "rasterizer/testdata/DropShadowBlurred8PxText-expected.png",
+ "rasterizer/testdata/DropShadowBlurredText-expected.png",
+ "rasterizer/testdata/DropShadowText-expected.png",
+ "rasterizer/testdata/EllipticalSubPixelBorder-expected.png",
+ "rasterizer/testdata/EllipticalThickBorder-expected.png",
+ "rasterizer/testdata/EllipticalViewportOverCascadeOfRects-expected.png",
+ "rasterizer/testdata/EllipticalViewportOverCascadeOfRectsWithOpacity-expected.png",
+ "rasterizer/testdata/EllipticalViewportOverCompositionOfImages-expected.png",
+ "rasterizer/testdata/EllipticalViewportOverImage-expected.png",
+ "rasterizer/testdata/EllipticalViewportOverWrappingImage-expected.png",
+ "rasterizer/testdata/EllipticalViewportOverZoomedInImage-expected.png",
+ "rasterizer/testdata/EmptyRectWith4DifferentRoundedCornersAndEdgeColorsAndEdgeWidthsBorder-expected.png",
+ "rasterizer/testdata/EmptyRectWith4DifferentRoundedCornersAndEdgeColorsBorder-expected.png",
+ "rasterizer/testdata/EmptyRectWith4DifferentRoundedCornersAndEdgeWidthsBorder-expected.png",
+ "rasterizer/testdata/EmptyRectWithRedBorderOnTopLeftOfSurface-expected.png",
+ "rasterizer/testdata/EmptyRectWithRoundedCornersAnd4DifferentEdgeColorsAndEdgeWidthsBorder-expected.png",
+ "rasterizer/testdata/EmptyRectWithRoundedCornersAnd4DifferentEdgeColorsBorder-expected.png",
+ "rasterizer/testdata/EmptyRectWithRoundedCornersAnd4DifferentEdgeWidthsBorder-expected.png",
+ "rasterizer/testdata/EndOfPlayingLottieAnimationTest-expected.png",
+ "rasterizer/testdata/FilterBlurred0Point1PxText-expected.png",
+ "rasterizer/testdata/FilterBlurred100PxText-expected.png",
+ "rasterizer/testdata/FilterBlurred1PxText-expected.png",
+ "rasterizer/testdata/FilterBlurred20PxText-expected.png",
+ "rasterizer/testdata/FilterBlurred2PxText-expected.png",
+ "rasterizer/testdata/FilterBlurred3PxText-expected.png",
+ "rasterizer/testdata/FilterBlurred4PxText-expected.png",
+ "rasterizer/testdata/FilterBlurred5PxText-expected.png",
+ "rasterizer/testdata/FilterBlurred6PxText-expected.png",
+ "rasterizer/testdata/FilterBlurred8PxText-expected.png",
+ "rasterizer/testdata/FractionallyPositionedViewportsRenderCircularImages-expected.png",
+ "rasterizer/testdata/FractionallyPositionedViewportsRenderOpacityCircle-expected.png",
+ "rasterizer/testdata/GreenFillRectOnEntireSurface-expected.png",
+ "rasterizer/testdata/GreenFillRectOnTopLeftQuarterOfSurface-expected.png",
+ "rasterizer/testdata/GreyBoxShadowBottomLeft-expected.png",
+ "rasterizer/testdata/GreyBoxShadowBottomRight-expected.png",
+ "rasterizer/testdata/GreyBoxShadowTopLeft-expected.png",
+ "rasterizer/testdata/GreyBoxShadowTopRight-expected.png",
+ "rasterizer/testdata/Height1Image-expected.png",
+ "rasterizer/testdata/Height1Opacity-expected.png",
+ "rasterizer/testdata/HorizontalEllipseGradient2Stops-expected.png",
+ "rasterizer/testdata/HorizontalEllipseGradient3Stops-expected.png",
+ "rasterizer/testdata/HorizontalEllipseGradient5Stops-expected.png",
+ "rasterizer/testdata/ImageEdgeNoWrap-expected.png",
+ "rasterizer/testdata/ImageEdgeNoWrapWithPixelCentersOffset-expected.png",
+ "rasterizer/testdata/ImageEdgeNoWrapWithPixelCentersOffsetAndRotatedTexture-expected.png",
+ "rasterizer/testdata/ImageNodeLocalTransformAndExternalTransform-expected.png",
+ "rasterizer/testdata/ImageNodeLocalTransformOfImageSmallerThanSurface-expected.png",
+ "rasterizer/testdata/ImageNodeLocalTransformRotationAndScale-expected.png",
+ "rasterizer/testdata/ImageNodeLocalTransformScaleAndTranslation-expected.png",
+ "rasterizer/testdata/ImageNodeLocalTransformTranslation-expected.png",
+ "rasterizer/testdata/ImageOfBlackTransparentGridOverlappingSolidRectPremultipliedAlpha-expected.png",
+ "rasterizer/testdata/ImageOfBlackTransparentGridOverlappingSolidRectUsingUnpremultipliedAlpha-expected.png",
+ "rasterizer/testdata/ImageOfWhiteTransparentGridOverlappingSolidRectPremultipliedAlpha-expected.png",
+ "rasterizer/testdata/ImageOfWhiteTransparentGridOverlappingSolidRectUnpremultipliedAlpha-expected.png",
+ "rasterizer/testdata/ImagesAreLinearlyInterpolated-expected.png",
+ "rasterizer/testdata/LargeEllipticalViewportOverImage-expected.png",
+ "rasterizer/testdata/LinearGradient2Stops315Degrees-expected.png",
+ "rasterizer/testdata/LinearGradient2Stops45Degrees-expected.png",
+ "rasterizer/testdata/LinearGradient2StopsLeftRight-expected.png",
+ "rasterizer/testdata/LinearGradient2StopsTopBottom-expected.png",
+ "rasterizer/testdata/LinearGradient3Stops210DegreesInset-expected.png",
+ "rasterizer/testdata/LinearGradient3Stops30DegreesInset-expected.png",
+ "rasterizer/testdata/LinearGradient3StopsLeftRightInset-expected.png",
+ "rasterizer/testdata/LinearGradient3StopsTopBottomInset-expected.png",
+ "rasterizer/testdata/LinearGradient5Stops150DegreesOutset-expected.png",
+ "rasterizer/testdata/LinearGradient5Stops330DegreesOutset-expected.png",
+ "rasterizer/testdata/LinearGradient5StopsLeftRightOutset-expected.png",
+ "rasterizer/testdata/LinearGradient5StopsTopBottomOutset-expected.png",
+ "rasterizer/testdata/LinearGradientWithTransparencyOnWhiteBackground-expected.png",
+ "rasterizer/testdata/LoopingLottieAnimationTest-expected.png",
+ "rasterizer/testdata/LottiePreserveAspectRatioTooNarrowAnimationTest-expected.png",
+ "rasterizer/testdata/LottiePreserveAspectRatioTooShortAnimationTest-expected.png",
+ "rasterizer/testdata/LottieScaledWideAnimationTest-expected.png",
+ "rasterizer/testdata/MapToMeshI420Test-expected.png",
+ "rasterizer/testdata/MapToMeshNV12Test-expected.png",
+ "rasterizer/testdata/MapToMeshRGBTest-expected.png",
+ "rasterizer/testdata/MapToMeshUYVYTest-expected.png",
+ "rasterizer/testdata/MiddleOfPlayingLottieAnimationTest-expected.png",
+ "rasterizer/testdata/MultipleColoredDropShadowBlurredText-expected.png",
+ "rasterizer/testdata/MultipleColoredDropShadowText-expected.png",
+ "rasterizer/testdata/NotLoopingLottieAnimationTest-expected.png",
+ "rasterizer/testdata/OpacityFilterOnCompositionOfThreeRectsTest-expected.png",
+ "rasterizer/testdata/OpacityFilterOnImageNodeTest-expected.png",
+ "rasterizer/testdata/OpacityFilterOnRectNodeTest-expected.png",
+ "rasterizer/testdata/OpacityFilterOnRotatedRectNodeTest-expected.png",
+ "rasterizer/testdata/OpacityFilterOnVeryLargeRectNodeTest-expected.png",
+ "rasterizer/testdata/OpacityFilterWithinOpacityFilter-expected.png",
+ "rasterizer/testdata/OpacityOnRectAndEllipseMaskedImage-expected.png",
+ "rasterizer/testdata/OvalViaRoundedCorners-expected.png",
+ "rasterizer/testdata/OverLoopLimitCountLottieAnimationTest-expected.png",
+ "rasterizer/testdata/PausedLottieAnimationTest-expected.png",
+ "rasterizer/testdata/PunchThroughVideoNodePunchesThroughSetBoundsCBReturnsFalse-expected.png",
+ "rasterizer/testdata/PunchThroughVideoNodePunchesThroughSetBoundsCBReturnsTrue-expected.png",
+ "rasterizer/testdata/RadialGradient2Stops-expected.png",
+ "rasterizer/testdata/RadialGradient3Stops-expected.png",
+ "rasterizer/testdata/RadialGradient5Stops-expected.png",
+ "rasterizer/testdata/RadialGradientWithTransparencyOnWhiteBackground-expected.png",
+ "rasterizer/testdata/RectDrawOrder-expected.png",
+ "rasterizer/testdata/RectNodeContainsBorderWithRotation-expected.png",
+ "rasterizer/testdata/RectNodeContainsBorderWithScale-expected.png",
+ "rasterizer/testdata/RectNodeContainsBorderWithTranslation-expected.png",
+ "rasterizer/testdata/RectNodeContainsBorderWithTranslationRotationAndScale-expected.png",
+ "rasterizer/testdata/RectNodeContainsSkinnyBorderWithTranslation-expected.png",
+ "rasterizer/testdata/RectWithRoundedCornersOnSolidColor-expected.png",
+ "rasterizer/testdata/RedFillRectOnEntireSurface-expected.png",
+ "rasterizer/testdata/RedFillRectOnTopLeftQuarterOfSurface-expected.png",
+ "rasterizer/testdata/RedRectWith2DifferentRadiusForEachCornerOnTopLeftOfSurface-expected.png",
+ "rasterizer/testdata/RedRectWith4DifferentBlueBordersOnTopLeftOfSurface-expected.png",
+ "rasterizer/testdata/RedRectWith4DifferentColorAndWidthBorders-expected.png",
+ "rasterizer/testdata/RedRectWith4DifferentColorBorders-expected.png",
+ "rasterizer/testdata/RedRectWithBlueBorderOnTopLeftOfSurface-expected.png",
+ "rasterizer/testdata/RedRectWithDifferentRoundedCornersOnTopLeftOfSurface-expected.png",
+ "rasterizer/testdata/RedTextIn500PtFont-expected.png",
+ "rasterizer/testdata/RedTextOnBlueIn40PtFont-expected.png",
+ "rasterizer/testdata/ReverseDirectionLottieAnimationTest-expected.png",
+ "rasterizer/testdata/RotatedOvalViaRoundedCorners-expected.png",
+ "rasterizer/testdata/RotatedRoundedCornersViewportOverImage-expected.png",
+ "rasterizer/testdata/RotatedTextInScaledRoundedCorners-expected.png",
+ "rasterizer/testdata/RotatedThenScaledRectWithDifferentRoundedCorners-expected.png",
+ "rasterizer/testdata/RoundedCornersDifferentCornersDifferentThicknessSolidBrush-expected.png",
+ "rasterizer/testdata/RoundedCornersDifferentViewportOverCascadedRects-expected.png",
+ "rasterizer/testdata/RoundedCornersDifferentViewportOverImage-expected.png",
+ "rasterizer/testdata/RoundedCornersEachDifferentThickBorder-expected.png",
+ "rasterizer/testdata/RoundedCornersEachDifferentThickBorderSolidBrush-expected.png",
+ "rasterizer/testdata/RoundedCornersRectangularSubPixelBorder-expected.png",
+ "rasterizer/testdata/RoundedCornersSubPixelBorder-expected.png",
+ "rasterizer/testdata/RoundedCornersThickBlueBorder-expected.png",
+ "rasterizer/testdata/RoundedCornersThickBorder-expected.png",
+ "rasterizer/testdata/RoundedCornersViewportOverCascadeOfImages-expected.png",
+ "rasterizer/testdata/RoundedCornersViewportOverCascadedRects-expected.png",
+ "rasterizer/testdata/RoundedCornersViewportOverImage-expected.png",
+ "rasterizer/testdata/RoundedCornersViewportOverTranslatedImage-expected.png",
+ "rasterizer/testdata/RoundedCornersViewportOverWrappingImage-expected.png",
+ "rasterizer/testdata/RoundedCornersViewportOverZoomedInImage-expected.png",
+ "rasterizer/testdata/ScaledBoxShadowEllipseWithOutset25pxSpread3pxBlurAndRoundedCorners-expected.png",
+ "rasterizer/testdata/ScaledBoxShadowEllipseWithOutset5pxSpreadAndRoundedCorners-expected.png",
+ "rasterizer/testdata/ScaledBoxShadowWithSpreadAndBlurCentered-expected.png",
+ "rasterizer/testdata/ScaledSingleRGBAImageWithAlphaFormatOpaqueAndRoundedCorners-expected.png",
+ "rasterizer/testdata/ScaledThenRotatedRectWithDifferentRoundedCorners-expected.png",
+ "rasterizer/testdata/ScaledThenRotatedRoundedCornersViewportOverImage-expected.png",
+ "rasterizer/testdata/ScalingUpAnOpacityFilterTextDoesNotPixellate-expected.png",
+ "rasterizer/testdata/SeekFrameLottieAnimationTest-expected.png",
+ "rasterizer/testdata/SeekPercentStringLottieAnimationTest-expected.png",
+ "rasterizer/testdata/ShearedText-expected.png",
+ "rasterizer/testdata/SimpleText40PtFontWithCharacterLowerThanBaseline-expected.png",
+ "rasterizer/testdata/SimpleTextIn20PtFont-expected.png",
+ "rasterizer/testdata/SimpleTextIn40PtFont-expected.png",
+ "rasterizer/testdata/SimpleTextIn500PtFont-expected.png",
+ "rasterizer/testdata/SimpleTextIn80PtFont-expected.png",
+ "rasterizer/testdata/SimpleTextInRed40PtChineseFont-expected.png",
+ "rasterizer/testdata/SimpleTextInRed40PtFont-expected.png",
+ "rasterizer/testdata/SimpleTextInRed40PtThaiFont-expected.png",
+ "rasterizer/testdata/SingleRGBAImageLargerThanRenderTarget-expected.png",
+ "rasterizer/testdata/SingleRGBAImageWithAlphaFormatOpaque-expected.png",
+ "rasterizer/testdata/SingleRGBAImageWithAlphaFormatOpaqueAndRoundedCorners-expected.png",
+ "rasterizer/testdata/SingleRGBAImageWithAlphaFormatOpaqueAndRoundedCornersOnSolidColor-expected.png",
+ "rasterizer/testdata/SingleRGBAImageWithEnlargedDestRect-expected.png",
+ "rasterizer/testdata/SingleRGBAImageWithReflection-expected.png",
+ "rasterizer/testdata/SingleRGBAImageWithSameSizeAsRenderTarget-expected.png",
+ "rasterizer/testdata/SingleRGBAImageWithShrunkenDestRect-expected.png",
+ "rasterizer/testdata/SquishedEllipticalThickBorder-expected.png",
+ "rasterizer/testdata/StoppedLottieAnimationTest-expected.png",
+ "rasterizer/testdata/StretchedRoundedCornersViewportOverCascadedRects-expected.png",
+ "rasterizer/testdata/TextNodesScaleDownSmoothly-expected.png",
+ "rasterizer/testdata/ThreePlaneYUVImageSupport-expected.png",
+ "rasterizer/testdata/ThreePlaneYUVImageWithDestSizeDifferentFromImage-expected.png",
+ "rasterizer/testdata/ThreePlaneYUVImageWithReflection-expected.png",
+ "rasterizer/testdata/ThreePlaneYUVImageWithTransform-expected.png",
+ "rasterizer/testdata/ToggleLoopingOffLottieAnimationTest-expected.png",
+ "rasterizer/testdata/ToggleLoopingOnLottieAnimationTest-expected.png",
+ "rasterizer/testdata/TogglePlayFromPausedLottieAnimationTest-expected.png",
+ "rasterizer/testdata/TogglePlayFromPlayingLottieAnimationTest-expected.png",
+ "rasterizer/testdata/TogglePlayFromStoppedLottieAnimationTest-expected.png",
+ "rasterizer/testdata/TooManyGlyphs-expected.png",
+ "rasterizer/testdata/TranslatedRightCircularViewportOverImage-expected.png",
+ "rasterizer/testdata/TransparencyLottieAnimationTest-expected.png",
+ "rasterizer/testdata/TransparentBlackTextOnRedIn40PtFont-expected.png",
+ "rasterizer/testdata/TransparentBoxShadowBlurOnGreenBackgroundCentered-expected.png",
+ "rasterizer/testdata/TransparentBoxShadowOnGreenBackgroundBottomRight-expected.png",
+ "rasterizer/testdata/TransparentRectOverlappingSolidRect-expected.png",
+ "rasterizer/testdata/TwoPlaneYUVImageSupport-expected.png",
+ "rasterizer/testdata/TwoPlaneYUVImageWithDestSizeDifferentFromImage-expected.png",
+ "rasterizer/testdata/TwoPlaneYUVImageWithTransform-expected.png",
+ "rasterizer/testdata/UnderLoopLimitCountLottieAnimationTest-expected.png",
+ "rasterizer/testdata/VerticalEllipseGradient2Stops-expected.png",
+ "rasterizer/testdata/VerticalEllipseGradient3Stops-expected.png",
+ "rasterizer/testdata/VerticalEllipseGradient5Stops-expected.png",
+ "rasterizer/testdata/VeryLargeOpacityFilterDoesNotOccupyVeryMuchMemory-expected.png",
+ "rasterizer/testdata/ViewportFilterAndOpacityFilterOnTextNodeTest-expected.png",
+ "rasterizer/testdata/ViewportFilterOnCompositionOfThreeRectsTest-expected.png",
+ "rasterizer/testdata/ViewportFilterOnRotatedRectNodeTest-expected.png",
+ "rasterizer/testdata/ViewportFilterOnTextNodeTest-expected.png",
+ "rasterizer/testdata/ViewportFilterWithTransformOnTextNodeTest-expected.png",
+ "rasterizer/testdata/ViewportFilterWithTranslateAndScaleOnRectNodeTest-expected.png",
+ "rasterizer/testdata/WhiteTextOnBlackIn40PtFont-expected.png",
+ "rasterizer/testdata/Width1Image-expected.png",
+ "rasterizer/testdata/Width1Opacity-expected.png",
+ "rasterizer/testdata/YUV2PlaneImagesAreLinearlyInterpolated-expected.png",
+ "rasterizer/testdata/YUV3PlaneImagesAreLinearlyInterpolated-expected.png",
+ "rasterizer/testdata/YUV422UYVYImageScaledAndTranslated-expected.png",
+ "rasterizer/testdata/YUV422UYVYImageScaledUpSupport-expected.png",
+ "rasterizer/testdata/YUV422UYVYImageSupport-expected.png",
+ "rasterizer/testdata/ZoomedInImagesDoNotWrapInterpolated-expected.png",
+ "rasterizer/testdata/hunter_gone_too_deep.json",
+ "rasterizer/testdata/loading-spinner-opaque.webp",
+ "rasterizer/testdata/white_material_wave_loading.json",
+ "rasterizer/testdata/ytk_ink_logo_rotate.json",
+ ]
+ file_path = "{{source_root_relative_dir}}/{{source_file_part}}"
+ outputs = [ "$sb_static_contents_output_data_dir/test/$file_path" ]
+}
+
+_lottie_resource_path = "rasterizer/testdata/lottie_coverage"
+
+copy("renderer_copy_lottie_test_data") {
+ # TODO(b/211909342): List the individual files that are to be copied.
+ sources = [ _lottie_resource_path ]
+ deps = [ ":renderer_download_lottie_test_data" ]
+ file_path = "{{source_root_relative_dir}}/{{source_file_part}}"
+ outputs = [ "$sb_static_contents_output_data_dir/test/$file_path" ]
+}
+
+action("renderer_download_lottie_test_data") {
+ script = "//tools/download_from_gcs.py"
+
+ inputs = [
+ "$_lottie_resource_path/finger_print-expected.png.sha1",
+ "$_lottie_resource_path/finger_print.json.sha1",
+ "$_lottie_resource_path/gesture_go_back-expected.png.sha1",
+ "$_lottie_resource_path/gesture_go_back.json.sha1",
+ "$_lottie_resource_path/gesture_go_home-expected.png.sha1",
+ "$_lottie_resource_path/gesture_go_home.json.sha1",
+ "$_lottie_resource_path/heart_preloader-expected.png.sha1",
+ "$_lottie_resource_path/heart_preloader.json.sha1",
+ "$_lottie_resource_path/ripple_loading_animation-expected.png.sha1",
+ "$_lottie_resource_path/ripple_loading_animation.json.sha1",
+ "$_lottie_resource_path/skottie-3d-2planes-expected.png.sha1",
+ "$_lottie_resource_path/skottie-3d-2planes.json.sha1",
+ "$_lottie_resource_path/skottie-effects-tranform-expected.png.sha1",
+ "$_lottie_resource_path/skottie-effects-tranform.json.sha1",
+ "$_lottie_resource_path/skottie-fill-effect-expected.png.sha1",
+ "$_lottie_resource_path/skottie-fill-effect.json.sha1",
+ "$_lottie_resource_path/skottie-gradient-opacity-expected.png.sha1",
+ "$_lottie_resource_path/skottie-gradient-opacity.json.sha1",
+ "$_lottie_resource_path/skottie-gradient-ramp-expected.png.sha1",
+ "$_lottie_resource_path/skottie-gradient-ramp.json.sha1",
+ "$_lottie_resource_path/skottie-linear-wipe-effect-expected.png.sha1",
+ "$_lottie_resource_path/skottie-linear-wipe-effect.json.sha1",
+ "$_lottie_resource_path/skottie-luma-matte-expected.png.sha1",
+ "$_lottie_resource_path/skottie-luma-matte.json.sha1",
+ "$_lottie_resource_path/skottie-mask-feather-expected.png.sha1",
+ "$_lottie_resource_path/skottie-mask-feather.json.sha1",
+ "$_lottie_resource_path/skottie-matte-blendmode-expected.png.sha1",
+ "$_lottie_resource_path/skottie-matte-blendmode.json.sha1",
+ "$_lottie_resource_path/skottie-motiontile-effect-phase-expected.png.sha1",
+ "$_lottie_resource_path/skottie-motiontile-effect-phase.json.sha1",
+ "$_lottie_resource_path/skottie-shift-channels-effect-expected.png.sha1",
+ "$_lottie_resource_path/skottie-shift-channels-effect.json.sha1",
+ "$_lottie_resource_path/skottie-tritone-effect-expected.png.sha1",
+ "$_lottie_resource_path/skottie-tritone-effect.json.sha1",
+ "$_lottie_resource_path/skottie-venetianblinds-effect-expected.png.sha1",
+ "$_lottie_resource_path/skottie-venetianblinds-effect.json.sha1",
+ "$_lottie_resource_path/white_material_wave_loading-expected.png.sha1",
+ "$_lottie_resource_path/white_material_wave_loading.json.sha1",
+ ]
+
+ # TODO(b/211909342): This script downloads files to the source tree. GN only
+ # allows outputs under the output folder so they can't be listed here.
+ # For now, use a placeholder file as GN requires an action to have outputs.
+ outputs = [ "$target_out_dir/lottie_download.stamp" ]
+ sha_dir = rebase_path(_lottie_resource_path, root_build_dir)
+ args = [
+ "--bucket",
+ "lottie-coverage-testdata",
+ "--sha1",
+ sha_dir,
+ "--output",
+ sha_dir,
+ "--stamp_file",
+ rebase_path(outputs[0], root_build_dir),
+ ]
+}
+
static_library("default_options") {
sources = [ "//cobalt/renderer/get_default_rasterizer_for_platform.cc" ]
diff --git a/cobalt/renderer/backend/BUILD.gn b/cobalt/renderer/backend/BUILD.gn
index b591a41..92f235b 100644
--- a/cobalt/renderer/backend/BUILD.gn
+++ b/cobalt/renderer/backend/BUILD.gn
@@ -30,14 +30,13 @@
target(gtest_target_type, "graphics_system_test") {
testonly = true
sources = [ "graphics_system_test.cc" ]
-
deps = [
":renderer_backend",
- "//base/test:run_all_unittests",
- "//base/test:test_support",
"//cobalt/base",
"//cobalt/system_window",
+ "//cobalt/test:run_all_unittests",
"//testing/gmock",
"//testing/gtest",
]
+ content_deps = [ "//third_party/icu:icudata" ]
}
diff --git a/cobalt/renderer/backend/egl/graphics_context.cc b/cobalt/renderer/backend/egl/graphics_context.cc
index ebc2d9d..480ac6d 100644
--- a/cobalt/renderer/backend/egl/graphics_context.cc
+++ b/cobalt/renderer/backend/egl/graphics_context.cc
@@ -17,6 +17,7 @@
#include <algorithm>
#include <memory>
+#include <utility>
#include "cobalt/renderer/backend/egl/graphics_context.h"
@@ -96,6 +97,10 @@
return base::polymorphic_downcast<GraphicsSystemEGL*>(system());
}
+const GraphicsSystemEGL* GraphicsContextEGL::system_egl() const {
+ return base::polymorphic_downcast<GraphicsSystemEGL*>(system());
+}
+
bool GraphicsContextEGL::ComputeReadPixelsNeedVerticalFlip() {
// This computation is expensive, so it is cached the first time that it is
// computed. Simply return the value if it is already cached.
@@ -204,10 +209,10 @@
GL_CALL(glCompileShader(blit_fragment_shader_));
GL_CALL(glAttachShader(blit_program_, blit_fragment_shader_));
- GL_CALL(glBindAttribLocation(
- blit_program_, kBlitPositionAttribute, "a_position"));
- GL_CALL(glBindAttribLocation(
- blit_program_, kBlitTexcoordAttribute, "a_tex_coord"));
+ GL_CALL(glBindAttribLocation(blit_program_, kBlitPositionAttribute,
+ "a_position"));
+ GL_CALL(glBindAttribLocation(blit_program_, kBlitTexcoordAttribute,
+ "a_tex_coord"));
GL_CALL(glLinkProgram(blit_program_));
@@ -286,8 +291,7 @@
}
void GraphicsContextEGL::MakeCurrentWithSurface(RenderTargetEGL* surface) {
- DCHECK_NE(EGL_NO_SURFACE, surface) <<
- "Use ReleaseCurrentContext().";
+ DCHECK_NE(EGL_NO_SURFACE, surface) << "Use ReleaseCurrentContext().";
// In some EGL implementations, like Angle, the first time we make current on
// a thread can result in global allocations being made that are never freed.
@@ -353,8 +357,8 @@
void GraphicsContextEGL::ReleaseCurrentContext() {
GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, 0));
- EGL_CALL(eglMakeCurrent(
- display_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
+ EGL_CALL(
+ eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
current_surface_ = NULL;
is_current_ = false;
@@ -380,8 +384,8 @@
scoped_refptr<RenderTarget> GraphicsContextEGL::CreateOffscreenRenderTarget(
const math::Size& dimensions) {
- scoped_refptr<RenderTarget> render_target(new PBufferRenderTargetEGL(
- display_, config_, dimensions));
+ scoped_refptr<RenderTarget> render_target(
+ new PBufferRenderTargetEGL(display_, config_, dimensions));
if (render_target->CreationError()) {
return scoped_refptr<RenderTarget>();
@@ -412,8 +416,7 @@
int half_height = height / 2;
for (int row = 0; row < half_height; ++row) {
uint8_t* top_row = pixels + row * pitch_in_bytes;
- uint8_t* bottom_row =
- pixels + (height - 1 - row) * pitch_in_bytes;
+ uint8_t* bottom_row = pixels + (height - 1 - row) * pitch_in_bytes;
for (int i = 0; i < pitch_in_bytes; ++i) {
std::swap(top_row[i], bottom_row[i]);
}
@@ -496,6 +499,10 @@
GL_CALL(glFinish());
}
+math::Size GraphicsContextEGL::GetWindowSize() const {
+ return system_egl()->GetWindowSize();
+}
+
void GraphicsContextEGL::Blit(GLuint texture, int x, int y, int width,
int height) {
// Render a texture to the specified output rectangle on the render target.
diff --git a/cobalt/renderer/backend/egl/graphics_context.h b/cobalt/renderer/backend/egl/graphics_context.h
index 19013dc..7c9e7c6 100644
--- a/cobalt/renderer/backend/egl/graphics_context.h
+++ b/cobalt/renderer/backend/egl/graphics_context.h
@@ -42,6 +42,7 @@
~GraphicsContextEGL() override;
GraphicsSystemEGL* system_egl();
+ const GraphicsSystemEGL* system_egl() const;
EGLContext GetContext() { return context_; }
@@ -66,6 +67,8 @@
void Finish() override;
+ math::Size GetWindowSize() const;
+
// Helper class to allow one to create a RAII object that will acquire the
// current context upon construction and release it upon destruction.
class ScopedMakeCurrent {
diff --git a/cobalt/renderer/backend/egl/graphics_system.cc b/cobalt/renderer/backend/egl/graphics_system.cc
index 8418244..d2642a5 100644
--- a/cobalt/renderer/backend/egl/graphics_system.cc
+++ b/cobalt/renderer/backend/egl/graphics_system.cc
@@ -16,6 +16,7 @@
#if SB_API_VERSION >= 12 || SB_HAS(GLES2)
#include <memory>
+#include <utility>
#include "cobalt/renderer/backend/egl/graphics_system.h"
@@ -31,7 +32,7 @@
#include "cobalt/renderer/backend/egl/texture.h"
#include "cobalt/renderer/backend/egl/utils.h"
#if defined(ENABLE_GLIMP_TRACING)
-#include "glimp/tracing/tracing.h"
+#include "glimp/tracing/tracing.h" // nogncheck
#endif
#include "cobalt/renderer/egl_and_gles.h"
@@ -283,6 +284,10 @@
#endif
}
+math::Size GraphicsSystemEGL::GetWindowSize() const {
+ return system_window_ ? system_window_->GetWindowSize() : math::Size();
+}
+
} // namespace backend
} // namespace renderer
} // namespace cobalt
diff --git a/cobalt/renderer/backend/egl/graphics_system.h b/cobalt/renderer/backend/egl/graphics_system.h
index 3ed462d..4920900 100644
--- a/cobalt/renderer/backend/egl/graphics_system.h
+++ b/cobalt/renderer/backend/egl/graphics_system.h
@@ -15,6 +15,8 @@
#ifndef COBALT_RENDERER_BACKEND_EGL_GRAPHICS_SYSTEM_H_
#define COBALT_RENDERER_BACKEND_EGL_GRAPHICS_SYSTEM_H_
+#include <memory>
+
#include "base/optional.h"
#include "cobalt/renderer/backend/egl/resource_context.h"
#include "cobalt/renderer/backend/egl/texture_data.h"
@@ -45,6 +47,8 @@
std::unique_ptr<RawTextureMemoryEGL> AllocateRawTextureMemory(
size_t size_in_bytes, size_t alignment);
+ math::Size GetWindowSize() const;
+
private:
EGLDisplay display_;
EGLConfig config_;
diff --git a/cobalt/renderer/backend/graphics_context.cc b/cobalt/renderer/backend/graphics_context.cc
index 1d3d0f6..3455980 100644
--- a/cobalt/renderer/backend/graphics_context.cc
+++ b/cobalt/renderer/backend/graphics_context.cc
@@ -63,11 +63,10 @@
graphics_context ? graphics_context->graphics_extension_ : nullptr;
#if SB_API_VERSION >= 12
#if defined(ENABLE_MAP_TO_MESH)
-#error \
- "ENABLE_MAP_TO_MESH is deprecated after Starboard version 12, use \
-the Cobalt graphics extension function IsMapToMeshEnabled() instead."
+#error "ENABLE_MAP_TO_MESH is deprecated after Starboard version 12, use "
+ "the Cobalt graphics extension function IsMapToMeshEnabled() instead."
#endif // defined(ENABLE_MAP_TO_MESH)
- if (graphics_ext && graphics_ext->version >= 3) {
+ if (graphics_ext && graphics_ext->version >= 3) {
return graphics_ext->IsMapToMeshEnabled();
}
diff --git a/cobalt/renderer/rasterizer/egl/BUILD.gn b/cobalt/renderer/rasterizer/egl/BUILD.gn
index 51363e6..ce17947 100644
--- a/cobalt/renderer/rasterizer/egl/BUILD.gn
+++ b/cobalt/renderer/rasterizer/egl/BUILD.gn
@@ -12,6 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import("//cobalt/renderer/rasterizer/skia/skia/skia_next.gni")
+
static_library("software_rasterizer") {
sources = [
"software_rasterizer.cc",
@@ -23,6 +25,10 @@
configs -= [ "//starboard/build/config:size" ]
configs += [ "//starboard/build/config:speed" ]
+ if (use_skia_next) {
+ include_dirs = [ skia_include_dir ]
+ }
+
deps = [
"//base",
"//cobalt/math",
@@ -87,6 +93,10 @@
configs -= [ "//starboard/build/config:size" ]
configs += [ "//starboard/build/config:speed" ]
+ if (use_skia_next) {
+ include_dirs = [ skia_include_dir ]
+ }
+
deps = [
":software_rasterizer",
"//base",
diff --git a/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.cc b/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.cc
index 076aaf8..a1d3f9b 100644
--- a/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.cc
+++ b/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.cc
@@ -100,6 +100,10 @@
64 * 1.1678f, 64 * 2.1479f, 0.0f, -1.1469f,
0.0f, 0.0f, 0.0f, 1.0f};
+const float k10BitBT2020ColorMatrixCompacted[16] = {
+ 1.1678f, 0.0f, 1.6835f, -0.9147f, 1.1678f, -0.1878f, -0.6522f, 0.347f,
+ 1.1678f, 2.1479f, 0.0f, -1.1469f, 0.0f, 0.0f, 0.0f, 1.0f};
+
const float* GetColorMatrixForImageType(
TexturedMeshRenderer::Image::Type type) {
switch (type) {
@@ -115,6 +119,9 @@
case TexturedMeshRenderer::Image::YUV_UYVY_422_BT709: {
return kBT709ColorMatrix;
} break;
+ case TexturedMeshRenderer::Image::YUV_3PLANE_10BIT_COMPACT_BT2020: {
+ return k10BitBT2020ColorMatrixCompacted;
+ } break;
case TexturedMeshRenderer::Image::YUV_3PLANE_10BIT_BT2020: {
return k10BitBT2020ColorMatrix;
} break;
@@ -177,6 +184,25 @@
scale_translate_vector));
}
+ if (image.type == Image::YUV_3PLANE_10BIT_COMPACT_BT2020) {
+ math::Size viewport_size = graphics_context_->GetWindowSize();
+ GL_CALL(glUniform2f(blit_program.viewport_size_uniform,
+ viewport_size.width(), viewport_size.height()));
+
+ SB_DCHECK(image.num_textures() >= 1);
+ math::Size texture_size = image.textures[0].texture->GetSize();
+ GL_CALL(glUniform2f(
+ blit_program.viewport_to_texture_size_ratio_uniform,
+ viewport_size.width() / static_cast<float>(texture_size.width()),
+ viewport_size.height() / static_cast<float>(texture_size.height())));
+
+ // The pixel shader requires the actual frame size of the first plane
+ // for calculations.
+ size_t subtexture_width = (texture_size.width() + 2) / 3;
+ GL_CALL(glUniform2f(blit_program.subtexture_size_uniform, subtexture_width,
+ texture_size.height()));
+ }
+
GL_CALL(glDrawArrays(mode, 0, num_vertices));
for (int i = 0; i < image.num_textures(); ++i) {
@@ -472,6 +498,71 @@
return CompileShader(blit_fragment_shader_source);
}
+uint32 TexturedMeshRenderer::CreateYUVCompactedTexturesFragmentShader(
+ uint32 texture_target) {
+ SamplerInfo sampler_info = GetSamplerInfo(texture_target);
+
+ std::string blit_fragment_shader_source = sampler_info.preamble;
+
+ // The fragment shader below performs YUV->RGB transform for
+ // compacted 10-bit yuv420 textures
+ blit_fragment_shader_source +=
+ "precision mediump float;"
+ "varying vec2 v_tex_coord_y;"
+ "varying vec2 v_tex_coord_u;"
+ "varying vec2 v_tex_coord_v;"
+ "uniform sampler2D texture_y;"
+ "uniform sampler2D texture_u;"
+ "uniform sampler2D texture_v;"
+ "uniform vec2 viewport_size;"
+ "uniform vec2 viewport_to_texture_ratio;"
+ "uniform vec2 texture_size;"
+ "uniform mat4 to_rgb_color_matrix;"
+ // In a compacted YUV image each channel, Y, U and V, is stored as a
+ // separate single-channel image plane. Its pixels are stored in 32-bit
+ // and each represents 3 10-bit pixels with 2 bits of gap.
+ // In order to extract compacted pixels, the horizontal position at the
+ // compacted texture is calculated as:
+ // index = compacted_position % 3;
+ // decompacted_position = compacted_position / 3;
+ // pixel = CompactedTexture.Sample(Sampler, decompacted_position)[index];
+ "void main() {"
+ // Take into account display size to texture size ratio for Y component.
+ "vec2 DTid = vec2(floor(v_tex_coord_y * viewport_size));"
+ "vec2 compact_pos_Y = vec2((DTid + 0.5) / viewport_to_texture_ratio);"
+ // Calculate the position of 10-bit pixel for Y.
+ "vec2 decompact_pos_Y;"
+ "decompact_pos_Y.x = (floor(compact_pos_Y.x / 3.0) + 0.5) / "
+ "texture_size.x;"
+ "decompact_pos_Y.y = compact_pos_Y.y / texture_size.y;"
+ // Calculate the index of 10-bit pixel for Y.
+ "int index_Y = int(mod(floor(compact_pos_Y.x), 3.0));"
+ // Extract Y component.
+ "float Y_component = texture2D(texture_y, decompact_pos_Y)[index_Y];"
+ // For yuv420 U and V textures have dimensions twice less than Y.
+ "DTid = vec2(DTid / 2.0);"
+ "vec2 texture_size_UV = vec2(texture_size / 2.0);"
+ "vec2 compact_pos_UV = vec2((DTid + 0.5) / viewport_to_texture_ratio);"
+ // Calculate the position of 10-bit pixels for U and V.
+ "vec2 decompact_pos_UV;"
+ "decompact_pos_UV.x = (floor(compact_pos_UV.x / 3.0) + 0.5) / "
+ "texture_size_UV.x;"
+ "decompact_pos_UV.y = (floor(compact_pos_UV.y) + 0.5) / "
+ "texture_size_UV.y;"
+ // Calculate the index of 10-bit pixels for U and V.
+ "int index_UV = int(mod(floor(compact_pos_UV), 3.0));"
+ // Extract U and V components.
+ "float U_component = texture2D(texture_u, decompact_pos_UV)[index_UV];"
+ "float V_component = texture2D(texture_v, decompact_pos_UV)[index_UV];"
+ // Perform the YUV->RGB transform and output the color value.
+ "vec4 untransformed_color = vec4(Y_component, U_component, V_component, "
+ "1.0);"
+ "gl_FragColor = untransformed_color * to_rgb_color_matrix;"
+ "}";
+
+ return CompileShader(blit_fragment_shader_source);
+}
+
// static
TexturedMeshRenderer::ProgramInfo TexturedMeshRenderer::MakeBlitProgram(
const float* color_matrix, const std::vector<TextureInfo>& textures,
@@ -530,7 +621,20 @@
glGetUniformLocation(result.gl_program_id, "to_rgb_color_matrix"));
GL_CALL(glUniformMatrix4fv(to_rgb_color_matrix_uniform, 1, GL_FALSE,
color_matrix));
+ if (color_matrix == k10BitBT2020ColorMatrixCompacted) {
+ result.viewport_size_uniform = GL_CALL_SIMPLE(
+ glGetUniformLocation(result.gl_program_id, "viewport_size"));
+ DCHECK_EQ(GL_NO_ERROR, GL_CALL_SIMPLE(glGetError()));
+ result.viewport_to_texture_size_ratio_uniform =
+ GL_CALL_SIMPLE(glGetUniformLocation(result.gl_program_id,
+ "viewport_to_texture_ratio"));
+ DCHECK_EQ(GL_NO_ERROR, GL_CALL_SIMPLE(glGetError()));
+ result.subtexture_size_uniform = GL_CALL_SIMPLE(
+ glGetUniformLocation(result.gl_program_id, "texture_size"));
+ DCHECK_EQ(GL_NO_ERROR, GL_CALL_SIMPLE(glGetError()));
+ }
}
+
GL_CALL(glUseProgram(0));
GL_CALL(glDeleteShader(blit_fragment_shader));
@@ -599,7 +703,8 @@
} break;
case Image::YUV_3PLANE_BT601_FULL_RANGE:
case Image::YUV_3PLANE_BT709:
- case Image::YUV_3PLANE_10BIT_BT2020: {
+ case Image::YUV_3PLANE_10BIT_BT2020:
+ case Image::YUV_3PLANE_10BIT_COMPACT_BT2020: {
std::vector<TextureInfo> texture_infos;
#if defined(GL_RED_EXT)
if (image.textures[0].texture->GetFormat() == GL_RED_EXT) {
@@ -622,9 +727,15 @@
texture_infos.push_back(TextureInfo("u", "a"));
texture_infos.push_back(TextureInfo("v", "a"));
#endif // defined(GL_RED_EXT)
- result = MakeBlitProgram(
- color_matrix, texture_infos,
- CreateFragmentShader(texture_target, texture_infos, color_matrix));
+ uint32 shader_program;
+ if (type == Image::YUV_3PLANE_10BIT_COMPACT_BT2020) {
+ shader_program =
+ CreateYUVCompactedTexturesFragmentShader(texture_target);
+ } else {
+ shader_program =
+ CreateFragmentShader(texture_target, texture_infos, color_matrix);
+ }
+ result = MakeBlitProgram(color_matrix, texture_infos, shader_program);
} break;
case Image::YUV_UYVY_422_BT709: {
std::vector<TextureInfo> texture_infos;
diff --git a/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.h b/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.h
index e607bd7..2faac78 100644
--- a/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.h
+++ b/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.h
@@ -59,6 +59,10 @@
// YUV BT2020 image where the Y, U and V components are all on different
// 10bit unnormalized textures.
YUV_3PLANE_10BIT_BT2020,
+ // YUV BT2020 image where the Y, U and V components are all on different
+ // textures. Its pixels are stored in 32 bits, and each pixel represents
+ // three 10-bit pixels with two bits of gap.
+ YUV_3PLANE_10BIT_COMPACT_BT2020,
// 1 texture is used that contains RGBA pixels.
RGBA,
// 1 texture plane is used where Y is sampled twice for each UV sample
@@ -79,6 +83,7 @@
case YUV_3PLANE_BT601_FULL_RANGE:
case YUV_3PLANE_BT709:
case YUV_3PLANE_10BIT_BT2020:
+ case YUV_3PLANE_10BIT_COMPACT_BT2020:
return 3;
case RGBA:
case YUV_UYVY_422_BT709:
@@ -121,6 +126,9 @@
int32 texture_uniforms[3];
int32 texture_size_uniforms[3];
uint32 gl_program_id;
+ int32 viewport_size_uniform;
+ int32 viewport_to_texture_size_ratio_uniform;
+ int32 subtexture_size_uniform;
};
// We key each program off of their GL texture type and image type.
typedef std::tuple<uint32, Image::Type, base::Optional<int32> > CacheKey;
@@ -142,6 +150,9 @@
// of applying bilinear filtering within a texel between the two Y values.
static uint32 CreateUYVYFragmentShader(uint32 texture_target,
int32 texture_wrap_s);
+ // YUV compacted textures need a special fragment shader to handle compacted
+ // pixels. Compacted textures support YUV420 only.
+ static uint32 CreateYUVCompactedTexturesFragmentShader(uint32 texture_target);
backend::GraphicsContextEGL* graphics_context_;
diff --git a/cobalt/renderer/rasterizer/skia/BUILD.gn b/cobalt/renderer/rasterizer/skia/BUILD.gn
index 11146df..abb46ad 100644
--- a/cobalt/renderer/rasterizer/skia/BUILD.gn
+++ b/cobalt/renderer/rasterizer/skia/BUILD.gn
@@ -12,6 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import("//cobalt/renderer/rasterizer/skia/skia/skia_next.gni")
+
static_library("common") {
visibility = [ ":*" ]
@@ -42,6 +44,10 @@
configs -= [ "//starboard/build/config:size" ]
configs += [ "//starboard/build/config:speed" ]
+ if (use_skia_next) {
+ include_dirs = [ skia_include_dir ]
+ }
+
public_deps = [
"//base",
"//cobalt/base",
@@ -78,6 +84,10 @@
configs -= [ "//starboard/build/config:size" ]
configs += [ "//starboard/build/config:speed" ]
+ if (use_skia_next) {
+ include_dirs = [ skia_include_dir ]
+ }
+
deps = [
":common",
"//cobalt/renderer:renderer_headers_only",
@@ -101,5 +111,9 @@
configs -= [ "//starboard/build/config:size" ]
configs += [ "//starboard/build/config:speed" ]
+ if (use_skia_next) {
+ include_dirs = [ skia_include_dir ]
+ }
+
deps = [ ":common" ]
}
diff --git a/cobalt/renderer/rasterizer/skia/hardware_rasterizer.cc b/cobalt/renderer/rasterizer/skia/hardware_rasterizer.cc
index 2c08712..db0c69c 100644
--- a/cobalt/renderer/rasterizer/skia/hardware_rasterizer.cc
+++ b/cobalt/renderer/rasterizer/skia/hardware_rasterizer.cc
@@ -328,6 +328,18 @@
return result;
}
+inline void SetImageTextures(egl::TexturedMeshRenderer::Image& result,
+ unsigned int textures_num,
+ const math::Matrix3F& local_transform,
+ HardwareMultiPlaneImage* hardware_image,
+ render_tree::StereoMode stereo_mode) {
+ for (auto i = 0; i < textures_num; i++) {
+ result.textures[i] = GetTextureFromHardwareFrontendImage(
+ local_transform, hardware_image->GetHardwareFrontendImage(i).get(),
+ stereo_mode);
+ }
+}
+
egl::TexturedMeshRenderer::Image SkiaImageToTexturedMeshRendererImage(
const math::Matrix3F& local_transform, Image* image,
render_tree::StereoMode stereo_mode) {
@@ -357,48 +369,25 @@
render_tree::kMultiPlaneImageFormatYUV3PlaneBT601FullRange) {
result.type =
egl::TexturedMeshRenderer::Image::YUV_3PLANE_BT601_FULL_RANGE;
- result.textures[0] = GetTextureFromHardwareFrontendImage(
- local_transform, hardware_image->GetHardwareFrontendImage(0).get(),
- stereo_mode);
- result.textures[1] = GetTextureFromHardwareFrontendImage(
- local_transform, hardware_image->GetHardwareFrontendImage(1).get(),
- stereo_mode);
- result.textures[2] = GetTextureFromHardwareFrontendImage(
- local_transform, hardware_image->GetHardwareFrontendImage(2).get(),
- stereo_mode);
+ SetImageTextures(result, 3, local_transform, hardware_image, stereo_mode);
} else if (hardware_image->GetFormat() ==
render_tree::kMultiPlaneImageFormatYUV2PlaneBT709) {
result.type = egl::TexturedMeshRenderer::Image::YUV_2PLANE_BT709;
- result.textures[0] = GetTextureFromHardwareFrontendImage(
- local_transform, hardware_image->GetHardwareFrontendImage(0).get(),
- stereo_mode);
- result.textures[1] = GetTextureFromHardwareFrontendImage(
- local_transform, hardware_image->GetHardwareFrontendImage(1).get(),
- stereo_mode);
+ SetImageTextures(result, 2, local_transform, hardware_image, stereo_mode);
} else if (hardware_image->GetFormat() ==
render_tree::kMultiPlaneImageFormatYUV3PlaneBT709) {
result.type = egl::TexturedMeshRenderer::Image::YUV_3PLANE_BT709;
- result.textures[0] = GetTextureFromHardwareFrontendImage(
- local_transform, hardware_image->GetHardwareFrontendImage(0).get(),
- stereo_mode);
- result.textures[1] = GetTextureFromHardwareFrontendImage(
- local_transform, hardware_image->GetHardwareFrontendImage(1).get(),
- stereo_mode);
- result.textures[2] = GetTextureFromHardwareFrontendImage(
- local_transform, hardware_image->GetHardwareFrontendImage(2).get(),
- stereo_mode);
+ SetImageTextures(result, 3, local_transform, hardware_image, stereo_mode);
} else if (hardware_image->GetFormat() ==
render_tree::kMultiPlaneImageFormatYUV3Plane10BitBT2020) {
result.type = egl::TexturedMeshRenderer::Image::YUV_3PLANE_10BIT_BT2020;
- result.textures[0] = GetTextureFromHardwareFrontendImage(
- local_transform, hardware_image->GetHardwareFrontendImage(0).get(),
- stereo_mode);
- result.textures[1] = GetTextureFromHardwareFrontendImage(
- local_transform, hardware_image->GetHardwareFrontendImage(1).get(),
- stereo_mode);
- result.textures[2] = GetTextureFromHardwareFrontendImage(
- local_transform, hardware_image->GetHardwareFrontendImage(2).get(),
- stereo_mode);
+ SetImageTextures(result, 3, local_transform, hardware_image, stereo_mode);
+ } else if (hardware_image->GetFormat() ==
+ render_tree::
+ kMultiPlaneImageFormatYUV3Plane10BitCompactedBT2020) {
+ result.type =
+ egl::TexturedMeshRenderer::Image::YUV_3PLANE_10BIT_COMPACT_BT2020;
+ SetImageTextures(result, 3, local_transform, hardware_image, stereo_mode);
}
} else {
NOTREACHED();
diff --git a/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc b/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc
index f3295a7..0ccee82 100644
--- a/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc
+++ b/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc
@@ -19,6 +19,7 @@
#include "cobalt/renderer/rasterizer/skia/hardware_resource_provider.h"
#include <memory>
+#include <utility>
#include "base/synchronization/waitable_event.h"
#include "base/trace_event/trace_event.h"
@@ -205,6 +206,7 @@
}
} break;
case kSbDecodeTargetFormat3Plane10BitYUVI420:
+ case kSbDecodeTargetFormat3Plane10BitYUVI420Compact:
case kSbDecodeTargetFormat3PlaneYUVI420: {
DCHECK_LT(plane, 3);
#if defined(GL_RED_EXT)
@@ -233,6 +235,9 @@
case kSbDecodeTargetFormat3Plane10BitYUVI420: {
return render_tree::kMultiPlaneImageFormatYUV3Plane10BitBT2020;
} break;
+ case kSbDecodeTargetFormat3Plane10BitYUVI420Compact: {
+ return render_tree::kMultiPlaneImageFormatYUV3Plane10BitCompactedBT2020;
+ }
default: { NOTREACHED(); }
}
return render_tree::kMultiPlaneImageFormatYUV2PlaneBT709;
diff --git a/cobalt/renderer/rasterizer/skia/skia/BUILD.gn b/cobalt/renderer/rasterizer/skia/skia/BUILD.gn
index 6fac75a..b43ec9f 100644
--- a/cobalt/renderer/rasterizer/skia/skia/BUILD.gn
+++ b/cobalt/renderer/rasterizer/skia/skia/BUILD.gn
@@ -12,26 +12,27 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import("//cobalt/renderer/rasterizer/skia/skia/skia_next.gni")
import("//cobalt/renderer/rasterizer/skia/skia/skia_sources.gni")
import("//cobalt/renderer/rasterizer/skia/skia/skia_template.gni")
# skia_opts_none and skia_opts targets contain the platform-specific optimizations for Skia
config("skia_opts_none") {
include_dirs = [
- "//third_party/skia/include/core",
- "//third_party/skia/include/effects",
- "//third_party/skia/src/core",
- "//third_party/skia/src/utils",
+ "//third_party/$skia_subdir/include/core",
+ "//third_party/$skia_subdir/include/effects",
+ "//third_party/$skia_subdir/src/core",
+ "//third_party/$skia_subdir/src/utils",
]
}
config("skia_opts") {
include_dirs = [
- "//third_party/skia/include/core",
- "//third_party/skia/include/effects",
- "//third_party/skia/src/core",
- "//third_party/skia/src/opts",
- "//third_party/skia/src/utils",
+ "//third_party/$skia_subdir/include/core",
+ "//third_party/$skia_subdir/include/effects",
+ "//third_party/$skia_subdir/src/core",
+ "//third_party/$skia_subdir/src/opts",
+ "//third_party/$skia_subdir/src/utils",
]
}
@@ -41,6 +42,11 @@
skia_common("skia_library_no_asan") {
check_includes = false
sources = skia_effects_imagefilter_sources_no_asan
+ if (use_skia_next) {
+ include_dirs = [ skia_include_dir ]
+ defines = [ "USE_SKIA_NEXT" ]
+ cflags_cc = [ "-Wno-c++17-extensions" ]
+ }
# TODO(b/207398024): If this is a target we need to build, then we'll
# need to rework the configuration framework to be able to remove this
@@ -58,21 +64,26 @@
config("skia_library_config_public") {
include_dirs = [
#temporary until we can hide SkFontHost
- "//third_party/skia",
- "//third_party/skia/src/core",
- "//third_party/skia/src/utils",
- "//third_party/skia/include/core",
- "//third_party/skia/include/effects",
- "//third_party/skia/include/gpu",
- "//third_party/skia/include/lazy",
- "//third_party/skia/include/pathops",
- "//third_party/skia/include/pipe",
- "//third_party/skia/include/ports",
- "//third_party/skia/include/private",
- "//third_party/skia/include/utils",
+ "//third_party/$skia_subdir",
+ "//third_party/$skia_subdir/src/core",
+ "//third_party/$skia_subdir/src/utils",
+ "//third_party/$skia_subdir/include/core",
+ "//third_party/$skia_subdir/include/effects",
+ "//third_party/$skia_subdir/include/gpu",
+ "//third_party/$skia_subdir/include/lazy",
+ "//third_party/$skia_subdir/include/pathops",
+ "//third_party/$skia_subdir/include/pipe",
+ "//third_party/$skia_subdir/include/ports",
+ "//third_party/$skia_subdir/include/private",
+ "//third_party/$skia_subdir/include/utils",
]
defines = [ "SK_BUILD_NO_OPTS" ]
+
+ if (use_skia_next) {
+ defines += [ "USE_SKIA_NEXT" ]
+ cflags_cc = [ "-Wno-c++17-extensions" ]
+ }
}
skia_common("skia_library") {
@@ -89,9 +100,9 @@
":skia_library_no_asan",
"//base",
"//starboard/common",
+ "//third_party/$skia_subdir/third_party/skcms",
"//third_party/freetype2",
"//third_party/libpng",
- "//third_party/skia/third_party/skcms",
"//third_party/zlib",
]
}
@@ -131,6 +142,10 @@
include_dirs = [ target_gen_dir ]
+ if (use_skia_next) {
+ include_dirs += [ skia_include_dir ]
+ }
+
deps = [
"//base",
"//cobalt/base",
diff --git a/cobalt/renderer/rasterizer/skia/skia/skia_next.gni b/cobalt/renderer/rasterizer/skia/skia/skia_next.gni
new file mode 100644
index 0000000..7c61125
--- /dev/null
+++ b/cobalt/renderer/rasterizer/skia/skia/skia_next.gni
@@ -0,0 +1,25 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+declare_args() {
+ use_skia_next = false
+}
+
+skia_subdir = "skia"
+skia_include_dir = ""
+
+if (use_skia_next) {
+ skia_subdir = "skia_next/third_party/skia"
+ skia_include_dir = "//third_party/skia_next"
+}
diff --git a/cobalt/renderer/rasterizer/skia/skia/skia_sources.gni b/cobalt/renderer/rasterizer/skia/skia/skia_sources.gni
index 40b3a97..03a9310 100644
--- a/cobalt/renderer/rasterizer/skia/skia/skia_sources.gni
+++ b/cobalt/renderer/rasterizer/skia/skia/skia_sources.gni
@@ -12,46 +12,52 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import("//third_party/skia/gn/core.gni")
-import("//third_party/skia/gn/effects.gni")
-import("//third_party/skia/gn/effects_imagefilters.gni")
-import("//third_party/skia/gn/gpu.gni")
-import("//third_party/skia/gn/utils.gni")
-import("//third_party/skia/modules/skottie/skottie.gni")
-import("//third_party/skia/modules/sksg/sksg.gni")
-import("//third_party/skia/modules/skshaper/skshaper.gni")
+import("//cobalt/renderer/rasterizer/skia/skia/skia_next.gni")
+import("//third_party/$skia_subdir/gn/core.gni")
+import("//third_party/$skia_subdir/gn/effects.gni")
+import("//third_party/$skia_subdir/gn/effects_imagefilters.gni")
+import("//third_party/$skia_subdir/gn/gpu.gni")
+import("//third_party/$skia_subdir/gn/utils.gni")
+import("//third_party/$skia_subdir/modules/skottie/skottie.gni")
+import("//third_party/$skia_subdir/modules/sksg/sksg.gni")
+import("//third_party/$skia_subdir/modules/skshaper/skshaper.gni")
combined_sources = []
shared_sources = [
- "//third_party/skia/src/codec/SkBmpBaseCodec.cpp",
- "//third_party/skia/src/codec/SkBmpCodec.cpp",
- "//third_party/skia/src/codec/SkBmpMaskCodec.cpp",
- "//third_party/skia/src/codec/SkBmpRLECodec.cpp",
- "//third_party/skia/src/codec/SkBmpStandardCodec.cpp",
- "//third_party/skia/src/codec/SkCodec.cpp",
- "//third_party/skia/src/codec/SkCodecImageGenerator.cpp",
- "//third_party/skia/src/codec/SkColorTable.cpp",
- "//third_party/skia/src/codec/SkGifCodec.cpp",
- "//third_party/skia/src/codec/SkMaskSwizzler.cpp",
- "//third_party/skia/src/codec/SkMasks.cpp",
- "//third_party/skia/src/codec/SkSampledCodec.cpp",
- "//third_party/skia/src/codec/SkSampler.cpp",
- "//third_party/skia/src/codec/SkStreamBuffer.cpp",
- "//third_party/skia/src/codec/SkSwizzler.cpp",
- "//third_party/skia/src/codec/SkWbmpCodec.cpp",
- "//third_party/skia/src/images/SkImageEncoder.cpp",
- "//third_party/skia/src/ports/SkDiscardableMemory_none.cpp",
- "//third_party/skia/src/ports/SkFontHost_FreeType.cpp",
- "//third_party/skia/src/ports/SkFontHost_FreeType_common.cpp",
- "//third_party/skia/src/ports/SkFontHost_FreeType_common.h",
- "//third_party/skia/src/ports/SkGlobalInitialization_default.cpp",
- "//third_party/skia/src/ports/SkImageGenerator_skia.cpp",
- "//third_party/skia/src/sfnt/SkOTTable_name.cpp",
- "//third_party/skia/src/sfnt/SkOTUtils.cpp",
- "//third_party/skia/third_party/gif/SkGifImageReader.cpp",
+ "//third_party/$skia_subdir/src/codec/SkBmpBaseCodec.cpp",
+ "//third_party/$skia_subdir/src/codec/SkBmpCodec.cpp",
+ "//third_party/$skia_subdir/src/codec/SkBmpMaskCodec.cpp",
+ "//third_party/$skia_subdir/src/codec/SkBmpRLECodec.cpp",
+ "//third_party/$skia_subdir/src/codec/SkBmpStandardCodec.cpp",
+ "//third_party/$skia_subdir/src/codec/SkCodec.cpp",
+ "//third_party/$skia_subdir/src/codec/SkCodecImageGenerator.cpp",
+ "//third_party/$skia_subdir/src/codec/SkColorTable.cpp",
+ "//third_party/$skia_subdir/src/codec/SkMaskSwizzler.cpp",
+ "//third_party/$skia_subdir/src/codec/SkMasks.cpp",
+ "//third_party/$skia_subdir/src/codec/SkSampledCodec.cpp",
+ "//third_party/$skia_subdir/src/codec/SkSampler.cpp",
+ "//third_party/$skia_subdir/src/codec/SkStreamBuffer.cpp",
+ "//third_party/$skia_subdir/src/codec/SkSwizzler.cpp",
+ "//third_party/$skia_subdir/src/codec/SkWbmpCodec.cpp",
+ "//third_party/$skia_subdir/src/images/SkImageEncoder.cpp",
+ "//third_party/$skia_subdir/src/ports/SkDiscardableMemory_none.cpp",
+ "//third_party/$skia_subdir/src/ports/SkFontHost_FreeType.cpp",
+ "//third_party/$skia_subdir/src/ports/SkFontHost_FreeType_common.cpp",
+ "//third_party/$skia_subdir/src/ports/SkFontHost_FreeType_common.h",
+ "//third_party/$skia_subdir/src/ports/SkGlobalInitialization_default.cpp",
+ "//third_party/$skia_subdir/src/ports/SkImageGenerator_skia.cpp",
+ "//third_party/$skia_subdir/src/sfnt/SkOTTable_name.cpp",
+ "//third_party/$skia_subdir/src/sfnt/SkOTUtils.cpp",
]
+if (!use_skia_next) {
+ shared_sources += [
+ "//third_party/$skia_subdir/src/codec/SkGifCodec.cpp",
+ "//third_party/$skia_subdir/third_party/gif/SkGifImageReader.cpp",
+ ]
+}
+
# from "core.gni"
combined_sources += shared_sources
combined_sources += skia_core_sources
@@ -70,68 +76,95 @@
# Exclude all unused skia files
combined_sources -= [
# codec
- "//third_party/skia/src/codec/SkSampledCodec.cpp",
+ "//third_party/$skia_subdir/src/codec/SkSampledCodec.cpp",
# core
- "//third_party/skia/src/core/SkMultiPictureDraw.cpp",
- "//third_party/skia/src/core/SkTime.cpp",
-
- # gpu
- "//third_party/skia/src/gpu/gl/GrGLMakeNativeInterface_none.cpp",
+ "//third_party/$skia_subdir/src/core/SkTime.cpp",
# utils bitmap
- "//third_party/skia/src/utils/SkCamera.cpp",
- "//third_party/skia/src/utils/SkCanvasStack.h",
- "//third_party/skia/src/utils/SkFloatUtils.h",
- "//third_party/skia/src/utils/SkFrontBufferedStream.cpp",
- "//third_party/skia/src/utils/SkInterpolator.cpp",
- "//third_party/skia/src/utils/SkParseColor.cpp",
- "//third_party/skia/src/utils/SkParsePath.cpp",
- "//third_party/skia/src/utils/SkThreadUtils_pthread.cpp",
- "//third_party/skia/src/utils/SkThreadUtils_win.cpp",
+ "//third_party/$skia_subdir/src/utils/SkCamera.cpp",
+ "//third_party/$skia_subdir/src/utils/SkCanvasStack.h",
+ "//third_party/$skia_subdir/src/utils/SkFloatUtils.h",
+ "//third_party/$skia_subdir/src/utils/SkParseColor.cpp",
+ "//third_party/$skia_subdir/src/utils/SkParsePath.cpp",
+ "//third_party/$skia_subdir/src/utils/SkThreadUtils_pthread.cpp",
+ "//third_party/$skia_subdir/src/utils/SkThreadUtils_win.cpp",
# mac
- "//third_party/skia/src/utils/mac/SkCreateCGImageRef.cpp",
+ "//third_party/$skia_subdir/src/utils/mac/SkCreateCGImageRef.cpp",
# windows
- "//third_party/skia/src/utils/win/SkAutoCoInitialize.cpp",
- "//third_party/skia/src/utils/win/SkAutoCoInitialize.h",
- "//third_party/skia/src/utils/win/SkDWrite.cpp",
- "//third_party/skia/src/utils/win/SkDWrite.h",
- "//third_party/skia/src/utils/win/SkDWriteFontFileStream.cpp",
- "//third_party/skia/src/utils/win/SkDWriteFontFileStream.h",
- "//third_party/skia/src/utils/win/SkDWriteGeometrySink.cpp",
- "//third_party/skia/src/utils/win/SkDWriteGeometrySink.h",
- "//third_party/skia/src/utils/win/SkHRESULT.cpp",
- "//third_party/skia/src/utils/win/SkHRESULT.h",
- "//third_party/skia/src/utils/win/SkIStream.cpp",
- "//third_party/skia/src/utils/win/SkIStream.h",
- "//third_party/skia/src/utils/win/SkTScopedComPtr.h",
- "//third_party/skia/src/utils/win/SkWGL.h",
- "//third_party/skia/src/utils/win/SkWGL_win.cpp",
+ "//third_party/$skia_subdir/src/utils/win/SkAutoCoInitialize.cpp",
+ "//third_party/$skia_subdir/src/utils/win/SkAutoCoInitialize.h",
+ "//third_party/$skia_subdir/src/utils/win/SkDWrite.cpp",
+ "//third_party/$skia_subdir/src/utils/win/SkDWrite.h",
+ "//third_party/$skia_subdir/src/utils/win/SkDWriteFontFileStream.cpp",
+ "//third_party/$skia_subdir/src/utils/win/SkDWriteFontFileStream.h",
+ "//third_party/$skia_subdir/src/utils/win/SkDWriteGeometrySink.cpp",
+ "//third_party/$skia_subdir/src/utils/win/SkDWriteGeometrySink.h",
+ "//third_party/$skia_subdir/src/utils/win/SkHRESULT.cpp",
+ "//third_party/$skia_subdir/src/utils/win/SkHRESULT.h",
+ "//third_party/$skia_subdir/src/utils/win/SkIStream.cpp",
+ "//third_party/$skia_subdir/src/utils/win/SkIStream.h",
+ "//third_party/$skia_subdir/src/utils/win/SkTScopedComPtr.h",
+ "//third_party/$skia_subdir/src/utils/win/SkWGL.h",
+ "//third_party/$skia_subdir/src/utils/win/SkWGL_win.cpp",
]
-sksl_sources = [
- "//third_party/skia/src/sksl/SkSLASTNode.cpp",
- "//third_party/skia/src/sksl/SkSLByteCode.cpp",
- "//third_party/skia/src/sksl/SkSLCFGGenerator.cpp",
- "//third_party/skia/src/sksl/SkSLCPPCodeGenerator.cpp",
- "//third_party/skia/src/sksl/SkSLCPPUniformCTypes.cpp",
- "//third_party/skia/src/sksl/SkSLCompiler.cpp",
- "//third_party/skia/src/sksl/SkSLGLSLCodeGenerator.cpp",
- "//third_party/skia/src/sksl/SkSLHCodeGenerator.cpp",
- "//third_party/skia/src/sksl/SkSLIRGenerator.cpp",
- "//third_party/skia/src/sksl/SkSLLexer.cpp",
- "//third_party/skia/src/sksl/SkSLMetalCodeGenerator.cpp",
- "//third_party/skia/src/sksl/SkSLOutputStream.cpp",
- "//third_party/skia/src/sksl/SkSLParser.cpp",
- "//third_party/skia/src/sksl/SkSLPipelineStageCodeGenerator.cpp",
- "//third_party/skia/src/sksl/SkSLSPIRVCodeGenerator.cpp",
- "//third_party/skia/src/sksl/SkSLSectionAndParameterHelper.cpp",
- "//third_party/skia/src/sksl/SkSLString.cpp",
- "//third_party/skia/src/sksl/SkSLUtil.cpp",
- "//third_party/skia/src/sksl/ir/SkSLSetting.cpp",
- "//third_party/skia/src/sksl/ir/SkSLSymbolTable.cpp",
- "//third_party/skia/src/sksl/ir/SkSLType.cpp",
- "//third_party/skia/src/sksl/ir/SkSLVariableReference.cpp",
-]
+# Exclude unused skia files which only exist in m79.
+if (!use_skia_next) {
+ combined_sources -= [
+ # core
+ "//third_party/$skia_subdir/src/core/SkMultiPictureDraw.cpp",
+
+ # gpu
+ "//third_party/$skia_subdir/src/gpu/gl/GrGLMakeNativeInterface_none.cpp",
+
+ # utils bitmap
+ "//third_party/$skia_subdir/src/utils/SkFrontBufferedStream.cpp",
+ "//third_party/$skia_subdir/src/utils/SkInterpolator.cpp",
+ ]
+}
+
+if (use_skia_next) {
+ sksl_sources = [
+ "//third_party/$skia_subdir/src/sksl/SkSLCompiler.cpp",
+ "//third_party/$skia_subdir/src/sksl/SkSLLexer.cpp",
+ "//third_party/$skia_subdir/src/sksl/SkSLOutputStream.cpp",
+ "//third_party/$skia_subdir/src/sksl/SkSLString.cpp",
+ "//third_party/$skia_subdir/src/sksl/SkSLUtil.cpp",
+ "//third_party/$skia_subdir/src/sksl/ir/SkSLSetting.cpp",
+ "//third_party/$skia_subdir/src/sksl/ir/SkSLSymbolTable.cpp",
+ "//third_party/$skia_subdir/src/sksl/ir/SkSLType.cpp",
+ "//third_party/$skia_subdir/src/sksl/ir/SkSLVariableReference.cpp",
+ ]
+} else {
+ sksl_sources = [
+ "//third_party/$skia_subdir/src/sksl/SkSLASTNode.cpp",
+ "//third_party/$skia_subdir/src/sksl/SkSLByteCode.cpp",
+ "//third_party/$skia_subdir/src/sksl/SkSLCFGGenerator.cpp",
+ "//third_party/$skia_subdir/src/sksl/SkSLCPPCodeGenerator.cpp",
+ "//third_party/$skia_subdir/src/sksl/SkSLCPPUniformCTypes.cpp",
+ "//third_party/$skia_subdir/src/sksl/SkSLCompiler.cpp",
+ "//third_party/$skia_subdir/src/sksl/SkSLGLSLCodeGenerator.cpp",
+ "//third_party/$skia_subdir/src/sksl/SkSLHCodeGenerator.cpp",
+ "//third_party/$skia_subdir/src/sksl/SkSLIRGenerator.cpp",
+ "//third_party/$skia_subdir/src/sksl/SkSLLexer.cpp",
+ "//third_party/$skia_subdir/src/sksl/SkSLMetalCodeGenerator.cpp",
+ "//third_party/$skia_subdir/src/sksl/SkSLOutputStream.cpp",
+ "//third_party/$skia_subdir/src/sksl/SkSLParser.cpp",
+ "//third_party/$skia_subdir/src/sksl/SkSLPipelineStageCodeGenerator.cpp",
+ "//third_party/$skia_subdir/src/sksl/SkSLSPIRVCodeGenerator.cpp",
+ "//third_party/$skia_subdir/src/sksl/SkSLSectionAndParameterHelper.cpp",
+ "//third_party/$skia_subdir/src/sksl/SkSLString.cpp",
+ "//third_party/$skia_subdir/src/sksl/SkSLUtil.cpp",
+ "//third_party/$skia_subdir/src/sksl/ir/SkSLSetting.cpp",
+ "//third_party/$skia_subdir/src/sksl/ir/SkSLSymbolTable.cpp",
+ "//third_party/$skia_subdir/src/sksl/ir/SkSLType.cpp",
+ "//third_party/$skia_subdir/src/sksl/ir/SkSLVariableReference.cpp",
+ ]
+
+ # Exclude unused skia sksl files
+ sksl_sources -=
+ [ "//third_party/$skia_subdir/src/sksl/SkSLMetalCodeGenerator.cpp" ]
+}
diff --git a/cobalt/renderer/rasterizer/skia/skia/skia_template.gni b/cobalt/renderer/rasterizer/skia/skia/skia_template.gni
index 516cb32..7ffe944 100644
--- a/cobalt/renderer/rasterizer/skia/skia/skia_template.gni
+++ b/cobalt/renderer/rasterizer/skia/skia/skia_template.gni
@@ -15,6 +15,8 @@
# This file handles the removal of platform-specific files from the
# Skia build.
+import("//cobalt/renderer/rasterizer/skia/skia/skia_next.gni")
+
template("skia_common") {
# This list will contain all defines that also need to be exported to
# dependent components.
@@ -57,12 +59,16 @@
forward_variables_from(invoker, "*", [ "configs" ])
if (defined(include_dirs)) {
- include_dirs += [ "//third_party/skia" ]
+ include_dirs += [ "//third_party/$skia_subdir" ]
} else {
- include_dirs = [ "//third_party/skia" ]
+ include_dirs = [ "//third_party/$skia_subdir" ]
}
- defines = [
+ if (!defined(defines)) {
+ defines = []
+ }
+
+ defines += [
# skia uses static initializers to initialize the serialization logic
# of its "pictures" library. This is currently not used in Cobalt; if
# it ever gets used the processes that use it need to call
diff --git a/cobalt/renderer/sandbox/BUILD.gn b/cobalt/renderer/sandbox/BUILD.gn
new file mode 100644
index 0000000..ba175a0
--- /dev/null
+++ b/cobalt/renderer/sandbox/BUILD.gn
@@ -0,0 +1,50 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This is a sample sandbox application for experimenting with the Cobalt
+# render tree/renderer interface.
+
+# This target will build a sandbox application that allows for easy
+# experimentation with the renderer interface on any platform. This can
+# also be useful for visually inspecting the output that the Cobalt
+# renderer is producing.
+target(final_executable_type, "renderer_sandbox") {
+ sources = [ "renderer_sandbox_main.cc" ]
+
+ deps = [
+ "//cobalt/base",
+ "//cobalt/math",
+ "//cobalt/renderer",
+ "//cobalt/renderer/test/scenes",
+ "//cobalt/system_window",
+ "//cobalt/trace_event",
+ ]
+}
+
+# This target will build a sandbox application that allows for easy
+# experimentation with the renderer's handling of text where its scale
+# is constantly animating, which for many implementations can be a
+# performance problem.
+target(final_executable_type, "scaling_text_sandbox") {
+ sources = [ "scaling_text_sandbox_main.cc" ]
+
+ deps = [
+ "//cobalt/base",
+ "//cobalt/math",
+ "//cobalt/renderer",
+ "//cobalt/renderer/test/scenes",
+ "//cobalt/system_window",
+ "//cobalt/trace_event",
+ ]
+}
diff --git a/cobalt/renderer/test/scenes/BUILD.gn b/cobalt/renderer/test/scenes/BUILD.gn
new file mode 100644
index 0000000..e443ad2
--- /dev/null
+++ b/cobalt/renderer/test/scenes/BUILD.gn
@@ -0,0 +1,55 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This is a sample sandbox application for experimenting with the Cobalt
+# render tree/renderer interface.
+
+# Contains code to build a demonstration scene used within benchmarking
+# code along with the sandbox application code. Is useful in general
+# wherever an interesting demo render_tree is required.
+static_library("scenes") {
+ sources = [
+ "all_scenes_combined_scene.cc",
+ "all_scenes_combined_scene.h",
+ "growing_rect_scene.cc",
+ "growing_rect_scene.h",
+ "image_wrap_scene.cc",
+ "image_wrap_scene.h",
+ "marquee_scene.cc",
+ "marquee_scene.h",
+ "scaling_text_scene.cc",
+ "scaling_text_scene.h",
+ "scene_helpers.cc",
+ "scene_helpers.h",
+ "spinning_sprites_scene.cc",
+ "spinning_sprites_scene.h",
+ ]
+
+ deps = [
+ ":scenes_copy_test_data",
+ "//cobalt/base",
+ "//cobalt/math",
+ "//cobalt/render_tree",
+ "//cobalt/render_tree:animations",
+ "//cobalt/renderer/test/png_utils",
+ "//cobalt/trace_event",
+ ]
+}
+
+copy("scenes_copy_test_data") {
+ sources = [ "demo_image.png" ]
+
+ outputs =
+ [ "$sb_static_contents_output_data_dir/test/test/scenes/demo_image.png" ]
+}
diff --git a/cobalt/site/docs/development/setup-android.md b/cobalt/site/docs/development/setup-android.md
index b2ddb55..621f5f3 100644
--- a/cobalt/site/docs/development/setup-android.md
+++ b/cobalt/site/docs/development/setup-android.md
@@ -9,7 +9,7 @@
## Preliminary Setup
<aside class="note">
-<b>Note:</b> Before proceeding further, refer to the documentation for <a href="setup-linux.html">"Set up your environment - Linux"</a>. Complete the section **Set up your workstation**, then return and complete the following steps.
+<b>Note:</b> Before proceeding further, refer to the documentation for <a href="setup-linux.html">"Set up your environment - Linux"</a>. Complete the section <b>Set up your workstation</b>, then return and complete the following steps.
</aside>
1. Additional build dependencies may need to be installed:
diff --git a/cobalt/site/docs/development/setup-docker.md b/cobalt/site/docs/development/setup-docker.md
new file mode 100644
index 0000000..afc7065
--- /dev/null
+++ b/cobalt/site/docs/development/setup-docker.md
@@ -0,0 +1,93 @@
+---
+layout: doc
+title: "Set up your environment - Docker"
+---
+
+We provide <a
+href="https://cobalt.googlesource.com/cobalt/+/refs/heads/22.lts.stable/src/docker/linux/">Docker image definitions</a> to simplify managing build environments.
+
+The instructions below assume Docker is installed and is able to run basic
+hello-world verification. `docker-compose` command is expected to be available as well.
+
+## Set up your workstation
+
+Clone the Cobalt code repository. The following `git` command creates a
+`cobalt` directory that contains the repository:
+
+```
+$ git clone https://cobalt.googlesource.com/cobalt
+$ cd cobalt
+```
+
+### Usage
+
+The simplest usage is:
+
+```
+docker-compose run <platform>
+```
+
+By default, a `debug` build will be built, with `cobalt` as a target.
+You can override this with an environment variable, e.g.
+
+```
+docker-compose run -e CONFIG=devel -e TARGET=nplb <platform>
+```
+
+where config is one of the four optimization levels, `debug`, `devel`,
+`qa` and `gold`, and target is the build target passed to ninja
+
+See <a
+href="https://cobalt.googlesource.com/cobalt/+/refs/heads/22.lts.stable/src/README.md#build-types">Cobalt README</a> for full details.
+
+Builds will be available in your `${COBALT_SRC}/out` directory.
+
+<aside class="note">
+Note that Docker runs processes as root user by default, hence
+output files in `src/out/<platform>` directory have `root` as file owner.
+</aside>
+
+### Customization
+
+To parametrize base operating system images used for the build, pass
+`BASE_OS` as an argument to `docker-compose` as follows:
+
+```
+docker-compose build --build-arg BASE_OS="ubuntu:bionic" base
+```
+
+This parameter is defined in `docker/linux/base/Dockerfile` and is passed
+to Docker `FROM ...` statement.
+
+Available parameters for customizing container execution are:
+
+**BASE_OS**: passed to `base` image at build time to select a Debian-based
+ base os image and version. Defaults to Debian 10. `ubuntu:bionic` and
+ `ubuntu:xenial` are other tested examples.
+
+**PLATFORM**: Cobalt build platform, passed to GYP
+
+**CONFIG**: Cobalt build config, passed to GYP. Defaults to `debug`
+
+**TARGET**: Build target, passed to `ninja`
+
+The `docker-compose.yml` contains the currently defined experimental build
+configurations. Edit or add new `service` entries as needed, to build custom
+configurations.
+
+
+### Pre-built images
+
+Note: Pre-built images from a public container registry are not yet available.
+
+### Troubleshooting
+
+To debug build issues, enter the shell of the corresponding build container
+by launching the bash shell, i.e.
+
+```
+docker-compose run linux-x64x11 /bin/bash
+```
+
+and try to build Cobalt with the <a
+href="https://cobalt.googlesource.com/cobalt/+/refs/heads/22.lts.stable/src/README.md#building-and-running-the-code">usual gyp / ninja flow.</a>
diff --git a/cobalt/site/docs/development/setup-linux.md b/cobalt/site/docs/development/setup-linux.md
index 288711b..21d07e5 100644
--- a/cobalt/site/docs/development/setup-linux.md
+++ b/cobalt/site/docs/development/setup-linux.md
@@ -9,16 +9,20 @@
on the machine that you are using to view the client. For example, you cannot
SSH into another machine and run the binary on that machine.
+These instructions were tested on a fresh ubuntu:20.04 Docker image. (1/12/22)
+Required libraries can differ depending on your Linux distribution and version.
+
## Set up your workstation
1. Run the following command to install packages needed to build and run
Cobalt on Linux:
```
- $ sudo apt install -qqy --no-install-recommends pkgconf ninja-build \
- bison yasm binutils clang libgles2-mesa-dev mesa-common-dev \
- libpulse-dev libavresample-dev libasound2-dev libxrender-dev \
- libxcomposite-dev
+ $ sudo apt update && sudo apt install -qqy --no-install-recommends \
+ pkgconf ninja-build bison yasm binutils clang libgles2-mesa-dev \
+ mesa-common-dev libpulse-dev libavresample-dev libasound2-dev \
+ libxrender-dev libxcomposite-dev libxml2-dev curl git \
+ python3.8-venv python2
```
1. Install Node.js via `nvm`:
@@ -48,6 +52,14 @@
$ ccache --max-size=20G
```
+1. Install necessary python2 packages for GYP. Until Cobalt 23, when we have
+ migrated our build system to GN, we still require some python2 packages:
+
+ ```
+ $ curl https://bootstrap.pypa.io/pip/2.7/get-pip.py | python2
+ $ python2 -m pip install --user requests selenium six
+ ```
+
1. Clone the Cobalt code repository. The following `git` command creates a
`cobalt` directory that contains the repository:
@@ -63,17 +75,17 @@
$ cd cobalt
```
-<aside class="note">
-<b>Note:</b> Pre-commit is only available on branches later than 22.lts.1+,
-including trunk. The below commands will fail on 22.lts.1+ and earlier branches.
-For earlier branches, run `cd src` and move on to the next section.
-</aside>
+ <aside class="note">
+ <b>Note:</b> Pre-commit is only available on branches later than 22.lts.1+,
+ including trunk. The below commands will fail on 22.lts.1+ and earlier branches.
+ For earlier branches, run `cd src` and move on to the next section.
+ </aside>
1. Create a Python 3 virtual environment for working on Cobalt (feel free to use `virtualenvwrapper` instead):
```
- $ python -m venv ~/.virtualenvs/cobalt_dev
- $ source ~/.virtualenvs/cobalt_dev
+ $ python3 -m venv ~/.virtualenvs/cobalt_dev
+ $ source ~/.virtualenvs/cobalt_dev/bin/activate
$ pip install -r requirements.txt
```
diff --git a/cobalt/site/docs/development/setup-raspi.md b/cobalt/site/docs/development/setup-raspi.md
index 48ca4cc..8badc0a 100644
--- a/cobalt/site/docs/development/setup-raspi.md
+++ b/cobalt/site/docs/development/setup-raspi.md
@@ -4,57 +4,42 @@
---
These instructions explain how to set up Cobalt for your workstation and your
-Raspberry Pi device.
+Raspberry Pi device. They have been tested with Ubuntu:20.04 and a Raspberry Pi
+3 Model B.
## Set up your device
-<aside class="note">
-<b>Note:</b> Raspberry Pi <em>cannot</em> have MesaGL installed and will return
-an error, like `DRI2 not supported` or `DRI2 failed to authenticate` if MesaGL
-is installed.
-</aside>
+Download the latest Cobalt customized Raspbian image from <a
+href="https://storage.googleapis.com/cobalt-static-storage/2020-02-13-raspbian-buster-lite_shrunk_20210427.img">GCS bucket</a>
+(this is built via <a
+href="https://github.com/youtube/cobalt/tree/master/tools/raspi_image#readme">this
+customization tool</a>)
-<aside class="note">
-<b>Note:</b> The current builds of Cobalt currently are verified to work only
-with a fairly old version of Raspbian Lite from 2017-07-05 (
-<a href="http://downloads.raspberrypi.org/raspbian_lite/images/raspbian_lite-2017-07-05/2017-07-05-raspbian-jessie-lite.zip">download link</a>
-). If you have a newer version, you may encounter linker errors when building
-Cobalt as the sysroot system libraries will differ in the latest version of
-Raspbian.
-</aside>
+On MacOS, use an image flashing tool like <a href="https://www.balena.io/etcher/">balenaEtcher</a> to write the image to a 32GB SD-card.
-Configure the Raspberry Pi memory split.
+On Linux, follow the steps below.
-1. `sudo raspi-config`
-1. Go to Advanced
-1. Memory Split: 256 for RasPi-0, 512 for all others.
-
-Cobalt assumes the Raspberry Pi is configured to use non-default thread
-schedulers and priorities. Ensure that **/etc/security/limits.conf** sets
-**rtprio** and **nice** limits for the user. For example, if the user is **pi**,
-then limits.conf should have the following lines:
+Check the location of your SD card (/dev/sdX or /dev/mmcblkX)
```
-@pi hard rtprio 99
-@pi soft rtprio 99
-@pi hard nice -20
-@pi soft nice -20
+$ sudo fdisk -l
```
+Make sure the card isn't mounted ( `umount /dev/sdX` ).
-The following commands update the package configuration on your Raspberry Pi
-so that Cobalt can run properly:
+Copy the downloaded image to your SD card (the disk, not the partition. E.g. /dev/sdX or /dev/mmcblkX):
```
-$ apt-get remove -y --purge --auto-remove libgl1-mesa-dev \
- libegl1-mesa-dev libgles2-mesa libgles2-mesa-dev
-$ apt-get install -y libpulse-dev libasound2-dev libavformat-dev \
- libavresample-dev rsync
+$ sudo dd bs=4M if=2020-02-13-raspbian-buster-lite_shrunk_20210427.img of=/dev/sdX
```
+After flashing your device, you'll still need to setup your wifi. Login with the
+default pi login, and run `sudo raspi-config`. You'll find wifi settings under
+`1. System Options`, then `S1 Wireless LAN`.
+
## Set up your workstation
<aside class="note">
-<b>Note:</b> Before proceeding further, refer to the documentation for <a href="setup-linux.html">"Set up your environment - Linux"</a>. Complete the section **Set up your workstation**, then return and complete the following steps.
+<b>Note:</b> Before proceeding further, refer to the documentation for <a href="setup-linux.html">"Set up your environment - Linux"</a>. Complete the section <b>Set up your workstation</b>, then return and complete the following steps.
</aside>
The following steps install the cross-compiling toolchain on your workstation.
@@ -66,7 +51,8 @@
```
$ sudo apt install -qqy --no-install-recommends g++-multilib \
- python-requests wget xz-utils
+ wget xz-utils libxml2 binutils-aarch64-linux-gnu \
+ binutils-arm-linux-gnueabi libglib2.0-dev
```
1. Choose a location for the installed toolchain – e.g. `raspi-tools`
@@ -77,32 +63,19 @@
1. Create the directory for the installed toolchain and go to it:
```
- mkdir -p $RASPI_HOME
- cd $RASPI_HOME
+ $ mkdir -p $RASPI_HOME
+ $ cd $RASPI_HOME
```
-1. Clone the GitHub repository for Raspberry Pi tools:
+1. Download the pre-packaged toolchain and extract it in `$RASPI_HOME`.
```
- git clone git://github.com/raspberrypi/tools.git
+ $ curl -O https://storage.googleapis.com/cobalt-static-storage/cobalt_raspi_tools.tar.bz2
+ $ tar xvpf cobalt_raspi_tools.tar.bz2
```
-1. Sync your sysroot by completing the following steps:
-
- 1. Boot up your RasPi, and set `$RASPI_ADDR` to the device's IP address.
- 1. Run `mkdir -p $RASPI_HOME/sysroot`
- 1. Run:
-
- ```
- rsync -avzh --safe-links \
- --delete-after pi@$RASPI_ADDR:/{opt,lib,usr} \
- --exclude="lib/firmware" --exclude="lib/modules" \
- --include="usr/lib" --include="usr/include" \
- --include="usr/local/include" --include="usr/local/lib" \
- --exclude="usr/*" --include="opt/vc" --exclude="opt/*" \
- $RASPI_HOME/sysroot
- password: raspberry
- ```
+ (This is a combination of old raspi tools and a newer one from linaro
+ to support older Raspbian Jessie and newer Raspbian Buster)
## Build, install, and run Cobalt for Raspberry Pi
@@ -123,7 +96,7 @@
on the device:
```
- rsync -avzLPh --exclude="obj*" --exclude="gen/" \
+ $ rsync -avzLPh --exclude="obj*" --exclude="gen/" \
$COBALT_SRC/out/raspi-2_debug pi@$RASPI_ADDR:~/
```
@@ -135,9 +108,9 @@
to quit or restart Cobalt.
```
- ssh pi@$RASPI_ADDR
- cd raspi-2_debug
- ./cobalt
+ $ ssh pi@$RASPI_ADDR
+ $ cd raspi-2_debug
+ $ ./cobalt
```
With this approach, you can just hit `[CTRL-C]` to close Cobalt. If you
@@ -147,21 +120,3 @@
Note that you can also exit YouTube on Cobalt by hitting the `[Esc]` key
enough times to bring up the "Do you want to quit YouTube?" dialog and
selecting "yes".
-
-### Improving Cobalt performance on Raspberry Pi
-
-1. You will find that there are some processes installed by default that run on the
- Raspberry Pi and can take away CPU time from Cobalt. You may wish to consider
- disabling these processes for maximum (and more consistent) performance, as they
- have been found to occasionally take >10% of the CPU according to `top`.
- You can do this by typing:
-
- ```
- apt-get remove -y --auto-remove [PACKAGE_NAME, ...]
- ```
-
- For example:
-
- ```
- apt-get remove -y --auto-remove avahi-daemon
- ```
diff --git a/cobalt/site/docs/gen/cobalt/doc/deep_links.md b/cobalt/site/docs/gen/cobalt/doc/deep_links.md
new file mode 100644
index 0000000..c73491f
--- /dev/null
+++ b/cobalt/site/docs/gen/cobalt/doc/deep_links.md
@@ -0,0 +1,139 @@
+---
+layout: doc
+title: "Cobalt Deep Links"
+---
+# Cobalt Deep Links
+
+- [Cobalt Deep Links](#cobalt-deep-links)
+ - [Deep Links](#deep-links)
+ - [Web API](#web-api)
+ - [Platform (Starboard) API](#platform-starboard-api)
+ - [Behavior details](#behavior-details)
+## Deep Links
+
+For Cobalt, a deep link is a string that can be sent from the platform to an
+application running in Cobalt. Generally, it can be used as any string value
+signal, but typically deep links are used to specify a view, page, or content
+to be shown by the application. While these strings typically are URI formatted
+values, when deep link strings are received by Cobalt they are forwarded to the
+running application without separate validation or modification.
+
+Applications should interpret received deep links as superseding previous deep
+links. Deep links received by Cobalt in rapid succession are not guaranteed to
+all be delivered to the application. On a busy or slow device, intermediate
+deep links can be dropped before they are delivered to the application.
+
+The startup URL passed to Cobalt determines which application Cobalt will load.
+Web deep links intended as a signal to the application should not be sent to
+Cobalt as a startup URL because that would result in a different application
+being loaded. Since a deep link is a string that may originate from an
+untrusted source on the device, it should not be used directly to determine
+what application Cobalt will load.
+
+Deep links are made visible to applications by Cobalt with a Web API that is
+separate from the Location interface web API. Cobalt will never directly
+navigate as a result of a received deep link, even if the link matches the
+current application location, for example with a query or fragment identifier.
+Applications that wish to navigate as a result of incoming deep links should do
+so explicitly when they are received.
+
+## Web API
+
+The deep link Web API consists of two parts: The
+`h5vcc.runtime.initialDeepLink` property and `h5vcc.runtime.onDeepLink` event
+target.
+
+Applications can read the value of `initialDeepLink`, and/or they can use
+`h5vcc.runtime.onDeepLink.addListener(foo)` to register callback functions to
+be called when deep links are received.
+
+A deep link is considered 'consumed' when it is read from `initialDeepLink`, or
+when it is reported to a callback function registered to `onDeepLink`.
+
+The IDL for this Cobalt specific interface can be found in cobalt/h5vcc, and is
+repeated below.
+
+```
+interface H5vccRuntime {
+ readonly attribute DOMString initialDeepLink;
+ readonly attribute H5vccDeepLinkEventTarget onDeepLink;
+}
+interface H5vccDeepLinkEventTarget {
+ void addListener(H5vccDeepLinkEventCallback callback);
+};
+callback H5vccDeepLinkEventCallback = void(DOMString link);
+interface H5vcc {
+ readonly attribute H5vccRuntime runtime;
+}
+```
+
+## Platform (Starboard) API
+
+Deep links can be passed into Cobalt in two ways:
+ * As the 'Startup Link':
+ * When Cobalt is first started, a deep link can be passed in with the
+ initial event (either `kSbEventTypePreload` or `kSbEventTypeStart`). This
+ can be achieved by calling `Application::SetStartLink` or by using and
+ overload of `Application::Run` that has the 'link_data' parameter to start
+ Cobalt. The value passed in there is then passed into Cobalt via the
+ 'link' member of the SbEventStartData event parameter, constructed in
+ `Application::CreateInitialEvent`.
+ * As a 'Deep Link Event':
+ * At any time while Cobalt is running, it can be sent as the string value
+ passed with a kSbEventTypeLink event. The `Application::Link` method can
+ be called to inject such an event.
+
+On many platforms, the 'Startup Link' value can also be set with the `--link`
+command-line parameter (See `kLinkSwitch` in `Application::Run`).
+
+The `Application` class mentioned above can be found at
+`starboard/shared/starboard/application.cc`.
+
+## Behavior details
+
+Both the 'Startup Link' and 'Deep Link Event' values are treated the same by
+Cobalt: A 'Startup Link' is treated as a 'Deep Link Event' that was received
+immediately at startup. For the application, it is transparent whether a deep
+link was received as a 'Startup Link' or arrived from a 'Deep Link Event'. Deep
+link values of either type are made available as soon as they are known, with
+the same Web API interface.
+
+The most recently received deep link is remembered by Cobalt until it is
+consumed by the application. This includes deep links received by Cobalt while
+the application is still being fetched and loaded, including during page
+redirects or reloads, and after the application is loaded, if it has not
+consumed the deep link.
+
+Deep link values are considered consumed when the application either reads them
+from the `initialDeepLink` attribute or receives them in a callback to an
+`onDeepLink` listener. An application can use either or both reads of
+`initialDeepLink` or `onDeepLink` listeners to consume the most recently
+received deep link value.
+
+Calls to `onDeepLink` listeners are done as soon as deep links are available.
+Specifically, they can be called before `document.onreadystatechange`,
+`document.onload` & `window.onload` event handlers are called. As a result,
+deep link values can be consumed in synchronously loaded JavaScript that
+executes before the `document.onload` event.
+
+Until the first `onDeepLink` listener is added, the `initialDeepLink` property
+will return the most recently received deep link value. When an `onDeepLink`
+listener is added, the `initialDeepLink` value will no longer be updated, even
+when additional deep link events are received subsequently.
+
+An application can decide to never register an `onDeepLink` listener and poll
+the `initialDeepLink` value instead. This will then always return the value of
+the most recently received deep link.
+
+An application can decide to register an `onDeepLink` listener without reading
+the `initialDeepLink` value. Upon registering, the most recently received deep
+link, which may be the 'Startup Link' or from a 'Deep Link Event', will be
+reported to the listener.
+
+If a deep link value is consumed, it will not be made available again if the
+page is navigated (e.g. redirected or reloaded). When a deep link is consumed
+before a page redirect or reload, the deep link will not be repeated later.
+
+If a deep link value is not consumed, it will be made available again if the
+page is navigated (e.g. redirected or reloaded). A deep link will not be lost
+if a page redirect or reload is done without consuming it.
diff --git a/cobalt/speech/BUILD.gn b/cobalt/speech/BUILD.gn
index b072bc4..8251374 100644
--- a/cobalt/speech/BUILD.gn
+++ b/cobalt/speech/BUILD.gn
@@ -29,14 +29,16 @@
}
copy("speech_testdata") {
+ install_content = true
+
sources = [
- "//cobalt/speech/testdata/audio1.raw",
- "//cobalt/speech/testdata/audio2.raw",
- "//cobalt/speech/testdata/audio3.raw",
- "//cobalt/speech/testdata/quit.raw",
+ "testdata/audio1.raw",
+ "testdata/audio2.raw",
+ "testdata/audio3.raw",
+ "testdata/quit.raw",
]
- outputs = [ "$root_build_dir/content/test/{{source_root_relative_dir}}/{{source_file_part}}" ]
+ outputs = [ "$sb_static_contents_output_data_dir/test/{{source_root_relative_dir}}/{{source_file_part}}" ]
}
static_library("speech") {
diff --git a/cobalt/speech/sandbox/BUILD.gn b/cobalt/speech/sandbox/BUILD.gn
new file mode 100644
index 0000000..bc8c99e
--- /dev/null
+++ b/cobalt/speech/sandbox/BUILD.gn
@@ -0,0 +1,42 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This is a sample sandbox application for experimenting with the Cobalt
+# Speech API.
+
+target(final_executable_type, "speech_sandbox") {
+ sources = [
+ "audio_loader.cc",
+ "audio_loader.h",
+ "speech_sandbox.cc",
+ "speech_sandbox.h",
+ "speech_sandbox_main.cc",
+ ]
+
+ deps = [
+ "//cobalt/audio",
+ "//cobalt/base",
+ "//cobalt/debug:console_command_manager",
+ "//cobalt/dom",
+ "//cobalt/loader",
+ "//cobalt/network",
+ "//cobalt/script",
+ "//cobalt/script:engine",
+ "//cobalt/speech",
+ "//cobalt/trace_event",
+ "//url",
+ ]
+
+ deps += cobalt_platform_dependencies
+}
diff --git a/cobalt/storage/store/BUILD.gn b/cobalt/storage/store/BUILD.gn
index f9c895f..852d03a 100644
--- a/cobalt/storage/store/BUILD.gn
+++ b/cobalt/storage/store/BUILD.gn
@@ -46,4 +46,6 @@
"//third_party/protobuf:protobuf_lite",
"//url",
]
+
+ content_deps = [ "//third_party/icu:icudata" ]
}
diff --git a/cobalt/test/BUILD.gn b/cobalt/test/BUILD.gn
index 9b8f32a..77c79aa 100644
--- a/cobalt/test/BUILD.gn
+++ b/cobalt/test/BUILD.gn
@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-static_library("run_all_unittests") {
+source_set("run_all_unittests") {
testonly = true
sources = [ "run_all_unittests.cc" ]
public_deps = [ "//base/test:test_support" ]
diff --git a/cobalt/updater/BUILD.gn b/cobalt/updater/BUILD.gn
new file mode 100644
index 0000000..83f69c1
--- /dev/null
+++ b/cobalt/updater/BUILD.gn
@@ -0,0 +1,87 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+static_library("updater") {
+ sources = [
+ "configurator.cc",
+ "configurator.h",
+ "network_fetcher.cc",
+ "network_fetcher.h",
+ "patcher.cc",
+ "patcher.h",
+ "prefs.cc",
+ "prefs.h",
+ "unzipper.cc",
+ "unzipper.h",
+ "updater_constants.cc",
+ "updater_constants.h",
+ "updater_module.cc",
+ "updater_module.h",
+ "utils.cc",
+ "utils.h",
+ ]
+
+ deps = [
+ "//base",
+ "//cobalt/base",
+ "//cobalt/browser:browser_switches",
+ "//cobalt/loader",
+ "//cobalt/network",
+ "//cobalt/script",
+ "//cobalt/script:engine",
+ "//components/crx_file",
+ "//components/prefs",
+ "//components/update_client",
+ "//crypto",
+ "//net",
+ "//starboard:starboard_headers_only",
+ "//starboard/common",
+ "//starboard/loader_app:app_key_files",
+ "//starboard/loader_app:drain_file",
+ "//third_party/zlib:zip",
+ "//url",
+ ]
+
+ # TODO(b/213388707): resolve the dependency cycle and remove this exception.
+ allow_circular_includes_from = [ "//components/update_client" ]
+}
+
+target(final_executable_type, "updater_sandbox") {
+ sources = [
+ "updater.cc",
+ "updater.h",
+ "updater_sandbox.cc",
+ ]
+
+ deps = [
+ ":updater",
+ "//base",
+ "//cobalt/debug:console_command_manager",
+ "//cobalt/network",
+ "//components/crx_file",
+ "//components/prefs",
+ "//components/update_client",
+ "//starboard",
+ ]
+}
+
+target(final_executable_type, "crash_sandbox") {
+ sources = [ "crash_sandbox.cc" ]
+ deps = [ "//starboard" ]
+}
+
+target(final_executable_type, "noop_sandbox") {
+ sources = [ "noop_sandbox.cc" ]
+ deps = [ "//starboard" ]
+}
diff --git a/cobalt/updater/OWNERS b/cobalt/updater/OWNERS
new file mode 100644
index 0000000..5a5b03e
--- /dev/null
+++ b/cobalt/updater/OWNERS
@@ -0,0 +1,4 @@
+borisv@chromium.org
+ganesh@chromium.org
+sorin@chromium.org
+waffles@chromium.org
diff --git a/cobalt/updater/configurator.cc b/cobalt/updater/configurator.cc
index 8f184c2..e823bbf 100644
--- a/cobalt/updater/configurator.cc
+++ b/cobalt/updater/configurator.cc
@@ -251,6 +251,16 @@
updater_status_ = status;
}
+void Configurator::SetMinFreeSpaceBytes(uint64_t bytes) {
+ base::AutoLock auto_lock(const_cast<base::Lock&>(min_free_space_bytes_lock_));
+ min_free_space_bytes_ = bytes;
+}
+
+uint64_t Configurator::GetMinFreeSpaceBytes() {
+ base::AutoLock auto_lock(const_cast<base::Lock&>(min_free_space_bytes_lock_));
+ return min_free_space_bytes_;
+}
+
std::string Configurator::GetPreviousUpdaterStatus() const {
base::AutoLock auto_lock(
const_cast<base::Lock&>(previous_updater_status_lock_));
diff --git a/cobalt/updater/configurator.h b/cobalt/updater/configurator.h
index 5314984..b7ed9e7 100644
--- a/cobalt/updater/configurator.h
+++ b/cobalt/updater/configurator.h
@@ -1,6 +1,16 @@
-// Copyright 2019 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.
+// Copyright 2019 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
#ifndef COBALT_UPDATER_CONFIGURATOR_H_
#define COBALT_UPDATER_CONFIGURATOR_H_
@@ -85,6 +95,9 @@
std::string GetPreviousUpdaterStatus() const override;
void SetPreviousUpdaterStatus(const std::string& status) override;
+ void SetMinFreeSpaceBytes(uint64_t bytes) override;
+ uint64_t GetMinFreeSpaceBytes() override;
+
private:
friend class base::RefCountedThreadSafe<Configurator>;
~Configurator() override;
@@ -102,6 +115,8 @@
std::string previous_updater_status_;
base::Lock previous_updater_status_lock_;
std::string user_agent_string_;
+ uint64_t min_free_space_bytes_ = 64 * 1024 * 1024;
+ base::Lock min_free_space_bytes_lock_;
DISALLOW_COPY_AND_ASSIGN(Configurator);
};
diff --git a/cobalt/updater/updater_module.cc b/cobalt/updater/updater_module.cc
index e2e4001..a9f9e6c 100644
--- a/cobalt/updater/updater_module.cc
+++ b/cobalt/updater/updater_module.cc
@@ -388,5 +388,12 @@
return index;
}
+void UpdaterModule::SetMinFreeSpaceBytes(uint64_t bytes) {
+ LOG(INFO) << "UpdaterModule::SetMinFreeSpaceBytes bytes=" << bytes;
+ if (updater_configurator_) {
+ updater_configurator_->SetMinFreeSpaceBytes(bytes);
+ }
+}
+
} // namespace updater
} // namespace cobalt
diff --git a/cobalt/updater/updater_module.h b/cobalt/updater/updater_module.h
index 353215e..0d6f793 100644
--- a/cobalt/updater/updater_module.h
+++ b/cobalt/updater/updater_module.h
@@ -151,6 +151,8 @@
int GetInstallationIndex() const;
+ void SetMinFreeSpaceBytes(uint64_t bytes);
+
private:
std::unique_ptr<base::Thread> updater_thread_;
scoped_refptr<update_client::UpdateClient> update_client_;
diff --git a/cobalt/webdriver/BUILD.gn b/cobalt/webdriver/BUILD.gn
index 1b35d40..2367a96 100644
--- a/cobalt/webdriver/BUILD.gn
+++ b/cobalt/webdriver/BUILD.gn
@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-config("webdriver_public_config") {
+config("webdriver_all_dependent_config") {
defines = [ "ENABLE_WEBDRIVER" ]
}
@@ -104,14 +104,49 @@
"window_driver.h",
]
deps += [ ":copy_webdriver_data" ]
- public_configs = [ ":webdriver_public_config" ]
+ all_dependent_configs = [ ":webdriver_all_dependent_config" ]
}
}
-# TODO: This declares content_deploy_subdirs which should be migrated for
-# install targets.
copy("copy_webdriver_data") {
+ install_content = true
sources = [ "content/webdriver-init.js" ]
outputs =
[ "$sb_static_contents_output_data_dir/webdriver/webdriver-init.js" ]
}
+
+copy("webdriver_copy_test_data") {
+ sources = [
+ "testdata/displayed_test.html",
+ "testdata/map.png",
+ "testdata/simple_test.py",
+ ]
+
+ outputs = [ "$sb_static_contents_output_data_dir/test/cobalt/webdriver_test/{{source_file_part}}" ]
+}
+
+target(gtest_target_type, "webdriver_test") {
+ testonly = true
+
+ sources = [
+ "execute_test.cc",
+ "get_element_text_test.cc",
+ "is_displayed_test.cc",
+ "keyboard_test.cc",
+ ]
+
+ deps = [
+ ":webdriver",
+ ":webdriver_copy_test_data",
+ "//cobalt/base",
+ "//cobalt/browser",
+ "//cobalt/css_parser:css_parser",
+ "//cobalt/cssom:cssom",
+ "//cobalt/dom/testing:dom_testing",
+ "//cobalt/script",
+ "//cobalt/test:run_all_unittests",
+ "//testing/gmock",
+ "//testing/gtest",
+ "//third_party/devtools:devtools_all_files",
+ ]
+}
diff --git a/cobalt/websocket/BUILD.gn b/cobalt/websocket/BUILD.gn
index e50677e..469ef27 100644
--- a/cobalt/websocket/BUILD.gn
+++ b/cobalt/websocket/BUILD.gn
@@ -54,6 +54,7 @@
"//cobalt/base",
"//cobalt/dom",
"//cobalt/dom:dom_exception",
+ "//cobalt/dom/testing:dom_testing",
"//cobalt/network",
"//cobalt/script",
"//cobalt/test:run_all_unittests",
@@ -69,4 +70,6 @@
if (!is_gold) {
deps += [ "//cobalt/debug" ]
}
+
+ content_deps = [ "//third_party/icu:icudata" ]
}
diff --git a/cobalt/worker/BUILD.gn b/cobalt/worker/BUILD.gn
new file mode 100644
index 0000000..4d2336a
--- /dev/null
+++ b/cobalt/worker/BUILD.gn
@@ -0,0 +1,30 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+static_library("worker") {
+ # Creates cycle with //cobalt/dom
+ check_includes = false
+
+ sources = [
+ "service_worker_container.cc",
+ "service_worker_container.h",
+ ]
+
+ public_deps = [
+ # Additionally, ensure that the include directories for generated
+ # headers are put on the include directories for targets that depend
+ # on this one.
+ "//cobalt/browser:generated_types",
+ ]
+}
diff --git a/cobalt/worker/navigator.idl b/cobalt/worker/navigator.idl
new file mode 100644
index 0000000..1eb4987
--- /dev/null
+++ b/cobalt/worker/navigator.idl
@@ -0,0 +1,19 @@
+// Copyright 2022 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// https://w3c.github.io/ServiceWorker/#navigator-serviceworker
+
+partial interface Navigator {
+ readonly attribute ServiceWorkerContainer serviceWorker;
+};
diff --git a/cobalt/worker/service_worker_container.cc b/cobalt/worker/service_worker_container.cc
new file mode 100644
index 0000000..7b47b46
--- /dev/null
+++ b/cobalt/worker/service_worker_container.cc
@@ -0,0 +1,32 @@
+// Copyright 2022 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cobalt/worker/service_worker_container.h"
+
+namespace cobalt {
+namespace worker {
+
+ServiceWorkerContainer::ServiceWorkerContainer(
+ script::ScriptValueFactory* script_value_factory)
+ : script_value_factory_(script_value_factory) {}
+
+script::Handle<script::Promise<void>> ServiceWorkerContainer::Register() {
+ LOG(INFO) << "The service worker is registered";
+ script::Handle<script::Promise<void>> promise =
+ script_value_factory_->CreateBasicPromise<void>();
+ return promise;
+}
+
+} // namespace worker
+} // namespace cobalt
diff --git a/cobalt/worker/service_worker_container.h b/cobalt/worker/service_worker_container.h
new file mode 100644
index 0000000..ed523f8
--- /dev/null
+++ b/cobalt/worker/service_worker_container.h
@@ -0,0 +1,44 @@
+// Copyright 2022 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COBALT_WORKER_SERVICE_WORKER_CONTAINER_H_
+#define COBALT_WORKER_SERVICE_WORKER_CONTAINER_H_
+
+#include <memory>
+
+#include "cobalt/script/promise.h"
+#include "cobalt/script/script_value.h"
+#include "cobalt/script/script_value_factory.h"
+#include "cobalt/script/wrappable.h"
+
+namespace cobalt {
+namespace worker {
+
+class ServiceWorkerContainer : public script::Wrappable {
+ public:
+ explicit ServiceWorkerContainer(
+ script::ScriptValueFactory* script_value_factory);
+
+ script::Handle<script::Promise<void>> Register();
+
+ DEFINE_WRAPPABLE_TYPE(ServiceWorkerContainer);
+
+ private:
+ script::ScriptValueFactory* script_value_factory_;
+};
+
+} // namespace worker
+} // namespace cobalt
+
+#endif // COBALT_WORKER_SERVICE_WORKER_CONTAINER_H_
diff --git a/cobalt/worker/service_worker_container.idl b/cobalt/worker/service_worker_container.idl
new file mode 100644
index 0000000..170e226
--- /dev/null
+++ b/cobalt/worker/service_worker_container.idl
@@ -0,0 +1,20 @@
+// Copyright 2022 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// https://w3c.github.io/ServiceWorker/#serviceworkercontainer-interface
+
+[Exposed=Window]
+interface ServiceWorkerContainer {
+ Promise<void> register();
+};
diff --git a/cobalt/worker/testdata/service-worker.html b/cobalt/worker/testdata/service-worker.html
new file mode 100644
index 0000000..45916cb
--- /dev/null
+++ b/cobalt/worker/testdata/service-worker.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script type="text/javascript">
+ // Demonstrates how the navigator.serviceWorker api can be used
+ const serviceWorker = navigator.serviceWorker;
+ serviceWorker.register().then(registration => {
+ console.log("success!");
+ }, err => {
+ console.error("Installing the worker failed!", err);
+ });
+ </script>
+</head>
+
+<body>
+</body>
+</html>
diff --git a/cobalt/worker/worker.gyp b/cobalt/worker/worker.gyp
new file mode 100644
index 0000000..1d3458e
--- /dev/null
+++ b/cobalt/worker/worker.gyp
@@ -0,0 +1,35 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+{
+ 'targets': [
+ {
+ 'target_name': 'worker',
+ 'type': 'static_library',
+ 'sources': [
+ 'service_worker_container.cc',
+ 'service_worker_container.h',
+ ],
+ 'dependencies': [
+ '<(DEPTH)/cobalt/browser/browser_bindings_gen.gyp:generated_types',
+ ],
+ 'export_dependent_settings': [
+ # Additionally, ensure that the include directories for generated
+ # headers are put on the include directories for targets that depend
+ # on this one.
+ '<(DEPTH)/cobalt/browser/browser_bindings_gen.gyp:generated_types',
+ ]
+ },
+ ],
+}
diff --git a/cobalt/xhr/BUILD.gn b/cobalt/xhr/BUILD.gn
index fd7f33c..45c43a3 100644
--- a/cobalt/xhr/BUILD.gn
+++ b/cobalt/xhr/BUILD.gn
@@ -40,7 +40,7 @@
if (enable_xhr_header_filtering && !sb_is_evergreen) {
sources = [ "xhr_modify_headers.h" ]
defines = [ "COBALT_ENABLE_XHR_HEADER_FILTERING" ]
- deps += [ cobalt_platform_dependencies ]
+ deps += cobalt_platform_dependencies
}
}
@@ -53,6 +53,7 @@
"//cobalt/base",
"//cobalt/dom",
"//cobalt/dom:dom_exception",
+ "//cobalt/dom/testing:dom_testing",
"//cobalt/test:run_all_unittests",
"//testing/gmock",
"//testing/gtest",
diff --git a/components/client_update_protocol/BUILD.gn b/components/client_update_protocol/BUILD.gn
new file mode 100644
index 0000000..6e0ddf2
--- /dev/null
+++ b/components/client_update_protocol/BUILD.gn
@@ -0,0 +1,24 @@
+# Copyright 2021 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+static_library("client_update_protocol") {
+ sources = [
+ "ecdsa.cc",
+ "ecdsa.h",
+ ]
+ deps = [
+ "//base",
+ "//crypto",
+ ]
+}
diff --git a/components/client_update_protocol/OWNERS b/components/client_update_protocol/OWNERS
new file mode 100644
index 0000000..c184691
--- /dev/null
+++ b/components/client_update_protocol/OWNERS
@@ -0,0 +1,4 @@
+sorin@chromium.org
+waffles@chromium.org
+# COMPONENT: Internals>Installer
+# TEAM: chrome-updates-dev@chromium.org
diff --git a/components/crx_file/BUILD.gn b/components/crx_file/BUILD.gn
new file mode 100644
index 0000000..2b103d2
--- /dev/null
+++ b/components/crx_file/BUILD.gn
@@ -0,0 +1,37 @@
+# Copyright 2021 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import("//third_party/protobuf/proto_library.gni")
+
+# The accompanying crx_creator target has been left behind during the migration
+# from GYP to GN because it introduces some -Wc++11-narrowing compiler errors
+# and isn't actually used in Cobalt. If it's at some point needed we can
+# likely pull in the narrowing fixes made by the Chromium team in
+# https://source.chromium.org/chromium/chromium/src/+/379c52be13901beae4f773fe9e8054ad42a186c4.
+static_library("crx_file") {
+ sources = [
+ "crx3.pb.cc",
+ "crx3.pb.h",
+ "crx_file.h",
+ "crx_verifier.cc",
+ "crx_verifier.h",
+ "id_util.cc",
+ "id_util.h",
+ ]
+ deps = [
+ "//base",
+ "//crypto",
+ "//third_party/protobuf:protobuf_lite",
+ ]
+}
diff --git a/components/crx_file/OWNERS b/components/crx_file/OWNERS
new file mode 100644
index 0000000..9562f1b
--- /dev/null
+++ b/components/crx_file/OWNERS
@@ -0,0 +1,5 @@
+file://extensions/OWNERS
+
+waffles@chromium.org
+sorin@chromium.org
+# COMPONENT: Platform>Extensions
diff --git a/components/prefs/OWNERS b/components/prefs/OWNERS
new file mode 100644
index 0000000..ef0ab79
--- /dev/null
+++ b/components/prefs/OWNERS
@@ -0,0 +1,5 @@
+battre@chromium.org
+gab@chromium.org
+
+# COMPONENT: UI>Browser>Preferences
+# TEAM: chromium-dev@chromium.org
diff --git a/components/test/data/update_client/OWNERS b/components/test/data/update_client/OWNERS
new file mode 100644
index 0000000..2780b5b
--- /dev/null
+++ b/components/test/data/update_client/OWNERS
@@ -0,0 +1,7 @@
+cpu@chromium.org
+laforge@chromium.org
+mal@chromium.org
+sorin@chromium.org
+waffles@chromium.org
+
+# COMPONENT: Internals>Installer>Components
diff --git a/components/update_client/BUILD.gn b/components/update_client/BUILD.gn
new file mode 100644
index 0000000..e2ccd9b
--- /dev/null
+++ b/components/update_client/BUILD.gn
@@ -0,0 +1,252 @@
+# Copyright 2021 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+config("update_client_config") {
+ defines = [
+ "LIBXML_READER_ENABLED",
+ "LIBXML_WRITER_ENABLED",
+ ]
+}
+
+static_library("update_client") {
+ sources = [
+ "action_runner.cc",
+ "action_runner.h",
+ "activity_data_service.h",
+ "cobalt_slot_management.cc",
+ "cobalt_slot_management.h",
+ "command_line_config_policy.cc",
+ "command_line_config_policy.h",
+ "component.cc",
+ "component.h",
+ "component_patcher.cc",
+ "component_patcher.h",
+ "component_patcher_operation.cc",
+ "component_patcher_operation.h",
+ "component_unpacker.cc",
+ "component_unpacker.h",
+ "configurator.h",
+ "crx_downloader.cc",
+ "crx_downloader.h",
+ "crx_update_item.h",
+ "network.cc",
+ "network.h",
+ "patcher.h",
+ "persisted_data.cc",
+ "persisted_data.h",
+ "ping_manager.cc",
+ "ping_manager.h",
+ "protocol_definition.cc",
+ "protocol_definition.h",
+ "protocol_handler.cc",
+ "protocol_handler.h",
+ "protocol_parser.cc",
+ "protocol_parser.h",
+ "protocol_parser_json.cc",
+ "protocol_parser_json.h",
+ "protocol_serializer.cc",
+ "protocol_serializer.h",
+ "protocol_serializer_json.cc",
+ "protocol_serializer_json.h",
+ "request_sender.cc",
+ "request_sender.h",
+ "task.h",
+ "task_send_uninstall_ping.cc",
+ "task_send_uninstall_ping.h",
+ "task_traits.h",
+ "task_update.cc",
+ "task_update.h",
+ "unzipper.h",
+ "update_checker.cc",
+ "update_checker.h",
+ "update_client.cc",
+ "update_client.h",
+ "update_client_errors.h",
+ "update_client_internal.h",
+ "update_engine.cc",
+ "update_engine.h",
+ "update_query_params.cc",
+ "update_query_params.h",
+ "update_query_params_delegate.cc",
+ "update_query_params_delegate.h",
+ "updater_state.cc",
+ "updater_state.h",
+ "url_fetcher_downloader.cc",
+ "url_fetcher_downloader.h",
+ "utils.cc",
+ "utils.h",
+ ]
+
+ configs += [ ":update_client_config" ]
+
+ deps = [
+ "//components/client_update_protocol",
+ "//components/crx_file",
+ "//components/prefs",
+ "//crypto",
+ "//net",
+ "//starboard/loader_app:app_key_files",
+ "//starboard/loader_app:drain_file",
+ "//third_party/libxml",
+ "//url",
+ ]
+}
+
+static_library("test_support") {
+ testonly = true
+
+ sources = [
+ "net/network_cobalt.h",
+ "net/network_impl_cobalt.cc",
+ "net/network_impl_cobalt.h",
+ "net/url_request_post_interceptor.cc",
+ "net/url_request_post_interceptor.h",
+ "patch/patch_impl_cobalt.cc",
+ "patch/patch_impl_cobalt.h",
+ "test_configurator.cc",
+ "test_configurator.h",
+ "unzip/unzip_impl_cobalt.cc",
+ "unzip/unzip_impl_cobalt.h",
+ ]
+
+ deps = [
+ ":update_client",
+ "//base",
+ "//cobalt/base",
+ "//cobalt/debug:console_command_manager",
+ "//cobalt/loader",
+ "//cobalt/network",
+ "//components/prefs",
+ "//net",
+ "//net:test_support",
+ "//testing/gmock",
+ "//testing/gtest",
+ "//third_party/zlib:zip",
+ "//url",
+ ]
+}
+
+copy("update_client_test_files") {
+ sources = [
+ "//components/test/data/update_client/ChromeRecovery.crx3",
+ "//components/test/data/update_client/binary_bsdiff_patch.bin",
+ "//components/test/data/update_client/binary_courgette_patch.bin",
+ "//components/test/data/update_client/binary_input.bin",
+ "//components/test/data/update_client/binary_output.bin",
+ "//components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc.pem",
+ "//components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1/a_changing_binary_file",
+ "//components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1/a_changing_text_file",
+ "//components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1/a_static_text_file",
+ "//components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1/manifest.json",
+ "//components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1to2/commands.json",
+ "//components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1to2/f0",
+ "//components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1to2/f1",
+ "//components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1to2/f2",
+ "//components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1to2_bad/commands.json",
+ "//components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1to2_bad/f1",
+ "//components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1to2_bad/f2",
+ "//components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_1to2_bad/f3",
+ "//components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_2/a_changing_binary_file",
+ "//components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_2/a_changing_text_file",
+ "//components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_2/a_static_text_file",
+ "//components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc/ihfokbkgjpifnbbojhneepfflplebdkc_2/manifest.json",
+ "//components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc_1.crx",
+ "//components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx",
+ "//components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc_1to2_bad.crx",
+ "//components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc_2.crx",
+ "//components/test/data/update_client/jebgalgnebhfojomionfpkfelancnnkf.crx",
+ "//components/test/data/update_client/jebgalgnebhfojomionfpkfelancnnkf.pem",
+ "//components/test/data/update_client/jebgalgnebhfojomionfpkfelancnnkf/component1.dll",
+ "//components/test/data/update_client/jebgalgnebhfojomionfpkfelancnnkf/manifest.json",
+ "//components/test/data/update_client/runaction_test_win.crx3",
+ "//components/test/data/update_client/updatecheck_reply_1.json",
+ "//components/test/data/update_client/updatecheck_reply_4.json",
+ "//components/test/data/update_client/updatecheck_reply_noupdate.json",
+ "//components/test/data/update_client/updatecheck_reply_parse_error.json",
+ "//components/test/data/update_client/updatecheck_reply_unknownapp.json",
+ ]
+
+ outputs = [ "$sb_static_contents_output_data_dir/test/{{source_root_relative_dir}}/{{source_file_part}}" ]
+}
+
+target(gtest_target_type, "update_client_test") {
+ testonly = true
+
+ sources = [
+ "component_unpacker_unittest.cc",
+
+ # TODO: enable the tests commented out
+ "crx_downloader_unittest.cc",
+ "persisted_data_unittest.cc",
+ "ping_manager_unittest.cc",
+ "protocol_parser_json_unittest.cc",
+
+ # "protocol_serializer_json_unittest.cc",
+ "protocol_serializer_unittest.cc",
+ "request_sender_unittest.cc",
+ "update_checker_unittest.cc",
+
+ # "update_client_unittest.cc",
+ "update_query_params_unittest.cc",
+ "updater_state_unittest.cc",
+ "utils_unittest.cc",
+ ]
+
+ deps = [
+ ":test_support",
+ ":update_client",
+ ":update_client_test_files",
+ "//cobalt/base",
+ "//cobalt/test:run_all_unittests",
+ "//cobalt/updater",
+ "//components/crx_file",
+ "//components/prefs",
+ "//components/prefs:test_support",
+ "//crypto",
+ "//net",
+ "//net:test_support",
+ "//testing/gmock",
+ "//testing/gtest",
+ "//url",
+ ]
+}
+
+target(gtest_target_type, "cobalt_slot_management_test") {
+ testonly = true
+
+ sources = [
+ "//starboard/common/test_main.cc",
+ "//starboard/loader_app/system_get_extension_shim.cc",
+ "cobalt_slot_management_test.cc",
+ ]
+
+ deps = [
+ ":update_client",
+ "//cobalt/base",
+ "//cobalt/updater",
+ "//components/crx_file",
+ "//components/prefs",
+ "//components/prefs:test_support",
+ "//crypto",
+ "//net:test_support",
+ "//starboard/loader_app",
+ "//starboard/loader_app:app_key_files",
+ "//starboard/loader_app:drain_file",
+ "//starboard/loader_app:installation_manager",
+ "//testing/gmock",
+ "//testing/gtest",
+ ]
+
+ content_deps = [ "//third_party/icu:icudata" ]
+}
diff --git a/components/update_client/OWNERS b/components/update_client/OWNERS
new file mode 100644
index 0000000..b68ef3b
--- /dev/null
+++ b/components/update_client/OWNERS
@@ -0,0 +1,6 @@
+cpu@chromium.org
+sorin@chromium.org
+waffles@chromium.org
+
+# COMPONENT: Internals>Installer>Components
+# TEAM: chrome-updates-dev@chromium.org
diff --git a/components/update_client/cobalt_slot_management.cc b/components/update_client/cobalt_slot_management.cc
index 0c601d7..d220b74 100644
--- a/components/update_client/cobalt_slot_management.cc
+++ b/components/update_client/cobalt_slot_management.cc
@@ -14,8 +14,10 @@
#include "components/update_client/cobalt_slot_management.h"
+#include <algorithm>
#include <vector>
+#include "base/files/file_util.h"
#include "base/values.h"
#include "cobalt/updater/utils.h"
#include "components/update_client/utils.h"
@@ -37,6 +39,32 @@
return !bad_app_key_file_path.empty() &&
SbFileExists(bad_app_key_file_path.c_str());
}
+
+uint64_t ComputeSlotSize(
+ const CobaltExtensionInstallationManagerApi* installation_api,
+ int index) {
+ if (!installation_api) {
+ LOG(WARNING) << "ComputeSlotSize: "
+ << "Missing installation manager extension.";
+ return 0;
+ }
+ std::vector<char> installation_path(kSbFileMaxPath);
+ if (installation_api->GetInstallationPath(index, installation_path.data(),
+ kSbFileMaxPath) == IM_EXT_ERROR) {
+ LOG(WARNING) << "ComputeSlotSize: "
+ << "Failed to get installation path for slot " << index;
+ return 0;
+ }
+ int64_t slot_size =
+ base::ComputeDirectorySize(base::FilePath(installation_path.data()));
+ LOG(INFO) << "ComputeSlotSize: slot_size=" << slot_size;
+ if (slot_size <= 0) {
+ LOG(WARNING) << "ComputeSlotSize: "
+ << "Failed to compute slot " << index << " size";
+ return 0;
+ }
+ return slot_size;
+}
} // namespace
CobaltSlotManagement::CobaltSlotManagement() : installation_api_(nullptr) {}
@@ -277,4 +305,52 @@
return false;
}
+bool CobaltSkipUpdate(
+ const CobaltExtensionInstallationManagerApi* installation_api,
+ uint64_t min_free_space_bytes,
+ int64_t free_space_bytes,
+ uint64_t installation_cleanup_size) {
+ LOG(INFO) << "CobaltSkipUpdate: "
+ << " min_free_space_bytes=" << min_free_space_bytes
+ << " free_space_bytes=" << free_space_bytes
+ << " installation_cleanup_size=" << installation_cleanup_size;
+
+ if (free_space_bytes < 0) {
+ LOG(WARNING) << "CobaltSkipUpdate: "
+ << "Unable to determine free space";
+ return false;
+ }
+
+ if (free_space_bytes + installation_cleanup_size < min_free_space_bytes) {
+ LOG(WARNING) << "CobaltSkipUpdate: Not enough free space";
+ return true;
+ }
+
+ return false;
+}
+
+uint64_t CobaltInstallationCleanupSize(
+ const CobaltExtensionInstallationManagerApi* installation_api) {
+ if (!installation_api) {
+ LOG(WARNING) << "CobaltInstallationCleanupSize: "
+ << "Missing installation manager extension.";
+ return 0;
+ }
+ int max_slots = installation_api->GetMaxNumberInstallations();
+ if (max_slots == IM_EXT_ERROR) {
+ LOG(ERROR)
+ << "CobaltInstallationCleanupSize: Failed to get max number of slots.";
+ return 0;
+ }
+ // Ignore the system slot 0 and start with slot 1.
+ uint64_t min_slot_size = ComputeSlotSize(installation_api, 1);
+ for (int i = 2; i < max_slots; i++) {
+ uint64_t slot_size = ComputeSlotSize(installation_api, i);
+ if (slot_size < min_slot_size) {
+ min_slot_size = slot_size;
+ }
+ }
+
+ return min_slot_size;
+}
} // namespace update_client
diff --git a/components/update_client/cobalt_slot_management.h b/components/update_client/cobalt_slot_management.h
index e5d185c..167d628 100644
--- a/components/update_client/cobalt_slot_management.h
+++ b/components/update_client/cobalt_slot_management.h
@@ -76,6 +76,22 @@
const CobaltExtensionInstallationManagerApi* installation_api,
const base::Version& current_version);
+// Computes whether Cobalt should skip the update based on the
+// |min_free_space_bytes|, the |free_space_bytes| and the amount of
+// space that can be recovered from |installation_cleanup_size|.
+// The default behavior is to NOT skip unless the measurements
+// show that there isn't enough space to perform the update.
+bool CobaltSkipUpdate(
+ const CobaltExtensionInstallationManagerApi* installation_api,
+ uint64_t min_free_space_bytes,
+ int64_t free_space_bytes,
+ uint64_t installation_cleanup_size);
+
+// Computes the installation cleanup size by taking the min space
+// from all the installation slots excluding the system slot 0.
+uint64_t CobaltInstallationCleanupSize(
+ const CobaltExtensionInstallationManagerApi* installation_api);
+
} // namespace update_client
#endif // COMPONENTS_UPDATE_CLIENT_COBALT_SLOT_MANAGEMENT_H_
diff --git a/components/update_client/cobalt_slot_management_test.cc b/components/update_client/cobalt_slot_management_test.cc
index 55256de..01aeb9a 100644
--- a/components/update_client/cobalt_slot_management_test.cc
+++ b/components/update_client/cobalt_slot_management_test.cc
@@ -14,7 +14,11 @@
#include "components/update_client/cobalt_slot_management.h"
+#include <algorithm>
+#include <vector>
+
#include "base/strings/string_util.h"
+#include "cobalt/extension/free_space.h"
#include "starboard/common/file.h"
#include "starboard/loader_app/app_key_files.h"
#include "starboard/loader_app/drain_file.h"
@@ -45,7 +49,7 @@
class CobaltSlotManagementTest : public testing::Test {
protected:
- virtual void SetUp() override {
+ void SetUp() override {
std::vector<char> buf(kSbFileMaxPath);
storage_path_implemented_ = SbSystemGetPath(kSbSystemPathStorageDirectory,
buf.data(), kSbFileMaxPath);
@@ -63,7 +67,7 @@
kCobaltExtensionInstallationManagerName));
}
- virtual void TearDown() override {
+ void TearDown() override {
starboard::SbFileDeleteRecursive(storage_path_.c_str(), true);
ImUninitialize();
}
@@ -229,6 +233,55 @@
base::Version version("1.0.0");
ASSERT_FALSE(CobaltQuickUpdate(api_, version));
}
+
+TEST_F(CobaltSlotManagementTest, CobaltSkipUpdateNoInstallationMoreSpace) {
+ ASSERT_FALSE(
+ CobaltSkipUpdate(api_, 1024 /* min */, 1025 /* free */, 0 /* cleanup */));
+}
+
+TEST_F(CobaltSlotManagementTest, CobaltSkipUpdateNoInstallationExactSpace) {
+ ASSERT_FALSE(
+ CobaltSkipUpdate(api_, 1024 /* min */, 1024 /* free */, 0 /* cleanup */));
+}
+
+TEST_F(CobaltSlotManagementTest, CobaltSkipUpdateNoInstallationNotEnoughSpace) {
+ ASSERT_TRUE(
+ CobaltSkipUpdate(api_, 1024 /* min */, 1023 /* free */, 0 /* cleanup */));
+}
+
+TEST_F(CobaltSlotManagementTest, CobaltSkipUpdateWithInstallationMoreSpace) {
+ ASSERT_FALSE(
+ CobaltSkipUpdate(api_, 1024 /* min */, 1024 /* free */, 1 /* cleanup */));
+}
+
+TEST_F(CobaltSlotManagementTest, CobaltSkipUpdateWithInstallationExactSpace) {
+ ASSERT_FALSE(
+ CobaltSkipUpdate(api_, 1024 /* min */, 1023 /* free */, 1 /* cleanup */));
+}
+
+TEST_F(CobaltSlotManagementTest,
+ CobaltSkipUpdateWithInstallationNotEnoughSpace) {
+ ASSERT_TRUE(
+ CobaltSkipUpdate(api_, 1024 /* min */, 1022 /* free */, 1 /* cleanup */));
+}
+
+TEST_F(CobaltSlotManagementTest, CobaltInstallationCleanupSizeNoInstallation) {
+ uint64_t size = CobaltInstallationCleanupSize(api_);
+ ASSERT_EQ(size, 0);
+}
+
+TEST_F(CobaltSlotManagementTest,
+ CobaltInstallationCleanupSizeTwoInstallations) {
+ int len1 = strlen(kManifestV1);
+ int len2 = strlen(kManifestV2);
+ ASSERT_NE(len1, len2);
+
+ CreateManifest("installation_1", kManifestV2, len1);
+ CreateManifest("installation_2", kManifestV1, len2);
+
+ uint64_t size = CobaltInstallationCleanupSize(api_);
+ ASSERT_EQ(size, std::min(len1, len2));
+}
} // namespace
} // namespace update_client
diff --git a/components/update_client/component.cc b/components/update_client/component.cc
index 8b4fa5d..6570995 100644
--- a/components/update_client/component.cc
+++ b/components/update_client/component.cc
@@ -215,8 +215,14 @@
// When there is an error unpacking the downloaded CRX, such as a failure to
// verify the package, we should remember to clear out any drain files.
if (base::DirectoryExists(crx_path.DirName())) {
- CobaltSlotManagement cobalt_slot_management;
- cobalt_slot_management.CleanupAllDrainFiles(crx_path.DirName());
+ const auto installation_api =
+ static_cast<const CobaltExtensionInstallationManagerApi*>(
+ SbSystemGetExtension(kCobaltExtensionInstallationManagerName));
+ if (installation_api) {
+ CobaltSlotManagement cobalt_slot_management;
+ cobalt_slot_management.Init(installation_api);
+ cobalt_slot_management.CleanupAllDrainFiles(crx_path.DirName());
+ }
}
#endif
main_task_runner->PostTask(
diff --git a/components/update_client/configurator.h b/components/update_client/configurator.h
index 16fd982..e4619e9 100644
--- a/components/update_client/configurator.h
+++ b/components/update_client/configurator.h
@@ -180,6 +180,8 @@
// Compare and swap the is_channel_changed flag.
virtual void CompareAndSwapChannelChanged(int old_value, int new_value) = 0;
+ virtual void SetMinFreeSpaceBytes(uint64_t bytes) = 0;
+ virtual uint64_t GetMinFreeSpaceBytes() = 0;
#endif
protected:
diff --git a/components/update_client/test_configurator.h b/components/update_client/test_configurator.h
index 6a9e5eb..9b37687 100644
--- a/components/update_client/test_configurator.h
+++ b/components/update_client/test_configurator.h
@@ -135,6 +135,10 @@
std::string GetPreviousUpdaterStatus() const override { return ""; }
void SetPreviousUpdaterStatus(const std::string& status) override {}
+
+ void SetMinFreeSpaceBytes(uint64_t bytes) override {}
+
+ uint64_t GetMinFreeSpaceBytes() override { return 0; }
#else
network::TestURLLoaderFactory* test_url_loader_factory() {
return &test_url_loader_factory_;
diff --git a/components/update_client/update_checker.cc b/components/update_client/update_checker.cc
index 4951abf..d0d77a6 100644
--- a/components/update_client/update_checker.cc
+++ b/components/update_client/update_checker.cc
@@ -22,11 +22,11 @@
#include "base/threading/thread_checker.h"
#if defined(STARBOARD)
#include "base/threading/thread_id_name_manager.h"
+#include "cobalt/extension/free_space.h"
#endif
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#if defined(STARBOARD)
-#include "cobalt/extension/installation_manager.h"
#include "cobalt/updater/utils.h"
#include "components/update_client/cobalt_slot_management.h"
#endif
@@ -102,7 +102,8 @@
const base::flat_map<std::string, std::string>& additional_attributes,
bool enabled_component_updates);
#if defined(STARBOARD)
- void Cancel();
+ void Cancel() ;
+ virtual bool SkipUpdate(const CobaltExtensionInstallationManagerApi* installation_api);
#endif
void OnRequestSenderComplete(int error,
const std::string& response,
@@ -228,6 +229,23 @@
base::Version current_version = crx_component->version;
#if defined(STARBOARD)
+ // Check if there is an available update already for quick roll-forward
+ auto installation_api =
+ static_cast<const CobaltExtensionInstallationManagerApi*>(
+ SbSystemGetExtension(kCobaltExtensionInstallationManagerName));
+ if (!installation_api) {
+ LOG(ERROR) << "UpdaterChecker: "
+ << "Failed to get installation manager extension.";
+ return;
+ }
+
+ if (SkipUpdate(installation_api)) {
+ LOG(WARNING) << "UpdaterChecker is skipping";
+ UpdateCheckFailed(ErrorCategory::kUpdateCheck,
+ static_cast<int>(UpdateCheckError::OUT_OF_SPACE), -1);
+ return;
+ }
+
std::string unpacked_version =
GetPersistedData()->GetLastUnpackedVersion(app_id);
// If the version of the last unpacked update package is higher than the
@@ -238,15 +256,6 @@
current_version = base::Version(unpacked_version);
}
- // Check if there is an available update already for quick roll-forward
- auto installation_api =
- static_cast<const CobaltExtensionInstallationManagerApi*>(
- SbSystemGetExtension(kCobaltExtensionInstallationManagerName));
- if (!installation_api) {
- LOG(ERROR) << "Failed to get installation manager extension.";
- return;
- }
-
if (CobaltQuickUpdate(installation_api, current_version)) {
// The last parameter in UpdateCheckFailed below, which is to be passed to
// update_check_callback_, indicates a throttling by the update server.
@@ -305,6 +314,24 @@
request_sender_->Cancel();
}
}
+
+bool UpdateCheckerImpl::SkipUpdate(
+ const CobaltExtensionInstallationManagerApi* installation_api) {
+ auto free_space_ext = static_cast<const CobaltExtensionFreeSpaceApi*>(
+ SbSystemGetExtension(kCobaltExtensionFreeSpaceName));
+ if (!installation_api) {
+ LOG(WARNING) << "UpdaterChecker::SkipUpdate: missing installation api";
+ return false;
+ }
+ if (!free_space_ext) {
+ LOG(WARNING) << "UpdaterChecker::SkipUpdate: No FreeSpace Cobalt extension";
+ return false;
+ }
+
+ return CobaltSkipUpdate(installation_api, config_->GetMinFreeSpaceBytes(),
+ free_space_ext->MeasureFreeSpace(kSbSystemPathStorageDirectory),
+ CobaltInstallationCleanupSize(installation_api)) ;
+}
#endif
void UpdateCheckerImpl::OnRequestSenderComplete(int error,
diff --git a/components/update_client/update_checker.h b/components/update_client/update_checker.h
index 78eb968..7c7c062 100644
--- a/components/update_client/update_checker.h
+++ b/components/update_client/update_checker.h
@@ -18,6 +18,10 @@
#include "components/update_client/protocol_parser.h"
#include "url/gurl.h"
+#if defined(STARBOARD)
+#include "cobalt/extension/installation_manager.h"
+#endif
+
namespace update_client {
class Configurator;
@@ -53,6 +57,7 @@
#if defined(STARBOARD)
virtual void Cancel() = 0;
+ virtual bool SkipUpdate(const CobaltExtensionInstallationManagerApi* installation_api) = 0;
#endif
static std::unique_ptr<UpdateChecker> Create(
diff --git a/components/update_client/update_client.cc b/components/update_client/update_client.cc
index 0ba76cd..4d6dfb8 100644
--- a/components/update_client/update_client.cc
+++ b/components/update_client/update_client.cc
@@ -79,7 +79,8 @@
DCHECK(tasks_.empty());
#if defined(STARBOARD)
- LOG(INFO) << "UpdateClientImpl::~UpdateClientImpl: task_queue_.size=" << task_queue_.size() << " tasks.size=" << tasks_.size();
+ LOG(INFO) << "UpdateClientImpl::~UpdateClientImpl: task_queue_.size="
+ << task_queue_.size() << " tasks.size=" << tasks_.size();
#endif
config_ = nullptr;
diff --git a/components/update_client/update_client_errors.h b/components/update_client/update_client_errors.h
index db08a24..4193ace 100644
--- a/components/update_client/update_client_errors.h
+++ b/components/update_client/update_client_errors.h
@@ -105,6 +105,7 @@
// Using 21 that doesn't conflict with the exsiting error codes and stays away
// from the other codes below 20.
QUICK_ROLL_FORWARD = 21,
+ OUT_OF_SPACE = 22,
};
#endif
diff --git a/crypto/BUILD.gn b/crypto/BUILD.gn
index 3eff4fb..4c4de15 100644
--- a/crypto/BUILD.gn
+++ b/crypto/BUILD.gn
@@ -148,6 +148,7 @@
target(gtest_target_type, "crypto_unittests") {
testonly = true
+
sources = [
"aead_unittest.cc",
"ec_private_key_unittest.cc",
@@ -187,6 +188,8 @@
"//testing/gmock",
"//testing/gtest",
]
+
+ content_deps = [ "//third_party/icu:icudata" ]
}
# This has no sources in some cases so can't be a static library.
diff --git a/crypto/DEPS b/crypto/DEPS
new file mode 100644
index 0000000..859cc4e
--- /dev/null
+++ b/crypto/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+third_party/boringssl/src/include",
+]
diff --git a/crypto/OWNERS b/crypto/OWNERS
new file mode 100644
index 0000000..963d05e
--- /dev/null
+++ b/crypto/OWNERS
@@ -0,0 +1,6 @@
+agl@chromium.org
+davidben@chromium.org
+rsleevi@chromium.org
+
+# TEAM: net-dev@chromium.org
+# COMPONENT: Internals>Network>SSL
diff --git a/docker-compose.yml b/docker-compose.yml
index 075e283..3d9b34b 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -37,6 +37,7 @@
x-shared-build-env: &shared-build-env
NINJA_PARALLEL: ${NINJA_PARALLEL:-32}
IS_CI: ${IS_CI:-0}
+ IS_DOCKER: 1
services:
#### Tools
@@ -201,7 +202,6 @@
depends_on: [ build-android ]
environment:
<<: *shared-build-env
- IS_DOCKER: 1
PLATFORM: android-x86
CONFIG: ${CONFIG:-debug}
@@ -214,7 +214,6 @@
dockerfile: ./gn/Dockerfile
environment:
<<: *shared-build-env
- IS_DOCKER: 1
PLATFORM: android-x86
CONFIG: ${CONFIG:-debug}
TARGET_CPU: ${TARGET_CPU:-x86}
@@ -225,7 +224,6 @@
depends_on: [ build-android ]
environment:
<<: *shared-build-env
- IS_DOCKER: 1
PLATFORM: android-arm
CONFIG: ${CONFIG:-debug}
@@ -238,7 +236,6 @@
dockerfile: ./gn/Dockerfile
environment:
<<: *shared-build-env
- IS_DOCKER: 1
PLATFORM: android-arm
CONFIG: ${CONFIG:-debug}
TARGET_CPU: ${TARGET_CPU:-arm}
@@ -249,7 +246,6 @@
depends_on: [ build-android ]
environment:
<<: *shared-build-env
- IS_DOCKER: 1
PLATFORM: android-arm64
CONFIG: ${CONFIG:-debug}
@@ -262,7 +258,6 @@
dockerfile: ./gn/Dockerfile
environment:
<<: *shared-build-env
- IS_DOCKER: 1
PLATFORM: android-arm64
CONFIG: ${CONFIG:-debug}
TARGET_CPU: ${TARGET_CPU:-arm64}
diff --git a/docker/linux/android/Dockerfile b/docker/linux/android/Dockerfile
index 12e5b53..eb26f78 100644
--- a/docker/linux/android/Dockerfile
+++ b/docker/linux/android/Dockerfile
@@ -16,6 +16,7 @@
RUN apt update -qqy \
&& apt install -qqy --no-install-recommends \
+ libxml2-dev \
default-jdk \
g++-multilib \
&& /opt/clean-after-apt.sh
diff --git a/docker/linux/base/build/Dockerfile b/docker/linux/base/build/Dockerfile
index 1289fcb..7c464bb 100644
--- a/docker/linux/base/build/Dockerfile
+++ b/docker/linux/base/build/Dockerfile
@@ -81,5 +81,15 @@
&& echo ${CLANG_VER} >> ${TC_HOME}/cr_build_revision \
&& rm clang-${CLANG_VER}.tgz
+# === Install portable sccache binary
+ARG SCCACHE=sccache-dist-v0.2.15-x86_64-unknown-linux-musl.tar.gz
+RUN cd /tmp \
+ && curl -L -O https://github.com/mozilla/sccache/releases/download/v0.2.15/${SCCACHE} \
+ && tar xvzf ${SCCACHE} -C /usr/local/bin --strip-components=1 \
+ && mv /usr/local/bin/sccache-dist /usr/local/bin/sccache \
+ && chmod +x /usr/local/bin/sccache \
+ && rm -rf ${SCCACHE} \
+ && sccache --version
+
WORKDIR /code
CMD ["/usr/bin/python","--version"]
diff --git a/download_resources.py b/download_resources.py
index acd91dd..688f4e8 100644
--- a/download_resources.py
+++ b/download_resources.py
@@ -17,14 +17,13 @@
import logging
import os
import platform
-
-import tools.download_from_gcs as download_from_gcs
-
+import subprocess
try:
- import download_resources_internal
+ import urllib.request as urllib
except ImportError:
- logging.warning('Skipping internal tools.')
- download_resources_internal = None
+ import urllib2 as urllib
+
+from tools import download_from_gcs
def DownloadClangFormat(force=False):
@@ -46,12 +45,38 @@
clang_format_sha_path, force)
+def DownloadGerritCommitMsgHook(force=False):
+ git_dir = subprocess.check_output(['git', 'rev-parse', '--git-common-dir'
+ ]).strip().decode('utf-8')
+ git_commit_msg_hook_path = os.path.join(git_dir, 'hooks', 'commit-msg')
+
+ if not force and os.path.exists(git_commit_msg_hook_path):
+ logging.info('commit-msg hook found, skipping download.')
+ return
+
+ hook_url = 'https://gerrit-review.googlesource.com/tools/hooks/commit-msg'
+ try:
+ res = urllib.urlopen(hook_url)
+ except urllib.URLError:
+ # pylint:disable=import-outside-toplevel
+ from ssl import _create_unverified_context
+ context = _create_unverified_context()
+ res = urllib.urlopen(hook_url, context=context)
+
+ if not res:
+ logging.error('Could not fetch %s', hook_url)
+ return
+
+ with open(git_commit_msg_hook_path, 'wb') as fd:
+ fd.write(res.read())
+ download_from_gcs.AddExecutableBits(git_commit_msg_hook_path)
+ logging.info('Gerrit commit-msg hook installed.')
+
+
if __name__ == '__main__':
logging_format = '[%(levelname)s:%(filename)s:%(lineno)s] %(message)s'
logging.basicConfig(
level=logging.INFO, format=logging_format, datefmt='%H:%M:%S')
DownloadClangFormat()
-
- if download_resources_internal:
- download_resources_internal.main()
+ DownloadGerritCommitMsgHook()
diff --git a/glimp/.gitattributes b/glimp/.gitattributes
new file mode 100644
index 0000000..0f70607
--- /dev/null
+++ b/glimp/.gitattributes
@@ -0,0 +1,30 @@
+# These files are text and should be normalized (convert crlf > lf).
+*.bat text eol=lf
+*.cc text eol=lf
+*.cg text eol=lf
+*.cpp text eol=lf
+*.css text eol=lf
+*.gyp text eol=lf
+*.gypi text eol=lf
+*.h text eol=lf
+*.html text eol=lf
+*.idl text eol=lf
+*.js text eol=lf
+*.pump text eol=lf
+*.py text eol=lf
+*.sublime-project text eol=lf
+*.sublime-workspace text eol=lf
+*.template text eol=lf
+*.txt text eol=lf
+*.y text eol=lf
+.clang-format text eol=lf
+codereview.settings text eol=lf
+gyp_cobalt text eol=lf
+
+# Images should be treated as binary files.
+*.exe binary
+*.jpg binary
+*.mp4 binary
+*.png binary
+*.pyc binary
+*.ttf binary
diff --git a/glimp/BUILD.gn b/glimp/BUILD.gn
new file mode 100644
index 0000000..cd7670f
--- /dev/null
+++ b/glimp/BUILD.gn
@@ -0,0 +1,113 @@
+# Copyright 2021 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+config("glimp_config") {
+ include_dirs = [ "include" ]
+ defines = [
+ # There doesn't appear to be any way to use the C preprocessor to do
+ # string concatenation with the / character. This prevents us from using
+ # the preprocessor to assemble an include file path, so we have to do
+ # the concatenation here in GYP.
+ # http://stackoverflow.com/questions/29601786/c-preprocessor-building-a-path-string
+ "GLIMP_EGLPLATFORM_INCLUDE=\"../../$target_platform/eglplatform_public.h\"",
+ "GLIMP_KHRPLATFORM_INCLUDE=\"../../$target_platform/khrplatform_public.h\"",
+
+ # Uncomment the define below to enable and use tracing inside glimp.
+ # "ENABLE_GLIMP_TRACING",
+ ]
+}
+
+group("glimp") {
+ public_configs = [ ":glimp_config" ]
+ deps = [ "//glimp/${target_platform}:glimp_platform" ]
+}
+
+config("glimp_common_sources_public_config") {
+ configs = [ ":glimp_config" ]
+ include_dirs = [ "$target_platform/platform" ]
+}
+
+source_set("glimp_common_sources") {
+ check_includes = false
+ sources = [
+ "egl/attrib_map.cc",
+ "egl/attrib_map.h",
+ "egl/config.cc",
+ "egl/config.h",
+ "egl/display.cc",
+ "egl/display.h",
+ "egl/display_impl.h",
+ "egl/display_registry.cc",
+ "egl/display_registry.h",
+ "egl/error.cc",
+ "egl/error.h",
+ "egl/get_proc_address_impl.h",
+ "egl/scoped_egl_lock.cc",
+ "egl/scoped_egl_lock.h",
+ "egl/surface.cc",
+ "egl/surface.h",
+ "egl/surface_impl.h",
+ "entry_points/egl.cc",
+ "entry_points/egl_ext.cc",
+ "entry_points/gles_2_0.cc",
+ "entry_points/gles_2_0_ext.cc",
+ "entry_points/gles_3_0.cc",
+ "gles/blend_state.h",
+ "gles/buffer.cc",
+ "gles/buffer.h",
+ "gles/buffer_impl.h",
+ "gles/context.cc",
+ "gles/context.h",
+ "gles/context_impl.h",
+ "gles/convert_pixel_data.cc",
+ "gles/convert_pixel_data.h",
+ "gles/cull_face_state.h",
+ "gles/draw_mode.h",
+ "gles/draw_state.cc",
+ "gles/draw_state.h",
+ "gles/framebuffer.cc",
+ "gles/framebuffer.h",
+ "gles/index_data_type.h",
+ "gles/pixel_format.cc",
+ "gles/pixel_format.h",
+ "gles/program.cc",
+ "gles/program.h",
+ "gles/program_impl.h",
+ "gles/ref_counted_resource_map.h",
+ "gles/renderbuffer.cc",
+ "gles/renderbuffer.h",
+ "gles/resource_manager.cc",
+ "gles/resource_manager.h",
+ "gles/sampler.h",
+ "gles/shader.cc",
+ "gles/shader.h",
+ "gles/shader_impl.h",
+ "gles/texture.cc",
+ "gles/texture.h",
+ "gles/texture_impl.h",
+ "gles/uniform_info.h",
+ "gles/unique_id_generator.cc",
+ "gles/unique_id_generator.h",
+ "gles/vertex_attribute.h",
+ "shaders/glsl_shader_map_helpers.h",
+ "shaders/hash_glsl_source.cc",
+ "shaders/hash_glsl_source.h",
+ ]
+ public_configs = [ ":glimp_common_sources_public_config" ]
+ deps = [
+ "//glimp/tracing",
+ "//nb",
+ "//starboard:starboard_headers_only",
+ ]
+}
diff --git a/glimp/shaders/generate_glsl_shader_map.py b/glimp/shaders/generate_glsl_shader_map.py
index 2ce1841..2f4efd1 100644
--- a/glimp/shaders/generate_glsl_shader_map.py
+++ b/glimp/shaders/generate_glsl_shader_map.py
@@ -1,5 +1,5 @@
#!/usr/bin/python
-# Copyright 2016 Google Inc. All Rights Reserved.
+# Copyright 2016 The Cobalt Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -12,7 +12,6 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-
"""Associates GLSL files with platform-specific shaders based on filename.
This script scans all input files and detects whether they are GLSL files or
@@ -54,9 +53,10 @@
"""Returns a string containing C++ that represents all file data in file."""
with open(input_file, 'rb') as f:
file_contents = f.read()
- def chunks(contents, chunk_size):
+
+ def Chunks(contents, chunk_size):
""" Yield successive |chunk_size|-sized chunks from |contents|."""
- for i in xrange(0, len(contents), chunk_size):
+ for i in range(0, len(contents), chunk_size):
yield contents[i:i + chunk_size]
# Break up the data into chunk sizes such that the produced output lines
@@ -68,9 +68,9 @@
# Convert each byte to ASCII hexadecimal form and output that to the C++
# header file, line-by-line.
data_definition_string = '{\n'
- for output_line_data in chunks(file_contents, chunk_size):
+ for output_line_data in Chunks(file_contents, chunk_size):
data_definition_string += (
- ' '.join(['0x%02x,' % ord(y) for y in output_line_data]) + '\n')
+ ' '.join(['0x%02x,' % ord(y) for y in output_line_data]) + '\n')
data_definition_string += '};\n\n'
return data_definition_string
@@ -81,8 +81,8 @@
data_definition_string = ''
for path in files:
input_file_variable_name = GetBasename(path)
- data_definition_string += (
- 'const uint8_t %s[] =\n' % input_file_variable_name)
+ data_definition_string += ('const uint8_t %s[] =\n' %
+ input_file_variable_name)
data_definition_string += GetHeaderDataDefinitionStringForFile(path)
return data_definition_string
@@ -114,7 +114,6 @@
return generate_map_function_string
-
def GetShaderNameFunctionString(hash_to_shader_map):
"""Generate C++ code to retrieve the shader name from a GLSL hash.
@@ -133,9 +132,8 @@
for k, v in hash_to_shader_map.iteritems():
input_file_variable_name = GetBasename(v)
- get_shader_name_function_string += (
- ' case %uU: return \"%s\";\n' %
- (k, input_file_variable_name))
+ get_shader_name_function_string += (' case %uU: return \"%s\";\n' %
+ (k, input_file_variable_name))
get_shader_name_function_string += ' }\n return NULL;\n}'
return get_shader_name_function_string
@@ -167,6 +165,7 @@
#endif // {include_guard}
"""
+
def GenerateHeaderFileOutput(output_file_name, hash_to_shader_map):
"""Generate the actual C++ header file code.
@@ -181,13 +180,13 @@
with open(output_file_name, 'w') as output_file:
output_file.write(
HEADER_FILE_TEMPLATE.format(
- include_guard = include_guard,
- data_definitions = GetHeaderDataDefinitionString(
- hash_to_shader_map.values()),
- generate_map_function = GetGenerateMapFunctionString(
- hash_to_shader_map),
- shader_name_function = GetShaderNameFunctionString(
- hash_to_shader_map)))
+ include_guard=include_guard,
+ data_definitions=GetHeaderDataDefinitionString(
+ hash_to_shader_map.values()),
+ generate_map_function=GetGenerateMapFunctionString(
+ hash_to_shader_map),
+ shader_name_function=GetShaderNameFunctionString(
+ hash_to_shader_map)))
def AssociateGLSLFilesWithPlatformFiles(all_shaders):
@@ -205,6 +204,7 @@
A dictionary mapping GLSL shader filenames to platform-specific shader
filenames.
"""
+
def IsGLSL(filename):
return os.path.splitext(filename)[1].upper() == '.GLSL'
@@ -225,12 +225,13 @@
if not IsGLSL(item):
basename = GetBasename(item)
if not basename in basename_to_glsl_file:
- raise Exception(
- 'Platform-specific file ' + item + ' has no GLSL match.')
+ raise Exception('Platform-specific file ' + item +
+ ' has no GLSL match.')
glsl_file = basename_to_glsl_file[basename]
if glsl_file in mapped_files:
- raise Exception('Multiple platform-specific files match the same GLSL '
- + 'file with basename ' + basename)
+ raise Exception(
+ 'Multiple platform-specific files match the same GLSL ' +
+ 'file with basename ' + basename)
mapped_files[glsl_file] = item
return mapped_files
@@ -255,6 +256,7 @@
def AddUint32(a, b):
return (a + b) & 0xffffffff
+
def XorUint32(a, b):
return (a ^ b) & 0xffffffff
@@ -263,7 +265,7 @@
# Introduce a generator function for returning only the hashable characters.
def GetHashableCharacters(hash_string):
str_without_comments = re.sub(r'//.*\n', '', hash_string)
- str_without_whitespace = re.sub(r'[ \n\t]', '', str_without_comments);
+ str_without_whitespace = re.sub(r'[ \n\t]', '', str_without_comments)
str_without_empty_braces = str_without_whitespace.replace('{}', '')
for c in str_without_empty_braces:
@@ -287,8 +289,8 @@
for k, v in glsl_to_shader_map.iteritems():
hashed_glsl = HashGLSLShaderFile(k)
if hashed_glsl in hash_shader_map:
- raise Exception('Hash collision between GLSL files ' + k + ' and '
- + hash_to_glsl_map[hashed_glsl] + '.')
+ raise Exception('Hash collision between GLSL files ' + k + ' and ' +
+ hash_to_glsl_map[hashed_glsl] + '.')
hash_to_glsl_map[hashed_glsl] = k
hash_shader_map[hashed_glsl] = v
return hash_shader_map
@@ -304,18 +306,19 @@
with open(input_files_filename) as input_files_file:
files = [x.strip() for x in input_files_file.readlines()]
- return [os.path.join(input_files_dir, x) for x in files]
+ # We filter out files that already have a full path (the case in GN).
+ return [os.path.join(input_files_dir, x) for x in files if '/' not in x]
def main(output_path, input_files_filename, input_files_dir):
all_shaders = GetAllShaderFiles(input_files_filename, input_files_dir)
- glsl_to_shader_map = AssociateGLSLFilesWithPlatformFiles(
- all_shaders)
+ glsl_to_shader_map = AssociateGLSLFilesWithPlatformFiles(all_shaders)
hash_to_shader_map = CreateHashToShaderMap(glsl_to_shader_map)
GenerateHeaderFileOutput(output_path, hash_to_shader_map)
+
if __name__ == '__main__':
main(sys.argv[1], sys.argv[2], sys.argv[3])
diff --git a/glimp/tracing/BUILD.gn b/glimp/tracing/BUILD.gn
new file mode 100644
index 0000000..d9605c1
--- /dev/null
+++ b/glimp/tracing/BUILD.gn
@@ -0,0 +1,21 @@
+# Copyright 2021 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+static_library("tracing") {
+ sources = [
+ "tracing.cc",
+ "tracing.h",
+ ]
+ configs += [ "//glimp:glimp_config" ]
+}
diff --git a/nb/BUILD.gn b/nb/BUILD.gn
index 2e19f21..6e9c036 100644
--- a/nb/BUILD.gn
+++ b/nb/BUILD.gn
@@ -68,6 +68,10 @@
"//starboard:starboard_headers_only",
"//starboard/common",
]
+
+ if (defined(has_nb_platform) && has_nb_platform) {
+ deps += [ "//nb/${target_platform}:nb_platform" ]
+ }
}
}
@@ -101,5 +105,7 @@
"//testing/gmock:gmock",
"//testing/gtest",
]
+
+ content_deps = [ "//third_party/icu:icudata" ]
}
}
diff --git a/net/BUILD.gn b/net/BUILD.gn
index 5ca254f..d27cb0b 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -2895,7 +2895,7 @@
"data/url_fetcher_impl_unittest/simple.html",
]
- outputs = [ "$root_build_dir/content/test/{{source_root_relative_dir}}/{{source_file_part}}" ]
+ outputs = [ "$sb_static_contents_output_data_dir/test/{{source_root_relative_dir}}/{{source_file_part}}" ]
}
copy("third_party_unittest_files") {
@@ -3480,746 +3480,746 @@
"third_party/nist-pkits/crls/WrongCRLCACRL.crl",
]
- outputs = [ "$root_build_dir/content/test/{{source_root_relative_dir}}/{{source_file_part}}" ]
+ outputs = [ "$sb_static_contents_output_data_dir/test/{{source_root_relative_dir}}/{{source_file_part}}" ]
}
-if (!is_win) {
- target(gtest_target_type, "net_unittests") {
- testonly = true
+target(gtest_target_type, "net_unittests") {
+ testonly = true
- sources = [
- "base/address_family_unittest.cc",
- "base/address_list_unittest.cc",
- "base/arena_unittest.cc",
- "base/backoff_entry_serializer_unittest.cc",
+ sources = [
+ "base/address_family_unittest.cc",
+ "base/address_list_unittest.cc",
+ "base/arena_unittest.cc",
+ "base/backoff_entry_serializer_unittest.cc",
- # Previously excluded via sources/.
- # "base/backoff_entry_unittest.cc",
- "base/chunked_upload_data_stream_unittest.cc",
- "base/data_url_unittest.cc",
- "base/datagram_buffer_unittest.cc",
- "base/directory_listing_unittest.cc",
- "base/elements_upload_data_stream_unittest.cc",
- "base/escape_unittest.cc",
- "base/expiring_cache_unittest.cc",
- "base/file_stream_unittest.cc",
- "base/filename_util_unittest.cc",
- "base/hex_utils_test.cc",
- "base/host_mapping_rules_unittest.cc",
- "base/host_port_pair_unittest.cc",
- "base/interval_set_test.cc",
- "base/interval_test.cc",
- "base/ip_address_unittest.cc",
- "base/ip_endpoint_unittest.cc",
- "base/ip_pattern_unittest.cc",
- "base/layered_network_delegate_unittest.cc",
- "base/lookup_string_in_fixed_set_unittest.cc",
- "base/mime_sniffer_unittest.cc",
- "base/mime_util_unittest.cc",
- "base/net_string_util_unittest.cc",
- "base/network_activity_monitor_unittest.cc",
- "base/network_change_notifier_unittest.cc",
- "base/parse_number_unittest.cc",
- "base/port_util_unittest.cc",
- "base/prioritized_dispatcher_unittest.cc",
- "base/prioritized_task_runner_unittest.cc",
- "base/priority_queue_unittest.cc",
- "base/registry_controlled_domains/registry_controlled_domain_unittest.cc",
- "base/static_cookie_policy_unittest.cc",
- "base/test_completion_callback_unittest.cc",
- "base/upload_bytes_element_reader_unittest.cc",
- "base/upload_file_element_reader_unittest.cc",
- "base/url_util_unittest.cc",
- "cert/caching_cert_verifier_unittest.cc",
- "cert/cert_verifier_unittest.cc",
+ # Previously excluded via sources/.
+ # "base/backoff_entry_unittest.cc",
+ "base/chunked_upload_data_stream_unittest.cc",
+ "base/data_url_unittest.cc",
+ "base/datagram_buffer_unittest.cc",
+ "base/directory_listing_unittest.cc",
+ "base/elements_upload_data_stream_unittest.cc",
+ "base/escape_unittest.cc",
+ "base/expiring_cache_unittest.cc",
+ "base/file_stream_unittest.cc",
+ "base/filename_util_unittest.cc",
+ "base/hex_utils_test.cc",
+ "base/host_mapping_rules_unittest.cc",
+ "base/host_port_pair_unittest.cc",
+ "base/interval_set_test.cc",
+ "base/interval_test.cc",
+ "base/ip_address_unittest.cc",
+ "base/ip_endpoint_unittest.cc",
+ "base/ip_pattern_unittest.cc",
+ "base/layered_network_delegate_unittest.cc",
+ "base/lookup_string_in_fixed_set_unittest.cc",
+ "base/mime_sniffer_unittest.cc",
+ "base/mime_util_unittest.cc",
+ "base/net_string_util_unittest.cc",
+ "base/network_activity_monitor_unittest.cc",
+ "base/network_change_notifier_unittest.cc",
+ "base/parse_number_unittest.cc",
+ "base/port_util_unittest.cc",
+ "base/prioritized_dispatcher_unittest.cc",
+ "base/prioritized_task_runner_unittest.cc",
+ "base/priority_queue_unittest.cc",
+ "base/registry_controlled_domains/registry_controlled_domain_unittest.cc",
+ "base/static_cookie_policy_unittest.cc",
+ "base/test_completion_callback_unittest.cc",
+ "base/upload_bytes_element_reader_unittest.cc",
+ "base/upload_file_element_reader_unittest.cc",
+ "base/url_util_unittest.cc",
+ "cert/caching_cert_verifier_unittest.cc",
+ "cert/cert_verifier_unittest.cc",
- # Previously excluded via sources/.
- # "cert/cert_verify_proc_unittest.cc",
- "cert/crl_set_unittest.cc",
- "cert/ct_log_response_parser_unittest.cc",
- "cert/ct_log_verifier_unittest.cc",
- "cert/ct_objects_extractor_unittest.cc",
- "cert/ct_serialization_unittest.cc",
- "cert/ev_root_ca_metadata_unittest.cc",
- "cert/internal/cert_issuer_source_aia_unittest.cc",
- "cert/internal/cert_issuer_source_static_unittest.cc",
- "cert/internal/cert_issuer_source_sync_unittest.h",
- "cert/internal/certificate_policies_unittest.cc",
- "cert/internal/extended_key_usage_unittest.cc",
+ # Previously excluded via sources/.
+ # "cert/cert_verify_proc_unittest.cc",
+ "cert/crl_set_unittest.cc",
+ "cert/ct_log_response_parser_unittest.cc",
+ "cert/ct_log_verifier_unittest.cc",
+ "cert/ct_objects_extractor_unittest.cc",
+ "cert/ct_serialization_unittest.cc",
+ "cert/ev_root_ca_metadata_unittest.cc",
+ "cert/internal/cert_issuer_source_aia_unittest.cc",
+ "cert/internal/cert_issuer_source_static_unittest.cc",
+ "cert/internal/cert_issuer_source_sync_unittest.h",
+ "cert/internal/certificate_policies_unittest.cc",
+ "cert/internal/extended_key_usage_unittest.cc",
- # "cert/x509_util_nss_unittest.cc",
- "cert/internal/general_names_unittest.cc",
- "cert/internal/name_constraints_unittest.cc",
- "cert/internal/nist_pkits_unittest.cc",
- "cert/internal/nist_pkits_unittest.h",
- "cert/internal/ocsp_unittest.cc",
- "cert/internal/parse_certificate_unittest.cc",
+ # "cert/x509_util_nss_unittest.cc",
+ "cert/internal/general_names_unittest.cc",
+ "cert/internal/name_constraints_unittest.cc",
+ "cert/internal/nist_pkits_unittest.cc",
+ "cert/internal/nist_pkits_unittest.h",
+ "cert/internal/ocsp_unittest.cc",
+ "cert/internal/parse_certificate_unittest.cc",
- # <certt.h> not found.
- # "cert_net/nss_ocsp_unittest.cc",
- "cert/internal/parse_name_unittest.cc",
- "cert/internal/parsed_certificate_unittest.cc",
+ # <certt.h> not found.
+ # "cert_net/nss_ocsp_unittest.cc",
+ "cert/internal/parse_name_unittest.cc",
+ "cert/internal/parsed_certificate_unittest.cc",
- # <cert.h> not found.
- # "cert/nss_cert_database_unittest.cc",
- "cert/internal/path_builder_pkits_unittest.cc",
- "cert/internal/path_builder_unittest.cc",
- "cert/internal/path_builder_verify_certificate_chain_unittest.cc",
- "cert/internal/revocation_checker_unittest.cc",
- "cert/internal/signature_algorithm_unittest.cc",
- "cert/internal/simple_path_builder_delegate_unittest.cc",
- "cert/internal/test_helpers.cc",
- "cert/internal/test_helpers.h",
- "cert/internal/trust_store_collection_unittest.cc",
+ # <cert.h> not found.
+ # "cert/nss_cert_database_unittest.cc",
+ "cert/internal/path_builder_pkits_unittest.cc",
+ "cert/internal/path_builder_unittest.cc",
+ "cert/internal/path_builder_verify_certificate_chain_unittest.cc",
+ "cert/internal/revocation_checker_unittest.cc",
+ "cert/internal/signature_algorithm_unittest.cc",
+ "cert/internal/simple_path_builder_delegate_unittest.cc",
+ "cert/internal/test_helpers.cc",
+ "cert/internal/test_helpers.h",
+ "cert/internal/trust_store_collection_unittest.cc",
- # "cert/internal/trust_store_nss_unittest.cc",
- "cert/internal/verify_certificate_chain_pkits_unittest.cc",
- "cert/internal/verify_certificate_chain_typed_unittest.h",
- "cert/internal/verify_certificate_chain_unittest.cc",
- "cert/internal/verify_name_match_unittest.cc",
- "cert/internal/verify_signed_data_unittest.cc",
- "cert/jwk_serializer_unittest.cc",
- "cert/known_roots_unittest.cc",
- "cert/merkle_audit_proof_unittest.cc",
- "cert/merkle_tree_leaf_unittest.cc",
- "cert/multi_threaded_cert_verifier_unittest.cc",
- "cert/pem_tokenizer_unittest.cc",
- "cert/signed_certificate_timestamp_unittest.cc",
- "cert/symantec_certs_unittest.cc",
- "cert/test_root_certs_unittest.cc",
- "cert/x509_cert_types_unittest.cc",
- "cert/x509_certificate_unittest.cc",
- "cert/x509_util_unittest.cc",
- "cert_net/cert_net_fetcher_impl_unittest.cc",
- "cookies/canonical_cookie_unittest.cc",
- "cookies/cookie_constants_unittest.cc",
- "cookies/cookie_deletion_info_unittest.cc",
- "cookies/cookie_monster_unittest.cc",
- "cookies/cookie_util_unittest.cc",
- "cookies/parsed_cookie_unittest.cc",
- "der/encode_values_unittest.cc",
- "der/input_unittest.cc",
- "der/parse_values_unittest.cc",
- "der/parser_unittest.cc",
- "dns/dns_config_service_unittest.cc",
- "dns/dns_hosts_unittest.cc",
- "dns/dns_query_unittest.cc",
- "dns/dns_response_unittest.cc",
- "dns/dns_session_unittest.cc",
- "dns/dns_socket_pool_unittest.cc",
- "dns/dns_transaction_unittest.cc",
- "dns/dns_util_unittest.cc",
- "dns/host_cache_unittest.cc",
- "dns/host_resolver_impl_unittest.cc",
- "dns/mapped_host_resolver_unittest.cc",
- "dns/record_parsed_unittest.cc",
- "dns/record_rdata_unittest.cc",
- "dns/serial_worker_unittest.cc",
+ # "cert/internal/trust_store_nss_unittest.cc",
+ "cert/internal/verify_certificate_chain_pkits_unittest.cc",
+ "cert/internal/verify_certificate_chain_typed_unittest.h",
+ "cert/internal/verify_certificate_chain_unittest.cc",
+ "cert/internal/verify_name_match_unittest.cc",
+ "cert/internal/verify_signed_data_unittest.cc",
+ "cert/jwk_serializer_unittest.cc",
+ "cert/known_roots_unittest.cc",
+ "cert/merkle_audit_proof_unittest.cc",
+ "cert/merkle_tree_leaf_unittest.cc",
+ "cert/multi_threaded_cert_verifier_unittest.cc",
+ "cert/pem_tokenizer_unittest.cc",
+ "cert/signed_certificate_timestamp_unittest.cc",
+ "cert/symantec_certs_unittest.cc",
+ "cert/test_root_certs_unittest.cc",
+ "cert/x509_cert_types_unittest.cc",
+ "cert/x509_certificate_unittest.cc",
+ "cert/x509_util_unittest.cc",
+ "cert_net/cert_net_fetcher_impl_unittest.cc",
+ "cookies/canonical_cookie_unittest.cc",
+ "cookies/cookie_constants_unittest.cc",
+ "cookies/cookie_deletion_info_unittest.cc",
+ "cookies/cookie_monster_unittest.cc",
+ "cookies/cookie_util_unittest.cc",
+ "cookies/parsed_cookie_unittest.cc",
+ "der/encode_values_unittest.cc",
+ "der/input_unittest.cc",
+ "der/parse_values_unittest.cc",
+ "der/parser_unittest.cc",
+ "dns/dns_config_service_unittest.cc",
+ "dns/dns_hosts_unittest.cc",
+ "dns/dns_query_unittest.cc",
+ "dns/dns_response_unittest.cc",
+ "dns/dns_session_unittest.cc",
+ "dns/dns_socket_pool_unittest.cc",
+ "dns/dns_transaction_unittest.cc",
+ "dns/dns_util_unittest.cc",
+ "dns/host_cache_unittest.cc",
+ "dns/host_resolver_impl_unittest.cc",
+ "dns/mapped_host_resolver_unittest.cc",
+ "dns/record_parsed_unittest.cc",
+ "dns/record_rdata_unittest.cc",
+ "dns/serial_worker_unittest.cc",
- # dial is a legacy component we kept from old Chromium net.
- "dial/dial_http_server_unittest.cc",
- "dial/dial_service_unittest.cc",
- "dial/dial_test_helpers.h",
- "dial/dial_udp_server_unittests.cc",
+ # dial is a legacy component we kept from old Chromium net.
+ "dial/dial_http_server_unittest.cc",
+ "dial/dial_service_unittest.cc",
+ "dial/dial_test_helpers.h",
+ "dial/dial_udp_server_unittests.cc",
- # disk_cache component is disabled because only http cache depends on
- # it and Cobalt does not support http cache yet.
- # "disk_cache/backend_cleanup_tracker_unittest.cc",
- # "disk_cache/backend_unittest.cc",
- # "disk_cache/blockfile/addr_unittest.cc",
- # "disk_cache/blockfile/bitmap_unittest.cc",
- # "disk_cache/blockfile/block_files_unittest.cc",
- # "disk_cache/blockfile/mapped_file_unittest.cc",
- # "disk_cache/blockfile/stats_unittest.cc",
- # "disk_cache/blockfile/storage_block_unittest.cc",
- # "disk_cache/cache_util_unittest.cc",
- # "disk_cache/entry_unittest.cc",
- # "disk_cache/simple/simple_file_tracker_unittest.cc",
- # "disk_cache/simple/simple_index_file_unittest.cc",
- # "disk_cache/simple/simple_index_unittest.cc",
- # "disk_cache/simple/simple_test_util.cc",
- # "disk_cache/simple/simple_test_util.h",
- # "disk_cache/simple/simple_util_unittest.cc",
- # "disk_cache/simple/simple_version_upgrade_unittest.cc",
+ # disk_cache component is disabled because only http cache depends on
+ # it and Cobalt does not support http cache yet.
+ # "disk_cache/backend_cleanup_tracker_unittest.cc",
+ # "disk_cache/backend_unittest.cc",
+ # "disk_cache/blockfile/addr_unittest.cc",
+ # "disk_cache/blockfile/bitmap_unittest.cc",
+ # "disk_cache/blockfile/block_files_unittest.cc",
+ # "disk_cache/blockfile/mapped_file_unittest.cc",
+ # "disk_cache/blockfile/stats_unittest.cc",
+ # "disk_cache/blockfile/storage_block_unittest.cc",
+ # "disk_cache/cache_util_unittest.cc",
+ # "disk_cache/entry_unittest.cc",
+ # "disk_cache/simple/simple_file_tracker_unittest.cc",
+ # "disk_cache/simple/simple_index_file_unittest.cc",
+ # "disk_cache/simple/simple_index_unittest.cc",
+ # "disk_cache/simple/simple_test_util.cc",
+ # "disk_cache/simple/simple_test_util.h",
+ # "disk_cache/simple/simple_util_unittest.cc",
+ # "disk_cache/simple/simple_version_upgrade_unittest.cc",
- # # Cobalt probably don"t need these.
- # # "extras/sqlite/sqlite_channel_id_store_unittest.cc",
- # # "extras/sqlite/sqlite_persistent_cookie_store_unittest.cc",
+ # # Cobalt probably don"t need these.
+ # # "extras/sqlite/sqlite_channel_id_store_unittest.cc",
+ # # "extras/sqlite/sqlite_persistent_cookie_store_unittest.cc",
- "filter/brotli_source_stream_unittest.cc",
- "filter/filter_source_stream_unittest.cc",
- "filter/gzip_source_stream_unittest.cc",
+ "filter/brotli_source_stream_unittest.cc",
+ "filter/filter_source_stream_unittest.cc",
+ "filter/gzip_source_stream_unittest.cc",
- # Cobalt does not support ftp.
- # "ftp/ftp_auth_cache_unittest.cc",
- # "ftp/ftp_ctrl_response_buffer_unittest.cc",
- # "ftp/ftp_directory_listing_parser_ls_unittest.cc",
- # "ftp/ftp_directory_listing_parser_unittest.cc",
- # "ftp/ftp_directory_listing_parser_unittest.h",
- # "ftp/ftp_directory_listing_parser_vms_unittest.cc",
- # "ftp/ftp_directory_listing_parser_windows_unittest.cc",
- # "ftp/ftp_network_transaction_unittest.cc",
- # "ftp/ftp_util_unittest.cc",
+ # Cobalt does not support ftp.
+ # "ftp/ftp_auth_cache_unittest.cc",
+ # "ftp/ftp_ctrl_response_buffer_unittest.cc",
+ # "ftp/ftp_directory_listing_parser_ls_unittest.cc",
+ # "ftp/ftp_directory_listing_parser_unittest.cc",
+ # "ftp/ftp_directory_listing_parser_unittest.h",
+ # "ftp/ftp_directory_listing_parser_vms_unittest.cc",
+ # "ftp/ftp_directory_listing_parser_windows_unittest.cc",
+ # "ftp/ftp_network_transaction_unittest.cc",
+ # "ftp/ftp_util_unittest.cc",
- "http/bidirectional_stream_unittest.cc",
- "http/broken_alternative_services_unittest.cc",
- "http/http_auth_cache_unittest.cc",
- "http/http_auth_challenge_tokenizer_unittest.cc",
- "http/http_auth_controller_unittest.cc",
- "http/http_auth_filter_unittest.cc",
- "http/http_auth_handler_basic_unittest.cc",
- "http/http_auth_handler_digest_unittest.cc",
- "http/http_auth_handler_factory_unittest.cc",
- "http/http_auth_handler_mock.cc",
- "http/http_auth_handler_mock.h",
+ "http/bidirectional_stream_unittest.cc",
+ "http/broken_alternative_services_unittest.cc",
+ "http/http_auth_cache_unittest.cc",
+ "http/http_auth_challenge_tokenizer_unittest.cc",
+ "http/http_auth_controller_unittest.cc",
+ "http/http_auth_filter_unittest.cc",
+ "http/http_auth_handler_basic_unittest.cc",
+ "http/http_auth_handler_digest_unittest.cc",
+ "http/http_auth_handler_factory_unittest.cc",
+ "http/http_auth_handler_mock.cc",
+ "http/http_auth_handler_mock.h",
- # Cobalt does not support Kerberos(gssapi) yet.
- # "http/http_auth_handler_negotiate_unittest.cc",
- "http/http_auth_handler_ntlm_portable_unittest.cc",
- "http/http_auth_handler_unittest.cc",
- "http/http_auth_multi_round_parse_unittest.cc",
- "http/http_auth_preferences_unittest.cc",
- "http/http_auth_unittest.cc",
- "http/http_basic_state_unittest.cc",
- "http/http_byte_range_unittest.cc",
+ # Cobalt does not support Kerberos(gssapi) yet.
+ # "http/http_auth_handler_negotiate_unittest.cc",
+ "http/http_auth_handler_ntlm_portable_unittest.cc",
+ "http/http_auth_handler_unittest.cc",
+ "http/http_auth_multi_round_parse_unittest.cc",
+ "http/http_auth_preferences_unittest.cc",
+ "http/http_auth_unittest.cc",
+ "http/http_basic_state_unittest.cc",
+ "http/http_byte_range_unittest.cc",
- # "http/http_cache_lookup_manager_unittest.cc",
- # "http/http_cache_unittest.cc",
- # "http/http_cache_writers_unittest.cc",
- # "url_request/view_cache_helper_unittest.cc",
- "http/http_chunked_decoder_unittest.cc",
+ # "http/http_cache_lookup_manager_unittest.cc",
+ # "http/http_cache_unittest.cc",
+ # "http/http_cache_writers_unittest.cc",
+ # "url_request/view_cache_helper_unittest.cc",
+ "http/http_chunked_decoder_unittest.cc",
- # Known ICU issue.
- # "http/http_content_disposition_unittest.cc",
- "http/http_log_util_unittest.cc",
- "http/http_network_layer_unittest.cc",
- "http/http_network_transaction_ssl_unittest.cc",
- "http/http_network_transaction_unittest.cc",
- "http/http_proxy_client_socket_pool_unittest.cc",
- "http/http_proxy_client_socket_unittest.cc",
- "http/http_proxy_client_socket_wrapper_unittest.cc",
- "http/http_request_headers_unittest.cc",
- "http/http_response_body_drainer_unittest.cc",
- "http/http_response_headers_unittest.cc",
- "http/http_response_info_unittest.cc",
- "http/http_security_headers_unittest.cc",
- "http/http_server_properties_impl_unittest.cc",
- "http/http_server_properties_manager_unittest.cc",
- "http/http_status_code_unittest.cc",
- "http/http_stream_factory_job_controller_unittest.cc",
- "http/http_stream_factory_unittest.cc",
- "http/http_stream_parser_unittest.cc",
- "http/http_stream_request_unittest.cc",
- "http/http_util_unittest.cc",
- "http/http_vary_data_unittest.cc",
- "http/mock_allow_http_auth_preferences.cc",
- "http/mock_allow_http_auth_preferences.h",
+ # Known ICU issue.
+ # "http/http_content_disposition_unittest.cc",
+ "http/http_log_util_unittest.cc",
+ "http/http_network_layer_unittest.cc",
+ "http/http_network_transaction_ssl_unittest.cc",
+ "http/http_network_transaction_unittest.cc",
+ "http/http_proxy_client_socket_pool_unittest.cc",
+ "http/http_proxy_client_socket_unittest.cc",
+ "http/http_proxy_client_socket_wrapper_unittest.cc",
+ "http/http_request_headers_unittest.cc",
+ "http/http_response_body_drainer_unittest.cc",
+ "http/http_response_headers_unittest.cc",
+ "http/http_response_info_unittest.cc",
+ "http/http_security_headers_unittest.cc",
+ "http/http_server_properties_impl_unittest.cc",
+ "http/http_server_properties_manager_unittest.cc",
+ "http/http_status_code_unittest.cc",
+ "http/http_stream_factory_job_controller_unittest.cc",
+ "http/http_stream_factory_unittest.cc",
+ "http/http_stream_parser_unittest.cc",
+ "http/http_stream_request_unittest.cc",
+ "http/http_util_unittest.cc",
+ "http/http_vary_data_unittest.cc",
+ "http/mock_allow_http_auth_preferences.cc",
+ "http/mock_allow_http_auth_preferences.h",
- # "http/mock_http_cache.cc",
- # "http/mock_http_cache.h",
- # Main test target not enabled yet.
- # "http/transport_security_persister_unittest.cc",
- # "log/file_net_log_observer_unittest.cc",
- "http/transport_security_state_unittest.cc",
- "http/url_security_manager_unittest.cc",
- "log/net_log_capture_mode_unittest.cc",
- "log/net_log_unittest.cc",
- "log/net_log_util_unittest.cc",
- "log/trace_net_log_observer_unittest.cc",
- "nqe/effective_connection_type_unittest.cc",
- "nqe/event_creator_unittest.cc",
- "nqe/network_id_unittest.cc",
- "nqe/network_qualities_prefs_manager_unittest.cc",
- "nqe/network_quality_estimator_params_unittest.cc",
- "nqe/network_quality_estimator_unittest.cc",
- "nqe/network_quality_estimator_util_unittest.cc",
- "nqe/network_quality_store_unittest.cc",
- "nqe/observation_buffer_unittest.cc",
- "nqe/socket_watcher_unittest.cc",
- "nqe/throughput_analyzer_unittest.cc",
- "ntlm/ntlm_buffer_reader_unittest.cc",
- "ntlm/ntlm_buffer_writer_unittest.cc",
- "ntlm/ntlm_client_unittest.cc",
- "ntlm/ntlm_test_data.h",
- "ntlm/ntlm_unittest.cc",
+ # "http/mock_http_cache.cc",
+ # "http/mock_http_cache.h",
+ # Main test target not enabled yet.
+ # "http/transport_security_persister_unittest.cc",
+ # "log/file_net_log_observer_unittest.cc",
+ "http/transport_security_state_unittest.cc",
+ "http/url_security_manager_unittest.cc",
+ "log/net_log_capture_mode_unittest.cc",
+ "log/net_log_unittest.cc",
+ "log/net_log_util_unittest.cc",
+ "log/trace_net_log_observer_unittest.cc",
+ "nqe/effective_connection_type_unittest.cc",
+ "nqe/event_creator_unittest.cc",
+ "nqe/network_id_unittest.cc",
+ "nqe/network_qualities_prefs_manager_unittest.cc",
+ "nqe/network_quality_estimator_params_unittest.cc",
+ "nqe/network_quality_estimator_unittest.cc",
+ "nqe/network_quality_estimator_util_unittest.cc",
+ "nqe/network_quality_store_unittest.cc",
+ "nqe/observation_buffer_unittest.cc",
+ "nqe/socket_watcher_unittest.cc",
+ "nqe/throughput_analyzer_unittest.cc",
+ "ntlm/ntlm_buffer_reader_unittest.cc",
+ "ntlm/ntlm_buffer_writer_unittest.cc",
+ "ntlm/ntlm_client_unittest.cc",
+ "ntlm/ntlm_test_data.h",
+ "ntlm/ntlm_unittest.cc",
- # "http/http_auth_gssapi_starboard_unittest.cc",
- "proxy_resolution/dhcp_pac_file_fetcher_factory_unittest.cc",
+ # "http/http_auth_gssapi_starboard_unittest.cc",
+ "proxy_resolution/dhcp_pac_file_fetcher_factory_unittest.cc",
- # mojo not used.
- # "proxy_resolution/mojo_proxy_resolver_v8_tracing_bindings_unittest.cc",
- "proxy_resolution/multi_threaded_proxy_resolver_unittest.cc",
- "proxy_resolution/network_delegate_error_observer_unittest.cc",
- "proxy_resolution/pac_file_decider_unittest.cc",
- "proxy_resolution/pac_file_fetcher_impl_unittest.cc",
- "proxy_resolution/proxy_bypass_rules_unittest.cc",
- "proxy_resolution/proxy_config_unittest.cc",
- "proxy_resolution/proxy_info_unittest.cc",
- "proxy_resolution/proxy_list_unittest.cc",
- "proxy_resolution/proxy_resolution_service_unittest.cc",
- "proxy_resolution/proxy_server_unittest.cc",
+ # mojo not used.
+ # "proxy_resolution/mojo_proxy_resolver_v8_tracing_bindings_unittest.cc",
+ "proxy_resolution/multi_threaded_proxy_resolver_unittest.cc",
+ "proxy_resolution/network_delegate_error_observer_unittest.cc",
+ "proxy_resolution/pac_file_decider_unittest.cc",
+ "proxy_resolution/pac_file_fetcher_impl_unittest.cc",
+ "proxy_resolution/proxy_bypass_rules_unittest.cc",
+ "proxy_resolution/proxy_config_unittest.cc",
+ "proxy_resolution/proxy_info_unittest.cc",
+ "proxy_resolution/proxy_list_unittest.cc",
+ "proxy_resolution/proxy_resolution_service_unittest.cc",
+ "proxy_resolution/proxy_server_unittest.cc",
- # not useful to us.
- # "proxy_resolution/proxy_resolver_v8_tracing_unittest.cc",
- # "proxy_resolution/proxy_resolver_v8_tracing_wrapper_unittest.cc",
- # "proxy_resolution/proxy_resolver_v8_unittest.cc",
- "quic/bidirectional_stream_quic_impl_unittest.cc",
- "quic/crypto/proof_test_chromium.cc",
- "quic/crypto/proof_verifier_chromium_test.cc",
- "quic/network_connection_unittest.cc",
- "quic/properties_based_quic_server_info_test.cc",
- "quic/quic_address_mismatch_test.cc",
- "quic/quic_chromium_alarm_factory_test.cc",
- "quic/quic_chromium_client_session_peer.cc",
- "quic/quic_chromium_client_session_peer.h",
- "quic/quic_chromium_client_session_test.cc",
- "quic/quic_chromium_client_stream_test.cc",
- "quic/quic_chromium_connection_helper_test.cc",
- "quic/quic_clock_skew_detector_test.cc",
- "quic/quic_connectivity_probing_manager_test.cc",
- "quic/quic_end_to_end_unittest.cc",
- "quic/quic_http_stream_test.cc",
- "quic/quic_http_utils_test.cc",
- "quic/quic_network_transaction_unittest.cc",
- "quic/quic_proxy_client_socket_unittest.cc",
- "quic/quic_stream_factory_peer.cc",
- "quic/quic_stream_factory_peer.h",
- "quic/quic_stream_factory_test.cc",
- "quic/quic_utils_chromium_test.cc",
- "socket/client_socket_pool_base_unittest.cc",
- "socket/mock_client_socket_pool_manager.cc",
- "socket/mock_client_socket_pool_manager.h",
- "socket/sequenced_socket_data_unittest.cc",
- "socket/socket_bio_adapter_unittest.cc",
- "socket/socket_tag_unittest.cc",
- "socket/socks5_client_socket_unittest.cc",
- "socket/socks_client_socket_pool_unittest.cc",
- "socket/socks_client_socket_unittest.cc",
- "socket/ssl_client_socket_pool_unittest.cc",
+ # not useful to us.
+ # "proxy_resolution/proxy_resolver_v8_tracing_unittest.cc",
+ # "proxy_resolution/proxy_resolver_v8_tracing_wrapper_unittest.cc",
+ # "proxy_resolution/proxy_resolver_v8_unittest.cc",
+ "quic/bidirectional_stream_quic_impl_unittest.cc",
+ "quic/crypto/proof_test_chromium.cc",
+ "quic/crypto/proof_verifier_chromium_test.cc",
+ "quic/network_connection_unittest.cc",
+ "quic/properties_based_quic_server_info_test.cc",
+ "quic/quic_address_mismatch_test.cc",
+ "quic/quic_chromium_alarm_factory_test.cc",
+ "quic/quic_chromium_client_session_peer.cc",
+ "quic/quic_chromium_client_session_peer.h",
+ "quic/quic_chromium_client_session_test.cc",
+ "quic/quic_chromium_client_stream_test.cc",
+ "quic/quic_chromium_connection_helper_test.cc",
+ "quic/quic_clock_skew_detector_test.cc",
+ "quic/quic_connectivity_probing_manager_test.cc",
+ "quic/quic_end_to_end_unittest.cc",
+ "quic/quic_http_stream_test.cc",
+ "quic/quic_http_utils_test.cc",
+ "quic/quic_network_transaction_unittest.cc",
+ "quic/quic_proxy_client_socket_unittest.cc",
+ "quic/quic_stream_factory_peer.cc",
+ "quic/quic_stream_factory_peer.h",
+ "quic/quic_stream_factory_test.cc",
+ "quic/quic_utils_chromium_test.cc",
+ "socket/client_socket_pool_base_unittest.cc",
+ "socket/mock_client_socket_pool_manager.cc",
+ "socket/mock_client_socket_pool_manager.h",
+ "socket/sequenced_socket_data_unittest.cc",
+ "socket/socket_bio_adapter_unittest.cc",
+ "socket/socket_tag_unittest.cc",
+ "socket/socks5_client_socket_unittest.cc",
+ "socket/socks_client_socket_pool_unittest.cc",
+ "socket/socks_client_socket_unittest.cc",
+ "socket/ssl_client_socket_pool_unittest.cc",
- # Previously excluded via sources/
- # "socket/ssl_client_socket_unittest.cc",
- "socket/ssl_server_socket_unittest.cc",
+ # Previously excluded via sources/
+ # "socket/ssl_client_socket_unittest.cc",
+ "socket/ssl_server_socket_unittest.cc",
- # Previously excluded via sources/
- # "socket/tcp_client_socket_unittest.cc",
- # "socket/tcp_server_socket_unittest.cc",
- "socket/tcp_socket_unittest.cc",
- "socket/transport_client_socket_pool_test_util.cc",
- "socket/transport_client_socket_pool_test_util.h",
- "socket/transport_client_socket_pool_unittest.cc",
- "socket/transport_client_socket_unittest.cc",
+ # Previously excluded via sources/
+ # "socket/tcp_client_socket_unittest.cc",
+ # "socket/tcp_server_socket_unittest.cc",
+ "socket/tcp_socket_unittest.cc",
+ "socket/transport_client_socket_pool_test_util.cc",
+ "socket/transport_client_socket_pool_test_util.h",
+ "socket/transport_client_socket_pool_unittest.cc",
+ "socket/transport_client_socket_unittest.cc",
- # Previously excluded via sources/
- # "socket/udp_socket_unittest.cc",
- "socket/websocket_endpoint_lock_manager_unittest.cc",
- "socket/websocket_transport_client_socket_pool_unittest.cc",
- "spdy/bidirectional_stream_spdy_impl_unittest.cc",
- "spdy/buffered_spdy_framer_unittest.cc",
- "spdy/header_coalescer_test.cc",
- "spdy/http2_priority_dependencies_unittest.cc",
- "spdy/http2_push_promise_index_test.cc",
- "spdy/spdy_buffer_unittest.cc",
- "spdy/spdy_http_stream_unittest.cc",
- "spdy/spdy_http_utils_unittest.cc",
- "spdy/spdy_log_util_unittest.cc",
- "spdy/spdy_network_transaction_unittest.cc",
- "spdy/spdy_proxy_client_socket_unittest.cc",
- "spdy/spdy_read_queue_unittest.cc",
- "spdy/spdy_session_pool_unittest.cc",
- "spdy/spdy_session_unittest.cc",
- "spdy/spdy_stream_unittest.cc",
- "spdy/spdy_write_queue_unittest.cc",
- "ssl/channel_id_service_unittest.cc",
- "ssl/client_cert_identity_unittest.cc",
- "ssl/client_cert_store_unittest-inl.h",
- "ssl/default_channel_id_store_unittest.cc",
- "ssl/ssl_cipher_suite_names_unittest.cc",
- "ssl/ssl_client_auth_cache_unittest.cc",
- "ssl/ssl_client_session_cache_unittest.cc",
- "ssl/ssl_config_service_unittest.cc",
- "ssl/ssl_config_unittest.cc",
- "ssl/ssl_connection_status_flags_unittest.cc",
- "ssl/ssl_platform_key_util_unittest.cc",
- "test/embedded_test_server/embedded_test_server_unittest.cc",
- "test/embedded_test_server/http_request_unittest.cc",
- "test/embedded_test_server/http_response_unittest.cc",
- "test/run_all_unittests.cc",
- "test/tcp_socket_proxy_unittest.cc",
- "third_party/nist-pkits/pkits_testcases-inl.h",
- "third_party/quic/core/congestion_control/bandwidth_sampler_test.cc",
- "third_party/quic/core/congestion_control/bbr_sender_test.cc",
- "third_party/quic/core/congestion_control/cubic_bytes_test.cc",
- "third_party/quic/core/congestion_control/general_loss_algorithm_test.cc",
- "third_party/quic/core/congestion_control/hybrid_slow_start_test.cc",
- "third_party/quic/core/congestion_control/pacing_sender_test.cc",
- "third_party/quic/core/congestion_control/prr_sender_test.cc",
- "third_party/quic/core/congestion_control/rtt_stats_test.cc",
- "third_party/quic/core/congestion_control/send_algorithm_test.cc",
- "third_party/quic/core/congestion_control/uber_loss_algorithm_test.cc",
- "third_party/quic/core/congestion_control/windowed_filter_test.cc",
- "third_party/quic/core/crypto/aes_128_gcm_12_decrypter_test.cc",
- "third_party/quic/core/crypto/aes_128_gcm_12_encrypter_test.cc",
- "third_party/quic/core/crypto/aes_128_gcm_decrypter_test.cc",
- "third_party/quic/core/crypto/aes_128_gcm_encrypter_test.cc",
- "third_party/quic/core/crypto/aes_256_gcm_decrypter_test.cc",
- "third_party/quic/core/crypto/aes_256_gcm_encrypter_test.cc",
- "third_party/quic/core/crypto/cert_compressor_test.cc",
- "third_party/quic/core/crypto/chacha20_poly1305_decrypter_test.cc",
- "third_party/quic/core/crypto/chacha20_poly1305_encrypter_test.cc",
- "third_party/quic/core/crypto/chacha20_poly1305_tls_decrypter_test.cc",
- "third_party/quic/core/crypto/chacha20_poly1305_tls_encrypter_test.cc",
- "third_party/quic/core/crypto/channel_id_test.cc",
+ # Previously excluded via sources/
+ # "socket/udp_socket_unittest.cc",
+ "socket/websocket_endpoint_lock_manager_unittest.cc",
+ "socket/websocket_transport_client_socket_pool_unittest.cc",
+ "spdy/bidirectional_stream_spdy_impl_unittest.cc",
+ "spdy/buffered_spdy_framer_unittest.cc",
+ "spdy/header_coalescer_test.cc",
+ "spdy/http2_priority_dependencies_unittest.cc",
+ "spdy/http2_push_promise_index_test.cc",
+ "spdy/spdy_buffer_unittest.cc",
+ "spdy/spdy_http_stream_unittest.cc",
+ "spdy/spdy_http_utils_unittest.cc",
+ "spdy/spdy_log_util_unittest.cc",
+ "spdy/spdy_network_transaction_unittest.cc",
+ "spdy/spdy_proxy_client_socket_unittest.cc",
+ "spdy/spdy_read_queue_unittest.cc",
+ "spdy/spdy_session_pool_unittest.cc",
+ "spdy/spdy_session_unittest.cc",
+ "spdy/spdy_stream_unittest.cc",
+ "spdy/spdy_write_queue_unittest.cc",
+ "ssl/channel_id_service_unittest.cc",
+ "ssl/client_cert_identity_unittest.cc",
+ "ssl/client_cert_store_unittest-inl.h",
+ "ssl/default_channel_id_store_unittest.cc",
+ "ssl/ssl_cipher_suite_names_unittest.cc",
+ "ssl/ssl_client_auth_cache_unittest.cc",
+ "ssl/ssl_client_session_cache_unittest.cc",
+ "ssl/ssl_config_service_unittest.cc",
+ "ssl/ssl_config_unittest.cc",
+ "ssl/ssl_connection_status_flags_unittest.cc",
+ "ssl/ssl_platform_key_util_unittest.cc",
+ "test/embedded_test_server/embedded_test_server_unittest.cc",
+ "test/embedded_test_server/http_request_unittest.cc",
+ "test/embedded_test_server/http_response_unittest.cc",
+ "test/run_all_unittests.cc",
+ "test/tcp_socket_proxy_unittest.cc",
+ "third_party/nist-pkits/pkits_testcases-inl.h",
+ "third_party/quic/core/congestion_control/bandwidth_sampler_test.cc",
+ "third_party/quic/core/congestion_control/bbr_sender_test.cc",
+ "third_party/quic/core/congestion_control/cubic_bytes_test.cc",
+ "third_party/quic/core/congestion_control/general_loss_algorithm_test.cc",
+ "third_party/quic/core/congestion_control/hybrid_slow_start_test.cc",
+ "third_party/quic/core/congestion_control/pacing_sender_test.cc",
+ "third_party/quic/core/congestion_control/prr_sender_test.cc",
+ "third_party/quic/core/congestion_control/rtt_stats_test.cc",
+ "third_party/quic/core/congestion_control/send_algorithm_test.cc",
+ "third_party/quic/core/congestion_control/uber_loss_algorithm_test.cc",
+ "third_party/quic/core/congestion_control/windowed_filter_test.cc",
+ "third_party/quic/core/crypto/aes_128_gcm_12_decrypter_test.cc",
+ "third_party/quic/core/crypto/aes_128_gcm_12_encrypter_test.cc",
+ "third_party/quic/core/crypto/aes_128_gcm_decrypter_test.cc",
+ "third_party/quic/core/crypto/aes_128_gcm_encrypter_test.cc",
+ "third_party/quic/core/crypto/aes_256_gcm_decrypter_test.cc",
+ "third_party/quic/core/crypto/aes_256_gcm_encrypter_test.cc",
+ "third_party/quic/core/crypto/cert_compressor_test.cc",
+ "third_party/quic/core/crypto/chacha20_poly1305_decrypter_test.cc",
+ "third_party/quic/core/crypto/chacha20_poly1305_encrypter_test.cc",
+ "third_party/quic/core/crypto/chacha20_poly1305_tls_decrypter_test.cc",
+ "third_party/quic/core/crypto/chacha20_poly1305_tls_encrypter_test.cc",
+ "third_party/quic/core/crypto/channel_id_test.cc",
- # "third_party/quic/core/crypto/common_cert_set_test.cc",
- "third_party/quic/core/crypto/crypto_framer_test.cc",
- "third_party/quic/core/crypto/crypto_handshake_message_test.cc",
- "third_party/quic/core/crypto/crypto_secret_boxer_test.cc",
- "third_party/quic/core/crypto/crypto_server_test.cc",
- "third_party/quic/core/crypto/crypto_utils_test.cc",
- "third_party/quic/core/crypto/curve25519_key_exchange_test.cc",
- "third_party/quic/core/crypto/null_decrypter_test.cc",
- "third_party/quic/core/crypto/null_encrypter_test.cc",
- "third_party/quic/core/crypto/p256_key_exchange_test.cc",
- "third_party/quic/core/crypto/quic_compressed_certs_cache_test.cc",
- "third_party/quic/core/crypto/quic_crypto_client_config_test.cc",
- "third_party/quic/core/crypto/quic_crypto_server_config_test.cc",
- "third_party/quic/core/crypto/quic_hkdf_test.cc",
- "third_party/quic/core/crypto/quic_random_test.cc",
- "third_party/quic/core/crypto/transport_parameters_test.cc",
- "third_party/quic/core/frames/quic_frames_test.cc",
- "third_party/quic/core/http/http_decoder_test.cc",
- "third_party/quic/core/http/http_encoder_test.cc",
- "third_party/quic/core/http/quic_client_promised_info_test.cc",
- "third_party/quic/core/http/quic_client_push_promise_index_test.cc",
- "third_party/quic/core/http/quic_header_list_test.cc",
- "third_party/quic/core/http/quic_headers_stream_test.cc",
- "third_party/quic/core/http/quic_server_session_base_test.cc",
- "third_party/quic/core/http/quic_spdy_session_test.cc",
- "third_party/quic/core/http/quic_spdy_stream_body_buffer_test.cc",
- "third_party/quic/core/http/quic_spdy_stream_test.cc",
- "third_party/quic/core/http/spdy_utils_test.cc",
- "third_party/quic/core/legacy_quic_stream_id_manager_test.cc",
- "third_party/quic/core/packet_number_indexed_queue_test.cc",
- "third_party/quic/core/qpack/qpack_decoded_headers_accumulator_test.cc",
- "third_party/quic/core/qpack/qpack_decoder_stream_receiver_test.cc",
- "third_party/quic/core/qpack/qpack_decoder_stream_sender_test.cc",
- "third_party/quic/core/qpack/qpack_decoder_test.cc",
- "third_party/quic/core/qpack/qpack_encoder_stream_receiver_test.cc",
- "third_party/quic/core/qpack/qpack_encoder_stream_sender_test.cc",
- "third_party/quic/core/qpack/qpack_encoder_test.cc",
- "third_party/quic/core/qpack/qpack_header_table_test.cc",
- "third_party/quic/core/qpack/qpack_instruction_decoder_test.cc",
- "third_party/quic/core/qpack/qpack_instruction_encoder_test.cc",
- "third_party/quic/core/qpack/qpack_progressive_decoder_test.cc",
- "third_party/quic/core/qpack/qpack_round_trip_test.cc",
- "third_party/quic/core/qpack/qpack_static_table_test.cc",
- "third_party/quic/core/quic_alarm_test.cc",
- "third_party/quic/core/quic_arena_scoped_ptr_test.cc",
- "third_party/quic/core/quic_bandwidth_test.cc",
- "third_party/quic/core/quic_buffered_packet_store_test.cc",
- "third_party/quic/core/quic_config_test.cc",
- "third_party/quic/core/quic_connection_id_test.cc",
- "third_party/quic/core/quic_connection_test.cc",
- "third_party/quic/core/quic_control_frame_manager_test.cc",
- "third_party/quic/core/quic_crypto_client_handshaker_test.cc",
- "third_party/quic/core/quic_crypto_client_stream_test.cc",
- "third_party/quic/core/quic_crypto_server_stream_test.cc",
- "third_party/quic/core/quic_crypto_stream_test.cc",
- "third_party/quic/core/quic_data_writer_test.cc",
- "third_party/quic/core/quic_dispatcher_test.cc",
- "third_party/quic/core/quic_error_codes_test.cc",
- "third_party/quic/core/quic_flow_controller_test.cc",
- "third_party/quic/core/quic_framer_test.cc",
- "third_party/quic/core/quic_ietf_framer_test.cc",
- "third_party/quic/core/quic_interval_set_test.cc",
- "third_party/quic/core/quic_interval_test.cc",
- "third_party/quic/core/quic_lru_cache_test.cc",
- "third_party/quic/core/quic_one_block_arena_test.cc",
- "third_party/quic/core/quic_packet_creator_test.cc",
- "third_party/quic/core/quic_packet_generator_test.cc",
- "third_party/quic/core/quic_packet_number_test.cc",
- "third_party/quic/core/quic_received_packet_manager_test.cc",
- "third_party/quic/core/quic_sent_packet_manager_test.cc",
- "third_party/quic/core/quic_server_id_test.cc",
- "third_party/quic/core/quic_session_test.cc",
- "third_party/quic/core/quic_simple_buffer_allocator_test.cc",
- "third_party/quic/core/quic_socket_address_coder_test.cc",
- "third_party/quic/core/quic_stream_id_manager_test.cc",
- "third_party/quic/core/quic_stream_send_buffer_test.cc",
- "third_party/quic/core/quic_stream_sequencer_buffer_test.cc",
- "third_party/quic/core/quic_stream_sequencer_test.cc",
- "third_party/quic/core/quic_stream_test.cc",
- "third_party/quic/core/quic_sustained_bandwidth_recorder_test.cc",
- "third_party/quic/core/quic_tag_test.cc",
- "third_party/quic/core/quic_time_test.cc",
- "third_party/quic/core/quic_time_wait_list_manager_test.cc",
+ # "third_party/quic/core/crypto/common_cert_set_test.cc",
+ "third_party/quic/core/crypto/crypto_framer_test.cc",
+ "third_party/quic/core/crypto/crypto_handshake_message_test.cc",
+ "third_party/quic/core/crypto/crypto_secret_boxer_test.cc",
+ "third_party/quic/core/crypto/crypto_server_test.cc",
+ "third_party/quic/core/crypto/crypto_utils_test.cc",
+ "third_party/quic/core/crypto/curve25519_key_exchange_test.cc",
+ "third_party/quic/core/crypto/null_decrypter_test.cc",
+ "third_party/quic/core/crypto/null_encrypter_test.cc",
+ "third_party/quic/core/crypto/p256_key_exchange_test.cc",
+ "third_party/quic/core/crypto/quic_compressed_certs_cache_test.cc",
+ "third_party/quic/core/crypto/quic_crypto_client_config_test.cc",
+ "third_party/quic/core/crypto/quic_crypto_server_config_test.cc",
+ "third_party/quic/core/crypto/quic_hkdf_test.cc",
+ "third_party/quic/core/crypto/quic_random_test.cc",
+ "third_party/quic/core/crypto/transport_parameters_test.cc",
+ "third_party/quic/core/frames/quic_frames_test.cc",
+ "third_party/quic/core/http/http_decoder_test.cc",
+ "third_party/quic/core/http/http_encoder_test.cc",
+ "third_party/quic/core/http/quic_client_promised_info_test.cc",
+ "third_party/quic/core/http/quic_client_push_promise_index_test.cc",
+ "third_party/quic/core/http/quic_header_list_test.cc",
+ "third_party/quic/core/http/quic_headers_stream_test.cc",
+ "third_party/quic/core/http/quic_server_session_base_test.cc",
+ "third_party/quic/core/http/quic_spdy_session_test.cc",
+ "third_party/quic/core/http/quic_spdy_stream_body_buffer_test.cc",
+ "third_party/quic/core/http/quic_spdy_stream_test.cc",
+ "third_party/quic/core/http/spdy_utils_test.cc",
+ "third_party/quic/core/legacy_quic_stream_id_manager_test.cc",
+ "third_party/quic/core/packet_number_indexed_queue_test.cc",
+ "third_party/quic/core/qpack/qpack_decoded_headers_accumulator_test.cc",
+ "third_party/quic/core/qpack/qpack_decoder_stream_receiver_test.cc",
+ "third_party/quic/core/qpack/qpack_decoder_stream_sender_test.cc",
+ "third_party/quic/core/qpack/qpack_decoder_test.cc",
+ "third_party/quic/core/qpack/qpack_encoder_stream_receiver_test.cc",
+ "third_party/quic/core/qpack/qpack_encoder_stream_sender_test.cc",
+ "third_party/quic/core/qpack/qpack_encoder_test.cc",
+ "third_party/quic/core/qpack/qpack_header_table_test.cc",
+ "third_party/quic/core/qpack/qpack_instruction_decoder_test.cc",
+ "third_party/quic/core/qpack/qpack_instruction_encoder_test.cc",
+ "third_party/quic/core/qpack/qpack_progressive_decoder_test.cc",
+ "third_party/quic/core/qpack/qpack_round_trip_test.cc",
+ "third_party/quic/core/qpack/qpack_static_table_test.cc",
+ "third_party/quic/core/quic_alarm_test.cc",
+ "third_party/quic/core/quic_arena_scoped_ptr_test.cc",
+ "third_party/quic/core/quic_bandwidth_test.cc",
+ "third_party/quic/core/quic_buffered_packet_store_test.cc",
+ "third_party/quic/core/quic_config_test.cc",
+ "third_party/quic/core/quic_connection_id_test.cc",
+ "third_party/quic/core/quic_connection_test.cc",
+ "third_party/quic/core/quic_control_frame_manager_test.cc",
+ "third_party/quic/core/quic_crypto_client_handshaker_test.cc",
+ "third_party/quic/core/quic_crypto_client_stream_test.cc",
+ "third_party/quic/core/quic_crypto_server_stream_test.cc",
+ "third_party/quic/core/quic_crypto_stream_test.cc",
+ "third_party/quic/core/quic_data_writer_test.cc",
+ "third_party/quic/core/quic_dispatcher_test.cc",
+ "third_party/quic/core/quic_error_codes_test.cc",
+ "third_party/quic/core/quic_flow_controller_test.cc",
+ "third_party/quic/core/quic_framer_test.cc",
+ "third_party/quic/core/quic_ietf_framer_test.cc",
+ "third_party/quic/core/quic_interval_set_test.cc",
+ "third_party/quic/core/quic_interval_test.cc",
+ "third_party/quic/core/quic_lru_cache_test.cc",
+ "third_party/quic/core/quic_one_block_arena_test.cc",
+ "third_party/quic/core/quic_packet_creator_test.cc",
+ "third_party/quic/core/quic_packet_generator_test.cc",
+ "third_party/quic/core/quic_packet_number_test.cc",
+ "third_party/quic/core/quic_received_packet_manager_test.cc",
+ "third_party/quic/core/quic_sent_packet_manager_test.cc",
+ "third_party/quic/core/quic_server_id_test.cc",
+ "third_party/quic/core/quic_session_test.cc",
+ "third_party/quic/core/quic_simple_buffer_allocator_test.cc",
+ "third_party/quic/core/quic_socket_address_coder_test.cc",
+ "third_party/quic/core/quic_stream_id_manager_test.cc",
+ "third_party/quic/core/quic_stream_send_buffer_test.cc",
+ "third_party/quic/core/quic_stream_sequencer_buffer_test.cc",
+ "third_party/quic/core/quic_stream_sequencer_test.cc",
+ "third_party/quic/core/quic_stream_test.cc",
+ "third_party/quic/core/quic_sustained_bandwidth_recorder_test.cc",
+ "third_party/quic/core/quic_tag_test.cc",
+ "third_party/quic/core/quic_time_test.cc",
+ "third_party/quic/core/quic_time_wait_list_manager_test.cc",
- # TraceVisitor is not supported yet.
- # "third_party/quic/core/quic_trace_visitor_test.cc",
- "third_party/quic/core/quic_unacked_packet_map_test.cc",
- "third_party/quic/core/quic_utils_test.cc",
- "third_party/quic/core/quic_version_manager_test.cc",
- "third_party/quic/core/quic_versions_test.cc",
- "third_party/quic/core/quic_write_blocked_list_test.cc",
- "third_party/quic/core/tls_handshaker_test.cc",
- "third_party/quic/core/uber_quic_stream_id_manager_test.cc",
- "third_party/quic/platform/api/quic_containers_test.cc",
- "third_party/quic/platform/api/quic_endian_test.cc",
- "third_party/quic/platform/api/quic_hostname_utils_test.cc",
- "third_party/quic/platform/api/quic_mem_slice_span_test.cc",
- "third_party/quic/platform/api/quic_mem_slice_storage_test.cc",
- "third_party/quic/platform/api/quic_mem_slice_test.cc",
- "third_party/quic/platform/api/quic_reference_counted_test.cc",
- "third_party/quic/platform/api/quic_singleton_test.cc",
- "third_party/quic/platform/api/quic_str_cat_test.cc",
- "third_party/quic/platform/api/quic_text_utils_test.cc",
- "third_party/quic/platform/impl/quic_chromium_clock_test.cc",
- "third_party/quic/platform/impl/quic_uint128_impl_unittest.cc",
- "third_party/quic/test_tools/crypto_test_utils_test.cc",
- "third_party/quic/test_tools/mock_quic_time_wait_list_manager.cc",
- "third_party/quic/test_tools/mock_quic_time_wait_list_manager.h",
- "third_party/quic/test_tools/quic_test_utils_test.cc",
- "third_party/quic/test_tools/simple_session_notifier_test.cc",
- "third_party/quic/test_tools/simulator/quic_endpoint_test.cc",
- "third_party/quic/test_tools/simulator/simulator_test.cc",
- "third_party/quiche/src/http2/decoder/decode_buffer_test.cc",
- "third_party/quiche/src/http2/decoder/decode_http2_structures_test.cc",
- "third_party/quiche/src/http2/decoder/frame_decoder_state_test_util.cc",
- "third_party/quiche/src/http2/decoder/frame_decoder_state_test_util.h",
- "third_party/quiche/src/http2/decoder/http2_frame_decoder_listener_test_util.cc",
- "third_party/quiche/src/http2/decoder/http2_frame_decoder_listener_test_util.h",
- "third_party/quiche/src/http2/decoder/http2_frame_decoder_test.cc",
- "third_party/quiche/src/http2/decoder/http2_structure_decoder_test.cc",
- "third_party/quiche/src/http2/decoder/http2_structure_decoder_test_util.cc",
- "third_party/quiche/src/http2/decoder/http2_structure_decoder_test_util.h",
- "third_party/quiche/src/http2/decoder/payload_decoders/altsvc_payload_decoder_test.cc",
- "third_party/quiche/src/http2/decoder/payload_decoders/continuation_payload_decoder_test.cc",
- "third_party/quiche/src/http2/decoder/payload_decoders/data_payload_decoder_test.cc",
- "third_party/quiche/src/http2/decoder/payload_decoders/goaway_payload_decoder_test.cc",
- "third_party/quiche/src/http2/decoder/payload_decoders/headers_payload_decoder_test.cc",
- "third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.cc",
- "third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h",
- "third_party/quiche/src/http2/decoder/payload_decoders/ping_payload_decoder_test.cc",
- "third_party/quiche/src/http2/decoder/payload_decoders/priority_payload_decoder_test.cc",
- "third_party/quiche/src/http2/decoder/payload_decoders/push_promise_payload_decoder_test.cc",
- "third_party/quiche/src/http2/decoder/payload_decoders/rst_stream_payload_decoder_test.cc",
- "third_party/quiche/src/http2/decoder/payload_decoders/settings_payload_decoder_test.cc",
- "third_party/quiche/src/http2/decoder/payload_decoders/unknown_payload_decoder_test.cc",
- "third_party/quiche/src/http2/decoder/payload_decoders/window_update_payload_decoder_test.cc",
- "third_party/quiche/src/http2/hpack/decoder/hpack_block_collector.cc",
- "third_party/quiche/src/http2/hpack/decoder/hpack_block_collector.h",
- "third_party/quiche/src/http2/hpack/decoder/hpack_block_decoder_test.cc",
- "third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state_test.cc",
- "third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer_test.cc",
- "third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables_test.cc",
- "third_party/quiche/src/http2/hpack/decoder/hpack_decoder_test.cc",
- "third_party/quiche/src/http2/hpack/decoder/hpack_entry_collector.cc",
- "third_party/quiche/src/http2/hpack/decoder/hpack_entry_collector.h",
- "third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_test.cc",
- "third_party/quiche/src/http2/hpack/decoder/hpack_entry_type_decoder_test.cc",
- "third_party/quiche/src/http2/hpack/decoder/hpack_string_collector.cc",
- "third_party/quiche/src/http2/hpack/decoder/hpack_string_collector.h",
- "third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder_test.cc",
- "third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer_test.cc",
- "third_party/quiche/src/http2/hpack/hpack_string_test.cc",
- "third_party/quiche/src/http2/hpack/http2_hpack_constants_test.cc",
- "third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder_test.cc",
- "third_party/quiche/src/http2/hpack/huffman/hpack_huffman_encoder_test.cc",
- "third_party/quiche/src/http2/hpack/huffman/hpack_huffman_transcoder_test.cc",
- "third_party/quiche/src/http2/hpack/tools/hpack_block_builder.cc",
- "third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h",
- "third_party/quiche/src/http2/hpack/tools/hpack_block_builder_test.cc",
- "third_party/quiche/src/http2/hpack/tools/hpack_example.cc",
- "third_party/quiche/src/http2/hpack/tools/hpack_example.h",
- "third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder_test.cc",
- "third_party/quiche/src/http2/hpack/varint/hpack_varint_encoder_test.cc",
- "third_party/quiche/src/http2/hpack/varint/hpack_varint_round_trip_test.cc",
- "third_party/quiche/src/http2/http2_constants_test.cc",
- "third_party/quiche/src/http2/http2_constants_test_util.cc",
- "third_party/quiche/src/http2/http2_constants_test_util.h",
- "third_party/quiche/src/http2/http2_structures_test.cc",
- "third_party/quiche/src/http2/http2_structures_test_util.cc",
- "third_party/quiche/src/http2/http2_structures_test_util.h",
- "third_party/quiche/src/http2/platform/api/http2_mock_log.h",
- "third_party/quiche/src/http2/platform/api/http2_string_utils_test.cc",
- "third_party/quiche/src/http2/platform/api/http2_test_helpers.h",
- "third_party/quiche/src/http2/test_tools/frame_parts.cc",
- "third_party/quiche/src/http2/test_tools/frame_parts.h",
- "third_party/quiche/src/http2/test_tools/frame_parts_collector.cc",
- "third_party/quiche/src/http2/test_tools/frame_parts_collector.h",
- "third_party/quiche/src/http2/test_tools/frame_parts_collector_listener.cc",
- "third_party/quiche/src/http2/test_tools/frame_parts_collector_listener.h",
- "third_party/quiche/src/http2/test_tools/http2_random.cc",
- "third_party/quiche/src/http2/test_tools/http2_random.h",
- "third_party/quiche/src/http2/test_tools/http2_random_test.cc",
- "third_party/quiche/src/http2/tools/http2_frame_builder.cc",
- "third_party/quiche/src/http2/tools/http2_frame_builder.h",
- "third_party/quiche/src/http2/tools/random_decoder_test.cc",
- "third_party/quiche/src/http2/tools/random_decoder_test.h",
- "third_party/quiche/src/http2/tools/random_util.cc",
- "third_party/quiche/src/http2/tools/random_util.h",
- "third_party/quiche/src/spdy/core/array_output_buffer.cc",
- "third_party/quiche/src/spdy/core/array_output_buffer.h",
- "third_party/quiche/src/spdy/core/array_output_buffer_test.cc",
- "third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter_test.cc",
- "third_party/quiche/src/spdy/core/hpack/hpack_encoder_test.cc",
- "third_party/quiche/src/spdy/core/hpack/hpack_entry_test.cc",
- "third_party/quiche/src/spdy/core/hpack/hpack_header_table_test.cc",
- "third_party/quiche/src/spdy/core/hpack/hpack_huffman_table_test.cc",
- "third_party/quiche/src/spdy/core/hpack/hpack_output_stream_test.cc",
- "third_party/quiche/src/spdy/core/hpack/hpack_round_trip_test.cc",
- "third_party/quiche/src/spdy/core/hpack/hpack_static_table_test.cc",
- "third_party/quiche/src/spdy/core/mock_spdy_framer_visitor.cc",
- "third_party/quiche/src/spdy/core/mock_spdy_framer_visitor.h",
- "third_party/quiche/src/spdy/core/priority_write_scheduler_test.cc",
- "third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format_test.cc",
- "third_party/quiche/src/spdy/core/spdy_deframer_visitor.cc",
- "third_party/quiche/src/spdy/core/spdy_deframer_visitor.h",
- "third_party/quiche/src/spdy/core/spdy_deframer_visitor_test.cc",
- "third_party/quiche/src/spdy/core/spdy_frame_builder_test.cc",
- "third_party/quiche/src/spdy/core/spdy_frame_reader_test.cc",
- "third_party/quiche/src/spdy/core/spdy_framer_test.cc",
- "third_party/quiche/src/spdy/core/spdy_header_block_test.cc",
- "third_party/quiche/src/spdy/core/spdy_no_op_visitor.cc",
- "third_party/quiche/src/spdy/core/spdy_no_op_visitor.h",
- "third_party/quiche/src/spdy/core/spdy_pinnable_buffer_piece_test.cc",
- "third_party/quiche/src/spdy/core/spdy_prefixed_buffer_reader_test.cc",
- "third_party/quiche/src/spdy/core/spdy_protocol_test.cc",
- "third_party/quiche/src/spdy/core/spdy_protocol_test_utils.cc",
- "third_party/quiche/src/spdy/core/spdy_protocol_test_utils.h",
- "third_party/quiche/src/spdy/core/spdy_test_utils.cc",
- "third_party/quiche/src/spdy/core/spdy_test_utils.h",
- "third_party/quiche/src/spdy/platform/api/spdy_mem_slice_test.cc",
- "third_party/quiche/src/spdy/platform/api/spdy_string_utils_test.cc",
- "third_party/quiche/src/spdy/platform/api/spdy_test_helpers.h",
- "tools/content_decoder_tool/content_decoder_tool.cc",
- "tools/content_decoder_tool/content_decoder_tool.h",
- "tools/content_decoder_tool/content_decoder_tool_unittest.cc",
- "tools/quic/quic_simple_client_test.cc",
+ # TraceVisitor is not supported yet.
+ # "third_party/quic/core/quic_trace_visitor_test.cc",
+ "third_party/quic/core/quic_unacked_packet_map_test.cc",
+ "third_party/quic/core/quic_utils_test.cc",
+ "third_party/quic/core/quic_version_manager_test.cc",
+ "third_party/quic/core/quic_versions_test.cc",
+ "third_party/quic/core/quic_write_blocked_list_test.cc",
+ "third_party/quic/core/tls_handshaker_test.cc",
+ "third_party/quic/core/uber_quic_stream_id_manager_test.cc",
+ "third_party/quic/platform/api/quic_containers_test.cc",
+ "third_party/quic/platform/api/quic_endian_test.cc",
+ "third_party/quic/platform/api/quic_hostname_utils_test.cc",
+ "third_party/quic/platform/api/quic_mem_slice_span_test.cc",
+ "third_party/quic/platform/api/quic_mem_slice_storage_test.cc",
+ "third_party/quic/platform/api/quic_mem_slice_test.cc",
+ "third_party/quic/platform/api/quic_reference_counted_test.cc",
+ "third_party/quic/platform/api/quic_singleton_test.cc",
+ "third_party/quic/platform/api/quic_str_cat_test.cc",
+ "third_party/quic/platform/api/quic_text_utils_test.cc",
+ "third_party/quic/platform/impl/quic_chromium_clock_test.cc",
+ "third_party/quic/platform/impl/quic_uint128_impl_unittest.cc",
+ "third_party/quic/test_tools/crypto_test_utils_test.cc",
+ "third_party/quic/test_tools/mock_quic_time_wait_list_manager.cc",
+ "third_party/quic/test_tools/mock_quic_time_wait_list_manager.h",
+ "third_party/quic/test_tools/quic_test_utils_test.cc",
+ "third_party/quic/test_tools/simple_session_notifier_test.cc",
+ "third_party/quic/test_tools/simulator/quic_endpoint_test.cc",
+ "third_party/quic/test_tools/simulator/simulator_test.cc",
+ "third_party/quiche/src/http2/decoder/decode_buffer_test.cc",
+ "third_party/quiche/src/http2/decoder/decode_http2_structures_test.cc",
+ "third_party/quiche/src/http2/decoder/frame_decoder_state_test_util.cc",
+ "third_party/quiche/src/http2/decoder/frame_decoder_state_test_util.h",
+ "third_party/quiche/src/http2/decoder/http2_frame_decoder_listener_test_util.cc",
+ "third_party/quiche/src/http2/decoder/http2_frame_decoder_listener_test_util.h",
+ "third_party/quiche/src/http2/decoder/http2_frame_decoder_test.cc",
+ "third_party/quiche/src/http2/decoder/http2_structure_decoder_test.cc",
+ "third_party/quiche/src/http2/decoder/http2_structure_decoder_test_util.cc",
+ "third_party/quiche/src/http2/decoder/http2_structure_decoder_test_util.h",
+ "third_party/quiche/src/http2/decoder/payload_decoders/altsvc_payload_decoder_test.cc",
+ "third_party/quiche/src/http2/decoder/payload_decoders/continuation_payload_decoder_test.cc",
+ "third_party/quiche/src/http2/decoder/payload_decoders/data_payload_decoder_test.cc",
+ "third_party/quiche/src/http2/decoder/payload_decoders/goaway_payload_decoder_test.cc",
+ "third_party/quiche/src/http2/decoder/payload_decoders/headers_payload_decoder_test.cc",
+ "third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.cc",
+ "third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h",
+ "third_party/quiche/src/http2/decoder/payload_decoders/ping_payload_decoder_test.cc",
+ "third_party/quiche/src/http2/decoder/payload_decoders/priority_payload_decoder_test.cc",
+ "third_party/quiche/src/http2/decoder/payload_decoders/push_promise_payload_decoder_test.cc",
+ "third_party/quiche/src/http2/decoder/payload_decoders/rst_stream_payload_decoder_test.cc",
+ "third_party/quiche/src/http2/decoder/payload_decoders/settings_payload_decoder_test.cc",
+ "third_party/quiche/src/http2/decoder/payload_decoders/unknown_payload_decoder_test.cc",
+ "third_party/quiche/src/http2/decoder/payload_decoders/window_update_payload_decoder_test.cc",
+ "third_party/quiche/src/http2/hpack/decoder/hpack_block_collector.cc",
+ "third_party/quiche/src/http2/hpack/decoder/hpack_block_collector.h",
+ "third_party/quiche/src/http2/hpack/decoder/hpack_block_decoder_test.cc",
+ "third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state_test.cc",
+ "third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer_test.cc",
+ "third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables_test.cc",
+ "third_party/quiche/src/http2/hpack/decoder/hpack_decoder_test.cc",
+ "third_party/quiche/src/http2/hpack/decoder/hpack_entry_collector.cc",
+ "third_party/quiche/src/http2/hpack/decoder/hpack_entry_collector.h",
+ "third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_test.cc",
+ "third_party/quiche/src/http2/hpack/decoder/hpack_entry_type_decoder_test.cc",
+ "third_party/quiche/src/http2/hpack/decoder/hpack_string_collector.cc",
+ "third_party/quiche/src/http2/hpack/decoder/hpack_string_collector.h",
+ "third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder_test.cc",
+ "third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer_test.cc",
+ "third_party/quiche/src/http2/hpack/hpack_string_test.cc",
+ "third_party/quiche/src/http2/hpack/http2_hpack_constants_test.cc",
+ "third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder_test.cc",
+ "third_party/quiche/src/http2/hpack/huffman/hpack_huffman_encoder_test.cc",
+ "third_party/quiche/src/http2/hpack/huffman/hpack_huffman_transcoder_test.cc",
+ "third_party/quiche/src/http2/hpack/tools/hpack_block_builder.cc",
+ "third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h",
+ "third_party/quiche/src/http2/hpack/tools/hpack_block_builder_test.cc",
+ "third_party/quiche/src/http2/hpack/tools/hpack_example.cc",
+ "third_party/quiche/src/http2/hpack/tools/hpack_example.h",
+ "third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder_test.cc",
+ "third_party/quiche/src/http2/hpack/varint/hpack_varint_encoder_test.cc",
+ "third_party/quiche/src/http2/hpack/varint/hpack_varint_round_trip_test.cc",
+ "third_party/quiche/src/http2/http2_constants_test.cc",
+ "third_party/quiche/src/http2/http2_constants_test_util.cc",
+ "third_party/quiche/src/http2/http2_constants_test_util.h",
+ "third_party/quiche/src/http2/http2_structures_test.cc",
+ "third_party/quiche/src/http2/http2_structures_test_util.cc",
+ "third_party/quiche/src/http2/http2_structures_test_util.h",
+ "third_party/quiche/src/http2/platform/api/http2_mock_log.h",
+ "third_party/quiche/src/http2/platform/api/http2_string_utils_test.cc",
+ "third_party/quiche/src/http2/platform/api/http2_test_helpers.h",
+ "third_party/quiche/src/http2/test_tools/frame_parts.cc",
+ "third_party/quiche/src/http2/test_tools/frame_parts.h",
+ "third_party/quiche/src/http2/test_tools/frame_parts_collector.cc",
+ "third_party/quiche/src/http2/test_tools/frame_parts_collector.h",
+ "third_party/quiche/src/http2/test_tools/frame_parts_collector_listener.cc",
+ "third_party/quiche/src/http2/test_tools/frame_parts_collector_listener.h",
+ "third_party/quiche/src/http2/test_tools/http2_random.cc",
+ "third_party/quiche/src/http2/test_tools/http2_random.h",
+ "third_party/quiche/src/http2/test_tools/http2_random_test.cc",
+ "third_party/quiche/src/http2/tools/http2_frame_builder.cc",
+ "third_party/quiche/src/http2/tools/http2_frame_builder.h",
+ "third_party/quiche/src/http2/tools/random_decoder_test.cc",
+ "third_party/quiche/src/http2/tools/random_decoder_test.h",
+ "third_party/quiche/src/http2/tools/random_util.cc",
+ "third_party/quiche/src/http2/tools/random_util.h",
+ "third_party/quiche/src/spdy/core/array_output_buffer.cc",
+ "third_party/quiche/src/spdy/core/array_output_buffer.h",
+ "third_party/quiche/src/spdy/core/array_output_buffer_test.cc",
+ "third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter_test.cc",
+ "third_party/quiche/src/spdy/core/hpack/hpack_encoder_test.cc",
+ "third_party/quiche/src/spdy/core/hpack/hpack_entry_test.cc",
+ "third_party/quiche/src/spdy/core/hpack/hpack_header_table_test.cc",
+ "third_party/quiche/src/spdy/core/hpack/hpack_huffman_table_test.cc",
+ "third_party/quiche/src/spdy/core/hpack/hpack_output_stream_test.cc",
+ "third_party/quiche/src/spdy/core/hpack/hpack_round_trip_test.cc",
+ "third_party/quiche/src/spdy/core/hpack/hpack_static_table_test.cc",
+ "third_party/quiche/src/spdy/core/mock_spdy_framer_visitor.cc",
+ "third_party/quiche/src/spdy/core/mock_spdy_framer_visitor.h",
+ "third_party/quiche/src/spdy/core/priority_write_scheduler_test.cc",
+ "third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format_test.cc",
+ "third_party/quiche/src/spdy/core/spdy_deframer_visitor.cc",
+ "third_party/quiche/src/spdy/core/spdy_deframer_visitor.h",
+ "third_party/quiche/src/spdy/core/spdy_deframer_visitor_test.cc",
+ "third_party/quiche/src/spdy/core/spdy_frame_builder_test.cc",
+ "third_party/quiche/src/spdy/core/spdy_frame_reader_test.cc",
+ "third_party/quiche/src/spdy/core/spdy_framer_test.cc",
+ "third_party/quiche/src/spdy/core/spdy_header_block_test.cc",
+ "third_party/quiche/src/spdy/core/spdy_no_op_visitor.cc",
+ "third_party/quiche/src/spdy/core/spdy_no_op_visitor.h",
+ "third_party/quiche/src/spdy/core/spdy_pinnable_buffer_piece_test.cc",
+ "third_party/quiche/src/spdy/core/spdy_prefixed_buffer_reader_test.cc",
+ "third_party/quiche/src/spdy/core/spdy_protocol_test.cc",
+ "third_party/quiche/src/spdy/core/spdy_protocol_test_utils.cc",
+ "third_party/quiche/src/spdy/core/spdy_protocol_test_utils.h",
+ "third_party/quiche/src/spdy/core/spdy_test_utils.cc",
+ "third_party/quiche/src/spdy/core/spdy_test_utils.h",
+ "third_party/quiche/src/spdy/platform/api/spdy_mem_slice_test.cc",
+ "third_party/quiche/src/spdy/platform/api/spdy_string_utils_test.cc",
+ "third_party/quiche/src/spdy/platform/api/spdy_test_helpers.h",
+ "tools/content_decoder_tool/content_decoder_tool.cc",
+ "tools/content_decoder_tool/content_decoder_tool.h",
+ "tools/content_decoder_tool/content_decoder_tool_unittest.cc",
+ "tools/quic/quic_simple_client_test.cc",
- # "tools/tld_cleanup/tld_cleanup_util_unittest.cc",
- "url_request/redirect_info_unittest.cc",
- "url_request/redirect_util_unittest.cc",
- "url_request/report_sender_unittest.cc",
- "url_request/url_fetcher_impl_unittest.cc",
- "url_request/url_fetcher_response_writer_unittest.cc",
- "url_request/url_request_context_builder_unittest.cc",
- "url_request/url_request_context_unittest.cc",
- "url_request/url_request_data_job_unittest.cc",
+ # "tools/tld_cleanup/tld_cleanup_util_unittest.cc",
+ "url_request/redirect_info_unittest.cc",
+ "url_request/redirect_util_unittest.cc",
+ "url_request/report_sender_unittest.cc",
+ "url_request/url_fetcher_impl_unittest.cc",
+ "url_request/url_fetcher_response_writer_unittest.cc",
+ "url_request/url_request_context_builder_unittest.cc",
+ "url_request/url_request_context_unittest.cc",
+ "url_request/url_request_data_job_unittest.cc",
- # Cobalt handles File Job itself.
- # "url_request/url_request_file_job_unittest.cc",
- "url_request/url_request_filter_unittest.cc",
+ # Cobalt handles File Job itself.
+ # "url_request/url_request_file_job_unittest.cc",
+ "url_request/url_request_filter_unittest.cc",
- # ftp not supported by Cobalt.
- # "url_request/url_request_file_dir_job_unittest.cc",
- # "url_request/url_request_ftp_job_unittest.cc",
+ # ftp not supported by Cobalt.
+ # "url_request/url_request_file_dir_job_unittest.cc",
+ # "url_request/url_request_ftp_job_unittest.cc",
- "url_request/url_request_http_job_unittest.cc",
- "url_request/url_request_job_factory_impl_unittest.cc",
- "url_request/url_request_job_unittest.cc",
- "url_request/url_request_quic_unittest.cc",
- "url_request/url_request_simple_job_unittest.cc",
- "url_request/url_request_throttler_simulation_unittest.cc",
- "url_request/url_request_throttler_test_support.cc",
- "url_request/url_request_throttler_test_support.h",
- "url_request/url_request_throttler_unittest.cc",
- "url_request/url_request_unittest.cc",
+ "url_request/url_request_http_job_unittest.cc",
+ "url_request/url_request_job_factory_impl_unittest.cc",
+ "url_request/url_request_job_unittest.cc",
+ "url_request/url_request_quic_unittest.cc",
+ "url_request/url_request_simple_job_unittest.cc",
+ "url_request/url_request_throttler_simulation_unittest.cc",
+ "url_request/url_request_throttler_test_support.cc",
+ "url_request/url_request_throttler_test_support.h",
+ "url_request/url_request_throttler_unittest.cc",
+ "url_request/url_request_unittest.cc",
- # Also include unittests for websockets.
- "server/http_connection_unittest.cc",
- "server/http_server_response_info_unittest.cc",
- "server/http_server_unittest.cc",
- "server/web_socket_encoder_unittest.cc",
- "websockets/websocket_basic_handshake_stream_test.cc",
- "websockets/websocket_basic_stream_adapters_test.cc",
- "websockets/websocket_basic_stream_test.cc",
- "websockets/websocket_channel_test.cc",
- "websockets/websocket_deflate_parameters_test.cc",
- "websockets/websocket_deflate_predictor_impl_test.cc",
- "websockets/websocket_deflate_stream_test.cc",
- "websockets/websocket_deflater_test.cc",
+ # Also include unittests for websockets.
+ "server/http_connection_unittest.cc",
+ "server/http_server_response_info_unittest.cc",
+ "server/http_server_unittest.cc",
+ "server/web_socket_encoder_unittest.cc",
+ "websockets/websocket_basic_handshake_stream_test.cc",
+ "websockets/websocket_basic_stream_adapters_test.cc",
+ "websockets/websocket_basic_stream_test.cc",
+ "websockets/websocket_channel_test.cc",
+ "websockets/websocket_deflate_parameters_test.cc",
+ "websockets/websocket_deflate_predictor_impl_test.cc",
+ "websockets/websocket_deflate_stream_test.cc",
+ "websockets/websocket_deflater_test.cc",
- # Uses the unsupported SpawnedWebServer.
- #"websockets/websocket_end_to_end_test.cc",
+ # Uses the unsupported SpawnedWebServer.
+ #"websockets/websocket_end_to_end_test.cc",
- "websockets/websocket_errors_test.cc",
- "websockets/websocket_extension_parser_test.cc",
- "websockets/websocket_extension_test.cc",
- "websockets/websocket_frame_parser_test.cc",
- "websockets/websocket_frame_test.cc",
- "websockets/websocket_handshake_challenge_test.cc",
- "websockets/websocket_handshake_stream_create_helper_test.cc",
- "websockets/websocket_inflater_test.cc",
- "websockets/websocket_stream_cookie_test.cc",
- "websockets/websocket_stream_create_test_base.cc",
- "websockets/websocket_stream_create_test_base.h",
- "websockets/websocket_stream_test.cc",
- "websockets/websocket_test_util.cc",
- "websockets/websocket_test_util.h",
+ "websockets/websocket_errors_test.cc",
+ "websockets/websocket_extension_parser_test.cc",
+ "websockets/websocket_extension_test.cc",
+ "websockets/websocket_frame_parser_test.cc",
+ "websockets/websocket_frame_test.cc",
+ "websockets/websocket_handshake_challenge_test.cc",
+ "websockets/websocket_handshake_stream_create_helper_test.cc",
+ "websockets/websocket_inflater_test.cc",
+ "websockets/websocket_stream_cookie_test.cc",
+ "websockets/websocket_stream_create_test_base.cc",
+ "websockets/websocket_stream_create_test_base.h",
+ "websockets/websocket_stream_test.cc",
+ "websockets/websocket_test_util.cc",
+ "websockets/websocket_test_util.h",
- # Also include unittests for network reporting.
- "reporting/reporting_browsing_data_remover_unittest.cc",
- "reporting/reporting_cache_unittest.cc",
- "reporting/reporting_delivery_agent_unittest.cc",
- "reporting/reporting_endpoint_manager_unittest.cc",
- "reporting/reporting_garbage_collector_unittest.cc",
- "reporting/reporting_header_parser_unittest.cc",
- "reporting/reporting_network_change_observer_unittest.cc",
- "reporting/reporting_service_unittest.cc",
- ]
+ # Also include unittests for network reporting.
+ "reporting/reporting_browsing_data_remover_unittest.cc",
+ "reporting/reporting_cache_unittest.cc",
+ "reporting/reporting_delivery_agent_unittest.cc",
+ "reporting/reporting_endpoint_manager_unittest.cc",
+ "reporting/reporting_garbage_collector_unittest.cc",
+ "reporting/reporting_header_parser_unittest.cc",
+ "reporting/reporting_network_change_observer_unittest.cc",
+ "reporting/reporting_service_unittest.cc",
+ ]
- if (is_win) {
- sources += [
- "dns/dns_config_service_win_unittest.cc",
- "http/http_auth_sspi_win_unittest.cc",
- "proxy_resolution/dhcp_pac_file_adapter_fetcher_win_unittest.cc",
- "proxy_resolution/dhcp_pac_file_fetcher_win_unittest.cc",
- "ssl/ssl_platform_key_win_unittest.cc",
- ]
- }
-
- # Avoid compiler errors due to conversion from "0x00" to char.
- if (is_win) {
- cflags = [ "/Wno-narrowing" ]
- } else {
- cflags = [ "-Wno-narrowing" ]
- }
-
- defines = [
- # To be removed in the future when want to enable HTTP cache.
- "HTTP_CACHE_DISABLED_FOR_STARBOARD",
- "GMOCK_NO_MOVE_MOCK",
- ]
-
- configs += [ "//build/config/compiler:chromium_code" ]
- configs -= [ "//starboard/build/config:size" ]
-
- deps = [
- # This is needed by dial_http_server in net
- ":http_server",
- ":net",
- ":net_generated_registry_controlled_domains",
- ":net_nqe_proto",
- ":net_quic_proto",
- ":net_unittest_files",
- ":quic_test_tools",
- ":test_support",
- ":third_party_unittest_files",
- "//base",
- "//base:i18n",
- "//base/test:test_support",
- "//base/third_party/dynamic_annotations",
- "//crypto",
- "//testing/gmock",
- "//testing/gtest",
- "//third_party/boringssl:crypto",
- "//third_party/zlib",
- "//url",
- "//url:url_features",
+ if (is_win && !is_starboard) {
+ sources += [
+ "dns/dns_config_service_win_unittest.cc",
+ "http/http_auth_sspi_win_unittest.cc",
+ "proxy_resolution/dhcp_pac_file_adapter_fetcher_win_unittest.cc",
+ "proxy_resolution/dhcp_pac_file_fetcher_win_unittest.cc",
+ "ssl/ssl_platform_key_win_unittest.cc",
]
}
+
+ # Avoid compiler errors due to conversion from "0x00" to char.
+ if (is_win) {
+ if (!is_starboard) {
+ cflags = [ "/Wno-narrowing" ]
+ }
+ } else {
+ cflags = [ "-Wno-narrowing" ]
+ }
+
+ defines = [
+ # To be removed in the future when want to enable HTTP cache.
+ "HTTP_CACHE_DISABLED_FOR_STARBOARD",
+ "GMOCK_NO_MOVE_MOCK",
+ ]
+
+ configs += [ "//build/config/compiler:chromium_code" ]
+ configs -= [ "//starboard/build/config:size" ]
+
+ deps = [
+ # This is needed by dial_http_server in net
+ ":http_server",
+ ":net",
+ ":net_generated_registry_controlled_domains",
+ ":net_nqe_proto",
+ ":net_quic_proto",
+ ":net_unittest_files",
+ ":quic_test_tools",
+ ":test_support",
+ ":third_party_unittest_files",
+ "//base",
+ "//base:i18n",
+ "//base/test:test_support",
+ "//base/third_party/dynamic_annotations",
+ "//crypto",
+ "//testing/gmock",
+ "//testing/gtest",
+ "//third_party/boringssl:crypto",
+ "//third_party/zlib",
+ "//url",
+ "//url:url_features",
+ ]
}
static_library("test_support") {
diff --git a/net/DEPS b/net/DEPS
new file mode 100644
index 0000000..df77e03
--- /dev/null
+++ b/net/DEPS
@@ -0,0 +1,80 @@
+include_rules = [
+ "+crypto",
+ "+gin",
+ "+jni",
+ "+mojo/public",
+ "+third_party/apple_apsl",
+ "+third_party/boringssl/src/include",
+ "+third_party/nss",
+ "+third_party/protobuf/src/google/protobuf",
+ "+third_party/zlib",
+ "+v8",
+
+ # Most of net should not depend on icu, and brotli to keep size down when
+ # built as a library.
+ "-base/i18n",
+ "-third_party/brotli",
+ "-third_party/icu",
+]
+
+specific_include_rules = {
+ # Within net, only used by file: requests.
+ "directory_lister(\.cc|_unittest\.cc)": [
+ "+base/i18n",
+ ],
+
+ # Functions largely not used by the rest of net.
+ "directory_listing\.cc": [
+ "+base/i18n",
+ ],
+
+ # Within net, only used by file: requests.
+ "filename_util_icu\.cc": [
+ "+base/i18n/file_util_icu.h",
+ ],
+
+ # Consolidated string functions that depend on icu.
+ "net_string_util_icu\.cc": [
+ "+base/i18n/case_conversion.h",
+ "+base/i18n/i18n_constants.h",
+ "+base/i18n/icu_string_conversions.h",
+ "+third_party/icu/source/common/unicode/ucnv.h"
+ ],
+
+ "websocket_channel\.h": [
+ "+base/i18n",
+ ],
+
+ "ftp_util\.cc": [
+ "+base/i18n",
+ "+third_party/icu",
+ ],
+ "ftp_directory_listing_parser\.cc": [
+ "+base/i18n",
+ ],
+
+ "run_all_unittests\.cc": [
+ "+mojo/core/embedder",
+ ],
+
+ "brotli_source_stream\.cc": [
+ "+third_party/brotli",
+ ],
+
+ "ssl_client_socket_impl\.cc": [
+ "+third_party/brotli",
+ ],
+
+ "fuzzer_test_support.cc": [
+ "+base/i18n",
+ ],
+
+ # Needed for fuzz targets written using libprotobuf-mutator library.
+ ".*fuzz.*": [
+ "+third_party/libprotobuf-mutator",
+ ]
+}
+
+skip_child_includes = [
+ "third_party",
+]
diff --git a/net/OWNERS b/net/OWNERS
new file mode 100644
index 0000000..5956672
--- /dev/null
+++ b/net/OWNERS
@@ -0,0 +1,21 @@
+agl@chromium.org
+asanka@chromium.org
+bnc@chromium.org
+davidben@chromium.org
+eroman@chromium.org
+jkarlin@chromium.org
+mattm@chromium.org
+mef@chromium.org
+mmenke@chromium.org
+morlovich@chromium.org
+nharper@chromium.org
+pauljensen@chromium.org
+rch@chromium.org
+rsleevi@chromium.org
+zhongyi@chromium.org
+
+per-file BUILD.gn=file://net/nqe/OWNERS
+per-file BUILD.gn=file://net/websockets/OWNERS
+
+# TEAM: net-dev@chromium.org
+# COMPONENT: Internals>Network
diff --git a/net/base/DEPS b/net/base/DEPS
new file mode 100644
index 0000000..fac79a6
--- /dev/null
+++ b/net/base/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+grit", # For generated headers
+]
diff --git a/net/base/OWNERS b/net/base/OWNERS
new file mode 100644
index 0000000..ddc7882
--- /dev/null
+++ b/net/base/OWNERS
@@ -0,0 +1,5 @@
+# For security review of MIME sniffing to avoid introducing security bugs
+per-file mime_sniffer*=set noparent
+per-file mime_sniffer*=rsleevi@chromium.org
+per-file mime_sniffer*=asanka@chromium.org
+per-file mime_sniffer*=mmenke@chromium.org
diff --git a/net/base/registry_controlled_domains/OWNERS b/net/base/registry_controlled_domains/OWNERS
new file mode 100644
index 0000000..10be63d7
--- /dev/null
+++ b/net/base/registry_controlled_domains/OWNERS
@@ -0,0 +1,5 @@
+pam@chromium.org
+pkasting@chromium.org
+rsleevi@chromium.org
+
+# COMPONENT: Internals>Network
diff --git a/net/cert/OWNERS b/net/cert/OWNERS
new file mode 100644
index 0000000..c5d05bc
--- /dev/null
+++ b/net/cert/OWNERS
@@ -0,0 +1,7 @@
+davidben@chromium.org
+eroman@chromium.org
+mattm@chromium.org
+rsleevi@chromium.org
+svaldez@chromium.org
+
+# COMPONENT: Internals>Network>Certificate
diff --git a/net/cert_net/OWNERS b/net/cert_net/OWNERS
new file mode 100644
index 0000000..b7d227e
--- /dev/null
+++ b/net/cert_net/OWNERS
@@ -0,0 +1,5 @@
+eroman@chromium.org
+mattm@chromium.org
+rsleevi@chromium.org
+
+# COMPONENT: Internals>Network>Certificate
diff --git a/net/cookies/OWNERS b/net/cookies/OWNERS
new file mode 100644
index 0000000..fb0dd2d
--- /dev/null
+++ b/net/cookies/OWNERS
@@ -0,0 +1,6 @@
+estark@chromium.org
+mkwst@chromium.org
+mmenke@chromium.org
+morlovich@chromium.org
+
+# COMPONENT: Internals>Network>Cookies
diff --git a/net/data/fuzzer_data/.gitattributes b/net/data/fuzzer_data/.gitattributes
new file mode 100644
index 0000000..77d76d4
--- /dev/null
+++ b/net/data/fuzzer_data/.gitattributes
@@ -0,0 +1 @@
+* binary
diff --git a/net/data/websocket/OWNERS b/net/data/websocket/OWNERS
new file mode 100644
index 0000000..d5f5069
--- /dev/null
+++ b/net/data/websocket/OWNERS
@@ -0,0 +1,5 @@
+ricea@chromium.org
+yhirano@chromium.org
+
+# TEAM: blink-network-dev@chromium.org
+# COMPONENT: Blink>Network>WebSockets
diff --git a/net/der/OWNERS b/net/der/OWNERS
new file mode 100644
index 0000000..4a621d5
--- /dev/null
+++ b/net/der/OWNERS
@@ -0,0 +1,6 @@
+davidben@chromium.org
+mattm@chromium.org
+nharper@chromium.org
+svaldez@chromium.org
+
+# COMPONENT: Internals>Network>Certificate
diff --git a/net/disk_cache/OWNERS b/net/disk_cache/OWNERS
new file mode 100644
index 0000000..7e2c65e
--- /dev/null
+++ b/net/disk_cache/OWNERS
@@ -0,0 +1,4 @@
+jkarlin@chromium.org
+morlovich@chromium.org
+
+# COMPONENT: Internals>Network>Cache
diff --git a/net/disk_cache/simple/OWNERS b/net/disk_cache/simple/OWNERS
new file mode 100644
index 0000000..bce1299
--- /dev/null
+++ b/net/disk_cache/simple/OWNERS
@@ -0,0 +1,3 @@
+pasko@chromium.org
+
+# COMPONENT: Internals>Network>Cache>Simple
diff --git a/net/dns/OWNERS b/net/dns/OWNERS
new file mode 100644
index 0000000..52b51f2
--- /dev/null
+++ b/net/dns/OWNERS
@@ -0,0 +1,4 @@
+per-file *_struct_traits*.*=set noparent
+per-file *_struct_traits*.*=file://ipc/SECURITY_OWNERS
+
+# COMPONENT: Internals>Network>DNS
diff --git a/net/extras/sqlite/DEPS b/net/extras/sqlite/DEPS
new file mode 100644
index 0000000..40ed5cf
--- /dev/null
+++ b/net/extras/sqlite/DEPS
@@ -0,0 +1,5 @@
+include_rules = [
+ "+base",
+ "+net",
+ "+sql",
+]
diff --git a/net/extras/sqlite/OWNERS b/net/extras/sqlite/OWNERS
new file mode 100644
index 0000000..5b2fdf3
--- /dev/null
+++ b/net/extras/sqlite/OWNERS
@@ -0,0 +1 @@
+pwnall@chromium.org
diff --git a/net/filter/OWNERS b/net/filter/OWNERS
new file mode 100644
index 0000000..89b786c
--- /dev/null
+++ b/net/filter/OWNERS
@@ -0,0 +1,3 @@
+file://net/OWNERS
+
+# COMPONENT: Internals>Network>Filters
diff --git a/net/ftp/OWNERS b/net/ftp/OWNERS
new file mode 100644
index 0000000..71184cb
--- /dev/null
+++ b/net/ftp/OWNERS
@@ -0,0 +1,4 @@
+eroman@chromium.org
+mmenke@chromium.org
+
+# COMPONENT: Internals>Network>FTP
diff --git a/net/http/OWNERS b/net/http/OWNERS
new file mode 100644
index 0000000..09d2e61
--- /dev/null
+++ b/net/http/OWNERS
@@ -0,0 +1,3 @@
+per-file http_cache_*=shivanisha@chromium.org
+per-file transport_security_state_static.*=nharper@chromium.org
+per-file transport_security_state_static.*=palmer@chromium.org
diff --git a/net/interfaces/OWNERS b/net/interfaces/OWNERS
new file mode 100644
index 0000000..b957a91
--- /dev/null
+++ b/net/interfaces/OWNERS
@@ -0,0 +1,8 @@
+per-file *.mojom=set noparent
+per-file *.mojom=file://ipc/SECURITY_OWNERS
+per-file *_struct_traits*.*=set noparent
+per-file *_struct_traits*.*=file://ipc/SECURITY_OWNERS
+per-file *.typemap=set noparent
+per-file *.typemap=file://ipc/SECURITY_OWNERS
+per-file *_mojom_traits*.*=set noparent
+per-file *_mojom_traits*.*=file://ipc/SECURITY_OWNERS
diff --git a/net/log/OWNERS b/net/log/OWNERS
new file mode 100644
index 0000000..be7a5ec
--- /dev/null
+++ b/net/log/OWNERS
@@ -0,0 +1,4 @@
+eroman@chromium.org
+mmenke@chormium.org
+
+# COMPONENT: Internals>Network>Logging
diff --git a/net/nqe/OWNERS b/net/nqe/OWNERS
new file mode 100644
index 0000000..30f461a
--- /dev/null
+++ b/net/nqe/OWNERS
@@ -0,0 +1,9 @@
+# Primary
+tbansal@chromium.org
+ryansturm@chromium.org
+
+# Secondary
+bengr@chromium.org
+
+# TEAM: net-dev@chromium.org
+# COMPONENT: Internals>Network>NetworkQuality
\ No newline at end of file
diff --git a/net/ntlm/OWNERS b/net/ntlm/OWNERS
new file mode 100644
index 0000000..156ce37
--- /dev/null
+++ b/net/ntlm/OWNERS
@@ -0,0 +1,6 @@
+asanka@chromium.org
+rsleevi@chromium.org
+zentaro@chromium.org
+
+# COMPONENT: Internals>Network>Auth
+# TEAM: net-dev@chromium.org
diff --git a/net/proxy_resolution/OWNERS b/net/proxy_resolution/OWNERS
new file mode 100644
index 0000000..b03baf3
--- /dev/null
+++ b/net/proxy_resolution/OWNERS
@@ -0,0 +1,6 @@
+eroman@chromium.org
+mmenke@chromium.org
+
+per-file *_v8*=jochen@chromium.org
+
+# COMPONENT: Internals>Network>Proxy
diff --git a/net/quic/OWNERS b/net/quic/OWNERS
new file mode 100644
index 0000000..1835f5b
--- /dev/null
+++ b/net/quic/OWNERS
@@ -0,0 +1,4 @@
+rch@chromium.org
+zhongyi@chromium.org
+
+# COMPONENT: Internals>Network>QUIC
diff --git a/net/socket/OWNERS b/net/socket/OWNERS
new file mode 100644
index 0000000..df81ef0
--- /dev/null
+++ b/net/socket/OWNERS
@@ -0,0 +1,2 @@
+# WebSocket
+per-file websocket*=file://net/websockets/OWNERS
diff --git a/net/spdy/OWNERS b/net/spdy/OWNERS
new file mode 100644
index 0000000..a5bbda6
--- /dev/null
+++ b/net/spdy/OWNERS
@@ -0,0 +1,3 @@
+file://net/third_party/quiche/src/http2/OWNERS
+
+# COMPONENT: Internals>Network>HTTP2
diff --git a/net/ssl/OWNERS b/net/ssl/OWNERS
new file mode 100644
index 0000000..0d1ddab
--- /dev/null
+++ b/net/ssl/OWNERS
@@ -0,0 +1,5 @@
+davidben@chromium.org
+mattm@chromium.org
+svaldez@chromium.org
+
+# COMPONENT: Internals>Network>SSL
diff --git a/net/test/android/javatests/src/org/chromium/net/test/OWNERS b/net/test/android/javatests/src/org/chromium/net/test/OWNERS
new file mode 100644
index 0000000..89442ab
--- /dev/null
+++ b/net/test/android/javatests/src/org/chromium/net/test/OWNERS
@@ -0,0 +1,2 @@
+per-file *.aidl=set noparent
+per-file *.aidl=file://ipc/SECURITY_OWNERS
\ No newline at end of file
diff --git a/net/third_party/nss/OWNERS b/net/third_party/nss/OWNERS
new file mode 100644
index 0000000..d332218
--- /dev/null
+++ b/net/third_party/nss/OWNERS
@@ -0,0 +1,6 @@
+agl@chromium.org
+davidben@chromium.org
+rsleevi@chromium.org
+wtc@chromium.org
+
+# COMPONENT: Internals>Network>SSL
diff --git a/net/third_party/uri_template/OWNERS b/net/third_party/uri_template/OWNERS
new file mode 100644
index 0000000..3c466ff
--- /dev/null
+++ b/net/third_party/uri_template/OWNERS
@@ -0,0 +1,3 @@
+mmenke@chromium.org
+
+# COMPONENT: Internals>Network>DNS
diff --git a/net/tools/DEPS b/net/tools/DEPS
new file mode 100644
index 0000000..3b22d68
--- /dev/null
+++ b/net/tools/DEPS
@@ -0,0 +1,7 @@
+include_rules = [
+ "+base/i18n",
+]
+
+skip_child_includes = [
+ "balsa",
+]
diff --git a/net/tools/dafsa/PRESUBMIT.py b/net/tools/dafsa/PRESUBMIT.py
new file mode 100644
index 0000000..253fcbb
--- /dev/null
+++ b/net/tools/dafsa/PRESUBMIT.py
@@ -0,0 +1,32 @@
+# 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.
+
+
+"""Chromium presubmit script for src/net/tools/dafsa."""
+
+
+def _RunMakeDafsaTests(input_api, output_api):
+ """Runs unittest for make_dafsa if any related file has been modified."""
+ files = ('net/tools/dafsa/make_dafsa.py',
+ 'net/tools/dafsa/make_dafsa_unittest.py')
+ if not any(f in input_api.LocalPaths() for f in files):
+ return []
+ test_path = input_api.os_path.join(input_api.PresubmitLocalPath(),
+ 'make_dafsa_unittest.py')
+ cmd_name = 'make_dafsa_unittest'
+ cmd = [input_api.python_executable, test_path]
+ test_cmd = input_api.Command(
+ name=cmd_name,
+ cmd=cmd,
+ kwargs={},
+ message=output_api.PresubmitPromptWarning)
+ return input_api.RunTests([test_cmd])
+
+
+def CheckChangeOnUpload(input_api, output_api):
+ return _RunMakeDafsaTests(input_api, output_api)
+
+
+def CheckChangeOnCommit(input_api, output_api):
+ return _RunMakeDafsaTests(input_api, output_api)
diff --git a/net/tools/tld_cleanup/OWNERS b/net/tools/tld_cleanup/OWNERS
new file mode 100644
index 0000000..f6e734a
--- /dev/null
+++ b/net/tools/tld_cleanup/OWNERS
@@ -0,0 +1 @@
+pam@chromium.org
diff --git a/net/websockets/OWNERS b/net/websockets/OWNERS
new file mode 100644
index 0000000..d5f5069
--- /dev/null
+++ b/net/websockets/OWNERS
@@ -0,0 +1,5 @@
+ricea@chromium.org
+yhirano@chromium.org
+
+# TEAM: blink-network-dev@chromium.org
+# COMPONENT: Blink>Network>WebSockets
diff --git a/precommit_hooks/check_copyright_year.py b/precommit_hooks/check_copyright_year.py
index 6e9f489..e682ac4 100755
--- a/precommit_hooks/check_copyright_year.py
+++ b/precommit_hooks/check_copyright_year.py
@@ -70,13 +70,13 @@
if match:
created_year = int(match.group('created'))
if filename in new_files and created_year != current_year:
- errors.append(f'Copyright header for file {filename}'
- f' has wrong year {created_year}')
+ print(f'Copyright header for file {filename} has wrong year '
+ f'{created_year}')
year = match.group('current')
if year and int(year) != current_year:
- errors.append(f'Copyright header for file {filename}'
- f' has wrong ending year {year}')
+ print(f'Copyright header for file {filename} has wrong ending year'
+ f' {year}')
# Strip to get rid of possible newline
author = match.group('author').rstrip()
diff --git a/starboard/BUILD.gn b/starboard/BUILD.gn
index 22b5233..eefbcc9 100644
--- a/starboard/BUILD.gn
+++ b/starboard/BUILD.gn
@@ -16,17 +16,16 @@
testonly = true
deps = [
- ":starboard",
+ ":default",
"//starboard/client_porting/cwrappers:cwrappers_test",
"//starboard/client_porting/eztime",
"//starboard/client_porting/eztime:eztime_test",
"//starboard/client_porting/icu_init",
"//starboard/client_porting/poem:poem_unittests",
"//starboard/examples/window:starboard_window_example",
+ "//starboard/loader_app:app_key_files_test",
"//starboard/nplb",
"//starboard/nplb/nplb_evergreen_compat_tests",
-
- # "//starboard/tools", TODO(andrewsavage)
]
if (gl_type != "none") {
@@ -67,6 +66,13 @@
}
}
+group("default") {
+ deps = [
+ ":starboard",
+ "//starboard/tools:build_app_launcher_zip",
+ ]
+}
+
group("starboard") {
public_deps = [
":starboard_headers_only",
@@ -150,10 +156,11 @@
"types.h",
"user.h",
"window.h",
-
- # Include private headers, if present.
- # "<!@pymod_do_main(starboard.build.gyp_functions file_glob <(DEPTH)/starboard/private *.h)",
]
+
+ if (is_internal_build) {
+ public_deps = [ "//starboard/private:private_starboard_headers" ]
+ }
}
if (platform_tests_path == "") {
@@ -168,5 +175,7 @@
":starboard",
"//testing/gmock",
]
+
+ content_deps = [ "//third_party/icu:icudata" ]
}
}
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/MediaPlaybackService.java b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/MediaPlaybackService.java
index 1b54143..e24faf6 100644
--- a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/MediaPlaybackService.java
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/MediaPlaybackService.java
@@ -40,13 +40,14 @@
@Override
public void onCreate() {
- super.onCreate();
- Log.i(TAG, "Creating a Media playback foreground service.");
if (getStarboardBridge() == null) {
Log.e(TAG, "StarboardBridge already destroyed.");
return;
}
+ Log.i(TAG, "Creating a Media playback foreground service.");
+ super.onCreate();
getStarboardBridge().onServiceStart(this);
+ createNotificationChannel();
}
@Override
@@ -69,13 +70,12 @@
Log.e(TAG, "StarboardBridge already destroyed.");
return;
}
+ Log.i(TAG, "Destroying the Media playback service.");
getStarboardBridge().onServiceDestroy(this);
super.onDestroy();
- Log.i(TAG, "Destroying the Media playback service.");
}
public void startService() {
- createChannel();
try {
startForeground(NOTIFICATION_ID, buildNotification());
} catch (IllegalStateException e) {
@@ -84,25 +84,15 @@
}
public void stopService() {
- // Do not remove notification here.
- stopForeground(false);
+ // Let service itself handle notification deletion.
+ stopForeground(true);
stopSelf();
- // Delete notification after foreground stopped.
- deleteChannel();
- hideNotification();
}
- private void hideNotification() {
- Log.i(TAG, "Hiding notification after stopped the service");
- NotificationManager notificationManager =
- (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
- notificationManager.cancel(NOTIFICATION_ID);
- }
-
- private void createChannel() {
+ private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= 26) {
try {
- createChannelInternalV26();
+ createNotificationChannelInternalV26();
} catch (RemoteException e) {
Log.e(TAG, "Failed to create Notification Channel.", e);
}
@@ -110,7 +100,7 @@
}
@RequiresApi(26)
- private void createChannelInternalV26() throws RemoteException {
+ private void createNotificationChannelInternalV26() throws RemoteException {
NotificationManager notificationManager =
(NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel channel =
@@ -122,19 +112,6 @@
notificationManager.createNotificationChannel(channel);
}
- public void deleteChannel() {
- if (Build.VERSION.SDK_INT >= 26) {
- deleteChannelInternalV26();
- }
- }
-
- @RequiresApi(26)
- private void deleteChannelInternalV26() {
- NotificationManager notificationManager =
- (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
- notificationManager.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
- }
-
Notification buildNotification() {
NotificationCompat.Builder builder =
new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java
index f265e98..5a13b01 100644
--- a/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java
@@ -60,14 +60,18 @@
int channelCount,
int preferredBufferSizeInBytes,
boolean enableAudioDeviceCallback,
- int tunnelModeAudioSessionId) {
+ boolean enablePcmContentTypeMovie,
+ int tunnelModeAudioSessionId,
+ boolean isWebAudio) {
AudioTrackBridge audioTrackBridge =
new AudioTrackBridge(
sampleType,
sampleRate,
channelCount,
preferredBufferSizeInBytes,
- tunnelModeAudioSessionId);
+ enablePcmContentTypeMovie,
+ tunnelModeAudioSessionId,
+ isWebAudio);
if (!audioTrackBridge.isAudioTrackValid()) {
Log.e(TAG, "AudioTrackBridge has invalid audio track");
return null;
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java
index 0001fde..0a7270e 100644
--- a/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java
@@ -64,7 +64,9 @@
int sampleRate,
int channelCount,
int preferredBufferSizeInBytes,
- int tunnelModeAudioSessionId) {
+ boolean enablePcmContentTypeMovie,
+ int tunnelModeAudioSessionId,
+ boolean isWebAudio) {
tunnelModeEnabled = tunnelModeAudioSessionId != -1;
int channelConfig;
@@ -109,15 +111,13 @@
.build();
} else {
// TODO: Support ENCODING_E_AC3_JOC for api level 28 or later.
- final boolean is_surround =
+ final boolean isSurround =
sampleType == AudioFormat.ENCODING_AC3 || sampleType == AudioFormat.ENCODING_E_AC3;
- // TODO: We start to enforce |CONTENT_TYPE_MOVIE| for surround playback, investigate if we
- // can use |CONTENT_TYPE_MOVIE| for all non-surround AudioTrack used by video
- // playback.
+ final boolean useContentTypeMovie = isSurround || (!isWebAudio && enablePcmContentTypeMovie);
attributes =
new AudioAttributes.Builder()
.setContentType(
- is_surround
+ useContentTypeMovie
? AudioAttributes.CONTENT_TYPE_MOVIE
: AudioAttributes.CONTENT_TYPE_MUSIC)
.setUsage(AudioAttributes.USAGE_MEDIA)
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java
index f4f6d3e..4dd5674 100644
--- a/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java
@@ -1089,6 +1089,15 @@
// https://github.com/google/ExoPlayer/blob/8595c65678a181296cdf673eacb93d8135479340/library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java
private void maybeSetMaxInputSize(MediaFormat format) {
if (format.containsKey(android.media.MediaFormat.KEY_MAX_INPUT_SIZE)) {
+ try {
+ Log.i(
+ TAG,
+ "Use default value for KEY_MAX_INPUT_SIZE: "
+ + format.getInteger(android.media.MediaFormat.KEY_MAX_INPUT_SIZE)
+ + '.');
+ } catch (Exception e) {
+ Log.w(TAG, "MediaFormat.getInteger(KEY_MAX_INPUT_SIZE) failed with exception: ", e);
+ }
// Already set. The source of the format may know better, so do nothing.
return;
}
@@ -1131,6 +1140,17 @@
// Estimate the maximum input size assuming three channel 4:2:0 subsampled input frames.
int maxInputSize = (maxPixels * 3) / (2 * minCompressionRatio);
format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, maxInputSize);
+ try {
+ Log.i(
+ TAG,
+ "KEY_MAX_INPUT_SIZE is "
+ + format.getInteger(android.media.MediaFormat.KEY_MAX_INPUT_SIZE)
+ + " after setting it to "
+ + maxInputSize
+ + '.');
+ } catch (Exception e) {
+ Log.w(TAG, "MediaFormat.getInteger(KEY_MAX_INPUT_SIZE) failed with exception: ", e);
+ }
}
@SuppressWarnings("unused")
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/VideoFrameReleaseTimeHelper.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/VideoFrameReleaseTimeHelper.java
index 6afac46..622e92b 100644
--- a/starboard/android/apk/app/src/main/java/dev/cobalt/media/VideoFrameReleaseTimeHelper.java
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/VideoFrameReleaseTimeHelper.java
@@ -35,12 +35,10 @@
import android.view.Choreographer;
import android.view.Choreographer.FrameCallback;
import android.view.Display;
-import androidx.annotation.RequiresApi;
import dev.cobalt.util.DisplayUtil;
import dev.cobalt.util.UsedByNative;
/** Makes a best effort to adjust frame release timestamps for a smoother visual result. */
-@RequiresApi(16)
@SuppressWarnings("unused")
@UsedByNative
public final class VideoFrameReleaseTimeHelper {
diff --git a/starboard/android/arm/args.gn b/starboard/android/arm/args.gn
new file mode 100644
index 0000000..c3b55c5
--- /dev/null
+++ b/starboard/android/arm/args.gn
@@ -0,0 +1,17 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+target_platform = "android-arm"
+target_os = "android"
+target_cpu = "arm"
diff --git a/starboard/android/arm/platform_configuration/BUILD.gn b/starboard/android/arm/platform_configuration/BUILD.gn
index b8996b8..56a3cda 100644
--- a/starboard/android/arm/platform_configuration/BUILD.gn
+++ b/starboard/android/arm/platform_configuration/BUILD.gn
@@ -16,5 +16,9 @@
configs = [ "//starboard/android/shared/platform_configuration" ]
if (current_toolchain == default_toolchain) {
cflags = [ "-march=armv7-a" ]
+ ldflags = [
+ # Mimic build/cmake/android.toolchain.cmake in the Android NDK.
+ "-Wl,--exclude-libs,libunwind.a",
+ ]
}
}
diff --git a/starboard/android/arm/test_filters.py b/starboard/android/arm/test_filters.py
new file mode 100644
index 0000000..8529436
--- /dev/null
+++ b/starboard/android/arm/test_filters.py
@@ -0,0 +1,28 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Android ARM Platform Test Filters."""
+
+from starboard.android.shared import test_filters as shared_test_filters
+
+
+def CreateTestFilters():
+ return AndroidArmTestFilters()
+
+
+class AndroidArmTestFilters(shared_test_filters.TestFilters):
+ """Starboard Android ARM Platform Test Filters."""
+
+ def GetTestFilters(self):
+ filters = super(AndroidArmTestFilters, self).GetTestFilters()
+ return filters
diff --git a/starboard/android/arm/toolchain/BUILD.gn b/starboard/android/arm/toolchain/BUILD.gn
index 6ffe4f4..ddc2c46 100644
--- a/starboard/android/arm/toolchain/BUILD.gn
+++ b/starboard/android/arm/toolchain/BUILD.gn
@@ -12,13 +12,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import("//build/config/clang/clang.gni")
import("//build/toolchain/gcc_toolchain.gni")
-
-_home_dir = getenv("HOME")
-_clang_base_path = "$_home_dir/starboard-toolchains/x86_64-linux-gnu-clang-chromium-365097-f7e52fbd-8"
+import("//starboard/android/shared/toolchain/toolchain.gni")
clang_toolchain("host") {
- clang_base_path = _clang_base_path
+ clang_base_path = clang_base_path
toolchain_args = {
current_os = "linux"
@@ -26,11 +25,8 @@
}
}
-_android_toolchain_path =
- "$android_ndk_path/toolchains/llvm/prebuilt/linux-x86_64"
-
gcc_toolchain("target") {
- prefix = rebase_path("$_android_toolchain_path/bin", root_build_dir)
+ prefix = rebase_path("$android_toolchain_path/bin", root_build_dir)
cc = "$prefix/armv7a-linux-androideabi${android_ndk_api_level}-clang"
cxx = "$prefix/armv7a-linux-androideabi${android_ndk_api_level}-clang++"
ld = cxx
diff --git a/starboard/android/arm64/args.gn b/starboard/android/arm64/args.gn
new file mode 100644
index 0000000..64c4ff9
--- /dev/null
+++ b/starboard/android/arm64/args.gn
@@ -0,0 +1,17 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+target_platform = "android-arm64"
+target_os = "android"
+target_cpu = "arm64"
diff --git a/starboard/android/arm64/test_filters.py b/starboard/android/arm64/test_filters.py
new file mode 100644
index 0000000..e0454ca
--- /dev/null
+++ b/starboard/android/arm64/test_filters.py
@@ -0,0 +1,28 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Android ARM-64 Platform Test Filters."""
+
+from starboard.android.shared import test_filters as shared_test_filters
+
+
+def CreateTestFilters():
+ return AndroidArm64TestFilters()
+
+
+class AndroidArm64TestFilters(shared_test_filters.TestFilters):
+ """Starboard Android ARM-64 Platform Test Filters."""
+
+ def GetTestFilters(self):
+ filters = super(AndroidArm64TestFilters, self).GetTestFilters()
+ return filters
diff --git a/starboard/android/arm64/toolchain/BUILD.gn b/starboard/android/arm64/toolchain/BUILD.gn
index 3f07d29..554b007 100644
--- a/starboard/android/arm64/toolchain/BUILD.gn
+++ b/starboard/android/arm64/toolchain/BUILD.gn
@@ -12,13 +12,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import("//build/config/clang/clang.gni")
import("//build/toolchain/gcc_toolchain.gni")
-
-_home_dir = getenv("HOME")
-_clang_base_path = "$_home_dir/starboard-toolchains/x86_64-linux-gnu-clang-chromium-365097-f7e52fbd-8"
+import("//starboard/android/shared/toolchain/toolchain.gni")
clang_toolchain("host") {
- clang_base_path = _clang_base_path
+ clang_base_path = clang_base_path
toolchain_args = {
current_os = "linux"
@@ -26,11 +25,8 @@
}
}
-_android_toolchain_path =
- "$android_ndk_path/toolchains/llvm/prebuilt/linux-x86_64"
-
gcc_toolchain("target") {
- prefix = rebase_path("$_android_toolchain_path/bin", root_build_dir)
+ prefix = rebase_path("$android_toolchain_path/bin", root_build_dir)
cc = "$prefix/aarch64-linux-android${android_ndk_api_level}-clang"
cxx = "$prefix/aarch64-linux-android${android_ndk_api_level}-clang++"
ld = cxx
diff --git a/starboard/android/arm64/vulkan/args.gn b/starboard/android/arm64/vulkan/args.gn
new file mode 100644
index 0000000..64c4ff9
--- /dev/null
+++ b/starboard/android/arm64/vulkan/args.gn
@@ -0,0 +1,17 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+target_platform = "android-arm64"
+target_os = "android"
+target_cpu = "arm64"
diff --git a/starboard/android/arm64/vulkan/test_filters.py b/starboard/android/arm64/vulkan/test_filters.py
new file mode 100644
index 0000000..374038d
--- /dev/null
+++ b/starboard/android/arm64/vulkan/test_filters.py
@@ -0,0 +1,28 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Android ARM-64 Vulkan Platform Test Filters."""
+
+from starboard.android.shared import test_filters as shared_test_filters
+
+
+def CreateTestFilters():
+ return AndroidArm64VulkanTestFilters()
+
+
+class AndroidArm64VulkanTestFilters(shared_test_filters.TestFilters):
+ """Starboard Android ARM-64 Vulkan Platform Test Filters."""
+
+ def GetTestFilters(self):
+ filters = super(AndroidArm64VulkanTestFilters, self).GetTestFilters()
+ return filters
diff --git a/starboard/android/arm64/vulkan/toolchain/BUILD.gn b/starboard/android/arm64/vulkan/toolchain/BUILD.gn
index 3f07d29..554b007 100644
--- a/starboard/android/arm64/vulkan/toolchain/BUILD.gn
+++ b/starboard/android/arm64/vulkan/toolchain/BUILD.gn
@@ -12,13 +12,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import("//build/config/clang/clang.gni")
import("//build/toolchain/gcc_toolchain.gni")
-
-_home_dir = getenv("HOME")
-_clang_base_path = "$_home_dir/starboard-toolchains/x86_64-linux-gnu-clang-chromium-365097-f7e52fbd-8"
+import("//starboard/android/shared/toolchain/toolchain.gni")
clang_toolchain("host") {
- clang_base_path = _clang_base_path
+ clang_base_path = clang_base_path
toolchain_args = {
current_os = "linux"
@@ -26,11 +25,8 @@
}
}
-_android_toolchain_path =
- "$android_ndk_path/toolchains/llvm/prebuilt/linux-x86_64"
-
gcc_toolchain("target") {
- prefix = rebase_path("$_android_toolchain_path/bin", root_build_dir)
+ prefix = rebase_path("$android_toolchain_path/bin", root_build_dir)
cc = "$prefix/aarch64-linux-android${android_ndk_api_level}-clang"
cxx = "$prefix/aarch64-linux-android${android_ndk_api_level}-clang++"
ld = cxx
diff --git a/starboard/android/shared/BUILD.gn b/starboard/android/shared/BUILD.gn
index 4ef3355..d2ac6c2 100644
--- a/starboard/android/shared/BUILD.gn
+++ b/starboard/android/shared/BUILD.gn
@@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import("//starboard/android/shared/toolchain/toolchain.gni")
import("//starboard/shared/starboard/media/media_tests.gni")
import("//starboard/shared/starboard/player/buildfiles.gni")
import("//starboard/shared/starboard/player/player_tests.gni")
@@ -224,7 +225,6 @@
"//starboard/shared/starboard/media/media_get_buffer_storage_type.cc",
"//starboard/shared/starboard/media/media_get_progressive_buffer_budget.cc",
"//starboard/shared/starboard/media/media_get_video_buffer_budget.cc",
- "//starboard/shared/starboard/media/media_is_buffer_pool_allocate_on_demand.cc",
"//starboard/shared/starboard/media/media_is_buffer_using_memory_pool.cc",
"//starboard/shared/starboard/media/mime_type.cc",
"//starboard/shared/starboard/media/mime_type.h",
@@ -361,6 +361,7 @@
"media_get_initial_buffer_capacity.cc",
"media_get_max_buffer_capacity.cc",
"media_is_audio_supported.cc",
+ "media_is_buffer_pool_allocate_on_demand.cc",
"media_is_video_supported.cc",
"microphone_impl.cc",
"network_status_impl.cc",
@@ -452,8 +453,8 @@
if (is_internal_build) {
sources += [
- "input_events_filter.cc",
- "input_events_filter.h",
+ "internal/input_events_filter.cc",
+ "internal/input_events_filter.h",
]
defines = [ "STARBOARD_INPUT_EVENTS_FILTER" ]
deps += [ "//starboard/android/shared/drm_system_extension" ]
diff --git a/starboard/android/shared/audio_renderer_passthrough.cc b/starboard/android/shared/audio_renderer_passthrough.cc
index 1f0849c..f80c44e 100644
--- a/starboard/android/shared/audio_renderer_passthrough.cc
+++ b/starboard/android/shared/audio_renderer_passthrough.cc
@@ -20,6 +20,7 @@
#include "starboard/android/shared/audio_decoder_passthrough.h"
#include "starboard/android/shared/jni_env_ext.h"
#include "starboard/android/shared/jni_utils.h"
+#include "starboard/common/string.h"
#include "starboard/memory.h"
namespace starboard {
@@ -384,7 +385,8 @@
optional<SbMediaAudioSampleType>(), // Not required in passthrough mode
audio_sample_info_.number_of_channels,
audio_sample_info_.samples_per_second, kPreferredBufferSizeInBytes,
- enable_audio_device_callback_, kTunnelModeAudioSessionId));
+ enable_audio_device_callback_, false /* enable_pcm_content_type_movie */,
+ kTunnelModeAudioSessionId, false /* is_web_audio */));
if (!audio_track_bridge->is_valid()) {
error_cb_(kSbPlayerErrorDecode, "Error creating AudioTrackBridge");
diff --git a/starboard/android/shared/audio_sink_min_required_frames_tester.cc b/starboard/android/shared/audio_sink_min_required_frames_tester.cc
index 1c7d413..80656e2 100644
--- a/starboard/android/shared/audio_sink_min_required_frames_tester.cc
+++ b/starboard/android/shared/audio_sink_min_required_frames_tester.cc
@@ -125,7 +125,7 @@
GetSampleSize(task.sample_type),
&MinRequiredFramesTester::UpdateSourceStatusFunc,
&MinRequiredFramesTester::ConsumeFramesFunc,
- &MinRequiredFramesTester::ErrorFunc, 0, -1, false, this);
+ &MinRequiredFramesTester::ErrorFunc, 0, -1, false, false, false, this);
{
ScopedLock scoped_lock(mutex_);
wait_timeout = !condition_variable_.WaitTimed(kSbTimeSecond * 5);
diff --git a/starboard/android/shared/audio_track_audio_sink_type.cc b/starboard/android/shared/audio_track_audio_sink_type.cc
index 57e9aed..0e9d29e 100644
--- a/starboard/android/shared/audio_track_audio_sink_type.cc
+++ b/starboard/android/shared/audio_track_audio_sink_type.cc
@@ -83,6 +83,8 @@
SbTime start_time,
int tunnel_mode_audio_session_id,
bool enable_audio_device_callback,
+ bool enable_pcm_content_type_movie,
+ bool is_web_audio,
void* context)
: type_(type),
channels_(channels),
@@ -106,7 +108,9 @@
sampling_frequency_hz,
preferred_buffer_size_in_bytes,
enable_audio_device_callback,
- tunnel_mode_audio_session_id) {
+ enable_pcm_content_type_movie,
+ tunnel_mode_audio_session_id,
+ is_web_audio) {
SB_DCHECK(update_source_status_func_);
SB_DCHECK(consume_frames_func_);
SB_DCHECK(frame_buffer_);
@@ -167,11 +171,7 @@
SB_LOG(INFO) << "AudioTrackAudioSink thread started.";
int accumulated_written_frames = 0;
- // TODO: |last_playback_head_changed_at| is also reset when a warning is
- // logged after the playback head position hasn't been updated for a
- // while. We should refine the name to make it better reflect its
- // usage.
- SbTime last_playback_head_changed_at = -1;
+ SbTime last_playback_head_event_at = -1;
SbTime playback_head_not_changed_duration = 0;
SbTime last_written_succeeded_at = -1;
@@ -197,21 +197,21 @@
playback_head_position - last_playback_head_position_;
SbTime now = SbTimeGetMonotonicNow();
- if (last_playback_head_changed_at == -1) {
- last_playback_head_changed_at = now;
+ if (last_playback_head_event_at == -1) {
+ last_playback_head_event_at = now;
}
if (last_playback_head_position_ == playback_head_position) {
- SbTime elapsed = now - last_playback_head_changed_at;
+ SbTime elapsed = now - last_playback_head_event_at;
if (elapsed > 5 * kSbTimeSecond) {
playback_head_not_changed_duration += elapsed;
- last_playback_head_changed_at = now;
+ last_playback_head_event_at = now;
SB_LOG(INFO) << "last playback head position is "
<< last_playback_head_position_
<< " and it hasn't been updated for " << elapsed
<< " microseconds.";
}
} else {
- last_playback_head_changed_at = now;
+ last_playback_head_event_at = now;
playback_head_not_changed_duration = 0;
}
@@ -243,7 +243,7 @@
bridge_.Pause();
} else if (!was_playing && is_playing) {
was_playing = true;
- last_playback_head_changed_at = -1;
+ last_playback_head_event_at = -1;
playback_head_not_changed_duration = 0;
last_written_succeeded_at = -1;
bridge_.Play();
@@ -424,11 +424,15 @@
const int kTunnelModeAudioSessionId = -1;
// Disable AudioDeviceCallback for WebAudio.
const bool kEnableAudioDeviceCallback = false;
+ const bool kIsWebAudio = true;
+ // Disable AudioAttributes::CONTENT_TYPE_MOVIE for WebAudio.
+ const bool kEnablePcmContentTypeMovie = false;
return Create(channels, sampling_frequency_hz, audio_sample_type,
audio_frame_storage_type, frame_buffers, frames_per_channel,
update_source_status_func, consume_frames_func, error_func,
kStartTime, kTunnelModeAudioSessionId,
- kEnableAudioDeviceCallback, context);
+ kEnableAudioDeviceCallback, kEnablePcmContentTypeMovie,
+ kIsWebAudio, context);
}
SbAudioSink AudioTrackAudioSinkType::Create(
@@ -444,6 +448,8 @@
SbTime start_media_time,
int tunnel_mode_audio_session_id,
bool enable_audio_device_callback,
+ bool enable_pcm_content_type_movie,
+ bool is_web_audio,
void* context) {
int min_required_frames = SbAudioSinkGetMinBufferSizeInFrames(
channels, audio_sample_type, sampling_frequency_hz);
@@ -455,7 +461,8 @@
frames_per_channel, preferred_buffer_size_in_bytes,
update_source_status_func, consume_frames_func, error_func,
start_media_time, tunnel_mode_audio_session_id,
- enable_audio_device_callback, context);
+ enable_audio_device_callback, enable_pcm_content_type_movie, is_web_audio,
+ context);
if (!audio_sink->IsAudioTrackValid()) {
SB_DLOG(ERROR)
<< "AudioTrackAudioSinkType::Create failed to create audio track";
diff --git a/starboard/android/shared/audio_track_audio_sink_type.h b/starboard/android/shared/audio_track_audio_sink_type.h
index adf050d..035b093 100644
--- a/starboard/android/shared/audio_track_audio_sink_type.h
+++ b/starboard/android/shared/audio_track_audio_sink_type.h
@@ -69,6 +69,8 @@
SbTime start_time,
int tunnel_mode_audio_session_id,
bool enable_audio_device_callback,
+ bool enable_pcm_content_type_movie,
+ bool is_web_audio,
void* context);
bool IsValid(SbAudioSink audio_sink) override {
@@ -112,6 +114,8 @@
SbTime start_media_time,
int tunnel_mode_audio_session_id,
bool enable_audio_device_callback,
+ bool enable_pcm_content_type_movie,
+ bool is_web_audio,
void* context);
~AudioTrackAudioSink() override;
diff --git a/starboard/android/shared/audio_track_bridge.cc b/starboard/android/shared/audio_track_bridge.cc
index 79fe83f..05b9c9b 100644
--- a/starboard/android/shared/audio_track_bridge.cc
+++ b/starboard/android/shared/audio_track_bridge.cc
@@ -40,7 +40,9 @@
int sampling_frequency_hz,
int preferred_buffer_size_in_bytes,
bool enable_audio_device_callback,
- int tunnel_mode_audio_session_id) {
+ bool enable_pcm_content_type_movie,
+ int tunnel_mode_audio_session_id,
+ bool is_web_audio) {
if (coding_type == kSbMediaAudioCodingTypePcm) {
SB_DCHECK(SbAudioSinkIsAudioSampleTypeSupported(sample_type.value()));
@@ -63,10 +65,11 @@
"getAudioOutputManager", "()Ldev/cobalt/media/AudioOutputManager;"));
jobject j_audio_track_bridge = env->CallObjectMethodOrAbort(
j_audio_output_manager.Get(), "createAudioTrackBridge",
- "(IIIIZI)Ldev/cobalt/media/AudioTrackBridge;",
+ "(IIIIZZIZ)Ldev/cobalt/media/AudioTrackBridge;",
GetAudioFormatSampleType(coding_type, sample_type), sampling_frequency_hz,
channels, preferred_buffer_size_in_bytes, enable_audio_device_callback,
- tunnel_mode_audio_session_id);
+ enable_pcm_content_type_movie, tunnel_mode_audio_session_id,
+ is_web_audio);
if (!j_audio_track_bridge) {
// One of the cases that this may hit is when output happened to be switched
// to a device that doesn't support tunnel mode.
diff --git a/starboard/android/shared/audio_track_bridge.h b/starboard/android/shared/audio_track_bridge.h
index 607837d..3804b6a 100644
--- a/starboard/android/shared/audio_track_bridge.h
+++ b/starboard/android/shared/audio_track_bridge.h
@@ -42,7 +42,9 @@
int sampling_frequency_hz,
int preferred_buffer_size_in_bytes,
bool enable_audio_device_callback,
- int tunnel_mode_audio_session_id);
+ bool enable_pcm_content_type_movie,
+ int tunnel_mode_audio_session_id,
+ bool is_web_audio);
~AudioTrackBridge();
static int GetMinBufferSizeInFrames(SbMediaAudioSampleType sample_type,
diff --git a/starboard/android/shared/input_events_generator.h b/starboard/android/shared/input_events_generator.h
index 9ea7e12..c192079 100644
--- a/starboard/android/shared/input_events_generator.h
+++ b/starboard/android/shared/input_events_generator.h
@@ -21,7 +21,7 @@
#include <vector>
#ifdef STARBOARD_INPUT_EVENTS_FILTER
-#include "starboard/android/shared/input_events_filter.h"
+#include "starboard/android/shared/internal/input_events_filter.h"
#endif
#include "starboard/input.h"
@@ -78,7 +78,7 @@
SbWindow window_;
#ifdef STARBOARD_INPUT_EVENTS_FILTER
- InputEventsFilter input_events_filter_;
+ internal::InputEventsFilter input_events_filter_;
#endif
// Map the device id with joystick flat position.
diff --git a/starboard/android/shared/install_target.gni b/starboard/android/shared/install_target.gni
index dbfe29c..aef5ff2 100644
--- a/starboard/android/shared/install_target.gni
+++ b/starboard/android/shared/install_target.gni
@@ -12,23 +12,25 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import("//starboard/android/shared/toolchain/toolchain.gni")
+import("//starboard/build/config/install.gni")
+
template("install_target") {
not_needed(invoker, [ "type" ])
- content_dir = "usr/share/cobalt"
+
+ installable_target_name = invoker.installable_target_name
action("build_${target_name}_apk") {
- forward_variables_from(invoker,
- [
- "installable_target_name",
- "testonly",
- ])
- deps = [
+ forward_variables_from(invoker, [ "testonly" ])
+
+ deps = invoker.deps
+ deps += [
":$installable_target_name",
"//starboard/android/apk:apk_sources",
]
target_output = "$root_out_dir/lib${installable_target_name}.so"
- gradle_content_dir = "$sb_install_output_dir/$content_dir"
+ gradle_content_dir = "$sb_install_output_dir/$sb_install_content_subdir"
gradle_files_dir = "$root_out_dir/gradle/$installable_target_name"
if (is_gold) {
gradle_build_type = "release"
@@ -80,4 +82,12 @@
"assembleCobalt_$gradle_build_type",
]
}
+
+ group(target_name) {
+ forward_variables_from(invoker, [ "testonly" ])
+ deps = [
+ ":$installable_target_name",
+ ":build_${target_name}_apk",
+ ]
+ }
}
diff --git a/starboard/android/shared/media_common.h b/starboard/android/shared/media_common.h
index aa17a47..f9e5a56 100644
--- a/starboard/android/shared/media_common.h
+++ b/starboard/android/shared/media_common.h
@@ -15,14 +15,12 @@
#ifndef STARBOARD_ANDROID_SHARED_MEDIA_COMMON_H_
#define STARBOARD_ANDROID_SHARED_MEDIA_COMMON_H_
-#include <deque>
-#include <queue>
+#include <cstring>
#include "starboard/android/shared/jni_env_ext.h"
#include "starboard/common/log.h"
#include "starboard/common/mutex.h"
#include "starboard/common/optional.h"
-#include "starboard/common/string.h"
#include "starboard/configuration.h"
#include "starboard/media.h"
#include "starboard/shared/starboard/player/filter/audio_frame_tracker.h"
@@ -36,10 +34,6 @@
strcmp(key_system, "com.widevine.alpha") == 0;
}
-inline bool IsWidevineL3(const char* key_system) {
- return strcmp(key_system, "com.youtube.widevine.l3") == 0;
-}
-
// Map a supported |SbMediaAudioCodec| into its corresponding mime type
// string. Returns |nullptr| if |audio_codec| is not supported.
// On return, |is_passthrough| will be set to true if the codec should be played
diff --git a/starboard/android/shared/media_is_audio_supported.cc b/starboard/android/shared/media_is_audio_supported.cc
index 419de2a..d9efe42 100644
--- a/starboard/android/shared/media_is_audio_supported.cc
+++ b/starboard/android/shared/media_is_audio_supported.cc
@@ -52,6 +52,10 @@
mime_type.RegisterBoolParameter("tunnelmode");
// Enables audio passthrough if the codec supports it.
mime_type.RegisterBoolParameter("audiopassthrough");
+ // Allows for disabling the CONTENT_TYPE_MOVIE AudioAttribute for
+ // non-tunneled playbacks with PCM audio. Enabled by default.
+ // (https://developer.android.com/reference/android/media/AudioAttributes#CONTENT_TYPE_MOVIE)
+ mime_type.RegisterBoolParameter("enablepcmcontenttypemovie");
if (!mime_type.is_valid()) {
return false;
diff --git a/starboard/android/shared/media_is_buffer_pool_allocate_on_demand.cc b/starboard/android/shared/media_is_buffer_pool_allocate_on_demand.cc
new file mode 100644
index 0000000..3fc0676
--- /dev/null
+++ b/starboard/android/shared/media_is_buffer_pool_allocate_on_demand.cc
@@ -0,0 +1,20 @@
+// Copyright 2021 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/media.h"
+
+bool SbMediaIsBufferPoolAllocateOnDemand() {
+ // Minimize fragmentation by always having the media buffer pool allocated.
+ return false;
+}
diff --git a/starboard/android/shared/platform_configuration/BUILD.gn b/starboard/android/shared/platform_configuration/BUILD.gn
index 6e5fe5f..a2dbcff 100644
--- a/starboard/android/shared/platform_configuration/BUILD.gn
+++ b/starboard/android/shared/platform_configuration/BUILD.gn
@@ -12,6 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import("//starboard/android/shared/toolchain/toolchain.gni")
+
config("platform_configuration") {
configs = [ "//starboard/build/config/sabi" ]
cflags = []
@@ -26,9 +28,6 @@
# those, rather use lld everywhere. See release notes for NDK 19:
# https://developer.android.com/ndk/downloads/revision_history
"-fuse-ld=lld",
-
- # Mimic build/cmake/android.toolchain.cmake in the Android NDK.
- "-Wl,--exclude-libs,libunwind.a",
]
if (is_debug) {
cflags += [ "-O0" ]
diff --git a/starboard/android/shared/platform_configuration/configuration.gni b/starboard/android/shared/platform_configuration/configuration.gni
index 32bf7c5..4eebd62 100644
--- a/starboard/android/shared/platform_configuration/configuration.gni
+++ b/starboard/android/shared/platform_configuration/configuration.gni
@@ -15,23 +15,9 @@
import("//starboard/build/config/base_configuration.gni")
declare_args() {
- android_sdk_path = getenv("ANDROID_HOME")
- android_ndk_path = ""
- sb_android_ndk_version = "21.1.6352462"
- android_ndk_api_level = 21
-
enable_vulkan = false
}
-if (android_sdk_path == "") {
- _home_dir = getenv("HOME")
- android_sdk_path = "$_home_dir/starboard-toolchains/AndroidSdk"
-}
-
-if (android_ndk_path == "") {
- android_ndk_path = "$android_sdk_path/ndk/$sb_android_ndk_version"
-}
-
final_executable_type = "shared_library"
gtest_target_type = "shared_library"
sb_enable_benchmark = true
diff --git a/starboard/android/shared/player_components_factory.h b/starboard/android/shared/player_components_factory.h
index 0707a84..e129b0f 100644
--- a/starboard/android/shared/player_components_factory.h
+++ b/starboard/android/shared/player_components_factory.h
@@ -69,6 +69,7 @@
filter::AudioRendererSinkImpl {
public:
explicit AudioRendererSinkAndroid(bool enable_audio_device_callback,
+ bool enable_pcm_content_type_movie,
int tunnel_mode_audio_session_id = -1)
: AudioRendererSinkImpl(
[=](SbTime start_media_time,
@@ -92,6 +93,7 @@
frame_buffers_size_in_frames, update_source_status_func,
consume_frames_func, error_func, start_media_time,
tunnel_mode_audio_session_id, enable_audio_device_callback,
+ enable_pcm_content_type_movie, false, /* is_web_audio */
context);
}) {}
@@ -280,6 +282,7 @@
strlen(creation_parameters.audio_mime()) > 0) {
audio_mime_type.RegisterBoolParameter("tunnelmode");
audio_mime_type.RegisterBoolParameter("enableaudiodevicecallback");
+ audio_mime_type.RegisterBoolParameter("enablepcmcontenttypemovie");
if (!audio_mime_type.is_valid()) {
*error_message =
@@ -390,6 +393,11 @@
audio_mime_type.GetParamBoolValue("enableaudiodevicecallback", true);
SB_LOG(INFO) << "AudioDeviceCallback is "
<< (enable_audio_device_callback ? "enabled." : "disabled.");
+ bool enable_pcm_content_type_movie =
+ audio_mime_type.GetParamBoolValue("enablepcmcontenttypemovie", true);
+ SB_LOG(INFO) << "AudioAttributes::CONTENT_TYPE_MOVIE is "
+ << (enable_pcm_content_type_movie ? "enabled" : "disabled")
+ << " for non-tunneled PCM audio playback.";
if (tunnel_mode_audio_session_id != -1) {
*audio_renderer_sink = TryToCreateTunnelModeAudioRendererSink(
@@ -400,8 +408,8 @@
}
}
if (!*audio_renderer_sink) {
- audio_renderer_sink->reset(
- new AudioRendererSinkAndroid(enable_audio_device_callback));
+ audio_renderer_sink->reset(new AudioRendererSinkAndroid(
+ enable_audio_device_callback, enable_pcm_content_type_movie));
}
}
@@ -589,7 +597,7 @@
const CreationParameters& creation_parameters,
bool enable_audio_device_callback) {
scoped_ptr<AudioRendererSink> audio_sink(new AudioRendererSinkAndroid(
- enable_audio_device_callback, tunnel_mode_audio_session_id));
+ enable_audio_device_callback, true, tunnel_mode_audio_session_id));
// We need to double check if the audio sink can actually be created.
int max_cached_frames, min_frames_per_append;
GetAudioRendererParams(creation_parameters, &max_cached_frames,
diff --git a/starboard/android/shared/sdk_utils.py b/starboard/android/shared/sdk_utils.py
index be35f7c..237e5c7 100644
--- a/starboard/android/shared/sdk_utils.py
+++ b/starboard/android/shared/sdk_utils.py
@@ -29,6 +29,8 @@
from starboard.tools import build
+# pylint: disable=consider-using-with
+
# Which version of the Android NDK and CMake to install and build with.
# Note that build.gradle parses these out of this file too.
_NDK_VERSION = '21.1.6352462'
@@ -54,7 +56,8 @@
# Location from which to download the SDK command-line tools
# see https://developer.android.com/studio/index.html#command-tools
-_SDK_URL = 'https://dl.google.com/android/repository/commandlinetools-linux-6200805_latest.zip'
+_SDK_URL = ('https://dl.google.com/android/repository/'
+ 'commandlinetools-linux-6200805_latest.zip')
# Location from which to download the Android NDK.
# see https://developer.android.com/ndk/downloads (perhaps in "NDK archives")
@@ -107,7 +110,7 @@
zip_file = zipfile.ZipFile(zip_path)
for info in zip_file.infolist():
zip_file.extract(info.filename, dest_path)
- os.chmod(os.path.join(dest_path, info.filename), info.external_attr >> 16L)
+ os.chmod(os.path.join(dest_path, info.filename), info.external_attr >> 16)
def InstallSdkIfNeeded():
@@ -138,6 +141,7 @@
logging.error('Error: ANDROID_HOME is is missing NDK %s.', _NDK_VERSION)
sys.exit(1)
+ # pylint: disable=undefined-variable
reply = raw_input(
'Do you want to continue using your custom Android tools? [y/N]')
if reply.upper() != 'Y':
@@ -225,6 +229,9 @@
# If we can't access the "sdkmanager" tool, we need to download the SDK
if not os.access(_SDKMANAGER_TOOL, os.X_OK):
+ if os.getenv('IS_CI', '') == '1':
+ raise Exception('Dynamic toolchain downloads are disabled in CI')
+
logging.warning('Downloading Android SDK to %s',
_STARBOARD_TOOLCHAINS_SDK_DIR)
if os.path.exists(_STARBOARD_TOOLCHAINS_SDK_DIR):
diff --git a/starboard/android/shared/starboard_platform.gypi b/starboard/android/shared/starboard_platform.gypi
index e8579cb..94eefcd 100644
--- a/starboard/android/shared/starboard_platform.gypi
+++ b/starboard/android/shared/starboard_platform.gypi
@@ -13,7 +13,7 @@
# limitations under the License.
{
'variables': {
- 'has_input_events_filter' : '<!pymod_do_main(starboard.build.gyp_functions file_exists <(DEPTH)/starboard/android/shared/input_events_filter.cc)',
+ 'has_input_events_filter' : '<!pymod_do_main(starboard.build.gyp_functions file_exists <(DEPTH)/starboard/android/shared/internal/input_events_filter.cc)',
'has_drm_system_extension%': '<!pymod_do_main(starboard.build.gyp_functions file_exists <(DEPTH)/starboard/android/shared/drm_system_extension/drm_system_extension.gyp)',
},
'includes': [
@@ -141,6 +141,7 @@
'media_get_initial_buffer_capacity.cc',
'media_get_max_buffer_capacity.cc',
'media_is_audio_supported.cc',
+ 'media_is_buffer_pool_allocate_on_demand.cc',
'media_is_video_supported.cc',
'network_status_impl.cc',
'microphone_impl.cc',
@@ -401,7 +402,6 @@
'<(DEPTH)/starboard/shared/starboard/media/media_get_buffer_storage_type.cc',
'<(DEPTH)/starboard/shared/starboard/media/media_get_progressive_buffer_budget.cc',
'<(DEPTH)/starboard/shared/starboard/media/media_get_video_buffer_budget.cc',
- '<(DEPTH)/starboard/shared/starboard/media/media_is_buffer_pool_allocate_on_demand.cc',
'<(DEPTH)/starboard/shared/starboard/media/media_is_buffer_using_memory_pool.cc',
'<(DEPTH)/starboard/shared/starboard/media/mime_type.cc',
'<(DEPTH)/starboard/shared/starboard/media/mime_type.h',
@@ -476,8 +476,8 @@
'conditions': [
['has_input_events_filter==1', {
'sources': [
- 'input_events_filter.cc',
- 'input_events_filter.h',
+ 'internal/input_events_filter.cc',
+ 'internal/input_events_filter.h',
],
'defines': [
'STARBOARD_INPUT_EVENTS_FILTER',
diff --git a/starboard/android/shared/system_request_freeze_no_freezedone_callback.cc b/starboard/android/shared/system_request_freeze_no_freezedone_callback.cc
index 1231877..fb522af 100644
--- a/starboard/android/shared/system_request_freeze_no_freezedone_callback.cc
+++ b/starboard/android/shared/system_request_freeze_no_freezedone_callback.cc
@@ -18,7 +18,7 @@
#include "starboard/shared/starboard/application.h"
#if SB_IS(EVERGREEN_COMPATIBLE) && !SB_IS(EVERGREEN_COMPATIBLE_LITE)
-#include "starboard/loader_app/pending_restart.h"
+#include "starboard/loader_app/pending_restart.h" // nogncheck
#endif // SB_IS(EVERGREEN_COMPATIBLE) && !SB_IS(EVERGREEN_COMPATIBLE_LITE)
#if SB_API_VERSION >= 13
diff --git a/starboard/android/shared/test_filters.py b/starboard/android/shared/test_filters.py
new file mode 100644
index 0000000..c3d38ef
--- /dev/null
+++ b/starboard/android/shared/test_filters.py
@@ -0,0 +1,84 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Android Platform Test Filters."""
+
+from starboard.tools.testing import test_filter
+
+
+class TestFilters(object):
+ """Starboard Android platform test filters."""
+
+ def GetTestFilters(self):
+ filters = []
+ for target, tests in self._FILTERED_TESTS.iteritems():
+ filters.extend(test_filter.TestFilter(target, test) for test in tests)
+ return filters
+
+ # A map of failing or crashing tests per target.
+ _FILTERED_TESTS = {
+ 'player_filter_tests': [
+ # GetMaxNumberOfCachedFrames() on Android is device dependent,
+ # and Android doesn't provide an API to get it. So, this function
+ # doesn't make sense on Android. But HoldFramesUntilFull tests depend
+ # on this number strictly.
+ 'VideoDecoderTests/VideoDecoderTest.HoldFramesUntilFull/*',
+
+ # Currently, invalid input tests are not supported.
+ 'VideoDecoderTests/VideoDecoderTest.SingleInvalidInput/*',
+ 'VideoDecoderTests/VideoDecoderTest'
+ '.MultipleValidInputsAfterInvalidKeyFrame/*',
+ 'VideoDecoderTests/VideoDecoderTest.MultipleInvalidInput/*',
+
+ # Android currently does not support multi-video playback, which
+ # the following tests depend upon.
+ 'VideoDecoderTests/VideoDecoderTest.ThreeMoreDecoders/*',
+
+ # The video pipeline will hang if it doesn't receive any input.
+ 'PlayerComponentsTests/PlayerComponentsTest.EOSWithoutInput/*',
+
+ # The e/eac3 audio time reporting during pause will be revisitied.
+ 'PlayerComponentsTests/PlayerComponentsTest.Pause/15',
+ ],
+ 'nplb': [
+ # This test is failing because localhost is not defined for IPv6 in
+ # /etc/hosts.
+ 'SbSocketAddressTypes/SbSocketResolveTest.Localhost/1',
+
+ # These tests are taking longer due to interop on android. Work is
+ # underway to investigate whether this is acceptable.
+ 'SbMediaCanPlayMimeAndKeySystem.ValidatePerformance',
+ 'SbMediaConfigurationTest.ValidatePerformance',
+
+ # SbDirectory has problems with empty Asset dirs.
+ 'SbDirectoryCanOpenTest.SunnyDayStaticContent',
+ 'SbDirectoryGetNextTest.SunnyDayStaticContent',
+ 'SbDirectoryOpenTest.SunnyDayStaticContent',
+ 'SbFileGetPathInfoTest.WorksOnStaticContentDirectories',
+
+ # These tests are disabled due to not receiving the kEndOfStream
+ # player state update within the specified timeout.
+ 'SbPlayerWriteSampleTests/SbPlayerWriteSampleTest.NoInput/*',
+
+ # Android does not use SbDrmSessionClosedFunc, which these tests
+ # depend on.
+ 'SbDrmSessionTest.SunnyDay',
+ 'SbDrmSessionTest.CloseDrmSessionBeforeUpdateSession',
+
+ # This test is failing because Android calls the
+ # SbDrmSessionUpdateRequestFunc with SbDrmStatus::kSbDrmStatusSuccess
+ # when invalid initialization data is passed to
+ # SbDrmGenerateSessionUpdateRequest().
+ 'SbDrmSessionTest.InvalidSessionUpdateRequestParams',
+ ],
+ }
diff --git a/starboard/android/shared/toolchain/toolchain.gni b/starboard/android/shared/toolchain/toolchain.gni
new file mode 100644
index 0000000..328edd6
--- /dev/null
+++ b/starboard/android/shared/toolchain/toolchain.gni
@@ -0,0 +1,33 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import("//build/config/clang/clang.gni")
+
+declare_args() {
+ android_sdk_path = getenv("ANDROID_HOME")
+ android_ndk_path = ""
+ sb_android_ndk_version = "21.1.6352462"
+ android_ndk_api_level = 21
+}
+
+if (android_sdk_path == "") {
+ android_sdk_path = "$starboard_toolchains_path/AndroidSdk"
+}
+
+if (android_ndk_path == "") {
+ android_ndk_path = "$android_sdk_path/ndk/$sb_android_ndk_version"
+}
+
+android_toolchain_path =
+ "$android_ndk_path/toolchains/llvm/prebuilt/linux-x86_64"
diff --git a/starboard/android/x86/args.gn b/starboard/android/x86/args.gn
new file mode 100644
index 0000000..977def0
--- /dev/null
+++ b/starboard/android/x86/args.gn
@@ -0,0 +1,17 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+target_platform = "android-x86"
+target_os = "android"
+target_cpu = "x86"
diff --git a/starboard/android/x86/test_filters.py b/starboard/android/x86/test_filters.py
new file mode 100644
index 0000000..a510516
--- /dev/null
+++ b/starboard/android/x86/test_filters.py
@@ -0,0 +1,54 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Android X86 Platform Test Filters."""
+
+from starboard.android.shared import test_filters as shared_test_filters
+from starboard.tools.testing import test_filter
+
+
+def CreateTestFilters():
+ return AndroidX86TestFilters()
+
+
+class AndroidX86TestFilters(shared_test_filters.TestFilters):
+ """Starboard Android X86 Platform Test Filters."""
+
+ def GetTestFilters(self):
+ filters = super(AndroidX86TestFilters, self).GetTestFilters()
+ for target, tests in self._FILTERED_TESTS.iteritems():
+ filters.extend(test_filter.TestFilter(target, test) for test in tests)
+ return filters
+
+ # A map of failing or crashing tests per target
+ _FILTERED_TESTS = {
+ 'nplb': [
+ 'SbAccessibilityTest.CallSetCaptionsEnabled',
+ 'SbAccessibilityTest.GetCaptionSettingsReturnIsValid',
+ 'SbAudioSinkTest.*',
+ 'SbMediaCanPlayMimeAndKeySystem.*',
+ 'SbMicrophoneCloseTest.*',
+ 'SbMicrophoneOpenTest.*',
+ 'SbMicrophoneReadTest.*',
+ 'SbPlayerWriteSampleTests/SbPlayerWriteSampleTest.*',
+ 'SbMediaSetAudioWriteDurationTests/SbMediaSetAudioWriteDurationTest'
+ '.WriteContinuedLimitedInput/*',
+ 'SbMediaSetAudioWriteDurationTests/SbMediaSetAudioWriteDurationTest'
+ '.WriteLimitedInput/*',
+ ],
+ 'player_filter_tests': [
+ 'AudioDecoderTests/*',
+ 'VideoDecoderTests/*',
+ 'PlayerComponentsTests/*',
+ ],
+ }
diff --git a/starboard/android/x86/toolchain/BUILD.gn b/starboard/android/x86/toolchain/BUILD.gn
index 3cd5126..3f6180e 100644
--- a/starboard/android/x86/toolchain/BUILD.gn
+++ b/starboard/android/x86/toolchain/BUILD.gn
@@ -12,13 +12,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import("//build/config/clang/clang.gni")
import("//build/toolchain/gcc_toolchain.gni")
-
-_home_dir = getenv("HOME")
-_clang_base_path = "$_home_dir/starboard-toolchains/x86_64-linux-gnu-clang-chromium-365097-f7e52fbd-8"
+import("//starboard/android/shared/toolchain/toolchain.gni")
clang_toolchain("host") {
- clang_base_path = _clang_base_path
+ clang_base_path = clang_base_path
toolchain_args = {
current_os = "linux"
@@ -26,11 +25,8 @@
}
}
-_android_toolchain_path =
- "$android_ndk_path/toolchains/llvm/prebuilt/linux-x86_64"
-
gcc_toolchain("target") {
- prefix = rebase_path("$_android_toolchain_path/bin", root_build_dir)
+ prefix = rebase_path("$android_toolchain_path/bin", root_build_dir)
cc = "$prefix/i686-linux-android${android_ndk_api_level}-clang"
cxx = "$prefix/i686-linux-android${android_ndk_api_level}-clang++"
ld = cxx
diff --git a/starboard/build/config/BUILD.gn b/starboard/build/config/BUILD.gn
index 5491f81..3f585bf 100644
--- a/starboard/build/config/BUILD.gn
+++ b/starboard/build/config/BUILD.gn
@@ -12,6 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import("//build/config/compiler/compiler.gni")
+
config("base") {
defines = []
@@ -92,6 +94,8 @@
}
}
+# TODO(b/212641065): Scope global defines migrated from
+# cobalt_configuration.gypi to only the targets they're necessary in.
config("starboard") {
if (current_toolchain == default_toolchain) {
defines = [
@@ -109,6 +113,7 @@
defines += [
"ENABLE_DEBUGGER",
"ENABLE_DEBUG_COMMAND_LINE_SWITCHES",
+ "ENABLE_TEST_RUNNER",
]
}
@@ -184,3 +189,21 @@
}
}
}
+
+config("warnings_as_errors") {
+ if (is_win && treat_warnings_as_errors) {
+ cflags = [ "/WX" ]
+ }
+}
+
+# This config is defined here and added as a default config so that the flags
+# specified in it can be overridden by targets. It's not possible for targets to
+# override flags specified in a platform's "platform_configuration" config,
+# which is where these particular flags would otherwise naturally fit.
+config("default_compiler_flags") {
+ if (current_toolchain == default_toolchain && sb_is_evergreen &&
+ target_cpu == "arm") {
+ cflags = [ "-mfpu=vfpv3" ]
+ asmflags = cflags
+ }
+}
diff --git a/starboard/build/config/BUILDCONFIG.gn b/starboard/build/config/BUILDCONFIG.gn
index 5f69d58..9e10f5d 100644
--- a/starboard/build/config/BUILDCONFIG.gn
+++ b/starboard/build/config/BUILDCONFIG.gn
@@ -12,7 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import("//starboard/build/platforms.gni")
+_is_python2 = exec_script("//build/util/is_python2.py", [], "json")
+assert(!_is_python2, "`python` must resolve to Python 3 when building with GN.")
declare_args() {
is_clang = true
@@ -23,11 +24,11 @@
is_starboard = true
- cobalt_fastbuild = getenv("IS_CI") == 1
+ cobalt_fastbuild = getenv("IS_CI") == "1"
- is_internal_build = false
+ is_internal_build = getenv("COBALT_USE_INTERNAL_BUILD") == "1"
- is_docker_build = getenv("IS_DOCKER") == 1
+ is_docker_build = getenv("IS_DOCKER") == "1"
}
is_debug = build_type == "debug"
@@ -36,20 +37,11 @@
is_gold = build_type == "gold"
assert(is_debug || is_devel || is_qa || is_gold)
-declare_args() {
- use_tsan = getenv("USE_TSAN") == 1
-}
-
-declare_args() {
- use_asan = (is_debug || is_devel || getenv("USE_ASAN") == 1) && !use_tsan
-}
-
-assert(!(use_tsan && use_asan), "ASAN and TSAN are mutually exclusive.")
-
# Set some variables we never want to change.
sb_allows_memory_tracking = !is_gold
host_byteorder = "little"
is_official_build = false # Chromium's build files expect this to be set.
+is_component_build = false
# The os/cpu definitions are here to set the current platform type in
# os_definitions.gni below.
@@ -75,13 +67,11 @@
# Get the path to the starboard implementation and include its GN
# configuration.
-foreach(platform, platforms) {
- if (platform.name == target_platform) {
- starboard_path = platform.path
- }
-}
+starboard_path = exec_script("//starboard/build/platforms.py",
+ [ target_platform ],
+ "trim string")
assert(defined(starboard_path),
- "Please port your platform to starboard/build/platforms.gni")
+ "Please add your platform to starboard/build/platforms.py")
host_toolchain = "//$starboard_path/toolchain:host"
_default_toolchain = "//$starboard_path/toolchain:target"
set_default_toolchain(_default_toolchain)
@@ -89,6 +79,18 @@
import("//$starboard_path/platform_configuration/configuration.gni")
import("//starboard/build/config/build_assertions.gni")
+declare_args() {
+ use_tsan = getenv("USE_TSAN") == 1
+}
+
+_use_asan_default = (is_debug || is_devel) && (is_linux || is_mac) &&
+ is_clang && !sb_is_evergreen
+declare_args() {
+ use_asan = (_use_asan_default || getenv("USE_ASAN") == 1) && !use_tsan
+}
+
+assert(!(use_tsan && use_asan), "ASAN and TSAN are mutually exclusive.")
+
# =============================================================================
# TARGET DEFAULTS
# =============================================================================
@@ -109,18 +111,23 @@
default_compiler_configs = [
"//build/config/compiler:default_include_dirs",
"//build/config/compiler:no_exceptions",
- "//build/config/compiler:thin_archive",
"//starboard/build/config:base",
"//starboard/build/config:host",
"//starboard/build/config:size",
"//starboard/build/config:target",
+ "//starboard/build/config:warnings_as_errors",
+ "//starboard/build/config:default_compiler_flags",
]
+if (use_thin_archive) {
+ default_compiler_configs += [ "//build/config/compiler:thin_archive" ]
+}
+
if (is_starboard) {
default_compiler_configs += [ "//starboard/build/config:starboard" ]
}
-if (is_qa || is_gold) {
+if (is_qa || is_gold || sb_use_no_rtti) {
default_compiler_configs += [ "//build/config/compiler:no_rtti" ]
}
@@ -190,25 +197,97 @@
}
}
+# Import configuration variables needed for the install targets and install
+# content targets.
+import("//starboard/build/config/install.gni")
+
# Set up the method of generating the install targets as defined by the
# platform.
import("$install_target_path")
+template("install_content") {
+ target(invoker.target_type, target_name) {
+ forward_variables_from(invoker, "*", [ "install_content" ])
+ if (defined(visibility)) {
+ visibility += [ ":${target_name}_install_content" ]
+ }
+ }
+
+ if (defined(invoker.install_content) && invoker.install_content) {
+ action("${target_name}_install_content") {
+ forward_variables_from(invoker, [ "testonly" ])
+
+ deps = [ ":${invoker.target_name}" ]
+
+ sources = get_target_outputs(":${invoker.target_name}")
+
+ install_content_dir = "$sb_install_output_dir/$sb_install_content_subdir"
+ outputs = []
+ foreach(source, sources) {
+ output_path = install_content_dir + "/" +
+ rebase_path(source, "$sb_static_contents_output_data_dir")
+ outputs += [ output_path ]
+ }
+
+ script = "//starboard/build/copy_install_content.py"
+ args = [
+ "--output_dir",
+ rebase_path(install_content_dir, root_build_dir),
+ "--base_dir",
+ rebase_path(sb_static_contents_output_data_dir, root_build_dir),
+ ]
+ args += rebase_path(sources, root_build_dir)
+ }
+ }
+}
+
+template("action") {
+ install_content(target_name) {
+ forward_variables_from(invoker, "*")
+ target_type = "action"
+ }
+}
+
+template("action_foreach") {
+ install_content(target_name) {
+ forward_variables_from(invoker, "*")
+ target_type = "action_foreach"
+ }
+}
+
+template("copy") {
+ install_content(target_name) {
+ forward_variables_from(invoker, "*")
+ target_type = "copy"
+ }
+}
+
template("executable") {
target_with_platform_configs(target_name) {
target_type = "executable"
- forward_variables_from(invoker, "*", [ "install_target" ])
+ forward_variables_from(invoker,
+ "*",
+ [
+ "install_target",
+ "content_deps",
+ ])
}
if (!defined(invoker.install_target) || invoker.install_target) {
executable_target_name = target_name
install_target(target_name + "_install") {
- forward_variables_from(invoker,
- [
- "content",
- "testonly",
- ])
+ forward_variables_from(invoker, [ "testonly" ])
installable_target_name = executable_target_name
type = "executable"
+ if (defined(invoker.deps)) {
+ deps = invoker.deps
+ } else {
+ deps = []
+ }
+ if (defined(invoker.content_deps)) {
+ foreach(content_dep, invoker.content_deps) {
+ deps += [ "${content_dep}_install_content" ]
+ }
+ }
}
}
}
@@ -216,19 +295,30 @@
template("shared_library") {
target_with_platform_configs(target_name) {
target_type = "shared_library"
- forward_variables_from(invoker, "*", [ "install_target" ])
+ forward_variables_from(invoker,
+ "*",
+ [
+ "install_target",
+ "content_deps",
+ ])
}
if (!defined(invoker.install_target) || invoker.install_target) {
shared_library_target_name = target_name
install_target(target_name + "_install") {
- forward_variables_from(invoker,
- [
- "content",
- "testonly",
- ])
+ forward_variables_from(invoker, [ "testonly" ])
installable_target_name = shared_library_target_name
type = "shared_library"
+ if (defined(invoker.deps)) {
+ deps = invoker.deps
+ } else {
+ deps = []
+ }
+ if (defined(invoker.content_deps)) {
+ foreach(content_dep, invoker.content_deps) {
+ deps += [ "${content_dep}_install_content" ]
+ }
+ }
}
}
}
diff --git a/starboard/build/config/base_configuration.gni b/starboard/build/config/base_configuration.gni
index 16893bf..a789330 100644
--- a/starboard/build/config/base_configuration.gni
+++ b/starboard/build/config/base_configuration.gni
@@ -13,6 +13,7 @@
# limitations under the License.
import("//cobalt/content/fonts/font_configuration.gni")
+import("//starboard/build/config/enable_vr.gni")
declare_args() {
# Enables the yasm compiler to be used to compile .asm files.
@@ -32,11 +33,6 @@
# Directory path to static contents' data.
sb_static_contents_output_data_dir = "$root_out_dir/content/data"
- # Top-level directory for staging deploy build output. Platform deploy
- # actions should use <(target_deploy_dir) defined in deploy.gypi to place
- # artifacts for each deploy target in its own subdirectoy.
- sb_install_output_dir = "$root_out_dir/install"
-
# Whether this is an Evergreen build.
sb_is_evergreen = false
@@ -92,7 +88,7 @@
# The path to the gni file containing the install_target template which
# defines how the build should produce the install/ directory.
- install_target_path = "//starboard/build/install/mock_install.gni"
+ install_target_path = "//starboard/build/install/no_install.gni"
# Target-specific configurations for each platform.
executable_configs = []
@@ -111,9 +107,6 @@
# Note: Only enable if there's no system-wide DIAL support.
in_app_dial = false
- # Whether VR is enabled.
- enable_vr = false
-
# Override this value to adjust the default rasterizer setting for your
# platform.
default_renderer_options_dependency = "//cobalt/renderer:default_options"
@@ -136,4 +129,9 @@
# TODO(b/173248397): Migrate to CobaltExtensions or PlatformServices.
# List of platform-specific targets that get compiled into cobalt.
cobalt_platform_dependencies = []
+
+ # Whether or not to link with thin archives.
+ use_thin_archive = true
+
+ sb_use_no_rtti = false
}
diff --git a/starboard/build/config/components.gni b/starboard/build/config/components.gni
index 7aef3d1..7a75048 100644
--- a/starboard/build/config/components.gni
+++ b/starboard/build/config/components.gni
@@ -2,10 +2,6 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-if (is_starboard) {
- is_component_build = false
-}
-
# A helper for forwarding testonly and visibility.
# Forwarding "*" does not include variables from outer scopes (to avoid copying
# all globals into each template invocation), so it will not pick up
diff --git a/starboard/build/config/enable_vr.gni b/starboard/build/config/enable_vr.gni
new file mode 100644
index 0000000..b7f2d81
--- /dev/null
+++ b/starboard/build/config/enable_vr.gni
@@ -0,0 +1,26 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+declare_args() {
+ # Whether VR is enabled.
+ enable_vr = getenv("USE_VR")
+}
+
+if (enable_vr == true || enable_vr == "1") {
+ enable_vr = true
+} else if (enable_vr == false || enable_vr == "0" || enable_vr == "") {
+ enable_vr = false
+} else {
+ assert(false, "enable_vr was set to an invalid value.")
+}
diff --git a/starboard/build/config/install.gni b/starboard/build/config/install.gni
new file mode 100644
index 0000000..5c64ac2
--- /dev/null
+++ b/starboard/build/config/install.gni
@@ -0,0 +1,27 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+declare_args() {
+ # Top-level directory for staging deploy build output. Platform deploy
+ # actions should use <(target_deploy_dir) defined in deploy.gypi to place
+ # artifacts for each deploy target in its own subdirectoy.
+ sb_install_output_dir = "$root_out_dir/install"
+
+ # Sub-directory for install content.
+ sb_install_content_subdir = ""
+}
+
+if (sb_install_content_subdir == "") {
+ sb_install_content_subdir = "usr/share/cobalt"
+}
diff --git a/starboard/build/convert_i18n_data.gni b/starboard/build/convert_i18n_data.gni
new file mode 100644
index 0000000..68a1300
--- /dev/null
+++ b/starboard/build/convert_i18n_data.gni
@@ -0,0 +1,36 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This template is meant to be used to convert a set of XLB files into files of
+# a simpler format (e.g. CSV) in the product directory, e.g.
+# out/stub_debug/content/data/i18n.
+
+template("convert_i18n_data") {
+ action_foreach(target_name) {
+ script = "//starboard/build/convert_i18n_data.py"
+
+ sources = invoker.xlb_files
+ inputs = sources
+
+ output_dir = "$sb_static_contents_output_data_dir/i18n"
+ outputs = [ "$output_dir/{{source_name_part}}.csv" ]
+
+ args = [
+ "--input_file",
+ "{{source}}",
+ "--output_file",
+ rebase_path(outputs[0], root_build_dir),
+ ]
+ }
+}
diff --git a/starboard/build/convert_i18n_data.py b/starboard/build/convert_i18n_data.py
index ddb0454..0d905f9 100644
--- a/starboard/build/convert_i18n_data.py
+++ b/starboard/build/convert_i18n_data.py
@@ -12,13 +12,14 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-
"""Converts XLB files into CSV files in a given output directory.
Since the output of this script is intended to be use by GYP, all resulting
paths are using Unix-style forward slashes.
"""
+from __future__ import print_function
+
import argparse
import os
import posixpath
@@ -38,7 +39,7 @@
def ChangeSuffix(filename, new_suffix):
"""Changes the suffix of |filename| to |new_suffix|. If no current suffix,
adds |new_suffix| to the end of |filename|."""
- (root, ext) = os.path.splitext(filename)
+ (root, _) = os.path.splitext(filename)
return root + '.' + new_suffix
@@ -54,18 +55,18 @@
with open(output_filename, 'wb') as output_file:
for msg in messages:
# Use ; as the separator. Which means it better not be in the name.
- assert not (';' in msg.attrib['name'])
- output_file.write(msg.attrib['name'])
- output_file.write(';')
+ assert ';' not in msg.attrib['name']
+ output_file.write(msg.attrib['name'].encode('utf8'))
+ output_file.write(';'.encode('utf8'))
# Encode the text as UTF8 to accommodate special characters.
output_file.write(msg.text.encode('utf8'))
- output_file.write('\n')
+ output_file.write('\n'.encode('utf8'))
def GetOutputs(files_to_convert, output_basedir):
"""Returns a list of filenames relative to the output directory,
based on a list of input files."""
- outputs = [];
+ outputs = []
for filename in files_to_convert:
dirname = posixpath.dirname(filename)
relative_filename = posixpath.relpath(filename, dirname)
@@ -88,7 +89,7 @@
os.makedirs(output_dir)
output_filename = ChangeSuffix(output_filename, 'csv')
- print 'Converting ' + filename + ' to ' + output_filename
+ print('Converting ' + filename + ' to ' + output_filename)
ConvertSingleFile(filename, output_filename)
@@ -96,12 +97,23 @@
"""Called by GYP using pymod_do_main."""
parser = argparse.ArgumentParser()
parser.add_argument('-o', dest='output_dir', help='output directory')
- parser.add_argument('--inputs', action='store_true', dest='list_inputs',
- help='prints a list of all input files')
- parser.add_argument('--outputs', action='store_true', dest='list_outputs',
- help='prints a list of all output files')
- parser.add_argument('input_paths', metavar='path', nargs='+',
- help='path to an input file or directory')
+ parser.add_argument(
+ '--inputs',
+ action='store_true',
+ dest='list_inputs',
+ help='prints a list of all input files')
+ parser.add_argument(
+ '--outputs',
+ action='store_true',
+ dest='list_outputs',
+ help='prints a list of all output files')
+ parser.add_argument(
+ 'input_paths',
+ metavar='path',
+ nargs='*',
+ help='path to an input file or directory')
+ parser.add_argument('--input_file', help='Input file to convert')
+ parser.add_argument('--output_file', help='Output file to write to')
options = parser.parse_args(argv)
files_to_convert = [EscapePath(x) for x in options.input_paths]
@@ -109,6 +121,10 @@
if options.list_inputs:
return '\n'.join(files_to_convert)
+ if options.input_file and options.output_file:
+ ConvertSingleFile(options.input_file, options.output_file)
+ return
+
if not options.output_dir:
raise WrongNumberOfArgumentsException('-o required.')
@@ -121,15 +137,16 @@
def main(argv):
- print 'Running... in main()'
+ print('Running... in main()')
try:
result = DoMain(argv[1:])
- except WrongNumberOfArgumentsException, e:
- print >> sys.stderr, e
+ except WrongNumberOfArgumentsException as e:
+ print(e, file=sys.stderr)
return 1
if result:
- print result
+ print(result)
return 0
+
if __name__ == '__main__':
sys.exit(main(sys.argv))
diff --git a/starboard/build/copy_file.py b/starboard/build/copy_file.py
new file mode 100644
index 0000000..dd1d24a
--- /dev/null
+++ b/starboard/build/copy_file.py
@@ -0,0 +1,21 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""GN Helper to copy a single file."""
+
+import shutil
+import sys
+
+src = sys.argv[1]
+dst = sys.argv[2]
+shutil.copyfile(src, dst)
diff --git a/starboard/build/copy_install_content.py b/starboard/build/copy_install_content.py
new file mode 100644
index 0000000..d63e884
--- /dev/null
+++ b/starboard/build/copy_install_content.py
@@ -0,0 +1,74 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# TODO(b/216341587): Refactor install_content to get rid of this file.
+
+# This file is loosely based on starboard/build/copy_data.py
+"""Copies all input files to the output directory.
+
+The folder structure of the input files is maintained in the output relative
+to the 'base_dir' parameter.
+"""
+
+import argparse
+import os
+import shutil
+
+
+class InvalidArgumentException(Exception):
+ pass
+
+
+def copy_files(files_to_copy, base_dir, output_dir):
+ for path in files_to_copy:
+ # All input paths must point at files.
+ if not os.path.isfile(path):
+ raise InvalidArgumentException(path + ' is not a file.')
+
+ # Get the path of the file relative to the source base_dir.
+ rel_path = os.path.relpath(path, base_dir)
+
+ # In certain cases, files would fail to open on windows if relative paths
+ # were provided. Using absolute paths fixes this.
+ filename = os.path.abspath(os.path.join(base_dir, rel_path))
+ output_dir = os.path.abspath(output_dir)
+ output_filename = os.path.abspath(os.path.join(output_dir, rel_path))
+
+ # In cases where a directory has turned into a file or vice versa, delete it
+ # before copying it below.
+ if os.path.exists(output_dir) and not os.path.isdir(output_dir):
+ os.remove(output_dir)
+ if os.path.exists(output_filename) and os.path.isdir(output_filename):
+ shutil.rmtree(output_filename)
+
+ if not os.path.exists(output_dir):
+ os.makedirs(output_dir)
+
+ shutil.copy(filename, output_filename)
+
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ '--output_dir', dest='output_dir', required=True, help='output directory')
+ parser.add_argument(
+ '--base_dir',
+ dest='base_dir',
+ required=True,
+ help='source base directory')
+ parser.add_argument(
+ 'input_paths', metavar='path', nargs='+', help='path to an input file')
+ options = parser.parse_args()
+
+ copy_files(options.input_paths, options.base_dir, options.output_dir)
diff --git a/starboard/build/install/install_target.gni b/starboard/build/install/install_target.gni
index e072622..765ed41 100644
--- a/starboard/build/install/install_target.gni
+++ b/starboard/build/install/install_target.gni
@@ -12,34 +12,34 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import("//starboard/build/config/install.gni")
+
template("install_target") {
+ installable_target_name = invoker.installable_target_name
+
if (invoker.type == "executable") {
install_subdir = "bin"
- source_name = invoker.installable_target_name
+ source_name = installable_target_name
} else if (invoker.type == "shared_library") {
install_subdir = "lib"
- source_name = "lib${invoker.installable_target_name}.so"
+ source_name = "lib${installable_target_name}.so"
} else {
assert(false, "You can only install an executable or shared library.")
}
copy("copy_" + target_name) {
- forward_variables_from(invoker,
- [
- "installable_target_name",
- "testonly",
- ])
- deps = [ ":$installable_target_name" ]
-
+ forward_variables_from(invoker, [ "testonly" ])
+ deps = invoker.deps
+ deps += [ ":$installable_target_name" ]
sources = [ "$root_out_dir/$source_name" ]
outputs = [ "$root_out_dir/install/$install_subdir/{{source_file_part}}" ]
}
- if (defined(invoker.content)) {
- copy("copy_content_" + target_name) {
- forward_variables_from(invoker, [ "testonly" ])
- sources = invoker.content
- outputs = [ "$root_out_dir/install/usr/share/cobalt/{{source_root_relative_dir}}/{{source_file_part}}" ]
- }
+ group(target_name) {
+ forward_variables_from(invoker, [ "testonly" ])
+ deps = [
+ ":$installable_target_name",
+ ":copy_${target_name}",
+ ]
}
}
diff --git a/starboard/build/install/mock_install.gni b/starboard/build/install/mock_install.gni
deleted file mode 100644
index 2c37068..0000000
--- a/starboard/build/install/mock_install.gni
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright 2021 The Cobalt Authors. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-template("install_target") {
- not_needed("*")
- not_needed(invoker, "*")
-}
diff --git a/starboard/build/install/no_install.gni b/starboard/build/install/no_install.gni
new file mode 100644
index 0000000..f2ca601
--- /dev/null
+++ b/starboard/build/install/no_install.gni
@@ -0,0 +1,31 @@
+# Copyright 2021 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+template("install_target") {
+ not_needed("*", [ "target_name" ])
+ not_needed(invoker,
+ "*",
+ [
+ "testonly",
+ "installable_target_name",
+ ])
+ forward_variables_from(invoker,
+ [
+ "testonly",
+ "installable_target_name",
+ ])
+ group(target_name) {
+ deps = [ ":$installable_target_name" ]
+ }
+}
diff --git a/starboard/build/nasm_assemble.gni b/starboard/build/nasm_assemble.gni
index eb1989c..9b23cd9 100644
--- a/starboard/build/nasm_assemble.gni
+++ b/starboard/build/nasm_assemble.gni
@@ -113,8 +113,6 @@
rebase_path(outputs[0], root_build_dir),
"{{source}}",
]
-
- depfile = outputs[0] + ".d"
}
# Gather the .o files into a linkable thing. This doesn't actually link
diff --git a/starboard/build/platforms.gni b/starboard/build/platforms.gni
deleted file mode 100644
index dbddfb4..0000000
--- a/starboard/build/platforms.gni
+++ /dev/null
@@ -1,80 +0,0 @@
-# Copyright 2021 The Cobalt Authors. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# This file contains a mapping of platform names to paths to the platforms's
-# implementation. To add another platform, simply add another entry in the same
-# style to this list.
-
-platforms = [
- {
- name = "stub"
- path = "starboard/stub"
- },
- {
- name = "linux-x64x11"
- path = "starboard/linux/x64x11"
- },
- {
- name = "linux-x64x11-egl"
- path = "starboard/linux/x64x11/egl"
- },
- {
- name = "linux-x64x11-internal"
- path = "starboard/linux/x64x11/internal"
- },
- {
- name = "linux-x64x11-skia"
- path = "starboard/linux/x64x11/skia"
- },
- {
- name = "android-arm"
- path = "starboard/android/arm"
- },
- {
- name = "android-arm64"
- path = "starboard/android/arm64"
- },
- {
- name = "android-arm64-vulkan"
- path = "starboard/android/arm64/vulkan"
- },
- {
- name = "android-x86"
- path = "starboard/android/x86"
- },
- {
- name = "raspi-2"
- path = "starboard/raspi/2"
- },
- {
- name = "raspi-2-skia"
- path = "starboard/raspi/2/skia"
- },
- {
- name = "win-win32"
- path = "starboard/win/win32"
- },
- {
- name = "darwin-tvos-arm64"
- path = "starboard/darwin/tvos/arm64"
- },
- {
- name = "darwin-tvos-simulator"
- path = "starboard/darwin/tvos/simulator"
- },
- {
- name = "evergreen-x64"
- path = "starboard/evergreen/x64"
- },
-]
diff --git a/starboard/build/platforms.py b/starboard/build/platforms.py
new file mode 100644
index 0000000..d090c56
--- /dev/null
+++ b/starboard/build/platforms.py
@@ -0,0 +1,41 @@
+# Copyright 2021 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""This file contains a mapping of platform names to paths to the platforms's
+implementation. To add another platform, simply add another entry in the same
+style to this dictionary.
+"""
+
+import sys
+
+PLATFORMS = {
+ 'stub': 'starboard/stub',
+ 'linux-x64x11': 'starboard/linux/x64x11',
+ 'linux-x64x11-egl': 'starboard/linux/x64x11/egl',
+ 'linux-x64x11-skia': 'starboard/linux/x64x11/skia',
+ 'linux-x64x11-clang-crosstool': 'starboard/linux/x64x11/clang/crosstool',
+ 'android-arm': 'starboard/android/arm',
+ 'android-arm64': 'starboard/android/arm64',
+ 'android-arm64-vulkan': 'starboard/android/arm64/vulkan',
+ 'android-x86': 'starboard/android/x86',
+ 'raspi-2': 'starboard/raspi/2',
+ 'raspi-2-skia': 'starboard/raspi/2/skia',
+ 'evergreen-x64': 'starboard/evergreen/x64',
+ 'evergreen-arm-hardfp': 'starboard/evergreen/arm/hardfp',
+ 'evergreen-arm-softfp': 'starboard/evergreen/arm/softfp',
+}
+
+if __name__ == '__main__':
+ if len(sys.argv) != 2:
+ raise TypeError('Usage: {} <platform_name>'.format(sys.argv[0]))
+ print(PLATFORMS[sys.argv[1]])
diff --git a/starboard/build/test.gni b/starboard/build/test.gni
index c78aac7..30e75b6 100644
--- a/starboard/build/test.gni
+++ b/starboard/build/test.gni
@@ -1,5 +1,19 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
template("test") {
- executable(target_name) {
+ target(gtest_target_type, target_name) {
testonly = true
forward_variables_from(invoker, "*", [ "configs" ])
configs += invoker.configs
diff --git a/starboard/build/toolchain/starboard_toolchains.gni b/starboard/build/toolchain/starboard_toolchains.gni
new file mode 100644
index 0000000..3e5f5bc
--- /dev/null
+++ b/starboard/build/toolchain/starboard_toolchains.gni
@@ -0,0 +1,24 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+_home_dir = getenv("HOME")
+_starboard_toolchains_default_path = "$_home_dir/starboard-toolchains"
+
+declare_args() {
+ starboard_toolchains_path = getenv("_STARBOARD_TOOLCHAINS_DIR_KEY")
+}
+
+if (starboard_toolchains_path == "") {
+ starboard_toolchains_path = _starboard_toolchains_default_path
+}
diff --git a/starboard/client_porting/cwrappers/BUILD.gn b/starboard/client_porting/cwrappers/BUILD.gn
index c9fd093..4594e64 100644
--- a/starboard/client_porting/cwrappers/BUILD.gn
+++ b/starboard/client_porting/cwrappers/BUILD.gn
@@ -14,7 +14,6 @@
static_library("cwrappers") {
sources = [ "pow_wrapper.cc" ]
-
public_deps = [ "//starboard/common" ]
}
@@ -30,4 +29,5 @@
"//testing/gmock",
"//testing/gtest",
]
+ content_deps = [ "//third_party/icu:icudata" ]
}
diff --git a/starboard/client_porting/eztime/BUILD.gn b/starboard/client_porting/eztime/BUILD.gn
index ce50e45..5611ee1 100644
--- a/starboard/client_porting/eztime/BUILD.gn
+++ b/starboard/client_porting/eztime/BUILD.gn
@@ -41,4 +41,6 @@
"//testing/gmock",
"//testing/gtest",
]
+
+ content_deps = [ "//third_party/icu:icudata" ]
}
diff --git a/starboard/client_porting/poem/BUILD.gn b/starboard/client_porting/poem/BUILD.gn
index eb23fef..a95274d 100644
--- a/starboard/client_porting/poem/BUILD.gn
+++ b/starboard/client_porting/poem/BUILD.gn
@@ -25,4 +25,6 @@
"//starboard",
"//testing/gtest",
]
+
+ content_deps = [ "//third_party/icu:icudata" ]
}
diff --git a/starboard/common/BUILD.gn b/starboard/common/BUILD.gn
index 43e1716..ab4f53d 100644
--- a/starboard/common/BUILD.gn
+++ b/starboard/common/BUILD.gn
@@ -24,11 +24,12 @@
}
static_library("common") {
+ check_includes = false
+
public_deps = [
":common_headers_only",
"//starboard:starboard_headers_only",
]
- check_includes = false
sources = [
"//starboard/shared/media_session/playback_state.cc",
@@ -82,3 +83,18 @@
"thread_collision_warner.h",
]
}
+
+target(gtest_target_type, "common_test") {
+ testonly = true
+ sources = [
+ "memory_test.cc",
+ "socket_test.cc",
+ "test_main.cc",
+ ]
+ deps = [
+ ":common",
+ "//testing/gmock",
+ "//testing/gtest",
+ ]
+ content_deps = [ "//third_party/icu:icudata" ]
+}
diff --git a/starboard/configuration.h b/starboard/configuration.h
index 552565b..5eea69c 100644
--- a/starboard/configuration.h
+++ b/starboard/configuration.h
@@ -927,6 +927,21 @@
" Please see configuration-public.md for more details."
#endif // SB_API_VERSION >= 12 && SB_HAS_QUIRK(SEEK_TO_KEYFRAME)
+#if SB_API_VERSION >= SB_EXPERIMENTAL_API_VERSION
+#if !defined(SB_HAS_COMPACT_10BITS_FRAME)
+#define SB_HAS_COMPACT_10BITS_FRAME 1
+#elif !SB_HAS(SB_HAS_COMPACT_10BITS_FRAME)
+#error "SB_HAS_COMPACT_10BITS_FRAME is required in this API version."
+#endif
+#endif // SB_API_VERSION >= SB_EXPERIMENTAL_API_VERSION
+#if SB_HAS(SB_HAS_COMPACT_10BITS_FRAME)
+#if SB_API_VERSION < SB_EXPERIMENTAL_API_VERSION
+#error \
+ "SB_HAS(SB_HAS_COMPACT_10BITS_FRAME) requires " \
+ "SB_API_VERSION SB_EXPERIMENTAL_API_VERSION or later."
+#endif // SB_API_VERSION < SB_EXPERIMENTAL_API_VERSION
+#endif // SB_HAS(SB_HAS_COMPACT_10BITS_FRAME)
+
// --- Derived Configuration -------------------------------------------------
#if SB_API_VERSION < 12
diff --git a/starboard/decode_target.h b/starboard/decode_target.h
index bb4f72a..e8183f9 100644
--- a/starboard/decode_target.h
+++ b/starboard/decode_target.h
@@ -131,9 +131,14 @@
kSbDecodeTargetFormat3PlaneYUVI420,
// A decoder target format consisting of 10bit Y, U, and V planes, in that
- // order.
+ // order. Each pixel is stored in 16 bits.
kSbDecodeTargetFormat3Plane10BitYUVI420,
+ // A decoder target format consisting of 10bit Y, U, and V planes, in that
+ // order. The plane data is stored in a compact format. Every three 10-bit
+ // pixels are packed into 32 bits.
+ kSbDecodeTargetFormat3Plane10BitYUVI420Compact,
+
// A decoder target format consisting of a single plane with pixels laid out
// in the format UYVY. Since there are two Y values per sample, but only one
// U value and only one V value, horizontally the Y resolution is twice the
@@ -324,6 +329,7 @@
case kSbDecodeTargetFormat2PlaneYUVNV12:
return 2;
case kSbDecodeTargetFormat3Plane10BitYUVI420:
+ case kSbDecodeTargetFormat3Plane10BitYUVI420Compact:
case kSbDecodeTargetFormat3PlaneYUVI420:
return 3;
default:
diff --git a/starboard/egl_and_gles/BUILD.gn b/starboard/egl_and_gles/BUILD.gn
index ccf1cd83..3afc18c 100644
--- a/starboard/egl_and_gles/BUILD.gn
+++ b/starboard/egl_and_gles/BUILD.gn
@@ -23,13 +23,28 @@
# in the configuration.gni for the current platform.
group("egl_and_gles") {
- deps = [ ":egl_and_gles_$gl_type" ]
+ public_deps = [ ":egl_and_gles_$gl_type" ]
}
group("egl_and_gles_system_gles2") {
# Use the system-provided implementation of GLES2.
}
+group("egl_and_gles_system_gles3") {
+ # Deprecated. Use GLES2.
+}
+
+config("egl_and_gles_glimp_public_config") {
+ defines = [ "GL_GLEXT_PROTOTYPES" ]
+}
+
+if (gl_type == "glimp") {
+ group("egl_and_gles_glimp") {
+ public_configs = [ ":egl_and_gles_glimp_public_config" ]
+ public_deps = [ "//glimp" ]
+ }
+}
+
declare_args() {
enable_d3d11_feature_level_11 = false
}
diff --git a/starboard/elf_loader/BUILD.gn b/starboard/elf_loader/BUILD.gn
index 6b7d3f4..34182c3 100644
--- a/starboard/elf_loader/BUILD.gn
+++ b/starboard/elf_loader/BUILD.gn
@@ -60,31 +60,43 @@
]
}
-static_library("elf_loader_sys") {
- # System loader based on dlopen/dlsym.
- # Should be used only for debugging/troubleshooting.
- sources = _elf_loader_sources + [
- "elf_loader_impl.h",
- "elf_loader_sys_impl.cc",
- "elf_loader_sys_impl.h",
- ]
+if (sb_is_evergreen_compatible) {
+ static_library("elf_loader_sys") {
+ # System loader based on dlopen/dlsym.
+ # Should be used only for debugging/troubleshooting.
+ sources = _elf_loader_sources + [
+ "elf_loader_impl.h",
+ "elf_loader_sys_impl.cc",
+ "elf_loader_sys_impl.h",
+ ]
- configs += [ ":elf_loader_config" ]
+ configs += [ ":elf_loader_config" ]
- deps = [
- ":evergreen_config",
- ":evergreen_info",
- "//starboard",
- ]
+ deps = [
+ ":evergreen_config",
+ ":evergreen_info",
+ "//starboard",
+ ]
- if (sb_is_evergreen_compatible) {
- deps += [ "//third_party/crashpad/wrapper" ]
- } else {
- deps += [ "//third_party/crashpad/wrapper:wrapper_stub" ]
+ if (sb_is_evergreen_compatible) {
+ deps += [ "//third_party/crashpad/wrapper" ]
+ } else {
+ deps += [ "//third_party/crashpad/wrapper:wrapper_stub" ]
+ }
}
}
target(final_executable_type, "elf_loader_sandbox") {
+ content_deps = [ "//third_party/icu:icudata" ]
+ if (cobalt_font_package == "empty") {
+ content_deps += [ "//cobalt/content/fonts:copy_font_data" ]
+ } else {
+ content_deps += [
+ "//cobalt/content/fonts:copy_fonts",
+ "//cobalt/content/fonts:fonts_xml",
+ ]
+ }
+
sources = [ "sandbox.cc" ]
configs += [ ":elf_loader_config" ]
@@ -104,34 +116,36 @@
}
}
-target(final_executable_type, "elf_loader_sys_sandbox") {
- # To properly function the system loader requires the starboard
- # symbols to be exported from the binary.
- # To allow symbols to be exported remove the '-fvisibility=hidden' flag
- # from your compiler_flags.gypi. For Linux this would be:
- # starboard/linux/shared/compiler_flags.gypi
- # Example run:
- # export LD_LIBRARY_PATH=.
- # ./elf_loader_sys_sandbox --evergreen_library=app/cobalt/lib/libcobalt.so --evergreen_content=app/cobalt/content
- sources = [ "sandbox.cc" ]
- configs += [ ":elf_loader_config" ]
+if (sb_is_evergreen_compatible) {
+ target(final_executable_type, "elf_loader_sys_sandbox") {
+ # To properly function the system loader requires the starboard
+ # symbols to be exported from the binary.
+ # To allow symbols to be exported remove the '-fvisibility=hidden' flag
+ # from your compiler_flags.gypi. For Linux this would be:
+ # starboard/linux/shared/compiler_flags.gypi
+ # Example run:
+ # export LD_LIBRARY_PATH=.
+ # ./elf_loader_sys_sandbox --evergreen_library=app/cobalt/lib/libcobalt.so --evergreen_content=app/cobalt/content
+ sources = [ "sandbox.cc" ]
+ configs += [ ":elf_loader_config" ]
- starboard_syms_path =
- rebase_path("//starboard/starboard.syms", root_build_dir)
- ldflags = [
- "-Wl,--dynamic-list=$starboard_syms_path",
- "-ldl",
- ]
+ starboard_syms_path =
+ rebase_path("//starboard/starboard.syms", root_build_dir)
+ ldflags = [
+ "-Wl,--dynamic-list=$starboard_syms_path",
+ "-ldl",
+ ]
- deps = [
- ":elf_loader_sys",
- ":evergreen_info",
- ":sabi_string",
- "//starboard",
- ]
+ deps = [
+ ":elf_loader_sys",
+ ":evergreen_info",
+ ":sabi_string",
+ "//starboard",
+ ]
- if (!sb_is_evergreen_compatible) {
- deps += [ "//third_party/crashpad/wrapper:wrapper_stub" ]
+ if (!sb_is_evergreen_compatible) {
+ deps += [ "//third_party/crashpad/wrapper:wrapper_stub" ]
+ }
}
}
@@ -158,11 +172,18 @@
":copy_elf_loader_testdata",
":elf_loader",
]
+
+ # TODO: Remove this dependency once MediaSession is migrated to use CobaltExtensions.
deps += cobalt_platform_dependencies
+ content_deps = [
+ ":copy_elf_loader_testdata",
+ "//third_party/icu:icudata",
+ ]
}
}
copy("copy_elf_loader_testdata") {
+ install_content = true
sources = [
"testdata/compressed.lz4",
"testdata/uncompressed",
diff --git a/starboard/evergreen/arm/hardfp/args.gn b/starboard/evergreen/arm/hardfp/args.gn
new file mode 100644
index 0000000..a4160f8
--- /dev/null
+++ b/starboard/evergreen/arm/hardfp/args.gn
@@ -0,0 +1,17 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+target_platform = "evergreen-arm-hardfp"
+target_cpu = "arm"
+target_os = "linux"
diff --git a/starboard/evergreen/arm/hardfp/platform_configuration/BUILD.gn b/starboard/evergreen/arm/hardfp/platform_configuration/BUILD.gn
new file mode 100644
index 0000000..1c13575
--- /dev/null
+++ b/starboard/evergreen/arm/hardfp/platform_configuration/BUILD.gn
@@ -0,0 +1,35 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+config("sabi_flags") {
+ cflags = [
+ "-mfloat-abi=hard",
+ "-target",
+ "armv7a-none-eabihf",
+ ]
+}
+
+config("platform_configuration") {
+ configs = [ "//starboard/build/config/sabi" ]
+
+ if (current_toolchain == default_toolchain) {
+ configs += [
+ "//starboard/evergreen/arm/shared/platform_configuration:sabi_flags",
+ ":sabi_flags",
+ "//starboard/evergreen/arm/shared/platform_configuration",
+ ]
+ } else {
+ cflags = [ "-O2" ]
+ }
+}
diff --git a/starboard/evergreen/arm/hardfp/platform_configuration/configuration.gni b/starboard/evergreen/arm/hardfp/platform_configuration/configuration.gni
new file mode 100644
index 0000000..b9758e2
--- /dev/null
+++ b/starboard/evergreen/arm/hardfp/platform_configuration/configuration.gni
@@ -0,0 +1,17 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import("//starboard/evergreen/shared/platform_configuration/configuration.gni")
+
+sabi_path = "//starboard/sabi/arm/hardfp/sabi-v$sb_api_version.json"
diff --git a/starboard/evergreen/arm/hardfp/sbversion/12/test_filters.py b/starboard/evergreen/arm/hardfp/sbversion/12/test_filters.py
new file mode 100644
index 0000000..f1521c8
--- /dev/null
+++ b/starboard/evergreen/arm/hardfp/sbversion/12/test_filters.py
@@ -0,0 +1,20 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Evergreen ARM Hard FP Platform Test Filters."""
+
+from starboard.evergreen.arm.hardfp import test_filters as parent_test_filters
+
+
+def CreateTestFilters():
+ return parent_test_filters.EvergreenArmHardFPTestFilters()
diff --git a/starboard/evergreen/arm/hardfp/sbversion/13/test_filters.py b/starboard/evergreen/arm/hardfp/sbversion/13/test_filters.py
new file mode 100644
index 0000000..f1521c8
--- /dev/null
+++ b/starboard/evergreen/arm/hardfp/sbversion/13/test_filters.py
@@ -0,0 +1,20 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Evergreen ARM Hard FP Platform Test Filters."""
+
+from starboard.evergreen.arm.hardfp import test_filters as parent_test_filters
+
+
+def CreateTestFilters():
+ return parent_test_filters.EvergreenArmHardFPTestFilters()
diff --git a/starboard/evergreen/arm/hardfp/test_filters.py b/starboard/evergreen/arm/hardfp/test_filters.py
new file mode 100644
index 0000000..574726f
--- /dev/null
+++ b/starboard/evergreen/arm/hardfp/test_filters.py
@@ -0,0 +1,29 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Evergreen ARM HardFP Platform Test Filters."""
+
+from starboard.evergreen.arm.shared import test_filters as shared_test_filters
+
+
+def CreateTestFilters():
+ return EvergreenArmHardFPTestFilters()
+
+
+class EvergreenArmHardFPTestFilters(shared_test_filters.EvergreenArmTestFilters
+ ):
+ """Starboard Evergreen ARM HardFP Platform Test Filters."""
+
+ def GetTestFilters(self):
+ filters = super(EvergreenArmHardFPTestFilters, self).GetTestFilters()
+ return filters
diff --git a/starboard/evergreen/arm/hardfp/toolchain/BUILD.gn b/starboard/evergreen/arm/hardfp/toolchain/BUILD.gn
new file mode 100644
index 0000000..ac87933
--- /dev/null
+++ b/starboard/evergreen/arm/hardfp/toolchain/BUILD.gn
@@ -0,0 +1,39 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import("//build/config/clang/clang.gni")
+import("//build/toolchain/gcc_toolchain.gni")
+import("//starboard/evergreen/arm/shared/toolchain/toolchain.gni")
+
+clang_toolchain("host") {
+ clang_base_path = clang_base_path
+
+ toolchain_args = {
+ current_os = "linux"
+ current_cpu = "x86"
+ }
+}
+
+gcc_toolchain("target") {
+ cc = evergreen_arm_target_cc
+ cxx = evergreen_arm_target_cxx
+ ld = evergreen_arm_target_ld
+ ar = evergreen_arm_target_ar
+ nm = evergreen_arm_target_nm
+ extra_ldflags = evergreen_arm_target_extra_ldflags
+
+ toolchain_args = {
+ is_clang = false
+ }
+}
diff --git a/starboard/evergreen/arm/shared/platform_configuration/BUILD.gn b/starboard/evergreen/arm/shared/platform_configuration/BUILD.gn
new file mode 100644
index 0000000..8a7581e
--- /dev/null
+++ b/starboard/evergreen/arm/shared/platform_configuration/BUILD.gn
@@ -0,0 +1,31 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+config("sabi_flags") {
+ cflags = [ "-march=armv7a" ]
+}
+
+config("platform_configuration") {
+ configs = [
+ ":sabi_flags",
+ "//starboard/evergreen/shared/platform_configuration",
+ ]
+
+ cflags = [
+ "-isystem" + rebase_path("//third_party/musl/arch/arm", root_build_dir),
+
+ # Force char to be signed.
+ "-fsigned-char",
+ ]
+}
diff --git a/starboard/evergreen/arm/shared/test_filters.py b/starboard/evergreen/arm/shared/test_filters.py
new file mode 100644
index 0000000..00d578a
--- /dev/null
+++ b/starboard/evergreen/arm/shared/test_filters.py
@@ -0,0 +1,40 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Evergreen Arm Platform Test Filters."""
+
+from starboard.evergreen.shared import test_filters as shared_test_filters
+from starboard.tools.testing import test_filter
+
+
+def CreateTestFilters():
+ return EvergreenArmTestFilters()
+
+
+class EvergreenArmTestFilters(shared_test_filters.TestFilters):
+ """Starboard Evergreen Arm Platform Test Filters."""
+
+ def GetTestFilters(self):
+ filters = super(EvergreenArmTestFilters, self).GetTestFilters()
+ for target, tests in self._FILTERED_TESTS.iteritems():
+ filters.extend(test_filter.TestFilter(target, test) for test in tests)
+ return filters
+
+ _FILTERED_TESTS = {
+ 'nplb': [
+ 'SbSystemGetStackTest.SunnyDayStackDirection',
+ 'SbSystemGetStackTest.SunnyDay',
+ 'SbSystemGetStackTest.SunnyDayShortStack',
+ 'SbSystemSymbolizeTest.SunnyDay'
+ ],
+ }
diff --git a/starboard/evergreen/arm/shared/toolchain/toolchain.gni b/starboard/evergreen/arm/shared/toolchain/toolchain.gni
new file mode 100644
index 0000000..6fa7de4
--- /dev/null
+++ b/starboard/evergreen/arm/shared/toolchain/toolchain.gni
@@ -0,0 +1,43 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import("//build/config/clang/clang.gni")
+
+prefix = rebase_path("$clang_base_path/bin", root_build_dir)
+evergreen_arm_target_cc = "$prefix/clang"
+evergreen_arm_target_cxx = "$prefix/clang++"
+evergreen_arm_target_ld = "$prefix/ld.lld"
+evergreen_arm_target_ar = "${prefix}/llvm-ar"
+evergreen_arm_target_nm = "nm"
+
+# TODO(b/206642994): see if any of these flags, migrated from
+# starboard/tools/toolchain/evergreen_linker.py, can be removed.
+extra_ldflags_list = [
+ "--build-id",
+ "--gc-sections",
+ "-X",
+ "-v",
+ "-eh-frame-hdr",
+ "--fini=__cxa_finalize",
+ "-m armelf",
+ "-shared",
+ "-L$clang_base_path",
+ "-L/usr/lib",
+ "-L/lib",
+ "-nostdlib",
+ "--whole-archive",
+ "--no-whole-archive",
+ "-u GetEvergreenSabiString",
+]
+evergreen_arm_target_extra_ldflags = string_join(" ", extra_ldflags_list)
diff --git a/starboard/evergreen/arm/softfp/args.gn b/starboard/evergreen/arm/softfp/args.gn
new file mode 100644
index 0000000..8344f8a
--- /dev/null
+++ b/starboard/evergreen/arm/softfp/args.gn
@@ -0,0 +1,17 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+target_platform = "evergreen-arm-softfp"
+target_cpu = "arm"
+target_os = "linux"
diff --git a/starboard/evergreen/arm/softfp/platform_configuration/BUILD.gn b/starboard/evergreen/arm/softfp/platform_configuration/BUILD.gn
new file mode 100644
index 0000000..a17c539
--- /dev/null
+++ b/starboard/evergreen/arm/softfp/platform_configuration/BUILD.gn
@@ -0,0 +1,35 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+config("sabi_flags") {
+ cflags = [
+ "-mfloat-abi=softfp",
+ "-target",
+ "armv7a-none-eabi",
+ ]
+}
+
+config("platform_configuration") {
+ configs = [ "//starboard/build/config/sabi" ]
+
+ if (current_toolchain == default_toolchain) {
+ configs += [
+ "//starboard/evergreen/arm/shared/platform_configuration:sabi_flags",
+ ":sabi_flags",
+ "//starboard/evergreen/arm/shared/platform_configuration",
+ ]
+ } else {
+ cflags = [ "-O2" ]
+ }
+}
diff --git a/starboard/evergreen/arm/softfp/platform_configuration/configuration.gni b/starboard/evergreen/arm/softfp/platform_configuration/configuration.gni
new file mode 100644
index 0000000..98e1fe6
--- /dev/null
+++ b/starboard/evergreen/arm/softfp/platform_configuration/configuration.gni
@@ -0,0 +1,17 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import("//starboard/evergreen/shared/platform_configuration/configuration.gni")
+
+sabi_path = "//starboard/sabi/arm/softfp/sabi-v$sb_api_version.json"
diff --git a/starboard/evergreen/arm/softfp/sbversion/12/test_filters.py b/starboard/evergreen/arm/softfp/sbversion/12/test_filters.py
new file mode 100644
index 0000000..9287f67
--- /dev/null
+++ b/starboard/evergreen/arm/softfp/sbversion/12/test_filters.py
@@ -0,0 +1,20 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Evergreen ARM Soft FP Platform Test Filters."""
+
+from starboard.evergreen.arm.softfp import test_filters as parent_test_filters
+
+
+def CreateTestFilters():
+ return parent_test_filters.EvergreenArmSoftFPTestFilters()
diff --git a/starboard/evergreen/arm/softfp/sbversion/13/test_filters.py b/starboard/evergreen/arm/softfp/sbversion/13/test_filters.py
new file mode 100644
index 0000000..9287f67
--- /dev/null
+++ b/starboard/evergreen/arm/softfp/sbversion/13/test_filters.py
@@ -0,0 +1,20 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Evergreen ARM Soft FP Platform Test Filters."""
+
+from starboard.evergreen.arm.softfp import test_filters as parent_test_filters
+
+
+def CreateTestFilters():
+ return parent_test_filters.EvergreenArmSoftFPTestFilters()
diff --git a/starboard/evergreen/arm/softfp/test_filters.py b/starboard/evergreen/arm/softfp/test_filters.py
new file mode 100644
index 0000000..b41a646
--- /dev/null
+++ b/starboard/evergreen/arm/softfp/test_filters.py
@@ -0,0 +1,29 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Evergreen ARM SoftFP Platform Test Filters."""
+
+from starboard.evergreen.arm.shared import test_filters as shared_test_filters
+
+
+def CreateTestFilters():
+ return EvergreenArmSoftFPTestFilters()
+
+
+class EvergreenArmSoftFPTestFilters(shared_test_filters.EvergreenArmTestFilters
+ ):
+ """Starboard Evergreen ARM SoftFP Platform Test Filters."""
+
+ def GetTestFilters(self):
+ filters = super(EvergreenArmSoftFPTestFilters, self).GetTestFilters()
+ return filters
diff --git a/starboard/evergreen/arm/softfp/toolchain/BUILD.gn b/starboard/evergreen/arm/softfp/toolchain/BUILD.gn
new file mode 100644
index 0000000..ac87933
--- /dev/null
+++ b/starboard/evergreen/arm/softfp/toolchain/BUILD.gn
@@ -0,0 +1,39 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import("//build/config/clang/clang.gni")
+import("//build/toolchain/gcc_toolchain.gni")
+import("//starboard/evergreen/arm/shared/toolchain/toolchain.gni")
+
+clang_toolchain("host") {
+ clang_base_path = clang_base_path
+
+ toolchain_args = {
+ current_os = "linux"
+ current_cpu = "x86"
+ }
+}
+
+gcc_toolchain("target") {
+ cc = evergreen_arm_target_cc
+ cxx = evergreen_arm_target_cxx
+ ld = evergreen_arm_target_ld
+ ar = evergreen_arm_target_ar
+ nm = evergreen_arm_target_nm
+ extra_ldflags = evergreen_arm_target_extra_ldflags
+
+ toolchain_args = {
+ is_clang = false
+ }
+}
diff --git a/starboard/evergreen/arm64/sbversion/12/test_filters.py b/starboard/evergreen/arm64/sbversion/12/test_filters.py
new file mode 100644
index 0000000..37accf0
--- /dev/null
+++ b/starboard/evergreen/arm64/sbversion/12/test_filters.py
@@ -0,0 +1,20 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Evergreen ARM64 Platform Test Filters."""
+
+from starboard.evergreen.arm64 import test_filters as parent_test_filters
+
+
+def CreateTestFilters():
+ return parent_test_filters.EvergreenArm64TestFilters
diff --git a/starboard/evergreen/arm64/sbversion/13/test_filters.py b/starboard/evergreen/arm64/sbversion/13/test_filters.py
new file mode 100644
index 0000000..a58ce9a
--- /dev/null
+++ b/starboard/evergreen/arm64/sbversion/13/test_filters.py
@@ -0,0 +1,20 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Evergreen ARM64 Platform Test Filters."""
+
+from starboard.evergreen.arm64 import test_filters as parent_test_filters
+
+
+def CreateTestFilters():
+ return parent_test_filters.EvergreenArm64TestFilters()
diff --git a/starboard/evergreen/arm64/test_filters.py b/starboard/evergreen/arm64/test_filters.py
new file mode 100644
index 0000000..c9e6c4b
--- /dev/null
+++ b/starboard/evergreen/arm64/test_filters.py
@@ -0,0 +1,28 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Evergreen Arm64 Platform Test Filters."""
+
+from starboard.evergreen.shared import test_filters as shared_test_filters
+
+
+def CreateTestFilters():
+ return EvergreenArm64TestFilters()
+
+
+class EvergreenArm64TestFilters(shared_test_filters.TestFilters):
+ """Starboard Evergreen Arm64 Platform Test Filters."""
+
+ def GetTestFilters(self):
+ filters = super(EvergreenArm64TestFilters, self).GetTestFilters()
+ return filters
diff --git a/starboard/evergreen/shared/launcher.py b/starboard/evergreen/shared/launcher.py
index 4a90b0c..7e621a2 100644
--- a/starboard/evergreen/shared/launcher.py
+++ b/starboard/evergreen/shared/launcher.py
@@ -32,7 +32,7 @@
This Evergreen launcher leverages the platform-specific launchers to start the
Evergreen loader on a particular device. A staging directory is built that
- resembles a typical deploy directory, with the Evergreen target and its
+ resembles a typical install directory, with the Evergreen target and its
content included in the Evergreen loader's content. The platform-specific
launcher, given command-line switches to tell the Evergreen loader where the
Evergreen target and its content are, can run the loader without needing to
@@ -146,7 +146,7 @@
regardless of whether or not we are running locally:
platform: raspi-2 raspi-2_devel
- config: devel +-- deploy
+ config: devel +-- install
target_name: nplb +-- nplb
+-- content
+-- nplb
@@ -154,15 +154,55 @@
This function effectively builds a directory structure that matches both of
these expectations while minimizing the hard copies made.
- Note: The Linux launcher does not yet look in the deploy directory and
+ Note: The Linux launcher does not yet look in the install directory and
instead runs the target from the top of its out-directory. This will
be changed in the future.
"""
-
if os.path.exists(self.staging_directory):
shutil.rmtree(self.staging_directory)
os.makedirs(self.staging_directory)
+ if os.path.exists(os.path.join(self.loader_out_directory, 'install')):
+ self._StageTargetsAndContentsGn()
+ else:
+ self._StageTargetsAndContentsGyp()
+
+ def _StageTargetsAndContentsGn(self):
+ content_subdir = os.path.join('usr', 'share', 'cobalt')
+
+ # Copy loader content and binaries
+ loader_install_path = os.path.join(self.loader_out_directory, 'install')
+
+ loader_content_src = os.path.join(loader_install_path, content_subdir)
+ loader_content_dst = os.path.join(self.staging_directory, 'content')
+ shutil.copytree(loader_content_src, loader_content_dst)
+
+ loader_target_src = os.path.join(loader_install_path, 'bin',
+ self.loader_target)
+ loader_target_dst = os.path.join(self.staging_directory, self.loader_target)
+ shutil.copy(loader_target_src, loader_target_dst)
+
+ crashpad_target_src = os.path.join(loader_install_path, 'bin',
+ _CRASHPAD_TARGET)
+ crashpad_target_dst = os.path.join(self.staging_directory, _CRASHPAD_TARGET)
+ shutil.copy(crashpad_target_src, crashpad_target_dst)
+
+ # Copy target content and binary
+ target_install_path = os.path.join(self.out_directory, 'install')
+ target_staging_dir = os.path.join(self.staging_directory, 'content', 'app',
+ self.target_name)
+
+ target_content_src = os.path.join(target_install_path, content_subdir)
+ target_content_dst = os.path.join(target_staging_dir, 'content')
+ shutil.copytree(target_content_src, target_content_dst)
+
+ shlib_name = 'lib{}.so'.format(self.target_name)
+ target_binary_src = os.path.join(target_install_path, 'lib', shlib_name)
+ target_binary_dst = os.path.join(target_staging_dir, 'lib', shlib_name)
+ os.makedirs(os.path.join(target_staging_dir, 'lib'))
+ shutil.copy(target_binary_src, target_binary_dst)
+
+ def _StageTargetsAndContentsGyp(self):
# <outpath>/deploy/elf_loader_sandbox
staging_directory_loader = os.path.join(self.staging_directory, 'deploy',
self.loader_target)
@@ -172,7 +212,7 @@
'content', 'app',
self.target_name)
- # Make a hard copy of the ELF Loader's deploy directory in the location
+ # Make a hard copy of the ELF Loader's install_directory in the location
# specified by |staging_directory_loader|. A symbolic link here would cause
# future symbolic links to fall through to the original out-directories.
shutil.copytree(
@@ -186,7 +226,7 @@
os.path.join(self.out_directory, 'deploy', self.target_name),
staging_directory_evergreen)
- # TODO: Make the Linux launcher run from the deploy directory, no longer
+ # TODO: Make the Linux launcher run from the install_directory, no longer
# create these symlinks, and remove the NOTE from the docstring.
port_symlink.MakeSymLink(
os.path.join(staging_directory_loader, self.loader_target),
diff --git a/starboard/evergreen/shared/platform_configuration/BUILD.gn b/starboard/evergreen/shared/platform_configuration/BUILD.gn
index 08c8621..43d8a69 100644
--- a/starboard/evergreen/shared/platform_configuration/BUILD.gn
+++ b/starboard/evergreen/shared/platform_configuration/BUILD.gn
@@ -79,10 +79,7 @@
}
if (is_clang) {
- ldflags += [
- "-fuse-ld=lld",
- "-nostdlib",
- ]
+ ldflags += [ "-nostdlib" ]
cflags += [
"-fcolor-diagnostics",
diff --git a/starboard/evergreen/shared/platform_configuration/configuration.gni b/starboard/evergreen/shared/platform_configuration/configuration.gni
index 44586a3..5b331dc 100644
--- a/starboard/evergreen/shared/platform_configuration/configuration.gni
+++ b/starboard/evergreen/shared/platform_configuration/configuration.gni
@@ -36,3 +36,5 @@
"//starboard/evergreen/shared/platform_configuration:no_pedantic_warnings"
cobalt_licenses_platform = "evergreen"
+
+install_target_path = "//starboard/build/install/install_target.gni"
diff --git a/starboard/evergreen/shared/strip_install_target.gni b/starboard/evergreen/shared/strip_install_target.gni
new file mode 100644
index 0000000..675ae4d
--- /dev/null
+++ b/starboard/evergreen/shared/strip_install_target.gni
@@ -0,0 +1,50 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+template("strip_install_target") {
+ installable_target_name = invoker.installable_target_name
+
+ assert(defined(invoker.strip_executable) && invoker.strip_executable != "",
+ "strip executable required for stripping")
+
+ if (invoker.type == "executable") {
+ install_subdir = "bin"
+ source_name = installable_target_name
+ } else if (invoker.type == "shared_library") {
+ install_subdir = "lib"
+ source_name = "lib${installable_target_name}.so"
+ } else {
+ assert(false, "You can only install an executable or shared library.")
+ }
+
+ action(target_name) {
+ forward_variables_from(invoker, [ "testonly" ])
+
+ script = "//starboard/build/run_bash.py"
+
+ inputs = [ "$root_out_dir/$source_name" ]
+
+ deps = invoker.deps
+ deps += [ ":$installable_target_name" ]
+
+ outputs = [ "$sb_install_output_dir/$install_subdir/$source_name" ]
+
+ args = [
+ strip_executable,
+ "-o",
+ rebase_path(outputs[0], root_out_dir),
+ rebase_path("$root_out_dir/$source_name", root_out_dir),
+ ]
+ }
+}
diff --git a/starboard/evergreen/shared/test_filters.py b/starboard/evergreen/shared/test_filters.py
new file mode 100644
index 0000000..c5a6592
--- /dev/null
+++ b/starboard/evergreen/shared/test_filters.py
@@ -0,0 +1,40 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Evergreen Platform Test Filters."""
+
+from starboard.tools.testing import test_filter
+
+
+class TestFilters(object):
+ """Starboard Evergreen platform test filters."""
+
+ def GetTestFilters(self):
+ filters = []
+ for target, tests in self._FILTERED_TESTS.iteritems():
+ filters.extend(test_filter.TestFilter(target, test) for test in tests)
+ return filters
+
+ _FILTERED_TESTS = {
+ 'nplb': [
+ 'MemoryReportingTest.CapturesOperatorDeleteNothrow',
+ 'SbAudioSinkTest.*', 'SbDrmTest.AnySupportedKeySystems'
+ ],
+
+ # player_filter_tests test the platform's Starboard implementation of
+ # the filter-based player, which is not exposed through the Starboard
+ # interface. Since Evergreen has no visibility of the platform's
+ # specific Starboard implementation, rely on the platform to test this
+ # directly instead.
+ 'player_filter_tests': [test_filter.FILTER_ALL],
+ }
diff --git a/starboard/evergreen/testing/tests/out_of_storage_test.sh b/starboard/evergreen/testing/tests/out_of_storage_test.sh
index 851b54c..6c3c219 100755
--- a/starboard/evergreen/testing/tests/out_of_storage_test.sh
+++ b/starboard/evergreen/testing/tests/out_of_storage_test.sh
@@ -45,9 +45,21 @@
OLD_TIMEOUT="${TIMEOUT}"
TIMEOUT=300
+ RESULT=0
- cycle_cobalt "file:///tests/${TEST_FILE}?channel=test" "${TEST_NAME}.0.log" "Failed to update, log \"error\" code is 12"
+ cycle_cobalt "file:///tests/${TEST_FILE}?channel=test" "${TEST_NAME}.0.log" "Failed to update, error code is 22"
+ if [[ $? -ne 0 ]]; then
+ log "error" "Failed to run out of storage"
+ RESULT=1
+ fi
+ if [[ $RESULT -ne 1 ]]; then
+ cycle_cobalt "file:///tests/${TEST_FILE}?channel=test\&min_storage=5" "${TEST_NAME}.0.log" "Failed to update, error code is 200"
+ if [[ $? -ne 0 ]]; then
+ log "error" "Failed to run out of storage"
+ RESULT=1
+ fi
+ fi
# Remove the symbolic link.
run_command "rm -f ${STORAGE_DIR}" 1> /dev/null
@@ -56,12 +68,7 @@
run_command "mv \"${STORAGE_DIR}.tmp\" \"${STORAGE_DIR}\"" 1> /dev/null
fi
- if [[ $? -ne 0 ]]; then
- log "error" "Failed to run out of storage"
- return 1
- fi
-
TIMEOUT="${OLD_TIMEOUT}"
- return 0
+ return $RESULT
}
diff --git a/starboard/evergreen/testing/tests/suspend_resume_test.sh b/starboard/evergreen/testing/tests/suspend_resume_test.sh
index 35b549f..6d2dcfa 100755
--- a/starboard/evergreen/testing/tests/suspend_resume_test.sh
+++ b/starboard/evergreen/testing/tests/suspend_resume_test.sh
@@ -39,7 +39,7 @@
clear_storage
LOG="${TEST_NAME}.0.log"
- start_cobalt "file:///tests/${TEST_FILE}?channel=test" "${LOG}" LOADER --update_check_delay=1
+ start_cobalt "file:///tests/${TEST_FILE}?channel=test" "${LOG}" LOADER --update_check_delay_seconds=1
VAR=1
diff --git a/starboard/evergreen/testing/tests/test.html b/starboard/evergreen/testing/tests/test.html
index e53f706..97f205a 100644
--- a/starboard/evergreen/testing/tests/test.html
+++ b/starboard/evergreen/testing/tests/test.html
@@ -53,6 +53,9 @@
}
query.forEach(part => {
+ if (part.startsWith("min_storage=")) {
+ window.h5vcc.settings.set("Updater.MinFreeSpaceBytes", part.split("=")[1]);
+ }
if (changeChannel) {
return;
}
@@ -61,6 +64,7 @@
targetChannel = part.split("=")[1];
changeChannel = setInterval(tryChangeChannel, 500);
}
+
});
</script>
</html>
diff --git a/starboard/evergreen/x64/args.gn b/starboard/evergreen/x64/args.gn
new file mode 100644
index 0000000..1c510ed
--- /dev/null
+++ b/starboard/evergreen/x64/args.gn
@@ -0,0 +1,17 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+target_platform = "evergreen-x64"
+target_os = "linux"
+target_cpu = "x64"
diff --git a/starboard/evergreen/x64/install_target.gni b/starboard/evergreen/x64/install_target.gni
new file mode 100644
index 0000000..ecfbc13
--- /dev/null
+++ b/starboard/evergreen/x64/install_target.gni
@@ -0,0 +1,23 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import("//starboard/evergreen/shared/strip_install_target.gni")
+import("//starboard/evergreen/x64/toolchain/strip.gni")
+
+template("install_target") {
+ strip_install_target(target_name) {
+ forward_variables_from(invoker, "*")
+ strip_executable = strip_executable
+ }
+}
diff --git a/starboard/evergreen/x64/platform_configuration/BUILD.gn b/starboard/evergreen/x64/platform_configuration/BUILD.gn
index 658a9ac..f07413a 100644
--- a/starboard/evergreen/x64/platform_configuration/BUILD.gn
+++ b/starboard/evergreen/x64/platform_configuration/BUILD.gn
@@ -31,6 +31,10 @@
cflags = [ "-isystem" +
rebase_path("//third_party/musl/arch/x86_64", root_build_dir) ]
+
+ if (is_clang) {
+ ldflags = [ "-fuse-ld=lld" ]
+ }
} else {
cflags = [ "-O2" ]
}
diff --git a/starboard/evergreen/x64/platform_configuration/configuration.gni b/starboard/evergreen/x64/platform_configuration/configuration.gni
index a5dff93..3d2eb48 100644
--- a/starboard/evergreen/x64/platform_configuration/configuration.gni
+++ b/starboard/evergreen/x64/platform_configuration/configuration.gni
@@ -15,3 +15,5 @@
import("//starboard/evergreen/shared/platform_configuration/configuration.gni")
sabi_path = "//starboard/sabi/x64/sysv/sabi-v$sb_api_version.json"
+
+install_target_path = "//starboard/evergreen/x64/install_target.gni"
diff --git a/starboard/evergreen/x64/sbversion/12/test_filters.py b/starboard/evergreen/x64/sbversion/12/test_filters.py
new file mode 100644
index 0000000..a56490a
--- /dev/null
+++ b/starboard/evergreen/x64/sbversion/12/test_filters.py
@@ -0,0 +1,20 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Evergreen X64 Platform Test Filters."""
+
+from starboard.evergreen.x64 import test_filters as parent_test_filters
+
+
+def CreateTestFilters():
+ parent_test_filters.EvergreenX64TestFilters()
diff --git a/starboard/evergreen/x64/sbversion/13/test_filters.py b/starboard/evergreen/x64/sbversion/13/test_filters.py
new file mode 100644
index 0000000..53b30f2
--- /dev/null
+++ b/starboard/evergreen/x64/sbversion/13/test_filters.py
@@ -0,0 +1,20 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Evergreen X64 Platform Test Filters."""
+
+from starboard.evergreen.x64 import test_filters as parent_test_filters
+
+
+def CreateTestFilters():
+ return parent_test_filters.EvergreenX64TestFilters()
diff --git a/starboard/evergreen/x64/test_filters.py b/starboard/evergreen/x64/test_filters.py
new file mode 100644
index 0000000..f4e36d7
--- /dev/null
+++ b/starboard/evergreen/x64/test_filters.py
@@ -0,0 +1,46 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Evergreen X64 Platform Test Filters."""
+
+import os
+
+from starboard.tools import paths
+from starboard.evergreen.shared import test_filters as shared_test_filters
+
+
+def CreateTestFilters():
+ return EvergreenX64TestFilters()
+
+
+class EvergreenX64TestFilters(shared_test_filters.TestFilters):
+ """Starboard Evergreen X64 Platform Test Filters."""
+
+ def GetTestFilters(self):
+ filters = super(EvergreenX64TestFilters, self).GetTestFilters()
+ # Remove the exclusion filter on SbDrmTest.AnySupportedKeySystems.
+ # Generally, children of linux/shared do not support widevine, but children
+ # of linux/x64x11 do, if the content decryption module is present.
+
+ has_cdm = os.path.isfile(
+ os.path.join(paths.REPOSITORY_ROOT, 'third_party', 'cdm', 'cdm',
+ 'include', 'content_decryption_module.h'))
+
+ if not has_cdm:
+ return filters
+
+ for test_filter in filters:
+ if (test_filter.target_name == 'nplb' and
+ test_filter.test_name == 'SbDrmTest.AnySupportedKeySystems'):
+ filters.remove(test_filter)
+ return filters
diff --git a/starboard/evergreen/x64/toolchain/BUILD.gn b/starboard/evergreen/x64/toolchain/BUILD.gn
index 20e9a1d..be6fcbb 100644
--- a/starboard/evergreen/x64/toolchain/BUILD.gn
+++ b/starboard/evergreen/x64/toolchain/BUILD.gn
@@ -12,17 +12,15 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import("//build/config/clang/clang.gni")
import("//build/toolchain/gcc_toolchain.gni")
-_home_dir = getenv("HOME")
-_clang_base_path = "$_home_dir/starboard-toolchains/x86_64-linux-gnu-clang-chromium-365097-f7e52fbd-8"
-
clang_toolchain("host") {
- clang_base_path = _clang_base_path
+ clang_base_path = clang_base_path
}
clang_toolchain("target") {
- clang_base_path = _clang_base_path
+ clang_base_path = clang_base_path
# TODO: additional work is likely needed to configure the linker for
# evergreen (see //starboard/tools/toolchain/evergreen_linker.py).
}
diff --git a/starboard/evergreen/x64/toolchain/strip.gni b/starboard/evergreen/x64/toolchain/strip.gni
new file mode 100644
index 0000000..fc1f4ee
--- /dev/null
+++ b/starboard/evergreen/x64/toolchain/strip.gni
@@ -0,0 +1,15 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+strip_executable = "strip"
diff --git a/starboard/evergreen/x86/sbversion/12/test_filters.py b/starboard/evergreen/x86/sbversion/12/test_filters.py
new file mode 100644
index 0000000..19fc15e
--- /dev/null
+++ b/starboard/evergreen/x86/sbversion/12/test_filters.py
@@ -0,0 +1,20 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Evergreen X86 Platform Test Filters."""
+
+from starboard.evergreen.x86 import test_filters as parent_test_filters
+
+
+def CreateTestFilters():
+ return parent_test_filters.EvergreenX64TestFilters()
diff --git a/starboard/evergreen/x86/sbversion/13/test_filters.py b/starboard/evergreen/x86/sbversion/13/test_filters.py
new file mode 100644
index 0000000..19fc15e
--- /dev/null
+++ b/starboard/evergreen/x86/sbversion/13/test_filters.py
@@ -0,0 +1,20 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Evergreen X86 Platform Test Filters."""
+
+from starboard.evergreen.x86 import test_filters as parent_test_filters
+
+
+def CreateTestFilters():
+ return parent_test_filters.EvergreenX64TestFilters()
diff --git a/starboard/evergreen/x86/test_filters.py b/starboard/evergreen/x86/test_filters.py
new file mode 100644
index 0000000..f8e9450
--- /dev/null
+++ b/starboard/evergreen/x86/test_filters.py
@@ -0,0 +1,46 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Evergreen X86 Platform Test Filters."""
+
+import os
+
+from starboard.tools import paths
+from starboard.evergreen.shared import test_filters as shared_test_filters
+
+
+def CreateTestFilters():
+ return EvergreenX86TestFilters()
+
+
+class EvergreenX86TestFilters(shared_test_filters.TestFilters):
+ """Starboard Evergreen X86 Platform Test Filters."""
+
+ def GetTestFilters(self):
+ filters = super(EvergreenX86TestFilters, self).GetTestFilters()
+ # Remove the exclusion filter on SbDrmTest.AnySupportedKeySystems.
+ # Generally, children of linux/shared do not support widevine, but children
+ # of linux/x64x11 do, if the content decryption module is present.
+
+ has_cdm = os.path.isfile(
+ os.path.join(paths.REPOSITORY_ROOT, 'third_party', 'cdm', 'cdm',
+ 'include', 'content_decryption_module.h'))
+
+ if not has_cdm:
+ return filters
+
+ for test_filter in filters:
+ if (test_filter.target_name == 'nplb' and
+ test_filter.test_name == 'SbDrmTest.AnySupportedKeySystems'):
+ filters.remove(test_filter)
+ return filters
diff --git a/starboard/examples/window/BUILD.gn b/starboard/examples/window/BUILD.gn
index ddd7b02..beb0040 100644
--- a/starboard/examples/window/BUILD.gn
+++ b/starboard/examples/window/BUILD.gn
@@ -17,4 +17,5 @@
sources = [ "main.cc" ]
public_deps = [ "//starboard" ]
+ content_deps = [ "//third_party/icu:icudata" ]
}
diff --git a/starboard/linux/shared/BUILD.gn b/starboard/linux/shared/BUILD.gn
index ea46558..73e2a93 100644
--- a/starboard/linux/shared/BUILD.gn
+++ b/starboard/linux/shared/BUILD.gn
@@ -71,6 +71,8 @@
"//starboard/linux/shared/player_components_factory.cc",
"//starboard/linux/shared/routes.cc",
"//starboard/linux/shared/routes.h",
+ "//starboard/linux/shared/soft_mic_platform_service.cc",
+ "//starboard/linux/shared/soft_mic_platform_service.h",
"//starboard/linux/shared/system_get_connection_type.cc",
"//starboard/linux/shared/system_get_device_type.cc",
"//starboard/linux/shared/system_get_extensions.cc",
@@ -178,6 +180,8 @@
"//starboard/shared/posix/file_seek.cc",
"//starboard/shared/posix/file_truncate.cc",
"//starboard/shared/posix/file_write.cc",
+ "//starboard/shared/posix/free_space.cc",
+ "//starboard/shared/posix/free_space.h",
"//starboard/shared/posix/log.cc",
"//starboard/shared/posix/log_flush.cc",
"//starboard/shared/posix/log_format.cc",
@@ -272,6 +276,8 @@
"//starboard/shared/pulse/pulse_dynamic_load_dispatcher.h",
"//starboard/shared/signal/crash_signals.h",
"//starboard/shared/signal/crash_signals_sigaction.cc",
+ "//starboard/shared/signal/debug_signals.cc",
+ "//starboard/shared/signal/debug_signals.h",
"//starboard/shared/signal/suspend_signals.cc",
"//starboard/shared/signal/suspend_signals.h",
"//starboard/shared/signal/system_request_conceal.cc",
@@ -417,7 +423,7 @@
if (is_internal_build) {
sources += [
"//starboard/linux/shared/drm_create_system.cc",
- "//starboard/linux/shared/oemcrypto_engine_device_properties_linux.cc",
+ "//starboard/linux/shared/internal/oemcrypto_engine_device_properties_linux.cc",
"//starboard/shared/starboard/drm/drm_close_session.cc",
"//starboard/shared/starboard/drm/drm_destroy_system.cc",
"//starboard/shared/starboard/drm/drm_generate_session_update_request.cc",
diff --git a/starboard/linux/shared/platform_configuration/BUILD.gn b/starboard/linux/shared/platform_configuration/BUILD.gn
index 684a926..d221e63 100644
--- a/starboard/linux/shared/platform_configuration/BUILD.gn
+++ b/starboard/linux/shared/platform_configuration/BUILD.gn
@@ -83,12 +83,6 @@
"-std=c99",
]
cflags_cc = [ "-std=gnu++14" ]
- ldflags += [
- "-Wl,-rpath=\$ORIGIN/lib",
-
- # Cleanup unused sections
- "-Wl,-gc-sections",
- ]
if (use_asan) {
cflags += [
diff --git a/starboard/linux/shared/soft_mic_platform_service.cc b/starboard/linux/shared/soft_mic_platform_service.cc
new file mode 100644
index 0000000..6d96f88
--- /dev/null
+++ b/starboard/linux/shared/soft_mic_platform_service.cc
@@ -0,0 +1,215 @@
+// Copyright 2021 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/linux/shared/soft_mic_platform_service.h"
+
+#include <memory>
+#include <string>
+
+#include "cobalt/extension/platform_service.h"
+#include "starboard/common/log.h"
+#include "starboard/common/string.h"
+#include "starboard/configuration.h"
+#include "starboard/shared/starboard/application.h"
+#if SB_IS(EVERGREEN_COMPATIBLE)
+#include "starboard/elf_loader/evergreen_config.h"
+#endif // SB_IS(EVERGREEN_COMPATIBLE)
+
+typedef struct CobaltExtensionPlatformServicePrivate {
+ void* context;
+ ReceiveMessageCallback receive_callback;
+} CobaltExtensionPlatformServicePrivate;
+
+// Omit namespace linux due to symbol name conflict.
+namespace starboard {
+namespace shared {
+
+namespace {
+
+const char kGetMicSupport[] = "\"getMicSupport\"";
+const char kNotifySearchActive[] = "\"notifySearchActive\"";
+const char kNotifySearchInactive[] = "\"notifySearchInactive\"";
+const char kHasHardMicSupport[] = "has_hard_mic_support";
+const char kHasSoftMicSupport[] = "has_soft_mic_support";
+const char kMicGesture[] = "mic_gesture";
+const char kJSONTrue[] = "true";
+const char kJSONFalse[] = "false";
+
+bool Has(const char* name) {
+ // Check if platform has service name.
+ SB_LOG(INFO) << "Has(): " << name;
+ return strcmp(name, "com.google.youtube.tv.SoftMic") == 0;
+}
+
+CobaltExtensionPlatformService Open(void* context,
+ const char* name,
+ ReceiveMessageCallback receive_callback) {
+ SB_DCHECK(context);
+
+ CobaltExtensionPlatformService service;
+
+ if (!Has(name)) {
+ SB_LOG(ERROR) << "Open() service name does not exist: " << name;
+ service = kCobaltExtensionPlatformServiceInvalid;
+ } else {
+ SB_LOG(INFO) << "Open() service created: " << name;
+ service =
+ new CobaltExtensionPlatformServicePrivate({context, receive_callback});
+ }
+
+#if SB_IS(EVERGREEN_COMPATIBLE)
+ const bool is_evergreen = elf_loader::EvergreenConfig::GetInstance() != NULL;
+#else
+ const bool is_evergreen = false;
+#endif // SB_IS(EVERGREEN_COMPATIBLE)
+
+ // The name parameter memory is allocated in h5vcc_platform_service::Open()
+ // with new[] and must be deallocated here.
+ // If we are in an Evergreen build, the name parameter must be deallocated
+ // with SbMemoryDeallocate(), since new[] will map to SbMemoryAllocate()
+ // in an Evergreen build.
+ if (is_evergreen) {
+ SbMemoryDeallocate((void*)name); // NOLINT
+ } else {
+ delete[] name;
+ }
+
+ return service;
+}
+
+void Close(CobaltExtensionPlatformService service) {
+ SB_LOG(INFO) << "Close() Service.";
+ delete static_cast<CobaltExtensionPlatformServicePrivate*>(service);
+}
+
+void* Send(CobaltExtensionPlatformService service,
+ void* data,
+ uint64_t length,
+ uint64_t* output_length,
+ bool* invalid_state) {
+ SB_DCHECK(service);
+ SB_DCHECK(data);
+ SB_DCHECK(output_length);
+
+ // This bool flag is set to true if the data argument
+ // is successfully parsed as a known web app request,
+ // false otherwise. It is then sent as a synchronous
+ // response to the web app indicating this success or
+ // failure.
+ auto valid_message_received = false;
+
+ char message[length + 1];
+ std::memcpy(message, data, length);
+ message[length] = '\0';
+
+ SB_LOG(INFO) << "Send() message: " << message;
+
+ if (strcmp(message, kGetMicSupport) == 0) {
+ // Process "getMicSupport" web app message.
+ SB_LOG(INFO) << "Send() kGetMicSupport message received.";
+
+ auto has_hard_mic = false;
+ auto has_soft_mic = true;
+ auto mic_gesture_hold = false;
+ auto mic_gesture_tap = false;
+
+#if !defined(COBALT_BUILD_TYPE_GOLD)
+ using shared::starboard::Application;
+
+ // Check for explicit true or false switch value for kHasHardMicSupport,
+ // kHasSoftMicSupport, kMicGestureHold, and kMicGestureTap optional target
+ // params. Use default values if none are set.
+ auto command_line = Application::Get()->GetCommandLine();
+
+ auto hard_mic_switch_value =
+ command_line->GetSwitchValue(kHasHardMicSupport);
+ if (hard_mic_switch_value == kJSONTrue) {
+ has_hard_mic = true;
+ } else if (hard_mic_switch_value == kJSONFalse) {
+ has_hard_mic = false;
+ }
+
+ auto soft_mic_switch_value =
+ command_line->GetSwitchValue(kHasSoftMicSupport);
+ if (soft_mic_switch_value == kJSONTrue) {
+ has_soft_mic = true;
+ } else if (soft_mic_switch_value == kJSONFalse) {
+ has_soft_mic = false;
+ }
+
+ auto mic_gesture_switch_value = command_line->GetSwitchValue(kMicGesture);
+ mic_gesture_hold = mic_gesture_switch_value == "hold";
+ mic_gesture_tap = mic_gesture_switch_value == "tap";
+#endif // !defined(COBALT_BUILD_TYPE_GOLD)
+
+ auto mic_gesture = "null";
+ if (mic_gesture_hold)
+ mic_gesture = "\"HOLD\"";
+ else if (mic_gesture_tap)
+ mic_gesture = "\"TAP\"";
+
+ auto response = FormatString(
+ "{\"hasHardMicSupport\": %s, \"hasSoftMicSupport\": %s, "
+ "\"micGesture\": %s}",
+ (has_hard_mic ? kJSONTrue : kJSONFalse),
+ (has_soft_mic ? kJSONTrue : kJSONFalse), mic_gesture);
+
+ SB_LOG(INFO) << "Send() kGetMicSupport response: " << response;
+
+ // Here we are synchronously calling the receive_callback() from within
+ // Send() which is unnecessary. Implementations should prioritize
+ // returning from Send() ASAP to avoid blocking the JavaScript thread.
+ static_cast<CobaltExtensionPlatformServicePrivate*>(service)
+ ->receive_callback(service->context,
+ static_cast<const void*>(response.c_str()),
+ response.length());
+
+ valid_message_received = true;
+ } else if (strcmp(message, kNotifySearchActive) == 0) {
+ // Process "notifySearchActive" web app message.
+ SB_LOG(INFO) << "Send() kNotifySearchActive message received";
+ valid_message_received = true;
+ } else if (strcmp(message, kNotifySearchInactive) == 0) {
+ // Process "notifySearchInactive" web app message.
+ SB_LOG(INFO) << "Send() kNotifySearchInactive message received";
+ valid_message_received = true;
+ }
+
+ // Synchronous bool response to the web app confirming the message
+ // has been successfully parsed by the platform. Response needs
+ // to be allocated with SbMemoryAllocate() as the caller of Send()
+ // in cobalt/h5vcc/h5vcc_platform_service.cc Send() uses
+ // SbMemoryDeallocate().
+ bool* ptr = reinterpret_cast<bool*>(SbMemoryAllocate(sizeof(bool)));
+ *ptr = valid_message_received;
+ *output_length = sizeof(bool);
+ return static_cast<void*>(ptr);
+}
+
+const CobaltExtensionPlatformServiceApi kPlatformServiceApi = {
+ kCobaltExtensionPlatformServiceName,
+ 1, // API version that's implemented.
+ &Has,
+ &Open,
+ &Close,
+ &Send};
+
+} // namespace
+
+const void* GetPlatformServiceApi() {
+ return &kPlatformServiceApi;
+}
+
+} // namespace shared
+} // namespace starboard
diff --git a/starboard/linux/shared/soft_mic_platform_service.h b/starboard/linux/shared/soft_mic_platform_service.h
new file mode 100644
index 0000000..93f0e0d
--- /dev/null
+++ b/starboard/linux/shared/soft_mic_platform_service.h
@@ -0,0 +1,27 @@
+// Copyright 2021 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_LINUX_SHARED_SOFT_MIC_PLATFORM_SERVICE_H_
+#define STARBOARD_LINUX_SHARED_SOFT_MIC_PLATFORM_SERVICE_H_
+
+// Omit namespace linux due to symbol name conflict.
+namespace starboard {
+namespace shared {
+
+const void* GetPlatformServiceApi();
+
+} // namespace shared
+} // namespace starboard
+
+#endif // STARBOARD_LINUX_SHARED_SOFT_MIC_PLATFORM_SERVICE_H_
diff --git a/starboard/linux/shared/starboard_platform.gypi b/starboard/linux/shared/starboard_platform.gypi
index fef07e1..9c3bec7 100644
--- a/starboard/linux/shared/starboard_platform.gypi
+++ b/starboard/linux/shared/starboard_platform.gypi
@@ -40,6 +40,8 @@
'<(DEPTH)/starboard/linux/shared/player_components_factory.cc',
'<(DEPTH)/starboard/linux/shared/routes.cc',
'<(DEPTH)/starboard/linux/shared/routes.h',
+ '<(DEPTH)/starboard/linux/shared/soft_mic_platform_service.cc',
+ '<(DEPTH)/starboard/linux/shared/soft_mic_platform_service.h',
'<(DEPTH)/starboard/linux/shared/system_get_connection_type.cc',
'<(DEPTH)/starboard/linux/shared/system_get_device_type.cc',
'<(DEPTH)/starboard/linux/shared/system_get_extensions.cc',
@@ -147,6 +149,8 @@
'<(DEPTH)/starboard/shared/posix/file_seek.cc',
'<(DEPTH)/starboard/shared/posix/file_truncate.cc',
'<(DEPTH)/starboard/shared/posix/file_write.cc',
+ '<(DEPTH)/starboard/shared/posix/free_space.cc',
+ '<(DEPTH)/starboard/shared/posix/free_space.h',
'<(DEPTH)/starboard/shared/posix/log_flush.cc',
'<(DEPTH)/starboard/shared/posix/log_format.cc',
'<(DEPTH)/starboard/shared/posix/log_is_tty.cc',
@@ -241,6 +245,8 @@
'<(DEPTH)/starboard/shared/pulse/pulse_dynamic_load_dispatcher.h',
'<(DEPTH)/starboard/shared/signal/crash_signals_sigaction.cc',
'<(DEPTH)/starboard/shared/signal/crash_signals.h',
+ '<(DEPTH)/starboard/shared/signal/debug_signals.cc',
+ '<(DEPTH)/starboard/shared/signal/debug_signals.h',
'<(DEPTH)/starboard/shared/signal/suspend_signals.cc',
'<(DEPTH)/starboard/shared/signal/suspend_signals.h',
'<(DEPTH)/starboard/shared/signal/system_request_conceal.cc',
@@ -379,7 +385,7 @@
],
'starboard_platform_sources': [
'<(DEPTH)/starboard/linux/shared/drm_create_system.cc',
- '<(DEPTH)/starboard/linux/shared/oemcrypto_engine_device_properties_linux.cc',
+ '<(DEPTH)/starboard/linux/shared/internal/oemcrypto_engine_device_properties_linux.cc',
'<(DEPTH)/starboard/shared/starboard/drm/drm_close_session.cc',
'<(DEPTH)/starboard/shared/starboard/drm/drm_destroy_system.cc',
diff --git a/starboard/linux/shared/system_get_extensions.cc b/starboard/linux/shared/system_get_extensions.cc
index 3411d93..3c44ffd 100644
--- a/starboard/linux/shared/system_get_extensions.cc
+++ b/starboard/linux/shared/system_get_extensions.cc
@@ -16,8 +16,12 @@
#include "cobalt/extension/configuration.h"
#include "cobalt/extension/crash_handler.h"
+#include "cobalt/extension/free_space.h"
#include "cobalt/extension/memory_mapped_file.h"
+#include "cobalt/extension/platform_service.h"
#include "starboard/common/string.h"
+#include "starboard/linux/shared/soft_mic_platform_service.h"
+#include "starboard/shared/posix/free_space.h"
#include "starboard/shared/posix/memory_mapped_file.h"
#include "starboard/shared/starboard/crash_handler.h"
#if SB_IS(EVERGREEN_COMPATIBLE)
@@ -37,6 +41,9 @@
}
}
#endif
+ if (strcmp(name, kCobaltExtensionPlatformServiceName) == 0) {
+ return starboard::shared::GetPlatformServiceApi();
+ }
if (strcmp(name, kCobaltExtensionConfigurationName) == 0) {
return starboard::shared::GetConfigurationApi();
}
@@ -46,5 +53,8 @@
if (strcmp(name, kCobaltExtensionMemoryMappedFileName) == 0) {
return starboard::shared::posix::GetMemoryMappedFileApi();
}
+ if (strcmp(name, kCobaltExtensionFreeSpaceName) == 0) {
+ return starboard::shared::posix::GetFreeSpaceApi();
+ }
return NULL;
}
diff --git a/starboard/linux/shared/test_filters.py b/starboard/linux/shared/test_filters.py
new file mode 100644
index 0000000..7f0d6ea
--- /dev/null
+++ b/starboard/linux/shared/test_filters.py
@@ -0,0 +1,87 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Linux Platform Test Filters."""
+
+import os
+
+from starboard.tools import paths
+from starboard.tools.testing import test_filter
+
+
+class TestFilters(object):
+ """Starboard Linux platform test filters."""
+
+ def GetTestFilters(self):
+ filters = []
+
+ has_cdm = os.path.isfile(
+ os.path.join(paths.REPOSITORY_ROOT, 'third_party', 'ce_cdm', 'cdm',
+ 'include', 'cdm.h'))
+
+ for target, tests in self._FILTERED_TESTS.iteritems():
+ filters.extend(test_filter.TestFilter(target, test) for test in tests)
+
+ if has_cdm:
+ return filters
+
+ # Filter the drm related tests, as ce_cdm is not present.
+ for target, tests in self._DRM_RELATED_TESTS.iteritems():
+ filters.extend(test_filter.TestFilter(target, test) for test in tests)
+ return filters
+
+ _DRM_RELATED_TESTS = {
+ 'nplb': [
+ 'SbDrmTest.AnySupportedKeySystems',
+ 'SbMediaCanPlayMimeAndKeySystem.AnySupportedKeySystems',
+ ],
+ }
+
+ # pylint: disable=line-too-long
+ _FILTERED_TESTS = {
+ 'player_filter_tests': [
+ # libdav1d crashes when fed invalid data
+ 'VideoDecoderTests/VideoDecoderTest.*Invalid*',
+ ],
+ }
+ _FILTERED_TESTS['nplb'] = []
+ # Conditionally disables tests that require ipv6
+ if os.getenv('IPV6_AVAILABLE', '1') == '0':
+ _FILTERED_TESTS['nplb'].extend([
+ 'SbSocketAddressTypes/SbSocketGetInterfaceAddressTest.SunnyDayDestination/1',
+ 'SbSocketAddressTypes/SbSocketGetInterfaceAddressTest.SunnyDaySourceForDestination/1',
+ 'SbSocketAddressTypes/SbSocketGetInterfaceAddressTest.SunnyDaySourceNotLoopback/1',
+ ])
+ # TODO: Re-enable once tests or infra fixed.
+ _FILTERED_TESTS['nplb'].extend([
+ 'SbSocketAddressTypes/SbSocketBindTest.RainyDayBadInterface/1',
+ 'SbSocketAddressTypes/PairSbSocketGetLocalAddressTest.SunnyDayConnected/1',
+ 'SbSocketAddressTypes/PairSbSocketIsConnectedAndIdleTest.SunnyDay/1',
+ 'SbSocketAddressTypes/PairSbSocketIsConnectedTest.SunnyDay/1',
+ 'SbSocketAddressTypes/PairSbSocketReceiveFromTest.SunnyDay/1',
+ 'SbSocketAddressTypes/SbSocketResolveTest.Localhost/1',
+ 'SbSocketAddressTypes/SbSocketResolveTest.SunnyDayFiltered/1',
+ 'SbSocketAddressTypes/PairSbSocketSendToTest.RainyDaySendToClosedSocket/1',
+ 'SbSocketAddressTypes/PairSbSocketSendToTest.RainyDaySendToSocketUntilBlocking/1',
+ 'SbSocketAddressTypes/PairSbSocketSendToTest.RainyDaySendToSocketConnectionReset/1',
+ 'SbSocketAddressTypes/PairSbSocketWaiterWaitTest.SunnyDay/1',
+ 'SbSocketAddressTypes/PairSbSocketWaiterWaitTest.SunnyDayAlreadyReady/1',
+ 'SbSocketAddressTypes/PairSbSocketWaiterWaitTimedTest.SunnyDay/1',
+ 'SbSocketAddressTypes/PairSbSocketWrapperTest.SunnyDay/1',
+ ])
+
+ # pylint: enable=line-too-long
+
+
+def CreateTestFilters():
+ return TestFilters()
diff --git a/starboard/linux/x64x11/args.gn b/starboard/linux/x64x11/args.gn
new file mode 100644
index 0000000..697d6f2
--- /dev/null
+++ b/starboard/linux/x64x11/args.gn
@@ -0,0 +1,17 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+target_platform = "linux-x64x11"
+target_os = "linux"
+target_cpu = "x64"
diff --git a/starboard/linux/x64x11/clang/3.9/test_filters.py b/starboard/linux/x64x11/clang/3.9/test_filters.py
new file mode 100644
index 0000000..256866d
--- /dev/null
+++ b/starboard/linux/x64x11/clang/3.9/test_filters.py
@@ -0,0 +1,28 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Linux X64 X11 Clang 3.9 Platform Test Filters."""
+
+from starboard.linux.shared import test_filters as shared_test_filters
+
+
+def CreateTestFilters():
+ return LinuxX64X11Clang39TestFilters()
+
+
+class LinuxX64X11Clang39TestFilters(shared_test_filters.TestFilters):
+ """Starboard Linux X64 X11 Clang 3.9 Platform Test Filters."""
+
+ def GetTestFilters(self):
+ filters = super(LinuxX64X11Clang39TestFilters, self).GetTestFilters()
+ return filters
diff --git a/starboard/linux/x64x11/egl/args.gn b/starboard/linux/x64x11/egl/args.gn
new file mode 100644
index 0000000..6bf2d33
--- /dev/null
+++ b/starboard/linux/x64x11/egl/args.gn
@@ -0,0 +1,17 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+target_platform = "linux-x64x11-egl"
+target_os = "linux"
+target_cpu = "x64"
diff --git a/starboard/linux/x64x11/egl/test_filters.py b/starboard/linux/x64x11/egl/test_filters.py
new file mode 100644
index 0000000..825987e
--- /dev/null
+++ b/starboard/linux/x64x11/egl/test_filters.py
@@ -0,0 +1,28 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Linux X64 X11 EGL Platform Test Filters."""
+
+from starboard.linux.x64x11 import test_filters as shared_test_filters
+
+
+def CreateTestFilters():
+ return LinuxX64X11EglTestFilters()
+
+
+class LinuxX64X11EglTestFilters(shared_test_filters.LinuxX64X11TestFilters):
+ """Starboard Linux X64 X11 EGL Platform Test Filters."""
+
+ def GetTestFilters(self):
+ filters = super(LinuxX64X11EglTestFilters, self).GetTestFilters()
+ return filters
diff --git a/starboard/linux/x64x11/egl/toolchain/BUILD.gn b/starboard/linux/x64x11/egl/toolchain/BUILD.gn
index 1ab2e67..c1469cb 100644
--- a/starboard/linux/x64x11/egl/toolchain/BUILD.gn
+++ b/starboard/linux/x64x11/egl/toolchain/BUILD.gn
@@ -12,13 +12,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import("//build/config/clang/clang.gni")
import("//build/toolchain/gcc_toolchain.gni")
-_home_dir = getenv("HOME")
-_clang_base_path = "$_home_dir/starboard-toolchains/x86_64-linux-gnu-clang-chromium-365097-f7e52fbd-8"
-
clang_toolchain("host") {
- clang_base_path = _clang_base_path
+ clang_base_path = clang_base_path
toolchain_args = {
current_os = "linux"
@@ -27,5 +25,5 @@
}
clang_toolchain("target") {
- clang_base_path = _clang_base_path
+ clang_base_path = clang_base_path
}
diff --git a/starboard/linux/x64x11/gcc/6.3/test_filters.py b/starboard/linux/x64x11/gcc/6.3/test_filters.py
new file mode 100644
index 0000000..5f24171
--- /dev/null
+++ b/starboard/linux/x64x11/gcc/6.3/test_filters.py
@@ -0,0 +1,36 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Linux X64 X11 gcc 6.3 Platform Test Filters."""
+
+from starboard.linux.shared import test_filters as shared_test_filters
+from starboard.tools.testing import test_filter
+
+
+def CreateTestFilters():
+ return LinuxX64X11Gcc63TestFilters()
+
+
+class LinuxX64X11Gcc63TestFilters(shared_test_filters.TestFilters):
+ """Starboard Linux X64 X11 gcc 6.3 Platform Test Filters."""
+
+ def GetTestFilters(self):
+ filters = super(LinuxX64X11Gcc63TestFilters, self).GetTestFilters()
+ for target, tests in self._FILTERED_TESTS.iteritems():
+ filters.extend(test_filter.TestFilter(target, test) for test in tests)
+ return filters
+
+ _FILTERED_TESTS = {
+ # TODO(b/206117361): Re-enable starboard_platform_tests once fixed.
+ 'starboard_platform_tests': ['JobQueueTest.JobsAreMovedAndNotCopied',],
+ }
diff --git a/starboard/linux/x64x11/gcc/6.3/toolchain/BUILD.gn b/starboard/linux/x64x11/gcc/6.3/toolchain/BUILD.gn
new file mode 100644
index 0000000..52826ea
--- /dev/null
+++ b/starboard/linux/x64x11/gcc/6.3/toolchain/BUILD.gn
@@ -0,0 +1,46 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import("//starboard/shared/toolchain/overridable_gcc_toolchain.gni")
+
+# Directory for GCC 6.3 if it is installed as a system dependency.
+_default_gcc_6_3_bin_dir = "/usr/bin"
+
+overridable_gcc_toolchain("host") {
+ cc = "${_default_gcc_6_3_bin_dir}/gcc-6"
+ cxx = "${_default_gcc_6_3_bin_dir}/g++-6"
+ ld = cxx
+
+ # We use whatever 'ar' resolves to.
+ ar = "ar"
+
+ toolchain_args = {
+ target_os = "linux"
+ target_cpu = "x64"
+ is_clang = false
+ }
+}
+
+overridable_gcc_toolchain("target") {
+ cc = "${_default_gcc_6_3_bin_dir}/gcc-6"
+ cxx = "${_default_gcc_6_3_bin_dir}/g++-6"
+ ld = cxx
+
+ # We use whatever 'ar' resolves to.
+ ar = "ar"
+
+ toolchain_args = {
+ is_clang = false
+ }
+}
diff --git a/starboard/linux/x64x11/main.cc b/starboard/linux/x64x11/main.cc
index 0c7dcfb..cf076af 100644
--- a/starboard/linux/x64x11/main.cc
+++ b/starboard/linux/x64x11/main.cc
@@ -16,6 +16,7 @@
#include "starboard/configuration.h"
#include "starboard/shared/signal/crash_signals.h"
+#include "starboard/shared/signal/debug_signals.h"
#include "starboard/shared/signal/suspend_signals.h"
#include "starboard/shared/starboard/link_receiver.h"
#include "starboard/shared/x11/application_x11.h"
@@ -25,6 +26,7 @@
extern "C" SB_EXPORT_PLATFORM int main(int argc, char** argv) {
tzset();
starboard::shared::signal::InstallCrashSignalHandlers();
+ starboard::shared::signal::InstallDebugSignalHandlers();
starboard::shared::signal::InstallSuspendSignalHandlers();
#if SB_IS(EVERGREEN_COMPATIBLE)
@@ -44,6 +46,7 @@
result = application.Run(argc, argv);
}
starboard::shared::signal::UninstallSuspendSignalHandlers();
+ starboard::shared::signal::UninstallDebugSignalHandlers();
starboard::shared::signal::UninstallCrashSignalHandlers();
return result;
}
diff --git a/starboard/linux/x64x11/sbversion/11/test_filters.py b/starboard/linux/x64x11/sbversion/11/test_filters.py
new file mode 100644
index 0000000..c6f1e2d
--- /dev/null
+++ b/starboard/linux/x64x11/sbversion/11/test_filters.py
@@ -0,0 +1,20 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Linux X64 X11 Platform Test Filters."""
+
+from starboard.linux.x64x11 import test_filters as parent_test_filters
+
+
+def CreateTestFilters():
+ return parent_test_filters.LinuxX64X11TestFilters()
diff --git a/starboard/linux/x64x11/sbversion/12/test_filters.py b/starboard/linux/x64x11/sbversion/12/test_filters.py
new file mode 100644
index 0000000..c6f1e2d
--- /dev/null
+++ b/starboard/linux/x64x11/sbversion/12/test_filters.py
@@ -0,0 +1,20 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Linux X64 X11 Platform Test Filters."""
+
+from starboard.linux.x64x11 import test_filters as parent_test_filters
+
+
+def CreateTestFilters():
+ return parent_test_filters.LinuxX64X11TestFilters()
diff --git a/starboard/linux/x64x11/sbversion/13/test_filters.py b/starboard/linux/x64x11/sbversion/13/test_filters.py
new file mode 100644
index 0000000..c6f1e2d
--- /dev/null
+++ b/starboard/linux/x64x11/sbversion/13/test_filters.py
@@ -0,0 +1,20 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Linux X64 X11 Platform Test Filters."""
+
+from starboard.linux.x64x11 import test_filters as parent_test_filters
+
+
+def CreateTestFilters():
+ return parent_test_filters.LinuxX64X11TestFilters()
diff --git a/starboard/linux/x64x11/shared/platform_configuration/BUILD.gn b/starboard/linux/x64x11/shared/platform_configuration/BUILD.gn
index 99595e2..d4c0954 100644
--- a/starboard/linux/x64x11/shared/platform_configuration/BUILD.gn
+++ b/starboard/linux/x64x11/shared/platform_configuration/BUILD.gn
@@ -18,6 +18,7 @@
":libraries",
"//starboard/linux/shared/platform_configuration",
"//starboard/linux/shared/platform_configuration:compiler_flags",
+ ":linker_flags",
]
} else {
cflags = [ "-O2" ]
@@ -31,3 +32,12 @@
"//third_party/libvpx/platforms/linux-x64/libvpx.a",
]
}
+
+config("linker_flags") {
+ ldflags = [
+ "-Wl,-rpath=\$ORIGIN/lib",
+
+ # Cleanup unused sections
+ "-Wl,-gc-sections",
+ ]
+}
diff --git a/starboard/linux/x64x11/skia/args.gn b/starboard/linux/x64x11/skia/args.gn
new file mode 100644
index 0000000..68d39a3
--- /dev/null
+++ b/starboard/linux/x64x11/skia/args.gn
@@ -0,0 +1,17 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+target_platform = "linux-x64x11-skia"
+target_os = "linux"
+target_cpu = "x64"
diff --git a/starboard/linux/x64x11/skia/test_filters.py b/starboard/linux/x64x11/skia/test_filters.py
new file mode 100644
index 0000000..b34aca5
--- /dev/null
+++ b/starboard/linux/x64x11/skia/test_filters.py
@@ -0,0 +1,28 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Linux X64 X11 Skia Platform Test Filters."""
+
+from starboard.linux.x64x11 import test_filters as shared_test_filters
+
+
+def CreateTestFilters():
+ return LinuxX64X11SkiaTestFilters()
+
+
+class LinuxX64X11SkiaTestFilters(shared_test_filters.LinuxX64X11TestFilters):
+ """Starboard Linux X64 X11 Skia Platform Test Filters."""
+
+ def GetTestFilters(self):
+ filters = super(LinuxX64X11SkiaTestFilters, self).GetTestFilters()
+ return filters
diff --git a/starboard/linux/x64x11/skia/toolchain/BUILD.gn b/starboard/linux/x64x11/skia/toolchain/BUILD.gn
index 1ab2e67..c1469cb 100644
--- a/starboard/linux/x64x11/skia/toolchain/BUILD.gn
+++ b/starboard/linux/x64x11/skia/toolchain/BUILD.gn
@@ -12,13 +12,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import("//build/config/clang/clang.gni")
import("//build/toolchain/gcc_toolchain.gni")
-_home_dir = getenv("HOME")
-_clang_base_path = "$_home_dir/starboard-toolchains/x86_64-linux-gnu-clang-chromium-365097-f7e52fbd-8"
-
clang_toolchain("host") {
- clang_base_path = _clang_base_path
+ clang_base_path = clang_base_path
toolchain_args = {
current_os = "linux"
@@ -27,5 +25,5 @@
}
clang_toolchain("target") {
- clang_base_path = _clang_base_path
+ clang_base_path = clang_base_path
}
diff --git a/starboard/linux/x64x11/test_filters.py b/starboard/linux/x64x11/test_filters.py
new file mode 100644
index 0000000..60e6c1c
--- /dev/null
+++ b/starboard/linux/x64x11/test_filters.py
@@ -0,0 +1,28 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Linux X64 X11 Platform Test Filters."""
+
+from starboard.linux.shared import test_filters as shared_test_filters
+
+
+def CreateTestFilters():
+ return LinuxX64X11TestFilters()
+
+
+class LinuxX64X11TestFilters(shared_test_filters.TestFilters):
+ """Starboard Linux X64 X11 Platform Test Filters."""
+
+ def GetTestFilters(self):
+ filters = super(LinuxX64X11TestFilters, self).GetTestFilters()
+ return filters
diff --git a/starboard/linux/x64x11/toolchain/BUILD.gn b/starboard/linux/x64x11/toolchain/BUILD.gn
index 1ab2e67..9e483e1 100644
--- a/starboard/linux/x64x11/toolchain/BUILD.gn
+++ b/starboard/linux/x64x11/toolchain/BUILD.gn
@@ -12,13 +12,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import("//build/toolchain/gcc_toolchain.gni")
+import("//build/config/clang/clang.gni")
+import("//starboard/shared/toolchain/overridable_gcc_toolchain.gni")
-_home_dir = getenv("HOME")
-_clang_base_path = "$_home_dir/starboard-toolchains/x86_64-linux-gnu-clang-chromium-365097-f7e52fbd-8"
-
-clang_toolchain("host") {
- clang_base_path = _clang_base_path
+overridable_clang_toolchain("host") {
+ clang_base_path = clang_base_path
toolchain_args = {
current_os = "linux"
@@ -26,6 +24,6 @@
}
}
-clang_toolchain("target") {
- clang_base_path = _clang_base_path
+overridable_clang_toolchain("target") {
+ clang_base_path = clang_base_path
}
diff --git a/starboard/loader_app/BUILD.gn b/starboard/loader_app/BUILD.gn
index 9ce5395..3d11846 100644
--- a/starboard/loader_app/BUILD.gn
+++ b/starboard/loader_app/BUILD.gn
@@ -26,8 +26,15 @@
":installation_manager",
":slot_management",
"//starboard",
+ "//starboard/elf_loader:evergreen_info",
"//starboard/elf_loader:sabi_string",
]
+
+ if (sb_is_evergreen_compatible) {
+ public_deps += [ "//third_party/crashpad/wrapper" ]
+ } else {
+ public_deps += [ "//third_party/crashpad/wrapper:wrapper_stub" ]
+ }
}
target(final_executable_type, "loader_app") {
@@ -43,23 +50,25 @@
}
}
-target(final_executable_type, "loader_app_sys") {
- if (target_cpu == "x86" || target_cpu == "x64" || target_cpu == "arm" ||
- target_cpu == "arm64") {
- sources = _common_loader_app_sources
+if (sb_is_evergreen_compatible) {
+ target(final_executable_type, "loader_app_sys") {
+ if (target_cpu == "x86" || target_cpu == "x64" || target_cpu == "arm" ||
+ target_cpu == "arm64") {
+ sources = _common_loader_app_sources
- starboard_syms_path =
- rebase_path("//starboard/starboard.syms", root_build_dir)
- ldflags = [
- "-Wl,--dynamic-list=$starboard_syms_path",
- "-ldl",
- ]
- deps = [
- ":common_loader_app_dependencies",
- "//cobalt/content/fonts:copy_font_data",
- "//starboard/elf_loader:elf_loader_sys",
- ]
- deps += cobalt_platform_dependencies
+ starboard_syms_path =
+ rebase_path("//starboard/starboard.syms", root_build_dir)
+ ldflags = [
+ "-Wl,--dynamic-list=$starboard_syms_path",
+ "-ldl",
+ ]
+ deps = [
+ ":common_loader_app_dependencies",
+ "//cobalt/content/fonts:copy_font_data",
+ "//starboard/elf_loader:elf_loader_sys",
+ ]
+ deps += cobalt_platform_dependencies
+ }
}
}
@@ -82,6 +91,7 @@
"//testing/gmock",
"//testing/gtest",
]
+ content_deps = [ "//third_party/icu:icudata" ]
}
static_library("app_key") {
@@ -108,6 +118,7 @@
"//testing/gmock",
"//testing/gtest",
]
+ content_deps = [ "//third_party/icu:icudata" ]
}
static_library("drain_file") {
@@ -131,6 +142,7 @@
"//testing/gmock",
"//testing/gtest",
]
+ content_deps = [ "//third_party/icu:icudata" ]
}
static_library("installation_store_proto") {
@@ -157,7 +169,7 @@
"//starboard",
]
- if (sb_evergreen_compatible_enable_lite) {
+ if (!sb_evergreen_compatible_enable_lite) {
deps += [ ":pending_restart" ]
}
}
@@ -177,6 +189,7 @@
"//testing/gmock",
"//testing/gtest",
]
+ content_deps = [ "//third_party/icu:icudata" ]
}
static_library("slot_management") {
@@ -192,6 +205,12 @@
"//starboard/elf_loader",
"//starboard/elf_loader:sabi_string",
]
+
+ if (sb_is_evergreen_compatible) {
+ deps += [ "//third_party/crashpad/wrapper" ]
+ } else {
+ deps += [ "//third_party/crashpad/wrapper:wrapper_stub" ]
+ }
}
target(gtest_target_type, "slot_management_test") {
diff --git a/starboard/loader_app/installation_manager.cc b/starboard/loader_app/installation_manager.cc
index c004621..bb9601f 100644
--- a/starboard/loader_app/installation_manager.cc
+++ b/starboard/loader_app/installation_manager.cc
@@ -28,7 +28,7 @@
#include "starboard/file.h"
#include "starboard/loader_app/installation_store.pb.h"
#if !SB_IS(EVERGREEN_COMPATIBLE_LITE)
-#include "starboard/loader_app/pending_restart.h"
+#include "starboard/loader_app/pending_restart.h" // nogncheck
#endif // !SB_IS(EVERGREEN_COMPATIBLE_LITE)
#include "starboard/once.h"
#include "starboard/string.h"
diff --git a/starboard/nplb/BUILD.gn b/starboard/nplb/BUILD.gn
index 6ca3e60..a8c6697 100644
--- a/starboard/nplb/BUILD.gn
+++ b/starboard/nplb/BUILD.gn
@@ -270,8 +270,7 @@
]
}
- deps = [ "//starboard/nplb/testdata/file_tests:nplb_file_tests_data" ]
- deps += cobalt_platform_dependencies
+ deps = cobalt_platform_dependencies
if (is_internal_build) {
deps += [ "//starboard/private/nplb:nplb_private" ]
@@ -281,11 +280,21 @@
"//starboard",
"//starboard/common",
"//starboard/shared/starboard/media:media_util",
- "//starboard/shared/starboard/player:player_download_test_data",
"//starboard/shared/starboard/player:video_dmp",
"//testing/gmock",
]
+ data_deps = [
+ "//starboard/nplb/testdata/file_tests:nplb_file_tests_data",
+ "//starboard/shared/starboard/player:player_download_test_data",
+ ]
+
+ content_deps = [
+ "//starboard/nplb/testdata/file_tests:nplb_file_tests_data",
+ "//starboard/shared/starboard/player:player_download_test_data",
+ "//third_party/icu:icudata",
+ ]
+
if (gl_type != "none") {
public_deps += [ "//starboard/egl_and_gles" ]
}
diff --git a/starboard/nplb/media_can_play_mime_and_key_system_test.cc b/starboard/nplb/media_can_play_mime_and_key_system_test.cc
index 4c94240..81f85e2 100644
--- a/starboard/nplb/media_can_play_mime_and_key_system_test.cc
+++ b/starboard/nplb/media_can_play_mime_and_key_system_test.cc
@@ -12,11 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include "starboard/nplb/media_can_play_mime_and_key_system_test_helpers.h"
+#include <map>
#include "starboard/common/string.h"
#include "starboard/media.h"
#include "starboard/nplb/drm_helpers.h"
+#include "starboard/nplb/media_can_play_mime_and_key_system_test_helpers.h"
#include "starboard/nplb/performance_helpers.h"
#include "starboard/time.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -291,280 +292,190 @@
}
}
-// TODO: Create an abstraction to shorten the length of this test.
TEST(SbMediaCanPlayMimeAndKeySystem, PrintMaximumSupport) {
- // AVC
- std::string avc_resolution = "Unsupported";
- // 1080p
- SbMediaSupportType result = SbMediaCanPlayMimeAndKeySystem(
- "video/mp4; codecs=\"avc1.4d402a\"; width=1920; height=1080; "
- "framerate=30",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- avc_resolution = "1080p";
- // 2K
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/mp4; codecs=\"avc1.64002a\"; width=2560; height=1440; "
- "framerate=30",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- avc_resolution = "2K";
- // 4K
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/mp4; codecs=\"avc1.64002a\"; width=3840; height=2160; "
- "framerate=30",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- avc_resolution = "4K";
+ const char* kResolution1080p = "1080p";
+ const char* kResolution2k = "2K";
+ const char* kResolution4k = "4K";
+ const char* kResolution8k = "8K";
+ auto get_max_video_codec_resolution =
+ [=](std::string video_type,
+ const std::map<const char*, std::string> codec_params,
+ std::string framerate) {
+ std::map<const char*, std::string> resolutions = {
+ {kResolution1080p, "width=1920; height=1080;"},
+ {kResolution2k, "width=2560; height=1440;"},
+ {kResolution4k, "width=3840; height=2160;"},
+ {kResolution8k, "width=7680; height=4320;"}};
+
+ std::string max_supported_resolution;
+ for (auto&& it : resolutions) {
+ if (codec_params.find(it.first) == codec_params.end()) {
+ break;
+ }
+ std::string content_type = video_type + "; codecs=\"" +
+ codec_params.at(it.first) + "\"; " +
+ it.second + " framerate=" + framerate;
+ if (SbMediaCanPlayMimeAndKeySystem(content_type.c_str(), "") !=
+ kSbMediaSupportTypeProbably) {
+ break;
+ }
+ max_supported_resolution = it.first;
+ }
+ return max_supported_resolution.empty() ? "Unsupported"
+ : max_supported_resolution;
+ };
+
+ auto get_drm_system_support = [](std::string key_system) {
+ std::map<const char*, std::string> content_types = {
+ {"video/mp4; codecs=\"avc1.4d402a\"", "AVC"},
+ {"video/webm; codecs=\"vp9\"", "VP9"},
+ {"video/mp4; codecs=\"av01.0.08M.08\"", "AV1"},
+ {"audio/mp4; codecs=\"mp4a.40.2\"", "AAC"},
+ {"audio/webm; codecs=\"opus\"", "Opus"},
+ {"audio/mp4; codecs=\"ac-3\"", "AC-3"},
+ {"audio/mp4; codecs=\"ec-3\"", "E-AC-3"}};
+ std::string supported_codecs;
+ for (auto&& it : content_types) {
+ if (SbMediaCanPlayMimeAndKeySystem(it.first, key_system.c_str()) ==
+ kSbMediaSupportTypeProbably) {
+ supported_codecs += it.second + " ";
}
}
+ return supported_codecs.empty() ? "Unsupported" : supported_codecs;
+ };
+
+ std::string avc_support =
+ get_max_video_codec_resolution("video/mp4",
+ {{kResolution1080p, "avc1.4d402a"},
+ {kResolution2k, "avc1.64002a"},
+ {kResolution4k, "avc1.64002a"}},
+ "30");
+ if (avc_support != "Unsupported") {
+ avc_support += "\n\tAVC HFR: " + get_max_video_codec_resolution(
+ "video/mp4",
+ {{kResolution1080p, "avc1.4d402a"},
+ {kResolution2k, "avc1.64402a"},
+ {kResolution4k, "avc1.64402a"}},
+ "60");
}
- // AVC HFR
- std::string avc_hfr_resolution = "Unsupported";
- // 1080p
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/mp4; codecs=\"avc1.4d402a\"; width=1920; height=1080; "
- "framerate=60",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- avc_hfr_resolution = "1080p";
- // 2K
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/mp4; codecs=\"avc1.64402a\"; width=2560; height=1440; "
- "framerate=60",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- avc_hfr_resolution = "2K";
- // 4K
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/mp4; codecs=\"avc1.64402a\"; width=3840; height=2160; "
- "framerate=60",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- avc_hfr_resolution = "4K";
- }
+ std::string vp9_support =
+ get_max_video_codec_resolution("video/webm",
+ {{kResolution1080p, "vp9"},
+ {kResolution2k, "vp9"},
+ {kResolution4k, "vp9"}},
+ "30");
+ if (vp9_support != "Unsupported") {
+ vp9_support += "\n\tVP9 HFR: " +
+ get_max_video_codec_resolution("video/webm",
+ {{kResolution1080p, "vp9"},
+ {kResolution2k, "vp9"},
+ {kResolution4k, "vp9"}},
+ "60");
+ std::string vp9_hdr_support = get_max_video_codec_resolution(
+ "video/webm",
+ {{kResolution1080p, "vp09.02.41.10.01.09.16.09.00"},
+ {kResolution2k, "vp09.02.51.10.01.09.16.09.00"},
+ {kResolution4k, "vp09.02.51.10.01.09.16.09.00"}},
+ "30");
+ if (vp9_hdr_support != "Unsupported") {
+ vp9_hdr_support +=
+ "\n\tVP9 HDR HFR: " +
+ get_max_video_codec_resolution(
+ "video/webm",
+ {{kResolution1080p, "vp09.02.41.10.01.09.16.09.00"},
+ {kResolution2k, "vp09.02.51.10.01.09.16.09.00"},
+ {kResolution4k, "vp09.02.51.10.01.09.16.09.00"}},
+ "60");
}
+ vp9_support += "\n\tVP9 HDR: " + vp9_hdr_support;
}
-
- // VP9
- std::string vp9_resolution = "Unsupported";
- // 1080p
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/webm; codecs=\"vp9\"; width=1920; height=1080; framerate=30", "");
- if (result == kSbMediaSupportTypeProbably) {
- vp9_resolution = "1080p";
- // 2K
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/webm; codecs=\"vp9\"; width=2560; height=1440; framerate=30",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- vp9_resolution = "2K";
- // 4K
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/webm; codecs=\"vp9\"; width=3840; height=2160; framerate=30",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- vp9_resolution = "4K";
- }
+ std::string av1_support =
+ get_max_video_codec_resolution("video/mp4",
+ {{kResolution1080p, "av01.0.08M.08"},
+ {kResolution2k, "av01.0.12M.08"},
+ {kResolution4k, "av01.0.12M.08"},
+ {kResolution8k, "av01.0.16M.08"}},
+ "30");
+ if (av1_support != "Unsupported") {
+ av1_support += "\n\tAV1 HFR: " + get_max_video_codec_resolution(
+ "video/mp4",
+ {{kResolution1080p, "av01.0.09M.08"},
+ {kResolution2k, "av01.0.12M.08"},
+ {kResolution4k, "av01.0.13M.08"},
+ {kResolution8k, "av01.0.17M.08"}},
+ "60");
+ std::string av1_hdr_support = get_max_video_codec_resolution(
+ "video/mp4",
+ {{kResolution1080p, "av01.0.09M.10.0.110.09.16.09.0"},
+ {kResolution2k, "av01.0.12M.10.0.110.09.16.09.0"},
+ {kResolution4k, "av01.0.13M.10.0.110.09.16.09.0"},
+ {kResolution8k, "av01.0.17M.10.0.110.09.16.09.0"}},
+ "30");
+ if (av1_hdr_support != "Unsupported") {
+ av1_hdr_support +=
+ "\n\tAV1 HDR HFR: " +
+ get_max_video_codec_resolution(
+ "video/mp4",
+ {{kResolution1080p, "av01.0.09M.10.0.110.09.16.09.0"},
+ {kResolution2k, "av01.0.12M.10.0.110.09.16.09.0"},
+ {kResolution4k, "av01.0.13M.10.0.110.09.16.09.0"},
+ {kResolution8k, "av01.0.17M.10.0.110.09.16.09.0"}},
+ "60");
}
+ av1_support += "\n\tAV1 HDR: " + av1_hdr_support;
}
- // VP9 HFR
- std::string vp9_hfr_resolution = "Unsupported";
- // 1080p
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/webm; codecs=\"vp9\"; width=1920; height=1080; framerate=60", "");
- if (result == kSbMediaSupportTypeProbably) {
- vp9_hfr_resolution = "1080p";
- // 2K
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/webm; codecs=\"vp9\"; width=2560; height=1440; framerate=60",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- vp9_hfr_resolution = "2K";
- // 4K
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/webm; codecs=\"vp9\"; width=3840; height=2160; framerate=60",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- vp9_hfr_resolution = "4K";
- }
- }
- }
+ std::string aac_support = SbMediaCanPlayMimeAndKeySystem(
+ "audio/mp4; codecs=\"mp4a.40.2\"; channels=2",
+ "") == kSbMediaSupportTypeProbably
+ ? "Supported"
+ : "Unsupported";
- // VP9 HDR
- std::string vp9_hdr_resolution = "Unsupported";
- // 1080p
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/webm; codecs=\"vp09.02.51.10.01.09.16.09.00\"; width=1920; "
- "height=1080; framerate=30",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- vp9_hdr_resolution = "1080p";
- // 2K
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/webm; codecs=\"vp09.02.51.10.01.09.16.09.00\"; width=2560; "
- "height=1440; framerate=30",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- vp9_hdr_resolution = "2K";
- // 4K
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/webm; codecs=\"vp09.02.51.10.01.09.16.09.00\"; width=3840; "
- "height=2160; framerate=30",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- vp9_hdr_resolution = "4K";
- }
- }
- }
+ std::string aac51_support = SbMediaCanPlayMimeAndKeySystem(
+ "audio/mp4; codecs=\"mp4a.40.2\"; channels=6",
+ "") == kSbMediaSupportTypeProbably
+ ? "Supported"
+ : "Unsupported";
- // AV1
- std::string av1_resolution = "Unsupported";
- // 1080p
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/mp4; codecs=\"av01.0.09M.08\"; width=1920; "
- "height=1080; framerate=30",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- av1_resolution = "1080p";
- // 2K
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/mp4; codecs=\"av01.0.12M.08\"; width=2560; "
- "height=1440; framerate=30",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- av1_resolution = "2K";
- // 4K
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/mp4; codecs=\"av01.0.12M.08\"; width=3840; "
- "height=2160; framerate=30",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- av1_resolution = "4K";
- }
- }
- }
- // AV1 HFR
- std::string av1_hfr_resolution = "Unsupported";
- // 1080p
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/mp4; codecs=\"av01.0.09M.08\"; width=1920; height=1080; "
- "framerate=60",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- av1_hfr_resolution = "1080p";
- // 2K
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/mp4; codecs=\"av01.0.12M.08\"; width=2560; height=1440; "
- "framerate=60",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- av1_hfr_resolution = "2K";
- // 4K
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/mp4; codecs=\"av01.0.13M.08\"; width=3840; height=2160; "
- "framerate=60",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- av1_hfr_resolution = "4K";
- }
- }
- }
+ std::string opus_support = SbMediaCanPlayMimeAndKeySystem(
+ "audio/webm; codecs=\"opus\"; "
+ "channels=2; bitrate=128000;",
+ "") == kSbMediaSupportTypeProbably
+ ? "Supported"
+ : "Unsupported";
- // AV1 HDR
- std::string av1_hdr_resolution = "Unsupported";
- // 1080p
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/mp4; codecs=\"av01.0.09M.10.0.110.09.16.09.0\"; width=1920; "
- "height=1080",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- av1_hdr_resolution = "1080p";
- // 2K
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/mp4; codecs=\"av01.0.12M.10.0.110.09.16.09.0\"; width=2560; "
- "height=1440",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- av1_hdr_resolution = "2K";
- // 4K
- result = SbMediaCanPlayMimeAndKeySystem(
- "video/mp4; codecs=\"av01.0.13M.10.0.110.09.16.09.0\"; width=3840; "
- "height=2160",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- av1_hdr_resolution = "4K";
- }
- }
- }
+ std::string opus51_support = SbMediaCanPlayMimeAndKeySystem(
+ "audio/webm; codecs=\"opus\"; "
+ "channels=6; bitrate=576000;",
+ "") == kSbMediaSupportTypeProbably
+ ? "Supported"
+ : "Unsupported";
- // AAC
- std::string aac_support = "Unsupported";
- result = SbMediaCanPlayMimeAndKeySystem(
- "audio/mp4; codecs=\"mp4a.40.2\"; channels=2", "");
- if (result == kSbMediaSupportTypeProbably) {
- aac_support = "Supported";
- }
+ std::string ac3_support =
+ SbMediaCanPlayMimeAndKeySystem(
+ "audio/mp4; codecs=\"ac-3\"; channels=6; bitrate=512000", "") ==
+ kSbMediaSupportTypeProbably
+ ? "Supported"
+ : "Unsupported";
- // AAC51
- std::string aac51_support = "Unsupported";
- result = SbMediaCanPlayMimeAndKeySystem(
- "audio/mp4; codecs=\"mp4a.40.2\"; channels=6", "");
- if (result == kSbMediaSupportTypeProbably) {
- aac51_support = "Supported";
- }
+ std::string eac3_support =
+ SbMediaCanPlayMimeAndKeySystem(
+ "audio/mp4; codecs=\"ec-3\"; channels=6; bitrate=512000", "") ==
+ kSbMediaSupportTypeProbably
+ ? "Supported"
+ : "Unsupported";
- // Opus
- std::string opus_support = "Unsupported";
- result = SbMediaCanPlayMimeAndKeySystem(
- "audio/webm; codecs=\"opus\"; "
- "channels=2; bitrate=128000;",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- opus_support = "Supported";
- }
-
- // Opus51
- std::string opus51_support = "Unsupported";
- result = SbMediaCanPlayMimeAndKeySystem(
- "audio/webm; codecs=\"opus\"; "
- "channels=6; bitrate=576000;",
- "");
- if (result == kSbMediaSupportTypeProbably) {
- opus51_support = "Supported";
- }
-
- // AC-3
- std::string ac3_support = "Unsupported";
- result = SbMediaCanPlayMimeAndKeySystem(
- "audio/mp4; codecs=\"ac-3\"; channels=6; bitrate=512000", "");
- if (result == kSbMediaSupportTypeProbably) {
- ac3_support = "Supported";
- }
-
- // E-AC-3
- std::string eac3_support = "Unsupported";
- result = SbMediaCanPlayMimeAndKeySystem(
- "audio/mp4; codecs=\"ec-3\"; channels=6; bitrate=512000", "");
- if (result == kSbMediaSupportTypeProbably) {
- eac3_support = "Supported";
- }
-
- SB_LOG(INFO) << "\nVideo Codec Capability:\n\tAVC: " << avc_resolution
- << "\n\tAVC HFR: " << avc_hfr_resolution
- << "\n\tVP9: " << vp9_resolution
- << "\n\tVP9 HFR: " << vp9_hfr_resolution
- << "\n\tVP9 HDR: " << vp9_hdr_resolution
- << "\n\tAV1: " << av1_resolution
- << "\n\tAV1 HFR:" << av1_hfr_resolution
- << "\n\tAV1 HDR:" << av1_hdr_resolution
+ SB_LOG(INFO) << "\nVideo Codec Capability:\n\tAVC: " << avc_support
+ << "\n\tVP9: " << vp9_support << "\n\tAV1: " << av1_support
<< "\n\nAudio Codec Capability:\n\tAAC: " << aac_support
<< "\n\tAAC 51: " << aac51_support
<< "\n\tOpus: " << opus_support
<< "\n\tOpus 51: " << opus51_support
- << "\n\tAC-3: " << ac3_support << "\n\tE-AC-3: " << eac3_support;
+ << "\n\tAC-3: " << ac3_support << "\n\tE-AC-3: " << eac3_support
+ << "\n\nDRM Capability:\n\tWidevine: "
+ << get_drm_system_support("com.widevine") << "\n\tPlayReady: "
+ << get_drm_system_support("com.youtube.playready");
}
// TODO: Create an abstraction to shorten the length of this test.
@@ -818,6 +729,9 @@
test_sequential_function_calls(kHdrQueryParams,
SB_ARRAY_SIZE_INT(kHdrQueryParams),
20 * kSbTimeMillisecond, "HDR queries");
+ test_sequential_function_calls(kDrmQueryParams,
+ SB_ARRAY_SIZE_INT(kDrmQueryParams),
+ 50 * kSbTimeMillisecond, "DRM queries");
}
} // namespace
diff --git a/starboard/nplb/media_can_play_mime_and_key_system_test_helpers.h b/starboard/nplb/media_can_play_mime_and_key_system_test_helpers.h
index a48201a..544961b 100644
--- a/starboard/nplb/media_can_play_mime_and_key_system_test_helpers.h
+++ b/starboard/nplb/media_can_play_mime_and_key_system_test_helpers.h
@@ -205,6 +205,158 @@
"audio/webm; codecs=\"opus\"; channels=2",
"audio/webm; codecs=\"opus\"; channels=2"};
+// Query params from https://youtu.be/1mSzHxMpji0.
+static const char* kDrmQueryParams[] = {
+ "video/mp4; codecs=\"avc1.4d4015\"; width=426; height=240; framerate=24; "
+ "bitrate=281854",
+ "video/mp4; codecs=\"avc1.4d401e\"; width=640; height=360; framerate=24; "
+ "bitrate=637760",
+ "video/mp4; codecs=\"avc1.4d401e\"; width=854; height=480; framerate=24; "
+ "bitrate=1164612",
+ "video/mp4; codecs=\"avc1.640028\"; width=1920; height=1080; framerate=24; "
+ "bitrate=4362827",
+ "audio/mp4; codecs=\"mp4a.40.2\"; channels=2",
+ "video/mp4; codecs=\"avc1.4d400c\"; width=256; height=144; framerate=24; "
+ "bitrate=138907",
+ "video/mp4; codecs=\"avc1.4d401e\"; width=854; height=480; framerate=24; "
+ "bitrate=1746306",
+ "video/mp4; codecs=\"avc1.4d401e\"; width=854; height=480; framerate=24; "
+ "bitrate=3473564",
+ "video/mp4; codecs=\"avc1.4d401f\"; width=1280; height=720; framerate=24; "
+ "bitrate=3481130",
+ "video/mp4; codecs=\"avc1.4d401f\"; width=1280; height=720; framerate=24; "
+ "bitrate=5789806",
+ "video/mp4; codecs=\"avc1.640028\"; width=1920; height=1080; framerate=24; "
+ "bitrate=5856175",
+ "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=854; "
+ "height=480; framerate=24; bitrate=2629046; eotf=bt709; "
+ "cryptoblockformat=subsample",
+ "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1280; "
+ "height=720; framerate=24; bitrate=1328071; eotf=bt709; "
+ "cryptoblockformat=subsample",
+ "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1920; "
+ "height=1080; framerate=24; bitrate=2375894; eotf=bt709; "
+ "cryptoblockformat=subsample",
+ "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=426; "
+ "height=240; framerate=24; bitrate=229634; eotf=bt709; "
+ "cryptoblockformat=subsample",
+ "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=640; "
+ "height=360; framerate=24; bitrate=324585; eotf=bt709; "
+ "cryptoblockformat=subsample",
+ "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=854; "
+ "height=480; framerate=24; bitrate=639196; eotf=bt709; "
+ "cryptoblockformat=subsample",
+ "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=854; "
+ "height=480; framerate=24; bitrate=1055128; eotf=bt709; "
+ "cryptoblockformat=subsample",
+ "audio/mp4; codecs=\"ec-3\"; channels=6",
+ "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1280; "
+ "height=720; framerate=24; bitrate=2111149; eotf=bt709; "
+ "cryptoblockformat=subsample",
+ "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1280; "
+ "height=720; framerate=24; bitrate=3709033; eotf=bt709; "
+ "cryptoblockformat=subsample",
+ "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1920; "
+ "height=1080; framerate=24; bitrate=3679792; eotf=bt709; "
+ "cryptoblockformat=subsample",
+ "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1920; "
+ "height=1080; framerate=24; bitrate=5524689; eotf=bt709; "
+ "cryptoblockformat=subsample",
+ "audio/mp4; codecs=\"ac-3\"; channels=6",
+ "video/mp4; codecs=\"avc1.4d4015\"; width=426; height=240; framerate=24; "
+ "bitrate=281854",
+ "video/mp4; codecs=\"avc1.4d401e\"; width=640; height=360; framerate=24; "
+ "bitrate=637760",
+ "video/mp4; codecs=\"avc1.4d401e\"; width=854; height=480; framerate=24; "
+ "bitrate=1164612",
+ "video/mp4; codecs=\"avc1.640028\"; width=1920; height=1080; framerate=24; "
+ "bitrate=4362827",
+ "audio/mp4; codecs=\"mp4a.40.2\"; channels=2",
+ "video/mp4; codecs=\"avc1.4d400c\"; width=256; height=144; framerate=24; "
+ "bitrate=138907",
+ "video/mp4; codecs=\"avc1.4d401e\"; width=854; height=480; framerate=24; "
+ "bitrate=1746306",
+ "video/mp4; codecs=\"avc1.4d401e\"; width=854; height=480; framerate=24; "
+ "bitrate=3473564",
+ "video/mp4; codecs=\"avc1.4d401f\"; width=1280; height=720; framerate=24; "
+ "bitrate=3481130",
+ "video/mp4; codecs=\"avc1.4d401f\"; width=1280; height=720; framerate=24; "
+ "bitrate=5789806",
+ "video/mp4; codecs=\"avc1.640028\"; width=1920; height=1080; framerate=24; "
+ "bitrate=5856175",
+ "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=854; "
+ "height=480; framerate=24; bitrate=2629046; eotf=bt709; "
+ "cryptoblockformat=subsample",
+ "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1280; "
+ "height=720; framerate=24; bitrate=1328071; eotf=bt709; "
+ "cryptoblockformat=subsample",
+ "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1920; "
+ "height=1080; framerate=24; bitrate=2375894; eotf=bt709; "
+ "cryptoblockformat=subsample",
+ "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=426; "
+ "height=240; framerate=24; bitrate=229634; eotf=bt709; "
+ "cryptoblockformat=subsample",
+ "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=640; "
+ "height=360; framerate=24; bitrate=324585; eotf=bt709; "
+ "cryptoblockformat=subsample",
+ "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=854; "
+ "height=480; framerate=24; bitrate=639196; eotf=bt709; "
+ "cryptoblockformat=subsample",
+ "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=854; "
+ "height=480; framerate=24; bitrate=1055128; eotf=bt709; "
+ "cryptoblockformat=subsample",
+ "audio/mp4; codecs=\"ec-3\"; channels=6",
+ "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1280; "
+ "height=720; framerate=24; bitrate=2111149; eotf=bt709; "
+ "cryptoblockformat=subsample",
+ "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1280; "
+ "height=720; framerate=24; bitrate=3709033; eotf=bt709; "
+ "cryptoblockformat=subsample",
+ "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1920; "
+ "height=1080; framerate=24; bitrate=3679792; eotf=bt709; "
+ "cryptoblockformat=subsample",
+ "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1920; "
+ "height=1080; framerate=24; bitrate=5524689; eotf=bt709; "
+ "cryptoblockformat=subsample",
+ "audio/mp4; codecs=\"ac-3\"; channels=6",
+ "video/mp4; codecs=\"avc1.4d4015\"; width=426; height=240; framerate=24; "
+ "bitrate=149590",
+ "video/mp4; codecs=\"avc1.4d401e\"; width=640; height=360; framerate=24; "
+ "bitrate=261202",
+ "video/mp4; codecs=\"avc1.4d401e\"; width=854; height=480; framerate=24; "
+ "bitrate=368187",
+ "video/mp4; codecs=\"avc1.4d401f\"; width=1280; height=720; framerate=24; "
+ "bitrate=676316",
+ "video/mp4; codecs=\"avc1.640028\"; width=1920; height=1080; framerate=24; "
+ "bitrate=2691722",
+ "audio/mp4; codecs=\"mp4a.40.2\"; channels=2",
+ "video/mp4; codecs=\"avc1.4d400c\"; width=256; height=144; framerate=24; "
+ "bitrate=84646",
+ "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=426; "
+ "height=240; framerate=24; bitrate=192698; eotf=bt709",
+ "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=640; "
+ "height=360; framerate=24; bitrate=342403; eotf=bt709",
+ "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=854; "
+ "height=480; framerate=24; bitrate=514976; eotf=bt709",
+ "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1280; "
+ "height=720; framerate=24; bitrate=852689; eotf=bt709",
+ "video/webm; codecs=\"vp09.00.51.08.01.01.01.01.00\"; width=1920; "
+ "height=1080; framerate=24; bitrate=2389269; eotf=bt709",
+ "audio/webm; codecs=\"opus\"; channels=2",
+ "audio/webm; codecs=\"opus\"; channels=2",
+ "video/mp4; codecs=\"av01.0.00M.08\"; width=256; height=144; framerate=24; "
+ "bitrate=74957; eotf=bt709",
+ "video/mp4; codecs=\"av01.0.00M.08\"; width=426; height=240; framerate=24; "
+ "bitrate=148691; eotf=bt709",
+ "video/mp4; codecs=\"av01.0.01M.08\"; width=640; height=360; framerate=24; "
+ "bitrate=305616; eotf=bt709",
+ "video/mp4; codecs=\"av01.0.04M.08\"; width=854; height=480; framerate=24; "
+ "bitrate=577104; eotf=bt709",
+ "video/mp4; codecs=\"av01.0.05M.08\"; width=1280; height=720; "
+ "framerate=24; bitrate=989646; eotf=bt709",
+ "video/mp4; codecs=\"av01.0.08M.08\"; width=1920; height=1080; "
+ "framerate=24; bitrate=1766589; eotf=bt709"};
+
} // namespace nplb
} // namespace starboard
diff --git a/starboard/nplb/nplb_evergreen_compat_tests/BUILD.gn b/starboard/nplb/nplb_evergreen_compat_tests/BUILD.gn
index 05753b8..705025b 100644
--- a/starboard/nplb/nplb_evergreen_compat_tests/BUILD.gn
+++ b/starboard/nplb/nplb_evergreen_compat_tests/BUILD.gn
@@ -36,4 +36,6 @@
"//starboard",
"//testing/gmock",
]
+
+ content_deps = [ "//third_party/icu:icudata" ]
}
diff --git a/starboard/nplb/testdata/file_tests/BUILD.gn b/starboard/nplb/testdata/file_tests/BUILD.gn
index 1566e4f..bb96507 100644
--- a/starboard/nplb/testdata/file_tests/BUILD.gn
+++ b/starboard/nplb/testdata/file_tests/BUILD.gn
@@ -13,6 +13,8 @@
# limitations under the License.
copy("nplb_file_tests_data") {
+ install_content = true
+
sources = [
"dir_with_files/file11",
"dir_with_files/file12",
diff --git a/starboard/raspi/2/args.gn b/starboard/raspi/2/args.gn
new file mode 100644
index 0000000..0b190f8
--- /dev/null
+++ b/starboard/raspi/2/args.gn
@@ -0,0 +1,18 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+target_platform = "raspi-2"
+target_os = "linux"
+target_cpu = "arm"
+is_clang = false
diff --git a/starboard/raspi/2/sbversion/12/test_filters.py b/starboard/raspi/2/sbversion/12/test_filters.py
new file mode 100644
index 0000000..d83e77e
--- /dev/null
+++ b/starboard/raspi/2/sbversion/12/test_filters.py
@@ -0,0 +1,23 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Raspberry Pi 2 Platform Test Filters."""
+
+import importlib
+
+# Dynamically imported to get around the number in the path.
+_PARENT_TEST_FILTERS = importlib.import_module('starboard.raspi.2.test_filters')
+
+
+def CreateTestFilters():
+ return _PARENT_TEST_FILTERS.Raspi2TestFilters()
diff --git a/starboard/raspi/2/sbversion/13/test_filters.py b/starboard/raspi/2/sbversion/13/test_filters.py
new file mode 100644
index 0000000..d83e77e
--- /dev/null
+++ b/starboard/raspi/2/sbversion/13/test_filters.py
@@ -0,0 +1,23 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Raspberry Pi 2 Platform Test Filters."""
+
+import importlib
+
+# Dynamically imported to get around the number in the path.
+_PARENT_TEST_FILTERS = importlib.import_module('starboard.raspi.2.test_filters')
+
+
+def CreateTestFilters():
+ return _PARENT_TEST_FILTERS.Raspi2TestFilters()
diff --git a/starboard/raspi/2/skia/args.gn b/starboard/raspi/2/skia/args.gn
new file mode 100644
index 0000000..c6c8d1d
--- /dev/null
+++ b/starboard/raspi/2/skia/args.gn
@@ -0,0 +1,18 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+target_platform = "raspi-2-skia"
+target_os = "linux"
+target_cpu = "arm"
+is_clang = false
diff --git a/starboard/raspi/2/skia/test_filters.py b/starboard/raspi/2/skia/test_filters.py
new file mode 100644
index 0000000..12e54d4
--- /dev/null
+++ b/starboard/raspi/2/skia/test_filters.py
@@ -0,0 +1,31 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Raspberry Pi 2 Skia Platform Test Filters."""
+
+import importlib
+
+# Dynamically imported to get around the number in the path.
+_PARENT_TEST_FILTERS = importlib.import_module('starboard.raspi.2.test_filters')
+
+
+def CreateTestFilters():
+ return Raspi2SkiaTestFilters()
+
+
+class Raspi2SkiaTestFilters(_PARENT_TEST_FILTERS.Raspi2TestFilters):
+ """Starboard Raspberry Pi 2 Skia Platform Test Filters."""
+
+ def GetTestFilters(self):
+ filters = super(Raspi2SkiaTestFilters, self).GetTestFilters()
+ return filters
diff --git a/starboard/raspi/2/skia/toolchain/BUILD.gn b/starboard/raspi/2/skia/toolchain/BUILD.gn
index 3b83f52..0d9a282 100644
--- a/starboard/raspi/2/skia/toolchain/BUILD.gn
+++ b/starboard/raspi/2/skia/toolchain/BUILD.gn
@@ -12,11 +12,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import("//build/config/clang/clang.gni")
import("//build/toolchain/gcc_toolchain.gni")
import("//starboard/raspi/shared/toolchain/raspi_shared_toolchain.gni")
clang_toolchain("host") {
- clang_base_path = raspi_clang_base_path
+ clang_base_path = clang_base_path
toolchain_args = {
current_os = "linux"
@@ -25,11 +26,13 @@
}
gcc_toolchain("target") {
- cc = "$gcc_toolchain_cc"
- cxx = "$gcc_toolchain_cxx"
+ cc = gcc_toolchain_cc
+ cxx = gcc_toolchain_cxx
ld = cxx
- ar = "$gcc_toolchain_ar"
+ ar = gcc_toolchain_ar
+
+ tail_lib_dependencies = "-l:libpthread.so.0"
toolchain_args = {
is_clang = false
diff --git a/starboard/raspi/2/test_filters.py b/starboard/raspi/2/test_filters.py
new file mode 100644
index 0000000..1254919
--- /dev/null
+++ b/starboard/raspi/2/test_filters.py
@@ -0,0 +1,28 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Raspberry Pi 2 Platform Test Filters."""
+
+from starboard.raspi.shared import test_filters as shared_test_filters
+
+
+def CreateTestFilters():
+ return Raspi2TestFilters()
+
+
+class Raspi2TestFilters(shared_test_filters.TestFilters):
+ """Starboard Raspberry Pi 2 Platform Test Filters."""
+
+ def GetTestFilters(self):
+ filters = super(Raspi2TestFilters, self).GetTestFilters()
+ return filters
diff --git a/starboard/raspi/2/toolchain/BUILD.gn b/starboard/raspi/2/toolchain/BUILD.gn
index b83ad10..9056b43 100644
--- a/starboard/raspi/2/toolchain/BUILD.gn
+++ b/starboard/raspi/2/toolchain/BUILD.gn
@@ -16,7 +16,7 @@
import("//starboard/raspi/shared/toolchain/raspi_shared_toolchain.gni")
clang_toolchain("host") {
- clang_base_path = raspi_clang_base_path
+ clang_base_path = clang_base_path
toolchain_args = {
current_os = "linux"
@@ -25,12 +25,14 @@
}
gcc_toolchain("target") {
- cc = "$gcc_toolchain_cc"
- cxx = "$gcc_toolchain_cxx"
+ cc = gcc_toolchain_cc
+ cxx = gcc_toolchain_cxx
ld = cxx
- # We use whatever 'ar' resolves to in gyp.
- ar = "$gcc_toolchain_ar"
+ # We use whatever 'ar' resolves to.
+ ar = gcc_toolchain_ar
+
+ tail_lib_dependencies = "-l:libpthread.so.0"
toolchain_args = {
is_clang = false
diff --git a/starboard/raspi/shared/BUILD.gn b/starboard/raspi/shared/BUILD.gn
index d224eda..70d60ff 100644
--- a/starboard/raspi/shared/BUILD.gn
+++ b/starboard/raspi/shared/BUILD.gn
@@ -254,6 +254,8 @@
"//starboard/shared/pthread/thread_yield.cc",
"//starboard/shared/signal/crash_signals.cc",
"//starboard/shared/signal/crash_signals.h",
+ "//starboard/shared/signal/debug_signals.cc",
+ "//starboard/shared/signal/debug_signals.h",
"//starboard/shared/signal/suspend_signals.cc",
"//starboard/shared/signal/suspend_signals.h",
"//starboard/shared/signal/system_request_conceal.cc",
diff --git a/starboard/raspi/shared/install_target.gni b/starboard/raspi/shared/install_target.gni
new file mode 100644
index 0000000..4c42ce1
--- /dev/null
+++ b/starboard/raspi/shared/install_target.gni
@@ -0,0 +1,52 @@
+# Copyright 2021 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import("//starboard/raspi/shared/toolchain/raspi_shared_toolchain.gni")
+template("install_target") {
+ installable_target_name = invoker.installable_target_name
+
+ if (invoker.type == "executable") {
+ install_subdir = "bin"
+ source_name = installable_target_name
+ } else if (invoker.type == "shared_library") {
+ install_subdir = "lib"
+ source_name = "lib${installable_target_name}.so"
+ } else {
+ assert(false, "You can only install an executable or shared library.")
+ }
+
+ action(target_name) {
+ forward_variables_from(invoker, [ "testonly" ])
+
+ script = "//starboard/build/run_bash.py"
+
+ strip_executable = gcc_toolchain_strip
+ inputs = [
+ strip_executable,
+ "$root_out_dir/$source_name",
+ ]
+
+ deps = invoker.deps
+ deps += [ ":$installable_target_name" ]
+
+ outputs = [ "$sb_install_output_dir/$install_subdir/$source_name" ]
+
+ args = [
+ rebase_path(strip_executable, root_build_dir),
+ "-o",
+ rebase_path(outputs[0], root_out_dir),
+ rebase_path("$root_out_dir/$source_name", root_out_dir),
+ ]
+ }
+}
diff --git a/starboard/raspi/shared/launcher.py b/starboard/raspi/shared/launcher.py
index 4617abc..bee7f90 100644
--- a/starboard/raspi/shared/launcher.py
+++ b/starboard/raspi/shared/launcher.py
@@ -107,7 +107,10 @@
def _InitPexpectCommands(self):
"""Initializes all of the pexpect commands needed for running the test."""
- test_dir = os.path.join(self.out_directory, 'deploy', self.target_name)
+ test_dir = os.path.join(self.out_directory, 'install', 'bin')
+ # TODO(b/216356058): Delete this conditional that's just for GYP.
+ if not os.path.isdir(test_dir):
+ test_dir = os.path.join(self.out_directory, 'deploy', self.target_name)
test_file = self.target_name
test_path = os.path.join(test_dir, test_file)
diff --git a/starboard/raspi/shared/main.cc b/starboard/raspi/shared/main.cc
index 41d52da..dfd20d5 100644
--- a/starboard/raspi/shared/main.cc
+++ b/starboard/raspi/shared/main.cc
@@ -12,19 +12,24 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#include <malloc.h>
#include <time.h>
#include "starboard/configuration.h"
#include "starboard/raspi/shared/application_dispmanx.h"
#include "starboard/shared/signal/crash_signals.h"
+#include "starboard/shared/signal/debug_signals.h"
#include "starboard/shared/signal/suspend_signals.h"
#include "third_party/crashpad/wrapper/wrapper.h"
int main(int argc, char** argv) {
+ // Set M_ARENA_MAX to a low value to slow memory growth due to fragmentation.
+ SB_CHECK(mallopt(M_ARENA_MAX, 2));
tzset();
starboard::shared::signal::InstallCrashSignalHandlers();
+ starboard::shared::signal::InstallDebugSignalHandlers();
starboard::shared::signal::InstallSuspendSignalHandlers();
#if SB_IS(EVERGREEN_COMPATIBLE)
@@ -33,6 +38,7 @@
starboard::raspi::shared::ApplicationDispmanx application;
int result = application.Run(argc, argv);
starboard::shared::signal::UninstallSuspendSignalHandlers();
+ starboard::shared::signal::UninstallDebugSignalHandlers();
starboard::shared::signal::UninstallCrashSignalHandlers();
return result;
}
diff --git a/starboard/raspi/shared/platform_configuration/BUILD.gn b/starboard/raspi/shared/platform_configuration/BUILD.gn
index 988df3e..f3d2403 100644
--- a/starboard/raspi/shared/platform_configuration/BUILD.gn
+++ b/starboard/raspi/shared/platform_configuration/BUILD.gn
@@ -130,7 +130,6 @@
"avcodec",
"avformat",
"avutil",
- ":libpthread.so.0",
"pthread",
"rt",
"openmaxil",
diff --git a/starboard/raspi/shared/platform_configuration/configuration.gni b/starboard/raspi/shared/platform_configuration/configuration.gni
index dfa14b8..b55c36f 100644
--- a/starboard/raspi/shared/platform_configuration/configuration.gni
+++ b/starboard/raspi/shared/platform_configuration/configuration.gni
@@ -16,7 +16,7 @@
arm_float_abi = "hard"
has_drm_support = false
-sb_pedantic_warnings = true
+
sb_static_contents_output_data_dir = "$root_out_dir/content"
no_pedantic_warnings_config_path =
@@ -26,3 +26,5 @@
sabi_path = "//starboard/sabi/arm/hardfp/sabi-v$sb_api_version.json"
platform_tests_path = "//starboard/raspi/shared:starboard_platform_tests"
+
+install_target_path = "//starboard/raspi/shared/install_target.gni"
diff --git a/starboard/raspi/shared/starboard_platform.gypi b/starboard/raspi/shared/starboard_platform.gypi
index 46c8a8f..eae5092 100644
--- a/starboard/raspi/shared/starboard_platform.gypi
+++ b/starboard/raspi/shared/starboard_platform.gypi
@@ -274,6 +274,8 @@
'<(DEPTH)/starboard/shared/pthread/thread_yield.cc',
'<(DEPTH)/starboard/shared/signal/crash_signals.cc',
'<(DEPTH)/starboard/shared/signal/crash_signals.h',
+ '<(DEPTH)/starboard/shared/signal/debug_signals.cc',
+ '<(DEPTH)/starboard/shared/signal/debug_signals.h',
'<(DEPTH)/starboard/shared/signal/suspend_signals.cc',
'<(DEPTH)/starboard/shared/signal/suspend_signals.h',
'<(DEPTH)/starboard/shared/signal/system_request_conceal.cc',
diff --git a/starboard/raspi/shared/test_filters.py b/starboard/raspi/shared/test_filters.py
new file mode 100644
index 0000000..aeaed61
--- /dev/null
+++ b/starboard/raspi/shared/test_filters.py
@@ -0,0 +1,57 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Raspberry Pi Platform Test Filters."""
+
+from starboard.tools.testing import test_filter
+
+
+class TestFilters(object):
+ """Starboard Raspberry Pi platform test filters."""
+
+ def GetTestFilters(self):
+ filters = []
+ for target, tests in self._FILTERED_TESTS.iteritems():
+ filters.extend(test_filter.TestFilter(target, test) for test in tests)
+ return filters
+
+ _FILTERED_TESTS = {
+ 'nplb': [
+ 'SbAudioSinkTest.*',
+
+ # Permanently filter out drm system related tests as raspi doesn't
+ # support any drm systems and there is no plan to implement such
+ # support.
+ 'SbDrmTest.AnySupportedKeySystems',
+ 'SbMediaCanPlayMimeAndKeySystem.AnySupportedKeySystems',
+ 'SbMediaCanPlayMimeAndKeySystem.KeySystemWithAttributes',
+ 'SbMediaCanPlayMimeAndKeySystem.MinimumSupport',
+ 'SbMediaSetAudioWriteDurationTests/*',
+ 'SbPlayerWriteSampleTests*',
+ 'SbUndefinedBehaviorTest.CallThisPointerIsNullRainyDay',
+ 'SbSystemGetPropertyTest.FLAKY_ReturnsRequired',
+ ],
+ 'player_filter_tests': [
+ # The implementations for the raspberry pi (0 and 2) are incomplete
+ # and not meant to be a reference implementation. As such we will
+ # not repair these failing tests for now.
+ 'VideoDecoderTests/VideoDecoderTest.EndOfStreamWithoutAnyInput/0',
+ 'VideoDecoderTests/VideoDecoderTest.MultipleResets/0',
+ # Filter failed tests.
+ 'PlayerComponentsTests/PlayerComponentsTest.*',
+ ],
+ }
+
+
+def CreateTestFilters():
+ return TestFilters()
diff --git a/starboard/raspi/shared/toolchain/raspi_shared_toolchain.gni b/starboard/raspi/shared/toolchain/raspi_shared_toolchain.gni
index 1b0b0e4..bf78fec 100644
--- a/starboard/raspi/shared/toolchain/raspi_shared_toolchain.gni
+++ b/starboard/raspi/shared/toolchain/raspi_shared_toolchain.gni
@@ -12,14 +12,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-_home_dir = getenv("HOME")
_raspi_home_dir = getenv("RASPI_HOME")
assert(_raspi_home_dir != "",
"RasPi builds require the 'RASPI_HOME' environment variable to be set.")
-raspi_clang_base_path = "$_home_dir/starboard-toolchains/x86_64-linux-gnu-clang-chromium-365097-f7e52fbd-8"
_raspi_toolchain_path = "$_raspi_home_dir/tools/arm-bcm2708/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin"
gcc_toolchain_ar = "ar"
gcc_toolchain_cc = "$_raspi_toolchain_path/arm-linux-gnueabihf-gcc"
gcc_toolchain_cxx = "$_raspi_toolchain_path/arm-linux-gnueabihf-g++"
+gcc_toolchain_strip = "$_raspi_toolchain_path/arm-linux-gnueabihf-strip"
diff --git a/starboard/shared/ffmpeg/BUILD.gn b/starboard/shared/ffmpeg/BUILD.gn
index d298a03..e6bc469 100644
--- a/starboard/shared/ffmpeg/BUILD.gn
+++ b/starboard/shared/ffmpeg/BUILD.gn
@@ -35,14 +35,19 @@
]
}
-static_library("ffmpeg_linked") {
- check_includes = false
- sources = ffmpeg_specialization_sources + [
- "ffmpeg_linked_audio_decoder_impl.cc",
- "ffmpeg_linked_dispatch_impl.cc",
- "ffmpeg_linked_video_decoder_impl.cc",
- ]
- public_deps = [ ":ffmpeg_dispatch_sources" ]
+# TODO(b/208910380): ffmpeg_linked doesn't compile for linux locally.
+_target_platform_name_parts = string_split(target_platform, "-")
+_building_linux_platform = _target_platform_name_parts[0] == "linux"
+if (!_building_linux_platform) {
+ static_library("ffmpeg_linked") {
+ check_includes = false
+ sources = ffmpeg_specialization_sources + [
+ "ffmpeg_linked_audio_decoder_impl.cc",
+ "ffmpeg_linked_dispatch_impl.cc",
+ "ffmpeg_linked_video_decoder_impl.cc",
+ ]
+ public_deps = [ ":ffmpeg_dispatch_sources" ]
+ }
}
# We recompile the specialization sources for each different library version.
diff --git a/starboard/shared/libjpeg/jpeg_image_decoder.cc b/starboard/shared/libjpeg/jpeg_image_decoder.cc
index c990179..5a24097 100644
--- a/starboard/shared/libjpeg/jpeg_image_decoder.cc
+++ b/starboard/shared/libjpeg/jpeg_image_decoder.cc
@@ -161,9 +161,11 @@
FillRow<2, 1, 0, 3>(static_cast<int>(info->image_width), pixel_data,
sample_buffer);
break;
+ // All the other decode formats are not supported.
case kSbDecodeTargetFormat2PlaneYUVNV12:
case kSbDecodeTargetFormat3PlaneYUVI420:
case kSbDecodeTargetFormat3Plane10BitYUVI420:
+ case kSbDecodeTargetFormat3Plane10BitYUVI420Compact:
case kSbDecodeTargetFormat1PlaneUYVY:
case kSbDecodeTargetFormatInvalid:
SB_NOTREACHED();
diff --git a/starboard/shared/posix/free_space.cc b/starboard/shared/posix/free_space.cc
new file mode 100644
index 0000000..f66de59
--- /dev/null
+++ b/starboard/shared/posix/free_space.cc
@@ -0,0 +1,55 @@
+// Copyright 2022 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/shared/posix/free_space.h"
+
+#include <sys/statvfs.h>
+#include <vector>
+
+#include "cobalt/extension/free_space.h"
+#include "starboard/common/log.h"
+#include "starboard/configuration_constants.h"
+
+namespace starboard {
+namespace shared {
+namespace posix {
+
+namespace {
+
+int64_t MeasureFreeSpace(SbSystemPathId system_path_id) {
+ std::vector<char> path(kSbFileMaxPath + 1);
+ if (!SbSystemGetPath(system_path_id, path.data(), path.size())) {
+ return -1;
+ }
+ struct statvfs s;
+ if (statvfs(path.data(), &s) != 0) {
+ return -1;
+ }
+
+ return s.f_bsize * s.f_bfree;
+}
+
+const CobaltExtensionFreeSpaceApi kFreeSpaceApi = {
+ kCobaltExtensionFreeSpaceName, 1, &MeasureFreeSpace,
+};
+
+} // namespace
+
+const void* GetFreeSpaceApi() {
+ return &kFreeSpaceApi;
+}
+
+} // namespace posix
+} // namespace shared
+} // namespace starboard
diff --git a/starboard/shared/posix/free_space.h b/starboard/shared/posix/free_space.h
new file mode 100644
index 0000000..1d44447
--- /dev/null
+++ b/starboard/shared/posix/free_space.h
@@ -0,0 +1,28 @@
+// Copyright 2022 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_SHARED_POSIX_FREE_SPACE_H_
+#define STARBOARD_SHARED_POSIX_FREE_SPACE_H_
+
+namespace starboard {
+namespace shared {
+namespace posix {
+
+const void* GetFreeSpaceApi();
+
+} // namespace posix
+} // namespace shared
+} // namespace starboard
+
+#endif // STARBOARD_SHARED_POSIX_FREE_SPACE_H_
diff --git a/starboard/shared/signal/debug_signals.cc b/starboard/shared/signal/debug_signals.cc
new file mode 100644
index 0000000..15ea2f4
--- /dev/null
+++ b/starboard/shared/signal/debug_signals.cc
@@ -0,0 +1,49 @@
+// Copyright 2022 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/shared/signal/debug_signals.h"
+
+#include <malloc.h>
+#include <signal.h>
+
+#include "starboard/common/log.h"
+#include "starboard/shared/signal/signal_internal.h"
+
+namespace starboard {
+namespace shared {
+namespace signal {
+
+namespace {
+
+const int kDebugSignalsToTrap[] = {
+ SIGRTMIN,
+};
+
+void MallocInfo(int signal_id) {
+ LogSignalCaught(signal_id);
+ malloc_info(0, stderr);
+}
+} // namespace
+
+void InstallDebugSignalHandlers() {
+ ::signal(SIGRTMIN, &MallocInfo);
+}
+
+void UninstallDebugSignalHandlers() {
+ ::signal(SIGRTMIN, SIG_DFL);
+}
+
+} // namespace signal
+} // namespace shared
+} // namespace starboard
diff --git a/starboard/shared/signal/debug_signals.h b/starboard/shared/signal/debug_signals.h
new file mode 100644
index 0000000..86f893b
--- /dev/null
+++ b/starboard/shared/signal/debug_signals.h
@@ -0,0 +1,31 @@
+// Copyright 2022 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_SHARED_SIGNAL_DEBUG_SIGNALS_H_
+#define STARBOARD_SHARED_SIGNAL_DEBUG_SIGNALS_H_
+
+#include "starboard/shared/internal_only.h"
+
+namespace starboard {
+namespace shared {
+namespace signal {
+
+void InstallDebugSignalHandlers();
+void UninstallDebugSignalHandlers();
+
+} // namespace signal
+} // namespace shared
+} // namespace starboard
+
+#endif // STARBOARD_SHARED_SIGNAL_DEBUG_SIGNALS_H_
diff --git a/starboard/shared/signal/suspend_signals.cc b/starboard/shared/signal/suspend_signals.cc
index bc05466..4b6feb0 100644
--- a/starboard/shared/signal/suspend_signals.cc
+++ b/starboard/shared/signal/suspend_signals.cc
@@ -26,7 +26,7 @@
#include "starboard/system.h"
#if SB_IS(EVERGREEN_COMPATIBLE) && !SB_IS(EVERGREEN_COMPATIBLE_LITE)
-#include "starboard/loader_app/pending_restart.h"
+#include "starboard/loader_app/pending_restart.h" // nogncheck
#endif // SB_IS(EVERGREEN_COMPATIBLE) && !SB_IS(EVERGREEN_COMPATIBLE_LITE)
namespace starboard {
diff --git a/starboard/shared/starboard/log_raw_dump_stack.cc b/starboard/shared/starboard/log_raw_dump_stack.cc
index 8972ce1..ded8383 100644
--- a/starboard/shared/starboard/log_raw_dump_stack.cc
+++ b/starboard/shared/starboard/log_raw_dump_stack.cc
@@ -15,7 +15,7 @@
#include "starboard/common/log.h"
#if SB_IS(EVERGREEN_COMPATIBLE)
-#include "starboard/elf_loader/evergreen_info.h"
+#include "starboard/elf_loader/evergreen_info.h" // nogncheck
#include "starboard/memory.h"
#endif
#include "starboard/system.h"
diff --git a/starboard/shared/starboard/player/BUILD.gn b/starboard/shared/starboard/player/BUILD.gn
index 161834f..997a39a 100644
--- a/starboard/shared/starboard/player/BUILD.gn
+++ b/starboard/shared/starboard/player/BUILD.gn
@@ -30,6 +30,8 @@
}
action("player_download_test_data") {
+ install_content = true
+
script = "//tools/download_from_gcs.py"
sha_sources = []
@@ -42,7 +44,7 @@
}
sha_outputs = []
- subdir = "starboard/shared/starboard/player/testdata"
+ subdir = "starboard/shared/starboard/player"
outdir = "$sb_static_contents_output_data_dir/test/$subdir"
foreach(sha_source, sha_sources) {
sha_outputs +=
@@ -64,6 +66,6 @@
"--sha1",
sha1_dir,
"--output",
- rebase_path(outdir, root_build_dir),
+ rebase_path("$outdir/testdata", root_build_dir),
]
}
diff --git a/starboard/shared/starboard/player/filter/testing/BUILD.gn b/starboard/shared/starboard/player/filter/testing/BUILD.gn
index 755b4e4..dc5cdcc 100644
--- a/starboard/shared/starboard/player/filter/testing/BUILD.gn
+++ b/starboard/shared/starboard/player/filter/testing/BUILD.gn
@@ -40,9 +40,14 @@
]
deps = cobalt_platform_dependencies
+
+ content_deps = [
+ "//starboard/shared/starboard/player:player_download_test_data",
+ "//third_party/icu:icudata",
+ ]
}
-if (!is_win) {
+if (host_os != "win") {
target(final_executable_type, "player_filter_benchmarks") {
testonly = true
diff --git a/starboard/shared/starboard/player/filter/tools/BUILD.gn b/starboard/shared/starboard/player/filter/tools/BUILD.gn
index af555d2..698d480 100644
--- a/starboard/shared/starboard/player/filter/tools/BUILD.gn
+++ b/starboard/shared/starboard/player/filter/tools/BUILD.gn
@@ -22,10 +22,13 @@
"//starboard/shared/starboard/player/video_dmp_reader.h",
"audio_dmp_player.cc",
]
-
configs += [ "//starboard/build/config:starboard_implementation" ]
public_deps = [
"//starboard",
"//starboard/shared/starboard/player:player_download_test_data",
]
+ content_deps = [
+ "//starboard/shared/starboard/player:player_download_test_data",
+ "//third_party/icu:icudata",
+ ]
}
diff --git a/starboard/shared/starboard/player/testdata/.gitignore b/starboard/shared/starboard/player/testdata/.gitignore
index f1136ab..14e98ce 100644
--- a/starboard/shared/starboard/player/testdata/.gitignore
+++ b/starboard/shared/starboard/player/testdata/.gitignore
@@ -1,2 +1 @@
*.dmp
-
diff --git a/starboard/shared/toolchain/overridable_gcc_toolchain.gni b/starboard/shared/toolchain/overridable_gcc_toolchain.gni
new file mode 100644
index 0000000..125f405
--- /dev/null
+++ b/starboard/shared/toolchain/overridable_gcc_toolchain.gni
@@ -0,0 +1,144 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import("//build/toolchain/gcc_toolchain.gni")
+
+# The environment variables that override arguments of
+# overridable_gcc_toolchain and overridable_clang_toolchain
+# templates in this file.
+declare_args() {
+ # The full path to the compiler for AR.
+ ar_compiler_override = getenv("GN_AR_COMPILER")
+
+ # The full path to the compiler for CC.
+ cc_compiler_override = getenv("GN_CC_COMPILER")
+
+ # The full path to the compiler for CXX.
+ cxx_compiler_override = getenv("GN_CXX_COMPILER")
+
+ # The full path to the compiler for LD.
+ ld_compiler_override = getenv("GN_LD_COMPILER")
+
+ # Optional parameters that control the tools:
+
+ # Extra flags to be appended when compiling C files (but not C++ files).
+ c_flags_override = getenv("GN_C_FLAGS")
+
+ # Extra flags to be appended when compiling both C and C++ files. "CPP"
+ # stands for "C PreProcessor" in this context, although it can be
+ # used for non-preprocessor flags as well. Not to be confused with
+ # "CXX" (which follows).
+ cpp_flags_override = getenv("GN_CPP_FLAGS")
+
+ # Extra flags to be appended when compiling C++ files (but not C files).
+ cxx_flags_override = getenv("GN_CXX_FLAGS")
+
+ # Extra flags to be appended when compiling assembly.
+ asm_flags_override = getenv("GN_ASM_FLAGS")
+
+ # Extra flags to be appended when linking.
+ ld_flags_override = getenv("GN_LD_FLAGS")
+}
+
+# This template applies environment variables overrides to gcc_toolchain in
+# build/toolchain/gcc_toolchain.gni.
+# The environment variables/gn arguments overrides specified in this file
+# will take precedence over the corresponding arguments passed in from the
+# invoked gcc_toolchain.
+template("overridable_gcc_toolchain") {
+ gcc_toolchain(target_name) {
+ forward_variables_from(invoker, "*")
+
+ if (ar_compiler_override != "") {
+ ar = ar_compiler_override
+ }
+
+ if (cc_compiler_override != "") {
+ cc = cc_compiler_override
+ }
+
+ if (cxx_compiler_override != "") {
+ cxx = cxx_compiler_override
+ }
+
+ if (ld_compiler_override != "") {
+ ld = ld_compiler_override
+ }
+
+ if (c_flags_override != "") {
+ extra_cflags = c_flags_override
+ }
+
+ if (cpp_flags_override != "") {
+ extra_cppflags = cpp_flags_override
+ }
+
+ if (cxx_flags_override != "") {
+ extra_cxxflags = cxx_flags_override
+ }
+
+ if (asm_flags_override != "") {
+ extra_asmflags = asm_flags_override
+ }
+
+ if (ld_flags_override != "") {
+ extra_ldflags = ld_flags_override
+ }
+ }
+}
+
+# This template applies environment variables overrides to clang_toolchain
+# in build/toolchain/gcc_toolchain.gni.
+# This is a shorthand for gcc_toolchain_set_env_vars instances based on the
+# Chromium-built version of Clang. Only the toolchain_cpu and toolchain_os
+# variables need to be specified by the invoker, and optionally toolprefix if
+# it's a cross-compile case. Note that for a cross-compile case this toolchain
+# requires a config to pass the appropriate -target option, or else it will
+# actually just be doing a native compile. The invoker can optionally override
+# use_gold too.
+template("overridable_clang_toolchain") {
+ if (defined(invoker.toolprefix)) {
+ toolprefix = invoker.toolprefix
+ } else {
+ toolprefix = ""
+ }
+ if (is_starboard) {
+ clang_base_path = invoker.clang_base_path
+ }
+
+ overridable_gcc_toolchain(target_name) {
+ prefix = rebase_path("$clang_base_path/bin", root_build_dir)
+ cc = "$prefix/clang"
+ cxx = "$prefix/clang++"
+ ld = cxx
+ readelf = "${toolprefix}readelf"
+ ar = "${prefix}/llvm-ar"
+ nm = "nm"
+
+ forward_variables_from(invoker,
+ [
+ "strip",
+ "default_shlib_subdir",
+ "enable_linker_map",
+ "use_unstripped_as_runtime_outputs",
+ ])
+
+ toolchain_args = {
+ if (defined(invoker.toolchain_args)) {
+ forward_variables_from(invoker.toolchain_args, "*")
+ }
+ is_clang = true
+ }
+ }
+}
diff --git a/starboard/shared/widevine/BUILD.gn b/starboard/shared/widevine/BUILD.gn
index 8ab037d..e10f1a3 100644
--- a/starboard/shared/widevine/BUILD.gn
+++ b/starboard/shared/widevine/BUILD.gn
@@ -31,8 +31,8 @@
sources = [
"//starboard/keyboxes/${sb_widevine_platform}/${sb_widevine_platform}.h",
"//starboard/keyboxes/${sb_widevine_platform}/${sb_widevine_platform}_client.c",
+ "//starboard/shared/widevine/internal/wv_keybox.cc",
"//starboard/shared/widevine/widevine_keybox_hash.cc",
- "//starboard/shared/widevine/wv_keybox.cc",
]
public_configs = [ ":oemcrypto_external" ]
diff --git a/starboard/shared/widevine/widevine3.gyp b/starboard/shared/widevine/widevine3.gyp
index fe8b7cf..8285e85 100644
--- a/starboard/shared/widevine/widevine3.gyp
+++ b/starboard/shared/widevine/widevine3.gyp
@@ -34,8 +34,8 @@
'platform_oem_sources': [
'<(DEPTH)/starboard/keyboxes/<(sb_widevine_platform)/<(sb_widevine_platform).h',
'<(DEPTH)/starboard/keyboxes/<(sb_widevine_platform)/<(sb_widevine_platform)_client.c',
+ '<(DEPTH)/starboard/shared/widevine/internal/wv_keybox.cc',
'<(DEPTH)/starboard/shared/widevine/widevine_keybox_hash.cc',
- '<(DEPTH)/starboard/shared/widevine/wv_keybox.cc',
],
},
'target_defaults': {
diff --git a/starboard/stub/args.gn b/starboard/stub/args.gn
new file mode 100644
index 0000000..7b09b13
--- /dev/null
+++ b/starboard/stub/args.gn
@@ -0,0 +1,17 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+target_platform = "stub"
+target_os = "linux"
+target_cpu = "x64"
diff --git a/starboard/stub/test_filters.py b/starboard/stub/test_filters.py
new file mode 100644
index 0000000..d289036
--- /dev/null
+++ b/starboard/stub/test_filters.py
@@ -0,0 +1,30 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Stub Platform Test Filters."""
+
+
+def CreateTestFilters():
+ return TestFilters()
+
+
+class TestFilters():
+ """Starboard Stub platform test filters."""
+
+ def GetTestFilters(self):
+ """Gets all tests to be excluded from a unit test run.
+
+ Returns:
+ A list of initialized TestFilter objects.
+ """
+ return []
diff --git a/starboard/stub/toolchain/BUILD.gn b/starboard/stub/toolchain/BUILD.gn
index 1ab2e67..c1469cb 100644
--- a/starboard/stub/toolchain/BUILD.gn
+++ b/starboard/stub/toolchain/BUILD.gn
@@ -12,13 +12,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import("//build/config/clang/clang.gni")
import("//build/toolchain/gcc_toolchain.gni")
-_home_dir = getenv("HOME")
-_clang_base_path = "$_home_dir/starboard-toolchains/x86_64-linux-gnu-clang-chromium-365097-f7e52fbd-8"
-
clang_toolchain("host") {
- clang_base_path = _clang_base_path
+ clang_base_path = clang_base_path
toolchain_args = {
current_os = "linux"
@@ -27,5 +25,5 @@
}
clang_toolchain("target") {
- clang_base_path = _clang_base_path
+ clang_base_path = clang_base_path
}
diff --git a/starboard/tools/BUILD.gn b/starboard/tools/BUILD.gn
new file mode 100644
index 0000000..b4f1ffd
--- /dev/null
+++ b/starboard/tools/BUILD.gn
@@ -0,0 +1,37 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+action("build_app_launcher_zip") {
+ script = "//starboard/build/run_bash.py"
+ py_script = "//starboard/tools/app_launcher_packager.py"
+
+ file_list = exec_script("//starboard/build/run_bash.py",
+ [
+ "python2",
+ rebase_path(py_script, root_build_dir),
+ "-l",
+ ],
+ "trim string",
+ [ py_script ])
+ inputs = string_split(file_list)
+ inputs += [ py_script ]
+ outputs = [ "$root_out_dir/app_launcher.zip" ]
+
+ args = [
+ "python2",
+ rebase_path(py_script, root_build_dir),
+ "-z",
+ rebase_path(outputs[0], root_build_dir),
+ ]
+}
diff --git a/starboard/tools/app_launcher_packager.py b/starboard/tools/app_launcher_packager.py
index 0420583..c228a95 100644
--- a/starboard/tools/app_launcher_packager.py
+++ b/starboard/tools/app_launcher_packager.py
@@ -259,6 +259,10 @@
help='List to stdout the application resources relative to the current '
'directory.')
parser.add_argument(
+ '--return_list',
+ action='store_true',
+ help='Return the application resources instead of printing them.')
+ parser.add_argument(
'-v',
'--verbose',
action='store_true',
@@ -289,7 +293,10 @@
src_file = src_file.replace('\\', '/')
src_files.append(src_file)
out = ' '.join(src_files)
- return out.strip()
+ if args.return_list:
+ return out.strip()
+ else:
+ print(out.strip())
return 0
diff --git a/starboard/tools/build.py b/starboard/tools/build.py
index 08923fe..64eca42 100644
--- a/starboard/tools/build.py
+++ b/starboard/tools/build.py
@@ -15,7 +15,7 @@
#
"""Build related constants and helper functions."""
-import imp
+import imp # pylint: disable=deprecated-module
import importlib
import logging
import os
@@ -198,6 +198,51 @@
return extensionless_loaded_path == extensionless_module_path
+def _LoadPlatformModule(platform_name, file_name, function_name):
+ """Loads a platform module.
+
+ The function will use the provided platform name to load
+ a python file with a matching name that contains the module.
+
+ Args:
+ platform_name: Platform name.
+ file_name: The file that contains the module. It can be
+ gyp_configuration.py or test_filters.py
+ function_name: The function name of the module.
+ For gyp_configuration.py, it is CreatePlatformConfig.
+ For test_filters.py, it is GetTestFilters
+
+ Returns: Instance of a class derived from module path.
+ """
+ try:
+ logging.debug('Loading platform %s for "%s".', file_name, platform_name)
+ if platform.IsValid(platform_name):
+ platform_path = os.path.join(paths.REPOSITORY_ROOT,
+ platform.Get(platform_name).path)
+ module_path = os.path.join(platform_path, file_name)
+ if not _ModuleLoaded('platform_module', module_path):
+ platform_module = imp.load_source('platform_module', module_path)
+ else:
+ platform_module = sys.modules['platform_module']
+ else:
+ module_path = os.path.join('config', '%s.py' % platform_name)
+ platform_module = importlib.import_module('config.%s' % platform_name)
+ except (ImportError, IOError):
+ logging.exception('Unable to import "%s".', module_path)
+ return None
+
+ if not hasattr(platform_module, function_name):
+ logging.error('"%s" does not contain %s.', module_path, function_name)
+ return None
+
+ try:
+ platform_attr = getattr(platform_module, function_name)()
+ return platform_attr, platform_path
+ except RuntimeError:
+ logging.exception('Exception in %s.', function_name)
+ return None
+
+
def _LoadPlatformConfig(platform_name):
"""Loads a platform specific configuration.
@@ -211,39 +256,35 @@
Returns:
Instance of a class derived from PlatformConfigBase.
"""
- try:
- logging.debug('Loading platform configuration for "%s".', platform_name)
- if platform.IsValid(platform_name):
- platform_path = os.path.join(paths.REPOSITORY_ROOT,
- platform.Get(platform_name).path)
- module_path = os.path.join(platform_path, 'gyp_configuration.py')
- if not _ModuleLoaded('platform_module', module_path):
- platform_module = imp.load_source('platform_module', module_path)
- else:
- platform_module = sys.modules['platform_module']
- else:
- module_path = os.path.join('config', '%s.py' % platform_name)
- platform_module = importlib.import_module('config.%s' % platform_name)
- except (ImportError, IOError):
- logging.exception('Unable to import "%s".', module_path)
- return None
+ platform_configuration, platform_path = _LoadPlatformModule(
+ platform_name, 'gyp_configuration.py', 'CreatePlatformConfig')
+ platform_configuration.SetDirectory(platform_path)
+ return platform_configuration
- if not hasattr(platform_module, 'CreatePlatformConfig'):
- logging.error('"%s" does not contain CreatePlatformConfig.', module_path)
- return None
- try:
- platform_configuration = platform_module.CreatePlatformConfig()
- platform_configuration.SetDirectory(platform_path)
- return platform_configuration
- except RuntimeError:
- logging.exception('Exception in CreatePlatformConfig.')
- return None
+def _LoadPlatformTestFilters(platform_name):
+ """Loads the platform specific test filters.
+
+ The function will use the provided platform name to load
+ a python file with a matching name that contains the platform
+ specific test filters.
+
+ Args:
+ platform_name: Platform name.
+
+ Returns:
+ Instance of a class derived from TestFilters.
+ """
+ platform_test_filters, _ = _LoadPlatformModule(platform_name,
+ 'test_filters.py',
+ 'CreateTestFilters')
+ return platform_test_filters
# Global cache of the platform configurations, so that platform config objects
# are only created once.
_PLATFORM_CONFIG_DICT = {}
+_PLATFORM_TEST_FILTERS_DICT = {}
def GetPlatformConfig(platform_name):
@@ -263,3 +304,20 @@
_PLATFORM_CONFIG_DICT[platform_name] = _LoadPlatformConfig(platform_name)
return _PLATFORM_CONFIG_DICT[platform_name]
+
+
+def GetPlatformTestFilters(platform_name):
+ """Returns a platform specific test filters.
+
+ Args:
+ platform_name: Platform name.
+
+ Returns:
+ Instance of a class derived from TestFilters.
+ """
+
+ if platform_name not in _PLATFORM_TEST_FILTERS_DICT:
+ _PLATFORM_TEST_FILTERS_DICT[platform_name] = _LoadPlatformTestFilters(
+ platform_name)
+
+ return _PLATFORM_CONFIG_DICT[platform_name]
diff --git a/starboard/tools/download_clang.py b/starboard/tools/download_clang.py
index 020aae0..274444b 100755
--- a/starboard/tools/download_clang.py
+++ b/starboard/tools/download_clang.py
@@ -75,6 +75,7 @@
if not os.path.exists(output_dir):
os.makedirs(output_dir)
+ # pylint: disable=consider-using-with
t = tarfile.open(mode='r:gz', fileobj=tmpfile)
t.extractall(path=output_dir)
@@ -92,6 +93,9 @@
except IOError:
pass
+ if os.getenv('IS_CI', '') == '1':
+ raise Exception('Dynamic toolchain downloads are disabled in CI')
+
if os.path.exists(target_dir):
shutil.rmtree(target_dir)
diff --git a/starboard/tools/testing/test_runner.py b/starboard/tools/testing/test_runner.py
index aa98b10..a51f5ca 100755
--- a/starboard/tools/testing/test_runner.py
+++ b/starboard/tools/testing/test_runner.py
@@ -242,8 +242,11 @@
logging.info("Getting platform configuration")
self._platform_config = build.GetPlatformConfig(platform)
+ self._platform_test_filters = build.GetPlatformTestFilters(platform)
if self.loader_platform:
self._loader_platform_config = build.GetPlatformConfig(loader_platform)
+ self._loader_platform_test_filters = build.GetPlatformTestFilters(
+ loader_platform)
logging.info("Got platform configuration")
logging.info("Getting application configuration")
self._app_config = self._platform_config.GetApplicationConfiguration(
@@ -347,7 +350,7 @@
def _GetTestFilters(self):
"""Get test filters for a given platform and configuration."""
- filters = self._platform_config.GetTestFilters()
+ filters = self._platform_test_filters.GetTestFilters()
app_filters = self._app_config.GetTestFilters()
if app_filters:
filters.extend(app_filters)
@@ -357,9 +360,11 @@
# filtered tests when it is running on a Raspberry Pi 2.
if self.loader_platform and self.loader_config:
loader_platform_config = build.GetPlatformConfig(self.loader_platform)
+ loader_platform_test_filters = build.GetPlatformTestFilters(
+ self.loader_platform)
loader_app_config = loader_platform_config.GetApplicationConfiguration(
self.application_name)
- for filter_ in (loader_platform_config.GetTestFilters() +
+ for filter_ in (loader_platform_test_filters.GetTestFilters() +
loader_app_config.GetTestFilters()):
if filter_ not in filters:
filters.append(filter_)
diff --git a/starboard/tools/tools.gyp b/starboard/tools/tools.gyp
index ce18897..f6fe913 100644
--- a/starboard/tools/tools.gyp
+++ b/starboard/tools/tools.gyp
@@ -29,7 +29,7 @@
'action_name': 'package_app_launcher',
'message': 'Zipping <(app_launcher_zip_file)',
'inputs': [
- '<!@pymod_do_main(starboard.tools.app_launcher_packager -l)',
+ '<!@pymod_do_main(starboard.tools.app_launcher_packager -l --return_list)',
],
'outputs': [
'<(app_launcher_zip_file)',
diff --git a/testing/OWNERS b/testing/OWNERS
new file mode 100644
index 0000000..72e8ffc
--- /dev/null
+++ b/testing/OWNERS
@@ -0,0 +1 @@
+*
diff --git a/testing/android/OWNERS b/testing/android/OWNERS
new file mode 100644
index 0000000..45b2d83
--- /dev/null
+++ b/testing/android/OWNERS
@@ -0,0 +1,4 @@
+bulach@chromium.org
+jrg@chromium.org
+nileshagrawal@chromium.org
+yfriedman@chromium.org
diff --git a/testing/gtest/BUILD.gn b/testing/gtest/BUILD.gn
index ca2258b..8758d77 100644
--- a/testing/gtest/BUILD.gn
+++ b/testing/gtest/BUILD.gn
@@ -24,7 +24,7 @@
"GTEST_HAS_POSIX_RE=0",
"_SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING",
]
- if (target_cpu != "ps4" && target_os != "win") {
+ if (!is_win) {
defines += [ "GTEST_USE_OWN_TR1_TUPLE=1" ]
}
}
diff --git a/testing/iossim/OWNERS b/testing/iossim/OWNERS
new file mode 100644
index 0000000..1b3348e
--- /dev/null
+++ b/testing/iossim/OWNERS
@@ -0,0 +1,2 @@
+rohitrao@chromium.org
+stuartmorgan@chromium.org
diff --git a/third_party/OWNERS b/third_party/OWNERS
new file mode 100644
index 0000000..72e8ffc
--- /dev/null
+++ b/third_party/OWNERS
@@ -0,0 +1 @@
+*
diff --git a/third_party/angle/.gitattributes b/third_party/angle/.gitattributes
new file mode 100644
index 0000000..e6713d0
--- /dev/null
+++ b/third_party/angle/.gitattributes
@@ -0,0 +1,17 @@
+* text=auto
+*.sln eol=crlf
+*.vcxproj eol=crlf
+*.vcxproj.filters eol=crlf
+*.bat eol=crlf
+*.rc eol=crlf
+**/compiled/*.h eol=crlf
+**/shaders/gen/*.inc eol=lf
+*.sh eol=lf
+*.gn eol=lf
+*.gni eol=lf
+src/compiler/preprocessor/preprocessor_*.* eol=lf
+src/compiler/translator/glslang_*.* eol=lf
+
+# Git conflict markers in the json file break the code generator.
+# Using a binary merge strategy forces conflicts without changing file contents.
+scripts/code_generation_hashes/*.json merge=binary
diff --git a/third_party/angle/BUILD.gn b/third_party/angle/BUILD.gn
index fbc64aa..5882ce0 100644
--- a/third_party/angle/BUILD.gn
+++ b/third_party/angle/BUILD.gn
@@ -120,6 +120,18 @@
if (is_chromeos) {
defines += [ "ANGLE_PLATFORM_CHROMEOS" ]
}
+
+ if (is_starboard && is_win) {
+ defines += [ "__WRL_NO_DEFAULT_LIB__" ]
+ if (current_os == "winuwp") {
+ defines += [ "WINAPI_FAMILY=WINAPI_FAMILY_APP" ] # UWP
+ } else {
+ defines += [
+ "_WIN32",
+ "WINAPI_FAMILY=WINAPI_FAMILY_DESKTOP_APP", # win32
+ ]
+ }
+ }
}
config("constructor_and_destructor_warnings") {
@@ -193,7 +205,7 @@
}
}
-_use_copy_compiler_dll = angle_has_build && is_win && target_cpu != "arm64"
+_use_copy_compiler_dll = !is_starboard && angle_has_build && is_win && target_cpu != "arm64"
# Windows ARM64 is available since 10.0.16299 so no need to copy
# d3dcompiler_47.dll because this file is available as inbox.
@@ -233,6 +245,10 @@
angle_static_library("preprocessor") {
sources = angle_preprocessor_sources
+ if (is_starboard) {
+ suppressed_configs += [ "//starboard/build/config:warnings_as_errors" ]
+ }
+
public_deps = [
":angle_common",
":angle_translator_headers",
@@ -491,6 +507,10 @@
public_configs += [ ":external_config" ]
+ if (is_starboard) {
+ suppressed_configs += [ "//starboard/build/config:warnings_as_errors" ]
+ }
+
deps = [
":includes",
":preprocessor",
diff --git a/third_party/angle/DEPS b/third_party/angle/DEPS
new file mode 100644
index 0000000..76a0854
--- /dev/null
+++ b/third_party/angle/DEPS
@@ -0,0 +1,424 @@
+# This file is used to manage the dependencies of the ANGLE git repo. It is
+# used by gclient to determine what version of each dependency to check out, and
+# where.
+
+# Avoids the need for a custom root variable.
+use_relative_paths = True
+use_relative_hooks = True
+
+vars = {
+ 'android_git': 'https://android.googlesource.com',
+ 'chromium_git': 'https://chromium.googlesource.com',
+ 'chrome_internal_git': 'https://chrome-internal.googlesource.com',
+ 'swiftshader_git': 'https://swiftshader.googlesource.com',
+
+ # This variable is overrided in Chromium's DEPS file.
+ 'build_with_chromium': False,
+
+ # Only check out public sources by default. This can be overridden with custom_vars.
+ # We overload Chromium's 'src-internal' for simplicity.
+ # TOOD(ynovikov): Use checkout_angle_internal custom variable instead.
+ 'checkout_src_internal': False,
+
+ # Version of Chromium our Chromium-based DEPS are mirrored from.
+ 'chromium_revision': 'd209d45ba4bacfaf948e0015f9f7ef71b0d93bbf',
+
+ # Current revision of VK-GL-CTS (a.k.a dEQP).
+ 'vk_gl_cts_revision': '54ec6f2b1390bf33ea10424dca610f8bcbfefa06',
+
+ # Current revision of glslang, the Khronos SPIRV compiler.
+ 'glslang_revision': '0de87ee9a5bf5d094a3faa1a71fd9080e80b6be0',
+
+ # Current revision of googletest.
+ # Note: this dep cannot be auto-rolled b/c of nesting.
+ 'googletest_revision': 'f2fb48c3b3d79a75a88a99fba6576b25d42ec528',
+
+ # Current revision of jsoncpp.
+ # Note: this dep cannot be auto-rolled b/c of nesting.
+ 'jsoncpp_revision': '645250b6690785be60ab6780ce4b58698d884d11',
+
+ # Current revision of patched-yasm.
+ # Note: this dep cannot be auto-rolled b/c of nesting.
+ 'patched_yasm_revision': '720b70524a4424b15fc57e82263568c8ba0496ad',
+
+ # Current revision of spirv-cross, the Khronos SPIRV cross compiler.
+ 'spirv_cross_revision': 'd253f41e17e27285756d031d8ba43bf370264e1f',
+
+ # Current revision fo the SPIRV-Headers Vulkan support library.
+ 'spirv_headers_revision': 'af64a9e826bf5bb5fcd2434dd71be1e41e922563',
+
+ # Current revision of SPIRV-Tools for Vulkan.
+ 'spirv_tools_revision': 'e82a428605f6ce0a07337b36f8ba3935c9f165ac',
+
+ # Current revision of Khronos Vulkan-Headers.
+ 'vulkan_headers_revision': '2b89fd4e2734b728ca0be72a13f2265c5f5aa88e',
+
+ # Current revision of Khronos Vulkan-Loader.
+ 'vulkan_loader_revision': '79e03670c2a328bea3c1a3f80ea913f296a487e6',
+
+ # Current revision of Khronos Vulkan-Tools.
+ 'vulkan_tools_revision': '0a0625a3dca69b9d7ecb73558539ce5e3cd4ddfa',
+
+ # Current revision of Khronos Vulkan-ValidationLayers.
+ 'vulkan_validation_revision': 'e72b61c7c20dd2443f955b956f96b2976ccee004',
+
+ # Three lines of non-changing comments so that
+ # the commit queue can handle CLs rolling catapult
+ # and whatever else without interference from each other.
+ 'catapult_revision': '1b3fb455bf1849f1e6187e1eaeaef32b9f30d3c5',
+}
+
+deps = {
+
+ 'build': {
+ 'url': '{chromium_git}/chromium/src/build.git@ebec9c5ad46c21f00d2e305ac233676327f672cd',
+ 'condition': 'not build_with_chromium',
+ },
+
+ 'buildtools': {
+ 'url': '{chromium_git}/chromium/src/buildtools.git@6b3e658d6fe8cd9c2588796d296f07312b776054',
+ 'condition': 'not build_with_chromium',
+ },
+
+ 'testing': {
+ 'url': '{chromium_git}/chromium/src/testing@72e16daaa56023ddf5e38460b8cfb785db048812',
+ 'condition': 'not build_with_chromium',
+ },
+
+ # Cherry is a dEQP/VK-GL-CTS management GUI written in Go. We use it for viewing test results.
+ 'third_party/cherry': {
+ 'url': '{android_git}/platform/external/cherry@4f8fb08d33ca5ff05a1c638f04c85bbb8d8b52cc',
+ 'condition': 'not build_with_chromium',
+ },
+
+ 'third_party/VK-GL-CTS/src': {
+ 'url': '{chromium_git}/external/github.com/KhronosGroup/VK-GL-CTS@{vk_gl_cts_revision}',
+ },
+
+ 'third_party/fuchsia-sdk': {
+ 'url': '{chromium_git}/chromium/src/third_party/fuchsia-sdk.git@1785f0ac8e1fe81cb25e260acbe7de8f62fa3e44',
+ 'condition': 'checkout_fuchsia and not build_with_chromium',
+ },
+
+ # Closed-source OpenGL ES 1.1 Conformance tests.
+ 'third_party/gles1_conform': {
+ 'url': '{chrome_internal_git}/angle/es-cts.git@dc9f502f709c9cd88d7f8d3974f1c77aa246958e',
+ 'condition': 'checkout_src_internal',
+ },
+
+ # glmark2 is a GPL3-licensed OpenGL ES 2.0 benchmark. We use it for testing.
+ 'third_party/glmark2/src': {
+ 'url': '{chromium_git}/external/github.com/glmark2/glmark2@9e01aef1a786b28aca73135a5b00f85c357e8f5e',
+ },
+
+ 'third_party/glslang/src': {
+ 'url': '{chromium_git}/external/github.com/KhronosGroup/glslang@{glslang_revision}',
+ 'condition': 'not build_with_chromium',
+ },
+
+ 'third_party/googletest': {
+ 'url': '{chromium_git}/chromium/src/third_party/googletest@60616473f7d8414aeb7575b487beecc7369fd52f',
+ 'condition': 'not build_with_chromium',
+ },
+
+ # libjpeg_turbo is used by glmark2.
+ 'third_party/libjpeg_turbo': {
+ 'url': '{chromium_git}/chromium/deps/libjpeg_turbo.git@bc13578529255ec75005ffc98aae151666122892',
+ 'condition': 'not build_with_chromium',
+ },
+
+ 'third_party/libpng/src': {
+ 'url': '{android_git}/platform/external/libpng@094e181e79a3d6c23fd005679025058b7df1ad6c',
+ 'condition': 'not build_with_chromium',
+ },
+
+ 'third_party/jsoncpp': {
+ 'url': '{chromium_git}/chromium/src/third_party/jsoncpp@1cfec065ed4cd9a01fa8e351baa3e714a5868522',
+ 'condition': 'not build_with_chromium',
+ },
+
+ 'third_party/Python-Markdown': {
+ 'url': '{chromium_git}/chromium/src/third_party/Python-Markdown@36657c103ce5964733bbbb29377085e9cc1a9472',
+ 'condition': 'not build_with_chromium',
+ },
+
+ 'third_party/qemu-linux-x64': {
+ 'packages': [
+ {
+ 'package': 'fuchsia/qemu/linux-amd64',
+ 'version': '9cc486c5b18a0be515c39a280ca9a309c54cf994'
+ },
+ ],
+ 'condition': 'not build_with_chromium and (host_os == "linux" and checkout_fuchsia)',
+ 'dep_type': 'cipd',
+ },
+
+ 'third_party/qemu-mac-x64': {
+ 'packages': [
+ {
+ 'package': 'fuchsia/qemu/mac-amd64',
+ 'version': '2d3358ae9a569b2d4a474f498b32b202a152134f'
+ },
+ ],
+ 'condition': 'not build_with_chromium and (host_os == "mac" and checkout_fuchsia)',
+ 'dep_type': 'cipd',
+ },
+
+ 'third_party/rapidjson/src': {
+ 'url': '{chromium_git}/external/github.com/Tencent/rapidjson@7484e06c589873e1ed80382d262087e4fa80fb63',
+ },
+
+ 'third_party/spirv-cross/src': {
+ 'url': '{chromium_git}/external/github.com/KhronosGroup/SPIRV-Cross@{spirv_cross_revision}',
+ 'condition': 'not build_with_chromium',
+ },
+
+ 'third_party/spirv-headers/src': {
+ 'url': '{chromium_git}/external/github.com/KhronosGroup/SPIRV-Headers@{spirv_headers_revision}',
+ 'condition': 'not build_with_chromium',
+ },
+
+ 'third_party/spirv-tools/src': {
+ 'url': '{chromium_git}/external/github.com/KhronosGroup/SPIRV-Tools@{spirv_tools_revision}',
+ 'condition': 'not build_with_chromium',
+ },
+
+ 'third_party/SwiftShader': {
+ 'url': '{swiftshader_git}/SwiftShader@bbd0694f9ab2fbcebba7f674d832dcca9a898a59',
+ 'condition': 'not build_with_chromium',
+ },
+
+ 'third_party/vulkan-headers/src': {
+ 'url': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Headers@{vulkan_headers_revision}',
+ },
+
+ 'third_party/vulkan-loader/src': {
+ 'url': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Loader@{vulkan_loader_revision}',
+ },
+
+ 'third_party/vulkan-tools/src': {
+ 'url': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Tools@{vulkan_tools_revision}',
+ },
+
+ 'third_party/vulkan-validation-layers/src': {
+ 'url': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-ValidationLayers@{vulkan_validation_revision}',
+ },
+
+ 'third_party/yasm': {
+ 'url': '{chromium_git}/chromium/src/third_party/yasm@02a8d2167f476660411ea7e1ee6fbd21dda44e96',
+ 'condition': 'not build_with_chromium',
+ },
+
+ 'third_party/zlib': {
+ 'url': '{chromium_git}/chromium/src/third_party/zlib@e77e1c06c8881abff0c7418368d147ff4a474d08',
+ 'condition': 'not build_with_chromium',
+ },
+
+ 'tools/clang': {
+ 'url': '{chromium_git}/chromium/src/tools/clang.git@05979d8cad7315e31c42120dd83e5765a87629d3',
+ 'condition': 'not build_with_chromium',
+ },
+
+ 'tools/clang/dsymutil': {
+ 'packages': [
+ {
+ 'package': 'chromium/llvm-build-tools/dsymutil',
+ 'version': 'M56jPzDv1620Rnm__jTMYS62Zi8rxHVq7yw0qeBFEgkC',
+ }
+ ],
+ 'condition': 'checkout_mac and not build_with_chromium',
+ 'dep_type': 'cipd',
+ },
+
+ 'tools/md_browser': {
+ 'url': '{chromium_git}/chromium/src/tools/md_browser@0bfd826f8566a99923e64a782908faca72bc457c',
+ 'condition': 'not build_with_chromium',
+ },
+
+ 'tools/memory': {
+ 'url': '{chromium_git}/chromium/src/tools/memory@89552acb6e60f528fe3c98eac7b445d4c34183ee',
+ 'condition': 'not build_with_chromium',
+ },
+
+ 'third_party/catapult': {
+ 'url': '{chromium_git}/catapult.git@{catapult_revision}',
+ 'condition': 'not build_with_chromium',
+ },
+
+ 'third_party/android_ndk': {
+ 'url': '{chromium_git}/android_ndk.git@27c0a8d090c666a50e40fceb4ee5b40b1a2d3f87',
+ 'condition': 'not build_with_chromium',
+ },
+}
+
+hooks = [
+ # Pull clang-format binaries using checked-in hashes.
+ {
+ 'name': 'clang_format_win',
+ 'pattern': '.',
+ 'condition': 'host_os == "win" and not build_with_chromium',
+ 'action': [ 'download_from_google_storage',
+ '--no_resume',
+ '--platform=win32',
+ '--no_auth',
+ '--bucket', 'chromium-clang-format',
+ '-s', 'buildtools/win/clang-format.exe.sha1',
+ ],
+ },
+ {
+ 'name': 'clang_format_mac',
+ 'pattern': '.',
+ 'condition': 'host_os == "mac" and not build_with_chromium',
+ 'action': [ 'download_from_google_storage',
+ '--no_resume',
+ '--platform=darwin',
+ '--no_auth',
+ '--bucket', 'chromium-clang-format',
+ '-s', 'buildtools/mac/clang-format.sha1',
+ ],
+ },
+ {
+ 'name': 'clang_format_linux',
+ 'pattern': '.',
+ 'condition': 'host_os == "linux" and not build_with_chromium',
+ 'action': [ 'download_from_google_storage',
+ '--no_resume',
+ '--platform=linux*',
+ '--no_auth',
+ '--bucket', 'chromium-clang-format',
+ '-s', 'buildtools/linux64/clang-format.sha1',
+ ],
+ },
+ {
+ 'name': 'sysroot_x86',
+ 'pattern': '.',
+ 'condition': 'checkout_linux and ((checkout_x86 or checkout_x64) and not build_with_chromium)',
+ 'action': ['python', 'build/linux/sysroot_scripts/install-sysroot.py',
+ '--arch=x86'],
+ },
+ {
+ 'name': 'sysroot_x64',
+ 'pattern': '.',
+ 'condition': 'checkout_linux and (checkout_x64 and not build_with_chromium)',
+ 'action': ['python', 'build/linux/sysroot_scripts/install-sysroot.py',
+ '--arch=x64'],
+ },
+ {
+ # Update the Windows toolchain if necessary. Must run before 'clang' below.
+ 'name': 'win_toolchain',
+ 'pattern': '.',
+ 'condition': 'checkout_win and not build_with_chromium',
+ 'action': ['python', 'build/vs_toolchain.py', 'update', '--force'],
+ },
+ {
+ # Update the Mac toolchain if necessary.
+ 'name': 'mac_toolchain',
+ 'pattern': '.',
+ 'condition': 'checkout_mac and not build_with_chromium',
+ 'action': ['python', 'build/mac_toolchain.py'],
+ },
+
+ {
+ # Note: On Win, this should run after win_toolchain, as it may use it.
+ 'name': 'clang',
+ 'pattern': '.',
+ 'action': ['python', 'tools/clang/scripts/update.py'],
+ 'condition': 'not build_with_chromium',
+ },
+
+ {
+ # Update LASTCHANGE.
+ 'name': 'lastchange',
+ 'pattern': '.',
+ 'condition': 'not build_with_chromium',
+ 'action': ['python', 'build/util/lastchange.py',
+ '-o', 'build/util/LASTCHANGE'],
+ },
+
+ # Pull rc binaries using checked-in hashes.
+ {
+ 'name': 'rc_win',
+ 'pattern': '.',
+ 'condition': 'checkout_win and (host_os == "win" and not build_with_chromium)',
+ 'action': [ 'download_from_google_storage',
+ '--no_resume',
+ '--no_auth',
+ '--bucket', 'chromium-browser-clang/rc',
+ '-s', 'build/toolchain/win/rc/win/rc.exe.sha1',
+ ],
+ },
+
+ {
+ 'name': 'fuchsia_sdk',
+ 'pattern': '.',
+ 'condition': 'checkout_fuchsia and not build_with_chromium',
+ 'action': [
+ 'python',
+ 'build/fuchsia/update_sdk.py',
+ ],
+ },
+
+ # Download glslang validator binary for Linux.
+ {
+ 'name': 'linux_glslang_validator',
+ 'pattern': '.',
+ 'condition': 'checkout_linux and not build_with_chromium',
+ 'action': [ 'download_from_google_storage',
+ '--no_resume',
+ '--platform=linux*',
+ '--no_auth',
+ '--bucket', 'angle-glslang-validator',
+ '-s', 'tools/glslang/glslang_validator.sha1',
+ ],
+ },
+
+ # Download glslang validator binary for Windows.
+ {
+ 'name': 'win_glslang_validator',
+ 'pattern': '.',
+ 'condition': 'checkout_win and not build_with_chromium',
+ 'action': [ 'download_from_google_storage',
+ '--no_resume',
+ '--platform=win32*',
+ '--no_auth',
+ '--bucket', 'angle-glslang-validator',
+ '-s', 'tools/glslang/glslang_validator.exe.sha1',
+ ],
+ },
+
+ # Download flex/bison binaries for Linux.
+ {
+ 'name': 'linux_flex_bison',
+ 'pattern': '.',
+ 'condition': 'checkout_linux and not build_with_chromium',
+ 'action': [ 'download_from_google_storage',
+ '--no_resume',
+ '--platform=linux*',
+ '--no_auth',
+ '--bucket', 'angle-flex-bison',
+ '-d', 'tools/flex-bison/linux/',
+ ],
+ },
+
+ # Download flex/bison binaries for Windows.
+ {
+ 'name': 'win_flex_bison',
+ 'pattern': '.',
+ 'condition': 'checkout_win and not build_with_chromium',
+ 'action': [ 'download_from_google_storage',
+ '--no_resume',
+ '--platform=win32*',
+ '--no_auth',
+ '--bucket', 'angle-flex-bison',
+ '-d', 'tools/flex-bison/windows/',
+ ],
+ },
+]
+
+recursedeps = [
+ # buildtools provides clang_format.
+ 'buildtools',
+ 'third_party/googletest',
+ 'third_party/jsoncpp',
+ 'third_party/yasm',
+]
diff --git a/third_party/angle/OWNERS b/third_party/angle/OWNERS
new file mode 100644
index 0000000..57a833b
--- /dev/null
+++ b/third_party/angle/OWNERS
@@ -0,0 +1,9 @@
+# See https://chromium.googlesource.com/angle/angle/+/master/doc/ContributingCode.md#selecting-reviewers for per-file owners
+cwallez@chromium.org
+geofflang@chromium.org
+jmadill@chromium.org
+syoussefi@chromium.org
+ynovikov@chromium.org
+
+# COMPONENT: Internals>GPU>ANGLE
+# TEAM: angleproject@googlegroups.com
diff --git a/third_party/angle/PRESUBMIT.py b/third_party/angle/PRESUBMIT.py
new file mode 100644
index 0000000..1e058c3
--- /dev/null
+++ b/third_party/angle/PRESUBMIT.py
@@ -0,0 +1,176 @@
+# Copyright 2019 The ANGLE Project Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Top-level presubmit script for code generation.
+
+See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
+for more details on the presubmit API built into depot_tools.
+"""
+
+import os
+import shutil
+import subprocess
+import sys
+import tempfile
+
+# Fragment of a regular expression that matches C++ and Objective-C++ implementation files.
+_IMPLEMENTATION_EXTENSIONS = r'\.(cc|cpp|cxx|mm)$'
+
+# Fragment of a regular expression that matches C++ and Objective-C++ header files.
+_HEADER_EXTENSIONS = r'\.(h|hpp|hxx)$'
+
+_PRIMARY_EXPORT_TARGETS = [
+ '//:libEGL',
+ '//:libGLESv1_CM',
+ '//:libGLESv2',
+ '//:translator',
+]
+
+
+def _CheckChangeHasBugField(input_api, output_api):
+ """Requires that the changelist have a Bug: field."""
+ bugs = input_api.change.BugsFromDescription()
+ if not bugs:
+ return [
+ output_api.PresubmitError('Please ensure that your description contains:\n'
+ '"Bug: angleproject:[bug number]"\n'
+ 'directly above the Change-Id tag.')
+ ]
+ elif not all([' ' not in bug for bug in bugs]):
+ return [
+ output_api.PresubmitError(
+ 'Check bug tag formatting. Ensure there are no spaces after the colon.')
+ ]
+ else:
+ return []
+
+
+def _CheckCodeGeneration(input_api, output_api):
+
+ class Msg(output_api.PresubmitError):
+ """Specialized error message"""
+
+ def __init__(self, message):
+ super(output_api.PresubmitError, self).__init__(
+ message,
+ long_text='Please ensure your ANGLE repositiory is synced to tip-of-tree\n'
+ 'and all ANGLE DEPS are fully up-to-date by running gclient sync.\n'
+ '\n'
+ 'If that fails, run scripts/run_code_generation.py to refresh generated hashes.\n'
+ '\n'
+ 'If you are building ANGLE inside Chromium you must bootstrap ANGLE\n'
+ 'before gclient sync. See the DevSetup documentation for more details.\n')
+
+ code_gen_path = input_api.os_path.join(input_api.PresubmitLocalPath(),
+ 'scripts/run_code_generation.py')
+ cmd_name = 'run_code_generation'
+ cmd = [input_api.python_executable, code_gen_path, '--verify-no-dirty']
+ test_cmd = input_api.Command(name=cmd_name, cmd=cmd, kwargs={}, message=Msg)
+ if input_api.verbose:
+ print('Running ' + cmd_name)
+ return input_api.RunTests([test_cmd])
+
+
+# Taken directly from Chromium's PRESUBMIT.py
+def _CheckNewHeaderWithoutGnChange(input_api, output_api):
+ """Checks that newly added header files have corresponding GN changes.
+ Note that this is only a heuristic. To be precise, run script:
+ build/check_gn_headers.py.
+ """
+
+ def headers(f):
+ return input_api.FilterSourceFile(f, white_list=(r'.+%s' % _HEADER_EXTENSIONS,))
+
+ new_headers = []
+ for f in input_api.AffectedSourceFiles(headers):
+ if f.Action() != 'A':
+ continue
+ new_headers.append(f.LocalPath())
+
+ def gn_files(f):
+ return input_api.FilterSourceFile(f, white_list=(r'.+\.gn',))
+
+ all_gn_changed_contents = ''
+ for f in input_api.AffectedSourceFiles(gn_files):
+ for _, line in f.ChangedContents():
+ all_gn_changed_contents += line
+
+ problems = []
+ for header in new_headers:
+ basename = input_api.os_path.basename(header)
+ if basename not in all_gn_changed_contents:
+ problems.append(header)
+
+ if problems:
+ return [
+ output_api.PresubmitPromptWarning(
+ 'Missing GN changes for new header files',
+ items=sorted(problems),
+ long_text='Please double check whether newly added header files need '
+ 'corresponding changes in gn or gni files.\nThis checking is only a '
+ 'heuristic. Run build/check_gn_headers.py to be precise.\n'
+ 'Read https://crbug.com/661774 for more info.')
+ ]
+ return []
+
+
+def _CheckExportValidity(input_api, output_api):
+ outdir = tempfile.mkdtemp()
+ # shell=True is necessary on Windows, as otherwise subprocess fails to find
+ # either 'gn' or 'vpython3' even if they are findable via PATH.
+ use_shell = input_api.is_windows
+ try:
+ try:
+ subprocess.check_output(['gn', 'gen', outdir], shell=use_shell)
+ except subprocess.CalledProcessError as e:
+ return [
+ output_api.PresubmitError(
+ 'Unable to run gn gen for export_targets.py: %s' % e.output)
+ ]
+ export_target_script = os.path.join(input_api.PresubmitLocalPath(), 'scripts',
+ 'export_targets.py')
+ try:
+ subprocess.check_output(
+ ['vpython3', export_target_script, outdir] + _PRIMARY_EXPORT_TARGETS,
+ stderr=subprocess.STDOUT,
+ shell=use_shell)
+ except subprocess.CalledProcessError as e:
+ if input_api.is_committing:
+ return [output_api.PresubmitError('export_targets.py failed: %s' % e.output)]
+ return [
+ output_api.PresubmitPromptWarning(
+ 'export_targets.py failed, this may just be due to your local checkout: %s' %
+ e.output)
+ ]
+ return []
+ finally:
+ shutil.rmtree(outdir)
+
+
+def CheckChangeOnUpload(input_api, output_api):
+ results = []
+ # Disabled in Cobalt.
+ # results.extend(_CheckCodeGeneration(input_api, output_api))
+ # results.extend(_CheckChangeHasBugField(input_api, output_api))
+ results.extend(input_api.canned_checks.CheckChangeHasDescription(input_api, output_api))
+ results.extend(_CheckNewHeaderWithoutGnChange(input_api, output_api))
+ # Disabled in Cobalt.
+ # results.extend(_CheckExportValidity(input_api, output_api))
+ # results.extend(
+ # input_api.canned_checks.CheckPatchFormatted(
+ # input_api, output_api, result_factory=output_api.PresubmitError))
+ return results
+
+
+def CheckChangeOnCommit(input_api, output_api):
+ results = []
+ # Disabled in Cobalt.
+ # results.extend(_CheckCodeGeneration(input_api, output_api))
+ results.extend(
+ input_api.canned_checks.CheckPatchFormatted(
+ input_api, output_api, result_factory=output_api.PresubmitError))
+ # Disabled in Cobalt.
+ # results.extend(_CheckChangeHasBugField(input_api, output_api))
+ # results.extend(_CheckExportValidity(input_api, output_api))
+ results.extend(input_api.canned_checks.CheckChangeHasDescription(input_api, output_api))
+ return results
diff --git a/third_party/angle/android/OWNERS b/third_party/angle/android/OWNERS
new file mode 100644
index 0000000..f38b366
--- /dev/null
+++ b/third_party/angle/android/OWNERS
@@ -0,0 +1,6 @@
+alanward@google.com
+cnorthrop@google.com
+courtneygo@google.com
+ianelliott@google.com
+timvp@google.com
+tobine@google.com
\ No newline at end of file
diff --git a/third_party/angle/infra/config/OWNERS b/third_party/angle/infra/config/OWNERS
new file mode 100644
index 0000000..9a1736e
--- /dev/null
+++ b/third_party/angle/infra/config/OWNERS
@@ -0,0 +1,2 @@
+geofflang@chromium.org
+jmadill@chromium.org
diff --git a/third_party/angle/infra/config/PRESUBMIT.py b/third_party/angle/infra/config/PRESUBMIT.py
new file mode 100644
index 0000000..0c43ad1
--- /dev/null
+++ b/third_party/angle/infra/config/PRESUBMIT.py
@@ -0,0 +1,11 @@
+# Copyright 2019 The ANGLE Project Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+
+def CheckChangeOnUpload(input_api, output_api):
+ return input_api.canned_checks.CheckChangedLUCIConfigs(input_api, output_api)
+
+
+def CheckChangeOnCommit(input_api, output_api):
+ return input_api.canned_checks.CheckChangedLUCIConfigs(input_api, output_api)
diff --git a/third_party/angle/infra/config/branch/OWNERS b/third_party/angle/infra/config/branch/OWNERS
new file mode 100644
index 0000000..9a1736e
--- /dev/null
+++ b/third_party/angle/infra/config/branch/OWNERS
@@ -0,0 +1,2 @@
+geofflang@chromium.org
+jmadill@chromium.org
diff --git a/third_party/angle/infra/config/global/OWNERS b/third_party/angle/infra/config/global/OWNERS
new file mode 100644
index 0000000..9a1736e
--- /dev/null
+++ b/third_party/angle/infra/config/global/OWNERS
@@ -0,0 +1,2 @@
+geofflang@chromium.org
+jmadill@chromium.org
diff --git a/third_party/angle/src/common/system_utils_winuwp.cpp b/third_party/angle/src/common/system_utils_winuwp.cpp
index 24b60c5..ac60e08 100644
--- a/third_party/angle/src/common/system_utils_winuwp.cpp
+++ b/third_party/angle/src/common/system_utils_winuwp.cpp
@@ -16,7 +16,7 @@
#include <string>
#include <vector>
-#include "common/debug.h"
+#include "common/debug.h" // nogncheck
namespace angle
{
diff --git a/third_party/angle/src/common/third_party/base/anglebase/numerics/OWNERS b/third_party/angle/src/common/third_party/base/anglebase/numerics/OWNERS
new file mode 100644
index 0000000..41f35fc
--- /dev/null
+++ b/third_party/angle/src/common/third_party/base/anglebase/numerics/OWNERS
@@ -0,0 +1,3 @@
+jschuh@chromium.org
+tsepez@chromium.org
+
diff --git a/third_party/angle/tools/flex-bison/third_party/.gitattributes b/third_party/angle/tools/flex-bison/third_party/.gitattributes
new file mode 100644
index 0000000..0688c8a
--- /dev/null
+++ b/third_party/angle/tools/flex-bison/third_party/.gitattributes
@@ -0,0 +1 @@
+* eol=lf
diff --git a/third_party/boringssl/BUILD.gn b/third_party/boringssl/BUILD.gn
index 47bb81f..430b77e 100644
--- a/third_party/boringssl/BUILD.gn
+++ b/third_party/boringssl/BUILD.gn
@@ -161,3 +161,27 @@
configs += [ "//starboard/build/config:speed" ]
}
}
+
+target(final_executable_type, "crypto_tool") {
+ sources = [
+ "src/tool/args.cc",
+ "src/tool/ciphers.cc",
+ "src/tool/const.cc",
+ "src/tool/digest.cc",
+ "src/tool/file.cc",
+ "src/tool/generate_ed25519.cc",
+ "src/tool/genrsa.cc",
+ "src/tool/pkcs12.cc",
+ "src/tool/rand.cc",
+ "src/tool/sign.cc",
+ "src/tool/speed.cc",
+ "src/tool/tool.cc",
+ ]
+ include_dirs = [ "src/include" ]
+ defines = [ "OPENSSL_NO_SOCK" ]
+ deps = [
+ ":crypto",
+ "//starboard",
+ ]
+ content_deps = [ "//third_party/icu:icudata" ]
+}
diff --git a/third_party/boringssl/OWNERS b/third_party/boringssl/OWNERS
new file mode 100644
index 0000000..77896b3
--- /dev/null
+++ b/third_party/boringssl/OWNERS
@@ -0,0 +1,6 @@
+agl@chromium.org
+davidben@chromium.org
+rsleevi@chromium.org
+svaldez@chromium.org
+
+# COMPONENT: Internals>Network>SSL
diff --git a/third_party/boringssl/src/util/bot/DEPS b/third_party/boringssl/src/util/bot/DEPS
new file mode 100644
index 0000000..5dab203
--- /dev/null
+++ b/third_party/boringssl/src/util/bot/DEPS
@@ -0,0 +1,212 @@
+# Copyright (c) 2015, Google Inc.
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+# SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+# OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+vars = {
+ 'chromium_git': 'https://chromium.googlesource.com',
+
+ 'checkout_clang': False,
+ 'checkout_fuzzer': False,
+ 'checkout_sde': False,
+ 'checkout_nasm': False,
+ 'checkout_libcxx': False,
+}
+
+deps = {
+ 'boringssl/util/bot/android_ndk': {
+ 'url': Var('chromium_git') + '/android_ndk.git' + '@' + '5cd86312e794bdf542a3685c6f10cbb96072990b',
+ 'condition': 'checkout_android',
+ },
+
+ 'boringssl/util/bot/android_tools': {
+ 'url': Var('chromium_git') + '/android_tools.git' + '@' + '130499e25286f4d56acafa252fee09f3cc595c49',
+ 'condition': 'checkout_android',
+ },
+
+ 'boringssl/util/bot/gyp':
+ Var('chromium_git') + '/external/gyp.git' + '@' + 'd61a9397e668fa9843c4aa7da9e79460fe590bfb',
+
+ 'boringssl/util/bot/libFuzzer': {
+ 'url': Var('chromium_git') + '/chromium/llvm-project/compiler-rt/lib/fuzzer.git' + '@' + '658ff786a213703ff0df6ba4a288e9a1e218c074',
+ 'condition': 'checkout_fuzzer',
+ },
+
+ # Update the following revisions from
+ # https://chromium.googlesource.com/chromium/buildtools/+/master/DEPS
+ 'boringssl/util/bot/libcxx': {
+ 'url': Var('chromium_git') + '/chromium/llvm-project/libcxx.git' + '@' + '85a7702b4cc5d69402791fe685f151cf3076be71',
+ 'condition': 'checkout_libcxx',
+ },
+ 'boringssl/util/bot/libcxxabi': {
+ 'url': Var('chromium_git') + '/chromium/llvm-project/libcxxabi.git' + '@' + '05a73941f3fb177c4a891d4ff2a4ed5785e3b80c',
+ 'condition': 'checkout_libcxx',
+ },
+}
+
+recursedeps = [
+ # android_tools pulls in the NDK from a separate repository.
+ 'boringssl/util/bot/android_tools',
+]
+
+hooks = [
+ {
+ 'name': 'cmake_linux64',
+ 'pattern': '.',
+ 'condition': 'host_os == "linux"',
+ 'action': [ 'download_from_google_storage',
+ '--no_resume',
+ '--platform=linux*',
+ '--no_auth',
+ '--bucket', 'chromium-tools',
+ '-s', 'boringssl/util/bot/cmake-linux64.tar.gz.sha1',
+ ],
+ },
+ {
+ 'name': 'cmake_linux64_extract',
+ 'pattern': '.',
+ 'condition': 'host_os == "linux"',
+ 'action': [ 'python',
+ 'boringssl/util/bot/extract.py',
+ 'boringssl/util/bot/cmake-linux64.tar.gz',
+ 'boringssl/util/bot/cmake-linux64/',
+ ],
+ },
+ {
+ 'name': 'cmake_mac',
+ 'pattern': '.',
+ 'condition': 'host_os == "mac"',
+ 'action': [ 'download_from_google_storage',
+ '--no_resume',
+ '--platform=darwin',
+ '--no_auth',
+ '--bucket', 'chromium-tools',
+ '-s', 'boringssl/util/bot/cmake-mac.tar.gz.sha1',
+ ],
+ },
+ {
+ 'name': 'cmake_mac_extract',
+ 'pattern': '.',
+ 'condition': 'host_os == "mac"',
+ 'action': [ 'python',
+ 'boringssl/util/bot/extract.py',
+ 'boringssl/util/bot/cmake-mac.tar.gz',
+ 'boringssl/util/bot/cmake-mac/',
+ ],
+ },
+ {
+ 'name': 'cmake_win32',
+ 'pattern': '.',
+ 'condition': 'host_os == "win"',
+ 'action': [ 'download_from_google_storage',
+ '--no_resume',
+ '--platform=win32',
+ '--no_auth',
+ '--bucket', 'chromium-tools',
+ '-s', 'boringssl/util/bot/cmake-win32.zip.sha1',
+ ],
+ },
+ {
+ 'name': 'cmake_win32_extract',
+ 'pattern': '.',
+ 'condition': 'host_os == "win"',
+ 'action': [ 'python',
+ 'boringssl/util/bot/extract.py',
+ 'boringssl/util/bot/cmake-win32.zip',
+ 'boringssl/util/bot/cmake-win32/',
+ ],
+ },
+ {
+ 'name': 'perl_win32',
+ 'pattern': '.',
+ 'condition': 'host_os == "win"',
+ 'action': [ 'download_from_google_storage',
+ '--no_resume',
+ '--platform=win32',
+ '--no_auth',
+ '--bucket', 'chromium-tools',
+ '-s', 'boringssl/util/bot/perl-win32.zip.sha1',
+ ],
+ },
+ {
+ 'name': 'perl_win32_extract',
+ 'pattern': '.',
+ 'condition': 'host_os == "win"',
+ 'action': [ 'python',
+ 'boringssl/util/bot/extract.py',
+ '--no-prefix',
+ 'boringssl/util/bot/perl-win32.zip',
+ 'boringssl/util/bot/perl-win32/',
+ ],
+ },
+ {
+ 'name': 'yasm_win32',
+ 'pattern': '.',
+ 'condition': 'host_os == "win" and not checkout_nasm',
+ 'action': [ 'download_from_google_storage',
+ '--no_resume',
+ '--platform=win32',
+ '--no_auth',
+ '--bucket', 'chromium-tools',
+ '-s', 'boringssl/util/bot/yasm-win32.exe.sha1',
+ ],
+ },
+ {
+ 'name': 'nasm_win32',
+ 'pattern': '.',
+ 'condition': 'host_os == "win" and checkout_nasm',
+ 'action': [ 'download_from_google_storage',
+ '--no_resume',
+ '--platform=win32',
+ '--no_auth',
+ '--bucket', 'chromium-tools',
+ '-s', 'boringssl/util/bot/nasm-win32.exe.sha1',
+ ],
+ },
+ {
+ 'name': 'win_toolchain',
+ 'pattern': '.',
+ 'condition': 'host_os == "win"',
+ 'action': [ 'python',
+ 'boringssl/util/bot/vs_toolchain.py',
+ 'update',
+ ],
+ },
+ {
+ 'name': 'clang',
+ 'pattern': '.',
+ 'condition': 'checkout_clang',
+ 'action': [ 'python',
+ 'boringssl/util/bot/update_clang.py',
+ ],
+ },
+ {
+ 'name': 'sde_linux64',
+ 'pattern': '.',
+ 'condition': 'checkout_sde and host_os == "linux"',
+ 'action': [ 'download_from_google_storage',
+ '--no_resume',
+ '--bucket', 'chrome-boringssl-sde',
+ '-s', 'boringssl/util/bot/sde-linux64.tar.bz2.sha1'
+ ],
+ },
+ {
+ 'name': 'sde_linux64_extract',
+ 'pattern': '.',
+ 'condition': 'checkout_sde and host_os == "linux"',
+ 'action': [ 'python',
+ 'boringssl/util/bot/extract.py',
+ 'boringssl/util/bot/sde-linux64.tar.bz2',
+ 'boringssl/util/bot/sde-linux64/',
+ ],
+ },
+]
diff --git a/third_party/brotli/.gitmodules b/third_party/brotli/.gitmodules
new file mode 100644
index 0000000..af7df38
--- /dev/null
+++ b/third_party/brotli/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "research/esaxx"]
+ path = research/esaxx
+ url = https://github.com/hillbig/esaxx
diff --git a/third_party/chromium/media/BUILD.gn b/third_party/chromium/media/BUILD.gn
new file mode 100644
index 0000000..d5c158c
--- /dev/null
+++ b/third_party/chromium/media/BUILD.gn
@@ -0,0 +1,463 @@
+# 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.
+
+import("//build/buildflag_header.gni")
+import("//build/config/android/config.gni")
+import("//build/config/arm.gni")
+import("//build/config/chromecast_build.gni")
+import("//build/config/features.gni")
+import("//build/config/linux/pkg_config.gni")
+import("//build/config/ui.gni")
+import("//media/cdm/library_cdm/cdm_paths.gni")
+import("//media/media_options.gni")
+import("//testing/libfuzzer/fuzzer_test.gni")
+import("//testing/test.gni")
+import("//third_party/ffmpeg/ffmpeg_options.gni")
+
+buildflag_header("media_buildflags") {
+ header = "media_buildflags.h"
+
+ flags = [
+ "ALTERNATE_CDM_STORAGE_ID_KEY=\"$alternate_cdm_storage_id_key\"",
+ "CDM_PLATFORM_SPECIFIC_PATH=\"$cdm_platform_specific_path\"",
+ "ENABLE_PLATFORM_AC3_EAC3_AUDIO=$enable_platform_ac3_eac3_audio",
+ "ENABLE_CAST_AUDIO_RENDERER=$enable_cast_audio_renderer",
+ "ENABLE_CDM_HOST_VERIFICATION=$enable_cdm_host_verification",
+ "ENABLE_CDM_STORAGE_ID=$enable_cdm_storage_id",
+ "ENABLE_DAV1D_DECODER=$enable_dav1d_decoder",
+ "ENABLE_AV1_DECODER=$enable_av1_decoder",
+ "ENABLE_PLATFORM_DOLBY_VISION=$enable_platform_dolby_vision",
+ "ENABLE_FFMPEG=$media_use_ffmpeg",
+ "ENABLE_FFMPEG_VIDEO_DECODERS=$enable_ffmpeg_video_decoders",
+ "ENABLE_PLATFORM_HEVC=$enable_platform_hevc",
+ "ENABLE_PLATFORM_HEVC_DECODING=$enable_platform_hevc_decoding",
+ "ENABLE_PLATFORM_ENCRYPTED_HEVC=$enable_platform_encrypted_hevc",
+ "ENABLE_HLS_SAMPLE_AES=$enable_hls_sample_aes",
+ "ENABLE_LIBGAV1_DECODER=$enable_libgav1_decoder",
+ "ENABLE_LIBRARY_CDMS=$enable_library_cdms",
+ "ENABLE_LIBVPX=$media_use_libvpx",
+ "ENABLE_LOGGING_OVERRIDE=$enable_logging_override",
+ "ENABLE_MEDIA_DRM_STORAGE=$enable_media_drm_storage",
+ "ENABLE_MEDIA_REMOTING=$enable_media_remoting",
+ "ENABLE_MEDIA_REMOTING_RPC=$enable_media_remoting_rpc",
+ "ENABLE_OPENH264=$media_use_openh264",
+ "ENABLE_PLATFORM_MPEG_H_AUDIO=$enable_platform_mpeg_h_audio",
+ "ENABLE_MSE_MPEG2TS_STREAM_PARSER=$enable_mse_mpeg2ts_stream_parser",
+ "ENABLE_CAST_STREAMING_RENDERER=$enable_cast_streaming_renderer",
+ "USE_CHROMEOS_MEDIA_ACCELERATION=$use_vaapi||$use_v4l2_codec",
+ "USE_CHROMEOS_PROTECTED_AV1=$use_chromeos_protected_av1",
+ "USE_CHROMEOS_PROTECTED_MEDIA=$use_chromeos_protected_media",
+ "USE_PROPRIETARY_CODECS=$proprietary_codecs",
+ ]
+}
+
+if (proprietary_codecs && media_use_ffmpeg) {
+ assert(
+ ffmpeg_branding != "Chromium",
+ "proprietary codecs and ffmpeg_branding set to Chromium are incompatible")
+}
+
+# Common configuration for targets in the media directory; these must not be
+# exported since things like USE_NEON and USE_CRAS have different meanings
+# elsewhere in the code base.
+config("media_config") {
+ defines = []
+ if (current_cpu == "arm64" || (current_cpu == "arm" && arm_use_neon)) {
+ defines += [ "USE_NEON" ]
+ }
+ if (use_pulseaudio) {
+ defines += [ "USE_PULSEAUDIO" ]
+ if (!link_pulseaudio) {
+ defines += [ "DLOPEN_PULSEAUDIO" ]
+ }
+ }
+ if (use_cras) {
+ defines += [ "USE_CRAS" ]
+ }
+}
+
+# Internal grouping of the configs necessary to support sub-folders having their
+# own BUILD.gn files; only targets which roll up into the "media" target should
+# include this config. I.e., not "test_support" or "unit_tests" targets.
+#
+# Without these configs having individual sub-folders take a //media/base DEP
+# (or others) can yield incorrectly imported and exported symbols on Windows:
+#
+# fatal error LNK1169: one or more multiply defined symbols found.
+#
+config("subcomponent_config") {
+ visibility = media_subcomponent_deps
+ if (is_mac) {
+ visibility += [ "//media/base/mac" ]
+ }
+ defines = [ "IS_MEDIA_IMPL" ]
+ configs = [
+ ":media_config",
+ "//build/config/compiler:wexit_time_destructors",
+ ]
+}
+
+component("media") {
+ libs = []
+
+ deps = [
+ "//base",
+ "//base:i18n",
+ "//base/third_party/dynamic_annotations",
+ "//cc/paint",
+ "//crypto:platform",
+ "//gpu/command_buffer/client:gles2_interface",
+ "//gpu/command_buffer/common",
+ "//third_party/libyuv",
+ "//ui/events:events_base",
+ "//ui/gfx",
+ "//ui/gfx/geometry",
+ "//ui/gl:gl",
+ "//url",
+ ]
+
+ public_configs = [ "//third_party/libwebm:libwebm_config" ]
+ public_deps = media_subcomponent_deps
+ public_deps += [
+ ":media_buildflags",
+ ":shared_memory_support",
+ "//ui/gfx:color_space",
+ ]
+
+ # This must be included here since it actually depends on //media/base.
+ if (is_apple) {
+ public_deps += [ "//media/base/mac" ]
+ }
+
+ if (use_ozone) {
+ deps += [ "//ui/ozone" ]
+ }
+}
+
+# Note: This can't be a static_library since it does not have any sources.
+source_set("test_support") {
+ testonly = true
+ public_deps = [
+ ":media",
+ "//media/audio:test_support",
+ "//media/base:test_support",
+ "//media/base/android:test_support",
+ "//media/filters:test_support",
+ "//media/formats:test_support",
+ "//media/renderers:test_support",
+ "//media/video:test_support",
+ ]
+}
+
+# Contains tests for all targets in the "media" folder.
+# TODO(xhwang): Move mojo/capture/remoting tests here where applicable.
+test("media_unittests") {
+ use_xvfb = use_xvfb_in_this_config
+
+ deps = [
+ "//media/audio:unit_tests",
+ "//media/base:unit_tests",
+ "//media/capabilities:unit_tests",
+ "//media/cast/receiver:unit_tests",
+ "//media/cdm:unit_tests",
+ "//media/device_monitors:unit_tests",
+ "//media/filters:unit_tests",
+ "//media/formats:unit_tests",
+ "//media/gpu:unit_tests",
+ "//media/learning:unit_tests",
+ "//media/mojo:unit_tests",
+ "//media/muxers:unit_tests",
+ "//media/parsers:unit_tests",
+ "//media/renderers:unit_tests",
+ "//media/test:pipeline_integration_tests",
+ "//media/test:run_all_unittests",
+ "//media/video:unit_tests",
+ "//media/webrtc:unit_tests",
+ ]
+
+ data = [
+ "test/data/",
+ "formats/mp4/h264_annex_b_fuzz_corpus/",
+ ]
+
+ data_deps = [ "//testing/buildbot/filters:media_unittests_filters" ]
+
+ if (media_use_ffmpeg) {
+ deps += [ "//media/ffmpeg:unit_tests" ]
+ }
+
+ if (is_android) {
+ deps += [
+ # The test needs the java dependencies to add the java classes for their
+ # native counterparts to the test apk.
+ "//gpu/command_buffer/service:android_texture_owner_unittests",
+ "//media/base/android:media_java",
+ "//media/base/android:unit_tests",
+ "//media/gpu:android_video_decode_accelerator_unittests",
+ "//ui/android:ui_java",
+ ]
+ }
+
+ if (is_fuchsia) {
+ deps += [
+ "//media/fuchsia/audio:unittests",
+ "//media/fuchsia/cdm/service:unittests",
+ ]
+
+ additional_manifest_fragments = [
+ "//build/config/fuchsia/test/audio_capabilities.test-cmx",
+
+ # TODO(crbug.com/1185811): Figure out why
+ # PaintCanvasVideoRendererWithGLTest.CopyVideoFrameYUVDataToGLTexture
+ # crashes without "deprecated-ambient-replace-as-executable".
+ "//build/config/fuchsia/test/jit_capabilities.test-cmx",
+
+ "//build/config/fuchsia/test/vulkan_capabilities.test-cmx",
+ ]
+ }
+
+ if (enable_media_remoting) {
+ deps += [ "//media/remoting:media_remoting_tests" ]
+ }
+
+ # The test needs OPUS_FIXED_POINT conditional define.
+ configs += [ "//third_party/opus:opus_config" ]
+}
+
+test("media_perftests") {
+ configs += [ ":media_config" ]
+ deps = [
+ ":test_support",
+ "//base/test:test_support",
+ "//media/base:perftests",
+ "//media/filters:perftests",
+ "//media/test:pipeline_integration_perftests",
+ "//testing/gmock",
+ "//testing/gtest",
+ "//testing/perf",
+ "//third_party/widevine/cdm:headers",
+ "//ui/gfx:test_support",
+ ]
+ if (media_use_ffmpeg) {
+ # Direct dependency required to inherit config.
+ deps += [ "//third_party/ffmpeg" ]
+ }
+
+ # This target should not require the Chrome executable to run.
+ assert_no_deps = [ "//chrome" ]
+
+ data = [ "test/data/" ]
+
+ data_deps = [
+ # Needed for isolate script to execute.
+ "//testing:run_perf_test",
+ ]
+}
+
+# The audio subset of media_unittests. This target exists for running only the
+# audio tests on the GPU bots (which have audio hardware).
+test("audio_unittests") {
+ deps = [
+ ":test_support",
+ "//base/test:test_support",
+ "//media/audio:unit_tests",
+ "//media/test:run_all_unittests",
+ ]
+ if (is_android) {
+ deps += [
+ # The test needs the java dependencies to add the java classes for their
+ # native counterparts to the test apk.
+ "//media/base/android:media_java",
+ "//ui/android:ui_java",
+ ]
+ }
+}
+
+# Note: Most external components should just depend on //media unless they
+# specifically need this pared own target (NaCl, PPAPI, etc). Internal targets
+# should just depend on //media/base which will propagate this target to them.
+component("shared_memory_support") {
+ sources = [
+ "base/audio_bus.cc",
+ "base/audio_bus.h",
+ "base/audio_latency.cc",
+ "base/audio_latency.h",
+ "base/audio_parameters.cc",
+ "base/audio_parameters.h",
+ "base/audio_point.cc",
+ "base/audio_point.h",
+ "base/audio_sample_types.h",
+ "base/channel_layout.cc",
+ "base/channel_layout.h",
+ "base/limits.h",
+ "base/media_shmem_export.h",
+ "base/sample_format.cc",
+ "base/sample_format.h",
+ "base/vector_math.cc",
+ "base/vector_math.h",
+ "base/vector_math_testing.h",
+ "base/video_types.cc",
+ "base/video_types.h",
+ ]
+ if (is_mac) {
+ # These need to be included here because audio_latency.cc depends on them.
+ sources += [
+ "base/mac/audio_latency_mac.cc",
+ "base/mac/audio_latency_mac.h",
+ ]
+ }
+
+ # Do not use "subcomponent_config" here since these files are in their own
+ # component target and thus can't share the standard export macros.
+ configs += [ ":media_config" ]
+ defines = [ "MEDIA_SHMEM_IMPLEMENTATION" ]
+
+ if (!is_debug) {
+ configs -= [ "//build/config/compiler:default_optimization" ]
+ configs += [ "//build/config/compiler:optimize_max" ]
+ }
+ deps = [
+ "//base",
+ "//build:chromeos_buildflags",
+ "//ui/gfx/geometry",
+ ]
+}
+
+# TODO(watk): Refactor tests that could be made to run on Android. See
+# http://crbug.com/570762
+if (media_use_ffmpeg && !is_android) {
+ test("ffmpeg_regression_tests") {
+ configs += [ "//media:media_config" ]
+
+ deps = [
+ ":test_support",
+ "//base/test:test_support",
+ "//media/ffmpeg:ffmpeg_regression_tests",
+ "//media/test:pipeline_integration_tests",
+ "//media/test:run_all_unittests",
+ "//testing/gmock",
+ "//testing/gtest",
+ "//ui/gfx:test_support",
+ "//ui/gfx/geometry",
+ ]
+ }
+}
+
+if (proprietary_codecs) {
+ fuzzer_test("media_cenc_utils_fuzzer") {
+ sources = [ "cdm/cenc_utils_fuzzertest.cc" ]
+ deps = [ ":media" ]
+ }
+}
+
+fuzzer_test("media_vp9_parser_fuzzer") {
+ sources = [ "filters/vp9_parser_fuzzertest.cc" ]
+ deps = [
+ ":test_support",
+ "//base",
+ ]
+ libfuzzer_options = [ "max_len = 400000" ]
+}
+
+fuzzer_test("media_vp9_parser_encrypted_fuzzer") {
+ sources = [ "filters/vp9_parser_encrypted_fuzzertest.cc" ]
+ deps = [
+ ":test_support",
+ "//base",
+ "//base/test:test_support",
+ ]
+ seed_corpus = "//media/test/data"
+}
+
+fuzzer_test("media_vpx_video_decoder_fuzzer") {
+ sources = [ "filters/vpx_video_decoder_fuzzertest.cc" ]
+ deps = [
+ ":media",
+ "//base",
+ "//base/test:test_support",
+ ]
+ libfuzzer_options = [ "max_len = 400000" ]
+ seed_corpus = "//media/test/data"
+}
+
+fuzzer_test("media_webm_muxer_fuzzer") {
+ sources = [ "muxers/webm_muxer_fuzzertest.cc" ]
+ deps = [
+ ":media",
+ "//base",
+ "//third_party/libwebm",
+ ]
+}
+
+fuzzer_test("cbcs_decryptor_fuzzer") {
+ sources = [ "cdm/cbcs_decryptor_fuzzer.cc" ]
+ deps = [
+ ":media",
+ "//base",
+ "//crypto",
+ ]
+}
+
+fuzzer_test("cenc_decryptor_fuzzer") {
+ sources = [ "cdm/cenc_decryptor_fuzzer.cc" ]
+ deps = [
+ ":media",
+ "//base",
+ "//crypto",
+ ]
+}
+
+fuzzer_test("json_web_key_fuzzer") {
+ sources = [ "cdm/json_web_key_fuzzer.cc" ]
+ deps = [
+ ":media",
+ "//base",
+ ]
+}
+
+if (proprietary_codecs) {
+ fuzzer_test("media_mp4_avcc_parser_fuzzer") {
+ sources = [ "formats/mp4/mp4_avcc_parser_fuzzer.cc" ]
+ deps = [
+ ":media",
+ "//base",
+ ]
+ }
+
+ fuzzer_test("media_mp4_box_reader_fuzzer") {
+ sources = [ "formats/mp4/mp4_box_reader_fuzzer.cc" ]
+ deps = [
+ ":media",
+ "//base",
+ ]
+ libfuzzer_options = [ "max_len=500" ]
+ dict = "test/mp4.dict"
+ }
+}
+
+if (enable_mse_mpeg2ts_stream_parser) {
+ fuzzer_test("media_es_parser_adts_fuzzer") {
+ sources = [ "formats/mp2t/es_parser_adts_fuzzer.cc" ]
+ deps = [
+ ":media",
+ "//base",
+ ]
+ }
+
+ fuzzer_test("media_es_parser_h264_fuzzer") {
+ sources = [ "formats/mp2t/es_parser_h264_fuzzer.cc" ]
+ deps = [
+ ":media",
+ "//base",
+ ]
+ }
+
+ fuzzer_test("media_es_parser_mpeg1audio_fuzzer") {
+ sources = [ "formats/mp2t/es_parser_mpeg1audio_fuzzer.cc" ]
+ deps = [
+ ":media",
+ "//base",
+ ]
+ }
+}
diff --git a/third_party/chromium/media/COMMON_METADATA b/third_party/chromium/media/COMMON_METADATA
new file mode 100644
index 0000000..0198eda
--- /dev/null
+++ b/third_party/chromium/media/COMMON_METADATA
@@ -0,0 +1,3 @@
+monorail {
+ component: "Internals>Media"
+}
\ No newline at end of file
diff --git a/third_party/chromium/media/DEPS b/third_party/chromium/media/DEPS
new file mode 100644
index 0000000..1804063
--- /dev/null
+++ b/third_party/chromium/media/DEPS
@@ -0,0 +1,56 @@
+# Do NOT add net/ or ui/base without a great reason, they're huge!
+include_rules = [
+ "+cc/base/math_util.h",
+ "+cc/paint",
+ "+components/crash/core/common/crash_key.h",
+ "+components/system_media_controls/linux/buildflags",
+ "+crypto",
+ "+device/udev_linux",
+ "+gpu",
+ "+media/midi/midi_jni_headers",
+ "+mojo/public/cpp/bindings/callback_helpers.h",
+ "+mojo/public/cpp/system/platform_handle.h",
+ "+net/cookies/site_for_cookies.h",
+ "+services/device/public",
+ "+services/viz/public/cpp/gpu/context_provider_command_buffer.h",
+ "+skia/ext",
+ "+third_party/dav1d",
+ "+third_party/ffmpeg",
+ "+third_party/libgav1",
+ "+third_party/libvpx",
+ "+third_party/libyuv",
+ "+third_party/openh264/src/codec/api/svc",
+ "+third_party/opus",
+ "+third_party/skia",
+ "+ui/base/ui_base_features.h",
+ "+ui/base/x/x11_user_input_monitor.h",
+ "+ui/display",
+ "+ui/events",
+ "+ui/gfx",
+ "+ui/gl",
+ "+ui/ozone",
+ "+third_party/widevine/cdm/widevine_cdm_common.h",
+ "-ipc",
+ "-media/webrtc",
+]
+
+specific_include_rules = {
+ "audio_manager_unittest.cc": [
+ "+chromeos/dbus"
+ ],
+ "cras_input_unittest.cc": [
+ "+chromeos/dbus"
+ ],
+ "cras_unified_unittest.cc": [
+ "+chromeos/dbus"
+ ],
+ "fuchsia_video_decoder_unittest.cc": [
+ "+components/viz/test/test_context_support.h",
+ ],
+ "gpu_memory_buffer_video_frame_pool_unittest.cc": [
+ "+components/viz/test/test_context_provider.h",
+ ],
+ "null_video_sink_unittest.cc": [
+ "+components/viz/common/frame_sinks/begin_frame_args.h",
+ ],
+}
diff --git a/third_party/chromium/media/DIR_METADATA b/third_party/chromium/media/DIR_METADATA
new file mode 100644
index 0000000..69cd629
--- /dev/null
+++ b/third_party/chromium/media/DIR_METADATA
@@ -0,0 +1,9 @@
+# Metadata information for this directory.
+#
+# For more information on DIR_METADATA files, see:
+# https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
+#
+# For the schema of this file, see Metadata message:
+# https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
+
+mixins: "//media/COMMON_METADATA"
diff --git a/third_party/chromium/media/OWNERS b/third_party/chromium/media/OWNERS
new file mode 100644
index 0000000..262c8c2
--- /dev/null
+++ b/third_party/chromium/media/OWNERS
@@ -0,0 +1,28 @@
+# NOTE: Do not use these owners when you're in a subdirectory that has
+# OWNERS file. For example:
+# - cast
+# - midi
+# - ozone
+# - capture/{content,video}
+# Instead prefer the OWNERS in the subdirectory as they will be more familiar,
+# and to load balance. Only use OWNERS in this file for these subdirectories
+# when doing refactorings and general cleanups.
+
+set noparent
+cassew@google.com
+chcunningham@chromium.org
+dalecurtis@chromium.org
+eugene@chromium.org
+jrummell@chromium.org
+liberato@chromium.org
+sandersd@chromium.org
+tguilbert@chromium.org
+tmathmeyer@chromium.org
+wolenetz@chromium.org
+xhwang@chromium.org
+
+# For Fuchsia-specific changes:
+per-file ..._fuchsia*=file://build/fuchsia/OWNERS
+
+# For GpuMemoryBuffer-related changes:
+per-file *gpu_memory_buffer*=dcastagna@chromium.org
diff --git a/third_party/chromium/media/PRESUBMIT.py b/third_party/chromium/media/PRESUBMIT.py
new file mode 100644
index 0000000..d3c636d
--- /dev/null
+++ b/third_party/chromium/media/PRESUBMIT.py
@@ -0,0 +1,249 @@
+# Copyright 2013 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.
+
+"""Top-level presubmit script for Chromium media component.
+
+See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
+for more details about the presubmit API built into depot_tools.
+"""
+
+import re
+
+# This line is 'magic' in that git-cl looks for it to decide whether to
+# use Python3 instead of Python2 when running the code in this file.
+USE_PYTHON3 = True
+
+# Well-defined simple classes containing only <= 4 ints, or <= 2 floats.
+BASE_TIME_TYPES = [
+ 'base::Time',
+ 'base::TimeDelta',
+ 'base::TimeTicks',
+]
+
+BASE_TIME_TYPES_RE = re.compile(r'\bconst (%s)&' % '|'.join(BASE_TIME_TYPES))
+
+def _FilterFile(affected_file):
+ """Return true if the file could contain code requiring a presubmit check."""
+ return affected_file.LocalPath().endswith(
+ ('.h', '.cc', '.cpp', '.cxx', '.mm'))
+
+
+def _CheckForUseOfWrongClock(input_api, output_api):
+ """Make sure new lines of media code don't use a clock susceptible to skew."""
+
+ # Regular expression that should detect any explicit references to the
+ # base::Time type (or base::Clock/DefaultClock), whether in using decls,
+ # typedefs, or to call static methods.
+ base_time_type_pattern = r'(^|\W)base::(Time|Clock|DefaultClock)(\W|$)'
+
+ # Regular expression that should detect references to the base::Time class
+ # members, such as a call to base::Time::Now.
+ base_time_member_pattern = r'(^|\W)(Time|Clock|DefaultClock)::'
+
+ # Regular expression to detect "using base::Time" declarations. We want to
+ # prevent these from triggerring a warning. For example, it's perfectly
+ # reasonable for code to be written like this:
+ #
+ # using base::Time;
+ # ...
+ # int64_t foo_us = foo_s * Time::kMicrosecondsPerSecond;
+ using_base_time_decl_pattern = r'^\s*using\s+(::)?base::Time\s*;'
+
+ # Regular expression to detect references to the kXXX constants in the
+ # base::Time class. We want to prevent these from triggerring a warning.
+ base_time_konstant_pattern = r'(^|\W)Time::k\w+'
+
+ problem_re = input_api.re.compile(
+ r'(' + base_time_type_pattern + r')|(' + base_time_member_pattern + r')')
+ exception_re = input_api.re.compile(
+ r'(' + using_base_time_decl_pattern + r')|(' +
+ base_time_konstant_pattern + r')')
+ problems = []
+ for f in input_api.AffectedSourceFiles(_FilterFile):
+ for line_number, line in f.ChangedContents():
+ if problem_re.search(line):
+ if not exception_re.search(line):
+ problems.append(
+ ' %s:%d\n %s' % (f.LocalPath(), line_number, line.strip()))
+
+ if problems:
+ return [output_api.PresubmitPromptOrNotify(
+ 'You added one or more references to the base::Time class and/or one\n'
+ 'of its member functions (or base::Clock/DefaultClock). In media\n'
+ 'code, it is rarely correct to use a clock susceptible to time skew!\n'
+ 'Instead, could you use base::TimeTicks to track the passage of\n'
+ 'real-world time?\n\n' +
+ '\n'.join(problems))]
+ else:
+ return []
+
+
+def _CheckForHistogramOffByOne(input_api, output_api):
+ """Make sure histogram enum maxes are used properly"""
+
+ # A general-purpose chunk of regex to match whitespace and/or comments
+ # that may be interspersed with the code we're interested in:
+ comment = r'/\*.*?\*/|//[^\n]*'
+ whitespace = r'(?:[\n\t ]|(?:' + comment + r'))*'
+
+ # The name is assumed to be a literal string.
+ histogram_name = r'"[^"]*"'
+
+ # This can be an arbitrary expression, so just ensure it isn't a ; to prevent
+ # matching past the end of this statement.
+ histogram_value = r'[^;]*'
+
+ # In parens so we can retrieve it for further checks.
+ histogram_max = r'([^;,]*)'
+
+ # This should match a uma histogram enumeration macro expression.
+ uma_macro_re = input_api.re.compile(
+ r'\bUMA_HISTOGRAM_ENUMERATION\(' + whitespace + histogram_name + r',' +
+ whitespace + histogram_value + r',' + whitespace + histogram_max +
+ whitespace + r'\)' + whitespace + r';(?:' + whitespace +
+ r'\/\/ (PRESUBMIT_IGNORE_UMA_MAX))?')
+
+ uma_max_re = input_api.re.compile(r'.*(?:Max|MAX).* \+ 1')
+
+ problems = []
+
+ for f in input_api.AffectedSourceFiles(_FilterFile):
+ contents = input_api.ReadFile(f)
+
+ # We want to match across lines, but still report a line number, so we keep
+ # track of the line we're on as we search through the file.
+ line_number = 1
+
+ # We search the entire file, then check if any violations are in the changed
+ # areas, this is inefficient, but simple. A UMA_HISTOGRAM_ENUMERATION call
+ # will often span multiple lines, so finding a match looking just at the
+ # deltas line-by-line won't catch problems.
+ match = uma_macro_re.search(contents)
+ while match:
+ line_number += contents.count('\n', 0, match.start())
+ max_arg = match.group(1) # The third argument.
+
+ if (not uma_max_re.match(max_arg) and match.group(2) !=
+ 'PRESUBMIT_IGNORE_UMA_MAX'):
+ uma_range = range(match.start(), match.end() + 1)
+ # Check if any part of the match is in the changed lines:
+ for num, line in f.ChangedContents():
+ if line_number <= num <= line_number + match.group().count('\n'):
+ problems.append('%s:%d' % (f, line_number))
+ break
+
+ # Strip off the file contents up to the end of the match and update the
+ # line number.
+ contents = contents[match.end():]
+ line_number += match.group().count('\n')
+ match = uma_macro_re.search(contents)
+
+ if problems:
+ return [output_api.PresubmitError(
+ 'UMA_HISTOGRAM_ENUMERATION reports in src/media/ are expected to adhere\n'
+ 'to the following guidelines:\n'
+ ' - The max value (3rd argument) should be an enum value equal to the\n'
+ ' last valid value, e.g. FOO_MAX = LAST_VALID_FOO.\n'
+ ' - 1 must be added to that max value.\n'
+ 'Contact dalecurtis@chromium.org if you have questions.' , problems)]
+
+ return []
+
+
+def _CheckPassByValue(input_api, output_api):
+ """Check that base::Time and derived classes are passed by value, and not by
+ const reference """
+
+ problems = []
+
+ for f in input_api.AffectedSourceFiles(_FilterFile):
+ for line_number, line in f.ChangedContents():
+ if BASE_TIME_TYPES_RE.search(line):
+ problems.append('%s:%d' % (f, line_number))
+
+ if problems:
+ return [output_api.PresubmitError(
+ 'base::Time and derived classes should be passed by value and not by\n'
+ 'const ref, see base/time/time.h for more information.', problems)]
+ return []
+
+
+def _CheckForUseOfLazyInstance(input_api, output_api):
+ """Check that base::LazyInstance is not used."""
+
+ problems = []
+
+ lazy_instance_re = re.compile(r'(^|\W)base::LazyInstance<')
+
+ for f in input_api.AffectedSourceFiles(_FilterFile):
+ for line_number, line in f.ChangedContents():
+ if lazy_instance_re.search(line):
+ problems.append('%s:%d' % (f, line_number))
+
+ if problems:
+ return [output_api.PresubmitError(
+ 'base::LazyInstance is deprecated; use a thread safe static.', problems)]
+ return []
+
+def _CheckNoLoggingOverrideInHeaders(input_api, output_api):
+ """Checks to make sure no .h files include logging_override_if_enabled.h."""
+ files = []
+ pattern = input_api.re.compile(
+ r'^#include\s*"media/base/logging_override_if_enabled.h"',
+ input_api.re.MULTILINE)
+ for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
+ if not f.LocalPath().endswith('.h'):
+ continue
+ contents = input_api.ReadFile(f)
+ if pattern.search(contents):
+ files.append(f)
+
+ if len(files):
+ return [output_api.PresubmitError(
+ 'Do not #include "logging_override_if_enabled.h" in header files, '
+ 'since it overrides DVLOG() in every file including the header. '
+ 'Instead, only include it in source files.',
+ files) ]
+ return []
+
+def _CheckForNoV4L2AggregateInitialization(input_api, output_api):
+ """Check that struct v4l2_* are not initialized as aggregates with a
+ braced-init-list"""
+
+ problems = []
+
+ v4l2_aggregate_initializer_re = re.compile(r'(^|\W)struct.+v4l2_.+=.+{+}+;')
+
+ for f in input_api.AffectedSourceFiles(_FilterFile):
+ for line_number, line in f.ChangedContents():
+ if v4l2_aggregate_initializer_re.search(line):
+ problems.append('%s:%d' % (f, line_number))
+
+ if problems:
+ return [output_api.PresubmitPromptWarning(
+ 'Avoid initializing V4L2 structures with braced-init-lists, i.e. as '
+ 'aggregates. V4L2 structs often contain unions of various sized members: '
+ 'when a union is initialized by aggregate initialization, only the first '
+ 'non-static member is initialized, leaving other members unitialized if '
+ 'they are larger. Use memset instead.',
+ problems)]
+ return []
+
+def _CheckChange(input_api, output_api):
+ results = []
+ results.extend(_CheckForUseOfWrongClock(input_api, output_api))
+ results.extend(_CheckPassByValue(input_api, output_api))
+ results.extend(_CheckForHistogramOffByOne(input_api, output_api))
+ results.extend(_CheckForUseOfLazyInstance(input_api, output_api))
+ results.extend(_CheckNoLoggingOverrideInHeaders(input_api, output_api))
+ results.extend(_CheckForNoV4L2AggregateInitialization(input_api, output_api))
+ return results
+
+
+def CheckChangeOnUpload(input_api, output_api):
+ return _CheckChange(input_api, output_api)
+
+
+def CheckChangeOnCommit(input_api, output_api):
+ return _CheckChange(input_api, output_api)
diff --git a/third_party/chromium/media/PRESUBMIT_test.py b/third_party/chromium/media/PRESUBMIT_test.py
new file mode 100755
index 0000000..80eabff
--- /dev/null
+++ b/third_party/chromium/media/PRESUBMIT_test.py
@@ -0,0 +1,174 @@
+#!/usr/bin/env python
+# 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.
+import os
+import re
+import unittest
+
+import PRESUBMIT
+
+class MockInputApi(object):
+ def __init__(self):
+ self.re = re
+ self.os_path = os.path
+ self.files = []
+ self.is_committing = False
+
+ def AffectedFiles(self):
+ return self.files
+
+ def AffectedSourceFiles(self, fn):
+ # we'll just pretend everything is a source file for the sake of simplicity
+ return self.files
+
+ def ReadFile(self, f):
+ return f.NewContents()
+
+
+class MockOutputApi(object):
+ class PresubmitResult(object):
+ def __init__(self, message, items=None, long_text=''):
+ self.message = message
+ self.items = items
+ self.long_text = long_text
+
+ class PresubmitError(PresubmitResult):
+ def __init__(self, message, items, long_text=''):
+ MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
+ self.type = 'error'
+
+ class PresubmitPromptWarning(PresubmitResult):
+ def __init__(self, message, items, long_text=''):
+ MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
+ self.type = 'warning'
+
+ class PresubmitNotifyResult(PresubmitResult):
+ def __init__(self, message, items, long_text=''):
+ MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
+ self.type = 'notify'
+
+ class PresubmitPromptOrNotify(PresubmitResult):
+ def __init__(self, message, items, long_text=''):
+ MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
+ self.type = 'promptOrNotify'
+
+
+class MockFile(object):
+ def __init__(self, local_path, new_contents):
+ self._local_path = local_path
+ self._new_contents = new_contents
+ self._changed_contents = [(i + 1, l) for i, l in enumerate(new_contents)]
+
+ def ChangedContents(self):
+ return self._changed_contents
+
+ def NewContents(self):
+ return self._new_contents
+
+ def LocalPath(self):
+ return self._local_path
+
+
+class MockChange(object):
+ def __init__(self, changed_files):
+ self._changed_files = changed_files
+
+ def LocalPaths(self):
+ return self._changed_files
+
+
+class HistogramOffByOneTest(unittest.TestCase):
+
+ # Take an input and make sure the problems found equals the expectation.
+ def simpleCheck(self, contents, expected_errors):
+ input_api = MockInputApi()
+ input_api.files.append(MockFile('test.cc', contents))
+ results = PRESUBMIT._CheckForHistogramOffByOne(input_api, MockOutputApi())
+ if expected_errors:
+ self.assertEqual(1, len(results))
+ self.assertEqual(expected_errors, len(results[0].items))
+ else:
+ self.assertEqual(0, len(results))
+
+ def testValid(self):
+ self.simpleCheck('UMA_HISTOGRAM_ENUMERATION("test", kFoo, kFooMax + 1);', 0)
+
+ def testValidComments(self):
+ self.simpleCheck('UMA_HISTOGRAM_ENUMERATION("test", /*...*/ kFoo, /*...*/'
+ 'kFooMax + 1);', 0)
+
+ def testValidMultiLine(self):
+ self.simpleCheck('UMA_HISTOGRAM_ENUMERATION("test",\n'
+ ' kFoo,\n'
+ ' kFooMax + 1);', 0)
+
+ def testValidMultiLineComments(self):
+ self.simpleCheck('UMA_HISTOGRAM_ENUMERATION("test", // This is the name\n'
+ ' kFoo, /* The value */\n'
+ ' kFooMax + 1 /* The max */ );',
+ 0)
+
+ def testNoPlusOne(self):
+ self.simpleCheck('UMA_HISTOGRAM_ENUMERATION("test", kFoo, kFooMax);', 1)
+
+ def testInvalidWithIgnore(self):
+ self.simpleCheck('UMA_HISTOGRAM_ENUMERATION("test", kFoo, kFooMax); '
+ '// PRESUBMIT_IGNORE_UMA_MAX', 0)
+
+ def testNoMax(self):
+ self.simpleCheck('UMA_HISTOGRAM_ENUMERATION("test", kFoo, kFoo + 1);', 1)
+
+ def testNoMaxNoPlusOne(self):
+ self.simpleCheck('UMA_HISTOGRAM_ENUMERATION("test", kFoo, kFoo);', 1)
+
+ def testMultipleErrors(self):
+ self.simpleCheck('UMA_HISTOGRAM_ENUMERATION("test", kFoo, kFoo);\n'
+ 'printf("hello, world!");\n'
+ 'UMA_HISTOGRAM_ENUMERATION("test", kBar, kBarMax);', 2)
+
+ def testValidAndInvalid(self):
+ self.simpleCheck('UMA_HISTOGRAM_ENUMERATION("test", kFoo, kFoo);\n'
+ 'UMA_HISTOGRAM_ENUMERATION("test", kFoo, kFooMax + 1);'
+ 'UMA_HISTOGRAM_ENUMERATION("test", kBar, kBarMax);', 2)
+
+ def testInvalidMultiLine(self):
+ self.simpleCheck('UMA_HISTOGRAM_ENUMERATION("test",\n'
+ ' kFoo,\n'
+ ' kFooMax + 2);', 1)
+
+ def testInvalidComments(self):
+ self.simpleCheck('UMA_HISTOGRAM_ENUMERATION("test", /*...*/, val, /*...*/,'
+ 'Max);\n', 1)
+
+ def testInvalidMultiLineComments(self):
+ self.simpleCheck('UMA_HISTOGRAM_ENUMERATION("test", // This is the name\n'
+ ' kFoo, /* The value */\n'
+ ' kFooMax + 2 /* The max */ );',
+ 1)
+
+class NoV4L2AggregateInitializationTest(unittest.TestCase):
+
+ def testValid(self):
+ self._testChange(['struct v4l2_format_ format;'], 0)
+
+ def testInvalid(self):
+ self._testChange(['struct v4l2_format format = {};'], 1)
+ self._testChange([' struct v4l2_format format = {};'], 1)
+ self._testChange([' struct std::vector<v4l2_format> format[] = {};'], 1)
+ self._testChange([' struct std::vector<v4l2_format> format[] = {{}};'], 1)
+
+ def _testChange(self, content, expected_warnings):
+ mock_input_api = MockInputApi()
+ mock_input_api.files.append(MockFile('test.cc', content))
+ results = PRESUBMIT._CheckForNoV4L2AggregateInitialization(mock_input_api,
+ MockOutputApi())
+ if expected_warnings:
+ self.assertEqual(1, len(results))
+ self.assertEqual(expected_warnings, len(results[0].items))
+ else:
+ self.assertEqual(0, len(results))
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/third_party/chromium/media/README.md b/third_party/chromium/media/README.md
new file mode 100644
index 0000000..eaf3d87
--- /dev/null
+++ b/third_party/chromium/media/README.md
@@ -0,0 +1,193 @@
+# media/
+
+Welcome to Chromium Media! This directory primarily contains a collection of
+components related to media capture and playback. Feel free to reach out to the
+media-dev@chromium.org mailing list with questions.
+
+As a top level component this may be depended on by almost every other Chromium
+component except base/. Certain components may not work properly in sandboxed
+processes.
+
+
+
+# Directory Breakdown
+
+* audio/ - Code for audio input and output. Includes platform specific output
+and input implementations. Due to use of platform APIs, can not normally be used
+from within a sandboxed process.
+
+* base/ - Contains miscellaneous enums, utility classes, and shuttling
+primitives used throughout `media/` and beyond; i.e. `AudioBus`, `AudioCodec`, and
+`VideoFrame` just to name a few. Can be used in any process.
+
+* blink/ - Code for interfacing with the Blink rendering engine for `MediaStreams`
+as well as `<video>` and `<audio>` playback. Used only in the same process as Blink;
+typically the render process.
+
+* capture/ - Contains content (as in the content layer) capturing and platform
+specific video capture implementations.
+
+* cast/ - Contains the tab casting implementation; not to be confused with the
+Chromecast code which lives in the top-level cast/ directory.
+
+* cdm/ - Contains code related to the Content Decryption Module (CDM) used for
+playback of content via Encrypted Media Extensions (EME).
+
+* device_monitors/ - Contains code for monitoring device changes; e.g. webcam
+and microphone plugin and unplug events.
+
+* ffmpeg/ - Contains binding code and helper methods necessary to use the ffmpeg
+library located in //third_party/ffmpeg.
+
+* filters/ - Contains data sources, decoders, demuxers, parsers, and rendering
+algorithms used for media playback.
+
+* formats/ - Contains parsers used by Media Source Extensions (MSE).
+
+* gpu/ - Contains the platform hardware encoder and decoder implementations.
+
+* midi/ - Contains the WebMIDI API implementation.
+
+* mojo/ - Contains mojo services for media. Typically used for providing out of
+process media functionality to a sandboxed process.
+
+* muxers/ - Code for muxing content for the Media Recorder API.
+
+* remoting/ - Code for transmitting muxed packets to a remote endpoint for
+playback.
+
+* renderers/ - Code for rendering audio and video to an output sink.
+
+* test/ - Code and data for testing the media playback pipeline.
+
+* tools/ - Standalone media test tools.
+
+* video/ - Abstract hardware video decoder interfaces and tooling.
+
+
+
+# Capture
+
+TODO(miu, chfemer): Fill in this section.
+
+
+
+# mojo
+
+See [media/mojo documentation](/media/mojo).
+
+
+
+# MIDI
+
+TODO(toyoshim): Fill in this section.
+
+
+
+# Playback
+
+Media playback encompasses a large swatch of technologies, so by necessity this
+will provide only a brief outline. Inside this directory you'll find components
+for media demuxing, software and hardware video decode, audio output, as well as
+audio and video rendering.
+
+Specifically under the playback heading, media/ contains the implementations of
+components required for HTML media elements and extensions:
+
+* [HTML5 Audio & Video](https://dev.w3.org/html5/spec-author-view/video.html)
+* [Media Source Extensions](https://www.w3.org/TR/media-source/)
+* [Encrypted Media Extensions](https://www.w3.org/TR/encrypted-media/)
+
+The following diagram provides a simplified overview of the media playback
+pipeline.
+
+
+
+As a case study we'll consider the playback of a video through the `<video>` tag.
+
+`<video>` (and `<audio>`) starts in `blink::HTMLMediaElement` in
+third_party/blink/ and reaches third_party/blink/public/platform/media/ in
+`media::WebMediaPlayerImpl` after a brief hop through `content::MediaFactory`.
+Each `blink::HTMLMediaElement` owns a `media::WebMediaPlayerImpl` for handling
+things like play, pause, seeks, and volume changes (among other things).
+
+`media::WebMediaPlayerImpl` handles or delegates media loading over the network
+as well as demuxer and pipeline initialization. `media::WebMediaPlayerImpl`
+owns a `media::PipelineController` which manages the coordination of a
+`media::DataSource`, `media::Demuxer`, and `media::Renderer` during playback.
+
+During a normal playback, the `media::Demuxer` owned by WebMediaPlayerImpl may
+be either `media::FFmpegDemuxer` or `media::ChunkDemuxer`. The ffmpeg variant
+is used for standard src= playback where WebMediaPlayerImpl is responsible for
+loading bytes over the network. `media::ChunkDemuxer` is used with Media Source
+Extensions (MSE), where JavaScript code provides the muxed bytes.
+
+The media::Renderer is typically `media::RendererImpl` which owns and
+coordinates `media::AudioRenderer` and `media::VideoRenderer` instances. Each
+of these in turn own a set of `media::AudioDecoder` and `media::VideoDecoder`
+implementations. Each issues an async read to a `media::DemuxerStream` exposed
+by the `media::Demuxer` which is routed to the right decoder by
+`media::DecoderStream`. Decoding is again async, so decoded frames are
+delivered at some later time to each renderer.
+
+The media/ library contains hardware decoder implementations in media/gpu for
+all supported Chromium platforms, as well as software decoding implementations
+in media/filters backed by FFmpeg and libvpx. Decoders are attempted in the
+order provided via the `media::RendererFactory`; the first one which reports
+success will be used for playback (typically the hardware decoder for video).
+
+Each renderer manages timing and rendering of audio and video via the event-
+driven `media::AudioRendererSink` and `media::VideoRendererSink` interfaces
+respectively. These interfaces both accept a callback that they will issue
+periodically when new audio or video frames are required.
+
+On the audio side, again in the normal case, the `media::AudioRendererSink` is
+driven via a `base::SyncSocket` and shared memory segment owned by the browser
+process. This socket is ticked periodically by a platform level implementation
+of `media::AudioOutputStream` within media/audio.
+
+On the video side, the `media::VideoRendererSink` is driven by async callbacks
+issued by the compositor to `media::VideoFrameCompositor`. The
+`media::VideoRenderer` will talk to the `media::AudioRenderer` through a
+`media::TimeSource` for coordinating audio and video sync.
+
+With that we've covered the basic flow of a typical playback. When debugging
+issues, it's helpful to review the internal logs at chrome://media-internals.
+The internals page contains information about active
+`media::WebMediaPlayerImpl`, `media::AudioInputController`,
+`media::AudioOutputController`, and `media::AudioOutputStream` instances.
+
+
+
+# Logging
+
+Media playback typically involves multiple threads, in many cases even multiple
+processes. Media operations are often asynchronous running in a sandbox. These
+make attaching a debugger (e.g. GDB) sometimes less efficient than other
+mechanisms like logging.
+
+## DVLOG
+
+In media we use DVLOG() a lot. It makes filename-based filtering super easy.
+Within one file, not all logs are created equal. To make log filtering
+more convenient, use appropriate log levels. Here are some general
+recommendations:
+
+* DVLOG(1): Once per playback events or other important events, e.g.
+ construction/destruction, initialization, playback start/end, suspend/resume,
+ any error conditions.
+* DVLOG(2): Recurring events per playback, e.g. seek/reset/flush, config change.
+* DVLOG(3): Frequent events, e.g. demuxer read, audio/video buffer decrypt or
+ decode, audio/video frame rendering.
+
+## MediaLog
+
+MediaLog will send logs to `about://media-internals`, which is easily accessible
+by developers (including web developes), testers and even users to get detailed
+information about a playback instance. For guidance on how to use MediaLog, see
+`media/base/media_log.h`.
+
+MediaLog messages should be concise and free of implementation details. Error
+messages should provide clues as to how to fix them, usually by precisely
+describing the circumstances that led to the error. Use properties, rather
+than messages, to record metadata and state changes.
diff --git a/third_party/chromium/media/audio/BUILD.gn b/third_party/chromium/media/audio/BUILD.gn
new file mode 100644
index 0000000..9c8628a
--- /dev/null
+++ b/third_party/chromium/media/audio/BUILD.gn
@@ -0,0 +1,483 @@
+# 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.
+
+import("//build/config/chromeos/ui_mode.gni")
+import("//build/config/linux/pkg_config.gni")
+import("//media/media_options.gni")
+import("//testing/libfuzzer/fuzzer_test.gni")
+import("//tools/generate_stubs/rules.gni")
+
+# When libpulse is not directly linked, use stubs to allow for dlopening of the
+# binary.
+if (use_pulseaudio && !link_pulseaudio) {
+ generate_stubs("libpulse_stubs") {
+ extra_header = "pulse/pulse_stub_header.fragment"
+ sigs = [ "pulse/pulse.sigs" ]
+ output_name = "pulse/pulse_stubs"
+ deps = [ "//base" ]
+ }
+}
+
+if (is_android) {
+ generate_stubs("aaudio_stubs") {
+ extra_header = "android/aaudio_stub_header.fragment"
+ sigs = [ "android/aaudio.sigs" ]
+ output_name = "android/aaudio_stubs"
+ deps = [ "//base" ]
+ }
+}
+
+config("platform_config") {
+ defines = []
+ if (use_alsa) {
+ defines += [ "USE_ALSA" ]
+ }
+}
+
+source_set("audio") {
+ # Do not expand the visibility here without double-checking with OWNERS, this
+ # is a roll-up target which is part of the //media component. Most other DEPs
+ # should be using //media and not directly DEP this roll-up target.
+ visibility = [
+ ":wav_audio_handler_fuzzer",
+ "//media",
+ "//media/renderers",
+
+ # TODO(dalecurtis): CoreAudioUtil::IsCoreAudioSupported() should probably
+ # move into media/base/win.
+ "//media/device_monitors",
+
+ # TODO(dalecurtis): Move android audio pieces into //media/audio.
+ "//media/base/android",
+ ]
+ sources = [
+ "agc_audio_stream.h",
+ "alive_checker.cc",
+ "alive_checker.h",
+ "audio_debug_file_writer.cc",
+ "audio_debug_file_writer.h",
+ "audio_debug_recording_helper.cc",
+ "audio_debug_recording_helper.h",
+ "audio_debug_recording_manager.cc",
+ "audio_debug_recording_manager.h",
+ "audio_debug_recording_session.h",
+ "audio_debug_recording_session_impl.cc",
+ "audio_debug_recording_session_impl.h",
+ "audio_device_description.cc",
+ "audio_device_description.h",
+ "audio_device_name.cc",
+ "audio_device_name.h",
+ "audio_device_thread.cc",
+ "audio_device_thread.h",
+ "audio_features.cc",
+ "audio_features.h",
+ "audio_input_delegate.cc",
+ "audio_input_delegate.h",
+ "audio_input_device.cc",
+ "audio_input_device.h",
+ "audio_input_ipc.cc",
+ "audio_input_ipc.h",
+ "audio_input_stream_data_interceptor.cc",
+ "audio_input_stream_data_interceptor.h",
+ "audio_io.h",
+ "audio_manager.cc",
+ "audio_manager.h",
+ "audio_manager_base.cc",
+ "audio_manager_base.h",
+ "audio_opus_encoder.cc",
+ "audio_opus_encoder.h",
+ "audio_output_delegate.cc",
+ "audio_output_delegate.h",
+ "audio_output_device.cc",
+ "audio_output_device.h",
+ "audio_output_device_thread_callback.cc",
+ "audio_output_device_thread_callback.h",
+ "audio_output_dispatcher.cc",
+ "audio_output_dispatcher.h",
+ "audio_output_dispatcher_impl.cc",
+ "audio_output_dispatcher_impl.h",
+ "audio_output_ipc.cc",
+ "audio_output_ipc.h",
+ "audio_output_proxy.cc",
+ "audio_output_proxy.h",
+ "audio_output_resampler.cc",
+ "audio_output_resampler.h",
+ "audio_output_stream_sink.cc",
+ "audio_output_stream_sink.h",
+ "audio_sink_parameters.cc",
+ "audio_sink_parameters.h",
+ "audio_source_diverter.h",
+ "audio_source_parameters.cc",
+ "audio_source_parameters.h",
+ "audio_system.cc",
+ "audio_system.h",
+ "audio_system_helper.cc",
+ "audio_system_helper.h",
+ "audio_system_impl.cc",
+ "audio_system_impl.h",
+ "audio_thread.h",
+ "audio_thread_hang_monitor.cc",
+ "audio_thread_hang_monitor.h",
+ "audio_thread_impl.cc",
+ "audio_thread_impl.h",
+ "clockless_audio_sink.cc",
+ "clockless_audio_sink.h",
+ "fake_audio_input_stream.cc",
+ "fake_audio_input_stream.h",
+ "fake_audio_log_factory.cc",
+ "fake_audio_log_factory.h",
+ "fake_audio_manager.cc",
+ "fake_audio_manager.h",
+ "fake_audio_output_stream.cc",
+ "fake_audio_output_stream.h",
+ "null_audio_sink.cc",
+ "null_audio_sink.h",
+ "power_observer_helper.cc",
+ "power_observer_helper.h",
+ "scoped_task_runner_observer.cc",
+ "scoped_task_runner_observer.h",
+ "simple_sources.cc",
+ "simple_sources.h",
+ "wav_audio_handler.cc",
+ "wav_audio_handler.h",
+ ]
+ deps = [
+ "//base",
+ "//build:chromecast_buildflags",
+ "//build:chromeos_buildflags",
+ "//media/base",
+ "//third_party/opus:opus",
+ "//url",
+ ]
+ libs = []
+ configs += [
+ ":platform_config",
+ "//media:subcomponent_config",
+ ]
+
+ if (is_mac) {
+ sources += [
+ "mac/audio_auhal_mac.cc",
+ "mac/audio_auhal_mac.h",
+ "mac/audio_device_listener_mac.cc",
+ "mac/audio_device_listener_mac.h",
+ "mac/audio_input_mac.cc",
+ "mac/audio_input_mac.h",
+ "mac/audio_low_latency_input_mac.cc",
+ "mac/audio_low_latency_input_mac.h",
+ "mac/audio_manager_mac.cc",
+ "mac/audio_manager_mac.h",
+ "mac/core_audio_util_mac.cc",
+ "mac/core_audio_util_mac.h",
+ "mac/coreaudio_dispatch_override.cc",
+ "mac/coreaudio_dispatch_override.h",
+ "mac/scoped_audio_unit.cc",
+ "mac/scoped_audio_unit.h",
+ ]
+ frameworks = [
+ "AudioToolbox.framework",
+ "AudioUnit.framework",
+ "CoreAudio.framework",
+ "CoreFoundation.framework",
+ ]
+ }
+
+ if (is_win) {
+ sources += [
+ "win/audio_device_listener_win.cc",
+ "win/audio_device_listener_win.h",
+ "win/audio_low_latency_input_win.cc",
+ "win/audio_low_latency_input_win.h",
+ "win/audio_low_latency_output_win.cc",
+ "win/audio_low_latency_output_win.h",
+ "win/audio_manager_win.cc",
+ "win/audio_manager_win.h",
+ "win/audio_session_event_listener_win.cc",
+ "win/audio_session_event_listener_win.h",
+ "win/avrt_wrapper_win.cc",
+ "win/avrt_wrapper_win.h",
+ "win/core_audio_util_win.cc",
+ "win/core_audio_util_win.h",
+ "win/device_enumeration_win.cc",
+ "win/device_enumeration_win.h",
+ "win/volume_range_util.cc",
+ "win/volume_range_util.h",
+ "win/waveout_output_win.cc",
+ "win/waveout_output_win.h",
+ ]
+
+ libs += [
+ "dxguid.lib",
+ "setupapi.lib",
+ "winmm.lib",
+ ]
+ }
+
+ if (is_android) {
+ sources += [
+ "android/aaudio_output.cc",
+ "android/aaudio_output.h",
+ "android/audio_manager_android.cc",
+ "android/audio_manager_android.h",
+ "android/audio_track_output_stream.cc",
+ "android/audio_track_output_stream.h",
+ "android/muteable_audio_output_stream.h",
+ "android/opensles_input.cc",
+ "android/opensles_input.h",
+ "android/opensles_output.cc",
+ "android/opensles_output.h",
+ "android/opensles_util.cc",
+ "android/opensles_util.h",
+ "android/opensles_wrapper.cc",
+ ]
+
+ deps += [
+ ":aaudio_stubs",
+ "//media/base/android:media_jni_headers",
+ ]
+ }
+
+ if (is_linux || is_chromeos) {
+ sources += [ "linux/audio_manager_linux.cc" ]
+ }
+
+ if (use_alsa) {
+ libs += [ "asound" ]
+ sources += [
+ "alsa/alsa_input.cc",
+ "alsa/alsa_input.h",
+ "alsa/alsa_output.cc",
+ "alsa/alsa_output.h",
+ "alsa/alsa_util.cc",
+ "alsa/alsa_util.h",
+ "alsa/alsa_wrapper.cc",
+ "alsa/alsa_wrapper.h",
+ "alsa/audio_manager_alsa.cc",
+ "alsa/audio_manager_alsa.h",
+ ]
+ }
+
+ if (use_cras) {
+ sources += [
+ "cras/audio_manager_cras_base.cc",
+ "cras/audio_manager_cras_base.h",
+ "cras/cras_input.cc",
+ "cras/cras_input.h",
+ "cras/cras_unified.cc",
+ "cras/cras_unified.h",
+ ]
+ configs += [ ":libcras" ]
+ if (is_chromeos_ash) {
+ sources += [
+ "cras/audio_manager_chromeos.cc",
+ "cras/audio_manager_chromeos.h",
+ ]
+ deps += [ "//ash/components/audio" ]
+ } else if (is_linux || is_chromeos_lacros) {
+ sources += [
+ "cras/audio_manager_cras.cc",
+ "cras/audio_manager_cras.h",
+ "cras/cras_util.cc",
+ "cras/cras_util.h",
+ ]
+ }
+ }
+
+ if (use_pulseaudio) {
+ sources += [
+ "pulse/audio_manager_pulse.cc",
+ "pulse/audio_manager_pulse.h",
+ "pulse/pulse_input.cc",
+ "pulse/pulse_input.h",
+ "pulse/pulse_output.cc",
+ "pulse/pulse_output.h",
+ "pulse/pulse_util.cc",
+ "pulse/pulse_util.h",
+ ]
+
+ deps += [ "//build:branding_buildflags" ]
+
+ if (link_pulseaudio) {
+ configs += [ ":libpulse" ]
+ } else {
+ deps += [ ":libpulse_stubs" ]
+ }
+ }
+
+ if (is_fuchsia) {
+ sources += [
+ "fuchsia/audio_manager_fuchsia.cc",
+ "fuchsia/audio_manager_fuchsia.h",
+ "fuchsia/audio_output_stream_fuchsia.cc",
+ "fuchsia/audio_output_stream_fuchsia.h",
+ ]
+ deps += [
+ "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.media",
+ "//third_party/fuchsia-sdk/sdk/pkg/sys_cpp",
+ ]
+ }
+}
+
+if (use_cras) {
+ pkg_config("libcras") {
+ packages = [ "libcras" ]
+ }
+}
+
+if (use_pulseaudio && link_pulseaudio) {
+ pkg_config("libpulse") {
+ packages = [ "libpulse" ]
+ }
+}
+
+# Note: This is a roll-up only target; do not expand the visibility. DEPS should
+# depend on the //media:test_support target instead.
+static_library("test_support") {
+ visibility = [ "//media:test_support" ]
+ testonly = true
+ sources = [
+ "audio_debug_recording_test.cc",
+ "audio_debug_recording_test.h",
+ "audio_device_info_accessor_for_tests.cc",
+ "audio_device_info_accessor_for_tests.h",
+ "audio_system_test_util.cc",
+ "audio_system_test_util.h",
+ "audio_unittest_util.cc",
+ "audio_unittest_util.h",
+ "mock_audio_debug_recording_manager.cc",
+ "mock_audio_debug_recording_manager.h",
+ "mock_audio_manager.cc",
+ "mock_audio_manager.h",
+ "mock_audio_source_callback.cc",
+ "mock_audio_source_callback.h",
+ "test_audio_thread.cc",
+ "test_audio_thread.h",
+ ]
+ configs += [
+ ":platform_config",
+ "//media:media_config",
+ ]
+ deps = [
+ "//base",
+ "//base/test:test_support",
+ "//build:chromeos_buildflags",
+
+ # Do not add any other //media deps except this; it will automatically pull
+ # a dep on //media which is required to ensure test_support targets all use
+ # the same //media component and not build a target's sources individually.
+ "//media/base:test_support",
+ "//testing/gmock",
+ "//testing/gtest",
+ ]
+ if (use_alsa) {
+ sources += [
+ "alsa/mock_alsa_wrapper.cc",
+ "alsa/mock_alsa_wrapper.h",
+ ]
+ }
+}
+
+source_set("unit_tests") {
+ testonly = true
+ sources = [
+ "alive_checker_unittest.cc",
+ "audio_debug_file_writer_unittest.cc",
+ "audio_debug_recording_helper_unittest.cc",
+ "audio_debug_recording_manager_unittest.cc",
+ "audio_debug_recording_session_impl_unittest.cc",
+ "audio_encoders_unittest.cc",
+ "audio_input_device_unittest.cc",
+ "audio_input_stream_data_interceptor_unittest.cc",
+ "audio_input_unittest.cc",
+ "audio_manager_unittest.cc",
+ "audio_output_device_unittest.cc",
+ "audio_output_proxy_unittest.cc",
+ "audio_output_unittest.cc",
+ "audio_system_impl_unittest.cc",
+ "audio_thread_hang_monitor_unittest.cc",
+ "power_observer_helper_unittest.cc",
+ "simple_sources_unittest.cc",
+ ]
+
+ deps = [
+ "//base",
+ "//base/test:test_support",
+ "//build:chromeos_buildflags",
+ "//media:test_support",
+ "//testing/gmock",
+ "//testing/gtest",
+ "//third_party/opus:opus",
+ "//url",
+ ]
+
+ configs += [
+ ":platform_config",
+ "//media:media_config",
+ ]
+
+ if (is_android) {
+ sources += [ "android/audio_android_unittest.cc" ]
+ deps += [ "//ui/gl" ]
+ }
+
+ if (is_mac) {
+ sources += [
+ "mac/audio_auhal_mac_unittest.cc",
+ "mac/audio_device_listener_mac_unittest.cc",
+ "mac/audio_low_latency_input_mac_unittest.cc",
+ ]
+ }
+
+ if (is_chromeos_ash || is_chromecast) {
+ sources += [
+ "test_data.h",
+ "wav_audio_handler_unittest.cc",
+ ]
+
+ if (!is_chromecast) {
+ deps += [
+ "//ash/components/audio",
+ "//chromeos/dbus/audio",
+ ]
+ }
+
+ if (use_cras) {
+ sources += [
+ "cras/audio_manager_chromeos_unittest.cc",
+ "cras/cras_input_unittest.cc",
+ "cras/cras_unified_unittest.cc",
+ ]
+ }
+ }
+
+ if (is_win) {
+ sources += [
+ "win/audio_device_listener_win_unittest.cc",
+ "win/audio_low_latency_input_win_unittest.cc",
+ "win/audio_low_latency_output_win_unittest.cc",
+ "win/audio_output_win_unittest.cc",
+ "win/audio_session_event_listener_win_unittest.cc",
+ "win/core_audio_util_win_unittest.cc",
+ "win/device_enumeration_win_unittest.cc",
+ "win/volume_range_util_unittest.cc",
+ ]
+ }
+
+ if (use_alsa) {
+ sources += [
+ "alsa/alsa_output_unittest.cc",
+ "alsa/alsa_util_unittest.cc",
+ "audio_low_latency_input_output_unittest.cc",
+ ]
+ }
+}
+
+fuzzer_test("wav_audio_handler_fuzzer") {
+ sources = [ "wav_audio_handler_fuzzer.cc" ]
+ deps = [
+ ":audio",
+ "//base",
+ "//media:test_support",
+ ]
+}
diff --git a/third_party/chromium/media/audio/DEPS b/third_party/chromium/media/audio/DEPS
new file mode 100644
index 0000000..e58d970
--- /dev/null
+++ b/third_party/chromium/media/audio/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+ash/components/audio",
+]
diff --git a/third_party/chromium/media/audio/DIR_METADATA b/third_party/chromium/media/audio/DIR_METADATA
new file mode 100644
index 0000000..e80d793
--- /dev/null
+++ b/third_party/chromium/media/audio/DIR_METADATA
@@ -0,0 +1,11 @@
+# Metadata information for this directory.
+#
+# For more information on DIR_METADATA files, see:
+# https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
+#
+# For the schema of this file, see Metadata message:
+# https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
+
+monorail {
+ component: "Internals>Media>Audio"
+}
\ No newline at end of file
diff --git a/third_party/chromium/media/audio/OWNERS b/third_party/chromium/media/audio/OWNERS
new file mode 100644
index 0000000..2dc6241
--- /dev/null
+++ b/third_party/chromium/media/audio/OWNERS
@@ -0,0 +1,9 @@
+tommi@chromium.org
+olka@chromium.org
+
+# Windows
+henrika@chromium.org
+
+# Mirroring (and related glue) OWNERS.
+jophba@chromium.org
+mfoltz@chromium.org
diff --git a/third_party/chromium/media/audio/agc_audio_stream.h b/third_party/chromium/media/audio/agc_audio_stream.h
new file mode 100644
index 0000000..41448c5
--- /dev/null
+++ b/third_party/chromium/media/audio/agc_audio_stream.h
@@ -0,0 +1,201 @@
+// Copyright (c) 2012 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 MEDIA_AUDIO_AGC_AUDIO_STREAM_H_
+#define MEDIA_AUDIO_AGC_AUDIO_STREAM_H_
+
+#include <atomic>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/threading/thread_checker.h"
+#include "base/timer/timer.h"
+#include "media/audio/audio_io.h"
+
+// The template based AgcAudioStream implements platform-independent parts
+// of the AudioInterface interface. Supported interfaces to pass as
+// AudioInterface are AudioIntputStream and AudioOutputStream. Each platform-
+// dependent implementation should derive from this class.
+//
+// Usage example (on Windows):
+//
+// class WASAPIAudioInputStream : public AgcAudioStream<AudioInputStream> {
+// public:
+// WASAPIAudioInputStream();
+// ...
+// };
+//
+// Call flow example:
+//
+// 1) User creates AgcAudioStream<AudioInputStream>
+// 2) User calls AudioInputStream::SetAutomaticGainControl(true) =>
+// AGC usage is now initialized but not yet started.
+// 3) User calls AudioInputStream::Start() => implementation calls
+// AgcAudioStream<AudioInputStream>::StartAgc() which detects that AGC
+// is enabled and then starts the periodic AGC timer.
+// 4) Microphone volume samples are now taken and included in all
+// AudioInputCallback::OnData() callbacks.
+// 5) User calls AudioInputStream::Stop() => implementation calls
+// AgcAudioStream<AudioInputStream>::StopAgc() which stops the timer.
+//
+// Note that, calling AudioInputStream::SetAutomaticGainControl(false) while
+// AGC measurements are active will not have an effect until StopAgc(),
+// StartAgc() are called again since SetAutomaticGainControl() only sets a
+// a state.
+//
+// Calling SetAutomaticGainControl(true) enables the AGC and StartAgc() starts
+// a periodic timer which calls QueryAndStoreNewMicrophoneVolume()
+// approximately once every second. QueryAndStoreNewMicrophoneVolume() asks
+// the actual microphone about its current volume level. This value is
+// normalized and stored so it can be read by GetAgcVolume() when the real-time
+// audio thread needs the value. The main idea behind this scheme is to avoid
+// accessing the audio hardware from the real-time audio thread and to ensure
+// that we don't take new microphone-level samples too often (~1 Hz is a
+// suitable compromise). The timer will be active until StopAgc() is called.
+//
+// This class should be created and destroyed on the audio manager thread and
+// a thread checker is added to ensure that this is the case (uses DCHECK).
+// All methods except GetAgcVolume() should be called on the creating thread
+// as well to ensure that thread safety is maintained. It will also guarantee
+// that the periodic timer runs on the audio manager thread.
+// |normalized_volume_|, which is updated by QueryAndStoreNewMicrophoneVolume()
+// and read in GetAgcVolume(), is atomic to ensure that it can be accessed from
+// any real-time audio thread that needs it to update the its AGC volume.
+
+namespace media {
+
+template <typename AudioInterface>
+class MEDIA_EXPORT AgcAudioStream : public AudioInterface {
+ public:
+ // Time between two successive timer events.
+ static const int kIntervalBetweenVolumeUpdatesMs = 1000;
+
+ AgcAudioStream()
+ : agc_is_enabled_(false), max_volume_(0.0), normalized_volume_(0.0) {
+ }
+
+ virtual ~AgcAudioStream() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ }
+
+ protected:
+ // Starts the periodic timer which periodically checks and updates the
+ // current microphone volume level.
+ // The timer is only started if AGC mode is first enabled using the
+ // SetAutomaticGainControl() method.
+ void StartAgc() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (!agc_is_enabled_ || timer_.IsRunning())
+ return;
+
+ max_volume_ = static_cast<AudioInterface*>(this)->GetMaxVolume();
+ if (max_volume_ <= 0) {
+ DLOG(WARNING) << "Failed to get max volume from hardware. Won't provide "
+ << "normalized volume.";
+ return;
+ }
+
+ // Query and cache the volume to avoid sending 0 as volume to AGC at the
+ // beginning of the audio stream, otherwise AGC will try to raise the
+ // volume from 0.
+ QueryAndStoreNewMicrophoneVolume();
+
+ timer_.Start(FROM_HERE, base::Milliseconds(kIntervalBetweenVolumeUpdatesMs),
+ this, &AgcAudioStream::QueryAndStoreNewMicrophoneVolume);
+ }
+
+ // Stops the periodic timer which periodically checks and updates the
+ // current microphone volume level.
+ void StopAgc() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (timer_.IsRunning())
+ timer_.Stop();
+ }
+
+ // Stores a new microphone volume level by checking the audio input device.
+ // Called on the audio manager thread.
+ void UpdateAgcVolume() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (!timer_.IsRunning())
+ return;
+
+ // We take new volume samples once every second when the AGC is enabled.
+ // To ensure that a new setting has an immediate effect, the new volume
+ // setting is cached here. It will ensure that the next OnData() callback
+ // will contain a new valid volume level. If this approach was not taken,
+ // we could report invalid volume levels to the client for a time period
+ // of up to one second.
+ QueryAndStoreNewMicrophoneVolume();
+ }
+
+ // Gets the latest stored volume level if AGC is enabled.
+ // Called at each capture callback on a real-time capture thread (platform
+ // dependent).
+ void GetAgcVolume(double* normalized_volume) {
+ *normalized_volume = normalized_volume_.load(std::memory_order_relaxed);
+ }
+
+ // Gets the current automatic gain control state.
+ bool GetAutomaticGainControl() override {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return agc_is_enabled_;
+ }
+
+ private:
+ // Sets the automatic gain control (AGC) to on or off. When AGC is enabled,
+ // the microphone volume is queried periodically and the volume level can
+ // be read in each AudioInputCallback::OnData() callback and fed to the
+ // render-side AGC. User must call StartAgc() as well to start measuring
+ // the microphone level.
+ bool SetAutomaticGainControl(bool enabled) override {
+ DVLOG(1) << "SetAutomaticGainControl(enabled=" << enabled << ")";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ agc_is_enabled_ = enabled;
+ return true;
+ }
+
+ // Takes a new microphone volume sample and stores it in |normalized_volume_|.
+ // Range is normalized to [0.0,1.0] or [0.0, 1.5] on Linux.
+ // This method is called periodically when AGC is enabled and always on the
+ // audio manager thread. We use it to read the current microphone level and
+ // to store it so it can be read by the main capture thread. By using this
+ // approach, we can avoid accessing audio hardware from a real-time audio
+ // thread and it leads to a more stable capture performance.
+ void QueryAndStoreNewMicrophoneVolume() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_GT(max_volume_, 0.0);
+
+ // Retrieve the current volume level by asking the audio hardware.
+ // Range is normalized to [0.0,1.0] or [0.0, 1.5] on Linux.
+ double normalized_volume =
+ static_cast<AudioInterface*>(this)->GetVolume() / max_volume_;
+ normalized_volume_.store(normalized_volume, std::memory_order_relaxed);
+ }
+
+ // Ensures that this class is created and destroyed on the same thread.
+ base::ThreadChecker thread_checker_;
+
+ // Repeating timer which cancels itself when it goes out of scope.
+ // Used to check the microphone volume periodically.
+ base::RepeatingTimer timer_;
+
+ // True when automatic gain control is enabled, false otherwise.
+ bool agc_is_enabled_;
+
+ // Stores the maximum volume which is used for normalization to a volume
+ // range of [0.0, 1.0].
+ double max_volume_;
+
+ // Contains last result of internal call to GetVolume(). We save resources
+ // by not querying the capture volume for each callback. The range is
+ // normalized to [0.0, 1.0].
+ std::atomic<double> normalized_volume_;
+
+ DISALLOW_COPY_AND_ASSIGN(AgcAudioStream<AudioInterface>);
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AGC_AUDIO_STREAM_H_
diff --git a/third_party/chromium/media/audio/alive_checker.cc b/third_party/chromium/media/audio/alive_checker.cc
new file mode 100644
index 0000000..8ca4e93
--- /dev/null
+++ b/third_party/chromium/media/audio/alive_checker.cc
@@ -0,0 +1,152 @@
+// Copyright 2017 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 "media/audio/alive_checker.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/threading/thread_task_runner_handle.h"
+
+namespace media {
+
+AliveChecker::AliveChecker(base::RepeatingClosure dead_callback,
+ base::TimeDelta check_interval,
+ base::TimeDelta timeout,
+ bool stop_at_first_alive_notification,
+ bool pause_check_during_suspend)
+ : AliveChecker(std::move(dead_callback),
+ check_interval,
+ timeout,
+ stop_at_first_alive_notification,
+ pause_check_during_suspend,
+ PowerObserverHelperFactoryCallback()) {}
+
+AliveChecker::AliveChecker(
+ base::RepeatingClosure dead_callback,
+ base::TimeDelta check_interval,
+ base::TimeDelta timeout,
+ bool stop_at_first_alive_notification,
+ PowerObserverHelperFactoryCallback power_observer_helper_factory_callback)
+ : AliveChecker(std::move(dead_callback),
+ check_interval,
+ timeout,
+ stop_at_first_alive_notification,
+ true,
+ std::move(power_observer_helper_factory_callback)) {}
+
+// The private constructor called by the above public constructors.
+AliveChecker::AliveChecker(
+ base::RepeatingClosure dead_callback,
+ base::TimeDelta check_interval,
+ base::TimeDelta timeout,
+ bool stop_at_first_alive_notification,
+ bool pause_check_during_suspend,
+ PowerObserverHelperFactoryCallback power_observer_helper_factory_callback)
+ : check_interval_(check_interval),
+ timeout_(timeout),
+ task_runner_(base::ThreadTaskRunnerHandle::Get()),
+ dead_callback_(std::move(dead_callback)),
+ stop_at_first_alive_notification_(stop_at_first_alive_notification) {
+ DCHECK(!dead_callback_.is_null());
+ DCHECK_GT(check_interval_, base::TimeDelta());
+ DCHECK_GT(timeout_, check_interval_);
+
+ if (pause_check_during_suspend) {
+ // When suspending, we don't need to take any action. When resuming, we
+ // reset |last_alive_notification_time_| to avoid false alarms.
+ // Unretained is safe since the PowerObserverHelper runs the callback on
+ // the task runner the AliveChecker (and consequently the
+ // PowerObserverHelper) is destroyed on.
+ if (power_observer_helper_factory_callback.is_null()) {
+ power_observer_ = std::make_unique<PowerObserverHelper>(
+ task_runner_, base::DoNothing(),
+ base::BindRepeating(
+ &AliveChecker::SetLastAliveNotificationTimeToNowOnTaskRunner,
+ base::Unretained(this)));
+ } else {
+ power_observer_ =
+ std::move(power_observer_helper_factory_callback)
+ .Run(task_runner_, base::DoNothing(),
+ base::BindRepeating(
+ &AliveChecker::
+ SetLastAliveNotificationTimeToNowOnTaskRunner,
+ base::Unretained(this)));
+ }
+ } else {
+ // If |pause_check_during_suspend| is false, we expect an empty factory
+ // callback.
+ DCHECK(power_observer_helper_factory_callback.is_null());
+ }
+}
+
+AliveChecker::~AliveChecker() {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+}
+
+void AliveChecker::Start() {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ SetLastAliveNotificationTimeToNowOnTaskRunner();
+ detected_dead_ = false;
+
+ DCHECK(!check_alive_timer_);
+ check_alive_timer_ = std::make_unique<base::RepeatingTimer>();
+ check_alive_timer_->Start(FROM_HERE, check_interval_, this,
+ &AliveChecker::CheckIfAlive);
+ DCHECK(check_alive_timer_->IsRunning());
+}
+
+void AliveChecker::Stop() {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ check_alive_timer_.reset();
+}
+
+bool AliveChecker::DetectedDead() {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ return detected_dead_;
+}
+
+void AliveChecker::NotifyAlive() {
+ if (!task_runner_->RunsTasksInCurrentSequence()) {
+ // We don't need high precision for setting |last_alive_notification_time_|
+ // so we don't have to care about the delay added with posting the task.
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&AliveChecker::NotifyAlive, weak_factory_.GetWeakPtr()));
+ return;
+ }
+
+ SetLastAliveNotificationTimeToNowOnTaskRunner();
+ if (stop_at_first_alive_notification_)
+ Stop();
+}
+
+void AliveChecker::CheckIfAlive() {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+ // The reason we check a flag instead of stopping the timer that runs this
+ // function at suspend is that it would require knowing what state we're in
+ // when resuming and maybe start the timer. Also, we would still need this
+ // flag anyway to maybe start the timer at stream creation.
+ // TODO(grunell): Suspend/resume notifications are not supported on Linux. We
+ // could possibly use wall clock time as a complement to be able to detect
+ // time jumps that probably are caused by suspend/resume.
+ if (power_observer_ && power_observer_->IsSuspending())
+ return;
+
+ if (base::TimeTicks::Now() - last_alive_notification_time_ > timeout_) {
+ Stop();
+ detected_dead_ = true;
+ dead_callback_.Run();
+ }
+}
+
+void AliveChecker::SetLastAliveNotificationTimeToNowOnTaskRunner() {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ last_alive_notification_time_ = base::TimeTicks::Now();
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/alive_checker.h b/third_party/chromium/media/audio/alive_checker.h
new file mode 100644
index 0000000..bea12bc
--- /dev/null
+++ b/third_party/chromium/media/audio/alive_checker.h
@@ -0,0 +1,146 @@
+// Copyright 2017 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 MEDIA_AUDIO_ALIVE_CHECKER_H_
+#define MEDIA_AUDIO_ALIVE_CHECKER_H_
+
+#include <memory>
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequenced_task_runner.h"
+#include "base/single_thread_task_runner.h"
+#include "base/timer/timer.h"
+#include "media/audio/power_observer_helper.h"
+#include "media/base/media_export.h"
+
+namespace media {
+
+// A class that checks if a client that is expected to have regular activity
+// is alive. For example, audio streams expect regular callbacks from the
+// operating system. The client informs regularly that it's alive by calling
+// NotifyAlive(). At a certain interval the AliveChecker checks that it has been
+// notified within a timeout period. If not, it runs a callback to inform about
+// detecting dead. The callback is run once and further checking is stopped at
+// detection. Checking can be restarted if desired.
+//
+// The AliveChecker can pause checking when the machine is suspending, i.e.
+// between suspend and resume notification from base::PowerMonitor. Checking
+// during this period can cause false positives. Shorter timeout gives higher
+// risk of false positives.
+//
+// It lives on the task runner it's created on; all functions except
+// NotifyAlive() must be called on it. NotifyAlive() can be called on any task
+// runner.
+//
+// It stops at the first NotifyAlive() call if
+// |stop_at_first_alive_notification| is specified at construction time. This
+// can be useful for example if the platform doesn't support suspend/resume
+// notifications, as Linux.
+class MEDIA_EXPORT AliveChecker {
+ public:
+ // Factory callback to create a PowerObserverHelper that can be injected. Can
+ // be used by tests to provide a mock.
+ using PowerObserverHelperFactoryCallback =
+ base::OnceCallback<std::unique_ptr<PowerObserverHelper>(
+ scoped_refptr<base::SequencedTaskRunner> task_runner,
+ base::RepeatingClosure suspend_callback,
+ base::RepeatingClosure resume_callback)>;
+
+ // See class description for general explanation of parameters.
+ // In addition the following must be true: |timeout| > |check_interval| > 0.
+ // The first version creates a PowerObserverHelper internally, the second
+ // version takes a factory callback to allow injecting a PowerObserverHelper,
+ // typically a mock for testing. The callback is run in the constructor. The
+ // second version doesn't have |pause_check_during_suspend|, since that's
+ // implicitly true when providing a PowerObserverHelper.
+ AliveChecker(base::RepeatingClosure dead_callback,
+ base::TimeDelta check_interval,
+ base::TimeDelta timeout,
+ bool stop_at_first_alive_notification,
+ bool pause_check_during_suspend);
+ AliveChecker(base::RepeatingClosure dead_callback,
+ base::TimeDelta check_interval,
+ base::TimeDelta timeout,
+ bool stop_at_first_alive_notification,
+ PowerObserverHelperFactoryCallback
+ power_observer_helper_factory_callback);
+
+ AliveChecker(const AliveChecker&) = delete;
+ AliveChecker& operator=(const AliveChecker&) = delete;
+
+ ~AliveChecker();
+
+ // Start and stop checking if the client is alive.
+ void Start();
+ void Stop();
+
+ // Returns whether dead was detected. Reset when Start() is called.
+ bool DetectedDead();
+
+ // Called regularly by the client to inform that it's alive. Can be called on
+ // any thread.
+ void NotifyAlive();
+
+ private:
+ // Internal version called by the public constructors, to keep the interface
+ // and contract clear in the public versions.
+ AliveChecker(base::RepeatingClosure dead_callback,
+ base::TimeDelta check_interval,
+ base::TimeDelta timeout,
+ bool stop_at_first_alive_notification,
+ bool pause_check_during_suspend,
+ PowerObserverHelperFactoryCallback
+ power_observer_helper_factory_callback);
+
+ // Checks if we have gotten an alive notification within a certain time
+ // period. If not, run |dead_callback_|.
+ void CheckIfAlive();
+
+ // Sets |last_alive_notification_time_| to the current time.
+ void SetLastAliveNotificationTimeToNowOnTaskRunner();
+
+ // Timer to run the check regularly.
+ std::unique_ptr<base::RepeatingTimer> check_alive_timer_;
+
+ // Stores the time NotifyAlive() was last called.
+ // TODO(grunell): Change from TimeTicks to Atomic32 and remove the task
+ // posting in NotifyAlive(). The Atomic32 variable would have to
+ // represent some time in seconds or tenths of seconds to be able to span over
+ // enough time. Atomic64 cannot be used since it's not supported on 32-bit
+ // platforms.
+ base::TimeTicks last_alive_notification_time_;
+
+ // The interval at which we check if alive.
+ const base::TimeDelta check_interval_;
+
+ // The time interval since |last_alive_notification_time_| after which we
+ // decide the client is dead and run |dead_callback_|.
+ const base::TimeDelta timeout_;
+
+ // Flags that dead was detected. Set in CheckIfAlive() if we have decided that
+ // the client is dead. Cleared in Start().
+ bool detected_dead_ = false;
+
+ // The task runner on which this object lives.
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+
+ // Dead notification callback.
+ base::RepeatingClosure dead_callback_;
+
+ // If true, checking stops after first alive notification, otherwise continues
+ // until Stop() is called or the client is decided to be dead.
+ const bool stop_at_first_alive_notification_;
+
+ // Used for getting suspend/resume notifications.
+ std::unique_ptr<PowerObserverHelper> power_observer_;
+
+ base::WeakPtrFactory<AliveChecker> weak_factory_{this};
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_ALIVE_CHECKER_H_
diff --git a/third_party/chromium/media/audio/alive_checker_unittest.cc b/third_party/chromium/media/audio/alive_checker_unittest.cc
new file mode 100644
index 0000000..729cf86
--- /dev/null
+++ b/third_party/chromium/media/audio/alive_checker_unittest.cc
@@ -0,0 +1,552 @@
+// Copyright 2017 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 <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/sequenced_task_runner.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/test/task_environment.h"
+#include "base/threading/thread.h"
+#include "media/audio/alive_checker.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+
+namespace {
+int kCheckIntervalMs = 10;
+int kNotifyIntervalMs = 7;
+int kTimeoutMs = 50;
+} // namespace
+
+class MockPowerObserverHelper : public PowerObserverHelper {
+ public:
+ MockPowerObserverHelper(scoped_refptr<base::SequencedTaskRunner> task_runner,
+ base::RepeatingClosure suspend_callback,
+ base::RepeatingClosure resume_callback)
+
+ : PowerObserverHelper(std::move(task_runner),
+ std::move(suspend_callback),
+ std::move(resume_callback)) {}
+
+ bool IsSuspending() const override {
+ DCHECK(TaskRunnerForTesting()->RunsTasksInCurrentSequence());
+ return is_suspending_;
+ }
+
+ void Suspend() {
+ DCHECK(TaskRunnerForTesting()->RunsTasksInCurrentSequence());
+ is_suspending_ = true;
+ SuspendCallbackForTesting()->Run();
+ }
+
+ void Resume() {
+ DCHECK(TaskRunnerForTesting()->RunsTasksInCurrentSequence());
+ is_suspending_ = false;
+ ResumeCallbackForTesting()->Run();
+ }
+
+ private:
+ bool is_suspending_ = false;
+};
+
+class AliveCheckerTest : public testing::Test {
+ public:
+ AliveCheckerTest()
+ : alive_checker_thread_("AliveCheckerThread"),
+ detected_dead_event_(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED) {
+ alive_checker_thread_.StartAndWaitForTesting();
+ }
+
+ void OnDetectedDead() {
+ EXPECT_TRUE(alive_checker_thread_.task_runner()->BelongsToCurrentThread());
+ detected_dead_event_.Signal();
+ }
+
+ std::unique_ptr<PowerObserverHelper> CreatePowerObserverHelper(
+ scoped_refptr<base::SequencedTaskRunner> task_runner,
+ base::RepeatingClosure suspend_callback,
+ base::RepeatingClosure resume_callback) {
+ std::unique_ptr<MockPowerObserverHelper> mock_power_observer_helper =
+ std::make_unique<MockPowerObserverHelper>(std::move(task_runner),
+ std::move(suspend_callback),
+ std::move(resume_callback));
+ mock_power_observer_helper_ = mock_power_observer_helper.get();
+ return mock_power_observer_helper;
+ }
+
+ protected:
+ ~AliveCheckerTest() override {
+ base::WaitableEvent done(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ alive_checker_thread_.task_runner()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&AliveCheckerTest::ResetAliveCheckerOnAliveCheckerThread,
+ base::Unretained(this), &done));
+ done.Wait();
+ }
+
+ void CreateAliveChecker(bool stop_at_first_alive_notification,
+ bool pause_check_during_suspend) {
+ base::WaitableEvent done(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ alive_checker_thread_.task_runner()->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ &AliveCheckerTest::CreateAliveCheckerOnAliveCheckerThread,
+ base::Unretained(this), stop_at_first_alive_notification,
+ pause_check_during_suspend, &done));
+ done.Wait();
+ }
+
+ void StartAliveChecker() {
+ alive_checker_thread_.task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&AliveChecker::Start,
+ base::Unretained(alive_checker_.get())));
+ }
+
+ void StopAliveChecker() {
+ alive_checker_thread_.task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&AliveChecker::Stop,
+ base::Unretained(alive_checker_.get())));
+ }
+
+ // Notifies |alive_checker_| that we're alive, and if
+ // |remaining_notifications| > 1, posts a delayed task to itself on
+ // |alive_checker_thread_| with |remaining_notifications| decreased by 1. Can
+ // be called on any task runner.
+ void NotifyAliveMultipleTimes(int remaining_notifications,
+ base::TimeDelta delay) {
+ alive_checker_->NotifyAlive();
+ if (remaining_notifications > 1) {
+ alive_checker_thread_.task_runner()->PostDelayedTask(
+ FROM_HERE,
+ base::BindOnce(&AliveCheckerTest::NotifyAliveMultipleTimes,
+ base::Unretained(this), remaining_notifications - 1,
+ delay),
+ delay);
+ }
+ }
+
+ void WaitUntilDetectedDead() {
+ detected_dead_event_.Wait();
+ detected_dead_event_.Reset();
+ }
+
+ // Returns true if the dead callback (AliveCheckerTest::OnDetectedDead) is run
+ // by the AliveChecker, false if timed out.
+ bool WaitUntilDetectedDeadWithTimeout(base::TimeDelta timeout) {
+ bool signaled = detected_dead_event_.TimedWait(timeout);
+ detected_dead_event_.Reset();
+ return signaled;
+ }
+
+ // Calls AliveChecker::DetectedDead() on the |alive_checker_thread_| and
+ // returns the result.
+ bool GetDetectedDead() {
+ bool detected_dead = false;
+ base::WaitableEvent done(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ alive_checker_thread_.task_runner()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&AliveCheckerTest::GetDetectedDeadOnAliveCheckerThread,
+ base::Unretained(this), &detected_dead, &done));
+ done.Wait();
+ return detected_dead;
+ }
+
+ // The test task environment.
+ base::test::TaskEnvironment task_environment_;
+
+ // The thread the checker is run on.
+ base::Thread alive_checker_thread_;
+
+ // AliveChecker under test.
+ std::unique_ptr<AliveChecker> alive_checker_;
+
+ // Mocks suspend status. Set in CreatePowerObserverHelper, owned by
+ // |alive_checker_|.
+ MockPowerObserverHelper* mock_power_observer_helper_;
+
+ private:
+ void CreateAliveCheckerOnAliveCheckerThread(
+ bool stop_at_first_alive_notification,
+ bool pause_check_during_suspend,
+ base::WaitableEvent* done) {
+ EXPECT_TRUE(alive_checker_thread_.task_runner()->BelongsToCurrentThread());
+
+ if (pause_check_during_suspend) {
+ alive_checker_ = std::make_unique<AliveChecker>(
+ base::BindRepeating(&AliveCheckerTest::OnDetectedDead,
+ base::Unretained(this)),
+ base::Milliseconds(kCheckIntervalMs), base::Milliseconds(kTimeoutMs),
+ stop_at_first_alive_notification,
+ base::BindOnce(&AliveCheckerTest::CreatePowerObserverHelper,
+ base::Unretained(this)));
+ } else {
+ alive_checker_ = std::make_unique<AliveChecker>(
+ base::BindRepeating(&AliveCheckerTest::OnDetectedDead,
+ base::Unretained(this)),
+ base::Milliseconds(kCheckIntervalMs), base::Milliseconds(kTimeoutMs),
+ stop_at_first_alive_notification, false);
+ }
+
+ done->Signal();
+ }
+
+ void GetDetectedDeadOnAliveCheckerThread(bool* detected_dead,
+ base::WaitableEvent* done) {
+ EXPECT_TRUE(alive_checker_thread_.task_runner()->BelongsToCurrentThread());
+ *detected_dead = alive_checker_->DetectedDead();
+ done->Signal();
+ }
+
+ void ResetAliveCheckerOnAliveCheckerThread(base::WaitableEvent* done) {
+ EXPECT_TRUE(alive_checker_thread_.task_runner()->BelongsToCurrentThread());
+ alive_checker_.reset();
+ done->Signal();
+ }
+
+ // Event to signal that we got a dead detection callback.
+ base::WaitableEvent detected_dead_event_;
+
+ DISALLOW_COPY_AND_ASSIGN(AliveCheckerTest);
+};
+
+// Start and Stop the checker, verify that we get no dead detection.
+// TODO(crbug.com/789804): Fix the test not to be flaky, e.g. by switching to
+// using a mocked clock, and re-enable it.
+TEST_F(AliveCheckerTest, DISABLED_StartStop) {
+ CreateAliveChecker(false, false);
+
+ StartAliveChecker();
+ EXPECT_FALSE(GetDetectedDead());
+
+ StopAliveChecker();
+ EXPECT_FALSE(GetDetectedDead());
+
+ // It can take up to the timeout + the check interval until detection. Add a
+ // margin to this.
+ EXPECT_FALSE(WaitUntilDetectedDeadWithTimeout(
+ base::Milliseconds(kTimeoutMs + kCheckIntervalMs + 10)));
+ EXPECT_FALSE(GetDetectedDead());
+}
+
+// Start the checker, don't send alive notifications, and run until it detects
+// dead. Verify that it only detects once. Repeat once.
+TEST_F(AliveCheckerTest, NoAliveNotificationsDetectTwice) {
+ CreateAliveChecker(false, false);
+
+ StartAliveChecker();
+ EXPECT_FALSE(GetDetectedDead());
+
+ WaitUntilDetectedDead();
+ EXPECT_TRUE(GetDetectedDead());
+
+ // Verify that AliveChecker doesn't detect (runs the callback) a second time.
+ // It can take up to the timeout + the check interval until detection. Add a
+ // margin to this. The detect state should still be that we have detected
+ // dead.
+ EXPECT_FALSE(WaitUntilDetectedDeadWithTimeout(
+ base::Milliseconds(kTimeoutMs + kCheckIntervalMs + 10)));
+ EXPECT_TRUE(GetDetectedDead());
+
+ // Start again, the detect state should be reset.
+ StartAliveChecker();
+ EXPECT_FALSE(GetDetectedDead());
+
+ WaitUntilDetectedDead();
+ EXPECT_TRUE(GetDetectedDead());
+}
+
+// Start the checker, notify that the client is alive several times, then stop
+// the checker. Verify that it doesn't detect dead.
+// TODO(crbug.com/789804): Fix the test not to be flaky, e.g. by switching to
+// using a mocked clock, and re-enable it.
+TEST_F(AliveCheckerTest, DISABLED_NotifyThenStop) {
+ CreateAliveChecker(false, false);
+
+ StartAliveChecker();
+ EXPECT_FALSE(GetDetectedDead());
+
+ NotifyAliveMultipleTimes(10, base::Milliseconds(kNotifyIntervalMs));
+ EXPECT_FALSE(GetDetectedDead());
+
+ StopAliveChecker();
+ EXPECT_FALSE(GetDetectedDead());
+
+ // It can take up to the timeout + the check interval until detection. Add a
+ // margin to this.
+ EXPECT_FALSE(WaitUntilDetectedDeadWithTimeout(
+ base::Milliseconds(kTimeoutMs + kCheckIntervalMs + 10)));
+ EXPECT_FALSE(GetDetectedDead());
+}
+
+// Start the checker, notify that the client is alive several times, then
+// run until detection. Repeat once.
+// TODO(crbug.com/789804): Fix the test not to be flaky, e.g. by switching to
+// using a mocked clock, and re-enable it.
+TEST_F(AliveCheckerTest, DISABLED_NotifyThenDetectDead) {
+ CreateAliveChecker(false, false);
+
+ StartAliveChecker();
+ NotifyAliveMultipleTimes(10, base::Milliseconds(kNotifyIntervalMs));
+ WaitUntilDetectedDead();
+ EXPECT_TRUE(GetDetectedDead());
+
+ StartAliveChecker();
+ EXPECT_FALSE(GetDetectedDead());
+ NotifyAliveMultipleTimes(10, base::Milliseconds(kNotifyIntervalMs));
+ EXPECT_FALSE(GetDetectedDead());
+ WaitUntilDetectedDead();
+ EXPECT_TRUE(GetDetectedDead());
+}
+
+// Setup the checker to stop at first alive notification. Start it and notify
+// that the client is alive once. Verify that we get no dead detection.
+TEST_F(AliveCheckerTest, StopAtFirstAliveNotification_DoNotify) {
+ CreateAliveChecker(true, false);
+
+ StartAliveChecker();
+ alive_checker_->NotifyAlive();
+
+ // It can take up to the timeout + the check interval until detection. Add a
+ // margin to this.
+ EXPECT_FALSE(WaitUntilDetectedDeadWithTimeout(
+ base::Milliseconds(kTimeoutMs + kCheckIntervalMs + 10)));
+ EXPECT_FALSE(GetDetectedDead());
+}
+
+// Setup the checker to stop at first alive notification. Start it and run until
+// it detects dead.
+TEST_F(AliveCheckerTest, StopAtFirstAliveNotification_DontNotify) {
+ CreateAliveChecker(true, false);
+ StartAliveChecker();
+ WaitUntilDetectedDead();
+ EXPECT_TRUE(GetDetectedDead());
+}
+
+// Setup the checker to pause checking when suspended. Start the checker, don't
+// send alive notifications, and run until it detects dead. Start it again and
+// notify that the client is alive several times. Suspend and verify that it
+// doesn't detect dead. Resume and run until detected dead.
+// TODO(crbug.com/789804): Fix the test not to be flaky, e.g. by switching to
+// using a mocked clock, and re-enable it.
+TEST_F(AliveCheckerTest, DISABLED_SuspendResume_StartBeforeSuspend) {
+ CreateAliveChecker(false, true);
+ ASSERT_TRUE(mock_power_observer_helper_);
+
+ StartAliveChecker();
+ WaitUntilDetectedDead();
+ EXPECT_TRUE(GetDetectedDead());
+
+ StartAliveChecker();
+ EXPECT_FALSE(GetDetectedDead());
+
+ NotifyAliveMultipleTimes(10, base::Milliseconds(kNotifyIntervalMs));
+
+ alive_checker_thread_.task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&MockPowerObserverHelper::Suspend,
+ base::Unretained(mock_power_observer_helper_)));
+
+ // It can take up to the timeout + the check interval until detection. Add a
+ // margin to this.
+ EXPECT_FALSE(WaitUntilDetectedDeadWithTimeout(
+ base::Milliseconds(kTimeoutMs + kCheckIntervalMs + 10)));
+ EXPECT_FALSE(GetDetectedDead());
+
+ alive_checker_thread_.task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&MockPowerObserverHelper::Resume,
+ base::Unretained(mock_power_observer_helper_)));
+
+ WaitUntilDetectedDead();
+ EXPECT_TRUE(GetDetectedDead());
+}
+
+// Setup the checker to pause checking when suspended. Suspend and verify that
+// it doesn't detect dead. Start the checker, don't send alive notifications,
+// and and verify that it doesn't detect dead. Resume and run until it detects
+// dead.
+TEST_F(AliveCheckerTest, SuspendResume_StartBetweenSuspendAndResume) {
+ CreateAliveChecker(false, true);
+ ASSERT_TRUE(mock_power_observer_helper_);
+
+ alive_checker_thread_.task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&MockPowerObserverHelper::Suspend,
+ base::Unretained(mock_power_observer_helper_)));
+
+ StartAliveChecker();
+
+ // It can take up to the timeout + the check interval until detection. Add a
+ // margin to this.
+ EXPECT_FALSE(WaitUntilDetectedDeadWithTimeout(
+ base::Milliseconds(kTimeoutMs + kCheckIntervalMs + 10)));
+ EXPECT_FALSE(GetDetectedDead());
+
+ alive_checker_thread_.task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&MockPowerObserverHelper::Resume,
+ base::Unretained(mock_power_observer_helper_)));
+
+ WaitUntilDetectedDead();
+ EXPECT_TRUE(GetDetectedDead());
+}
+
+// Setup the checker to stop at first alive notification and pause checking when
+// suspended. Start the checker, send one alive notifications, and verify it
+// doesn't detect dead. Suspend and verify that it doesn't detect dead. Resume
+// and and verify that it doesn't detect dead.
+TEST_F(AliveCheckerTest, SuspendResumeWithAutoStop_NotifyBeforeSuspend) {
+ CreateAliveChecker(true, true);
+ ASSERT_TRUE(mock_power_observer_helper_);
+
+ StartAliveChecker();
+ alive_checker_->NotifyAlive();
+
+ // It can take up to the timeout + the check interval until detection. Add a
+ // margin to this.
+ EXPECT_FALSE(WaitUntilDetectedDeadWithTimeout(
+ base::Milliseconds(kTimeoutMs + kCheckIntervalMs + 10)));
+ EXPECT_FALSE(GetDetectedDead());
+
+ alive_checker_thread_.task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&MockPowerObserverHelper::Suspend,
+ base::Unretained(mock_power_observer_helper_)));
+
+ EXPECT_FALSE(WaitUntilDetectedDeadWithTimeout(
+ base::Milliseconds(kTimeoutMs + kCheckIntervalMs + 10)));
+ EXPECT_FALSE(GetDetectedDead());
+
+ alive_checker_thread_.task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&MockPowerObserverHelper::Resume,
+ base::Unretained(mock_power_observer_helper_)));
+
+ EXPECT_FALSE(WaitUntilDetectedDeadWithTimeout(
+ base::Milliseconds(kTimeoutMs + kCheckIntervalMs + 10)));
+ EXPECT_FALSE(GetDetectedDead());
+}
+
+// Setup the checker to stop at first alive notification and pause checking when
+// suspended. Start the checker, send one alive notifications, and verify it
+// doesn't detect dead. Start it again, suspend and verify that it doesn't
+// detect dead. Resume and run until detected dead.
+TEST_F(AliveCheckerTest,
+ SuspendResumeWithAutoStop_NotifyBeforeSuspendAndRestart) {
+ CreateAliveChecker(true, true);
+ ASSERT_TRUE(mock_power_observer_helper_);
+
+ StartAliveChecker();
+ alive_checker_->NotifyAlive();
+
+ // It can take up to the timeout + the check interval until detection. Add a
+ // margin to this.
+ EXPECT_FALSE(WaitUntilDetectedDeadWithTimeout(
+ base::Milliseconds(kTimeoutMs + kCheckIntervalMs + 10)));
+ EXPECT_FALSE(GetDetectedDead());
+
+ StartAliveChecker();
+ EXPECT_FALSE(GetDetectedDead());
+
+ alive_checker_thread_.task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&MockPowerObserverHelper::Suspend,
+ base::Unretained(mock_power_observer_helper_)));
+
+ EXPECT_FALSE(WaitUntilDetectedDeadWithTimeout(
+ base::Milliseconds(kTimeoutMs + kCheckIntervalMs + 10)));
+ EXPECT_FALSE(GetDetectedDead());
+
+ alive_checker_thread_.task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&MockPowerObserverHelper::Resume,
+ base::Unretained(mock_power_observer_helper_)));
+
+ WaitUntilDetectedDead();
+ EXPECT_TRUE(GetDetectedDead());
+}
+
+// Setup the checker to stop at first alive notification and pause checking when
+// suspended. Start the checker, suspend. Send one alive notification and
+// verify it doesn't detected dead. Resume and verify it doesn't detected dead.
+TEST_F(AliveCheckerTest,
+ SuspendResumeWithAutoStop_NotifyBetweenSuspendAndResume) {
+ CreateAliveChecker(true, true);
+ ASSERT_TRUE(mock_power_observer_helper_);
+
+ StartAliveChecker();
+
+ alive_checker_thread_.task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&MockPowerObserverHelper::Suspend,
+ base::Unretained(mock_power_observer_helper_)));
+
+ alive_checker_->NotifyAlive();
+
+ // It can take up to the timeout + the check interval until detection. Add a
+ // margin to this.
+ EXPECT_FALSE(WaitUntilDetectedDeadWithTimeout(
+ base::Milliseconds(kTimeoutMs + kCheckIntervalMs + 10)));
+ EXPECT_FALSE(GetDetectedDead());
+
+ alive_checker_thread_.task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&MockPowerObserverHelper::Resume,
+ base::Unretained(mock_power_observer_helper_)));
+
+ EXPECT_FALSE(WaitUntilDetectedDeadWithTimeout(
+ base::Milliseconds(kTimeoutMs + kCheckIntervalMs + 10)));
+ EXPECT_FALSE(GetDetectedDead());
+}
+
+// Setup the checker to stop at first alive notification and pause checking when
+// suspended. Start the checker, suspend, resume, send one alive notification
+// and verify it doesn't detected dead.
+TEST_F(AliveCheckerTest, SuspendResumeWithAutoStop_NotifyAfterResume) {
+ CreateAliveChecker(true, true);
+ ASSERT_TRUE(mock_power_observer_helper_);
+
+ StartAliveChecker();
+
+ alive_checker_thread_.task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&MockPowerObserverHelper::Suspend,
+ base::Unretained(mock_power_observer_helper_)));
+
+ alive_checker_thread_.task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&MockPowerObserverHelper::Resume,
+ base::Unretained(mock_power_observer_helper_)));
+
+ alive_checker_->NotifyAlive();
+
+ // It can take up to the timeout + the check interval until detection. Add a
+ // margin to this.
+ EXPECT_FALSE(WaitUntilDetectedDeadWithTimeout(
+ base::Milliseconds(kTimeoutMs + kCheckIntervalMs + 10)));
+ EXPECT_FALSE(GetDetectedDead());
+}
+
+// Setup the checker to stop at first alive notification and pause checking when
+// suspended. Start the checker suspend, and and verify it doesn't detected
+// dead. Resume and run until it detects dead.
+TEST_F(AliveCheckerTest, SuspendResumeWithAutoStop_DontNotify) {
+ CreateAliveChecker(true, true);
+ ASSERT_TRUE(mock_power_observer_helper_);
+
+ StartAliveChecker();
+
+ alive_checker_thread_.task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&MockPowerObserverHelper::Suspend,
+ base::Unretained(mock_power_observer_helper_)));
+
+ // It can take up to the timeout + the check interval until detection. Add a
+ // margin to this.
+ EXPECT_FALSE(WaitUntilDetectedDeadWithTimeout(
+ base::Milliseconds(kTimeoutMs + kCheckIntervalMs + 10)));
+ EXPECT_FALSE(GetDetectedDead());
+
+ alive_checker_thread_.task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&MockPowerObserverHelper::Resume,
+ base::Unretained(mock_power_observer_helper_)));
+
+ WaitUntilDetectedDead();
+ EXPECT_TRUE(GetDetectedDead());
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/alsa/alsa_input.cc b/third_party/chromium/media/audio/alsa/alsa_input.cc
new file mode 100644
index 0000000..e4eaa0b
--- /dev/null
+++ b/third_party/chromium/media/audio/alsa/alsa_input.cc
@@ -0,0 +1,372 @@
+// Copyright 2013 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 "media/audio/alsa/alsa_input.h"
+
+#include <stddef.h>
+
+#include "base/bind.h"
+#include "base/cxx17_backports.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/single_thread_task_runner.h"
+#include "media/audio/alsa/alsa_output.h"
+#include "media/audio/alsa/alsa_util.h"
+#include "media/audio/alsa/alsa_wrapper.h"
+#include "media/audio/alsa/audio_manager_alsa.h"
+#include "media/audio/audio_manager.h"
+
+namespace media {
+
+static const SampleFormat kSampleFormat = kSampleFormatS16;
+static const snd_pcm_format_t kAlsaSampleFormat = SND_PCM_FORMAT_S16;
+
+static const int kNumPacketsInRingBuffer = 3;
+
+static const char kDefaultDevice1[] = "default";
+static const char kDefaultDevice2[] = "plug:default";
+
+const char AlsaPcmInputStream::kAutoSelectDevice[] = "";
+
+AlsaPcmInputStream::AlsaPcmInputStream(AudioManagerBase* audio_manager,
+ const std::string& device_name,
+ const AudioParameters& params,
+ AlsaWrapper* wrapper)
+ : audio_manager_(audio_manager),
+ device_name_(device_name),
+ params_(params),
+ bytes_per_buffer_(params.GetBytesPerBuffer(kSampleFormat)),
+ wrapper_(wrapper),
+ buffer_duration_(base::Microseconds(
+ params.frames_per_buffer() * base::Time::kMicrosecondsPerSecond /
+ static_cast<float>(params.sample_rate()))),
+ callback_(nullptr),
+ device_handle_(nullptr),
+ mixer_handle_(nullptr),
+ mixer_element_handle_(nullptr),
+ read_callback_behind_schedule_(false),
+ audio_bus_(AudioBus::Create(params)),
+ capture_thread_("AlsaInput"),
+ running_(false) {}
+
+AlsaPcmInputStream::~AlsaPcmInputStream() = default;
+
+AudioInputStream::OpenOutcome AlsaPcmInputStream::Open() {
+ if (device_handle_)
+ return OpenOutcome::kAlreadyOpen;
+
+ uint32_t packet_us = buffer_duration_.InMicroseconds();
+ uint32_t buffer_us = packet_us * kNumPacketsInRingBuffer;
+
+ // Use the same minimum required latency as output.
+ buffer_us = std::max(buffer_us, AlsaPcmOutputStream::kMinLatencyMicros);
+
+ if (device_name_ == kAutoSelectDevice) {
+ const char* device_names[] = { kDefaultDevice1, kDefaultDevice2 };
+ for (size_t i = 0; i < base::size(device_names); ++i) {
+ device_handle_ = alsa_util::OpenCaptureDevice(
+ wrapper_, device_names[i], params_.channels(), params_.sample_rate(),
+ kAlsaSampleFormat, buffer_us, packet_us);
+
+ if (device_handle_) {
+ device_name_ = device_names[i];
+ break;
+ }
+ }
+ } else {
+ device_handle_ = alsa_util::OpenCaptureDevice(
+ wrapper_, device_name_.c_str(), params_.channels(),
+ params_.sample_rate(), kAlsaSampleFormat, buffer_us, packet_us);
+ }
+
+ if (device_handle_) {
+ audio_buffer_.reset(new uint8_t[bytes_per_buffer_]);
+
+ // Open the microphone mixer.
+ mixer_handle_ = alsa_util::OpenMixer(wrapper_, device_name_);
+ if (mixer_handle_) {
+ mixer_element_handle_ = alsa_util::LoadCaptureMixerElement(
+ wrapper_, mixer_handle_);
+ }
+ }
+
+ return device_handle_ != nullptr ? OpenOutcome::kSuccess
+ : OpenOutcome::kFailed;
+}
+
+void AlsaPcmInputStream::Start(AudioInputCallback* callback) {
+ DCHECK(!callback_ && callback);
+ callback_ = callback;
+ StartAgc();
+ int error = wrapper_->PcmPrepare(device_handle_);
+ if (error < 0) {
+ HandleError("PcmPrepare", error);
+ } else {
+ error = wrapper_->PcmStart(device_handle_);
+ if (error < 0)
+ HandleError("PcmStart", error);
+ }
+
+ if (error < 0) {
+ callback_ = nullptr;
+ } else {
+ base::Thread::Options options;
+ options.priority = base::ThreadPriority::REALTIME_AUDIO;
+ CHECK(capture_thread_.StartWithOptions(std::move(options)));
+
+ // We start reading data half |buffer_duration_| later than when the
+ // buffer might have got filled, to accommodate some delays in the audio
+ // driver. This could also give us a smooth read sequence going forward.
+ base::TimeDelta delay = buffer_duration_ + buffer_duration_ / 2;
+ next_read_time_ = base::TimeTicks::Now() + delay;
+ running_ = true;
+ capture_thread_.task_runner()->PostDelayedTask(
+ FROM_HERE,
+ base::BindOnce(&AlsaPcmInputStream::ReadAudio, base::Unretained(this)),
+ delay);
+ }
+}
+
+bool AlsaPcmInputStream::Recover(int original_error) {
+ DCHECK(capture_thread_.task_runner()->BelongsToCurrentThread());
+ int error = wrapper_->PcmRecover(device_handle_, original_error, 1);
+ if (error < 0) {
+ // Docs say snd_pcm_recover returns the original error if it is not one
+ // of the recoverable ones, so this log message will probably contain the
+ // same error twice.
+ LOG(WARNING) << "Unable to recover from \""
+ << wrapper_->StrError(original_error) << "\": "
+ << wrapper_->StrError(error);
+ return false;
+ }
+
+ if (original_error == -EPIPE) { // Buffer underrun/overrun.
+ // For capture streams we have to repeat the explicit start() to get
+ // data flowing again.
+ error = wrapper_->PcmStart(device_handle_);
+ if (error < 0) {
+ HandleError("PcmStart", error);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void AlsaPcmInputStream::StopRunningOnCaptureThread() {
+ DCHECK(capture_thread_.IsRunning());
+ if (!capture_thread_.task_runner()->BelongsToCurrentThread()) {
+ capture_thread_.task_runner()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&AlsaPcmInputStream::StopRunningOnCaptureThread,
+ base::Unretained(this)));
+ return;
+ }
+ running_ = false;
+}
+
+void AlsaPcmInputStream::ReadAudio() {
+ DCHECK(capture_thread_.task_runner()->BelongsToCurrentThread());
+ DCHECK(callback_);
+ if (!running_)
+ return;
+
+ snd_pcm_sframes_t frames = wrapper_->PcmAvailUpdate(device_handle_);
+ if (frames < 0) { // Potentially recoverable error?
+ LOG(WARNING) << "PcmAvailUpdate(): " << wrapper_->StrError(frames);
+ Recover(frames);
+ }
+
+ if (frames < params_.frames_per_buffer()) {
+ // Not enough data yet or error happened. In both cases wait for a very
+ // small duration before checking again.
+ // Even Though read callback was behind schedule, there is no data, so
+ // reset the next_read_time_.
+ if (read_callback_behind_schedule_) {
+ next_read_time_ = base::TimeTicks::Now();
+ read_callback_behind_schedule_ = false;
+ }
+
+ base::TimeDelta next_check_time = buffer_duration_ / 2;
+ capture_thread_.task_runner()->PostDelayedTask(
+ FROM_HERE,
+ base::BindOnce(&AlsaPcmInputStream::ReadAudio, base::Unretained(this)),
+ next_check_time);
+ return;
+ }
+
+ // Update the AGC volume level once every second. Note that, |volume| is
+ // also updated each time SetVolume() is called through IPC by the
+ // render-side AGC.
+ double normalized_volume = 0.0;
+ GetAgcVolume(&normalized_volume);
+
+ int num_buffers = frames / params_.frames_per_buffer();
+ while (num_buffers--) {
+ int frames_read = wrapper_->PcmReadi(device_handle_, audio_buffer_.get(),
+ params_.frames_per_buffer());
+ if (frames_read == params_.frames_per_buffer()) {
+ audio_bus_->FromInterleaved<SignedInt16SampleTypeTraits>(
+ reinterpret_cast<int16_t*>(audio_buffer_.get()),
+ audio_bus_->frames());
+
+ // TODO(dalecurtis): This should probably use snd_pcm_htimestamp() so that
+ // we can have |capture_time| directly instead of computing it as
+ // Now() - available frames.
+ snd_pcm_sframes_t avail_frames = wrapper_->PcmAvailUpdate(device_handle_);
+ if (avail_frames < 0) {
+ LOG(WARNING) << "PcmAvailUpdate(): "
+ << wrapper_->StrError(avail_frames);
+ avail_frames = 0; // Error getting number of avail frames, set it to 0
+ }
+ base::TimeDelta hardware_delay = base::Seconds(
+ avail_frames / static_cast<double>(params_.sample_rate()));
+
+ callback_->OnData(audio_bus_.get(),
+ base::TimeTicks::Now() - hardware_delay,
+ normalized_volume);
+ } else if (frames_read < 0) {
+ bool success = Recover(frames_read);
+ LOG(WARNING) << "PcmReadi failed with error "
+ << wrapper_->StrError(frames_read) << ". "
+ << (success ? "Successfully" : "Unsuccessfully")
+ << " recovered.";
+ } else {
+ LOG(WARNING) << "PcmReadi returning less than expected frames: "
+ << frames_read << " vs. " << params_.frames_per_buffer()
+ << ". Dropping this buffer.";
+ }
+ }
+
+ next_read_time_ += buffer_duration_;
+ base::TimeDelta delay = next_read_time_ - base::TimeTicks::Now();
+ if (delay < base::TimeDelta()) {
+ DVLOG(1) << "Audio read callback behind schedule by "
+ << (buffer_duration_ - delay).InMicroseconds()
+ << " (us).";
+ // Read callback is behind schedule. Assuming there is data pending in
+ // the soundcard, invoke the read callback immediate in order to catch up.
+ read_callback_behind_schedule_ = true;
+ delay = base::TimeDelta();
+ }
+
+ capture_thread_.task_runner()->PostDelayedTask(
+ FROM_HERE,
+ base::BindOnce(&AlsaPcmInputStream::ReadAudio, base::Unretained(this)),
+ delay);
+}
+
+void AlsaPcmInputStream::Stop() {
+ if (!device_handle_ || !callback_)
+ return;
+
+ StopAgc();
+
+ StopRunningOnCaptureThread();
+ capture_thread_.Stop();
+ int error = wrapper_->PcmDrop(device_handle_);
+ if (error < 0)
+ HandleError("PcmDrop", error);
+
+ callback_ = nullptr;
+}
+
+void AlsaPcmInputStream::Close() {
+ if (device_handle_) {
+ Stop();
+ int error = alsa_util::CloseDevice(wrapper_, device_handle_);
+ if (error < 0)
+ HandleError("PcmClose", error);
+
+ if (mixer_handle_)
+ alsa_util::CloseMixer(wrapper_, mixer_handle_, device_name_);
+
+ audio_buffer_.reset();
+ device_handle_ = nullptr;
+ mixer_handle_ = nullptr;
+ mixer_element_handle_ = nullptr;
+ }
+
+ audio_manager_->ReleaseInputStream(this);
+}
+
+double AlsaPcmInputStream::GetMaxVolume() {
+ if (!mixer_handle_ || !mixer_element_handle_) {
+ DLOG(WARNING) << "GetMaxVolume is not supported for " << device_name_;
+ return 0.0;
+ }
+
+ if (!wrapper_->MixerSelemHasCaptureVolume(mixer_element_handle_)) {
+ DLOG(WARNING) << "Unsupported microphone volume for " << device_name_;
+ return 0.0;
+ }
+
+ long min = 0;
+ long max = 0;
+ if (wrapper_->MixerSelemGetCaptureVolumeRange(mixer_element_handle_,
+ &min,
+ &max)) {
+ DLOG(WARNING) << "Unsupported max microphone volume for " << device_name_;
+ return 0.0;
+ }
+ DCHECK(min == 0);
+ DCHECK(max > 0);
+
+ return static_cast<double>(max);
+}
+
+void AlsaPcmInputStream::SetVolume(double volume) {
+ if (!mixer_handle_ || !mixer_element_handle_) {
+ DLOG(WARNING) << "SetVolume is not supported for " << device_name_;
+ return;
+ }
+
+ int error = wrapper_->MixerSelemSetCaptureVolumeAll(
+ mixer_element_handle_, static_cast<long>(volume));
+ if (error < 0) {
+ DLOG(WARNING) << "Unable to set volume for " << device_name_;
+ }
+
+ // Update the AGC volume level based on the last setting above. Note that,
+ // the volume-level resolution is not infinite and it is therefore not
+ // possible to assume that the volume provided as input parameter can be
+ // used directly. Instead, a new query to the audio hardware is required.
+ // This method does nothing if AGC is disabled.
+ UpdateAgcVolume();
+}
+
+double AlsaPcmInputStream::GetVolume() {
+ if (!mixer_handle_ || !mixer_element_handle_) {
+ DLOG(WARNING) << "GetVolume is not supported for " << device_name_;
+ return 0.0;
+ }
+
+ long current_volume = 0;
+ int error = wrapper_->MixerSelemGetCaptureVolume(
+ mixer_element_handle_, static_cast<snd_mixer_selem_channel_id_t>(0),
+ ¤t_volume);
+ if (error < 0) {
+ DLOG(WARNING) << "Unable to get volume for " << device_name_;
+ return 0.0;
+ }
+
+ return static_cast<double>(current_volume);
+}
+
+bool AlsaPcmInputStream::IsMuted() {
+ return false;
+}
+
+void AlsaPcmInputStream::SetOutputDeviceForAec(
+ const std::string& output_device_id) {
+ // Not supported. Do nothing.
+}
+
+void AlsaPcmInputStream::HandleError(const char* method, int error) {
+ LOG(WARNING) << method << ": " << wrapper_->StrError(error);
+ if (callback_)
+ callback_->OnError();
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/alsa/alsa_input.h b/third_party/chromium/media/audio/alsa/alsa_input.h
new file mode 100644
index 0000000..f96b391
--- /dev/null
+++ b/third_party/chromium/media/audio/alsa/alsa_input.h
@@ -0,0 +1,102 @@
+// Copyright 2013 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 MEDIA_AUDIO_ALSA_ALSA_INPUT_H_
+#define MEDIA_AUDIO_ALSA_ALSA_INPUT_H_
+
+#include <alsa/asoundlib.h>
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread.h"
+#include "base/time/time.h"
+#include "media/audio/agc_audio_stream.h"
+#include "media/audio/audio_io.h"
+#include "media/base/audio_parameters.h"
+
+namespace media {
+
+class AlsaWrapper;
+class AudioManagerBase;
+
+// Provides an input stream for audio capture based on the ALSA PCM interface.
+// This object is not thread safe and all methods should be invoked in the
+// thread that created the object.
+class MEDIA_EXPORT AlsaPcmInputStream
+ : public AgcAudioStream<AudioInputStream> {
+ public:
+ // Pass this to the constructor if you want to attempt auto-selection
+ // of the audio recording device.
+ static const char kAutoSelectDevice[];
+
+ // Create a PCM Output stream for the ALSA device identified by
+ // |device_name|. If unsure of what to use for |device_name|, use
+ // |kAutoSelectDevice|.
+ AlsaPcmInputStream(AudioManagerBase* audio_manager,
+ const std::string& device_name,
+ const AudioParameters& params,
+ AlsaWrapper* wrapper);
+
+ AlsaPcmInputStream(const AlsaPcmInputStream&) = delete;
+ AlsaPcmInputStream& operator=(const AlsaPcmInputStream&) = delete;
+
+ ~AlsaPcmInputStream() override;
+
+ // Implementation of AudioInputStream.
+ OpenOutcome Open() override;
+ void Start(AudioInputCallback* callback) override;
+ void Stop() override;
+ void Close() override;
+ double GetMaxVolume() override;
+ void SetVolume(double volume) override;
+ double GetVolume() override;
+ bool IsMuted() override;
+ void SetOutputDeviceForAec(const std::string& output_device_id) override;
+
+ private:
+ // Logs the error and invokes any registered callbacks.
+ void HandleError(const char* method, int error);
+
+ // Reads one or more buffers of audio from the device, passes on to the
+ // registered callback and schedules the next read.
+ void ReadAudio();
+
+ // Recovers from any device errors if possible.
+ bool Recover(int error);
+
+ // Set |running_| to false on |capture_thread_|.
+ void StopRunningOnCaptureThread();
+
+ // Non-refcounted pointer back to the audio manager.
+ // The AudioManager indirectly holds on to stream objects, so we don't
+ // want circular references. Additionally, stream objects live on the audio
+ // thread, which is owned by the audio manager and we don't want to addref
+ // the manager from that thread.
+ AudioManagerBase* audio_manager_;
+ std::string device_name_;
+ AudioParameters params_;
+ int bytes_per_buffer_;
+ AlsaWrapper* wrapper_;
+ base::TimeDelta buffer_duration_; // Length of each recorded buffer.
+ AudioInputCallback* callback_; // Valid during a recording session.
+ base::TimeTicks next_read_time_; // Scheduled time for next read callback.
+ snd_pcm_t* device_handle_; // Handle to the ALSA PCM recording device.
+ snd_mixer_t* mixer_handle_; // Handle to the ALSA microphone mixer.
+ snd_mixer_elem_t* mixer_element_handle_; // Handle to the capture element.
+ // Buffer used for reading audio data.
+ std::unique_ptr<uint8_t[]> audio_buffer_;
+ bool read_callback_behind_schedule_;
+ std::unique_ptr<AudioBus> audio_bus_;
+ base::Thread capture_thread_;
+ bool running_;
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_ALSA_ALSA_INPUT_H_
diff --git a/third_party/chromium/media/audio/alsa/alsa_output.cc b/third_party/chromium/media/audio/alsa/alsa_output.cc
new file mode 100644
index 0000000..a05f60d
--- /dev/null
+++ b/third_party/chromium/media/audio/alsa/alsa_output.cc
@@ -0,0 +1,808 @@
+// Copyright 2013 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.
+//
+// THREAD SAFETY
+//
+// AlsaPcmOutputStream object is *not* thread-safe and should only be used
+// from the audio thread. We DCHECK on this assumption whenever we can.
+//
+// SEMANTICS OF Close()
+//
+// Close() is responsible for cleaning up any resources that were acquired after
+// a successful Open(). Close() will nullify any scheduled outstanding runnable
+// methods.
+//
+//
+// SEMANTICS OF ERROR STATES
+//
+// The object has two distinct error states: |state_| == kInError
+// and |stop_stream_|. The |stop_stream_| variable is used to indicate
+// that the playback_handle should no longer be used either because of a
+// hardware/low-level event.
+//
+// When |state_| == kInError, all public API functions will fail with an error
+// (Start() will call the OnError() function on the callback immediately), or
+// no-op themselves with the exception of Close(). Even if an error state has
+// been entered, if Open() has previously returned successfully, Close() must be
+// called to cleanup the ALSA devices and release resources.
+//
+// When |stop_stream_| is set, no more commands will be made against the
+// ALSA device, and playback will effectively stop. From the client's point of
+// view, it will seem that the device has just clogged and stopped requesting
+// data.
+
+#include "media/audio/alsa/alsa_output.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/memory/free_deleter.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/default_tick_clock.h"
+#include "base/trace_event/trace_event.h"
+#include "media/audio/alsa/alsa_util.h"
+#include "media/audio/alsa/alsa_wrapper.h"
+#include "media/audio/alsa/audio_manager_alsa.h"
+#include "media/base/audio_timestamp_helper.h"
+#include "media/base/channel_mixer.h"
+#include "media/base/data_buffer.h"
+#include "media/base/seekable_buffer.h"
+
+namespace media {
+
+// Set to 0 during debugging if you want error messages due to underrun
+// events or other recoverable errors.
+#if defined(NDEBUG)
+static const int kPcmRecoverIsSilent = 1;
+#else
+static const int kPcmRecoverIsSilent = 0;
+#endif
+
+// The output channel layout if we set up downmixing for the kDefaultDevice
+// device.
+static const ChannelLayout kDefaultOutputChannelLayout = CHANNEL_LAYOUT_STEREO;
+
+// While the "default" device may support multi-channel audio, in Alsa, only
+// the device names surround40, surround41, surround50, etc, have a defined
+// channel mapping according to Lennart:
+//
+// http://0pointer.de/blog/projects/guide-to-sound-apis.html
+//
+// This function makes a best guess at the specific > 2 channel device name
+// based on the number of channels requested. nullptr is returned if no device
+// can be found to match the channel numbers. In this case, using
+// kDefaultDevice is probably the best bet.
+//
+// A five channel source is assumed to be surround50 instead of surround41
+// (which is also 5 channels).
+//
+// TODO(ajwong): The source data should have enough info to tell us if we want
+// surround41 versus surround51, etc., instead of needing us to guess based on
+// channel number. Fix API to pass that data down.
+static const char* GuessSpecificDeviceName(uint32_t channels) {
+ switch (channels) {
+ case 8:
+ return "surround71";
+
+ case 7:
+ return "surround70";
+
+ case 6:
+ return "surround51";
+
+ case 5:
+ return "surround50";
+
+ case 4:
+ return "surround40";
+
+ default:
+ return nullptr;
+ }
+}
+
+std::ostream& operator<<(std::ostream& os,
+ AlsaPcmOutputStream::InternalState state) {
+ switch (state) {
+ case AlsaPcmOutputStream::kInError:
+ os << "kInError";
+ break;
+ case AlsaPcmOutputStream::kCreated:
+ os << "kCreated";
+ break;
+ case AlsaPcmOutputStream::kIsOpened:
+ os << "kIsOpened";
+ break;
+ case AlsaPcmOutputStream::kIsPlaying:
+ os << "kIsPlaying";
+ break;
+ case AlsaPcmOutputStream::kIsStopped:
+ os << "kIsStopped";
+ break;
+ case AlsaPcmOutputStream::kIsClosed:
+ os << "kIsClosed";
+ break;
+ }
+ return os;
+}
+
+static const SampleFormat kSampleFormat = kSampleFormatS16;
+static const snd_pcm_format_t kAlsaSampleFormat = SND_PCM_FORMAT_S16;
+
+const char AlsaPcmOutputStream::kDefaultDevice[] = "default";
+const char AlsaPcmOutputStream::kAutoSelectDevice[] = "";
+const char AlsaPcmOutputStream::kPlugPrefix[] = "plug:";
+
+// We use 40ms as our minimum required latency. If it is needed, we may be able
+// to get it down to 20ms.
+const uint32_t AlsaPcmOutputStream::kMinLatencyMicros = 40 * 1000;
+
+AlsaPcmOutputStream::AlsaPcmOutputStream(const std::string& device_name,
+ const AudioParameters& params,
+ AlsaWrapper* wrapper,
+ AudioManagerBase* manager)
+ : requested_device_name_(device_name),
+ pcm_format_(kAlsaSampleFormat),
+ channels_(params.channels()),
+ channel_layout_(params.channel_layout()),
+ sample_rate_(params.sample_rate()),
+ bytes_per_sample_(SampleFormatToBytesPerChannel(kSampleFormat)),
+ bytes_per_frame_(params.GetBytesPerFrame(kSampleFormat)),
+ packet_size_(params.GetBytesPerBuffer(kSampleFormat)),
+ latency_(std::max(
+ base::Microseconds(kMinLatencyMicros),
+ AudioTimestampHelper::FramesToTime(params.frames_per_buffer() * 2,
+ sample_rate_))),
+ bytes_per_output_frame_(bytes_per_frame_),
+ alsa_buffer_frames_(0),
+ stop_stream_(false),
+ wrapper_(wrapper),
+ manager_(manager),
+ task_runner_(base::ThreadTaskRunnerHandle::Get()),
+ playback_handle_(nullptr),
+ frames_per_packet_(packet_size_ / bytes_per_frame_),
+ state_(kCreated),
+ volume_(1.0f),
+ source_callback_(nullptr),
+ audio_bus_(AudioBus::Create(params)),
+ tick_clock_(base::DefaultTickClock::GetInstance()) {
+ DCHECK(manager_->GetTaskRunner()->BelongsToCurrentThread());
+ DCHECK_EQ(audio_bus_->frames() * bytes_per_frame_, packet_size_);
+
+ // Sanity check input values.
+ if (!params.IsValid()) {
+ LOG(WARNING) << "Unsupported audio parameters.";
+ TransitionTo(kInError);
+ }
+}
+
+AlsaPcmOutputStream::~AlsaPcmOutputStream() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ InternalState current_state = state();
+ DCHECK(current_state == kCreated ||
+ current_state == kIsClosed ||
+ current_state == kInError);
+ DCHECK(!playback_handle_);
+}
+
+bool AlsaPcmOutputStream::Open() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (state() == kInError)
+ return false;
+
+ if (!CanTransitionTo(kIsOpened)) {
+ NOTREACHED() << "Invalid state: " << state();
+ return false;
+ }
+
+ // We do not need to check if the transition was successful because
+ // CanTransitionTo() was checked above, and it is assumed that this
+ // object's public API is only called on one thread so the state cannot
+ // transition out from under us.
+ TransitionTo(kIsOpened);
+
+ // Try to open the device.
+ if (requested_device_name_ == kAutoSelectDevice) {
+ playback_handle_ = AutoSelectDevice(latency_.InMicroseconds());
+ if (playback_handle_)
+ DVLOG(1) << "Auto-selected device: " << device_name_;
+ } else {
+ device_name_ = requested_device_name_;
+ playback_handle_ = alsa_util::OpenPlaybackDevice(
+ wrapper_, device_name_.c_str(), channels_, sample_rate_,
+ pcm_format_, latency_.InMicroseconds());
+ }
+
+ // Finish initializing the stream if the device was opened successfully.
+ if (playback_handle_ == nullptr) {
+ stop_stream_ = true;
+ TransitionTo(kInError);
+ return false;
+ }
+ bytes_per_output_frame_ =
+ channel_mixer_ ? mixed_audio_bus_->channels() * bytes_per_sample_
+ : bytes_per_frame_;
+ uint32_t output_packet_size = frames_per_packet_ * bytes_per_output_frame_;
+ buffer_ = std::make_unique<SeekableBuffer>(0, output_packet_size);
+
+ // Get alsa buffer size.
+ snd_pcm_uframes_t buffer_size;
+ snd_pcm_uframes_t period_size;
+ int error =
+ wrapper_->PcmGetParams(playback_handle_, &buffer_size, &period_size);
+ if (error < 0) {
+ LOG(ERROR) << "Failed to get playback buffer size from ALSA: "
+ << wrapper_->StrError(error);
+ // Buffer size is at least twice of packet size.
+ alsa_buffer_frames_ = frames_per_packet_ * 2;
+ } else {
+ alsa_buffer_frames_ = buffer_size;
+ }
+
+ return true;
+}
+
+void AlsaPcmOutputStream::Close() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (state() != kIsClosed)
+ TransitionTo(kIsClosed);
+
+ // Shutdown the audio device.
+ if (playback_handle_) {
+ if (alsa_util::CloseDevice(wrapper_, playback_handle_) < 0) {
+ LOG(WARNING) << "Unable to close audio device. Leaking handle.";
+ }
+ playback_handle_ = nullptr;
+
+ // Release the buffer.
+ buffer_.reset();
+
+ // Signal anything that might already be scheduled to stop.
+ stop_stream_ = true; // Not necessary in production, but unit tests
+ // uses the flag to verify that stream was closed.
+ }
+
+ weak_factory_.InvalidateWeakPtrs();
+
+ // Signal to the manager that we're closed and can be removed.
+ // Should be last call in the method as it deletes "this".
+ manager_->ReleaseOutputStream(this);
+}
+
+void AlsaPcmOutputStream::Start(AudioSourceCallback* callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ CHECK(callback);
+
+ if (stop_stream_)
+ return;
+
+ // Only post the task if we can enter the playing state.
+ if (TransitionTo(kIsPlaying) != kIsPlaying)
+ return;
+
+ // Before starting, the buffer might have audio from previous user of this
+ // device.
+ buffer_->Clear();
+
+ // When starting again, drop all packets in the device and prepare it again
+ // in case we are restarting from a pause state and need to flush old data.
+ int error = wrapper_->PcmDrop(playback_handle_);
+ if (error < 0 && error != -EAGAIN) {
+ LOG(ERROR) << "Failure clearing playback device ("
+ << wrapper_->PcmName(playback_handle_) << "): "
+ << wrapper_->StrError(error);
+ stop_stream_ = true;
+ return;
+ }
+
+ error = wrapper_->PcmPrepare(playback_handle_);
+ if (error < 0 && error != -EAGAIN) {
+ LOG(ERROR) << "Failure preparing stream ("
+ << wrapper_->PcmName(playback_handle_) << "): "
+ << wrapper_->StrError(error);
+ stop_stream_ = true;
+ return;
+ }
+
+ // Ensure the first buffer is silence to avoid startup glitches.
+ int buffer_size = GetAvailableFrames() * bytes_per_output_frame_;
+ scoped_refptr<DataBuffer> silent_packet = new DataBuffer(buffer_size);
+ silent_packet->set_data_size(buffer_size);
+ memset(silent_packet->writable_data(), 0, silent_packet->data_size());
+ buffer_->Append(silent_packet);
+ WritePacket();
+
+ // Start the callback chain.
+ set_source_callback(callback);
+ WriteTask();
+}
+
+void AlsaPcmOutputStream::Stop() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ // Reset the callback, so that it is not called anymore.
+ set_source_callback(nullptr);
+ weak_factory_.InvalidateWeakPtrs();
+
+ TransitionTo(kIsStopped);
+}
+
+// This stream is always used with sub second buffer sizes, where it's
+// sufficient to simply always flush upon Start().
+void AlsaPcmOutputStream::Flush() {}
+
+void AlsaPcmOutputStream::SetVolume(double volume) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ volume_ = static_cast<float>(volume);
+}
+
+void AlsaPcmOutputStream::GetVolume(double* volume) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ *volume = volume_;
+}
+
+void AlsaPcmOutputStream::SetTickClockForTesting(
+ const base::TickClock* tick_clock) {
+ DCHECK(tick_clock);
+ tick_clock_ = tick_clock;
+}
+
+void AlsaPcmOutputStream::BufferPacket(bool* source_exhausted) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ // If stopped, simulate a 0-length packet.
+ if (stop_stream_) {
+ buffer_->Clear();
+ *source_exhausted = true;
+ return;
+ }
+
+ *source_exhausted = false;
+
+ // Request more data only when we run out of data in the buffer, because
+ // WritePacket() consumes only the current chunk of data.
+ if (!buffer_->forward_bytes()) {
+ // Before making a request to source for data we need to determine the
+ // delay for the requested data to be played.
+ const base::TimeDelta delay =
+ AudioTimestampHelper::FramesToTime(GetCurrentDelay(), sample_rate_);
+
+ scoped_refptr<DataBuffer> packet = new DataBuffer(packet_size_);
+ int frames_filled =
+ RunDataCallback(delay, tick_clock_->NowTicks(), audio_bus_.get());
+
+ size_t packet_size = frames_filled * bytes_per_frame_;
+ DCHECK_LE(packet_size, packet_size_);
+
+ // TODO(dalecurtis): Channel downmixing, upmixing, should be done in mixer;
+ // volume adjust should use SSE optimized vector_fmul() prior to interleave.
+ AudioBus* output_bus = audio_bus_.get();
+ ChannelLayout output_channel_layout = channel_layout_;
+ if (channel_mixer_) {
+ output_bus = mixed_audio_bus_.get();
+ channel_mixer_->Transform(audio_bus_.get(), output_bus);
+ output_channel_layout = kDefaultOutputChannelLayout;
+ // Adjust packet size for downmix.
+ packet_size = packet_size / bytes_per_frame_ * bytes_per_output_frame_;
+ }
+
+ // Reorder channels for 5.0, 5.1, and 7.1 to match ALSA's channel order,
+ // which has front center at channel index 4 and LFE at channel index 5.
+ // See http://ffmpeg.org/pipermail/ffmpeg-cvslog/2011-June/038454.html.
+ switch (output_channel_layout) {
+ case CHANNEL_LAYOUT_5_0:
+ case CHANNEL_LAYOUT_5_0_BACK:
+ output_bus->SwapChannels(2, 3);
+ output_bus->SwapChannels(3, 4);
+ break;
+ case CHANNEL_LAYOUT_5_1:
+ case CHANNEL_LAYOUT_5_1_BACK:
+ case CHANNEL_LAYOUT_7_1:
+ output_bus->SwapChannels(2, 4);
+ output_bus->SwapChannels(3, 5);
+ break;
+ default:
+ break;
+ }
+
+ // Note: If this ever changes to output raw float the data must be clipped
+ // and sanitized since it may come from an untrusted source such as NaCl.
+ output_bus->Scale(volume_);
+ output_bus->ToInterleaved<SignedInt16SampleTypeTraits>(
+ frames_filled, reinterpret_cast<int16_t*>(packet->writable_data()));
+
+ if (packet_size > 0) {
+ packet->set_data_size(packet_size);
+ // Add the packet to the buffer.
+ buffer_->Append(packet);
+ } else {
+ *source_exhausted = true;
+ }
+ }
+}
+
+void AlsaPcmOutputStream::WritePacket() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ // If the device is in error, just eat the bytes.
+ if (stop_stream_) {
+ buffer_->Clear();
+ return;
+ }
+
+ if (state() != kIsPlaying)
+ return;
+
+ CHECK_EQ(buffer_->forward_bytes() % bytes_per_output_frame_, 0u);
+
+ const uint8_t* buffer_data;
+ int buffer_size;
+ if (buffer_->GetCurrentChunk(&buffer_data, &buffer_size)) {
+ snd_pcm_sframes_t frames = std::min(
+ static_cast<snd_pcm_sframes_t>(buffer_size / bytes_per_output_frame_),
+ GetAvailableFrames());
+
+ if (!frames)
+ return;
+
+ snd_pcm_sframes_t frames_written =
+ wrapper_->PcmWritei(playback_handle_, buffer_data, frames);
+ if (frames_written < 0) {
+ // Attempt once to immediately recover from EINTR,
+ // EPIPE (overrun/underrun), ESTRPIPE (stream suspended). WritePacket
+ // will eventually be called again, so eventual recovery will happen if
+ // muliple retries are required.
+ frames_written = wrapper_->PcmRecover(playback_handle_,
+ frames_written,
+ kPcmRecoverIsSilent);
+ if (frames_written < 0) {
+ if (frames_written != -EAGAIN) {
+ LOG(ERROR) << "Failed to write to pcm device: "
+ << wrapper_->StrError(frames_written);
+ RunErrorCallback(frames_written);
+ stop_stream_ = true;
+ }
+ }
+ } else {
+ DCHECK_EQ(frames_written, frames);
+
+ // Seek forward in the buffer after we've written some data to ALSA.
+ buffer_->Seek(frames_written * bytes_per_output_frame_);
+ }
+ } else {
+ // If nothing left to write and playback hasn't started yet, start it now.
+ // This ensures that shorter sounds will still play.
+ if (playback_handle_ &&
+ (wrapper_->PcmState(playback_handle_) == SND_PCM_STATE_PREPARED) &&
+ GetCurrentDelay() > 0) {
+ wrapper_->PcmStart(playback_handle_);
+ }
+ }
+}
+
+void AlsaPcmOutputStream::WriteTask() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (stop_stream_)
+ return;
+
+ if (state() == kIsStopped)
+ return;
+
+ bool source_exhausted;
+ BufferPacket(&source_exhausted);
+ WritePacket();
+
+ ScheduleNextWrite(source_exhausted);
+}
+
+void AlsaPcmOutputStream::ScheduleNextWrite(bool source_exhausted) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (stop_stream_ || state() != kIsPlaying)
+ return;
+
+ const uint32_t kTargetFramesAvailable = alsa_buffer_frames_ / 2;
+ uint32_t available_frames = GetAvailableFrames();
+
+ base::TimeDelta next_fill_time;
+ if (buffer_->forward_bytes() && available_frames) {
+ // If we've got data available and ALSA has room, deliver it immediately.
+ next_fill_time = base::TimeDelta();
+ } else if (buffer_->forward_bytes()) {
+ // If we've got data available and no room, poll until room is available.
+ // Polling in this manner allows us to ensure a more consistent callback
+ // schedule. In testing this yields a variance of +/- 5ms versus the non-
+ // polling strategy which is around +/- 30ms and bimodal.
+ next_fill_time = base::Milliseconds(5);
+ } else if (available_frames < kTargetFramesAvailable) {
+ // Schedule the next write for the moment when the available buffer of the
+ // sound card hits |kTargetFramesAvailable|.
+ next_fill_time = AudioTimestampHelper::FramesToTime(
+ kTargetFramesAvailable - available_frames, sample_rate_);
+ } else if (!source_exhausted) {
+ // The sound card has |kTargetFramesAvailable| or more frames available.
+ // Invoke the next write immediately to avoid underrun.
+ next_fill_time = base::TimeDelta();
+ } else {
+ // The sound card has frames available, but our source is exhausted, so
+ // avoid busy looping by delaying a bit.
+ next_fill_time = base::Milliseconds(10);
+ }
+
+ task_runner_->PostDelayedTask(FROM_HERE,
+ base::BindOnce(&AlsaPcmOutputStream::WriteTask,
+ weak_factory_.GetWeakPtr()),
+ next_fill_time);
+}
+
+std::string AlsaPcmOutputStream::FindDeviceForChannels(uint32_t channels) {
+ // Constants specified by the ALSA API for device hints.
+ static const int kGetAllDevices = -1;
+ static const char kPcmInterfaceName[] = "pcm";
+ static const char kIoHintName[] = "IOID";
+ static const char kNameHintName[] = "NAME";
+
+ const char* wanted_device = GuessSpecificDeviceName(channels);
+ if (!wanted_device)
+ return std::string();
+
+ std::string guessed_device;
+ void** hints = nullptr;
+ int error = wrapper_->DeviceNameHint(kGetAllDevices,
+ kPcmInterfaceName,
+ &hints);
+ if (error == 0) {
+ // NOTE: Do not early return from inside this if statement. The
+ // hints above need to be freed.
+ for (void** hint_iter = hints; *hint_iter != nullptr; hint_iter++) {
+ // Only examine devices that are output capable.. Valid values are
+ // "Input", "Output", and nullptr which means both input and output.
+ std::unique_ptr<char, base::FreeDeleter> io(
+ wrapper_->DeviceNameGetHint(*hint_iter, kIoHintName));
+ if (io != nullptr && strcmp(io.get(), "Input") == 0)
+ continue;
+
+ // Attempt to select the closest device for number of channels.
+ std::unique_ptr<char, base::FreeDeleter> name(
+ wrapper_->DeviceNameGetHint(*hint_iter, kNameHintName));
+ if (strncmp(wanted_device, name.get(), strlen(wanted_device)) == 0) {
+ guessed_device = name.get();
+ break;
+ }
+ }
+
+ // Destroy the hint now that we're done with it.
+ wrapper_->DeviceNameFreeHint(hints);
+ hints = nullptr;
+ } else {
+ LOG(ERROR) << "Unable to get hints for devices: "
+ << wrapper_->StrError(error);
+ }
+
+ return guessed_device;
+}
+
+snd_pcm_sframes_t AlsaPcmOutputStream::GetCurrentDelay() {
+ snd_pcm_sframes_t delay = -1;
+ // Don't query ALSA's delay if we have underrun since it'll be jammed at some
+ // non-zero value and potentially even negative!
+ //
+ // Also, if we're in the prepared state, don't query because that seems to
+ // cause an I/O error when we do query the delay.
+ snd_pcm_state_t pcm_state = wrapper_->PcmState(playback_handle_);
+ if (pcm_state != SND_PCM_STATE_XRUN &&
+ pcm_state != SND_PCM_STATE_PREPARED) {
+ int error = wrapper_->PcmDelay(playback_handle_, &delay);
+ if (error < 0) {
+ // Assume a delay of zero and attempt to recover the device.
+ delay = -1;
+ error = wrapper_->PcmRecover(playback_handle_,
+ error,
+ kPcmRecoverIsSilent);
+ if (error < 0) {
+ LOG(ERROR) << "Failed querying delay: " << wrapper_->StrError(error);
+ }
+ }
+ }
+
+ // snd_pcm_delay() sometimes returns crazy values. In this case return delay
+ // of data we know currently is in ALSA's buffer. Note: When the underlying
+ // driver is PulseAudio based, certain configuration settings (e.g., tsched=1)
+ // will generate much larger delay values than |alsa_buffer_frames_|, so only
+ // clip if delay is truly crazy (> 10x expected).
+ if (delay < 0 ||
+ static_cast<snd_pcm_uframes_t>(delay) > alsa_buffer_frames_ * 10) {
+ delay = alsa_buffer_frames_ - GetAvailableFrames();
+ }
+
+ if (delay < 0) {
+ delay = 0;
+ }
+
+ return delay;
+}
+
+snd_pcm_sframes_t AlsaPcmOutputStream::GetAvailableFrames() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (stop_stream_)
+ return 0;
+
+ // Find the number of frames queued in the sound device.
+ snd_pcm_sframes_t available_frames =
+ wrapper_->PcmAvailUpdate(playback_handle_);
+ if (available_frames < 0) {
+ available_frames = wrapper_->PcmRecover(playback_handle_,
+ available_frames,
+ kPcmRecoverIsSilent);
+ }
+ if (available_frames < 0) {
+ LOG(ERROR) << "Failed querying available frames. Assuming 0: "
+ << wrapper_->StrError(available_frames);
+ return 0;
+ }
+ if (static_cast<uint32_t>(available_frames) > alsa_buffer_frames_ * 2) {
+ LOG(ERROR) << "ALSA returned " << available_frames << " of "
+ << alsa_buffer_frames_ << " frames available.";
+ return alsa_buffer_frames_;
+ }
+
+ return available_frames;
+}
+
+snd_pcm_t* AlsaPcmOutputStream::AutoSelectDevice(unsigned int latency) {
+ // For auto-selection:
+ // 1) Attempt to open a device that best matches the number of channels
+ // requested.
+ // 2) If that fails, attempt the "plug:" version of it in case ALSA can
+ // remap and do some software conversion to make it work.
+ // 3) If that fails, attempt the "plug:" version of the guessed name in
+ // case ALSA can remap and do some software conversion to make it work.
+ // 4) Fallback to kDefaultDevice.
+ // 5) If that fails too, try the "plug:" version of kDefaultDevice.
+ // 6) Give up.
+ snd_pcm_t* handle = nullptr;
+ device_name_ = FindDeviceForChannels(channels_);
+
+ // Step 1.
+ if (!device_name_.empty()) {
+ if ((handle = alsa_util::OpenPlaybackDevice(
+ wrapper_, device_name_.c_str(), channels_, sample_rate_,
+ pcm_format_, latency)) != nullptr) {
+ return handle;
+ }
+
+ // Step 2.
+ device_name_ = kPlugPrefix + device_name_;
+ if ((handle = alsa_util::OpenPlaybackDevice(
+ wrapper_, device_name_.c_str(), channels_, sample_rate_,
+ pcm_format_, latency)) != nullptr) {
+ return handle;
+ }
+
+ // Step 3.
+ device_name_ = GuessSpecificDeviceName(channels_);
+ if (!device_name_.empty()) {
+ device_name_ = kPlugPrefix + device_name_;
+ if ((handle = alsa_util::OpenPlaybackDevice(
+ wrapper_, device_name_.c_str(), channels_, sample_rate_,
+ pcm_format_, latency)) != nullptr) {
+ return handle;
+ }
+ }
+ }
+
+ // For the kDefaultDevice device, we can only reliably depend on 2-channel
+ // output to have the correct ordering according to Lennart. For the channel
+ // formats that we know how to downmix from (3 channel to 8 channel), setup
+ // downmixing.
+ uint32_t default_channels = channels_;
+ if (default_channels > 2) {
+ channel_mixer_ = std::make_unique<ChannelMixer>(
+ channel_layout_, kDefaultOutputChannelLayout);
+ default_channels = 2;
+ mixed_audio_bus_ = AudioBus::Create(
+ default_channels, audio_bus_->frames());
+ }
+
+ // Step 4.
+ device_name_ = kDefaultDevice;
+ if ((handle = alsa_util::OpenPlaybackDevice(
+ wrapper_, device_name_.c_str(), default_channels, sample_rate_,
+ pcm_format_, latency)) != nullptr) {
+ return handle;
+ }
+
+ // Step 5.
+ device_name_ = kPlugPrefix + device_name_;
+ if ((handle = alsa_util::OpenPlaybackDevice(
+ wrapper_, device_name_.c_str(), default_channels, sample_rate_,
+ pcm_format_, latency)) != nullptr) {
+ return handle;
+ }
+
+ // Unable to open any device.
+ device_name_.clear();
+ return nullptr;
+}
+
+bool AlsaPcmOutputStream::CanTransitionTo(InternalState to) {
+ switch (state_) {
+ case kCreated:
+ return to == kIsOpened || to == kIsClosed || to == kInError;
+
+ case kIsOpened:
+ return to == kIsPlaying || to == kIsStopped ||
+ to == kIsClosed || to == kInError;
+
+ case kIsPlaying:
+ return to == kIsPlaying || to == kIsStopped ||
+ to == kIsClosed || to == kInError;
+
+ case kIsStopped:
+ return to == kIsPlaying || to == kIsStopped ||
+ to == kIsClosed || to == kInError;
+
+ case kInError:
+ return to == kIsClosed || to == kInError;
+
+ case kIsClosed:
+ default:
+ return false;
+ }
+}
+
+AlsaPcmOutputStream::InternalState
+AlsaPcmOutputStream::TransitionTo(InternalState to) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (!CanTransitionTo(to)) {
+ NOTREACHED() << "Cannot transition from: " << state_ << " to: " << to;
+ state_ = kInError;
+ } else {
+ state_ = to;
+ }
+ return state_;
+}
+
+AlsaPcmOutputStream::InternalState AlsaPcmOutputStream::state() {
+ return state_;
+}
+
+int AlsaPcmOutputStream::RunDataCallback(base::TimeDelta delay,
+ base::TimeTicks delay_timestamp,
+ AudioBus* audio_bus) {
+ TRACE_EVENT0("audio", "AlsaPcmOutputStream::RunDataCallback");
+
+ if (source_callback_)
+ return source_callback_->OnMoreData(delay, delay_timestamp, 0, audio_bus);
+
+ return 0;
+}
+
+void AlsaPcmOutputStream::RunErrorCallback(int code) {
+ // TODO(dalecurtis): Consider sending a translated |code| value.
+ if (source_callback_)
+ source_callback_->OnError(AudioSourceCallback::ErrorType::kUnknown);
+}
+
+// Changes the AudioSourceCallback to proxy calls to. Pass in nullptr to
+// release ownership of the currently registered callback.
+void AlsaPcmOutputStream::set_source_callback(AudioSourceCallback* callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ source_callback_ = callback;
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/alsa/alsa_output.h b/third_party/chromium/media/audio/alsa/alsa_output.h
new file mode 100644
index 0000000..d10bbba
--- /dev/null
+++ b/third_party/chromium/media/audio/alsa/alsa_output.h
@@ -0,0 +1,233 @@
+// Copyright 2013 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.
+//
+// Creates an output stream based on the ALSA PCM interface.
+//
+// On device write failure, the stream will move itself to an invalid state.
+// No more data will be pulled from the data source, or written to the device.
+// All calls to public API functions will either no-op themselves, or return an
+// error if possible. Specifically, If the stream is in an error state, Open()
+// will return false, and Start() will call OnError() immediately on the
+// provided callback.
+//
+// If the stream is successfully opened, Close() must be called. After Close
+// has been called, the object should be regarded as deleted and not touched.
+//
+// AlsaPcmOutputStream is a single threaded class that should only be used from
+// the audio thread. When modifying the code in this class, please read the
+// threading assumptions at the top of the implementation.
+
+#ifndef MEDIA_AUDIO_ALSA_ALSA_OUTPUT_H_
+#define MEDIA_AUDIO_ALSA_ALSA_OUTPUT_H_
+
+#include <alsa/asoundlib.h>
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "base/single_thread_task_runner.h"
+#include "base/time/tick_clock.h"
+#include "base/time/time.h"
+#include "media/audio/audio_io.h"
+#include "media/base/audio_parameters.h"
+
+namespace media {
+
+class AlsaWrapper;
+class AudioManagerBase;
+class ChannelMixer;
+class SeekableBuffer;
+
+class MEDIA_EXPORT AlsaPcmOutputStream : public AudioOutputStream {
+ public:
+ // String for the generic "default" ALSA device that has the highest
+ // compatibility and chance of working.
+ static const char kDefaultDevice[];
+
+ // Pass this to the AlsaPcmOutputStream if you want to attempt auto-selection
+ // of the audio device.
+ static const char kAutoSelectDevice[];
+
+ // Prefix for device names to enable ALSA library resampling.
+ static const char kPlugPrefix[];
+
+ // The minimum latency that is accepted by the device.
+ static const uint32_t kMinLatencyMicros;
+
+ // Create a PCM Output stream for the ALSA device identified by
+ // |device_name|. The AlsaPcmOutputStream uses |wrapper| to communicate with
+ // the alsa libraries, allowing for dependency injection during testing. All
+ // requesting of data, and writing to the alsa device will be done on
+ // the current thread.
+ //
+ // If unsure of what to use for |device_name|, use |kAutoSelectDevice|.
+ AlsaPcmOutputStream(const std::string& device_name,
+ const AudioParameters& params,
+ AlsaWrapper* wrapper,
+ AudioManagerBase* manager);
+
+ AlsaPcmOutputStream(const AlsaPcmOutputStream&) = delete;
+ AlsaPcmOutputStream& operator=(const AlsaPcmOutputStream&) = delete;
+
+ ~AlsaPcmOutputStream() override;
+
+ // Implementation of AudioOutputStream.
+ bool Open() override;
+ void Close() override;
+ void Start(AudioSourceCallback* callback) override;
+ void Stop() override;
+ void Flush() override;
+ void SetVolume(double volume) override;
+ void GetVolume(double* volume) override;
+
+ void SetTickClockForTesting(const base::TickClock* tick_clock);
+
+ private:
+ friend class AlsaPcmOutputStreamTest;
+ FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest,
+ AutoSelectDevice_DeviceSelect);
+ FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest,
+ AutoSelectDevice_FallbackDevices);
+ FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, AutoSelectDevice_HintFail);
+ FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, BufferPacket);
+ FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, BufferPacket_Negative);
+ FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, BufferPacket_StopStream);
+ FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, BufferPacket_Underrun);
+ FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, BufferPacket_FullBuffer);
+ FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, ConstructedState);
+ FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, LatencyFloor);
+ FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, OpenClose);
+ FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, PcmOpenFailed);
+ FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, PcmSetParamsFailed);
+ FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, ScheduleNextWrite);
+ FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest,
+ ScheduleNextWrite_StopStream);
+ FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, StartStop);
+ FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, WritePacket_FinishedPacket);
+ FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, WritePacket_NormalPacket);
+ FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, WritePacket_StopStream);
+ FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, WritePacket_WriteFails);
+
+ // Flags indicating the state of the stream.
+ enum InternalState {
+ kInError = 0,
+ kCreated,
+ kIsOpened,
+ kIsPlaying,
+ kIsStopped,
+ kIsClosed
+ };
+ friend std::ostream& operator<<(std::ostream& os, InternalState);
+
+ // Functions to get another packet from the data source and write it into the
+ // ALSA device.
+ void BufferPacket(bool* source_exhausted);
+ void WritePacket();
+ void WriteTask();
+ void ScheduleNextWrite(bool source_exhausted);
+
+ // Utility functions for talking with the ALSA API.
+ std::string FindDeviceForChannels(uint32_t channels);
+ snd_pcm_sframes_t GetAvailableFrames();
+ snd_pcm_sframes_t GetCurrentDelay();
+
+ // Attempts to find the best matching linux audio device for the given number
+ // of channels. This function will set |device_name_| and |channel_mixer_|.
+ snd_pcm_t* AutoSelectDevice(uint32_t latency);
+
+ // Functions to safeguard state transitions. All changes to the object state
+ // should go through these functions.
+ bool CanTransitionTo(InternalState to);
+ InternalState TransitionTo(InternalState to);
+ InternalState state();
+
+ // API for Proxying calls to the AudioSourceCallback provided during
+ // Start().
+ //
+ // TODO(ajwong): This is necessary because the ownership semantics for the
+ // |source_callback_| object are incorrect in AudioRenderHost. The callback
+ // is passed into the output stream, but ownership is not transfered which
+ // requires a synchronization on access of the |source_callback_| to avoid
+ // using a deleted callback.
+ int RunDataCallback(base::TimeDelta delay,
+ base::TimeTicks delay_timestamp,
+ AudioBus* audio_bus);
+ void RunErrorCallback(int code);
+
+ // Changes the AudioSourceCallback to proxy calls to. Pass in nullptr to
+ // release ownership of the currently registered callback.
+ void set_source_callback(AudioSourceCallback* callback);
+
+ // Configuration constants from the constructor. Referenceable by all threads
+ // since they are constants.
+ const std::string requested_device_name_;
+ const snd_pcm_format_t pcm_format_;
+ const uint32_t channels_;
+ const ChannelLayout channel_layout_;
+ const uint32_t sample_rate_;
+ const uint32_t bytes_per_sample_;
+ const uint32_t bytes_per_frame_;
+
+ // Device configuration data. Populated after OpenTask() completes.
+ std::string device_name_;
+ uint32_t packet_size_;
+ base::TimeDelta latency_;
+ uint32_t bytes_per_output_frame_;
+ uint32_t alsa_buffer_frames_;
+
+ // Flag indicating the code should stop reading from the data source or
+ // writing to the ALSA device. This is set because the device has entered
+ // an unrecoverable error state, or the ClosedTask() has executed.
+ bool stop_stream_;
+
+ // Wrapper class to invoke all the ALSA functions.
+ AlsaWrapper* wrapper_;
+
+ // Audio manager that created us. Used to report that we've been closed.
+ AudioManagerBase* manager_;
+
+ // Task runner to use for polling.
+ const scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+
+ // Handle to the actual PCM playback device.
+ snd_pcm_t* playback_handle_;
+
+ std::unique_ptr<SeekableBuffer> buffer_;
+ uint32_t frames_per_packet_;
+
+ InternalState state_;
+ float volume_; // Volume level from 0.0 to 1.0.
+
+ AudioSourceCallback* source_callback_;
+
+ // Container for retrieving data from AudioSourceCallback::OnMoreData().
+ std::unique_ptr<AudioBus> audio_bus_;
+
+ // Channel mixer and temporary bus for the final mixed channel data.
+ std::unique_ptr<ChannelMixer> channel_mixer_;
+ std::unique_ptr<AudioBus> mixed_audio_bus_;
+
+ const base::TickClock* tick_clock_;
+
+ SEQUENCE_CHECKER(sequence_checker_);
+
+ // Allows us to run tasks on the AlsaPcmOutputStream instance which are
+ // bound by its lifetime.
+ // NOTE: Weak pointers must be invalidated before all other member variables.
+ base::WeakPtrFactory<AlsaPcmOutputStream> weak_factory_{this};
+};
+
+MEDIA_EXPORT std::ostream& operator<<(std::ostream& os,
+ AlsaPcmOutputStream::InternalState);
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_ALSA_ALSA_OUTPUT_H_
diff --git a/third_party/chromium/media/audio/alsa/alsa_output_unittest.cc b/third_party/chromium/media/audio/alsa/alsa_output_unittest.cc
new file mode 100644
index 0000000..33fd1c5
--- /dev/null
+++ b/third_party/chromium/media/audio/alsa/alsa_output_unittest.cc
@@ -0,0 +1,828 @@
+// Copyright 2013 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 <stdint.h>
+#include <memory>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/simple_test_tick_clock.h"
+#include "base/test/test_message_loop.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
+#include "media/audio/alsa/alsa_output.h"
+#include "media/audio/alsa/alsa_wrapper.h"
+#include "media/audio/alsa/audio_manager_alsa.h"
+#include "media/audio/alsa/mock_alsa_wrapper.h"
+#include "media/audio/fake_audio_log_factory.h"
+#include "media/audio/mock_audio_source_callback.h"
+#include "media/audio/test_audio_thread.h"
+#include "media/base/audio_timestamp_helper.h"
+#include "media/base/data_buffer.h"
+#include "media/base/seekable_buffer.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::AllOf;
+using testing::AtLeast;
+using testing::DoAll;
+using testing::Field;
+using testing::InSequence;
+using testing::Invoke;
+using testing::InvokeWithoutArgs;
+using testing::Mock;
+using testing::MockFunction;
+using testing::Return;
+using testing::SetArgPointee;
+using testing::StrictMock;
+using testing::StrEq;
+using testing::Unused;
+
+namespace media {
+
+class MockAudioManagerAlsa : public AudioManagerAlsa {
+ public:
+ MockAudioManagerAlsa()
+ : AudioManagerAlsa(std::make_unique<TestAudioThread>(),
+ &fake_audio_log_factory_) {}
+ MOCK_METHOD0(Init, void());
+ MOCK_METHOD0(HasAudioOutputDevices, bool());
+ MOCK_METHOD0(HasAudioInputDevices, bool());
+ MOCK_METHOD2(MakeLinearOutputStream,
+ AudioOutputStream*(const AudioParameters& params,
+ const LogCallback& log_callback));
+ MOCK_METHOD3(MakeLowLatencyOutputStream,
+ AudioOutputStream*(const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback));
+ MOCK_METHOD3(MakeLowLatencyInputStream,
+ AudioInputStream*(const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback));
+
+ // We need to override this function in order to skip the checking the number
+ // of active output streams. It is because the number of active streams
+ // is managed inside MakeAudioOutputStream, and we don't use
+ // MakeAudioOutputStream to create the stream in the tests.
+ void ReleaseOutputStream(AudioOutputStream* stream) override {
+ DCHECK(stream);
+ delete stream;
+ }
+
+ private:
+ FakeAudioLogFactory fake_audio_log_factory_;
+};
+
+class AlsaPcmOutputStreamTest : public testing::Test {
+ protected:
+ AlsaPcmOutputStreamTest() {
+ mock_manager_ = std::make_unique<StrictMock<MockAudioManagerAlsa>>();
+ }
+
+ ~AlsaPcmOutputStreamTest() override { mock_manager_->Shutdown(); }
+
+ AlsaPcmOutputStream* CreateStream(ChannelLayout layout) {
+ return CreateStream(layout, kTestFramesPerPacket);
+ }
+
+ AlsaPcmOutputStream* CreateStream(ChannelLayout layout,
+ int32_t samples_per_packet) {
+ AudioParameters params(kTestFormat, layout, kTestSampleRate,
+ samples_per_packet);
+ return new AlsaPcmOutputStream(kTestDeviceName,
+ params,
+ &mock_alsa_wrapper_,
+ mock_manager_.get());
+ }
+
+ // Helper function to malloc the string returned by DeviceNameHint for NAME.
+ static char* EchoHint(const void* name, Unused) {
+ return strdup(static_cast<const char*>(name));
+ }
+
+ // Helper function to malloc the string returned by DeviceNameHint for IOID.
+ static char* OutputHint(Unused, Unused) {
+ return strdup("Output");
+ }
+
+ // Helper function to initialize |test_stream->buffer_|. Must be called
+ // in all tests that use buffer_ without opening the stream.
+ void InitBuffer(AlsaPcmOutputStream* test_stream) {
+ DCHECK(test_stream);
+ packet_ = new DataBuffer(kTestPacketSize);
+ packet_->set_data_size(kTestPacketSize);
+ test_stream->buffer_ = std::make_unique<SeekableBuffer>(0, kTestPacketSize);
+ test_stream->buffer_->Append(packet_.get());
+ }
+
+ static const ChannelLayout kTestChannelLayout;
+ static const int kTestSampleRate;
+ static const int kTestBitsPerSample;
+ static const int kTestBytesPerFrame;
+ static const AudioParameters::Format kTestFormat;
+ static const char kTestDeviceName[];
+ static const char kDummyMessage[];
+ static const uint32_t kTestFramesPerPacket;
+ static const int kTestPacketSize;
+ static const int kTestFailedErrno;
+ static snd_pcm_t* const kFakeHandle;
+
+ // Used to simulate DeviceNameHint.
+ static char kSurround40[];
+ static char kSurround41[];
+ static char kSurround50[];
+ static char kSurround51[];
+ static char kSurround70[];
+ static char kSurround71[];
+ static void* kFakeHints[];
+ static char kGenericSurround50[];
+
+ base::TestMessageLoop message_loop_;
+ StrictMock<MockAlsaWrapper> mock_alsa_wrapper_;
+ std::unique_ptr<StrictMock<MockAudioManagerAlsa>> mock_manager_;
+ scoped_refptr<DataBuffer> packet_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AlsaPcmOutputStreamTest);
+};
+
+const ChannelLayout AlsaPcmOutputStreamTest::kTestChannelLayout =
+ CHANNEL_LAYOUT_STEREO;
+const int AlsaPcmOutputStreamTest::kTestSampleRate =
+ AudioParameters::kAudioCDSampleRate;
+const int AlsaPcmOutputStreamTest::kTestBitsPerSample = 16;
+const int AlsaPcmOutputStreamTest::kTestBytesPerFrame =
+ AlsaPcmOutputStreamTest::kTestBitsPerSample / 8 *
+ ChannelLayoutToChannelCount(AlsaPcmOutputStreamTest::kTestChannelLayout);
+const AudioParameters::Format AlsaPcmOutputStreamTest::kTestFormat =
+ AudioParameters::AUDIO_PCM_LINEAR;
+const char AlsaPcmOutputStreamTest::kTestDeviceName[] = "TestDevice";
+const char AlsaPcmOutputStreamTest::kDummyMessage[] = "dummy";
+const uint32_t AlsaPcmOutputStreamTest::kTestFramesPerPacket = 1000;
+const int AlsaPcmOutputStreamTest::kTestPacketSize =
+ AlsaPcmOutputStreamTest::kTestFramesPerPacket *
+ AlsaPcmOutputStreamTest::kTestBytesPerFrame;
+const int AlsaPcmOutputStreamTest::kTestFailedErrno = -EACCES;
+snd_pcm_t* const AlsaPcmOutputStreamTest::kFakeHandle =
+ reinterpret_cast<snd_pcm_t*>(1);
+
+char AlsaPcmOutputStreamTest::kSurround40[] = "surround40:CARD=foo,DEV=0";
+char AlsaPcmOutputStreamTest::kSurround41[] = "surround41:CARD=foo,DEV=0";
+char AlsaPcmOutputStreamTest::kSurround50[] = "surround50:CARD=foo,DEV=0";
+char AlsaPcmOutputStreamTest::kSurround51[] = "surround51:CARD=foo,DEV=0";
+char AlsaPcmOutputStreamTest::kSurround70[] = "surround70:CARD=foo,DEV=0";
+char AlsaPcmOutputStreamTest::kSurround71[] = "surround71:CARD=foo,DEV=0";
+void* AlsaPcmOutputStreamTest::kFakeHints[] = {
+ kSurround40, kSurround41, kSurround50, kSurround51,
+ kSurround70, kSurround71, nullptr};
+char AlsaPcmOutputStreamTest::kGenericSurround50[] = "surround50";
+
+// Custom action to clear a memory buffer.
+ACTION(ClearBuffer) {
+ arg3->Zero();
+}
+
+TEST_F(AlsaPcmOutputStreamTest, ConstructedState) {
+ AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
+ EXPECT_EQ(AlsaPcmOutputStream::kCreated, test_stream->state());
+ test_stream->Close();
+
+ // Should support mono.
+ test_stream = CreateStream(CHANNEL_LAYOUT_MONO);
+ EXPECT_EQ(AlsaPcmOutputStream::kCreated, test_stream->state());
+ test_stream->Close();
+
+ // Should support multi-channel.
+ test_stream = CreateStream(CHANNEL_LAYOUT_SURROUND);
+ EXPECT_EQ(AlsaPcmOutputStream::kCreated, test_stream->state());
+ test_stream->Close();
+}
+
+TEST_F(AlsaPcmOutputStreamTest, LatencyFloor) {
+ const double kMicrosPerFrame =
+ static_cast<double>(1000000) / kTestSampleRate;
+ const double kPacketFramesInMinLatency =
+ AlsaPcmOutputStream::kMinLatencyMicros / kMicrosPerFrame / 2.0;
+
+ // Test that packets which would cause a latency under less than
+ // AlsaPcmOutputStream::kMinLatencyMicros will get clipped to
+ // AlsaPcmOutputStream::kMinLatencyMicros,
+ EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<0>(kFakeHandle), Return(0)));
+ EXPECT_CALL(mock_alsa_wrapper_,
+ PcmSetParams(_, _, _, _, _, _,
+ AlsaPcmOutputStream::kMinLatencyMicros))
+ .WillOnce(Return(0));
+ EXPECT_CALL(mock_alsa_wrapper_, PcmGetParams(_, _, _))
+ .WillOnce(DoAll(SetArgPointee<1>(kTestFramesPerPacket),
+ SetArgPointee<2>(kTestFramesPerPacket / 2), Return(0)));
+
+ AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout,
+ kPacketFramesInMinLatency);
+ ASSERT_TRUE(test_stream->Open());
+
+ // Now close it and test that everything was released.
+ EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle)).WillOnce(Return(0));
+ EXPECT_CALL(mock_alsa_wrapper_, PcmName(kFakeHandle))
+ .WillOnce(Return(kTestDeviceName));
+ test_stream->Close();
+
+ Mock::VerifyAndClear(&mock_alsa_wrapper_);
+ Mock::VerifyAndClear(mock_manager_.get());
+
+ // Test that having more packets ends up with a latency based on packet size.
+ const int kOverMinLatencyPacketSize = kPacketFramesInMinLatency + 1;
+ int64_t expected_micros = AudioTimestampHelper::FramesToTime(
+ kOverMinLatencyPacketSize * 2, kTestSampleRate)
+ .InMicroseconds();
+
+ EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<0>(kFakeHandle), Return(0)));
+ EXPECT_CALL(mock_alsa_wrapper_,
+ PcmSetParams(_, _, _, _, _, _, expected_micros))
+ .WillOnce(Return(0));
+ EXPECT_CALL(mock_alsa_wrapper_, PcmGetParams(_, _, _))
+ .WillOnce(DoAll(SetArgPointee<1>(kTestFramesPerPacket),
+ SetArgPointee<2>(kTestFramesPerPacket / 2), Return(0)));
+
+ test_stream = CreateStream(kTestChannelLayout,
+ kOverMinLatencyPacketSize);
+ ASSERT_TRUE(test_stream->Open());
+
+ // Now close it and test that everything was released.
+ EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle))
+ .WillOnce(Return(0));
+ EXPECT_CALL(mock_alsa_wrapper_, PcmName(kFakeHandle))
+ .WillOnce(Return(kTestDeviceName));
+ test_stream->Close();
+
+ Mock::VerifyAndClear(&mock_alsa_wrapper_);
+ Mock::VerifyAndClear(mock_manager_.get());
+}
+
+TEST_F(AlsaPcmOutputStreamTest, OpenClose) {
+ int64_t expected_micros = AudioTimestampHelper::FramesToTime(
+ 2 * kTestFramesPerPacket, kTestSampleRate)
+ .InMicroseconds();
+
+ // Open() call opens the playback device, sets the parameters, posts a task
+ // with the resulting configuration data, and transitions the object state to
+ // kIsOpened.
+ EXPECT_CALL(mock_alsa_wrapper_,
+ PcmOpen(_, StrEq(kTestDeviceName), SND_PCM_STREAM_PLAYBACK,
+ SND_PCM_NONBLOCK))
+ .WillOnce(DoAll(SetArgPointee<0>(kFakeHandle), Return(0)));
+ EXPECT_CALL(mock_alsa_wrapper_,
+ PcmSetParams(kFakeHandle, SND_PCM_FORMAT_S16_LE,
+ SND_PCM_ACCESS_RW_INTERLEAVED,
+ ChannelLayoutToChannelCount(kTestChannelLayout),
+ kTestSampleRate, 1, expected_micros))
+ .WillOnce(Return(0));
+ EXPECT_CALL(mock_alsa_wrapper_, PcmGetParams(kFakeHandle, _, _))
+ .WillOnce(DoAll(SetArgPointee<1>(kTestFramesPerPacket),
+ SetArgPointee<2>(kTestFramesPerPacket / 2), Return(0)));
+
+ // Open the stream.
+ AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
+ ASSERT_TRUE(test_stream->Open());
+
+ EXPECT_EQ(AlsaPcmOutputStream::kIsOpened, test_stream->state());
+ EXPECT_EQ(kFakeHandle, test_stream->playback_handle_);
+ EXPECT_EQ(kTestFramesPerPacket, test_stream->frames_per_packet_);
+ EXPECT_TRUE(test_stream->buffer_.get());
+ EXPECT_FALSE(test_stream->stop_stream_);
+
+ // Now close it and test that everything was released.
+ EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle))
+ .WillOnce(Return(0));
+ EXPECT_CALL(mock_alsa_wrapper_, PcmName(kFakeHandle))
+ .WillOnce(Return(kTestDeviceName));
+ test_stream->Close();
+}
+
+TEST_F(AlsaPcmOutputStreamTest, PcmOpenFailed) {
+ EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _))
+ .WillOnce(Return(kTestFailedErrno));
+ EXPECT_CALL(mock_alsa_wrapper_, StrError(kTestFailedErrno))
+ .WillOnce(Return(kDummyMessage));
+
+ AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
+ ASSERT_FALSE(test_stream->Open());
+ ASSERT_EQ(AlsaPcmOutputStream::kInError, test_stream->state());
+
+ // Ensure internal state is set for a no-op stream if PcmOpen() failes.
+ EXPECT_TRUE(test_stream->stop_stream_);
+ EXPECT_FALSE(test_stream->playback_handle_);
+ EXPECT_FALSE(test_stream->buffer_.get());
+
+ // Close the stream since we opened it to make destruction happy.
+ test_stream->Close();
+}
+
+TEST_F(AlsaPcmOutputStreamTest, PcmSetParamsFailed) {
+ EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<0>(kFakeHandle), Return(0)));
+ EXPECT_CALL(mock_alsa_wrapper_, PcmSetParams(_, _, _, _, _, _, _))
+ .WillOnce(Return(kTestFailedErrno));
+ EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle))
+ .WillOnce(Return(0));
+ EXPECT_CALL(mock_alsa_wrapper_, PcmName(kFakeHandle))
+ .WillOnce(Return(kTestDeviceName));
+ EXPECT_CALL(mock_alsa_wrapper_, StrError(kTestFailedErrno))
+ .WillOnce(Return(kDummyMessage));
+
+ // If open fails, the stream stays in kCreated because it has effectively had
+ // no changes.
+ AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
+ ASSERT_FALSE(test_stream->Open());
+ EXPECT_EQ(AlsaPcmOutputStream::kInError, test_stream->state());
+
+ // Ensure internal state is set for a no-op stream if PcmSetParams() failes.
+ EXPECT_TRUE(test_stream->stop_stream_);
+ EXPECT_FALSE(test_stream->playback_handle_);
+ EXPECT_FALSE(test_stream->buffer_.get());
+
+ // Close the stream since we opened it to make destruction happy.
+ test_stream->Close();
+}
+
+TEST_F(AlsaPcmOutputStreamTest, StartStop) {
+ // Open() call opens the playback device, sets the parameters, posts a task
+ // with the resulting configuration data, and transitions the object state to
+ // kIsOpened.
+ EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<0>(kFakeHandle), Return(0)));
+ EXPECT_CALL(mock_alsa_wrapper_, PcmSetParams(_, _, _, _, _, _, _))
+ .WillOnce(Return(0));
+ EXPECT_CALL(mock_alsa_wrapper_, PcmGetParams(_, _, _))
+ .WillOnce(DoAll(SetArgPointee<1>(kTestFramesPerPacket),
+ SetArgPointee<2>(kTestFramesPerPacket / 2), Return(0)));
+
+ // Open the stream.
+ AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
+ ASSERT_TRUE(test_stream->Open());
+ base::SimpleTestTickClock tick_clock;
+ tick_clock.SetNowTicks(base::TimeTicks::Now());
+ test_stream->SetTickClockForTesting(&tick_clock);
+
+ // Expect Device setup.
+ EXPECT_CALL(mock_alsa_wrapper_, PcmDrop(kFakeHandle))
+ .WillOnce(Return(0));
+ EXPECT_CALL(mock_alsa_wrapper_, PcmPrepare(kFakeHandle))
+ .WillOnce(Return(0));
+
+ // Expect the pre-roll.
+ MockAudioSourceCallback mock_callback;
+ EXPECT_CALL(mock_alsa_wrapper_, PcmState(kFakeHandle))
+ .WillRepeatedly(Return(SND_PCM_STATE_RUNNING));
+ EXPECT_CALL(mock_alsa_wrapper_, PcmDelay(kFakeHandle, _))
+ .WillRepeatedly(DoAll(SetArgPointee<1>(0), Return(0)));
+ EXPECT_CALL(mock_callback,
+ OnMoreData(base::TimeDelta(), tick_clock.NowTicks(), 0, _))
+ .WillRepeatedly(DoAll(ClearBuffer(), Return(kTestFramesPerPacket)));
+ EXPECT_CALL(mock_alsa_wrapper_, PcmWritei(kFakeHandle, _, _))
+ .WillRepeatedly(Return(kTestFramesPerPacket));
+
+ // Expect scheduling.
+ EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(kFakeHandle))
+ .Times(AtLeast(2))
+ .WillRepeatedly(Return(kTestFramesPerPacket));
+
+ test_stream->Start(&mock_callback);
+ // Start() will issue a WriteTask() directly and then schedule the next one,
+ // call Stop() immediately after to ensure we don't run the message loop
+ // forever.
+ test_stream->Stop();
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle))
+ .WillOnce(Return(0));
+ EXPECT_CALL(mock_alsa_wrapper_, PcmName(kFakeHandle))
+ .WillOnce(Return(kTestDeviceName));
+ test_stream->Close();
+}
+
+TEST_F(AlsaPcmOutputStreamTest, WritePacket_FinishedPacket) {
+ AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
+ InitBuffer(test_stream);
+ test_stream->TransitionTo(AlsaPcmOutputStream::kIsOpened);
+ test_stream->TransitionTo(AlsaPcmOutputStream::kIsPlaying);
+
+ // Nothing should happen. Don't set any expectations and Our strict mocks
+ // should verify most of this.
+
+ // Test empty buffer.
+ test_stream->buffer_->Clear();
+ test_stream->WritePacket();
+ test_stream->Close();
+}
+
+TEST_F(AlsaPcmOutputStreamTest, WritePacket_NormalPacket) {
+ // We need to open the stream before writing data to ALSA.
+ EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<0>(kFakeHandle), Return(0)));
+ EXPECT_CALL(mock_alsa_wrapper_, PcmSetParams(_, _, _, _, _, _, _))
+ .WillOnce(Return(0));
+ EXPECT_CALL(mock_alsa_wrapper_, PcmGetParams(_, _, _))
+ .WillOnce(DoAll(SetArgPointee<1>(kTestFramesPerPacket),
+ SetArgPointee<2>(kTestFramesPerPacket / 2), Return(0)));
+ AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
+ ASSERT_TRUE(test_stream->Open());
+ InitBuffer(test_stream);
+ test_stream->TransitionTo(AlsaPcmOutputStream::kIsPlaying);
+
+ // Write a little less than half the data.
+ int written = packet_->data_size() / kTestBytesPerFrame / 2 - 1;
+ EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(kFakeHandle))
+ .WillOnce(Return(written));
+ EXPECT_CALL(mock_alsa_wrapper_, PcmWritei(kFakeHandle, packet_->data(), _))
+ .WillOnce(Return(written));
+
+ test_stream->WritePacket();
+
+ ASSERT_EQ(test_stream->buffer_->forward_bytes(),
+ packet_->data_size() - written * kTestBytesPerFrame);
+
+ // Write the rest.
+ EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(kFakeHandle))
+ .WillOnce(Return(kTestFramesPerPacket - written));
+ EXPECT_CALL(mock_alsa_wrapper_,
+ PcmWritei(kFakeHandle,
+ packet_->data() + written * kTestBytesPerFrame,
+ _))
+ .WillOnce(Return(packet_->data_size() / kTestBytesPerFrame - written));
+ test_stream->WritePacket();
+ EXPECT_EQ(0, test_stream->buffer_->forward_bytes());
+
+ // Now close it and test that everything was released.
+ EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle))
+ .WillOnce(Return(0));
+ EXPECT_CALL(mock_alsa_wrapper_, PcmName(kFakeHandle))
+ .WillOnce(Return(kTestDeviceName));
+ test_stream->Close();
+}
+
+TEST_F(AlsaPcmOutputStreamTest, WritePacket_WriteFails) {
+ // We need to open the stream before writing data to ALSA.
+ EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<0>(kFakeHandle), Return(0)));
+ EXPECT_CALL(mock_alsa_wrapper_, PcmSetParams(_, _, _, _, _, _, _))
+ .WillOnce(Return(0));
+ EXPECT_CALL(mock_alsa_wrapper_, PcmGetParams(_, _, _))
+ .WillOnce(DoAll(SetArgPointee<1>(kTestFramesPerPacket),
+ SetArgPointee<2>(kTestFramesPerPacket / 2), Return(0)));
+ AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
+ ASSERT_TRUE(test_stream->Open());
+ InitBuffer(test_stream);
+ test_stream->TransitionTo(AlsaPcmOutputStream::kIsPlaying);
+
+ // Fail due to a recoverable error and see that PcmRecover code path
+ // continues normally.
+ EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(kFakeHandle))
+ .WillOnce(Return(kTestFramesPerPacket));
+ EXPECT_CALL(mock_alsa_wrapper_, PcmWritei(kFakeHandle, _, _))
+ .WillOnce(Return(-EINTR));
+ EXPECT_CALL(mock_alsa_wrapper_, PcmRecover(kFakeHandle, _, _))
+ .WillOnce(Return(0));
+
+ test_stream->WritePacket();
+
+ ASSERT_EQ(test_stream->buffer_->forward_bytes(), packet_->data_size());
+
+ // Fail the next write, and see that stop_stream_ is set.
+ EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(kFakeHandle))
+ .WillOnce(Return(kTestFramesPerPacket));
+ EXPECT_CALL(mock_alsa_wrapper_, PcmWritei(kFakeHandle, _, _))
+ .WillOnce(Return(kTestFailedErrno));
+ EXPECT_CALL(mock_alsa_wrapper_, PcmRecover(kFakeHandle, _, _))
+ .WillOnce(Return(kTestFailedErrno));
+ EXPECT_CALL(mock_alsa_wrapper_, StrError(kTestFailedErrno))
+ .WillOnce(Return(kDummyMessage));
+ test_stream->WritePacket();
+ EXPECT_EQ(test_stream->buffer_->forward_bytes(), packet_->data_size());
+ EXPECT_TRUE(test_stream->stop_stream_);
+
+ // Now close it and test that everything was released.
+ EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle))
+ .WillOnce(Return(0));
+ EXPECT_CALL(mock_alsa_wrapper_, PcmName(kFakeHandle))
+ .WillOnce(Return(kTestDeviceName));
+ test_stream->Close();
+}
+
+TEST_F(AlsaPcmOutputStreamTest, WritePacket_StopStream) {
+ AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
+ InitBuffer(test_stream);
+ test_stream->TransitionTo(AlsaPcmOutputStream::kIsOpened);
+ test_stream->TransitionTo(AlsaPcmOutputStream::kIsPlaying);
+
+ // No expectations set on the strict mock because nothing should be called.
+ test_stream->stop_stream_ = true;
+ test_stream->WritePacket();
+ EXPECT_EQ(0, test_stream->buffer_->forward_bytes());
+ test_stream->Close();
+}
+
+TEST_F(AlsaPcmOutputStreamTest, BufferPacket) {
+ AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
+ base::SimpleTestTickClock tick_clock;
+ tick_clock.SetNowTicks(base::TimeTicks::Now());
+ test_stream->SetTickClockForTesting(&tick_clock);
+ InitBuffer(test_stream);
+ test_stream->buffer_->Clear();
+
+ MockAudioSourceCallback mock_callback;
+ EXPECT_CALL(mock_alsa_wrapper_, PcmState(_))
+ .WillOnce(Return(SND_PCM_STATE_RUNNING));
+ EXPECT_CALL(mock_alsa_wrapper_, PcmDelay(_, _))
+ .WillOnce(DoAll(SetArgPointee<1>(1), Return(0)));
+ EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(_))
+ .WillRepeatedly(Return(0)); // Buffer is full.
+
+ // Return a partially filled packet.
+ EXPECT_CALL(mock_callback,
+ OnMoreData(base::TimeDelta(), tick_clock.NowTicks(), 0, _))
+ .WillOnce(DoAll(ClearBuffer(), Return(kTestFramesPerPacket / 2)));
+
+ bool source_exhausted;
+ test_stream->set_source_callback(&mock_callback);
+ test_stream->packet_size_ = kTestPacketSize;
+ test_stream->BufferPacket(&source_exhausted);
+
+ EXPECT_EQ(kTestPacketSize / 2, test_stream->buffer_->forward_bytes());
+ EXPECT_FALSE(source_exhausted);
+ test_stream->Close();
+}
+
+TEST_F(AlsaPcmOutputStreamTest, BufferPacket_Negative) {
+ AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
+ base::SimpleTestTickClock tick_clock;
+ tick_clock.SetNowTicks(base::TimeTicks::Now());
+ test_stream->SetTickClockForTesting(&tick_clock);
+ InitBuffer(test_stream);
+ test_stream->buffer_->Clear();
+
+ // Simulate where the underrun has occurred right after checking the delay.
+ MockAudioSourceCallback mock_callback;
+ EXPECT_CALL(mock_alsa_wrapper_, PcmState(_))
+ .WillOnce(Return(SND_PCM_STATE_RUNNING));
+ EXPECT_CALL(mock_alsa_wrapper_, PcmDelay(_, _))
+ .WillOnce(DoAll(SetArgPointee<1>(-1), Return(0)));
+ EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(_))
+ .WillRepeatedly(Return(0)); // Buffer is full.
+ EXPECT_CALL(mock_callback,
+ OnMoreData(base::TimeDelta(), tick_clock.NowTicks(), 0, _))
+ .WillOnce(DoAll(ClearBuffer(), Return(kTestFramesPerPacket / 2)));
+
+ bool source_exhausted;
+ test_stream->set_source_callback(&mock_callback);
+ test_stream->packet_size_ = kTestPacketSize;
+ test_stream->BufferPacket(&source_exhausted);
+
+ EXPECT_EQ(kTestPacketSize / 2, test_stream->buffer_->forward_bytes());
+ EXPECT_FALSE(source_exhausted);
+ test_stream->Close();
+}
+
+TEST_F(AlsaPcmOutputStreamTest, BufferPacket_Underrun) {
+ AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
+ base::SimpleTestTickClock tick_clock;
+ tick_clock.SetNowTicks(base::TimeTicks::Now());
+ test_stream->SetTickClockForTesting(&tick_clock);
+ InitBuffer(test_stream);
+ test_stream->buffer_->Clear();
+
+ // If ALSA has underrun then we should assume a delay of zero.
+ MockAudioSourceCallback mock_callback;
+ EXPECT_CALL(mock_alsa_wrapper_, PcmState(_))
+ .WillOnce(Return(SND_PCM_STATE_XRUN));
+ EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(_))
+ .WillRepeatedly(Return(0)); // Buffer is full.
+ EXPECT_CALL(mock_callback,
+ OnMoreData(base::TimeDelta(), tick_clock.NowTicks(), 0, _))
+ .WillOnce(DoAll(ClearBuffer(), Return(kTestFramesPerPacket / 2)));
+
+ bool source_exhausted;
+ test_stream->set_source_callback(&mock_callback);
+ test_stream->packet_size_ = kTestPacketSize;
+ test_stream->BufferPacket(&source_exhausted);
+
+ EXPECT_EQ(kTestPacketSize / 2, test_stream->buffer_->forward_bytes());
+ EXPECT_FALSE(source_exhausted);
+ test_stream->Close();
+}
+
+TEST_F(AlsaPcmOutputStreamTest, BufferPacket_FullBuffer) {
+ AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
+ InitBuffer(test_stream);
+ // No expectations set on the strict mock because nothing should be called.
+ bool source_exhausted;
+ test_stream->packet_size_ = kTestPacketSize;
+ test_stream->BufferPacket(&source_exhausted);
+ EXPECT_EQ(kTestPacketSize, test_stream->buffer_->forward_bytes());
+ EXPECT_FALSE(source_exhausted);
+ test_stream->Close();
+}
+
+TEST_F(AlsaPcmOutputStreamTest, AutoSelectDevice_DeviceSelect) {
+ // Try channels from 1 -> 9. and see that we get the more specific surroundXX
+ // device opened for channels 4-8. For all other channels, the device should
+ // default to |AlsaPcmOutputStream::kDefaultDevice|. We should also not
+ // downmix any channel in this case because downmixing is only defined for
+ // channels 4-8, which we are guaranteeing to work.
+ //
+ // Note that the loop starts at "1", so the first parameter is ignored in
+ // these arrays.
+ const char* kExpectedDeviceName[] = {nullptr,
+ AlsaPcmOutputStream::kDefaultDevice,
+ AlsaPcmOutputStream::kDefaultDevice,
+ AlsaPcmOutputStream::kDefaultDevice,
+ kSurround40,
+ kSurround50,
+ kSurround51,
+ kSurround70,
+ kSurround71,
+ AlsaPcmOutputStream::kDefaultDevice};
+ bool kExpectedDownmix[] = { false, false, false, false, false, true,
+ false, false, false, false };
+ ChannelLayout kExpectedLayouts[] = { CHANNEL_LAYOUT_NONE,
+ CHANNEL_LAYOUT_MONO,
+ CHANNEL_LAYOUT_STEREO,
+ CHANNEL_LAYOUT_SURROUND,
+ CHANNEL_LAYOUT_4_0,
+ CHANNEL_LAYOUT_5_0,
+ CHANNEL_LAYOUT_5_1,
+ CHANNEL_LAYOUT_7_0,
+ CHANNEL_LAYOUT_7_1 };
+
+
+ for (int i = 1; i < 9; ++i) {
+ if (i == 3 || i == 4 || i == 5) // invalid number of channels
+ continue;
+ SCOPED_TRACE(base::StringPrintf("Attempting %d Channel", i));
+
+ // Hints will only be grabbed for channel numbers that have non-default
+ // devices associated with them.
+ if (kExpectedDeviceName[i] != AlsaPcmOutputStream::kDefaultDevice) {
+ // The DeviceNameHint and DeviceNameFreeHint need to be paired to avoid a
+ // memory leak.
+ EXPECT_CALL(mock_alsa_wrapper_, DeviceNameHint(_, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(&kFakeHints[0]), Return(0)));
+ EXPECT_CALL(mock_alsa_wrapper_, DeviceNameFreeHint(&kFakeHints[0]))
+ .Times(1);
+ }
+
+ EXPECT_CALL(mock_alsa_wrapper_,
+ PcmOpen(_, StrEq(kExpectedDeviceName[i]), _, _))
+ .WillOnce(DoAll(SetArgPointee<0>(kFakeHandle), Return(0)));
+ EXPECT_CALL(mock_alsa_wrapper_,
+ PcmSetParams(kFakeHandle, _, _, i, _, _, _))
+ .WillOnce(Return(0));
+
+ // The parameters are specified by ALSA documentation, and are in constants
+ // in the implementation files.
+ EXPECT_CALL(mock_alsa_wrapper_, DeviceNameGetHint(_, StrEq("IOID")))
+ .WillRepeatedly(Invoke(OutputHint));
+ EXPECT_CALL(mock_alsa_wrapper_, DeviceNameGetHint(_, StrEq("NAME")))
+ .WillRepeatedly(Invoke(EchoHint));
+
+ AlsaPcmOutputStream* test_stream = CreateStream(kExpectedLayouts[i]);
+ EXPECT_TRUE(test_stream->AutoSelectDevice(i));
+ EXPECT_EQ(kExpectedDownmix[i],
+ static_cast<bool>(test_stream->channel_mixer_));
+
+ Mock::VerifyAndClearExpectations(&mock_alsa_wrapper_);
+ Mock::VerifyAndClearExpectations(mock_manager_.get());
+ test_stream->Close();
+ }
+}
+
+TEST_F(AlsaPcmOutputStreamTest, AutoSelectDevice_FallbackDevices) {
+ using std::string;
+
+ // If there are problems opening a multi-channel device, it the fallbacks
+ // operations should be as follows. Assume the multi-channel device name is
+ // surround50:
+ //
+ // 1) Try open "surround50:CARD=foo,DEV=0"
+ // 2) Try open "plug:surround50:CARD=foo,DEV=0".
+ // 3) Try open "plug:surround50".
+ // 4) Try open "default".
+ // 5) Try open "plug:default".
+ // 6) Give up trying to open.
+ //
+ const string first_try = kSurround50;
+ const string second_try = string(AlsaPcmOutputStream::kPlugPrefix) +
+ kSurround50;
+ const string third_try = string(AlsaPcmOutputStream::kPlugPrefix) +
+ kGenericSurround50;
+ const string fourth_try = AlsaPcmOutputStream::kDefaultDevice;
+ const string fifth_try = string(AlsaPcmOutputStream::kPlugPrefix) +
+ AlsaPcmOutputStream::kDefaultDevice;
+
+ EXPECT_CALL(mock_alsa_wrapper_, DeviceNameHint(_, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(&kFakeHints[0]), Return(0)));
+ EXPECT_CALL(mock_alsa_wrapper_, DeviceNameFreeHint(&kFakeHints[0]))
+ .Times(1);
+ EXPECT_CALL(mock_alsa_wrapper_, DeviceNameGetHint(_, StrEq("IOID")))
+ .WillRepeatedly(Invoke(OutputHint));
+ EXPECT_CALL(mock_alsa_wrapper_, DeviceNameGetHint(_, StrEq("NAME")))
+ .WillRepeatedly(Invoke(EchoHint));
+ EXPECT_CALL(mock_alsa_wrapper_, StrError(kTestFailedErrno))
+ .WillRepeatedly(Return(kDummyMessage));
+
+ InSequence s;
+ EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, StrEq(first_try.c_str()), _, _))
+ .WillOnce(Return(kTestFailedErrno));
+ EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, StrEq(second_try.c_str()), _, _))
+ .WillOnce(Return(kTestFailedErrno));
+ EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, StrEq(third_try.c_str()), _, _))
+ .WillOnce(Return(kTestFailedErrno));
+ EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, StrEq(fourth_try.c_str()), _, _))
+ .WillOnce(Return(kTestFailedErrno));
+ EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, StrEq(fifth_try.c_str()), _, _))
+ .WillOnce(Return(kTestFailedErrno));
+
+ AlsaPcmOutputStream* test_stream = CreateStream(CHANNEL_LAYOUT_5_0);
+ EXPECT_FALSE(test_stream->AutoSelectDevice(5));
+ test_stream->Close();
+}
+
+TEST_F(AlsaPcmOutputStreamTest, AutoSelectDevice_HintFail) {
+ // Should get |kDefaultDevice|, and force a 2-channel downmix on a failure to
+ // enumerate devices.
+ EXPECT_CALL(mock_alsa_wrapper_, DeviceNameHint(_, _, _))
+ .WillRepeatedly(Return(kTestFailedErrno));
+ EXPECT_CALL(mock_alsa_wrapper_,
+ PcmOpen(_, StrEq(AlsaPcmOutputStream::kDefaultDevice), _, _))
+ .WillOnce(DoAll(SetArgPointee<0>(kFakeHandle), Return(0)));
+ EXPECT_CALL(mock_alsa_wrapper_,
+ PcmSetParams(kFakeHandle, _, _, 2, _, _, _))
+ .WillOnce(Return(0));
+ EXPECT_CALL(mock_alsa_wrapper_, StrError(kTestFailedErrno))
+ .WillOnce(Return(kDummyMessage));
+
+ AlsaPcmOutputStream* test_stream = CreateStream(CHANNEL_LAYOUT_5_0);
+ EXPECT_TRUE(test_stream->AutoSelectDevice(5));
+ EXPECT_TRUE(test_stream->channel_mixer_);
+ test_stream->Close();
+}
+
+TEST_F(AlsaPcmOutputStreamTest, BufferPacket_StopStream) {
+ AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
+ InitBuffer(test_stream);
+ test_stream->stop_stream_ = true;
+ bool source_exhausted;
+ test_stream->BufferPacket(&source_exhausted);
+ EXPECT_EQ(0, test_stream->buffer_->forward_bytes());
+ EXPECT_TRUE(source_exhausted);
+ test_stream->Close();
+}
+
+TEST_F(AlsaPcmOutputStreamTest, ScheduleNextWrite) {
+ AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
+ test_stream->TransitionTo(AlsaPcmOutputStream::kIsOpened);
+ test_stream->TransitionTo(AlsaPcmOutputStream::kIsPlaying);
+ InitBuffer(test_stream);
+ DVLOG(1) << test_stream->state();
+ EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(_))
+ .WillOnce(Return(10));
+ test_stream->ScheduleNextWrite(false);
+ DVLOG(1) << test_stream->state();
+ // TODO(sergeyu): Figure out how to check that the task has been added to the
+ // message loop.
+
+ // Cleanup the message queue. Currently ~MessageQueue() doesn't free pending
+ // tasks unless running on valgrind. The code below is needed to keep
+ // heapcheck happy.
+
+ test_stream->stop_stream_ = true;
+ DVLOG(1) << test_stream->state();
+ test_stream->TransitionTo(AlsaPcmOutputStream::kIsClosed);
+ DVLOG(1) << test_stream->state();
+ test_stream->Close();
+}
+
+TEST_F(AlsaPcmOutputStreamTest, ScheduleNextWrite_StopStream) {
+ AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
+ test_stream->TransitionTo(AlsaPcmOutputStream::kIsOpened);
+ test_stream->TransitionTo(AlsaPcmOutputStream::kIsPlaying);
+
+ InitBuffer(test_stream);
+
+ test_stream->stop_stream_ = true;
+ test_stream->ScheduleNextWrite(true);
+
+ // TODO(ajwong): Find a way to test whether or not another task has been
+ // posted so we can verify that the Alsa code will indeed break the task
+ // posting loop.
+
+ test_stream->TransitionTo(AlsaPcmOutputStream::kIsClosed);
+ test_stream->Close();
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/alsa/alsa_util.cc b/third_party/chromium/media/audio/alsa/alsa_util.cc
new file mode 100644
index 0000000..84b4133
--- /dev/null
+++ b/third_party/chromium/media/audio/alsa/alsa_util.cc
@@ -0,0 +1,380 @@
+// Copyright 2013 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 "media/audio/alsa/alsa_util.h"
+
+#include <stddef.h>
+
+#include <functional>
+#include <memory>
+
+#include "base/logging.h"
+#include "base/time/time.h"
+#include "media/audio/alsa/alsa_wrapper.h"
+
+namespace alsa_util {
+
+namespace {
+
+// Set hardware parameters of PCM. It does the same thing as the corresponding
+// part in snd_pcm_set_params() (https://www.alsa-project.org, source code:
+// https://github.com/tiwai/alsa-lib/blob/master/src/pcm/pcm.c#L8459), except
+// that it configures buffer size and period size both to closest available
+// values instead of forcing the buffer size be 4 times of the period size.
+int ConfigureHwParams(media::AlsaWrapper* wrapper,
+ snd_pcm_t* handle,
+ snd_pcm_format_t format,
+ snd_pcm_access_t access,
+ unsigned int channels,
+ unsigned int sample_rate,
+ int soft_resample,
+ snd_pcm_uframes_t frames_per_buffer,
+ snd_pcm_uframes_t frames_per_period) {
+ int error = 0;
+
+ snd_pcm_hw_params_t* hw_params = nullptr;
+ error = wrapper->PcmHwParamsMalloc(&hw_params);
+ if (error < 0) {
+ LOG(ERROR) << "PcmHwParamsMalloc: " << wrapper->StrError(error);
+ return error;
+ }
+ // |snd_pcm_hw_params_t| is not exposed and requires memory allocation through
+ // ALSA API. Therefore, use a smart pointer to pointer to insure freeing
+ // memory when the function returns.
+ std::unique_ptr<snd_pcm_hw_params_t*,
+ std::function<void(snd_pcm_hw_params_t**)>>
+ params_holder(&hw_params, [wrapper](snd_pcm_hw_params_t** params) {
+ wrapper->PcmHwParamsFree(*params);
+ });
+
+ error = wrapper->PcmHwParamsAny(handle, hw_params);
+ if (error < 0) {
+ LOG(ERROR) << "PcmHwParamsAny: " << wrapper->StrError(error);
+ return error;
+ }
+
+ error = wrapper->PcmHwParamsSetRateResample(handle, hw_params, soft_resample);
+ if (error < 0) {
+ LOG(ERROR) << "PcmHwParamsSetRateResample: " << wrapper->StrError(error);
+ return error;
+ }
+
+ error = wrapper->PcmHwParamsSetAccess(handle, hw_params, access);
+ if (error < 0) {
+ LOG(ERROR) << "PcmHwParamsSetAccess: " << wrapper->StrError(error);
+ return error;
+ }
+
+ error = wrapper->PcmHwParamsSetFormat(handle, hw_params, format);
+ if (error < 0) {
+ LOG(ERROR) << "PcmHwParamsSetFormat: " << wrapper->StrError(error);
+ return error;
+ }
+
+ error = wrapper->PcmHwParamsSetChannels(handle, hw_params, channels);
+ if (error < 0) {
+ LOG(ERROR) << "PcmHwParamsSetChannels: " << wrapper->StrError(error);
+ return error;
+ }
+
+ unsigned int rate = sample_rate;
+ error = wrapper->PcmHwParamsSetRateNear(handle, hw_params, &rate, nullptr);
+ if (error < 0) {
+ LOG(ERROR) << "PcmHwParamsSetRateNear: " << wrapper->StrError(error);
+ return error;
+ }
+ if (rate != sample_rate) {
+ LOG(ERROR) << "Rate doesn't match, required: " << sample_rate
+ << "Hz, but get: " << rate << "Hz.";
+ return -EINVAL;
+ }
+
+ error = wrapper->PcmHwParamsSetBufferSizeNear(handle, hw_params,
+ &frames_per_buffer);
+ if (error < 0) {
+ LOG(ERROR) << "PcmHwParamsSetBufferSizeNear: " << wrapper->StrError(error);
+ return error;
+ }
+
+ int direction = 0;
+ error = wrapper->PcmHwParamsSetPeriodSizeNear(handle, hw_params,
+ &frames_per_period, &direction);
+ if (error < 0) {
+ LOG(ERROR) << "PcmHwParamsSetPeriodSizeNear: " << wrapper->StrError(error);
+ return error;
+ }
+
+ if (frames_per_period > frames_per_buffer / 2) {
+ LOG(ERROR) << "Period size (" << frames_per_period
+ << ") is too big; buffer size = " << frames_per_buffer;
+ return -EINVAL;
+ }
+
+ error = wrapper->PcmHwParams(handle, hw_params);
+ if (error < 0)
+ LOG(ERROR) << "PcmHwParams: " << wrapper->StrError(error);
+
+ return error;
+}
+
+// Set software parameters of PCM. It does the same thing as the corresponding
+// part in snd_pcm_set_params()
+// (https://github.com/tiwai/alsa-lib/blob/master/src/pcm/pcm.c#L8603).
+int ConfigureSwParams(media::AlsaWrapper* wrapper,
+ snd_pcm_t* handle,
+ snd_pcm_uframes_t frames_per_buffer,
+ snd_pcm_uframes_t frames_per_period) {
+ int error = 0;
+
+ snd_pcm_sw_params_t* sw_params = nullptr;
+ error = wrapper->PcmSwParamsMalloc(&sw_params);
+ if (error < 0) {
+ LOG(ERROR) << "PcmSwParamsMalloc: " << wrapper->StrError(error);
+ return error;
+ }
+ // |snd_pcm_sw_params_t| is not exposed and thus use a smart pointer to
+ // pointer to insure freeing memory when the function returns.
+ std::unique_ptr<snd_pcm_sw_params_t*,
+ std::function<void(snd_pcm_sw_params_t**)>>
+ params_holder(&sw_params, [wrapper](snd_pcm_sw_params_t** params) {
+ wrapper->PcmSwParamsFree(*params);
+ });
+
+ error = wrapper->PcmSwParamsCurrent(handle, sw_params);
+ if (error < 0) {
+ LOG(ERROR) << "PcmSwParamsCurrent: " << wrapper->StrError(error);
+ return error;
+ }
+
+ // For playback, start the transfer when the buffer is almost full.
+ int start_threshold =
+ (frames_per_buffer / frames_per_period) * frames_per_period;
+ error =
+ wrapper->PcmSwParamsSetStartThreshold(handle, sw_params, start_threshold);
+ if (error < 0) {
+ LOG(ERROR) << "PcmSwParamsSetStartThreshold: " << wrapper->StrError(error);
+ return error;
+ }
+
+ // For capture, wake capture thread as soon as possible (1 period).
+ error = wrapper->PcmSwParamsSetAvailMin(handle, sw_params, frames_per_period);
+ if (error < 0) {
+ LOG(ERROR) << "PcmSwParamsSetAvailMin: " << wrapper->StrError(error);
+ return error;
+ }
+
+ error = wrapper->PcmSwParams(handle, sw_params);
+ if (error < 0)
+ LOG(ERROR) << "PcmSwParams: " << wrapper->StrError(error);
+
+ return error;
+}
+
+int SetParams(media::AlsaWrapper* wrapper,
+ snd_pcm_t* handle,
+ snd_pcm_format_t format,
+ unsigned int channels,
+ unsigned int rate,
+ unsigned int frames_per_buffer,
+ unsigned int frames_per_period) {
+ int error = ConfigureHwParams(
+ wrapper, handle, format, SND_PCM_ACCESS_RW_INTERLEAVED, channels, rate,
+ 1 /* Enable resampling */, frames_per_buffer, frames_per_period);
+ if (error == 0) {
+ error = ConfigureSwParams(wrapper, handle, frames_per_buffer,
+ frames_per_period);
+ }
+ return error;
+}
+
+} // namespace
+
+static snd_pcm_t* OpenDevice(media::AlsaWrapper* wrapper,
+ const char* device_name,
+ snd_pcm_stream_t type,
+ int channels,
+ int sample_rate,
+ snd_pcm_format_t pcm_format,
+ int buffer_us,
+ int period_us = 0) {
+ snd_pcm_t* handle = NULL;
+ int error = wrapper->PcmOpen(&handle, device_name, type, SND_PCM_NONBLOCK);
+ if (error < 0) {
+ LOG(ERROR) << "PcmOpen: " << device_name << "," << wrapper->StrError(error);
+ return NULL;
+ }
+
+ error =
+ wrapper->PcmSetParams(handle, pcm_format, SND_PCM_ACCESS_RW_INTERLEAVED,
+ channels, sample_rate, 1, buffer_us);
+ if (error < 0) {
+ LOG(WARNING) << "PcmSetParams: " << device_name << ", "
+ << wrapper->StrError(error);
+ // Default parameter setting function failed, try again with the customized
+ // one if |period_us| is set, which is the case for capture but not for
+ // playback.
+ if (period_us > 0) {
+ const unsigned int frames_per_buffer = static_cast<unsigned int>(
+ static_cast<int64_t>(buffer_us) * sample_rate /
+ base::Time::kMicrosecondsPerSecond);
+ const unsigned int frames_per_period = static_cast<unsigned int>(
+ static_cast<int64_t>(period_us) * sample_rate /
+ base::Time::kMicrosecondsPerSecond);
+ LOG(WARNING) << "SetParams: " << device_name
+ << " - Format: " << pcm_format << " Channels: " << channels
+ << " Sample rate: " << sample_rate
+ << " Buffer size: " << frames_per_buffer
+ << " Period size: " << frames_per_period;
+ error = SetParams(wrapper, handle, pcm_format, channels, sample_rate,
+ frames_per_buffer, frames_per_period);
+ }
+ }
+ if (error < 0) {
+ if (alsa_util::CloseDevice(wrapper, handle) < 0) {
+ // TODO(ajwong): Retry on certain errors?
+ LOG(WARNING) << "Unable to close audio device. Leaking handle.";
+ }
+ return NULL;
+ }
+
+ return handle;
+}
+
+static std::string DeviceNameToControlName(const std::string& device_name) {
+ const char kMixerPrefix[] = "hw";
+ std::string control_name;
+ size_t pos1 = device_name.find(':');
+ if (pos1 == std::string::npos) {
+ control_name = device_name;
+ } else {
+ // Examples:
+ // deviceName: "front:CARD=Intel,DEV=0", controlName: "hw:CARD=Intel".
+ // deviceName: "default:CARD=Intel", controlName: "CARD=Intel".
+ size_t pos2 = device_name.find(',');
+ control_name = (pos2 == std::string::npos)
+ ? device_name.substr(pos1 + 1)
+ : kMixerPrefix + device_name.substr(pos1, pos2 - pos1);
+ }
+
+ return control_name;
+}
+
+int CloseDevice(media::AlsaWrapper* wrapper, snd_pcm_t* handle) {
+ std::string device_name = wrapper->PcmName(handle);
+ int error = wrapper->PcmClose(handle);
+ if (error < 0) {
+ LOG(ERROR) << "PcmClose: " << device_name << ", "
+ << wrapper->StrError(error);
+ }
+
+ return error;
+}
+
+snd_pcm_t* OpenCaptureDevice(media::AlsaWrapper* wrapper,
+ const char* device_name,
+ int channels,
+ int sample_rate,
+ snd_pcm_format_t pcm_format,
+ int buffer_us,
+ int period_us) {
+ return OpenDevice(wrapper, device_name, SND_PCM_STREAM_CAPTURE, channels,
+ sample_rate, pcm_format, buffer_us, period_us);
+}
+
+snd_pcm_t* OpenPlaybackDevice(media::AlsaWrapper* wrapper,
+ const char* device_name,
+ int channels,
+ int sample_rate,
+ snd_pcm_format_t pcm_format,
+ int buffer_us) {
+ return OpenDevice(wrapper, device_name, SND_PCM_STREAM_PLAYBACK, channels,
+ sample_rate, pcm_format, buffer_us);
+}
+
+snd_mixer_t* OpenMixer(media::AlsaWrapper* wrapper,
+ const std::string& device_name) {
+ snd_mixer_t* mixer = NULL;
+
+ int error = wrapper->MixerOpen(&mixer, 0);
+ if (error < 0) {
+ LOG(ERROR) << "MixerOpen: " << device_name << ", "
+ << wrapper->StrError(error);
+ return NULL;
+ }
+
+ std::string control_name = DeviceNameToControlName(device_name);
+ error = wrapper->MixerAttach(mixer, control_name.c_str());
+ if (error < 0) {
+ LOG(ERROR) << "MixerAttach, " << control_name << ", "
+ << wrapper->StrError(error);
+ alsa_util::CloseMixer(wrapper, mixer, device_name);
+ return NULL;
+ }
+
+ error = wrapper->MixerElementRegister(mixer, NULL, NULL);
+ if (error < 0) {
+ LOG(ERROR) << "MixerElementRegister: " << control_name << ", "
+ << wrapper->StrError(error);
+ alsa_util::CloseMixer(wrapper, mixer, device_name);
+ return NULL;
+ }
+
+ return mixer;
+}
+
+void CloseMixer(media::AlsaWrapper* wrapper, snd_mixer_t* mixer,
+ const std::string& device_name) {
+ if (!mixer)
+ return;
+
+ wrapper->MixerFree(mixer);
+
+ int error = 0;
+ if (!device_name.empty()) {
+ std::string control_name = DeviceNameToControlName(device_name);
+ error = wrapper->MixerDetach(mixer, control_name.c_str());
+ if (error < 0) {
+ LOG(WARNING) << "MixerDetach: " << control_name << ", "
+ << wrapper->StrError(error);
+ }
+ }
+
+ error = wrapper->MixerClose(mixer);
+ if (error < 0) {
+ LOG(WARNING) << "MixerClose: " << wrapper->StrError(error);
+ }
+}
+
+snd_mixer_elem_t* LoadCaptureMixerElement(media::AlsaWrapper* wrapper,
+ snd_mixer_t* mixer) {
+ if (!mixer)
+ return NULL;
+
+ int error = wrapper->MixerLoad(mixer);
+ if (error < 0) {
+ LOG(ERROR) << "MixerLoad: " << wrapper->StrError(error);
+ return NULL;
+ }
+
+ snd_mixer_elem_t* elem = NULL;
+ snd_mixer_elem_t* mic_elem = NULL;
+ const char kCaptureElemName[] = "Capture";
+ const char kMicElemName[] = "Mic";
+ for (elem = wrapper->MixerFirstElem(mixer);
+ elem;
+ elem = wrapper->MixerNextElem(elem)) {
+ if (wrapper->MixerSelemIsActive(elem)) {
+ const char* elem_name = wrapper->MixerSelemName(elem);
+ if (strcmp(elem_name, kCaptureElemName) == 0)
+ return elem;
+ else if (strcmp(elem_name, kMicElemName) == 0)
+ mic_elem = elem;
+ }
+ }
+
+ // Did not find any Capture handle, use the Mic handle.
+ return mic_elem;
+}
+
+} // namespace alsa_util
diff --git a/third_party/chromium/media/audio/alsa/alsa_util.h b/third_party/chromium/media/audio/alsa/alsa_util.h
new file mode 100644
index 0000000..47b5d1b
--- /dev/null
+++ b/third_party/chromium/media/audio/alsa/alsa_util.h
@@ -0,0 +1,52 @@
+// Copyright 2013 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 MEDIA_AUDIO_ALSA_ALSA_UTIL_H_
+#define MEDIA_AUDIO_ALSA_ALSA_UTIL_H_
+
+#include <alsa/asoundlib.h>
+#include <string>
+
+#include "media/base/media_export.h"
+
+namespace media {
+class AlsaWrapper;
+}
+
+namespace alsa_util {
+
+// When opening ALSA devices, |period_us| is the size of a packet and
+// |buffer_us| is the size of the ring buffer, which consists of multiple
+// packets. In capture devices, the latency relies more on |period_us|, and thus
+// one may require more details upon the value implicitly set by ALSA.
+MEDIA_EXPORT snd_pcm_t* OpenCaptureDevice(media::AlsaWrapper* wrapper,
+ const char* device_name,
+ int channels,
+ int sample_rate,
+ snd_pcm_format_t pcm_format,
+ int buffer_us,
+ int period_us);
+
+snd_pcm_t* OpenPlaybackDevice(media::AlsaWrapper* wrapper,
+ const char* device_name,
+ int channels,
+ int sample_rate,
+ snd_pcm_format_t pcm_format,
+ int buffer_us);
+
+int CloseDevice(media::AlsaWrapper* wrapper, snd_pcm_t* handle);
+
+snd_mixer_t* OpenMixer(media::AlsaWrapper* wrapper,
+ const std::string& device_name);
+
+void CloseMixer(media::AlsaWrapper* wrapper,
+ snd_mixer_t* mixer,
+ const std::string& device_name);
+
+snd_mixer_elem_t* LoadCaptureMixerElement(media::AlsaWrapper* wrapper,
+ snd_mixer_t* mixer);
+
+} // namespace alsa_util
+
+#endif // MEDIA_AUDIO_ALSA_ALSA_UTIL_H_
diff --git a/third_party/chromium/media/audio/alsa/alsa_util_unittest.cc b/third_party/chromium/media/audio/alsa/alsa_util_unittest.cc
new file mode 100644
index 0000000..343730d
--- /dev/null
+++ b/third_party/chromium/media/audio/alsa/alsa_util_unittest.cc
@@ -0,0 +1,44 @@
+// Copyright 2018 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 "media/audio/alsa/alsa_util.h"
+#include "media/audio/alsa/mock_alsa_wrapper.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace alsa_util {
+
+namespace {
+
+using ::testing::_;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::Return;
+
+} // namespace
+
+TEST(AlsaUtilTest, FreeHwParams) {
+ InSequence seq;
+ media::MockAlsaWrapper mock_alsa_wrapper;
+ snd_pcm_hw_params_t* params_ptr = (snd_pcm_hw_params_t*)malloc(1);
+ EXPECT_CALL(mock_alsa_wrapper, PcmOpen(_, _, _, _)).WillOnce(Return(0));
+ EXPECT_CALL(mock_alsa_wrapper, PcmSetParams(_, _, _, _, _, _, _))
+ .WillOnce(Return(-1));
+ EXPECT_CALL(mock_alsa_wrapper, StrError(_)).WillOnce(Return("error"));
+ EXPECT_CALL(mock_alsa_wrapper, PcmHwParamsMalloc(_))
+ .WillOnce(Invoke([params_ptr](snd_pcm_hw_params_t** params) {
+ *params = params_ptr;
+ return 0;
+ }));
+ EXPECT_CALL(mock_alsa_wrapper, PcmHwParamsAny(_, _)).WillOnce(Return(-1));
+ EXPECT_CALL(mock_alsa_wrapper, StrError(_)).WillOnce(Return("error"));
+ EXPECT_CALL(mock_alsa_wrapper, PcmHwParamsFree(params_ptr));
+ EXPECT_CALL(mock_alsa_wrapper, PcmName(_)).WillOnce(Return("default"));
+ EXPECT_CALL(mock_alsa_wrapper, PcmClose(_)).WillOnce(Return(0));
+ snd_pcm_t* handle = OpenCaptureDevice(&mock_alsa_wrapper, "default", 2, 48000,
+ SND_PCM_FORMAT_S16, 40000, 10000);
+ EXPECT_EQ(handle, nullptr);
+ free(params_ptr);
+}
+
+} // namespace alsa_util
diff --git a/third_party/chromium/media/audio/alsa/alsa_wrapper.cc b/third_party/chromium/media/audio/alsa/alsa_wrapper.cc
new file mode 100644
index 0000000..b24630d
--- /dev/null
+++ b/third_party/chromium/media/audio/alsa/alsa_wrapper.cc
@@ -0,0 +1,383 @@
+// Copyright 2013 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 "media/audio/alsa/alsa_wrapper.h"
+
+namespace media {
+
+AlsaWrapper::AlsaWrapper() = default;
+
+AlsaWrapper::~AlsaWrapper() = default;
+
+int AlsaWrapper::PcmOpen(snd_pcm_t** handle, const char* name,
+ snd_pcm_stream_t stream, int mode) {
+ return snd_pcm_open(handle, name, stream, mode);
+}
+
+int AlsaWrapper::DeviceNameHint(int card, const char* iface, void*** hints) {
+ return snd_device_name_hint(card, iface, hints);
+}
+
+char* AlsaWrapper::DeviceNameGetHint(const void* hint, const char* id) {
+ return snd_device_name_get_hint(hint, id);
+}
+
+int AlsaWrapper::DeviceNameFreeHint(void** hints) {
+ return snd_device_name_free_hint(hints);
+}
+
+int AlsaWrapper::CardNext(int* rcard) {
+ return snd_card_next(rcard);
+}
+
+int AlsaWrapper::PcmClose(snd_pcm_t* handle) {
+ return snd_pcm_close(handle);
+}
+
+int AlsaWrapper::PcmPrepare(snd_pcm_t* handle) {
+ return snd_pcm_prepare(handle);
+}
+
+int AlsaWrapper::PcmDrain(snd_pcm_t* handle) {
+ return snd_pcm_drain(handle);
+}
+
+int AlsaWrapper::PcmDrop(snd_pcm_t* handle) {
+ return snd_pcm_drop(handle);
+}
+
+int AlsaWrapper::PcmDelay(snd_pcm_t* handle, snd_pcm_sframes_t* delay) {
+ return snd_pcm_delay(handle, delay);
+}
+
+int AlsaWrapper::PcmResume(snd_pcm_t* handle) {
+ return snd_pcm_resume(handle);
+}
+
+snd_pcm_sframes_t AlsaWrapper::PcmWritei(snd_pcm_t* handle,
+ const void* buffer,
+ snd_pcm_uframes_t size) {
+ return snd_pcm_writei(handle, buffer, size);
+}
+
+snd_pcm_sframes_t AlsaWrapper::PcmReadi(snd_pcm_t* handle,
+ void* buffer,
+ snd_pcm_uframes_t size) {
+ return snd_pcm_readi(handle, buffer, size);
+}
+
+int AlsaWrapper::PcmRecover(snd_pcm_t* handle, int err, int silent) {
+ return snd_pcm_recover(handle, err, silent);
+}
+
+const char* AlsaWrapper::PcmName(snd_pcm_t* handle) {
+ return snd_pcm_name(handle);
+}
+
+int AlsaWrapper::PcmSetParams(snd_pcm_t* handle, snd_pcm_format_t format,
+ snd_pcm_access_t access, unsigned int channels,
+ unsigned int rate, int soft_resample,
+ unsigned int latency) {
+ return snd_pcm_set_params(handle,
+ format,
+ access,
+ channels,
+ rate,
+ soft_resample,
+ latency);
+}
+
+int AlsaWrapper::PcmGetParams(snd_pcm_t* handle, snd_pcm_uframes_t* buffer_size,
+ snd_pcm_uframes_t* period_size) {
+ return snd_pcm_get_params(handle, buffer_size, period_size);
+}
+
+int AlsaWrapper::PcmHwParamsMalloc(snd_pcm_hw_params_t** hw_params) {
+ return snd_pcm_hw_params_malloc(hw_params);
+}
+
+int AlsaWrapper::PcmHwParamsAny(snd_pcm_t* handle,
+ snd_pcm_hw_params_t* hw_params) {
+ return snd_pcm_hw_params_any(handle, hw_params);
+}
+
+int AlsaWrapper::PcmHwParamsCanResume(snd_pcm_hw_params_t* hw_params) {
+ return snd_pcm_hw_params_can_resume(hw_params);
+}
+
+int AlsaWrapper::PcmHwParamsSetRateResample(snd_pcm_t* handle,
+ snd_pcm_hw_params_t* hw_params,
+ unsigned int value) {
+ return snd_pcm_hw_params_set_rate_resample(handle, hw_params, value);
+}
+
+int AlsaWrapper::PcmHwParamsSetRateNear(snd_pcm_t* handle,
+ snd_pcm_hw_params_t* hw_params,
+ unsigned int* rate,
+ int* direction) {
+ return snd_pcm_hw_params_set_rate_near(handle, hw_params, rate, direction);
+}
+
+int AlsaWrapper::PcmHwParamsTestFormat(snd_pcm_t* handle,
+ snd_pcm_hw_params_t* hw_params,
+ snd_pcm_format_t format) {
+ return snd_pcm_hw_params_test_format(handle, hw_params, format);
+}
+
+int AlsaWrapper::PcmFormatSize(snd_pcm_format_t format, size_t samples) {
+ return snd_pcm_format_size(format, samples);
+}
+
+int AlsaWrapper::PcmHwParamsGetChannelsMin(const snd_pcm_hw_params_t* hw_params,
+ unsigned int* min_channels) {
+ return snd_pcm_hw_params_get_channels_min(hw_params, min_channels);
+}
+
+int AlsaWrapper::PcmHwParamsGetChannelsMax(const snd_pcm_hw_params_t* hw_params,
+ unsigned int* max_channels) {
+ return snd_pcm_hw_params_get_channels_min(hw_params, max_channels);
+}
+
+int AlsaWrapper::PcmHwParamsSetFormat(snd_pcm_t* handle,
+ snd_pcm_hw_params_t* hw_params,
+ snd_pcm_format_t format) {
+ return snd_pcm_hw_params_set_format(handle, hw_params, format);
+}
+
+int AlsaWrapper::PcmHwParamsSetAccess(snd_pcm_t* handle,
+ snd_pcm_hw_params_t* hw_params,
+ snd_pcm_access_t access) {
+ return snd_pcm_hw_params_set_access(handle, hw_params, access);
+}
+
+int AlsaWrapper::PcmHwParamsSetChannels(snd_pcm_t* handle,
+ snd_pcm_hw_params_t* hw_params,
+ unsigned int channels) {
+ return snd_pcm_hw_params_set_channels(handle, hw_params, channels);
+}
+
+int AlsaWrapper::PcmHwParamsSetBufferSizeNear(snd_pcm_t* handle,
+ snd_pcm_hw_params_t* hw_params,
+ snd_pcm_uframes_t* buffer_size) {
+ return snd_pcm_hw_params_set_buffer_size_near(handle, hw_params, buffer_size);
+}
+
+int AlsaWrapper::PcmHwParamsSetPeriodSizeNear(snd_pcm_t* handle,
+ snd_pcm_hw_params_t* hw_params,
+ snd_pcm_uframes_t* period_size,
+ int* direction) {
+ return snd_pcm_hw_params_set_period_size_near(handle, hw_params, period_size,
+ direction);
+}
+
+int AlsaWrapper::PcmHwParams(snd_pcm_t* handle,
+ snd_pcm_hw_params_t* hw_params) {
+ return snd_pcm_hw_params(handle, hw_params);
+}
+
+void AlsaWrapper::PcmHwParamsFree(snd_pcm_hw_params_t* hw_params) {
+ return snd_pcm_hw_params_free(hw_params);
+}
+
+int AlsaWrapper::PcmSwParamsMalloc(snd_pcm_sw_params_t** sw_params) {
+ return snd_pcm_sw_params_malloc(sw_params);
+}
+
+int AlsaWrapper::PcmSwParamsCurrent(snd_pcm_t* handle,
+ snd_pcm_sw_params_t* sw_params) {
+ return snd_pcm_sw_params_current(handle, sw_params);
+}
+
+int AlsaWrapper::PcmSwParamsSetStartThreshold(
+ snd_pcm_t* handle,
+ snd_pcm_sw_params_t* sw_params,
+ snd_pcm_uframes_t start_threshold) {
+ return snd_pcm_sw_params_set_start_threshold(handle, sw_params,
+ start_threshold);
+}
+
+int AlsaWrapper::PcmSwParamsSetAvailMin(snd_pcm_t* handle,
+ snd_pcm_sw_params_t* sw_params,
+ snd_pcm_uframes_t period_size) {
+ return snd_pcm_sw_params_set_avail_min(handle, sw_params, period_size);
+}
+
+int AlsaWrapper::PcmSwParams(snd_pcm_t* handle,
+ snd_pcm_sw_params_t* sw_params) {
+ return snd_pcm_sw_params(handle, sw_params);
+}
+
+void AlsaWrapper::PcmSwParamsFree(snd_pcm_sw_params_t* sw_params) {
+ return snd_pcm_sw_params_free(sw_params);
+}
+
+snd_pcm_sframes_t AlsaWrapper::PcmAvailUpdate(snd_pcm_t* handle) {
+ return snd_pcm_avail_update(handle);
+}
+
+snd_pcm_state_t AlsaWrapper::PcmState(snd_pcm_t* handle) {
+ return snd_pcm_state(handle);
+}
+
+const char* AlsaWrapper::StrError(int errnum) {
+ return snd_strerror(errnum);
+}
+
+int AlsaWrapper::PcmStart(snd_pcm_t* handle) {
+ return snd_pcm_start(handle);
+}
+
+int AlsaWrapper::MixerOpen(snd_mixer_t** mixer, int mode) {
+ return snd_mixer_open(mixer, mode);
+}
+
+int AlsaWrapper::MixerAttach(snd_mixer_t* mixer, const char* name) {
+ return snd_mixer_attach(mixer, name);
+}
+
+int AlsaWrapper::MixerElementRegister(snd_mixer_t* mixer,
+ struct snd_mixer_selem_regopt* options,
+ snd_mixer_class_t** classp) {
+ return snd_mixer_selem_register(mixer, options, classp);
+}
+
+void AlsaWrapper::MixerFree(snd_mixer_t* mixer) {
+ snd_mixer_free(mixer);
+}
+
+int AlsaWrapper::MixerDetach(snd_mixer_t* mixer, const char* name) {
+ return snd_mixer_detach(mixer, name);
+}
+
+int AlsaWrapper::MixerClose(snd_mixer_t* mixer) {
+ return snd_mixer_close(mixer);
+}
+
+int AlsaWrapper::MixerLoad(snd_mixer_t* mixer) {
+ return snd_mixer_load(mixer);
+}
+
+snd_mixer_elem_t* AlsaWrapper::MixerFirstElem(snd_mixer_t* mixer) {
+ return snd_mixer_first_elem(mixer);
+}
+
+snd_mixer_elem_t* AlsaWrapper::MixerNextElem(snd_mixer_elem_t* elem) {
+ return snd_mixer_elem_next(elem);
+}
+
+int AlsaWrapper::MixerSelemIsActive(snd_mixer_elem_t* elem) {
+ return snd_mixer_selem_is_active(elem);
+}
+
+const char* AlsaWrapper::MixerSelemName(snd_mixer_elem_t* elem) {
+ return snd_mixer_selem_get_name(elem);
+}
+
+int AlsaWrapper::MixerSelemSetCaptureVolumeAll(
+ snd_mixer_elem_t* elem, long value) {
+ return snd_mixer_selem_set_capture_volume_all(elem, value);
+}
+
+int AlsaWrapper::MixerSelemGetCaptureVolume(
+ snd_mixer_elem_t* elem, snd_mixer_selem_channel_id_t channel, long* value) {
+ return snd_mixer_selem_get_capture_volume(elem, channel, value);
+}
+
+int AlsaWrapper::MixerSelemHasCaptureVolume(snd_mixer_elem_t* elem) {
+ return snd_mixer_selem_has_capture_volume(elem);
+}
+
+int AlsaWrapper::MixerSelemGetCaptureVolumeRange(snd_mixer_elem_t* elem,
+ long* min, long* max) {
+ return snd_mixer_selem_get_capture_volume_range(elem, min, max);
+}
+
+void* AlsaWrapper::MixerElemGetCallbackPrivate(const snd_mixer_elem_t* obj) {
+ return snd_mixer_elem_get_callback_private(obj);
+}
+
+void AlsaWrapper::MixerElemSetCallback(snd_mixer_elem_t* obj,
+ snd_mixer_elem_callback_t val) {
+ snd_mixer_elem_set_callback(obj, val);
+}
+
+void AlsaWrapper::MixerElemSetCallbackPrivate(snd_mixer_elem_t* obj,
+ void* val) {
+ snd_mixer_elem_set_callback_private(obj, val);
+}
+
+snd_mixer_elem_t* AlsaWrapper::MixerFindSelem(snd_mixer_t* mixer,
+ const snd_mixer_selem_id_t* id) {
+ return snd_mixer_find_selem(mixer, id);
+}
+
+int AlsaWrapper::MixerHandleEvents(snd_mixer_t* mixer) {
+ return snd_mixer_handle_events(mixer);
+}
+
+int AlsaWrapper::MixerPollDescriptors(snd_mixer_t* mixer,
+ struct pollfd* pfds,
+ unsigned int space) {
+ return snd_mixer_poll_descriptors(mixer, pfds, space);
+}
+
+int AlsaWrapper::MixerPollDescriptorsCount(snd_mixer_t* mixer) {
+ return snd_mixer_poll_descriptors_count(mixer);
+}
+
+int AlsaWrapper::MixerSelemGetPlaybackSwitch(
+ snd_mixer_elem_t* elem,
+ snd_mixer_selem_channel_id_t channel,
+ int* value) {
+ return snd_mixer_selem_get_playback_switch(elem, channel, value);
+}
+
+int AlsaWrapper::MixerSelemGetPlaybackVolume(
+ snd_mixer_elem_t* elem,
+ snd_mixer_selem_channel_id_t channel,
+ long* value) {
+ return snd_mixer_selem_get_playback_volume(elem, channel, value);
+}
+
+int AlsaWrapper::MixerSelemGetPlaybackVolumeRange(snd_mixer_elem_t* elem,
+ long* min,
+ long* max) {
+ return snd_mixer_selem_get_playback_volume_range(elem, min, max);
+}
+
+int AlsaWrapper::MixerSelemHasPlaybackSwitch(snd_mixer_elem_t* elem) {
+ return snd_mixer_selem_has_playback_switch(elem);
+}
+
+void AlsaWrapper::MixerSelemIdSetIndex(snd_mixer_selem_id_t* obj,
+ unsigned int val) {
+ snd_mixer_selem_id_set_index(obj, val);
+}
+
+void AlsaWrapper::MixerSelemIdSetName(snd_mixer_selem_id_t* obj,
+ const char* val) {
+ snd_mixer_selem_id_set_name(obj, val);
+}
+
+int AlsaWrapper::MixerSelemSetPlaybackSwitch(
+ snd_mixer_elem_t* elem,
+ snd_mixer_selem_channel_id_t channel,
+ int value) {
+ return snd_mixer_selem_set_playback_switch(elem, channel, value);
+}
+
+int AlsaWrapper::MixerSelemSetPlaybackVolumeAll(snd_mixer_elem_t* elem,
+ long value) {
+ return snd_mixer_selem_set_playback_volume_all(elem, value);
+}
+
+int AlsaWrapper::MixerSelemIdMalloc(snd_mixer_selem_id_t** ptr) {
+ return snd_mixer_selem_id_malloc(ptr);
+}
+
+void AlsaWrapper::MixerSelemIdFree(snd_mixer_selem_id_t* obj) {
+ snd_mixer_selem_id_free(obj);
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/alsa/alsa_wrapper.h b/third_party/chromium/media/audio/alsa/alsa_wrapper.h
new file mode 100644
index 0000000..30905b4
--- /dev/null
+++ b/third_party/chromium/media/audio/alsa/alsa_wrapper.h
@@ -0,0 +1,163 @@
+// Copyright 2013 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.
+//
+// AlsaWrapper is a simple stateless class that wraps the alsa library commands
+// we want to use. It's purpose is to allow injection of a mock so that the
+// higher level code is testable.
+
+#ifndef MEDIA_AUDIO_ALSA_ALSA_WRAPPER_H_
+#define MEDIA_AUDIO_ALSA_ALSA_WRAPPER_H_
+
+#include <alsa/asoundlib.h>
+
+#include "base/macros.h"
+#include "media/base/media_export.h"
+
+namespace media {
+
+class MEDIA_EXPORT AlsaWrapper {
+ public:
+ AlsaWrapper();
+
+ AlsaWrapper(const AlsaWrapper&) = delete;
+ AlsaWrapper& operator=(const AlsaWrapper&) = delete;
+
+ virtual ~AlsaWrapper();
+
+ virtual int DeviceNameHint(int card, const char* iface, void*** hints);
+ virtual char* DeviceNameGetHint(const void* hint, const char* id);
+ virtual int DeviceNameFreeHint(void** hints);
+ virtual int CardNext(int* rcard);
+
+ virtual int PcmOpen(snd_pcm_t** handle, const char* name,
+ snd_pcm_stream_t stream, int mode);
+ virtual int PcmClose(snd_pcm_t* handle);
+ virtual int PcmPrepare(snd_pcm_t* handle);
+ virtual int PcmDrain(snd_pcm_t* handle);
+ virtual int PcmDrop(snd_pcm_t* handle);
+ virtual int PcmDelay(snd_pcm_t* handle, snd_pcm_sframes_t* delay);
+ virtual int PcmResume(snd_pcm_t* handle);
+ virtual snd_pcm_sframes_t PcmWritei(snd_pcm_t* handle,
+ const void* buffer,
+ snd_pcm_uframes_t size);
+ virtual snd_pcm_sframes_t PcmReadi(snd_pcm_t* handle,
+ void* buffer,
+ snd_pcm_uframes_t size);
+ virtual int PcmRecover(snd_pcm_t* handle, int err, int silent);
+ virtual int PcmSetParams(snd_pcm_t* handle, snd_pcm_format_t format,
+ snd_pcm_access_t access, unsigned int channels,
+ unsigned int rate, int soft_resample,
+ unsigned int latency);
+ virtual int PcmGetParams(snd_pcm_t* handle, snd_pcm_uframes_t* buffer_size,
+ snd_pcm_uframes_t* period_size);
+ virtual int PcmHwParamsMalloc(snd_pcm_hw_params_t** hw_params);
+ virtual int PcmHwParamsAny(snd_pcm_t* handle, snd_pcm_hw_params_t* hw_params);
+ virtual int PcmHwParamsCanResume(snd_pcm_hw_params_t* hw_params);
+ virtual int PcmHwParamsSetRateResample(snd_pcm_t* handle,
+ snd_pcm_hw_params_t* hw_params,
+ unsigned int value);
+ virtual int PcmHwParamsSetRateNear(snd_pcm_t* handle,
+ snd_pcm_hw_params_t* hw_params,
+ unsigned int* rate,
+ int* direction);
+ virtual int PcmHwParamsTestFormat(snd_pcm_t* handle,
+ snd_pcm_hw_params_t* hw_params,
+ snd_pcm_format_t format);
+ virtual int PcmFormatSize(snd_pcm_format_t format, size_t samples);
+ virtual int PcmHwParamsGetChannelsMin(const snd_pcm_hw_params_t* hw_params,
+ unsigned int* min_channels);
+ virtual int PcmHwParamsGetChannelsMax(const snd_pcm_hw_params_t* hw_params,
+ unsigned int* max_channels);
+ virtual int PcmHwParamsSetFormat(snd_pcm_t* handle,
+ snd_pcm_hw_params_t* hw_params,
+ snd_pcm_format_t format);
+ virtual int PcmHwParamsSetAccess(snd_pcm_t* handle,
+ snd_pcm_hw_params_t* hw_params,
+ snd_pcm_access_t access);
+ virtual int PcmHwParamsSetChannels(snd_pcm_t* handle,
+ snd_pcm_hw_params_t* hw_params,
+ unsigned int channels);
+ virtual int PcmHwParamsSetBufferSizeNear(snd_pcm_t* handle,
+ snd_pcm_hw_params_t* hw_params,
+ snd_pcm_uframes_t* buffer_size);
+ virtual int PcmHwParamsSetPeriodSizeNear(snd_pcm_t* handle,
+ snd_pcm_hw_params_t* hw_params,
+ snd_pcm_uframes_t* period_size,
+ int* direction);
+ virtual int PcmHwParams(snd_pcm_t* handle, snd_pcm_hw_params_t* hw_params);
+ virtual void PcmHwParamsFree(snd_pcm_hw_params_t* hw_params);
+ virtual int PcmSwParamsMalloc(snd_pcm_sw_params_t** sw_params);
+ virtual int PcmSwParamsCurrent(snd_pcm_t* handle,
+ snd_pcm_sw_params_t* sw_params);
+ virtual int PcmSwParamsSetStartThreshold(snd_pcm_t* handle,
+ snd_pcm_sw_params_t* sw_params,
+ snd_pcm_uframes_t start_threshold);
+ virtual int PcmSwParamsSetAvailMin(snd_pcm_t* handle,
+ snd_pcm_sw_params_t* sw_params,
+ snd_pcm_uframes_t period_size);
+ virtual int PcmSwParams(snd_pcm_t* handle, snd_pcm_sw_params_t* sw_params);
+ virtual void PcmSwParamsFree(snd_pcm_sw_params_t* sw_params);
+ virtual const char* PcmName(snd_pcm_t* handle);
+ virtual snd_pcm_sframes_t PcmAvailUpdate(snd_pcm_t* handle);
+ virtual snd_pcm_state_t PcmState(snd_pcm_t* handle);
+ virtual int PcmStart(snd_pcm_t* handle);
+
+ virtual int MixerOpen(snd_mixer_t** mixer, int mode);
+ virtual int MixerAttach(snd_mixer_t* mixer, const char* name);
+ virtual int MixerElementRegister(snd_mixer_t* mixer,
+ struct snd_mixer_selem_regopt* options,
+ snd_mixer_class_t** classp);
+ virtual void MixerFree(snd_mixer_t* mixer);
+ virtual int MixerDetach(snd_mixer_t* mixer, const char* name);
+ virtual int MixerClose(snd_mixer_t* mixer);
+ virtual int MixerLoad(snd_mixer_t* mixer);
+ virtual snd_mixer_elem_t* MixerFirstElem(snd_mixer_t* mixer);
+ virtual snd_mixer_elem_t* MixerNextElem(snd_mixer_elem_t* elem);
+ virtual int MixerSelemIsActive(snd_mixer_elem_t* elem);
+ virtual const char* MixerSelemName(snd_mixer_elem_t* elem);
+ virtual int MixerSelemSetCaptureVolumeAll(snd_mixer_elem_t* elem, long value);
+ virtual int MixerSelemGetCaptureVolume(snd_mixer_elem_t* elem,
+ snd_mixer_selem_channel_id_t channel,
+ long* value);
+ virtual int MixerSelemHasCaptureVolume(snd_mixer_elem_t* elem);
+ virtual int MixerSelemGetCaptureVolumeRange(snd_mixer_elem_t* elem,
+ long* min, long* max);
+ virtual void* MixerElemGetCallbackPrivate(const snd_mixer_elem_t* obj);
+ virtual void MixerElemSetCallback(snd_mixer_elem_t* obj,
+ snd_mixer_elem_callback_t val);
+ virtual void MixerElemSetCallbackPrivate(snd_mixer_elem_t* obj, void* val);
+ virtual snd_mixer_elem_t* MixerFindSelem(snd_mixer_t* mixer,
+ const snd_mixer_selem_id_t* id);
+ virtual int MixerHandleEvents(snd_mixer_t* mixer);
+ virtual int MixerPollDescriptors(snd_mixer_t* mixer,
+ struct pollfd* pfds,
+ unsigned int space);
+ virtual int MixerPollDescriptorsCount(snd_mixer_t* mixer);
+ virtual int MixerSelemGetPlaybackSwitch(snd_mixer_elem_t* elem,
+ snd_mixer_selem_channel_id_t channel,
+ int* value);
+ virtual int MixerSelemGetPlaybackVolume(snd_mixer_elem_t* elem,
+ snd_mixer_selem_channel_id_t channel,
+ long* value);
+ virtual int MixerSelemGetPlaybackVolumeRange(snd_mixer_elem_t* elem,
+ long* min,
+ long* max);
+ virtual int MixerSelemHasPlaybackSwitch(snd_mixer_elem_t* elem);
+ virtual void MixerSelemIdSetIndex(snd_mixer_selem_id_t* obj,
+ unsigned int val);
+ virtual void MixerSelemIdSetName(snd_mixer_selem_id_t* obj, const char* val);
+ virtual int MixerSelemSetPlaybackSwitch(snd_mixer_elem_t* elem,
+ snd_mixer_selem_channel_id_t channel,
+ int value);
+ virtual int MixerSelemSetPlaybackVolumeAll(snd_mixer_elem_t* elem,
+ long value);
+ virtual int MixerSelemIdMalloc(snd_mixer_selem_id_t** ptr);
+ virtual void MixerSelemIdFree(snd_mixer_selem_id_t* obj);
+
+ virtual const char* StrError(int errnum);
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_ALSA_ALSA_WRAPPER_H_
diff --git a/third_party/chromium/media/audio/alsa/audio_manager_alsa.cc b/third_party/chromium/media/audio/alsa/audio_manager_alsa.cc
new file mode 100644
index 0000000..12a95b3
--- /dev/null
+++ b/third_party/chromium/media/audio/alsa/audio_manager_alsa.cc
@@ -0,0 +1,324 @@
+// Copyright 2013 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 "media/audio/alsa/audio_manager_alsa.h"
+
+#include <stddef.h>
+
+#include "base/command_line.h"
+#include "base/cxx17_backports.h"
+#include "base/logging.h"
+#include "base/memory/free_deleter.h"
+#include "base/metrics/histogram.h"
+#include "media/audio/alsa/alsa_input.h"
+#include "media/audio/alsa/alsa_output.h"
+#include "media/audio/alsa/alsa_wrapper.h"
+#include "media/audio/audio_device_description.h"
+#include "media/audio/audio_output_dispatcher.h"
+#if defined(USE_PULSEAUDIO)
+#include "media/audio/pulse/audio_manager_pulse.h"
+#endif
+#include "media/base/audio_parameters.h"
+#include "media/base/channel_layout.h"
+#include "media/base/limits.h"
+#include "media/base/media_switches.h"
+
+namespace media {
+
+// Maximum number of output streams that can be open simultaneously.
+static const int kMaxOutputStreams = 50;
+
+// Default sample rate for input and output streams.
+static const int kDefaultSampleRate = 48000;
+
+// Since "default", "pulse" and "dmix" devices are virtual devices mapped to
+// real devices, we remove them from the list to avoiding duplicate counting.
+// In addition, note that we support no more than 2 channels for recording,
+// hence surround devices are not stored in the list.
+static const char* const kInvalidAudioInputDevices[] = {
+ "default", "dmix", "null", "pulse", "surround",
+};
+
+AudioManagerAlsa::AudioManagerAlsa(std::unique_ptr<AudioThread> audio_thread,
+ AudioLogFactory* audio_log_factory)
+ : AudioManagerBase(std::move(audio_thread), audio_log_factory),
+ wrapper_(new AlsaWrapper()) {
+ SetMaxOutputStreamsAllowed(kMaxOutputStreams);
+}
+
+AudioManagerAlsa::~AudioManagerAlsa() = default;
+
+bool AudioManagerAlsa::HasAudioOutputDevices() {
+ return HasAnyAlsaAudioDevice(kStreamPlayback);
+}
+
+bool AudioManagerAlsa::HasAudioInputDevices() {
+ return HasAnyAlsaAudioDevice(kStreamCapture);
+}
+
+void AudioManagerAlsa::GetAudioInputDeviceNames(
+ AudioDeviceNames* device_names) {
+ DCHECK(device_names->empty());
+ GetAlsaAudioDevices(kStreamCapture, device_names);
+}
+
+void AudioManagerAlsa::GetAudioOutputDeviceNames(
+ AudioDeviceNames* device_names) {
+ DCHECK(device_names->empty());
+ GetAlsaAudioDevices(kStreamPlayback, device_names);
+}
+
+AudioParameters AudioManagerAlsa::GetInputStreamParameters(
+ const std::string& device_id) {
+ static const int kDefaultInputBufferSize = 1024;
+
+ return AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
+ CHANNEL_LAYOUT_STEREO, kDefaultSampleRate,
+ kDefaultInputBufferSize);
+}
+
+const char* AudioManagerAlsa::GetName() {
+ return "ALSA";
+}
+
+void AudioManagerAlsa::GetAlsaAudioDevices(StreamType type,
+ AudioDeviceNames* device_names) {
+ // Constants specified by the ALSA API for device hints.
+ static const char kPcmInterfaceName[] = "pcm";
+ int card = -1;
+
+ // Loop through the sound cards to get ALSA device hints.
+ while (!wrapper_->CardNext(&card) && card >= 0) {
+ void** hints = NULL;
+ int error = wrapper_->DeviceNameHint(card, kPcmInterfaceName, &hints);
+ if (!error) {
+ GetAlsaDevicesInfo(type, hints, device_names);
+
+ // Destroy the hints now that we're done with it.
+ wrapper_->DeviceNameFreeHint(hints);
+ } else {
+ DLOG(WARNING) << "GetAlsaAudioDevices: unable to get device hints: "
+ << wrapper_->StrError(error);
+ }
+ }
+}
+
+void AudioManagerAlsa::GetAlsaDevicesInfo(AudioManagerAlsa::StreamType type,
+ void** hints,
+ AudioDeviceNames* device_names) {
+ static const char kIoHintName[] = "IOID";
+ static const char kNameHintName[] = "NAME";
+ static const char kDescriptionHintName[] = "DESC";
+
+ const char* unwanted_device_type = UnwantedDeviceTypeWhenEnumerating(type);
+
+ for (void** hint_iter = hints; *hint_iter != NULL; hint_iter++) {
+ // Only examine devices of the right type. Valid values are
+ // "Input", "Output", and NULL which means both input and output.
+ std::unique_ptr<char, base::FreeDeleter> io(
+ wrapper_->DeviceNameGetHint(*hint_iter, kIoHintName));
+ if (io != NULL && strcmp(unwanted_device_type, io.get()) == 0)
+ continue;
+
+ // Found a device, prepend the default device since we always want
+ // it to be on the top of the list for all platforms. And there is
+ // no duplicate counting here since it is only done if the list is
+ // still empty. Note, pulse has exclusively opened the default
+ // device, so we must open the device via the "default" moniker.
+ if (device_names->empty())
+ device_names->push_front(AudioDeviceName::CreateDefault());
+
+ // Get the unique device name for the device.
+ std::unique_ptr<char, base::FreeDeleter> unique_device_name(
+ wrapper_->DeviceNameGetHint(*hint_iter, kNameHintName));
+
+ // Find out if the device is available.
+ if (IsAlsaDeviceAvailable(type, unique_device_name.get())) {
+ // Get the description for the device.
+ std::unique_ptr<char, base::FreeDeleter> desc(
+ wrapper_->DeviceNameGetHint(*hint_iter, kDescriptionHintName));
+
+ AudioDeviceName name;
+ name.unique_id = unique_device_name.get();
+ if (desc) {
+ // Use the more user friendly description as name.
+ // Replace '\n' with '-'.
+ char* pret = strchr(desc.get(), '\n');
+ if (pret)
+ *pret = '-';
+ name.device_name = desc.get();
+ } else {
+ // Virtual devices don't necessarily have descriptions.
+ // Use their names instead.
+ name.device_name = unique_device_name.get();
+ }
+
+ // Store the device information.
+ device_names->push_back(name);
+ }
+ }
+}
+
+// static
+bool AudioManagerAlsa::IsAlsaDeviceAvailable(
+ AudioManagerAlsa::StreamType type,
+ const char* device_name) {
+ if (!device_name)
+ return false;
+
+ // We do prefix matches on the device name to see whether to include
+ // it or not.
+ if (type == kStreamCapture) {
+ // Check if the device is in the list of invalid devices.
+ for (size_t i = 0; i < base::size(kInvalidAudioInputDevices); ++i) {
+ if (strncmp(kInvalidAudioInputDevices[i], device_name,
+ strlen(kInvalidAudioInputDevices[i])) == 0)
+ return false;
+ }
+ return true;
+ }
+
+ DCHECK_EQ(kStreamPlayback, type);
+ // We prefer the device type that maps straight to hardware but
+ // goes through software conversion if needed (e.g. incompatible
+ // sample rate).
+ // TODO(joi): Should we prefer "hw" instead?
+ static const char kDeviceTypeDesired[] = "plughw";
+ return strncmp(kDeviceTypeDesired, device_name,
+ base::size(kDeviceTypeDesired) - 1) == 0;
+}
+
+// static
+const char* AudioManagerAlsa::UnwantedDeviceTypeWhenEnumerating(
+ AudioManagerAlsa::StreamType wanted_type) {
+ return wanted_type == kStreamPlayback ? "Input" : "Output";
+}
+
+bool AudioManagerAlsa::HasAnyAlsaAudioDevice(
+ AudioManagerAlsa::StreamType stream) {
+ static const char kPcmInterfaceName[] = "pcm";
+ static const char kIoHintName[] = "IOID";
+ void** hints = NULL;
+ bool has_device = false;
+ int card = -1;
+
+ // Loop through the sound cards.
+ // Don't use snd_device_name_hint(-1,..) since there is a access violation
+ // inside this ALSA API with libasound.so.2.0.0.
+ while (!wrapper_->CardNext(&card) && (card >= 0) && !has_device) {
+ int error = wrapper_->DeviceNameHint(card, kPcmInterfaceName, &hints);
+ if (!error) {
+ for (void** hint_iter = hints; *hint_iter != NULL; hint_iter++) {
+ // Only examine devices that are |stream| capable. Valid values are
+ // "Input", "Output", and NULL which means both input and output.
+ std::unique_ptr<char, base::FreeDeleter> io(
+ wrapper_->DeviceNameGetHint(*hint_iter, kIoHintName));
+ const char* unwanted_type = UnwantedDeviceTypeWhenEnumerating(stream);
+ if (io != NULL && strcmp(unwanted_type, io.get()) == 0)
+ continue; // Wrong type, skip the device.
+
+ // Found an input device.
+ has_device = true;
+ break;
+ }
+
+ // Destroy the hints now that we're done with it.
+ wrapper_->DeviceNameFreeHint(hints);
+ hints = NULL;
+ } else {
+ DLOG(WARNING) << "HasAnyAudioDevice: unable to get device hints: "
+ << wrapper_->StrError(error);
+ }
+ }
+
+ return has_device;
+}
+
+AudioOutputStream* AudioManagerAlsa::MakeLinearOutputStream(
+ const AudioParameters& params,
+ const LogCallback& log_callback) {
+ DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format());
+ return MakeOutputStream(params);
+}
+
+AudioOutputStream* AudioManagerAlsa::MakeLowLatencyOutputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) {
+ DLOG_IF(ERROR, !device_id.empty()) << "Not implemented!";
+ DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format());
+ return MakeOutputStream(params);
+}
+
+AudioInputStream* AudioManagerAlsa::MakeLinearInputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) {
+ DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format());
+ return MakeInputStream(params, device_id);
+}
+
+AudioInputStream* AudioManagerAlsa::MakeLowLatencyInputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) {
+ DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format());
+ return MakeInputStream(params, device_id);
+}
+
+AudioParameters AudioManagerAlsa::GetPreferredOutputStreamParameters(
+ const std::string& output_device_id,
+ const AudioParameters& input_params) {
+ // TODO(tommi): Support |output_device_id|.
+ DLOG_IF(ERROR, !output_device_id.empty()) << "Not implemented!";
+ static const int kDefaultOutputBufferSize = 2048;
+ ChannelLayout channel_layout = CHANNEL_LAYOUT_STEREO;
+ int sample_rate = kDefaultSampleRate;
+ int buffer_size = kDefaultOutputBufferSize;
+ if (input_params.IsValid()) {
+ // Some clients, such as WebRTC, have a more limited use case and work
+ // acceptably with a smaller buffer size. The check below allows clients
+ // which want to try a smaller buffer size on Linux to do so.
+ // TODO(dalecurtis): This should include bits per channel and channel layout
+ // eventually.
+ sample_rate = input_params.sample_rate();
+ channel_layout = input_params.channel_layout();
+ buffer_size = std::min(input_params.frames_per_buffer(), buffer_size);
+ }
+
+ int user_buffer_size = GetUserBufferSize();
+ if (user_buffer_size)
+ buffer_size = user_buffer_size;
+
+ return AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, channel_layout,
+ sample_rate, buffer_size);
+}
+
+AudioOutputStream* AudioManagerAlsa::MakeOutputStream(
+ const AudioParameters& params) {
+ std::string device_name = AlsaPcmOutputStream::kAutoSelectDevice;
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kAlsaOutputDevice)) {
+ device_name = base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ switches::kAlsaOutputDevice);
+ }
+ return new AlsaPcmOutputStream(device_name, params, wrapper_.get(), this);
+}
+
+AudioInputStream* AudioManagerAlsa::MakeInputStream(
+ const AudioParameters& params, const std::string& device_id) {
+ std::string device_name =
+ (device_id == AudioDeviceDescription::kDefaultDeviceId)
+ ? AlsaPcmInputStream::kAutoSelectDevice
+ : device_id;
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kAlsaInputDevice)) {
+ device_name = base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ switches::kAlsaInputDevice);
+ }
+
+ return new AlsaPcmInputStream(this, device_name, params, wrapper_.get());
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/alsa/audio_manager_alsa.h b/third_party/chromium/media/audio/alsa/audio_manager_alsa.h
new file mode 100644
index 0000000..8b15c0a
--- /dev/null
+++ b/third_party/chromium/media/audio/alsa/audio_manager_alsa.h
@@ -0,0 +1,99 @@
+// Copyright 2013 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 MEDIA_AUDIO_ALSA_AUDIO_MANAGER_ALSA_H_
+#define MEDIA_AUDIO_ALSA_AUDIO_MANAGER_ALSA_H_
+
+#include <memory>
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/threading/thread.h"
+#include "media/audio/audio_manager_base.h"
+
+namespace media {
+
+class AlsaWrapper;
+
+class MEDIA_EXPORT AudioManagerAlsa : public AudioManagerBase {
+ public:
+ AudioManagerAlsa(std::unique_ptr<AudioThread> audio_thread,
+ AudioLogFactory* audio_log_factory);
+
+ AudioManagerAlsa(const AudioManagerAlsa&) = delete;
+ AudioManagerAlsa& operator=(const AudioManagerAlsa&) = delete;
+
+ ~AudioManagerAlsa() override;
+
+ // Implementation of AudioManager.
+ bool HasAudioOutputDevices() override;
+ bool HasAudioInputDevices() override;
+ void GetAudioInputDeviceNames(AudioDeviceNames* device_names) override;
+ void GetAudioOutputDeviceNames(AudioDeviceNames* device_names) override;
+ AudioParameters GetInputStreamParameters(
+ const std::string& device_id) override;
+ const char* GetName() override;
+
+ // Implementation of AudioManagerBase.
+ AudioOutputStream* MakeLinearOutputStream(
+ const AudioParameters& params,
+ const LogCallback& log_callback) override;
+ AudioOutputStream* MakeLowLatencyOutputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) override;
+ AudioInputStream* MakeLinearInputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) override;
+ AudioInputStream* MakeLowLatencyInputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) override;
+
+ protected:
+ AudioParameters GetPreferredOutputStreamParameters(
+ const std::string& output_device_id,
+ const AudioParameters& input_params) override;
+
+ private:
+ enum StreamType {
+ kStreamPlayback = 0,
+ kStreamCapture,
+ };
+
+ // Gets a list of available ALSA devices.
+ void GetAlsaAudioDevices(StreamType type, AudioDeviceNames* device_names);
+
+ // Gets the ALSA devices' names and ids that support streams of the
+ // given type.
+ void GetAlsaDevicesInfo(StreamType type,
+ void** hint,
+ AudioDeviceNames* device_names);
+
+ // Checks if the specific ALSA device is available.
+ static bool IsAlsaDeviceAvailable(StreamType type,
+ const char* device_name);
+
+ static const char* UnwantedDeviceTypeWhenEnumerating(
+ StreamType wanted_type);
+
+ // Returns true if a device is present for the given stream type.
+ bool HasAnyAlsaAudioDevice(StreamType stream);
+
+ // Called by MakeLinearOutputStream and MakeLowLatencyOutputStream.
+ AudioOutputStream* MakeOutputStream(const AudioParameters& params);
+
+ // Called by MakeLinearInputStream and MakeLowLatencyInputStream.
+ AudioInputStream* MakeInputStream(const AudioParameters& params,
+ const std::string& device_id);
+
+ std::unique_ptr<AlsaWrapper> wrapper_;
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_ALSA_AUDIO_MANAGER_ALSA_H_
diff --git a/third_party/chromium/media/audio/alsa/mock_alsa_wrapper.cc b/third_party/chromium/media/audio/alsa/mock_alsa_wrapper.cc
new file mode 100644
index 0000000..fdb9573
--- /dev/null
+++ b/third_party/chromium/media/audio/alsa/mock_alsa_wrapper.cc
@@ -0,0 +1,13 @@
+// Copyright 2018 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 "media/audio/alsa/mock_alsa_wrapper.h"
+
+namespace media {
+
+MockAlsaWrapper::MockAlsaWrapper() {}
+
+MockAlsaWrapper::~MockAlsaWrapper() = default;
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/alsa/mock_alsa_wrapper.h b/third_party/chromium/media/audio/alsa/mock_alsa_wrapper.h
new file mode 100644
index 0000000..91371ef
--- /dev/null
+++ b/third_party/chromium/media/audio/alsa/mock_alsa_wrapper.h
@@ -0,0 +1,191 @@
+// Copyright (c) 2018 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 MEDIA_AUDIO_ALSA_MOCK_ALSA_WRAPPER_H_
+#define MEDIA_AUDIO_ALSA_MOCK_ALSA_WRAPPER_H_
+
+#include "base/macros.h"
+#include "media/audio/alsa/alsa_wrapper.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace media {
+
+class MockAlsaWrapper : public AlsaWrapper {
+ public:
+ MockAlsaWrapper();
+
+ MockAlsaWrapper(const MockAlsaWrapper&) = delete;
+ MockAlsaWrapper& operator=(const MockAlsaWrapper&) = delete;
+
+ ~MockAlsaWrapper() override;
+
+ MOCK_METHOD3(DeviceNameHint, int(int card, const char* iface, void*** hints));
+ MOCK_METHOD2(DeviceNameGetHint, char*(const void* hint, const char* id));
+ MOCK_METHOD1(DeviceNameFreeHint, int(void** hints));
+ MOCK_METHOD1(CardNext, int(int* rcard));
+ MOCK_METHOD4(PcmOpen,
+ int(snd_pcm_t** handle,
+ const char* name,
+ snd_pcm_stream_t stream,
+ int mode));
+ MOCK_METHOD1(PcmClose, int(snd_pcm_t* handle));
+ MOCK_METHOD1(PcmPrepare, int(snd_pcm_t* handle));
+ MOCK_METHOD1(PcmDrain, int(snd_pcm_t* handle));
+ MOCK_METHOD1(PcmDrop, int(snd_pcm_t* handle));
+ MOCK_METHOD2(PcmDelay, int(snd_pcm_t* handle, snd_pcm_sframes_t* delay));
+ MOCK_METHOD1(PcmResume, int(snd_pcm_t* handle));
+ MOCK_METHOD3(PcmWritei,
+ snd_pcm_sframes_t(snd_pcm_t* handle,
+ const void* buffer,
+ snd_pcm_uframes_t size));
+ MOCK_METHOD3(PcmReadi,
+ snd_pcm_sframes_t(snd_pcm_t* handle,
+ void* buffer,
+ snd_pcm_uframes_t size));
+ MOCK_METHOD3(PcmRecover, int(snd_pcm_t* handle, int err, int silent));
+ MOCK_METHOD7(PcmSetParams,
+ int(snd_pcm_t* handle,
+ snd_pcm_format_t format,
+ snd_pcm_access_t access,
+ unsigned int channels,
+ unsigned int rate,
+ int soft_resample,
+ unsigned int latency));
+ MOCK_METHOD3(PcmGetParams,
+ int(snd_pcm_t* handle,
+ snd_pcm_uframes_t* buffer_size,
+ snd_pcm_uframes_t* period_size));
+ MOCK_METHOD1(PcmHwParamsMalloc, int(snd_pcm_hw_params_t** hw_params));
+ MOCK_METHOD2(PcmHwParamsAny,
+ int(snd_pcm_t* handle, snd_pcm_hw_params_t* hw_params));
+ MOCK_METHOD1(PcmHwParamsCanResume, int(snd_pcm_hw_params_t* hw_params));
+
+ MOCK_METHOD3(PcmHwParamsSetRateResample,
+ int(snd_pcm_t* handle,
+ snd_pcm_hw_params_t* hw_params,
+ unsigned int value));
+ MOCK_METHOD4(PcmHwParamsSetRateNear,
+ int(snd_pcm_t* handle,
+ snd_pcm_hw_params_t* hw_params,
+ unsigned int* rate,
+ int* direction));
+ MOCK_METHOD3(PcmHwParamsTestFormat,
+ int(snd_pcm_t* handle,
+ snd_pcm_hw_params_t* hw_params,
+ snd_pcm_format_t format));
+ MOCK_METHOD2(PcmFormatSize, int(snd_pcm_format_t format, size_t samples));
+ MOCK_METHOD2(PcmHwParamsGetChannelsMin,
+ int(const snd_pcm_hw_params_t* hw_params,
+ unsigned int* min_channels));
+ MOCK_METHOD2(PcmHwParamsGetChannelsMax,
+ int(const snd_pcm_hw_params_t* hw_params,
+ unsigned int* max_channels));
+ MOCK_METHOD3(PcmHwParamsSetFormat,
+ int(snd_pcm_t* handle,
+ snd_pcm_hw_params_t* hw_params,
+ snd_pcm_format_t format));
+ MOCK_METHOD3(PcmHwParamsSetAccess,
+ int(snd_pcm_t* handle,
+ snd_pcm_hw_params_t* hw_params,
+ snd_pcm_access_t access));
+ MOCK_METHOD3(PcmHwParamsSetChannels,
+ int(snd_pcm_t* handle,
+ snd_pcm_hw_params_t* hw_params,
+ unsigned int channels));
+ MOCK_METHOD3(PcmHwParamsSetBufferSizeNear,
+ int(snd_pcm_t* handle,
+ snd_pcm_hw_params_t* hw_params,
+ snd_pcm_uframes_t* buffer_size));
+ MOCK_METHOD4(PcmHwParamsSetPeriodSizeNear,
+ int(snd_pcm_t* handle,
+ snd_pcm_hw_params_t* hw_params,
+ snd_pcm_uframes_t* period_size,
+ int* direction));
+ MOCK_METHOD2(PcmHwParams,
+ int(snd_pcm_t* handle, snd_pcm_hw_params_t* hw_params));
+ MOCK_METHOD1(PcmHwParamsFree, void(snd_pcm_hw_params_t* hw_params));
+ MOCK_METHOD1(PcmSwParamsMalloc, int(snd_pcm_sw_params_t** sw_params));
+ MOCK_METHOD2(PcmSwParamsCurrent,
+ int(snd_pcm_t* handle, snd_pcm_sw_params_t* sw_params));
+ MOCK_METHOD3(PcmSwParamsSetStartThreshold,
+ int(snd_pcm_t* handle,
+ snd_pcm_sw_params_t* sw_params,
+ snd_pcm_uframes_t start_threshold));
+ MOCK_METHOD3(PcmSwParamsSetAvailMin,
+ int(snd_pcm_t* handle,
+ snd_pcm_sw_params_t* sw_params,
+ snd_pcm_uframes_t period_size));
+ MOCK_METHOD2(PcmSwParams,
+ int(snd_pcm_t* handle, snd_pcm_sw_params_t* sw_params));
+ MOCK_METHOD1(PcmSwParamsFree, void(snd_pcm_sw_params_t* sw_params));
+ MOCK_METHOD1(PcmName, const char*(snd_pcm_t* handle));
+ MOCK_METHOD1(PcmAvailUpdate, snd_pcm_sframes_t(snd_pcm_t* handle));
+ MOCK_METHOD1(PcmState, snd_pcm_state_t(snd_pcm_t* handle));
+ MOCK_METHOD1(PcmStart, int(snd_pcm_t* handle));
+ MOCK_METHOD2(MixerOpen, int(snd_mixer_t** mixer, int mode));
+ MOCK_METHOD2(MixerAttach, int(snd_mixer_t* mixer, const char* name));
+ MOCK_METHOD3(MixerElementRegister,
+ int(snd_mixer_t* mixer,
+ struct snd_mixer_selem_regopt* options,
+ snd_mixer_class_t** classp));
+ MOCK_METHOD1(MixerFree, void(snd_mixer_t* mixer));
+ MOCK_METHOD2(MixerDetach, int(snd_mixer_t* mixer, const char* name));
+ MOCK_METHOD1(MixerClose, int(snd_mixer_t* mixer));
+ MOCK_METHOD1(MixerLoad, int(snd_mixer_t* mixer));
+ MOCK_METHOD1(MixerFirstElem, snd_mixer_elem_t*(snd_mixer_t* mixer));
+ MOCK_METHOD1(MixerNextElem, snd_mixer_elem_t*(snd_mixer_elem_t* elem));
+ MOCK_METHOD1(MixerSelemIsActive, int(snd_mixer_elem_t* elem));
+ MOCK_METHOD1(MixerSelemName, const char*(snd_mixer_elem_t* elem));
+ MOCK_METHOD2(MixerSelemSetCaptureVolumeAll,
+ int(snd_mixer_elem_t* elem, long value));
+ MOCK_METHOD3(MixerSelemGetCaptureVolume,
+ int(snd_mixer_elem_t* elem,
+ snd_mixer_selem_channel_id_t channel,
+ long* value));
+ MOCK_METHOD1(MixerSelemHasCaptureVolume, int(snd_mixer_elem_t* elem));
+ MOCK_METHOD3(MixerSelemGetCaptureVolumeRange,
+ int(snd_mixer_elem_t* elem, long* min, long* max));
+ MOCK_METHOD1(MixerElemGetCallbackPrivate, void*(const snd_mixer_elem_t* obj));
+ MOCK_METHOD2(MixerElemSetCallback,
+ void(snd_mixer_elem_t* obj, snd_mixer_elem_callback_t val));
+ MOCK_METHOD2(MixerElemSetCallbackPrivate,
+ void(snd_mixer_elem_t* obj, void* val));
+ MOCK_METHOD2(MixerFindSelem,
+ snd_mixer_elem_t*(snd_mixer_t* mixer,
+ const snd_mixer_selem_id_t* id));
+ MOCK_METHOD1(MixerHandleEvents, int(snd_mixer_t* mixer));
+ MOCK_METHOD3(MixerPollDescriptors,
+ int(snd_mixer_t* mixer,
+ struct pollfd* pfds,
+ unsigned int space));
+ MOCK_METHOD1(MixerPollDescriptorsCount, int(snd_mixer_t* mixer));
+ MOCK_METHOD3(MixerSelemGetPlaybackSwitch,
+ int(snd_mixer_elem_t* elem,
+ snd_mixer_selem_channel_id_t channel,
+ int* value));
+ MOCK_METHOD3(MixerSelemGetPlaybackVolume,
+ int(snd_mixer_elem_t* elem,
+ snd_mixer_selem_channel_id_t channel,
+ long* value));
+ MOCK_METHOD3(MixerSelemGetPlaybackVolumeRange,
+ int(snd_mixer_elem_t* elem, long* min, long* max));
+ MOCK_METHOD1(MixerSelemHasPlaybackSwitch, int(snd_mixer_elem_t* elem));
+ MOCK_METHOD2(MixerSelemIdSetIndex,
+ void(snd_mixer_selem_id_t* obj, unsigned int val));
+ MOCK_METHOD2(MixerSelemIdSetName,
+ void(snd_mixer_selem_id_t* obj, const char* val));
+ MOCK_METHOD3(MixerSelemSetPlaybackSwitch,
+ int(snd_mixer_elem_t* elem,
+ snd_mixer_selem_channel_id_t channel,
+ int value));
+ MOCK_METHOD2(MixerSelemSetPlaybackVolumeAll,
+ int(snd_mixer_elem_t* elem, long value));
+ MOCK_METHOD1(MixerSelemIdMalloc, int(snd_mixer_selem_id_t** ptr));
+ MOCK_METHOD1(MixerSelemIdFree, void(snd_mixer_selem_id_t* obj));
+ MOCK_METHOD1(StrError, const char*(int errnum));
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_ALSA_MOCK_ALSA_WRAPPER_H_
diff --git a/third_party/chromium/media/audio/android/aaudio.sigs b/third_party/chromium/media/audio/android/aaudio.sigs
new file mode 100644
index 0000000..1d89b56
--- /dev/null
+++ b/third_party/chromium/media/audio/android/aaudio.sigs
@@ -0,0 +1,32 @@
+// Copyright 2019 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.
+
+//------------------------------------------------
+// Functions from AAudio used in media code.
+//------------------------------------------------
+
+const char * AAudio_convertResultToText(aaudio_result_t returnCode);
+aaudio_result_t AAudio_createStreamBuilder(AAudioStreamBuilder** builder);
+void AAudioStreamBuilder_setDeviceId(AAudioStreamBuilder* builder, int32_t deviceId);
+void AAudioStreamBuilder_setSampleRate(AAudioStreamBuilder* builder, int32_t sampleRate);
+void AAudioStreamBuilder_setChannelCount(AAudioStreamBuilder* builder, int32_t channelCount);
+void AAudioStreamBuilder_setSamplesPerFrame(AAudioStreamBuilder* builder, int32_t samplesPerFrame);
+void AAudioStreamBuilder_setFormat(AAudioStreamBuilder* builder, aaudio_format_t format);
+void AAudioStreamBuilder_setDirection(AAudioStreamBuilder* builder, aaudio_direction_t direction);
+void AAudioStreamBuilder_setBufferCapacityInFrames(AAudioStreamBuilder* builder, int32_t numFrames);
+void AAudioStreamBuilder_setPerformanceMode(AAudioStreamBuilder* builder, aaudio_performance_mode_t mode);
+void AAudioStreamBuilder_setFramesPerDataCallback(AAudioStreamBuilder* builder, int32_t numFrames);
+void AAudioStreamBuilder_setUsage(AAudioStreamBuilder* builder, aaudio_usage_t usage);
+void AAudioStreamBuilder_setDataCallback(AAudioStreamBuilder* builder, AAudioStream_dataCallback callback, void *userData);
+void AAudioStreamBuilder_setErrorCallback(AAudioStreamBuilder* builder, AAudioStream_errorCallback callback, void *userData);
+aaudio_result_t AAudioStreamBuilder_openStream(AAudioStreamBuilder* builder, AAudioStream** stream);
+aaudio_result_t AAudioStreamBuilder_delete(AAudioStreamBuilder* builder);
+aaudio_result_t AAudioStream_close(AAudioStream* stream);
+aaudio_result_t AAudioStream_requestStart(AAudioStream* stream);
+aaudio_result_t AAudioStream_requestStop(AAudioStream* stream);
+aaudio_result_t AAudioStream_getTimestamp(AAudioStream* stream, clockid_t clockid, int64_t *framePosition, int64_t *timeNanoseconds);
+aaudio_result_t AAudioStream_setBufferSizeInFrames(AAudioStream* stream, int32_t numFrames);
+int32_t AAudioStream_getFramesPerBurst(AAudioStream* stream);
+int64_t AAudioStream_getFramesWritten(AAudioStream* stream);
+aaudio_result_t AAudioStream_waitForStateChange(AAudioStream* stream, aaudio_stream_state_t inputState, aaudio_stream_state_t *nextState, int64_t timeoutNanoseconds);
\ No newline at end of file
diff --git a/third_party/chromium/media/audio/android/aaudio_output.cc b/third_party/chromium/media/audio/android/aaudio_output.cc
new file mode 100644
index 0000000..d06a6b8
--- /dev/null
+++ b/third_party/chromium/media/audio/android/aaudio_output.cc
@@ -0,0 +1,367 @@
+// Copyright 2019 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 "media/audio/android/aaudio_output.h"
+
+#include "base/android/build_info.h"
+#include "base/logging.h"
+#include "base/thread_annotations.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/trace_event/trace_event.h"
+#include "media/audio/android/aaudio_stubs.h"
+#include "media/audio/android/audio_manager_android.h"
+#include "media/base/audio_bus.h"
+
+namespace media {
+
+// Used to circumvent issues where the AAudio thread callbacks continue
+// after AAudioStream_requestStop() completes. See crbug.com/1183255.
+class LOCKABLE AAudioDestructionHelper {
+ public:
+ explicit AAudioDestructionHelper(AAudioOutputStream* stream)
+ : output_stream_(stream) {}
+
+ ~AAudioDestructionHelper() {
+ DCHECK(is_closing_);
+ if (aaudio_stream_)
+ AAudioStream_close(aaudio_stream_);
+ }
+
+ AAudioOutputStream* GetAndLockStream() EXCLUSIVE_LOCK_FUNCTION() {
+ lock_.Acquire();
+ return is_closing_ ? nullptr : output_stream_;
+ }
+
+ void UnlockStream() UNLOCK_FUNCTION() { lock_.Release(); }
+
+ void DeferStreamClosure(AAudioStream* stream) {
+ base::AutoLock al(lock_);
+ DCHECK(!is_closing_);
+
+ is_closing_ = true;
+ aaudio_stream_ = stream;
+ }
+
+ private:
+ base::Lock lock_;
+ AAudioOutputStream* output_stream_ GUARDED_BY(lock_) = nullptr;
+ AAudioStream* aaudio_stream_ GUARDED_BY(lock_) = nullptr;
+ bool is_closing_ GUARDED_BY(lock_) = false;
+};
+
+static aaudio_data_callback_result_t OnAudioDataRequestedCallback(
+ AAudioStream* stream,
+ void* user_data,
+ void* audio_data,
+ int32_t num_frames) {
+ AAudioDestructionHelper* destruction_helper =
+ reinterpret_cast<AAudioDestructionHelper*>(user_data);
+
+ AAudioOutputStream* output_stream = destruction_helper->GetAndLockStream();
+
+ aaudio_data_callback_result_t result = AAUDIO_CALLBACK_RESULT_STOP;
+ if (output_stream)
+ result = output_stream->OnAudioDataRequested(audio_data, num_frames);
+
+ destruction_helper->UnlockStream();
+
+ return result;
+}
+
+static void OnStreamErrorCallback(AAudioStream* stream,
+ void* user_data,
+ aaudio_result_t error) {
+ AAudioDestructionHelper* destruction_helper =
+ reinterpret_cast<AAudioDestructionHelper*>(user_data);
+
+ AAudioOutputStream* output_stream = destruction_helper->GetAndLockStream();
+
+ if (output_stream)
+ output_stream->OnStreamError(error);
+
+ destruction_helper->UnlockStream();
+}
+
+AAudioOutputStream::AAudioOutputStream(AudioManagerAndroid* manager,
+ const AudioParameters& params,
+ aaudio_usage_t usage)
+ : audio_manager_(manager),
+ params_(params),
+ usage_(usage),
+ performance_mode_(AAUDIO_PERFORMANCE_MODE_NONE),
+ ns_per_frame_(base::Time::kNanosecondsPerSecond /
+ static_cast<double>(params.sample_rate())),
+ destruction_helper_(std::make_unique<AAudioDestructionHelper>(this)) {
+ DCHECK(manager);
+ DCHECK(params.IsValid());
+
+ if (AudioManagerAndroid::SupportsPerformanceModeForOutput()) {
+ switch (params.latency_tag()) {
+ case AudioLatency::LATENCY_EXACT_MS:
+ case AudioLatency::LATENCY_INTERACTIVE:
+ case AudioLatency::LATENCY_RTC:
+ performance_mode_ = AAUDIO_PERFORMANCE_MODE_LOW_LATENCY;
+ break;
+ case AudioLatency::LATENCY_PLAYBACK:
+ performance_mode_ = AAUDIO_PERFORMANCE_MODE_POWER_SAVING;
+ break;
+ default:
+ performance_mode_ = AAUDIO_PERFORMANCE_MODE_NONE;
+ }
+ }
+
+ TRACE_EVENT2("audio", "AAudioOutputStream::AAudioOutputStream",
+ "AAUDIO_PERFORMANCE_MODE_LOW_LATENCY",
+ performance_mode_ == AAUDIO_PERFORMANCE_MODE_LOW_LATENCY
+ ? "true" : "false",
+ "frames_per_buffer", params.frames_per_buffer());
+}
+
+AAudioOutputStream::~AAudioOutputStream() {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+ if (base::android::SdkVersion::SDK_VERSION_S >=
+ base::android::BuildInfo::GetInstance()->sdk_int()) {
+ // On Android S+, |destruction_helper_| can be destroyed as part of the
+ // normal class teardown.
+ return;
+ }
+
+ // In R and earlier, it is possible for callbacks to still be running even
+ // after calling AAudioStream_close(). The code below is a mitigation to work
+ // around this issue. See crbug.com/1183255.
+
+ // Keep |destruction_helper_| alive longer than |this|, so the |user_data|
+ // bound to the callback stays valid, until the callbacks stop.
+ base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE,
+ base::BindOnce([](std::unique_ptr<AAudioDestructionHelper>) {},
+ std::move(destruction_helper_)),
+ base::Seconds(1));
+}
+
+void AAudioOutputStream::Flush() {}
+
+bool AAudioOutputStream::Open() {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+ AAudioStreamBuilder* builder;
+ auto result = AAudio_createStreamBuilder(&builder);
+ if (AAUDIO_OK != result)
+ return false;
+
+ // Parameters
+ AAudioStreamBuilder_setDirection(builder, AAUDIO_DIRECTION_OUTPUT);
+ AAudioStreamBuilder_setSampleRate(builder, params_.sample_rate());
+ AAudioStreamBuilder_setChannelCount(builder, params_.channels());
+ AAudioStreamBuilder_setFormat(builder, AAUDIO_FORMAT_PCM_FLOAT);
+ AAudioStreamBuilder_setUsage(builder, usage_);
+ AAudioStreamBuilder_setPerformanceMode(builder, performance_mode_);
+ AAudioStreamBuilder_setFramesPerDataCallback(builder,
+ params_.frames_per_buffer());
+
+ // Callbacks
+ AAudioStreamBuilder_setDataCallback(builder, OnAudioDataRequestedCallback,
+ destruction_helper_.get());
+ AAudioStreamBuilder_setErrorCallback(builder, OnStreamErrorCallback,
+ destruction_helper_.get());
+
+ result = AAudioStreamBuilder_openStream(builder, &aaudio_stream_);
+
+ AAudioStreamBuilder_delete(builder);
+
+ if (AAUDIO_OK != result)
+ return false;
+
+ // After opening the stream, sets the effective buffer size to 3X the burst
+ // size to prevent glitching if the burst is small (e.g. < 128). On some
+ // devices you can get by with 1X or 2X, but 3X is safer.
+ int32_t framesPerBurst = AAudioStream_getFramesPerBurst(aaudio_stream_);
+ int32_t sizeRequested = framesPerBurst * (framesPerBurst < 128 ? 3 : 2);
+ AAudioStream_setBufferSizeInFrames(aaudio_stream_, sizeRequested);
+
+ audio_bus_ = AudioBus::Create(params_);
+
+ TRACE_EVENT2("audio", "AAudioOutputStream::Open",
+ "params_", params_.AsHumanReadableString(),
+ "requested BufferSizeInFrames", sizeRequested);
+
+ return true;
+}
+
+void AAudioOutputStream::Close() {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+ Stop();
+
+ // |destruction_helper_->GetStreamAndLock()| will return nullptr after this.
+ destruction_helper_->DeferStreamClosure(aaudio_stream_);
+
+ // We shouldn't be acessing |aaudio_stream_| after it's stopped.
+ aaudio_stream_ = nullptr;
+
+ // Note: This must be last, it will delete |this|.
+ audio_manager_->ReleaseOutputStream(this);
+}
+
+void AAudioOutputStream::Start(AudioSourceCallback* callback) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ DCHECK(aaudio_stream_);
+
+ {
+ base::AutoLock al(lock_);
+
+ // The device might have been disconnected between Open() and Start().
+ if (device_changed_) {
+ callback->OnError(AudioSourceCallback::ErrorType::kDeviceChange);
+ return;
+ }
+
+ DCHECK(!callback_);
+ callback_ = callback;
+ }
+
+ auto result = AAudioStream_requestStart(aaudio_stream_);
+ if (result != AAUDIO_OK) {
+ DLOG(ERROR) << "Failed to start audio stream, result: "
+ << AAudio_convertResultToText(result);
+
+ // Lock is required in case a previous asynchronous requestStop() still has
+ // not completed by the time we reach this point.
+ base::AutoLock al(lock_);
+ callback_->OnError(AudioSourceCallback::ErrorType::kUnknown);
+ callback_ = nullptr;
+ }
+}
+
+void AAudioOutputStream::Stop() {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+ {
+ base::AutoLock al(lock_);
+ if (!callback_ || !aaudio_stream_)
+ return;
+ }
+
+ // Note: This call may be asynchronous, so we must clear |callback_| under
+ // lock below to ensure no further calls occur after Stop(). Since it may
+ // not always be asynchronous, we don't hold |lock_| while we call stop.
+ auto result = AAudioStream_requestStop(aaudio_stream_);
+
+ {
+ base::AutoLock al(lock_);
+ if (result != AAUDIO_OK) {
+ DLOG(ERROR) << "Failed to stop audio stream, result: "
+ << AAudio_convertResultToText(result);
+ callback_->OnError(AudioSourceCallback::ErrorType::kUnknown);
+ }
+
+ callback_ = nullptr;
+ }
+
+ // Wait for AAUDIO_STREAM_STATE_STOPPED, but do not explicitly check for the
+ // success of this wait.
+ aaudio_stream_state_t current_state = AAUDIO_STREAM_STATE_STOPPING;
+ aaudio_stream_state_t next_state = AAUDIO_STREAM_STATE_UNINITIALIZED;
+ static const int64_t kTimeoutNanoseconds = 1e8;
+ result = AAudioStream_waitForStateChange(aaudio_stream_, current_state,
+ &next_state, kTimeoutNanoseconds);
+}
+
+base::TimeDelta AAudioOutputStream::GetDelay(base::TimeTicks delay_timestamp) {
+ // Get the time that a known audio frame was presented for playing.
+ int64_t existing_frame_index;
+ int64_t existing_frame_pts;
+ auto result =
+ AAudioStream_getTimestamp(aaudio_stream_, CLOCK_MONOTONIC,
+ &existing_frame_index, &existing_frame_pts);
+
+ if (result != AAUDIO_OK) {
+ DLOG(ERROR) << "Failed to get audio latency, result: "
+ << AAudio_convertResultToText(result);
+ return base::TimeDelta();
+ }
+
+ // Calculate the number of frames between our known frame and the write index.
+ const int64_t frame_index_delta =
+ AAudioStream_getFramesWritten(aaudio_stream_) - existing_frame_index;
+
+ // Calculate the time which the next frame will be presented.
+ const base::TimeDelta next_frame_pts =
+ base::Nanoseconds(existing_frame_pts + frame_index_delta * ns_per_frame_);
+
+ // Calculate the latency between write time and presentation time. At startup
+ // we may end up with negative values here.
+ return std::max(base::TimeDelta(),
+ next_frame_pts - (delay_timestamp - base::TimeTicks()));
+}
+
+aaudio_data_callback_result_t AAudioOutputStream::OnAudioDataRequested(
+ void* audio_data,
+ int32_t num_frames) {
+ // TODO(tguilbert): This can be downgraded to a DCHECK after we've launched.
+ CHECK_EQ(num_frames, audio_bus_->frames());
+
+ base::AutoLock al(lock_);
+ if (!callback_)
+ return AAUDIO_CALLBACK_RESULT_STOP;
+
+ const base::TimeTicks delay_timestamp = base::TimeTicks::Now();
+ const base::TimeDelta delay = GetDelay(delay_timestamp);
+
+ const int frames_filled =
+ callback_->OnMoreData(delay, delay_timestamp, 0, audio_bus_.get());
+
+ audio_bus_->Scale(muted_ ? 0.0 : volume_);
+ audio_bus_->ToInterleaved<Float32SampleTypeTraits>(
+ frames_filled, reinterpret_cast<float*>(audio_data));
+ return AAUDIO_CALLBACK_RESULT_CONTINUE;
+}
+
+void AAudioOutputStream::OnStreamError(aaudio_result_t error) {
+ base::AutoLock al(lock_);
+
+ if (error == AAUDIO_ERROR_DISCONNECTED)
+ device_changed_ = true;
+
+ if (!callback_)
+ return;
+
+ if (device_changed_) {
+ callback_->OnError(AudioSourceCallback::ErrorType::kDeviceChange);
+ return;
+ }
+
+ // TODO(dalecurtis): Consider sending a translated |error| code.
+ callback_->OnError(AudioSourceCallback::ErrorType::kUnknown);
+}
+
+void AAudioOutputStream::SetVolume(double volume) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+ double volume_override = 0;
+ if (audio_manager_->HasOutputVolumeOverride(&volume_override))
+ volume = volume_override;
+
+ if (volume < 0.0 || volume > 1.0)
+ return;
+
+ base::AutoLock al(lock_);
+ volume_ = volume;
+}
+
+void AAudioOutputStream::GetVolume(double* volume) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ base::AutoLock al(lock_);
+ *volume = volume_;
+}
+
+void AAudioOutputStream::SetMute(bool muted) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+ base::AutoLock al(lock_);
+ muted_ = muted;
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/android/aaudio_output.h b/third_party/chromium/media/audio/android/aaudio_output.h
new file mode 100644
index 0000000..9576ab3
--- /dev/null
+++ b/third_party/chromium/media/audio/android/aaudio_output.h
@@ -0,0 +1,84 @@
+// Copyright 2019 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 MEDIA_AUDIO_ANDROID_AAUDIO_OUTPUT_H_
+#define MEDIA_AUDIO_ANDROID_AAUDIO_OUTPUT_H_
+
+#include <aaudio/AAudio.h>
+
+#include "base/synchronization/lock.h"
+#include "base/thread_annotations.h"
+#include "base/threading/thread_checker.h"
+#include "media/audio/android/muteable_audio_output_stream.h"
+#include "media/base/audio_parameters.h"
+
+namespace media {
+
+class AAudioDestructionHelper;
+class AudioManagerAndroid;
+
+class AAudioOutputStream : public MuteableAudioOutputStream {
+ public:
+ AAudioOutputStream(AudioManagerAndroid* manager,
+ const AudioParameters& params,
+ aaudio_usage_t usage);
+
+ AAudioOutputStream(const AAudioOutputStream&) = delete;
+ AAudioOutputStream& operator=(const AAudioOutputStream&) = delete;
+
+ ~AAudioOutputStream() override;
+
+ // Implementation of MuteableAudioOutputStream.
+ bool Open() override;
+ void Close() override;
+ void Start(AudioSourceCallback* callback) override;
+ void Stop() override;
+ void Flush() override;
+ void SetVolume(double volume) override;
+ void GetVolume(double* volume) override;
+ void SetMute(bool muted) override;
+
+ // Public callbacks.
+ aaudio_data_callback_result_t OnAudioDataRequested(void* audio_data,
+ int32_t num_frames);
+ void OnStreamError(aaudio_result_t error);
+
+ private:
+ // Returns the amount of unplayed audio relative to |delay_timestamp|. See the
+ // definition for AudioOutputStream::AudioSourceCallback::OnMoreData() for
+ // more information on these terms.
+ base::TimeDelta GetDelay(base::TimeTicks delay_timestamp);
+
+ THREAD_CHECKER(thread_checker_);
+
+ AudioManagerAndroid* const audio_manager_;
+ const AudioParameters params_;
+
+ aaudio_usage_t usage_;
+ aaudio_performance_mode_t performance_mode_;
+
+ // Constant used for calculating latency. Amount of nanoseconds per frame.
+ const double ns_per_frame_;
+
+ std::unique_ptr<AudioBus> audio_bus_;
+
+ AAudioStream* aaudio_stream_ = nullptr;
+
+ // Bound to the audio data callback. Outlives |this| in case the callbacks
+ // continue after |this| is destroyed. See crbug.com/1183255.
+ std::unique_ptr<AAudioDestructionHelper> destruction_helper_;
+
+ // Lock protects all members below which may be read concurrently from the
+ // audio manager thread and the OS provided audio thread.
+ base::Lock lock_;
+
+ AudioSourceCallback* callback_ GUARDED_BY(lock_) = nullptr;
+ bool muted_ GUARDED_BY(lock_) = false;
+ double volume_ GUARDED_BY(lock_) = 1.0;
+ bool device_changed_ GUARDED_BY(lock_) = false;
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_ANDROID_AAUDIO_OUTPUT_H_
diff --git a/third_party/chromium/media/audio/android/aaudio_stub_header.fragment b/third_party/chromium/media/audio/android/aaudio_stub_header.fragment
new file mode 100644
index 0000000..01fce79
--- /dev/null
+++ b/third_party/chromium/media/audio/android/aaudio_stub_header.fragment
@@ -0,0 +1,8 @@
+// The extra include header needed in the generated stub file for defining
+// various AAudio types.
+
+extern "C" {
+
+#include <aaudio/AAudio.h>
+
+}
diff --git a/third_party/chromium/media/audio/android/audio_android_unittest.cc b/third_party/chromium/media/audio/android/audio_android_unittest.cc
new file mode 100644
index 0000000..f95b6a5
--- /dev/null
+++ b/third_party/chromium/media/audio/android/audio_android_unittest.cc
@@ -0,0 +1,938 @@
+// Copyright 2013 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 <stdint.h>
+
+#include <memory>
+
+#include "base/android/build_info.h"
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/path_service.h"
+#include "base/run_loop.h"
+#include "base/strings/stringprintf.h"
+#include "base/synchronization/lock.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/test/task_environment.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "media/audio/android/audio_manager_android.h"
+#include "media/audio/audio_device_description.h"
+#include "media/audio/audio_device_info_accessor_for_tests.h"
+#include "media/audio/audio_io.h"
+#include "media/audio/audio_unittest_util.h"
+#include "media/audio/mock_audio_source_callback.h"
+#include "media/audio/test_audio_thread.h"
+#include "media/base/decoder_buffer.h"
+#include "media/base/seekable_buffer.h"
+#include "media/base/test_data_util.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+using ::testing::AtLeast;
+using ::testing::DoAll;
+using ::testing::Invoke;
+using ::testing::NotNull;
+using ::testing::Return;
+
+namespace media {
+namespace {
+
+ACTION_P4(CheckCountAndPostQuitTask, count, limit, task_runner, quit_closure) {
+ if (++*count >= limit)
+ task_runner->PostTask(FROM_HERE, quit_closure);
+}
+
+const float kCallbackTestTimeMs = 2000.0;
+const int kBytesPerSample = 2;
+const SampleFormat kSampleFormat = kSampleFormatS16;
+
+// Converts AudioParameters::Format enumerator to readable string.
+std::string FormatToString(AudioParameters::Format format) {
+ switch (format) {
+ case AudioParameters::AUDIO_PCM_LINEAR:
+ return std::string("AUDIO_PCM_LINEAR");
+ case AudioParameters::AUDIO_PCM_LOW_LATENCY:
+ return std::string("AUDIO_PCM_LOW_LATENCY");
+ case AudioParameters::AUDIO_FAKE:
+ return std::string("AUDIO_FAKE");
+ default:
+ return std::string();
+ }
+}
+
+// Converts ChannelLayout enumerator to readable string. Does not include
+// multi-channel cases since these layouts are not supported on Android.
+std::string LayoutToString(ChannelLayout channel_layout) {
+ switch (channel_layout) {
+ case CHANNEL_LAYOUT_NONE:
+ return std::string("CHANNEL_LAYOUT_NONE");
+ case CHANNEL_LAYOUT_MONO:
+ return std::string("CHANNEL_LAYOUT_MONO");
+ case CHANNEL_LAYOUT_STEREO:
+ return std::string("CHANNEL_LAYOUT_STEREO");
+ case CHANNEL_LAYOUT_UNSUPPORTED:
+ default:
+ return std::string("CHANNEL_LAYOUT_UNSUPPORTED");
+ }
+}
+
+double ExpectedTimeBetweenCallbacks(AudioParameters params) {
+ return (base::Microseconds(params.frames_per_buffer() *
+ base::Time::kMicrosecondsPerSecond /
+ static_cast<double>(params.sample_rate())))
+ .InMillisecondsF();
+}
+
+// Helper method which verifies that the device list starts with a valid
+// default device name followed by non-default device names.
+void CheckDeviceDescriptions(
+ const AudioDeviceDescriptions& device_descriptions) {
+ DVLOG(2) << "Got " << device_descriptions.size() << " audio devices.";
+ if (device_descriptions.empty()) {
+ // Log a warning so we can see the status on the build bots. No need to
+ // break the test though since this does successfully test the code and
+ // some failure cases.
+ LOG(WARNING) << "No input devices detected";
+ return;
+ }
+
+ AudioDeviceDescriptions::const_iterator it = device_descriptions.begin();
+
+ // The first device in the list should always be the default device.
+ EXPECT_EQ(std::string(AudioDeviceDescription::kDefaultDeviceId),
+ it->unique_id);
+ ++it;
+
+ // Other devices should have non-empty name and id and should not contain
+ // default name or id.
+ while (it != device_descriptions.end()) {
+ EXPECT_FALSE(it->device_name.empty());
+ EXPECT_FALSE(it->unique_id.empty());
+ EXPECT_FALSE(it->group_id.empty());
+ DVLOG(2) << "Device ID(" << it->unique_id << "), label: " << it->device_name
+ << " group: " << it->group_id;
+ EXPECT_NE(AudioDeviceDescription::GetDefaultDeviceName(), it->device_name);
+ EXPECT_NE(std::string(AudioDeviceDescription::kDefaultDeviceId),
+ it->unique_id);
+ ++it;
+ }
+}
+
+// We clear the data bus to ensure that the test does not cause noise.
+int RealOnMoreData(base::TimeDelta /* delay */,
+ base::TimeTicks /* delay_timestamp */,
+ int /* prior_frames_skipped */,
+ AudioBus* dest) {
+ dest->Zero();
+ return dest->frames();
+}
+
+} // namespace
+
+std::ostream& operator<<(std::ostream& os, const AudioParameters& params) {
+ using std::endl;
+ os << endl
+ << "format: " << FormatToString(params.format()) << endl
+ << "channel layout: " << LayoutToString(params.channel_layout()) << endl
+ << "sample rate: " << params.sample_rate() << endl
+ << "frames per buffer: " << params.frames_per_buffer() << endl
+ << "channels: " << params.channels() << endl
+ << "bytes per buffer: " << params.GetBytesPerBuffer(kSampleFormat) << endl
+ << "bytes per second: "
+ << params.sample_rate() * params.GetBytesPerFrame(kSampleFormat) << endl
+ << "bytes per frame: " << params.GetBytesPerFrame(kSampleFormat) << endl
+ << "chunk size in ms: " << ExpectedTimeBetweenCallbacks(params) << endl
+ << "echo_canceller: "
+ << (params.effects() & AudioParameters::ECHO_CANCELLER);
+ return os;
+}
+
+// Gmock implementation of AudioInputStream::AudioInputCallback.
+class MockAudioInputCallback : public AudioInputStream::AudioInputCallback {
+ public:
+ MOCK_METHOD3(OnData,
+ void(const AudioBus* src,
+ base::TimeTicks capture_time,
+ double volume));
+ MOCK_METHOD0(OnError, void());
+};
+
+// Implements AudioOutputStream::AudioSourceCallback and provides audio data
+// by reading from a data file.
+class FileAudioSource : public AudioOutputStream::AudioSourceCallback {
+ public:
+ explicit FileAudioSource(base::WaitableEvent* event, const std::string& name)
+ : event_(event), pos_(0) {
+ // Reads a test file from media/test/data directory and stores it in
+ // a DecoderBuffer.
+ file_ = ReadTestDataFile(name);
+
+ // Log the name of the file which is used as input for this test.
+ base::FilePath file_path = GetTestDataFilePath(name);
+ DVLOG(0) << "Reading from file: " << file_path.value().c_str();
+ }
+
+ FileAudioSource(const FileAudioSource&) = delete;
+ FileAudioSource& operator=(const FileAudioSource&) = delete;
+
+ ~FileAudioSource() override {}
+
+ // AudioOutputStream::AudioSourceCallback implementation.
+
+ // Use samples read from a data file and fill up the audio buffer
+ // provided to us in the callback.
+ int OnMoreData(base::TimeDelta /* delay */,
+ base::TimeTicks /* delay_timestamp */,
+ int /* prior_frames_skipped */,
+ AudioBus* dest) override {
+ bool stop_playing = false;
+ int max_size = dest->frames() * dest->channels() * kBytesPerSample;
+
+ // Adjust data size and prepare for end signal if file has ended.
+ if (pos_ + max_size > file_size()) {
+ stop_playing = true;
+ max_size = file_size() - pos_;
+ }
+
+ // File data is stored as interleaved 16-bit values. Copy data samples from
+ // the file and deinterleave to match the audio bus format.
+ // FromInterleaved() will zero out any unfilled frames when there is not
+ // sufficient data remaining in the file to fill up the complete frame.
+ int frames = max_size / (dest->channels() * kBytesPerSample);
+ if (max_size) {
+ auto* source = reinterpret_cast<const int16_t*>(file_->data() + pos_);
+ dest->FromInterleaved<SignedInt16SampleTypeTraits>(source, frames);
+ pos_ += max_size;
+ }
+
+ // Set event to ensure that the test can stop when the file has ended.
+ if (stop_playing)
+ event_->Signal();
+
+ return frames;
+ }
+
+ void OnError(ErrorType type) override {}
+
+ int file_size() { return file_->data_size(); }
+
+ private:
+ base::WaitableEvent* event_;
+ int pos_;
+ scoped_refptr<DecoderBuffer> file_;
+};
+
+// Implements AudioInputStream::AudioInputCallback and writes the recorded
+// audio data to a local output file. Note that this implementation should
+// only be used for manually invoked and evaluated tests, hence the created
+// file will not be destroyed after the test is done since the intention is
+// that it shall be available for off-line analysis.
+class FileAudioSink : public AudioInputStream::AudioInputCallback {
+ public:
+ explicit FileAudioSink(base::WaitableEvent* event,
+ const AudioParameters& params,
+ const std::string& file_name)
+ : event_(event), params_(params) {
+ // Allocate space for ~10 seconds of data.
+ const int kMaxBufferSize =
+ 10 * params.sample_rate() * params.GetBytesPerFrame(kSampleFormat);
+ buffer_ = std::make_unique<media::SeekableBuffer>(0, kMaxBufferSize);
+
+ // Open up the binary file which will be written to in the destructor.
+ base::FilePath file_path;
+ EXPECT_TRUE(base::PathService::Get(base::DIR_SOURCE_ROOT, &file_path));
+ file_path = file_path.AppendASCII(file_name.c_str());
+ binary_file_ = base::OpenFile(file_path, "wb");
+ DLOG_IF(ERROR, !binary_file_) << "Failed to open binary PCM data file.";
+ DVLOG(0) << "Writing to file: " << file_path.value().c_str();
+ }
+
+ FileAudioSink(const FileAudioSink&) = delete;
+ FileAudioSink& operator=(const FileAudioSink&) = delete;
+
+ ~FileAudioSink() override {
+ int bytes_written = 0;
+ while (bytes_written < buffer_->forward_capacity()) {
+ const uint8_t* chunk;
+ int chunk_size;
+
+ // Stop writing if no more data is available.
+ if (!buffer_->GetCurrentChunk(&chunk, &chunk_size))
+ break;
+
+ // Write recorded data chunk to the file and prepare for next chunk.
+ // TODO(henrika): use file_util:: instead.
+ fwrite(chunk, 1, chunk_size, binary_file_);
+ buffer_->Seek(chunk_size);
+ bytes_written += chunk_size;
+ }
+ base::CloseFile(binary_file_);
+ }
+
+ // AudioInputStream::AudioInputCallback implementation.
+ void OnData(const AudioBus* src,
+ base::TimeTicks capture_time,
+ double volume) override {
+ const int num_samples = src->frames() * src->channels();
+ std::unique_ptr<int16_t> interleaved(new int16_t[num_samples]);
+ src->ToInterleaved<SignedInt16SampleTypeTraits>(src->frames(),
+ interleaved.get());
+
+ // Store data data in a temporary buffer to avoid making blocking
+ // fwrite() calls in the audio callback. The complete buffer will be
+ // written to file in the destructor.
+ const int bytes_per_sample = sizeof(*interleaved);
+ const int size = bytes_per_sample * num_samples;
+ if (!buffer_->Append((const uint8_t*)interleaved.get(), size))
+ event_->Signal();
+ }
+
+ void OnError() override {}
+
+ private:
+ base::WaitableEvent* event_;
+ AudioParameters params_;
+ std::unique_ptr<media::SeekableBuffer> buffer_;
+ FILE* binary_file_;
+};
+
+// Implements AudioInputCallback and AudioSourceCallback to support full
+// duplex audio where captured samples are played out in loopback after
+// reading from a temporary FIFO storage.
+class FullDuplexAudioSinkSource
+ : public AudioInputStream::AudioInputCallback,
+ public AudioOutputStream::AudioSourceCallback {
+ public:
+ explicit FullDuplexAudioSinkSource(const AudioParameters& params)
+ : params_(params),
+ previous_time_(base::TimeTicks::Now()),
+ started_(false) {
+ // Start with a reasonably small FIFO size. It will be increased
+ // dynamically during the test if required.
+ size_t buffer_size = params.GetBytesPerBuffer(kSampleFormat);
+ fifo_ = std::make_unique<media::SeekableBuffer>(0, 2 * buffer_size);
+ buffer_.reset(new uint8_t[buffer_size]);
+ }
+
+ FullDuplexAudioSinkSource(const FullDuplexAudioSinkSource&) = delete;
+ FullDuplexAudioSinkSource& operator=(const FullDuplexAudioSinkSource&) =
+ delete;
+
+ ~FullDuplexAudioSinkSource() override {}
+
+ // AudioInputStream::AudioInputCallback implementation
+ void OnError() override {}
+ void OnData(const AudioBus* src,
+ base::TimeTicks capture_time,
+ double volume) override {
+ const base::TimeTicks now_time = base::TimeTicks::Now();
+ const int diff = (now_time - previous_time_).InMilliseconds();
+
+ const int num_samples = src->frames() * src->channels();
+ std::unique_ptr<int16_t> interleaved(new int16_t[num_samples]);
+ src->ToInterleaved<SignedInt16SampleTypeTraits>(src->frames(),
+ interleaved.get());
+ const int bytes_per_sample = sizeof(*interleaved);
+ const int size = bytes_per_sample * num_samples;
+
+ base::AutoLock lock(lock_);
+ if (diff > 1000) {
+ started_ = true;
+ previous_time_ = now_time;
+
+ // Log out the extra delay added by the FIFO. This is a best effort
+ // estimate. We might be +- 10ms off here.
+ int extra_fifo_delay =
+ static_cast<int>(BytesToMilliseconds(fifo_->forward_bytes() + size));
+ DVLOG(1) << extra_fifo_delay;
+ }
+
+ // We add an initial delay of ~1 second before loopback starts to ensure
+ // a stable callback sequence and to avoid initial bursts which might add
+ // to the extra FIFO delay.
+ if (!started_)
+ return;
+
+ // Append new data to the FIFO and extend the size if the max capacity
+ // was exceeded. Flush the FIFO when extended just in case.
+ if (!fifo_->Append((const uint8_t*)interleaved.get(), size)) {
+ fifo_->set_forward_capacity(2 * fifo_->forward_capacity());
+ fifo_->Clear();
+ }
+ }
+
+ // AudioOutputStream::AudioSourceCallback implementation
+ void OnError(ErrorType type) override {}
+ int OnMoreData(base::TimeDelta /* delay */,
+ base::TimeTicks /* delay_timestamp */,
+ int /* prior_frames_skipped */,
+ AudioBus* dest) override {
+ const int size_in_bytes =
+ kBytesPerSample * dest->frames() * dest->channels();
+ EXPECT_EQ(size_in_bytes, params_.GetBytesPerBuffer(kSampleFormat));
+
+ base::AutoLock lock(lock_);
+
+ // We add an initial delay of ~1 second before loopback starts to ensure
+ // a stable callback sequences and to avoid initial bursts which might add
+ // to the extra FIFO delay.
+ if (!started_) {
+ dest->Zero();
+ return dest->frames();
+ }
+
+ // Fill up destination with zeros if the FIFO does not contain enough
+ // data to fulfill the request.
+ if (fifo_->forward_bytes() < size_in_bytes) {
+ dest->Zero();
+ } else {
+ fifo_->Read(buffer_.get(), size_in_bytes);
+ dest->FromInterleaved<SignedInt16SampleTypeTraits>(
+ reinterpret_cast<int16_t*>(buffer_.get()), dest->frames());
+ }
+
+ return dest->frames();
+ }
+
+ private:
+ // Converts from bytes to milliseconds given number of bytes and existing
+ // audio parameters.
+ double BytesToMilliseconds(int bytes) const {
+ const int frames = bytes / params_.GetBytesPerFrame(kSampleFormat);
+ return (base::Microseconds(frames * base::Time::kMicrosecondsPerSecond /
+ static_cast<double>(params_.sample_rate())))
+ .InMillisecondsF();
+ }
+
+ AudioParameters params_;
+ base::TimeTicks previous_time_;
+ base::Lock lock_;
+ std::unique_ptr<media::SeekableBuffer> fifo_;
+ std::unique_ptr<uint8_t[]> buffer_;
+ bool started_;
+};
+
+// Test fixture class for tests which only exercise the output path.
+class AudioAndroidOutputTest : public testing::Test {
+ public:
+ AudioAndroidOutputTest()
+ : task_environment_(
+ base::test::SingleThreadTaskEnvironment::MainThreadType::UI),
+ audio_manager_(AudioManager::CreateForTesting(
+ std::make_unique<TestAudioThread>())),
+ audio_manager_device_info_(audio_manager_.get()),
+ audio_output_stream_(nullptr) {
+ // Flush the message loop to ensure that AudioManager is fully initialized.
+ base::RunLoop().RunUntilIdle();
+ }
+
+ AudioAndroidOutputTest(const AudioAndroidOutputTest&) = delete;
+ AudioAndroidOutputTest& operator=(const AudioAndroidOutputTest&) = delete;
+
+ ~AudioAndroidOutputTest() override {
+ audio_manager_->Shutdown();
+ base::RunLoop().RunUntilIdle();
+ }
+
+ protected:
+ AudioManager* audio_manager() { return audio_manager_.get(); }
+ AudioDeviceInfoAccessorForTests* audio_manager_device_info() {
+ return &audio_manager_device_info_;
+ }
+ const AudioParameters& audio_output_parameters() {
+ return audio_output_parameters_;
+ }
+
+ // Synchronously runs the provided callback/closure on the audio thread.
+ void RunOnAudioThread(base::OnceClosure closure) {
+ if (!audio_manager()->GetTaskRunner()->BelongsToCurrentThread()) {
+ base::WaitableEvent event(
+ base::WaitableEvent::ResetPolicy::AUTOMATIC,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ audio_manager()->GetTaskRunner()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&AudioAndroidOutputTest::RunOnAudioThreadImpl,
+ base::Unretained(this), std::move(closure), &event));
+ event.Wait();
+ } else {
+ std::move(closure).Run();
+ }
+ }
+
+ void RunOnAudioThreadImpl(base::OnceClosure closure,
+ base::WaitableEvent* event) {
+ DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
+ std::move(closure).Run();
+ event->Signal();
+ }
+
+ void GetDefaultOutputStreamParametersOnAudioThread() {
+ RunOnAudioThread(base::BindOnce(
+ &AudioAndroidOutputTest::GetDefaultOutputStreamParameters,
+ base::Unretained(this)));
+ }
+
+ void MakeAudioOutputStreamOnAudioThread(const AudioParameters& params) {
+ RunOnAudioThread(base::BindOnce(&AudioAndroidOutputTest::MakeOutputStream,
+ base::Unretained(this), params));
+ }
+
+ void OpenAndCloseAudioOutputStreamOnAudioThread() {
+ RunOnAudioThread(base::BindOnce(&AudioAndroidOutputTest::OpenAndClose,
+ base::Unretained(this)));
+ }
+
+ void OpenAndStartAudioOutputStreamOnAudioThread(
+ AudioOutputStream::AudioSourceCallback* source) {
+ RunOnAudioThread(base::BindOnce(&AudioAndroidOutputTest::OpenAndStart,
+ base::Unretained(this), source));
+ }
+
+ void StopAndCloseAudioOutputStreamOnAudioThread() {
+ RunOnAudioThread(base::BindOnce(&AudioAndroidOutputTest::StopAndClose,
+ base::Unretained(this)));
+ }
+
+ double AverageTimeBetweenCallbacks(int num_callbacks) const {
+ return ((end_time_ - start_time_) / static_cast<double>(num_callbacks - 1))
+ .InMillisecondsF();
+ }
+
+ void StartOutputStreamCallbacks(const AudioParameters& params) {
+ double expected_time_between_callbacks_ms =
+ ExpectedTimeBetweenCallbacks(params);
+ const int num_callbacks =
+ (kCallbackTestTimeMs / expected_time_between_callbacks_ms);
+ MakeAudioOutputStreamOnAudioThread(params);
+
+ int count = 0;
+ MockAudioSourceCallback source;
+
+ base::RunLoop run_loop;
+ EXPECT_CALL(source, OnMoreData(_, _, 0, NotNull()))
+ .Times(AtLeast(num_callbacks))
+ .WillRepeatedly(
+ DoAll(CheckCountAndPostQuitTask(&count, num_callbacks,
+ base::ThreadTaskRunnerHandle::Get(),
+ run_loop.QuitWhenIdleClosure()),
+ Invoke(RealOnMoreData)));
+ EXPECT_CALL(source, OnError(_)).Times(0);
+
+ OpenAndStartAudioOutputStreamOnAudioThread(&source);
+
+ start_time_ = base::TimeTicks::Now();
+ run_loop.Run();
+ end_time_ = base::TimeTicks::Now();
+
+ StopAndCloseAudioOutputStreamOnAudioThread();
+
+ double average_time_between_callbacks_ms =
+ AverageTimeBetweenCallbacks(num_callbacks);
+ DVLOG(0) << "expected time between callbacks: "
+ << expected_time_between_callbacks_ms << " ms";
+ DVLOG(0) << "average time between callbacks: "
+ << average_time_between_callbacks_ms << " ms";
+ EXPECT_GE(average_time_between_callbacks_ms,
+ 0.70 * expected_time_between_callbacks_ms);
+ EXPECT_LE(average_time_between_callbacks_ms,
+ 1.50 * expected_time_between_callbacks_ms);
+ }
+
+ void GetDefaultOutputStreamParameters() {
+ DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
+ audio_output_parameters_ =
+ audio_manager_device_info()->GetDefaultOutputStreamParameters();
+ EXPECT_TRUE(audio_output_parameters_.IsValid());
+ }
+
+ void MakeOutputStream(const AudioParameters& params) {
+ DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
+ audio_output_stream_ = audio_manager()->MakeAudioOutputStream(
+ params, std::string(), AudioManager::LogCallback());
+ EXPECT_TRUE(audio_output_stream_);
+ }
+
+ void OpenAndClose() {
+ DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
+ EXPECT_TRUE(audio_output_stream_->Open());
+ audio_output_stream_->Close();
+ audio_output_stream_ = nullptr;
+ }
+
+ void OpenAndStart(AudioOutputStream::AudioSourceCallback* source) {
+ DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
+ EXPECT_TRUE(audio_output_stream_->Open());
+ audio_output_stream_->Start(source);
+ }
+
+ void StopAndClose() {
+ DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
+ audio_output_stream_->Stop();
+ audio_output_stream_->Close();
+ audio_output_stream_ = nullptr;
+ }
+
+ base::test::SingleThreadTaskEnvironment task_environment_;
+ std::unique_ptr<AudioManager> audio_manager_;
+ AudioDeviceInfoAccessorForTests audio_manager_device_info_;
+ AudioParameters audio_output_parameters_;
+ AudioOutputStream* audio_output_stream_;
+ base::TimeTicks start_time_;
+ base::TimeTicks end_time_;
+};
+
+// Test fixture class for tests which exercise the input path, or both input and
+// output paths. It is value-parameterized to test against both the Java
+// AudioRecord (when true) and native OpenSLES (when false) input paths.
+class AudioAndroidInputTest : public AudioAndroidOutputTest,
+ public testing::WithParamInterface<bool> {
+ public:
+ AudioAndroidInputTest() : audio_input_stream_(nullptr) {}
+
+ protected:
+ const AudioParameters& audio_input_parameters() {
+ return audio_input_parameters_;
+ }
+
+ AudioParameters GetInputStreamParameters() {
+ GetDefaultInputStreamParametersOnAudioThread();
+
+ AudioParameters params = audio_input_parameters();
+
+ // Only the AudioRecord path supports effects, so we can force it to be
+ // selected for the test by requesting one. OpenSLES is used otherwise.
+ params.set_effects(GetParam() ? AudioParameters::ECHO_CANCELLER
+ : AudioParameters::NO_EFFECTS);
+ return params;
+ }
+
+ void GetDefaultInputStreamParametersOnAudioThread() {
+ RunOnAudioThread(
+ base::BindOnce(&AudioAndroidInputTest::GetDefaultInputStreamParameters,
+ base::Unretained(this)));
+ }
+
+ void MakeAudioInputStreamOnAudioThread(const AudioParameters& params) {
+ RunOnAudioThread(base::BindOnce(&AudioAndroidInputTest::MakeInputStream,
+ base::Unretained(this), params));
+ }
+
+ void OpenAndCloseAudioInputStreamOnAudioThread() {
+ RunOnAudioThread(base::BindOnce(&AudioAndroidInputTest::OpenAndClose,
+ base::Unretained(this)));
+ }
+
+ void OpenAndStartAudioInputStreamOnAudioThread(
+ AudioInputStream::AudioInputCallback* sink) {
+ RunOnAudioThread(base::BindOnce(&AudioAndroidInputTest::OpenAndStart,
+ base::Unretained(this), sink));
+ }
+
+ void StopAndCloseAudioInputStreamOnAudioThread() {
+ RunOnAudioThread(base::BindOnce(&AudioAndroidInputTest::StopAndClose,
+ base::Unretained(this)));
+ }
+
+ void StartInputStreamCallbacks(const AudioParameters& params) {
+ double expected_time_between_callbacks_ms =
+ ExpectedTimeBetweenCallbacks(params);
+ const int num_callbacks =
+ (kCallbackTestTimeMs / expected_time_between_callbacks_ms);
+
+ MakeAudioInputStreamOnAudioThread(params);
+
+ int count = 0;
+ MockAudioInputCallback sink;
+
+ base::RunLoop run_loop;
+ EXPECT_CALL(sink, OnData(NotNull(), _, _))
+ .Times(AtLeast(num_callbacks))
+ .WillRepeatedly(CheckCountAndPostQuitTask(
+ &count, num_callbacks, base::ThreadTaskRunnerHandle::Get(),
+ run_loop.QuitWhenIdleClosure()));
+ EXPECT_CALL(sink, OnError()).Times(0);
+
+ OpenAndStartAudioInputStreamOnAudioThread(&sink);
+
+ start_time_ = base::TimeTicks::Now();
+ run_loop.Run();
+ end_time_ = base::TimeTicks::Now();
+
+ StopAndCloseAudioInputStreamOnAudioThread();
+
+ double average_time_between_callbacks_ms =
+ AverageTimeBetweenCallbacks(num_callbacks);
+ DVLOG(0) << "expected time between callbacks: "
+ << expected_time_between_callbacks_ms << " ms";
+ DVLOG(0) << "average time between callbacks: "
+ << average_time_between_callbacks_ms << " ms";
+ EXPECT_GE(average_time_between_callbacks_ms,
+ 0.70 * expected_time_between_callbacks_ms);
+ EXPECT_LE(average_time_between_callbacks_ms,
+ 1.30 * expected_time_between_callbacks_ms);
+ }
+
+ void GetDefaultInputStreamParameters() {
+ DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
+ audio_input_parameters_ =
+ audio_manager_device_info()->GetInputStreamParameters(
+ AudioDeviceDescription::kDefaultDeviceId);
+ }
+
+ void MakeInputStream(const AudioParameters& params) {
+ DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
+ audio_input_stream_ = audio_manager()->MakeAudioInputStream(
+ params, AudioDeviceDescription::kDefaultDeviceId,
+ AudioManager::LogCallback());
+ EXPECT_TRUE(audio_input_stream_);
+ }
+
+ void OpenAndClose() {
+ DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
+ EXPECT_EQ(audio_input_stream_->Open(),
+ AudioInputStream::OpenOutcome::kSuccess);
+ audio_input_stream_->Close();
+ audio_input_stream_ = nullptr;
+ }
+
+ void OpenAndStart(AudioInputStream::AudioInputCallback* sink) {
+ DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
+ EXPECT_EQ(audio_input_stream_->Open(),
+ AudioInputStream::OpenOutcome::kSuccess);
+ audio_input_stream_->Start(sink);
+ }
+
+ void StopAndClose() {
+ DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
+ audio_input_stream_->Stop();
+ audio_input_stream_->Close();
+ audio_input_stream_ = nullptr;
+ }
+
+ AudioInputStream* audio_input_stream_;
+ AudioParameters audio_input_parameters_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AudioAndroidInputTest);
+};
+
+// Get the default audio input parameters and log the result.
+TEST_P(AudioAndroidInputTest, GetDefaultInputStreamParameters) {
+ // We don't go through AudioAndroidInputTest::GetInputStreamParameters() here
+ // so that we can log the real (non-overridden) values of the effects.
+ GetDefaultInputStreamParametersOnAudioThread();
+ EXPECT_TRUE(audio_input_parameters().IsValid());
+ DVLOG(1) << audio_input_parameters();
+}
+
+// Get the default audio output parameters and log the result.
+TEST_F(AudioAndroidOutputTest, GetDefaultOutputStreamParameters) {
+ GetDefaultOutputStreamParametersOnAudioThread();
+ DVLOG(1) << audio_output_parameters();
+}
+
+// Verify input device enumeration.
+TEST_F(AudioAndroidInputTest, GetAudioInputDeviceDescriptions) {
+ ABORT_AUDIO_TEST_IF_NOT(audio_manager_device_info()->HasAudioInputDevices());
+ AudioDeviceDescriptions devices;
+ RunOnAudioThread(base::BindOnce(
+ &AudioDeviceInfoAccessorForTests::GetAudioInputDeviceDescriptions,
+ base::Unretained(audio_manager_device_info()), &devices));
+ CheckDeviceDescriptions(devices);
+}
+
+// Verify output device enumeration.
+TEST_F(AudioAndroidOutputTest, GetAudioOutputDeviceDescriptions) {
+ ABORT_AUDIO_TEST_IF_NOT(audio_manager_device_info()->HasAudioOutputDevices());
+ AudioDeviceDescriptions devices;
+ RunOnAudioThread(base::BindOnce(
+ &AudioDeviceInfoAccessorForTests::GetAudioOutputDeviceDescriptions,
+ base::Unretained(audio_manager_device_info()), &devices));
+ CheckDeviceDescriptions(devices);
+}
+
+// Ensure that a default input stream can be created and closed.
+TEST_P(AudioAndroidInputTest, CreateAndCloseInputStream) {
+ AudioParameters params = GetInputStreamParameters();
+ MakeAudioInputStreamOnAudioThread(params);
+ RunOnAudioThread(base::BindOnce(&AudioInputStream::Close,
+ base::Unretained(audio_input_stream_)));
+}
+
+// Ensure that a default output stream can be created and closed.
+// TODO(henrika): should we also verify that this API changes the audio mode
+// to communication mode, and calls RegisterHeadsetReceiver, the first time
+// it is called?
+TEST_F(AudioAndroidOutputTest, CreateAndCloseOutputStream) {
+ GetDefaultOutputStreamParametersOnAudioThread();
+ MakeAudioOutputStreamOnAudioThread(audio_output_parameters());
+ RunOnAudioThread(base::BindOnce(&AudioOutputStream::Close,
+ base::Unretained(audio_output_stream_)));
+}
+
+// Ensure that a default input stream can be opened and closed.
+TEST_P(AudioAndroidInputTest, OpenAndCloseInputStream) {
+ AudioParameters params = GetInputStreamParameters();
+ MakeAudioInputStreamOnAudioThread(params);
+ OpenAndCloseAudioInputStreamOnAudioThread();
+}
+
+// Ensure that a default output stream can be opened and closed.
+TEST_F(AudioAndroidOutputTest, OpenAndCloseOutputStream) {
+ GetDefaultOutputStreamParametersOnAudioThread();
+ MakeAudioOutputStreamOnAudioThread(audio_output_parameters());
+ OpenAndCloseAudioOutputStreamOnAudioThread();
+}
+
+// Start input streaming using default input parameters and ensure that the
+// callback sequence is sane.
+// Flaky, see crbug.com/683408.
+TEST_P(AudioAndroidInputTest, DISABLED_StartInputStreamCallbacks) {
+ AudioParameters native_params = GetInputStreamParameters();
+ StartInputStreamCallbacks(native_params);
+}
+
+// Start input streaming using non default input parameters and ensure that the
+// callback sequence is sane. The only change we make in this test is to select
+// a 10ms buffer size instead of the default size.
+// Flaky, see crbug.com/683408.
+TEST_P(AudioAndroidInputTest,
+ DISABLED_StartInputStreamCallbacksNonDefaultParameters) {
+ AudioParameters params = GetInputStreamParameters();
+ params.set_frames_per_buffer(params.sample_rate() / 100);
+ StartInputStreamCallbacks(params);
+}
+
+// Start output streaming using default output parameters and ensure that the
+// callback sequence is sane.
+TEST_F(AudioAndroidOutputTest, StartOutputStreamCallbacks) {
+ GetDefaultOutputStreamParametersOnAudioThread();
+ StartOutputStreamCallbacks(audio_output_parameters());
+}
+
+// Start output streaming using non default output parameters and ensure that
+// the callback sequence is sane. The only change we make in this test is to
+// select a 10ms buffer size instead of the default size and to open up the
+// device in mono.
+// TODO(henrika): possibly add support for more variations.
+TEST_F(AudioAndroidOutputTest, StartOutputStreamCallbacksNonDefaultParameters) {
+ GetDefaultOutputStreamParametersOnAudioThread();
+ AudioParameters params(audio_output_parameters().format(),
+ CHANNEL_LAYOUT_MONO,
+ audio_output_parameters().sample_rate(),
+ audio_output_parameters().sample_rate() / 100);
+ StartOutputStreamCallbacks(params);
+}
+
+// Start input streaming and run it for ten seconds while recording to a
+// local audio file.
+// NOTE: this test requires user interaction and is not designed to run as an
+// automatized test on bots.
+TEST_P(AudioAndroidInputTest, DISABLED_RunSimplexInputStreamWithFileAsSink) {
+ AudioParameters params = GetInputStreamParameters();
+ DVLOG(1) << params;
+ MakeAudioInputStreamOnAudioThread(params);
+
+ std::string file_name = base::StringPrintf("out_simplex_%d_%d_%d.pcm",
+ params.sample_rate(),
+ params.frames_per_buffer(),
+ params.channels());
+
+ base::WaitableEvent event(base::WaitableEvent::ResetPolicy::AUTOMATIC,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ FileAudioSink sink(&event, params, file_name);
+
+ OpenAndStartAudioInputStreamOnAudioThread(&sink);
+ DVLOG(0) << ">> Speak into the microphone to record audio...";
+ EXPECT_TRUE(event.TimedWait(TestTimeouts::action_max_timeout()));
+ StopAndCloseAudioInputStreamOnAudioThread();
+}
+
+// Same test as RunSimplexInputStreamWithFileAsSink but this time output
+// streaming is active as well (reads zeros only).
+// NOTE: this test requires user interaction and is not designed to run as an
+// automatized test on bots.
+TEST_P(AudioAndroidInputTest, DISABLED_RunDuplexInputStreamWithFileAsSink) {
+ AudioParameters in_params = GetInputStreamParameters();
+ DVLOG(1) << in_params;
+ MakeAudioInputStreamOnAudioThread(in_params);
+
+ GetDefaultOutputStreamParametersOnAudioThread();
+ DVLOG(1) << audio_output_parameters();
+ MakeAudioOutputStreamOnAudioThread(audio_output_parameters());
+
+ std::string file_name = base::StringPrintf("out_duplex_%d_%d_%d.pcm",
+ in_params.sample_rate(),
+ in_params.frames_per_buffer(),
+ in_params.channels());
+
+ base::WaitableEvent event(base::WaitableEvent::ResetPolicy::AUTOMATIC,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ FileAudioSink sink(&event, in_params, file_name);
+ MockAudioSourceCallback source;
+
+ EXPECT_CALL(source, OnMoreData(_, _, 0, NotNull()))
+ .WillRepeatedly(Invoke(RealOnMoreData));
+ EXPECT_CALL(source, OnError(_)).Times(0);
+
+ OpenAndStartAudioInputStreamOnAudioThread(&sink);
+ OpenAndStartAudioOutputStreamOnAudioThread(&source);
+ DVLOG(0) << ">> Speak into the microphone to record audio";
+ EXPECT_TRUE(event.TimedWait(TestTimeouts::action_max_timeout()));
+ StopAndCloseAudioOutputStreamOnAudioThread();
+ StopAndCloseAudioInputStreamOnAudioThread();
+}
+
+// Start audio in both directions while feeding captured data into a FIFO so
+// it can be read directly (in loopback) by the render side. A small extra
+// delay will be added by the FIFO and an estimate of this delay will be
+// printed out during the test.
+// NOTE: this test requires user interaction and is not designed to run as an
+// automatized test on bots.
+TEST_P(AudioAndroidInputTest,
+ DISABLED_RunSymmetricInputAndOutputStreamsInFullDuplex) {
+ // Get native audio parameters for the input side.
+ AudioParameters default_input_params = GetInputStreamParameters();
+
+ // Modify the parameters so that both input and output can use the same
+ // parameters by selecting 10ms as buffer size. This will also ensure that
+ // the output stream will be a mono stream since mono is default for input
+ // audio on Android.
+ AudioParameters io_params = default_input_params;
+ default_input_params.set_frames_per_buffer(io_params.sample_rate() / 100);
+ DVLOG(1) << io_params;
+
+ // Create input and output streams using the common audio parameters.
+ MakeAudioInputStreamOnAudioThread(io_params);
+ MakeAudioOutputStreamOnAudioThread(io_params);
+
+ FullDuplexAudioSinkSource full_duplex(io_params);
+
+ // Start a full duplex audio session and print out estimates of the extra
+ // delay we should expect from the FIFO. If real-time delay measurements are
+ // performed, the result should be reduced by this extra delay since it is
+ // something that has been added by the test.
+ OpenAndStartAudioInputStreamOnAudioThread(&full_duplex);
+ OpenAndStartAudioOutputStreamOnAudioThread(&full_duplex);
+ DVLOG(1) << "HINT: an estimate of the extra FIFO delay will be updated "
+ << "once per second during this test.";
+ DVLOG(0) << ">> Speak into the mic and listen to the audio in loopback...";
+ fflush(stdout);
+ base::PlatformThread::Sleep(base::Seconds(20));
+ printf("\n");
+ StopAndCloseAudioOutputStreamOnAudioThread();
+ StopAndCloseAudioInputStreamOnAudioThread();
+}
+
+INSTANTIATE_TEST_SUITE_P(AudioAndroidInputTest,
+ AudioAndroidInputTest,
+ testing::Bool());
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/android/audio_manager_android.cc b/third_party/chromium/media/audio/android/audio_manager_android.cc
new file mode 100644
index 0000000..7008e59
--- /dev/null
+++ b/third_party/chromium/media/audio/android/audio_manager_android.cc
@@ -0,0 +1,503 @@
+// Copyright (c) 2012 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 "media/audio/android/audio_manager_android.h"
+
+#include <memory>
+
+#include "base/android/build_info.h"
+#include "base/android/jni_array.h"
+#include "base/android/jni_string.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "media/audio/android/aaudio_output.h"
+#include "media/audio/android/aaudio_stubs.h"
+#include "media/audio/android/audio_track_output_stream.h"
+#include "media/audio/android/opensles_input.h"
+#include "media/audio/android/opensles_output.h"
+#include "media/audio/audio_device_description.h"
+#include "media/audio/audio_features.h"
+#include "media/audio/audio_manager.h"
+#include "media/audio/fake_audio_input_stream.h"
+#include "media/base/android/media_jni_headers/AudioManagerAndroid_jni.h"
+#include "media/base/audio_parameters.h"
+#include "media/base/channel_layout.h"
+
+using base::android::AppendJavaStringArrayToStringVector;
+using base::android::AttachCurrentThread;
+using base::android::ConvertJavaStringToUTF8;
+using base::android::ConvertUTF8ToJavaString;
+using base::android::JavaParamRef;
+using base::android::JavaRef;
+using base::android::ScopedJavaLocalRef;
+
+using media_audio_android::InitializeStubs;
+using media_audio_android::kModuleAaudio;
+using media_audio_android::StubPathMap;
+
+static const base::FilePath::CharType kAaudioLib[] =
+ FILE_PATH_LITERAL("libaaudio.so");
+
+namespace media {
+namespace {
+
+void AddDefaultDevice(AudioDeviceNames* device_names) {
+ DCHECK(device_names->empty());
+ device_names->push_front(AudioDeviceName::CreateDefault());
+}
+
+// Maximum number of output streams that can be open simultaneously.
+const int kMaxOutputStreams = 10;
+
+const int kDefaultInputBufferSize = 1024;
+const int kDefaultOutputBufferSize = 2048;
+
+} // namespace
+
+static bool InitAAudio() {
+ StubPathMap paths;
+
+ // Check if the AAudio library is available.
+ paths[kModuleAaudio].push_back(kAaudioLib);
+ if (!InitializeStubs(paths)) {
+ VLOG(1) << "Failed on loading the AAudio library and symbols";
+ return false;
+ }
+ return true;
+}
+
+std::unique_ptr<AudioManager> CreateAudioManager(
+ std::unique_ptr<AudioThread> audio_thread,
+ AudioLogFactory* audio_log_factory) {
+ return std::make_unique<AudioManagerAndroid>(std::move(audio_thread),
+ audio_log_factory);
+}
+
+AudioManagerAndroid::AudioManagerAndroid(
+ std::unique_ptr<AudioThread> audio_thread,
+ AudioLogFactory* audio_log_factory)
+ : AudioManagerBase(std::move(audio_thread), audio_log_factory),
+ communication_mode_is_on_(false),
+ output_volume_override_set_(false),
+ output_volume_override_(0) {
+ SetMaxOutputStreamsAllowed(kMaxOutputStreams);
+}
+
+AudioManagerAndroid::~AudioManagerAndroid() = default;
+
+void AudioManagerAndroid::InitializeIfNeeded() {
+ GetTaskRunner()->PostTask(
+ FROM_HERE, base::BindOnce(base::IgnoreResult(
+ &AudioManagerAndroid::GetJavaAudioManager),
+ base::Unretained(this)));
+}
+
+void AudioManagerAndroid::ShutdownOnAudioThread() {
+ AudioManagerBase::ShutdownOnAudioThread();
+
+ // Destory java android manager here because it can only be accessed on the
+ // audio thread.
+ if (!j_audio_manager_.is_null()) {
+ DVLOG(2) << "Destroying Java part of the audio manager";
+ Java_AudioManagerAndroid_close(base::android::AttachCurrentThread(),
+ j_audio_manager_);
+ j_audio_manager_.Reset();
+ }
+}
+
+bool AudioManagerAndroid::HasAudioOutputDevices() {
+ return true;
+}
+
+bool AudioManagerAndroid::HasAudioInputDevices() {
+ return true;
+}
+
+void AudioManagerAndroid::GetAudioInputDeviceNames(
+ AudioDeviceNames* device_names) {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+
+ // Always add default device parameters as first element.
+ DCHECK(device_names->empty());
+ AddDefaultDevice(device_names);
+
+ // Get list of available audio devices.
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobjectArray> j_device_array =
+ Java_AudioManagerAndroid_getAudioInputDeviceNames(env,
+ GetJavaAudioManager());
+ if (j_device_array.is_null()) {
+ // Most probable reason for a NULL result here is that the process lacks
+ // MODIFY_AUDIO_SETTINGS or RECORD_AUDIO permissions.
+ return;
+ }
+ AudioDeviceName device;
+ for (auto j_device : j_device_array.ReadElements<jobject>()) {
+ ScopedJavaLocalRef<jstring> j_device_name =
+ Java_AudioDeviceName_name(env, j_device);
+ ConvertJavaStringToUTF8(env, j_device_name.obj(), &device.device_name);
+ ScopedJavaLocalRef<jstring> j_device_id =
+ Java_AudioDeviceName_id(env, j_device);
+ ConvertJavaStringToUTF8(env, j_device_id.obj(), &device.unique_id);
+ device_names->push_back(device);
+ }
+
+ for (auto d : *device_names) {
+ DVLOG(1) << "device_name: " << d.device_name;
+ DVLOG(1) << "unique_id: " << d.unique_id;
+ }
+}
+
+void AudioManagerAndroid::GetAudioOutputDeviceNames(
+ AudioDeviceNames* device_names) {
+ // TODO(henrika): enumerate using GetAudioInputDeviceNames().
+ AddDefaultDevice(device_names);
+}
+
+AudioParameters AudioManagerAndroid::GetInputStreamParameters(
+ const std::string& device_id) {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+
+ // Use mono as preferred number of input channels on Android to save
+ // resources. Using mono also avoids a driver issue seen on Samsung
+ // Galaxy S3 and S4 devices. See http://crbug.com/256851 for details.
+ JNIEnv* env = AttachCurrentThread();
+ ChannelLayout channel_layout = CHANNEL_LAYOUT_MONO;
+ int buffer_size = Java_AudioManagerAndroid_getMinInputFrameSize(
+ env, GetNativeOutputSampleRate(),
+ ChannelLayoutToChannelCount(channel_layout));
+ buffer_size = buffer_size <= 0 ? kDefaultInputBufferSize : buffer_size;
+ int effects = AudioParameters::NO_EFFECTS;
+ effects |= Java_AudioManagerAndroid_acousticEchoCancelerIsAvailable(env)
+ ? AudioParameters::ECHO_CANCELLER
+ : AudioParameters::NO_EFFECTS;
+
+ int user_buffer_size = GetUserBufferSize();
+ if (user_buffer_size)
+ buffer_size = user_buffer_size;
+
+ AudioParameters params(AudioParameters::AUDIO_PCM_LOW_LATENCY, channel_layout,
+ GetNativeOutputSampleRate(), buffer_size);
+ params.set_effects(effects);
+ DVLOG(1) << params.AsHumanReadableString();
+ return params;
+}
+
+const char* AudioManagerAndroid::GetName() {
+ return "Android";
+}
+
+AudioOutputStream* AudioManagerAndroid::MakeAudioOutputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ AudioOutputStream* stream = AudioManagerBase::MakeAudioOutputStream(
+ params, device_id, AudioManager::LogCallback());
+ if (stream)
+ streams_.insert(static_cast<MuteableAudioOutputStream*>(stream));
+ return stream;
+}
+
+AudioInputStream* AudioManagerAndroid::MakeAudioInputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ bool has_no_input_streams = HasNoAudioInputStreams();
+ AudioInputStream* stream = AudioManagerBase::MakeAudioInputStream(
+ params, device_id, AudioManager::LogCallback());
+
+ // By default, the audio manager for Android creates streams intended for
+ // real-time VoIP sessions and therefore sets the audio mode to
+ // MODE_IN_COMMUNICATION. However, the user might have asked for a special
+ // mode where all audio input processing is disabled, and if that is the case
+ // we avoid changing the mode.
+ if (stream && has_no_input_streams &&
+ params.effects() != AudioParameters::NO_EFFECTS) {
+ communication_mode_is_on_ = true;
+ SetCommunicationAudioModeOn(true);
+ }
+ return stream;
+}
+
+void AudioManagerAndroid::ReleaseOutputStream(AudioOutputStream* stream) {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ streams_.erase(static_cast<MuteableAudioOutputStream*>(stream));
+ AudioManagerBase::ReleaseOutputStream(stream);
+}
+
+void AudioManagerAndroid::ReleaseInputStream(AudioInputStream* stream) {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ AudioManagerBase::ReleaseInputStream(stream);
+
+ // Restore the audio mode which was used before the first communication-
+ // mode stream was created.
+ if (HasNoAudioInputStreams() && communication_mode_is_on_) {
+ communication_mode_is_on_ = false;
+ SetCommunicationAudioModeOn(false);
+ }
+}
+
+AudioOutputStream* AudioManagerAndroid::MakeLinearOutputStream(
+ const AudioParameters& params,
+ const LogCallback& log_callback) {
+ DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format());
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+
+ if (UseAAudio())
+ return new AAudioOutputStream(this, params, AAUDIO_USAGE_MEDIA);
+
+ return new OpenSLESOutputStream(this, params, SL_ANDROID_STREAM_MEDIA);
+}
+
+AudioOutputStream* AudioManagerAndroid::MakeLowLatencyOutputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) {
+ DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format());
+
+ if (UseAAudio()) {
+ const aaudio_usage_t usage = communication_mode_is_on_
+ ? AAUDIO_USAGE_VOICE_COMMUNICATION
+ : AAUDIO_USAGE_MEDIA;
+ return new AAudioOutputStream(this, params, usage);
+ }
+
+ // Set stream type which matches the current system-wide audio mode used by
+ // the Android audio manager.
+ const SLint32 stream_type = communication_mode_is_on_
+ ? SL_ANDROID_STREAM_VOICE
+ : SL_ANDROID_STREAM_MEDIA;
+ return new OpenSLESOutputStream(this, params, stream_type);
+}
+
+AudioOutputStream* AudioManagerAndroid::MakeBitstreamOutputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) {
+ DCHECK(params.IsBitstreamFormat());
+ return new AudioTrackOutputStream(this, params);
+}
+
+AudioInputStream* AudioManagerAndroid::MakeLinearInputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) {
+ // TODO(henrika): add support for device selection if/when any client
+ // needs it.
+ DLOG_IF(ERROR, !device_id.empty()) << "Not implemented!";
+ DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format());
+ return new OpenSLESInputStream(this, params);
+}
+
+AudioInputStream* AudioManagerAndroid::MakeLowLatencyInputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) {
+ DVLOG(1) << "MakeLowLatencyInputStream: " << params.effects();
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format());
+ DLOG_IF(ERROR, device_id.empty()) << "Invalid device ID!";
+
+ // Use the device ID to select the correct input device.
+ // Note that the input device is always associated with a certain output
+ // device, i.e., this selection does also switch the output device.
+ // All input and output streams will be affected by the device selection.
+ if (!SetAudioDevice(device_id)) {
+ LOG(ERROR) << "Unable to select audio device!";
+ return NULL;
+ }
+
+ // Create a new audio input stream and enable or disable all audio effects
+ // given |params.effects()|.
+ return new OpenSLESInputStream(this, params);
+}
+
+// static
+bool AudioManagerAndroid::SupportsPerformanceModeForOutput() {
+ return base::android::BuildInfo::GetInstance()->sdk_int() >=
+ base::android::SDK_VERSION_NOUGAT_MR1;
+}
+
+void AudioManagerAndroid::SetMute(JNIEnv* env,
+ const JavaParamRef<jobject>& obj,
+ jboolean muted) {
+ GetTaskRunner()->PostTask(
+ FROM_HERE, base::BindOnce(&AudioManagerAndroid::DoSetMuteOnAudioThread,
+ base::Unretained(this), muted));
+}
+
+void AudioManagerAndroid::SetOutputVolumeOverride(double volume) {
+ GetTaskRunner()->PostTask(
+ FROM_HERE, base::BindOnce(&AudioManagerAndroid::DoSetVolumeOnAudioThread,
+ base::Unretained(this), volume));
+}
+
+bool AudioManagerAndroid::HasOutputVolumeOverride(double* out_volume) const {
+ if (output_volume_override_set_) {
+ *out_volume = output_volume_override_;
+ }
+ return output_volume_override_set_;
+}
+
+base::TimeDelta AudioManagerAndroid::GetOutputLatency() {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ JNIEnv* env = AttachCurrentThread();
+ return base::Milliseconds(
+ Java_AudioManagerAndroid_getOutputLatency(env, GetJavaAudioManager()));
+}
+
+AudioParameters AudioManagerAndroid::GetPreferredOutputStreamParameters(
+ const std::string& output_device_id,
+ const AudioParameters& input_params) {
+ DVLOG(1) << __FUNCTION__;
+ // TODO(tommi): Support |output_device_id|.
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ DLOG_IF(ERROR, !output_device_id.empty()) << "Not implemented!";
+ ChannelLayout channel_layout = CHANNEL_LAYOUT_STEREO;
+ int sample_rate = GetNativeOutputSampleRate();
+ int buffer_size = GetOptimalOutputFrameSize(sample_rate, 2);
+ if (input_params.IsValid()) {
+ // Use the client's input parameters if they are valid.
+ sample_rate = input_params.sample_rate();
+
+ // Pre-Lollipop devices don't support > stereo OpenSLES output and the
+ // AudioManager APIs for GetOptimalOutputFrameSize() don't support channel
+ // layouts greater than stereo unless low latency audio is supported.
+ if (input_params.channels() <= 2 ||
+ (base::android::BuildInfo::GetInstance()->sdk_int() >=
+ base::android::SDK_VERSION_LOLLIPOP &&
+ IsAudioLowLatencySupported())) {
+ channel_layout = input_params.channel_layout();
+ }
+
+ // For high latency playback on supported platforms, pass through the
+ // requested buffer size; this provides significant power savings (~25%) and
+ // reduces the potential for glitches under load.
+ if (SupportsPerformanceModeForOutput() &&
+ input_params.latency_tag() == AudioLatency::LATENCY_PLAYBACK) {
+ buffer_size = input_params.frames_per_buffer();
+ } else {
+ buffer_size = GetOptimalOutputFrameSize(
+ sample_rate, ChannelLayoutToChannelCount(channel_layout));
+ }
+ }
+
+ int user_buffer_size = GetUserBufferSize();
+ if (user_buffer_size)
+ buffer_size = user_buffer_size;
+
+ return AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, channel_layout,
+ sample_rate, buffer_size);
+}
+
+bool AudioManagerAndroid::HasNoAudioInputStreams() {
+ return input_stream_count() == 0;
+}
+
+const JavaRef<jobject>& AudioManagerAndroid::GetJavaAudioManager() {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ if (j_audio_manager_.is_null()) {
+ // Create the Android audio manager on the audio thread.
+ DVLOG(2) << "Creating Java part of the audio manager";
+ j_audio_manager_.Reset(Java_AudioManagerAndroid_createAudioManagerAndroid(
+ base::android::AttachCurrentThread(),
+ reinterpret_cast<intptr_t>(this)));
+
+ // Prepare the list of audio devices and register receivers for device
+ // notifications.
+ Java_AudioManagerAndroid_init(base::android::AttachCurrentThread(),
+ j_audio_manager_);
+ }
+ return j_audio_manager_;
+}
+
+void AudioManagerAndroid::SetCommunicationAudioModeOn(bool on) {
+ DVLOG(1) << __FUNCTION__ << ": " << on;
+ Java_AudioManagerAndroid_setCommunicationAudioModeOn(
+ base::android::AttachCurrentThread(), GetJavaAudioManager(), on);
+}
+
+bool AudioManagerAndroid::SetAudioDevice(const std::string& device_id) {
+ DVLOG(1) << __FUNCTION__ << ": " << device_id;
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+
+ // Send the unique device ID to the Java audio manager and make the
+ // device switch. Provide an empty string to the Java audio manager
+ // if the default device is selected.
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jstring> j_device_id = ConvertUTF8ToJavaString(
+ env, device_id == AudioDeviceDescription::kDefaultDeviceId ? std::string()
+ : device_id);
+ return Java_AudioManagerAndroid_setDevice(env, GetJavaAudioManager(),
+ j_device_id);
+}
+
+int AudioManagerAndroid::GetNativeOutputSampleRate() {
+ return Java_AudioManagerAndroid_getNativeOutputSampleRate(
+ base::android::AttachCurrentThread(), GetJavaAudioManager());
+}
+
+bool AudioManagerAndroid::IsAudioLowLatencySupported() {
+ return Java_AudioManagerAndroid_isAudioLowLatencySupported(
+ base::android::AttachCurrentThread(), GetJavaAudioManager());
+}
+
+int AudioManagerAndroid::GetAudioLowLatencyOutputFrameSize() {
+ return Java_AudioManagerAndroid_getAudioLowLatencyOutputFrameSize(
+ base::android::AttachCurrentThread(), GetJavaAudioManager());
+}
+
+int AudioManagerAndroid::GetOptimalOutputFrameSize(int sample_rate,
+ int channels) {
+ if (IsAudioLowLatencySupported())
+ return GetAudioLowLatencyOutputFrameSize();
+
+ return std::max(kDefaultOutputBufferSize,
+ Java_AudioManagerAndroid_getMinOutputFrameSize(
+ base::android::AttachCurrentThread(),
+ sample_rate, channels));
+}
+
+void AudioManagerAndroid::DoSetMuteOnAudioThread(bool muted) {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ for (OutputStreams::iterator it = streams_.begin();
+ it != streams_.end(); ++it) {
+ (*it)->SetMute(muted);
+ }
+}
+
+void AudioManagerAndroid::DoSetVolumeOnAudioThread(double volume) {
+ output_volume_override_set_ = true;
+ output_volume_override_ = volume;
+
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ for (OutputStreams::iterator it = streams_.begin(); it != streams_.end();
+ ++it) {
+ (*it)->SetVolume(volume);
+ }
+}
+
+bool AudioManagerAndroid::UseAAudio() {
+ if (!base::FeatureList::IsEnabled(features::kUseAAudioDriver))
+ return false;
+
+ if (base::android::BuildInfo::GetInstance()->sdk_int() <
+ base::android::SDK_VERSION_Q) {
+ // We need APIs that weren't added until API Level 28. Also, AAudio crashes
+ // on Android P, so only consider Q and above.
+ return false;
+ }
+
+ if (!is_aaudio_available_.has_value())
+ is_aaudio_available_ = InitAAudio();
+
+ return is_aaudio_available_.value();
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/android/audio_manager_android.h b/third_party/chromium/media/audio/android/audio_manager_android.h
new file mode 100644
index 0000000..92632a8
--- /dev/null
+++ b/third_party/chromium/media/audio/android/audio_manager_android.h
@@ -0,0 +1,138 @@
+// Copyright (c) 2012 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 MEDIA_AUDIO_ANDROID_AUDIO_MANAGER_ANDROID_H_
+#define MEDIA_AUDIO_ANDROID_AUDIO_MANAGER_ANDROID_H_
+
+#include <set>
+
+#include "base/android/jni_android.h"
+#include "base/macros.h"
+#include "base/synchronization/lock.h"
+#include "base/synchronization/waitable_event.h"
+#include "media/audio/audio_manager_base.h"
+
+namespace media {
+
+class MuteableAudioOutputStream;
+
+// Android implemention of AudioManager.
+class MEDIA_EXPORT AudioManagerAndroid : public AudioManagerBase {
+ public:
+ AudioManagerAndroid(std::unique_ptr<AudioThread> audio_thread,
+ AudioLogFactory* audio_log_factory);
+
+ AudioManagerAndroid(const AudioManagerAndroid&) = delete;
+ AudioManagerAndroid& operator=(const AudioManagerAndroid&) = delete;
+
+ ~AudioManagerAndroid() override;
+
+ void InitializeIfNeeded();
+
+ // Implementation of AudioManager.
+ bool HasAudioOutputDevices() override;
+ bool HasAudioInputDevices() override;
+ void GetAudioInputDeviceNames(AudioDeviceNames* device_names) override;
+ void GetAudioOutputDeviceNames(AudioDeviceNames* device_names) override;
+ AudioParameters GetInputStreamParameters(
+ const std::string& device_id) override;
+
+ AudioOutputStream* MakeAudioOutputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) override;
+ AudioInputStream* MakeAudioInputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) override;
+ void ReleaseOutputStream(AudioOutputStream* stream) override;
+ void ReleaseInputStream(AudioInputStream* stream) override;
+ const char* GetName() override;
+
+ // Implementation of AudioManagerBase.
+ AudioOutputStream* MakeLinearOutputStream(
+ const AudioParameters& params,
+ const LogCallback& log_callback) override;
+ AudioOutputStream* MakeLowLatencyOutputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) override;
+ AudioOutputStream* MakeBitstreamOutputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) override;
+ AudioInputStream* MakeLinearInputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) override;
+ AudioInputStream* MakeLowLatencyInputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) override;
+
+ // Indicates if there's support for the OpenSLES performance mode keys. See
+ // OpenSLESOutputStream for specific details. Essentially this allows for low
+ // power audio when large buffer sizes can be used.
+ static bool SupportsPerformanceModeForOutput();
+
+ void SetMute(JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& obj,
+ jboolean muted);
+
+ // Sets a volume that applies to all this manager's output audio streams.
+ // This overrides other SetVolume calls (e.g. through AudioHostMsg_SetVolume).
+ void SetOutputVolumeOverride(double volume);
+ bool HasOutputVolumeOverride(double* out_volume) const;
+
+ // Get the latency introduced by the hardware. It relies on
+ // AudioManager.getOutputLatency, which is both (a) hidden and (b) not
+ // guaranteed to be meaningful. Do not use this, except in the context of
+ // b/80326798 to adjust (hackily) for hardware latency that OpenSLES isn't
+ // otherwise accounting for.
+ base::TimeDelta GetOutputLatency();
+
+ bool IsUsingAAudioForTesting() { return UseAAudio(); }
+
+ protected:
+ void ShutdownOnAudioThread() override;
+ AudioParameters GetPreferredOutputStreamParameters(
+ const std::string& output_device_id,
+ const AudioParameters& input_params) override;
+
+ private:
+ const base::android::JavaRef<jobject>& GetJavaAudioManager();
+ bool HasNoAudioInputStreams();
+ void SetCommunicationAudioModeOn(bool on);
+ bool SetAudioDevice(const std::string& device_id);
+ int GetNativeOutputSampleRate();
+ bool IsAudioLowLatencySupported();
+ int GetAudioLowLatencyOutputFrameSize();
+ int GetOptimalOutputFrameSize(int sample_rate, int channels);
+
+ void DoSetMuteOnAudioThread(bool muted);
+ void DoSetVolumeOnAudioThread(double volume);
+
+ // Returns whether or not we can and should use AAudio.
+ bool UseAAudio();
+
+ // Java AudioManager instance.
+ base::android::ScopedJavaGlobalRef<jobject> j_audio_manager_;
+
+ typedef std::set<MuteableAudioOutputStream*> OutputStreams;
+ OutputStreams streams_;
+
+ // Enabled when first input stream is created and set to false when last
+ // input stream is destroyed. Also affects the stream type of output streams.
+ bool communication_mode_is_on_;
+
+ absl::optional<bool> is_aaudio_available_;
+
+ // If set, overrides volume level on output streams
+ bool output_volume_override_set_;
+ double output_volume_override_;
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_ANDROID_AUDIO_MANAGER_ANDROID_H_
diff --git a/third_party/chromium/media/audio/android/audio_track_output_stream.cc b/third_party/chromium/media/audio/android/audio_track_output_stream.cc
new file mode 100644
index 0000000..5b98e2d
--- /dev/null
+++ b/third_party/chromium/media/audio/android/audio_track_output_stream.cc
@@ -0,0 +1,185 @@
+// Copyright 2017 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 "media/audio/android/audio_track_output_stream.h"
+
+#include <cmath>
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/logging.h"
+#include "base/single_thread_task_runner.h"
+#include "base/time/default_tick_clock.h"
+#include "media/audio/audio_manager_base.h"
+#include "media/base/android/media_jni_headers/AudioTrackOutputStream_jni.h"
+#include "media/base/audio_sample_types.h"
+#include "media/base/audio_timestamp_helper.h"
+
+using base::android::AttachCurrentThread;
+using base::android::ScopedJavaLocalRef;
+
+namespace media {
+
+// Android audio format. For more information, please see:
+// https://developer.android.com/reference/android/media/AudioFormat.html
+enum {
+ kEncodingPcm16bit = 2, // ENCODING_PCM_16BIT
+ kEncodingAc3 = 5, // ENCODING_AC3
+ kEncodingEac3 = 6, // ENCODING_E_AC3
+};
+
+AudioTrackOutputStream::AudioTrackOutputStream(AudioManagerBase* manager,
+ const AudioParameters& params)
+ : params_(params),
+ audio_manager_(manager),
+ tick_clock_(base::DefaultTickClock::GetInstance()) {
+ if (!params_.IsBitstreamFormat()) {
+ audio_bus_ = AudioBus::Create(params_);
+ }
+}
+
+AudioTrackOutputStream::~AudioTrackOutputStream() {
+ DCHECK(!callback_);
+}
+
+bool AudioTrackOutputStream::Open() {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+ JNIEnv* env = AttachCurrentThread();
+ j_audio_output_stream_.Reset(Java_AudioTrackOutputStream_create(env));
+
+ int format = kEncodingPcm16bit;
+ if (params_.IsBitstreamFormat()) {
+ if (params_.format() == AudioParameters::AUDIO_BITSTREAM_AC3) {
+ format = kEncodingAc3;
+ } else if (params_.format() == AudioParameters::AUDIO_BITSTREAM_EAC3) {
+ format = kEncodingEac3;
+ } else {
+ NOTREACHED();
+ }
+ }
+
+ return Java_AudioTrackOutputStream_open(env, j_audio_output_stream_,
+ params_.channels(),
+ params_.sample_rate(), format);
+}
+
+void AudioTrackOutputStream::Start(AudioSourceCallback* callback) {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+ callback_ = callback;
+ Java_AudioTrackOutputStream_start(AttachCurrentThread(),
+ j_audio_output_stream_,
+ reinterpret_cast<intptr_t>(this));
+}
+
+void AudioTrackOutputStream::Stop() {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+ Java_AudioTrackOutputStream_stop(AttachCurrentThread(),
+ j_audio_output_stream_);
+ callback_ = nullptr;
+}
+
+void AudioTrackOutputStream::Close() {
+ DCHECK(!callback_);
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+
+ Java_AudioTrackOutputStream_close(AttachCurrentThread(),
+ j_audio_output_stream_);
+ audio_manager_->ReleaseOutputStream(this);
+}
+
+// This stream is always used with sub second buffer sizes, where it's
+// sufficient to simply always flush upon Start().
+void AudioTrackOutputStream::Flush() {}
+
+void AudioTrackOutputStream::SetMute(bool muted) {
+ if (params_.IsBitstreamFormat() && muted) {
+ LOG(WARNING)
+ << "Mute is not supported for compressed audio bitstream formats.";
+ return;
+ }
+
+ if (muted_ == muted)
+ return;
+
+ muted_ = muted;
+ Java_AudioTrackOutputStream_setVolume(
+ AttachCurrentThread(), j_audio_output_stream_, muted_ ? 0.0 : volume_);
+}
+
+void AudioTrackOutputStream::SetVolume(double volume) {
+ if (params_.IsBitstreamFormat()) {
+ LOG(WARNING) << "Volume change is not supported for compressed audio "
+ "bitstream formats.";
+ return;
+ }
+
+ // Track |volume_| since AudioTrack uses a scaled value.
+ volume_ = volume;
+ if (muted_)
+ return;
+
+ Java_AudioTrackOutputStream_setVolume(AttachCurrentThread(),
+ j_audio_output_stream_, volume);
+}
+
+void AudioTrackOutputStream::GetVolume(double* volume) {
+ *volume = volume_;
+}
+
+// AudioOutputStream::SourceCallback implementation methods called from Java.
+ScopedJavaLocalRef<jobject> AudioTrackOutputStream::OnMoreData(
+ JNIEnv* env,
+ jobject obj,
+ jobject audio_data,
+ jlong delay_in_frame) {
+ DCHECK(callback_);
+
+ base::TimeDelta delay =
+ AudioTimestampHelper::FramesToTime(delay_in_frame, params_.sample_rate());
+
+ void* native_buffer = env->GetDirectBufferAddress(audio_data);
+
+ if (params_.IsBitstreamFormat()) {
+ // For bitstream formats, use the direct buffer memory to avoid additional
+ // memory copy.
+ std::unique_ptr<AudioBus> audio_bus(
+ AudioBus::WrapMemory(params_, native_buffer));
+ audio_bus->set_is_bitstream_format(true);
+
+ callback_->OnMoreData(delay, tick_clock_->NowTicks(), 0, audio_bus.get());
+
+ if (audio_bus->GetBitstreamDataSize() <= 0)
+ return nullptr;
+
+ return Java_AudioTrackOutputStream_createAudioBufferInfo(
+ env, j_audio_output_stream_, audio_bus->GetBitstreamFrames(),
+ audio_bus->GetBitstreamDataSize());
+ }
+
+ // For PCM format, we need extra memory to convert planar float32 into
+ // interleaved int16.
+
+ callback_->OnMoreData(delay, tick_clock_->NowTicks(), 0, audio_bus_.get());
+
+ int16_t* native_bus = reinterpret_cast<int16_t*>(native_buffer);
+ audio_bus_->ToInterleaved<SignedInt16SampleTypeTraits>(audio_bus_->frames(),
+ native_bus);
+
+ return Java_AudioTrackOutputStream_createAudioBufferInfo(
+ env, j_audio_output_stream_, audio_bus_->frames(),
+ sizeof(*native_bus) * audio_bus_->channels() * audio_bus_->frames());
+}
+
+void AudioTrackOutputStream::OnError(JNIEnv* env, jobject obj) {
+ DCHECK(callback_);
+ callback_->OnError(AudioSourceCallback::ErrorType::kUnknown);
+}
+
+jlong AudioTrackOutputStream::GetAddress(JNIEnv* env,
+ jobject obj,
+ jobject byte_buffer) {
+ return reinterpret_cast<jlong>(env->GetDirectBufferAddress(byte_buffer));
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/android/audio_track_output_stream.h b/third_party/chromium/media/audio/android/audio_track_output_stream.h
new file mode 100644
index 0000000..6bc6e69
--- /dev/null
+++ b/third_party/chromium/media/audio/android/audio_track_output_stream.h
@@ -0,0 +1,70 @@
+// Copyright 2017 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 MEDIA_AUDIO_ANDROID_AUDIO_TRACK_OUTPUT_STREAM_H_
+#define MEDIA_AUDIO_ANDROID_AUDIO_TRACK_OUTPUT_STREAM_H_
+
+#include <memory>
+
+#include "base/android/jni_android.h"
+#include "base/time/tick_clock.h"
+#include "media/audio/android/muteable_audio_output_stream.h"
+#include "media/base/audio_parameters.h"
+
+namespace media {
+
+class AudioManagerBase;
+
+// A MuteableAudioOutputStream implementation based on the Android AudioTrack
+// API.
+class MEDIA_EXPORT AudioTrackOutputStream : public MuteableAudioOutputStream {
+ public:
+ AudioTrackOutputStream(AudioManagerBase* manager,
+ const AudioParameters& params);
+
+ AudioTrackOutputStream(const AudioTrackOutputStream&) = delete;
+ AudioTrackOutputStream& operator=(const AudioTrackOutputStream&) = delete;
+
+ ~AudioTrackOutputStream() override;
+
+ // AudioOutputStream implementation.
+ bool Open() override;
+ void Start(AudioSourceCallback* callback) override;
+ void Stop() override;
+ void SetVolume(double volume) override;
+ void GetVolume(double* volume) override;
+ void Close() override;
+ void Flush() override;
+
+ // MuteableAudioOutputStream implementation.
+ void SetMute(bool muted) override;
+
+ // AudioOutputStream::SourceCallback implementation methods called from Java.
+ base::android::ScopedJavaLocalRef<jobject> OnMoreData(JNIEnv* env,
+ jobject obj,
+ jobject audio_data,
+ jlong delay);
+ void OnError(JNIEnv* env, jobject obj);
+ jlong GetAddress(JNIEnv* env, jobject obj, jobject byte_buffer);
+
+ private:
+ const AudioParameters params_;
+
+ AudioManagerBase* audio_manager_;
+ AudioSourceCallback* callback_ = nullptr;
+ bool muted_ = false;
+ double volume_ = 1.0;
+
+ // Extra buffer for PCM format.
+ std::unique_ptr<AudioBus> audio_bus_;
+
+ const base::TickClock* tick_clock_;
+
+ // Java AudioTrackOutputStream instance.
+ base::android::ScopedJavaGlobalRef<jobject> j_audio_output_stream_;
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_ANDROID_AUDIO_TRACK_OUTPUT_STREAM_H_
diff --git a/third_party/chromium/media/audio/android/muteable_audio_output_stream.h b/third_party/chromium/media/audio/android/muteable_audio_output_stream.h
new file mode 100644
index 0000000..db4f6b1
--- /dev/null
+++ b/third_party/chromium/media/audio/android/muteable_audio_output_stream.h
@@ -0,0 +1,23 @@
+// Copyright 2016 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 MEDIA_AUDIO_ANDROID_MUTEABLE_AUDIO_OUTPUT_STREAM_H_
+#define MEDIA_AUDIO_ANDROID_MUTEABLE_AUDIO_OUTPUT_STREAM_H_
+
+#include "media/audio/audio_io.h"
+
+namespace media {
+
+class MEDIA_EXPORT MuteableAudioOutputStream : public AudioOutputStream {
+ public:
+ // Volume control coming from hardware. It overrides volume when it's
+ // true. Otherwise, use SetVolume(double volume) for scaling.
+ // This is needed because platform voice volume never goes to zero in
+ // COMMUNICATION mode on Android.
+ virtual void SetMute(bool muted) = 0;
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_ANDROID_MUTEABLE_AUDIO_OUTPUT_STREAM_H_
diff --git a/third_party/chromium/media/audio/android/opensles_input.cc b/third_party/chromium/media/audio/android/opensles_input.cc
new file mode 100644
index 0000000..ca10473
--- /dev/null
+++ b/third_party/chromium/media/audio/android/opensles_input.cc
@@ -0,0 +1,351 @@
+// Copyright (c) 2012 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 "media/audio/android/opensles_input.h"
+
+#include "base/cxx17_backports.h"
+#include "base/logging.h"
+#include "base/trace_event/trace_event.h"
+#include "media/audio/android/audio_manager_android.h"
+#include "media/base/audio_bus.h"
+
+#define LOG_ON_FAILURE_AND_RETURN(op, ...) \
+ do { \
+ SLresult err = (op); \
+ if (err != SL_RESULT_SUCCESS) { \
+ DLOG(ERROR) << #op << " failed: " << err; \
+ return __VA_ARGS__; \
+ } \
+ } while (0)
+
+namespace media {
+
+OpenSLESInputStream::OpenSLESInputStream(AudioManagerAndroid* audio_manager,
+ const AudioParameters& params)
+ : audio_manager_(audio_manager),
+ callback_(nullptr),
+ recorder_(nullptr),
+ simple_buffer_queue_(nullptr),
+ active_buffer_index_(0),
+ buffer_size_bytes_(0),
+ started_(false),
+ audio_bus_(media::AudioBus::Create(params)),
+ no_effects_(params.effects() == AudioParameters::NO_EFFECTS) {
+ DVLOG(2) << __PRETTY_FUNCTION__;
+ DVLOG(1) << "Audio effects enabled: " << !no_effects_;
+
+ const SampleFormat kSampleFormat = kSampleFormatS16;
+
+ format_.formatType = SL_DATAFORMAT_PCM;
+ format_.numChannels = static_cast<SLuint32>(params.channels());
+ // Provides sampling rate in milliHertz to OpenSLES.
+ format_.samplesPerSec = static_cast<SLuint32>(params.sample_rate() * 1000);
+ format_.bitsPerSample = format_.containerSize =
+ SampleFormatToBitsPerChannel(kSampleFormat);
+ format_.endianness = SL_BYTEORDER_LITTLEENDIAN;
+ format_.channelMask = ChannelCountToSLESChannelMask(params.channels());
+
+ buffer_size_bytes_ = params.GetBytesPerBuffer(kSampleFormat);
+ hardware_delay_ = base::Seconds(params.frames_per_buffer() /
+ static_cast<double>(params.sample_rate()));
+
+ memset(&audio_data_, 0, sizeof(audio_data_));
+}
+
+OpenSLESInputStream::~OpenSLESInputStream() {
+ DVLOG(2) << __PRETTY_FUNCTION__;
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!recorder_object_.Get());
+ DCHECK(!engine_object_.Get());
+ DCHECK(!recorder_);
+ DCHECK(!simple_buffer_queue_);
+ DCHECK(!audio_data_[0]);
+}
+
+AudioInputStream::OpenOutcome OpenSLESInputStream::Open() {
+ DVLOG(2) << __PRETTY_FUNCTION__;
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (engine_object_.Get())
+ return AudioInputStream::OpenOutcome::kFailed;
+
+ if (!CreateRecorder())
+ return AudioInputStream::OpenOutcome::kFailed;
+
+ SetupAudioBuffer();
+ return AudioInputStream::OpenOutcome::kSuccess;
+}
+
+void OpenSLESInputStream::Start(AudioInputCallback* callback) {
+ DVLOG(2) << __PRETTY_FUNCTION__;
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(callback);
+ DCHECK(recorder_);
+ DCHECK(simple_buffer_queue_);
+ if (started_)
+ return;
+
+ base::AutoLock lock(lock_);
+ DCHECK(!callback_ || callback_ == callback);
+ callback_ = callback;
+ active_buffer_index_ = 0;
+
+ // Enqueues kMaxNumOfBuffersInQueue zero buffers to get the ball rolling.
+ // TODO(henrika): add support for Start/Stop/Start sequences when we are
+ // able to clear the buffer queue. There is currently a bug in the OpenSLES
+ // implementation which forces us to always call Stop() and Close() before
+ // calling Start() again.
+ SLresult err = SL_RESULT_UNKNOWN_ERROR;
+ for (int i = 0; i < kMaxNumOfBuffersInQueue; ++i) {
+ err = (*simple_buffer_queue_)->Enqueue(
+ simple_buffer_queue_, audio_data_[i], buffer_size_bytes_);
+ if (SL_RESULT_SUCCESS != err) {
+ HandleError(err);
+ started_ = false;
+ return;
+ }
+ }
+
+ // Start the recording by setting the state to SL_RECORDSTATE_RECORDING.
+ // When the object is in the SL_RECORDSTATE_RECORDING state, adding buffers
+ // will implicitly start the filling process.
+ err = (*recorder_)->SetRecordState(recorder_, SL_RECORDSTATE_RECORDING);
+ if (SL_RESULT_SUCCESS != err) {
+ HandleError(err);
+ started_ = false;
+ return;
+ }
+
+ started_ = true;
+}
+
+void OpenSLESInputStream::Stop() {
+ DVLOG(2) << __PRETTY_FUNCTION__;
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (!started_)
+ return;
+
+ base::AutoLock lock(lock_);
+
+ // Stop recording by setting the record state to SL_RECORDSTATE_STOPPED.
+ LOG_ON_FAILURE_AND_RETURN(
+ (*recorder_)->SetRecordState(recorder_, SL_RECORDSTATE_STOPPED));
+
+ // Clear the buffer queue to get rid of old data when resuming recording.
+ LOG_ON_FAILURE_AND_RETURN(
+ (*simple_buffer_queue_)->Clear(simple_buffer_queue_));
+
+ started_ = false;
+ callback_ = nullptr;
+}
+
+void OpenSLESInputStream::Close() {
+ DVLOG(2) << __PRETTY_FUNCTION__;
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Stop the stream if it is still recording.
+ Stop();
+ {
+ // TODO(henrika): Do we need to hold the lock here?
+ base::AutoLock lock(lock_);
+
+ // Destroy the buffer queue recorder object and invalidate all associated
+ // interfaces.
+ recorder_object_.Reset();
+ simple_buffer_queue_ = nullptr;
+ recorder_ = nullptr;
+
+ // Destroy the engine object. We don't store any associated interface for
+ // this object.
+ engine_object_.Reset();
+ ReleaseAudioBuffer();
+ }
+
+ audio_manager_->ReleaseInputStream(this);
+}
+
+double OpenSLESInputStream::GetMaxVolume() {
+ return 0.0;
+}
+
+void OpenSLESInputStream::SetVolume(double volume) {
+}
+
+double OpenSLESInputStream::GetVolume() {
+ return 0.0;
+}
+
+bool OpenSLESInputStream::SetAutomaticGainControl(bool enabled) {
+ return false;
+}
+
+bool OpenSLESInputStream::GetAutomaticGainControl() {
+ return false;
+}
+
+bool OpenSLESInputStream::IsMuted() {
+ return false;
+}
+
+void OpenSLESInputStream::SetOutputDeviceForAec(
+ const std::string& output_device_id) {
+ // Not supported. Do nothing.
+}
+
+bool OpenSLESInputStream::CreateRecorder() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!engine_object_.Get());
+ DCHECK(!recorder_object_.Get());
+ DCHECK(!recorder_);
+ DCHECK(!simple_buffer_queue_);
+
+ // Initializes the engine object with specific option. After working with the
+ // object, we need to free the object and its resources.
+ SLEngineOption option[] = {
+ {SL_ENGINEOPTION_THREADSAFE, static_cast<SLuint32>(SL_BOOLEAN_TRUE)}};
+ LOG_ON_FAILURE_AND_RETURN(
+ slCreateEngine(engine_object_.Receive(), 1, option, 0, nullptr, nullptr),
+ false);
+
+ // Realize the SL engine object in synchronous mode.
+ LOG_ON_FAILURE_AND_RETURN(
+ engine_object_->Realize(engine_object_.Get(), SL_BOOLEAN_FALSE), false);
+
+ // Get the SL engine interface which is implicit.
+ SLEngineItf engine;
+ LOG_ON_FAILURE_AND_RETURN(engine_object_->GetInterface(
+ engine_object_.Get(), SL_IID_ENGINE, &engine),
+ false);
+
+ // Audio source configuration.
+ SLDataLocator_IODevice mic_locator = {SL_DATALOCATOR_IODEVICE,
+ SL_IODEVICE_AUDIOINPUT,
+ SL_DEFAULTDEVICEID_AUDIOINPUT, nullptr};
+ SLDataSource audio_source = {&mic_locator, nullptr};
+
+ // Audio sink configuration.
+ SLDataLocator_AndroidSimpleBufferQueue buffer_queue = {
+ SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
+ static_cast<SLuint32>(kMaxNumOfBuffersInQueue)};
+ SLDataSink audio_sink = {&buffer_queue, &format_};
+
+ // Create an audio recorder.
+ const SLInterfaceID interface_id[] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
+ SL_IID_ANDROIDCONFIGURATION};
+ const SLboolean interface_required[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
+
+ // Create AudioRecorder and specify SL_IID_ANDROIDCONFIGURATION.
+ LOG_ON_FAILURE_AND_RETURN(
+ (*engine)->CreateAudioRecorder(
+ engine, recorder_object_.Receive(), &audio_source, &audio_sink,
+ base::size(interface_id), interface_id, interface_required),
+ false);
+
+ SLAndroidConfigurationItf recorder_config;
+ LOG_ON_FAILURE_AND_RETURN(
+ recorder_object_->GetInterface(recorder_object_.Get(),
+ SL_IID_ANDROIDCONFIGURATION,
+ &recorder_config),
+ false);
+
+ // Uses the main microphone tuned for audio communications if effects are
+ // enabled and disables all audio processing if effects are disabled.
+ SLint32 stream_type = no_effects_
+ ? SL_ANDROID_RECORDING_PRESET_CAMCORDER
+ : SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION;
+ LOG_ON_FAILURE_AND_RETURN(
+ (*recorder_config)->SetConfiguration(recorder_config,
+ SL_ANDROID_KEY_RECORDING_PRESET,
+ &stream_type,
+ sizeof(SLint32)),
+ false);
+
+ // Realize the recorder object in synchronous mode.
+ LOG_ON_FAILURE_AND_RETURN(
+ recorder_object_->Realize(recorder_object_.Get(), SL_BOOLEAN_FALSE),
+ false);
+
+ // Get an implicit recorder interface.
+ LOG_ON_FAILURE_AND_RETURN(
+ recorder_object_->GetInterface(
+ recorder_object_.Get(), SL_IID_RECORD, &recorder_),
+ false);
+
+ // Get the simple buffer queue interface.
+ LOG_ON_FAILURE_AND_RETURN(
+ recorder_object_->GetInterface(recorder_object_.Get(),
+ SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
+ &simple_buffer_queue_),
+ false);
+
+ // Register the input callback for the simple buffer queue.
+ // This callback will be called when receiving new data from the device.
+ LOG_ON_FAILURE_AND_RETURN(
+ (*simple_buffer_queue_)->RegisterCallback(
+ simple_buffer_queue_, SimpleBufferQueueCallback, this),
+ false);
+
+ return true;
+}
+
+void OpenSLESInputStream::SimpleBufferQueueCallback(
+ SLAndroidSimpleBufferQueueItf buffer_queue,
+ void* instance) {
+ OpenSLESInputStream* stream =
+ reinterpret_cast<OpenSLESInputStream*>(instance);
+ stream->ReadBufferQueue();
+}
+
+void OpenSLESInputStream::ReadBufferQueue() {
+ base::AutoLock lock(lock_);
+ if (!started_)
+ return;
+
+ TRACE_EVENT0("audio", "OpenSLESOutputStream::ReadBufferQueue");
+
+ // Convert from interleaved format to deinterleaved audio bus format.
+ audio_bus_->FromInterleaved<SignedInt16SampleTypeTraits>(
+ reinterpret_cast<int16_t*>(audio_data_[active_buffer_index_]),
+ audio_bus_->frames());
+
+ // TODO(henrika): Investigate if it is possible to get an accurate
+ // delay estimation.
+ callback_->OnData(audio_bus_.get(), base::TimeTicks::Now() - hardware_delay_,
+ 0.0);
+
+ // Done with this buffer. Send it to device for recording.
+ SLresult err =
+ (*simple_buffer_queue_)->Enqueue(simple_buffer_queue_,
+ audio_data_[active_buffer_index_],
+ buffer_size_bytes_);
+ if (SL_RESULT_SUCCESS != err)
+ HandleError(err);
+
+ active_buffer_index_ = (active_buffer_index_ + 1) % kMaxNumOfBuffersInQueue;
+}
+
+void OpenSLESInputStream::SetupAudioBuffer() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!audio_data_[0]);
+ for (int i = 0; i < kMaxNumOfBuffersInQueue; ++i) {
+ audio_data_[i] = new uint8_t[buffer_size_bytes_];
+ }
+}
+
+void OpenSLESInputStream::ReleaseAudioBuffer() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (audio_data_[0]) {
+ for (int i = 0; i < kMaxNumOfBuffersInQueue; ++i) {
+ delete[] audio_data_[i];
+ audio_data_[i] = nullptr;
+ }
+ }
+}
+
+void OpenSLESInputStream::HandleError(SLresult error) {
+ DLOG(ERROR) << "OpenSLES Input error " << error;
+ if (callback_)
+ callback_->OnError();
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/android/opensles_input.h b/third_party/chromium/media/audio/android/opensles_input.h
new file mode 100644
index 0000000..f0d86fd
--- /dev/null
+++ b/third_party/chromium/media/audio/android/opensles_input.h
@@ -0,0 +1,117 @@
+// Copyright (c) 2012 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 MEDIA_AUDIO_ANDROID_OPENSLES_INPUT_H_
+#define MEDIA_AUDIO_ANDROID_OPENSLES_INPUT_H_
+
+#include <SLES/OpenSLES.h>
+#include <SLES/OpenSLES_Android.h>
+#include <stdint.h>
+
+#include <memory>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/thread_checker.h"
+#include "media/audio/android/opensles_util.h"
+#include "media/audio/audio_io.h"
+#include "media/base/audio_parameters.h"
+
+namespace media {
+
+class AudioBus;
+class AudioManagerAndroid;
+
+// Implements PCM audio input support for Android using the OpenSLES API.
+// This class is created and lives on the Audio Manager thread but recorded
+// audio buffers are delivered on an internal OpenSLES audio thread. All public
+// methods should be called on the Audio Manager thread.
+class OpenSLESInputStream : public AudioInputStream {
+ public:
+ static const int kMaxNumOfBuffersInQueue = 2;
+
+ OpenSLESInputStream(AudioManagerAndroid* manager,
+ const AudioParameters& params);
+
+ OpenSLESInputStream(const OpenSLESInputStream&) = delete;
+ OpenSLESInputStream& operator=(const OpenSLESInputStream&) = delete;
+
+ ~OpenSLESInputStream() override;
+
+ // Implementation of AudioInputStream.
+ OpenOutcome Open() override;
+ void Start(AudioInputCallback* callback) override;
+ void Stop() override;
+ void Close() override;
+ double GetMaxVolume() override;
+ void SetVolume(double volume) override;
+ double GetVolume() override;
+ bool SetAutomaticGainControl(bool enabled) override;
+ bool GetAutomaticGainControl() override;
+ bool IsMuted() override;
+ void SetOutputDeviceForAec(const std::string& output_device_id) override;
+
+ private:
+ bool CreateRecorder();
+
+ // Called from OpenSLES specific audio worker thread.
+ static void SimpleBufferQueueCallback(
+ SLAndroidSimpleBufferQueueItf buffer_queue,
+ void* instance);
+
+ // Called from OpenSLES specific audio worker thread.
+ void ReadBufferQueue();
+
+ // Called in Open();
+ void SetupAudioBuffer();
+
+ // Called in Close();
+ void ReleaseAudioBuffer();
+
+ // If OpenSLES reports an error this function handles it and passes it to
+ // the attached AudioInputCallback::OnError().
+ void HandleError(SLresult error);
+
+ base::ThreadChecker thread_checker_;
+
+ // Protects |callback_|, |active_buffer_index_|, |audio_data_|,
+ // |buffer_size_bytes_| and |simple_buffer_queue_|.
+ base::Lock lock_;
+
+ AudioManagerAndroid* audio_manager_;
+
+ AudioInputCallback* callback_;
+
+ // Shared engine interfaces for the app.
+ media::ScopedSLObjectItf recorder_object_;
+ media::ScopedSLObjectItf engine_object_;
+
+ SLRecordItf recorder_;
+
+ // Buffer queue recorder interface.
+ SLAndroidSimpleBufferQueueItf simple_buffer_queue_;
+
+ SLDataFormat_PCM format_;
+
+ // Audio buffers that are allocated in the constructor based on
+ // info from audio parameters.
+ uint8_t* audio_data_[kMaxNumOfBuffersInQueue];
+
+ int active_buffer_index_;
+ int buffer_size_bytes_;
+
+ bool started_;
+
+ base::TimeDelta hardware_delay_;
+
+ std::unique_ptr<media::AudioBus> audio_bus_;
+
+ // Set to true at construction if user wants to disable all audio effects.
+ const bool no_effects_ = false;
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_ANDROID_OPENSLES_INPUT_H_
diff --git a/third_party/chromium/media/audio/android/opensles_output.cc b/third_party/chromium/media/audio/android/opensles_output.cc
new file mode 100644
index 0000000..a15692a
--- /dev/null
+++ b/third_party/chromium/media/audio/android/opensles_output.cc
@@ -0,0 +1,508 @@
+// Copyright (c) 2012 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 "media/audio/android/opensles_output.h"
+
+#include "base/android/build_info.h"
+#include "base/cxx17_backports.h"
+#include "base/feature_list.h"
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "base/time/time.h"
+#include "base/trace_event/trace_event.h"
+#include "media/audio/android/audio_manager_android.h"
+#include "media/base/audio_sample_types.h"
+#include "media/base/audio_timestamp_helper.h"
+#include "media/base/media_switches.h"
+
+#define LOG_ON_FAILURE_AND_RETURN(op, ...) \
+ do { \
+ SLresult err = (op); \
+ if (err != SL_RESULT_SUCCESS) { \
+ DLOG(ERROR) << #op << " failed: " << err; \
+ return __VA_ARGS__; \
+ } \
+ } while (0)
+
+namespace media {
+
+static bool IsFloatAudioSupported() {
+ const auto* build_info = base::android::BuildInfo::GetInstance();
+ if (build_info->sdk_int() < base::android::SDK_VERSION_LOLLIPOP)
+ return false;
+
+ // Vivo devices up until Lollipop used their own Audio Mixer which does not
+ // support float audio output. https://crbug.com/737188.
+ if (build_info->sdk_int() == base::android::SDK_VERSION_LOLLIPOP &&
+ base::EqualsCaseInsensitiveASCII(build_info->manufacturer(), "vivo")) {
+ return false;
+ }
+
+ return true;
+}
+
+OpenSLESOutputStream::OpenSLESOutputStream(AudioManagerAndroid* manager,
+ const AudioParameters& params,
+ SLint32 stream_type)
+ : audio_manager_(manager),
+ stream_type_(stream_type),
+ callback_(nullptr),
+ player_(nullptr),
+ simple_buffer_queue_(nullptr),
+ audio_data_(),
+ active_buffer_index_(0),
+ started_(false),
+ muted_(false),
+ volume_(1.0),
+ samples_per_second_(params.sample_rate()),
+ sample_format_(IsFloatAudioSupported() ? kSampleFormatF32
+ : kSampleFormatS16),
+ bytes_per_frame_(params.GetBytesPerFrame(sample_format_)),
+ buffer_size_bytes_(params.GetBytesPerBuffer(sample_format_)),
+ performance_mode_(SL_ANDROID_PERFORMANCE_NONE),
+ delay_calculator_(samples_per_second_) {
+ DVLOG(2) << "OpenSLESOutputStream::OpenSLESOutputStream("
+ << "stream_type=" << stream_type << ")";
+
+ if (AudioManagerAndroid::SupportsPerformanceModeForOutput()) {
+ if (params.latency_tag() == AudioLatency::LATENCY_PLAYBACK)
+ performance_mode_ = SL_ANDROID_PERFORMANCE_POWER_SAVING;
+ else if (params.latency_tag() == AudioLatency::LATENCY_RTC)
+ performance_mode_ = SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS;
+ }
+
+ audio_bus_ = AudioBus::Create(params);
+
+ if (sample_format_ == kSampleFormatF32) {
+ float_format_.formatType = SL_ANDROID_DATAFORMAT_PCM_EX;
+ float_format_.numChannels = static_cast<SLuint32>(params.channels());
+ // Despite the name, this field is actually the sampling rate in millihertz.
+ float_format_.sampleRate =
+ static_cast<SLuint32>(samples_per_second_ * 1000);
+ float_format_.bitsPerSample = float_format_.containerSize =
+ SampleFormatToBitsPerChannel(sample_format_);
+ float_format_.endianness = SL_BYTEORDER_LITTLEENDIAN;
+ float_format_.channelMask =
+ ChannelCountToSLESChannelMask(params.channels());
+ float_format_.representation = SL_ANDROID_PCM_REPRESENTATION_FLOAT;
+ return;
+ }
+
+ format_.formatType = SL_DATAFORMAT_PCM;
+ format_.numChannels = static_cast<SLuint32>(params.channels());
+ // Despite the name, this field is actually the sampling rate in millihertz :|
+ format_.samplesPerSec = static_cast<SLuint32>(samples_per_second_ * 1000);
+ format_.bitsPerSample = format_.containerSize =
+ SampleFormatToBitsPerChannel(sample_format_);
+ format_.endianness = SL_BYTEORDER_LITTLEENDIAN;
+ format_.channelMask = ChannelCountToSLESChannelMask(params.channels());
+}
+
+OpenSLESOutputStream::~OpenSLESOutputStream() {
+ DVLOG(2) << "OpenSLESOutputStream::~OpenSLESOutputStream()";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!engine_object_.Get());
+ DCHECK(!player_object_.Get());
+ DCHECK(!output_mixer_.Get());
+ DCHECK(!player_);
+ DCHECK(!simple_buffer_queue_);
+ DCHECK(!audio_data_[0]);
+}
+
+bool OpenSLESOutputStream::Open() {
+ DVLOG(2) << "OpenSLESOutputStream::Open()";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (engine_object_.Get())
+ return false;
+
+ if (!CreatePlayer())
+ return false;
+
+ SetupAudioBuffer();
+ active_buffer_index_ = 0;
+
+ return true;
+}
+
+void OpenSLESOutputStream::Start(AudioSourceCallback* callback) {
+ DVLOG(2) << "OpenSLESOutputStream::Start()";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(callback);
+ DCHECK(player_);
+ DCHECK(simple_buffer_queue_);
+ if (started_)
+ return;
+
+ base::AutoLock lock(lock_);
+ DCHECK(!callback_);
+ callback_ = callback;
+
+ CacheHardwareLatencyIfNeeded();
+
+ // Fill audio data with silence to avoid start-up glitches. Don't use
+ // FillBufferQueueNoLock() since it can trigger recursive entry if an error
+ // occurs while writing into the stream. See http://crbug.com/624877.
+ memset(audio_data_[active_buffer_index_], 0, buffer_size_bytes_);
+ LOG_ON_FAILURE_AND_RETURN((*simple_buffer_queue_)
+ ->Enqueue(simple_buffer_queue_,
+ audio_data_[active_buffer_index_],
+ buffer_size_bytes_));
+ active_buffer_index_ = (active_buffer_index_ + 1) % kMaxNumOfBuffersInQueue;
+
+ // Start streaming data by setting the play state to SL_PLAYSTATE_PLAYING.
+ // For a player object, when the object is in the SL_PLAYSTATE_PLAYING
+ // state, adding buffers will implicitly start playback.
+ LOG_ON_FAILURE_AND_RETURN(
+ (*player_)->SetPlayState(player_, SL_PLAYSTATE_PLAYING));
+
+ // On older version of Android, the position may not be reset even though we
+ // call Clear() during Stop(), in this case the best we can do is assume that
+ // we're continuing on from this previous position.
+ uint32_t position_in_ms = 0;
+ LOG_ON_FAILURE_AND_RETURN((*player_)->GetPosition(player_, &position_in_ms));
+ delay_calculator_.SetBaseTimestamp(base::Milliseconds(position_in_ms));
+ delay_calculator_.AddFrames(audio_bus_->frames());
+
+ started_ = true;
+}
+
+void OpenSLESOutputStream::Stop() {
+ DVLOG(2) << "OpenSLESOutputStream::Stop()";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (!started_)
+ return;
+
+ base::AutoLock lock(lock_);
+
+ // Stop playing by setting the play state to SL_PLAYSTATE_STOPPED.
+ LOG_ON_FAILURE_AND_RETURN(
+ (*player_)->SetPlayState(player_, SL_PLAYSTATE_STOPPED));
+
+ // Clear the buffer queue so that the old data won't be played when
+ // resuming playing.
+ LOG_ON_FAILURE_AND_RETURN(
+ (*simple_buffer_queue_)->Clear(simple_buffer_queue_));
+
+#ifndef NDEBUG
+ // Verify that the buffer queue is in fact cleared as it should.
+ SLAndroidSimpleBufferQueueState buffer_queue_state;
+ LOG_ON_FAILURE_AND_RETURN((*simple_buffer_queue_)->GetState(
+ simple_buffer_queue_, &buffer_queue_state));
+ DCHECK_EQ(0u, buffer_queue_state.count);
+ DCHECK_EQ(0u, buffer_queue_state.index);
+#endif
+
+ callback_ = nullptr;
+ started_ = false;
+}
+
+void OpenSLESOutputStream::Close() {
+ DVLOG(2) << "OpenSLESOutputStream::Close()";
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Stop the stream if it is still playing.
+ Stop();
+ {
+ // Destroy the buffer queue player object and invalidate all associated
+ // interfaces.
+ player_object_.Reset();
+ simple_buffer_queue_ = nullptr;
+ player_ = nullptr;
+
+ // Destroy the mixer object. We don't store any associated interface for
+ // this object.
+ output_mixer_.Reset();
+
+ // Destroy the engine object. We don't store any associated interface for
+ // this object.
+ engine_object_.Reset();
+ ReleaseAudioBuffer();
+ }
+
+ audio_manager_->ReleaseOutputStream(this);
+}
+
+// This stream is always used with sub second buffer sizes, where it's
+// sufficient to simply always flush upon Start().
+void OpenSLESOutputStream::Flush() {}
+
+void OpenSLESOutputStream::SetVolume(double volume) {
+ DVLOG(2) << "OpenSLESOutputStream::SetVolume(" << volume << ")";
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ double volume_override = 0;
+ if (audio_manager_->HasOutputVolumeOverride(&volume_override)) {
+ volume = volume_override;
+ }
+
+ float volume_float = static_cast<float>(volume);
+ if (volume_float < 0.0f || volume_float > 1.0f) {
+ return;
+ }
+ volume_ = volume_float;
+}
+
+void OpenSLESOutputStream::GetVolume(double* volume) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ *volume = static_cast<double>(volume_);
+}
+
+void OpenSLESOutputStream::SetMute(bool muted) {
+ DVLOG(2) << "OpenSLESOutputStream::SetMute(" << muted << ")";
+ DCHECK(thread_checker_.CalledOnValidThread());
+ muted_ = muted;
+}
+
+bool OpenSLESOutputStream::CreatePlayer() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!engine_object_.Get());
+ DCHECK(!player_object_.Get());
+ DCHECK(!output_mixer_.Get());
+ DCHECK(!player_);
+ DCHECK(!simple_buffer_queue_);
+
+ // Initializes the engine object with specific option. After working with the
+ // object, we need to free the object and its resources.
+ SLEngineOption option[] = {
+ {SL_ENGINEOPTION_THREADSAFE, static_cast<SLuint32>(SL_BOOLEAN_TRUE)}};
+ LOG_ON_FAILURE_AND_RETURN(
+ slCreateEngine(engine_object_.Receive(), 1, option, 0, nullptr, nullptr),
+ false);
+
+ // Realize the SL engine object in synchronous mode.
+ LOG_ON_FAILURE_AND_RETURN(
+ engine_object_->Realize(engine_object_.Get(), SL_BOOLEAN_FALSE), false);
+
+ // Get the SL engine interface which is implicit.
+ SLEngineItf engine;
+ LOG_ON_FAILURE_AND_RETURN(engine_object_->GetInterface(
+ engine_object_.Get(), SL_IID_ENGINE, &engine),
+ false);
+
+ // Create output mixer object to be used by the player.
+ LOG_ON_FAILURE_AND_RETURN(
+ (*engine)->CreateOutputMix(engine, output_mixer_.Receive(), 0, nullptr,
+ nullptr),
+ false);
+
+ // Realizing the output mix object in synchronous mode.
+ LOG_ON_FAILURE_AND_RETURN(
+ output_mixer_->Realize(output_mixer_.Get(), SL_BOOLEAN_FALSE), false);
+
+ // Audio source configuration.
+ SLDataLocator_AndroidSimpleBufferQueue simple_buffer_queue = {
+ SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
+ static_cast<SLuint32>(kMaxNumOfBuffersInQueue)};
+ SLDataSource audio_source;
+ if (sample_format_ == kSampleFormatF32)
+ audio_source = {&simple_buffer_queue, &float_format_};
+ else
+ audio_source = {&simple_buffer_queue, &format_};
+
+ // Audio sink configuration.
+ SLDataLocator_OutputMix locator_output_mix = {SL_DATALOCATOR_OUTPUTMIX,
+ output_mixer_.Get()};
+ SLDataSink audio_sink = {&locator_output_mix, nullptr};
+
+ // Create an audio player.
+ const SLInterfaceID interface_id[] = {SL_IID_BUFFERQUEUE, SL_IID_VOLUME,
+ SL_IID_ANDROIDCONFIGURATION};
+ const SLboolean interface_required[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE,
+ SL_BOOLEAN_TRUE};
+ LOG_ON_FAILURE_AND_RETURN(
+ (*engine)->CreateAudioPlayer(
+ engine, player_object_.Receive(), &audio_source, &audio_sink,
+ base::size(interface_id), interface_id, interface_required),
+ false);
+
+ // Create AudioPlayer and specify SL_IID_ANDROIDCONFIGURATION.
+ SLAndroidConfigurationItf player_config;
+ LOG_ON_FAILURE_AND_RETURN(
+ player_object_->GetInterface(
+ player_object_.Get(), SL_IID_ANDROIDCONFIGURATION, &player_config),
+ false);
+
+ // Set configuration using the stream type provided at construction.
+ LOG_ON_FAILURE_AND_RETURN(
+ (*player_config)
+ ->SetConfiguration(player_config, SL_ANDROID_KEY_STREAM_TYPE,
+ &stream_type_, sizeof(SLint32)),
+ false);
+
+ // Set configuration using the stream type provided at construction.
+ if (performance_mode_ > SL_ANDROID_PERFORMANCE_NONE) {
+ LOG_ON_FAILURE_AND_RETURN(
+ (*player_config)
+ ->SetConfiguration(player_config, SL_ANDROID_KEY_PERFORMANCE_MODE,
+ &performance_mode_, sizeof(SLuint32)),
+ false);
+ }
+
+ // Realize the player object in synchronous mode.
+ LOG_ON_FAILURE_AND_RETURN(
+ player_object_->Realize(player_object_.Get(), SL_BOOLEAN_FALSE), false);
+
+ // Get an implicit player interface.
+ LOG_ON_FAILURE_AND_RETURN(
+ player_object_->GetInterface(player_object_.Get(), SL_IID_PLAY, &player_),
+ false);
+
+ // Get the simple buffer queue interface.
+ LOG_ON_FAILURE_AND_RETURN(
+ player_object_->GetInterface(
+ player_object_.Get(), SL_IID_BUFFERQUEUE, &simple_buffer_queue_),
+ false);
+
+ // Register the input callback for the simple buffer queue.
+ // This callback will be called when the soundcard needs data.
+ LOG_ON_FAILURE_AND_RETURN(
+ (*simple_buffer_queue_)->RegisterCallback(
+ simple_buffer_queue_, SimpleBufferQueueCallback, this),
+ false);
+
+ return true;
+}
+
+void OpenSLESOutputStream::SimpleBufferQueueCallback(
+ SLAndroidSimpleBufferQueueItf buffer_queue,
+ void* instance) {
+ OpenSLESOutputStream* stream =
+ reinterpret_cast<OpenSLESOutputStream*>(instance);
+ stream->FillBufferQueue();
+}
+
+void OpenSLESOutputStream::FillBufferQueue() {
+ base::AutoLock lock(lock_);
+ if (!started_)
+ return;
+
+ TRACE_EVENT0("audio", "OpenSLESOutputStream::FillBufferQueue");
+
+ // Verify that we are in a playing state.
+ SLuint32 state;
+ SLresult err = (*player_)->GetPlayState(player_, &state);
+ if (SL_RESULT_SUCCESS != err) {
+ HandleError(err);
+ return;
+ }
+ if (state != SL_PLAYSTATE_PLAYING) {
+ DLOG(WARNING) << "Received callback in non-playing state";
+ return;
+ }
+
+ // Fill up one buffer in the queue by asking the registered source for
+ // data using the OnMoreData() callback.
+ FillBufferQueueNoLock();
+}
+
+void OpenSLESOutputStream::FillBufferQueueNoLock() {
+ // Ensure that the calling thread has acquired the lock since it is not
+ // done in this method.
+ lock_.AssertAcquired();
+
+ // Calculate the position relative to the number of frames written.
+ uint32_t position_in_ms = 0;
+ SLresult err = (*player_)->GetPosition(player_, &position_in_ms);
+
+ // Given the position of the playback head, compute the approximate number of
+ // frames that have been queued to the buffer but not yet played out.
+ // Note that the value returned by GetFramesToTarget() is negative because
+ // more frames have been added to |delay_calculator_| than have been played
+ // out and thus the target timestamp is earlier than the current timestamp of
+ // |delay_calculator_|.
+ const int delay_frames =
+ err == SL_RESULT_SUCCESS
+ ? -delay_calculator_.GetFramesToTarget(
+ AdjustPositionForHardwareLatency(position_in_ms))
+ : 0;
+ DCHECK_GE(delay_frames, 0);
+
+ // Note: *DO NOT* use format_.samplesPerSecond in any calculations, it is not
+ // actually the sample rate! See constructor comments. :|
+ const base::TimeDelta delay =
+ AudioTimestampHelper::FramesToTime(delay_frames, samples_per_second_);
+
+ // Read data from the registered client source.
+ const int frames_filled =
+ callback_->OnMoreData(delay, base::TimeTicks::Now(), 0, audio_bus_.get());
+ if (frames_filled <= 0) {
+ // Audio source is shutting down, or halted on error.
+ return;
+ }
+
+ // Note: If the internal representation ever changes from 16-bit PCM to
+ // raw float, the data must be clipped and sanitized since it may come
+ // from an untrusted source such as NaCl.
+ audio_bus_->Scale(muted_ ? 0.0f : volume_);
+ if (sample_format_ == kSampleFormatS16) {
+ audio_bus_->ToInterleaved<SignedInt16SampleTypeTraits>(
+ frames_filled,
+ reinterpret_cast<int16_t*>(audio_data_[active_buffer_index_]));
+ } else {
+ DCHECK_EQ(sample_format_, kSampleFormatF32);
+
+ // We skip clipping since that occurs at the shared memory boundary.
+ audio_bus_->ToInterleaved<Float32SampleTypeTraitsNoClip>(
+ frames_filled,
+ reinterpret_cast<float*>(audio_data_[active_buffer_index_]));
+ }
+
+ delay_calculator_.AddFrames(frames_filled);
+ const int num_filled_bytes = frames_filled * bytes_per_frame_;
+ DCHECK_LE(static_cast<size_t>(num_filled_bytes), buffer_size_bytes_);
+
+ // Enqueue the buffer for playback.
+ err = (*simple_buffer_queue_)
+ ->Enqueue(simple_buffer_queue_, audio_data_[active_buffer_index_],
+ num_filled_bytes);
+ if (SL_RESULT_SUCCESS != err)
+ HandleError(err);
+
+ active_buffer_index_ = (active_buffer_index_ + 1) % kMaxNumOfBuffersInQueue;
+}
+
+void OpenSLESOutputStream::SetupAudioBuffer() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!audio_data_[0]);
+ for (int i = 0; i < kMaxNumOfBuffersInQueue; ++i)
+ audio_data_[i] = new uint8_t[buffer_size_bytes_];
+}
+
+void OpenSLESOutputStream::ReleaseAudioBuffer() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (audio_data_[0]) {
+ for (int i = 0; i < kMaxNumOfBuffersInQueue; ++i) {
+ delete[] audio_data_[i];
+ audio_data_[i] = nullptr;
+ }
+ }
+}
+
+void OpenSLESOutputStream::HandleError(SLresult error) {
+ DLOG(ERROR) << "OpenSLES Output error " << error;
+ // TODO(dalecurtis): Consider sending a translated |error|.
+ if (callback_)
+ callback_->OnError(AudioSourceCallback::ErrorType::kUnknown);
+}
+
+void OpenSLESOutputStream::CacheHardwareLatencyIfNeeded() {
+ // If the feature is turned off, then leave it at its default (zero) value.
+ // In general, GetOutputLatency is not reliable.
+ if (!base::FeatureList::IsEnabled(kUseAudioLatencyFromHAL))
+ return;
+
+ hardware_latency_ = audio_manager_->GetOutputLatency();
+}
+
+base::TimeDelta OpenSLESOutputStream::AdjustPositionForHardwareLatency(
+ uint32_t position_in_ms) {
+ base::TimeDelta position = base::Milliseconds(position_in_ms);
+
+ if (position <= hardware_latency_)
+ return base::Milliseconds(0);
+
+ return position - hardware_latency_;
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/android/opensles_output.h b/third_party/chromium/media/audio/android/opensles_output.h
new file mode 100644
index 0000000..a932da9
--- /dev/null
+++ b/third_party/chromium/media/audio/android/opensles_output.h
@@ -0,0 +1,160 @@
+// Copyright (c) 2012 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 MEDIA_AUDIO_ANDROID_OPENSLES_OUTPUT_H_
+#define MEDIA_AUDIO_ANDROID_OPENSLES_OUTPUT_H_
+
+#include <SLES/OpenSLES.h>
+#include <SLES/OpenSLES_Android.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/thread_checker.h"
+#include "media/audio/android/muteable_audio_output_stream.h"
+#include "media/audio/android/opensles_util.h"
+#include "media/base/audio_parameters.h"
+#include "media/base/audio_timestamp_helper.h"
+
+namespace media {
+
+class AudioManagerAndroid;
+
+// Implements PCM audio output support for Android using the OpenSLES API.
+// This class is created and lives on the Audio Manager thread but recorded
+// audio buffers are given to us from an internal OpenSLES audio thread.
+// All public methods should be called on the Audio Manager thread.
+class OpenSLESOutputStream : public MuteableAudioOutputStream {
+ public:
+ static const int kMaxNumOfBuffersInQueue = 2;
+
+ OpenSLESOutputStream(AudioManagerAndroid* manager,
+ const AudioParameters& params,
+ SLint32 stream_type);
+
+ OpenSLESOutputStream(const OpenSLESOutputStream&) = delete;
+ OpenSLESOutputStream& operator=(const OpenSLESOutputStream&) = delete;
+
+ ~OpenSLESOutputStream() override;
+
+ // Implementation of MuteableAudioOutputStream.
+ bool Open() override;
+ void Close() override;
+ void Flush() override;
+ void Start(AudioSourceCallback* callback) override;
+ void Stop() override;
+ void SetVolume(double volume) override;
+ void GetVolume(double* volume) override;
+
+ // Set the value of |muted_|. It does not affect |volume_| which can be
+ // got by calling GetVolume(). See comments for |muted_| below.
+ void SetMute(bool muted) override;
+
+ private:
+ bool CreatePlayer();
+
+ // Called from OpenSLES specific audio worker thread.
+ static void SimpleBufferQueueCallback(
+ SLAndroidSimpleBufferQueueItf buffer_queue,
+ void* instance);
+
+ // Fills up one buffer by asking the registered source for data.
+ // Called from OpenSLES specific audio worker thread.
+ void FillBufferQueue();
+
+ // Called from the audio manager thread.
+ void FillBufferQueueNoLock();
+
+ // Called in Open();
+ void SetupAudioBuffer();
+
+ // Called in Close();
+ void ReleaseAudioBuffer();
+
+ // If OpenSLES reports an error this function handles it and passes it to
+ // the attached AudioOutputCallback::OnError().
+ void HandleError(SLresult error);
+
+ // Cache |hardware_latency_in_ms_| by asking |audio_manager_| for it, if the
+ // kUseAudioLatencyFromHAL is enabled.
+ void CacheHardwareLatencyIfNeeded();
+
+ // Adjust |position_in_ms| for hardware latency, and return the result.
+ base::TimeDelta AdjustPositionForHardwareLatency(uint32_t position_in_ms);
+
+ base::ThreadChecker thread_checker_;
+
+ // Protects |callback_|, |active_buffer_index_|, |audio_data_|,
+ // |buffer_size_bytes_| and |simple_buffer_queue_|.
+ base::Lock lock_;
+
+ AudioManagerAndroid* audio_manager_;
+
+ // Audio playback stream type.
+ // See SLES/OpenSLES_Android.h for details.
+ SLint32 stream_type_;
+
+ AudioSourceCallback* callback_;
+
+ // Shared engine interfaces for the app.
+ media::ScopedSLObjectItf engine_object_;
+ media::ScopedSLObjectItf player_object_;
+ media::ScopedSLObjectItf output_mixer_;
+
+ SLPlayItf player_;
+
+ // Buffer queue recorder interface.
+ SLAndroidSimpleBufferQueueItf simple_buffer_queue_;
+
+ SLDataFormat_PCM format_;
+ SLAndroidDataFormat_PCM_EX float_format_;
+
+ // Audio buffers that are allocated during Open() based on parameters given
+ // during construction.
+ uint8_t* audio_data_[kMaxNumOfBuffersInQueue];
+
+ int active_buffer_index_;
+
+ bool started_;
+
+ // Volume control coming from hardware. It overrides |volume_| when it's
+ // true. Otherwise, use |volume_| for scaling.
+ // This is needed because platform voice volume never goes to zero in
+ // COMMUNICATION mode on Android.
+ bool muted_;
+
+ // Volume level from 0 to 1.
+ float volume_;
+
+ int samples_per_second_;
+
+ // On Android 5.0+ we can output directly to float instead of in integer, so
+ // there we'll use kSampleFormatF32. If not, this will be kSampleFormatS16.
+ SampleFormat sample_format_;
+
+ int bytes_per_frame_;
+ size_t buffer_size_bytes_;
+
+ // On API level 25+ we can provide hints to OpenSLES about what type of
+ // content the stream is being used for.
+ SLuint32 performance_mode_;
+
+ // Used to calculate the delay value for each OnMoreData() call.
+ AudioTimestampHelper delay_calculator_;
+
+ // Container for retrieving data from AudioSourceCallback::OnMoreData().
+ std::unique_ptr<AudioBus> audio_bus_;
+
+ // Adjustment for hardware latency. Needed for some cast targets, since
+ // OpenSLES's GetPosition doesn't properly account for HAL latency.
+ base::TimeDelta hardware_latency_;
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_ANDROID_OPENSLES_OUTPUT_H_
diff --git a/third_party/chromium/media/audio/android/opensles_util.cc b/third_party/chromium/media/audio/android/opensles_util.cc
new file mode 100644
index 0000000..128a3bd
--- /dev/null
+++ b/third_party/chromium/media/audio/android/opensles_util.cc
@@ -0,0 +1,52 @@
+// Copyright 2016 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 "media/audio/android/opensles_util.h"
+
+#include "base/logging.h"
+
+namespace media {
+
+#define SL_ANDROID_SPEAKER_QUAD \
+ (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT | SL_SPEAKER_BACK_LEFT | \
+ SL_SPEAKER_BACK_RIGHT)
+#define SL_ANDROID_SPEAKER_5DOT1 \
+ (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT | SL_SPEAKER_FRONT_CENTER | \
+ SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_BACK_LEFT | SL_SPEAKER_BACK_RIGHT)
+#define SL_ANDROID_SPEAKER_7DOT1 \
+ (SL_ANDROID_SPEAKER_5DOT1 | SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT)
+
+// Ported from:
+// https://android.googlesource.com/platform/frameworks/wilhelm/+/refs/heads/master/src/android/channels.h
+// https://android.googlesource.com/platform/frameworks/wilhelm/+/refs/heads/master/src/android/channels.c
+SLuint32 ChannelCountToSLESChannelMask(int channel_count) {
+ if (channel_count > 2) {
+ LOG(WARNING) << "Guessing channel layout for " << channel_count
+ << " channels; speaker order may be incorrect.";
+ }
+
+ switch (channel_count) {
+ case 1:
+ return SL_SPEAKER_FRONT_LEFT;
+ case 2:
+ return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
+ case 3:
+ return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT |
+ SL_SPEAKER_FRONT_CENTER;
+ case 4:
+ return SL_ANDROID_SPEAKER_QUAD;
+ case 5:
+ return SL_ANDROID_SPEAKER_QUAD | SL_SPEAKER_FRONT_CENTER;
+ case 6:
+ return SL_ANDROID_SPEAKER_5DOT1;
+ case 7:
+ return SL_ANDROID_SPEAKER_5DOT1 | SL_SPEAKER_BACK_CENTER;
+ case 8:
+ return SL_ANDROID_SPEAKER_7DOT1;
+ }
+
+ return 0;
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/android/opensles_util.h b/third_party/chromium/media/audio/android/opensles_util.h
new file mode 100644
index 0000000..2e8d270
--- /dev/null
+++ b/third_party/chromium/media/audio/android/opensles_util.h
@@ -0,0 +1,51 @@
+// Copyright (c) 2012 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 MEDIA_AUDIO_ANDROID_OPENSLES_UTIL_H_
+#define MEDIA_AUDIO_ANDROID_OPENSLES_UTIL_H_
+
+#include <SLES/OpenSLES.h>
+
+#include "base/check.h"
+#include "media/base/media_export.h"
+
+namespace media {
+
+template <typename SLType, typename SLDerefType>
+class ScopedSLObject {
+ public:
+ ScopedSLObject() : obj_(NULL) {}
+
+ ~ScopedSLObject() { Reset(); }
+
+ SLType* Receive() {
+ DCHECK(!obj_);
+ return &obj_;
+ }
+
+ SLDerefType operator->() { return *obj_; }
+
+ SLType Get() const { return obj_; }
+
+ void Reset() {
+ if (obj_) {
+ (*obj_)->Destroy(obj_);
+ obj_ = NULL;
+ }
+ }
+
+ private:
+ SLType obj_;
+};
+
+typedef ScopedSLObject<SLObjectItf, const SLObjectItf_*> ScopedSLObjectItf;
+
+// Guesses the channel mask for a given channel count. Android does not offer a
+// way to configure the layout, so this will be incorrect for less common
+// channel layouts.
+MEDIA_EXPORT SLuint32 ChannelCountToSLESChannelMask(int channel_count);
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_ANDROID_OPENSLES_UTIL_H_
diff --git a/third_party/chromium/media/audio/android/opensles_wrapper.cc b/third_party/chromium/media/audio/android/opensles_wrapper.cc
new file mode 100644
index 0000000..ba7dba7
--- /dev/null
+++ b/third_party/chromium/media/audio/android/opensles_wrapper.cc
@@ -0,0 +1,123 @@
+// Copyright 2013 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.
+
+// The file defines the symbols from OpenSLES that android is using. It then
+// loads the library dynamically on first use.
+
+// The openSLES API is using constant as part of the API. This file will define
+// proxies for those constants and redefine those when the library is first
+// loaded. For this, it need to be able to change their content and so import
+// the headers without const. This is correct because OpenSLES.h is a C API.
+
+// We include stdint.h here as a workaround for an issue caused by the
+// #define const below. The inclusion of OpenSLES headers on newer Android NDK
+// versions causes stdint.h to be included, which in turn includes __config.
+// This causes the declaration of __sanitizer_annotate_contiguous_container to
+// not use const parameters, which causes compile issues when building with
+// asan. Including here forces __config to be included while const is still
+// untouched.
+#include <stdint.h>
+
+#define const
+#include <SLES/OpenSLES.h>
+#include <SLES/OpenSLES_Android.h>
+#undef const
+
+#include <stddef.h>
+
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/native_library.h"
+
+// The constants used in chromium. SLInterfaceID is actually a pointer to
+// SLInterfaceID_. Those symbols are defined as extern symbols in the OpenSLES
+// headers. They will be initialized to their correct values when the library is
+// loaded.
+SLInterfaceID SL_IID_ENGINE = NULL;
+SLInterfaceID SL_IID_ANDROIDSIMPLEBUFFERQUEUE = NULL;
+SLInterfaceID SL_IID_ANDROIDCONFIGURATION = NULL;
+SLInterfaceID SL_IID_RECORD = NULL;
+SLInterfaceID SL_IID_BUFFERQUEUE = NULL;
+SLInterfaceID SL_IID_VOLUME = NULL;
+SLInterfaceID SL_IID_PLAY = NULL;
+
+namespace {
+
+// The name of the library to load.
+const char kOpenSLLibraryName[] = "libOpenSLES.so";
+
+// Loads the OpenSLES library, and initializes all the proxies.
+base::NativeLibrary IntializeLibraryHandle() {
+ base::NativeLibrary handle =
+ base::LoadNativeLibrary(base::FilePath(kOpenSLLibraryName), NULL);
+ if (!handle) {
+ DLOG(ERROR) << "Unable to load " << kOpenSLLibraryName;
+ return NULL;
+ }
+
+ // Setup the proxy for each symbol.
+ // Attach the symbol name to the proxy address.
+ struct SymbolDefinition {
+ const char* name;
+ SLInterfaceID* sl_iid;
+ };
+
+ // The list of defined symbols.
+ const SymbolDefinition kSymbols[] = {
+ {"SL_IID_ENGINE", &SL_IID_ENGINE},
+ {"SL_IID_ANDROIDSIMPLEBUFFERQUEUE", &SL_IID_ANDROIDSIMPLEBUFFERQUEUE},
+ {"SL_IID_ANDROIDCONFIGURATION", &SL_IID_ANDROIDCONFIGURATION},
+ {"SL_IID_RECORD", &SL_IID_RECORD},
+ {"SL_IID_BUFFERQUEUE", &SL_IID_BUFFERQUEUE},
+ {"SL_IID_VOLUME", &SL_IID_VOLUME},
+ {"SL_IID_PLAY", &SL_IID_PLAY}};
+
+ for (size_t i = 0; i < sizeof(kSymbols) / sizeof(kSymbols[0]); ++i) {
+ void* func_ptr =
+ base::GetFunctionPointerFromNativeLibrary(handle, kSymbols[i].name);
+ if (!func_ptr) {
+ DLOG(ERROR) << "Unable to find symbol for " << kSymbols[i].name;
+ return NULL;
+ }
+ memcpy(kSymbols[i].sl_iid, func_ptr, sizeof(SLInterfaceID));
+ }
+ return handle;
+}
+
+// Returns the handler to the shared library. The library itself will be lazily
+// loaded during the first call to this function.
+base::NativeLibrary LibraryHandle() {
+ // The handle is lazily initialized on the first call.
+ static base::NativeLibrary g_handle = IntializeLibraryHandle();
+ return g_handle;
+}
+
+} // namespace
+
+// Redefine slCreateEngine symbol.
+SLresult slCreateEngine(SLObjectItf* engine,
+ SLuint32 num_options,
+ SLEngineOption* engine_options,
+ SLuint32 num_interfaces,
+ SLInterfaceID* interface_ids,
+ SLboolean* interfaces_required) {
+ typedef SLresult (*SlCreateEngineSignature)(SLObjectItf*, SLuint32,
+ SLEngineOption*, SLuint32,
+ SLInterfaceID*, SLboolean*);
+ base::NativeLibrary handle = LibraryHandle();
+ if (!handle)
+ return SL_RESULT_INTERNAL_ERROR;
+
+ static SlCreateEngineSignature g_sl_create_engine_handle =
+ reinterpret_cast<SlCreateEngineSignature>(
+ base::GetFunctionPointerFromNativeLibrary(handle, "slCreateEngine"));
+ if (!g_sl_create_engine_handle) {
+ DLOG(ERROR) << "Unable to find symbol for slCreateEngine";
+ return SL_RESULT_INTERNAL_ERROR;
+ }
+
+ return g_sl_create_engine_handle(engine, num_options, engine_options,
+ num_interfaces, interface_ids,
+ interfaces_required);
+}
diff --git a/third_party/chromium/media/audio/audio_debug_file_writer.cc b/third_party/chromium/media/audio/audio_debug_file_writer.cc
new file mode 100644
index 0000000..8fd566a
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_debug_file_writer.cc
@@ -0,0 +1,306 @@
+// Copyright 2015 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 "media/audio/audio_debug_file_writer.h"
+
+#include <stdint.h>
+#include <array>
+#include <limits>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/sys_byteorder.h"
+#include "media/base/audio_bus.h"
+#include "media/base/audio_sample_types.h"
+
+namespace media {
+
+namespace {
+
+// Windows WAVE format header
+// Byte order: Little-endian
+// Offset Length Content
+// 0 4 "RIFF"
+// 4 4 <file length - 8>
+// 8 4 "WAVE"
+// 12 4 "fmt "
+// 16 4 <length of the fmt data> (=16)
+// 20 2 <WAVE file encoding tag>
+// 22 2 <channels>
+// 24 4 <sample rate>
+// 28 4 <bytes per second> (sample rate * block align)
+// 32 2 <block align> (channels * bits per sample / 8)
+// 34 2 <bits per sample>
+// 36 4 "data"
+// 40 4 <sample data size(n)>
+// 44 (n) <sample data>
+
+// We write 16 bit PCM only.
+static const uint16_t kBytesPerSample = 2;
+
+static const uint32_t kWavHeaderSize = 44;
+static const uint32_t kFmtChunkSize = 16;
+// 4 bytes for ID + 4 bytes for size.
+static const uint32_t kChunkHeaderSize = 8;
+static const uint16_t kWavFormatPcm = 1;
+
+static const char kRiff[] = {'R', 'I', 'F', 'F'};
+static const char kWave[] = {'W', 'A', 'V', 'E'};
+static const char kFmt[] = {'f', 'm', 't', ' '};
+static const char kData[] = {'d', 'a', 't', 'a'};
+
+typedef std::array<char, kWavHeaderSize> WavHeaderBuffer;
+
+class CharBufferWriter {
+ public:
+ CharBufferWriter(char* buf, int max_size)
+ : buf_(buf), max_size_(max_size), size_(0) {}
+
+ void Write(const char* data, int data_size) {
+ CHECK_LE(size_ + data_size, max_size_);
+ memcpy(&buf_[size_], data, data_size);
+ size_ += data_size;
+ }
+
+ void Write(const char (&data)[4]) {
+ Write(static_cast<const char*>(data), 4);
+ }
+
+ void WriteLE16(uint16_t data) {
+ uint16_t val = base::ByteSwapToLE16(data);
+ Write(reinterpret_cast<const char*>(&val), sizeof(val));
+ }
+
+ void WriteLE32(uint32_t data) {
+ uint32_t val = base::ByteSwapToLE32(data);
+ Write(reinterpret_cast<const char*>(&val), sizeof(val));
+ }
+
+ private:
+ char* buf_;
+ const int max_size_;
+ int size_;
+
+ DISALLOW_COPY_AND_ASSIGN(CharBufferWriter);
+};
+
+// Writes Wave header to the specified address, there should be at least
+// kWavHeaderSize bytes allocated for it.
+void WriteWavHeader(WavHeaderBuffer* buf,
+ uint32_t channels,
+ uint32_t sample_rate,
+ uint64_t samples) {
+ // We'll need to add (kWavHeaderSize - kChunkHeaderSize) to payload to
+ // calculate Riff chunk size.
+ static const uint32_t kMaxBytesInPayload =
+ std::numeric_limits<uint32_t>::max() -
+ (kWavHeaderSize - kChunkHeaderSize);
+ const uint64_t bytes_in_payload_64 = samples * kBytesPerSample;
+
+ // In case payload is too large and causes uint32_t overflow, we just specify
+ // the maximum possible value; all the payload above that count will be
+ // interpreted as garbage.
+ const uint32_t bytes_in_payload = bytes_in_payload_64 > kMaxBytesInPayload
+ ? kMaxBytesInPayload
+ : bytes_in_payload_64;
+ LOG_IF(WARNING, bytes_in_payload < bytes_in_payload_64)
+ << "Number of samples is too large and will be clipped by Wave header,"
+ << " all the data above " << kMaxBytesInPayload
+ << " bytes will appear as junk";
+ const uint32_t block_align = channels * kBytesPerSample;
+ const uint32_t byte_rate = channels * sample_rate * kBytesPerSample;
+ const uint32_t riff_chunk_size =
+ bytes_in_payload + kWavHeaderSize - kChunkHeaderSize;
+
+ CharBufferWriter writer(&(*buf)[0], kWavHeaderSize);
+
+ writer.Write(kRiff);
+ writer.WriteLE32(riff_chunk_size);
+ writer.Write(kWave);
+ writer.Write(kFmt);
+ writer.WriteLE32(kFmtChunkSize);
+ writer.WriteLE16(kWavFormatPcm);
+ writer.WriteLE16(channels);
+ writer.WriteLE32(sample_rate);
+ writer.WriteLE32(byte_rate);
+ writer.WriteLE16(block_align);
+ writer.WriteLE16(kBytesPerSample * 8);
+ writer.Write(kData);
+ writer.WriteLE32(bytes_in_payload);
+}
+
+} // namespace
+
+// Manages the debug recording file and writes to it. Can be created on any
+// thread. All the operations must be executed on a thread that has IO
+// permissions.
+class AudioDebugFileWriter::AudioFileWriter {
+ public:
+ static AudioFileWriterUniquePtr Create(
+ base::File file,
+ const AudioParameters& params,
+ scoped_refptr<base::SequencedTaskRunner> task_runner);
+
+ ~AudioFileWriter();
+
+ // Write data from |data| to file.
+ void Write(const AudioBus* data);
+
+ private:
+ explicit AudioFileWriter(const AudioParameters& params);
+
+ // Write wave header to file. Called on the |task_runner_| twice: on
+ // construction
+ // of AudioFileWriter size of the wave data is unknown, so the header is
+ // written with zero sizes; then on destruction it is re-written with the
+ // actual size info accumulated throughout the object lifetime.
+ void WriteHeader();
+
+ void StartRecording(base::File file);
+
+ // The file to write to.
+ base::File file_;
+
+ // Number of written samples.
+ uint64_t samples_;
+
+ // Audio parameters required to build wave header. Number of channels and
+ // sample rate are used.
+ const AudioParameters params_;
+
+ // Intermediate buffer to be written to file. Interleaved 16 bit audio data.
+ std::unique_ptr<int16_t[]> interleaved_data_;
+ int interleaved_data_size_;
+
+ SEQUENCE_CHECKER(sequence_checker_);
+};
+
+// static
+AudioDebugFileWriter::AudioFileWriterUniquePtr
+AudioDebugFileWriter::AudioFileWriter::Create(
+ base::File file,
+ const AudioParameters& params,
+ scoped_refptr<base::SequencedTaskRunner> task_runner) {
+ AudioFileWriterUniquePtr file_writer(new AudioFileWriter(params),
+ base::OnTaskRunnerDeleter(task_runner));
+
+ // base::Unretained is safe, because destructor is called on
+ // |task_runner|.
+ task_runner->PostTask(
+ FROM_HERE,
+ base::BindOnce(&AudioFileWriter::StartRecording,
+ base::Unretained(file_writer.get()), std::move(file)));
+ return file_writer;
+}
+
+AudioDebugFileWriter::AudioFileWriter::AudioFileWriter(
+ const AudioParameters& params)
+ : samples_(0), params_(params), interleaved_data_size_(0) {
+ DETACH_FROM_SEQUENCE(sequence_checker_);
+}
+
+AudioDebugFileWriter::AudioFileWriter::~AudioFileWriter() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ if (file_.IsValid())
+ WriteHeader();
+}
+
+void AudioDebugFileWriter::AudioFileWriter::Write(const AudioBus* data) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK_EQ(params_.channels(), data->channels());
+ if (!file_.IsValid())
+ return;
+
+ // Convert to 16 bit audio and write to file.
+ int data_size = data->frames() * data->channels();
+ if (!interleaved_data_ || interleaved_data_size_ < data_size) {
+ interleaved_data_.reset(new int16_t[data_size]);
+ interleaved_data_size_ = data_size;
+ }
+ samples_ += data_size;
+ data->ToInterleaved<media::SignedInt16SampleTypeTraits>(
+ data->frames(), interleaved_data_.get());
+
+#ifndef ARCH_CPU_LITTLE_ENDIAN
+ static_assert(sizeof(interleaved_data_[0]) == sizeof(uint16_t),
+ "Only 2 bytes per channel is supported.");
+ for (int i = 0; i < data_size; ++i)
+ interleaved_data_[i] = base::ByteSwapToLE16(interleaved_data_[i]);
+#endif
+
+ file_.WriteAtCurrentPos(reinterpret_cast<char*>(interleaved_data_.get()),
+ data_size * sizeof(interleaved_data_[0]));
+}
+
+void AudioDebugFileWriter::AudioFileWriter::WriteHeader() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ if (!file_.IsValid())
+ return;
+ WavHeaderBuffer buf;
+ WriteWavHeader(&buf, params_.channels(), params_.sample_rate(), samples_);
+ file_.Write(0, &buf[0], kWavHeaderSize);
+
+ // Write() does not move the cursor if file is not in APPEND mode; Seek() so
+ // that the header is not overwritten by the following writes.
+ file_.Seek(base::File::FROM_BEGIN, kWavHeaderSize);
+}
+
+void AudioDebugFileWriter::AudioFileWriter::StartRecording(base::File file) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!file_.IsValid());
+
+ file_ = std::move(file);
+ WriteHeader();
+}
+
+AudioDebugFileWriter::AudioDebugFileWriter(const AudioParameters& params)
+ : params_(params),
+ file_writer_(nullptr, base::OnTaskRunnerDeleter(nullptr)) {
+ DETACH_FROM_SEQUENCE(client_sequence_checker_);
+}
+
+AudioDebugFileWriter::~AudioDebugFileWriter() {
+ // |file_writer_| will be deleted on |task_runner_|.
+}
+
+void AudioDebugFileWriter::Start(base::File file) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_);
+ DCHECK(!file_writer_);
+ file_writer_ =
+ AudioFileWriter::Create(std::move(file), params_, file_task_runner_);
+}
+
+void AudioDebugFileWriter::Stop() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_);
+ // |file_writer_| is deleted on FILE thread.
+ file_writer_.reset();
+ DETACH_FROM_SEQUENCE(client_sequence_checker_);
+}
+
+void AudioDebugFileWriter::Write(std::unique_ptr<AudioBus> data) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_);
+ if (!file_writer_)
+ return;
+
+ // base::Unretained for |file_writer_| is safe, see the destructor.
+ file_task_runner_->PostTask(
+ FROM_HERE,
+ // Callback takes ownership of |data|:
+ base::BindOnce(&AudioFileWriter::Write,
+ base::Unretained(file_writer_.get()),
+ base::Owned(data.release())));
+}
+
+bool AudioDebugFileWriter::WillWrite() {
+ // Note that if this is called from any place other than
+ // |client_sequence_checker_| then there is a data race here, but it's fine,
+ // because Write() will check for |file_writer_|. So, we are not very precise
+ // here, but it's fine: we can afford missing some data or scheduling some
+ // no-op writes.
+ return !!file_writer_;
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_debug_file_writer.h b/third_party/chromium/media/audio/audio_debug_file_writer.h
new file mode 100644
index 0000000..bf073c7
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_debug_file_writer.h
@@ -0,0 +1,80 @@
+// Copyright 2015 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 MEDIA_AUDIO_AUDIO_DEBUG_FILE_WRITER_H_
+#define MEDIA_AUDIO_AUDIO_DEBUG_FILE_WRITER_H_
+
+#include <stdint.h>
+
+#include <memory>
+
+#include "base/files/file.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/sequence_checker.h"
+#include "base/sequenced_task_runner.h"
+#include "base/task/post_task.h"
+#include "base/task/thread_pool.h"
+#include "media/base/audio_parameters.h"
+#include "media/base/media_export.h"
+
+namespace media {
+
+class AudioBus;
+
+// Writes audio data to a 16 bit PCM WAVE file used for debugging purposes. All
+// operations are non-blocking.
+// Functions are virtual for the purpose of test mocking.
+class MEDIA_EXPORT AudioDebugFileWriter {
+ public:
+ // Number of channels and sample rate are used from |params|, the other
+ // parameters are ignored. The number of channels in the data passed to
+ // Write() must match |params|.
+ explicit AudioDebugFileWriter(const AudioParameters& params);
+
+ AudioDebugFileWriter(const AudioDebugFileWriter&) = delete;
+ AudioDebugFileWriter& operator=(const AudioDebugFileWriter&) = delete;
+
+ virtual ~AudioDebugFileWriter();
+
+ // Must be called before calling Write() for the first time after creation or
+ // Stop() call. Can be called on any sequence; Write() and Stop() must be
+ // called on the same sequence as Start().
+ virtual void Start(base::File file);
+
+ // Must be called to finish recording. Each call to Start() requires a call to
+ // Stop(). Will be automatically called on destruction.
+ virtual void Stop();
+
+ // Write |data| to file.
+ virtual void Write(std::unique_ptr<AudioBus> data);
+
+ // Returns true if Write() call scheduled at this point will most likely write
+ // data to the file, and false if it most likely will be a no-op. The result
+ // may be ambigulous if Start() or Stop() is executed at the moment. Can be
+ // called from any sequence.
+ virtual bool WillWrite();
+
+ protected:
+ const AudioParameters params_;
+
+ private:
+ class AudioFileWriter;
+
+ using AudioFileWriterUniquePtr =
+ std::unique_ptr<AudioFileWriter, base::OnTaskRunnerDeleter>;
+
+ // The task runner to do file output operations on.
+ const scoped_refptr<base::SequencedTaskRunner> file_task_runner_ =
+ base::ThreadPool::CreateSequencedTaskRunner(
+ {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
+ base::TaskShutdownBehavior::BLOCK_SHUTDOWN});
+
+ AudioFileWriterUniquePtr file_writer_;
+ SEQUENCE_CHECKER(client_sequence_checker_);
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_DEBUG_FILE_WRITER_H_
diff --git a/third_party/chromium/media/audio/audio_debug_file_writer_unittest.cc b/third_party/chromium/media/audio/audio_debug_file_writer_unittest.cc
new file mode 100644
index 0000000..0ad0b6e
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_debug_file_writer_unittest.cc
@@ -0,0 +1,354 @@
+// Copyright 2015 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 <stdint.h>
+
+#include <limits>
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/memory/ptr_util.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/sys_byteorder.h"
+#include "base/test/task_environment.h"
+#include "base/threading/thread.h"
+#include "media/audio/audio_debug_file_writer.h"
+#include "media/base/audio_bus.h"
+#include "media/base/audio_sample_types.h"
+#include "media/base/test_helpers.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+
+namespace {
+
+static const uint16_t kBytesPerSample = sizeof(uint16_t);
+static const uint16_t kPcmEncoding = 1;
+static const size_t kWavHeaderSize = 44;
+
+uint16_t ReadLE2(const char* buf) {
+ return static_cast<uint8_t>(buf[0]) | (static_cast<uint8_t>(buf[1]) << 8);
+}
+
+uint32_t ReadLE4(const char* buf) {
+ return static_cast<uint8_t>(buf[0]) | (static_cast<uint8_t>(buf[1]) << 8) |
+ (static_cast<uint8_t>(buf[2]) << 16) |
+ (static_cast<uint8_t>(buf[3]) << 24);
+}
+
+base::File OpenFile(const base::FilePath& file_path) {
+ return base::File(file_path, base::File::FLAG_OPEN | base::File::FLAG_WRITE);
+}
+
+} // namespace
+
+// <channel layout, sample rate, frames per buffer, number of buffer writes
+typedef std::tuple<ChannelLayout, int, int, int> AudioDebugFileWriterTestData;
+
+class AudioDebugFileWriterTest
+ : public testing::TestWithParam<AudioDebugFileWriterTestData> {
+ public:
+ explicit AudioDebugFileWriterTest(
+ base::test::TaskEnvironment::ThreadPoolExecutionMode execution_mode)
+ : task_environment_(base::test::TaskEnvironment::MainThreadType::DEFAULT,
+ execution_mode),
+ params_(AudioParameters::Format::AUDIO_PCM_LINEAR,
+ std::get<0>(GetParam()),
+ std::get<1>(GetParam()),
+ std::get<2>(GetParam())),
+ writes_(std::get<3>(GetParam())),
+ source_samples_(params_.frames_per_buffer() * params_.channels() *
+ writes_),
+ source_interleaved_(source_samples_ ? new int16_t[source_samples_]
+ : nullptr) {
+ InitSourceInterleaved(source_interleaved_.get(), source_samples_);
+ }
+ AudioDebugFileWriterTest()
+ : AudioDebugFileWriterTest(
+ base::test::TaskEnvironment::ThreadPoolExecutionMode::ASYNC) {}
+
+ protected:
+ virtual ~AudioDebugFileWriterTest() = default;
+
+ static void InitSourceInterleaved(int16_t* source_interleaved,
+ int source_samples) {
+ if (source_samples) {
+ // equal steps to cover int16_t range of values
+ int16_t step = 0xffff / source_samples;
+ int16_t val = std::numeric_limits<int16_t>::min();
+ for (int i = 0; i < source_samples; ++i, val += step)
+ source_interleaved[i] = val;
+ }
+ }
+
+ static void VerifyHeader(const char (&wav_header)[kWavHeaderSize],
+ const AudioParameters& params,
+ int writes,
+ int64_t file_length) {
+ uint32_t block_align = params.channels() * kBytesPerSample;
+ uint32_t data_size =
+ static_cast<uint32_t>(params.frames_per_buffer() * params.channels() *
+ writes * kBytesPerSample);
+ // Offset Length Content
+ // 0 4 "RIFF"
+ EXPECT_EQ(0, strncmp(wav_header, "RIFF", 4));
+ // 4 4 <file length - 8>
+ ASSERT_GT(file_length, 8);
+ EXPECT_EQ(static_cast<uint64_t>(file_length - 8), ReadLE4(wav_header + 4));
+ EXPECT_EQ(static_cast<uint32_t>(data_size + kWavHeaderSize - 8),
+ ReadLE4(wav_header + 4));
+ // 8 4 "WAVE"
+ // 12 4 "fmt "
+ EXPECT_EQ(0, strncmp(wav_header + 8, "WAVEfmt ", 8));
+ // 16 4 <length of the fmt data> (=16)
+ EXPECT_EQ(16U, ReadLE4(wav_header + 16));
+ // 20 2 <WAVE file encoding tag>
+ EXPECT_EQ(kPcmEncoding, ReadLE2(wav_header + 20));
+ // 22 2 <channels>
+ EXPECT_EQ(params.channels(), ReadLE2(wav_header + 22));
+ // 24 4 <sample rate>
+ EXPECT_EQ(static_cast<uint32_t>(params.sample_rate()),
+ ReadLE4(wav_header + 24));
+ // 28 4 <bytes per second> (sample rate * block align)
+ EXPECT_EQ(static_cast<uint32_t>(params.sample_rate()) * block_align,
+ ReadLE4(wav_header + 28));
+ // 32 2 <block align> (channels * bits per sample / 8)
+ EXPECT_EQ(block_align, ReadLE2(wav_header + 32));
+ // 34 2 <bits per sample>
+ EXPECT_EQ(kBytesPerSample * 8, ReadLE2(wav_header + 34));
+ // 36 4 "data"
+ EXPECT_EQ(0, strncmp(wav_header + 36, "data", 4));
+ // 40 4 <sample data size(n)>
+ EXPECT_EQ(data_size, ReadLE4(wav_header + 40));
+ }
+
+ // |result_interleaved| is expected to be little-endian.
+ static void VerifyDataRecording(const int16_t* source_interleaved,
+ const int16_t* result_interleaved,
+ int16_t source_samples) {
+ // Allow mismatch by 1 due to rounding error in int->float->int
+ // calculations.
+ for (int i = 0; i < source_samples; ++i)
+ EXPECT_LE(std::abs(static_cast<int16_t>(
+ base::ByteSwapToLE16(source_interleaved[i])) -
+ result_interleaved[i]),
+ 1)
+ << "i = " << i << " source " << source_interleaved[i] << " result "
+ << result_interleaved[i];
+ }
+
+ void VerifyRecording(const base::FilePath& file_path) {
+ base::File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
+ ASSERT_TRUE(file.IsValid());
+
+ char wav_header[kWavHeaderSize];
+ EXPECT_EQ(file.Read(0, wav_header, kWavHeaderSize),
+ static_cast<int>(kWavHeaderSize));
+ VerifyHeader(wav_header, params_, writes_, file.GetLength());
+
+ if (source_samples_ > 0) {
+ std::unique_ptr<int16_t[]> result_interleaved(
+ new int16_t[source_samples_]);
+ memset(result_interleaved.get(), 0, source_samples_ * kBytesPerSample);
+
+ // Recording is read from file as a byte sequence, so it stored as
+ // little-endian.
+ int read = file.Read(kWavHeaderSize,
+ reinterpret_cast<char*>(result_interleaved.get()),
+ source_samples_ * kBytesPerSample);
+ EXPECT_EQ(static_cast<int>(file.GetLength() - kWavHeaderSize), read);
+
+ VerifyDataRecording(source_interleaved_.get(), result_interleaved.get(),
+ source_samples_);
+ }
+ }
+
+ void DoDebugRecording() {
+ for (int i = 0; i < writes_; ++i) {
+ std::unique_ptr<AudioBus> bus =
+ AudioBus::Create(params_.channels(), params_.frames_per_buffer());
+
+ bus->FromInterleaved<media::SignedInt16SampleTypeTraits>(
+ source_interleaved_.get() +
+ i * params_.channels() * params_.frames_per_buffer(),
+ params_.frames_per_buffer());
+
+ debug_writer_->Write(std::move(bus));
+ }
+ }
+
+ void RecordAndVerifyOnce() {
+ base::FilePath file_path;
+ ASSERT_TRUE(base::CreateTemporaryFile(&file_path));
+ base::File file = OpenFile(file_path);
+ ASSERT_TRUE(file.IsValid());
+
+ debug_writer_->Start(std::move(file));
+
+ DoDebugRecording();
+
+ debug_writer_->Stop();
+
+ task_environment_.RunUntilIdle();
+
+ VerifyRecording(file_path);
+
+ if (::testing::Test::HasFailure()) {
+ LOG(ERROR) << "Test failed; keeping recording(s) at ["
+ << file_path.value().c_str() << "].";
+ } else {
+ ASSERT_TRUE(base::DeleteFile(file_path));
+ }
+ }
+
+ protected:
+ // The test task environment.
+ base::test::TaskEnvironment task_environment_;
+
+ // Writer under test.
+ std::unique_ptr<AudioDebugFileWriter> debug_writer_;
+
+ // AudioBus parameters.
+ AudioParameters params_;
+
+ // Number of times to write AudioBus to the file.
+ int writes_;
+
+ // Number of samples in the source data.
+ int source_samples_;
+
+ // Source data.
+ std::unique_ptr<int16_t[]> source_interleaved_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AudioDebugFileWriterTest);
+};
+
+class AudioDebugFileWriterBehavioralTest : public AudioDebugFileWriterTest {};
+
+class AudioDebugFileWriterSingleThreadTest : public AudioDebugFileWriterTest {
+ public:
+ AudioDebugFileWriterSingleThreadTest()
+ : AudioDebugFileWriterTest(
+ base::test::TaskEnvironment::ThreadPoolExecutionMode::QUEUED) {}
+};
+
+TEST_P(AudioDebugFileWriterTest, WaveRecordingTest) {
+ debug_writer_ = std::make_unique<AudioDebugFileWriter>(params_);
+ RecordAndVerifyOnce();
+}
+
+TEST_P(AudioDebugFileWriterSingleThreadTest,
+ DeletedBeforeRecordingFinishedOnFileThread) {
+ debug_writer_ = std::make_unique<AudioDebugFileWriter>(params_);
+
+ base::FilePath file_path;
+ ASSERT_TRUE(base::CreateTemporaryFile(&file_path));
+ base::File file = OpenFile(file_path);
+ ASSERT_TRUE(file.IsValid());
+
+ debug_writer_->Start(std::move(file));
+
+ DoDebugRecording();
+
+ debug_writer_.reset();
+
+ task_environment_.RunUntilIdle();
+
+ VerifyRecording(file_path);
+
+ if (::testing::Test::HasFailure()) {
+ LOG(ERROR) << "Test failed; keeping recording(s) at ["
+ << file_path.value().c_str() << "].";
+ } else {
+ ASSERT_TRUE(base::DeleteFile(file_path));
+ }
+}
+
+TEST_P(AudioDebugFileWriterBehavioralTest, StartWithInvalidFile) {
+ debug_writer_ = std::make_unique<AudioDebugFileWriter>(params_);
+ base::File file; // Invalid file, recording should not crash
+ debug_writer_->Start(std::move(file));
+ DoDebugRecording();
+}
+
+TEST_P(AudioDebugFileWriterBehavioralTest, StartStopStartStop) {
+ debug_writer_ = std::make_unique<AudioDebugFileWriter>(params_);
+ RecordAndVerifyOnce();
+ RecordAndVerifyOnce();
+}
+
+TEST_P(AudioDebugFileWriterBehavioralTest, DestroyNotStarted) {
+ debug_writer_ = std::make_unique<AudioDebugFileWriter>(params_);
+ debug_writer_.reset();
+}
+
+TEST_P(AudioDebugFileWriterBehavioralTest, DestroyStarted) {
+ debug_writer_ = std::make_unique<AudioDebugFileWriter>(params_);
+ base::FilePath file_path;
+ ASSERT_TRUE(base::CreateTemporaryFile(&file_path));
+ base::File file = OpenFile(file_path);
+ ASSERT_TRUE(file.IsValid());
+ debug_writer_->Start(std::move(file));
+ debug_writer_.reset();
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ AudioDebugFileWriterTest,
+ AudioDebugFileWriterTest,
+ // Using 10ms frames per buffer everywhere.
+ testing::Values(
+ // No writes.
+ std::make_tuple(ChannelLayout::CHANNEL_LAYOUT_MONO,
+ 44100,
+ 44100 / 100,
+ 0),
+ // 1 write of mono.
+ std::make_tuple(ChannelLayout::CHANNEL_LAYOUT_MONO,
+ 44100,
+ 44100 / 100,
+ 1),
+ // 1 second of mono.
+ std::make_tuple(ChannelLayout::CHANNEL_LAYOUT_MONO,
+ 44100,
+ 44100 / 100,
+ 100),
+ // 1 second of mono, higher rate.
+ std::make_tuple(ChannelLayout::CHANNEL_LAYOUT_MONO,
+ 48000,
+ 48000 / 100,
+ 100),
+ // 1 second of stereo.
+ std::make_tuple(ChannelLayout::CHANNEL_LAYOUT_STEREO,
+ 44100,
+ 44100 / 100,
+ 100),
+ // 15 seconds of stereo, higher rate.
+ std::make_tuple(ChannelLayout::CHANNEL_LAYOUT_STEREO,
+ 48000,
+ 48000 / 100,
+ 1500)));
+
+INSTANTIATE_TEST_SUITE_P(AudioDebugFileWriterBehavioralTest,
+ AudioDebugFileWriterBehavioralTest,
+ // Using 10ms frames per buffer everywhere.
+ testing::Values(
+ // No writes.
+ std::make_tuple(ChannelLayout::CHANNEL_LAYOUT_MONO,
+ 44100,
+ 44100 / 100,
+ 100)));
+
+INSTANTIATE_TEST_SUITE_P(AudioDebugFileWriterSingleThreadTest,
+ AudioDebugFileWriterSingleThreadTest,
+ // Using 10ms frames per buffer everywhere.
+ testing::Values(
+ // No writes.
+ std::make_tuple(ChannelLayout::CHANNEL_LAYOUT_MONO,
+ 44100,
+ 44100 / 100,
+ 100)));
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_debug_recording_helper.cc b/third_party/chromium/media/audio/audio_debug_recording_helper.cc
new file mode 100644
index 0000000..7b0341d
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_debug_recording_helper.cc
@@ -0,0 +1,117 @@
+// Copyright 2017 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 "media/audio/audio_debug_recording_helper.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/files/file.h"
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/single_thread_task_runner.h"
+#include "media/audio/audio_debug_file_writer.h"
+
+namespace media {
+
+AudioDebugRecordingHelper::AudioDebugRecordingHelper(
+ const AudioParameters& params,
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+ base::OnceClosure on_destruction_closure)
+ : params_(params),
+ recording_enabled_(0),
+ task_runner_(std::move(task_runner)),
+ on_destruction_closure_(std::move(on_destruction_closure)) {}
+
+AudioDebugRecordingHelper::~AudioDebugRecordingHelper() {
+ if (on_destruction_closure_)
+ std::move(on_destruction_closure_).Run();
+}
+
+void AudioDebugRecordingHelper::EnableDebugRecording(
+ AudioDebugRecordingStreamType stream_type,
+ uint32_t id,
+ CreateWavFileCallback create_file_callback) {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ DCHECK(!debug_writer_);
+
+ debug_writer_ = CreateAudioDebugFileWriter(params_);
+ std::move(create_file_callback)
+ .Run(stream_type, id,
+ base::BindOnce(&AudioDebugRecordingHelper::StartDebugRecordingToFile,
+ weak_factory_.GetWeakPtr()));
+}
+
+void AudioDebugRecordingHelper::StartDebugRecordingToFile(base::File file) {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+
+ if (!file.IsValid()) {
+ PLOG(ERROR) << "Invalid debug recording file, error="
+ << file.error_details();
+ debug_writer_.reset();
+ return;
+ }
+
+ debug_writer_->Start(std::move(file));
+
+ base::subtle::NoBarrier_Store(&recording_enabled_, 1);
+}
+
+void AudioDebugRecordingHelper::DisableDebugRecording() {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+
+ base::subtle::NoBarrier_Store(&recording_enabled_, 0);
+
+ if (debug_writer_) {
+ debug_writer_->Stop();
+ debug_writer_.reset();
+ }
+}
+
+void AudioDebugRecordingHelper::OnData(const AudioBus* source) {
+ // Check if debug recording is enabled to avoid an unecessary copy and thread
+ // jump if not. Recording can be disabled between the atomic Load() here and
+ // DoWrite(), but it's fine with a single unnecessary copy+jump at disable
+ // time. We use an atomic operation for accessing the flag on different
+ // threads. No memory barrier is needed for the same reason; a race is no
+ // problem at enable and disable time. Missing one buffer of data doesn't
+ // matter.
+ base::subtle::Atomic32 recording_enabled =
+ base::subtle::NoBarrier_Load(&recording_enabled_);
+ if (!recording_enabled)
+ return;
+
+ // TODO(tommi): This is costly. AudioBus heap allocs and we create a new one
+ // for every callback. We could instead have a pool of bus objects that get
+ // returned to us somehow.
+ // We should also avoid calling PostTask here since the implementation of the
+ // debug writer will basically do a PostTask straight away anyway. Might
+ // require some modifications to AudioDebugFileWriter though since there are
+ // some threading concerns there and AudioDebugFileWriter's lifetime
+ // guarantees need to be longer than that of associated active audio streams.
+ std::unique_ptr<AudioBus> audio_bus_copy =
+ AudioBus::Create(source->channels(), source->frames());
+ source->CopyTo(audio_bus_copy.get());
+
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&AudioDebugRecordingHelper::DoWrite,
+ weak_factory_.GetWeakPtr(), std::move(audio_bus_copy)));
+}
+
+void AudioDebugRecordingHelper::DoWrite(std::unique_ptr<media::AudioBus> data) {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+
+ if (debug_writer_)
+ debug_writer_->Write(std::move(data));
+}
+
+std::unique_ptr<AudioDebugFileWriter>
+AudioDebugRecordingHelper::CreateAudioDebugFileWriter(
+ const AudioParameters& params) {
+ return std::make_unique<AudioDebugFileWriter>(params);
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_debug_recording_helper.h b/third_party/chromium/media/audio/audio_debug_recording_helper.h
new file mode 100644
index 0000000..86d2ed7
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_debug_recording_helper.h
@@ -0,0 +1,115 @@
+// Copyright 2017 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 MEDIA_AUDIO_AUDIO_DEBUG_RECORDING_HELPER_H_
+#define MEDIA_AUDIO_AUDIO_DEBUG_RECORDING_HELPER_H_
+
+#include <memory>
+
+#include "base/atomicops.h"
+#include "base/callback.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "media/audio/audio_debug_file_writer.h"
+#include "media/base/audio_parameters.h"
+#include "media/base/media_export.h"
+
+namespace base {
+class File;
+class SingleThreadTaskRunner;
+}
+
+namespace media {
+
+class AudioBus;
+
+enum class AudioDebugRecordingStreamType { kInput = 0, kOutput = 1 };
+
+// Interface for feeding data to a recorder.
+class AudioDebugRecorder {
+ public:
+ virtual ~AudioDebugRecorder() {}
+
+ // If debug recording is enabled, copies audio data and makes sure it's
+ // written on the right thread. Otherwise ignores the data. Can be called on
+ // any thread.
+ virtual void OnData(const AudioBus* source) = 0;
+};
+
+// A helper class for those who want to use AudioDebugFileWriter. It handles
+// copying AudioBus data, thread jump (OnData() can be called on any
+// thread), and creating and deleting the AudioDebugFileWriter at enable and
+// disable. All functions except OnData() must be called on the thread
+// |task_runner| belongs to.
+// TODO(grunell): When input debug recording is moved to AudioManager, it should
+// be possible to merge this class into AudioDebugFileWriter. One thread jump
+// could be skipped then. Currently we have
+// soundcard thread -> control thread -> file thread,
+// and with the merge we should be able to do
+// soundcard thread -> file thread.
+class MEDIA_EXPORT AudioDebugRecordingHelper : public AudioDebugRecorder {
+ public:
+ using CreateWavFileCallback = base::OnceCallback<void(
+ AudioDebugRecordingStreamType stream_type,
+ uint32_t id,
+ base::OnceCallback<void(base::File)> reply_callback)>;
+
+ AudioDebugRecordingHelper(
+ const AudioParameters& params,
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+ base::OnceClosure on_destruction_closure);
+
+ AudioDebugRecordingHelper(const AudioDebugRecordingHelper&) = delete;
+ AudioDebugRecordingHelper& operator=(const AudioDebugRecordingHelper&) =
+ delete;
+
+ ~AudioDebugRecordingHelper() override;
+
+ // Enable debug recording. Creates |debug_writer_| and runs
+ // |create_file_callback| to create debug recording file.
+ virtual void EnableDebugRecording(AudioDebugRecordingStreamType stream_type,
+ uint32_t id,
+ CreateWavFileCallback create_file_callback);
+
+ // Disable debug recording. Destroys |debug_writer_|.
+ virtual void DisableDebugRecording();
+
+ // AudioDebugRecorder implementation. Can be called on any thread.
+ void OnData(const AudioBus* source) override;
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(AudioDebugRecordingHelperTest, EnableDisable);
+ FRIEND_TEST_ALL_PREFIXES(AudioDebugRecordingHelperTest, OnData);
+
+ // Writes debug data to |debug_writer_|.
+ void DoWrite(std::unique_ptr<media::AudioBus> data);
+
+ // Creates an AudioDebugFileWriter. Overridden by test.
+ virtual std::unique_ptr<AudioDebugFileWriter> CreateAudioDebugFileWriter(
+ const AudioParameters& params);
+
+ // Passed to |create_file_callback| in EnableDebugRecording, to be called
+ // after debug recording file was created.
+ void StartDebugRecordingToFile(base::File file);
+
+ const AudioParameters params_;
+ std::unique_ptr<AudioDebugFileWriter> debug_writer_;
+
+ // Used as a flag to indicate if recording is enabled, accessed on different
+ // threads.
+ base::subtle::Atomic32 recording_enabled_;
+
+ // The task runner for accessing |debug_writer_|.
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+
+ // Runs in destructor if set.
+ base::OnceClosure on_destruction_closure_;
+
+ base::WeakPtrFactory<AudioDebugRecordingHelper> weak_factory_{this};
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_DEBUG_RECORDING_HELPER_H_
diff --git a/third_party/chromium/media/audio/audio_debug_recording_helper_unittest.cc b/third_party/chromium/media/audio/audio_debug_recording_helper_unittest.cc
new file mode 100644
index 0000000..99f67b3
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_debug_recording_helper_unittest.cc
@@ -0,0 +1,280 @@
+// Copyright 2017 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 "media/audio/audio_debug_recording_helper.h"
+
+#include <limits>
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/ptr_util.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/test/task_environment.h"
+#include "media/base/audio_bus.h"
+#include "media/base/audio_sample_types.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::Return;
+
+namespace media {
+
+namespace {
+
+const base::FilePath::CharType kFileName[] =
+ FILE_PATH_LITERAL("debug_recording.output.1.wav");
+
+} // namespace
+
+// Mock class for the audio file writer that the helper wraps.
+class MockAudioDebugFileWriter : public AudioDebugFileWriter {
+ public:
+ explicit MockAudioDebugFileWriter(const AudioParameters& params)
+ : AudioDebugFileWriter(params), reference_data_(nullptr) {}
+
+ MockAudioDebugFileWriter(const MockAudioDebugFileWriter&) = delete;
+ MockAudioDebugFileWriter& operator=(const MockAudioDebugFileWriter&) = delete;
+
+ ~MockAudioDebugFileWriter() override = default;
+
+ MOCK_METHOD1(DoStart, void(bool));
+ void Start(base::File file) override { DoStart(file.IsValid()); }
+ MOCK_METHOD0(Stop, void());
+
+ // Functions with move-only types as arguments can't be mocked directly, so
+ // we pass on to DoWrite(). Also, we can verify the data this way.
+ MOCK_METHOD1(DoWrite, void(AudioBus*));
+ void Write(std::unique_ptr<AudioBus> data) override {
+ CHECK(reference_data_);
+ EXPECT_EQ(reference_data_->channels(), data->channels());
+ EXPECT_EQ(reference_data_->frames(), data->frames());
+ for (int i = 0; i < data->channels(); ++i) {
+ float* data_ptr = data->channel(i);
+ float* ref_data_ptr = reference_data_->channel(i);
+ for (int j = 0; j < data->frames(); ++j, ++data_ptr, ++ref_data_ptr)
+ EXPECT_EQ(*ref_data_ptr, *data_ptr);
+ }
+ DoWrite(data.get());
+ }
+
+ MOCK_METHOD0(WillWrite, bool());
+
+ // Set reference data to compare against. Must be called before Write() is
+ // called.
+ void SetReferenceData(AudioBus* reference_data) {
+ EXPECT_EQ(params_.channels(), reference_data->channels());
+ EXPECT_EQ(params_.frames_per_buffer(), reference_data->frames());
+ reference_data_ = reference_data;
+ }
+
+ private:
+ AudioBus* reference_data_;
+};
+
+// Sub-class of the helper that overrides the CreateAudioDebugFileWriter
+// function to create the above mock instead.
+class AudioDebugRecordingHelperUnderTest : public AudioDebugRecordingHelper {
+ public:
+ AudioDebugRecordingHelperUnderTest(
+ const AudioParameters& params,
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+ base::OnceClosure on_destruction_closure)
+ : AudioDebugRecordingHelper(params,
+ std::move(task_runner),
+ std::move(on_destruction_closure)) {}
+
+ AudioDebugRecordingHelperUnderTest(
+ const AudioDebugRecordingHelperUnderTest&) = delete;
+ AudioDebugRecordingHelperUnderTest& operator=(
+ const AudioDebugRecordingHelperUnderTest&) = delete;
+
+ ~AudioDebugRecordingHelperUnderTest() override = default;
+
+ private:
+ // Creates the mock writer. After the mock writer is returned, we always
+ // expect Start() to be called on it by the helper.
+ std::unique_ptr<AudioDebugFileWriter> CreateAudioDebugFileWriter(
+ const AudioParameters& params) override {
+ MockAudioDebugFileWriter* writer = new MockAudioDebugFileWriter(params);
+ EXPECT_CALL(*writer, DoStart(true));
+ return base::WrapUnique<AudioDebugFileWriter>(writer);
+ }
+};
+
+class AudioDebugRecordingHelperTest : public ::testing::Test {
+ public:
+ AudioDebugRecordingHelperTest() {}
+
+ AudioDebugRecordingHelperTest(const AudioDebugRecordingHelperTest&) = delete;
+ AudioDebugRecordingHelperTest& operator=(
+ const AudioDebugRecordingHelperTest&) = delete;
+
+ ~AudioDebugRecordingHelperTest() override = default;
+
+ // Helper function that creates a recording helper.
+ std::unique_ptr<AudioDebugRecordingHelper> CreateRecordingHelper(
+ const AudioParameters& params,
+ base::OnceClosure on_destruction_closure) {
+ return std::make_unique<AudioDebugRecordingHelperUnderTest>(
+ params, task_environment_.GetMainThreadTaskRunner(),
+ std::move(on_destruction_closure));
+ }
+
+ MOCK_METHOD0(OnAudioDebugRecordingHelperDestruction, void());
+
+ // Bound and passed to AudioDebugRecordingHelper::EnableDebugRecording as
+ // AudioDebugRecordingHelper::CreateWavFileCallback.
+ void CreateWavFile(AudioDebugRecordingStreamType stream_type,
+ uint32_t id,
+ base::OnceCallback<void(base::File)> reply_callback) {
+ // Check that AudioDebugRecordingHelper::EnableDebugRecording calls
+ // CreateWavFileCallback with expected stream type and id.
+ EXPECT_EQ(stream_type_, stream_type);
+ EXPECT_EQ(id_, id);
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ base::FilePath path(temp_dir.GetPath().Append(base::FilePath(kFileName)));
+ base::File debug_file(
+ path, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
+ // Run |reply_callback| with a valid file for expected
+ // MockAudioDebugFileWriter::Start mocked call to happen.
+ std::move(reply_callback).Run(std::move(debug_file));
+ // File can be removed right away because MockAudioDebugFileWriter::Start is
+ // called synchronously.
+ ASSERT_TRUE(base::DeleteFile(path));
+ }
+
+ protected:
+ const AudioDebugRecordingStreamType stream_type_ =
+ AudioDebugRecordingStreamType::kInput;
+ const uint32_t id_ = 1;
+
+ // The test task environment.
+ base::test::TaskEnvironment task_environment_;
+};
+
+// Creates a helper with an on destruction closure, and verifies that it's run.
+TEST_F(AudioDebugRecordingHelperTest, TestDestructionClosure) {
+ const AudioParameters params;
+ std::unique_ptr<AudioDebugRecordingHelper> recording_helper =
+ CreateRecordingHelper(
+ params, base::BindOnce(&AudioDebugRecordingHelperTest::
+ OnAudioDebugRecordingHelperDestruction,
+ base::Unretained(this)));
+
+ EXPECT_CALL(*this, OnAudioDebugRecordingHelperDestruction());
+}
+
+// Verifies that disable can be called without being enabled.
+TEST_F(AudioDebugRecordingHelperTest, OnlyDisable) {
+ const AudioParameters params;
+ std::unique_ptr<AudioDebugRecordingHelper> recording_helper =
+ CreateRecordingHelper(params, base::OnceClosure());
+
+ recording_helper->DisableDebugRecording();
+}
+
+TEST_F(AudioDebugRecordingHelperTest, EnableDisable) {
+ const AudioParameters params;
+ std::unique_ptr<AudioDebugRecordingHelper> recording_helper =
+ CreateRecordingHelper(params, base::OnceClosure());
+
+ recording_helper->EnableDebugRecording(
+ stream_type_, id_,
+ base::BindOnce(&AudioDebugRecordingHelperTest::CreateWavFile,
+ base::Unretained(this)));
+ EXPECT_CALL(*static_cast<MockAudioDebugFileWriter*>(
+ recording_helper->debug_writer_.get()),
+ Stop());
+ recording_helper->DisableDebugRecording();
+
+ recording_helper->EnableDebugRecording(
+ stream_type_, id_,
+ base::BindOnce(&AudioDebugRecordingHelperTest::CreateWavFile,
+ base::Unretained(this)));
+ EXPECT_CALL(*static_cast<MockAudioDebugFileWriter*>(
+ recording_helper->debug_writer_.get()),
+ Stop());
+ recording_helper->DisableDebugRecording();
+}
+
+TEST_F(AudioDebugRecordingHelperTest, OnData) {
+ // Only channel layout and frames per buffer is used in the file writer and
+ // AudioBus, the other parameters are ignored.
+ const int number_of_frames = 100;
+ const AudioParameters params(AudioParameters::AUDIO_PCM_LINEAR,
+ ChannelLayout::CHANNEL_LAYOUT_STEREO, 0,
+ number_of_frames);
+
+ // Setup some data.
+ const int number_of_samples = number_of_frames * params.channels();
+ const float step = std::numeric_limits<int16_t>::max() / number_of_frames;
+ std::unique_ptr<float[]> source_data(new float[number_of_samples]);
+ for (float i = 0; i < number_of_samples; ++i)
+ source_data[i] = i * step;
+ std::unique_ptr<AudioBus> audio_bus = AudioBus::Create(params);
+ audio_bus->FromInterleaved<Float32SampleTypeTraits>(source_data.get(),
+ number_of_frames);
+
+ std::unique_ptr<AudioDebugRecordingHelper> recording_helper =
+ CreateRecordingHelper(params, base::OnceClosure());
+
+ // Should not do anything.
+ recording_helper->OnData(audio_bus.get());
+
+ recording_helper->EnableDebugRecording(
+ stream_type_, id_,
+ base::BindOnce(&AudioDebugRecordingHelperTest::CreateWavFile,
+ base::Unretained(this)));
+ MockAudioDebugFileWriter* mock_audio_file_writer =
+ static_cast<MockAudioDebugFileWriter*>(
+ recording_helper->debug_writer_.get());
+ mock_audio_file_writer->SetReferenceData(audio_bus.get());
+
+ EXPECT_CALL(*mock_audio_file_writer, DoWrite(_));
+ recording_helper->OnData(audio_bus.get());
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_CALL(*mock_audio_file_writer, Stop());
+ recording_helper->DisableDebugRecording();
+
+ // Make sure we clear the loop before enabling again.
+ base::RunLoop().RunUntilIdle();
+
+ // Enable again, this time with two OnData() calls, one OnData() call without
+ // running the message loop until after disabling, and one call after
+ // disabling.
+ recording_helper->EnableDebugRecording(
+ stream_type_, id_,
+ base::BindOnce(&AudioDebugRecordingHelperTest::CreateWavFile,
+ base::Unretained(this)));
+ mock_audio_file_writer = static_cast<MockAudioDebugFileWriter*>(
+ recording_helper->debug_writer_.get());
+ mock_audio_file_writer->SetReferenceData(audio_bus.get());
+
+ EXPECT_CALL(*mock_audio_file_writer, DoWrite(_)).Times(2);
+ recording_helper->OnData(audio_bus.get());
+ recording_helper->OnData(audio_bus.get());
+ base::RunLoop().RunUntilIdle();
+
+ // This call should not yield a DoWrite() call on the mock, since the message
+ // loop isn't run until after disabling. WillWrite() is expected since
+ // recording is enabled.
+ recording_helper->OnData(audio_bus.get());
+
+ EXPECT_CALL(*mock_audio_file_writer, Stop());
+ recording_helper->DisableDebugRecording();
+
+ // This call should not yield a DoWrite() call on the mock either.
+ recording_helper->OnData(audio_bus.get());
+ base::RunLoop().RunUntilIdle();
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_debug_recording_manager.cc b/third_party/chromium/media/audio/audio_debug_recording_manager.cc
new file mode 100644
index 0000000..1a668a7
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_debug_recording_manager.cc
@@ -0,0 +1,101 @@
+// Copyright 2017 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 "media/audio/audio_debug_recording_manager.h"
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/memory/ptr_util.h"
+#include "base/single_thread_task_runner.h"
+#include "base/task_runner_util.h"
+
+namespace media {
+
+namespace {
+// Running id recording sources.
+uint32_t g_next_stream_id = 1;
+}
+
+AudioDebugRecordingManager::AudioDebugRecordingManager(
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner)
+ : task_runner_(std::move(task_runner)) {}
+
+AudioDebugRecordingManager::~AudioDebugRecordingManager() = default;
+
+void AudioDebugRecordingManager::EnableDebugRecording(
+ CreateWavFileCallback create_file_callback) {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ DCHECK(!create_file_callback.is_null());
+ create_file_callback_ = std::move(create_file_callback);
+
+ for (const auto& it : debug_recording_helpers_) {
+ uint32_t id = it.first;
+ AudioDebugRecordingHelper* recording_helper = it.second.first;
+ AudioDebugRecordingStreamType stream_type = it.second.second;
+ recording_helper->EnableDebugRecording(stream_type, id,
+ create_file_callback_);
+ }
+}
+
+void AudioDebugRecordingManager::DisableDebugRecording() {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ DCHECK(!create_file_callback_.is_null());
+ for (const auto& it : debug_recording_helpers_) {
+ AudioDebugRecordingHelper* recording_helper = it.second.first;
+ recording_helper->DisableDebugRecording();
+ }
+ create_file_callback_.Reset();
+}
+
+std::unique_ptr<AudioDebugRecorder>
+AudioDebugRecordingManager::RegisterDebugRecordingSource(
+ AudioDebugRecordingStreamType stream_type,
+ const AudioParameters& params) {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+
+ const uint32_t id = g_next_stream_id++;
+
+ // Normally, the manager will outlive the one who registers and owns the
+ // returned recorder. But to not require this we use a weak pointer.
+ std::unique_ptr<AudioDebugRecordingHelper> recording_helper =
+ CreateAudioDebugRecordingHelper(
+ params, task_runner_,
+ base::BindOnce(
+ &AudioDebugRecordingManager::UnregisterDebugRecordingSource,
+ weak_factory_.GetWeakPtr(), id));
+
+ if (IsDebugRecordingEnabled()) {
+ recording_helper->EnableDebugRecording(stream_type, id,
+ create_file_callback_);
+ }
+
+ debug_recording_helpers_[id] =
+ std::make_pair(recording_helper.get(), stream_type);
+
+ return base::WrapUnique<AudioDebugRecorder>(recording_helper.release());
+}
+
+void AudioDebugRecordingManager::UnregisterDebugRecordingSource(uint32_t id) {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ auto it = debug_recording_helpers_.find(id);
+ DCHECK(it != debug_recording_helpers_.end());
+ debug_recording_helpers_.erase(id);
+}
+
+std::unique_ptr<AudioDebugRecordingHelper>
+AudioDebugRecordingManager::CreateAudioDebugRecordingHelper(
+ const AudioParameters& params,
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+ base::OnceClosure on_destruction_closure) {
+ return std::make_unique<AudioDebugRecordingHelper>(
+ params, task_runner, std::move(on_destruction_closure));
+}
+
+bool AudioDebugRecordingManager::IsDebugRecordingEnabled() {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ return !create_file_callback_.is_null();
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_debug_recording_manager.h b/third_party/chromium/media/audio/audio_debug_recording_manager.h
new file mode 100644
index 0000000..5ea162d
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_debug_recording_manager.h
@@ -0,0 +1,123 @@
+// Copyright 2017 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 MEDIA_AUDIO_AUDIO_DEBUG_RECORDING_MANAGER_H_
+#define MEDIA_AUDIO_AUDIO_DEBUG_RECORDING_MANAGER_H_
+
+#include <map>
+#include <memory>
+#include <utility>
+
+#include "base/callback.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "media/audio/audio_debug_recording_helper.h"
+#include "media/base/audio_parameters.h"
+#include "media/base/media_export.h"
+
+namespace base {
+class SingleThreadTaskRunner;
+}
+
+namespace media {
+
+// A manager for audio debug recording that handles registration of data
+// sources and hands them a recorder (AudioDebugRecordingHelper) to feed data
+// to. The recorder will unregister with the manager automatically when deleted.
+// When debug recording is enabled, it is enabled on all recorders and
+// constructs a unique file name for each recorder by using a running ID.
+// A somewhat simplified diagram of the the debug recording infrastructure,
+// interfaces omitted:
+//
+// AudioDebugFileWriter
+// ^
+// | owns
+// owns | owns
+// OnMoreDataConverter ----> AudioDebugRecordingHelper <---------
+// ^ ^ |
+// | owns several | raw pointer to several |
+// | AudioDebugRecordingManager |
+// AudioOutputResampler ^ |
+// ^ | AudioInputStreamDataInterceptor
+// | | ^
+// | owns several | owns owns several |
+// ------------------ AudioManagerBase ----------------
+//
+// AudioDebugRecordingManager is created when
+// AudioManager::InitializeDebugRecording() is called. That is done in
+// AudioManager::Create() in WebRTC enabled builds, but not in non WebRTC
+// enabled builds. If AudioDebugRecordingManager is not created, neither is
+// AudioDebugRecordingHelper or AudioDebugFileWriter. In this case the pointers
+// to AudioDebugRecordingManager and AudioDebugRecordingHelper are null.
+
+class MEDIA_EXPORT AudioDebugRecordingManager {
+ public:
+ using CreateWavFileCallback = base::RepeatingCallback<void(
+ AudioDebugRecordingStreamType stream_type,
+ uint32_t id,
+ base::OnceCallback<void(base::File)> reply_callback)>;
+
+ AudioDebugRecordingManager(
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner);
+
+ AudioDebugRecordingManager(const AudioDebugRecordingManager&) = delete;
+ AudioDebugRecordingManager& operator=(const AudioDebugRecordingManager&) =
+ delete;
+
+ virtual ~AudioDebugRecordingManager();
+
+ // Enables and disables debug recording.
+ virtual void EnableDebugRecording(CreateWavFileCallback create_file_callback);
+ virtual void DisableDebugRecording();
+
+ // Registers a source and returns a wrapped recorder. |stream_type| is added
+ // to the base filename, along with a unique running ID.
+ std::unique_ptr<AudioDebugRecorder> RegisterDebugRecordingSource(
+ AudioDebugRecordingStreamType stream_type,
+ const AudioParameters& params);
+
+ protected:
+ // Creates a AudioDebugRecordingHelper. Overridden by test.
+ virtual std::unique_ptr<AudioDebugRecordingHelper>
+ CreateAudioDebugRecordingHelper(
+ const AudioParameters& params,
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+ base::OnceClosure on_destruction_closure);
+
+ // The task runner this class lives on. Also handed to
+ // AudioDebugRecordingHelpers.
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(AudioDebugRecordingManagerTest,
+ RegisterAutomaticUnregisterAtDelete);
+ FRIEND_TEST_ALL_PREFIXES(AudioDebugRecordingManagerTest,
+ RegisterEnableDisable);
+ FRIEND_TEST_ALL_PREFIXES(AudioDebugRecordingManagerTest,
+ EnableRegisterDisable);
+
+ // Map type from source id to recorder and stream type (input/output).
+ using DebugRecordingHelperMap = std::map<
+ uint32_t,
+ std::pair<AudioDebugRecordingHelper*, AudioDebugRecordingStreamType>>;
+
+ // Unregisters a source.
+ void UnregisterDebugRecordingSource(uint32_t id);
+
+ bool IsDebugRecordingEnabled();
+
+ // Recorders, one per source.
+ DebugRecordingHelperMap debug_recording_helpers_;
+
+ // Callback for creating debug recording files. When this is not null, debug
+ // recording is enabled.
+ CreateWavFileCallback create_file_callback_;
+
+ base::WeakPtrFactory<AudioDebugRecordingManager> weak_factory_{this};
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_DEBUG_RECORDING_MANAGER_H_
diff --git a/third_party/chromium/media/audio/audio_debug_recording_manager_unittest.cc b/third_party/chromium/media/audio/audio_debug_recording_manager_unittest.cc
new file mode 100644
index 0000000..a9f903e
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_debug_recording_manager_unittest.cc
@@ -0,0 +1,232 @@
+// Copyright 2017 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 "media/audio/audio_debug_recording_manager.h"
+
+#include <memory>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/single_thread_task_runner.h"
+#include "base/test/task_environment.h"
+#include "media/audio/audio_debug_recording_helper.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+
+namespace media {
+
+namespace {
+
+// The stream type expected to be added to file name.
+const AudioDebugRecordingStreamType kStreamType(
+ AudioDebugRecordingStreamType::kOutput);
+
+// Used to be able to set call expectations in the MockAudioDebugRecordingHelper
+// ctor. See also comment on the test EnableRegisterDisable.
+bool g_expect_enable_after_create_helper = false;
+
+// A helper struct to be able to set and unset
+// |g_expect_enable_after_create_helper| scoped.
+struct ScopedExpectEnableAfterCreateHelper {
+ ScopedExpectEnableAfterCreateHelper() {
+ CHECK(!g_expect_enable_after_create_helper);
+ g_expect_enable_after_create_helper = true;
+ }
+ ~ScopedExpectEnableAfterCreateHelper() {
+ CHECK(g_expect_enable_after_create_helper);
+ g_expect_enable_after_create_helper = false;
+ }
+};
+
+// Function bound and passed to AudioDebugRecordingManager::EnableDebugRecording
+// as AudioDebugRecordingManager::CreateWavFileCallback.
+void CreateWavFile(AudioDebugRecordingStreamType stream_type,
+ uint32_t id,
+ base::OnceCallback<void(base::File)>) {}
+
+} // namespace
+
+// Mock class to verify enable and disable calls.
+class MockAudioDebugRecordingHelper : public AudioDebugRecordingHelper {
+ public:
+ MockAudioDebugRecordingHelper(
+ const AudioParameters& params,
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+ base::OnceClosure on_destruction_closure)
+ : AudioDebugRecordingHelper(params,
+ std::move(task_runner),
+ base::OnceClosure()),
+ on_destruction_closure_in_mock_(std::move(on_destruction_closure)) {
+ if (g_expect_enable_after_create_helper)
+ EXPECT_CALL(*this, DoEnableDebugRecording(_, _));
+ }
+
+ MockAudioDebugRecordingHelper(const MockAudioDebugRecordingHelper&) = delete;
+ MockAudioDebugRecordingHelper& operator=(
+ const MockAudioDebugRecordingHelper&) = delete;
+
+ ~MockAudioDebugRecordingHelper() override {
+ if (on_destruction_closure_in_mock_)
+ std::move(on_destruction_closure_in_mock_).Run();
+ }
+
+ MOCK_METHOD2(DoEnableDebugRecording,
+ void(AudioDebugRecordingStreamType, uint32_t));
+ void EnableDebugRecording(AudioDebugRecordingStreamType stream_type,
+ uint32_t id,
+ AudioDebugRecordingHelper::CreateWavFileCallback
+ create_file_callback) override {
+ DoEnableDebugRecording(stream_type, id);
+ }
+
+ MOCK_METHOD0(DisableDebugRecording, void());
+
+ private:
+ // We let the mock run the destruction closure to not rely on the real
+ // implementation.
+ base::OnceClosure on_destruction_closure_in_mock_;
+};
+
+// Sub-class of the manager that overrides the CreateAudioDebugRecordingHelper
+// function to create the above mock instead.
+class AudioDebugRecordingManagerUnderTest : public AudioDebugRecordingManager {
+ public:
+ AudioDebugRecordingManagerUnderTest(
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner)
+ : AudioDebugRecordingManager(std::move(task_runner)) {}
+
+ AudioDebugRecordingManagerUnderTest(
+ const AudioDebugRecordingManagerUnderTest&) = delete;
+ AudioDebugRecordingManagerUnderTest& operator=(
+ const AudioDebugRecordingManagerUnderTest&) = delete;
+
+ ~AudioDebugRecordingManagerUnderTest() override = default;
+
+ private:
+ std::unique_ptr<AudioDebugRecordingHelper> CreateAudioDebugRecordingHelper(
+ const AudioParameters& params,
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+ base::OnceClosure on_destruction_closure) override {
+ return std::make_unique<MockAudioDebugRecordingHelper>(
+ params, std::move(task_runner),
+ std::move(on_destruction_closure));
+ }
+};
+
+// The test fixture.
+class AudioDebugRecordingManagerTest : public ::testing::Test {
+ public:
+ AudioDebugRecordingManagerTest()
+ : manager_(task_environment_.GetMainThreadTaskRunner()) {}
+
+ AudioDebugRecordingManagerTest(const AudioDebugRecordingManagerTest&) =
+ delete;
+ AudioDebugRecordingManagerTest& operator=(
+ const AudioDebugRecordingManagerTest&) = delete;
+
+ ~AudioDebugRecordingManagerTest() override = default;
+
+ // Registers a source and increases counter for the expected next source id.
+ std::unique_ptr<AudioDebugRecorder> RegisterDebugRecordingSource(
+ const AudioParameters& params) {
+ ++expected_next_source_id_;
+ return manager_.RegisterDebugRecordingSource(kStreamType, params);
+ }
+
+ protected:
+ // The test task environment.
+ base::test::TaskEnvironment task_environment_;
+
+ AudioDebugRecordingManagerUnderTest manager_;
+
+ // The expected next source id the manager will assign. It's static since the
+ // manager uses a global running id, thus doesn't restart at each
+ // instantiation.
+ static uint32_t expected_next_source_id_;
+};
+
+uint32_t AudioDebugRecordingManagerTest::expected_next_source_id_ = 1;
+
+// Shouldn't do anything but store the CreateWavFileCallback, i.e. no calls to
+// recorders.
+TEST_F(AudioDebugRecordingManagerTest, EnableDisable) {
+ manager_.EnableDebugRecording(base::BindRepeating(&CreateWavFile));
+ manager_.DisableDebugRecording();
+}
+
+// Tests registration and automatic unregistration on destruction of a recorder.
+// The unregistration relies on that the MockAudioDebugRecordingHelper runs the
+// |on_destruction_closure| given to it.
+TEST_F(AudioDebugRecordingManagerTest, RegisterAutomaticUnregisterAtDelete) {
+ const AudioParameters params;
+ std::vector<std::unique_ptr<AudioDebugRecorder>> recorders;
+ recorders.push_back(RegisterDebugRecordingSource(params));
+ recorders.push_back(RegisterDebugRecordingSource(params));
+ recorders.push_back(RegisterDebugRecordingSource(params));
+ EXPECT_EQ(3ul, recorders.size());
+ EXPECT_EQ(recorders.size(), manager_.debug_recording_helpers_.size());
+
+ while (!recorders.empty()) {
+ recorders.pop_back();
+ EXPECT_EQ(recorders.size(), manager_.debug_recording_helpers_.size());
+ }
+ EXPECT_EQ(0ul, recorders.size());
+}
+
+TEST_F(AudioDebugRecordingManagerTest, RegisterEnableDisable) {
+ // Store away the extected id for the next source to use after registering all
+ // sources.
+ uint32_t expected_id = expected_next_source_id_;
+
+ const AudioParameters params;
+ std::vector<std::unique_ptr<AudioDebugRecorder>> recorders;
+ recorders.push_back(RegisterDebugRecordingSource(params));
+ recorders.push_back(RegisterDebugRecordingSource(params));
+ recorders.push_back(RegisterDebugRecordingSource(params));
+ EXPECT_EQ(3ul, recorders.size());
+ EXPECT_EQ(recorders.size(), manager_.debug_recording_helpers_.size());
+
+ for (const auto& recorder : recorders) {
+ MockAudioDebugRecordingHelper* mock_recording_helper =
+ static_cast<MockAudioDebugRecordingHelper*>(recorder.get());
+ EXPECT_CALL(*mock_recording_helper,
+ DoEnableDebugRecording(kStreamType, expected_id++));
+ EXPECT_CALL(*mock_recording_helper, DisableDebugRecording());
+ }
+
+ manager_.EnableDebugRecording(base::BindRepeating(&CreateWavFile));
+ manager_.DisableDebugRecording();
+}
+
+// Test enabling first, then registering. This should call enable on the
+// recoders, but we can't set expectation for that since the mock object is
+// created and called enable upon in RegisterDebugRecordingSource(), then
+// returned. Instead expectation is set in the ctor of the mock by setting
+// |g_expect_enable_after_create_helper| to true here (by using the scoped
+// variable).
+TEST_F(AudioDebugRecordingManagerTest, EnableRegisterDisable) {
+ ScopedExpectEnableAfterCreateHelper scoped_enable_after_create_helper;
+
+ manager_.EnableDebugRecording(base::BindRepeating(&CreateWavFile));
+
+ const AudioParameters params;
+ std::vector<std::unique_ptr<AudioDebugRecorder>> recorders;
+ recorders.push_back(RegisterDebugRecordingSource(params));
+ recorders.push_back(RegisterDebugRecordingSource(params));
+ recorders.push_back(RegisterDebugRecordingSource(params));
+ EXPECT_EQ(3ul, recorders.size());
+ EXPECT_EQ(recorders.size(), manager_.debug_recording_helpers_.size());
+
+ for (const auto& recorder : recorders) {
+ MockAudioDebugRecordingHelper* mock_recording_helper =
+ static_cast<MockAudioDebugRecordingHelper*>(recorder.get());
+ EXPECT_CALL(*mock_recording_helper, DisableDebugRecording());
+ }
+
+ manager_.DisableDebugRecording();
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_debug_recording_session.h b/third_party/chromium/media/audio/audio_debug_recording_session.h
new file mode 100644
index 0000000..b6b582a
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_debug_recording_session.h
@@ -0,0 +1,29 @@
+// Copyright 2018 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 MEDIA_AUDIO_AUDIO_DEBUG_RECORDING_SESSION_H_
+#define MEDIA_AUDIO_AUDIO_DEBUG_RECORDING_SESSION_H_
+
+#include "base/macros.h"
+#include "media/base/media_export.h"
+
+namespace media {
+
+// Enables/disables audio debug recording on construction/destruction. Objects
+// are created using audio::CreateAudioDebugRecordingSession.
+class MEDIA_EXPORT AudioDebugRecordingSession {
+ public:
+ AudioDebugRecordingSession(const AudioDebugRecordingSession&) = delete;
+ AudioDebugRecordingSession& operator=(const AudioDebugRecordingSession&) =
+ delete;
+
+ virtual ~AudioDebugRecordingSession() = default;
+
+ protected:
+ AudioDebugRecordingSession() = default;
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_DEBUG_RECORDING_SESSION_H_
diff --git a/third_party/chromium/media/audio/audio_debug_recording_session_impl.cc b/third_party/chromium/media/audio/audio_debug_recording_session_impl.cc
new file mode 100644
index 0000000..93113a3
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_debug_recording_session_impl.cc
@@ -0,0 +1,119 @@
+// Copyright 2018 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 "media/audio/audio_debug_recording_session_impl.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/task/post_task.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
+#include "build/build_config.h"
+#include "media/audio/audio_debug_recording_manager.h"
+#include "media/audio/audio_manager.h"
+
+namespace media {
+
+// Posting AudioManager::Get() as unretained is safe because
+// AudioManager::Shutdown() (called before AudioManager destruction) shuts down
+// AudioManager thread.
+
+// TODO(https://crbug.com/788657) remove this file after switching to mojo
+// implementation.
+
+namespace {
+
+#if defined(OS_WIN)
+#define NumberToStringType base::NumberToWString
+#else
+#define NumberToStringType base::NumberToString
+#endif
+
+bool StreamTypeToStringType(AudioDebugRecordingStreamType stream_type,
+ base::FilePath::StringType* out) {
+ switch (stream_type) {
+ case AudioDebugRecordingStreamType::kInput:
+ *out = FILE_PATH_LITERAL("input");
+ return true;
+ case AudioDebugRecordingStreamType::kOutput:
+ *out = FILE_PATH_LITERAL("output");
+ return true;
+ }
+ NOTREACHED();
+ return false;
+}
+
+void CreateWavFile(const base::FilePath& debug_recording_file_path,
+ AudioDebugRecordingStreamType stream_type,
+ uint32_t id,
+ base::OnceCallback<void(base::File)> reply_callback) {
+ base::FilePath::StringType stream_type_str;
+ if (!StreamTypeToStringType(stream_type, &stream_type_str)) {
+ std::move(reply_callback).Run(base::File());
+ return;
+ }
+
+ base::ThreadPool::PostTaskAndReplyWithResult(
+ FROM_HERE,
+ {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
+ base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
+ base::BindOnce(
+ [](const base::FilePath& file_name) {
+ return base::File(file_name, base::File::FLAG_CREATE_ALWAYS |
+ base::File::FLAG_WRITE);
+ },
+ debug_recording_file_path.AddExtension(stream_type_str)
+ .AddExtension(NumberToStringType(id))
+ .AddExtension(FILE_PATH_LITERAL("wav"))),
+ std::move(reply_callback));
+}
+
+} // namespace
+
+AudioDebugRecordingSessionImpl::AudioDebugRecordingSessionImpl(
+ const base::FilePath& debug_recording_file_path) {
+ AudioManager* audio_manager = AudioManager::Get();
+ if (audio_manager == nullptr)
+ return;
+
+ audio_manager->GetTaskRunner()->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ [](AudioManager* manager,
+ AudioDebugRecordingManager::CreateWavFileCallback
+ create_file_callback) {
+ AudioDebugRecordingManager* debug_recording_manager =
+ manager->GetAudioDebugRecordingManager();
+ if (debug_recording_manager == nullptr)
+ return;
+ debug_recording_manager->EnableDebugRecording(
+ std::move(create_file_callback));
+ },
+ base::Unretained(audio_manager),
+ base::BindRepeating(&CreateWavFile, debug_recording_file_path)));
+}
+
+AudioDebugRecordingSessionImpl::~AudioDebugRecordingSessionImpl() {
+ AudioManager* audio_manager = AudioManager::Get();
+ if (audio_manager == nullptr)
+ return;
+
+ audio_manager->GetTaskRunner()->PostTask(
+ FROM_HERE, base::BindOnce(
+ [](AudioManager* manager) {
+ AudioDebugRecordingManager* debug_recording_manager =
+ manager->GetAudioDebugRecordingManager();
+ if (debug_recording_manager == nullptr)
+ return;
+ debug_recording_manager->DisableDebugRecording();
+ },
+ base::Unretained(audio_manager)));
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_debug_recording_session_impl.h b/third_party/chromium/media/audio/audio_debug_recording_session_impl.h
new file mode 100644
index 0000000..957c39b
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_debug_recording_session_impl.h
@@ -0,0 +1,33 @@
+// Copyright 2018 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 MEDIA_AUDIO_AUDIO_DEBUG_RECORDING_SESSION_IMPL_H_
+#define MEDIA_AUDIO_AUDIO_DEBUG_RECORDING_SESSION_IMPL_H_
+
+#include "media/audio/audio_debug_recording_session.h"
+#include "media/base/media_export.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace media {
+
+class MEDIA_EXPORT AudioDebugRecordingSessionImpl
+ : public AudioDebugRecordingSession {
+ public:
+ explicit AudioDebugRecordingSessionImpl(
+ const base::FilePath& debug_recording_file_path);
+
+ AudioDebugRecordingSessionImpl(const AudioDebugRecordingSessionImpl&) =
+ delete;
+ AudioDebugRecordingSessionImpl& operator=(
+ const AudioDebugRecordingSessionImpl&) = delete;
+
+ ~AudioDebugRecordingSessionImpl() override;
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_DEBUG_RECORDING_SESSION_IMPL_H_
diff --git a/third_party/chromium/media/audio/audio_debug_recording_session_impl_unittest.cc b/third_party/chromium/media/audio/audio_debug_recording_session_impl_unittest.cc
new file mode 100644
index 0000000..a30b205
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_debug_recording_session_impl_unittest.cc
@@ -0,0 +1,144 @@
+// Copyright 2018 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 "media/audio/audio_debug_recording_session_impl.h"
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/test/task_environment.h"
+#include "build/build_config.h"
+#include "media/audio/audio_debug_recording_test.h"
+#include "media/audio/mock_audio_debug_recording_manager.h"
+#include "media/audio/mock_audio_manager.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace media {
+
+namespace {
+
+#if defined(OS_WIN)
+#define NumberToStringType base::NumberToWString
+#else
+#define NumberToStringType base::NumberToString
+#endif
+
+const base::FilePath::CharType kBaseFileName[] =
+ FILE_PATH_LITERAL("debug_recording");
+const base::FilePath::CharType kInput[] = FILE_PATH_LITERAL("input");
+const base::FilePath::CharType kOutput[] = FILE_PATH_LITERAL("output");
+const int kId = 1;
+const base::FilePath::CharType kWavExtension[] = FILE_PATH_LITERAL("wav");
+
+void OnFileCreated(base::File debug_file) {}
+
+// Action function called on
+// MockAudioDebugRecordingManager::EnableDebugRecording mocked method to test
+// |create_file_callback| behavior.
+void CreateInputOutputDebugRecordingFiles(
+ const AudioDebugRecordingManager::CreateWavFileCallback&
+ create_file_callback) {
+ create_file_callback.Run(AudioDebugRecordingStreamType::kInput, kId,
+ base::BindOnce(&OnFileCreated));
+ create_file_callback.Run(AudioDebugRecordingStreamType::kOutput, kId,
+ base::BindOnce(&OnFileCreated));
+}
+
+} // namespace
+
+class AudioDebugRecordingSessionImplTest : public AudioDebugRecordingTest {
+ public:
+ AudioDebugRecordingSessionImplTest() {
+ CHECK(temp_dir_.CreateUniqueTempDir());
+ base_file_path_ = temp_dir_.GetPath().Append(base::FilePath(kBaseFileName));
+ }
+
+ protected:
+ void CreateDebugRecordingSession() {
+ audio_debug_recording_session_impl_ =
+ std::make_unique<media::AudioDebugRecordingSessionImpl>(
+ base_file_path_);
+ }
+
+ void DestroyDebugRecordingSession() {
+ audio_debug_recording_session_impl_.reset();
+ }
+
+ base::FilePath GetFileName(const base::FilePath::StringType& stream_type,
+ uint32_t id) {
+ return base_file_path_.AddExtension(stream_type)
+ .AddExtension(NumberToStringType(id))
+ .AddExtension(kWavExtension);
+ }
+
+ base::FilePath base_file_path_;
+
+ private:
+ base::ScopedTempDir temp_dir_;
+ std::unique_ptr<AudioDebugRecordingSessionImpl>
+ audio_debug_recording_session_impl_;
+
+ DISALLOW_COPY_AND_ASSIGN(AudioDebugRecordingSessionImplTest);
+};
+
+TEST_F(AudioDebugRecordingSessionImplTest,
+ ConstructorEnablesAndDestructorDisablesDebugRecordingOnAudioManager) {
+ ::testing::InSequence seq;
+
+ CreateAudioManager();
+ InitializeAudioDebugRecordingManager();
+ EXPECT_CALL(*mock_debug_recording_manager_, EnableDebugRecording(testing::_));
+ CreateDebugRecordingSession();
+
+ EXPECT_CALL(*mock_debug_recording_manager_, DisableDebugRecording());
+ DestroyDebugRecordingSession();
+
+ ShutdownAudioManager();
+}
+
+TEST_F(AudioDebugRecordingSessionImplTest,
+ CreateDestroySessionDontCrashWithNoAudioManager) {
+ ASSERT_EQ(nullptr, AudioManager::Get());
+ CreateDebugRecordingSession();
+ DestroyDebugRecordingSession();
+}
+
+TEST_F(AudioDebugRecordingSessionImplTest,
+ CreateDestroySessionDontCrashWithoutInitializingDebugRecordingManager) {
+ CreateAudioManager();
+ CreateDebugRecordingSession();
+ DestroyDebugRecordingSession();
+ ShutdownAudioManager();
+}
+
+// Tests the CreateWavFile method from AudioDebugRecordingSessionImpl unnamed
+// namespace.
+TEST_F(AudioDebugRecordingSessionImplTest, CreateWavFileCreatesExpectedFiles) {
+ CreateAudioManager();
+ InitializeAudioDebugRecordingManager();
+ EXPECT_CALL(*mock_debug_recording_manager_, EnableDebugRecording(testing::_))
+ .WillOnce(testing::Invoke(CreateInputOutputDebugRecordingFiles));
+ CreateDebugRecordingSession();
+
+ // Wait for files to be created.
+ task_environment_.RunUntilIdle();
+
+ // Check that expected files were created.
+ base::FilePath input_recording_filename(GetFileName(kInput, kId));
+ base::FilePath output_recording_filename(GetFileName(kOutput, kId));
+ EXPECT_TRUE(base::PathExists(output_recording_filename));
+ EXPECT_TRUE(base::PathExists(input_recording_filename));
+
+ // Clean-up.
+ EXPECT_CALL(*mock_debug_recording_manager_, DisableDebugRecording());
+ DestroyDebugRecordingSession();
+ ShutdownAudioManager();
+ EXPECT_TRUE(base::DeleteFile(output_recording_filename));
+ EXPECT_TRUE(base::DeleteFile(input_recording_filename));
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_debug_recording_test.cc b/third_party/chromium/media/audio/audio_debug_recording_test.cc
new file mode 100644
index 0000000..bc19e83
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_debug_recording_test.cc
@@ -0,0 +1,39 @@
+// Copyright 2018 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 "media/audio/audio_debug_recording_test.h"
+
+#include "media/audio/mock_audio_debug_recording_manager.h"
+#include "media/audio/mock_audio_manager.h"
+#include "media/audio/test_audio_thread.h"
+
+namespace media {
+
+AudioDebugRecordingTest::AudioDebugRecordingTest() = default;
+
+AudioDebugRecordingTest::~AudioDebugRecordingTest() = default;
+
+void AudioDebugRecordingTest::CreateAudioManager() {
+ DCHECK(AudioManager::Get() == nullptr);
+ mock_audio_manager_ =
+ std::make_unique<MockAudioManager>(std::make_unique<TestAudioThread>());
+ ASSERT_NE(nullptr, AudioManager::Get());
+ ASSERT_EQ(static_cast<AudioManager*>(mock_audio_manager_.get()),
+ AudioManager::Get());
+}
+
+void AudioDebugRecordingTest::ShutdownAudioManager() {
+ DCHECK(mock_audio_manager_);
+ ASSERT_TRUE(mock_audio_manager_->Shutdown());
+}
+
+void AudioDebugRecordingTest::InitializeAudioDebugRecordingManager() {
+ DCHECK(mock_audio_manager_);
+ mock_audio_manager_->InitializeDebugRecording();
+ mock_debug_recording_manager_ = static_cast<MockAudioDebugRecordingManager*>(
+ mock_audio_manager_->GetAudioDebugRecordingManager());
+ ASSERT_NE(nullptr, mock_debug_recording_manager_);
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_debug_recording_test.h b/third_party/chromium/media/audio/audio_debug_recording_test.h
new file mode 100644
index 0000000..2c20ccc
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_debug_recording_test.h
@@ -0,0 +1,42 @@
+// Copyright 2018 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 MEDIA_AUDIO_AUDIO_DEBUG_RECORDING_TEST_H_
+#define MEDIA_AUDIO_AUDIO_DEBUG_RECORDING_TEST_H_
+
+#include <memory>
+
+#include "base/test/task_environment.h"
+#include "media/base/media_export.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+
+class MockAudioDebugRecordingManager;
+class MockAudioManager;
+
+// Base test class for media/audio/ and services/audio/ debug recording test
+// classes.
+class AudioDebugRecordingTest : public testing::Test {
+ public:
+ AudioDebugRecordingTest();
+
+ AudioDebugRecordingTest(const AudioDebugRecordingTest&) = delete;
+ AudioDebugRecordingTest& operator=(const AudioDebugRecordingTest&) = delete;
+
+ ~AudioDebugRecordingTest() override;
+
+ protected:
+ void CreateAudioManager();
+ void ShutdownAudioManager();
+ void InitializeAudioDebugRecordingManager();
+
+ base::test::TaskEnvironment task_environment_;
+ std::unique_ptr<MockAudioManager> mock_audio_manager_;
+ MockAudioDebugRecordingManager* mock_debug_recording_manager_;
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_DEBUG_RECORDING_TEST_H_
diff --git a/third_party/chromium/media/audio/audio_device_description.cc b/third_party/chromium/media/audio/audio_device_description.cc
new file mode 100644
index 0000000..bd3f611
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_device_description.cc
@@ -0,0 +1,112 @@
+// Copyright 2016 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 "media/audio/audio_device_description.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/notreached.h"
+#include "build/chromecast_buildflags.h"
+#include "media/base/localized_strings.h"
+
+namespace media {
+const char AudioDeviceDescription::kDefaultDeviceId[] = "default";
+const char AudioDeviceDescription::kCommunicationsDeviceId[] = "communications";
+const char AudioDeviceDescription::kLoopbackInputDeviceId[] = "loopback";
+const char AudioDeviceDescription::kLoopbackWithMuteDeviceId[] =
+ "loopbackWithMute";
+
+// static
+bool AudioDeviceDescription::IsDefaultDevice(const std::string& device_id) {
+ return device_id.empty() ||
+ device_id == AudioDeviceDescription::kDefaultDeviceId;
+}
+
+// static
+bool AudioDeviceDescription::IsCommunicationsDevice(
+ const std::string& device_id) {
+ return device_id == AudioDeviceDescription::kCommunicationsDeviceId;
+}
+
+// static
+bool AudioDeviceDescription::IsLoopbackDevice(const std::string& device_id) {
+ return device_id.compare(kLoopbackInputDeviceId) == 0 ||
+ device_id.compare(kLoopbackWithMuteDeviceId) == 0;
+}
+
+// static
+bool AudioDeviceDescription::UseSessionIdToSelectDevice(
+ const base::UnguessableToken& session_id,
+ const std::string& device_id) {
+ return !session_id.is_empty() && device_id.empty();
+}
+
+// static
+std::string AudioDeviceDescription::GetDefaultDeviceName() {
+#if !defined(OS_IOS)
+ return GetLocalizedStringUTF8(DEFAULT_AUDIO_DEVICE_NAME);
+#else
+ NOTREACHED();
+ return "";
+#endif
+}
+
+// static
+std::string AudioDeviceDescription::GetCommunicationsDeviceName() {
+#if defined(OS_WIN)
+ return GetLocalizedStringUTF8(COMMUNICATIONS_AUDIO_DEVICE_NAME);
+#elif BUILDFLAG(IS_CHROMECAST)
+ return "";
+#else
+ NOTREACHED();
+ return "";
+#endif
+}
+
+// static
+std::string AudioDeviceDescription::GetDefaultDeviceName(
+ const std::string& real_device_name) {
+ if (real_device_name.empty())
+ return GetDefaultDeviceName();
+ // TODO(guidou): Put the names together in a localized manner.
+ // http://crbug.com/788767
+ return GetDefaultDeviceName() + " - " + real_device_name;
+}
+
+// static
+std::string AudioDeviceDescription::GetCommunicationsDeviceName(
+ const std::string& real_device_name) {
+ if (real_device_name.empty())
+ return GetCommunicationsDeviceName();
+ // TODO(guidou): Put the names together in a localized manner.
+ // http://crbug.com/788767
+ return GetCommunicationsDeviceName() + " - " + real_device_name;
+}
+
+// static
+void AudioDeviceDescription::LocalizeDeviceDescriptions(
+ AudioDeviceDescriptions* device_descriptions) {
+ for (auto& description : *device_descriptions) {
+ if (media::AudioDeviceDescription::IsDefaultDevice(description.unique_id)) {
+ description.device_name =
+ media::AudioDeviceDescription::GetDefaultDeviceName(
+ description.device_name);
+ } else if (media::AudioDeviceDescription::IsCommunicationsDevice(
+ description.unique_id)) {
+ description.device_name =
+ media::AudioDeviceDescription::GetCommunicationsDeviceName(
+ description.device_name);
+ }
+ }
+}
+
+AudioDeviceDescription::AudioDeviceDescription(std::string device_name,
+ std::string unique_id,
+ std::string group_id)
+ : device_name(std::move(device_name)),
+ unique_id(std::move(unique_id)),
+ group_id(std::move(group_id)) {}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_device_description.h b/third_party/chromium/media/audio/audio_device_description.h
new file mode 100644
index 0000000..ccba18e
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_device_description.h
@@ -0,0 +1,98 @@
+// Copyright 2015 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 MEDIA_AUDIO_AUDIO_DEVICE_DESCRIPTION_H_
+#define MEDIA_AUDIO_AUDIO_DEVICE_DESCRIPTION_H_
+
+#include <string>
+#include <vector>
+
+#include "base/unguessable_token.h"
+#include "media/base/media_export.h"
+
+namespace media {
+
+// Provides common information on audio device names and ids.
+struct MEDIA_EXPORT AudioDeviceDescription {
+ // Unique Id of the generic "default" device. Associated with the localized
+ // name returned from GetDefaultDeviceName().
+ static const char kDefaultDeviceId[];
+
+ // Unique Id of the generic default communications device. Associated with
+ // the localized name returned from GetCommunicationsDeviceName().
+ static const char kCommunicationsDeviceId[];
+
+ // Input device ID used to capture the default system playback stream. When
+ // this device ID is passed to MakeAudioInputStream() the returned
+ // AudioInputStream will be capturing audio currently being played on the
+ // default playback device. At the moment this feature is supported only on
+ // some platforms. AudioInputStream::Intialize() will return an error on
+ // platforms that don't support it. GetInputStreamParameters() must be used
+ // to get the parameters of the loopback device before creating a loopback
+ // stream, otherwise stream initialization may fail.
+ static const char kLoopbackInputDeviceId[];
+
+ // Similar to |kLoopbackInputDeviceId|, with only difference that this ID
+ // will mute system audio during capturing.
+ static const char kLoopbackWithMuteDeviceId[];
+
+ // Returns true if |device_id| represents the default device.
+ static bool IsDefaultDevice(const std::string& device_id);
+
+ // Returns true if |device_id| represents the communications device.
+ static bool IsCommunicationsDevice(const std::string& device_id);
+
+ // Returns true if |device_id| represents a loopback audio capture device.
+ static bool IsLoopbackDevice(const std::string& device_id);
+
+ // If |device_id| is not empty, |session_id| should be ignored and the output
+ // device should be selected basing on |device_id|.
+ // If |device_id| is empty and |session_id| is nonzero, output device
+ // associated with the opened input device designated by |session_id| should
+ // be used.
+ static bool UseSessionIdToSelectDevice(
+ const base::UnguessableToken& session_id,
+ const std::string& device_id);
+
+ // The functions dealing with localization are not reliable in the audio
+ // service, and should be avoided there.
+ // Returns the localized name of the generic "default" device.
+ static std::string GetDefaultDeviceName();
+
+ // Returns a localized version of name of the generic "default" device that
+ // includes the given |real_device_name|.
+ static std::string GetDefaultDeviceName(const std::string& real_device_name);
+
+ // Returns the localized name of the generic default communications device.
+ // This device is not supported on all platforms.
+ static std::string GetCommunicationsDeviceName();
+
+ // Returns a localized version of name of the generic communications device
+ // that includes the given |real_device_name|.
+ static std::string GetCommunicationsDeviceName(
+ const std::string& real_device_name);
+
+ // This prepends localized "Default" or "Communications" strings to
+ // default and communications device names in |device_descriptions|.
+ static void LocalizeDeviceDescriptions(
+ std::vector<AudioDeviceDescription>* device_descriptions);
+
+ AudioDeviceDescription() = default;
+ AudioDeviceDescription(const AudioDeviceDescription& other) = default;
+ AudioDeviceDescription(std::string device_name,
+ std::string unique_id,
+ std::string group_id);
+
+ ~AudioDeviceDescription() = default;
+
+ std::string device_name; // Friendly name of the device.
+ std::string unique_id; // Unique identifier for the device.
+ std::string group_id; // Group identifier.
+};
+
+typedef std::vector<AudioDeviceDescription> AudioDeviceDescriptions;
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_DEVICE_DESCRIPTION_H_
diff --git a/third_party/chromium/media/audio/audio_device_info_accessor_for_tests.cc b/third_party/chromium/media/audio/audio_device_info_accessor_for_tests.cc
new file mode 100644
index 0000000..6c0a9d6
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_device_info_accessor_for_tests.cc
@@ -0,0 +1,84 @@
+// Copyright 2017 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 "media/audio/audio_device_info_accessor_for_tests.h"
+
+#include "base/single_thread_task_runner.h"
+#include "media/audio/audio_manager.h"
+
+namespace media {
+
+AudioDeviceInfoAccessorForTests::AudioDeviceInfoAccessorForTests(
+ AudioManager* audio_manager)
+ : audio_manager_(audio_manager) {
+ DCHECK(audio_manager_);
+}
+
+bool AudioDeviceInfoAccessorForTests::HasAudioOutputDevices() {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+ return audio_manager_->HasAudioOutputDevices();
+}
+
+bool AudioDeviceInfoAccessorForTests::HasAudioInputDevices() {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+ return audio_manager_->HasAudioInputDevices();
+}
+
+void AudioDeviceInfoAccessorForTests::GetAudioInputDeviceDescriptions(
+ AudioDeviceDescriptions* device_descriptions) {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+ audio_manager_->GetAudioInputDeviceDescriptions(device_descriptions);
+}
+
+void AudioDeviceInfoAccessorForTests::GetAudioOutputDeviceDescriptions(
+ AudioDeviceDescriptions* device_descriptions) {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+ audio_manager_->GetAudioOutputDeviceDescriptions(device_descriptions);
+}
+
+AudioParameters
+AudioDeviceInfoAccessorForTests::GetDefaultOutputStreamParameters() {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+ return audio_manager_->GetDefaultOutputStreamParameters();
+}
+
+AudioParameters AudioDeviceInfoAccessorForTests::GetOutputStreamParameters(
+ const std::string& device_id) {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+ return audio_manager_->GetOutputStreamParameters(device_id);
+}
+
+AudioParameters AudioDeviceInfoAccessorForTests::GetInputStreamParameters(
+ const std::string& device_id) {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+ return audio_manager_->GetInputStreamParameters(device_id);
+}
+
+std::string AudioDeviceInfoAccessorForTests::GetAssociatedOutputDeviceID(
+ const std::string& input_device_id) {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+ return audio_manager_->GetAssociatedOutputDeviceID(input_device_id);
+}
+
+std::string AudioDeviceInfoAccessorForTests::GetDefaultInputDeviceID() {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+ return audio_manager_->GetDefaultInputDeviceID();
+}
+
+std::string AudioDeviceInfoAccessorForTests::GetDefaultOutputDeviceID() {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+ return audio_manager_->GetDefaultOutputDeviceID();
+}
+
+std::string AudioDeviceInfoAccessorForTests::GetCommunicationsInputDeviceID() {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+ return audio_manager_->GetCommunicationsInputDeviceID();
+}
+
+std::string AudioDeviceInfoAccessorForTests::GetCommunicationsOutputDeviceID() {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+ return audio_manager_->GetCommunicationsOutputDeviceID();
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_device_info_accessor_for_tests.h b/third_party/chromium/media/audio/audio_device_info_accessor_for_tests.h
new file mode 100644
index 0000000..79bedf7
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_device_info_accessor_for_tests.h
@@ -0,0 +1,57 @@
+// Copyright 2017 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 MEDIA_AUDIO_AUDIO_DEVICE_INFO_ACCESSOR_FOR_TESTS_H_
+#define MEDIA_AUDIO_AUDIO_DEVICE_INFO_ACCESSOR_FOR_TESTS_H_
+
+#include <string>
+
+#include "media/audio/audio_device_description.h"
+#include "media/base/audio_parameters.h"
+
+namespace media {
+
+class AudioManager;
+
+// Accessor for protected device info-related AudioManager. To be used in media
+// unit tests only.
+class AudioDeviceInfoAccessorForTests {
+ public:
+ explicit AudioDeviceInfoAccessorForTests(AudioManager* audio_manager);
+
+ bool HasAudioOutputDevices();
+
+ bool HasAudioInputDevices();
+
+ void GetAudioInputDeviceDescriptions(
+ AudioDeviceDescriptions* device_descriptions);
+
+ void GetAudioOutputDeviceDescriptions(
+ AudioDeviceDescriptions* device_descriptions);
+
+ AudioParameters GetDefaultOutputStreamParameters();
+
+ AudioParameters GetOutputStreamParameters(const std::string& device_id);
+
+ AudioParameters GetInputStreamParameters(const std::string& device_id);
+
+ std::string GetAssociatedOutputDeviceID(const std::string& input_device_id);
+
+ std::string GetDefaultInputDeviceID();
+
+ std::string GetDefaultOutputDeviceID();
+
+ std::string GetCommunicationsInputDeviceID();
+
+ std::string GetCommunicationsOutputDeviceID();
+
+ private:
+ AudioManager* const audio_manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(AudioDeviceInfoAccessorForTests);
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_DEVICE_INFO_ACCESSOR_FOR_TESTS_H_
diff --git a/third_party/chromium/media/audio/audio_device_name.cc b/third_party/chromium/media/audio/audio_device_name.cc
new file mode 100644
index 0000000..c14ba73
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_device_name.cc
@@ -0,0 +1,30 @@
+// Copyright (c) 2011 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 "media/audio/audio_device_name.h"
+
+#include <utility>
+
+#include "media/audio/audio_device_description.h"
+
+namespace media {
+
+AudioDeviceName::AudioDeviceName() = default;
+
+AudioDeviceName::AudioDeviceName(std::string device_name, std::string unique_id)
+ : device_name(std::move(device_name)), unique_id(std::move(unique_id)) {}
+
+// static
+AudioDeviceName AudioDeviceName::CreateDefault() {
+ return AudioDeviceName(std::string(),
+ AudioDeviceDescription::kDefaultDeviceId);
+}
+
+// static
+AudioDeviceName AudioDeviceName::CreateCommunications() {
+ return AudioDeviceName(std::string(),
+ AudioDeviceDescription::kCommunicationsDeviceId);
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_device_name.h b/third_party/chromium/media/audio/audio_device_name.h
new file mode 100644
index 0000000..a0ecfb5
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_device_name.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2011 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 MEDIA_AUDIO_AUDIO_DEVICE_NAME_H_
+#define MEDIA_AUDIO_AUDIO_DEVICE_NAME_H_
+
+#include <list>
+#include <string>
+#include "media/base/media_export.h"
+
+namespace media {
+
+struct MEDIA_EXPORT AudioDeviceName {
+ AudioDeviceName();
+ AudioDeviceName(std::string device_name, std::string unique_id);
+
+ // Creates default device representation.
+ // Shouldn't be used in the audio service, since the audio service doesn't
+ // have access to localized device names.
+ static AudioDeviceName CreateDefault();
+
+ // Creates communications device representation.
+ // Shouldn't be used in the audio service, since the audio service doesn't
+ // have access to localized device names.
+ static AudioDeviceName CreateCommunications();
+
+ std::string device_name; // Friendly name of the device.
+ std::string unique_id; // Unique identifier for the device.
+};
+
+typedef std::list<AudioDeviceName> AudioDeviceNames;
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_DEVICE_NAME_H_
diff --git a/third_party/chromium/media/audio/audio_device_thread.cc b/third_party/chromium/media/audio/audio_device_thread.cc
new file mode 100644
index 0000000..2340cc1
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_device_thread.cc
@@ -0,0 +1,114 @@
+// Copyright (c) 2012 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 "media/audio/audio_device_thread.h"
+
+#include <limits>
+
+#include "base/check_op.h"
+#include "base/system/sys_info.h"
+#include "build/build_config.h"
+
+namespace media {
+
+// AudioDeviceThread::Callback implementation
+
+AudioDeviceThread::Callback::Callback(const AudioParameters& audio_parameters,
+ uint32_t segment_length,
+ uint32_t total_segments)
+ : audio_parameters_(audio_parameters),
+ memory_length_(
+ base::CheckMul(segment_length, total_segments).ValueOrDie()),
+ total_segments_(total_segments),
+ segment_length_(segment_length) {
+ CHECK_GT(total_segments_, 0u);
+ thread_checker_.DetachFromThread();
+}
+
+AudioDeviceThread::Callback::~Callback() = default;
+
+void AudioDeviceThread::Callback::InitializeOnAudioThread() {
+ // Normally this function is called before the thread checker is used
+ // elsewhere, but it's not guaranteed. DCHECK to ensure it was not used on
+ // another thread before we get here.
+ DCHECK(thread_checker_.CalledOnValidThread())
+ << "Thread checker was attached on the wrong thread";
+ MapSharedMemory();
+}
+
+// AudioDeviceThread implementation
+
+AudioDeviceThread::AudioDeviceThread(Callback* callback,
+ base::SyncSocket::ScopedHandle socket,
+ const char* thread_name,
+ base::ThreadPriority thread_priority)
+ : callback_(callback),
+ thread_name_(thread_name),
+ socket_(std::move(socket)) {
+#if defined(ARCH_CPU_X86)
+ // Audio threads don't need a huge stack, they don't have a message loop and
+ // they are used exclusively for polling the next frame of audio. See
+ // https://crbug.com/1141563 for discussion.
+ constexpr size_t kStackSize = 256 * 1024;
+#else
+ constexpr size_t kStackSize = 0; // Default.
+#endif
+
+ CHECK(base::PlatformThread::CreateWithPriority(
+ kStackSize, this, &thread_handle_, thread_priority));
+
+ DCHECK(!thread_handle_.is_null());
+}
+
+AudioDeviceThread::~AudioDeviceThread() {
+ socket_.Shutdown();
+ if (thread_handle_.is_null())
+ return;
+ base::PlatformThread::Join(thread_handle_);
+}
+
+base::TimeDelta AudioDeviceThread::GetRealtimePeriod() {
+ return callback_->buffer_duration();
+}
+
+void AudioDeviceThread::ThreadMain() {
+ base::PlatformThread::SetName(thread_name_);
+ callback_->InitializeOnAudioThread();
+
+ uint32_t buffer_index = 0;
+ while (true) {
+ uint32_t pending_data = 0;
+ size_t bytes_read = socket_.Receive(&pending_data, sizeof(pending_data));
+ if (bytes_read != sizeof(pending_data))
+ break;
+
+ // std::numeric_limits<uint32_t>::max() is a special signal which is
+ // returned after the browser stops the output device in response to a
+ // renderer side request.
+ //
+ // Avoid running Process() for the paused signal, we still need to update
+ // the buffer index for synchronized buffers though.
+ //
+ // See comments in AudioOutputController::DoPause() for details on why.
+ if (pending_data != std::numeric_limits<uint32_t>::max())
+ callback_->Process(pending_data);
+
+ // The usage of synchronized buffers differs between input and output cases.
+ //
+ // Input: Let the other end know that we have read data, so that it can
+ // verify it doesn't overwrite any data before read. The |buffer_index|
+ // value is not used. For more details, see AudioInputSyncWriter::Write().
+ //
+ // Output: Let the other end know which buffer we just filled. The
+ // |buffer_index| is used to ensure the other end is getting the buffer it
+ // expects. For more details on how this works see
+ // AudioSyncReader::WaitUntilDataIsReady().
+ ++buffer_index;
+ size_t bytes_sent = socket_.Send(&buffer_index, sizeof(buffer_index));
+ if (bytes_sent != sizeof(buffer_index))
+ break;
+ }
+}
+
+} // namespace media.
diff --git a/third_party/chromium/media/audio/audio_device_thread.h b/third_party/chromium/media/audio/audio_device_thread.h
new file mode 100644
index 0000000..dd88b91
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_device_thread.h
@@ -0,0 +1,95 @@
+// Copyright (c) 2012 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 MEDIA_AUDIO_AUDIO_DEVICE_THREAD_H_
+#define MEDIA_AUDIO_AUDIO_DEVICE_THREAD_H_
+
+#include <stdint.h>
+
+#include "base/macros.h"
+#include "base/sync_socket.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/thread_checker.h"
+#include "media/base/audio_parameters.h"
+#include "media/base/media_export.h"
+
+namespace media {
+
+// Data transfer between browser and render process uses a combination
+// of sync sockets and shared memory. To read from the socket and render
+// data, we use a worker thread, a.k.a. the AudioDeviceThread, which reads
+// data from the browser via the socket and fills the shared memory from the
+// audio thread via the AudioDeviceThread::Callback interface/class.
+class MEDIA_EXPORT AudioDeviceThread : public base::PlatformThread::Delegate {
+ public:
+ // This is the callback interface/base class that Audio[Output|Input]Device
+ // implements to render input/output data. The callbacks run on the
+ // thread owned by AudioDeviceThread.
+ class Callback {
+ public:
+ Callback(const AudioParameters& audio_parameters,
+ uint32_t segment_length,
+ uint32_t total_segments);
+
+ // One time initialization for the callback object on the audio thread.
+ void InitializeOnAudioThread();
+
+ // Derived implementations must map shared memory appropriately before
+ // Process can be called.
+ virtual void MapSharedMemory() = 0;
+
+ // Called whenever we receive notifications about pending input data.
+ virtual void Process(uint32_t pending_data) = 0;
+
+ base::TimeDelta buffer_duration() const {
+ return audio_parameters_.GetBufferDuration();
+ }
+
+ protected:
+ virtual ~Callback();
+
+ // Protected so that derived classes can access directly.
+ // The variables are 'const' since values are calculated/set in the
+ // constructor and must never change.
+ const AudioParameters audio_parameters_;
+
+ const uint32_t memory_length_;
+ const uint32_t total_segments_;
+ const uint32_t segment_length_;
+
+ // Detached in constructor and attached in InitializeOnAudioThread() which
+ // is called on the audio device thread. Sub-classes can then use it for
+ // various thread checking purposes.
+ base::ThreadChecker thread_checker_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Callback);
+ };
+
+ // Creates and automatically starts the audio thread.
+ AudioDeviceThread(Callback* callback,
+ base::SyncSocket::ScopedHandle socket,
+ const char* thread_name,
+ base::ThreadPriority thread_priority);
+
+ // This tells the audio thread to stop and clean up the data; this is a
+ // synchronous process and the thread will stop before the method returns.
+ // Blocking call, see base/threading/thread_restrictions.h.
+ ~AudioDeviceThread() override;
+
+ private:
+ base::TimeDelta GetRealtimePeriod() final;
+ void ThreadMain() final;
+
+ Callback* const callback_;
+ const char* thread_name_;
+ base::CancelableSyncSocket socket_;
+ base::PlatformThreadHandle thread_handle_;
+
+ DISALLOW_COPY_AND_ASSIGN(AudioDeviceThread);
+};
+
+} // namespace media.
+
+#endif // MEDIA_AUDIO_AUDIO_DEVICE_THREAD_H_
diff --git a/third_party/chromium/media/audio/audio_encoders_unittest.cc b/third_party/chromium/media/audio/audio_encoders_unittest.cc
new file mode 100644
index 0000000..0be36ca
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_encoders_unittest.cc
@@ -0,0 +1,323 @@
+// Copyright 2020 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 <cstring>
+#include <limits>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/test/bind.h"
+#include "base/test/task_environment.h"
+#include "base/time/time.h"
+#include "media/audio/audio_opus_encoder.h"
+#include "media/audio/simple_sources.h"
+#include "media/base/audio_encoder.h"
+#include "media/base/audio_parameters.h"
+#include "media/base/status.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/opus/src/include/opus.h"
+
+namespace media {
+
+namespace {
+
+constexpr int kAudioSampleRate = 48000;
+
+// This is the preferred opus buffer duration (60 ms), which corresponds to a
+// value of 2880 frames per buffer (|kOpusFramesPerBuffer|).
+constexpr base::TimeDelta kOpusBufferDuration = base::Milliseconds(60);
+constexpr int kOpusFramesPerBuffer = kOpusBufferDuration.InMicroseconds() *
+ kAudioSampleRate /
+ base::Time::kMicrosecondsPerSecond;
+
+struct TestAudioParams {
+ const int channels;
+ const int sample_rate;
+};
+
+constexpr TestAudioParams kTestAudioParams[] = {
+ {2, kAudioSampleRate},
+ // Change to mono:
+ {1, kAudioSampleRate},
+ // Different sampling rate as well:
+ {1, 24000},
+ {2, 8000},
+ // Using a non-default Opus sampling rate (48, 24, 16, 12, or 8 kHz).
+ {1, 22050},
+ {2, 44100},
+ {2, 96000},
+ {1, kAudioSampleRate},
+ {2, kAudioSampleRate},
+};
+
+} // namespace
+
+class AudioEncodersTest : public ::testing::TestWithParam<TestAudioParams> {
+ public:
+ AudioEncodersTest()
+ : audio_source_(GetParam().channels,
+ /*freq=*/440,
+ GetParam().sample_rate) {
+ options_.sample_rate = GetParam().sample_rate;
+ options_.channels = GetParam().channels;
+ }
+ AudioEncodersTest(const AudioEncodersTest&) = delete;
+ AudioEncodersTest& operator=(const AudioEncodersTest&) = delete;
+ ~AudioEncodersTest() override = default;
+
+ using MaybeDesc = absl::optional<AudioEncoder::CodecDescription>;
+
+ AudioEncoder* encoder() const { return encoder_.get(); }
+
+ void SetupEncoder(AudioEncoder::OutputCB output_cb) {
+ encoder_ = std::make_unique<AudioOpusEncoder>();
+
+ bool called_done = false;
+ AudioEncoder::StatusCB done_cb =
+ base::BindLambdaForTesting([&](Status error) {
+ if (!error.is_ok())
+ FAIL() << error.message();
+ called_done = true;
+ });
+
+ encoder_->Initialize(options_, std::move(output_cb), std::move(done_cb));
+
+ RunLoop();
+ EXPECT_TRUE(called_done);
+ }
+
+ // Produces an audio data that corresponds to a |buffer_duration_| and the
+ // sample rate of the current |options_|. The produced data is send to
+ // |encoder_| to be encoded, and the number of frames generated is returned.
+ int ProduceAudioAndEncode(
+ base::TimeTicks timestamp = base::TimeTicks::Now()) {
+ DCHECK(encoder_);
+ const int num_frames = options_.sample_rate * buffer_duration_.InSecondsF();
+ auto audio_bus = AudioBus::Create(options_.channels, num_frames);
+ audio_source_.OnMoreData(base::TimeDelta(), timestamp, 0, audio_bus.get());
+
+ bool called_done = false;
+ auto done_cb = base::BindLambdaForTesting([&](Status error) {
+ if (!error.is_ok())
+ FAIL() << error.message();
+ called_done = true;
+ });
+
+ encoder_->Encode(std::move(audio_bus), timestamp, std::move(done_cb));
+ RunLoop();
+ EXPECT_TRUE(called_done);
+ return num_frames;
+ }
+
+ void RunLoop() { task_environment_.RunUntilIdle(); }
+
+ base::test::TaskEnvironment task_environment_;
+
+ // The input params as initialized from the test's parameter.
+ AudioEncoder::Options options_;
+
+ // The audio source used to fill in the data of the |current_audio_bus_|.
+ SineWaveAudioSource audio_source_;
+
+ // The encoder the test is verifying.
+ std::unique_ptr<AudioEncoder> encoder_;
+
+ // The audio bus that was most recently generated and sent to the |encoder_|
+ // by ProduceAudioAndEncode().
+ std::unique_ptr<AudioBus> current_audio_bus_;
+
+ base::TimeDelta buffer_duration_ = base::Milliseconds(10);
+};
+
+TEST_P(AudioEncodersTest, OpusTimestamps) {
+ constexpr int kCount = 12;
+ for (base::TimeDelta duration :
+ {kOpusBufferDuration * 10, kOpusBufferDuration,
+ kOpusBufferDuration * 2 / 3}) {
+ buffer_duration_ = duration;
+ size_t expected_outputs = (buffer_duration_ * kCount) / kOpusBufferDuration;
+ base::TimeTicks current_timestamp;
+ std::vector<base::TimeTicks> timestamps;
+
+ auto output_cb =
+ base::BindLambdaForTesting([&](EncodedAudioBuffer output, MaybeDesc) {
+ timestamps.push_back(output.timestamp);
+ });
+
+ SetupEncoder(std::move(output_cb));
+
+ for (int i = 0; i < kCount; ++i) {
+ ProduceAudioAndEncode(current_timestamp);
+ current_timestamp += buffer_duration_;
+ }
+
+ bool flush_done = false;
+ auto done_cb = base::BindLambdaForTesting([&](Status error) {
+ if (!error.is_ok())
+ FAIL() << error.message();
+ flush_done = true;
+ });
+ encoder()->Flush(std::move(done_cb));
+ RunLoop();
+ EXPECT_TRUE(flush_done);
+ EXPECT_EQ(expected_outputs, timestamps.size());
+
+ current_timestamp = base::TimeTicks();
+ for (auto& ts : timestamps) {
+ auto drift = (current_timestamp - ts).magnitude();
+ EXPECT_LE(drift, base::Microseconds(1));
+ current_timestamp += kOpusBufferDuration;
+ }
+ }
+}
+
+TEST_P(AudioEncodersTest, OpusExtraData) {
+ std::vector<uint8_t> extra;
+ auto output_cb = base::BindLambdaForTesting(
+ [&](EncodedAudioBuffer output, MaybeDesc desc) {
+ DCHECK(desc.has_value());
+ extra = desc.value();
+ });
+
+ SetupEncoder(std::move(output_cb));
+ buffer_duration_ = kOpusBufferDuration;
+ ProduceAudioAndEncode();
+ RunLoop();
+
+ ASSERT_GT(extra.size(), 0u);
+ EXPECT_EQ(extra[0], 'O');
+ EXPECT_EQ(extra[1], 'p');
+ EXPECT_EQ(extra[2], 'u');
+ EXPECT_EQ(extra[3], 's');
+
+ uint16_t* sample_rate_ptr = reinterpret_cast<uint16_t*>(extra.data() + 12);
+ if (options_.sample_rate < std::numeric_limits<uint16_t>::max())
+ EXPECT_EQ(*sample_rate_ptr, options_.sample_rate);
+ else
+ EXPECT_EQ(*sample_rate_ptr, 48000);
+
+ uint8_t* channels_ptr = reinterpret_cast<uint8_t*>(extra.data() + 9);
+ EXPECT_EQ(*channels_ptr, options_.channels);
+
+ uint16_t* skip_ptr = reinterpret_cast<uint16_t*>(extra.data() + 10);
+ EXPECT_GT(*skip_ptr, 0);
+}
+
+// Check how Opus encoder reacts to breaks in continuity of incoming sound.
+// Under normal circumstances capture times are expected to be exactly
+// a buffer's duration apart, but if they are not, the encoder just ignores
+// incoming capture times. In other words the only capture times that matter
+// are
+// 1. timestamp of the first encoded buffer
+// 2. timestamps of buffers coming immediately after Flush() calls.
+TEST_P(AudioEncodersTest, OpusTimeContinuityBreak) {
+ base::TimeTicks current_timestamp = base::TimeTicks::Now();
+ base::TimeDelta gap = base::Microseconds(1500);
+ buffer_duration_ = kOpusBufferDuration;
+ std::vector<base::TimeTicks> timestamps;
+
+ auto output_cb =
+ base::BindLambdaForTesting([&](EncodedAudioBuffer output, MaybeDesc) {
+ timestamps.push_back(output.timestamp);
+ });
+
+ SetupEncoder(std::move(output_cb));
+
+ // Encode first normal buffer and immediately get an output for it.
+ auto ts0 = current_timestamp;
+ ProduceAudioAndEncode(current_timestamp);
+ current_timestamp += buffer_duration_;
+ EXPECT_EQ(1u, timestamps.size());
+ EXPECT_EQ(ts0, timestamps[0]);
+
+ // Encode another buffer after a large gap, output timestamp should
+ // disregard the gap.
+ auto ts1 = current_timestamp;
+ current_timestamp += gap;
+ ProduceAudioAndEncode(current_timestamp);
+ current_timestamp += buffer_duration_;
+ EXPECT_EQ(2u, timestamps.size());
+ EXPECT_EQ(ts1, timestamps[1]);
+
+ // Another buffer without a gap.
+ auto ts2 = ts1 + buffer_duration_;
+ ProduceAudioAndEncode(current_timestamp);
+ EXPECT_EQ(3u, timestamps.size());
+ EXPECT_EQ(ts2, timestamps[2]);
+
+ encoder()->Flush(base::BindOnce([](Status error) {
+ if (!error.is_ok())
+ FAIL() << error.message();
+ }));
+ RunLoop();
+
+ // Reset output timestamp after Flush(), the encoder should start producing
+ // timestamps from new base 0.
+ current_timestamp = base::TimeTicks();
+
+ auto ts3 = current_timestamp;
+ ProduceAudioAndEncode(current_timestamp);
+ current_timestamp += buffer_duration_;
+ EXPECT_EQ(4u, timestamps.size());
+ EXPECT_EQ(ts3, timestamps[3]);
+}
+
+TEST_P(AudioEncodersTest, FullCycleEncodeDecode) {
+ int error;
+ int encode_callback_count = 0;
+ std::vector<float> buffer(kOpusFramesPerBuffer * options_.channels);
+ OpusDecoder* opus_decoder =
+ opus_decoder_create(kAudioSampleRate, options_.channels, &error);
+ ASSERT_TRUE(error == OPUS_OK && opus_decoder);
+ int total_frames = 0;
+
+ auto verify_opus_encoding = [&](EncodedAudioBuffer output, MaybeDesc) {
+ ++encode_callback_count;
+
+ // Use the libopus decoder to decode the |encoded_data| and check we
+ // get the expected number of frames per buffer.
+ EXPECT_EQ(kOpusFramesPerBuffer,
+ opus_decode_float(opus_decoder, output.encoded_data.get(),
+ output.encoded_data_size, buffer.data(),
+ kOpusFramesPerBuffer, 0));
+ };
+
+ SetupEncoder(base::BindLambdaForTesting(verify_opus_encoding));
+
+ // The opus encoder encodes in multiple of 60 ms. Wait for the total number of
+ // frames that will be generated in 60 ms at the input sampling rate.
+ const int frames_in_60_ms =
+ kOpusBufferDuration.InSecondsF() * options_.sample_rate;
+
+ base::TimeTicks time;
+ while (total_frames < frames_in_60_ms) {
+ total_frames += ProduceAudioAndEncode(time);
+ time += buffer_duration_;
+ }
+
+ EXPECT_EQ(1, encode_callback_count);
+
+ // If there are remaining frames in the opus encoder FIFO, we need to flush
+ // them before we destroy the encoder. Flushing should trigger the encode
+ // callback and we should be able to decode the resulting encoded frames.
+ if (total_frames > frames_in_60_ms) {
+ encoder()->Flush(base::BindOnce([](Status error) {
+ if (!error.is_ok())
+ FAIL() << error.message();
+ }));
+ RunLoop();
+ EXPECT_EQ(2, encode_callback_count);
+ }
+
+ opus_decoder_destroy(opus_decoder);
+ opus_decoder = nullptr;
+}
+
+INSTANTIATE_TEST_SUITE_P(All,
+ AudioEncodersTest,
+ testing::ValuesIn(kTestAudioParams));
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_features.cc b/third_party/chromium/media/audio/audio_features.cc
new file mode 100644
index 0000000..dd1c1f3
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_features.cc
@@ -0,0 +1,60 @@
+// Copyright 2016 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 "media/audio/audio_features.h"
+#include "base/feature_list.h"
+#include "build/chromeos_buildflags.h"
+
+namespace features {
+
+// When the audio service in a separate process, kill it when a hang is
+// detected. It will be restarted when needed.
+const base::Feature kAudioServiceOutOfProcessKillAtHang{
+ "AudioServiceOutOfProcessKillAtHang",
+#if defined(OS_WIN) || defined(OS_MAC) || defined(OS_LINUX) || \
+ defined(OS_CHROMEOS)
+ base::FEATURE_ENABLED_BY_DEFAULT
+#else
+ base::FEATURE_DISABLED_BY_DEFAULT
+#endif
+};
+
+// If enabled, base::DumpWithoutCrashing is called whenever an audio service
+// hang is detected.
+const base::Feature kDumpOnAudioServiceHang{"DumpOnAudioServiceHang",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+
+#if defined(OS_ANDROID)
+// Enables loading and using AAudio instead of OpenSLES on compatible devices,
+// for audio output streams.
+const base::Feature kUseAAudioDriver{"UseAAudioDriver",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+#endif
+
+#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
+const base::Feature kCrOSSystemAEC{"CrOSSystemAECWithBoardTuningsAllowed",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+const base::Feature kCrOSSystemAECDeactivatedGroups{
+ "CrOSSystemAECDeactivatedGroups", base::FEATURE_ENABLED_BY_DEFAULT};
+const base::Feature kCrOSEnforceSystemAecNsAgc{
+ "CrOSEnforceSystemAecNsAgc", base::FEATURE_DISABLED_BY_DEFAULT};
+const base::Feature kCrOSEnforceSystemAecNs{"CrOSEnforceSystemAecNs",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+const base::Feature kCrOSEnforceSystemAecAgc{"CrOSEnforceSystemAecAgc",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+const base::Feature kCrOSEnforceSystemAec{"CrOSEnforceSystemAec",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+
+#endif
+
+#if defined(OS_MAC) || BUILDFLAG(IS_CHROMEOS_ASH)
+const base::Feature kForceEnableSystemAec{"ForceEnableSystemAec",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+#endif
+
+#if defined(OS_WIN)
+const base::Feature kAllowIAudioClient3{"AllowIAudioClient3",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+#endif
+} // namespace features
diff --git a/third_party/chromium/media/audio/audio_features.h b/third_party/chromium/media/audio/audio_features.h
new file mode 100644
index 0000000..38bcf40
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_features.h
@@ -0,0 +1,41 @@
+// Copyright 2016 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 MEDIA_AUDIO_AUDIO_FEATURES_H_
+#define MEDIA_AUDIO_AUDIO_FEATURES_H_
+
+#include "base/feature_list.h"
+#include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
+#include "media/base/media_export.h"
+
+namespace features {
+
+MEDIA_EXPORT extern const base::Feature kAudioServiceOutOfProcessKillAtHang;
+MEDIA_EXPORT extern const base::Feature kDumpOnAudioServiceHang;
+
+#if defined(OS_ANDROID)
+MEDIA_EXPORT extern const base::Feature kUseAAudioDriver;
+#endif
+
+#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
+MEDIA_EXPORT extern const base::Feature kCrOSSystemAEC;
+MEDIA_EXPORT extern const base::Feature kCrOSSystemAECDeactivatedGroups;
+MEDIA_EXPORT extern const base::Feature kCrOSEnforceSystemAecNsAgc;
+MEDIA_EXPORT extern const base::Feature kCrOSEnforceSystemAecNs;
+MEDIA_EXPORT extern const base::Feature kCrOSEnforceSystemAecAgc;
+MEDIA_EXPORT extern const base::Feature kCrOSEnforceSystemAec;
+#endif
+
+#if defined(OS_MAC) || BUILDFLAG(IS_CHROMEOS_ASH)
+MEDIA_EXPORT extern const base::Feature kForceEnableSystemAec;
+#endif
+
+#if defined(OS_WIN)
+MEDIA_EXPORT extern const base::Feature kAllowIAudioClient3;
+#endif
+
+} // namespace features
+
+#endif // MEDIA_AUDIO_AUDIO_FEATURES_H_
diff --git a/third_party/chromium/media/audio/audio_input_delegate.cc b/third_party/chromium/media/audio/audio_input_delegate.cc
new file mode 100644
index 0000000..1310c00
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_input_delegate.cc
@@ -0,0 +1,9 @@
+// Copyright 2017 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 "media/audio/audio_input_delegate.h"
+
+media::AudioInputDelegate::EventHandler::~EventHandler() = default;
+
+media::AudioInputDelegate::~AudioInputDelegate() = default;
diff --git a/third_party/chromium/media/audio/audio_input_delegate.h b/third_party/chromium/media/audio/audio_input_delegate.h
new file mode 100644
index 0000000..203c978
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_input_delegate.h
@@ -0,0 +1,57 @@
+// Copyright 2017 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 MEDIA_AUDIO_AUDIO_INPUT_DELEGATE_H_
+#define MEDIA_AUDIO_AUDIO_INPUT_DELEGATE_H_
+
+#include <memory>
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "media/base/media_export.h"
+
+namespace base {
+class CancelableSyncSocket;
+class ReadOnlySharedMemoryRegion;
+} // namespace base
+
+namespace media {
+
+class MEDIA_EXPORT AudioInputDelegate {
+ public:
+ // An AudioInputDelegate must not call back to its EventHandler in its
+ // constructor.
+ class MEDIA_EXPORT EventHandler {
+ public:
+ virtual ~EventHandler() = 0;
+
+ // Called when the underlying stream is ready for recording.
+ virtual void OnStreamCreated(
+ int stream_id,
+ base::ReadOnlySharedMemoryRegion shared_memory_region,
+ std::unique_ptr<base::CancelableSyncSocket> socket,
+ bool initially_muted) = 0;
+
+ // Called when the microphone is muted/unmuted.
+ virtual void OnMuted(int stream_id, bool is_muted) = 0;
+
+ // Called if stream encounters an error and has become unusable.
+ virtual void OnStreamError(int stream_id) = 0;
+ };
+
+ virtual ~AudioInputDelegate() = 0;
+
+ virtual int GetStreamId() = 0;
+
+ // Stream control:
+ virtual void OnRecordStream() = 0;
+ virtual void OnSetVolume(double volume) = 0;
+ virtual void OnSetOutputDeviceForAec(
+ const std::string& raw_output_device_id) = 0;
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_INPUT_DELEGATE_H_
diff --git a/third_party/chromium/media/audio/audio_input_device.cc b/third_party/chromium/media/audio/audio_input_device.cc
new file mode 100644
index 0000000..8b238c3
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_input_device.cc
@@ -0,0 +1,483 @@
+// Copyright (c) 2012 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 "media/audio/audio_input_device.h"
+
+#include <stdint.h>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/format_macros.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/strings/stringprintf.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/trace_event/trace_event.h"
+#include "build/build_config.h"
+#include "media/audio/audio_manager_base.h"
+#include "media/base/audio_bus.h"
+
+namespace media {
+
+namespace {
+
+// The number of shared memory buffer segments indicated to browser process
+// in order to avoid data overwriting. This number can be any positive number,
+// dependent how fast the renderer process can pick up captured data from
+// shared memory.
+const int kRequestedSharedMemoryCount = 10;
+
+// The number of seconds with missing callbacks before we report a capture
+// error. The value is based on that the Mac audio implementation can defer
+// start for 5 seconds when resuming after standby, and has a startup success
+// check 5 seconds after actually starting, where stats is logged. We must allow
+// enough time for this. See AUAudioInputStream::CheckInputStartupSuccess().
+const int kMissingCallbacksTimeBeforeErrorSeconds = 12;
+
+// The interval for checking missing callbacks.
+const int kCheckMissingCallbacksIntervalSeconds = 5;
+
+// How often AudioInputDevice::AudioThreadCallback informs that it has gotten
+// data from the source.
+const int kGotDataCallbackIntervalSeconds = 1;
+
+base::ThreadPriority ThreadPriorityFromPurpose(
+ AudioInputDevice::Purpose purpose) {
+ switch (purpose) {
+ case AudioInputDevice::Purpose::kUserInput:
+ return base::ThreadPriority::REALTIME_AUDIO;
+ case AudioInputDevice::Purpose::kLoopback:
+ return base::ThreadPriority::NORMAL;
+ }
+}
+
+} // namespace
+
+// Takes care of invoking the capture callback on the audio thread.
+// An instance of this class is created for each capture stream in
+// OnLowLatencyCreated().
+class AudioInputDevice::AudioThreadCallback
+ : public AudioDeviceThread::Callback {
+ public:
+ AudioThreadCallback(const AudioParameters& audio_parameters,
+ base::ReadOnlySharedMemoryRegion shared_memory_region,
+ uint32_t total_segments,
+ bool enable_uma,
+ CaptureCallback* capture_callback,
+ base::RepeatingClosure got_data_callback);
+
+ AudioThreadCallback(const AudioThreadCallback&) = delete;
+ AudioThreadCallback& operator=(const AudioThreadCallback&) = delete;
+
+ ~AudioThreadCallback() override;
+
+ void MapSharedMemory() override;
+
+ // Called whenever we receive notifications about pending data.
+ void Process(uint32_t pending_data) override;
+
+ private:
+ const bool enable_uma_;
+ base::ReadOnlySharedMemoryRegion shared_memory_region_;
+ base::ReadOnlySharedMemoryMapping shared_memory_mapping_;
+ const base::TimeTicks start_time_;
+ size_t current_segment_id_;
+ uint32_t last_buffer_id_;
+ std::vector<std::unique_ptr<const media::AudioBus>> audio_buses_;
+ CaptureCallback* capture_callback_;
+
+ // Used for informing AudioInputDevice that we have gotten data, i.e. the
+ // stream is alive. |got_data_callback_| is run every
+ // |got_data_callback_interval_in_frames_| frames, calculated from
+ // kGotDataCallbackIntervalSeconds.
+ const int got_data_callback_interval_in_frames_;
+ int frames_since_last_got_data_callback_;
+ base::RepeatingClosure got_data_callback_;
+};
+
+AudioInputDevice::AudioInputDevice(std::unique_ptr<AudioInputIPC> ipc,
+ Purpose purpose,
+ DeadStreamDetection detect_dead_stream)
+ : thread_priority_(ThreadPriorityFromPurpose(purpose)),
+ enable_uma_(purpose == AudioInputDevice::Purpose::kUserInput),
+ callback_(nullptr),
+ ipc_(std::move(ipc)),
+ state_(IDLE),
+ agc_is_enabled_(false),
+ detect_dead_stream_(detect_dead_stream) {
+ CHECK(ipc_);
+
+ // The correctness of the code depends on the relative values assigned in the
+ // State enum.
+ static_assert(IPC_CLOSED < IDLE, "invalid enum value assignment 0");
+ static_assert(IDLE < CREATING_STREAM, "invalid enum value assignment 1");
+ static_assert(CREATING_STREAM < RECORDING, "invalid enum value assignment 2");
+}
+
+void AudioInputDevice::Initialize(const AudioParameters& params,
+ CaptureCallback* callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(params.IsValid());
+ DCHECK(!callback_);
+ audio_parameters_ = params;
+ callback_ = callback;
+}
+
+void AudioInputDevice::Start() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(callback_) << "Initialize hasn't been called";
+ TRACE_EVENT0("audio", "AudioInputDevice::Start");
+
+ // Make sure we don't call Start() more than once.
+ if (state_ != IDLE)
+ return;
+
+ state_ = CREATING_STREAM;
+ ipc_->CreateStream(this, audio_parameters_, agc_is_enabled_,
+ kRequestedSharedMemoryCount);
+}
+
+void AudioInputDevice::Stop() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ TRACE_EVENT0("audio", "AudioInputDevice::Stop");
+
+ if (enable_uma_) {
+ if (detect_dead_stream_ == DeadStreamDetection::kEnabled) {
+ UMA_HISTOGRAM_BOOLEAN(
+ "Media.Audio.Capture.DetectedMissingCallbacks",
+ alive_checker_ ? alive_checker_->DetectedDead() : false);
+ }
+
+ UMA_HISTOGRAM_ENUMERATION("Media.Audio.Capture.StreamCallbackError2",
+ had_error_);
+ }
+ had_error_ = kNoError;
+
+ // Close the stream, if we haven't already.
+ if (state_ >= CREATING_STREAM) {
+ ipc_->CloseStream();
+ state_ = IDLE;
+ agc_is_enabled_ = false;
+ }
+
+ // We can run into an issue where Stop is called right after
+ // OnStreamCreated is called in cases where Start/Stop are called before we
+ // get the OnStreamCreated callback. To handle that corner case, we call
+ // audio_thread_.reset(). In most cases, the thread will already be stopped.
+ //
+ // |alive_checker_| must outlive |audio_callback_|.
+ base::ScopedAllowBaseSyncPrimitivesOutsideBlockingScope allow_thread_join;
+ audio_thread_.reset();
+ audio_callback_.reset();
+ alive_checker_.reset();
+}
+
+void AudioInputDevice::SetVolume(double volume) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ TRACE_EVENT1("audio", "AudioInputDevice::SetVolume", "volume", volume);
+
+ if (volume < 0 || volume > 1.0) {
+ DLOG(ERROR) << "Invalid volume value specified";
+ return;
+ }
+
+ if (state_ >= CREATING_STREAM)
+ ipc_->SetVolume(volume);
+}
+
+void AudioInputDevice::SetAutomaticGainControl(bool enabled) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ TRACE_EVENT1("audio", "AudioInputDevice::SetAutomaticGainControl", "enabled",
+ enabled);
+
+ if (state_ >= CREATING_STREAM) {
+ DLOG(WARNING) << "The AGC state can not be modified after starting.";
+ return;
+ }
+
+ // We simply store the new AGC setting here. This value will be used when
+ // a new stream is initialized and by GetAutomaticGainControl().
+ agc_is_enabled_ = enabled;
+}
+
+void AudioInputDevice::SetOutputDeviceForAec(
+ const std::string& output_device_id) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ TRACE_EVENT1("audio", "AudioInputDevice::SetOutputDeviceForAec",
+ "output_device_id", output_device_id);
+
+ output_device_id_for_aec_ = output_device_id;
+ if (state_ > CREATING_STREAM)
+ ipc_->SetOutputDeviceForAec(output_device_id);
+}
+
+void AudioInputDevice::OnStreamCreated(
+ base::ReadOnlySharedMemoryRegion shared_memory_region,
+ base::SyncSocket::ScopedHandle socket_handle,
+ bool initially_muted) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ TRACE_EVENT0("audio", "AudioInputDevice::OnStreamCreated");
+ DCHECK(shared_memory_region.IsValid());
+#if defined(OS_WIN)
+ DCHECK(socket_handle.IsValid());
+#else
+ DCHECK(socket_handle.is_valid());
+#endif
+ DCHECK_GT(shared_memory_region.GetSize(), 0u);
+
+ if (state_ != CREATING_STREAM)
+ return;
+
+ DCHECK(!audio_callback_);
+ DCHECK(!audio_thread_);
+
+ if (initially_muted)
+ callback_->OnCaptureMuted(true);
+
+ if (auto* controls = ipc_->GetProcessorControls())
+ callback_->OnCaptureProcessorCreated(controls);
+
+ if (output_device_id_for_aec_)
+ ipc_->SetOutputDeviceForAec(*output_device_id_for_aec_);
+
+// Set up checker for detecting missing audio data. We pass a callback which
+// holds a reference to this. |alive_checker_| is deleted in
+// Stop() which we expect to always be called (see comment in
+// destructor). Suspend/resume notifications are not supported on Linux and
+// there's a risk of false positives when suspending. So on Linux we only detect
+// missing audio data until the first audio buffer arrives. Note that there's
+// also a risk of false positives if we are suspending when starting the stream
+// here. See comments in AliveChecker and PowerObserverHelper for details and
+// todos.
+ if (detect_dead_stream_ == DeadStreamDetection::kEnabled) {
+#if defined(OS_LINUX) || defined(OS_CHROMEOS)
+ const bool stop_at_first_alive_notification = true;
+ const bool pause_check_during_suspend = false;
+#else
+ const bool stop_at_first_alive_notification = false;
+ const bool pause_check_during_suspend = true;
+#endif
+ alive_checker_ = std::make_unique<AliveChecker>(
+ base::BindRepeating(&AudioInputDevice::DetectedDeadInputStream, this),
+ base::Seconds(kCheckMissingCallbacksIntervalSeconds),
+ base::Seconds(kMissingCallbacksTimeBeforeErrorSeconds),
+ stop_at_first_alive_notification, pause_check_during_suspend);
+ }
+
+ // Unretained is safe since |alive_checker_| outlives |audio_callback_|.
+ base::RepeatingClosure notify_alive_closure =
+ alive_checker_
+ ? base::BindRepeating(&AliveChecker::NotifyAlive,
+ base::Unretained(alive_checker_.get()))
+ : base::DoNothing();
+
+ audio_callback_ = std::make_unique<AudioInputDevice::AudioThreadCallback>(
+ audio_parameters_, std::move(shared_memory_region),
+ kRequestedSharedMemoryCount, enable_uma_, callback_,
+ notify_alive_closure);
+ audio_thread_ = std::make_unique<AudioDeviceThread>(
+ audio_callback_.get(), std::move(socket_handle), "AudioInputDevice",
+ thread_priority_);
+
+ state_ = RECORDING;
+ ipc_->RecordStream();
+
+ // Start detecting missing audio data.
+ if (alive_checker_)
+ alive_checker_->Start();
+}
+
+void AudioInputDevice::OnError(AudioCapturerSource::ErrorCode code) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ TRACE_EVENT0("audio", "AudioInputDevice::OnError");
+
+ // Do nothing if the stream has been closed.
+ if (state_ < CREATING_STREAM)
+ return;
+
+ if (state_ == CREATING_STREAM) {
+ // At this point, we haven't attempted to start the audio thread.
+ // Accessing the hardware might have failed or we may have reached
+ // the limit of the number of allowed concurrent streams.
+ // We must report the error to the |callback_| so that a potential
+ // audio source object will enter the correct state (e.g. 'ended' for
+ // a local audio source).
+ had_error_ = kErrorDuringCreation;
+ callback_->OnCaptureError(
+ code, code == AudioCapturerSource::ErrorCode::kSystemPermissions
+ ? "Unable to open due to failing an OS Permissions check."
+ : "Maximum allowed input device limit reached or an OS "
+ "failure occured.");
+ } else {
+ // Don't dereference the callback object if the audio thread
+ // is stopped or stopping. That could mean that the callback
+ // object has been deleted.
+ // TODO(tommi): Add an explicit contract for clearing the callback
+ // object. Possibly require calling Initialize again or provide
+ // a callback object via Start() and clear it in Stop().
+ had_error_ = kErrorDuringCapture;
+ if (audio_thread_)
+ callback_->OnCaptureError(code, "IPC delegate state error.");
+ }
+}
+
+void AudioInputDevice::OnMuted(bool is_muted) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ TRACE_EVENT0("audio", "AudioInputDevice::OnMuted");
+
+ // Do nothing if the stream has been closed.
+ if (state_ < CREATING_STREAM)
+ return;
+ callback_->OnCaptureMuted(is_muted);
+}
+
+void AudioInputDevice::OnIPCClosed() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ TRACE_EVENT0("audio", "AudioInputDevice::OnIPCClosed");
+
+ state_ = IPC_CLOSED;
+ ipc_.reset();
+}
+
+AudioInputDevice::~AudioInputDevice() {
+#if DCHECK_IS_ON()
+ // Make sure we've stopped the stream properly before destructing |this|.
+ DCHECK_LE(state_, IDLE);
+ DCHECK(!audio_thread_);
+ DCHECK(!audio_callback_);
+ DCHECK(!alive_checker_);
+#endif // DCHECK_IS_ON()
+}
+
+void AudioInputDevice::DetectedDeadInputStream() {
+ callback_->OnCaptureError(media::AudioCapturerSource::ErrorCode::kUnknown,
+ "No audio received from audio capture device.");
+}
+
+// AudioInputDevice::AudioThreadCallback
+AudioInputDevice::AudioThreadCallback::AudioThreadCallback(
+ const AudioParameters& audio_parameters,
+ base::ReadOnlySharedMemoryRegion shared_memory_region,
+ uint32_t total_segments,
+ bool enable_uma,
+ CaptureCallback* capture_callback,
+ base::RepeatingClosure got_data_callback_)
+ : AudioDeviceThread::Callback(
+ audio_parameters,
+ ComputeAudioInputBufferSize(audio_parameters, 1u),
+ total_segments),
+ enable_uma_(enable_uma),
+ shared_memory_region_(std::move(shared_memory_region)),
+ start_time_(base::TimeTicks::Now()),
+ current_segment_id_(0u),
+ last_buffer_id_(UINT32_MAX),
+ capture_callback_(capture_callback),
+ got_data_callback_interval_in_frames_(kGotDataCallbackIntervalSeconds *
+ audio_parameters.sample_rate()),
+ frames_since_last_got_data_callback_(0),
+ got_data_callback_(std::move(got_data_callback_)) {
+ // CHECK that the shared memory is large enough. The memory allocated must
+ // be at least as large as expected.
+ CHECK_LE(memory_length_, shared_memory_region_.GetSize());
+}
+
+AudioInputDevice::AudioThreadCallback::~AudioThreadCallback() {
+ if (enable_uma_) {
+ UMA_HISTOGRAM_LONG_TIMES("Media.Audio.Capture.InputStreamDuration",
+ base::TimeTicks::Now() - start_time_);
+ }
+}
+
+void AudioInputDevice::AudioThreadCallback::MapSharedMemory() {
+ shared_memory_mapping_ = shared_memory_region_.MapAt(0, memory_length_);
+
+ // Create vector of audio buses by wrapping existing blocks of memory.
+ const uint8_t* ptr =
+ static_cast<const uint8_t*>(shared_memory_mapping_.memory());
+ for (uint32_t i = 0; i < total_segments_; ++i) {
+ const media::AudioInputBuffer* buffer =
+ reinterpret_cast<const media::AudioInputBuffer*>(ptr);
+ audio_buses_.push_back(
+ media::AudioBus::WrapReadOnlyMemory(audio_parameters_, buffer->audio));
+ ptr += segment_length_;
+ }
+
+ // Indicate that browser side capture initialization has succeeded and IPC
+ // channel initialized. This effectively completes the
+ // AudioCapturerSource::Start()' phase as far as the caller of that function
+ // is concerned.
+ capture_callback_->OnCaptureStarted();
+}
+
+void AudioInputDevice::AudioThreadCallback::Process(uint32_t pending_data) {
+ TRACE_EVENT_BEGIN0("audio", "AudioInputDevice::AudioThreadCallback::Process");
+ // The shared memory represents parameters, size of the data buffer and the
+ // actual data buffer containing audio data. Map the memory into this
+ // structure and parse out parameters and the data area.
+ const uint8_t* ptr =
+ static_cast<const uint8_t*>(shared_memory_mapping_.memory());
+ ptr += current_segment_id_ * segment_length_;
+ const AudioInputBuffer* buffer =
+ reinterpret_cast<const AudioInputBuffer*>(ptr);
+
+ // Usually this will be equal but in the case of low sample rate (e.g. 8kHz,
+ // the buffer may be bigger (on mac at least)).
+ DCHECK_GE(buffer->params.size,
+ segment_length_ - sizeof(AudioInputBufferParameters));
+
+ // Verify correct sequence.
+ if (buffer->params.id != last_buffer_id_ + 1) {
+ std::string message = base::StringPrintf(
+ "Incorrect buffer sequence. Expected = %u. Actual = %u.",
+ last_buffer_id_ + 1, buffer->params.id);
+ LOG(ERROR) << message;
+ capture_callback_->OnCaptureError(
+ media::AudioCapturerSource::ErrorCode::kUnknown, message);
+ }
+ if (current_segment_id_ != pending_data) {
+ std::string message = base::StringPrintf(
+ "Segment id not matching. Remote = %u. Local = %" PRIuS ".",
+ pending_data, current_segment_id_);
+ LOG(ERROR) << message;
+ capture_callback_->OnCaptureError(
+ media::AudioCapturerSource::ErrorCode::kUnknown, message);
+ }
+ last_buffer_id_ = buffer->params.id;
+
+ // Use pre-allocated audio bus wrapping existing block of shared memory.
+ const media::AudioBus* audio_bus = audio_buses_[current_segment_id_].get();
+
+ // Regularly inform that we have gotten data.
+ frames_since_last_got_data_callback_ += audio_bus->frames();
+ if (frames_since_last_got_data_callback_ >=
+ got_data_callback_interval_in_frames_) {
+ got_data_callback_.Run();
+ frames_since_last_got_data_callback_ = 0;
+ }
+
+ // Deliver captured data to the client in floating point format and update
+ // the audio delay measurement.
+ // TODO(olka, tommi): Take advantage of |capture_time| in the renderer.
+ const base::TimeTicks capture_time =
+ base::TimeTicks() + base::Microseconds(buffer->params.capture_time_us);
+ const base::TimeTicks now_time = base::TimeTicks::Now();
+ DCHECK_GE(now_time, capture_time);
+
+ capture_callback_->Capture(audio_bus, capture_time, buffer->params.volume,
+ buffer->params.key_pressed);
+
+ if (++current_segment_id_ >= total_segments_)
+ current_segment_id_ = 0u;
+
+ TRACE_EVENT_END2(
+ "audio", "AudioInputDevice::AudioThreadCallback::Process",
+ "capture_time (ms)", (capture_time - base::TimeTicks()).InMillisecondsF(),
+ "now_time (ms)", (now_time - base::TimeTicks()).InMillisecondsF());
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_input_device.h b/third_party/chromium/media/audio/audio_input_device.h
new file mode 100644
index 0000000..ab9d099
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_input_device.h
@@ -0,0 +1,170 @@
+// Copyright (c) 2012 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.
+
+// Low-latency audio capturing class utilizing audio input stream provided
+// by a server process by use of an IPC interface.
+//
+// Relationship of classes:
+//
+// AudioInputController AudioInputDevice
+// ^ ^
+// | |
+// v IPC v
+// MojoAudioInputStream <-----------> AudioInputIPC
+// ^ (MojoAudioInputIPC)
+// |
+// v
+// AudioInputDeviceManager
+//
+// Transportation of audio samples from the browser to the render process
+// is done by using shared memory in combination with a SyncSocket.
+// The AudioInputDevice user registers an AudioInputDevice::CaptureCallback by
+// calling Initialize(). The callback will be called with recorded audio from
+// the underlying audio layers.
+// The session ID is used by the RenderFrameAudioInputStreamFactory to start
+// the device referenced by this ID.
+//
+// State sequences:
+//
+// Start -> CreateStream ->
+// <- OnStreamCreated <-
+// -> RecordStream ->
+//
+// AudioInputDevice::Capture => low latency audio transport on audio thread =>
+//
+// Stop -> CloseStream -> Close
+//
+// This class depends on the audio transport thread. That thread is responsible
+// for calling the CaptureCallback and feeding it audio samples from the server
+// side audio layer using a socket and shared memory.
+//
+// Implementation notes:
+// - The user must call Stop() before deleting the class instance.
+
+#ifndef MEDIA_AUDIO_AUDIO_INPUT_DEVICE_H_
+#define MEDIA_AUDIO_AUDIO_INPUT_DEVICE_H_
+
+#include <memory>
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/read_only_shared_memory_region.h"
+#include "base/sequence_checker.h"
+#include "base/threading/platform_thread.h"
+#include "media/audio/alive_checker.h"
+#include "media/audio/audio_device_thread.h"
+#include "media/audio/audio_input_ipc.h"
+#include "media/base/audio_capturer_source.h"
+#include "media/base/audio_parameters.h"
+#include "media/base/media_export.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace media {
+
+class MEDIA_EXPORT AudioInputDevice : public AudioCapturerSource,
+ public AudioInputIPCDelegate {
+ public:
+ enum Purpose : int8_t { kUserInput, kLoopback };
+ enum class DeadStreamDetection : bool { kDisabled = false, kEnabled = true };
+
+ // NOTE: Clients must call Initialize() before using.
+ // |enable_uma| controls logging of UMA stats. It is used to ensure that
+ // stats are not logged for mirroring service streams.
+ // |detect_dead_stream| controls the dead stream detection.
+ AudioInputDevice(std::unique_ptr<AudioInputIPC> ipc,
+ Purpose purpose,
+ DeadStreamDetection detect_dead_stream);
+
+ // AudioCapturerSource implementation.
+ void Initialize(const AudioParameters& params,
+ CaptureCallback* callback) override;
+ void Start() override;
+ void Stop() override;
+ void SetVolume(double volume) override;
+ void SetAutomaticGainControl(bool enabled) override;
+ void SetOutputDeviceForAec(const std::string& output_device_id) override;
+
+ private:
+ friend class base::RefCountedThreadSafe<AudioInputDevice>;
+
+ // Our audio thread callback class. See source file for details.
+ class AudioThreadCallback;
+
+ // Note: The ordering of members in this enum is critical to correct behavior!
+ enum State {
+ IPC_CLOSED, // No more IPCs can take place.
+ IDLE, // Not started.
+ CREATING_STREAM, // Waiting for OnStreamCreated() to be called back.
+ RECORDING, // Receiving audio data.
+ };
+
+ // This enum is used for UMA, so the only allowed operation on this definition
+ // is to add new states to the bottom, update kMaxValue, and update the
+ // histogram "Media.Audio.Capture.StreamCallbackError2".
+ enum Error {
+ kNoError = 0,
+ kErrorDuringCreation = 1,
+ kErrorDuringCapture = 2,
+ kMaxValue = kErrorDuringCapture
+ };
+
+ ~AudioInputDevice() override;
+
+ // AudioInputIPCDelegate implementation.
+ void OnStreamCreated(base::ReadOnlySharedMemoryRegion shared_memory_region,
+ base::SyncSocket::ScopedHandle socket_handle,
+ bool initially_muted) override;
+ void OnError(AudioCapturerSource::ErrorCode code) override;
+ void OnMuted(bool is_muted) override;
+ void OnIPCClosed() override;
+
+ // This is called by |alive_checker_| if it detects that the input stream is
+ // dead.
+ void DetectedDeadInputStream();
+
+ AudioParameters audio_parameters_;
+
+ const base::ThreadPriority thread_priority_;
+
+ const bool enable_uma_;
+
+ CaptureCallback* callback_;
+
+ // A pointer to the IPC layer that takes care of sending requests over to
+ // the stream implementation. Only valid when state_ != IPC_CLOSED.
+ std::unique_ptr<AudioInputIPC> ipc_;
+
+ // Current state. See comments for State enum above.
+ State state_;
+
+ // For UMA stats. May only be accessed on the IO thread.
+ Error had_error_ = kNoError;
+
+ // Stores the Automatic Gain Control state. Default is false.
+ bool agc_is_enabled_;
+
+ // Controls the dead stream detection. Only the DSP hotword devices set this
+ // to kDisabled to disable dead stream detection.
+ const DeadStreamDetection detect_dead_stream_;
+
+ // Checks regularly that the input stream is alive and notifies us if it
+ // isn't by calling DetectedDeadInputStream(). Must outlive |audio_callback_|.
+ std::unique_ptr<AliveChecker> alive_checker_;
+
+ std::unique_ptr<AudioInputDevice::AudioThreadCallback> audio_callback_;
+ std::unique_ptr<AudioDeviceThread> audio_thread_;
+
+ SEQUENCE_CHECKER(sequence_checker_);
+
+ // Cache the output device used for AEC in case it's called before the stream
+ // is created.
+ absl::optional<std::string> output_device_id_for_aec_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(AudioInputDevice);
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_INPUT_DEVICE_H_
diff --git a/third_party/chromium/media/audio/audio_input_device_unittest.cc b/third_party/chromium/media/audio/audio_input_device_unittest.cc
new file mode 100644
index 0000000..b0c1eba
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_input_device_unittest.cc
@@ -0,0 +1,154 @@
+// Copyright (c) 2017 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 "media/audio/audio_input_device.h"
+
+#include <utility>
+
+#include "base/memory/ptr_util.h"
+#include "base/memory/read_only_shared_memory_region.h"
+#include "base/process/process_handle.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/sync_socket.h"
+#include "base/test/task_environment.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::CancelableSyncSocket;
+using base::SyncSocket;
+using testing::_;
+using testing::DoAll;
+using testing::Invoke;
+using testing::InvokeWithoutArgs;
+
+namespace media {
+
+namespace {
+
+const size_t kMemorySegmentCount = 10u;
+
+class MockAudioInputIPC : public AudioInputIPC {
+ public:
+ MockAudioInputIPC() = default;
+ ~MockAudioInputIPC() override = default;
+
+ MOCK_METHOD4(CreateStream,
+ void(AudioInputIPCDelegate* delegate,
+ const AudioParameters& params,
+ bool automatic_gain_control,
+ uint32_t total_segments));
+ MOCK_METHOD0(RecordStream, void());
+ MOCK_METHOD1(SetVolume, void(double volume));
+ MOCK_METHOD1(SetOutputDeviceForAec, void(const std::string&));
+ MOCK_METHOD0(CloseStream, void());
+};
+
+class MockCaptureCallback : public AudioCapturerSource::CaptureCallback {
+ public:
+ MockCaptureCallback() = default;
+ ~MockCaptureCallback() override = default;
+
+ MOCK_METHOD0(OnCaptureStarted, void());
+ MOCK_METHOD4(Capture,
+ void(const AudioBus* audio_source,
+ base::TimeTicks audio_capture_time,
+ double volume,
+ bool key_pressed));
+
+ MOCK_METHOD2(OnCaptureError,
+ void(AudioCapturerSource::ErrorCode code,
+ const std::string& message));
+ MOCK_METHOD1(OnCaptureMuted, void(bool is_muted));
+};
+
+} // namespace.
+
+class AudioInputDeviceTest
+ : public ::testing::TestWithParam<AudioInputDevice::DeadStreamDetection> {};
+
+// Regular construction.
+TEST_P(AudioInputDeviceTest, Noop) {
+ base::test::SingleThreadTaskEnvironment task_environment(
+ base::test::SingleThreadTaskEnvironment::MainThreadType::IO);
+ MockAudioInputIPC* input_ipc = new MockAudioInputIPC();
+ scoped_refptr<AudioInputDevice> device(new AudioInputDevice(
+ base::WrapUnique(input_ipc), AudioInputDevice::Purpose::kUserInput,
+ AudioInputDeviceTest::GetParam()));
+}
+
+ACTION_P(ReportStateChange, device) {
+ static_cast<AudioInputIPCDelegate*>(device)->OnError(
+ media::AudioCapturerSource::ErrorCode::kUnknown);
+}
+
+// Verify that we get an OnCaptureError() callback if CreateStream fails.
+TEST_P(AudioInputDeviceTest, FailToCreateStream) {
+ AudioParameters params(AudioParameters::AUDIO_PCM_LOW_LATENCY,
+ CHANNEL_LAYOUT_STEREO, 48000, 480);
+
+ MockCaptureCallback callback;
+ MockAudioInputIPC* input_ipc = new MockAudioInputIPC();
+ scoped_refptr<AudioInputDevice> device(new AudioInputDevice(
+ base::WrapUnique(input_ipc), AudioInputDevice::Purpose::kUserInput,
+ AudioInputDeviceTest::GetParam()));
+ device->Initialize(params, &callback);
+ EXPECT_CALL(*input_ipc, CreateStream(_, _, _, _))
+ .WillOnce(ReportStateChange(device.get()));
+ EXPECT_CALL(callback,
+ OnCaptureError(AudioCapturerSource::ErrorCode::kUnknown, _));
+ EXPECT_CALL(*input_ipc, CloseStream());
+ device->Start();
+ device->Stop();
+}
+
+TEST_P(AudioInputDeviceTest, CreateStream) {
+ AudioParameters params(AudioParameters::AUDIO_PCM_LOW_LATENCY,
+ CHANNEL_LAYOUT_STEREO, 48000, 480);
+ base::MappedReadOnlyRegion shared_memory;
+ CancelableSyncSocket browser_socket;
+ CancelableSyncSocket renderer_socket;
+
+ const uint32_t memory_size =
+ media::ComputeAudioInputBufferSize(params, kMemorySegmentCount);
+
+ shared_memory = base::ReadOnlySharedMemoryRegion::Create(memory_size);
+ ASSERT_TRUE(shared_memory.IsValid());
+ memset(shared_memory.mapping.memory(), 0xff, memory_size);
+
+ ASSERT_TRUE(
+ CancelableSyncSocket::CreatePair(&browser_socket, &renderer_socket));
+ base::ReadOnlySharedMemoryRegion duplicated_shared_memory_region =
+ shared_memory.region.Duplicate();
+ ASSERT_TRUE(duplicated_shared_memory_region.IsValid());
+
+ base::test::TaskEnvironment ste;
+ MockCaptureCallback callback;
+ MockAudioInputIPC* input_ipc = new MockAudioInputIPC();
+ scoped_refptr<AudioInputDevice> device(new AudioInputDevice(
+ base::WrapUnique(input_ipc), AudioInputDevice::Purpose::kUserInput,
+ AudioInputDeviceTest::GetParam()));
+ device->Initialize(params, &callback);
+
+ EXPECT_CALL(*input_ipc, CreateStream(_, _, _, _))
+ .WillOnce(InvokeWithoutArgs([&]() {
+ static_cast<AudioInputIPCDelegate*>(device.get())
+ ->OnStreamCreated(std::move(duplicated_shared_memory_region),
+ renderer_socket.Take(), false);
+ }));
+ EXPECT_CALL(*input_ipc, RecordStream());
+
+ EXPECT_CALL(callback, OnCaptureStarted());
+ device->Start();
+ EXPECT_CALL(*input_ipc, CloseStream());
+ device->Stop();
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ AudioInputDeviceGroup,
+ AudioInputDeviceTest,
+ ::testing::Values(AudioInputDevice::DeadStreamDetection::kDisabled,
+ AudioInputDevice::DeadStreamDetection::kEnabled));
+
+} // namespace media.
diff --git a/third_party/chromium/media/audio/audio_input_ipc.cc b/third_party/chromium/media/audio/audio_input_ipc.cc
new file mode 100644
index 0000000..e18a9b8
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_input_ipc.cc
@@ -0,0 +1,17 @@
+// Copyright (c) 2012 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 "media/audio/audio_input_ipc.h"
+
+namespace media {
+
+AudioInputIPCDelegate::~AudioInputIPCDelegate() = default;
+
+AudioInputIPC::~AudioInputIPC() = default;
+
+AudioProcessorControls* AudioInputIPC::GetProcessorControls() {
+ return nullptr;
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_input_ipc.h b/third_party/chromium/media/audio/audio_input_ipc.h
new file mode 100644
index 0000000..28663a9
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_input_ipc.h
@@ -0,0 +1,89 @@
+// Copyright (c) 2012 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 MEDIA_AUDIO_AUDIO_INPUT_IPC_H_
+#define MEDIA_AUDIO_AUDIO_INPUT_IPC_H_
+
+#include <stdint.h>
+
+#include "base/memory/read_only_shared_memory_region.h"
+#include "base/sync_socket.h"
+#include "media/base/audio_capturer_source.h"
+#include "media/base/audio_parameters.h"
+#include "media/base/media_export.h"
+
+namespace media {
+
+class AudioProcessorControls;
+
+// Contains IPC notifications for the state of the server side
+// (AudioInputController) audio state changes and when an AudioInputController
+// has been created. Implemented by AudioInputDevice.
+class MEDIA_EXPORT AudioInputIPCDelegate {
+ public:
+ // Called when an AudioInputController has been created.
+ // See media/mojo/mojom/audio_data_pipe.mojom for documentation of
+ // |handle| and |socket_handle|.
+ virtual void OnStreamCreated(
+ base::ReadOnlySharedMemoryRegion shared_memory_region,
+ base::SyncSocket::ScopedHandle socket_handle,
+ bool initially_muted) = 0;
+
+ // Called when state of an audio stream has changed.
+ virtual void OnError(AudioCapturerSource::ErrorCode code) = 0;
+
+ // Called when an audio stream is muted or unmuted.
+ virtual void OnMuted(bool is_muted) = 0;
+
+ // Called when the AudioInputIPC object is going away and/or when the
+ // IPC channel has been closed and no more IPC requests can be made.
+ // Implementations should delete their owned AudioInputIPC instance
+ // immediately.
+ virtual void OnIPCClosed() = 0;
+
+ protected:
+ virtual ~AudioInputIPCDelegate();
+};
+
+// Provides IPC functionality for an AudioInputIPCDelegate (e.g., an
+// AudioInputDevice). The implementation should asynchronously deliver the
+// messages to an AudioInputController object (or create one in the case of
+// CreateStream()), that may live in a separate process.
+class MEDIA_EXPORT AudioInputIPC {
+ public:
+ virtual ~AudioInputIPC();
+
+ // Sends a request to create an AudioInputController object in the peer
+ // process, and configures it to use the specified audio |params|. The
+ // |total_segments| indidates number of equal-lengthed segments in the shared
+ // memory buffer. Once the stream has been created, the implementation will
+ // notify |delegate| by calling OnStreamCreated().
+ virtual void CreateStream(AudioInputIPCDelegate* delegate,
+ const AudioParameters& params,
+ bool automatic_gain_control,
+ uint32_t total_segments) = 0;
+
+ // Corresponds to a call to AudioInputController::Record() on the server side.
+ virtual void RecordStream() = 0;
+
+ // Sets the volume of the audio stream.
+ virtual void SetVolume(double volume) = 0;
+
+ // Sets the output device from which to cancel echo, if supported. The
+ // |output_device_id| can be gotten from a device enumeration. Must not be
+ // called before the stream has been successfully created.
+ virtual void SetOutputDeviceForAec(const std::string& output_device_id) = 0;
+
+ // If the input has built-in processing, returns a pointer to processing
+ // controls. Valid after the stream has been created.
+ virtual AudioProcessorControls* GetProcessorControls();
+
+ // Closes the audio stream, which should shut down the corresponding
+ // AudioInputController in the peer process.
+ virtual void CloseStream() = 0;
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_INPUT_IPC_H_
diff --git a/third_party/chromium/media/audio/audio_input_stream_data_interceptor.cc b/third_party/chromium/media/audio/audio_input_stream_data_interceptor.cc
new file mode 100644
index 0000000..332b3ae
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_input_stream_data_interceptor.cc
@@ -0,0 +1,100 @@
+// Copyright 2017 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 "media/audio/audio_input_stream_data_interceptor.h"
+
+#include <utility>
+
+#include "media/audio/audio_debug_recording_helper.h"
+
+namespace media {
+
+AudioInputStreamDataInterceptor::AudioInputStreamDataInterceptor(
+ CreateDebugRecorderCB create_debug_recorder_cb,
+ AudioInputStream* stream)
+ : create_debug_recorder_cb_(std::move(create_debug_recorder_cb)),
+ stream_(stream) {
+ DCHECK(create_debug_recorder_cb_);
+ DCHECK(stream_);
+}
+
+AudioInputStreamDataInterceptor::~AudioInputStreamDataInterceptor() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+// Implementation of AudioInputStream.
+AudioInputStream::OpenOutcome AudioInputStreamDataInterceptor::Open() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return stream_->Open();
+}
+
+void AudioInputStreamDataInterceptor::Start(
+ AudioInputStream::AudioInputCallback* callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ callback_ = callback;
+ debug_recorder_ = create_debug_recorder_cb_.Run();
+ stream_->Start(this);
+}
+
+void AudioInputStreamDataInterceptor::Stop() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ stream_->Stop();
+ debug_recorder_.reset();
+ callback_ = nullptr;
+}
+
+void AudioInputStreamDataInterceptor::Close() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ stream_->Close();
+ delete this;
+}
+
+double AudioInputStreamDataInterceptor::GetMaxVolume() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return stream_->GetMaxVolume();
+}
+
+void AudioInputStreamDataInterceptor::SetVolume(double volume) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ stream_->SetVolume(volume);
+}
+
+double AudioInputStreamDataInterceptor::GetVolume() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return stream_->GetVolume();
+}
+
+bool AudioInputStreamDataInterceptor::IsMuted() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return stream_->IsMuted();
+}
+
+bool AudioInputStreamDataInterceptor::SetAutomaticGainControl(bool enabled) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return stream_->SetAutomaticGainControl(enabled);
+}
+
+bool AudioInputStreamDataInterceptor::GetAutomaticGainControl() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return stream_->GetAutomaticGainControl();
+}
+
+void AudioInputStreamDataInterceptor::SetOutputDeviceForAec(
+ const std::string& output_device_id) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return stream_->SetOutputDeviceForAec(output_device_id);
+}
+
+void AudioInputStreamDataInterceptor::OnData(const AudioBus* source,
+ base::TimeTicks capture_time,
+ double volume) {
+ callback_->OnData(source, capture_time, volume);
+ debug_recorder_->OnData(source);
+}
+
+void AudioInputStreamDataInterceptor::OnError() {
+ callback_->OnError();
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_input_stream_data_interceptor.h b/third_party/chromium/media/audio/audio_input_stream_data_interceptor.h
new file mode 100644
index 0000000..3ba937c
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_input_stream_data_interceptor.h
@@ -0,0 +1,73 @@
+// Copyright 2017 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 MEDIA_AUDIO_AUDIO_INPUT_STREAM_DATA_INTERCEPTOR_H_
+#define MEDIA_AUDIO_AUDIO_INPUT_STREAM_DATA_INTERCEPTOR_H_
+
+#include <memory>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/sequence_checker.h"
+#include "media/audio/audio_io.h"
+#include "media/base/media_export.h"
+
+namespace media {
+
+class AudioDebugRecorder;
+
+// This class wraps an AudioInputStream to be able to intercept the data for
+// debug recording purposes.
+class MEDIA_EXPORT AudioInputStreamDataInterceptor
+ : public AudioInputStream,
+ public AudioInputStream::AudioInputCallback {
+ public:
+ using CreateDebugRecorderCB =
+ base::RepeatingCallback<std::unique_ptr<AudioDebugRecorder>()>;
+
+ // |stream| is the stream that this object should forward all stream
+ // operations to. It will intercept OnData callbacks and send the audio data
+ // to the debug recorder created by |create_debug_recorder_cb|.
+ AudioInputStreamDataInterceptor(
+ CreateDebugRecorderCB create_debug_recorder_cb,
+ AudioInputStream* stream);
+
+ AudioInputStreamDataInterceptor(const AudioInputStreamDataInterceptor&) =
+ delete;
+ AudioInputStreamDataInterceptor& operator=(
+ const AudioInputStreamDataInterceptor&) = delete;
+
+ ~AudioInputStreamDataInterceptor() override;
+
+ // Implementation of AudioInputStream.
+ OpenOutcome Open() override;
+ void Start(AudioInputStream::AudioInputCallback* callback) override;
+ void Stop() override;
+ void Close() override;
+ double GetMaxVolume() override;
+ void SetVolume(double volume) override;
+ double GetVolume() override;
+ bool IsMuted() override;
+ bool SetAutomaticGainControl(bool enabled) override;
+ bool GetAutomaticGainControl() override;
+ void SetOutputDeviceForAec(const std::string& output_device_id) override;
+
+ // Implementation of AudioInputCallback
+ void OnData(const AudioBus* source,
+ base::TimeTicks capture_time,
+ double volume) override;
+
+ void OnError() override;
+
+ private:
+ const CreateDebugRecorderCB create_debug_recorder_cb_;
+ std::unique_ptr<AudioDebugRecorder> debug_recorder_;
+ AudioInputStream* const stream_;
+ AudioInputStream::AudioInputCallback* callback_;
+ SEQUENCE_CHECKER(sequence_checker_);
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_INPUT_STREAM_DATA_INTERCEPTOR_H_
diff --git a/third_party/chromium/media/audio/audio_input_stream_data_interceptor_unittest.cc b/third_party/chromium/media/audio/audio_input_stream_data_interceptor_unittest.cc
new file mode 100644
index 0000000..3a2ec62
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_input_stream_data_interceptor_unittest.cc
@@ -0,0 +1,314 @@
+// Copyright 2017 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 "media/audio/audio_input_stream_data_interceptor.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/memory/ptr_util.h"
+#include "media/audio/audio_debug_recording_helper.h"
+#include "media/audio/audio_io.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+
+namespace {
+
+using testing::Return;
+using testing::StrictMock;
+using testing::Mock;
+
+const double kMaxVolume = 0.1234;
+const double kNewVolume = 0.2345;
+const double kVolume = 0.3456;
+
+class MockStream : public AudioInputStream {
+ public:
+ MockStream() = default;
+ ~MockStream() override = default;
+ MOCK_METHOD0(Open, AudioInputStream::OpenOutcome());
+ MOCK_METHOD1(Start, void(AudioInputStream::AudioInputCallback*));
+ MOCK_METHOD0(Stop, void());
+ MOCK_METHOD0(Close, void());
+ MOCK_METHOD0(GetMaxVolume, double());
+ MOCK_METHOD1(SetVolume, void(double));
+ MOCK_METHOD0(GetVolume, double());
+ MOCK_METHOD1(SetAutomaticGainControl, bool(bool));
+ MOCK_METHOD0(GetAutomaticGainControl, bool());
+ MOCK_METHOD0(IsMuted, bool());
+ MOCK_METHOD1(SetOutputDeviceForAec, void(const std::string&));
+};
+
+class MockDebugRecorder : public AudioDebugRecorder {
+ public:
+ MockDebugRecorder() = default;
+ ~MockDebugRecorder() override = default;
+ MOCK_METHOD1(OnData, void(const AudioBus* source));
+};
+
+class MockCallback : public AudioInputStream::AudioInputCallback {
+ public:
+ MockCallback() = default;
+ ~MockCallback() override = default;
+
+ MOCK_METHOD3(OnData, void(const AudioBus*, base::TimeTicks, double));
+ MOCK_METHOD0(OnError, void());
+};
+
+class MockDebugRecorderFactory {
+ public:
+ MockDebugRecorderFactory() = default;
+ ~MockDebugRecorderFactory() { DCHECK(!prepared_recorder_); }
+
+ std::unique_ptr<AudioDebugRecorder> CreateDebugRecorder() {
+ DCHECK(prepared_recorder_);
+ return std::move(prepared_recorder_);
+ }
+
+ void ExpectRecorderCreation(
+ std::unique_ptr<AudioDebugRecorder> recorder_ptr) {
+ DCHECK(!prepared_recorder_);
+ prepared_recorder_ = std::move(recorder_ptr);
+ DCHECK(prepared_recorder_);
+ }
+
+ private:
+ std::unique_ptr<AudioDebugRecorder> prepared_recorder_;
+};
+
+void TestSetAutomaticGainControl(bool enable, bool agc_is_supported) {
+ MockDebugRecorderFactory factory;
+ StrictMock<MockStream> stream;
+ AudioInputStream* interceptor = new AudioInputStreamDataInterceptor(
+ base::BindRepeating(&MockDebugRecorderFactory::CreateDebugRecorder,
+ base::Unretained(&factory)),
+ &stream);
+
+ EXPECT_CALL(stream, SetAutomaticGainControl(enable))
+ .WillOnce(Return(agc_is_supported));
+ EXPECT_EQ(interceptor->SetAutomaticGainControl(enable), agc_is_supported);
+
+ Mock::VerifyAndClearExpectations(&stream);
+ EXPECT_CALL(stream, Close());
+ interceptor->Close();
+}
+
+} // namespace
+
+TEST(AudioInputStreamDataInterceptorTest, Open) {
+ MockDebugRecorderFactory factory;
+ StrictMock<MockStream> stream;
+ AudioInputStream* interceptor = new AudioInputStreamDataInterceptor(
+ base::BindRepeating(&MockDebugRecorderFactory::CreateDebugRecorder,
+ base::Unretained(&factory)),
+ &stream);
+ EXPECT_CALL(stream, Open());
+ interceptor->Open();
+
+ Mock::VerifyAndClearExpectations(&stream);
+ EXPECT_CALL(stream, Close());
+ interceptor->Close();
+}
+
+TEST(AudioInputStreamDataInterceptorTest, Start) {
+ MockDebugRecorderFactory factory;
+ StrictMock<MockStream> stream;
+ StrictMock<MockCallback> callback;
+ auto* recorder = new StrictMock<MockDebugRecorder>();
+ std::unique_ptr<AudioBus> audio_bus = AudioBus::Create(1, 1);
+ AudioInputStreamDataInterceptor* interceptor =
+ new AudioInputStreamDataInterceptor(
+ base::BindRepeating(&MockDebugRecorderFactory::CreateDebugRecorder,
+ base::Unretained(&factory)),
+ &stream);
+
+ EXPECT_CALL(stream, Start(interceptor));
+ factory.ExpectRecorderCreation(base::WrapUnique(recorder));
+ interceptor->Start(&callback);
+
+ Mock::VerifyAndClearExpectations(&stream);
+
+ base::TimeTicks time = base::TimeTicks::Now();
+
+ // Audio data should be passed to both callback and recorder.
+ EXPECT_CALL(callback, OnData(audio_bus.get(), time, kVolume));
+ EXPECT_CALL(*recorder, OnData(audio_bus.get()));
+ interceptor->OnData(audio_bus.get(), time, kVolume);
+
+ Mock::VerifyAndClearExpectations(&callback);
+ Mock::VerifyAndClearExpectations(recorder);
+
+ // Errors should be propagated to the renderer
+ EXPECT_CALL(callback, OnError());
+ interceptor->OnError();
+
+ Mock::VerifyAndClearExpectations(&callback);
+
+ EXPECT_CALL(stream, Close());
+ interceptor->Close();
+}
+
+TEST(AudioInputStreamDataInterceptorTest, Stop) {
+ MockDebugRecorderFactory factory;
+ StrictMock<MockStream> stream;
+ AudioInputStream* interceptor = new AudioInputStreamDataInterceptor(
+ base::BindRepeating(&MockDebugRecorderFactory::CreateDebugRecorder,
+ base::Unretained(&factory)),
+ &stream);
+ EXPECT_CALL(stream, Stop());
+ interceptor->Stop();
+
+ Mock::VerifyAndClearExpectations(&stream);
+ EXPECT_CALL(stream, Close());
+ interceptor->Close();
+}
+
+TEST(AudioInputStreamDataInterceptorTest, Close) {
+ MockDebugRecorderFactory factory;
+ StrictMock<MockStream> stream;
+ AudioInputStream* interceptor = new AudioInputStreamDataInterceptor(
+ base::BindRepeating(&MockDebugRecorderFactory::CreateDebugRecorder,
+ base::Unretained(&factory)),
+ &stream);
+
+ EXPECT_CALL(stream, Close());
+ interceptor->Close();
+}
+
+TEST(AudioInputStreamDataInterceptorTest, GetMaxVolume) {
+ MockDebugRecorderFactory factory;
+ StrictMock<MockStream> stream;
+ AudioInputStream* interceptor = new AudioInputStreamDataInterceptor(
+ base::BindRepeating(&MockDebugRecorderFactory::CreateDebugRecorder,
+ base::Unretained(&factory)),
+ &stream);
+
+ EXPECT_CALL(stream, GetMaxVolume()).WillOnce(Return(kMaxVolume));
+ EXPECT_EQ(interceptor->GetMaxVolume(), kMaxVolume);
+
+ Mock::VerifyAndClearExpectations(&stream);
+ EXPECT_CALL(stream, Close());
+ interceptor->Close();
+}
+
+TEST(AudioInputStreamDataInterceptorTest, SetVolume) {
+ MockDebugRecorderFactory factory;
+ StrictMock<MockStream> stream;
+ AudioInputStream* interceptor = new AudioInputStreamDataInterceptor(
+ base::BindRepeating(&MockDebugRecorderFactory::CreateDebugRecorder,
+ base::Unretained(&factory)),
+ &stream);
+
+ EXPECT_CALL(stream, SetVolume(kNewVolume));
+ interceptor->SetVolume(kNewVolume);
+
+ Mock::VerifyAndClearExpectations(&stream);
+ EXPECT_CALL(stream, Close());
+ interceptor->Close();
+}
+
+TEST(AudioInputStreamDataInterceptorTest, GetVolume) {
+ MockDebugRecorderFactory factory;
+ StrictMock<MockStream> stream;
+ AudioInputStream* interceptor = new AudioInputStreamDataInterceptor(
+ base::BindRepeating(&MockDebugRecorderFactory::CreateDebugRecorder,
+ base::Unretained(&factory)),
+ &stream);
+
+ EXPECT_CALL(stream, GetVolume()).WillOnce(Return(kVolume));
+ EXPECT_EQ(interceptor->GetVolume(), kVolume);
+
+ Mock::VerifyAndClearExpectations(&stream);
+ EXPECT_CALL(stream, Close());
+ interceptor->Close();
+}
+
+TEST(AudioInputStreamDataInterceptorTest,
+ SetAutomaticGainControlTrueWhenSupported) {
+ TestSetAutomaticGainControl(true, true);
+}
+
+TEST(AudioInputStreamDataInterceptorTest,
+ SetAutomaticGainControlFalseWhenSupported) {
+ TestSetAutomaticGainControl(false, true);
+}
+
+TEST(AudioInputStreamDataInterceptorTest,
+ SetAutomaticGainControlTrueWhenNotSupported) {
+ TestSetAutomaticGainControl(true, false);
+}
+
+TEST(AudioInputStreamDataInterceptorTest,
+ SetAutomaticGainControlFalseWhenNotSupported) {
+ TestSetAutomaticGainControl(false, false);
+}
+
+TEST(AudioInputStreamDataInterceptorTest, GetAutomaticGainControl_True) {
+ MockDebugRecorderFactory factory;
+ StrictMock<MockStream> stream;
+ AudioInputStream* interceptor = new AudioInputStreamDataInterceptor(
+ base::BindRepeating(&MockDebugRecorderFactory::CreateDebugRecorder,
+ base::Unretained(&factory)),
+ &stream);
+
+ EXPECT_CALL(stream, GetAutomaticGainControl()).WillOnce(Return(true));
+ EXPECT_EQ(interceptor->GetAutomaticGainControl(), true);
+
+ Mock::VerifyAndClearExpectations(&stream);
+ EXPECT_CALL(stream, Close());
+ interceptor->Close();
+}
+
+TEST(AudioInputStreamDataInterceptorTest, GetAutomaticGainControl_False) {
+ MockDebugRecorderFactory factory;
+ StrictMock<MockStream> stream;
+ AudioInputStream* interceptor = new AudioInputStreamDataInterceptor(
+ base::BindRepeating(&MockDebugRecorderFactory::CreateDebugRecorder,
+ base::Unretained(&factory)),
+ &stream);
+
+ EXPECT_CALL(stream, GetAutomaticGainControl()).WillOnce(Return(false));
+ EXPECT_EQ(interceptor->GetAutomaticGainControl(), false);
+
+ Mock::VerifyAndClearExpectations(&stream);
+ EXPECT_CALL(stream, Close());
+ interceptor->Close();
+}
+
+TEST(AudioInputStreamDataInterceptorTest, IsMuted_True) {
+ MockDebugRecorderFactory factory;
+ StrictMock<MockStream> stream;
+ AudioInputStream* interceptor = new AudioInputStreamDataInterceptor(
+ base::BindRepeating(&MockDebugRecorderFactory::CreateDebugRecorder,
+ base::Unretained(&factory)),
+ &stream);
+
+ EXPECT_CALL(stream, IsMuted()).WillOnce(Return(true));
+ EXPECT_EQ(interceptor->IsMuted(), true);
+
+ Mock::VerifyAndClearExpectations(&stream);
+ EXPECT_CALL(stream, Close());
+ interceptor->Close();
+}
+
+TEST(AudioInputStreamDataInterceptorTest, IsMuted_False) {
+ MockDebugRecorderFactory factory;
+ StrictMock<MockStream> stream;
+ AudioInputStream* interceptor = new AudioInputStreamDataInterceptor(
+ base::BindRepeating(&MockDebugRecorderFactory::CreateDebugRecorder,
+ base::Unretained(&factory)),
+ &stream);
+
+ EXPECT_CALL(stream, IsMuted()).WillOnce(Return(false));
+ EXPECT_EQ(interceptor->IsMuted(), false);
+
+ Mock::VerifyAndClearExpectations(&stream);
+ EXPECT_CALL(stream, Close());
+ interceptor->Close();
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_input_unittest.cc b/third_party/chromium/media/audio/audio_input_unittest.cc
new file mode 100644
index 0000000..8756f44
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_input_unittest.cc
@@ -0,0 +1,234 @@
+// Copyright (c) 2012 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 <stdint.h>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/command_line.h"
+#include "base/environment.h"
+#include "base/macros.h"
+#include "base/message_loop/message_pump_type.h"
+#include "base/run_loop.h"
+#include "base/test/test_message_loop.h"
+#include "base/threading/platform_thread.h"
+#include "build/build_config.h"
+#include "media/audio/audio_device_description.h"
+#include "media/audio/audio_device_info_accessor_for_tests.h"
+#include "media/audio/audio_io.h"
+#include "media/audio/audio_manager.h"
+#include "media/audio/audio_unittest_util.h"
+#include "media/audio/test_audio_thread.h"
+#include "media/base/media_switches.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+
+// This class allows to find out if the callbacks are occurring as
+// expected and if any error has been reported.
+class TestInputCallback : public AudioInputStream::AudioInputCallback {
+ public:
+ TestInputCallback(base::OnceClosure quit_closure)
+ : quit_closure_(std::move(quit_closure)),
+ callback_count_(0),
+ had_error_(0) {}
+ void OnData(const AudioBus* source,
+ base::TimeTicks capture_time,
+ double volume) override {
+ if (!quit_closure_.is_null()) {
+ ++callback_count_;
+ if (callback_count_ >= 2) {
+ std::move(quit_closure_).Run();
+ }
+ }
+ }
+ void OnError() override {
+ if (!quit_closure_.is_null()) {
+ ++had_error_;
+ std::move(quit_closure_).Run();
+ }
+ }
+ // Returns how many times OnData() has been called. This should not be called
+ // until |quit_closure_| has run.
+ int callback_count() const {
+ DCHECK(quit_closure_.is_null());
+ return callback_count_;
+ }
+ // Returns how many times the OnError callback was called. This should not be
+ // called until |quit_closure_| has run.
+ int had_error() const {
+ DCHECK(quit_closure_.is_null());
+ return had_error_;
+ }
+
+ private:
+ base::OnceClosure quit_closure_;
+ int callback_count_;
+ int had_error_;
+};
+
+class AudioInputTest : public testing::Test {
+ public:
+ AudioInputTest()
+ : message_loop_(base::MessagePumpType::UI),
+ audio_manager_(AudioManager::CreateForTesting(
+ std::make_unique<TestAudioThread>())),
+ audio_input_stream_(nullptr) {
+ base::RunLoop().RunUntilIdle();
+ }
+
+ AudioInputTest(const AudioInputTest&) = delete;
+ AudioInputTest& operator=(const AudioInputTest&) = delete;
+
+ ~AudioInputTest() override { audio_manager_->Shutdown(); }
+
+ protected:
+ bool InputDevicesAvailable() {
+#if defined(OS_FUCHSIA)
+ // On Fuchsia HasAudioInputDevices() returns true, but AudioInputStream is
+ // not implemented. Audio input is implemented in
+ // FuchsiaAudioCapturerStream. It implements AudioCapturerStream interface
+ // and runs in the renderer process.
+ return false;
+#elif defined(OS_MAC) && defined(ARCH_CPU_ARM64)
+ // TODO(crbug.com/1128458): macOS on ARM64 says it has devices, but won't
+ // let any of them be opened or listed.
+ return false;
+#else
+ return AudioDeviceInfoAccessorForTests(audio_manager_.get())
+ .HasAudioInputDevices();
+#endif
+ }
+
+ void MakeAudioInputStreamOnAudioThread() {
+ RunOnAudioThread(base::BindOnce(&AudioInputTest::MakeAudioInputStream,
+ base::Unretained(this)));
+ }
+
+ void CloseAudioInputStreamOnAudioThread() {
+ RunOnAudioThread(base::BindOnce(&AudioInputStream::Close,
+ base::Unretained(audio_input_stream_)));
+ audio_input_stream_ = nullptr;
+ }
+
+ void OpenAndCloseAudioInputStreamOnAudioThread() {
+ RunOnAudioThread(
+ base::BindOnce(&AudioInputTest::OpenAndClose, base::Unretained(this)));
+ }
+
+ void OpenStopAndCloseAudioInputStreamOnAudioThread() {
+ RunOnAudioThread(base::BindOnce(&AudioInputTest::OpenStopAndClose,
+ base::Unretained(this)));
+ }
+
+ void OpenAndStartAudioInputStreamOnAudioThread(
+ AudioInputStream::AudioInputCallback* sink) {
+ RunOnAudioThread(base::BindOnce(&AudioInputTest::OpenAndStart,
+ base::Unretained(this), sink));
+ }
+
+ void StopAndCloseAudioInputStreamOnAudioThread() {
+ RunOnAudioThread(
+ base::BindOnce(&AudioInputTest::StopAndClose, base::Unretained(this)));
+ }
+
+ void MakeAudioInputStream() {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+ AudioParameters params =
+ AudioDeviceInfoAccessorForTests(audio_manager_.get())
+ .GetInputStreamParameters(AudioDeviceDescription::kDefaultDeviceId);
+ audio_input_stream_ = audio_manager_->MakeAudioInputStream(
+ params, AudioDeviceDescription::kDefaultDeviceId,
+ base::BindRepeating(&AudioInputTest::OnLogMessage,
+ base::Unretained(this)));
+ ASSERT_TRUE(audio_input_stream_);
+ }
+
+ void OpenAndClose() {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+ ASSERT_TRUE(audio_input_stream_);
+ EXPECT_EQ(audio_input_stream_->Open(),
+ AudioInputStream::OpenOutcome::kSuccess);
+ audio_input_stream_->Close();
+ audio_input_stream_ = nullptr;
+ }
+
+ void OpenAndStart(AudioInputStream::AudioInputCallback* sink) {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+ ASSERT_TRUE(audio_input_stream_);
+ EXPECT_EQ(audio_input_stream_->Open(),
+ AudioInputStream::OpenOutcome::kSuccess);
+ audio_input_stream_->Start(sink);
+ }
+
+ void OpenStopAndClose() {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+ ASSERT_TRUE(audio_input_stream_);
+ EXPECT_EQ(audio_input_stream_->Open(),
+ AudioInputStream::OpenOutcome::kSuccess);
+ audio_input_stream_->Stop();
+ audio_input_stream_->Close();
+ audio_input_stream_ = nullptr;
+ }
+
+ void StopAndClose() {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+ ASSERT_TRUE(audio_input_stream_);
+ audio_input_stream_->Stop();
+ audio_input_stream_->Close();
+ audio_input_stream_ = nullptr;
+ }
+
+ // Synchronously runs the provided callback/closure on the audio thread.
+ void RunOnAudioThread(base::OnceClosure closure) {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+ std::move(closure).Run();
+ }
+
+ void OnLogMessage(const std::string& message) {}
+
+ base::TestMessageLoop message_loop_;
+ std::unique_ptr<AudioManager> audio_manager_;
+ AudioInputStream* audio_input_stream_;
+};
+
+// Test create and close of an AudioInputStream without recording audio.
+TEST_F(AudioInputTest, CreateAndClose) {
+ ABORT_AUDIO_TEST_IF_NOT(InputDevicesAvailable());
+ MakeAudioInputStreamOnAudioThread();
+ CloseAudioInputStreamOnAudioThread();
+}
+
+// Test create, open and close of an AudioInputStream without recording audio.
+TEST_F(AudioInputTest, OpenAndClose) {
+ ABORT_AUDIO_TEST_IF_NOT(InputDevicesAvailable());
+ MakeAudioInputStreamOnAudioThread();
+ OpenAndCloseAudioInputStreamOnAudioThread();
+}
+
+// Test create, open, stop and close of an AudioInputStream without recording.
+TEST_F(AudioInputTest, OpenStopAndClose) {
+ ABORT_AUDIO_TEST_IF_NOT(InputDevicesAvailable());
+ MakeAudioInputStreamOnAudioThread();
+ OpenStopAndCloseAudioInputStreamOnAudioThread();
+}
+
+// Test a normal recording sequence using an AudioInputStream.
+// Very simple test which starts capturing and verifies that recording starts.
+TEST_F(AudioInputTest, Record) {
+ ABORT_AUDIO_TEST_IF_NOT(InputDevicesAvailable());
+ MakeAudioInputStreamOnAudioThread();
+
+ base::RunLoop run_loop;
+ TestInputCallback test_callback(run_loop.QuitClosure());
+ OpenAndStartAudioInputStreamOnAudioThread(&test_callback);
+
+ run_loop.Run();
+ EXPECT_GE(test_callback.callback_count(), 2);
+ EXPECT_FALSE(test_callback.had_error());
+
+ StopAndCloseAudioInputStreamOnAudioThread();
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_io.h b/third_party/chromium/media/audio/audio_io.h
new file mode 100644
index 0000000..295be09
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_io.h
@@ -0,0 +1,212 @@
+// Copyright (c) 2012 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 MEDIA_AUDIO_AUDIO_IO_H_
+#define MEDIA_AUDIO_AUDIO_IO_H_
+
+#include <stdint.h>
+
+#include "base/time/time.h"
+#include "media/base/audio_bus.h"
+#include "media/base/media_export.h"
+
+// Low-level audio output support. To make sound there are 3 objects involved:
+// - AudioSource : produces audio samples on a pull model. Implements
+// the AudioSourceCallback interface.
+// - AudioOutputStream : uses the AudioSource to render audio on a given
+// channel, format and sample frequency configuration. Data from the
+// AudioSource is delivered in a 'pull' model.
+// - AudioManager : factory for the AudioOutputStream objects, manager
+// of the hardware resources and mixer control.
+//
+// The number and configuration of AudioOutputStream does not need to match the
+// physically available hardware resources. For example you can have:
+//
+// MonoPCMSource1 --> MonoPCMStream1 --> | | --> audio left channel
+// StereoPCMSource -> StereoPCMStream -> | mixer |
+// MonoPCMSource2 --> MonoPCMStream2 --> | | --> audio right channel
+//
+// This facility's objective is mix and render audio with low overhead using
+// the OS basic audio support, abstracting as much as possible the
+// idiosyncrasies of each platform. Non-goals:
+// - Positional, 3d audio
+// - Dependence on non-default libraries such as DirectX 9, 10, XAudio
+// - Digital signal processing or effects
+// - Extra features if a specific hardware is installed (EAX, X-fi)
+//
+// The primary client of this facility is audio coming from several tabs.
+// Specifically for this case we avoid supporting complex formats such as MP3
+// or WMA. Complex format decoding should be done by the renderers.
+
+// Models an audio stream that gets rendered to the audio hardware output.
+// Because we support more audio streams than physically available channels
+// a given AudioOutputStream might or might not talk directly to hardware.
+// An audio stream allocates several buffers for audio data and calls
+// AudioSourceCallback::OnMoreData() periodically to fill these buffers,
+// as the data is written to the audio device. Size of each packet is determined
+// by |samples_per_packet| specified in AudioParameters when the stream is
+// created.
+
+namespace media {
+
+class MEDIA_EXPORT AudioOutputStream {
+ public:
+ // Audio sources must implement AudioSourceCallback. This interface will be
+ // called in a random thread which very likely is a high priority thread. Do
+ // not rely on using this thread TLS or make calls that alter the thread
+ // itself such as creating Windows or initializing COM.
+ class MEDIA_EXPORT AudioSourceCallback {
+ public:
+ virtual ~AudioSourceCallback() {}
+
+ // Provide more data by fully filling |dest|. The source will return the
+ // number of frames it filled. |delay| is the duration of audio written to
+ // |dest| in prior calls to OnMoreData() that has not yet been played out,
+ // and |delay_timestamp| is the time when |delay| was measured. The time
+ // when the first sample added to |dest| is expected to be played out can be
+ // calculated by adding |delay| to |delay_timestamp|. The accuracy of
+ // |delay| and |delay_timestamp| may vary depending on the platform and
+ // implementation. |prior_frames_skipped| is the number of frames skipped by
+ // the consumer.
+ virtual int OnMoreData(base::TimeDelta delay,
+ base::TimeTicks delay_timestamp,
+ int prior_frames_skipped,
+ AudioBus* dest) = 0;
+
+ // There was an error while playing a buffer. Audio source cannot be
+ // destroyed yet. No direct action needed by the AudioStream, but it is
+ // a good place to stop accumulating sound data since is is likely that
+ // playback will not continue.
+ //
+ // An ErrorType may be provided with more information on what went wrong. An
+ // unhandled kDeviceChange type error is likely to result in further errors;
+ // so it's recommended that sources close their existing output stream and
+ // request a new one when this error is sent.
+ enum class ErrorType { kUnknown, kDeviceChange };
+ virtual void OnError(ErrorType type) = 0;
+ };
+
+ virtual ~AudioOutputStream() {}
+
+ // Open the stream. false is returned if the stream cannot be opened. Open()
+ // must always be followed by a call to Close() even if Open() fails.
+ virtual bool Open() = 0;
+
+ // Starts playing audio and generating AudioSourceCallback::OnMoreData().
+ // Since implementor of AudioOutputStream may have internal buffers, right
+ // after calling this method initial buffers are fetched.
+ //
+ // The output stream does not take ownership of this callback.
+ virtual void Start(AudioSourceCallback* callback) = 0;
+
+ // Stops playing audio. The operation completes synchronously meaning that
+ // once Stop() has completed executing, no further callbacks will be made to
+ // the callback object that was supplied to Start() and it can be safely
+ // deleted. Stop() may be called in any state, e.g. before Start() or after
+ // Stop().
+ virtual void Stop() = 0;
+
+ // Sets the relative volume, with range [0.0, 1.0] inclusive.
+ virtual void SetVolume(double volume) = 0;
+
+ // Gets the relative volume, with range [0.0, 1.0] inclusive.
+ virtual void GetVolume(double* volume) = 0;
+
+ // Close the stream.
+ // After calling this method, the object should not be used anymore.
+ // After calling this method, no further AudioSourceCallback methods
+ // should be called on the callback object that was supplied to Start()
+ // by the AudioOutputStream implementation.
+ virtual void Close() = 0;
+
+ // Flushes the stream. This should only be called if the stream is not
+ // playing. (i.e. called after Stop or Open)
+ virtual void Flush() = 0;
+};
+
+// Models an audio sink receiving recorded audio from the audio driver.
+class MEDIA_EXPORT AudioInputStream {
+ public:
+ class MEDIA_EXPORT AudioInputCallback {
+ public:
+ // Called by the audio recorder when a full packet of audio data is
+ // available. This is called from a special audio thread and the
+ // implementation should return as soon as possible.
+ //
+ // |capture_time| is the time at which the first sample in |source| was
+ // received. The age of the audio data may be calculated by subtracting
+ // |capture_time| from base::TimeTicks::Now(). |capture_time| is always
+ // monotonically increasing.
+ virtual void OnData(const AudioBus* source,
+ base::TimeTicks capture_time,
+ double volume) = 0;
+
+ // There was an error while recording audio. The audio sink cannot be
+ // destroyed yet. No direct action needed by the AudioInputStream, but it
+ // is a good place to stop accumulating sound data since is is likely that
+ // recording will not continue.
+ virtual void OnError() = 0;
+
+ protected:
+ virtual ~AudioInputCallback() {}
+ };
+
+ virtual ~AudioInputStream() {}
+
+ enum class OpenOutcome {
+ kSuccess,
+ kAlreadyOpen,
+ // Failed due to an unknown or unspecified reason.
+ kFailed,
+ // Failed to open due to OS-level System permissions.
+ kFailedSystemPermissions,
+ // Failed to open as the device is exclusively opened by another app.
+ kFailedInUse,
+ };
+
+ // Open the stream and prepares it for recording. Call Start() to actually
+ // begin recording.
+ virtual OpenOutcome Open() = 0;
+
+ // Starts recording audio and generating AudioInputCallback::OnData().
+ // The input stream does not take ownership of this callback.
+ virtual void Start(AudioInputCallback* callback) = 0;
+
+ // Stops recording audio. Effect might not be instantaneous as there could be
+ // pending audio callbacks in the queue which will be issued first before
+ // recording stops.
+ virtual void Stop() = 0;
+
+ // Close the stream. This also generates AudioInputCallback::OnClose(). This
+ // should be the last call made on this object.
+ virtual void Close() = 0;
+
+ // Returns the maximum microphone analog volume or 0.0 if device does not
+ // have volume control.
+ virtual double GetMaxVolume() = 0;
+
+ // Sets the microphone analog volume, with range [0, max_volume] inclusive.
+ virtual void SetVolume(double volume) = 0;
+
+ // Returns the microphone analog volume, with range [0, max_volume] inclusive.
+ virtual double GetVolume() = 0;
+
+ // Sets the Automatic Gain Control (AGC) state.
+ virtual bool SetAutomaticGainControl(bool enabled) = 0;
+
+ // Returns the Automatic Gain Control (AGC) state.
+ virtual bool GetAutomaticGainControl() = 0;
+
+ // Returns the current muting state for the microphone.
+ virtual bool IsMuted() = 0;
+
+ // Sets the output device from which to cancel echo, if echo cancellation is
+ // supported by this stream. E.g. called by WebRTC when it changes playback
+ // devices.
+ virtual void SetOutputDeviceForAec(const std::string& output_device_id) = 0;
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_IO_H_
diff --git a/third_party/chromium/media/audio/audio_logging.h b/third_party/chromium/media/audio/audio_logging.h
new file mode 100644
index 0000000..764b8b2
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_logging.h
@@ -0,0 +1,80 @@
+// Copyright 2013 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 MEDIA_AUDIO_AUDIO_LOGGING_H_
+#define MEDIA_AUDIO_AUDIO_LOGGING_H_
+
+#include <memory>
+#include <string>
+
+
+namespace media {
+
+class AudioParameters;
+
+// AudioLog logs state information about an active audio component.
+class AudioLog {
+ public:
+ virtual ~AudioLog() {}
+
+ // Called when an audio component is created. |params| are the parameters of
+ // the created stream. |device_id| is the id of the audio device opened by
+ // the created stream.
+ virtual void OnCreated(const media::AudioParameters& params,
+ const std::string& device_id) = 0;
+
+ // Called when an audio component is started, generally this is synonymous
+ // with "playing."
+ virtual void OnStarted() = 0;
+
+ // Called when an audio component is stopped, generally this is synonymous
+ // with "paused."
+ virtual void OnStopped() = 0;
+
+ // Called when an audio component is closed, generally this is synonymous
+ // with "deleted."
+ virtual void OnClosed() = 0;
+
+ // Called when an audio component encounters an error.
+ virtual void OnError() = 0;
+
+ // Called when an audio component changes volume. |volume| is the new volume.
+ virtual void OnSetVolume(double volume) = 0;
+
+ // Called with information about audio processing set-up for an audio
+ // component.
+ virtual void OnProcessingStateChanged(const std::string& message) = 0;
+
+ // Called when an audio component wants to forward a log message.
+ virtual void OnLogMessage(const std::string& message) = 0;
+};
+
+// AudioLogFactory dispenses AudioLog instances for tracking AudioComponent
+// behavior.
+class AudioLogFactory {
+ public:
+ enum AudioComponent {
+ // Input controllers have a 1:1 mapping with streams, so there's no need to
+ // track both controllers and streams.
+ AUDIO_INPUT_CONTROLLER,
+ // Output controllers may or may not be backed by an active stream, so we
+ // need to track both controllers and streams.
+ AUDIO_OUTPUT_CONTROLLER,
+ AUDIO_OUTPUT_STREAM,
+ AUDIO_COMPONENT_MAX
+ };
+
+ // Create a new AudioLog object for tracking the behavior for one instance of
+ // the given component. Each instance of an "owning" class must create its
+ // own AudioLog.
+ virtual std::unique_ptr<AudioLog> CreateAudioLog(AudioComponent component,
+ int component_id) = 0;
+
+ protected:
+ virtual ~AudioLogFactory() {}
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_LOGGING_H_
diff --git a/third_party/chromium/media/audio/audio_low_latency_input_output_unittest.cc b/third_party/chromium/media/audio/audio_low_latency_input_output_unittest.cc
new file mode 100644
index 0000000..eca3b36
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_low_latency_input_output_unittest.cc
@@ -0,0 +1,416 @@
+// Copyright (c) 2012 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 <stddef.h>
+#include <stdint.h>
+
+#include <algorithm>
+#include <memory>
+
+#include "base/bind.h"
+#include "base/environment.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/path_service.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/synchronization/lock.h"
+#include "base/test/task_environment.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "media/audio/audio_device_description.h"
+#include "media/audio/audio_device_info_accessor_for_tests.h"
+#include "media/audio/audio_io.h"
+#include "media/audio/audio_manager.h"
+#include "media/audio/audio_unittest_util.h"
+#include "media/audio/test_audio_thread.h"
+#include "media/base/seekable_buffer.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+
+namespace {
+
+// Limits the number of delay measurements we can store in an array and
+// then write to file at end of the WASAPIAudioInputOutputFullDuplex test.
+static const size_t kMaxDelayMeasurements = 1000;
+
+// Name of the output text file. The output file will be stored in the
+// directory containing media_unittests.exe.
+// Example: \src\build\Debug\audio_delay_values_ms.txt.
+// See comments for the WASAPIAudioInputOutputFullDuplex test for more details
+// about the file format.
+static const char kDelayValuesFileName[] = "audio_delay_values_ms.txt";
+
+// Contains delay values which are reported during the full-duplex test.
+// Total delay = |buffer_delay_ms| + |input_delay_ms| + |output_delay_ms|.
+struct AudioDelayState {
+ AudioDelayState()
+ : delta_time_ms(0),
+ buffer_delay_ms(0),
+ input_delay_ms(0),
+ output_delay_ms(0) {
+ }
+
+ // Time in milliseconds since last delay report. Typical value is ~10 [ms].
+ int delta_time_ms;
+
+ // Size of internal sync buffer. Typical value is ~0 [ms].
+ int buffer_delay_ms;
+
+ // Reported capture/input delay. Typical value is ~10 [ms].
+ int input_delay_ms;
+
+ // Reported render/output delay. Typical value is ~40 [ms].
+ int output_delay_ms;
+};
+
+void OnLogMessage(const std::string& message) {}
+
+// Test fixture class.
+class AudioLowLatencyInputOutputTest : public testing::Test {
+ protected:
+ AudioLowLatencyInputOutputTest() {
+ audio_manager_ =
+ AudioManager::CreateForTesting(std::make_unique<TestAudioThread>());
+ }
+
+ ~AudioLowLatencyInputOutputTest() override { audio_manager_->Shutdown(); }
+
+ AudioManager* audio_manager() { return audio_manager_.get(); }
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner() {
+ return task_environment_.GetMainThreadTaskRunner();
+ }
+
+ private:
+ base::test::TaskEnvironment task_environment_{
+ base::test::TaskEnvironment::MainThreadType::UI};
+ std::unique_ptr<AudioManager> audio_manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(AudioLowLatencyInputOutputTest);
+};
+
+// This audio source/sink implementation should be used for manual tests
+// only since delay measurements are stored on an output text file.
+// All incoming/recorded audio packets are stored in an intermediate media
+// buffer which the renderer reads from when it needs audio for playout.
+// The total effect is that recorded audio is played out in loop back using
+// a sync buffer as temporary storage.
+class FullDuplexAudioSinkSource
+ : public AudioInputStream::AudioInputCallback,
+ public AudioOutputStream::AudioSourceCallback {
+ public:
+ FullDuplexAudioSinkSource(int sample_rate,
+ int samples_per_packet,
+ int channels)
+ : sample_rate_(sample_rate),
+ samples_per_packet_(samples_per_packet),
+ channels_(channels),
+ input_elements_to_write_(0),
+ output_elements_to_write_(0),
+ previous_write_time_(base::TimeTicks::Now()) {
+ // Size in bytes of each audio frame (4 bytes for 16-bit stereo PCM).
+ frame_size_ = (16 / 8) * channels_;
+
+ // Start with the smallest possible buffer size. It will be increased
+ // dynamically during the test if required.
+ buffer_ = std::make_unique<media::SeekableBuffer>(
+ 0, samples_per_packet_ * frame_size_);
+
+ frames_to_ms_ = static_cast<double>(1000.0 / sample_rate_);
+ delay_states_ = std::make_unique<AudioDelayState[]>(kMaxDelayMeasurements);
+ }
+
+ ~FullDuplexAudioSinkSource() override {
+ // Get complete file path to output file in the directory containing
+ // media_unittests.exe. Example: src/build/Debug/audio_delay_values_ms.txt.
+ base::FilePath file_name;
+ EXPECT_TRUE(base::PathService::Get(base::DIR_EXE, &file_name));
+ file_name = file_name.AppendASCII(kDelayValuesFileName);
+
+ FILE* text_file = base::OpenFile(file_name, "wt");
+ DLOG_IF(ERROR, !text_file) << "Failed to open log file.";
+ VLOG(0) << ">> Output file " << file_name.value() << " has been created.";
+
+ // Write the array which contains time-stamps, buffer size and
+ // audio delays values to a text file.
+ size_t elements_written = 0;
+ while (elements_written <
+ std::min(input_elements_to_write_, output_elements_to_write_)) {
+ const AudioDelayState state = delay_states_[elements_written];
+ fprintf(text_file, "%d %d %d %d\n",
+ state.delta_time_ms,
+ state.buffer_delay_ms,
+ state.input_delay_ms,
+ state.output_delay_ms);
+ ++elements_written;
+ }
+
+ base::CloseFile(text_file);
+ }
+
+ // AudioInputStream::AudioInputCallback.
+ void OnError() override {}
+ void OnData(const AudioBus* src,
+ base::TimeTicks capture_time,
+ double volume) override {
+ base::AutoLock lock(lock_);
+
+ // Update three components in the AudioDelayState for this recorded
+ // audio packet.
+ const base::TimeTicks now_time = base::TimeTicks::Now();
+ const int diff = (now_time - previous_write_time_).InMilliseconds();
+ previous_write_time_ = now_time;
+ if (input_elements_to_write_ < kMaxDelayMeasurements) {
+ delay_states_[input_elements_to_write_].delta_time_ms = diff;
+ delay_states_[input_elements_to_write_].buffer_delay_ms =
+ BytesToMilliseconds(buffer_->forward_bytes());
+ delay_states_[input_elements_to_write_].input_delay_ms =
+ (base::TimeTicks::Now() - capture_time).InMilliseconds();
+ ++input_elements_to_write_;
+ }
+
+ // TODO(henrika): fix this and use AudioFifo instead.
+ // Store the captured audio packet in a seekable media buffer.
+ // if (!buffer_->Append(src, size)) {
+ // An attempt to write outside the buffer limits has been made.
+ // Double the buffer capacity to ensure that we have a buffer large
+ // enough to handle the current sample test scenario.
+ // buffer_->set_forward_capacity(2 * buffer_->forward_capacity());
+ // buffer_->Clear();
+ // }
+ }
+
+ // AudioOutputStream::AudioSourceCallback.
+ void OnError(ErrorType type) override {}
+ int OnMoreData(base::TimeDelta delay,
+ base::TimeTicks /* delay_timestamp */,
+ int /* prior_frames_skipped */,
+ AudioBus* dest) override {
+ base::AutoLock lock(lock_);
+
+ // Update one component in the AudioDelayState for the packet
+ // which is about to be played out.
+ if (output_elements_to_write_ < kMaxDelayMeasurements) {
+ delay_states_[output_elements_to_write_].output_delay_ms =
+ delay.InMilliseconds();
+ ++output_elements_to_write_;
+ }
+
+ int size;
+ const uint8_t* source;
+ // Read the data from the seekable media buffer which contains
+ // captured data at the same size and sample rate as the output side.
+ if (buffer_->GetCurrentChunk(&source, &size) && size > 0) {
+ EXPECT_EQ(channels_, dest->channels());
+ size = std::min(dest->frames() * frame_size_, size);
+ EXPECT_EQ(static_cast<size_t>(size) % sizeof(*dest->channel(0)), 0U);
+
+ // We should only have 16 bits per sample.
+ DCHECK_EQ(frame_size_ / channels_, 2);
+ dest->FromInterleaved<SignedInt16SampleTypeTraits>(
+ reinterpret_cast<const int16_t*>(source), size / channels_);
+
+ buffer_->Seek(size);
+ return size / frame_size_;
+ }
+
+ return 0;
+ }
+
+ protected:
+ // Converts from bytes to milliseconds taking the sample rate and size
+ // of an audio frame into account.
+ int BytesToMilliseconds(uint32_t delay_bytes) const {
+ return static_cast<int>((delay_bytes / frame_size_) * frames_to_ms_ + 0.5);
+ }
+
+ private:
+ base::Lock lock_;
+ std::unique_ptr<media::SeekableBuffer> buffer_;
+ int sample_rate_;
+ int samples_per_packet_;
+ int channels_;
+ int frame_size_;
+ double frames_to_ms_;
+ std::unique_ptr<AudioDelayState[]> delay_states_;
+ size_t input_elements_to_write_;
+ size_t output_elements_to_write_;
+ base::TimeTicks previous_write_time_;
+};
+
+class AudioInputStreamTraits {
+ public:
+ typedef AudioInputStream StreamType;
+
+ static AudioParameters GetDefaultAudioStreamParameters(
+ AudioManager* audio_manager) {
+ return AudioDeviceInfoAccessorForTests(audio_manager)
+ .GetInputStreamParameters(AudioDeviceDescription::kDefaultDeviceId);
+ }
+
+ static StreamType* CreateStream(AudioManager* audio_manager,
+ const AudioParameters& params) {
+ return audio_manager->MakeAudioInputStream(
+ params, AudioDeviceDescription::kDefaultDeviceId,
+ base::BindRepeating(&OnLogMessage));
+ }
+};
+
+class AudioOutputStreamTraits {
+ public:
+ typedef AudioOutputStream StreamType;
+
+ static AudioParameters GetDefaultAudioStreamParameters(
+ AudioManager* audio_manager) {
+ return AudioDeviceInfoAccessorForTests(audio_manager)
+ .GetDefaultOutputStreamParameters();
+ }
+
+ static StreamType* CreateStream(AudioManager* audio_manager,
+ const AudioParameters& params) {
+ return audio_manager->MakeAudioOutputStream(
+ params, std::string(), base::BindRepeating(&OnLogMessage));
+ }
+};
+
+// Traits template holding a trait of StreamType. It encapsulates
+// AudioInputStream and AudioOutputStream stream types.
+template <typename StreamTraits>
+class StreamWrapper {
+ public:
+ typedef typename StreamTraits::StreamType StreamType;
+
+ explicit StreamWrapper(AudioManager* audio_manager)
+ : audio_manager_(audio_manager),
+ format_(AudioParameters::AUDIO_PCM_LOW_LATENCY),
+#if defined(OS_ANDROID)
+ channel_layout_(CHANNEL_LAYOUT_MONO)
+#else
+ channel_layout_(CHANNEL_LAYOUT_STEREO)
+#endif
+ {
+ // Use the preferred sample rate.
+ const AudioParameters& params =
+ StreamTraits::GetDefaultAudioStreamParameters(audio_manager_);
+ sample_rate_ = params.sample_rate();
+
+ // Use the preferred buffer size. Note that the input side uses the same
+ // size as the output side in this implementation.
+ samples_per_packet_ = params.frames_per_buffer();
+ }
+
+ virtual ~StreamWrapper() = default;
+
+ // Creates an Audio[Input|Output]Stream stream object using default
+ // parameters.
+ StreamType* Create() {
+ return CreateStream();
+ }
+
+ int channels() const {
+ return ChannelLayoutToChannelCount(channel_layout_);
+ }
+ int sample_rate() const { return sample_rate_; }
+ int samples_per_packet() const { return samples_per_packet_; }
+
+ private:
+ StreamType* CreateStream() {
+ StreamType* stream = StreamTraits::CreateStream(
+ audio_manager_, AudioParameters(format_, channel_layout_, sample_rate_,
+ samples_per_packet_));
+ EXPECT_TRUE(stream);
+ return stream;
+ }
+
+ AudioManager* audio_manager_;
+ AudioParameters::Format format_;
+ ChannelLayout channel_layout_;
+ int sample_rate_;
+ int samples_per_packet_;
+};
+
+typedef StreamWrapper<AudioInputStreamTraits> AudioInputStreamWrapper;
+typedef StreamWrapper<AudioOutputStreamTraits> AudioOutputStreamWrapper;
+
+// This test is intended for manual tests and should only be enabled
+// when it is required to make a real-time test of audio in full duplex and
+// at the same time create a text file which contains measured delay values.
+// The file can later be analyzed off line using e.g. MATLAB.
+// MATLAB example:
+// D=load('audio_delay_values_ms.txt');
+// x=cumsum(D(:,1));
+// plot(x, D(:,2), x, D(:,3), x, D(:,4), x, D(:,2)+D(:,3)+D(:,4));
+// axis([0, max(x), 0, max(D(:,2)+D(:,3)+D(:,4))+10]);
+// legend('buffer delay','input delay','output delay','total delay');
+// xlabel('time [msec]')
+// ylabel('delay [msec]')
+// title('Full-duplex audio delay measurement');
+TEST_F(AudioLowLatencyInputOutputTest, DISABLED_FullDuplexDelayMeasurement) {
+ AudioDeviceInfoAccessorForTests device_info_accessor(audio_manager());
+ ABORT_AUDIO_TEST_IF_NOT(device_info_accessor.HasAudioInputDevices() &&
+ device_info_accessor.HasAudioOutputDevices());
+
+ AudioInputStreamWrapper aisw(audio_manager());
+ AudioInputStream* ais = aisw.Create();
+ EXPECT_TRUE(ais);
+
+ AudioOutputStreamWrapper aosw(audio_manager());
+ AudioOutputStream* aos = aosw.Create();
+ EXPECT_TRUE(aos);
+
+ // This test only supports identical parameters in both directions.
+ // TODO(henrika): it is possible to cut delay here by using different
+ // buffer sizes for input and output.
+ if (aisw.sample_rate() != aosw.sample_rate() ||
+ aisw.samples_per_packet() != aosw.samples_per_packet() ||
+ aisw.channels() != aosw.channels()) {
+ LOG(ERROR) << "This test requires symmetric input and output parameters. "
+ "Ensure that sample rate and number of channels are identical in "
+ "both directions";
+ aos->Close();
+ ais->Close();
+ return;
+ }
+
+ EXPECT_EQ(ais->Open(), AudioInputStream::OpenOutcome::kSuccess);
+ EXPECT_TRUE(aos->Open());
+
+ FullDuplexAudioSinkSource full_duplex(
+ aisw.sample_rate(), aisw.samples_per_packet(), aisw.channels());
+
+ VLOG(0) << ">> You should now be able to hear yourself in loopback...";
+ DVLOG(0) << " sample_rate : " << aisw.sample_rate();
+ DVLOG(0) << " samples_per_packet: " << aisw.samples_per_packet();
+ DVLOG(0) << " channels : " << aisw.channels();
+
+ ais->Start(&full_duplex);
+ aos->Start(&full_duplex);
+
+ // Wait for approximately 10 seconds. The user will hear their own voice
+ // in loop back during this time. At the same time, delay recordings are
+ // performed and stored in the output text file.
+ base::RunLoop run_loop;
+ task_runner()->PostDelayedTask(
+ FROM_HERE, run_loop.QuitClosure(), TestTimeouts::action_timeout());
+ run_loop.Run();
+
+ aos->Stop();
+ ais->Stop();
+
+ // All Close() operations that run on the mocked audio thread,
+ // should be synchronous and not post additional close tasks to
+ // mocked the audio thread. Hence, there is no need to call
+ // message_loop()->RunUntilIdle() after the Close() methods.
+ aos->Close();
+ ais->Close();
+}
+
+} // namespace
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_manager.cc b/third_party/chromium/media/audio/audio_manager.cc
new file mode 100644
index 0000000..0c33df6
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_manager.cc
@@ -0,0 +1,186 @@
+// Copyright (c) 2012 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 "media/audio/audio_manager.h"
+
+#include <stdint.h>
+
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/power_monitor/power_monitor.h"
+#include "base/single_thread_task_runner.h"
+#include "base/thread_annotations.h"
+#include "build/build_config.h"
+#include "media/audio/fake_audio_log_factory.h"
+#include "media/base/media_switches.h"
+
+#if defined(OS_WIN)
+#include "base/win/scoped_com_initializer.h"
+#endif
+
+namespace media {
+namespace {
+
+// The singleton instance of AudioManager. This is set when Create() is called.
+AudioManager* g_last_created = nullptr;
+
+// Helper class for managing global AudioManager data.
+class AudioManagerHelper {
+ public:
+ AudioManagerHelper() = default;
+
+ AudioManagerHelper(const AudioManagerHelper&) = delete;
+ AudioManagerHelper& operator=(const AudioManagerHelper&) = delete;
+
+ ~AudioManagerHelper() = default;
+
+ AudioLogFactory* fake_log_factory() { return &fake_log_factory_; }
+
+#if defined(OS_WIN)
+ // This should be called before creating an AudioManager in tests to ensure
+ // that the creating thread is COM initialized.
+ void InitializeCOMForTesting() {
+ com_initializer_for_testing_ =
+ std::make_unique<base::win::ScopedCOMInitializer>();
+ }
+#endif
+
+ void set_app_name(const std::string& app_name) { app_name_ = app_name; }
+ const std::string& app_name() const { return app_name_; }
+
+ FakeAudioLogFactory fake_log_factory_;
+
+#if defined(OS_WIN)
+ std::unique_ptr<base::win::ScopedCOMInitializer> com_initializer_for_testing_;
+#endif
+
+ std::string app_name_;
+};
+
+AudioManagerHelper* GetHelper() {
+ static AudioManagerHelper* helper = new AudioManagerHelper();
+ return helper;
+}
+
+} // namespace
+
+// Forward declaration of the platform specific AudioManager factory function.
+std::unique_ptr<AudioManager> CreateAudioManager(
+ std::unique_ptr<AudioThread> audio_thread,
+ AudioLogFactory* audio_log_factory);
+
+void AudioManager::SetMaxStreamCountForTesting(int max_input, int max_output) {
+ NOTREACHED();
+}
+
+AudioManager::AudioManager(std::unique_ptr<AudioThread> audio_thread)
+ : audio_thread_(std::move(audio_thread)) {
+ DCHECK(audio_thread_);
+
+ if (g_last_created) {
+ // We create multiple instances of AudioManager only when testing.
+ // We should not encounter this case in production.
+ LOG(WARNING) << "Multiple instances of AudioManager detected";
+ }
+ // We always override |g_last_created| irrespective of whether it is already
+ // set or not because it represents the last created instance.
+ g_last_created = this;
+}
+
+AudioManager::~AudioManager() {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ DCHECK(shutdown_);
+
+ if (g_last_created == this) {
+ g_last_created = nullptr;
+ } else {
+ // We create multiple instances of AudioManager only when testing.
+ // We should not encounter this case in production.
+ LOG(WARNING) << "Multiple instances of AudioManager detected";
+ }
+}
+
+// static
+std::unique_ptr<AudioManager> AudioManager::Create(
+ std::unique_ptr<AudioThread> audio_thread,
+ AudioLogFactory* audio_log_factory) {
+ std::unique_ptr<AudioManager> manager =
+ CreateAudioManager(std::move(audio_thread), audio_log_factory);
+ manager->InitializeDebugRecording();
+ return manager;
+}
+
+// static
+std::unique_ptr<AudioManager> AudioManager::CreateForTesting(
+ std::unique_ptr<AudioThread> audio_thread) {
+#if defined(OS_WIN)
+ GetHelper()->InitializeCOMForTesting();
+#endif
+ return Create(std::move(audio_thread), GetHelper()->fake_log_factory());
+}
+
+// static
+void AudioManager::SetGlobalAppName(const std::string& app_name) {
+ GetHelper()->set_app_name(app_name);
+}
+
+// static
+const std::string& AudioManager::GetGlobalAppName() {
+ return GetHelper()->app_name();
+}
+
+// static
+AudioManager* AudioManager::Get() {
+ return g_last_created;
+}
+
+bool AudioManager::Shutdown() {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+ if (audio_thread_->GetTaskRunner()->BelongsToCurrentThread()) {
+ // If this is the audio thread, there is no need to check if it's hung
+ // (since it's clearly not). https://crbug.com/919854.
+ ShutdownOnAudioThread();
+ } else {
+ // Do not attempt to stop the audio thread if it is hung.
+ // Otherwise the current thread will hang too: https://crbug.com/729494
+ // TODO(olka, grunell): Will be fixed when audio is its own process.
+ if (audio_thread_->IsHung())
+ return false;
+
+ audio_thread_->GetTaskRunner()->PostTask(
+ FROM_HERE, base::BindOnce(&AudioManager::ShutdownOnAudioThread,
+ base::Unretained(this)));
+ }
+ audio_thread_->Stop();
+ shutdown_ = true;
+ return true;
+}
+
+void AudioManager::SetDiverterCallbacks(
+ AddDiverterCallback add_callback,
+ RemoveDiverterCallback remove_callback) {
+ add_diverter_callback_ = std::move(add_callback);
+ remove_diverter_callback_ = std::move(remove_callback);
+}
+
+void AudioManager::AddDiverter(const base::UnguessableToken& group_id,
+ media::AudioSourceDiverter* diverter) {
+ if (!add_diverter_callback_.is_null())
+ add_diverter_callback_.Run(group_id, diverter);
+}
+
+void AudioManager::RemoveDiverter(media::AudioSourceDiverter* diverter) {
+ if (!remove_diverter_callback_.is_null())
+ remove_diverter_callback_.Run(diverter);
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_manager.h b/third_party/chromium/media/audio/audio_manager.h
new file mode 100644
index 0000000..bb2fc8b
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_manager.h
@@ -0,0 +1,284 @@
+// Copyright (c) 2012 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 MEDIA_AUDIO_AUDIO_MANAGER_H_
+#define MEDIA_AUDIO_AUDIO_MANAGER_H_
+
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/threading/thread_checker.h"
+#include "build/build_config.h"
+#include "media/audio/audio_device_description.h"
+#include "media/audio/audio_logging.h"
+#include "media/audio/audio_thread.h"
+#include "media/base/audio_parameters.h"
+
+namespace base {
+class SingleThreadTaskRunner;
+class UnguessableToken;
+}
+
+namespace media {
+
+class AudioDebugRecordingManager;
+class AudioInputStream;
+class AudioManager;
+class AudioOutputStream;
+class AudioSourceDiverter;
+
+// Manages all audio resources. Provides some convenience functions that avoid
+// the need to provide iterators over the existing streams.
+class MEDIA_EXPORT AudioManager {
+ public:
+ AudioManager(const AudioManager&) = delete;
+ AudioManager& operator=(const AudioManager&) = delete;
+
+ virtual ~AudioManager();
+
+ // Construct the audio manager; only one instance is allowed.
+ //
+ // The manager will forward CreateAudioLog() calls to the provided
+ // AudioLogFactory; as such |audio_log_factory| must outlive the AudioManager.
+ //
+ // The manager will use |audio_thread->GetTaskRunner()| for audio IO.
+ // On OS_MAC, CoreAudio requires that |audio_thread->GetTaskRunner()|
+ // must belong to the main thread of the process, which in our case is sadly
+ // the browser UI thread. Failure to execute calls on the right thread leads
+ // to crashes and odd behavior. See http://crbug.com/158170.
+ //
+ // The manager will use |audio_thread->GetWorkerTaskRunner()| for heavyweight
+ // tasks. The |audio_thread->GetWorkerTaskRunner()| may be the same as
+ // |audio_thread->GetTaskRunner()|.
+ static std::unique_ptr<AudioManager> Create(
+ std::unique_ptr<AudioThread> audio_thread,
+ AudioLogFactory* audio_log_factory);
+
+ // A convenience wrapper of AudioManager::Create for testing.
+ static std::unique_ptr<AudioManager> CreateForTesting(
+ std::unique_ptr<AudioThread> audio_thread);
+
+ // Sets the name of the audio source as seen by external apps.
+ static void SetGlobalAppName(const std::string& app_name);
+
+ // Returns the app name or an empty string if it is not set.
+ static const std::string& GetGlobalAppName();
+
+ // Returns the pointer to the last created instance, or NULL if not yet
+ // created. This is a utility method for the code outside of media directory,
+ // like src/chrome.
+ static AudioManager* Get();
+
+ // Synchronously releases all audio resources.
+ // Must be called before deletion and on the same thread as AudioManager
+ // was created.
+ // Returns true on success but false if AudioManager could not be shutdown.
+ // AudioManager instance must not be deleted if shutdown failed.
+ virtual bool Shutdown();
+
+ // Log callback used for sending log messages from a stream to the object
+ // that manages the stream.
+ using LogCallback = base::RepeatingCallback<void(const std::string&)>;
+
+ // Factory for all the supported stream formats. |params| defines parameters
+ // of the audio stream to be created.
+ //
+ // |params.sample_per_packet| is the requested buffer allocation which the
+ // audio source thinks it can usually fill without blocking. Internally two
+ // or three buffers are created, one will be locked for playback and one will
+ // be ready to be filled in the call to AudioSourceCallback::OnMoreData().
+ //
+ // To create a stream for the default output device, pass an empty string
+ // for |device_id|, otherwise the specified audio device will be opened.
+ //
+ // Returns NULL if the combination of the parameters is not supported, or if
+ // we have reached some other platform specific limit.
+ //
+ // |params.format| can be set to AUDIO_PCM_LOW_LATENCY and that has two
+ // effects:
+ // 1- Instead of triple buffered the audio will be double buffered.
+ // 2- A low latency driver or alternative audio subsystem will be used when
+ // available.
+ //
+ // Do not free the returned AudioOutputStream. It is owned by AudioManager.
+ virtual AudioOutputStream* MakeAudioOutputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) = 0;
+
+ // Creates new audio output proxy. A proxy implements
+ // AudioOutputStream interface, but unlike regular output stream
+ // created with MakeAudioOutputStream() it opens device only when a
+ // sound is actually playing.
+ virtual AudioOutputStream* MakeAudioOutputStreamProxy(
+ const AudioParameters& params,
+ const std::string& device_id) = 0;
+
+ // Factory to create audio recording streams.
+ // |channels| can be 1 or 2.
+ // |sample_rate| is in hertz and can be any value supported by the platform.
+ // |samples_per_packet| is in hertz as well and can be 0 to |sample_rate|,
+ // with 0 suggesting that the implementation use a default value for that
+ // platform.
+ // Returns NULL if the combination of the parameters is not supported, or if
+ // we have reached some other platform specific limit.
+ //
+ // Do not free the returned AudioInputStream. It is owned by AudioManager.
+ // When you are done with it, call |Stop()| and |Close()| to release it.
+ virtual AudioInputStream* MakeAudioInputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) = 0;
+
+ // Returns the task runner used for audio IO.
+ base::SingleThreadTaskRunner* GetTaskRunner() const {
+ return audio_thread_->GetTaskRunner();
+ }
+
+ // Heavyweight tasks should use GetWorkerTaskRunner() instead of
+ // GetTaskRunner(). On most platforms they are the same, but some share the
+ // UI loop with the audio IO loop.
+ base::SingleThreadTaskRunner* GetWorkerTaskRunner() const {
+ return audio_thread_->GetWorkerTaskRunner();
+ }
+
+ // Allows clients to listen for device state changes; e.g. preferred sample
+ // rate or channel layout changes. The typical response to receiving this
+ // callback is to recreate the stream.
+ class AudioDeviceListener {
+ public:
+ virtual void OnDeviceChange() = 0;
+ };
+
+ virtual void AddOutputDeviceChangeListener(AudioDeviceListener* listener) = 0;
+ virtual void RemoveOutputDeviceChangeListener(
+ AudioDeviceListener* listener) = 0;
+
+ // Create a new AudioLog object for tracking the behavior for one or more
+ // instances of the given component. See AudioLogFactory for more details.
+ virtual std::unique_ptr<AudioLog> CreateAudioLog(
+ AudioLogFactory::AudioComponent component,
+ int component_id) = 0;
+
+ // Get debug recording manager. This can only be called on AudioManager's
+ // thread (GetTaskRunner()).
+ virtual AudioDebugRecordingManager* GetAudioDebugRecordingManager() = 0;
+
+ // Gets the name of the audio manager (e.g., Windows, Mac, PulseAudio).
+ virtual const char* GetName() = 0;
+
+ // Limits the number of streams that can be created for testing purposes.
+ virtual void SetMaxStreamCountForTesting(int max_input, int max_output);
+
+ // TODO(crbug/824019): The following are temporary, as a middle-ground step
+ // necessary to resolve a chicken-and-egg problem as we migrate audio
+ // mirroring into the new AudioService. Add/RemoveDiverter() allow
+ // AudioOutputController to (de)register itself as an AudioSourceDiverter,
+ // while SetDiverterCallbacks() allows the entity that is interested in such
+ // notifications to receive them.
+ using AddDiverterCallback =
+ base::RepeatingCallback<void(const base::UnguessableToken&,
+ media::AudioSourceDiverter*)>;
+ using RemoveDiverterCallback =
+ base::RepeatingCallback<void(media::AudioSourceDiverter*)>;
+ virtual void SetDiverterCallbacks(AddDiverterCallback add_callback,
+ RemoveDiverterCallback remove_callback);
+ virtual void AddDiverter(const base::UnguessableToken& group_id,
+ media::AudioSourceDiverter* diverter);
+ virtual void RemoveDiverter(media::AudioSourceDiverter* diverter);
+
+ protected:
+ FRIEND_TEST_ALL_PREFIXES(AudioManagerTest, AudioDebugRecording);
+ friend class AudioDeviceInfoAccessorForTests;
+
+ explicit AudioManager(std::unique_ptr<AudioThread> audio_thread);
+
+ virtual void ShutdownOnAudioThread() = 0;
+
+ // Initializes debug recording. Can be called on any thread; will post to the
+ // audio thread if not called on it.
+ virtual void InitializeDebugRecording() = 0;
+
+ // Returns true if the OS reports existence of audio devices. This does not
+ // guarantee that the existing devices support all formats and sample rates.
+ virtual bool HasAudioOutputDevices() = 0;
+
+ // Returns true if the OS reports existence of audio recording devices. This
+ // does not guarantee that the existing devices support all formats and
+ // sample rates.
+ virtual bool HasAudioInputDevices() = 0;
+
+ // Appends a list of available input devices to |device_descriptions|,
+ // which must initially be empty. It is not guaranteed that all the
+ // devices in the list support all formats and sample rates for
+ // recording.
+ //
+ // Not threadsafe; in production this should only be called from the
+ // Audio worker thread (see GetTaskRunner()).
+ virtual void GetAudioInputDeviceDescriptions(
+ AudioDeviceDescriptions* device_descriptions) = 0;
+
+ // Appends a list of available output devices to |device_descriptions|,
+ // which must initially be empty.
+ //
+ // Not threadsafe; in production this should only be called from the
+ // Audio worker thread (see GetTaskRunner()).
+ virtual void GetAudioOutputDeviceDescriptions(
+ AudioDeviceDescriptions* device_descriptions) = 0;
+
+ // Returns the default output hardware audio parameters for opening output
+ // streams. It is a convenience interface to
+ // AudioManagerBase::GetPreferredOutputStreamParameters and each AudioManager
+ // does not need their own implementation to this interface.
+ // TODO(tommi): Remove this method and use GetOutputStreamParameteres instead.
+ virtual AudioParameters GetDefaultOutputStreamParameters() = 0;
+
+ // Returns the output hardware audio parameters for a specific output device.
+ virtual AudioParameters GetOutputStreamParameters(
+ const std::string& device_id) = 0;
+
+ // Returns the input hardware audio parameters of the specific device
+ // for opening input streams. Each AudioManager needs to implement their own
+ // version of this interface.
+ virtual AudioParameters GetInputStreamParameters(
+ const std::string& device_id) = 0;
+
+ // Returns the device id of an output device that belongs to the same hardware
+ // as the specified input device.
+ // If the hardware has only an input device (e.g. a webcam), the return value
+ // will be empty (which the caller can then interpret to be the default output
+ // device). Implementations that don't yet support this feature, must return
+ // an empty string. Must be called on the audio worker thread (see
+ // GetTaskRunner()).
+ virtual std::string GetAssociatedOutputDeviceID(
+ const std::string& input_device_id) = 0;
+
+ // These functions return the ID of the default/communications audio
+ // input/output devices respectively.
+ // Implementations that do not support this functionality should return an
+ // empty string.
+ virtual std::string GetDefaultInputDeviceID() = 0;
+ virtual std::string GetDefaultOutputDeviceID() = 0;
+ virtual std::string GetCommunicationsInputDeviceID() = 0;
+ virtual std::string GetCommunicationsOutputDeviceID() = 0;
+
+ private:
+ friend class AudioSystemHelper;
+
+ std::unique_ptr<AudioThread> audio_thread_;
+ bool shutdown_ = false; // True after |this| has been shutdown.
+
+ AddDiverterCallback add_diverter_callback_;
+ RemoveDiverterCallback remove_diverter_callback_;
+
+ THREAD_CHECKER(thread_checker_);
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_MANAGER_H_
diff --git a/third_party/chromium/media/audio/audio_manager_base.cc b/third_party/chromium/media/audio/audio_manager_base.cc
new file mode 100644
index 0000000..5400e09
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_manager_base.cc
@@ -0,0 +1,632 @@
+// Copyright (c) 2012 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 "media/audio/audio_manager_base.h"
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/command_line.h"
+#include "base/macros.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "build/buildflag.h"
+#include "media/audio/audio_device_description.h"
+#include "media/audio/audio_output_dispatcher_impl.h"
+#include "media/audio/audio_output_proxy.h"
+#include "media/audio/audio_output_resampler.h"
+#include "media/audio/fake_audio_input_stream.h"
+#include "media/audio/fake_audio_output_stream.h"
+#include "media/base/media_switches.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+#include "base/logging.h"
+#include "build/chromeos_buildflags.h"
+#include "media/audio/audio_input_stream_data_interceptor.h"
+
+namespace media {
+
+namespace {
+
+const int kStreamCloseDelaySeconds = 5;
+
+// Default maximum number of output streams that can be open simultaneously
+// for all platforms.
+const int kDefaultMaxOutputStreams = 16;
+
+// Default maximum number of input streams that can be open simultaneously
+// for all platforms.
+const int kMaxInputStreams = 16;
+
+const int kMaxInputChannels = 3;
+
+// Helper function to pass as callback when the audio debug recording is not
+// enabled.
+std::unique_ptr<AudioDebugRecorder> GetNullptrAudioDebugRecorder(
+ const AudioParameters& params) {
+ return nullptr;
+}
+
+// This enum must match the numbering for AudioOutputProxyStreamFormat in
+// enums.xml. Do not reorder or remove items, only add new items before
+// STREAM_FORMAT_MAX.
+enum StreamFormat {
+ STREAM_FORMAT_BITSTREAM = 0,
+ STREAM_FORMAT_PCM_LINEAR = 1,
+ STREAM_FORMAT_PCM_LOW_LATENCY = 2,
+ STREAM_FORMAT_PCM_LOW_LATENCY_FALLBACK_TO_FAKE = 3,
+ STREAM_FORMAT_FAKE = 4,
+ STREAM_FORMAT_MAX = 4,
+};
+
+PRINTF_FORMAT(2, 3)
+void SendLogMessage(const AudioManagerBase::LogCallback& callback,
+ const char* format,
+ ...) {
+ if (callback.is_null())
+ return;
+ va_list args;
+ va_start(args, format);
+ callback.Run("AMB::" + base::StringPrintV(format, args));
+ va_end(args);
+}
+
+} // namespace
+
+struct AudioManagerBase::DispatcherParams {
+ DispatcherParams(const AudioParameters& input,
+ const AudioParameters& output,
+ const std::string& output_device_id)
+ : input_params(input),
+ output_params(output),
+ output_device_id(output_device_id) {}
+ ~DispatcherParams() = default;
+
+ const AudioParameters input_params;
+ const AudioParameters output_params;
+ const std::string output_device_id;
+ std::unique_ptr<AudioOutputDispatcher> dispatcher;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DispatcherParams);
+};
+
+class AudioManagerBase::CompareByParams {
+ public:
+ explicit CompareByParams(const DispatcherParams* dispatcher)
+ : dispatcher_(dispatcher) {}
+ bool operator()(
+ const std::unique_ptr<DispatcherParams>& dispatcher_in) const {
+ // We will reuse the existing dispatcher when:
+ // 1) Unified IO is not used, input_params and output_params of the
+ // existing dispatcher are the same as the requested dispatcher.
+ // 2) Unified IO is used, input_params and output_params of the existing
+ // dispatcher are the same as the request dispatcher.
+ return (dispatcher_->input_params.Equals(dispatcher_in->input_params) &&
+ dispatcher_->output_params.Equals(dispatcher_in->output_params) &&
+ dispatcher_->output_device_id == dispatcher_in->output_device_id);
+ }
+
+ private:
+ const DispatcherParams* dispatcher_;
+};
+
+AudioManagerBase::AudioManagerBase(std::unique_ptr<AudioThread> audio_thread,
+ AudioLogFactory* audio_log_factory)
+ : AudioManager(std::move(audio_thread)),
+ max_num_output_streams_(kDefaultMaxOutputStreams),
+ num_output_streams_(0),
+ // TODO(dalecurtis): Switch this to an base::ObserverListThreadSafe, so we
+ // don't block the UI thread when swapping devices.
+ output_listeners_(base::ObserverListPolicy::EXISTING_ONLY),
+ audio_log_factory_(audio_log_factory) {}
+
+AudioManagerBase::~AudioManagerBase() {
+ // All the output streams should have been deleted.
+ CHECK_EQ(0, num_output_streams_);
+ // All the input streams should have been deleted.
+ CHECK(input_streams_.empty());
+}
+
+void AudioManagerBase::GetAudioInputDeviceDescriptions(
+ AudioDeviceDescriptions* device_descriptions) {
+ CHECK(GetTaskRunner()->BelongsToCurrentThread());
+ GetAudioDeviceDescriptions(device_descriptions,
+ &AudioManagerBase::GetAudioInputDeviceNames,
+ &AudioManagerBase::GetDefaultInputDeviceID,
+ &AudioManagerBase::GetCommunicationsInputDeviceID,
+ &AudioManagerBase::GetGroupIDInput);
+}
+
+void AudioManagerBase::GetAudioOutputDeviceDescriptions(
+ AudioDeviceDescriptions* device_descriptions) {
+ CHECK(GetTaskRunner()->BelongsToCurrentThread());
+ GetAudioDeviceDescriptions(device_descriptions,
+ &AudioManagerBase::GetAudioOutputDeviceNames,
+ &AudioManagerBase::GetDefaultOutputDeviceID,
+ &AudioManagerBase::GetCommunicationsOutputDeviceID,
+ &AudioManagerBase::GetGroupIDOutput);
+}
+
+void AudioManagerBase::GetAudioDeviceDescriptions(
+ AudioDeviceDescriptions* device_descriptions,
+ void (AudioManagerBase::*get_device_names)(AudioDeviceNames*),
+ std::string (AudioManagerBase::*get_default_device_id)(),
+ std::string (AudioManagerBase::*get_communications_device_id)(),
+ std::string (AudioManagerBase::*get_group_id)(const std::string&)) {
+ CHECK(GetTaskRunner()->BelongsToCurrentThread());
+ AudioDeviceNames device_names;
+ (this->*get_device_names)(&device_names);
+ std::string real_default_device_id = (this->*get_default_device_id)();
+ std::string real_communications_device_id =
+ (this->*get_communications_device_id)();
+ std::string real_default_name;
+ std::string real_communications_name;
+
+ // Find the names for the real devices that are mapped to the default and
+ // communications devices.
+ for (const auto& name : device_names) {
+ if (name.unique_id == real_default_device_id)
+ real_default_name = name.device_name;
+ if (name.unique_id == real_communications_device_id)
+ real_communications_name = name.device_name;
+ }
+
+ for (auto& name : device_names) {
+ if (AudioDeviceDescription::IsDefaultDevice(name.unique_id))
+ name.device_name = real_default_name;
+ else if (AudioDeviceDescription::IsCommunicationsDevice(name.unique_id))
+ name.device_name = real_communications_name;
+ std::string group_id = (this->*get_group_id)(name.unique_id);
+ device_descriptions->emplace_back(std::move(name.device_name),
+ std::move(name.unique_id),
+ std::move(group_id));
+ }
+}
+
+AudioOutputStream* AudioManagerBase::MakeAudioOutputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) {
+ CHECK(GetTaskRunner()->BelongsToCurrentThread());
+ DCHECK(params.IsValid());
+
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kFailAudioStreamCreation)) {
+ return nullptr;
+ }
+
+ SendLogMessage(log_callback, "%s({device_id=%s}, {params=[%s]})", __func__,
+ device_id.c_str(), params.AsHumanReadableString().c_str());
+
+ // Limit the number of audio streams opened. This is to prevent using
+ // excessive resources for a large number of audio streams. More
+ // importantly it prevents instability on certain systems.
+ // See bug: http://crbug.com/30242.
+ if (num_output_streams_ >= max_num_output_streams_) {
+ LOG(ERROR) << "Number of opened output audio streams "
+ << num_output_streams_ << " exceed the max allowed number "
+ << max_num_output_streams_;
+ return nullptr;
+ }
+
+ AudioOutputStream* stream;
+ switch (params.format()) {
+ case AudioParameters::AUDIO_PCM_LINEAR:
+ DCHECK(AudioDeviceDescription::IsDefaultDevice(device_id))
+ << "AUDIO_PCM_LINEAR supports only the default device.";
+ stream = MakeLinearOutputStream(params, log_callback);
+ break;
+ case AudioParameters::AUDIO_PCM_LOW_LATENCY:
+ stream = MakeLowLatencyOutputStream(params, device_id, log_callback);
+ break;
+ case AudioParameters::AUDIO_BITSTREAM_AC3:
+ case AudioParameters::AUDIO_BITSTREAM_EAC3:
+ stream = MakeBitstreamOutputStream(params, device_id, log_callback);
+ break;
+ case AudioParameters::AUDIO_FAKE:
+ stream = FakeAudioOutputStream::MakeFakeStream(this, params);
+ break;
+ default:
+ stream = nullptr;
+ break;
+ }
+
+ if (stream) {
+ ++num_output_streams_;
+ SendLogMessage(log_callback, "%s => (number of streams=%d)", __func__,
+ output_stream_count());
+ }
+
+ return stream;
+}
+
+AudioOutputStream* AudioManagerBase::MakeBitstreamOutputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) {
+ return nullptr;
+}
+
+AudioInputStream* AudioManagerBase::MakeAudioInputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) {
+ CHECK(GetTaskRunner()->BelongsToCurrentThread());
+
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kFailAudioStreamCreation)) {
+ return nullptr;
+ }
+
+ SendLogMessage(log_callback, "%s({device_id=%s}, {params=[%s]})", __func__,
+ device_id.c_str(), params.AsHumanReadableString().c_str());
+
+ if (!params.IsValid() || (params.channels() > kMaxInputChannels) ||
+ device_id.empty()) {
+ DLOG(ERROR) << "Audio parameters are invalid for device " << device_id
+ << ", params: " << params.AsHumanReadableString();
+ return nullptr;
+ }
+
+ if (input_stream_count() >= kMaxInputStreams) {
+ LOG(ERROR) << "Number of opened input audio streams "
+ << input_stream_count() << " exceed the max allowed number "
+ << kMaxInputStreams;
+ return nullptr;
+ }
+
+ DVLOG(2) << "Creating a new AudioInputStream with buffer size = "
+ << params.frames_per_buffer();
+
+ AudioInputStream* stream;
+ switch (params.format()) {
+ case AudioParameters::AUDIO_PCM_LINEAR:
+ stream = MakeLinearInputStream(params, device_id, log_callback);
+ break;
+ case AudioParameters::AUDIO_PCM_LOW_LATENCY:
+ stream = MakeLowLatencyInputStream(params, device_id, log_callback);
+ break;
+ case AudioParameters::AUDIO_FAKE:
+ stream = FakeAudioInputStream::MakeFakeStream(this, params);
+ break;
+ default:
+ stream = nullptr;
+ break;
+ }
+
+ if (stream) {
+ input_streams_.insert(stream);
+ if (!log_callback.is_null()) {
+ SendLogMessage(log_callback, "%s => (number of streams=%d)", __func__,
+ input_stream_count());
+ }
+
+ if (!params.IsBitstreamFormat() && debug_recording_manager_) {
+ // Using unretained for |debug_recording_manager_| is safe since it
+ // outlives the audio thread, on which streams are operated.
+ // Note: The AudioInputStreamDataInterceptor takes ownership of the
+ // created stream and cleans it up when it is Close()d, transparently to
+ // the user of the stream. I the case where the audio manager closes the
+ // stream (Mac), this will result in a dangling pointer.
+ stream = new AudioInputStreamDataInterceptor(
+ base::BindRepeating(
+ &AudioDebugRecordingManager::RegisterDebugRecordingSource,
+ base::Unretained(debug_recording_manager_.get()),
+ AudioDebugRecordingStreamType::kInput, params),
+ stream);
+ }
+ }
+
+ return stream;
+}
+
+AudioOutputStream* AudioManagerBase::MakeAudioOutputStreamProxy(
+ const AudioParameters& params,
+ const std::string& device_id) {
+ CHECK(GetTaskRunner()->BelongsToCurrentThread());
+ DCHECK(params.IsValid());
+ absl::optional<StreamFormat> uma_stream_format;
+
+ // If the caller supplied an empty device id to select the default device,
+ // we fetch the actual device id of the default device so that the lookup
+ // will find the correct device regardless of whether it was opened as
+ // "default" or via the specific id.
+ // NOTE: Implementations that don't yet support opening non-default output
+ // devices may return an empty string from GetDefaultOutputDeviceID().
+ std::string output_device_id =
+ AudioDeviceDescription::IsDefaultDevice(device_id)
+ ?
+#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS)
+ // On ChromeOS, it is expected that, if the default device is given,
+ // no specific device ID should be used since the actual output device
+ // should change dynamically if the system default device changes.
+ // See http://crbug.com/750614.
+ std::string()
+#else
+ GetDefaultOutputDeviceID()
+#endif
+ : device_id;
+
+ // If we're not using AudioOutputResampler our output parameters are the same
+ // as our input parameters.
+ AudioParameters output_params = params;
+
+ // If audio has been disabled force usage of a fake audio stream.
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kDisableAudioOutput)) {
+ output_params.set_format(AudioParameters::AUDIO_FAKE);
+ }
+
+ if (params.format() == AudioParameters::AUDIO_PCM_LOW_LATENCY &&
+ output_params.format() != AudioParameters::AUDIO_FAKE) {
+ output_params =
+ GetPreferredOutputStreamParameters(output_device_id, params);
+
+ // Ensure we only pass on valid output parameters.
+ if (output_params.IsValid()) {
+ if (params.effects() & AudioParameters::MULTIZONE) {
+ // Never turn off the multizone effect even if it is not preferred.
+ output_params.set_effects(output_params.effects() |
+ AudioParameters::MULTIZONE);
+ }
+ if (params.effects() != output_params.effects()) {
+ // Turn off effects that weren't requested.
+ output_params.set_effects(params.effects() & output_params.effects());
+ }
+
+ uma_stream_format = STREAM_FORMAT_PCM_LOW_LATENCY;
+ } else {
+ // We've received invalid audio output parameters, so switch to a mock
+ // output device based on the input parameters. This may happen if the OS
+ // provided us junk values for the hardware configuration.
+ LOG(ERROR) << "Invalid audio output parameters received; using fake "
+ << "audio path: " << output_params.AsHumanReadableString();
+
+ // Tell the AudioManager to create a fake output device.
+ output_params = params;
+ output_params.set_format(AudioParameters::AUDIO_FAKE);
+ uma_stream_format = STREAM_FORMAT_PCM_LOW_LATENCY_FALLBACK_TO_FAKE;
+ }
+
+ output_params.set_latency_tag(params.latency_tag());
+ } else {
+ switch (output_params.format()) {
+ case AudioParameters::AUDIO_PCM_LINEAR:
+ uma_stream_format = STREAM_FORMAT_PCM_LINEAR;
+ break;
+ case AudioParameters::AUDIO_FAKE:
+ uma_stream_format = STREAM_FORMAT_FAKE;
+ break;
+ default:
+ if (output_params.IsBitstreamFormat())
+ uma_stream_format = STREAM_FORMAT_BITSTREAM;
+ else
+ NOTREACHED();
+ }
+ }
+
+ if (uma_stream_format) {
+ UMA_HISTOGRAM_ENUMERATION("Media.AudioOutputStreamProxy.StreamFormat",
+ *uma_stream_format, STREAM_FORMAT_MAX + 1);
+ } else {
+ NOTREACHED();
+ }
+
+ std::unique_ptr<DispatcherParams> dispatcher_params =
+ std::make_unique<DispatcherParams>(params, output_params,
+ output_device_id);
+
+ auto it = std::find_if(output_dispatchers_.begin(), output_dispatchers_.end(),
+ CompareByParams(dispatcher_params.get()));
+ if (it != output_dispatchers_.end())
+ return (*it)->dispatcher->CreateStreamProxy();
+
+ const base::TimeDelta kCloseDelay = base::Seconds(kStreamCloseDelaySeconds);
+ std::unique_ptr<AudioOutputDispatcher> dispatcher;
+ if (output_params.format() != AudioParameters::AUDIO_FAKE &&
+ !output_params.IsBitstreamFormat()) {
+ // Using unretained for |debug_recording_manager_| is safe since it
+ // outlives the dispatchers (cleared in ShutdownOnAudioThread()).
+ dispatcher = std::make_unique<AudioOutputResampler>(
+ this, params, output_params, output_device_id, kCloseDelay,
+ debug_recording_manager_
+ ? base::BindRepeating(
+ &AudioDebugRecordingManager::RegisterDebugRecordingSource,
+ base::Unretained(debug_recording_manager_.get()),
+ AudioDebugRecordingStreamType::kOutput)
+ : base::BindRepeating(&GetNullptrAudioDebugRecorder));
+ } else {
+ dispatcher = std::make_unique<AudioOutputDispatcherImpl>(
+ this, output_params, output_device_id, kCloseDelay);
+ }
+
+ dispatcher_params->dispatcher = std::move(dispatcher);
+ output_dispatchers_.push_back(std::move(dispatcher_params));
+ return output_dispatchers_.back()->dispatcher->CreateStreamProxy();
+}
+
+void AudioManagerBase::GetAudioInputDeviceNames(
+ AudioDeviceNames* device_names) {
+}
+
+void AudioManagerBase::GetAudioOutputDeviceNames(
+ AudioDeviceNames* device_names) {
+}
+
+void AudioManagerBase::ReleaseOutputStream(AudioOutputStream* stream) {
+ CHECK(GetTaskRunner()->BelongsToCurrentThread());
+ DCHECK(stream);
+ CHECK_GT(num_output_streams_, 0);
+ // TODO(xians) : Have a clearer destruction path for the AudioOutputStream.
+ // For example, pass the ownership to AudioManager so it can delete the
+ // streams.
+ --num_output_streams_;
+ delete stream;
+}
+
+void AudioManagerBase::ReleaseInputStream(AudioInputStream* stream) {
+ CHECK(GetTaskRunner()->BelongsToCurrentThread());
+ DCHECK(stream);
+ // TODO(xians) : Have a clearer destruction path for the AudioInputStream.
+ CHECK_EQ(1u, input_streams_.erase(stream));
+ delete stream;
+}
+
+void AudioManagerBase::ShutdownOnAudioThread() {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+
+ // Close all output streams.
+ output_dispatchers_.clear();
+}
+
+void AudioManagerBase::AddOutputDeviceChangeListener(
+ AudioDeviceListener* listener) {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ output_listeners_.AddObserver(listener);
+}
+
+void AudioManagerBase::RemoveOutputDeviceChangeListener(
+ AudioDeviceListener* listener) {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ output_listeners_.RemoveObserver(listener);
+}
+
+void AudioManagerBase::NotifyAllOutputDeviceChangeListeners() {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ DVLOG(1) << "Firing OnDeviceChange() notifications.";
+ for (auto& observer : output_listeners_)
+ observer.OnDeviceChange();
+}
+
+AudioParameters AudioManagerBase::GetDefaultOutputStreamParameters() {
+ return GetPreferredOutputStreamParameters(GetDefaultOutputDeviceID(),
+ AudioParameters());
+}
+
+AudioParameters AudioManagerBase::GetOutputStreamParameters(
+ const std::string& device_id) {
+ return GetPreferredOutputStreamParameters(device_id,
+ AudioParameters());
+}
+
+AudioParameters AudioManagerBase::GetInputStreamParameters(
+ const std::string& device_id) {
+ NOTREACHED();
+ return AudioParameters();
+}
+
+std::string AudioManagerBase::GetAssociatedOutputDeviceID(
+ const std::string& input_device_id) {
+ return std::string();
+}
+
+std::string AudioManagerBase::GetGroupIDOutput(
+ const std::string& output_device_id) {
+ if (output_device_id == AudioDeviceDescription::kDefaultDeviceId) {
+ std::string real_device_id = GetDefaultOutputDeviceID();
+ if (!real_device_id.empty())
+ return real_device_id;
+ } else if (output_device_id ==
+ AudioDeviceDescription::kCommunicationsDeviceId) {
+ std::string real_device_id = GetCommunicationsOutputDeviceID();
+ if (!real_device_id.empty())
+ return real_device_id;
+ }
+ return output_device_id;
+}
+
+std::string AudioManagerBase::GetGroupIDInput(
+ const std::string& input_device_id) {
+ const std::string& real_input_device_id =
+ input_device_id == AudioDeviceDescription::kDefaultDeviceId
+ ? GetDefaultInputDeviceID()
+ : input_device_id == AudioDeviceDescription::kCommunicationsDeviceId
+ ? GetCommunicationsInputDeviceID()
+ : input_device_id;
+ std::string output_device_id =
+ GetAssociatedOutputDeviceID(real_input_device_id);
+ if (output_device_id.empty()) {
+ // Some characters are added to avoid accidentally
+ // giving the input the same group id as an output.
+ return real_input_device_id + "input";
+ }
+ return GetGroupIDOutput(output_device_id);
+}
+
+void AudioManagerBase::CloseAllInputStreams() {
+ for (auto iter = input_streams_.begin(); iter != input_streams_.end();) {
+ // Note: Closing the stream will invalidate the iterator.
+ // Increment the iterator before closing the stream.
+ AudioInputStream* stream = *iter++;
+ stream->Close();
+ }
+ CHECK(input_streams_.empty());
+}
+
+std::string AudioManagerBase::GetDefaultInputDeviceID() {
+ return std::string();
+}
+
+std::string AudioManagerBase::GetDefaultOutputDeviceID() {
+ return std::string();
+}
+
+std::string AudioManagerBase::GetCommunicationsInputDeviceID() {
+ return std::string();
+}
+
+std::string AudioManagerBase::GetCommunicationsOutputDeviceID() {
+ return std::string();
+}
+
+// static
+int AudioManagerBase::GetUserBufferSize() {
+ const base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
+ int buffer_size = 0;
+ std::string buffer_size_str(cmd_line->GetSwitchValueASCII(
+ switches::kAudioBufferSize));
+ if (base::StringToInt(buffer_size_str, &buffer_size) && buffer_size > 0)
+ return buffer_size;
+
+ return 0;
+}
+
+std::unique_ptr<AudioLog> AudioManagerBase::CreateAudioLog(
+ AudioLogFactory::AudioComponent component,
+ int component_id) {
+ return audio_log_factory_->CreateAudioLog(component, component_id);
+}
+
+void AudioManagerBase::InitializeDebugRecording() {
+ if (!GetTaskRunner()->BelongsToCurrentThread()) {
+ // AudioManager is deleted on the audio thread, so it's safe to post
+ // unretained.
+ GetTaskRunner()->PostTask(
+ FROM_HERE, base::BindOnce(&AudioManagerBase::InitializeDebugRecording,
+ base::Unretained(this)));
+ return;
+ }
+
+ DCHECK(!debug_recording_manager_);
+ debug_recording_manager_ = CreateAudioDebugRecordingManager(GetTaskRunner());
+}
+
+std::unique_ptr<AudioDebugRecordingManager>
+AudioManagerBase::CreateAudioDebugRecordingManager(
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
+ return std::make_unique<AudioDebugRecordingManager>(std::move(task_runner));
+}
+
+AudioDebugRecordingManager* AudioManagerBase::GetAudioDebugRecordingManager() {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ return debug_recording_manager_.get();
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_manager_base.h b/third_party/chromium/media/audio/audio_manager_base.h
new file mode 100644
index 0000000..f1e36ca
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_manager_base.h
@@ -0,0 +1,222 @@
+// Copyright (c) 2012 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 MEDIA_AUDIO_AUDIO_MANAGER_BASE_H_
+#define MEDIA_AUDIO_AUDIO_MANAGER_BASE_H_
+
+#include <memory>
+#include <string>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/observer_list.h"
+#include "base/single_thread_task_runner.h"
+#include "base/threading/thread.h"
+#include "build/build_config.h"
+#include "media/audio/audio_debug_recording_manager.h"
+#include "media/audio/audio_device_name.h"
+#include "media/audio/audio_manager.h"
+#include "media/audio/audio_output_dispatcher.h"
+
+#if defined(OS_WIN)
+#include "base/win/scoped_com_initializer.h"
+#endif
+
+namespace media {
+
+class AudioOutputDispatcher;
+
+// AudioManagerBase provides AudioManager functions common for all platforms.
+class MEDIA_EXPORT AudioManagerBase : public AudioManager {
+ public:
+ enum class VoiceProcessingMode { kDisabled = 0, kEnabled = 1 };
+
+ AudioManagerBase(const AudioManagerBase&) = delete;
+ AudioManagerBase& operator=(const AudioManagerBase&) = delete;
+
+ ~AudioManagerBase() override;
+
+ AudioOutputStream* MakeAudioOutputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) override;
+ AudioInputStream* MakeAudioInputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) override;
+ AudioOutputStream* MakeAudioOutputStreamProxy(
+ const AudioParameters& params,
+ const std::string& device_id) override;
+
+ // Listeners will be notified on the GetTaskRunner() task runner.
+ void AddOutputDeviceChangeListener(AudioDeviceListener* listener) override;
+ void RemoveOutputDeviceChangeListener(AudioDeviceListener* listener) override;
+
+ std::unique_ptr<AudioLog> CreateAudioLog(
+ AudioLogFactory::AudioComponent component,
+ int component_id) override;
+
+ // AudioManagerBase:
+
+ // Called internally by the audio stream when it has been closed.
+ virtual void ReleaseOutputStream(AudioOutputStream* stream);
+ virtual void ReleaseInputStream(AudioInputStream* stream);
+
+ // Creates the output stream for the |AUDIO_PCM_LINEAR| format. The legacy
+ // name is also from |AUDIO_PCM_LINEAR|.
+ virtual AudioOutputStream* MakeLinearOutputStream(
+ const AudioParameters& params,
+ const LogCallback& log_callback) = 0;
+
+ // Creates the output stream for the |AUDIO_PCM_LOW_LATENCY| format.
+ virtual AudioOutputStream* MakeLowLatencyOutputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) = 0;
+
+ // Creates the output stream for the |AUDIO_BITSTREAM_XXX| format.
+ virtual AudioOutputStream* MakeBitstreamOutputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback);
+
+ // Creates the input stream for the |AUDIO_PCM_LINEAR| format. The legacy
+ // name is also from |AUDIO_PCM_LINEAR|.
+ virtual AudioInputStream* MakeLinearInputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) = 0;
+
+ // Creates the input stream for the |AUDIO_PCM_LOW_LATENCY| format.
+ virtual AudioInputStream* MakeLowLatencyInputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) = 0;
+
+ // Get number of input or output streams.
+ int input_stream_count() const {
+ return static_cast<int>(input_streams_.size());
+ }
+ int output_stream_count() const { return num_output_streams_; }
+
+ protected:
+ AudioManagerBase(std::unique_ptr<AudioThread> audio_thread,
+ AudioLogFactory* audio_log_factory);
+
+ // AudioManager:
+ void ShutdownOnAudioThread() override;
+
+ void GetAudioInputDeviceDescriptions(
+ AudioDeviceDescriptions* device_descriptions) final;
+ void GetAudioOutputDeviceDescriptions(
+ AudioDeviceDescriptions* device_descriptions) final;
+
+ AudioParameters GetDefaultOutputStreamParameters() override;
+ AudioParameters GetOutputStreamParameters(
+ const std::string& device_id) override;
+ AudioParameters GetInputStreamParameters(
+ const std::string& device_id) override;
+ std::string GetAssociatedOutputDeviceID(
+ const std::string& input_device_id) override;
+
+ void SetMaxOutputStreamsAllowed(int max) { max_num_output_streams_ = max; }
+
+ // Called by each platform specific AudioManager to notify output state change
+ // listeners that a state change has occurred. Must be called from the audio
+ // thread.
+ void NotifyAllOutputDeviceChangeListeners();
+
+ // Returns user buffer size as specified on the command line or 0 if no buffer
+ // size has been specified.
+ static int GetUserBufferSize();
+
+ // Returns the preferred hardware audio output parameters for opening output
+ // streams. If the users inject a valid |input_params|, each AudioManager
+ // will decide if they should return the values from |input_params| or the
+ // default hardware values. If the |input_params| is invalid, it will return
+ // the default hardware audio parameters.
+ // If |output_device_id| is empty, the implementation must treat that as
+ // a request for the default output device.
+ virtual AudioParameters GetPreferredOutputStreamParameters(
+ const std::string& output_device_id,
+ const AudioParameters& input_params) = 0;
+
+ // Appends a list of available input devices to |device_names|,
+ // which must initially be empty.
+ virtual void GetAudioInputDeviceNames(AudioDeviceNames* device_names);
+
+ // Appends a list of available output devices to |device_names|,
+ // which must initially be empty.
+ virtual void GetAudioOutputDeviceNames(AudioDeviceNames* device_names);
+
+ std::string GetDefaultInputDeviceID() override;
+ std::string GetDefaultOutputDeviceID() override;
+ std::string GetCommunicationsInputDeviceID() override;
+ std::string GetCommunicationsOutputDeviceID() override;
+
+ virtual std::unique_ptr<AudioDebugRecordingManager>
+ CreateAudioDebugRecordingManager(
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner);
+ AudioDebugRecordingManager* GetAudioDebugRecordingManager() final;
+
+ // These functions assign group ids to devices based on their device ids. The
+ // default implementation is an attempt to do this based on
+ // GetAssociatedOutputDeviceID. They may be overridden by subclasses that want
+ // a different logic for assigning group ids. Must be called on the audio
+ // worker thread (see GetTaskRunner()).
+ virtual std::string GetGroupIDOutput(const std::string& output_device_id);
+ virtual std::string GetGroupIDInput(const std::string& input_device_id);
+
+ // Closes all currently open input streams.
+ void CloseAllInputStreams();
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(AudioManagerTest, AudioDebugRecording);
+
+ struct DispatcherParams;
+ typedef std::vector<std::unique_ptr<DispatcherParams>> AudioOutputDispatchers;
+
+ class CompareByParams;
+
+ // AudioManager:
+ void InitializeDebugRecording() final;
+
+ void GetAudioDeviceDescriptions(
+ AudioDeviceDescriptions* descriptions,
+ void (AudioManagerBase::*get_device_names)(AudioDeviceNames*),
+ std::string (AudioManagerBase::*get_default_device_id)(),
+ std::string (AudioManagerBase::*get_communications_device_id)(),
+ std::string (AudioManagerBase::*get_group_id)(const std::string&));
+
+ // Max number of open output streams, modified by
+ // SetMaxOutputStreamsAllowed().
+ int max_num_output_streams_;
+
+ // Number of currently open output streams.
+ int num_output_streams_;
+
+ // Track output state change listeners.
+ base::ObserverList<AudioDeviceListener>::Unchecked output_listeners_;
+
+ // Contains currently open input streams.
+ std::unordered_set<AudioInputStream*> input_streams_;
+
+ // Map of cached AudioOutputDispatcher instances. Must only be touched
+ // from the audio thread (no locking).
+ AudioOutputDispatchers output_dispatchers_;
+
+ // Proxy for creating AudioLog objects.
+ AudioLogFactory* const audio_log_factory_;
+
+ // Debug recording manager.
+ std::unique_ptr<AudioDebugRecordingManager> debug_recording_manager_;
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_MANAGER_BASE_H_
diff --git a/third_party/chromium/media/audio/audio_manager_unittest.cc b/third_party/chromium/media/audio/audio_manager_unittest.cc
new file mode 100644
index 0000000..fdf54f8
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_manager_unittest.cc
@@ -0,0 +1,1087 @@
+// Copyright 2013 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 "media/audio/audio_manager.h"
+
+#include <map>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/environment.h"
+#include "base/logging.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/system/sys_info.h"
+#include "base/test/test_message_loop.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
+#include "media/audio/audio_device_description.h"
+#include "media/audio/audio_device_info_accessor_for_tests.h"
+#include "media/audio/audio_device_name.h"
+#include "media/audio/audio_output_proxy.h"
+#include "media/audio/audio_unittest_util.h"
+#include "media/audio/fake_audio_log_factory.h"
+#include "media/audio/fake_audio_manager.h"
+#include "media/audio/test_audio_thread.h"
+#include "media/base/limits.h"
+#include "media/base/media_switches.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(USE_ALSA)
+#include "media/audio/alsa/audio_manager_alsa.h"
+#endif // defined(USE_ALSA)
+
+#if defined(OS_MAC)
+#include "media/audio/mac/audio_manager_mac.h"
+#include "media/base/mac/audio_latency_mac.h"
+#endif
+
+#if defined(OS_WIN)
+#include "base/win/scoped_com_initializer.h"
+#include "media/audio/win/audio_manager_win.h"
+#endif
+
+#if defined(USE_PULSEAUDIO)
+#include "media/audio/pulse/audio_manager_pulse.h"
+#include "media/audio/pulse/pulse_util.h"
+#endif // defined(USE_PULSEAUDIO)
+
+#if defined(USE_CRAS) && BUILDFLAG(IS_CHROMEOS_ASH)
+#include "ash/components/audio/audio_devices_pref_handler_stub.h"
+#include "ash/components/audio/cras_audio_handler.h"
+#include "chromeos/dbus/audio/fake_cras_audio_client.h"
+#include "media/audio/cras/audio_manager_chromeos.h"
+#elif defined(USE_CRAS) && (defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS))
+#include "media/audio/cras/audio_manager_cras.h"
+#endif
+
+namespace media {
+namespace {
+
+#if defined(USE_CRAS) && BUILDFLAG(IS_CHROMEOS_ASH)
+using ::ash::CrasAudioHandler;
+#endif
+
+template <typename T>
+struct TestAudioManagerFactory {
+ static std::unique_ptr<AudioManager> Create(
+ AudioLogFactory* audio_log_factory) {
+ return std::make_unique<T>(std::make_unique<TestAudioThread>(),
+ audio_log_factory);
+ }
+};
+
+#if defined(USE_PULSEAUDIO)
+template <>
+struct TestAudioManagerFactory<AudioManagerPulse> {
+ static std::unique_ptr<AudioManager> Create(
+ AudioLogFactory* audio_log_factory) {
+ pa_threaded_mainloop* pa_mainloop = nullptr;
+ pa_context* pa_context = nullptr;
+ if (!pulse::InitPulse(&pa_mainloop, &pa_context))
+ return nullptr;
+ return std::make_unique<AudioManagerPulse>(
+ std::make_unique<TestAudioThread>(), audio_log_factory, pa_mainloop,
+ pa_context);
+ }
+};
+#endif // defined(USE_PULSEAUDIO)
+
+template <>
+struct TestAudioManagerFactory<std::nullptr_t> {
+ static std::unique_ptr<AudioManager> Create(
+ AudioLogFactory* audio_log_factory) {
+ return AudioManager::CreateForTesting(std::make_unique<TestAudioThread>());
+ }
+};
+
+#if defined(USE_CRAS) && BUILDFLAG(IS_CHROMEOS_ASH)
+using chromeos::AudioNode;
+using chromeos::AudioNodeList;
+
+const int kDefaultSampleRate = 48000;
+
+const uint64_t kInternalSpeakerId = 10001;
+const uint64_t kInternalSpeakerStableDeviceId = 10001;
+const uint64_t kInternalMicId = 10002;
+const uint64_t kInternalMicStableDeviceId = 10002;
+const uint64_t kJabraSpeaker1Id = 30001;
+const uint64_t kJabraSpeaker1StableDeviceId = 80001;
+const uint64_t kJabraSpeaker2Id = 30002;
+const uint64_t kJabraSpeaker2StableDeviceId = 80002;
+const uint64_t kHDMIOutputId = 30003;
+const uint64_t kHDMIOutputStabeDevicelId = 80003;
+const uint64_t kJabraMic1Id = 40001;
+const uint64_t kJabraMic1StableDeviceId = 90001;
+const uint64_t kJabraMic2Id = 40002;
+const uint64_t kJabraMic2StableDeviceId = 90002;
+const uint64_t kWebcamMicId = 40003;
+const uint64_t kWebcamMicStableDeviceId = 90003;
+
+const AudioNode kInternalSpeaker(false,
+ kInternalSpeakerId,
+ true,
+ kInternalSpeakerStableDeviceId,
+ kInternalSpeakerStableDeviceId ^ 0xFF,
+ "Internal Speaker",
+ "INTERNAL_SPEAKER",
+ "Speaker",
+ false,
+ 0,
+ 2,
+ 0);
+
+const AudioNode kInternalMic(true,
+ kInternalMicId,
+ true,
+ kInternalMicStableDeviceId,
+ kInternalMicStableDeviceId ^ 0xFF,
+ "Internal Mic",
+ "INTERNAL_MIC",
+ "Internal Mic",
+ false,
+ 0,
+ 1,
+ 1); // EFFECT_TYPE_NOISE_CANCELLATION
+
+const AudioNode kJabraSpeaker1(false,
+ kJabraSpeaker1Id,
+ true,
+ kJabraSpeaker1StableDeviceId,
+ kJabraSpeaker1StableDeviceId ^ 0xFF,
+ "Jabra Speaker",
+ "USB",
+ "Jabra Speaker 1",
+ false,
+ 0,
+ 2, // expects CHANNEL_LAYOUT_STEREO
+ 0);
+
+const AudioNode kJabraSpeaker2(false,
+ kJabraSpeaker2Id,
+ true,
+ kJabraSpeaker2StableDeviceId,
+ kJabraSpeaker2StableDeviceId ^ 0xFF,
+ "Jabra Speaker",
+ "USB",
+ "Jabra Speaker 2",
+ false,
+ 0,
+ 6, // expects CHANNEL_LAYOUT_5_1
+ 0);
+
+const AudioNode kHDMIOutput(false,
+ kHDMIOutputId,
+ true,
+ kHDMIOutputStabeDevicelId,
+ kHDMIOutputStabeDevicelId ^ 0xFF,
+ "HDMI output",
+ "HDMI",
+ "HDA Intel MID",
+ false,
+ 0,
+ 8, // expects CHANNEL_LAYOUT_7_1
+ 0);
+
+const AudioNode kJabraMic1(true,
+ kJabraMic1Id,
+ true,
+ kJabraMic1StableDeviceId,
+ kJabraMic1StableDeviceId ^ 0xFF,
+ "Jabra Mic",
+ "USB",
+ "Jabra Mic 1",
+ false,
+ 0,
+ 1,
+ 0);
+
+const AudioNode kJabraMic2(true,
+ kJabraMic2Id,
+ true,
+ kJabraMic2StableDeviceId,
+ kJabraMic2StableDeviceId ^ 0xFF,
+ "Jabra Mic",
+ "USB",
+ "Jabra Mic 2",
+ false,
+ 0,
+ 1,
+ 0);
+
+const AudioNode kUSBCameraMic(true,
+ kWebcamMicId,
+ true,
+ kWebcamMicStableDeviceId,
+ kWebcamMicStableDeviceId ^ 0xFF,
+ "Webcam Mic",
+ "USB",
+ "Logitech Webcam",
+ false,
+ 0,
+ 1,
+ 0);
+#endif // defined(USE_CRAS)
+
+const char kRealDefaultInputDeviceID[] = "input2";
+const char kRealDefaultOutputDeviceID[] = "output3";
+const char kRealCommunicationsInputDeviceID[] = "input1";
+const char kRealCommunicationsOutputDeviceID[] = "output1";
+
+void CheckDescriptionLabels(const AudioDeviceDescriptions& descriptions,
+ const std::string& real_default_id,
+ const std::string& real_communications_id) {
+ std::string real_default_label;
+ std::string real_communications_label;
+ for (const auto& description : descriptions) {
+ if (description.unique_id == real_default_id)
+ real_default_label = description.device_name;
+ else if (description.unique_id == real_communications_id)
+ real_communications_label = description.device_name;
+ }
+
+ for (const auto& description : descriptions) {
+ if (AudioDeviceDescription::IsDefaultDevice(description.unique_id)) {
+ EXPECT_TRUE(base::EndsWith(description.device_name, real_default_label,
+ base::CompareCase::SENSITIVE));
+ } else if (description.unique_id ==
+ AudioDeviceDescription::kCommunicationsDeviceId) {
+ EXPECT_TRUE(base::EndsWith(description.device_name,
+ real_communications_label,
+ base::CompareCase::SENSITIVE));
+ }
+ }
+}
+
+} // namespace
+
+// Test fixture which allows us to override the default enumeration API on
+// Windows.
+class AudioManagerTest : public ::testing::Test {
+ public:
+ void HandleDefaultDeviceIDsTest() {
+ AudioParameters params(AudioParameters::AUDIO_PCM_LOW_LATENCY,
+ CHANNEL_LAYOUT_STEREO, 48000, 2048);
+
+ // Create a stream with the default device id "".
+ AudioOutputStream* stream =
+ audio_manager_->MakeAudioOutputStreamProxy(params, "");
+ ASSERT_TRUE(stream);
+ AudioOutputDispatcher* dispatcher1 =
+ reinterpret_cast<AudioOutputProxy*>(stream)
+ ->get_dispatcher_for_testing();
+
+ // Closing this stream will put it up for reuse.
+ stream->Close();
+ stream = audio_manager_->MakeAudioOutputStreamProxy(
+ params, AudioDeviceDescription::kDefaultDeviceId);
+
+ // Verify both streams are created with the same dispatcher (which is unique
+ // per device).
+ ASSERT_EQ(dispatcher1, reinterpret_cast<AudioOutputProxy*>(stream)
+ ->get_dispatcher_for_testing());
+ stream->Close();
+
+ // Create a non-default device and ensure it gets a different dispatcher.
+ stream = audio_manager_->MakeAudioOutputStreamProxy(params, "123456");
+ ASSERT_NE(dispatcher1, reinterpret_cast<AudioOutputProxy*>(stream)
+ ->get_dispatcher_for_testing());
+ stream->Close();
+ }
+
+ void GetDefaultOutputStreamParameters(media::AudioParameters* params) {
+ *params = device_info_accessor_->GetDefaultOutputStreamParameters();
+ }
+
+ void GetAssociatedOutputDeviceID(const std::string& input_device_id,
+ std::string* output_device_id) {
+ *output_device_id =
+ device_info_accessor_->GetAssociatedOutputDeviceID(input_device_id);
+ }
+
+#if defined(USE_CRAS) && BUILDFLAG(IS_CHROMEOS_ASH)
+ void TearDown() override {
+ CrasAudioHandler::Shutdown();
+ audio_pref_handler_ = nullptr;
+ chromeos::CrasAudioClient::Shutdown();
+ }
+
+ void SetUpCrasAudioHandlerWithTestingNodes(const AudioNodeList& audio_nodes) {
+ chromeos::CrasAudioClient::InitializeFake();
+ chromeos::FakeCrasAudioClient::Get()->SetAudioNodesForTesting(audio_nodes);
+ audio_pref_handler_ = new ash::AudioDevicesPrefHandlerStub();
+ CrasAudioHandler::Initialize(
+ /*media_controller_manager*/ mojo::NullRemote(), audio_pref_handler_);
+ cras_audio_handler_ = CrasAudioHandler::Get();
+ base::RunLoop().RunUntilIdle();
+ }
+
+ void SetActiveOutputNode(uint64_t node_id) {
+ cras_audio_handler_->SwitchToDevice(
+ *cras_audio_handler_->GetDeviceFromId(node_id), true /* notify */,
+ CrasAudioHandler::ACTIVATE_BY_USER /* activate_by */);
+ }
+
+ AudioParameters GetPreferredOutputStreamParameters(
+ ChannelLayout channel_layout, int32_t user_buffer_size = 0) {
+ // Generated AudioParameters should follow the same rule as in
+ // AudioManagerCras::GetPreferredOutputStreamParameters().
+ int sample_rate = kDefaultSampleRate;
+ int32_t buffer_size = user_buffer_size;
+ if (buffer_size == 0) // Not user-provided.
+ cras_audio_handler_->GetDefaultOutputBufferSize(&buffer_size);
+ return AudioParameters(
+ AudioParameters::AUDIO_PCM_LOW_LATENCY, channel_layout, sample_rate,
+ buffer_size,
+ AudioParameters::HardwareCapabilities(limits::kMinAudioBufferSize,
+ limits::kMaxAudioBufferSize));
+ }
+#endif // defined(USE_CRAS) && BUILDFLAG(IS_CHROMEOS_ASH)
+
+ protected:
+ AudioManagerTest() {
+ CreateAudioManagerForTesting();
+ }
+ ~AudioManagerTest() override { audio_manager_->Shutdown(); }
+
+ // Helper method which verifies that the device list starts with a valid
+ // default record followed by non-default device names.
+ static void CheckDeviceDescriptions(
+ const AudioDeviceDescriptions& device_descriptions) {
+ DVLOG(2) << "Got " << device_descriptions.size() << " audio devices.";
+ if (!device_descriptions.empty()) {
+ auto it = device_descriptions.begin();
+
+ // The first device in the list should always be the default device.
+ EXPECT_EQ(std::string(AudioDeviceDescription::kDefaultDeviceId),
+ it->unique_id);
+ ++it;
+
+ // Other devices should have non-empty name and id and should not contain
+ // default name or id.
+ while (it != device_descriptions.end()) {
+ EXPECT_FALSE(it->device_name.empty());
+ EXPECT_FALSE(it->unique_id.empty());
+ EXPECT_FALSE(it->group_id.empty());
+ DVLOG(2) << "Device ID(" << it->unique_id
+ << "), label: " << it->device_name
+ << "group: " << it->group_id;
+ EXPECT_NE(AudioDeviceDescription::GetDefaultDeviceName(),
+ it->device_name);
+ EXPECT_NE(std::string(AudioDeviceDescription::kDefaultDeviceId),
+ it->unique_id);
+ ++it;
+ }
+ } else {
+ // Log a warning so we can see the status on the build bots. No need to
+ // break the test though since this does successfully test the code and
+ // some failure cases.
+ LOG(WARNING) << "No input devices detected";
+ }
+ }
+
+#if defined(USE_CRAS) && BUILDFLAG(IS_CHROMEOS_ASH)
+ // Helper method for (USE_CRAS) which verifies that the device list starts
+ // with a valid default record followed by physical device names.
+ static void CheckDeviceDescriptionsCras(
+ const AudioDeviceDescriptions& device_descriptions,
+ const std::map<uint64_t, std::string>& expectation) {
+ DVLOG(2) << "Got " << device_descriptions.size() << " audio devices.";
+ if (!device_descriptions.empty()) {
+ AudioDeviceDescriptions::const_iterator it = device_descriptions.begin();
+
+ // The first device in the list should always be the default device.
+ EXPECT_EQ(AudioDeviceDescription::GetDefaultDeviceName(),
+ it->device_name);
+ EXPECT_EQ(std::string(AudioDeviceDescription::kDefaultDeviceId),
+ it->unique_id);
+
+ // |device_descriptions|'size should be |expectation|'s size plus one
+ // because of
+ // default device.
+ EXPECT_EQ(device_descriptions.size(), expectation.size() + 1);
+ ++it;
+ // Check other devices that should have non-empty name and id, and should
+ // be contained in expectation.
+ while (it != device_descriptions.end()) {
+ EXPECT_FALSE(it->device_name.empty());
+ EXPECT_FALSE(it->unique_id.empty());
+ EXPECT_FALSE(it->group_id.empty());
+ DVLOG(2) << "Device ID(" << it->unique_id
+ << "), label: " << it->device_name
+ << "group: " << it->group_id;
+ uint64_t key;
+ EXPECT_TRUE(base::StringToUint64(it->unique_id, &key));
+ EXPECT_TRUE(expectation.find(key) != expectation.end());
+ EXPECT_EQ(expectation.find(key)->second, it->device_name);
+ ++it;
+ }
+ } else {
+ // Log a warning so we can see the status on the build bots. No need to
+ // break the test though since this does successfully test the code and
+ // some failure cases.
+ LOG(WARNING) << "No input devices detected";
+ }
+ }
+
+ // Helper method for (USE_CRAS) which returns |group_id| from |device_id|.
+ std::string getGroupID(const AudioDeviceDescriptions& device_descriptions,
+ const std::string device_id) {
+ AudioDeviceDescriptions::const_iterator it =
+ std::find_if(device_descriptions.begin(), device_descriptions.end(),
+ [&device_id](const auto& audio_device_desc) {
+ return audio_device_desc.unique_id == device_id;
+ });
+
+ EXPECT_NE(it, device_descriptions.end());
+ return it->group_id;
+ }
+#endif // defined(USE_CRAS) && BUILDFLAG(IS_CHROMEOS_ASH)
+
+ bool InputDevicesAvailable() {
+#if defined(OS_MAC) && defined(ARCH_CPU_ARM64)
+ // TODO(crbug.com/1128458): macOS on ARM64 says it has devices, but won't
+ // let any of them be opened or listed.
+ return false;
+#else
+ return device_info_accessor_->HasAudioInputDevices();
+#endif
+ }
+ bool OutputDevicesAvailable() {
+ return device_info_accessor_->HasAudioOutputDevices();
+ }
+
+ template <typename T = std::nullptr_t>
+ void CreateAudioManagerForTesting() {
+ // Only one AudioManager may exist at a time, so destroy the one we're
+ // currently holding before creating a new one.
+ // Flush the message loop to run any shutdown tasks posted by AudioManager.
+ if (audio_manager_) {
+ audio_manager_->Shutdown();
+ audio_manager_.reset();
+ }
+
+ audio_manager_ =
+ TestAudioManagerFactory<T>::Create(&fake_audio_log_factory_);
+ // A few AudioManager implementations post initialization tasks to
+ // audio thread. Flush the thread to ensure that |audio_manager_| is
+ // initialized and ready to use before returning from this function.
+ // TODO(alokp): We should perhaps do this in AudioManager::Create().
+ base::RunLoop().RunUntilIdle();
+ device_info_accessor_ =
+ std::make_unique<AudioDeviceInfoAccessorForTests>(audio_manager_.get());
+ }
+
+ base::TestMessageLoop message_loop_;
+ FakeAudioLogFactory fake_audio_log_factory_;
+ std::unique_ptr<AudioManager> audio_manager_;
+ std::unique_ptr<AudioDeviceInfoAccessorForTests> device_info_accessor_;
+
+#if defined(USE_CRAS) && BUILDFLAG(IS_CHROMEOS_ASH)
+ CrasAudioHandler* cras_audio_handler_ = nullptr; // Not owned.
+ scoped_refptr<ash::AudioDevicesPrefHandlerStub> audio_pref_handler_;
+#endif // defined(USE_CRAS) && BUILDFLAG(IS_CHROMEOS_ASH)
+};
+
+#if defined(USE_CRAS) && BUILDFLAG(IS_CHROMEOS_ASH)
+TEST_F(AudioManagerTest, EnumerateInputDevicesCras) {
+ // Setup the devices without internal mic, so that it doesn't exist
+ // beamforming capable mic.
+ AudioNodeList audio_nodes;
+ audio_nodes.push_back(kJabraMic1);
+ audio_nodes.push_back(kJabraMic2);
+ audio_nodes.push_back(kUSBCameraMic);
+ audio_nodes.push_back(kHDMIOutput);
+ audio_nodes.push_back(kJabraSpeaker1);
+ SetUpCrasAudioHandlerWithTestingNodes(audio_nodes);
+
+ ABORT_AUDIO_TEST_IF_NOT(InputDevicesAvailable());
+
+ // Setup expectation with physical devices.
+ std::map<uint64_t, std::string> expectation;
+ expectation[kJabraMic1.id] =
+ cras_audio_handler_->GetDeviceFromId(kJabraMic1.id)->display_name;
+ expectation[kJabraMic2.id] =
+ cras_audio_handler_->GetDeviceFromId(kJabraMic2.id)->display_name;
+ expectation[kUSBCameraMic.id] =
+ cras_audio_handler_->GetDeviceFromId(kUSBCameraMic.id)->display_name;
+
+ DVLOG(2) << "Testing AudioManagerCras.";
+ CreateAudioManagerForTesting<AudioManagerChromeOS>();
+ AudioDeviceDescriptions device_descriptions;
+ device_info_accessor_->GetAudioInputDeviceDescriptions(&device_descriptions);
+ CheckDeviceDescriptionsCras(device_descriptions, expectation);
+}
+
+TEST_F(AudioManagerTest, EnumerateOutputDevicesCras) {
+ // Setup the devices without internal mic, so that it doesn't exist
+ // beamforming capable mic.
+ AudioNodeList audio_nodes;
+ audio_nodes.push_back(kJabraMic1);
+ audio_nodes.push_back(kJabraMic2);
+ audio_nodes.push_back(kUSBCameraMic);
+ audio_nodes.push_back(kHDMIOutput);
+ audio_nodes.push_back(kJabraSpeaker1);
+ SetUpCrasAudioHandlerWithTestingNodes(audio_nodes);
+
+ ABORT_AUDIO_TEST_IF_NOT(OutputDevicesAvailable());
+
+ // Setup expectation with physical devices.
+ std::map<uint64_t, std::string> expectation;
+ expectation[kHDMIOutput.id] =
+ cras_audio_handler_->GetDeviceFromId(kHDMIOutput.id)->display_name;
+ expectation[kJabraSpeaker1.id] =
+ cras_audio_handler_->GetDeviceFromId(kJabraSpeaker1.id)->display_name;
+
+ DVLOG(2) << "Testing AudioManagerCras.";
+ CreateAudioManagerForTesting<AudioManagerChromeOS>();
+ AudioDeviceDescriptions device_descriptions;
+ device_info_accessor_->GetAudioOutputDeviceDescriptions(&device_descriptions);
+ CheckDeviceDescriptionsCras(device_descriptions, expectation);
+}
+
+TEST_F(AudioManagerTest, CheckOutputStreamParametersCras) {
+ // Setup the devices without internal mic, so that it doesn't exist
+ // beamforming capable mic.
+ AudioNodeList audio_nodes;
+ audio_nodes.push_back(kJabraMic1);
+ audio_nodes.push_back(kJabraMic2);
+ audio_nodes.push_back(kUSBCameraMic);
+ audio_nodes.push_back(kHDMIOutput);
+ audio_nodes.push_back(kJabraSpeaker1);
+ audio_nodes.push_back(kJabraSpeaker2);
+
+ SetUpCrasAudioHandlerWithTestingNodes(audio_nodes);
+
+ ABORT_AUDIO_TEST_IF_NOT(OutputDevicesAvailable());
+
+ DVLOG(2) << "Testing AudioManagerCras.";
+ CreateAudioManagerForTesting<AudioManagerChromeOS>();
+ AudioParameters params, golden_params;
+
+ // channel_layout:
+ // JabraSpeaker1 (2-channel): CHANNEL_LAYOUT_STEREO
+ // JabraSpeaker2 (6-channel): CHANNEL_LAYOUT_5_1
+ // HDMIOutput (8-channel): CHANNEL_LAYOUT_7_1
+
+ // Check GetOutputStreamParameters() with device ID. The returned parameters
+ // should be reflected to the specific output device.
+ params = device_info_accessor_->GetOutputStreamParameters(
+ base::NumberToString(kJabraSpeaker1Id));
+ golden_params = GetPreferredOutputStreamParameters(
+ ChannelLayout::CHANNEL_LAYOUT_STEREO);
+ EXPECT_TRUE(params.Equals(golden_params));
+ params = device_info_accessor_->GetOutputStreamParameters(
+ base::NumberToString(kJabraSpeaker2Id));
+ golden_params = GetPreferredOutputStreamParameters(
+ ChannelLayout::CHANNEL_LAYOUT_5_1);
+ EXPECT_TRUE(params.Equals(golden_params));
+ params = device_info_accessor_->GetOutputStreamParameters(
+ base::NumberToString(kHDMIOutputId));
+ golden_params = GetPreferredOutputStreamParameters(
+ ChannelLayout::CHANNEL_LAYOUT_7_1);
+ EXPECT_TRUE(params.Equals(golden_params));
+
+ // Set user-provided audio buffer size by command line, then check the buffer
+ // size in stream parameters is equal to the user-provided one.
+ int argc = 2;
+ char const *argv0 = "dummy";
+ char const *argv1 = "--audio-buffer-size=2048";
+ const char* argv[] = {argv0, argv1, 0};
+ base::CommandLine::Reset();
+ EXPECT_TRUE(base::CommandLine::Init(argc, argv));
+
+ // Check GetOutputStreamParameters() with default ID. The returned parameters
+ // should reflect the currently active output device.
+ SetActiveOutputNode(kJabraSpeaker1Id);
+ params = device_info_accessor_->GetOutputStreamParameters(
+ AudioDeviceDescription::kDefaultDeviceId);
+ golden_params = GetPreferredOutputStreamParameters(
+ ChannelLayout::CHANNEL_LAYOUT_STEREO, 2048);
+ EXPECT_TRUE(params.Equals(golden_params));
+ SetActiveOutputNode(kJabraSpeaker2Id);
+ params = device_info_accessor_->GetOutputStreamParameters(
+ AudioDeviceDescription::kDefaultDeviceId);
+ golden_params = GetPreferredOutputStreamParameters(
+ ChannelLayout::CHANNEL_LAYOUT_5_1, 2048);
+ EXPECT_TRUE(params.Equals(golden_params));
+ SetActiveOutputNode(kHDMIOutputId);
+ params = device_info_accessor_->GetOutputStreamParameters(
+ AudioDeviceDescription::kDefaultDeviceId);
+ golden_params = GetPreferredOutputStreamParameters(
+ ChannelLayout::CHANNEL_LAYOUT_7_1, 2048);
+ EXPECT_TRUE(params.Equals(golden_params));
+
+ // Check non-default device again.
+ params = device_info_accessor_->GetOutputStreamParameters(
+ base::NumberToString(kJabraSpeaker1Id));
+ golden_params = GetPreferredOutputStreamParameters(
+ ChannelLayout::CHANNEL_LAYOUT_STEREO, 2048);
+ EXPECT_TRUE(params.Equals(golden_params));
+}
+
+TEST_F(AudioManagerTest, LookupDefaultInputDeviceWithProperGroupId) {
+ // Setup devices with external microphone as active device.
+ // Switch active device to the internal microphone.
+ // Check if default device has the same group id as internal microphone.
+ AudioNodeList audio_nodes;
+ audio_nodes.push_back(kInternalMic);
+ audio_nodes.push_back(kJabraMic1);
+ SetUpCrasAudioHandlerWithTestingNodes(audio_nodes);
+
+ ABORT_AUDIO_TEST_IF_NOT(InputDevicesAvailable());
+
+ // Setup expectation with physical devices.
+ std::map<uint64_t, std::string> expectation;
+ expectation[kInternalMic.id] =
+ cras_audio_handler_->GetDeviceFromId(kInternalMic.id)->display_name;
+ expectation[kJabraMic1.id] =
+ cras_audio_handler_->GetDeviceFromId(kJabraMic1.id)->display_name;
+
+ CreateAudioManagerForTesting<AudioManagerChromeOS>();
+ auto previous_default_device_id =
+ device_info_accessor_->GetDefaultInputDeviceID();
+ EXPECT_EQ(base::NumberToString(kJabraMic1.id), previous_default_device_id);
+ AudioDeviceDescriptions device_descriptions;
+ device_info_accessor_->GetAudioInputDeviceDescriptions(&device_descriptions);
+
+ CheckDeviceDescriptions(device_descriptions);
+
+ // Set internal microphone as active.
+ ash::AudioDevice internal_microphone(kInternalMic);
+ cras_audio_handler_->SwitchToDevice(internal_microphone, true,
+ CrasAudioHandler::ACTIVATE_BY_USER);
+ auto new_default_device_id = device_info_accessor_->GetDefaultInputDeviceID();
+ EXPECT_NE(previous_default_device_id, new_default_device_id);
+
+ auto default_device_group_id =
+ getGroupID(device_descriptions, new_default_device_id);
+ auto mic_group_id =
+ getGroupID(device_descriptions, base::NumberToString(kInternalMic.id));
+
+ EXPECT_EQ(default_device_group_id, mic_group_id);
+ EXPECT_EQ(base::NumberToString(kInternalMic.id), new_default_device_id);
+}
+
+TEST_F(AudioManagerTest, LookupDefaultOutputDeviceWithProperGroupId) {
+ // Setup devices with external speaker as active device.
+ // Switch active device to the internal speaker.
+ // Check if default device has the same group id as internal speaker.
+ AudioNodeList audio_nodes;
+ audio_nodes.push_back(kInternalSpeaker);
+ audio_nodes.push_back(kJabraSpeaker1);
+
+ SetUpCrasAudioHandlerWithTestingNodes(audio_nodes);
+
+ ABORT_AUDIO_TEST_IF_NOT(OutputDevicesAvailable());
+
+ // Setup expectation with physical devices.
+ std::map<uint64_t, std::string> expectation;
+ expectation[kInternalSpeaker.id] =
+ cras_audio_handler_->GetDeviceFromId(kInternalSpeaker.id)->display_name;
+ expectation[kJabraSpeaker1.id] =
+ cras_audio_handler_->GetDeviceFromId(kJabraSpeaker1.id)->display_name;
+
+ CreateAudioManagerForTesting<AudioManagerChromeOS>();
+ auto previous_default_device_id =
+ device_info_accessor_->GetDefaultOutputDeviceID();
+ EXPECT_EQ(base::NumberToString(kJabraSpeaker1.id),
+ previous_default_device_id);
+ AudioDeviceDescriptions device_descriptions;
+ device_info_accessor_->GetAudioOutputDeviceDescriptions(&device_descriptions);
+
+ CheckDeviceDescriptions(device_descriptions);
+
+ // Set internal speaker as active.
+ ash::AudioDevice internal_speaker(kInternalSpeaker);
+ cras_audio_handler_->SwitchToDevice(internal_speaker, true,
+ CrasAudioHandler::ACTIVATE_BY_USER);
+ auto new_default_device_id =
+ device_info_accessor_->GetDefaultOutputDeviceID();
+ EXPECT_NE(previous_default_device_id, new_default_device_id);
+
+ auto default_device_group_id =
+ getGroupID(device_descriptions, new_default_device_id);
+ auto speaker_group_id = getGroupID(device_descriptions,
+ base::NumberToString(kInternalSpeaker.id));
+
+ EXPECT_EQ(default_device_group_id, speaker_group_id);
+ EXPECT_EQ(base::NumberToString(kInternalSpeaker.id), new_default_device_id);
+}
+#else // !(defined(USE_CRAS) && BUILDFLAG(IS_CHROMEOS_ASH))
+
+TEST_F(AudioManagerTest, HandleDefaultDeviceIDs) {
+ // Use a fake manager so we can makeup device ids, this will still use the
+ // AudioManagerBase code.
+ CreateAudioManagerForTesting<FakeAudioManager>();
+ HandleDefaultDeviceIDsTest();
+ base::RunLoop().RunUntilIdle();
+}
+
+// Test that devices can be enumerated.
+TEST_F(AudioManagerTest, EnumerateInputDevices) {
+ ABORT_AUDIO_TEST_IF_NOT(InputDevicesAvailable());
+
+ AudioDeviceDescriptions device_descriptions;
+ device_info_accessor_->GetAudioInputDeviceDescriptions(&device_descriptions);
+ CheckDeviceDescriptions(device_descriptions);
+}
+
+// Test that devices can be enumerated.
+TEST_F(AudioManagerTest, EnumerateOutputDevices) {
+ ABORT_AUDIO_TEST_IF_NOT(OutputDevicesAvailable());
+
+ AudioDeviceDescriptions device_descriptions;
+ device_info_accessor_->GetAudioOutputDeviceDescriptions(&device_descriptions);
+ CheckDeviceDescriptions(device_descriptions);
+}
+
+// Run additional tests for Windows since enumeration can be done using
+// two different APIs. MMDevice is default for Vista and higher and Wave
+// is default for XP and lower.
+#if defined(OS_WIN)
+
+// Override default enumeration API and force usage of Windows MMDevice.
+// This test will only run on Windows Vista and higher.
+TEST_F(AudioManagerTest, EnumerateInputDevicesWinMMDevice) {
+ ABORT_AUDIO_TEST_IF_NOT(InputDevicesAvailable());
+
+ AudioDeviceDescriptions device_descriptions;
+ device_info_accessor_->GetAudioInputDeviceDescriptions(&device_descriptions);
+ CheckDeviceDescriptions(device_descriptions);
+}
+
+TEST_F(AudioManagerTest, EnumerateOutputDevicesWinMMDevice) {
+ ABORT_AUDIO_TEST_IF_NOT(OutputDevicesAvailable());
+
+ AudioDeviceDescriptions device_descriptions;
+ device_info_accessor_->GetAudioOutputDeviceDescriptions(&device_descriptions);
+ CheckDeviceDescriptions(device_descriptions);
+}
+#endif // defined(OS_WIN)
+
+#if defined(USE_PULSEAUDIO)
+// On Linux, there are two implementations available and both can
+// sometimes be tested on a single system. These tests specifically
+// test Pulseaudio.
+
+TEST_F(AudioManagerTest, EnumerateInputDevicesPulseaudio) {
+ ABORT_AUDIO_TEST_IF_NOT(InputDevicesAvailable());
+
+ CreateAudioManagerForTesting<AudioManagerPulse>();
+ if (audio_manager_.get()) {
+ AudioDeviceDescriptions device_descriptions;
+ device_info_accessor_->GetAudioInputDeviceDescriptions(
+ &device_descriptions);
+ CheckDeviceDescriptions(device_descriptions);
+ } else {
+ LOG(WARNING) << "No pulseaudio on this system.";
+ }
+}
+
+TEST_F(AudioManagerTest, EnumerateOutputDevicesPulseaudio) {
+ ABORT_AUDIO_TEST_IF_NOT(OutputDevicesAvailable());
+
+ CreateAudioManagerForTesting<AudioManagerPulse>();
+ if (audio_manager_.get()) {
+ AudioDeviceDescriptions device_descriptions;
+ device_info_accessor_->GetAudioOutputDeviceDescriptions(
+ &device_descriptions);
+ CheckDeviceDescriptions(device_descriptions);
+ } else {
+ LOG(WARNING) << "No pulseaudio on this system.";
+ }
+}
+#endif // defined(USE_PULSEAUDIO)
+
+#if defined(USE_ALSA)
+// On Linux, there are two implementations available and both can
+// sometimes be tested on a single system. These tests specifically
+// test Alsa.
+
+TEST_F(AudioManagerTest, EnumerateInputDevicesAlsa) {
+ ABORT_AUDIO_TEST_IF_NOT(InputDevicesAvailable());
+
+ DVLOG(2) << "Testing AudioManagerAlsa.";
+ CreateAudioManagerForTesting<AudioManagerAlsa>();
+ AudioDeviceDescriptions device_descriptions;
+ device_info_accessor_->GetAudioInputDeviceDescriptions(&device_descriptions);
+ CheckDeviceDescriptions(device_descriptions);
+}
+
+TEST_F(AudioManagerTest, EnumerateOutputDevicesAlsa) {
+ ABORT_AUDIO_TEST_IF_NOT(OutputDevicesAvailable());
+
+ DVLOG(2) << "Testing AudioManagerAlsa.";
+ CreateAudioManagerForTesting<AudioManagerAlsa>();
+ AudioDeviceDescriptions device_descriptions;
+ device_info_accessor_->GetAudioOutputDeviceDescriptions(&device_descriptions);
+ CheckDeviceDescriptions(device_descriptions);
+}
+#endif // defined(USE_ALSA)
+
+TEST_F(AudioManagerTest, GetDefaultOutputStreamParameters) {
+#if defined(OS_WIN) || defined(OS_MAC)
+ ABORT_AUDIO_TEST_IF_NOT(InputDevicesAvailable());
+
+ AudioParameters params;
+ GetDefaultOutputStreamParameters(¶ms);
+ EXPECT_TRUE(params.IsValid());
+#endif // defined(OS_WIN) || defined(OS_MAC)
+}
+
+TEST_F(AudioManagerTest, GetAssociatedOutputDeviceID) {
+#if defined(OS_WIN) || defined(OS_MAC)
+ ABORT_AUDIO_TEST_IF_NOT(InputDevicesAvailable() && OutputDevicesAvailable());
+
+ AudioDeviceDescriptions device_descriptions;
+ device_info_accessor_->GetAudioInputDeviceDescriptions(&device_descriptions);
+ bool found_an_associated_device = false;
+ for (const auto& description : device_descriptions) {
+ EXPECT_FALSE(description.unique_id.empty());
+ EXPECT_FALSE(description.device_name.empty());
+ EXPECT_FALSE(description.group_id.empty());
+ std::string output_device_id;
+ GetAssociatedOutputDeviceID(description.unique_id, &output_device_id);
+ if (!output_device_id.empty()) {
+ DVLOG(2) << description.unique_id << " matches with " << output_device_id;
+ found_an_associated_device = true;
+ }
+ }
+
+ EXPECT_TRUE(found_an_associated_device);
+#endif // defined(OS_WIN) || defined(OS_MAC)
+}
+#endif // defined(USE_CRAS) && BUILDFLAG(IS_CHROMEOS_ASH)
+
+class TestAudioManager : public FakeAudioManager {
+ // For testing the default implementation of GetGroupId(Input|Output)
+ // input$i is associated to output$i, if both exist.
+ // Default input is input1.
+ // Default output is output2.
+ public:
+ TestAudioManager(std::unique_ptr<AudioThread> audio_thread,
+ AudioLogFactory* audio_log_factory)
+ : FakeAudioManager(std::move(audio_thread), audio_log_factory) {}
+
+ std::string GetDefaultInputDeviceID() override {
+ return kRealDefaultInputDeviceID;
+ }
+ std::string GetDefaultOutputDeviceID() override {
+ return kRealDefaultOutputDeviceID;
+ }
+ std::string GetCommunicationsInputDeviceID() override {
+ return kRealCommunicationsInputDeviceID;
+ }
+ std::string GetCommunicationsOutputDeviceID() override {
+ return kRealCommunicationsOutputDeviceID;
+ }
+
+ std::string GetAssociatedOutputDeviceID(
+ const std::string& input_id) override {
+ if (input_id == "input1")
+ return "output1";
+ DCHECK_EQ(std::string(kRealDefaultInputDeviceID), "input2");
+ if (input_id == AudioDeviceDescription::kDefaultDeviceId ||
+ input_id == kRealDefaultInputDeviceID)
+ return "output2";
+ return std::string();
+ }
+
+ private:
+ void GetAudioInputDeviceNames(AudioDeviceNames* device_names) override {
+ DCHECK(device_names->empty());
+ device_names->emplace_back(AudioDeviceName::CreateDefault());
+ device_names->emplace_back("Input 1", "input1");
+ device_names->emplace_back("Input 2", "input2");
+ device_names->emplace_back("Input 3", "input3");
+ }
+
+ void GetAudioOutputDeviceNames(AudioDeviceNames* device_names) override {
+ DCHECK(device_names->empty());
+ device_names->emplace_back(AudioDeviceName::CreateDefault());
+ device_names->emplace_back("Output 1", "output1");
+ device_names->emplace_back("Output 2", "output2");
+ device_names->emplace_back("Output 3", "output3");
+ }
+};
+
+TEST_F(AudioManagerTest, GroupId) {
+ CreateAudioManagerForTesting<TestAudioManager>();
+ // Groups:
+ // input1, output1
+ // input2, output2, default input
+ // input3
+ // output3, default output
+ AudioDeviceDescriptions inputs;
+ device_info_accessor_->GetAudioInputDeviceDescriptions(&inputs);
+ AudioDeviceDescriptions outputs;
+ device_info_accessor_->GetAudioOutputDeviceDescriptions(&outputs);
+ // default input
+ EXPECT_EQ(inputs[0].group_id, outputs[2].group_id);
+ // default input and default output are not associated
+ EXPECT_NE(inputs[0].group_id, outputs[0].group_id);
+
+ // default output
+ EXPECT_EQ(outputs[0].group_id, outputs[3].group_id);
+
+ // real inputs and outputs that are associated
+ EXPECT_EQ(inputs[1].group_id, outputs[1].group_id);
+ EXPECT_EQ(inputs[2].group_id, outputs[2].group_id);
+
+ // real inputs and outputs that are not associated
+ EXPECT_NE(inputs[3].group_id, outputs[3].group_id);
+
+ // group IDs of different devices should differ.
+ EXPECT_NE(inputs[1].group_id, inputs[2].group_id);
+ EXPECT_NE(inputs[1].group_id, inputs[3].group_id);
+ EXPECT_NE(inputs[2].group_id, inputs[3].group_id);
+ EXPECT_NE(outputs[1].group_id, outputs[2].group_id);
+ EXPECT_NE(outputs[1].group_id, outputs[3].group_id);
+ EXPECT_NE(outputs[2].group_id, outputs[3].group_id);
+}
+
+TEST_F(AudioManagerTest, DefaultCommunicationsLabelsContainRealLabels) {
+ CreateAudioManagerForTesting<TestAudioManager>();
+ std::string default_input_id =
+ device_info_accessor_->GetDefaultInputDeviceID();
+ EXPECT_EQ(default_input_id, kRealDefaultInputDeviceID);
+ std::string default_output_id =
+ device_info_accessor_->GetDefaultOutputDeviceID();
+ EXPECT_EQ(default_output_id, kRealDefaultOutputDeviceID);
+ std::string communications_input_id =
+ device_info_accessor_->GetCommunicationsInputDeviceID();
+ EXPECT_EQ(communications_input_id, kRealCommunicationsInputDeviceID);
+ std::string communications_output_id =
+ device_info_accessor_->GetCommunicationsOutputDeviceID();
+ EXPECT_EQ(communications_output_id, kRealCommunicationsOutputDeviceID);
+ AudioDeviceDescriptions inputs;
+ device_info_accessor_->GetAudioInputDeviceDescriptions(&inputs);
+ CheckDescriptionLabels(inputs, default_input_id, communications_input_id);
+
+ AudioDeviceDescriptions outputs;
+ device_info_accessor_->GetAudioOutputDeviceDescriptions(&outputs);
+ CheckDescriptionLabels(outputs, default_output_id, communications_output_id);
+}
+
+// GetPreferredOutputStreamParameters() can make changes to its input_params,
+// ensure that creating a stream with the default parameters always works.
+TEST_F(AudioManagerTest, CheckMakeOutputStreamWithPreferredParameters) {
+ ABORT_AUDIO_TEST_IF_NOT(OutputDevicesAvailable());
+
+ AudioParameters params;
+ GetDefaultOutputStreamParameters(¶ms);
+ ASSERT_TRUE(params.IsValid());
+
+ AudioOutputStream* stream =
+ audio_manager_->MakeAudioOutputStreamProxy(params, "");
+ ASSERT_TRUE(stream);
+
+ stream->Close();
+}
+
+#if defined(OS_MAC) || defined(USE_CRAS)
+class TestAudioSourceCallback : public AudioOutputStream::AudioSourceCallback {
+ public:
+ TestAudioSourceCallback(int expected_frames_per_buffer,
+ base::WaitableEvent* event)
+ : expected_frames_per_buffer_(expected_frames_per_buffer),
+ event_(event) {}
+
+ TestAudioSourceCallback(const TestAudioSourceCallback&) = delete;
+ TestAudioSourceCallback& operator=(const TestAudioSourceCallback&) = delete;
+
+ ~TestAudioSourceCallback() override {}
+
+ int OnMoreData(base::TimeDelta,
+ base::TimeTicks,
+ int,
+ AudioBus* dest) override {
+ EXPECT_EQ(dest->frames(), expected_frames_per_buffer_);
+ event_->Signal();
+ return 0;
+ }
+
+ void OnError(ErrorType type) override { FAIL(); }
+
+ private:
+ const int expected_frames_per_buffer_;
+ base::WaitableEvent* event_;
+};
+
+// Test that we can create an AudioOutputStream with kMinAudioBufferSize and
+// kMaxAudioBufferSize and that the callback AudioBus is the expected size.
+TEST_F(AudioManagerTest, CheckMinMaxAudioBufferSizeCallbacks) {
+ ABORT_AUDIO_TEST_IF_NOT(OutputDevicesAvailable());
+
+#if defined(OS_MAC)
+ CreateAudioManagerForTesting<AudioManagerMac>();
+#elif defined(USE_CRAS) && BUILDFLAG(IS_CHROMEOS_ASH)
+ CreateAudioManagerForTesting<AudioManagerChromeOS>();
+#endif
+
+ DCHECK(audio_manager_);
+
+ AudioParameters default_params;
+ GetDefaultOutputStreamParameters(&default_params);
+ ASSERT_LT(default_params.frames_per_buffer(),
+ media::limits::kMaxAudioBufferSize);
+
+#if defined(OS_MAC)
+ // On OSX the preferred output buffer size is higher than the minimum
+ // but users may request the minimum size explicitly.
+ ASSERT_GT(default_params.frames_per_buffer(),
+ GetMinAudioBufferSizeMacOS(media::limits::kMinAudioBufferSize,
+ default_params.sample_rate()));
+#elif defined(USE_CRAS)
+ // On CRAS the preferred output buffer size varies per board and may be as low
+ // as the minimum for some boards.
+ ASSERT_GE(default_params.frames_per_buffer(),
+ media::limits::kMinAudioBufferSize);
+#else
+ NOTREACHED();
+#endif
+
+ AudioOutputStream* stream;
+ base::WaitableEvent event(base::WaitableEvent::ResetPolicy::AUTOMATIC,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+
+ // Create an output stream with the minimum buffer size parameters and ensure
+ // that no errors are returned.
+ AudioParameters min_params = default_params;
+ min_params.set_frames_per_buffer(media::limits::kMinAudioBufferSize);
+ stream = audio_manager_->MakeAudioOutputStreamProxy(min_params, "");
+ ASSERT_TRUE(stream);
+ EXPECT_TRUE(stream->Open());
+ event.Reset();
+ TestAudioSourceCallback min_source(min_params.frames_per_buffer(), &event);
+ stream->Start(&min_source);
+ event.Wait();
+ stream->Stop();
+ stream->Close();
+
+ // Verify the same for the maximum buffer size.
+ AudioParameters max_params = default_params;
+ max_params.set_frames_per_buffer(media::limits::kMaxAudioBufferSize);
+ stream = audio_manager_->MakeAudioOutputStreamProxy(max_params, "");
+ ASSERT_TRUE(stream);
+ EXPECT_TRUE(stream->Open());
+ event.Reset();
+ TestAudioSourceCallback max_source(max_params.frames_per_buffer(), &event);
+ stream->Start(&max_source);
+ event.Wait();
+ stream->Stop();
+ stream->Close();
+}
+#endif
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_opus_encoder.cc b/third_party/chromium/media/audio/audio_opus_encoder.cc
new file mode 100644
index 0000000..22d1cff
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_opus_encoder.cc
@@ -0,0 +1,327 @@
+// Copyright 2020 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 "media/audio/audio_opus_encoder.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/numerics/checked_math.h"
+#include "base/strings/stringprintf.h"
+#include "media/base/bind_to_current_loop.h"
+#include "media/base/status.h"
+#include "media/base/status_codes.h"
+#include "media/base/timestamp_constants.h"
+
+namespace media {
+
+namespace {
+
+// Recommended value for opus_encode_float(), according to documentation in
+// third_party/opus/src/include/opus.h, so that the Opus encoder does not
+// degrade the audio due to memory constraints, and is independent of the
+// duration of the encoded buffer.
+constexpr int kOpusMaxDataBytes = 4000;
+
+// Opus preferred sampling rate for encoding. This is also the one WebM likes
+// to have: https://wiki.xiph.org/MatroskaOpus.
+constexpr int kOpusPreferredSamplingRate = 48000;
+
+// For Opus, we try to encode 60ms, the maximum Opus buffer, for quality
+// reasons.
+constexpr int kOpusPreferredBufferDurationMs = 60;
+
+// Deletes the libopus encoder instance pointed to by |encoder_ptr|.
+inline void OpusEncoderDeleter(OpusEncoder* encoder_ptr) {
+ opus_encoder_destroy(encoder_ptr);
+}
+
+AudioParameters CreateInputParams(const AudioEncoder::Options& options) {
+ const int frames_per_buffer = options.sample_rate *
+ kOpusPreferredBufferDurationMs /
+ base::Time::kMillisecondsPerSecond;
+ AudioParameters result(media::AudioParameters::AUDIO_PCM_LINEAR,
+ media::CHANNEL_LAYOUT_DISCRETE, options.sample_rate,
+ frames_per_buffer);
+ result.set_channels_for_discrete(options.channels);
+ return result;
+}
+
+// Creates the audio parameters of the converted audio format that Opus prefers,
+// which will be used as the input to the libopus encoder.
+AudioParameters CreateOpusCompatibleParams(const AudioParameters& params) {
+ // third_party/libopus supports up to 2 channels (see implementation of
+ // opus_encoder_create()): force |converted_params| to at most those.
+ // Also, the libopus encoder can accept sample rates of 8, 12, 16, 24, and the
+ // default preferred 48 kHz. If the input sample rate is anything else, we'll
+ // use 48 kHz.
+ const int input_rate = params.sample_rate();
+ const int used_rate = (input_rate == 8000 || input_rate == 12000 ||
+ input_rate == 16000 || input_rate == 24000)
+ ? input_rate
+ : kOpusPreferredSamplingRate;
+ const int frames_per_buffer = used_rate * kOpusPreferredBufferDurationMs /
+ base::Time::kMillisecondsPerSecond;
+
+ AudioParameters result(AudioParameters::AUDIO_PCM_LOW_LATENCY,
+ GuessChannelLayout(std::min(params.channels(), 2)),
+ used_rate, frames_per_buffer);
+ return result;
+}
+
+// During this object's lifetime, it will use its |audio_bus_| to provide input
+// to its |converter_|.
+class ScopedConverterInputProvider : public AudioConverter::InputCallback {
+ public:
+ ScopedConverterInputProvider(AudioConverter* converter,
+ const AudioBus* audio_bus)
+ : converter_(converter), audio_bus_(audio_bus) {
+ DCHECK(converter_);
+ DCHECK(audio_bus_);
+ converter_->AddInput(this);
+ }
+ ScopedConverterInputProvider(const ScopedConverterInputProvider&) = delete;
+ ScopedConverterInputProvider& operator=(const ScopedConverterInputProvider&) =
+ delete;
+ ~ScopedConverterInputProvider() override { converter_->RemoveInput(this); }
+
+ // AudioConverted::InputCallback:
+ double ProvideInput(AudioBus* audio_bus, uint32_t frames_delayed) override {
+ audio_bus_->CopyTo(audio_bus);
+ return 1.0f;
+ }
+
+ private:
+ AudioConverter* const converter_;
+ const AudioBus* const audio_bus_;
+};
+
+} // namespace
+
+// TODO: Remove after switching to C++17
+constexpr int AudioOpusEncoder::kMinBitrate;
+
+AudioOpusEncoder::AudioOpusEncoder()
+ : opus_encoder_(nullptr, OpusEncoderDeleter) {}
+
+void AudioOpusEncoder::Initialize(const Options& options,
+ OutputCB output_callback,
+ StatusCB done_cb) {
+ DCHECK(!output_callback.is_null());
+ DCHECK(!done_cb.is_null());
+
+ done_cb = BindToCurrentLoop(std::move(done_cb));
+ if (opus_encoder_) {
+ std::move(done_cb).Run(StatusCode::kEncoderInitializeTwice);
+ return;
+ }
+
+ options_ = options;
+ input_params_ = CreateInputParams(options);
+ if (!input_params_.IsValid()) {
+ std::move(done_cb).Run(StatusCode::kEncoderInitializationError);
+ return;
+ }
+
+ converted_params_ = CreateOpusCompatibleParams(input_params_);
+ if (!input_params_.IsValid()) {
+ std::move(done_cb).Run(StatusCode::kEncoderInitializationError);
+ return;
+ }
+
+ converter_ =
+ std::make_unique<AudioConverter>(input_params_, converted_params_,
+ /*disable_fifo=*/false);
+ timestamp_tracker_ =
+ std::make_unique<AudioTimestampHelper>(converted_params_.sample_rate());
+ fifo_ = std::make_unique<AudioPushFifo>(base::BindRepeating(
+ &AudioOpusEncoder::OnFifoOutput, base::Unretained(this)));
+ converted_audio_bus_ = AudioBus::Create(
+ converted_params_.channels(), converted_params_.frames_per_buffer());
+ buffer_.resize(converted_params_.channels() *
+ converted_params_.frames_per_buffer());
+ auto status_or_encoder = CreateOpusEncoder();
+ if (status_or_encoder.has_error()) {
+ std::move(done_cb).Run(std::move(status_or_encoder).error());
+ return;
+ }
+
+ opus_encoder_ = std::move(status_or_encoder).value();
+ converter_->PrimeWithSilence();
+ fifo_->Reset(converter_->GetMaxInputFramesRequested(
+ converted_params_.frames_per_buffer()));
+
+ output_cb_ = BindToCurrentLoop(std::move(output_callback));
+ std::move(done_cb).Run(OkStatus());
+}
+
+AudioOpusEncoder::~AudioOpusEncoder() = default;
+
+AudioOpusEncoder::CodecDescription AudioOpusEncoder::PrepareExtraData() {
+ CodecDescription extra_data;
+ // RFC #7845 Ogg Encapsulation for the Opus Audio Codec
+ // https://tools.ietf.org/html/rfc7845
+ static const uint8_t kExtraDataTemplate[19] = {
+ 'O', 'p', 'u', 's', 'H', 'e', 'a', 'd',
+ 1, // offset 8, version, always 1
+ 0, // offset 9, channel count
+ 0, 0, // offset 10, pre-skip
+ 0, 0, 0, 0, // offset 12, original input sample rate in Hz
+ 0, 0, 0};
+
+ extra_data.assign(kExtraDataTemplate,
+ kExtraDataTemplate + sizeof(kExtraDataTemplate));
+
+ // Save number of channels
+ base::CheckedNumeric<uint8_t> channels(converted_params_.channels());
+ if (channels.IsValid())
+ extra_data.data()[9] = channels.ValueOrDie();
+
+ // Number of samples to skip from the start of the decoder's output.
+ // Real data begins this many samples late. These samples need to be skipped
+ // only at the very beginning of the audio stream, NOT at beginning of each
+ // decoded output.
+ if (opus_encoder_) {
+ int32_t samples_to_skip = 0;
+
+ opus_encoder_ctl(opus_encoder_.get(), OPUS_GET_LOOKAHEAD(&samples_to_skip));
+ base::CheckedNumeric<uint16_t> samples_to_skip_safe = samples_to_skip;
+ if (samples_to_skip_safe.IsValid())
+ *reinterpret_cast<uint16_t*>(extra_data.data() + 10) =
+ samples_to_skip_safe.ValueOrDie();
+ }
+
+ // Save original sample rate
+ base::CheckedNumeric<uint16_t> sample_rate = input_params_.sample_rate();
+ uint16_t* sample_rate_ptr =
+ reinterpret_cast<uint16_t*>(extra_data.data() + 12);
+ if (sample_rate.IsValid())
+ *sample_rate_ptr = sample_rate.ValueOrDie();
+ else
+ *sample_rate_ptr = uint16_t{kOpusPreferredSamplingRate};
+ return extra_data;
+}
+
+void AudioOpusEncoder::Encode(std::unique_ptr<AudioBus> audio_bus,
+ base::TimeTicks capture_time,
+ StatusCB done_cb) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK_EQ(audio_bus->channels(), input_params_.channels());
+ DCHECK(!done_cb.is_null());
+ DCHECK(timestamp_tracker_);
+
+ current_done_cb_ = BindToCurrentLoop(std::move(done_cb));
+ if (!opus_encoder_) {
+ std::move(current_done_cb_)
+ .Run(StatusCode::kEncoderInitializeNeverCompleted);
+ return;
+ }
+
+ if (timestamp_tracker_->base_timestamp() == kNoTimestamp)
+ timestamp_tracker_->SetBaseTimestamp(capture_time - base::TimeTicks());
+
+ // The |fifo_| won't trigger OnFifoOutput() until we have enough frames
+ // suitable for the converter.
+ fifo_->Push(*audio_bus);
+ if (!current_done_cb_.is_null()) {
+ // Is |current_done_cb_| is null, it means OnFifoOutput() has already
+ // reported an error.
+ std::move(current_done_cb_).Run(OkStatus());
+ }
+}
+
+void AudioOpusEncoder::Flush(StatusCB done_cb) {
+ DCHECK(!done_cb.is_null());
+
+ done_cb = BindToCurrentLoop(std::move(done_cb));
+ if (!opus_encoder_) {
+ std::move(done_cb).Run(StatusCode::kEncoderInitializeNeverCompleted);
+ return;
+ }
+
+ current_done_cb_ = std::move(done_cb);
+ fifo_->Flush();
+ timestamp_tracker_->SetBaseTimestamp(kNoTimestamp);
+ if (!current_done_cb_.is_null()) {
+ // Is |current_done_cb_| is null, it means OnFifoOutput() has already
+ // reported an error.
+ std::move(current_done_cb_).Run(OkStatus());
+ }
+}
+
+void AudioOpusEncoder::OnFifoOutput(const AudioBus& output_bus,
+ int frame_delay) {
+ // Provides input to the converter from |output_bus| within this scope only.
+ ScopedConverterInputProvider provider(converter_.get(), &output_bus);
+ converter_->Convert(converted_audio_bus_.get());
+ converted_audio_bus_->ToInterleaved<Float32SampleTypeTraits>(
+ converted_audio_bus_->frames(), buffer_.data());
+
+ std::unique_ptr<uint8_t[]> encoded_data(new uint8_t[kOpusMaxDataBytes]);
+ auto result = opus_encode_float(opus_encoder_.get(), buffer_.data(),
+ converted_params_.frames_per_buffer(),
+ encoded_data.get(), kOpusMaxDataBytes);
+
+ if (result < 0 && !current_done_cb_.is_null()) {
+ std::move(current_done_cb_)
+ .Run(Status(StatusCode::kEncoderFailedEncode, opus_strerror(result)));
+ return;
+ }
+
+ size_t encoded_data_size = result;
+ // If |result| in {0,1}, do nothing; the documentation says that a return
+ // value of zero or one means the packet does not need to be transmitted.
+ if (encoded_data_size > 1) {
+ absl::optional<CodecDescription> desc;
+ if (need_to_emit_extra_data_) {
+ desc = PrepareExtraData();
+ need_to_emit_extra_data_ = false;
+ }
+
+ auto ts = base::TimeTicks() + timestamp_tracker_->GetTimestamp();
+
+ auto duration = timestamp_tracker_->GetFrameDuration(
+ converted_params_.frames_per_buffer());
+
+ EncodedAudioBuffer encoded_buffer(converted_params_,
+ std::move(encoded_data),
+ encoded_data_size, ts, duration);
+ output_cb_.Run(std::move(encoded_buffer), desc);
+ }
+ timestamp_tracker_->AddFrames(converted_params_.frames_per_buffer());
+}
+
+// Creates and returns the libopus encoder instance. Returns nullptr if the
+// encoder creation fails.
+StatusOr<OwnedOpusEncoder> AudioOpusEncoder::CreateOpusEncoder() {
+ int opus_result;
+ OwnedOpusEncoder encoder(
+ opus_encoder_create(converted_params_.sample_rate(),
+ converted_params_.channels(), OPUS_APPLICATION_AUDIO,
+ &opus_result),
+ OpusEncoderDeleter);
+
+ if (opus_result < 0) {
+ return Status(
+ StatusCode::kEncoderInitializationError,
+ base::StringPrintf(
+ "Couldn't init Opus encoder: %s, sample rate: %d, channels: %d",
+ opus_strerror(opus_result), converted_params_.sample_rate(),
+ converted_params_.channels()));
+ }
+
+ int bitrate =
+ options_.bitrate.has_value() ? options_.bitrate.value() : OPUS_AUTO;
+ if (encoder &&
+ opus_encoder_ctl(encoder.get(), OPUS_SET_BITRATE(bitrate)) != OPUS_OK) {
+ return Status(
+ StatusCode::kEncoderInitializationError,
+ base::StringPrintf("Failed to set Opus bitrate: %d", bitrate));
+ }
+
+ return encoder;
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_opus_encoder.h b/third_party/chromium/media/audio/audio_opus_encoder.h
new file mode 100644
index 0000000..c11c5c0
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_opus_encoder.h
@@ -0,0 +1,92 @@
+// Copyright 2020 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 MEDIA_AUDIO_AUDIO_OPUS_ENCODER_H_
+#define MEDIA_AUDIO_AUDIO_OPUS_ENCODER_H_
+
+#include <memory>
+#include <vector>
+
+#include "media/base/audio_bus.h"
+#include "media/base/audio_converter.h"
+#include "media/base/audio_encoder.h"
+#include "media/base/audio_push_fifo.h"
+#include "media/base/audio_timestamp_helper.h"
+#include "third_party/opus/src/include/opus.h"
+
+namespace media {
+
+using OpusEncoderDeleterType = void (*)(OpusEncoder* encoder_ptr);
+using OwnedOpusEncoder = std::unique_ptr<OpusEncoder, OpusEncoderDeleterType>;
+
+// Performs Opus encoding of the input audio. The input audio is converted to a
+// a format suitable for Opus before it is passed to the libopus encoder
+// instance to do the actual encoding.
+class MEDIA_EXPORT AudioOpusEncoder : public AudioEncoder {
+ public:
+ AudioOpusEncoder();
+ AudioOpusEncoder(const AudioOpusEncoder&) = delete;
+ AudioOpusEncoder& operator=(const AudioOpusEncoder&) = delete;
+ ~AudioOpusEncoder() override;
+
+ // AudioEncoder:
+ void Initialize(const Options& options,
+ OutputCB output_callback,
+ StatusCB done_cb) override;
+
+ void Encode(std::unique_ptr<AudioBus> audio_bus,
+ base::TimeTicks capture_time,
+ StatusCB done_cb) override;
+
+ void Flush(StatusCB done_cb) override;
+
+ static constexpr int kMinBitrate = 6000;
+
+ private:
+ // Called synchronously by |fifo_| once enough audio frames have been
+ // buffered. Calls libopus to do actual encoding.
+ void OnFifoOutput(const AudioBus& output_bus, int frame_delay);
+
+ CodecDescription PrepareExtraData();
+
+ StatusOr<OwnedOpusEncoder> CreateOpusEncoder();
+
+ AudioParameters input_params_;
+
+ // Output parameters after audio conversion. This may differ from the input
+ // params in the number of channels, sample rate, and the frames per buffer.
+ // (See CreateOpusInputParams() in the .cc file for details).
+ AudioParameters converted_params_;
+
+ // Sample rate adapter from the input audio to what OpusEncoder desires.
+ std::unique_ptr<AudioConverter> converter_;
+
+ // Buffer for holding the original input audio before it goes to the
+ // converter.
+ std::unique_ptr<AudioPushFifo> fifo_;
+
+ // This is the destination AudioBus where the |converter_| teh audio into.
+ std::unique_ptr<AudioBus> converted_audio_bus_;
+
+ // Buffer for passing AudioBus data from the converter to the encoder.
+ std::vector<float> buffer_;
+
+ // The actual libopus encoder instance. This is nullptr if creating the
+ // encoder fails.
+ OwnedOpusEncoder opus_encoder_;
+
+ // Keeps track of the timestamps for the each |output_callback_|
+ std::unique_ptr<AudioTimestampHelper> timestamp_tracker_;
+
+ // Callback for reporting completion and status of the current Flush() or
+ // Encoder()
+ StatusCB current_done_cb_;
+
+ // True if the next output needs to have extra_data in it, only happens once.
+ bool need_to_emit_extra_data_ = true;
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_OPUS_ENCODER_H_
diff --git a/third_party/chromium/media/audio/audio_output_delegate.cc b/third_party/chromium/media/audio/audio_output_delegate.cc
new file mode 100644
index 0000000..98e11e9
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_output_delegate.cc
@@ -0,0 +1,9 @@
+// Copyright 2017 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 "media/audio/audio_output_delegate.h"
+
+media::AudioOutputDelegate::EventHandler::~EventHandler() = default;
+
+media::AudioOutputDelegate::~AudioOutputDelegate() = default;
diff --git a/third_party/chromium/media/audio/audio_output_delegate.h b/third_party/chromium/media/audio/audio_output_delegate.h
new file mode 100644
index 0000000..7cb5e00
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_output_delegate.h
@@ -0,0 +1,52 @@
+// Copyright 2016 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 MEDIA_AUDIO_AUDIO_OUTPUT_DELEGATE_H_
+#define MEDIA_AUDIO_AUDIO_OUTPUT_DELEGATE_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "media/base/media_export.h"
+
+namespace base {
+class UnsafeSharedMemoryRegion;
+class CancelableSyncSocket;
+}
+
+namespace media {
+
+class MEDIA_EXPORT AudioOutputDelegate {
+ public:
+ // An AudioOutputDelegate must not call back to its EventHandler in its
+ // constructor.
+ class MEDIA_EXPORT EventHandler {
+ public:
+ virtual ~EventHandler() = 0;
+
+ // Called when the underlying stream is ready for playout.
+ virtual void OnStreamCreated(
+ int stream_id,
+ base::UnsafeSharedMemoryRegion shared_memory_region,
+ std::unique_ptr<base::CancelableSyncSocket> socket) = 0;
+
+ // Called if stream encounters an error and has become unusable.
+ virtual void OnStreamError(int stream_id) = 0;
+ };
+
+ virtual ~AudioOutputDelegate() = 0;
+
+ virtual int GetStreamId() = 0;
+
+ // Stream control:
+ virtual void OnPlayStream() = 0;
+ virtual void OnPauseStream() = 0;
+ virtual void OnFlushStream() = 0;
+ virtual void OnSetVolume(double volume) = 0;
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_OUTPUT_DELEGATE_H_
diff --git a/third_party/chromium/media/audio/audio_output_device.cc b/third_party/chromium/media/audio/audio_output_device.cc
new file mode 100644
index 0000000..338d9f6
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_output_device.cc
@@ -0,0 +1,486 @@
+// Copyright (c) 2012 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 "media/audio/audio_output_device.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <cmath>
+#include <memory>
+#include <utility>
+
+#include "base/callback_helpers.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/single_thread_task_runner.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/timer/timer.h"
+#include "base/trace_event/trace_event.h"
+#include "build/build_config.h"
+#include "media/audio/audio_device_description.h"
+#include "media/audio/audio_output_device_thread_callback.h"
+#include "media/base/bind_to_current_loop.h"
+#include "media/base/limits.h"
+
+namespace media {
+
+AudioOutputDevice::AudioOutputDevice(
+ std::unique_ptr<AudioOutputIPC> ipc,
+ const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner,
+ const AudioSinkParameters& sink_params,
+ base::TimeDelta authorization_timeout)
+ : io_task_runner_(io_task_runner),
+ callback_(nullptr),
+ ipc_(std::move(ipc)),
+ state_(IDLE),
+ session_id_(sink_params.session_id),
+ device_id_(sink_params.device_id),
+ processing_id_(sink_params.processing_id),
+ stopping_hack_(false),
+ did_receive_auth_(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED),
+ output_params_(AudioParameters::UnavailableDeviceParams()),
+ device_status_(OUTPUT_DEVICE_STATUS_ERROR_INTERNAL),
+ auth_timeout_(authorization_timeout) {
+ DCHECK(ipc_);
+ DCHECK(io_task_runner_);
+}
+
+void AudioOutputDevice::Initialize(const AudioParameters& params,
+ RenderCallback* callback) {
+ io_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(&AudioOutputDevice::InitializeOnIOThread, this,
+ params, callback));
+}
+
+void AudioOutputDevice::InitializeOnIOThread(const AudioParameters& params,
+ RenderCallback* callback) {
+ DCHECK(!callback_) << "Calling Initialize() twice?";
+ DCHECK(params.IsValid());
+ DVLOG(1) << __func__ << ": " << params.AsHumanReadableString();
+ audio_parameters_ = params;
+
+ base::AutoLock auto_lock(audio_thread_lock_);
+ // If Stop() has already been called, RenderCallback has already been
+ // destroyed. So |callback| would be a dangling pointer.
+ if (!stopping_hack_)
+ callback_ = callback;
+}
+
+AudioOutputDevice::~AudioOutputDevice() {
+ {
+ // Abort any pending callbacks. Technically we don't need to acquire the
+ // lock here since there should be no other calls outstanding, but because
+ // we've used the GUARDED_BY compiler syntax, we'll get an error without it.
+ base::AutoLock auto_lock(device_info_lock_);
+ if (pending_device_info_cb_) {
+ std::move(pending_device_info_cb_)
+ .Run(OutputDeviceInfo(OUTPUT_DEVICE_STATUS_ERROR_INTERNAL));
+ }
+ }
+
+#if DCHECK_IS_ON()
+ // Make sure we've stopped the stream properly before destructing |this|.
+ DCHECK(audio_thread_lock_.Try());
+ DCHECK_EQ(state_, IDLE);
+ DCHECK(!audio_thread_);
+ DCHECK(!audio_callback_);
+ DCHECK(!stopping_hack_);
+ audio_thread_lock_.Release();
+#endif // DCHECK_IS_ON()
+}
+
+void AudioOutputDevice::RequestDeviceAuthorization() {
+ TRACE_EVENT0("audio", "AudioOutputDevice::RequestDeviceAuthorization");
+ io_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&AudioOutputDevice::RequestDeviceAuthorizationOnIOThread,
+ this));
+}
+
+void AudioOutputDevice::Start() {
+ TRACE_EVENT0("audio", "AudioOutputDevice::Start");
+ io_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&AudioOutputDevice::CreateStreamOnIOThread, this));
+}
+
+void AudioOutputDevice::Stop() {
+ TRACE_EVENT0("audio", "AudioOutputDevice::Stop");
+ {
+ base::AutoLock auto_lock(audio_thread_lock_);
+ audio_thread_.reset();
+ stopping_hack_ = true;
+ }
+ io_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(&AudioOutputDevice::ShutDownOnIOThread, this));
+}
+
+void AudioOutputDevice::Play() {
+ TRACE_EVENT0("audio", "AudioOutputDevice::Play");
+ io_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(&AudioOutputDevice::PlayOnIOThread, this));
+}
+
+void AudioOutputDevice::Pause() {
+ TRACE_EVENT0("audio", "AudioOutputDevice::Pause");
+ io_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(&AudioOutputDevice::PauseOnIOThread, this));
+}
+
+void AudioOutputDevice::Flush() {
+ TRACE_EVENT0("audio", "AudioOutputDevice::Flush");
+ io_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(&AudioOutputDevice::FlushOnIOThread, this));
+}
+
+bool AudioOutputDevice::SetVolume(double volume) {
+ TRACE_EVENT1("audio", "AudioOutputDevice::Pause", "volume", volume);
+
+ if (volume < 0 || volume > 1.0)
+ return false;
+
+ return io_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&AudioOutputDevice::SetVolumeOnIOThread, this, volume));
+}
+
+OutputDeviceInfo AudioOutputDevice::GetOutputDeviceInfo() {
+ TRACE_EVENT0("audio", "AudioOutputDevice::GetOutputDeviceInfo");
+ DCHECK(!io_task_runner_->BelongsToCurrentThread());
+ did_receive_auth_.Wait();
+ return GetOutputDeviceInfo_Signaled();
+}
+
+void AudioOutputDevice::GetOutputDeviceInfoAsync(OutputDeviceInfoCB info_cb) {
+ {
+ // Hold the lock while checking the signal and setting the pending callback
+ // to avoid racing with authorization completion on the IO thread.
+ base::AutoLock auto_lock(device_info_lock_);
+ if (!did_receive_auth_.IsSignaled()) {
+ DCHECK(!pending_device_info_cb_);
+ pending_device_info_cb_ = BindToCurrentLoop(std::move(info_cb));
+ return;
+ }
+ }
+
+ // Always post to avoid the caller being reentrant. Local testing shows even
+ // on a powerful desktop, we haven't received device authorization by this
+ // point when AOD construction and GetOutputDeviceInfoAsync() happen back to
+ // back (which is the most common use case).
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::BindOnce(std::move(info_cb), GetOutputDeviceInfo_Signaled()));
+}
+
+bool AudioOutputDevice::IsOptimizedForHardwareParameters() {
+ return true;
+}
+
+bool AudioOutputDevice::CurrentThreadIsRenderingThread() {
+ // Since this function is supposed to be called on the rendering thread,
+ // it's safe to access |audio_callback_| here. It will always be valid when
+ // the rendering thread is running.
+ return audio_callback_->CurrentThreadIsAudioDeviceThread();
+}
+
+void AudioOutputDevice::RequestDeviceAuthorizationOnIOThread() {
+ DCHECK(io_task_runner_->BelongsToCurrentThread());
+ DCHECK_EQ(state_, IDLE);
+
+ state_ = AUTHORIZATION_REQUESTED;
+ ipc_->RequestDeviceAuthorization(this, session_id_, device_id_);
+
+ if (auth_timeout_ > base::TimeDelta()) {
+ // Create the timer on the thread it's used on. It's guaranteed to be
+ // deleted on the same thread since users must call Stop() before deleting
+ // AudioOutputDevice; see ShutDownOnIOThread().
+ auth_timeout_action_ = std::make_unique<base::OneShotTimer>();
+ auth_timeout_action_->Start(
+ FROM_HERE, auth_timeout_,
+ base::BindOnce(&AudioOutputDevice::OnDeviceAuthorized, this,
+ OUTPUT_DEVICE_STATUS_ERROR_TIMED_OUT, AudioParameters(),
+ std::string()));
+ }
+}
+
+void AudioOutputDevice::CreateStreamOnIOThread() {
+ TRACE_EVENT0("audio", "AudioOutputDevice::Create");
+ DCHECK(io_task_runner_->BelongsToCurrentThread());
+#if DCHECK_IS_ON()
+ {
+ base::AutoLock auto_lock(audio_thread_lock_);
+ if (!stopping_hack_)
+ DCHECK(callback_) << "Initialize hasn't been called";
+ }
+#endif
+ DCHECK_NE(state_, STREAM_CREATION_REQUESTED);
+
+ if (!ipc_) {
+ NotifyRenderCallbackOfError();
+ return;
+ }
+
+ if (state_ == IDLE && !(did_receive_auth_.IsSignaled() && device_id_.empty()))
+ RequestDeviceAuthorizationOnIOThread();
+
+ ipc_->CreateStream(this, audio_parameters_, processing_id_);
+ // By default, start playing right away.
+ ipc_->PlayStream();
+ state_ = STREAM_CREATION_REQUESTED;
+}
+
+void AudioOutputDevice::PlayOnIOThread() {
+ DCHECK(io_task_runner_->BelongsToCurrentThread());
+ if (audio_callback_)
+ audio_callback_->InitializePlayStartTime();
+
+ if (ipc_)
+ ipc_->PlayStream();
+}
+
+void AudioOutputDevice::PauseOnIOThread() {
+ DCHECK(io_task_runner_->BelongsToCurrentThread());
+
+ if (ipc_)
+ ipc_->PauseStream();
+}
+
+void AudioOutputDevice::FlushOnIOThread() {
+ DCHECK(io_task_runner_->BelongsToCurrentThread());
+
+ if (ipc_)
+ ipc_->FlushStream();
+}
+
+void AudioOutputDevice::ShutDownOnIOThread() {
+ DCHECK(io_task_runner_->BelongsToCurrentThread());
+
+ if (ipc_)
+ ipc_->CloseStream();
+
+ state_ = IDLE;
+
+ // Destoy the timer on the thread it's used on.
+ auth_timeout_action_.reset();
+
+ UMA_HISTOGRAM_ENUMERATION("Media.Audio.Render.StreamCallbackError2",
+ had_error_);
+ had_error_ = kNoError;
+
+ // We can run into an issue where ShutDownOnIOThread is called right after
+ // OnStreamCreated is called in cases where Start/Stop are called before we
+ // get the OnStreamCreated callback. To handle that corner case, we call
+ // Stop(). In most cases, the thread will already be stopped.
+ //
+ // Another situation is when the IO thread goes away before Stop() is called
+ // in which case, we cannot use the message loop to close the thread handle
+ // and can't rely on the main thread existing either.
+ base::AutoLock auto_lock_(audio_thread_lock_);
+ base::ScopedAllowBaseSyncPrimitivesOutsideBlockingScope allow_thread_join;
+ audio_thread_.reset();
+ audio_callback_.reset();
+ stopping_hack_ = false;
+}
+
+void AudioOutputDevice::SetVolumeOnIOThread(double volume) {
+ DCHECK(io_task_runner_->BelongsToCurrentThread());
+ if (ipc_)
+ ipc_->SetVolume(volume);
+}
+
+void AudioOutputDevice::OnError() {
+ TRACE_EVENT0("audio", "AudioOutputDevice::OnError");
+
+ DCHECK(io_task_runner_->BelongsToCurrentThread());
+
+ // Do nothing if the stream has been closed.
+ if (state_ == IDLE)
+ return;
+
+ // Don't dereference the callback object if the audio thread
+ // is stopped or stopping. That could mean that the callback
+ // object has been deleted.
+ // TODO(tommi): Add an explicit contract for clearing the callback
+ // object. Possibly require calling Initialize again or provide
+ // a callback object via Start() and clear it in Stop().
+ NotifyRenderCallbackOfError();
+}
+
+void AudioOutputDevice::OnDeviceAuthorized(
+ OutputDeviceStatus device_status,
+ const AudioParameters& output_params,
+ const std::string& matched_device_id) {
+ DCHECK(io_task_runner_->BelongsToCurrentThread());
+
+ auth_timeout_action_.reset();
+
+ // Do nothing if late authorization is received after timeout.
+ if (!ipc_)
+ return;
+
+ UMA_HISTOGRAM_BOOLEAN("Media.Audio.Render.OutputDeviceAuthorizationTimedOut",
+ device_status == OUTPUT_DEVICE_STATUS_ERROR_TIMED_OUT);
+ LOG_IF(WARNING, device_status == OUTPUT_DEVICE_STATUS_ERROR_TIMED_OUT)
+ << "Output device authorization timed out";
+
+ // It may happen that a second authorization is received as a result to a
+ // call to Start() after Stop(). If the status for the second authorization
+ // differs from the first, it will not be reflected in |device_status_|
+ // to avoid a race.
+ // This scenario is unlikely. If it occurs, the new value will be
+ // different from OUTPUT_DEVICE_STATUS_OK, so the AudioOutputDevice
+ // will enter the |ipc_| == nullptr state anyway, which is the safe thing to
+ // do. This is preferable to holding a lock.
+ if (!did_receive_auth_.IsSignaled()) {
+ device_status_ = device_status;
+ UMA_HISTOGRAM_ENUMERATION("Media.Audio.Render.OutputDeviceStatus",
+ device_status, OUTPUT_DEVICE_STATUS_MAX + 1);
+ }
+
+ if (device_status == OUTPUT_DEVICE_STATUS_OK) {
+ TRACE_EVENT0("audio", "AudioOutputDevice authorized");
+
+ if (!did_receive_auth_.IsSignaled()) {
+ output_params_ = output_params;
+
+ // It's possible to not have a matched device obtained via session id. It
+ // means matching output device through |session_id_| failed and the
+ // default device is used.
+ DCHECK(AudioDeviceDescription::UseSessionIdToSelectDevice(session_id_,
+ device_id_) ||
+ matched_device_id_.empty());
+ matched_device_id_ = matched_device_id;
+
+ DVLOG(1) << "AudioOutputDevice authorized, session_id: " << session_id_
+ << ", device_id: " << device_id_
+ << ", matched_device_id: " << matched_device_id_;
+
+ OnAuthSignal();
+ }
+ } else {
+ TRACE_EVENT1("audio", "AudioOutputDevice not authorized", "auth status",
+ device_status_);
+
+ // Closing IPC forces a Signal(), so no clients are locked waiting
+ // indefinitely after this method returns.
+ ipc_->CloseStream();
+ OnIPCClosed();
+
+ NotifyRenderCallbackOfError();
+ }
+}
+
+void AudioOutputDevice::OnStreamCreated(
+ base::UnsafeSharedMemoryRegion shared_memory_region,
+ base::SyncSocket::ScopedHandle socket_handle,
+ bool playing_automatically) {
+ TRACE_EVENT0("audio", "AudioOutputDevice::OnStreamCreated");
+
+ DCHECK(io_task_runner_->BelongsToCurrentThread());
+ DCHECK(shared_memory_region.IsValid());
+#if defined(OS_WIN)
+ DCHECK(socket_handle.IsValid());
+#else
+ DCHECK(socket_handle.is_valid());
+#endif
+ DCHECK_GT(shared_memory_region.GetSize(), 0u);
+
+ if (state_ != STREAM_CREATION_REQUESTED)
+ return;
+
+ // We can receive OnStreamCreated() on the IO thread after the client has
+ // called Stop() but before ShutDownOnIOThread() is processed. In such a
+ // situation |callback_| might point to freed memory. Instead of starting
+ // |audio_thread_| do nothing and wait for ShutDownOnIOThread() to get called.
+ //
+ // TODO(scherkus): The real fix is to have sane ownership semantics. The fact
+ // that |callback_| (which should own and outlive this object!) can point to
+ // freed memory is a mess. AudioRendererSink should be non-refcounted so that
+ // owners (WebRtcAudioDeviceImpl, AudioRendererImpl, etc...) can Stop() and
+ // delete as they see fit. AudioOutputDevice should internally use WeakPtr
+ // to handle teardown and thread hopping. See http://crbug.com/151051 for
+ // details.
+ {
+ base::AutoLock auto_lock(audio_thread_lock_);
+ if (stopping_hack_)
+ return;
+
+ DCHECK(!audio_thread_);
+ DCHECK(!audio_callback_);
+
+ audio_callback_ = std::make_unique<AudioOutputDeviceThreadCallback>(
+ audio_parameters_, std::move(shared_memory_region), callback_);
+ if (playing_automatically)
+ audio_callback_->InitializePlayStartTime();
+ audio_thread_ = std::make_unique<AudioDeviceThread>(
+ audio_callback_.get(), std::move(socket_handle), "AudioOutputDevice",
+ base::ThreadPriority::REALTIME_AUDIO);
+ }
+}
+
+void AudioOutputDevice::OnIPCClosed() {
+ TRACE_EVENT0("audio", "AudioOutputDevice::OnIPCClosed");
+ DCHECK(io_task_runner_->BelongsToCurrentThread());
+
+ ipc_.reset();
+ state_ = IDLE;
+
+ OnAuthSignal();
+}
+
+OutputDeviceInfo AudioOutputDevice::GetOutputDeviceInfo_Signaled() {
+ DCHECK(did_receive_auth_.IsSignaled());
+ return OutputDeviceInfo(AudioDeviceDescription::UseSessionIdToSelectDevice(
+ session_id_, device_id_)
+ ? matched_device_id_
+ : device_id_,
+ device_status_, output_params_);
+}
+
+void AudioOutputDevice::OnAuthSignal() {
+ DCHECK(io_task_runner_->BelongsToCurrentThread());
+
+ // This lock is held while signaling to avoid any thread safety issues while
+ // GetOutputDeviceInfoAsync() may be checking the signal and modifying the
+ // |pending_device_info_cb_| on another thread.
+ //
+ // We might be able to get away with signaling outside of the lock, but this
+ // requires more careful construction for anyone checking the signal and
+ // using the result to set or get the pending callback value. The failure
+ // mode is also more subtle, callbacks will be lost versus a thread hang which
+ // is more easily detectable in the production population.
+ base::AutoLock auto_lock(device_info_lock_);
+
+ // Signal to unblock any blocked threads waiting for parameters.
+ did_receive_auth_.Signal();
+
+ // The callback is always posted by way media::BindToCurrentLoop() usage upon
+ // receipt, so this is safe to run under the lock.
+ if (pending_device_info_cb_)
+ std::move(pending_device_info_cb_).Run(GetOutputDeviceInfo_Signaled());
+}
+
+void AudioOutputDevice::NotifyRenderCallbackOfError() {
+ TRACE_EVENT0("audio", "AudioOutputDevice::NotifyRenderCallbackOfError");
+ DCHECK(io_task_runner_->BelongsToCurrentThread());
+
+ base::AutoLock auto_lock(audio_thread_lock_);
+ // Avoid signaling error if Initialize() hasn't been called yet, or if
+ // Stop() has already been called.
+ if (callback_ && !stopping_hack_) {
+ // Update |had_error_| for UMA stats.
+ if (audio_callback_)
+ had_error_ = kErrorDuringRendering;
+ else
+ had_error_ = kErrorDuringCreation;
+ callback_->OnRenderError();
+ }
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_output_device.h b/third_party/chromium/media/audio/audio_output_device.h
new file mode 100644
index 0000000..9770413
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_output_device.h
@@ -0,0 +1,248 @@
+// Copyright (c) 2012 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.
+//
+// Audio rendering unit utilizing audio output stream provided by browser
+// process through IPC.
+//
+// Relationship of classes.
+//
+// AudioOutputController AudioOutputDevice
+// ^ ^
+// | |
+// v IPC v
+// MojoAudioOutputStream <---------> AudioOutputIPC (MojoAudioOutputIPC)
+//
+// Transportation of audio samples from the render to the browser process
+// is done by using shared memory in combination with a sync socket pair
+// to generate a low latency transport. The AudioOutputDevice user registers an
+// AudioOutputDevice::RenderCallback at construction and will be polled by the
+// AudioOutputController for audio to be played out by the underlying audio
+// layers.
+//
+// State sequences.
+//
+// Task [IO thread] IPC [IO thread]
+// RequestDeviceAuthorization -> RequestDeviceAuthorizationOnIOThread ------>
+// RequestDeviceAuthorization ->
+// <- OnDeviceAuthorized <- AudioMsg_NotifyDeviceAuthorized <-
+//
+// Start -> CreateStreamOnIOThread -----> CreateStream ------>
+// <- OnStreamCreated <- AudioMsg_NotifyStreamCreated <-
+// ---> PlayOnIOThread -----------> PlayStream -------->
+//
+// Optionally Play() / Pause() sequences may occur:
+// Play -> PlayOnIOThread --------------> PlayStream --------->
+// Pause -> PauseOnIOThread ------------> PauseStream -------->
+// (note that Play() / Pause() sequences before
+// OnStreamCreated are deferred until OnStreamCreated, with the last valid
+// state being used)
+//
+// AudioOutputDevice::Render => audio transport on audio thread =>
+// |
+// Stop --> ShutDownOnIOThread --------> CloseStream -> Close
+//
+// This class utilizes several threads during its lifetime, namely:
+// 1. Creating thread.
+// Must be the main render thread.
+// 2. Control thread (may be the main render thread or another thread).
+// The methods: Start(), Stop(), Play(), Pause(), SetVolume()
+// must be called on the same thread.
+// 3. IO thread (internal implementation detail - not exposed to public API)
+// The thread within which this class receives all the IPC messages and
+// IPC communications can only happen in this thread.
+// 4. Audio transport thread (See AudioDeviceThread).
+// Responsible for calling the AudioOutputDeviceThreadCallback
+// implementation that in turn calls AudioRendererSink::RenderCallback
+// which feeds audio samples to the audio layer in the browser process using
+// sync sockets and shared memory.
+//
+// Implementation notes:
+// - The user must call Stop() before deleting the class instance.
+
+#ifndef MEDIA_AUDIO_AUDIO_OUTPUT_DEVICE_H_
+#define MEDIA_AUDIO_AUDIO_OUTPUT_DEVICE_H_
+
+#include <memory>
+#include <string>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/memory/unsafe_shared_memory_region.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/thread_annotations.h"
+#include "base/time/time.h"
+#include "media/audio/audio_device_thread.h"
+#include "media/audio/audio_output_ipc.h"
+#include "media/audio/audio_sink_parameters.h"
+#include "media/base/audio_parameters.h"
+#include "media/base/audio_renderer_sink.h"
+#include "media/base/media_export.h"
+#include "media/base/output_device_info.h"
+
+namespace base {
+class OneShotTimer;
+class SingleThreadTaskRunner;
+}
+
+namespace media {
+class AudioOutputDeviceThreadCallback;
+
+class MEDIA_EXPORT AudioOutputDevice : public AudioRendererSink,
+ public AudioOutputIPCDelegate {
+ public:
+ // NOTE: Clients must call Initialize() before using.
+ AudioOutputDevice(
+ std::unique_ptr<AudioOutputIPC> ipc,
+ const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner,
+ const AudioSinkParameters& sink_params,
+ base::TimeDelta authorization_timeout);
+
+ // Request authorization to use the device specified in the constructor.
+ void RequestDeviceAuthorization();
+
+ // AudioRendererSink implementation.
+ void Initialize(const AudioParameters& params,
+ RenderCallback* callback) override;
+ void Start() override;
+ void Stop() override;
+ void Play() override;
+ void Pause() override;
+ void Flush() override;
+ bool SetVolume(double volume) override;
+ OutputDeviceInfo GetOutputDeviceInfo() override;
+ void GetOutputDeviceInfoAsync(OutputDeviceInfoCB info_cb) override;
+ bool IsOptimizedForHardwareParameters() override;
+ bool CurrentThreadIsRenderingThread() override;
+
+ // Methods called on IO thread ----------------------------------------------
+ // AudioOutputIPCDelegate methods.
+ void OnError() override;
+ void OnDeviceAuthorized(OutputDeviceStatus device_status,
+ const AudioParameters& output_params,
+ const std::string& matched_device_id) override;
+ void OnStreamCreated(base::UnsafeSharedMemoryRegion shared_memory_region,
+ base::SyncSocket::ScopedHandle socket_handle,
+ bool play_automatically) override;
+ void OnIPCClosed() override;
+
+ protected:
+ // Magic required by ref_counted.h to avoid any code deleting the object
+ // accidentally while there are references to it.
+ friend class base::RefCountedThreadSafe<AudioOutputDevice>;
+ ~AudioOutputDevice() override;
+
+ private:
+ enum StartupState {
+ IDLE, // Authorization not requested.
+ AUTHORIZATION_REQUESTED, // Sent (possibly completed) device
+ // authorization request.
+ STREAM_CREATION_REQUESTED, // Sent (possibly completed) device creation
+ // request. Can Play()/Pause()/Stop().
+ };
+
+ // This enum is used for UMA, so the only allowed operation on this definition
+ // is to add new states to the bottom, update kMaxValue, and update the
+ // histogram "Media.Audio.Render.StreamCallbackError2".
+ enum Error {
+ kNoError = 0,
+ kErrorDuringCreation = 1,
+ kErrorDuringRendering = 2,
+ kMaxValue = kErrorDuringRendering
+ };
+
+ // Methods called on IO thread ----------------------------------------------
+ // The following methods are tasks posted on the IO thread that need to
+ // be executed on that thread. They use AudioOutputIPC to send IPC messages
+ // upon state changes.
+ void RequestDeviceAuthorizationOnIOThread();
+ void InitializeOnIOThread(const AudioParameters& params,
+ RenderCallback* callback);
+ void CreateStreamOnIOThread();
+ void PlayOnIOThread();
+ void PauseOnIOThread();
+ void FlushOnIOThread();
+ void ShutDownOnIOThread();
+ void SetVolumeOnIOThread(double volume);
+
+ // Process device authorization result on the IO thread.
+ void ProcessDeviceAuthorizationOnIOThread(
+ OutputDeviceStatus device_status,
+ const AudioParameters& output_params,
+ const std::string& matched_device_id,
+ bool timed_out);
+
+ void NotifyRenderCallbackOfError();
+
+ OutputDeviceInfo GetOutputDeviceInfo_Signaled();
+ void OnAuthSignal();
+
+ const scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
+
+ AudioParameters audio_parameters_;
+
+ RenderCallback* callback_;
+
+ // A pointer to the IPC layer that takes care of sending requests over to
+ // the implementation. May be set to nullptr after errors.
+ std::unique_ptr<AudioOutputIPC> ipc_;
+
+ // Current state (must only be accessed from the IO thread). See comments for
+ // State enum above.
+ StartupState state_;
+
+ // For UMA stats. May only be accessed on the IO thread.
+ Error had_error_ = kNoError;
+
+ // Last set volume.
+ double volume_ = 1.0;
+
+ // The media session ID used to identify which input device to be started.
+ // Only used by Unified IO.
+ base::UnguessableToken session_id_;
+
+ // ID of hardware output device to be used (provided |session_id_| is zero)
+ const std::string device_id_;
+
+ // If |device_id_| is empty and |session_id_| is not, |matched_device_id_| is
+ // received in OnDeviceAuthorized().
+ std::string matched_device_id_;
+
+ absl::optional<base::UnguessableToken> processing_id_;
+
+ // In order to avoid a race between OnStreamCreated and Stop(), we use this
+ // guard to control stopping and starting the audio thread.
+ base::Lock audio_thread_lock_;
+ std::unique_ptr<AudioOutputDeviceThreadCallback> audio_callback_;
+ std::unique_ptr<AudioDeviceThread> audio_thread_
+ GUARDED_BY(audio_thread_lock_);
+
+ // Temporary hack to ignore OnStreamCreated() due to the user calling Stop()
+ // so we don't start the audio thread pointing to a potentially freed
+ // |callback_|.
+ //
+ // TODO(scherkus): Replace this by changing AudioRendererSink to either accept
+ // the callback via Start(). See http://crbug.com/151051 for details.
+ bool stopping_hack_ GUARDED_BY(audio_thread_lock_);
+
+ base::WaitableEvent did_receive_auth_;
+ AudioParameters output_params_;
+ OutputDeviceStatus device_status_;
+
+ const base::TimeDelta auth_timeout_;
+ std::unique_ptr<base::OneShotTimer> auth_timeout_action_;
+
+ // Pending callback for OutputDeviceInfo if it has not been received by the
+ // time a call to GetGetOutputDeviceInfoAsync() is called.
+ //
+ // Lock for use ONLY with |pending_device_info_cb_| and |did_receive_auth_|,
+ // if you add more usage of this lock ensure you have not added a deadlock.
+ base::Lock device_info_lock_;
+ OutputDeviceInfoCB pending_device_info_cb_ GUARDED_BY(device_info_lock_);
+
+ DISALLOW_COPY_AND_ASSIGN(AudioOutputDevice);
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_OUTPUT_DEVICE_H_
diff --git a/third_party/chromium/media/audio/audio_output_device_thread_callback.cc b/third_party/chromium/media/audio/audio_output_device_thread_callback.cc
new file mode 100644
index 0000000..d64d1c1
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_output_device_thread_callback.cc
@@ -0,0 +1,99 @@
+// Copyright 2018 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 "media/audio/audio_output_device_thread_callback.h"
+
+#include <utility>
+
+#include "base/logging.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/trace_event/trace_event.h"
+
+namespace media {
+
+AudioOutputDeviceThreadCallback::AudioOutputDeviceThreadCallback(
+ const media::AudioParameters& audio_parameters,
+ base::UnsafeSharedMemoryRegion shared_memory_region,
+ media::AudioRendererSink::RenderCallback* render_callback)
+ : media::AudioDeviceThread::Callback(
+ audio_parameters,
+ ComputeAudioOutputBufferSize(audio_parameters),
+ /*segment count*/ 1),
+ shared_memory_region_(std::move(shared_memory_region)),
+ render_callback_(render_callback),
+ callback_num_(0) {
+ // CHECK that the shared memory is large enough. The memory allocated must be
+ // at least as large as expected.
+ CHECK(memory_length_ <= shared_memory_region_.GetSize());
+}
+
+AudioOutputDeviceThreadCallback::~AudioOutputDeviceThreadCallback() = default;
+
+void AudioOutputDeviceThreadCallback::MapSharedMemory() {
+ CHECK_EQ(total_segments_, 1u);
+ shared_memory_mapping_ = shared_memory_region_.MapAt(0, memory_length_);
+ CHECK(shared_memory_mapping_.IsValid());
+
+ media::AudioOutputBuffer* buffer =
+ reinterpret_cast<media::AudioOutputBuffer*>(
+ shared_memory_mapping_.memory());
+ output_bus_ = media::AudioBus::WrapMemory(audio_parameters_, buffer->audio);
+ output_bus_->set_is_bitstream_format(audio_parameters_.IsBitstreamFormat());
+}
+
+// Called whenever we receive notifications about pending data.
+void AudioOutputDeviceThreadCallback::Process(uint32_t control_signal) {
+ callback_num_++;
+
+ // Read and reset the number of frames skipped.
+ media::AudioOutputBuffer* buffer =
+ reinterpret_cast<media::AudioOutputBuffer*>(
+ shared_memory_mapping_.memory());
+ uint32_t frames_skipped = buffer->params.frames_skipped;
+ buffer->params.frames_skipped = 0;
+
+ TRACE_EVENT_BEGIN2("audio", "AudioOutputDevice::FireRenderCallback",
+ "callback_num", callback_num_, "frames skipped",
+ frames_skipped);
+
+ base::TimeDelta delay = base::Microseconds(buffer->params.delay_us);
+
+ base::TimeTicks delay_timestamp =
+ base::TimeTicks() + base::Microseconds(buffer->params.delay_timestamp_us);
+
+ DVLOG(4) << __func__ << " delay:" << delay << " delay_timestamp:" << delay
+ << " frames_skipped:" << frames_skipped;
+
+ // When playback starts, we get an immediate callback to Process to make sure
+ // that we have some data, we'll get another one after the device is awake and
+ // ingesting data, which is what we want to track with this trace.
+ if (callback_num_ == 2)
+ TRACE_EVENT_NESTABLE_ASYNC_END0("audio", "StartingPlayback",
+ TRACE_ID_LOCAL(this));
+
+ // Update the audio-delay measurement, inform about the number of skipped
+ // frames, and ask client to render audio. Since |output_bus_| is wrapping
+ // the shared memory the Render() call is writing directly into the shared
+ // memory.
+ render_callback_->Render(delay, delay_timestamp, frames_skipped,
+ output_bus_.get());
+
+ if (audio_parameters_.IsBitstreamFormat()) {
+ buffer->params.bitstream_data_size = output_bus_->GetBitstreamDataSize();
+ buffer->params.bitstream_frames = output_bus_->GetBitstreamFrames();
+ }
+
+ TRACE_EVENT_END2("audio", "AudioOutputDevice::FireRenderCallback",
+ "timestamp (ms)",
+ (delay_timestamp - base::TimeTicks()).InMillisecondsF(),
+ "delay (ms)", delay.InMillisecondsF());
+}
+
+bool AudioOutputDeviceThreadCallback::CurrentThreadIsAudioDeviceThread() {
+ return thread_checker_.CalledOnValidThread();
+}
+
+void AudioOutputDeviceThreadCallback::InitializePlayStartTime() {}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_output_device_thread_callback.h b/third_party/chromium/media/audio/audio_output_device_thread_callback.h
new file mode 100644
index 0000000..988187d
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_output_device_thread_callback.h
@@ -0,0 +1,59 @@
+// Copyright 2018 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 MEDIA_AUDIO_AUDIO_OUTPUT_DEVICE_THREAD_CALLBACK_H_
+#define MEDIA_AUDIO_AUDIO_OUTPUT_DEVICE_THREAD_CALLBACK_H_
+
+#include <memory>
+
+#include "base/memory/unsafe_shared_memory_region.h"
+#include "media/audio/audio_device_thread.h"
+#include "media/base/audio_renderer_sink.h"
+
+namespace media {
+
+// Takes care of invoking the render callback on the audio thread.
+// An instance of this class is created for each capture stream on output device
+// stream created.
+class MEDIA_EXPORT AudioOutputDeviceThreadCallback
+ : public media::AudioDeviceThread::Callback {
+ public:
+ AudioOutputDeviceThreadCallback(
+ const media::AudioParameters& audio_parameters,
+ base::UnsafeSharedMemoryRegion shared_memory_region,
+ media::AudioRendererSink::RenderCallback* render_callback);
+
+ AudioOutputDeviceThreadCallback(const AudioOutputDeviceThreadCallback&) =
+ delete;
+ AudioOutputDeviceThreadCallback& operator=(
+ const AudioOutputDeviceThreadCallback&) = delete;
+
+ ~AudioOutputDeviceThreadCallback() override;
+
+ void MapSharedMemory() override;
+
+ // Called whenever we receive notifications about pending data.
+ void Process(uint32_t control_signal) override;
+
+ // Returns whether the current thread is the audio device thread or not.
+ // Will always return true if DCHECKs are not enabled.
+ bool CurrentThreadIsAudioDeviceThread();
+
+ // Sets |first_play_start_time_| to the current time unless it's already set,
+ // in which case it's a no-op. The first call to this method MUST have
+ // completed by the time we recieve our first Process() callback to avoid
+ // data races.
+ void InitializePlayStartTime();
+
+ private:
+ base::UnsafeSharedMemoryRegion shared_memory_region_;
+ base::WritableSharedMemoryMapping shared_memory_mapping_;
+ media::AudioRendererSink::RenderCallback* render_callback_;
+ std::unique_ptr<media::AudioBus> output_bus_;
+ uint64_t callback_num_;
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_OUTPUT_DEVICE_THREAD_CALLBACK_H_
diff --git a/third_party/chromium/media/audio/audio_output_device_unittest.cc b/third_party/chromium/media/audio/audio_output_device_unittest.cc
new file mode 100644
index 0000000..0f7db33
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_output_device_unittest.cc
@@ -0,0 +1,399 @@
+// Copyright (c) 2012 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 "media/audio/audio_output_device.h"
+
+#include <stdint.h>
+
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/callback_helpers.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/shared_memory_mapping.h"
+#include "base/memory/unsafe_shared_memory_region.h"
+#include "base/single_thread_task_runner.h"
+#include "base/sync_socket.h"
+#include "base/task_runner.h"
+#include "base/test/task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::CancelableSyncSocket;
+using base::UnsafeSharedMemoryRegion;
+using base::WritableSharedMemoryMapping;
+using base::SyncSocket;
+using testing::_;
+using testing::DoAll;
+using testing::Invoke;
+using testing::Return;
+using testing::WithArg;
+using testing::StrictMock;
+using testing::NiceMock;
+using testing::NotNull;
+using testing::Mock;
+
+namespace media {
+
+namespace {
+
+constexpr char kDefaultDeviceId[] = "";
+constexpr char kNonDefaultDeviceId[] = "valid-nondefault-device-id";
+constexpr char kUnauthorizedDeviceId[] = "unauthorized-device-id";
+constexpr base::TimeDelta kAuthTimeout = base::Milliseconds(10000);
+
+class MockRenderCallback : public AudioRendererSink::RenderCallback {
+ public:
+ MockRenderCallback() = default;
+ ~MockRenderCallback() override = default;
+
+ MOCK_METHOD4(Render,
+ int(base::TimeDelta delay,
+ base::TimeTicks timestamp,
+ int prior_frames_skipped,
+ AudioBus* dest));
+ MOCK_METHOD0(OnRenderError, void());
+};
+
+class MockAudioOutputIPC : public AudioOutputIPC {
+ public:
+ MockAudioOutputIPC() = default;
+ ~MockAudioOutputIPC() override = default;
+
+ MOCK_METHOD3(RequestDeviceAuthorization,
+ void(AudioOutputIPCDelegate* delegate,
+ const base::UnguessableToken& session_id,
+ const std::string& device_id));
+ MOCK_METHOD3(
+ CreateStream,
+ void(AudioOutputIPCDelegate* delegate,
+ const AudioParameters& params,
+ const absl::optional<base::UnguessableToken>& processing_id));
+ MOCK_METHOD0(PlayStream, void());
+ MOCK_METHOD0(PauseStream, void());
+ MOCK_METHOD0(FlushStream, void());
+ MOCK_METHOD0(CloseStream, void());
+ MOCK_METHOD1(SetVolume, void(double volume));
+};
+
+} // namespace.
+
+class AudioOutputDeviceTest : public testing::Test {
+ public:
+ AudioOutputDeviceTest();
+
+ AudioOutputDeviceTest(const AudioOutputDeviceTest&) = delete;
+ AudioOutputDeviceTest& operator=(const AudioOutputDeviceTest&) = delete;
+
+ ~AudioOutputDeviceTest() override;
+
+ void ReceiveAuthorization(OutputDeviceStatus device_status);
+ void StartAudioDevice();
+ void CallOnStreamCreated();
+ void StopAudioDevice();
+ void FlushAudioDevice();
+ void CreateDevice(const std::string& device_id,
+ base::TimeDelta timeout = kAuthTimeout);
+ void SetDevice(const std::string& device_id);
+
+ MOCK_METHOD1(OnDeviceInfoReceived, void(OutputDeviceInfo));
+
+ protected:
+ base::test::TaskEnvironment task_env_{
+ base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+ AudioParameters default_audio_parameters_;
+ StrictMock<MockRenderCallback> callback_;
+ MockAudioOutputIPC* audio_output_ipc_; // owned by audio_device_
+ scoped_refptr<AudioOutputDevice> audio_device_;
+ OutputDeviceStatus device_status_;
+
+ private:
+ int CalculateMemorySize();
+
+ UnsafeSharedMemoryRegion shared_memory_region_;
+ WritableSharedMemoryMapping shared_memory_mapping_;
+ CancelableSyncSocket browser_socket_;
+ CancelableSyncSocket renderer_socket_;
+};
+
+AudioOutputDeviceTest::AudioOutputDeviceTest()
+ : device_status_(OUTPUT_DEVICE_STATUS_ERROR_INTERNAL) {
+ default_audio_parameters_.Reset(AudioParameters::AUDIO_PCM_LINEAR,
+ CHANNEL_LAYOUT_STEREO, 48000, 1024);
+ SetDevice(kDefaultDeviceId);
+}
+
+AudioOutputDeviceTest::~AudioOutputDeviceTest() {
+ audio_device_ = nullptr;
+}
+
+void AudioOutputDeviceTest::CreateDevice(const std::string& device_id,
+ base::TimeDelta timeout) {
+ // Make sure the previous device is properly cleaned up.
+ if (audio_device_)
+ StopAudioDevice();
+
+ audio_output_ipc_ = new NiceMock<MockAudioOutputIPC>();
+ audio_device_ = new AudioOutputDevice(
+ base::WrapUnique(audio_output_ipc_), task_env_.GetMainThreadTaskRunner(),
+ AudioSinkParameters(base::UnguessableToken(), device_id), timeout);
+}
+
+void AudioOutputDeviceTest::SetDevice(const std::string& device_id) {
+ CreateDevice(device_id);
+ EXPECT_CALL(*audio_output_ipc_,
+ RequestDeviceAuthorization(audio_device_.get(),
+ base::UnguessableToken(), device_id));
+ audio_device_->RequestDeviceAuthorization();
+ task_env_.FastForwardBy(base::TimeDelta());
+
+ // Simulate response from browser
+ OutputDeviceStatus device_status =
+ (device_id == kUnauthorizedDeviceId)
+ ? OUTPUT_DEVICE_STATUS_ERROR_NOT_AUTHORIZED
+ : OUTPUT_DEVICE_STATUS_OK;
+ ReceiveAuthorization(device_status);
+
+ audio_device_->Initialize(default_audio_parameters_,
+ &callback_);
+}
+
+void AudioOutputDeviceTest::ReceiveAuthorization(OutputDeviceStatus status) {
+ device_status_ = status;
+ if (device_status_ != OUTPUT_DEVICE_STATUS_OK)
+ EXPECT_CALL(*audio_output_ipc_, CloseStream());
+
+ audio_device_->OnDeviceAuthorized(device_status_, default_audio_parameters_,
+ kDefaultDeviceId);
+ task_env_.FastForwardBy(base::TimeDelta());
+}
+
+void AudioOutputDeviceTest::StartAudioDevice() {
+ if (device_status_ == OUTPUT_DEVICE_STATUS_OK)
+ EXPECT_CALL(*audio_output_ipc_, CreateStream(audio_device_.get(), _, _));
+ else
+ EXPECT_CALL(callback_, OnRenderError());
+
+ audio_device_->Start();
+ task_env_.FastForwardBy(base::TimeDelta());
+}
+
+void AudioOutputDeviceTest::CallOnStreamCreated() {
+ const uint32_t kMemorySize =
+ ComputeAudioOutputBufferSize(default_audio_parameters_);
+
+ shared_memory_region_ = base::UnsafeSharedMemoryRegion::Create(kMemorySize);
+ ASSERT_TRUE(shared_memory_region_.IsValid());
+ shared_memory_mapping_ = shared_memory_region_.Map();
+ ASSERT_TRUE(shared_memory_mapping_.IsValid());
+ memset(shared_memory_mapping_.memory(), 0xff, kMemorySize);
+
+ ASSERT_TRUE(CancelableSyncSocket::CreatePair(&browser_socket_,
+ &renderer_socket_));
+
+ // Create duplicates of the handles we pass to AudioOutputDevice since
+ // ownership will be transferred and AudioOutputDevice is responsible for
+ // freeing.
+ base::UnsafeSharedMemoryRegion duplicated_memory_region =
+ shared_memory_region_.Duplicate();
+ ASSERT_TRUE(duplicated_memory_region.IsValid());
+
+ audio_device_->OnStreamCreated(std::move(duplicated_memory_region),
+ renderer_socket_.Take(),
+ /*playing_automatically*/ false);
+ task_env_.FastForwardBy(base::TimeDelta());
+}
+
+void AudioOutputDeviceTest::StopAudioDevice() {
+ if (device_status_ == OUTPUT_DEVICE_STATUS_OK)
+ EXPECT_CALL(*audio_output_ipc_, CloseStream());
+
+ audio_device_->Stop();
+ task_env_.FastForwardBy(base::TimeDelta());
+}
+
+void AudioOutputDeviceTest::FlushAudioDevice() {
+ if (device_status_ == OUTPUT_DEVICE_STATUS_OK)
+ EXPECT_CALL(*audio_output_ipc_, FlushStream());
+
+ audio_device_->Flush();
+ task_env_.FastForwardBy(base::TimeDelta());
+}
+
+TEST_F(AudioOutputDeviceTest, Initialize) {
+ // Tests that the object can be constructed, initialized and destructed
+ // without having ever been started.
+ StopAudioDevice();
+}
+
+// Calls Start() followed by an immediate Stop() and check for the basic message
+// filter messages being sent in that case.
+TEST_F(AudioOutputDeviceTest, StartStop) {
+ StartAudioDevice();
+ StopAudioDevice();
+}
+
+// AudioOutputDevice supports multiple start/stop sequences.
+TEST_F(AudioOutputDeviceTest, StartStopStartStop) {
+ StartAudioDevice();
+ StopAudioDevice();
+ StartAudioDevice();
+ StopAudioDevice();
+}
+
+// Simulate receiving OnStreamCreated() prior to processing ShutDownOnIOThread()
+// on the IO loop.
+TEST_F(AudioOutputDeviceTest, StopBeforeRender) {
+ StartAudioDevice();
+
+ // Call Stop() but don't run the IO loop yet.
+ audio_device_->Stop();
+
+ // Expect us to shutdown IPC but not to render anything despite the stream
+ // getting created.
+ EXPECT_CALL(*audio_output_ipc_, CloseStream());
+ CallOnStreamCreated();
+}
+
+// Multiple start/stop with nondefault device
+TEST_F(AudioOutputDeviceTest, NonDefaultStartStopStartStop) {
+ SetDevice(kNonDefaultDeviceId);
+ StartAudioDevice();
+ StopAudioDevice();
+
+ EXPECT_CALL(*audio_output_ipc_,
+ RequestDeviceAuthorization(audio_device_.get(),
+ base::UnguessableToken(), _));
+ StartAudioDevice();
+ // Simulate reply from browser
+ ReceiveAuthorization(OUTPUT_DEVICE_STATUS_OK);
+
+ StopAudioDevice();
+}
+
+TEST_F(AudioOutputDeviceTest, UnauthorizedDevice) {
+ SetDevice(kUnauthorizedDeviceId);
+ StartAudioDevice();
+ StopAudioDevice();
+}
+
+TEST_F(AudioOutputDeviceTest,
+ StartUnauthorizedDeviceAndStopBeforeErrorFires_NoError) {
+ SetDevice(kUnauthorizedDeviceId);
+ audio_device_->Start();
+ // Don't run the runloop. We stop before |audio_device| gets the
+ // authorization error, so it's not allowed to dereference |callback_|.
+ EXPECT_CALL(callback_, OnRenderError()).Times(0);
+ StopAudioDevice();
+}
+
+TEST_F(AudioOutputDeviceTest, AuthorizationFailsBeforeInitialize_NoError) {
+ // Clear audio device set by fixture.
+ StopAudioDevice();
+ audio_output_ipc_ = new NiceMock<MockAudioOutputIPC>();
+ audio_device_ = new AudioOutputDevice(
+ base::WrapUnique(audio_output_ipc_), task_env_.GetMainThreadTaskRunner(),
+ AudioSinkParameters(base::UnguessableToken(), kDefaultDeviceId),
+ kAuthTimeout);
+ EXPECT_CALL(
+ *audio_output_ipc_,
+ RequestDeviceAuthorization(audio_device_.get(), base::UnguessableToken(),
+ kDefaultDeviceId));
+
+ audio_device_->RequestDeviceAuthorization();
+ audio_device_->Initialize(default_audio_parameters_, &callback_);
+ task_env_.FastForwardBy(base::TimeDelta());
+ audio_device_->Stop();
+
+ // We've stopped, so accessing |callback_| isn't ok.
+ EXPECT_CALL(callback_, OnRenderError()).Times(0);
+ audio_device_->OnDeviceAuthorized(OUTPUT_DEVICE_STATUS_ERROR_NOT_AUTHORIZED,
+ default_audio_parameters_,
+ kDefaultDeviceId);
+ task_env_.FastForwardBy(base::TimeDelta());
+}
+
+TEST_F(AudioOutputDeviceTest, AuthorizationTimedOut) {
+ CreateDevice(kNonDefaultDeviceId);
+ EXPECT_CALL(
+ *audio_output_ipc_,
+ RequestDeviceAuthorization(audio_device_.get(), base::UnguessableToken(),
+ kNonDefaultDeviceId));
+ EXPECT_CALL(*audio_output_ipc_, CloseStream());
+
+ // Request authorization; no reply from the browser.
+ audio_device_->RequestDeviceAuthorization();
+
+ // Advance time until we hit the timeout.
+ task_env_.FastForwardUntilNoTasksRemain();
+
+ audio_device_->Stop();
+ task_env_.FastForwardBy(base::TimeDelta());
+}
+
+TEST_F(AudioOutputDeviceTest, GetOutputDeviceInfoAsync_Error) {
+ CreateDevice(kUnauthorizedDeviceId, base::TimeDelta());
+ EXPECT_CALL(
+ *audio_output_ipc_,
+ RequestDeviceAuthorization(audio_device_.get(), base::UnguessableToken(),
+ kUnauthorizedDeviceId));
+ audio_device_->RequestDeviceAuthorization();
+ audio_device_->GetOutputDeviceInfoAsync(base::BindOnce(
+ &AudioOutputDeviceTest::OnDeviceInfoReceived, base::Unretained(this)));
+ task_env_.FastForwardBy(base::TimeDelta());
+
+ OutputDeviceInfo info;
+ constexpr auto kExpectedStatus = OUTPUT_DEVICE_STATUS_ERROR_NOT_AUTHORIZED;
+ EXPECT_CALL(*this, OnDeviceInfoReceived(_))
+ .WillOnce(testing::SaveArg<0>(&info));
+ ReceiveAuthorization(kExpectedStatus);
+
+ task_env_.FastForwardUntilNoTasksRemain();
+ EXPECT_EQ(kExpectedStatus, info.device_status());
+ EXPECT_EQ(kUnauthorizedDeviceId, info.device_id());
+ EXPECT_TRUE(
+ AudioParameters::UnavailableDeviceParams().Equals(info.output_params()));
+
+ audio_device_->Stop();
+ task_env_.FastForwardBy(base::TimeDelta());
+}
+
+TEST_F(AudioOutputDeviceTest, GetOutputDeviceInfoAsync_Okay) {
+ CreateDevice(kDefaultDeviceId, base::TimeDelta());
+ EXPECT_CALL(
+ *audio_output_ipc_,
+ RequestDeviceAuthorization(audio_device_.get(), base::UnguessableToken(),
+ kDefaultDeviceId));
+ audio_device_->RequestDeviceAuthorization();
+ audio_device_->GetOutputDeviceInfoAsync(base::BindOnce(
+ &AudioOutputDeviceTest::OnDeviceInfoReceived, base::Unretained(this)));
+ task_env_.FastForwardBy(base::TimeDelta());
+
+ OutputDeviceInfo info;
+ constexpr auto kExpectedStatus = OUTPUT_DEVICE_STATUS_OK;
+ EXPECT_CALL(*this, OnDeviceInfoReceived(_))
+ .WillOnce(testing::SaveArg<0>(&info));
+ ReceiveAuthorization(kExpectedStatus);
+
+ task_env_.FastForwardUntilNoTasksRemain();
+ EXPECT_EQ(kExpectedStatus, info.device_status());
+ EXPECT_EQ(kDefaultDeviceId, info.device_id());
+ EXPECT_TRUE(default_audio_parameters_.Equals(info.output_params()));
+
+ audio_device_->Stop();
+ task_env_.FastForwardBy(base::TimeDelta());
+}
+
+TEST_F(AudioOutputDeviceTest, StreamIsFlushed) {
+ StartAudioDevice();
+ FlushAudioDevice();
+ StopAudioDevice();
+}
+
+} // namespace media.
diff --git a/third_party/chromium/media/audio/audio_output_dispatcher.cc b/third_party/chromium/media/audio/audio_output_dispatcher.cc
new file mode 100644
index 0000000..0e50642
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_output_dispatcher.cc
@@ -0,0 +1,21 @@
+// Copyright (c) 2012 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 "media/audio/audio_output_dispatcher.h"
+
+#include "base/single_thread_task_runner.h"
+#include "media/audio/audio_manager.h"
+
+namespace media {
+
+AudioOutputDispatcher::AudioOutputDispatcher(AudioManager* audio_manager)
+ : audio_manager_(audio_manager) {
+ DCHECK(audio_manager->GetTaskRunner()->BelongsToCurrentThread());
+}
+
+AudioOutputDispatcher::~AudioOutputDispatcher() {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_output_dispatcher.h b/third_party/chromium/media/audio/audio_output_dispatcher.h
new file mode 100644
index 0000000..a6aaaf8
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_output_dispatcher.h
@@ -0,0 +1,80 @@
+// Copyright (c) 2012 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.
+
+// AudioOutputDispatcher is a single-threaded base class that dispatches
+// creation and deletion of audio output streams. AudioOutputProxy objects use
+// this class to allocate and recycle actual audio output streams. When playback
+// is started, the proxy calls StartStream() to get an output stream that it
+// uses to play audio. When playback is stopped, the proxy returns the stream
+// back to the dispatcher by calling StopStream().
+//
+// AudioManagerBase creates one specialization of AudioOutputDispatcher on the
+// audio thread for each possible set of audio parameters. I.e streams with
+// different parameters are managed independently. The AudioOutputDispatcher
+// instance is then deleted on the audio thread when the AudioManager shuts
+// down.
+
+#ifndef MEDIA_AUDIO_AUDIO_OUTPUT_DISPATCHER_H_
+#define MEDIA_AUDIO_AUDIO_OUTPUT_DISPATCHER_H_
+
+#include "base/macros.h"
+#include "media/audio/audio_io.h"
+
+namespace media {
+class AudioManager;
+class AudioOutputProxy;
+
+// Lives and must be called on AudioManager device thread.
+class MEDIA_EXPORT AudioOutputDispatcher {
+ public:
+ AudioOutputDispatcher(AudioManager* audio_manager);
+
+ AudioOutputDispatcher(const AudioOutputDispatcher&) = delete;
+ AudioOutputDispatcher& operator=(const AudioOutputDispatcher&) = delete;
+
+ virtual ~AudioOutputDispatcher();
+
+ // Creates an instance of AudioOutputProxy, which uses |this| as dispatcher.
+ // The client owns the returned pointer, which can be deleted using
+ // AudioOutputProxy::Close.
+ virtual AudioOutputProxy* CreateStreamProxy() = 0;
+
+ // Called by AudioOutputProxy to open the stream.
+ // Returns false, if it fails to open it.
+ virtual bool OpenStream() = 0;
+
+ // Called by AudioOutputProxy when the stream is started.
+ // Uses |callback| to get source data and report errors, if any.
+ // Does *not* take ownership of this callback.
+ // Returns true if started successfully, false otherwise.
+ virtual bool StartStream(AudioOutputStream::AudioSourceCallback* callback,
+ AudioOutputProxy* stream_proxy) = 0;
+
+ // Called by AudioOutputProxy when the stream is stopped.
+ // Ownership of the |stream_proxy| is passed to the dispatcher.
+ virtual void StopStream(AudioOutputProxy* stream_proxy) = 0;
+
+ // Called by AudioOutputProxy when the volume is set.
+ virtual void StreamVolumeSet(AudioOutputProxy* stream_proxy,
+ double volume) = 0;
+
+ // Called by AudioOutputProxy when the stream is closed.
+ virtual void CloseStream(AudioOutputProxy* stream_proxy) = 0;
+
+ // Called by AudioOutputProxy to flush the stream. This should only be
+ // called when a stream is stopped.
+ virtual void FlushStream(AudioOutputProxy* stream_proxy) = 0;
+
+ protected:
+ AudioManager* audio_manager() const { return audio_manager_; }
+
+ private:
+ // A no-reference-held pointer (we don't want circular references) back to the
+ // AudioManager that owns this object.
+ AudioManager* const audio_manager_;
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_OUTPUT_DISPATCHER_H_
diff --git a/third_party/chromium/media/audio/audio_output_dispatcher_impl.cc b/third_party/chromium/media/audio/audio_output_dispatcher_impl.cc
new file mode 100644
index 0000000..a8c428c
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_output_dispatcher_impl.cc
@@ -0,0 +1,210 @@
+// Copyright (c) 2012 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 "media/audio/audio_output_dispatcher_impl.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/containers/contains.h"
+#include "base/single_thread_task_runner.h"
+#include "base/time/time.h"
+#include "media/audio/audio_logging.h"
+#include "media/audio/audio_manager.h"
+#include "media/audio/audio_output_proxy.h"
+
+namespace media {
+
+AudioOutputDispatcherImpl::AudioOutputDispatcherImpl(
+ AudioManager* audio_manager,
+ const AudioParameters& params,
+ const std::string& output_device_id,
+ base::TimeDelta close_delay)
+ : AudioOutputDispatcher(audio_manager),
+ params_(params),
+ device_id_(output_device_id),
+ idle_proxies_(0),
+ close_timer_(FROM_HERE,
+ close_delay,
+ this,
+ &AudioOutputDispatcherImpl::CloseAllIdleStreams),
+ audio_stream_id_(0) {
+ DCHECK(audio_manager->GetTaskRunner()->BelongsToCurrentThread());
+ audio_manager->AddOutputDeviceChangeListener(this);
+}
+
+AudioOutputDispatcherImpl::~AudioOutputDispatcherImpl() {
+ DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
+
+ // Stop all active streams.
+ for (auto& iter : proxy_to_physical_map_) {
+ StopPhysicalStream(iter.second);
+ }
+
+ // Close all idle streams immediately. The |close_timer_| will handle
+ // invalidating any outstanding tasks upon its destruction.
+ CloseAllIdleStreams();
+
+ audio_manager()->RemoveOutputDeviceChangeListener(this);
+
+ // All idle physical streams must have been closed during shutdown.
+ CHECK(idle_streams_.empty());
+}
+
+AudioOutputProxy* AudioOutputDispatcherImpl::CreateStreamProxy() {
+ DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
+ return new AudioOutputProxy(weak_factory_.GetWeakPtr());
+}
+
+bool AudioOutputDispatcherImpl::OpenStream() {
+ DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
+
+ // Ensure that there is at least one open stream.
+ if (idle_streams_.empty() && !CreateAndOpenStream())
+ return false;
+
+ ++idle_proxies_;
+ close_timer_.Reset();
+ return true;
+}
+
+bool AudioOutputDispatcherImpl::StartStream(
+ AudioOutputStream::AudioSourceCallback* callback,
+ AudioOutputProxy* stream_proxy) {
+ DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
+ DCHECK(proxy_to_physical_map_.find(stream_proxy) ==
+ proxy_to_physical_map_.end());
+
+ if (idle_streams_.empty() && !CreateAndOpenStream())
+ return false;
+
+ AudioOutputStream* physical_stream = idle_streams_.back();
+ idle_streams_.pop_back();
+
+ DCHECK_GT(idle_proxies_, 0u);
+ --idle_proxies_;
+
+ double volume = 0;
+ stream_proxy->GetVolume(&volume);
+ physical_stream->SetVolume(volume);
+ DCHECK(base::Contains(audio_logs_, physical_stream));
+ AudioLog* const audio_log = audio_logs_[physical_stream].get();
+ audio_log->OnSetVolume(volume);
+ physical_stream->Start(callback);
+ audio_log->OnStarted();
+ proxy_to_physical_map_[stream_proxy] = physical_stream;
+
+ close_timer_.Reset();
+ return true;
+}
+
+void AudioOutputDispatcherImpl::StopStream(AudioOutputProxy* stream_proxy) {
+ DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
+ auto it = proxy_to_physical_map_.find(stream_proxy);
+ DCHECK(it != proxy_to_physical_map_.end());
+ StopPhysicalStream(it->second);
+ proxy_to_physical_map_.erase(it);
+ ++idle_proxies_;
+}
+
+void AudioOutputDispatcherImpl::StreamVolumeSet(AudioOutputProxy* stream_proxy,
+ double volume) {
+ DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
+ auto it = proxy_to_physical_map_.find(stream_proxy);
+ if (it != proxy_to_physical_map_.end()) {
+ AudioOutputStream* physical_stream = it->second;
+ physical_stream->SetVolume(volume);
+ DCHECK(base::Contains(audio_logs_, physical_stream));
+ audio_logs_[physical_stream]->OnSetVolume(volume);
+ }
+}
+
+void AudioOutputDispatcherImpl::CloseStream(AudioOutputProxy* stream_proxy) {
+ DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
+ DCHECK_GT(idle_proxies_, 0u);
+ --idle_proxies_;
+
+ // Leave at least a single stream running until the close timer fires to help
+ // cycle time when streams are opened and closed repeatedly.
+ CloseIdleStreams(std::max(idle_proxies_, static_cast<size_t>(1)));
+ close_timer_.Reset();
+}
+
+// There is nothing to flush since the phsyical stream is removed during
+// StopStream().
+void AudioOutputDispatcherImpl::FlushStream(AudioOutputProxy* stream_proxy) {}
+
+void AudioOutputDispatcherImpl::OnDeviceChange() {
+ DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
+
+ // We don't want to end up reusing streams which were opened for the wrong
+ // default device. We need to post this task so it runs after device changes
+ // have been sent to all listeners and they've had time to close streams.
+ audio_manager()->GetTaskRunner()->PostTask(
+ FROM_HERE, base::BindOnce(&AudioOutputDispatcherImpl::CloseAllIdleStreams,
+ weak_factory_.GetWeakPtr()));
+}
+
+bool AudioOutputDispatcherImpl::HasOutputProxies() const {
+ DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
+ return idle_proxies_ || !proxy_to_physical_map_.empty();
+}
+
+bool AudioOutputDispatcherImpl::CreateAndOpenStream() {
+ DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
+ const int stream_id = audio_stream_id_++;
+ std::unique_ptr<AudioLog> audio_log = audio_manager()->CreateAudioLog(
+ AudioLogFactory::AUDIO_OUTPUT_STREAM, stream_id);
+ AudioOutputStream* stream = audio_manager()->MakeAudioOutputStream(
+ params_, device_id_,
+ base::BindRepeating(&AudioLog::OnLogMessage,
+ base::Unretained(audio_log.get())));
+ if (!stream)
+ return false;
+
+ if (!stream->Open()) {
+ stream->Close();
+ return false;
+ }
+
+ audio_log->OnCreated(params_, device_id_);
+ audio_logs_[stream] = std::move(audio_log);
+
+ idle_streams_.push_back(stream);
+ return true;
+}
+
+void AudioOutputDispatcherImpl::CloseAllIdleStreams() {
+ DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
+ CloseIdleStreams(0);
+}
+
+void AudioOutputDispatcherImpl::CloseIdleStreams(size_t keep_alive) {
+ DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
+ if (idle_streams_.size() <= keep_alive)
+ return;
+ for (size_t i = keep_alive; i < idle_streams_.size(); ++i) {
+ AudioOutputStream* stream = idle_streams_[i];
+ stream->Close();
+
+ auto it = audio_logs_.find(stream);
+ DCHECK(it != audio_logs_.end());
+ it->second->OnClosed();
+ audio_logs_.erase(it);
+ }
+ idle_streams_.erase(idle_streams_.begin() + keep_alive, idle_streams_.end());
+}
+
+void AudioOutputDispatcherImpl::StopPhysicalStream(AudioOutputStream* stream) {
+ DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
+ stream->Stop();
+ DCHECK(base::Contains(audio_logs_, stream));
+ audio_logs_[stream]->OnStopped();
+ idle_streams_.push_back(stream);
+ close_timer_.Reset();
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_output_dispatcher_impl.h b/third_party/chromium/media/audio/audio_output_dispatcher_impl.h
new file mode 100644
index 0000000..6d0f6ea
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_output_dispatcher_impl.h
@@ -0,0 +1,107 @@
+// Copyright (c) 2012 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.
+
+// AudioOutputDispatcherImpl is an implementation of AudioOutputDispatcher.
+//
+// To avoid opening and closing audio devices more frequently than necessary,
+// each dispatcher has a pool of inactive physical streams. A stream is closed
+// only if it hasn't been used for a certain period of time (specified via the
+// constructor).
+//
+
+#ifndef MEDIA_AUDIO_AUDIO_OUTPUT_DISPATCHER_IMPL_H_
+#define MEDIA_AUDIO_AUDIO_OUTPUT_DISPATCHER_IMPL_H_
+
+#include <stddef.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/containers/flat_map.h"
+#include "base/macros.h"
+#include "base/timer/timer.h"
+#include "media/audio/audio_io.h"
+#include "media/audio/audio_manager.h"
+#include "media/audio/audio_output_dispatcher.h"
+#include "media/base/audio_parameters.h"
+
+namespace media {
+class AudioLog;
+
+class MEDIA_EXPORT AudioOutputDispatcherImpl
+ : public AudioOutputDispatcher,
+ public AudioManager::AudioDeviceListener {
+ public:
+ // |close_delay| specifies delay after the stream is idle until the audio
+ // device is closed.
+ AudioOutputDispatcherImpl(AudioManager* audio_manager,
+ const AudioParameters& params,
+ const std::string& output_device_id,
+ base::TimeDelta close_delay);
+
+ AudioOutputDispatcherImpl(const AudioOutputDispatcherImpl&) = delete;
+ AudioOutputDispatcherImpl& operator=(const AudioOutputDispatcherImpl&) =
+ delete;
+
+ ~AudioOutputDispatcherImpl() override;
+
+ // AudioOutputDispatcher implementation.
+ AudioOutputProxy* CreateStreamProxy() override;
+ bool OpenStream() override;
+ bool StartStream(AudioOutputStream::AudioSourceCallback* callback,
+ AudioOutputProxy* stream_proxy) override;
+ void StopStream(AudioOutputProxy* stream_proxy) override;
+ void StreamVolumeSet(AudioOutputProxy* stream_proxy, double volume) override;
+ void CloseStream(AudioOutputProxy* stream_proxy) override;
+ void FlushStream(AudioOutputProxy* stream_proxy) override;
+
+ // AudioDeviceListener implementation.
+ void OnDeviceChange() override;
+
+ // Returns true if there are any open AudioOutputProxy objects.
+ bool HasOutputProxies() const;
+
+ // Closes all |idle_streams_|.
+ void CloseAllIdleStreams();
+
+ private:
+ // Creates a new physical output stream, opens it and pushes to
+ // |idle_streams_|. Returns false if the stream couldn't be created or
+ // opened.
+ bool CreateAndOpenStream();
+
+ // Similar to CloseAllIdleStreams(), but keeps |keep_alive| streams alive.
+ void CloseIdleStreams(size_t keep_alive);
+
+ void StopPhysicalStream(AudioOutputStream* stream);
+
+ // Output parameters.
+ const AudioParameters params_;
+
+ // Output device id.
+ const std::string device_id_;
+
+ size_t idle_proxies_;
+ std::vector<AudioOutputStream*> idle_streams_;
+
+ // When streams are stopped they're added to |idle_streams_|, if no stream is
+ // reused before |close_delay_| elapses |close_timer_| will run
+ // CloseIdleStreams().
+ base::DelayTimer close_timer_;
+
+ typedef base::flat_map<AudioOutputProxy*, AudioOutputStream*> AudioStreamMap;
+ AudioStreamMap proxy_to_physical_map_;
+
+ using AudioLogMap =
+ base::flat_map<AudioOutputStream*, std::unique_ptr<media::AudioLog>>;
+ AudioLogMap audio_logs_;
+ int audio_stream_id_;
+
+ base::WeakPtrFactory<AudioOutputDispatcherImpl> weak_factory_{this};
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_OUTPUT_DISPATCHER_IMPL_H_
diff --git a/third_party/chromium/media/audio/audio_output_ipc.cc b/third_party/chromium/media/audio/audio_output_ipc.cc
new file mode 100644
index 0000000..eb07d30
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_output_ipc.cc
@@ -0,0 +1,13 @@
+// Copyright (c) 2012 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 "media/audio/audio_output_ipc.h"
+
+namespace media {
+
+AudioOutputIPCDelegate::~AudioOutputIPCDelegate() = default;
+
+AudioOutputIPC::~AudioOutputIPC() = default;
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_output_ipc.h b/third_party/chromium/media/audio/audio_output_ipc.h
new file mode 100644
index 0000000..a826727
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_output_ipc.h
@@ -0,0 +1,117 @@
+// Copyright (c) 2012 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 MEDIA_AUDIO_AUDIO_OUTPUT_IPC_H_
+#define MEDIA_AUDIO_AUDIO_OUTPUT_IPC_H_
+
+#include <string>
+
+#include "base/memory/unsafe_shared_memory_region.h"
+#include "base/sync_socket.h"
+#include "base/unguessable_token.h"
+#include "media/base/audio_parameters.h"
+#include "media/base/media_export.h"
+#include "media/base/output_device_info.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace media {
+
+// Contains IPC notifications for the state of the server side
+// (AudioOutputController) audio state changes and when an AudioOutputController
+// has been created. Implemented by AudioOutputDevice.
+class MEDIA_EXPORT AudioOutputIPCDelegate {
+ public:
+ // Called when state of an audio stream has changed.
+ virtual void OnError() = 0;
+
+ // Called when an authorization request for an output device has been
+ // completed. The AudioOutputIPCDelegate will delete the AudioOutputIPC, if
+ // |device_status| is not OUTPUT_DEVICE_STATUS_OK.
+ virtual void OnDeviceAuthorized(OutputDeviceStatus device_status,
+ const media::AudioParameters& output_params,
+ const std::string& matched_device_id) = 0;
+
+ // Called when an audio stream has been created.
+ // See media/mojo/mojom/audio_data_pipe.mojom for documentation of
+ // |handle| and |socket_handle|. |playing_automatically| indicates if the
+ // AudioOutputIPCDelegate is playing right away due to an earlier call to
+ // Play();
+ virtual void OnStreamCreated(
+ base::UnsafeSharedMemoryRegion shared_memory_region,
+ base::SyncSocket::ScopedHandle socket_handle,
+ bool playing_automatically) = 0;
+
+ // Called when the AudioOutputIPC object is going away and/or when the IPC
+ // channel has been closed and no more ipc requests can be made.
+ // Implementations should delete their owned AudioOutputIPC instance
+ // immediately.
+ virtual void OnIPCClosed() = 0;
+
+ protected:
+ virtual ~AudioOutputIPCDelegate();
+};
+
+// Provides the IPC functionality for an AudioOutputIPCDelegate (e.g., an
+// AudioOutputDevice). The implementation should asynchronously deliver the
+// messages to an AudioOutputController object (or create one in the case of
+// CreateStream()), that may live in a separate process.
+class MEDIA_EXPORT AudioOutputIPC {
+ public:
+ virtual ~AudioOutputIPC();
+
+ // Sends a request to authorize the use of a specific audio output device
+ // in the peer process.
+ // If |device_id| is nonempty, the browser selects the device
+ // indicated by |device_id|, regardless of the value of |session_id|.
+ // If |device_id| is empty and |session_id| is nonzero, the browser selects
+ // the output device associated with an opened input device indicated by
+ // |session_id|. If no such device is found, the default device will be
+ // selected.
+ // If |device_id| is empty and |session_id| is zero, the browser selects
+ // the default device.
+ // Once the authorization process is complete, the implementation will
+ // notify |delegate| by calling OnDeviceAuthorized().
+ virtual void RequestDeviceAuthorization(
+ AudioOutputIPCDelegate* delegate,
+ const base::UnguessableToken& session_id,
+ const std::string& device_id) = 0;
+
+ // Sends a request to create an AudioOutputController object in the peer
+ // process and configures it to use the specified audio |params| including
+ // number of synchronized input channels.
+ // If no authorization for an output device has been previously requested,
+ // the default device will be used.
+ // Once the stream has been created, the implementation will notify
+ // |delegate| by calling OnStreamCreated().
+ virtual void CreateStream(
+ AudioOutputIPCDelegate* delegate,
+ const AudioParameters& params,
+ const absl::optional<base::UnguessableToken>& processing_id) = 0;
+
+ // Starts playing the stream. This should generate a call to
+ // AudioOutputController::Play().
+ virtual void PlayStream() = 0;
+
+ // Pauses an audio stream. This should generate a call to
+ // AudioOutputController::Pause().
+ virtual void PauseStream() = 0;
+
+ // Flushes an audio stream. This should only be called when the stream is
+ // paused.
+ virtual void FlushStream() = 0;
+
+ // Closes the audio stream which should shut down the corresponding
+ // AudioOutputController in the peer process. Usage of an AudioOutputIPC must
+ // always end with a call to CloseStream(), and the |delegate| passed to other
+ // method must remain valid until then. An exception is if OnIPCClosed is
+ // called first.
+ virtual void CloseStream() = 0;
+
+ // Sets the volume of the audio stream.
+ virtual void SetVolume(double volume) = 0;
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_OUTPUT_IPC_H_
diff --git a/third_party/chromium/media/audio/audio_output_proxy.cc b/third_party/chromium/media/audio/audio_output_proxy.cc
new file mode 100644
index 0000000..d205d67
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_output_proxy.cc
@@ -0,0 +1,102 @@
+// Copyright (c) 2012 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 "media/audio/audio_output_proxy.h"
+
+#include "base/check_op.h"
+#include "media/audio/audio_manager.h"
+#include "media/audio/audio_output_dispatcher.h"
+
+namespace media {
+
+AudioOutputProxy::AudioOutputProxy(
+ base::WeakPtr<AudioOutputDispatcher> dispatcher)
+ : dispatcher_(std::move(dispatcher)), state_(kCreated), volume_(1.0) {
+ DCHECK(dispatcher_);
+}
+
+AudioOutputProxy::~AudioOutputProxy() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(state_ == kCreated || state_ == kClosed) << "State is: " << state_;
+}
+
+bool AudioOutputProxy::Open() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK_EQ(state_, kCreated);
+
+ if (!dispatcher_ || !dispatcher_->OpenStream()) {
+ state_ = kOpenError;
+ return false;
+ }
+
+ state_ = kOpened;
+ return true;
+}
+
+void AudioOutputProxy::Start(AudioSourceCallback* callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ // We need to support both states since the callback may not handle OnError()
+ // immediately (or at all). It's also possible for subsequent StartStream()
+ // calls to succeed after failing, so we allow it to be called again.
+ DCHECK(state_ == kOpened || state_ == kStartError);
+
+ if (!dispatcher_ || !dispatcher_->StartStream(callback, this)) {
+ state_ = kStartError;
+ callback->OnError(AudioSourceCallback::ErrorType::kUnknown);
+ return;
+ }
+ state_ = kPlaying;
+}
+
+void AudioOutputProxy::Stop() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ if (state_ != kPlaying)
+ return;
+
+ if (dispatcher_)
+ dispatcher_->StopStream(this);
+ state_ = kOpened;
+}
+
+void AudioOutputProxy::SetVolume(double volume) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ volume_ = volume;
+
+ if (dispatcher_)
+ dispatcher_->StreamVolumeSet(this, volume);
+}
+
+void AudioOutputProxy::GetVolume(double* volume) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ *volume = volume_;
+}
+
+void AudioOutputProxy::Close() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(state_ == kCreated || state_ == kOpenError || state_ == kOpened ||
+ state_ == kStartError);
+
+ // kStartError means OpenStream() succeeded and the stream must be closed
+ // before destruction.
+ if (state_ != kCreated && state_ != kOpenError && dispatcher_)
+ dispatcher_->CloseStream(this);
+
+ state_ = kClosed;
+
+ // Delete the object now like is done in the Close() implementation of
+ // physical stream objects. If we delete the object via DeleteSoon, we
+ // unnecessarily complicate the Shutdown procedure of the
+ // dispatcher+audio manager.
+ delete this;
+}
+
+void AudioOutputProxy::Flush() {
+ DCHECK(state_ != kPlaying);
+
+ if (dispatcher_)
+ dispatcher_->FlushStream(this);
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_output_proxy.h b/third_party/chromium/media/audio/audio_output_proxy.h
new file mode 100644
index 0000000..cf6b13d
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_output_proxy.h
@@ -0,0 +1,71 @@
+// Copyright (c) 2012 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 MEDIA_AUDIO_AUDIO_OUTPUT_PROXY_H_
+#define MEDIA_AUDIO_AUDIO_OUTPUT_PROXY_H_
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "media/audio/audio_io.h"
+#include "media/base/audio_parameters.h"
+
+namespace media {
+
+class AudioOutputDispatcher;
+
+// AudioOutputProxy is an audio output stream that uses resources more
+// efficiently than a regular audio output stream: it opens audio
+// device only when sound is playing, i.e. between Start() and Stop()
+// (there is still one physical stream per each audio output proxy in
+// playing state).
+//
+// AudioOutputProxy uses AudioOutputDispatcher to open and close
+// physical output streams.
+class MEDIA_EXPORT AudioOutputProxy : public AudioOutputStream {
+ public:
+ // Caller keeps ownership of |dispatcher|.
+ explicit AudioOutputProxy(base::WeakPtr<AudioOutputDispatcher> dispatcher);
+
+ // AudioOutputStream interface.
+ bool Open() override;
+ void Start(AudioSourceCallback* callback) override;
+ void Stop() override;
+ void SetVolume(double volume) override;
+ void GetVolume(double* volume) override;
+ void Close() override;
+ void Flush() override;
+
+ AudioOutputDispatcher* get_dispatcher_for_testing() const {
+ return dispatcher_.get();
+ }
+
+ private:
+ enum State {
+ kCreated,
+ kOpened,
+ kPlaying,
+ kClosed,
+ kOpenError,
+ kStartError,
+ };
+
+ ~AudioOutputProxy() override;
+
+ base::WeakPtr<AudioOutputDispatcher> dispatcher_;
+ State state_;
+
+ // Need to save volume here, so that we can restore it in case the stream
+ // is stopped, and then started again.
+ double volume_;
+
+ SEQUENCE_CHECKER(sequence_checker_);
+
+ DISALLOW_COPY_AND_ASSIGN(AudioOutputProxy);
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_OUTPUT_PROXY_H_
diff --git a/third_party/chromium/media/audio/audio_output_proxy_unittest.cc b/third_party/chromium/media/audio/audio_output_proxy_unittest.cc
new file mode 100644
index 0000000..2ec7ae3
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_output_proxy_unittest.cc
@@ -0,0 +1,873 @@
+// Copyright (c) 2012 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 <stdint.h>
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/test/task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "media/audio/audio_manager.h"
+#include "media/audio/audio_manager_base.h"
+#include "media/audio/audio_output_dispatcher_impl.h"
+#include "media/audio/audio_output_proxy.h"
+#include "media/audio/audio_output_resampler.h"
+#include "media/audio/fake_audio_log_factory.h"
+#include "media/audio/fake_audio_output_stream.h"
+#include "media/audio/test_audio_thread.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+using ::testing::AllOf;
+using ::testing::DoAll;
+using ::testing::Field;
+using ::testing::Mock;
+using ::testing::NotNull;
+using ::testing::Return;
+using ::testing::SetArrayArgument;
+using media::AudioBus;
+using media::AudioInputStream;
+using media::AudioManager;
+using media::AudioManagerBase;
+using media::AudioOutputDispatcher;
+using media::AudioOutputProxy;
+using media::AudioOutputStream;
+using media::AudioParameters;
+using media::FakeAudioOutputStream;
+using media::TestAudioThread;
+
+namespace {
+
+static const int kTestCloseDelayMs = 10;
+
+// Delay between callbacks to AudioSourceCallback::OnMoreData.
+static const int kOnMoreDataCallbackDelayMs = 10;
+
+// Let start run long enough for many OnMoreData callbacks to occur.
+static const int kStartRunTimeMs = kOnMoreDataCallbackDelayMs * 10;
+
+// Dummy function.
+std::unique_ptr<media::AudioDebugRecorder> RegisterDebugRecording(
+ const media::AudioParameters& params) {
+ return nullptr;
+}
+
+class MockAudioOutputStream : public AudioOutputStream {
+ public:
+ MockAudioOutputStream(AudioManagerBase* manager,
+ const AudioParameters& params)
+ : start_called_(false),
+ stop_called_(false),
+ params_(params),
+ fake_output_stream_(
+ FakeAudioOutputStream::MakeFakeStream(manager, params_)) {
+ }
+
+ void Start(AudioSourceCallback* callback) override {
+ start_called_ = true;
+ fake_output_stream_->Start(callback);
+ }
+
+ void Stop() override {
+ stop_called_ = true;
+ fake_output_stream_->Stop();
+ }
+
+ ~MockAudioOutputStream() override = default;
+
+ bool start_called() { return start_called_; }
+ bool stop_called() { return stop_called_; }
+
+ MOCK_METHOD0(Open, bool());
+ MOCK_METHOD1(SetVolume, void(double volume));
+ MOCK_METHOD1(GetVolume, void(double* volume));
+ MOCK_METHOD0(Close, void());
+ MOCK_METHOD0(Flush, void());
+
+ private:
+ bool start_called_;
+ bool stop_called_;
+ AudioParameters params_;
+ std::unique_ptr<AudioOutputStream> fake_output_stream_;
+};
+
+class MockAudioManager : public AudioManagerBase {
+ public:
+ MockAudioManager()
+ : AudioManagerBase(std::make_unique<TestAudioThread>(),
+ &fake_audio_log_factory_) {}
+ ~MockAudioManager() override { Shutdown(); }
+
+ MOCK_METHOD3(MakeAudioOutputStream,
+ AudioOutputStream*(const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback));
+ MOCK_METHOD2(MakeAudioOutputStreamProxy, AudioOutputStream*(
+ const AudioParameters& params,
+ const std::string& device_id));
+ MOCK_METHOD3(MakeAudioInputStream,
+ AudioInputStream*(const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback));
+ MOCK_METHOD0(GetTaskRunner, scoped_refptr<base::SingleThreadTaskRunner>());
+ MOCK_METHOD0(GetWorkerTaskRunner,
+ scoped_refptr<base::SingleThreadTaskRunner>());
+ MOCK_METHOD0(GetName, const char*());
+
+ MOCK_METHOD2(MakeLinearOutputStream,
+ AudioOutputStream*(const AudioParameters& params,
+ const LogCallback& log_callback));
+ MOCK_METHOD3(MakeLowLatencyOutputStream,
+ AudioOutputStream*(const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback));
+ MOCK_METHOD3(MakeLinearInputStream,
+ AudioInputStream*(const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback));
+ MOCK_METHOD3(MakeLowLatencyInputStream,
+ AudioInputStream*(const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback));
+
+ protected:
+ MOCK_METHOD0(HasAudioOutputDevices, bool());
+ MOCK_METHOD0(HasAudioInputDevices, bool());
+ MOCK_METHOD1(GetAudioInputDeviceNames,
+ void(media::AudioDeviceNames* device_name));
+ MOCK_METHOD2(GetPreferredOutputStreamParameters, AudioParameters(
+ const std::string& device_id, const AudioParameters& params));
+
+ private:
+ media::FakeAudioLogFactory fake_audio_log_factory_;
+};
+
+class MockAudioSourceCallback : public AudioOutputStream::AudioSourceCallback {
+ public:
+ int OnMoreData(base::TimeDelta /* delay */,
+ base::TimeTicks /* delay_timestamp */,
+ int /* prior_frames_skipped */,
+ AudioBus* dest) override {
+ dest->Zero();
+ return dest->frames();
+ }
+ MOCK_METHOD1(OnError, void(ErrorType));
+};
+
+} // namespace
+
+namespace media {
+
+class AudioOutputProxyTest : public testing::Test {
+ protected:
+ void SetUp() override {
+ // Use a low sample rate and large buffer size when testing otherwise the
+ // FakeAudioOutputStream will keep the message loop busy indefinitely; i.e.,
+ // RunUntilIdle() will never terminate.
+ params_ = AudioParameters(AudioParameters::AUDIO_PCM_LINEAR,
+ CHANNEL_LAYOUT_STEREO, 8000, 2048);
+ InitDispatcher(base::Milliseconds(kTestCloseDelayMs));
+ }
+
+ void TearDown() override {
+ // This is necessary to free all proxy objects that have been
+ // closed by the test.
+ base::RunLoop().RunUntilIdle();
+ }
+
+ virtual void InitDispatcher(base::TimeDelta close_delay) {
+ dispatcher_impl_ = std::make_unique<AudioOutputDispatcherImpl>(
+ &manager(), params_, std::string(), close_delay);
+ }
+
+ virtual void OnStart() {}
+
+ MockAudioManager& manager() {
+ return manager_;
+ }
+
+ void WaitForCloseTimer(MockAudioOutputStream* stream) {
+ base::RunLoop run_loop;
+ EXPECT_CALL(*stream, Close())
+ .WillOnce(testing::InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
+ run_loop.Run();
+ }
+
+ void CloseAndWaitForCloseTimer(AudioOutputProxy* proxy,
+ MockAudioOutputStream* stream) {
+ // Close the stream and verify it doesn't happen immediately.
+ proxy->Close();
+ Mock::VerifyAndClear(stream);
+
+ // Wait for the actual close event to come from the close timer.
+ WaitForCloseTimer(stream);
+ }
+
+ // Basic Open() and Close() test.
+ void OpenAndClose(AudioOutputDispatcher* dispatcher) {
+ MockAudioOutputStream stream(&manager_, params_);
+
+ EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _))
+ .WillOnce(Return(&stream));
+ EXPECT_CALL(stream, Open())
+ .WillOnce(Return(true));
+
+ AudioOutputProxy* proxy = dispatcher->CreateStreamProxy();
+ EXPECT_TRUE(proxy->Open());
+ CloseAndWaitForCloseTimer(proxy, &stream);
+ }
+
+ // Creates a stream, and then calls Start() and Stop().
+ void StartAndStop(AudioOutputDispatcher* dispatcher) {
+ MockAudioOutputStream stream(&manager_, params_);
+
+ EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _))
+ .WillOnce(Return(&stream));
+ EXPECT_CALL(stream, Open())
+ .WillOnce(Return(true));
+ EXPECT_CALL(stream, SetVolume(_))
+ .Times(1);
+
+ AudioOutputProxy* proxy = dispatcher->CreateStreamProxy();
+ EXPECT_TRUE(proxy->Open());
+
+ proxy->Start(&callback_);
+ OnStart();
+ proxy->Stop();
+
+ CloseAndWaitForCloseTimer(proxy, &stream);
+ EXPECT_TRUE(stream.stop_called());
+ EXPECT_TRUE(stream.start_called());
+ }
+
+ // Verify that the stream is closed after Stop() is called.
+ void CloseAfterStop(AudioOutputDispatcher* dispatcher) {
+ MockAudioOutputStream stream(&manager_, params_);
+
+ EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _))
+ .WillOnce(Return(&stream));
+ EXPECT_CALL(stream, Open())
+ .WillOnce(Return(true));
+ EXPECT_CALL(stream, SetVolume(_))
+ .Times(1);
+
+ AudioOutputProxy* proxy = dispatcher->CreateStreamProxy();
+ EXPECT_TRUE(proxy->Open());
+
+ proxy->Start(&callback_);
+ OnStart();
+ proxy->Stop();
+
+ // Wait for the close timer to fire after StopStream().
+ WaitForCloseTimer(&stream);
+ proxy->Close();
+ EXPECT_TRUE(stream.stop_called());
+ EXPECT_TRUE(stream.start_called());
+ }
+
+ // Create two streams, but don't start them. Only one device must be opened.
+ void TwoStreams(AudioOutputDispatcher* dispatcher) {
+ MockAudioOutputStream stream(&manager_, params_);
+
+ EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _))
+ .WillOnce(Return(&stream));
+ EXPECT_CALL(stream, Open())
+ .WillOnce(Return(true));
+
+ AudioOutputProxy* proxy1 = dispatcher->CreateStreamProxy();
+ AudioOutputProxy* proxy2 = dispatcher->CreateStreamProxy();
+ EXPECT_TRUE(proxy1->Open());
+ EXPECT_TRUE(proxy2->Open());
+ proxy1->Close();
+ CloseAndWaitForCloseTimer(proxy2, &stream);
+ EXPECT_FALSE(stream.stop_called());
+ EXPECT_FALSE(stream.start_called());
+ }
+
+ // Open() method failed.
+ void OpenFailed(AudioOutputDispatcher* dispatcher) {
+ MockAudioOutputStream stream(&manager_, params_);
+
+ EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _))
+ .WillOnce(Return(&stream));
+ EXPECT_CALL(stream, Open())
+ .WillOnce(Return(false));
+ EXPECT_CALL(stream, Close())
+ .Times(1);
+
+ AudioOutputProxy* proxy = dispatcher->CreateStreamProxy();
+ EXPECT_FALSE(proxy->Open());
+ proxy->Close();
+ EXPECT_FALSE(stream.stop_called());
+ EXPECT_FALSE(stream.start_called());
+ }
+
+ void CreateAndWait(AudioOutputDispatcher* dispatcher) {
+ MockAudioOutputStream stream(&manager_, params_);
+
+ EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _))
+ .WillOnce(Return(&stream));
+ EXPECT_CALL(stream, Open())
+ .WillOnce(Return(true));
+
+ AudioOutputProxy* proxy = dispatcher->CreateStreamProxy();
+ EXPECT_TRUE(proxy->Open());
+
+ WaitForCloseTimer(&stream);
+ proxy->Close();
+ EXPECT_FALSE(stream.stop_called());
+ EXPECT_FALSE(stream.start_called());
+ }
+
+ void OneStream_TwoPlays(AudioOutputDispatcher* dispatcher) {
+ MockAudioOutputStream stream(&manager_, params_);
+
+ EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _))
+ .WillOnce(Return(&stream));
+
+ EXPECT_CALL(stream, Open())
+ .WillOnce(Return(true));
+ EXPECT_CALL(stream, SetVolume(_))
+ .Times(2);
+
+ AudioOutputProxy* proxy1 = dispatcher->CreateStreamProxy();
+ EXPECT_TRUE(proxy1->Open());
+
+ proxy1->Start(&callback_);
+ OnStart();
+ proxy1->Stop();
+
+ // The stream should now be idle and get reused by |proxy2|.
+ AudioOutputProxy* proxy2 = dispatcher->CreateStreamProxy();
+ EXPECT_TRUE(proxy2->Open());
+ proxy2->Start(&callback_);
+ OnStart();
+ proxy2->Stop();
+
+ proxy1->Close();
+ CloseAndWaitForCloseTimer(proxy2, &stream);
+ EXPECT_TRUE(stream.stop_called());
+ EXPECT_TRUE(stream.start_called());
+ }
+
+ void TwoStreams_BothPlaying(AudioOutputDispatcher* dispatcher) {
+ MockAudioOutputStream stream1(&manager_, params_);
+ MockAudioOutputStream stream2(&manager_, params_);
+
+ EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _))
+ .WillOnce(Return(&stream1))
+ .WillOnce(Return(&stream2));
+
+ EXPECT_CALL(stream1, Open())
+ .WillOnce(Return(true));
+ EXPECT_CALL(stream1, SetVolume(_))
+ .Times(1);
+
+ EXPECT_CALL(stream2, Open())
+ .WillOnce(Return(true));
+ EXPECT_CALL(stream2, SetVolume(_))
+ .Times(1);
+
+ AudioOutputProxy* proxy1 = dispatcher->CreateStreamProxy();
+ AudioOutputProxy* proxy2 = dispatcher->CreateStreamProxy();
+ EXPECT_TRUE(proxy1->Open());
+ EXPECT_TRUE(proxy2->Open());
+
+ proxy1->Start(&callback_);
+ proxy2->Start(&callback_);
+ OnStart();
+ proxy1->Stop();
+ CloseAndWaitForCloseTimer(proxy1, &stream1);
+
+ proxy2->Stop();
+ CloseAndWaitForCloseTimer(proxy2, &stream2);
+
+ EXPECT_TRUE(stream1.stop_called());
+ EXPECT_TRUE(stream1.start_called());
+ EXPECT_TRUE(stream2.stop_called());
+ EXPECT_TRUE(stream2.start_called());
+ }
+
+ void StartFailed(AudioOutputDispatcher* dispatcher) {
+ MockAudioOutputStream stream(&manager_, params_);
+
+ EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _))
+ .WillOnce(Return(&stream));
+ EXPECT_CALL(stream, Open())
+ .WillOnce(Return(true));
+
+ AudioOutputProxy* proxy = dispatcher->CreateStreamProxy();
+ EXPECT_TRUE(proxy->Open());
+
+ WaitForCloseTimer(&stream);
+
+ // |stream| is closed at this point. Start() should reopen it again.
+ EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _))
+ .Times(2)
+ .WillRepeatedly(Return(reinterpret_cast<AudioOutputStream*>(NULL)));
+
+ EXPECT_CALL(callback_, OnError(_)).Times(2);
+
+ proxy->Start(&callback_);
+
+ // Double Start() in the error case should be allowed since it's possible a
+ // callback may not have had time to process the OnError() in between.
+ proxy->Stop();
+ proxy->Start(&callback_);
+
+ Mock::VerifyAndClear(&callback_);
+
+ proxy->Close();
+ }
+
+ void DispatcherDestroyed_BeforeOpen(
+ std::unique_ptr<AudioOutputDispatcher> dispatcher) {
+ EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _)).Times(0);
+ AudioOutputProxy* proxy = dispatcher->CreateStreamProxy();
+ dispatcher.reset();
+ EXPECT_FALSE(proxy->Open());
+ proxy->Close();
+ }
+
+ void DispatcherDestroyed_BeforeStart(
+ std::unique_ptr<AudioOutputDispatcher> dispatcher) {
+ MockAudioOutputStream stream(&manager_, params_);
+ EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _))
+ .WillOnce(Return(&stream));
+ EXPECT_CALL(stream, Open()).WillOnce(Return(true));
+ EXPECT_CALL(stream, Close()).Times(1);
+ AudioOutputProxy* proxy = dispatcher->CreateStreamProxy();
+ EXPECT_TRUE(proxy->Open());
+
+ EXPECT_CALL(callback_, OnError(_)).Times(1);
+ dispatcher.reset();
+ proxy->Start(&callback_);
+ proxy->Stop();
+ proxy->Close();
+ }
+
+ void DispatcherDestroyed_BeforeStop(
+ std::unique_ptr<AudioOutputDispatcher> dispatcher) {
+ MockAudioOutputStream stream(&manager_, params_);
+ EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _))
+ .WillOnce(Return(&stream));
+ EXPECT_CALL(stream, Open()).WillOnce(Return(true));
+ EXPECT_CALL(stream, Close()).Times(1);
+ EXPECT_CALL(stream, SetVolume(_)).Times(1);
+
+ AudioOutputProxy* proxy = dispatcher->CreateStreamProxy();
+ EXPECT_TRUE(proxy->Open());
+ proxy->Start(&callback_);
+ dispatcher.reset();
+ proxy->Stop();
+ proxy->Close();
+ }
+
+ void DispatcherDestroyed_AfterStop(
+ std::unique_ptr<AudioOutputDispatcher> dispatcher) {
+ MockAudioOutputStream stream(&manager_, params_);
+ EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _))
+ .WillOnce(Return(&stream));
+ EXPECT_CALL(stream, Open()).WillOnce(Return(true));
+ EXPECT_CALL(stream, Close()).Times(1);
+ EXPECT_CALL(stream, SetVolume(_)).Times(1);
+
+ AudioOutputProxy* proxy = dispatcher->CreateStreamProxy();
+ EXPECT_TRUE(proxy->Open());
+ proxy->Start(&callback_);
+ proxy->Stop();
+ dispatcher.reset();
+ proxy->Close();
+ }
+
+ base::test::SingleThreadTaskEnvironment task_environment_;
+ MockAudioManager manager_;
+ std::unique_ptr<AudioOutputDispatcherImpl> dispatcher_impl_;
+ MockAudioSourceCallback callback_;
+ AudioParameters params_;
+};
+
+class AudioOutputResamplerTest : public AudioOutputProxyTest {
+ public:
+ void InitDispatcher(base::TimeDelta close_delay) override {
+ // Use a low sample rate and large buffer size when testing otherwise the
+ // FakeAudioOutputStream will keep the message loop busy indefinitely; i.e.,
+ // RunUntilIdle() will never terminate.
+ resampler_params_ = AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
+ CHANNEL_LAYOUT_STEREO, 16000, 1024);
+ resampler_ = std::make_unique<AudioOutputResampler>(
+ &manager(), params_, resampler_params_, std::string(), close_delay,
+ base::BindRepeating(&RegisterDebugRecording));
+ }
+
+ void OnStart() override {
+ // Let Start() run for a bit.
+ base::RunLoop run_loop;
+ task_environment_.GetMainThreadTaskRunner()->PostDelayedTask(
+ FROM_HERE, run_loop.QuitClosure(), base::Milliseconds(kStartRunTimeMs));
+ run_loop.Run();
+ }
+
+ protected:
+ AudioParameters resampler_params_;
+ std::unique_ptr<AudioOutputResampler> resampler_;
+};
+
+TEST_F(AudioOutputProxyTest, CreateAndClose) {
+ AudioOutputProxy* proxy = dispatcher_impl_->CreateStreamProxy();
+ proxy->Close();
+}
+
+TEST_F(AudioOutputResamplerTest, CreateAndClose) {
+ AudioOutputProxy* proxy = resampler_->CreateStreamProxy();
+ proxy->Close();
+}
+
+TEST_F(AudioOutputProxyTest, OpenAndClose) {
+ OpenAndClose(dispatcher_impl_.get());
+}
+
+TEST_F(AudioOutputResamplerTest, OpenAndClose) {
+ OpenAndClose(resampler_.get());
+}
+
+// Create a stream, and verify that it is closed after kTestCloseDelayMs.
+// if it doesn't start playing.
+TEST_F(AudioOutputProxyTest, CreateAndWait) {
+ CreateAndWait(dispatcher_impl_.get());
+}
+
+// Create a stream, and verify that it is closed after kTestCloseDelayMs.
+// if it doesn't start playing.
+TEST_F(AudioOutputResamplerTest, CreateAndWait) {
+ CreateAndWait(resampler_.get());
+}
+
+TEST_F(AudioOutputProxyTest, StartAndStop) {
+ StartAndStop(dispatcher_impl_.get());
+}
+
+TEST_F(AudioOutputResamplerTest, StartAndStop) {
+ StartAndStop(resampler_.get());
+}
+
+TEST_F(AudioOutputProxyTest, CloseAfterStop) {
+ CloseAfterStop(dispatcher_impl_.get());
+}
+
+TEST_F(AudioOutputResamplerTest, CloseAfterStop) {
+ CloseAfterStop(resampler_.get());
+}
+
+TEST_F(AudioOutputProxyTest, TwoStreams) {
+ TwoStreams(dispatcher_impl_.get());
+}
+
+TEST_F(AudioOutputResamplerTest, TwoStreams) {
+ TwoStreams(resampler_.get());
+}
+
+// Two streams: verify that second stream is allocated when the first
+// starts playing.
+TEST_F(AudioOutputProxyTest, OneStream_TwoPlays) {
+ OneStream_TwoPlays(dispatcher_impl_.get());
+}
+
+TEST_F(AudioOutputResamplerTest, OneStream_TwoPlays) {
+ OneStream_TwoPlays(resampler_.get());
+}
+
+// Two streams, both are playing. Dispatcher should not open a third stream.
+TEST_F(AudioOutputProxyTest, TwoStreams_BothPlaying) {
+ TwoStreams_BothPlaying(dispatcher_impl_.get());
+}
+
+TEST_F(AudioOutputResamplerTest, TwoStreams_BothPlaying) {
+ TwoStreams_BothPlaying(resampler_.get());
+}
+
+TEST_F(AudioOutputProxyTest, OpenFailed) {
+ OpenFailed(dispatcher_impl_.get());
+}
+
+// Start() method failed.
+TEST_F(AudioOutputProxyTest, StartFailed) {
+ StartFailed(dispatcher_impl_.get());
+}
+
+TEST_F(AudioOutputResamplerTest, StartFailed) {
+ StartFailed(resampler_.get());
+}
+
+TEST_F(AudioOutputProxyTest, DispatcherDestroyed_BeforeOpen) {
+ DispatcherDestroyed_BeforeOpen(std::move(dispatcher_impl_));
+}
+
+TEST_F(AudioOutputResamplerTest, DispatcherDestroyed_BeforeOpen) {
+ DispatcherDestroyed_BeforeOpen(std::move(resampler_));
+}
+
+TEST_F(AudioOutputProxyTest, DispatcherDestroyed_BeforeStart) {
+ DispatcherDestroyed_BeforeStart(std::move(dispatcher_impl_));
+}
+
+TEST_F(AudioOutputResamplerTest, DispatcherDestroyed_BeforeStart) {
+ DispatcherDestroyed_BeforeStart(std::move(resampler_));
+}
+
+TEST_F(AudioOutputProxyTest, DispatcherDestroyed_BeforeStop) {
+ DispatcherDestroyed_BeforeStop(std::move(dispatcher_impl_));
+}
+
+TEST_F(AudioOutputResamplerTest, DispatcherDestroyed_BeforeStop) {
+ DispatcherDestroyed_BeforeStop(std::move(resampler_));
+}
+
+TEST_F(AudioOutputProxyTest, DispatcherDestroyed_AfterStop) {
+ DispatcherDestroyed_AfterStop(std::move(dispatcher_impl_));
+}
+
+TEST_F(AudioOutputResamplerTest, DispatcherDestroyed_AfterStop) {
+ DispatcherDestroyed_AfterStop(std::move(resampler_));
+}
+
+TEST_F(AudioOutputProxyTest, DispatcherDeviceChangeClosesIdleStreams) {
+ // Set close delay so long that it triggers a test timeout if relied upon.
+ InitDispatcher(base::Seconds(1000));
+
+ MockAudioOutputStream stream(&manager_, params_);
+
+ EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _))
+ .WillOnce(Return(&stream));
+ EXPECT_CALL(stream, Open()).WillOnce(Return(true));
+
+ AudioOutputProxy* proxy = dispatcher_impl_->CreateStreamProxy();
+ EXPECT_TRUE(proxy->Open());
+
+ // Close the stream and verify it doesn't happen immediately.
+ proxy->Close();
+ Mock::VerifyAndClear(&stream);
+
+ // This should trigger a true close on the stream.
+ dispatcher_impl_->OnDeviceChange();
+
+ base::RunLoop run_loop;
+ EXPECT_CALL(stream, Close())
+ .WillOnce(testing::InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
+ run_loop.Run();
+}
+
+// Simulate AudioOutputStream::Create() failure with a low latency stream and
+// ensure AudioOutputResampler falls back to the high latency path.
+TEST_F(AudioOutputResamplerTest, LowLatencyCreateFailedFallback) {
+ MockAudioOutputStream stream(&manager_, params_);
+ EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _))
+ .Times(2)
+ .WillOnce(Return(static_cast<AudioOutputStream*>(NULL)))
+ .WillRepeatedly(Return(&stream));
+ EXPECT_CALL(stream, Open())
+ .WillOnce(Return(true));
+
+ AudioOutputProxy* proxy = resampler_->CreateStreamProxy();
+ EXPECT_TRUE(proxy->Open());
+ CloseAndWaitForCloseTimer(proxy, &stream);
+}
+
+// Simulate AudioOutputStream::Open() failure with a low latency stream and
+// ensure AudioOutputResampler falls back to the high latency path.
+TEST_F(AudioOutputResamplerTest, LowLatencyOpenFailedFallback) {
+ MockAudioOutputStream failed_stream(&manager_, params_);
+ MockAudioOutputStream okay_stream(&manager_, params_);
+ EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _))
+ .Times(2)
+ .WillOnce(Return(&failed_stream))
+ .WillRepeatedly(Return(&okay_stream));
+ EXPECT_CALL(failed_stream, Open())
+ .WillOnce(Return(false));
+ EXPECT_CALL(failed_stream, Close())
+ .Times(1);
+ EXPECT_CALL(okay_stream, Open())
+ .WillOnce(Return(true));
+
+ AudioOutputProxy* proxy = resampler_->CreateStreamProxy();
+ EXPECT_TRUE(proxy->Open());
+ CloseAndWaitForCloseTimer(proxy, &okay_stream);
+}
+
+// Simulate failures to open both the low latency and the fallback high latency
+// stream and ensure AudioOutputResampler falls back to a fake stream.
+TEST_F(AudioOutputResamplerTest, HighLatencyFallbackFailed) {
+ MockAudioOutputStream okay_stream(&manager_, params_);
+
+// Only Windows has a high latency output driver that is not the same as the low
+// latency path.
+#if defined(OS_WIN)
+ static const int kFallbackCount = 2;
+#else
+ static const int kFallbackCount = 1;
+#endif
+ EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _))
+ .Times(kFallbackCount)
+ .WillRepeatedly(Return(static_cast<AudioOutputStream*>(NULL)));
+
+ // To prevent shared memory issues the sample rate and buffer size should
+ // match the input stream parameters.
+ EXPECT_CALL(manager(),
+ MakeAudioOutputStream(
+ AllOf(testing::Property(&AudioParameters::format,
+ AudioParameters::AUDIO_FAKE),
+ testing::Property(&AudioParameters::sample_rate,
+ params_.sample_rate()),
+ testing::Property(&AudioParameters::frames_per_buffer,
+ params_.frames_per_buffer())),
+ _, _))
+ .Times(1)
+ .WillOnce(Return(&okay_stream));
+ EXPECT_CALL(okay_stream, Open())
+ .WillOnce(Return(true));
+
+ AudioOutputProxy* proxy = resampler_->CreateStreamProxy();
+ EXPECT_TRUE(proxy->Open());
+ CloseAndWaitForCloseTimer(proxy, &okay_stream);
+}
+
+// Simulate failures to open both the low latency, the fallback high latency
+// stream, and the fake audio output stream and ensure AudioOutputResampler
+// terminates normally.
+TEST_F(AudioOutputResamplerTest, AllFallbackFailed) {
+// Only Windows has a high latency output driver that is not the same as the low
+// latency path.
+#if defined(OS_WIN)
+ static const int kFallbackCount = 3;
+#else
+ static const int kFallbackCount = 2;
+#endif
+ EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _))
+ .Times(kFallbackCount)
+ .WillRepeatedly(Return(static_cast<AudioOutputStream*>(NULL)));
+
+ AudioOutputProxy* proxy = resampler_->CreateStreamProxy();
+ EXPECT_FALSE(proxy->Open());
+ proxy->Close();
+}
+
+// Simulate an eventual OpenStream() failure; i.e. successful OpenStream() calls
+// eventually followed by one which fails; root cause of http://crbug.com/150619
+TEST_F(AudioOutputResamplerTest, LowLatencyOpenEventuallyFails) {
+ MockAudioOutputStream stream1(&manager_, params_);
+ MockAudioOutputStream stream2(&manager_, params_);
+
+ // Setup the mock such that all three streams are successfully created.
+ EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _))
+ .WillOnce(Return(&stream1))
+ .WillOnce(Return(&stream2))
+ .WillRepeatedly(Return(static_cast<AudioOutputStream*>(NULL)));
+
+ // Stream1 should be able to successfully open and start.
+ EXPECT_CALL(stream1, Open())
+ .WillOnce(Return(true));
+ EXPECT_CALL(stream1, SetVolume(_))
+ .Times(1);
+
+ // Stream2 should also be able to successfully open and start.
+ EXPECT_CALL(stream2, Open())
+ .WillOnce(Return(true));
+ EXPECT_CALL(stream2, SetVolume(_))
+ .Times(1);
+
+ // Open and start the first proxy and stream.
+ AudioOutputProxy* proxy1 = resampler_->CreateStreamProxy();
+ EXPECT_TRUE(proxy1->Open());
+ proxy1->Start(&callback_);
+ OnStart();
+
+ // Open and start the second proxy and stream.
+ AudioOutputProxy* proxy2 = resampler_->CreateStreamProxy();
+ EXPECT_TRUE(proxy2->Open());
+ proxy2->Start(&callback_);
+ OnStart();
+
+ // Attempt to open the third stream which should fail.
+ AudioOutputProxy* proxy3 = resampler_->CreateStreamProxy();
+ EXPECT_FALSE(proxy3->Open());
+ proxy3->Close();
+
+ // Perform the required Stop()/Close() shutdown dance for each proxy. Under
+ // the hood each proxy should correctly call CloseStream() if OpenStream()
+ // succeeded or not.
+ proxy2->Stop();
+ CloseAndWaitForCloseTimer(proxy2, &stream2);
+
+ proxy1->Stop();
+ CloseAndWaitForCloseTimer(proxy1, &stream1);
+
+ EXPECT_TRUE(stream1.stop_called());
+ EXPECT_TRUE(stream1.start_called());
+ EXPECT_TRUE(stream2.stop_called());
+ EXPECT_TRUE(stream2.start_called());
+}
+
+// Simulate failures to open both the low latency and the fallback high latency
+// stream and ensure AudioOutputResampler falls back to a fake stream. Ensure
+// that after the close delay elapses, opening another stream succeeds with a
+// non-fake stream.
+TEST_F(AudioOutputResamplerTest, FallbackRecovery) {
+ MockAudioOutputStream fake_stream(&manager_, params_);
+
+ // Trigger the fallback mechanism until a fake output stream is created.
+#if defined(OS_WIN)
+ static const int kFallbackCount = 2;
+#else
+ static const int kFallbackCount = 1;
+#endif
+ EXPECT_CALL(manager(), MakeAudioOutputStream(_, _, _))
+ .Times(kFallbackCount)
+ .WillRepeatedly(Return(static_cast<AudioOutputStream*>(NULL)));
+ EXPECT_CALL(manager(),
+ MakeAudioOutputStream(
+ AllOf(testing::Property(&AudioParameters::format,
+ AudioParameters::AUDIO_FAKE),
+ testing::Property(&AudioParameters::sample_rate,
+ params_.sample_rate()),
+ testing::Property(&AudioParameters::frames_per_buffer,
+ params_.frames_per_buffer())),
+ _, _))
+ .WillOnce(Return(&fake_stream));
+ EXPECT_CALL(fake_stream, Open()).WillOnce(Return(true));
+ AudioOutputProxy* proxy = resampler_->CreateStreamProxy();
+ EXPECT_TRUE(proxy->Open());
+ CloseAndWaitForCloseTimer(proxy, &fake_stream);
+
+ // Once all proxies have been closed, AudioOutputResampler will start the
+ // reinitialization timer and execute it after the close delay elapses.
+ base::RunLoop run_loop;
+ task_environment_.GetMainThreadTaskRunner()->PostDelayedTask(
+ FROM_HERE, run_loop.QuitClosure(),
+ base::Milliseconds(2 * kTestCloseDelayMs));
+ run_loop.Run();
+
+ // Verify a non-fake stream can be created.
+ MockAudioOutputStream real_stream(&manager_, params_);
+ EXPECT_CALL(manager(),
+ MakeAudioOutputStream(
+ testing::Property(&AudioParameters::format,
+ testing::Ne(AudioParameters::AUDIO_FAKE)),
+ _, _))
+ .WillOnce(Return(&real_stream));
+
+ // Stream1 should be able to successfully open and start.
+ EXPECT_CALL(real_stream, Open()).WillOnce(Return(true));
+ proxy = resampler_->CreateStreamProxy();
+ EXPECT_TRUE(proxy->Open());
+ CloseAndWaitForCloseTimer(proxy, &real_stream);
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_output_resampler.cc b/third_party/chromium/media/audio/audio_output_resampler.cc
new file mode 100644
index 0000000..8b75d98
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_output_resampler.cc
@@ -0,0 +1,502 @@
+// Copyright (c) 2012 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 "media/audio/audio_output_resampler.h"
+
+#include <stdint.h>
+
+#include <algorithm>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/trace_event/trace_event.h"
+#include "build/build_config.h"
+#include "media/audio/audio_manager.h"
+#include "media/audio/audio_output_dispatcher_impl.h"
+#include "media/audio/audio_output_proxy.h"
+#include "media/base/audio_converter.h"
+#include "media/base/audio_timestamp_helper.h"
+#include "media/base/limits.h"
+#include "media/base/sample_rates.h"
+
+namespace media {
+
+class OnMoreDataConverter
+ : public AudioOutputStream::AudioSourceCallback,
+ public AudioConverter::InputCallback {
+ public:
+ OnMoreDataConverter(const AudioParameters& input_params,
+ const AudioParameters& output_params,
+ std::unique_ptr<AudioDebugRecorder> debug_recorder);
+
+ OnMoreDataConverter(const OnMoreDataConverter&) = delete;
+ OnMoreDataConverter& operator=(const OnMoreDataConverter&) = delete;
+
+ ~OnMoreDataConverter() override;
+
+ // AudioSourceCallback interface.
+ int OnMoreData(base::TimeDelta delay,
+ base::TimeTicks delay_timestamp,
+ int prior_frames_skipped,
+ AudioBus* dest) override;
+ void OnError(ErrorType type) override;
+
+ // Sets |source_callback_|. If this is not a new object, then Stop() must be
+ // called before Start().
+ void Start(AudioOutputStream::AudioSourceCallback* callback);
+
+ // Clears |source_callback_| and flushes the resampler.
+ void Stop();
+
+ bool started() const { return source_callback_ != nullptr; }
+
+ bool error_occurred() const { return error_occurred_; }
+
+ private:
+ // AudioConverter::InputCallback implementation.
+ double ProvideInput(AudioBus* audio_bus, uint32_t frames_delayed) override;
+
+ // Source callback.
+ AudioOutputStream::AudioSourceCallback* source_callback_;
+
+ // Last |delay| and |delay_timestamp| received via OnMoreData(). Used to
+ // correct playback delay in ProvideInput() before calling |source_callback_|.
+ base::TimeDelta current_delay_;
+ base::TimeTicks current_delay_timestamp_;
+
+ const int input_samples_per_second_;
+
+ // Handles resampling, buffering, and channel mixing between input and output
+ // parameters.
+ AudioConverter audio_converter_;
+
+ // True if OnError() was ever called. Should only be read if the underlying
+ // stream has been stopped.
+ bool error_occurred_;
+
+ // Information about input and output buffer sizes to be traced.
+ const int input_buffer_size_;
+ const int output_buffer_size_;
+
+ // For audio debug recordings.
+ std::unique_ptr<AudioDebugRecorder> debug_recorder_;
+};
+
+namespace {
+
+// Record UMA statistics for hardware output configuration.
+static void RecordStats(const AudioParameters& output_params) {
+ base::UmaHistogramEnumeration(
+ "Media.HardwareAudioChannelLayout", output_params.channel_layout(),
+ static_cast<ChannelLayout>(CHANNEL_LAYOUT_MAX + 1));
+ base::UmaHistogramExactLinear("Media.HardwareAudioChannelCount",
+ output_params.channels(),
+ static_cast<int>(limits::kMaxChannels));
+
+ AudioSampleRate asr;
+ if (!ToAudioSampleRate(output_params.sample_rate(), &asr))
+ return;
+
+ base::UmaHistogramEnumeration(
+ "Media.HardwareAudioSamplesPerSecond", asr,
+ static_cast<AudioSampleRate>(kAudioSampleRateMax + 1));
+}
+
+// Only Windows has a high latency output driver that is not the same as the low
+// latency path.
+#if defined(OS_WIN)
+// Converts low latency based |output_params| into high latency appropriate
+// output parameters in error situations.
+AudioParameters GetFallbackOutputParams(
+ const AudioParameters& original_output_params) {
+ DCHECK_EQ(original_output_params.format(),
+ AudioParameters::AUDIO_PCM_LOW_LATENCY);
+ // Choose AudioParameters appropriate for opening the device in high latency
+ // mode. |kMinLowLatencyFrameSize| is arbitrarily based on Pepper Flash's
+ // MAXIMUM frame size for low latency.
+ static const int kMinLowLatencyFrameSize = 2048;
+ const int frames_per_buffer = std::max(
+ original_output_params.frames_per_buffer(), kMinLowLatencyFrameSize);
+
+ return AudioParameters(AudioParameters::AUDIO_PCM_LINEAR,
+ original_output_params.channel_layout(),
+ original_output_params.sample_rate(),
+ frames_per_buffer);
+}
+#endif
+
+// This enum must match the numbering for
+// AudioOutputResamplerOpenLowLatencyStreamResult in enums.xml. Do not reorder
+// or remove items, only add new items before OPEN_STREAM_MAX.
+enum class OpenStreamResult {
+ kFail = 0,
+ kFallbackToFake = 1,
+ kFallbackToLinear = 2,
+ kSuccess = 3,
+ kFallbackToFakeFail = 4,
+ kFallbackToFakeSuccess = 5,
+ kFallbackToLinearFail = 6,
+ kFallbackToLinearSuccess = 7,
+ kSubsequentFail = 8,
+ kSubsequentSuccess = 9,
+ kMaxValue = kSubsequentSuccess,
+};
+
+OpenStreamResult GetSubsequentStreamCreationResultBucket(
+ const AudioParameters& current_params,
+ bool success) {
+ switch (current_params.format()) {
+ case AudioParameters::AUDIO_PCM_LOW_LATENCY:
+ return success ? OpenStreamResult::kSubsequentSuccess
+ : OpenStreamResult::kSubsequentFail;
+ case AudioParameters::AUDIO_PCM_LINEAR:
+ return success ? OpenStreamResult::kFallbackToLinearSuccess
+ : OpenStreamResult::kFallbackToLinearFail;
+ case AudioParameters::AUDIO_FAKE:
+ return success ? OpenStreamResult::kFallbackToFakeSuccess
+ : OpenStreamResult::kFallbackToFakeFail;
+ default:
+ NOTREACHED();
+ return OpenStreamResult::kFail;
+ }
+}
+
+} // namespace
+
+AudioOutputResampler::AudioOutputResampler(
+ AudioManager* audio_manager,
+ const AudioParameters& input_params,
+ const AudioParameters& output_params,
+ const std::string& output_device_id,
+ base::TimeDelta close_delay,
+ const RegisterDebugRecordingSourceCallback&
+ register_debug_recording_source_callback)
+ : AudioOutputDispatcher(audio_manager),
+ close_delay_(close_delay),
+ input_params_(input_params),
+ output_params_(output_params),
+ original_output_params_(output_params),
+ device_id_(output_device_id),
+ reinitialize_timer_(
+ FROM_HERE,
+ close_delay_,
+ base::BindRepeating(&AudioOutputResampler::Reinitialize,
+ base::Unretained(this))),
+ register_debug_recording_source_callback_(
+ register_debug_recording_source_callback) {
+ DCHECK(audio_manager->GetTaskRunner()->BelongsToCurrentThread());
+ DCHECK(input_params.IsValid());
+ DCHECK(output_params.IsValid());
+ DCHECK(output_params_.format() == AudioParameters::AUDIO_PCM_LOW_LATENCY ||
+ output_params_.format() == AudioParameters::AUDIO_PCM_LINEAR);
+ DCHECK(register_debug_recording_source_callback_);
+
+ // Record UMA statistics for the hardware configuration.
+ RecordStats(output_params);
+}
+
+AudioOutputResampler::~AudioOutputResampler() {
+ DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
+ for (const auto& item : callbacks_) {
+ if (item.second->started())
+ StopStreamInternal(item);
+ }
+}
+
+void AudioOutputResampler::Reinitialize() {
+ DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
+
+ // We can only reinitialize the dispatcher if it has no active proxies. Check
+ // if one has been created since the reinitialization timer was started.
+ if (dispatcher_ && dispatcher_->HasOutputProxies())
+ return;
+
+ DCHECK(callbacks_.empty());
+
+ // Log a trace event so we can get feedback in the field when this happens.
+ TRACE_EVENT0("audio", "AudioOutputResampler::Reinitialize");
+
+ output_params_ = original_output_params_;
+ dispatcher_.reset();
+}
+
+std::unique_ptr<AudioOutputDispatcherImpl> AudioOutputResampler::MakeDispatcher(
+ const std::string& output_device_id,
+ const AudioParameters& params) {
+ DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
+ DCHECK(callbacks_.empty());
+ return std::make_unique<AudioOutputDispatcherImpl>(
+ audio_manager(), params, output_device_id, close_delay_);
+}
+
+AudioOutputProxy* AudioOutputResampler::CreateStreamProxy() {
+ DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
+ return new AudioOutputProxy(weak_factory_.GetWeakPtr());
+}
+
+bool AudioOutputResampler::OpenStream() {
+ DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
+
+ bool first_stream = false;
+ if (!dispatcher_) {
+ first_stream = true;
+ // No open streams => no fallback has happened.
+ DCHECK(original_output_params_.Equals(output_params_));
+ DCHECK(callbacks_.empty());
+ dispatcher_ = MakeDispatcher(device_id_, output_params_);
+ }
+
+ constexpr char kFallbackHistogramName[] =
+ "Media.FallbackToHighLatencyAudioPath";
+ constexpr char kOpenLowLatencyHistogramName[] =
+ "Media.AudioOutputResampler.OpenLowLatencyStream";
+
+ if (dispatcher_->OpenStream()) {
+ // Only record the UMA statistic if we didn't fallback during construction
+ // and only for the first stream we open.
+ if (original_output_params_.format() ==
+ AudioParameters::AUDIO_PCM_LOW_LATENCY) {
+ if (first_stream)
+ base::UmaHistogramBoolean(kFallbackHistogramName, false);
+
+ base::UmaHistogramEnumeration(
+ kOpenLowLatencyHistogramName,
+ first_stream
+ ? OpenStreamResult::kSuccess
+ : GetSubsequentStreamCreationResultBucket(output_params_, true));
+ }
+ return true;
+ }
+
+ // Fallback is available for low latency streams only.
+ if (original_output_params_.format() !=
+ AudioParameters::AUDIO_PCM_LOW_LATENCY) {
+ return false;
+ }
+
+ // If we have successfully opened a stream previously, there's nothing more to
+ // be done.
+ if (!first_stream) {
+ base::UmaHistogramEnumeration(
+ kOpenLowLatencyHistogramName,
+ GetSubsequentStreamCreationResultBucket(output_params_, false));
+ return false;
+ }
+
+ base::UmaHistogramBoolean(kFallbackHistogramName, true);
+
+ // Only Windows has a high latency output driver that is not the same as the
+ // low latency path.
+#if defined(OS_WIN)
+ DLOG(ERROR) << "Unable to open audio device in low latency mode. Falling "
+ << "back to high latency audio output.";
+
+ output_params_ = GetFallbackOutputParams(original_output_params_);
+ const std::string fallback_device_id = "";
+ dispatcher_ = MakeDispatcher(fallback_device_id, output_params_);
+ if (dispatcher_->OpenStream()) {
+ base::UmaHistogramEnumeration(kOpenLowLatencyHistogramName,
+ OpenStreamResult::kFallbackToLinear);
+ return true;
+ }
+#endif
+
+ DLOG(ERROR) << "Unable to open audio device in high latency mode. Falling "
+ << "back to fake audio output.";
+
+ // Finally fall back to a fake audio output device.
+ output_params_ = input_params_;
+ output_params_.set_format(AudioParameters::AUDIO_FAKE);
+ dispatcher_ = MakeDispatcher(device_id_, output_params_);
+ if (dispatcher_->OpenStream()) {
+ base::UmaHistogramEnumeration(kOpenLowLatencyHistogramName,
+ OpenStreamResult::kFallbackToFake);
+ return true;
+ }
+
+ // Resetting the malfunctioning dispatcher.
+ Reinitialize();
+
+ base::UmaHistogramEnumeration(kOpenLowLatencyHistogramName,
+ OpenStreamResult::kFail);
+ return false;
+}
+
+bool AudioOutputResampler::StartStream(
+ AudioOutputStream::AudioSourceCallback* callback,
+ AudioOutputProxy* stream_proxy) {
+ DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
+ DCHECK(dispatcher_);
+
+ OnMoreDataConverter* resampler_callback = nullptr;
+ auto it = callbacks_.find(stream_proxy);
+ if (it == callbacks_.end()) {
+ // If a register callback has been given, register and pass the returned
+ // recoder to the converter. Data is fed to same recorder for the lifetime
+ // of the converter, which is until the stream is closed.
+ resampler_callback = new OnMoreDataConverter(
+ input_params_, output_params_,
+ register_debug_recording_source_callback_.Run(output_params_));
+ callbacks_[stream_proxy] =
+ base::WrapUnique<OnMoreDataConverter>(resampler_callback);
+ } else {
+ resampler_callback = it->second.get();
+ }
+
+ resampler_callback->Start(callback);
+ bool result = dispatcher_->StartStream(resampler_callback, stream_proxy);
+ if (!result)
+ resampler_callback->Stop();
+ return result;
+}
+
+void AudioOutputResampler::StreamVolumeSet(AudioOutputProxy* stream_proxy,
+ double volume) {
+ DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
+ DCHECK(dispatcher_);
+ dispatcher_->StreamVolumeSet(stream_proxy, volume);
+}
+
+void AudioOutputResampler::StopStream(AudioOutputProxy* stream_proxy) {
+ DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
+
+ auto it = callbacks_.find(stream_proxy);
+ DCHECK(it != callbacks_.end());
+ StopStreamInternal(*it);
+}
+
+void AudioOutputResampler::CloseStream(AudioOutputProxy* stream_proxy) {
+ DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
+ DCHECK(dispatcher_);
+
+ dispatcher_->CloseStream(stream_proxy);
+
+ // We assume that StopStream() is always called prior to CloseStream(), so
+ // that it is safe to delete the OnMoreDataConverter here.
+ callbacks_.erase(stream_proxy);
+
+ // Start the reinitialization timer if there are no active proxies and we're
+ // not using the originally requested output parameters. This allows us to
+ // recover from transient output creation errors.
+ if (!dispatcher_->HasOutputProxies() && callbacks_.empty() &&
+ !output_params_.Equals(original_output_params_)) {
+ reinitialize_timer_.Reset();
+ }
+}
+
+void AudioOutputResampler::FlushStream(AudioOutputProxy* stream_proxy) {
+ DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
+ DCHECK(dispatcher_);
+
+ dispatcher_->FlushStream(stream_proxy);
+}
+
+void AudioOutputResampler::StopStreamInternal(
+ const CallbackMap::value_type& item) {
+ DCHECK(audio_manager()->GetTaskRunner()->BelongsToCurrentThread());
+ DCHECK(dispatcher_);
+ AudioOutputProxy* stream_proxy = item.first;
+ OnMoreDataConverter* callback = item.second.get();
+ DCHECK(callback->started());
+
+ // Stop the underlying physical stream.
+ dispatcher_->StopStream(stream_proxy);
+
+ // Now that StopStream() has completed the underlying physical stream should
+ // be stopped and no longer calling OnMoreData(), making it safe to Stop() the
+ // OnMoreDataConverter.
+ callback->Stop();
+
+ // Destroy idle streams if any errors occurred during output; this ensures
+ // bad streams will not be reused. Note: Errors may occur during the Stop()
+ // call above.
+ if (callback->error_occurred())
+ dispatcher_->CloseAllIdleStreams();
+}
+
+OnMoreDataConverter::OnMoreDataConverter(
+ const AudioParameters& input_params,
+ const AudioParameters& output_params,
+ std::unique_ptr<AudioDebugRecorder> debug_recorder)
+ : source_callback_(nullptr),
+ input_samples_per_second_(input_params.sample_rate()),
+ audio_converter_(input_params, output_params, false),
+ error_occurred_(false),
+ input_buffer_size_(input_params.frames_per_buffer()),
+ output_buffer_size_(output_params.frames_per_buffer()),
+ debug_recorder_(std::move(debug_recorder)) {}
+
+OnMoreDataConverter::~OnMoreDataConverter() {
+ // Ensure Stop() has been called so we don't end up with an AudioOutputStream
+ // calling back into OnMoreData() after destruction.
+ CHECK(!source_callback_);
+}
+
+void OnMoreDataConverter::Start(
+ AudioOutputStream::AudioSourceCallback* callback) {
+ CHECK(!source_callback_);
+ CHECK(callback);
+ source_callback_ = callback;
+
+ // While AudioConverter can handle multiple inputs, we're using it only with
+ // a single input currently. Eventually this may be the basis for a browser
+ // side mixer.
+ audio_converter_.AddInput(this);
+}
+
+void OnMoreDataConverter::Stop() {
+ CHECK(source_callback_);
+ audio_converter_.RemoveInput(this);
+ source_callback_ = nullptr;
+}
+
+int OnMoreDataConverter::OnMoreData(base::TimeDelta delay,
+ base::TimeTicks delay_timestamp,
+ int /* prior_frames_skipped */,
+ AudioBus* dest) {
+ TRACE_EVENT2("audio", "OnMoreDataConverter::OnMoreData", "input buffer size",
+ input_buffer_size_, "output buffer size", output_buffer_size_);
+ current_delay_ = delay;
+ current_delay_timestamp_ = delay_timestamp;
+ audio_converter_.Convert(dest);
+
+ if (debug_recorder_)
+ debug_recorder_->OnData(dest);
+
+ // Always return the full number of frames requested, ProvideInput()
+ // will pad with silence if it wasn't able to acquire enough data.
+ return dest->frames();
+}
+
+double OnMoreDataConverter::ProvideInput(AudioBus* dest,
+ uint32_t frames_delayed) {
+ base::TimeDelta new_delay =
+ current_delay_ + AudioTimestampHelper::FramesToTime(
+ frames_delayed, input_samples_per_second_);
+ // Retrieve data from the original callback.
+ const int frames = source_callback_->OnMoreData(
+ new_delay, current_delay_timestamp_, 0, dest);
+
+ // Zero any unfilled frames if anything was filled, otherwise we'll just
+ // return a volume of zero and let AudioConverter drop the output.
+ if (frames > 0 && frames < dest->frames())
+ dest->ZeroFramesPartial(frames, dest->frames() - frames);
+ return frames > 0 ? 1 : 0;
+}
+
+void OnMoreDataConverter::OnError(ErrorType type) {
+ error_occurred_ = true;
+ source_callback_->OnError(type);
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_output_resampler.h b/third_party/chromium/media/audio/audio_output_resampler.h
new file mode 100644
index 0000000..dc79475
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_output_resampler.h
@@ -0,0 +1,117 @@
+// Copyright (c) 2012 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 MEDIA_AUDIO_AUDIO_OUTPUT_RESAMPLER_H_
+#define MEDIA_AUDIO_AUDIO_OUTPUT_RESAMPLER_H_
+
+#include "base/containers/flat_map.h"
+#include "base/macros.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "media/audio/audio_debug_recording_helper.h"
+#include "media/audio/audio_io.h"
+#include "media/audio/audio_output_dispatcher.h"
+#include "media/base/audio_parameters.h"
+
+namespace media {
+
+class AudioManager;
+class AudioOutputDispatcherImpl;
+class OnMoreDataConverter;
+
+// AudioOutputResampler is a browser-side resampling and buffering solution
+// which ensures audio data is always output at given parameters. See the
+// AudioConverter class for details on the conversion process.
+//
+// AOR works by intercepting the AudioSourceCallback provided to StartStream()
+// and redirecting it through an AudioConverter instance.
+//
+// AOR will automatically fall back from AUDIO_PCM_LOW_LATENCY to
+// AUDIO_PCM_LINEAR if the output device fails to open at the requested output
+// parameters. If opening still fails, it will fallback to AUDIO_FAKE.
+class MEDIA_EXPORT AudioOutputResampler : public AudioOutputDispatcher {
+ public:
+ // Callback type to register an AudioDebugRecorder.
+ using RegisterDebugRecordingSourceCallback =
+ base::RepeatingCallback<std::unique_ptr<AudioDebugRecorder>(
+ const AudioParameters&)>;
+
+ AudioOutputResampler(AudioManager* audio_manager,
+ const AudioParameters& input_params,
+ const AudioParameters& output_params,
+ const std::string& output_device_id,
+ base::TimeDelta close_delay,
+ const RegisterDebugRecordingSourceCallback&
+ register_debug_recording_source_callback);
+
+ AudioOutputResampler(const AudioOutputResampler&) = delete;
+ AudioOutputResampler& operator=(const AudioOutputResampler&) = delete;
+
+ ~AudioOutputResampler() override;
+
+ // AudioOutputDispatcher interface.
+ AudioOutputProxy* CreateStreamProxy() override;
+ bool OpenStream() override;
+ bool StartStream(AudioOutputStream::AudioSourceCallback* callback,
+ AudioOutputProxy* stream_proxy) override;
+ void StopStream(AudioOutputProxy* stream_proxy) override;
+ void StreamVolumeSet(AudioOutputProxy* stream_proxy, double volume) override;
+ void CloseStream(AudioOutputProxy* stream_proxy) override;
+ void FlushStream(AudioOutputProxy* stream_proxy) override;
+
+ private:
+ using CallbackMap =
+ base::flat_map<AudioOutputProxy*, std::unique_ptr<OnMoreDataConverter>>;
+
+ // Used to reinitialize |dispatcher_| upon timeout if there are no open
+ // streams.
+ void Reinitialize();
+
+ // Used to initialize |dispatcher_|.
+ std::unique_ptr<AudioOutputDispatcherImpl> MakeDispatcher(
+ const std::string& output_device_id,
+ const AudioParameters& params);
+
+ // Stops the stream corresponding to the |item| in |callbacks_|.
+ void StopStreamInternal(const CallbackMap::value_type& item);
+
+ // Dispatcher to proxy all AudioOutputDispatcher calls too.
+ // Lazily initialized on a first stream open request.
+ std::unique_ptr<AudioOutputDispatcherImpl> dispatcher_;
+
+ // Map of outstanding OnMoreDataConverter objects. A new object is created
+ // on every StartStream() call and destroyed on CloseStream().
+ CallbackMap callbacks_;
+
+ // Used by AudioOutputDispatcherImpl; kept so we can reinitialize on the fly.
+ const base::TimeDelta close_delay_;
+
+ // Source AudioParameters.
+ const AudioParameters input_params_;
+
+ // AudioParameters used to setup the output stream; changed upon fallback.
+ AudioParameters output_params_;
+
+ // The original AudioParameters we were constructed with.
+ const AudioParameters original_output_params_;
+
+ // Output device id.
+ const std::string device_id_;
+
+ // The reinitialization timer provides a way to recover from temporary failure
+ // states by clearing the dispatcher if all proxies have been closed and none
+ // have been created within |close_delay_|. Without this, audio may be lost
+ // to a fake stream indefinitely for transient errors.
+ base::RetainingOneShotTimer reinitialize_timer_;
+
+ // Callback for registering a debug recording source.
+ RegisterDebugRecordingSourceCallback
+ register_debug_recording_source_callback_;
+
+ base::WeakPtrFactory<AudioOutputResampler> weak_factory_{this};
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_OUTPUT_RESAMPLER_H_
diff --git a/third_party/chromium/media/audio/audio_output_stream_sink.cc b/third_party/chromium/media/audio/audio_output_stream_sink.cc
new file mode 100644
index 0000000..1dff40e
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_output_stream_sink.cc
@@ -0,0 +1,182 @@
+// 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 "media/audio/audio_output_stream_sink.h"
+
+#include <algorithm>
+#include <cmath>
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/location.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "media/audio/audio_manager.h"
+#include "media/base/audio_timestamp_helper.h"
+
+namespace media {
+
+AudioOutputStreamSink::AudioOutputStreamSink()
+ : initialized_(false),
+ started_(false),
+ render_callback_(nullptr),
+ active_render_callback_(nullptr),
+ audio_task_runner_(AudioManager::Get()->GetTaskRunner()),
+ stream_(nullptr) {}
+
+AudioOutputStreamSink::~AudioOutputStreamSink() = default;
+
+void AudioOutputStreamSink::Initialize(const AudioParameters& params,
+ RenderCallback* callback) {
+ DCHECK(callback);
+ DCHECK(!started_);
+ params_ = params;
+ render_callback_ = callback;
+ initialized_ = true;
+}
+
+void AudioOutputStreamSink::Start() {
+ DCHECK(initialized_);
+ DCHECK(!started_);
+ {
+ base::AutoLock al(callback_lock_);
+ active_render_callback_ = render_callback_;
+ }
+ started_ = true;
+ audio_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&AudioOutputStreamSink::DoStart, this, params_));
+}
+
+void AudioOutputStreamSink::Stop() {
+ ClearCallback();
+ started_ = false;
+ audio_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(&AudioOutputStreamSink::DoStop, this));
+}
+
+void AudioOutputStreamSink::Pause() {
+ ClearCallback();
+ audio_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(&AudioOutputStreamSink::DoPause, this));
+}
+
+void AudioOutputStreamSink::Flush() {
+ audio_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(&AudioOutputStreamSink::DoFlush, this));
+}
+
+void AudioOutputStreamSink::Play() {
+ {
+ base::AutoLock al(callback_lock_);
+ active_render_callback_ = render_callback_;
+ }
+ audio_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(&AudioOutputStreamSink::DoPlay, this));
+}
+
+bool AudioOutputStreamSink::SetVolume(double volume) {
+ audio_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&AudioOutputStreamSink::DoSetVolume, this, volume));
+ return true;
+}
+
+OutputDeviceInfo AudioOutputStreamSink::GetOutputDeviceInfo() {
+ return OutputDeviceInfo(OUTPUT_DEVICE_STATUS_OK);
+}
+
+void AudioOutputStreamSink::GetOutputDeviceInfoAsync(
+ OutputDeviceInfoCB info_cb) {
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(info_cb), GetOutputDeviceInfo()));
+}
+
+bool AudioOutputStreamSink::IsOptimizedForHardwareParameters() {
+ return true;
+}
+
+bool AudioOutputStreamSink::CurrentThreadIsRenderingThread() {
+ NOTIMPLEMENTED();
+ return false;
+}
+
+int AudioOutputStreamSink::OnMoreData(base::TimeDelta delay,
+ base::TimeTicks delay_timestamp,
+ int prior_frames_skipped,
+ AudioBus* dest) {
+ // Note: Runs on the audio thread created by the OS.
+ base::AutoLock al(callback_lock_);
+ if (!active_render_callback_)
+ return 0;
+
+ return active_render_callback_->Render(delay, delay_timestamp,
+ prior_frames_skipped, dest);
+}
+
+void AudioOutputStreamSink::OnError(ErrorType type) {
+ // Note: Runs on the audio thread created by the OS.
+ base::AutoLock al(callback_lock_);
+ if (active_render_callback_)
+ active_render_callback_->OnRenderError();
+}
+
+void AudioOutputStreamSink::DoStart(const AudioParameters& params) {
+ DCHECK(audio_task_runner_->BelongsToCurrentThread());
+
+ // Create an AudioOutputStreamProxy which will handle any and all resampling
+ // necessary to generate a low latency output stream.
+ active_params_ = params;
+ stream_ = AudioManager::Get()->MakeAudioOutputStreamProxy(active_params_,
+ std::string());
+ if (!stream_ || !stream_->Open()) {
+ {
+ base::AutoLock al(callback_lock_);
+ if (active_render_callback_)
+ active_render_callback_->OnRenderError();
+ }
+ if (stream_)
+ stream_->Close();
+ stream_ = nullptr;
+ }
+}
+
+void AudioOutputStreamSink::DoStop() {
+ DCHECK(audio_task_runner_->BelongsToCurrentThread());
+
+ if (!stream_)
+ return;
+
+ DoPause();
+ stream_->Close();
+ stream_ = nullptr;
+}
+
+void AudioOutputStreamSink::DoPause() {
+ DCHECK(audio_task_runner_->BelongsToCurrentThread());
+ stream_->Stop();
+}
+
+void AudioOutputStreamSink::DoFlush() {
+ DCHECK(audio_task_runner_->BelongsToCurrentThread());
+ if (stream_) {
+ stream_->Flush();
+ }
+}
+
+void AudioOutputStreamSink::DoPlay() {
+ DCHECK(audio_task_runner_->BelongsToCurrentThread());
+ stream_->Start(this);
+}
+
+void AudioOutputStreamSink::DoSetVolume(double volume) {
+ DCHECK(audio_task_runner_->BelongsToCurrentThread());
+ stream_->SetVolume(volume);
+}
+
+void AudioOutputStreamSink::ClearCallback() {
+ base::AutoLock al(callback_lock_);
+ active_render_callback_ = nullptr;
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_output_stream_sink.h b/third_party/chromium/media/audio/audio_output_stream_sink.h
new file mode 100644
index 0000000..fcc8bc1
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_output_stream_sink.h
@@ -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.
+
+#ifndef MEDIA_AUDIO_AUDIO_OUTPUT_STREAM_SINK_H_
+#define MEDIA_AUDIO_AUDIO_OUTPUT_STREAM_SINK_H_
+
+#include <stdint.h>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/single_thread_task_runner.h"
+#include "base/synchronization/lock.h"
+#include "base/thread_annotations.h"
+#include "base/time/time.h"
+#include "media/audio/audio_io.h"
+#include "media/base/audio_renderer_sink.h"
+#include "media/base/media_export.h"
+
+namespace media {
+
+// Wrapper which exposes the browser side audio interface (AudioOutputStream) as
+// if it were a renderer side audio interface (AudioRendererSink). Note: This
+// will not work for sandboxed renderers.
+//
+// TODO(dalecurtis): Delete this class once we have a proper mojo audio service;
+// tracked by http://crbug.com/425368
+class MEDIA_EXPORT AudioOutputStreamSink
+ : public RestartableAudioRendererSink,
+ public AudioOutputStream::AudioSourceCallback {
+ public:
+ AudioOutputStreamSink();
+
+ // RestartableAudioRendererSink implementation.
+ void Initialize(const AudioParameters& params,
+ RenderCallback* callback) override;
+ void Start() override;
+ void Stop() override;
+ void Pause() override;
+ void Play() override;
+ bool SetVolume(double volume) override;
+ OutputDeviceInfo GetOutputDeviceInfo() override;
+ void GetOutputDeviceInfoAsync(OutputDeviceInfoCB info_cb) override;
+ bool IsOptimizedForHardwareParameters() override;
+ bool CurrentThreadIsRenderingThread() override;
+
+ // AudioSourceCallback implementation.
+ int OnMoreData(base::TimeDelta delay,
+ base::TimeTicks delay_timestamp,
+ int prior_frames_skipped,
+ AudioBus* dest) override;
+ void OnError(ErrorType type) override;
+ void Flush() override;
+
+ private:
+ ~AudioOutputStreamSink() override;
+ void ClearCallback();
+
+ // Helper methods for running AudioManager methods on the audio thread.
+ void DoStart(const AudioParameters& params);
+ void DoStop();
+ void DoPause();
+ void DoFlush();
+ void DoPlay();
+ void DoSetVolume(double volume);
+
+ bool initialized_;
+ bool started_;
+
+ // Parameters provided by Initialize().
+ AudioParameters params_;
+ RenderCallback* render_callback_;
+
+ // State latched for the audio thread.
+ // |active_render_callback_| allows Stop()/Pause() to synchronously prevent
+ // callbacks. Access is synchronized by |callback_lock_|.
+ // |active_params_| is set on the audio thread and therefore does not need
+ // synchronization.
+ AudioParameters active_params_;
+ RenderCallback* active_render_callback_ GUARDED_BY(callback_lock_);
+
+ // Lock to synchronize setting and clearing of |active_render_callback_|.
+ base::Lock callback_lock_;
+
+ // The task runner for the audio thread.
+ const scoped_refptr<base::SingleThreadTaskRunner> audio_task_runner_;
+
+ // The actual AudioOutputStream, must only be accessed on the audio thread.
+ AudioOutputStream* stream_;
+
+ DISALLOW_COPY_AND_ASSIGN(AudioOutputStreamSink);
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_OUTPUT_STREAM_SINK_H_
diff --git a/third_party/chromium/media/audio/audio_output_unittest.cc b/third_party/chromium/media/audio/audio_output_unittest.cc
new file mode 100644
index 0000000..c69ba66
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_output_unittest.cc
@@ -0,0 +1,214 @@
+// Copyright 2017 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 <stdint.h>
+
+#include <memory>
+
+#include "base/command_line.h"
+#include "base/memory/aligned_memory.h"
+#include "base/run_loop.h"
+#include "base/test/bind.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/task_environment.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "media/audio/audio_device_info_accessor_for_tests.h"
+#include "media/audio/audio_features.h"
+#include "media/audio/audio_io.h"
+#include "media/audio/audio_manager.h"
+#include "media/audio/audio_unittest_util.h"
+#include "media/audio/simple_sources.h"
+#include "media/audio/test_audio_thread.h"
+#include "media/base/limits.h"
+#include "media/base/media_switches.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_ANDROID)
+#include "media/audio/android/audio_manager_android.h"
+#endif
+
+namespace media {
+
+class AudioOutputTest : public testing::TestWithParam<bool> {
+ public:
+ AudioOutputTest() {
+ audio_manager_ =
+ AudioManager::CreateForTesting(std::make_unique<TestAudioThread>());
+ audio_manager_device_info_ =
+ std::make_unique<AudioDeviceInfoAccessorForTests>(audio_manager_.get());
+#if defined(OS_ANDROID)
+ // The only parameter is used to enable/disable AAudio.
+ should_use_aaudio_ = GetParam();
+ if (should_use_aaudio_) {
+ features_.InitAndEnableFeature(features::kUseAAudioDriver);
+
+ aaudio_is_supported_ =
+ reinterpret_cast<AudioManagerAndroid*>(audio_manager_.get())
+ ->IsUsingAAudioForTesting();
+ }
+#endif
+ base::RunLoop().RunUntilIdle();
+ }
+ ~AudioOutputTest() override {
+ if (stream_)
+ stream_->Close();
+ audio_manager_->Shutdown();
+ }
+
+ void CreateWithDefaultParameters() {
+ stream_params_ =
+ audio_manager_device_info_->GetDefaultOutputStreamParameters();
+ stream_ = audio_manager_->MakeAudioOutputStream(
+ stream_params_, std::string(), AudioManager::LogCallback());
+ }
+
+ // Runs message loop for the specified amount of time.
+ void RunMessageLoop(base::TimeDelta delay) {
+ base::RunLoop run_loop;
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE, run_loop.QuitClosure(), delay);
+ run_loop.Run();
+ }
+
+ protected:
+ base::test::SingleThreadTaskEnvironment task_environment_{
+ base::test::SingleThreadTaskEnvironment::MainThreadType::IO};
+ std::unique_ptr<AudioManager> audio_manager_;
+ std::unique_ptr<AudioDeviceInfoAccessorForTests> audio_manager_device_info_;
+ AudioParameters stream_params_;
+ AudioOutputStream* stream_ = nullptr;
+ bool should_use_aaudio_ = false;
+ bool aaudio_is_supported_ = false;
+#if defined(OS_ANDROID)
+ base::test::ScopedFeatureList features_;
+#endif
+};
+
+// Test that can it be created and closed.
+TEST_P(AudioOutputTest, GetAndClose) {
+ if (should_use_aaudio_ && !aaudio_is_supported_)
+ return;
+
+ ABORT_AUDIO_TEST_IF_NOT(audio_manager_device_info_->HasAudioOutputDevices());
+ CreateWithDefaultParameters();
+ ASSERT_TRUE(stream_);
+}
+
+// Test that it can be opened and closed.
+TEST_P(AudioOutputTest, OpenAndClose) {
+ if (should_use_aaudio_ && !aaudio_is_supported_)
+ return;
+
+ ABORT_AUDIO_TEST_IF_NOT(audio_manager_device_info_->HasAudioOutputDevices());
+
+ CreateWithDefaultParameters();
+ ASSERT_TRUE(stream_);
+ EXPECT_TRUE(stream_->Open());
+}
+
+// Verify that Stop() can be called before Start().
+TEST_P(AudioOutputTest, StopBeforeStart) {
+ if (should_use_aaudio_ && !aaudio_is_supported_)
+ return;
+
+ ABORT_AUDIO_TEST_IF_NOT(audio_manager_device_info_->HasAudioOutputDevices());
+ CreateWithDefaultParameters();
+ EXPECT_TRUE(stream_->Open());
+ stream_->Stop();
+}
+
+// Verify that Stop() can be called more than once.
+TEST_P(AudioOutputTest, StopTwice) {
+ if (should_use_aaudio_ && !aaudio_is_supported_)
+ return;
+
+ ABORT_AUDIO_TEST_IF_NOT(audio_manager_device_info_->HasAudioOutputDevices());
+ CreateWithDefaultParameters();
+ EXPECT_TRUE(stream_->Open());
+ SineWaveAudioSource source(1, 200.0, stream_params_.sample_rate());
+
+ stream_->Start(&source);
+ stream_->Stop();
+ stream_->Stop();
+}
+
+// This test produces actual audio for .25 seconds on the default device.
+TEST_P(AudioOutputTest, Play200HzTone) {
+ if (should_use_aaudio_ && !aaudio_is_supported_)
+ return;
+
+ ABORT_AUDIO_TEST_IF_NOT(audio_manager_device_info_->HasAudioOutputDevices());
+
+ stream_params_ =
+ audio_manager_device_info_->GetDefaultOutputStreamParameters();
+ stream_ = audio_manager_->MakeAudioOutputStream(stream_params_, std::string(),
+ AudioManager::LogCallback());
+ ASSERT_TRUE(stream_);
+
+ SineWaveAudioSource source(1, 200.0, stream_params_.sample_rate());
+
+ // Play for 100ms.
+ const int samples_to_play = stream_params_.sample_rate() / 10;
+
+ EXPECT_TRUE(stream_->Open());
+ stream_->SetVolume(1.0);
+
+ // Play the stream until position gets past |samples_to_play|.
+ base::RunLoop run_loop;
+ source.set_on_more_data_callback(
+ base::BindLambdaForTesting([&source, &run_loop, samples_to_play]() {
+ if (source.pos_samples() >= samples_to_play)
+ run_loop.Quit();
+ }));
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE, run_loop.QuitClosure(), TestTimeouts::action_timeout());
+
+ stream_->Start(&source);
+ run_loop.Run();
+
+ stream_->Stop();
+
+ EXPECT_FALSE(source.errors());
+ EXPECT_GE(source.callbacks(), 1);
+ EXPECT_GE(source.pos_samples(), samples_to_play);
+}
+
+// Test that SetVolume() and GetVolume() work as expected.
+TEST_P(AudioOutputTest, VolumeControl) {
+ if (should_use_aaudio_ && !aaudio_is_supported_)
+ return;
+
+ ABORT_AUDIO_TEST_IF_NOT(audio_manager_device_info_->HasAudioOutputDevices());
+
+ CreateWithDefaultParameters();
+ ASSERT_TRUE(stream_);
+ EXPECT_TRUE(stream_->Open());
+
+ double volume = 0.0;
+
+ stream_->GetVolume(&volume);
+ EXPECT_EQ(volume, 1.0);
+
+ stream_->SetVolume(0.5);
+
+ stream_->GetVolume(&volume);
+ EXPECT_LT(volume, 0.51);
+ EXPECT_GT(volume, 0.49);
+ stream_->Stop();
+}
+
+// The test parameter is only relevant on Android. It controls whether or not we
+// allow the use of AAudio.
+INSTANTIATE_TEST_SUITE_P(Base, AudioOutputTest, testing::Values(false));
+
+#if defined(OS_ANDROID)
+// Run tests with AAudio enabled. On Android P and below, these tests should not
+// run, as we only use AAudio on Q+.
+INSTANTIATE_TEST_SUITE_P(AAudio, AudioOutputTest, testing::Values(true));
+#endif
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_sink_parameters.cc b/third_party/chromium/media/audio/audio_sink_parameters.cc
new file mode 100644
index 0000000..8287fa0
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_sink_parameters.cc
@@ -0,0 +1,18 @@
+// Copyright 2018 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 "media/audio/audio_sink_parameters.h"
+
+namespace media {
+
+AudioSinkParameters::AudioSinkParameters() = default;
+AudioSinkParameters::AudioSinkParameters(
+ const base::UnguessableToken& session_id,
+ const std::string& device_id)
+ : session_id(session_id), device_id(device_id) {}
+AudioSinkParameters::AudioSinkParameters(const AudioSinkParameters& params) =
+ default;
+AudioSinkParameters::~AudioSinkParameters() = default;
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_sink_parameters.h b/third_party/chromium/media/audio/audio_sink_parameters.h
new file mode 100644
index 0000000..45b89b3
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_sink_parameters.h
@@ -0,0 +1,39 @@
+// Copyright 2018 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 MEDIA_AUDIO_AUDIO_SINK_PARAMETERS_H_
+#define MEDIA_AUDIO_AUDIO_SINK_PARAMETERS_H_
+
+#include <string>
+
+#include "base/unguessable_token.h"
+#include "media/base/media_export.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace media {
+
+// The set of parameters used to create an AudioOutputDevice.
+// |session_id| and |device_id| are used to select which device to
+// use. |device_id| is preferred over |session_id| if both are set
+// (i.e. session_id is nonzero). If neither is set, the default output device
+// will be selected. This is the state when default constructed.
+// If the optional |processing_id| is provided, it is used to indicate that this
+// stream is to be used as the reference signal during audio processing. An
+// audio source must be constructed with the same processing id to complete the
+// association.
+struct MEDIA_EXPORT AudioSinkParameters final {
+ AudioSinkParameters();
+ AudioSinkParameters(const base::UnguessableToken& session_id,
+ const std::string& device_id);
+ AudioSinkParameters(const AudioSinkParameters& params);
+ ~AudioSinkParameters();
+
+ base::UnguessableToken session_id;
+ std::string device_id;
+ absl::optional<base::UnguessableToken> processing_id;
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_SINK_PARAMETERS_H_
diff --git a/third_party/chromium/media/audio/audio_source_diverter.h b/third_party/chromium/media/audio/audio_source_diverter.h
new file mode 100644
index 0000000..6ac55a6
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_source_diverter.h
@@ -0,0 +1,61 @@
+// Copyright (c) 2013 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 MEDIA_AUDIO_AUDIO_SOURCE_DIVERTER_H_
+#define MEDIA_AUDIO_AUDIO_SOURCE_DIVERTER_H_
+
+#include "base/time/time.h"
+#include "media/base/audio_bus.h"
+#include "media/base/media_export.h"
+
+// Audio sources may optionally implement AudioSourceDiverter to temporarily
+// divert audio data to an alternate AudioOutputStream. This allows the audio
+// data to be plumbed to an alternate consumer; for example, a loopback
+// mechanism for audio mirroring.
+
+namespace media {
+
+class AudioOutputStream;
+class AudioParameters;
+
+class MEDIA_EXPORT AudioPushSink {
+ public:
+ // Call this function to push audio data into the sink.
+ virtual void OnData(std::unique_ptr<AudioBus> source,
+ base::TimeTicks reference_time) = 0;
+
+ // Close the stream.
+ // After calling this method, the object should not be used anymore.
+ virtual void Close() = 0;
+};
+
+class MEDIA_EXPORT AudioSourceDiverter {
+public:
+ // Returns the audio parameters of the divertable audio data.
+ virtual const AudioParameters& GetAudioParameters() = 0;
+
+ // Start providing audio data to the given |to_stream|, which is in an
+ // unopened state. |to_stream| remains under the control of the
+ // AudioSourceDiverter.
+ virtual void StartDiverting(AudioOutputStream* to_stream) = 0;
+
+ // Stops diverting audio data to the stream. The AudioSourceDiverter is
+ // responsible for making sure the stream is closed, perhaps asynchronously.
+ virtual void StopDiverting() = 0;
+
+ // Start duplicating the current audio stream, and push the copied data into
+ // |sink|.
+ virtual void StartDuplicating(AudioPushSink* sink) = 0;
+
+ // Stop duplicating for the specified |sink|. The AudioSourceDiverter is
+ // responsible for making sure the sink is closed, perhaps asynchronously.
+ virtual void StopDuplicating(AudioPushSink* sink) = 0;
+
+ protected:
+ virtual ~AudioSourceDiverter() {}
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_SOURCE_DIVERTER_H_
diff --git a/third_party/chromium/media/audio/audio_source_parameters.cc b/third_party/chromium/media/audio/audio_source_parameters.cc
new file mode 100644
index 0000000..b8c631af
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_source_parameters.cc
@@ -0,0 +1,24 @@
+// Copyright 2018 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 "media/audio/audio_source_parameters.h"
+
+namespace media {
+
+AudioSourceParameters::AudioSourceParameters() = default;
+AudioSourceParameters::AudioSourceParameters(
+ const base::UnguessableToken& session_id)
+ : session_id(session_id) {}
+AudioSourceParameters::AudioSourceParameters(
+ const AudioSourceParameters& params) = default;
+AudioSourceParameters::~AudioSourceParameters() = default;
+
+AudioSourceParameters::ProcessingConfig::ProcessingConfig(
+ base::UnguessableToken id,
+ AudioProcessingSettings settings)
+ : id(id), settings(settings) {
+ DCHECK(!id.is_empty());
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_source_parameters.h b/third_party/chromium/media/audio/audio_source_parameters.h
new file mode 100644
index 0000000..efd2da3
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_source_parameters.h
@@ -0,0 +1,39 @@
+// Copyright 2018 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 MEDIA_AUDIO_AUDIO_SOURCE_PARAMETERS_H_
+#define MEDIA_AUDIO_AUDIO_SOURCE_PARAMETERS_H_
+
+#include "base/unguessable_token.h"
+#include "media/base/audio_processing.h"
+#include "media/base/media_export.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace media {
+
+// The set of parameters used to create an AudioInputDevice.
+// If |session_id| is nonzero, it is used by the browser
+// to select the correct input device ID. If |session_id| is zero, the default
+// input device will be selected. This is the state when default constructed.
+struct MEDIA_EXPORT AudioSourceParameters final {
+ AudioSourceParameters();
+ explicit AudioSourceParameters(const base::UnguessableToken& session_id);
+ AudioSourceParameters(const AudioSourceParameters& params);
+ ~AudioSourceParameters();
+
+ base::UnguessableToken session_id;
+
+ struct MEDIA_EXPORT ProcessingConfig {
+ ProcessingConfig(base::UnguessableToken id,
+ AudioProcessingSettings settings);
+ base::UnguessableToken id;
+ AudioProcessingSettings settings;
+ };
+
+ absl::optional<ProcessingConfig> processing;
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_SOURCE_PARAMETERS_H_
diff --git a/third_party/chromium/media/audio/audio_system.cc b/third_party/chromium/media/audio/audio_system.cc
new file mode 100644
index 0000000..81c58aa
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_system.cc
@@ -0,0 +1,29 @@
+// Copyright 2018 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 "media/audio/audio_system.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "media/audio/audio_device_description.h"
+
+namespace media {
+
+// static
+AudioSystem::OnDeviceDescriptionsCallback
+AudioSystem::WrapCallbackWithDeviceNameLocalization(
+ OnDeviceDescriptionsCallback callback) {
+ return base::BindOnce(
+ [](OnDeviceDescriptionsCallback cb,
+ media::AudioDeviceDescriptions descriptions) {
+ media::AudioDeviceDescription::LocalizeDeviceDescriptions(
+ &descriptions);
+ std::move(cb).Run(std::move(descriptions));
+ },
+ std::move(callback));
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_system.h b/third_party/chromium/media/audio/audio_system.h
new file mode 100644
index 0000000..9701570
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_system.h
@@ -0,0 +1,88 @@
+// Copyright 2017 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 MEDIA_AUDIO_AUDIO_SYSTEM_H_
+#define MEDIA_AUDIO_AUDIO_SYSTEM_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "media/audio/audio_device_description.h"
+#include "media/base/audio_parameters.h"
+#include "media/base/media_export.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace media {
+
+// Provides asynchronous interface to access audio device information
+class MEDIA_EXPORT AudioSystem {
+ public:
+ // Replies are sent asynchronously to the thread the calls are issued on.
+ // Instance is bound to the thread it's called on the first time.
+ // Attention! Audio system thread may outlive the client
+ // objects; bind callbacks with care.
+
+ // Non-empty optional AudioParameters are guaranteed to be valid.
+ // If optional AudioParameters are empty, it means the specified device is not
+ // found. This is best-effort: non-empty parameters do not guarantee existence
+ // of the device.
+ // TODO(olka,tommi): fix all AudioManager implementations to always report
+ // when a device is not found, instead of returning sub parameter values.
+ // Non-empty optional matched output device id is guaranteed to be a non-empty
+ // std::string. If optional matched output device id is empty, it means there
+ // is no associated output device.
+ using OnAudioParamsCallback =
+ base::OnceCallback<void(const absl::optional<AudioParameters>&)>;
+ using OnDeviceIdCallback =
+ base::OnceCallback<void(const absl::optional<std::string>&)>;
+ using OnInputDeviceInfoCallback =
+ base::OnceCallback<void(const absl::optional<AudioParameters>&,
+ const absl::optional<std::string>&)>;
+
+ using OnBoolCallback = base::OnceCallback<void(bool)>;
+ using OnDeviceDescriptionsCallback =
+ base::OnceCallback<void(AudioDeviceDescriptions)>;
+
+ virtual ~AudioSystem() = default;
+
+ virtual void GetInputStreamParameters(const std::string& device_id,
+ OnAudioParamsCallback on_params_cb) = 0;
+
+ virtual void GetOutputStreamParameters(
+ const std::string& device_id,
+ OnAudioParamsCallback on_params_cb) = 0;
+
+ virtual void HasInputDevices(OnBoolCallback on_has_devices_cb) = 0;
+
+ virtual void HasOutputDevices(OnBoolCallback on_has_devices_cb) = 0;
+
+ // Replies with device descriptions of input audio devices if |for_input| is
+ // true, and of output audio devices otherwise.
+ virtual void GetDeviceDescriptions(
+ bool for_input,
+ OnDeviceDescriptionsCallback on_descriptions_cb) = 0;
+
+ // Replies with an empty optional if there is no associated output device
+ // found and a non-empty string otherwise.
+ virtual void GetAssociatedOutputDeviceID(
+ const std::string& input_device_id,
+ OnDeviceIdCallback on_device_id_cb) = 0;
+
+ // Replies with audio parameters for the specified input device and
+ // device ID of the associated output device, if any (otherwise
+ // the associated output device ID is an empty optional).
+ virtual void GetInputDeviceInfo(
+ const std::string& input_device_id,
+ OnInputDeviceInfoCallback on_input_device_info_cb) = 0;
+
+ // This function wraps |callback| with a call to
+ // AudioDeviceDescription::LocalizeDeviceDescriptions for convenience. This is
+ // typically used by AudioSystem implementations, not AudioSystem clients.
+ static OnDeviceDescriptionsCallback WrapCallbackWithDeviceNameLocalization(
+ OnDeviceDescriptionsCallback callback);
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_SYSTEM_H_
diff --git a/third_party/chromium/media/audio/audio_system_helper.cc b/third_party/chromium/media/audio/audio_system_helper.cc
new file mode 100644
index 0000000..ad026bc
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_system_helper.cc
@@ -0,0 +1,145 @@
+// Copyright 2017 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 "media/audio/audio_system_helper.h"
+
+#include "base/single_thread_task_runner.h"
+#include "media/audio/audio_manager.h"
+#include "media/base/limits.h"
+
+namespace media {
+
+namespace {
+
+absl::optional<AudioParameters> TryToFixChannels(
+ const AudioParameters& params) {
+ DCHECK(!params.IsValid());
+ AudioParameters params_copy(params);
+
+ // If the number of output channels is greater than the maximum, use the
+ // maximum allowed value. Hardware channels are ignored upstream, so it is
+ // better to report a valid value if this is the only problem.
+ if (params.channels() > limits::kMaxChannels) {
+ DCHECK(params.channel_layout() == CHANNEL_LAYOUT_DISCRETE);
+ params_copy.set_channels_for_discrete(limits::kMaxChannels);
+ }
+
+ return params_copy.IsValid() ? params_copy
+ : absl::optional<AudioParameters>();
+}
+
+} // namespace
+
+AudioSystemHelper::AudioSystemHelper(AudioManager* audio_manager)
+ : audio_manager_(audio_manager) {
+ DCHECK(audio_manager_);
+}
+
+AudioSystemHelper::~AudioSystemHelper() = default;
+
+void AudioSystemHelper::GetInputStreamParameters(
+ const std::string& device_id,
+ AudioSystem::OnAudioParamsCallback on_params_cb) {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+ std::move(on_params_cb).Run(ComputeInputParameters(device_id));
+}
+
+void AudioSystemHelper::GetOutputStreamParameters(
+ const std::string& device_id,
+ AudioSystem::OnAudioParamsCallback on_params_cb) {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+ std::move(on_params_cb).Run(ComputeOutputParameters(device_id));
+}
+
+void AudioSystemHelper::HasInputDevices(
+ AudioSystem::OnBoolCallback on_has_devices_cb) {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+ std::move(on_has_devices_cb).Run(audio_manager_->HasAudioInputDevices());
+}
+
+void AudioSystemHelper::HasOutputDevices(
+ AudioSystem::OnBoolCallback on_has_devices_cb) {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+ std::move(on_has_devices_cb).Run(audio_manager_->HasAudioOutputDevices());
+}
+
+void AudioSystemHelper::GetDeviceDescriptions(
+ bool for_input,
+ AudioSystem::OnDeviceDescriptionsCallback on_descriptions_cb) {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+ AudioDeviceDescriptions descriptions;
+ if (for_input)
+ audio_manager_->GetAudioInputDeviceDescriptions(&descriptions);
+ else
+ audio_manager_->GetAudioOutputDeviceDescriptions(&descriptions);
+ std::move(on_descriptions_cb).Run(std::move(descriptions));
+}
+
+void AudioSystemHelper::GetAssociatedOutputDeviceID(
+ const std::string& input_device_id,
+ AudioSystem::OnDeviceIdCallback on_device_id_cb) {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+ const std::string associated_output_device_id =
+ audio_manager_->GetAssociatedOutputDeviceID(input_device_id);
+ std::move(on_device_id_cb)
+ .Run(associated_output_device_id.empty() ? absl::optional<std::string>()
+ : associated_output_device_id);
+}
+
+void AudioSystemHelper::GetInputDeviceInfo(
+ const std::string& input_device_id,
+ AudioSystem::OnInputDeviceInfoCallback on_input_device_info_cb) {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+ const std::string associated_output_device_id =
+ audio_manager_->GetAssociatedOutputDeviceID(input_device_id);
+ std::move(on_input_device_info_cb)
+ .Run(ComputeInputParameters(input_device_id),
+ associated_output_device_id.empty() ? absl::optional<std::string>()
+ : associated_output_device_id);
+}
+
+absl::optional<AudioParameters> AudioSystemHelper::ComputeInputParameters(
+ const std::string& device_id) {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+
+ // TODO(olka): remove this when AudioManager::GetInputStreamParameters()
+ // returns invalid parameters if the device is not found.
+ if (AudioDeviceDescription::IsLoopbackDevice(device_id)) {
+ // For system audio capture, we need an output device (namely speaker)
+ // instead of an input device (namely microphone) to work.
+ // AudioManager::GetInputStreamParameters will check |device_id| and
+ // query the correct device for audio parameters by itself.
+ if (!audio_manager_->HasAudioOutputDevices())
+ return absl::optional<AudioParameters>();
+ } else {
+ if (!audio_manager_->HasAudioInputDevices())
+ return absl::optional<AudioParameters>();
+ }
+
+ AudioParameters params = audio_manager_->GetInputStreamParameters(device_id);
+ return params.IsValid() ? params : TryToFixChannels(params);
+}
+
+absl::optional<AudioParameters> AudioSystemHelper::ComputeOutputParameters(
+ const std::string& device_id) {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+
+ // TODO(olka): remove this when
+ // AudioManager::Get[Default]OutputStreamParameters() returns invalid
+ // parameters if the device is not found.
+ if (!audio_manager_->HasAudioOutputDevices())
+ return absl::optional<AudioParameters>();
+
+ AudioParameters params =
+ AudioDeviceDescription::IsDefaultDevice(device_id)
+ ? audio_manager_->GetDefaultOutputStreamParameters()
+ : audio_manager_->GetOutputStreamParameters(device_id);
+
+ if (params.IsValid())
+ return params;
+
+ return TryToFixChannels(params);
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_system_helper.h b/third_party/chromium/media/audio/audio_system_helper.h
new file mode 100644
index 0000000..30897d9
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_system_helper.h
@@ -0,0 +1,62 @@
+// Copyright 2017 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 MEDIA_AUDIO_AUDIO_SYSTEM_HELPER_H_
+#define MEDIA_AUDIO_AUDIO_SYSTEM_HELPER_H_
+
+#include "media/audio/audio_system.h"
+#include "media/base/media_export.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace media {
+class AudioManager;
+
+// Helper class wrapping AudioManager functionality. Methods to be called on
+// audio thread only. Only audio system implementations are allowed to use it.
+// See AudioSystem interface for method descriptions.
+class MEDIA_EXPORT AudioSystemHelper {
+ public:
+ AudioSystemHelper(AudioManager* audio_manager);
+
+ AudioSystemHelper(const AudioSystemHelper&) = delete;
+ AudioSystemHelper& operator=(const AudioSystemHelper&) = delete;
+
+ ~AudioSystemHelper();
+
+ void GetInputStreamParameters(
+ const std::string& device_id,
+ AudioSystem::OnAudioParamsCallback on_params_cb);
+
+ void GetOutputStreamParameters(
+ const std::string& device_id,
+ AudioSystem::OnAudioParamsCallback on_params_cb);
+
+ void HasInputDevices(AudioSystem::OnBoolCallback on_has_devices_cb);
+
+ void HasOutputDevices(AudioSystem::OnBoolCallback on_has_devices_cb);
+
+ void GetDeviceDescriptions(
+ bool for_input,
+ AudioSystem::OnDeviceDescriptionsCallback on_descriptions_cp);
+
+ void GetAssociatedOutputDeviceID(
+ const std::string& input_device_id,
+ AudioSystem::OnDeviceIdCallback on_device_id_cb);
+
+ void GetInputDeviceInfo(
+ const std::string& input_device_id,
+ AudioSystem::OnInputDeviceInfoCallback on_input_device_info_cb);
+
+ private:
+ absl::optional<AudioParameters> ComputeInputParameters(
+ const std::string& device_id);
+ absl::optional<AudioParameters> ComputeOutputParameters(
+ const std::string& device_id);
+
+ AudioManager* const audio_manager_;
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_SYSTEM_HELPER_H_
diff --git a/third_party/chromium/media/audio/audio_system_impl.cc b/third_party/chromium/media/audio/audio_system_impl.cc
new file mode 100644
index 0000000..7a3efa5
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_system_impl.cc
@@ -0,0 +1,181 @@
+// Copyright 2017 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 "media/audio/audio_system_impl.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/memory/ptr_util.h"
+#include "base/single_thread_task_runner.h"
+#include "base/task_runner_util.h"
+#include "media/audio/audio_device_description.h"
+#include "media/audio/audio_manager.h"
+#include "media/base/bind_to_current_loop.h"
+
+// Using base::Unretained for |audio_manager_| is safe since AudioManager is
+// deleted after audio thread is stopped.
+
+// No need to bind the callback to the current loop if we are on the audio
+// thread. However, the client still expects to receive the reply
+// asynchronously, so we always post the helper function, which will
+// syncronously call the (bound to current loop or not) callback. Thus the
+// client always receives the callback on the thread it accesses AudioSystem on.
+
+namespace media {
+
+namespace {
+
+void GetInputStreamParametersOnAudioThread(
+ AudioManager* audio_manager,
+ const std::string& device_id,
+ AudioSystem::OnAudioParamsCallback on_params_cb) {
+ AudioSystemHelper(audio_manager)
+ .GetInputStreamParameters(device_id, std::move(on_params_cb));
+}
+
+void GetOutputStreamParametersOnAudioThread(
+ AudioManager* audio_manager,
+ const std::string& device_id,
+ AudioSystem::OnAudioParamsCallback on_params_cb) {
+ AudioSystemHelper(audio_manager)
+ .GetOutputStreamParameters(device_id, std::move(on_params_cb));
+}
+
+void HasInputDevicesOnAudioThread(
+ AudioManager* audio_manager,
+ AudioSystem::OnBoolCallback on_has_devices_cb) {
+ AudioSystemHelper(audio_manager)
+ .HasInputDevices(std::move(on_has_devices_cb));
+}
+
+void HasOutputDevicesOnAudioThread(
+ AudioManager* audio_manager,
+ AudioSystem::OnBoolCallback on_has_devices_cb) {
+ AudioSystemHelper(audio_manager)
+ .HasOutputDevices(std::move(on_has_devices_cb));
+}
+
+void GetDeviceDescriptionsOnAudioThread(
+ AudioManager* audio_manager,
+ bool for_input,
+ AudioSystem::OnDeviceDescriptionsCallback on_descriptions_cb) {
+ AudioSystemHelper(audio_manager)
+ .GetDeviceDescriptions(for_input, std::move(on_descriptions_cb));
+}
+
+void GetAssociatedOutputDeviceIDOnAudioThread(
+ AudioManager* audio_manager,
+ const std::string& input_device_id,
+ AudioSystem::OnDeviceIdCallback on_device_id_cb) {
+ AudioSystemHelper(audio_manager)
+ .GetAssociatedOutputDeviceID(input_device_id, std::move(on_device_id_cb));
+}
+
+void GetInputDeviceInfoOnAudioThread(
+ AudioManager* audio_manager,
+ const std::string& input_device_id,
+ AudioSystem::OnInputDeviceInfoCallback on_input_device_info_cb) {
+ AudioSystemHelper(audio_manager)
+ .GetInputDeviceInfo(input_device_id, std::move(on_input_device_info_cb));
+}
+
+} // namespace
+
+template <typename... Args>
+inline base::OnceCallback<void(Args...)>
+AudioSystemImpl::MaybeBindToCurrentLoop(
+ base::OnceCallback<void(Args...)> callback) {
+ return audio_manager_->GetTaskRunner()->BelongsToCurrentThread()
+ ? std::move(callback)
+ : media::BindToCurrentLoop(std::move(callback));
+}
+
+// static
+std::unique_ptr<AudioSystem> AudioSystemImpl::CreateInstance() {
+ DCHECK(AudioManager::Get()) << "AudioManager instance is not created";
+ return std::make_unique<AudioSystemImpl>(AudioManager::Get());
+}
+
+AudioSystemImpl::AudioSystemImpl(AudioManager* audio_manager)
+ : audio_manager_(audio_manager) {
+ DETACH_FROM_THREAD(thread_checker_);
+}
+
+void AudioSystemImpl::GetInputStreamParameters(
+ const std::string& device_id,
+ OnAudioParamsCallback on_params_cb) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ audio_manager_->GetTaskRunner()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&GetInputStreamParametersOnAudioThread,
+ base::Unretained(audio_manager_), device_id,
+ MaybeBindToCurrentLoop(std::move(on_params_cb))));
+}
+
+void AudioSystemImpl::GetOutputStreamParameters(
+ const std::string& device_id,
+ OnAudioParamsCallback on_params_cb) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ audio_manager_->GetTaskRunner()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&GetOutputStreamParametersOnAudioThread,
+ base::Unretained(audio_manager_), device_id,
+ MaybeBindToCurrentLoop(std::move(on_params_cb))));
+}
+
+void AudioSystemImpl::HasInputDevices(OnBoolCallback on_has_devices_cb) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ audio_manager_->GetTaskRunner()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&HasInputDevicesOnAudioThread,
+ base::Unretained(audio_manager_),
+ MaybeBindToCurrentLoop(std::move(on_has_devices_cb))));
+}
+
+void AudioSystemImpl::HasOutputDevices(OnBoolCallback on_has_devices_cb) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ audio_manager_->GetTaskRunner()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&HasOutputDevicesOnAudioThread,
+ base::Unretained(audio_manager_),
+ MaybeBindToCurrentLoop(std::move(on_has_devices_cb))));
+}
+
+void AudioSystemImpl::GetDeviceDescriptions(
+ bool for_input,
+ OnDeviceDescriptionsCallback on_descriptions_cb) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ audio_manager_->GetTaskRunner()->PostTask(
+ FROM_HERE, base::BindOnce(&GetDeviceDescriptionsOnAudioThread,
+ base::Unretained(audio_manager_), for_input,
+ MaybeBindToCurrentLoop(
+ WrapCallbackWithDeviceNameLocalization(
+ std::move(on_descriptions_cb)))));
+}
+
+void AudioSystemImpl::GetAssociatedOutputDeviceID(
+ const std::string& input_device_id,
+ OnDeviceIdCallback on_device_id_cb) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ audio_manager_->GetTaskRunner()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&GetAssociatedOutputDeviceIDOnAudioThread,
+ base::Unretained(audio_manager_), input_device_id,
+ MaybeBindToCurrentLoop(std::move(on_device_id_cb))));
+}
+
+void AudioSystemImpl::GetInputDeviceInfo(
+ const std::string& input_device_id,
+ OnInputDeviceInfoCallback on_input_device_info_cb) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ audio_manager_->GetTaskRunner()->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ &GetInputDeviceInfoOnAudioThread, base::Unretained(audio_manager_),
+ input_device_id,
+ MaybeBindToCurrentLoop(std::move(on_input_device_info_cb))));
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_system_impl.h b/third_party/chromium/media/audio/audio_system_impl.h
new file mode 100644
index 0000000..59fc179
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_system_impl.h
@@ -0,0 +1,64 @@
+// Copyright 2017 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 MEDIA_AUDIO_AUDIO_SYSTEM_IMPL_H_
+#define MEDIA_AUDIO_AUDIO_SYSTEM_IMPL_H_
+
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "base/threading/thread_checker.h"
+#include "media/audio/audio_system.h"
+#include "media/audio/audio_system_helper.h"
+
+namespace media {
+class AudioManager;
+
+class MEDIA_EXPORT AudioSystemImpl : public AudioSystem {
+ public:
+ // Creates AudioSystem using the global AudioManager instance, which must be
+ // created prior to that.
+ static std::unique_ptr<AudioSystem> CreateInstance();
+
+ explicit AudioSystemImpl(AudioManager* audio_manager);
+
+ // AudioSystem implementation.
+ void GetInputStreamParameters(const std::string& device_id,
+ OnAudioParamsCallback on_params_cb) override;
+
+ void GetOutputStreamParameters(const std::string& device_id,
+ OnAudioParamsCallback on_params_cb) override;
+
+ void HasInputDevices(OnBoolCallback on_has_devices_cb) override;
+
+ void HasOutputDevices(OnBoolCallback on_has_devices_cb) override;
+
+ void GetDeviceDescriptions(
+ bool for_input,
+ OnDeviceDescriptionsCallback on_descriptions_cp) override;
+
+ void GetAssociatedOutputDeviceID(const std::string& input_device_id,
+ OnDeviceIdCallback on_device_id_cb) override;
+
+ void GetInputDeviceInfo(
+ const std::string& input_device_id,
+ OnInputDeviceInfoCallback on_input_device_info_cb) override;
+
+ private:
+ // No-op if called on helper_.GetTaskRunner() thread, otherwise binds
+ // |callback| to the current loop.
+ template <typename... Args>
+ base::OnceCallback<void(Args...)> MaybeBindToCurrentLoop(
+ base::OnceCallback<void(Args...)> callback);
+
+ THREAD_CHECKER(thread_checker_);
+ AudioManager* const audio_manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(AudioSystemImpl);
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_SYSTEM_IMPL_H_
diff --git a/third_party/chromium/media/audio/audio_system_impl_unittest.cc b/third_party/chromium/media/audio/audio_system_impl_unittest.cc
new file mode 100644
index 0000000..8375d35
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_system_impl_unittest.cc
@@ -0,0 +1,53 @@
+// Copyright 2017 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 "media/audio/audio_system_impl.h"
+
+#include "base/test/task_environment.h"
+#include "media/audio/audio_system_test_util.h"
+#include "media/audio/audio_thread_impl.h"
+#include "media/audio/mock_audio_manager.h"
+#include "media/audio/test_audio_thread.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+
+// TODO(olka): These are the only tests for AudioSystemHelper. Make sure that
+// AudioSystemHelper is tested if AudioSystemImpl goes away.
+
+// Typed tests cannot be parametrized, so using template parameter instead of
+// inheriting from TestWithParams<>
+template <bool use_audio_thread>
+class AudioSystemImplTestBase : public testing::Test {
+ public:
+ AudioSystemImplTestBase() = default;
+
+ ~AudioSystemImplTestBase() override = default;
+
+ void SetUp() override {
+ audio_manager_ = std::make_unique<MockAudioManager>(
+ std::make_unique<TestAudioThread>(use_audio_thread));
+ audio_system_ = std::make_unique<AudioSystemImpl>(audio_manager_.get());
+ }
+ void TearDown() override { audio_manager_->Shutdown(); }
+
+ protected:
+ MockAudioManager* audio_manager() { return audio_manager_.get(); }
+ AudioSystem* audio_system() { return audio_system_.get(); }
+
+ base::test::SingleThreadTaskEnvironment task_environment_;
+ std::unique_ptr<MockAudioManager> audio_manager_;
+ std::unique_ptr<AudioSystem> audio_system_;
+ // AudioSystemTester tester_;
+};
+
+using AudioSystemTestBaseVariations =
+ testing::Types<AudioSystemImplTestBase<false>,
+ AudioSystemImplTestBase<true>>;
+
+INSTANTIATE_TYPED_TEST_SUITE_P(AudioSystemImpl,
+ AudioSystemTestTemplate,
+ AudioSystemTestBaseVariations);
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_system_test_util.cc b/third_party/chromium/media/audio/audio_system_test_util.cc
new file mode 100644
index 0000000..7e31a7f
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_system_test_util.cc
@@ -0,0 +1,151 @@
+// Copyright 2017 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 "media/audio/audio_system_test_util.h"
+
+#include "base/bind.h"
+#include "base/memory/ptr_util.h"
+
+namespace media {
+
+bool operator==(const AudioDeviceDescription& lhs,
+ const AudioDeviceDescription& rhs) {
+ return lhs.device_name == rhs.device_name && lhs.unique_id == rhs.unique_id &&
+ lhs.group_id == rhs.group_id;
+}
+
+AudioSystem::OnAudioParamsCallback
+AudioSystemCallbackExpectations::GetAudioParamsCallback(
+ const base::Location& location,
+ base::OnceClosure on_cb_received,
+ const absl::optional<AudioParameters>& expected_params) {
+ return base::BindOnce(&AudioSystemCallbackExpectations::OnAudioParams,
+ base::Unretained(this), location.ToString(),
+ std::move(on_cb_received), expected_params);
+}
+
+AudioSystem::OnBoolCallback AudioSystemCallbackExpectations::GetBoolCallback(
+ const base::Location& location,
+ base::OnceClosure on_cb_received,
+ bool expected) {
+ return base::BindOnce(&AudioSystemCallbackExpectations::OnBool,
+ base::Unretained(this), location.ToString(),
+ std::move(on_cb_received), expected);
+}
+
+AudioSystem::OnDeviceDescriptionsCallback
+AudioSystemCallbackExpectations::GetDeviceDescriptionsCallback(
+ const base::Location& location,
+ base::OnceClosure on_cb_received,
+ const AudioDeviceDescriptions& expected_descriptions) {
+ return base::BindOnce(&AudioSystemCallbackExpectations::OnDeviceDescriptions,
+ base::Unretained(this), location.ToString(),
+ std::move(on_cb_received), expected_descriptions);
+}
+
+AudioSystem::OnInputDeviceInfoCallback
+AudioSystemCallbackExpectations::GetInputDeviceInfoCallback(
+ const base::Location& location,
+ base::OnceClosure on_cb_received,
+ const absl::optional<AudioParameters>& expected_input,
+ const absl::optional<std::string>& expected_associated_device_id) {
+ return base::BindOnce(&AudioSystemCallbackExpectations::OnInputDeviceInfo,
+ base::Unretained(this), location.ToString(),
+ std::move(on_cb_received), expected_input,
+ expected_associated_device_id);
+}
+
+AudioSystem::OnDeviceIdCallback
+AudioSystemCallbackExpectations::GetDeviceIdCallback(
+ const base::Location& location,
+ base::OnceClosure on_cb_received,
+ const absl::optional<std::string>& expected_id) {
+ return base::BindOnce(&AudioSystemCallbackExpectations::OnDeviceId,
+ base::Unretained(this), location.ToString(),
+ std::move(on_cb_received), expected_id);
+}
+
+void AudioSystemCallbackExpectations::OnAudioParams(
+ const std::string& from_here,
+ base::OnceClosure on_cb_received,
+ const absl::optional<AudioParameters>& expected,
+ const absl::optional<AudioParameters>& received) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_, from_here);
+ if (expected) {
+ EXPECT_TRUE(received) << from_here;
+ EXPECT_EQ(expected->AsHumanReadableString(),
+ received->AsHumanReadableString())
+ << from_here;
+ } else {
+ EXPECT_FALSE(received) << from_here;
+ }
+ std::move(on_cb_received).Run();
+}
+
+void AudioSystemCallbackExpectations::OnBool(const std::string& from_here,
+ base::OnceClosure on_cb_received,
+ bool expected,
+ bool result) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_, from_here);
+ EXPECT_EQ(expected, result) << from_here;
+ std::move(on_cb_received).Run();
+}
+
+void AudioSystemCallbackExpectations::OnDeviceDescriptions(
+ const std::string& from_here,
+ base::OnceClosure on_cb_received,
+ const AudioDeviceDescriptions& expected_descriptions,
+ AudioDeviceDescriptions descriptions) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ EXPECT_EQ(expected_descriptions, descriptions);
+ std::move(on_cb_received).Run();
+}
+
+void AudioSystemCallbackExpectations::OnInputDeviceInfo(
+ const std::string& from_here,
+ base::OnceClosure on_cb_received,
+ const absl::optional<AudioParameters>& expected_input,
+ const absl::optional<std::string>& expected_associated_device_id,
+ const absl::optional<AudioParameters>& input,
+ const absl::optional<std::string>& associated_device_id) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_, from_here);
+ EXPECT_TRUE(!input || input->IsValid());
+ if (expected_input) {
+ EXPECT_TRUE(input) << from_here;
+ EXPECT_EQ(expected_input->AsHumanReadableString(),
+ input->AsHumanReadableString())
+ << from_here;
+ } else {
+ EXPECT_FALSE(input) << from_here;
+ }
+ EXPECT_TRUE(!associated_device_id || !associated_device_id->empty());
+ if (expected_associated_device_id) {
+ EXPECT_TRUE(associated_device_id) << from_here;
+ EXPECT_EQ(expected_associated_device_id, associated_device_id) << from_here;
+ } else {
+ EXPECT_FALSE(associated_device_id) << from_here;
+ }
+ std::move(on_cb_received).Run();
+}
+
+void AudioSystemCallbackExpectations::OnDeviceId(
+ const std::string& from_here,
+ base::OnceClosure on_cb_received,
+ const absl::optional<std::string>& expected_id,
+ const absl::optional<std::string>& result_id) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_, from_here);
+ EXPECT_TRUE(!result_id || !result_id->empty());
+ if (expected_id) {
+ EXPECT_TRUE(result_id) << from_here;
+ EXPECT_EQ(expected_id, result_id) << from_here;
+ } else {
+ EXPECT_FALSE(result_id) << from_here;
+ }
+ std::move(on_cb_received).Run();
+}
+
+// This suite is instantiated in binaries that use //media:test_support.
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioSystemTestTemplate);
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_system_test_util.h b/third_party/chromium/media/audio/audio_system_test_util.h
new file mode 100644
index 0000000..b14c1cc
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_system_test_util.h
@@ -0,0 +1,366 @@
+// Copyright 2017 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 MEDIA_AUDIO_AUDIO_SYSTEM_TEST_UTIL_H_
+#define MEDIA_AUDIO_AUDIO_SYSTEM_TEST_UTIL_H_
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/threading/thread_checker.h"
+#include "media/audio/audio_device_description.h"
+#include "media/audio/audio_system.h"
+#include "media/audio/mock_audio_manager.h"
+#include "media/base/audio_parameters.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace media {
+
+// For tests only. Creates AudioSystem callbacks to be passed to AudioSystem
+// methods. When AudioSystem calls such a callback, it verifies treading
+// expectations and checks recieved parameters against expected values passed
+// during its creation. After that it calls |on_cb_received| closure.
+// Note AudioSystemCallbackExpectations object must outlive all the callbacks
+// it produced, since they contain raw pointers to it.
+class AudioSystemCallbackExpectations {
+ public:
+ AudioSystemCallbackExpectations() = default;
+ AudioSystem::OnAudioParamsCallback GetAudioParamsCallback(
+ const base::Location& location,
+ base::OnceClosure on_cb_received,
+ const absl::optional<AudioParameters>& expected_params);
+
+ AudioSystem::OnBoolCallback GetBoolCallback(const base::Location& location,
+ base::OnceClosure on_cb_received,
+ bool expected);
+
+ AudioSystem::OnDeviceDescriptionsCallback GetDeviceDescriptionsCallback(
+ const base::Location& location,
+ base::OnceClosure on_cb_received,
+ const AudioDeviceDescriptions& expected_descriptions);
+
+ AudioSystem::OnInputDeviceInfoCallback GetInputDeviceInfoCallback(
+ const base::Location& location,
+ base::OnceClosure on_cb_received,
+ const absl::optional<AudioParameters>& expected_input,
+ const absl::optional<std::string>& expected_associated_device_id);
+
+ AudioSystem::OnDeviceIdCallback GetDeviceIdCallback(
+ const base::Location& location,
+ base::OnceClosure on_cb_received,
+ const absl::optional<std::string>& expected_id);
+
+ private:
+ // Methods to verify correctness of received data.
+ void OnAudioParams(const std::string& from_here,
+ base::OnceClosure on_cb_received,
+ const absl::optional<AudioParameters>& expected,
+ const absl::optional<AudioParameters>& received);
+
+ void OnBool(const std::string& from_here,
+ base::OnceClosure on_cb_received,
+ bool expected,
+ bool result);
+
+ void OnDeviceDescriptions(
+ const std::string& from_here,
+ base::OnceClosure on_cb_received,
+ const AudioDeviceDescriptions& expected_descriptions,
+ AudioDeviceDescriptions descriptions);
+
+ void OnInputDeviceInfo(
+ const std::string& from_here,
+ base::OnceClosure on_cb_received,
+ const absl::optional<AudioParameters>& expected_input,
+ const absl::optional<std::string>& expected_associated_device_id,
+ const absl::optional<AudioParameters>& input,
+ const absl::optional<std::string>& associated_device_id);
+
+ void OnDeviceId(const std::string& from_here,
+ base::OnceClosure on_cb_received,
+ const absl::optional<std::string>& expected_id,
+ const absl::optional<std::string>& result_id);
+
+ THREAD_CHECKER(thread_checker_);
+ DISALLOW_COPY_AND_ASSIGN(AudioSystemCallbackExpectations);
+};
+
+// Template test case to test AudioSystem implementations.
+template <class T>
+class AudioSystemTestTemplate : public T {
+ public:
+ AudioSystemTestTemplate() {}
+
+ AudioSystemTestTemplate(const AudioSystemTestTemplate&) = delete;
+ AudioSystemTestTemplate& operator=(const AudioSystemTestTemplate&) = delete;
+
+ ~AudioSystemTestTemplate() override {}
+
+ void SetUp() override {
+ T::SetUp();
+ input_params_ =
+ AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_MONO,
+ AudioParameters::kTelephoneSampleRate,
+ AudioParameters::kTelephoneSampleRate / 10);
+ output_params_ =
+ AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_MONO,
+ AudioParameters::kTelephoneSampleRate,
+ AudioParameters::kTelephoneSampleRate / 20);
+ default_output_params_ =
+ AudioParameters(AudioParameters::AUDIO_PCM_LINEAR, CHANNEL_LAYOUT_MONO,
+ AudioParameters::kTelephoneSampleRate,
+ AudioParameters::kTelephoneSampleRate / 30);
+ audio_manager()->SetInputStreamParameters(input_params_);
+ audio_manager()->SetOutputStreamParameters(output_params_);
+ audio_manager()->SetDefaultOutputStreamParameters(default_output_params_);
+
+ auto get_device_descriptions = [](const AudioDeviceDescriptions* source,
+ AudioDeviceDescriptions* destination) {
+ destination->insert(destination->end(), source->begin(), source->end());
+ };
+
+ audio_manager()->SetInputDeviceDescriptionsCallback(
+ base::BindRepeating(get_device_descriptions,
+ base::Unretained(&input_device_descriptions_)));
+ audio_manager()->SetOutputDeviceDescriptionsCallback(
+ base::BindRepeating(get_device_descriptions,
+ base::Unretained(&output_device_descriptions_)));
+ }
+
+ protected:
+ MockAudioManager* audio_manager() { return T::audio_manager(); }
+ AudioSystem* audio_system() { return T::audio_system(); }
+
+ AudioSystemCallbackExpectations expectations_;
+ AudioParameters input_params_;
+ AudioParameters output_params_;
+ AudioParameters default_output_params_;
+ AudioDeviceDescriptions input_device_descriptions_;
+ AudioDeviceDescriptions output_device_descriptions_;
+};
+
+TYPED_TEST_SUITE_P(AudioSystemTestTemplate);
+
+TYPED_TEST_P(AudioSystemTestTemplate, GetInputStreamParametersNormal) {
+ base::RunLoop wait_loop;
+ this->audio_system()->GetInputStreamParameters(
+ AudioDeviceDescription::kDefaultDeviceId,
+ this->expectations_.GetAudioParamsCallback(
+ FROM_HERE, wait_loop.QuitClosure(), this->input_params_));
+ wait_loop.Run();
+}
+
+TYPED_TEST_P(AudioSystemTestTemplate, GetInputStreamParametersNoDevice) {
+ this->audio_manager()->SetHasInputDevices(false);
+
+ base::RunLoop wait_loop;
+ this->audio_system()->GetInputStreamParameters(
+ AudioDeviceDescription::kDefaultDeviceId,
+ this->expectations_.GetAudioParamsCallback(
+ FROM_HERE, wait_loop.QuitClosure(),
+ absl::optional<AudioParameters>()));
+ wait_loop.Run();
+}
+
+TYPED_TEST_P(AudioSystemTestTemplate, GetOutputStreamParameters) {
+ base::RunLoop wait_loop;
+ this->audio_system()->GetOutputStreamParameters(
+ "non-default-device-id",
+ this->expectations_.GetAudioParamsCallback(
+ FROM_HERE, wait_loop.QuitClosure(), this->output_params_));
+ wait_loop.Run();
+}
+
+TYPED_TEST_P(AudioSystemTestTemplate, GetDefaultOutputStreamParameters) {
+ base::RunLoop wait_loop;
+ this->audio_system()->GetOutputStreamParameters(
+ AudioDeviceDescription::kDefaultDeviceId,
+ this->expectations_.GetAudioParamsCallback(
+ FROM_HERE, wait_loop.QuitClosure(), this->default_output_params_));
+ wait_loop.Run();
+}
+
+TYPED_TEST_P(AudioSystemTestTemplate,
+ GetOutputStreamParametersForDefaultDeviceNoDevices) {
+ this->audio_manager()->SetHasOutputDevices(false);
+ base::RunLoop wait_loop;
+ this->audio_system()->GetOutputStreamParameters(
+ AudioDeviceDescription::kDefaultDeviceId,
+ this->expectations_.GetAudioParamsCallback(
+ FROM_HERE, wait_loop.QuitClosure(),
+ absl::optional<AudioParameters>()));
+ wait_loop.Run();
+}
+
+TYPED_TEST_P(AudioSystemTestTemplate,
+ GetOutputStreamParametersForNonDefaultDeviceNoDevices) {
+ this->audio_manager()->SetHasOutputDevices(false);
+ base::RunLoop wait_loop;
+ this->audio_system()->GetOutputStreamParameters(
+ "non-default-device-id", this->expectations_.GetAudioParamsCallback(
+ FROM_HERE, wait_loop.QuitClosure(),
+ absl::optional<AudioParameters>()));
+ wait_loop.Run();
+}
+
+TYPED_TEST_P(AudioSystemTestTemplate, HasInputDevices) {
+ base::RunLoop wait_loop;
+ this->audio_system()->HasInputDevices(this->expectations_.GetBoolCallback(
+ FROM_HERE, wait_loop.QuitClosure(), true));
+ wait_loop.Run();
+}
+
+TYPED_TEST_P(AudioSystemTestTemplate, HasNoInputDevices) {
+ this->audio_manager()->SetHasInputDevices(false);
+ base::RunLoop wait_loop;
+ this->audio_system()->HasInputDevices(this->expectations_.GetBoolCallback(
+ FROM_HERE, wait_loop.QuitClosure(), false));
+ wait_loop.Run();
+}
+
+TYPED_TEST_P(AudioSystemTestTemplate, HasOutputDevices) {
+ base::RunLoop wait_loop;
+ this->audio_system()->HasOutputDevices(this->expectations_.GetBoolCallback(
+ FROM_HERE, wait_loop.QuitClosure(), true));
+ wait_loop.Run();
+}
+
+TYPED_TEST_P(AudioSystemTestTemplate, HasNoOutputDevices) {
+ this->audio_manager()->SetHasOutputDevices(false);
+ base::RunLoop wait_loop;
+ this->audio_system()->HasOutputDevices(this->expectations_.GetBoolCallback(
+ FROM_HERE, wait_loop.QuitClosure(), false));
+ wait_loop.Run();
+}
+
+TYPED_TEST_P(AudioSystemTestTemplate,
+ GetInputDeviceDescriptionsNoInputDevices) {
+ this->output_device_descriptions_.emplace_back(
+ "output_device_name", "output_device_id", "group_id");
+ EXPECT_EQ(0, static_cast<int>(this->input_device_descriptions_.size()));
+ EXPECT_EQ(1, static_cast<int>(this->output_device_descriptions_.size()));
+
+ base::RunLoop wait_loop;
+ this->audio_system()->GetDeviceDescriptions(
+ true, this->expectations_.GetDeviceDescriptionsCallback(
+ FROM_HERE, wait_loop.QuitClosure(),
+ this->input_device_descriptions_));
+ wait_loop.Run();
+}
+
+TYPED_TEST_P(AudioSystemTestTemplate, GetInputDeviceDescriptions) {
+ this->output_device_descriptions_.emplace_back(
+ "output_device_name", "output_device_id", "group_id");
+ this->input_device_descriptions_.emplace_back(
+ "input_device_name1", "input_device_id1", "group_id1");
+ this->input_device_descriptions_.emplace_back(
+ "input_device_name2", "input_device_id2", "group_id2");
+ EXPECT_EQ(2, static_cast<int>(this->input_device_descriptions_.size()));
+ EXPECT_EQ(1, static_cast<int>(this->output_device_descriptions_.size()));
+
+ base::RunLoop wait_loop;
+ this->audio_system()->GetDeviceDescriptions(
+ true, this->expectations_.GetDeviceDescriptionsCallback(
+ FROM_HERE, wait_loop.QuitClosure(),
+ this->input_device_descriptions_));
+ wait_loop.Run();
+}
+
+TYPED_TEST_P(AudioSystemTestTemplate,
+ GetOutputDeviceDescriptionsNoInputDevices) {
+ this->input_device_descriptions_.emplace_back("input_device_name",
+ "input_device_id", "group_id");
+ EXPECT_EQ(0, static_cast<int>(this->output_device_descriptions_.size()));
+ EXPECT_EQ(1, static_cast<int>(this->input_device_descriptions_.size()));
+
+ base::RunLoop wait_loop;
+ this->audio_system()->GetDeviceDescriptions(
+ false, this->expectations_.GetDeviceDescriptionsCallback(
+ FROM_HERE, wait_loop.QuitClosure(),
+ this->output_device_descriptions_));
+ wait_loop.Run();
+}
+
+TYPED_TEST_P(AudioSystemTestTemplate, GetOutputDeviceDescriptions) {
+ this->input_device_descriptions_.emplace_back("input_device_name",
+ "input_device_id", "group_id");
+ this->output_device_descriptions_.emplace_back(
+ "output_device_name1", "output_device_id1", "group_id1");
+ this->output_device_descriptions_.emplace_back(
+ "output_device_name2", "output_device_id2", "group_id2");
+ EXPECT_EQ(2, static_cast<int>(this->output_device_descriptions_.size()));
+ EXPECT_EQ(1, static_cast<int>(this->input_device_descriptions_.size()));
+
+ base::RunLoop wait_loop;
+ this->audio_system()->GetDeviceDescriptions(
+ false, this->expectations_.GetDeviceDescriptionsCallback(
+ FROM_HERE, wait_loop.QuitClosure(),
+ this->output_device_descriptions_));
+ wait_loop.Run();
+}
+
+TYPED_TEST_P(AudioSystemTestTemplate, GetAssociatedOutputDeviceID) {
+ const std::string associated_id("associated_id");
+ this->audio_manager()->SetAssociatedOutputDeviceIDCallback(
+ base::BindRepeating([](const std::string& result, const std::string&)
+ -> std::string { return result; },
+ associated_id));
+
+ base::RunLoop wait_loop;
+ this->audio_system()->GetAssociatedOutputDeviceID(
+ std::string(), this->expectations_.GetDeviceIdCallback(
+ FROM_HERE, wait_loop.QuitClosure(), associated_id));
+ wait_loop.Run();
+}
+
+TYPED_TEST_P(AudioSystemTestTemplate, GetInputDeviceInfoNoAssociation) {
+ base::RunLoop wait_loop;
+ this->audio_system()->GetInputDeviceInfo(
+ "non-default-device-id",
+ this->expectations_.GetInputDeviceInfoCallback(
+ FROM_HERE, wait_loop.QuitClosure(), this->input_params_,
+ absl::optional<std::string>()));
+ wait_loop.Run();
+}
+
+TYPED_TEST_P(AudioSystemTestTemplate, GetInputDeviceInfoWithAssociation) {
+ const std::string associated_id("associated_id");
+ this->audio_manager()->SetAssociatedOutputDeviceIDCallback(
+ base::BindRepeating([](const std::string& result, const std::string&)
+ -> std::string { return result; },
+ associated_id));
+
+ base::RunLoop wait_loop;
+ this->audio_system()->GetInputDeviceInfo(
+ "non-default-device-id", this->expectations_.GetInputDeviceInfoCallback(
+ FROM_HERE, wait_loop.QuitClosure(),
+ this->input_params_, associated_id));
+ wait_loop.Run();
+}
+
+REGISTER_TYPED_TEST_SUITE_P(
+ AudioSystemTestTemplate,
+ GetInputStreamParametersNormal,
+ GetInputStreamParametersNoDevice,
+ GetOutputStreamParameters,
+ GetDefaultOutputStreamParameters,
+ GetOutputStreamParametersForDefaultDeviceNoDevices,
+ GetOutputStreamParametersForNonDefaultDeviceNoDevices,
+ HasInputDevices,
+ HasNoInputDevices,
+ HasOutputDevices,
+ HasNoOutputDevices,
+ GetInputDeviceDescriptionsNoInputDevices,
+ GetInputDeviceDescriptions,
+ GetOutputDeviceDescriptionsNoInputDevices,
+ GetOutputDeviceDescriptions,
+ GetAssociatedOutputDeviceID,
+ GetInputDeviceInfoNoAssociation,
+ GetInputDeviceInfoWithAssociation);
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_SYSTEM_TEST_UTIL_H_
diff --git a/third_party/chromium/media/audio/audio_thread.h b/third_party/chromium/media/audio/audio_thread.h
new file mode 100644
index 0000000..b014b89
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_thread.h
@@ -0,0 +1,42 @@
+// Copyright 2017 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 MEDIA_AUDIO_AUDIO_THREAD_H_
+#define MEDIA_AUDIO_AUDIO_THREAD_H_
+
+#include "media/base/media_export.h"
+
+namespace base {
+class SingleThreadTaskRunner;
+} // namespace base
+
+namespace media {
+
+// This class encapulates the logic for the thread and task runners that the
+// AudioManager and related classes run on.
+class MEDIA_EXPORT AudioThread {
+ public:
+ virtual ~AudioThread() {}
+
+ // Synchronously stops all underlying threads.
+ virtual void Stop() = 0;
+
+ // Indicates whether the audio thread is responsive. If false, calling Stop()
+ // will likely block forever.
+ virtual bool IsHung() const = 0;
+
+ // Returns the task runner used for audio IO.
+ // It always returns a non-null task runner (even after Stop has been called).
+ virtual base::SingleThreadTaskRunner* GetTaskRunner() = 0;
+
+ // Heavyweight tasks should use GetWorkerTaskRunner() instead of
+ // GetTaskRunner(). On most platforms they are the same, but some share the
+ // UI loop with the audio IO loop.
+ // It always returns a non-null task runner (even after Stop has been called).
+ virtual base::SingleThreadTaskRunner* GetWorkerTaskRunner() = 0;
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_THREAD_H_
diff --git a/third_party/chromium/media/audio/audio_thread_hang_monitor.cc b/third_party/chromium/media/audio/audio_thread_hang_monitor.cc
new file mode 100644
index 0000000..f69a79b
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_thread_hang_monitor.cc
@@ -0,0 +1,205 @@
+// Copyright 2018 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 "media/audio/audio_thread_hang_monitor.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/debug/dump_without_crashing.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/power_monitor/power_monitor.h"
+#include "base/process/process.h"
+#include "base/single_thread_task_runner.h"
+#include "base/task/post_task.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/tick_clock.h"
+
+namespace media {
+
+namespace {
+
+// Maximum number of failed pings to the audio thread allowed. A UMA will be
+// recorded once this count is reached. We require at least three failed pings
+// before recording to ensure unobservable power events aren't mistakenly
+// caught (e.g., the system suspends before a OnSuspend() event can be fired).
+constexpr int kMaxFailedPingsCount = 3;
+
+// The default deadline after which we consider the audio thread hung.
+constexpr base::TimeDelta kDefaultHangDeadline = base::Minutes(3);
+
+} // namespace
+
+AudioThreadHangMonitor::SharedAtomicFlag::SharedAtomicFlag() {}
+AudioThreadHangMonitor::SharedAtomicFlag::~SharedAtomicFlag() {}
+
+// static
+AudioThreadHangMonitor::Ptr AudioThreadHangMonitor::Create(
+ HangAction hang_action,
+ absl::optional<base::TimeDelta> hang_deadline,
+ const base::TickClock* clock,
+ scoped_refptr<base::SingleThreadTaskRunner> audio_thread_task_runner,
+ scoped_refptr<base::SequencedTaskRunner> monitor_task_runner) {
+ if (!monitor_task_runner)
+ monitor_task_runner = base::ThreadPool::CreateSequencedTaskRunner({});
+
+ auto monitor =
+ Ptr(new AudioThreadHangMonitor(hang_action, hang_deadline, clock,
+ std::move(audio_thread_task_runner)),
+ base::OnTaskRunnerDeleter(monitor_task_runner));
+
+ // |monitor| is destroyed on |monitor_task_runner|, so Unretained is safe.
+ monitor_task_runner->PostTask(
+ FROM_HERE, base::BindOnce(&AudioThreadHangMonitor::StartTimer,
+ base::Unretained(monitor.get())));
+ return monitor;
+}
+
+AudioThreadHangMonitor::~AudioThreadHangMonitor() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(monitor_sequence_);
+}
+
+bool AudioThreadHangMonitor::IsAudioThreadHung() const {
+ return audio_thread_status_ == ThreadStatus::kHung;
+}
+
+AudioThreadHangMonitor::AudioThreadHangMonitor(
+ HangAction hang_action,
+ absl::optional<base::TimeDelta> hang_deadline,
+ const base::TickClock* clock,
+ scoped_refptr<base::SingleThreadTaskRunner> audio_thread_task_runner)
+ : clock_(clock),
+ alive_flag_(base::MakeRefCounted<SharedAtomicFlag>()),
+ audio_task_runner_(std::move(audio_thread_task_runner)),
+ hang_action_(hang_action),
+ ping_interval_((hang_deadline ? hang_deadline.value().is_zero()
+ ? kDefaultHangDeadline
+ : hang_deadline.value()
+ : kDefaultHangDeadline) /
+ kMaxFailedPingsCount),
+ timer_(clock_) {
+ DETACH_FROM_SEQUENCE(monitor_sequence_);
+}
+
+void AudioThreadHangMonitor::StartTimer() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(monitor_sequence_);
+
+ // Set the flag to true so that the first run doesn't detect a hang.
+ alive_flag_->flag_ = true;
+
+ last_check_time_ = clock_->NowTicks();
+
+ LogHistogramThreadStatus();
+
+ // |this| owns |timer_|, so Unretained is safe.
+ timer_.Start(
+ FROM_HERE, ping_interval_,
+ base::BindRepeating(&AudioThreadHangMonitor::CheckIfAudioThreadIsAlive,
+ base::Unretained(this)));
+}
+
+bool AudioThreadHangMonitor::NeverLoggedThreadHung() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(monitor_sequence_);
+ return audio_thread_status_ == ThreadStatus::kStarted;
+}
+
+bool AudioThreadHangMonitor::NeverLoggedThreadRecoveredAfterHung() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(monitor_sequence_);
+ return audio_thread_status_ == ThreadStatus::kHung;
+}
+
+void AudioThreadHangMonitor::CheckIfAudioThreadIsAlive() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(monitor_sequence_);
+
+ const base::TimeDelta time_since_last_check =
+ clock_->NowTicks() - last_check_time_;
+
+ // An unexpected |time_since_last_check| may indicate that the system has been
+ // in sleep mode, in which case the audio thread may have had insufficient
+ // time to respond to the ping. In such a case, skip the check for now.
+ if (time_since_last_check > ping_interval_ + base::Seconds(1))
+ return;
+
+ const bool audio_thread_responded_to_last_ping = alive_flag_->flag_;
+ if (audio_thread_responded_to_last_ping) {
+ recent_ping_state_ = std::max(recent_ping_state_, 0) + 1;
+
+ // Update the thread status if it was previously hung. Will only log
+ // "recovered" once for the lifetime of this object.
+ if (NeverLoggedThreadRecoveredAfterHung() &&
+ recent_ping_state_ >= kMaxFailedPingsCount) {
+ // Require just as many successful pings to recover from failure.
+ audio_thread_status_ = ThreadStatus::kRecovered;
+ LogHistogramThreadStatus();
+ }
+ } else {
+ recent_ping_state_ = std::min(recent_ping_state_, 0) - 1;
+
+ // Update the thread status if it was previously live and has never been
+ // considered hung before. Will only log "hung" once for the lifetime of
+ // this object.
+ if (-recent_ping_state_ >= kMaxFailedPingsCount &&
+ NeverLoggedThreadHung()) {
+ LOG(ERROR)
+ << "Audio thread hang has been detected. You may need to restart "
+ "your browser. Please file a bug at https://crbug.com/new";
+
+ audio_thread_status_ = ThreadStatus::kHung;
+ LogHistogramThreadStatus();
+
+ if (hang_action_ == HangAction::kDump ||
+ hang_action_ == HangAction::kDumpAndTerminateCurrentProcess) {
+ DumpWithoutCrashing();
+ }
+ if (hang_action_ == HangAction::kTerminateCurrentProcess ||
+ hang_action_ == HangAction::kDumpAndTerminateCurrentProcess) {
+ TerminateCurrentProcess();
+ }
+ }
+ }
+
+ alive_flag_->flag_ = false;
+ last_check_time_ = clock_->NowTicks();
+ audio_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ [](scoped_refptr<SharedAtomicFlag> flag) { flag->flag_ = true; },
+ alive_flag_));
+}
+
+void AudioThreadHangMonitor::LogHistogramThreadStatus() {
+ UMA_HISTOGRAM_ENUMERATION("Media.AudioThreadStatus",
+ audio_thread_status_.load());
+}
+
+void AudioThreadHangMonitor::SetHangActionCallbacksForTesting(
+ base::RepeatingClosure dump_callback,
+ base::RepeatingClosure terminate_process_callback) {
+ dump_callback_ = std::move(dump_callback);
+ terminate_process_callback_ = std::move(terminate_process_callback);
+}
+
+void AudioThreadHangMonitor::DumpWithoutCrashing() {
+ LOG(ERROR) << "Creating non-crash dump for audio thread hang.";
+ if (!dump_callback_.is_null())
+ dump_callback_.Run();
+ else
+ base::debug::DumpWithoutCrashing();
+}
+
+void AudioThreadHangMonitor::TerminateCurrentProcess() {
+ LOG(ERROR) << "Terminating process for audio thread hang.";
+ if (!terminate_process_callback_.is_null())
+ terminate_process_callback_.Run();
+ else
+ base::Process::TerminateCurrentProcessImmediately(1);
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_thread_hang_monitor.h b/third_party/chromium/media/audio/audio_thread_hang_monitor.h
new file mode 100644
index 0000000..f625545
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_thread_hang_monitor.h
@@ -0,0 +1,172 @@
+// Copyright 2018 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 MEDIA_AUDIO_AUDIO_THREAD_HANG_MONITOR_H_
+#define MEDIA_AUDIO_AUDIO_THREAD_HANG_MONITOR_H_
+
+#include "media/audio/audio_manager.h"
+
+#include <atomic>
+#include <memory>
+
+#include "base/callback_forward.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/sequence_checker.h"
+#include "base/sequenced_task_runner.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "media/base/media_export.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace base {
+class TickClock;
+class SingleThreadTaskRunner;
+} // namespace base
+
+namespace media {
+
+// This class detects if the audio manager thread is hung. It logs a histogram,
+// and can optionally (if |dump_on_hang| is set) upload a crash dump when a hang
+// is detected. It runs on a task runner from the task scheduler. It works by
+// posting a task to the audio thread every minute and checking that it was
+// executed. If three consecutive such pings are missed, the thread is
+// considered hung.
+class MEDIA_EXPORT AudioThreadHangMonitor final {
+ public:
+ using Ptr =
+ std::unique_ptr<AudioThreadHangMonitor, base::OnTaskRunnerDeleter>;
+
+ // These values are histogrammed over time; do not change their ordinal
+ // values.
+ enum class ThreadStatus {
+ // kNone = 0, obsolete.
+ kStarted = 1,
+ kHung,
+ kRecovered,
+ kMaxValue = kRecovered
+ };
+
+ enum class HangAction {
+ // Do nothing. (UMA logging is always done.)
+ kDoNothing,
+ // A crash dump will be collected the first time the thread is detected as
+ // hung (note that no actual crashing is involved).
+ kDump,
+ // Terminate the current process with exit code 0.
+ kTerminateCurrentProcess,
+ // Terminate the current process with exit code 1, which yields a crash
+ // dump.
+ kDumpAndTerminateCurrentProcess
+ };
+
+ // |monitor_task_runner| may be set explicitly by tests only. Other callers
+ // should use the default. If |hang_deadline| is not provided, or if it's
+ // zero, a default value is used.
+ static Ptr Create(
+ HangAction hang_action,
+ absl::optional<base::TimeDelta> hang_deadline,
+ const base::TickClock* clock,
+ scoped_refptr<base::SingleThreadTaskRunner> audio_thread_task_runner,
+ scoped_refptr<base::SequencedTaskRunner> monitor_task_runner = nullptr);
+
+ AudioThreadHangMonitor(const AudioThreadHangMonitor&) = delete;
+ AudioThreadHangMonitor& operator=(const AudioThreadHangMonitor&) = delete;
+
+ ~AudioThreadHangMonitor();
+
+ // Thread-safe.
+ bool IsAudioThreadHung() const;
+
+ private:
+ friend class AudioThreadHangMonitorTest;
+
+ class SharedAtomicFlag final
+ : public base::RefCountedThreadSafe<SharedAtomicFlag> {
+ public:
+ SharedAtomicFlag();
+
+ std::atomic_bool flag_ = {false};
+
+ private:
+ friend class base::RefCountedThreadSafe<SharedAtomicFlag>;
+ ~SharedAtomicFlag();
+ };
+
+ AudioThreadHangMonitor(
+ HangAction hang_action,
+ absl::optional<base::TimeDelta> hang_deadline,
+ const base::TickClock* clock,
+ scoped_refptr<base::SingleThreadTaskRunner> audio_thread_task_runner);
+
+ void StartTimer();
+
+ bool NeverLoggedThreadHung() const;
+ bool NeverLoggedThreadRecoveredAfterHung() const;
+
+ // This function is run by the |timer_|. It checks if the audio thread has
+ // shown signs of life since the last time it was called (by checking the
+ // |alive_flag_|) and updates the value of |successful_pings_| and
+ // |failed_pings_| as appropriate. It also changes the thread status and logs
+ // its value to a histogram.
+ void CheckIfAudioThreadIsAlive();
+
+ // LogHistogramThreadStatus logs |thread_status_| to a histogram.
+ void LogHistogramThreadStatus();
+
+ // For tests. See below functions.
+ void SetHangActionCallbacksForTesting(
+ base::RepeatingClosure dump_callback,
+ base::RepeatingClosure terminate_process_callback);
+
+ // Thin wrapper functions that either executes the default or runs a callback
+ // set with SetHangActioncallbacksForTesting(), for testing purposes.
+ void DumpWithoutCrashing();
+ void TerminateCurrentProcess();
+
+ const base::TickClock* const clock_;
+
+ // This flag is set to false on the monitor sequence and then set to true on
+ // the audio thread to indicate that the audio thread is alive.
+ const scoped_refptr<SharedAtomicFlag> alive_flag_;
+
+ // |audio_task_runner_| is the task runner of the audio thread.
+ const scoped_refptr<base::SingleThreadTaskRunner> audio_task_runner_;
+
+ // Which action(s) to take when detected hung thread.
+ const HangAction hang_action_;
+
+ // At which interval to ping and see if the thread is running.
+ const base::TimeDelta ping_interval_;
+
+ // For testing. See DumpWithoutCrashing() and TerminateCurrentProcess().
+ base::RepeatingClosure dump_callback_;
+ base::RepeatingClosure terminate_process_callback_;
+
+ std::atomic<ThreadStatus> audio_thread_status_ = {ThreadStatus::kStarted};
+
+ // All fields below are accessed on |monitor_sequence|.
+ SEQUENCE_CHECKER(monitor_sequence_);
+
+ // Timer to check |alive_flag_| regularly.
+ base::RepeatingTimer timer_;
+
+ // This variable is used to check to detect suspend/resume cycles.
+ // If a long time has passed since the timer was last fired, it is likely due
+ // to the machine being suspended. In such a case, we want to avoid falsely
+ // detecting the audio thread as hung.
+ base::TimeTicks last_check_time_ = base::TimeTicks();
+
+ // |recent_ping_state_| tracks the recent life signs from the audio thread. If
+ // the most recent ping was successful, the number indicates the number of
+ // successive successful pings. If the most recent ping was failed, the number
+ // is the negative of the number of successive failed pings.
+ int recent_ping_state_ = 0;
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_THREAD_HANG_MONITOR_H_
diff --git a/third_party/chromium/media/audio/audio_thread_hang_monitor_unittest.cc b/third_party/chromium/media/audio/audio_thread_hang_monitor_unittest.cc
new file mode 100644
index 0000000..0412cfb
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_thread_hang_monitor_unittest.cc
@@ -0,0 +1,307 @@
+// Copyright 2018 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 "media/audio/audio_thread_hang_monitor.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/task/post_task.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/task_environment.h"
+#include "base/threading/thread.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+using testing::ElementsAre;
+using testing::Test;
+using HangAction = media::AudioThreadHangMonitor::HangAction;
+
+namespace media {
+
+namespace {
+
+constexpr int kStarted =
+ static_cast<int>(AudioThreadHangMonitor::ThreadStatus::kStarted);
+constexpr int kHung =
+ static_cast<int>(AudioThreadHangMonitor::ThreadStatus::kHung);
+constexpr int kRecovered =
+ static_cast<int>(AudioThreadHangMonitor::ThreadStatus::kRecovered);
+
+constexpr base::TimeDelta kShortHangDeadline = base::Seconds(5);
+constexpr base::TimeDelta kLongHangDeadline = base::Minutes(30);
+
+} // namespace
+
+class AudioThreadHangMonitorTest : public Test {
+ public:
+ AudioThreadHangMonitorTest()
+ : task_env_(base::test::TaskEnvironment::TimeSource::MOCK_TIME),
+ histograms_(),
+ audio_thread_("Audio thread"),
+ hang_monitor_({nullptr, base::OnTaskRunnerDeleter(nullptr)}) {
+ CHECK(audio_thread_.Start());
+ // We must inject the main thread task runner as the hang monitor task
+ // runner since TaskEnvironment::FastForwardBy only works for the main
+ // thread.
+ hang_monitor_ = AudioThreadHangMonitor::Create(
+ HangAction::kDoNothing, absl::nullopt, task_env_.GetMockTickClock(),
+ audio_thread_.task_runner(), task_env_.GetMainThreadTaskRunner());
+ }
+
+ ~AudioThreadHangMonitorTest() override {
+ hang_monitor_.reset();
+ task_env_.RunUntilIdle();
+ }
+
+ void SetHangActionCallbacksForTesting() {
+ hang_monitor_->SetHangActionCallbacksForTesting(
+ base::BindRepeating(&AudioThreadHangMonitorTest::HangActionDump,
+ base::Unretained(this)),
+ base::BindRepeating(&AudioThreadHangMonitorTest::HangActionTerminate,
+ base::Unretained(this)));
+ }
+
+ void RunUntilIdle() { task_env_.RunUntilIdle(); }
+
+ void FlushAudioThread() {
+ base::WaitableEvent ev;
+ audio_thread_.task_runner()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&base::WaitableEvent::Signal, base::Unretained(&ev)));
+ ev.Wait();
+ }
+
+ void BlockAudioThreadUntilEvent() {
+ // We keep |event_| as a member of the test fixture to make sure that the
+ // audio thread terminates before |event_| is destructed.
+ event_.Reset();
+ audio_thread_.task_runner()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&base::WaitableEvent::Wait, base::Unretained(&event_)));
+ }
+
+ MOCK_METHOD0(HangActionDump, void());
+ MOCK_METHOD0(HangActionTerminate, void());
+
+ base::WaitableEvent event_;
+ base::test::TaskEnvironment task_env_;
+ base::HistogramTester histograms_;
+ base::Thread audio_thread_;
+ AudioThreadHangMonitor::Ptr hang_monitor_;
+};
+
+TEST_F(AudioThreadHangMonitorTest, LogsThreadStarted) {
+ RunUntilIdle();
+
+ EXPECT_THAT(histograms_.GetAllSamples("Media.AudioThreadStatus"),
+ ElementsAre(base::Bucket(kStarted, 1)));
+}
+
+TEST_F(AudioThreadHangMonitorTest, DoesNotLogThreadHungWhenOk) {
+ RunUntilIdle();
+
+ for (int i = 0; i < 10; ++i) {
+ // Flush the audio thread, then advance the clock. The audio thread should
+ // register as "alive" every time.
+ FlushAudioThread();
+ task_env_.FastForwardBy(base::Minutes(1));
+ }
+
+ EXPECT_THAT(histograms_.GetAllSamples("Media.AudioThreadStatus"),
+ ElementsAre(base::Bucket(kStarted, 1)));
+}
+
+TEST_F(AudioThreadHangMonitorTest, LogsHungWhenAudioThreadIsBlocked) {
+ RunUntilIdle();
+
+ BlockAudioThreadUntilEvent();
+ task_env_.FastForwardBy(base::Minutes(10));
+ event_.Signal();
+
+ EXPECT_THAT(histograms_.GetAllSamples("Media.AudioThreadStatus"),
+ ElementsAre(base::Bucket(kStarted, 1), base::Bucket(kHung, 1)));
+}
+
+TEST_F(AudioThreadHangMonitorTest, DoesNotLogThreadHungWithShortDeadline) {
+ hang_monitor_ = AudioThreadHangMonitor::Create(
+ HangAction::kDoNothing, kShortHangDeadline, task_env_.GetMockTickClock(),
+ audio_thread_.task_runner(), task_env_.GetMainThreadTaskRunner());
+ RunUntilIdle();
+
+ BlockAudioThreadUntilEvent();
+ task_env_.FastForwardBy(kShortHangDeadline / 2);
+ event_.Signal();
+
+ // Two started events, one for the originally created hang monitor and one for
+ // the new created here.
+ EXPECT_THAT(histograms_.GetAllSamples("Media.AudioThreadStatus"),
+ ElementsAre(base::Bucket(kStarted, 2)));
+}
+
+TEST_F(AudioThreadHangMonitorTest, LogsThreadHungWithShortDeadline) {
+ hang_monitor_ = AudioThreadHangMonitor::Create(
+ HangAction::kDoNothing, kShortHangDeadline, task_env_.GetMockTickClock(),
+ audio_thread_.task_runner(), task_env_.GetMainThreadTaskRunner());
+ RunUntilIdle();
+
+ BlockAudioThreadUntilEvent();
+ task_env_.FastForwardBy(kShortHangDeadline * 2);
+ event_.Signal();
+
+ // Two started events, one for the originally created hang monitor and one for
+ // the new created here.
+ EXPECT_THAT(histograms_.GetAllSamples("Media.AudioThreadStatus"),
+ ElementsAre(base::Bucket(kStarted, 2), base::Bucket(kHung, 1)));
+}
+
+TEST_F(AudioThreadHangMonitorTest, DoesNotLogThreadHungWithLongDeadline) {
+ hang_monitor_ = AudioThreadHangMonitor::Create(
+ HangAction::kDoNothing, kLongHangDeadline, task_env_.GetMockTickClock(),
+ audio_thread_.task_runner(), task_env_.GetMainThreadTaskRunner());
+ RunUntilIdle();
+
+ BlockAudioThreadUntilEvent();
+ task_env_.FastForwardBy(kLongHangDeadline / 2);
+ event_.Signal();
+
+ // Two started events, one for the originally created hang monitor and one for
+ // the new created here.
+ EXPECT_THAT(histograms_.GetAllSamples("Media.AudioThreadStatus"),
+ ElementsAre(base::Bucket(kStarted, 2)));
+}
+
+TEST_F(AudioThreadHangMonitorTest, LogsThreadHungWithLongDeadline) {
+ hang_monitor_ = AudioThreadHangMonitor::Create(
+ HangAction::kDoNothing, kLongHangDeadline, task_env_.GetMockTickClock(),
+ audio_thread_.task_runner(), task_env_.GetMainThreadTaskRunner());
+ RunUntilIdle();
+
+ BlockAudioThreadUntilEvent();
+ task_env_.FastForwardBy(kLongHangDeadline * 2);
+ event_.Signal();
+
+ // Two started events, one for the originally created hang monitor and one for
+ // the new created here.
+ EXPECT_THAT(histograms_.GetAllSamples("Media.AudioThreadStatus"),
+ ElementsAre(base::Bucket(kStarted, 2), base::Bucket(kHung, 1)));
+}
+
+// Zero deadline means that the default deadline should be used.
+TEST_F(AudioThreadHangMonitorTest, ZeroDeadlineMeansDefaultDeadline) {
+ hang_monitor_ = AudioThreadHangMonitor::Create(
+ HangAction::kDoNothing, base::TimeDelta(), task_env_.GetMockTickClock(),
+ audio_thread_.task_runner(), task_env_.GetMainThreadTaskRunner());
+ RunUntilIdle();
+
+ for (int i = 0; i < 10; ++i) {
+ // Flush the audio thread, then advance the clock. The audio thread should
+ // register as "alive" every time.
+ FlushAudioThread();
+ task_env_.FastForwardBy(base::Minutes(1));
+ }
+
+ EXPECT_THAT(histograms_.GetAllSamples("Media.AudioThreadStatus"),
+ ElementsAre(base::Bucket(kStarted, 2)));
+
+ BlockAudioThreadUntilEvent();
+ task_env_.FastForwardBy(base::Minutes(10));
+ event_.Signal();
+
+ EXPECT_THAT(histograms_.GetAllSamples("Media.AudioThreadStatus"),
+ ElementsAre(base::Bucket(kStarted, 2), base::Bucket(kHung, 1)));
+}
+
+TEST_F(AudioThreadHangMonitorTest,
+ LogsRecoveredWhenAudioThreadIsBlockedThenRecovers) {
+ RunUntilIdle();
+
+ BlockAudioThreadUntilEvent();
+ task_env_.FastForwardBy(base::Minutes(10));
+ event_.Signal();
+
+ for (int i = 0; i < 10; ++i) {
+ // Flush the audio thread, then advance the clock. The audio thread should
+ // register as "alive" every time.
+ FlushAudioThread();
+ task_env_.FastForwardBy(base::Minutes(1));
+ }
+
+ EXPECT_THAT(histograms_.GetAllSamples("Media.AudioThreadStatus"),
+ ElementsAre(base::Bucket(kStarted, 1), base::Bucket(kHung, 1),
+ base::Bucket(kRecovered, 1)));
+}
+
+TEST_F(AudioThreadHangMonitorTest, NoHangActionWhenOk) {
+ SetHangActionCallbacksForTesting();
+ RunUntilIdle();
+
+ for (int i = 0; i < 10; ++i) {
+ // Flush the audio thread, then advance the clock. The audio thread should
+ // register as "alive" every time.
+ FlushAudioThread();
+ task_env_.FastForwardBy(base::Minutes(1));
+ }
+
+ EXPECT_THAT(histograms_.GetAllSamples("Media.AudioThreadStatus"),
+ ElementsAre(base::Bucket(kStarted, 1)));
+}
+
+TEST_F(AudioThreadHangMonitorTest, DumpsWhenAudioThreadIsBlocked) {
+ hang_monitor_ = AudioThreadHangMonitor::Create(
+ HangAction::kDump, absl::nullopt, task_env_.GetMockTickClock(),
+ audio_thread_.task_runner(), task_env_.GetMainThreadTaskRunner());
+ SetHangActionCallbacksForTesting();
+ RunUntilIdle();
+
+ EXPECT_CALL(*this, HangActionDump).Times(1);
+
+ BlockAudioThreadUntilEvent();
+ task_env_.FastForwardBy(base::Minutes(10));
+ event_.Signal();
+
+ EXPECT_THAT(histograms_.GetAllSamples("Media.AudioThreadStatus"),
+ ElementsAre(base::Bucket(kStarted, 2), base::Bucket(kHung, 1)));
+}
+
+TEST_F(AudioThreadHangMonitorTest, TerminatesProcessWhenAudioThreadIsBlocked) {
+ hang_monitor_ = AudioThreadHangMonitor::Create(
+ HangAction::kTerminateCurrentProcess, absl::nullopt,
+ task_env_.GetMockTickClock(), audio_thread_.task_runner(),
+ task_env_.GetMainThreadTaskRunner());
+ SetHangActionCallbacksForTesting();
+ RunUntilIdle();
+
+ EXPECT_CALL(*this, HangActionTerminate).Times(1);
+
+ BlockAudioThreadUntilEvent();
+ task_env_.FastForwardBy(base::Minutes(10));
+ event_.Signal();
+
+ EXPECT_THAT(histograms_.GetAllSamples("Media.AudioThreadStatus"),
+ ElementsAre(base::Bucket(kStarted, 2), base::Bucket(kHung, 1)));
+}
+
+TEST_F(AudioThreadHangMonitorTest,
+ DumpsAndTerminatesProcessWhenAudioThreadIsBlocked) {
+ hang_monitor_ = AudioThreadHangMonitor::Create(
+ HangAction::kDumpAndTerminateCurrentProcess, absl::nullopt,
+ task_env_.GetMockTickClock(), audio_thread_.task_runner(),
+ task_env_.GetMainThreadTaskRunner());
+ SetHangActionCallbacksForTesting();
+ RunUntilIdle();
+
+ EXPECT_CALL(*this, HangActionDump).Times(1);
+ EXPECT_CALL(*this, HangActionTerminate).Times(1);
+
+ BlockAudioThreadUntilEvent();
+ task_env_.FastForwardBy(base::Minutes(10));
+ event_.Signal();
+
+ EXPECT_THAT(histograms_.GetAllSamples("Media.AudioThreadStatus"),
+ ElementsAre(base::Bucket(kStarted, 2), base::Bucket(kHung, 1)));
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_thread_impl.cc b/third_party/chromium/media/audio/audio_thread_impl.cc
new file mode 100644
index 0000000..f1cd692
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_thread_impl.cc
@@ -0,0 +1,76 @@
+// Copyright 2016 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 "media/audio/audio_thread_impl.h"
+
+#include "base/message_loop/message_pump_type.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/default_tick_clock.h"
+#include "build/build_config.h"
+#include "media/audio/audio_thread_hang_monitor.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace media {
+
+AudioThreadImpl::AudioThreadImpl()
+ : thread_("AudioThread"),
+ hang_monitor_(nullptr, base::OnTaskRunnerDeleter(nullptr)) {
+ base::Thread::Options thread_options;
+#if defined(OS_WIN)
+ thread_.init_com_with_mta(true);
+#elif defined(OS_FUCHSIA)
+ // FIDL-based APIs require async_t, which is initialized on IO thread.
+ thread_options.message_pump_type = base::MessagePumpType::IO;
+ thread_options.priority = base::ThreadPriority::REALTIME_AUDIO;
+#endif
+ CHECK(thread_.StartWithOptions(std::move(thread_options)));
+
+#if defined(OS_MAC)
+ // On Mac, the audio task runner must belong to the main thread.
+ // See http://crbug.com/158170.
+ task_runner_ = base::ThreadTaskRunnerHandle::Get();
+#else
+ task_runner_ = thread_.task_runner();
+#endif
+ worker_task_runner_ = thread_.task_runner();
+
+#if !defined(OS_MAC) && !defined(OS_ANDROID)
+ // Since we run on the main thread on Mac, we don't need a hang monitor.
+ // https://crbug.com/946968: The hang monitor possibly causes crashes on
+ // Android
+ hang_monitor_ = AudioThreadHangMonitor::Create(
+ AudioThreadHangMonitor::HangAction::kDoNothing, absl::nullopt,
+ base::DefaultTickClock::GetInstance(), task_runner_);
+#endif
+}
+
+AudioThreadImpl::~AudioThreadImpl() {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+}
+
+void AudioThreadImpl::Stop() {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+ hang_monitor_.reset();
+
+ // Note that on MACOSX, we can still have tasks posted on the |task_runner_|,
+ // since it is the main thread task runner and we do not stop the main thread.
+ // But this is fine because none of those tasks will actually run.
+ thread_.Stop();
+}
+
+bool AudioThreadImpl::IsHung() const {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ return hang_monitor_ ? hang_monitor_->IsAudioThreadHung() : false;
+}
+
+base::SingleThreadTaskRunner* AudioThreadImpl::GetTaskRunner() {
+ return task_runner_.get();
+}
+
+base::SingleThreadTaskRunner* AudioThreadImpl::GetWorkerTaskRunner() {
+ return worker_task_runner_.get();
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_thread_impl.h b/third_party/chromium/media/audio/audio_thread_impl.h
new file mode 100644
index 0000000..a63a40f
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_thread_impl.h
@@ -0,0 +1,44 @@
+// Copyright 2016 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 MEDIA_AUDIO_AUDIO_THREAD_IMPL_H_
+#define MEDIA_AUDIO_AUDIO_THREAD_IMPL_H_
+
+#include "base/sequenced_task_runner.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_checker.h"
+#include "media/audio/audio_thread.h"
+#include "media/audio/audio_thread_hang_monitor.h"
+
+namespace media {
+
+class MEDIA_EXPORT AudioThreadImpl final : public AudioThread {
+ public:
+ AudioThreadImpl();
+
+ AudioThreadImpl(const AudioThreadImpl&) = delete;
+ AudioThreadImpl& operator=(const AudioThreadImpl&) = delete;
+
+ ~AudioThreadImpl() final;
+
+ // AudioThread implementation.
+ void Stop() final;
+ bool IsHung() const final;
+ base::SingleThreadTaskRunner* GetTaskRunner() final;
+ base::SingleThreadTaskRunner* GetWorkerTaskRunner() final;
+
+ private:
+ base::Thread thread_;
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+ scoped_refptr<base::SingleThreadTaskRunner> worker_task_runner_;
+
+ // Null on Mac OS, initialized in the constructor on other platforms.
+ AudioThreadHangMonitor::Ptr hang_monitor_;
+
+ THREAD_CHECKER(thread_checker_);
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_THREAD_IMPL_H_
diff --git a/third_party/chromium/media/audio/audio_unittest_util.cc b/third_party/chromium/media/audio/audio_unittest_util.cc
new file mode 100644
index 0000000..0ddf94a
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_unittest_util.cc
@@ -0,0 +1,29 @@
+// Copyright 2015 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 "media/audio/audio_unittest_util.h"
+
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "media/base/media_switches.h"
+
+namespace media {
+
+// For macro ABORT_AUDIO_TEST_IF_NOT.
+bool ShouldAbortAudioTest(bool requirements_satisfied,
+ const char* requirements_expression,
+ bool* should_fail) {
+ bool fail_if_unsatisfied = base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kRequireAudioHardwareForTesting);
+ if (!requirements_satisfied) {
+ LOG(WARNING) << "Requirement(s) not satisfied (" << requirements_expression
+ << ")";
+ *should_fail = fail_if_unsatisfied;
+ return true;
+ }
+ *should_fail = false;
+ return false;
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/audio_unittest_util.h b/third_party/chromium/media/audio/audio_unittest_util.h
new file mode 100644
index 0000000..3bd0dfa
--- /dev/null
+++ b/third_party/chromium/media/audio/audio_unittest_util.h
@@ -0,0 +1,34 @@
+// Copyright 2015 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 MEDIA_AUDIO_AUDIO_UNITTEST_UTIL_H_
+#define MEDIA_AUDIO_AUDIO_UNITTEST_UTIL_H_
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+
+// Use in tests to either skip or fail a test when the system is missing a
+// required audio device or library. If the --require-audio-hardware-for-testing
+// flag is set, missing requirements will cause the test to fail. Otherwise it
+// will be skipped.
+#define ABORT_AUDIO_TEST_IF_NOT(requirements_satisfied) \
+ do { \
+ bool fail = false; \
+ if (ShouldAbortAudioTest(requirements_satisfied, #requirements_satisfied, \
+ &fail)) { \
+ if (fail) \
+ FAIL(); \
+ else \
+ return; \
+ } \
+ } while (false)
+
+bool ShouldAbortAudioTest(bool requirements_satisfied,
+ const char* requirements_expression,
+ bool* should_fail);
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_AUDIO_UNITTEST_UTIL_H_
diff --git a/third_party/chromium/media/audio/clockless_audio_sink.cc b/third_party/chromium/media/audio/clockless_audio_sink.cc
new file mode 100644
index 0000000..1de786b
--- /dev/null
+++ b/third_party/chromium/media/audio/clockless_audio_sink.cc
@@ -0,0 +1,173 @@
+// Copyright 2013 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 "media/audio/clockless_audio_sink.h"
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/single_thread_task_runner.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/threading/simple_thread.h"
+#include "media/base/audio_hash.h"
+
+namespace media {
+
+// Internal to ClocklessAudioSink. Class is used to call Render() on a seperate
+// thread, running as fast as it can read the data.
+class ClocklessAudioSinkThread : public base::DelegateSimpleThread::Delegate {
+ public:
+ ClocklessAudioSinkThread(const AudioParameters& params,
+ AudioRendererSink::RenderCallback* callback,
+ bool hashing)
+ : callback_(callback),
+ audio_bus_(AudioBus::Create(params)),
+ stop_event_(new base::WaitableEvent(
+ base::WaitableEvent::ResetPolicy::AUTOMATIC,
+ base::WaitableEvent::InitialState::NOT_SIGNALED)) {
+ if (hashing)
+ audio_hash_ = std::make_unique<AudioHash>();
+ }
+
+ void Start() {
+ stop_event_->Reset();
+ thread_ = std::make_unique<base::DelegateSimpleThread>(
+ this, "ClocklessAudioSink");
+ thread_->Start();
+ }
+
+ // Generate a signal to stop calling Render().
+ base::TimeDelta Stop() {
+ stop_event_->Signal();
+ thread_->Join();
+ return playback_time_;
+ }
+
+ std::string GetAudioHash() {
+ DCHECK(audio_hash_);
+ return audio_hash_->ToString();
+ }
+
+ private:
+ // Call Render() repeatedly, keeping track of the rendering time.
+ void Run() override {
+ base::TimeTicks start;
+ while (!stop_event_->IsSignaled()) {
+ const int frames_received = callback_->Render(
+ base::TimeDelta(), base::TimeTicks::Now(), 0, audio_bus_.get());
+ DCHECK_GE(frames_received, 0);
+ if (audio_hash_)
+ audio_hash_->Update(audio_bus_.get(), frames_received);
+ if (!frames_received) {
+ // No data received, so let other threads run to provide data.
+ base::PlatformThread::YieldCurrentThread();
+ } else if (start.is_null()) {
+ // First time we processed some audio, so record the starting time.
+ start = base::TimeTicks::Now();
+ } else {
+ // Keep track of the last time data was rendered.
+ playback_time_ = base::TimeTicks::Now() - start;
+ }
+ }
+ }
+
+ AudioRendererSink::RenderCallback* callback_;
+ std::unique_ptr<AudioBus> audio_bus_;
+ std::unique_ptr<base::WaitableEvent> stop_event_;
+ std::unique_ptr<base::DelegateSimpleThread> thread_;
+ base::TimeDelta playback_time_;
+ std::unique_ptr<AudioHash> audio_hash_;
+};
+
+ClocklessAudioSink::ClocklessAudioSink()
+ : ClocklessAudioSink(OutputDeviceInfo()) {}
+
+ClocklessAudioSink::ClocklessAudioSink(const OutputDeviceInfo& device_info)
+ : device_info_(device_info),
+ initialized_(false),
+ playing_(false),
+ hashing_(false),
+ is_optimized_for_hw_params_(true) {}
+
+ClocklessAudioSink::~ClocklessAudioSink() = default;
+
+void ClocklessAudioSink::Initialize(const AudioParameters& params,
+ RenderCallback* callback) {
+ DCHECK(!initialized_);
+ thread_ =
+ std::make_unique<ClocklessAudioSinkThread>(params, callback, hashing_);
+ initialized_ = true;
+}
+
+void ClocklessAudioSink::Start() {
+ DCHECK(initialized_);
+ DCHECK(!playing_);
+}
+
+void ClocklessAudioSink::Stop() {
+ if (initialized_)
+ Pause();
+}
+
+void ClocklessAudioSink::Flush() {}
+
+void ClocklessAudioSink::Play() {
+ DCHECK(initialized_);
+
+ if (playing_)
+ return;
+
+ playing_ = true;
+ thread_->Start();
+}
+
+void ClocklessAudioSink::Pause() {
+ DCHECK(initialized_);
+
+ if (!playing_)
+ return;
+
+ playing_ = false;
+ playback_time_ = thread_->Stop();
+}
+
+bool ClocklessAudioSink::SetVolume(double volume) {
+ // Audio is always muted.
+ return volume == 0.0;
+}
+
+OutputDeviceInfo ClocklessAudioSink::GetOutputDeviceInfo() {
+ return device_info_;
+}
+
+void ClocklessAudioSink::GetOutputDeviceInfoAsync(OutputDeviceInfoCB info_cb) {
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(info_cb), device_info_));
+}
+
+bool ClocklessAudioSink::IsOptimizedForHardwareParameters() {
+ return is_optimized_for_hw_params_;
+}
+
+bool ClocklessAudioSink::CurrentThreadIsRenderingThread() {
+ NOTIMPLEMENTED();
+ return false;
+}
+
+void ClocklessAudioSink::StartAudioHashForTesting() {
+ DCHECK(!initialized_);
+ hashing_ = true;
+}
+
+std::string ClocklessAudioSink::GetAudioHashForTesting() {
+ return thread_ && hashing_ ? thread_->GetAudioHash() : std::string();
+}
+
+void ClocklessAudioSink::SetIsOptimizedForHardwareParametersForTesting(
+ bool value) {
+ is_optimized_for_hw_params_ = value;
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/clockless_audio_sink.h b/third_party/chromium/media/audio/clockless_audio_sink.h
new file mode 100644
index 0000000..cf6d6a6
--- /dev/null
+++ b/third_party/chromium/media/audio/clockless_audio_sink.h
@@ -0,0 +1,69 @@
+// Copyright 2013 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 MEDIA_AUDIO_CLOCKLESS_AUDIO_SINK_H_
+#define MEDIA_AUDIO_CLOCKLESS_AUDIO_SINK_H_
+
+#include <memory>
+#include <string>
+
+#include "base/macros.h"
+#include "base/time/time.h"
+#include "media/base/audio_renderer_sink.h"
+
+namespace media {
+class ClocklessAudioSinkThread;
+
+// Implementation of an AudioRendererSink that consumes the audio as fast as
+// possible. This class does not support multiple Play()/Pause() events.
+class MEDIA_EXPORT ClocklessAudioSink : public AudioRendererSink {
+ public:
+ ClocklessAudioSink();
+ explicit ClocklessAudioSink(const OutputDeviceInfo& device_info);
+
+ // AudioRendererSink implementation.
+ void Initialize(const AudioParameters& params,
+ RenderCallback* callback) override;
+ void Start() override;
+ void Stop() override;
+ void Flush() override;
+ void Pause() override;
+ void Play() override;
+ bool SetVolume(double volume) override;
+ OutputDeviceInfo GetOutputDeviceInfo() override;
+ void GetOutputDeviceInfoAsync(OutputDeviceInfoCB info_cb) override;
+ bool IsOptimizedForHardwareParameters() override;
+ bool CurrentThreadIsRenderingThread() override;
+
+ // Returns the time taken to consume all the audio.
+ base::TimeDelta render_time() { return playback_time_; }
+
+ // Enables audio frame hashing. Must be called prior to Initialize().
+ void StartAudioHashForTesting();
+
+ // Returns the hash of all audio frames seen since construction.
+ std::string GetAudioHashForTesting();
+
+ void SetIsOptimizedForHardwareParametersForTesting(bool value);
+
+ protected:
+ ~ClocklessAudioSink() override;
+
+ private:
+ const OutputDeviceInfo device_info_;
+ std::unique_ptr<ClocklessAudioSinkThread> thread_;
+ bool initialized_;
+ bool playing_;
+ bool hashing_;
+ bool is_optimized_for_hw_params_;
+
+ // Time taken in last set of Render() calls.
+ base::TimeDelta playback_time_;
+
+ DISALLOW_COPY_AND_ASSIGN(ClocklessAudioSink);
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_CLOCKLESS_AUDIO_SINK_H_
diff --git a/third_party/chromium/media/audio/cras/audio_manager_chromeos.cc b/third_party/chromium/media/audio/cras/audio_manager_chromeos.cc
new file mode 100644
index 0000000..e18f874
--- /dev/null
+++ b/third_party/chromium/media/audio/cras/audio_manager_chromeos.cc
@@ -0,0 +1,605 @@
+// Copyright 2020 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 "media/audio/cras/audio_manager_chromeos.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <map>
+#include <utility>
+
+#include "ash/components/audio/audio_device.h"
+#include "ash/components/audio/cras_audio_handler.h"
+#include "base/bind.h"
+#include "base/check_op.h"
+#include "base/command_line.h"
+#include "base/cxx17_backports.h"
+#include "base/environment.h"
+#include "base/metrics/field_trial_params.h"
+#include "base/nix/xdg_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/system/sys_info.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "media/audio/audio_device_description.h"
+#include "media/audio/audio_features.h"
+#include "media/audio/cras/cras_input.h"
+#include "media/audio/cras/cras_unified.h"
+#include "media/base/channel_layout.h"
+#include "media/base/limits.h"
+#include "media/base/localized_strings.h"
+
+namespace media {
+namespace {
+
+using ::ash::AudioDevice;
+using ::ash::AudioDeviceList;
+using ::ash::CrasAudioHandler;
+
+// Default sample rate for input and output streams.
+const int kDefaultSampleRate = 48000;
+
+// Default input buffer size.
+const int kDefaultInputBufferSize = 1024;
+
+const char kInternalInputVirtualDevice[] = "Built-in mic";
+const char kInternalOutputVirtualDevice[] = "Built-in speaker";
+const char kHeadphoneLineOutVirtualDevice[] = "Headphone/Line Out";
+
+// Used for the Media.CrosBeamformingDeviceState histogram, currently not used
+// since beamforming is disabled.
+enum CrosBeamformingDeviceState {
+ BEAMFORMING_DEFAULT_ENABLED = 0,
+ BEAMFORMING_USER_ENABLED,
+ BEAMFORMING_DEFAULT_DISABLED,
+ BEAMFORMING_USER_DISABLED,
+ BEAMFORMING_STATE_MAX = BEAMFORMING_USER_DISABLED
+};
+
+bool HasKeyboardMic(const AudioDeviceList& devices) {
+ for (const auto& device : devices) {
+ if (device.is_input &&
+ device.type == chromeos::AudioDeviceType::kKeyboardMic) {
+ return true;
+ }
+ }
+ return false;
+}
+
+const AudioDevice* GetDeviceFromId(const AudioDeviceList& devices,
+ uint64_t id) {
+ for (const auto& device : devices) {
+ if (device.id == id) {
+ return &device;
+ }
+ }
+ return nullptr;
+}
+
+// Process |device_list| that two shares the same dev_index by creating a
+// virtual device name for them.
+void ProcessVirtualDeviceName(AudioDeviceNames* device_names,
+ const AudioDeviceList& device_list) {
+ DCHECK_EQ(2U, device_list.size());
+ if (device_list[0].type == chromeos::AudioDeviceType::kLineout ||
+ device_list[1].type == chromeos::AudioDeviceType::kLineout) {
+ device_names->emplace_back(kHeadphoneLineOutVirtualDevice,
+ base::NumberToString(device_list[0].id));
+ } else if (device_list[0].type ==
+ chromeos::AudioDeviceType::kInternalSpeaker ||
+ device_list[1].type ==
+ chromeos::AudioDeviceType::kInternalSpeaker) {
+ device_names->emplace_back(kInternalOutputVirtualDevice,
+ base::NumberToString(device_list[0].id));
+ } else {
+ DCHECK(device_list[0].IsInternalMic() || device_list[1].IsInternalMic());
+ device_names->emplace_back(kInternalInputVirtualDevice,
+ base::NumberToString(device_list[0].id));
+ }
+}
+
+// Collects flags values for whether, and in what way, the AEC, NS or AGC
+// effects should be enforced in spite of them not being flagged as supported by
+// the board.
+void RetrieveSystemEffectFeatures(bool& enforce_system_aec,
+ bool& enforce_system_ns,
+ bool& enforce_system_agc,
+ bool& tuned_system_aec_allowed) {
+ const bool enforce_system_aec_ns_agc_feature =
+ base::FeatureList::IsEnabled(features::kCrOSEnforceSystemAecNsAgc);
+ const bool enforce_system_aec_ns_feature =
+ base::FeatureList::IsEnabled(features::kCrOSEnforceSystemAecNs);
+ const bool enforce_system_aec_agc_feature =
+ base::FeatureList::IsEnabled(features::kCrOSEnforceSystemAecAgc);
+ const bool enforce_system_aec_feature =
+ base::FeatureList::IsEnabled(features::kCrOSEnforceSystemAec);
+
+ enforce_system_aec =
+ enforce_system_aec_feature || enforce_system_aec_ns_agc_feature ||
+ enforce_system_aec_ns_feature || enforce_system_aec_agc_feature;
+ enforce_system_ns =
+ enforce_system_aec_ns_agc_feature || enforce_system_aec_ns_feature;
+ enforce_system_agc =
+ enforce_system_aec_ns_agc_feature || enforce_system_aec_agc_feature;
+
+ tuned_system_aec_allowed =
+ base::FeatureList::IsEnabled(features::kCrOSSystemAEC);
+}
+
+// Checks if a system AEC with a specific group ID is flagged to be deactivated
+// by the field trial.
+bool IsSystemAecDeactivated(int aec_group_id) {
+ return base::GetFieldTrialParamByFeatureAsBool(
+ features::kCrOSSystemAECDeactivatedGroups, std::to_string(aec_group_id),
+ false);
+}
+
+} // namespace
+
+bool AudioManagerChromeOS::HasAudioOutputDevices() {
+ return true;
+}
+
+bool AudioManagerChromeOS::HasAudioInputDevices() {
+ AudioDeviceList devices;
+ GetAudioDevices(&devices);
+ for (size_t i = 0; i < devices.size(); ++i) {
+ if (devices[i].is_input && devices[i].is_for_simple_usage())
+ return true;
+ }
+ return false;
+}
+
+AudioManagerChromeOS::AudioManagerChromeOS(
+ std::unique_ptr<AudioThread> audio_thread,
+ AudioLogFactory* audio_log_factory)
+ : AudioManagerCrasBase(std::move(audio_thread), audio_log_factory),
+ on_shutdown_(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED),
+ main_task_runner_(base::ThreadTaskRunnerHandle::Get()),
+ weak_ptr_factory_(this) {
+ weak_this_ = weak_ptr_factory_.GetWeakPtr();
+}
+
+AudioManagerChromeOS::~AudioManagerChromeOS() = default;
+
+void AudioManagerChromeOS::GetAudioDeviceNamesImpl(
+ bool is_input,
+ AudioDeviceNames* device_names) {
+ DCHECK(device_names->empty());
+
+ device_names->push_back(AudioDeviceName::CreateDefault());
+
+ AudioDeviceList devices;
+ GetAudioDevices(&devices);
+
+ // |dev_idx_map| is a map of dev_index and their audio devices.
+ std::map<int, AudioDeviceList> dev_idx_map;
+ for (const auto& device : devices) {
+ if (device.is_input != is_input || !device.is_for_simple_usage())
+ continue;
+
+ dev_idx_map[dev_index_of(device.id)].push_back(device);
+ }
+
+ for (const auto& item : dev_idx_map) {
+ if (1 == item.second.size()) {
+ const AudioDevice& device = item.second.front();
+ device_names->emplace_back(device.display_name,
+ base::NumberToString(device.id));
+ } else {
+ // Create virtual device name for audio nodes that share the same device
+ // index.
+ ProcessVirtualDeviceName(device_names, item.second);
+ }
+ }
+}
+
+void AudioManagerChromeOS::GetAudioInputDeviceNames(
+ AudioDeviceNames* device_names) {
+ GetAudioDeviceNamesImpl(true, device_names);
+}
+
+void AudioManagerChromeOS::GetAudioOutputDeviceNames(
+ AudioDeviceNames* device_names) {
+ GetAudioDeviceNamesImpl(false, device_names);
+}
+
+AudioParameters AudioManagerChromeOS::GetInputStreamParameters(
+ const std::string& device_id) {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+
+ // Check if the device has keyboard.
+ AudioDeviceList devices;
+ GetAudioDevices(&devices);
+ const bool has_keyboard = HasKeyboardMic(devices);
+
+ // Retrieve buffer size.
+ int user_buffer_size = GetUserBufferSize();
+ user_buffer_size =
+ user_buffer_size != 0 ? user_buffer_size : kDefaultInputBufferSize;
+
+ // Retrieve the board support in terms of APM effects and properties.
+ const SystemAudioProcessingInfo system_apm_info =
+ GetSystemApmEffectsSupportedPerBoard();
+
+ // TODO(hshi): Fine-tune audio parameters based on |device_id|. The optimal
+ // parameters for the loopback stream may differ from the default.
+ return GetStreamParametersForSystem(user_buffer_size, has_keyboard,
+ system_apm_info);
+}
+
+std::string AudioManagerChromeOS::GetAssociatedOutputDeviceID(
+ const std::string& input_device_id) {
+ AudioDeviceList devices;
+ GetAudioDevices(&devices);
+
+ if (input_device_id == AudioDeviceDescription::kDefaultDeviceId) {
+ // Note: the default input should not be associated to any output, as this
+ // may lead to accidental uses of a pinned stream.
+ return "";
+ }
+
+ const std::string device_name =
+ GetHardwareDeviceFromDeviceId(devices, true, input_device_id);
+
+ if (device_name.empty())
+ return "";
+
+ // Now search for an output device with the same device name.
+ auto output_device_it = std::find_if(
+ devices.begin(), devices.end(), [device_name](const AudioDevice& device) {
+ return !device.is_input && device.device_name == device_name;
+ });
+ return output_device_it == devices.end()
+ ? ""
+ : base::NumberToString(output_device_it->id);
+}
+
+std::string AudioManagerChromeOS::GetDefaultInputDeviceID() {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ return base::NumberToString(GetPrimaryActiveInputNode());
+}
+
+std::string AudioManagerChromeOS::GetDefaultOutputDeviceID() {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ return base::NumberToString(GetPrimaryActiveOutputNode());
+}
+
+std::string AudioManagerChromeOS::GetGroupIDOutput(
+ const std::string& output_device_id) {
+ AudioDeviceList devices;
+ GetAudioDevices(&devices);
+
+ return GetHardwareDeviceFromDeviceId(devices, false, output_device_id);
+}
+
+std::string AudioManagerChromeOS::GetGroupIDInput(
+ const std::string& input_device_id) {
+ AudioDeviceList devices;
+ GetAudioDevices(&devices);
+
+ return GetHardwareDeviceFromDeviceId(devices, true, input_device_id);
+}
+
+bool AudioManagerChromeOS::Shutdown() {
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+ weak_ptr_factory_.InvalidateWeakPtrs();
+ on_shutdown_.Signal();
+ return AudioManager::Shutdown();
+}
+
+int AudioManagerChromeOS::GetDefaultOutputBufferSizePerBoard() {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ int32_t buffer_size = 512;
+ base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ if (main_task_runner_->BelongsToCurrentThread()) {
+ // Unittest may use the same thread for audio thread.
+ GetDefaultOutputBufferSizeOnMainThread(&buffer_size, &event);
+ } else {
+ main_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ &AudioManagerChromeOS::GetDefaultOutputBufferSizeOnMainThread,
+ weak_this_, base::Unretained(&buffer_size),
+ base::Unretained(&event)));
+ }
+ WaitEventOrShutdown(&event);
+ return static_cast<int>(buffer_size);
+}
+
+AudioManagerChromeOS::SystemAudioProcessingInfo
+AudioManagerChromeOS::GetSystemApmEffectsSupportedPerBoard() {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+
+ SystemAudioProcessingInfo system_apm_info;
+ if (main_task_runner_->BelongsToCurrentThread()) {
+ // Unittest may use the same thread for audio thread.
+ GetSystemApmEffectsSupportedOnMainThread(&system_apm_info, &event);
+ } else {
+ // Using base::Unretained is safe here because we wait for callback be
+ // executed in main thread before local variables are destructed.
+ main_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ &AudioManagerChromeOS::GetSystemApmEffectsSupportedOnMainThread,
+ weak_this_, base::Unretained(&system_apm_info),
+ base::Unretained(&event)));
+ }
+ WaitEventOrShutdown(&event);
+ return system_apm_info;
+}
+
+AudioParameters AudioManagerChromeOS::GetPreferredOutputStreamParameters(
+ const std::string& output_device_id,
+ const AudioParameters& input_params) {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+
+ ChannelLayout channel_layout = CHANNEL_LAYOUT_STEREO;
+ int sample_rate = kDefaultSampleRate;
+ int buffer_size = GetUserBufferSize();
+ if (input_params.IsValid()) {
+ channel_layout = input_params.channel_layout();
+ sample_rate = input_params.sample_rate();
+ if (!buffer_size) // Not user-provided.
+ buffer_size =
+ std::min(static_cast<int>(limits::kMaxAudioBufferSize),
+ std::max(static_cast<int>(limits::kMinAudioBufferSize),
+ input_params.frames_per_buffer()));
+ return AudioParameters(
+ AudioParameters::AUDIO_PCM_LOW_LATENCY, channel_layout, sample_rate,
+ buffer_size,
+ AudioParameters::HardwareCapabilities(limits::kMinAudioBufferSize,
+ limits::kMaxAudioBufferSize));
+ }
+
+ // Get max supported channels from |output_device_id| or the primary active
+ // one if |output_device_id| is the default device.
+ uint64_t preferred_device_id;
+ if (AudioDeviceDescription::IsDefaultDevice(output_device_id)) {
+ preferred_device_id = GetPrimaryActiveOutputNode();
+ } else {
+ if (!base::StringToUint64(output_device_id, &preferred_device_id))
+ preferred_device_id = 0; // 0 represents invalid |output_device_id|.
+ }
+
+ if (preferred_device_id) {
+ AudioDeviceList devices;
+ GetAudioDevices(&devices);
+ const AudioDevice* device = GetDeviceFromId(devices, preferred_device_id);
+ if (device && device->is_input == false) {
+ channel_layout =
+ GuessChannelLayout(static_cast<int>(device->max_supported_channels));
+ // Fall-back to old fashion: always fixed to STEREO layout.
+ if (channel_layout == CHANNEL_LAYOUT_UNSUPPORTED) {
+ channel_layout = CHANNEL_LAYOUT_STEREO;
+ }
+ }
+ }
+
+ if (!buffer_size) // Not user-provided.
+ buffer_size = GetDefaultOutputBufferSizePerBoard();
+
+ return AudioParameters(
+ AudioParameters::AUDIO_PCM_LOW_LATENCY, channel_layout, sample_rate,
+ buffer_size,
+ AudioParameters::HardwareCapabilities(limits::kMinAudioBufferSize,
+ limits::kMaxAudioBufferSize));
+}
+
+bool AudioManagerChromeOS::IsDefault(const std::string& device_id,
+ bool is_input) {
+ AudioDeviceNames device_names;
+ GetAudioDeviceNamesImpl(is_input, &device_names);
+ DCHECK(!device_names.empty());
+ const AudioDeviceName& device_name = device_names.front();
+ return device_name.unique_id == device_id;
+}
+
+std::string AudioManagerChromeOS::GetHardwareDeviceFromDeviceId(
+ const AudioDeviceList& devices,
+ bool is_input,
+ const std::string& device_id) {
+ uint64_t u64_device_id = 0;
+ if (AudioDeviceDescription::IsDefaultDevice(device_id)) {
+ u64_device_id =
+ is_input ? GetPrimaryActiveInputNode() : GetPrimaryActiveOutputNode();
+ } else {
+ if (!base::StringToUint64(device_id, &u64_device_id))
+ return "";
+ }
+
+ const AudioDevice* device = GetDeviceFromId(devices, u64_device_id);
+
+ return device ? device->device_name : "";
+}
+
+void AudioManagerChromeOS::GetAudioDevices(AudioDeviceList* devices) {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ if (main_task_runner_->BelongsToCurrentThread()) {
+ GetAudioDevicesOnMainThread(devices, &event);
+ } else {
+ main_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&AudioManagerChromeOS::GetAudioDevicesOnMainThread,
+ weak_this_, base::Unretained(devices),
+ base::Unretained(&event)));
+ }
+ WaitEventOrShutdown(&event);
+}
+
+void AudioManagerChromeOS::GetAudioDevicesOnMainThread(
+ AudioDeviceList* devices,
+ base::WaitableEvent* event) {
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+ // CrasAudioHandler is shut down before AudioManagerChromeOS.
+ if (CrasAudioHandler::Get())
+ CrasAudioHandler::Get()->GetAudioDevices(devices);
+ event->Signal();
+}
+
+uint64_t AudioManagerChromeOS::GetPrimaryActiveInputNode() {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ uint64_t device_id = 0;
+ base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ if (main_task_runner_->BelongsToCurrentThread()) {
+ GetPrimaryActiveInputNodeOnMainThread(&device_id, &event);
+ } else {
+ main_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ &AudioManagerChromeOS::GetPrimaryActiveInputNodeOnMainThread,
+ weak_this_, &device_id, &event));
+ }
+ WaitEventOrShutdown(&event);
+ return device_id;
+}
+
+uint64_t AudioManagerChromeOS::GetPrimaryActiveOutputNode() {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ uint64_t device_id = 0;
+ if (main_task_runner_->BelongsToCurrentThread()) {
+ // Unittest may use the same thread for audio thread.
+ GetPrimaryActiveOutputNodeOnMainThread(&device_id, &event);
+ } else {
+ main_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ &AudioManagerChromeOS::GetPrimaryActiveOutputNodeOnMainThread,
+ weak_this_, base::Unretained(&device_id),
+ base::Unretained(&event)));
+ }
+ WaitEventOrShutdown(&event);
+ return device_id;
+}
+
+void AudioManagerChromeOS::GetPrimaryActiveInputNodeOnMainThread(
+ uint64_t* active_input_node_id,
+ base::WaitableEvent* event) {
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+ if (CrasAudioHandler::Get()) {
+ *active_input_node_id =
+ CrasAudioHandler::Get()->GetPrimaryActiveInputNode();
+ }
+ event->Signal();
+}
+
+void AudioManagerChromeOS::GetPrimaryActiveOutputNodeOnMainThread(
+ uint64_t* active_output_node_id,
+ base::WaitableEvent* event) {
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+ if (CrasAudioHandler::Get()) {
+ *active_output_node_id =
+ CrasAudioHandler::Get()->GetPrimaryActiveOutputNode();
+ }
+ event->Signal();
+}
+
+void AudioManagerChromeOS::GetDefaultOutputBufferSizeOnMainThread(
+ int32_t* buffer_size,
+ base::WaitableEvent* event) {
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+ if (CrasAudioHandler::Get())
+ CrasAudioHandler::Get()->GetDefaultOutputBufferSize(buffer_size);
+ event->Signal();
+}
+
+void AudioManagerChromeOS::GetSystemApmEffectsSupportedOnMainThread(
+ SystemAudioProcessingInfo* system_apm_info,
+ base::WaitableEvent* event) {
+ DCHECK(main_task_runner_->BelongsToCurrentThread());
+ if (CrasAudioHandler::Get()) {
+ system_apm_info->aec_supported =
+ CrasAudioHandler::Get()->system_aec_supported();
+ system_apm_info->aec_group_id =
+ CrasAudioHandler::Get()->system_aec_group_id();
+ system_apm_info->ns_supported =
+ CrasAudioHandler::Get()->system_ns_supported();
+ system_apm_info->agc_supported =
+ CrasAudioHandler::Get()->system_agc_supported();
+ }
+ event->Signal();
+}
+
+void AudioManagerChromeOS::WaitEventOrShutdown(base::WaitableEvent* event) {
+ base::WaitableEvent* waitables[] = {event, &on_shutdown_};
+ base::WaitableEvent::WaitMany(waitables, base::size(waitables));
+}
+
+enum CRAS_CLIENT_TYPE AudioManagerChromeOS::GetClientType() {
+ return CRAS_CLIENT_TYPE_CHROME;
+}
+
+AudioParameters AudioManagerChromeOS::GetStreamParametersForSystem(
+ int user_buffer_size,
+ bool has_keyboard,
+ const AudioManagerChromeOS::SystemAudioProcessingInfo& system_apm_info) {
+ AudioParameters params(
+ AudioParameters::AUDIO_PCM_LOW_LATENCY, CHANNEL_LAYOUT_STEREO,
+ kDefaultSampleRate, user_buffer_size,
+ AudioParameters::HardwareCapabilities(limits::kMinAudioBufferSize,
+ limits::kMaxAudioBufferSize));
+ if (has_keyboard)
+ params.set_effects(AudioParameters::KEYBOARD_MIC);
+
+ bool enforce_system_aec;
+ bool enforce_system_ns;
+ bool enforce_system_agc;
+ bool tuned_system_aec_allowed;
+ RetrieveSystemEffectFeatures(enforce_system_aec, enforce_system_ns,
+ enforce_system_agc, tuned_system_aec_allowed);
+
+ // Activation of the system AEC. Allow experimentation with system AEC with
+ // all devices, but enable it by default on devices that actually support it.
+ params.set_effects(params.effects() |
+ AudioParameters::EXPERIMENTAL_ECHO_CANCELLER);
+
+ // Rephrase the field aec_supported to properly reflect its meaning in this
+ // context (since it currently signals whether an CrAS APM with tuned settings
+ // is available).
+ const bool tuned_system_apm_available = system_apm_info.aec_supported;
+
+ // Don't use the system AEC if it is deactivated for this group ID. Also never
+ // activate NS nor AGC for this board if the AEC is not activated, since this
+ // will cause issues for the Browser AEC.
+ bool use_system_aec =
+ (tuned_system_apm_available && tuned_system_aec_allowed) ||
+ enforce_system_aec;
+
+ if (!use_system_aec || IsSystemAecDeactivated(system_apm_info.aec_group_id)) {
+ return params;
+ }
+
+ // Activation of the system AEC.
+ params.set_effects(params.effects() | AudioParameters::ECHO_CANCELLER);
+
+ // Don't use system NS or AGC if the AEC has board-specific tunings.
+ if (tuned_system_apm_available) {
+ return params;
+ }
+
+ // Activation of the system NS.
+ if (system_apm_info.ns_supported || enforce_system_ns) {
+ params.set_effects(params.effects() | AudioParameters::NOISE_SUPPRESSION);
+ }
+
+ // Activation of the system AGC.
+ if (system_apm_info.agc_supported || enforce_system_agc) {
+ params.set_effects(params.effects() |
+ AudioParameters::AUTOMATIC_GAIN_CONTROL);
+ }
+
+ return params;
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/cras/audio_manager_chromeos.h b/third_party/chromium/media/audio/cras/audio_manager_chromeos.h
new file mode 100644
index 0000000..1006644
--- /dev/null
+++ b/third_party/chromium/media/audio/cras/audio_manager_chromeos.h
@@ -0,0 +1,119 @@
+// Copyright 2020 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 MEDIA_AUDIO_CRAS_AUDIO_MANAGER_CHROMEOS_H_
+#define MEDIA_AUDIO_CRAS_AUDIO_MANAGER_CHROMEOS_H_
+
+#include <cras_types.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "ash/components/audio/audio_device.h"
+#include "ash/components/audio/cras_audio_handler.h"
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "media/audio/audio_manager_base.h"
+#include "media/audio/cras/audio_manager_cras_base.h"
+
+namespace media {
+
+class MEDIA_EXPORT AudioManagerChromeOS : public AudioManagerCrasBase {
+ public:
+ AudioManagerChromeOS(std::unique_ptr<AudioThread> audio_thread,
+ AudioLogFactory* audio_log_factory);
+
+ AudioManagerChromeOS(const AudioManagerChromeOS&) = delete;
+ AudioManagerChromeOS& operator=(const AudioManagerChromeOS&) = delete;
+
+ ~AudioManagerChromeOS() override;
+
+ // AudioManager implementation.
+ bool HasAudioOutputDevices() override;
+ bool HasAudioInputDevices() override;
+ void GetAudioInputDeviceNames(AudioDeviceNames* device_names) override;
+ void GetAudioOutputDeviceNames(AudioDeviceNames* device_names) override;
+ AudioParameters GetInputStreamParameters(
+ const std::string& device_id) override;
+ std::string GetAssociatedOutputDeviceID(
+ const std::string& input_device_id) override;
+ std::string GetDefaultInputDeviceID() override;
+ std::string GetDefaultOutputDeviceID() override;
+ std::string GetGroupIDOutput(const std::string& output_device_id) override;
+ std::string GetGroupIDInput(const std::string& input_device_id) override;
+ bool Shutdown() override;
+
+ // AudioManagerCrasBase implementation.
+ bool IsDefault(const std::string& device_id, bool is_input) override;
+ enum CRAS_CLIENT_TYPE GetClientType() override;
+
+ // Stores information about the system audio processing effects and
+ // properties that are provided by the system audio processing module (APM).
+ struct SystemAudioProcessingInfo {
+ bool aec_supported = false;
+ int32_t aec_group_id = ash::CrasAudioHandler::kSystemAecGroupIdNotAvailable;
+ bool ns_supported = false;
+ bool agc_supported = false;
+ };
+
+ // Produces AudioParameters for the system, including audio processing
+ // capabilities tailored for the system,
+ static AudioParameters GetStreamParametersForSystem(
+ int user_buffer_size,
+ bool has_keyboard,
+ const AudioManagerChromeOS::SystemAudioProcessingInfo& system_apm_info);
+
+ protected:
+ AudioParameters GetPreferredOutputStreamParameters(
+ const std::string& output_device_id,
+ const AudioParameters& input_params) override;
+
+ private:
+ // Get default output buffer size for this board.
+ int GetDefaultOutputBufferSizePerBoard();
+
+ // Get any system APM effects that are supported for this board.
+ SystemAudioProcessingInfo GetSystemApmEffectsSupportedPerBoard();
+
+ void GetAudioDeviceNamesImpl(bool is_input, AudioDeviceNames* device_names);
+
+ std::string GetHardwareDeviceFromDeviceId(const ash::AudioDeviceList& devices,
+ bool is_input,
+ const std::string& device_id);
+
+ void GetAudioDevices(ash::AudioDeviceList* devices);
+ void GetAudioDevicesOnMainThread(ash::AudioDeviceList* devices,
+ base::WaitableEvent* event);
+ uint64_t GetPrimaryActiveInputNode();
+ uint64_t GetPrimaryActiveOutputNode();
+ void GetPrimaryActiveInputNodeOnMainThread(uint64_t* active_input_node_id,
+ base::WaitableEvent* event);
+ void GetPrimaryActiveOutputNodeOnMainThread(uint64_t* active_output_node_id,
+ base::WaitableEvent* event);
+ void GetDefaultOutputBufferSizeOnMainThread(int32_t* buffer_size,
+ base::WaitableEvent* event);
+ void GetSystemApmEffectsSupportedOnMainThread(
+ SystemAudioProcessingInfo* system_apm_info,
+ base::WaitableEvent* event);
+
+ void WaitEventOrShutdown(base::WaitableEvent* event);
+
+ // Signaled if AudioManagerCras is shutting down.
+ base::WaitableEvent on_shutdown_;
+
+ // Task runner of browser main thread. CrasAudioHandler should be only
+ // accessed on this thread.
+ scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
+
+ // For posting tasks from audio thread to |main_task_runner_|.
+ base::WeakPtr<AudioManagerChromeOS> weak_this_;
+
+ base::WeakPtrFactory<AudioManagerChromeOS> weak_ptr_factory_;
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_CRAS_AUDIO_MANAGER_CHROMEOS_H_
diff --git a/third_party/chromium/media/audio/cras/audio_manager_chromeos_unittest.cc b/third_party/chromium/media/audio/cras/audio_manager_chromeos_unittest.cc
new file mode 100644
index 0000000..b11144a
--- /dev/null
+++ b/third_party/chromium/media/audio/cras/audio_manager_chromeos_unittest.cc
@@ -0,0 +1,229 @@
+// Copyright (c) 2021 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 "media/audio/cras/audio_manager_chromeos.h"
+
+#include "base/test/scoped_feature_list.h"
+#include "media/audio/audio_features.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+
+namespace {
+constexpr int kAecTestGroupId = 9;
+constexpr int kNoAecFlaggedGroupId = 0;
+
+// Chosen to be the same as in `audio_manager_chromeos.cc`, but any size should
+// work since this should not affect the testing done herein.
+constexpr int kDefaultInputBufferSize = 1024;
+
+bool ExperimentalAecActive(const AudioParameters& params) {
+ return params.effects() & AudioParameters::EXPERIMENTAL_ECHO_CANCELLER;
+}
+
+bool AecActive(const AudioParameters& params) {
+ return params.effects() & AudioParameters::ECHO_CANCELLER;
+}
+
+bool NsActive(const AudioParameters& params) {
+ return params.effects() & AudioParameters::NOISE_SUPPRESSION;
+}
+
+bool AgcActive(const AudioParameters& params) {
+ return params.effects() & AudioParameters::AUTOMATIC_GAIN_CONTROL;
+}
+
+} // namespace
+
+class GetStreamParametersForSystem
+ : public ::testing::Test,
+ public ::testing::WithParamInterface<
+ std::tuple<bool, int, bool, int32_t, bool, bool>> {
+ protected:
+ // Retrieve test parameter values.
+ void GetTestParameters() {
+ has_keyboard_ = std::get<0>(GetParam());
+ user_buffer_size_ = std::get<1>(GetParam());
+ system_apm_info_.aec_supported = std::get<2>(GetParam());
+ system_apm_info_.aec_group_id = std::get<3>(GetParam());
+ system_apm_info_.ns_supported = std::get<4>(GetParam());
+ system_apm_info_.agc_supported = std::get<5>(GetParam());
+ }
+
+ AudioManagerChromeOS::SystemAudioProcessingInfo system_apm_info_;
+ size_t has_keyboard_;
+ size_t user_buffer_size_;
+};
+
+INSTANTIATE_TEST_SUITE_P(
+ AllInputParameters,
+ GetStreamParametersForSystem,
+ ::testing::Combine(::testing::Values(false, true),
+ ::testing::Values(512, kDefaultInputBufferSize),
+ ::testing::Values(false, true),
+ ::testing::Values(kNoAecFlaggedGroupId, kAecTestGroupId),
+ ::testing::Values(false, true),
+ ::testing::Values(false, true)));
+
+TEST_P(GetStreamParametersForSystem, DefaultBehavior) {
+ GetTestParameters();
+ AudioParameters params = AudioManagerChromeOS::GetStreamParametersForSystem(
+ user_buffer_size_, has_keyboard_, system_apm_info_);
+
+ EXPECT_TRUE(ExperimentalAecActive(params));
+ EXPECT_EQ(AecActive(params), system_apm_info_.aec_supported);
+ if (system_apm_info_.aec_supported) {
+ EXPECT_FALSE(NsActive(params));
+ EXPECT_FALSE(AgcActive(params));
+ } else {
+ EXPECT_FALSE(NsActive(params));
+ EXPECT_FALSE(AgcActive(params));
+ }
+}
+
+TEST_P(GetStreamParametersForSystem,
+ BehaviorWithCrOSEnforceSystemAecDisallowed) {
+ base::test::ScopedFeatureList feature_list;
+ feature_list.InitAndDisableFeature(features::kCrOSSystemAEC);
+
+ GetTestParameters();
+ AudioParameters params = AudioManagerChromeOS::GetStreamParametersForSystem(
+ user_buffer_size_, has_keyboard_, system_apm_info_);
+
+ EXPECT_TRUE(ExperimentalAecActive(params));
+ EXPECT_FALSE(AecActive(params));
+ EXPECT_FALSE(NsActive(params));
+ EXPECT_FALSE(AgcActive(params));
+}
+
+TEST_P(GetStreamParametersForSystem, BehaviorWithCrOSEnforceSystemAecNsAgc) {
+ base::test::ScopedFeatureList feature_list;
+ feature_list.InitAndEnableFeature(features::kCrOSEnforceSystemAecNsAgc);
+
+ GetTestParameters();
+ AudioParameters params = AudioManagerChromeOS::GetStreamParametersForSystem(
+ user_buffer_size_, has_keyboard_, system_apm_info_);
+
+ EXPECT_TRUE(ExperimentalAecActive(params));
+ EXPECT_TRUE(AecActive(params));
+ if (system_apm_info_.aec_supported) {
+ EXPECT_FALSE(NsActive(params));
+ EXPECT_FALSE(AgcActive(params));
+ } else {
+ EXPECT_TRUE(NsActive(params));
+ EXPECT_TRUE(AgcActive(params));
+ }
+}
+
+// TODO(crbug.com/1216273): DCHECKs are disabled during automated testing on
+// CrOS and this test failed when tested on an experimental builder with
+// DCHECKs. Revert https://crrev.com/c/2959990 to re-enable it.
+// See go/chrome-dcheck-on-cros or http://crbug.com/1113456 for more details.
+#if !DCHECK_IS_ON()
+TEST_P(GetStreamParametersForSystem,
+ BehaviorWithCrOSEnforceSystemAecNsAndAecAgc) {
+ base::test::ScopedFeatureList feature_list;
+ feature_list.InitAndEnableFeature(features::kCrOSEnforceSystemAecNs);
+ feature_list.InitAndEnableFeature(features::kCrOSEnforceSystemAecAgc);
+
+ GetTestParameters();
+ AudioParameters params = AudioManagerChromeOS::GetStreamParametersForSystem(
+ user_buffer_size_, has_keyboard_, system_apm_info_);
+
+ EXPECT_TRUE(ExperimentalAecActive(params));
+ EXPECT_TRUE(AecActive(params));
+ if (system_apm_info_.aec_supported) {
+ EXPECT_FALSE(NsActive(params));
+ EXPECT_FALSE(AgcActive(params));
+ } else {
+ EXPECT_TRUE(NsActive(params));
+ EXPECT_TRUE(AgcActive(params));
+ }
+}
+#endif
+
+// TODO(crbug.com/1216273): DCHECKs are disabled during automated testing on
+// CrOS and this test failed when tested on an experimental builder with
+// DCHECKs. Revert https://crrev.com/c/2959990 to re-enable it.
+// See go/chrome-dcheck-on-cros or http://crbug.com/1113456 for more details.
+#if !DCHECK_IS_ON()
+TEST_P(GetStreamParametersForSystem,
+ BehaviorWithCrOSEnforceSystemAecNsAgcAndDisallowedSystemAec) {
+ base::test::ScopedFeatureList feature_list;
+ feature_list.InitAndEnableFeature(features::kCrOSEnforceSystemAecNsAgc);
+ feature_list.InitAndDisableFeature(features::kCrOSSystemAEC);
+
+ GetTestParameters();
+ AudioParameters params = AudioManagerChromeOS::GetStreamParametersForSystem(
+ user_buffer_size_, has_keyboard_, system_apm_info_);
+
+ EXPECT_TRUE(ExperimentalAecActive(params));
+ EXPECT_TRUE(AecActive(params));
+ if (system_apm_info_.aec_supported) {
+ EXPECT_FALSE(NsActive(params));
+ EXPECT_FALSE(AgcActive(params));
+ } else {
+ EXPECT_TRUE(NsActive(params));
+ EXPECT_TRUE(AgcActive(params));
+ }
+}
+#endif
+
+TEST_P(GetStreamParametersForSystem, BehaviorWithCrOSEnforceSystemAecNs) {
+ base::test::ScopedFeatureList feature_list;
+ feature_list.InitAndEnableFeature(features::kCrOSEnforceSystemAecNs);
+
+ GetTestParameters();
+ AudioParameters params = AudioManagerChromeOS::GetStreamParametersForSystem(
+ user_buffer_size_, has_keyboard_, system_apm_info_);
+
+ EXPECT_TRUE(ExperimentalAecActive(params));
+ EXPECT_TRUE(AecActive(params));
+ if (system_apm_info_.aec_supported) {
+ EXPECT_FALSE(NsActive(params));
+ EXPECT_FALSE(AgcActive(params));
+ } else {
+ EXPECT_TRUE(NsActive(params));
+ EXPECT_EQ(AgcActive(params), system_apm_info_.agc_supported);
+ }
+}
+
+TEST_P(GetStreamParametersForSystem, BehaviorWithCrOSEnforceSystemAecAgc) {
+ base::test::ScopedFeatureList feature_list;
+ feature_list.InitAndEnableFeature(features::kCrOSEnforceSystemAecAgc);
+
+ GetTestParameters();
+ AudioParameters params = AudioManagerChromeOS::GetStreamParametersForSystem(
+ user_buffer_size_, has_keyboard_, system_apm_info_);
+
+ EXPECT_TRUE(ExperimentalAecActive(params));
+ EXPECT_TRUE(AecActive(params));
+ if (system_apm_info_.aec_supported) {
+ EXPECT_FALSE(NsActive(params));
+ EXPECT_FALSE(AgcActive(params));
+ } else {
+ EXPECT_EQ(NsActive(params), system_apm_info_.ns_supported);
+ EXPECT_TRUE(AgcActive(params));
+ }
+}
+
+TEST_P(GetStreamParametersForSystem, BehaviorWithCrOSEnforceSystemAec) {
+ base::test::ScopedFeatureList feature_list;
+ feature_list.InitAndEnableFeature(features::kCrOSEnforceSystemAec);
+
+ GetTestParameters();
+ AudioParameters params = AudioManagerChromeOS::GetStreamParametersForSystem(
+ user_buffer_size_, has_keyboard_, system_apm_info_);
+
+ EXPECT_TRUE(ExperimentalAecActive(params));
+ EXPECT_TRUE(AecActive(params));
+ if (system_apm_info_.aec_supported) {
+ EXPECT_FALSE(NsActive(params));
+ EXPECT_FALSE(AgcActive(params));
+ } else {
+ EXPECT_EQ(NsActive(params), system_apm_info_.ns_supported);
+ EXPECT_EQ(AgcActive(params), system_apm_info_.agc_supported);
+ }
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/cras/audio_manager_cras.cc b/third_party/chromium/media/audio/cras/audio_manager_cras.cc
new file mode 100644
index 0000000..5044a50
--- /dev/null
+++ b/third_party/chromium/media/audio/cras/audio_manager_cras.cc
@@ -0,0 +1,226 @@
+// Copyright 2020 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 "media/audio/cras/audio_manager_cras.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <map>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/check_op.h"
+#include "base/command_line.h"
+#include "base/environment.h"
+#include "base/metrics/field_trial_params.h"
+#include "base/nix/xdg_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/system/sys_info.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "media/audio/audio_device_description.h"
+#include "media/audio/audio_features.h"
+#include "media/audio/cras/cras_input.h"
+#include "media/audio/cras/cras_unified.h"
+#include "media/audio/cras/cras_util.h"
+#include "media/base/channel_layout.h"
+#include "media/base/limits.h"
+#include "media/base/localized_strings.h"
+
+namespace media {
+namespace {
+
+// Default sample rate for input and output streams.
+const int kDefaultSampleRate = 48000;
+
+// Default input buffer size.
+const int kDefaultInputBufferSize = 1024;
+
+// Default output buffer size.
+const int kDefaultOutputBufferSize = 512;
+
+} // namespace
+
+bool AudioManagerCras::HasAudioOutputDevices() {
+ return true;
+}
+
+bool AudioManagerCras::HasAudioInputDevices() {
+ return !CrasGetAudioDevices(DeviceType::kInput).empty();
+}
+
+AudioManagerCras::AudioManagerCras(
+ std::unique_ptr<AudioThread> audio_thread,
+ AudioLogFactory* audio_log_factory)
+ : AudioManagerCrasBase(std::move(audio_thread), audio_log_factory),
+ main_task_runner_(base::ThreadTaskRunnerHandle::Get()),
+ weak_ptr_factory_(this) {
+ weak_this_ = weak_ptr_factory_.GetWeakPtr();
+}
+
+AudioManagerCras::~AudioManagerCras() = default;
+
+void AudioManagerCras::GetAudioInputDeviceNames(
+ AudioDeviceNames* device_names) {
+ device_names->push_back(AudioDeviceName::CreateDefault());
+ for (const auto& device : CrasGetAudioDevices(DeviceType::kInput)) {
+ device_names->emplace_back(device.name, base::NumberToString(device.id));
+ }
+}
+
+void AudioManagerCras::GetAudioOutputDeviceNames(
+ AudioDeviceNames* device_names) {
+ device_names->push_back(AudioDeviceName::CreateDefault());
+ for (const auto& device : CrasGetAudioDevices(DeviceType::kOutput)) {
+ device_names->emplace_back(device.name, base::NumberToString(device.id));
+ }
+}
+
+AudioParameters AudioManagerCras::GetInputStreamParameters(
+ const std::string& device_id) {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+
+ int user_buffer_size = GetUserBufferSize();
+ int buffer_size =
+ user_buffer_size ? user_buffer_size : kDefaultInputBufferSize;
+
+ AudioParameters params(
+ AudioParameters::AUDIO_PCM_LOW_LATENCY, CHANNEL_LAYOUT_STEREO,
+ kDefaultSampleRate, buffer_size,
+ AudioParameters::HardwareCapabilities(limits::kMinAudioBufferSize,
+ limits::kMaxAudioBufferSize));
+
+ if (CrasHasKeyboardMic())
+ params.set_effects(AudioParameters::KEYBOARD_MIC);
+
+ // Allow experimentation with system echo cancellation with all devices,
+ // but enable it by default on devices that actually support it.
+ params.set_effects(params.effects() |
+ AudioParameters::EXPERIMENTAL_ECHO_CANCELLER);
+ if (base::FeatureList::IsEnabled(features::kCrOSSystemAEC)) {
+ if (CrasGetAecSupported()) {
+ const int32_t aec_group_id = CrasGetAecGroupId();
+
+ // Check if the system AEC has a group ID which is flagged to be
+ // deactivated by the field trial.
+ const bool system_aec_deactivated =
+ base::GetFieldTrialParamByFeatureAsBool(
+ features::kCrOSSystemAECDeactivatedGroups,
+ base::NumberToString(aec_group_id), false);
+
+ if (!system_aec_deactivated) {
+ params.set_effects(params.effects() | AudioParameters::ECHO_CANCELLER);
+ }
+ }
+ }
+
+ return params;
+}
+
+std::string AudioManagerCras::GetDefaultInputDeviceID() {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ return base::NumberToString(GetPrimaryActiveInputNode());
+}
+
+std::string AudioManagerCras::GetDefaultOutputDeviceID() {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ return base::NumberToString(GetPrimaryActiveOutputNode());
+}
+
+std::string AudioManagerCras::GetGroupIDInput(const std::string& device_id) {
+ for (const auto& device : CrasGetAudioDevices(DeviceType::kInput)) {
+ if (base::NumberToString(device.id) == device_id ||
+ (AudioDeviceDescription::IsDefaultDevice(device_id) && device.active)) {
+ return device.dev_name;
+ }
+ }
+ return "";
+}
+
+std::string AudioManagerCras::GetGroupIDOutput(const std::string& device_id) {
+ for (const auto& device : CrasGetAudioDevices(DeviceType::kOutput)) {
+ if (base::NumberToString(device.id) == device_id ||
+ (AudioDeviceDescription::IsDefaultDevice(device_id) && device.active)) {
+ return device.dev_name;
+ }
+ }
+ return "";
+}
+
+std::string AudioManagerCras::GetAssociatedOutputDeviceID(
+ const std::string& input_device_id) {
+ if (AudioDeviceDescription::IsDefaultDevice(input_device_id)) {
+ // Note: the default input should not be associated to any output, as this
+ // may lead to accidental uses of a pinned stream.
+ return "";
+ }
+
+ std::string device_name = GetGroupIDInput(input_device_id);
+
+ if (device_name.empty())
+ return "";
+
+ // Now search for an output device with the same device name.
+ for (const auto& device : CrasGetAudioDevices(DeviceType::kOutput)) {
+ if (device.dev_name == device_name)
+ return base::NumberToString(device.id);
+ }
+ return "";
+}
+
+AudioParameters AudioManagerCras::GetPreferredOutputStreamParameters(
+ const std::string& output_device_id,
+ const AudioParameters& input_params) {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ ChannelLayout channel_layout = CHANNEL_LAYOUT_STEREO;
+ int sample_rate = kDefaultSampleRate;
+ int buffer_size = GetUserBufferSize();
+ if (input_params.IsValid()) {
+ channel_layout = input_params.channel_layout();
+ sample_rate = input_params.sample_rate();
+ if (!buffer_size) // Not user-provided.
+ buffer_size =
+ std::min(static_cast<int>(limits::kMaxAudioBufferSize),
+ std::max(static_cast<int>(limits::kMinAudioBufferSize),
+ input_params.frames_per_buffer()));
+ }
+
+ if (!buffer_size) // Not user-provided.
+ buffer_size = CrasGetDefaultOutputBufferSize();
+
+ if (buffer_size <= 0)
+ buffer_size = kDefaultOutputBufferSize;
+
+ return AudioParameters(
+ AudioParameters::AUDIO_PCM_LOW_LATENCY, channel_layout, sample_rate,
+ buffer_size,
+ AudioParameters::HardwareCapabilities(limits::kMinAudioBufferSize,
+ limits::kMaxAudioBufferSize));
+}
+
+uint64_t AudioManagerCras::GetPrimaryActiveInputNode() {
+ for (const auto& device : CrasGetAudioDevices(DeviceType::kInput)) {
+ if (device.active)
+ return device.id;
+ }
+ return 0;
+}
+
+uint64_t AudioManagerCras::GetPrimaryActiveOutputNode() {
+ for (const auto& device : CrasGetAudioDevices(DeviceType::kOutput)) {
+ if (device.active)
+ return device.id;
+ }
+ return 0;
+}
+
+bool AudioManagerCras::IsDefault(const std::string& device_id, bool is_input) {
+ return AudioDeviceDescription::IsDefaultDevice(device_id);
+}
+
+enum CRAS_CLIENT_TYPE AudioManagerCras::GetClientType() {
+ return CRAS_CLIENT_TYPE_LACROS;
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/cras/audio_manager_cras.h b/third_party/chromium/media/audio/cras/audio_manager_cras.h
new file mode 100644
index 0000000..ffd8656
--- /dev/null
+++ b/third_party/chromium/media/audio/cras/audio_manager_cras.h
@@ -0,0 +1,76 @@
+// Copyright 2020 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 MEDIA_AUDIO_CRAS_AUDIO_MANAGER_CRAS_H_
+#define MEDIA_AUDIO_CRAS_AUDIO_MANAGER_CRAS_H_
+
+#include <cras_types.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "media/audio/cras/audio_manager_cras_base.h"
+
+namespace media {
+
+class MEDIA_EXPORT AudioManagerCras : public AudioManagerCrasBase {
+ public:
+ AudioManagerCras(std::unique_ptr<AudioThread> audio_thread,
+ AudioLogFactory* audio_log_factory);
+
+ AudioManagerCras(const AudioManagerCras&) = delete;
+ AudioManagerCras& operator=(const AudioManagerCras&) = delete;
+
+ ~AudioManagerCras() override;
+
+ // AudioManager implementation.
+ bool HasAudioOutputDevices() override;
+ bool HasAudioInputDevices() override;
+ void GetAudioInputDeviceNames(AudioDeviceNames* device_names) override;
+ void GetAudioOutputDeviceNames(AudioDeviceNames* device_names) override;
+ AudioParameters GetInputStreamParameters(
+ const std::string& device_id) override;
+ std::string GetDefaultInputDeviceID() override;
+ std::string GetDefaultOutputDeviceID() override;
+ std::string GetGroupIDInput(const std::string& device_id) override;
+ std::string GetGroupIDOutput(const std::string& device_id) override;
+ std::string GetAssociatedOutputDeviceID(
+ const std::string& input_device_id) override;
+
+ // AudioManagerCrasBase implementation.
+ bool IsDefault(const std::string& device_id, bool is_input) override;
+ enum CRAS_CLIENT_TYPE GetClientType() override;
+
+ protected:
+ AudioParameters GetPreferredOutputStreamParameters(
+ const std::string& output_device_id,
+ const AudioParameters& input_params) override;
+
+ private:
+ uint64_t GetPrimaryActiveInputNode();
+ uint64_t GetPrimaryActiveOutputNode();
+ void GetPrimaryActiveInputNodeOnMainThread(uint64_t* active_input_node_id,
+ base::WaitableEvent* event);
+ void GetPrimaryActiveOutputNodeOnMainThread(uint64_t* active_output_node_id,
+ base::WaitableEvent* event);
+ void GetDefaultOutputBufferSizeOnMainThread(int32_t* buffer_size,
+ base::WaitableEvent* event);
+
+ // Task runner of browser main thread. CrasAudioHandler should be only
+ // accessed on this thread.
+ scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
+
+ // For posting tasks from audio thread to |main_task_runner_|.
+ base::WeakPtr<AudioManagerCras> weak_this_;
+
+ base::WeakPtrFactory<AudioManagerCras> weak_ptr_factory_;
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_CRAS_AUDIO_MANAGER_CRAS_H_
diff --git a/third_party/chromium/media/audio/cras/audio_manager_cras_base.cc b/third_party/chromium/media/audio/cras/audio_manager_cras_base.cc
new file mode 100644
index 0000000..b7eecf4
--- /dev/null
+++ b/third_party/chromium/media/audio/cras/audio_manager_cras_base.cc
@@ -0,0 +1,95 @@
+// Copyright 2013 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 "media/audio/cras/audio_manager_cras_base.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <map>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/check_op.h"
+#include "base/command_line.h"
+#include "base/environment.h"
+#include "base/metrics/field_trial_params.h"
+#include "base/nix/xdg_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/system/sys_info.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "media/audio/audio_device_description.h"
+#include "media/audio/audio_features.h"
+#include "media/audio/cras/cras_input.h"
+#include "media/audio/cras/cras_unified.h"
+#include "media/base/channel_layout.h"
+#include "media/base/limits.h"
+#include "media/base/localized_strings.h"
+
+namespace media {
+namespace {
+
+// Maximum number of output streams that can be open simultaneously.
+const int kMaxOutputStreams = 50;
+
+} // namespace
+
+AudioManagerCrasBase::AudioManagerCrasBase(
+ std::unique_ptr<AudioThread> audio_thread,
+ AudioLogFactory* audio_log_factory)
+ : AudioManagerBase(std::move(audio_thread), audio_log_factory) {
+ SetMaxOutputStreamsAllowed(kMaxOutputStreams);
+}
+
+AudioManagerCrasBase::~AudioManagerCrasBase() = default;
+
+const char* AudioManagerCrasBase::GetName() {
+ return "CRAS";
+}
+
+AudioOutputStream* AudioManagerCrasBase::MakeLinearOutputStream(
+ const AudioParameters& params,
+ const LogCallback& log_callback) {
+ DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format());
+ // Pinning stream is not supported for MakeLinearOutputStream.
+ return MakeOutputStream(params, AudioDeviceDescription::kDefaultDeviceId);
+}
+
+AudioOutputStream* AudioManagerCrasBase::MakeLowLatencyOutputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) {
+ DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format());
+ return MakeOutputStream(params, device_id);
+}
+
+AudioInputStream* AudioManagerCrasBase::MakeLinearInputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) {
+ DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format());
+ return MakeInputStream(params, device_id);
+}
+
+AudioInputStream* AudioManagerCrasBase::MakeLowLatencyInputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) {
+ DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format());
+ return MakeInputStream(params, device_id);
+}
+
+AudioOutputStream* AudioManagerCrasBase::MakeOutputStream(
+ const AudioParameters& params,
+ const std::string& device_id) {
+ return new CrasUnifiedStream(params, this, device_id);
+}
+
+AudioInputStream* AudioManagerCrasBase::MakeInputStream(
+ const AudioParameters& params, const std::string& device_id) {
+ return new CrasInputStream(params, this, device_id);
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/cras/audio_manager_cras_base.h b/third_party/chromium/media/audio/cras/audio_manager_cras_base.h
new file mode 100644
index 0000000..4f67626
--- /dev/null
+++ b/third_party/chromium/media/audio/cras/audio_manager_cras_base.h
@@ -0,0 +1,70 @@
+// Copyright 2013 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 MEDIA_AUDIO_CRAS_AUDIO_MANAGER_CRAS_BASE_H_
+#define MEDIA_AUDIO_CRAS_AUDIO_MANAGER_CRAS_BASE_H_
+
+#include <cras_types.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "media/audio/audio_manager_base.h"
+
+namespace media {
+
+class MEDIA_EXPORT AudioManagerCrasBase : public AudioManagerBase {
+ public:
+ AudioManagerCrasBase(std::unique_ptr<AudioThread> audio_thread,
+ AudioLogFactory* audio_log_factory);
+
+ AudioManagerCrasBase(const AudioManagerCrasBase&) = delete;
+ AudioManagerCrasBase& operator=(const AudioManagerCrasBase&) = delete;
+
+ ~AudioManagerCrasBase() override;
+
+ // AudioManager implementation.
+ const char* GetName() override;
+
+ // AudioManagerBase implementation.
+ AudioOutputStream* MakeLinearOutputStream(
+ const AudioParameters& params,
+ const LogCallback& log_callback) override;
+ AudioOutputStream* MakeLowLatencyOutputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) override;
+ AudioInputStream* MakeLinearInputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) override;
+ AudioInputStream* MakeLowLatencyInputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) override;
+
+ // Checks if |device_id| corresponds to the default device.
+ // Set |is_input| to true for capture devices, false for output.
+ virtual bool IsDefault(const std::string& device_id, bool is_input) = 0;
+
+ // Returns CRAS client type.
+ virtual enum CRAS_CLIENT_TYPE GetClientType() = 0;
+
+ protected:
+ // Called by MakeLinearOutputStream and MakeLowLatencyOutputStream.
+ AudioOutputStream* MakeOutputStream(const AudioParameters& params,
+ const std::string& device_id);
+
+ // Called by MakeLinearInputStream and MakeLowLatencyInputStream.
+ AudioInputStream* MakeInputStream(const AudioParameters& params,
+ const std::string& device_id);
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_CRAS_AUDIO_MANAGER_CRAS_BASE_H_
diff --git a/third_party/chromium/media/audio/cras/cras_input.cc b/third_party/chromium/media/audio/cras/cras_input.cc
new file mode 100644
index 0000000..dc98f23
--- /dev/null
+++ b/third_party/chromium/media/audio/cras/cras_input.cc
@@ -0,0 +1,361 @@
+// Copyright (c) 2012 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 "media/audio/cras/cras_input.h"
+
+#include <math.h>
+#include <algorithm>
+
+#include "base/cxx17_backports.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/time/time.h"
+#include "media/audio/audio_device_description.h"
+#include "media/audio/cras/audio_manager_cras_base.h"
+
+namespace media {
+
+CrasInputStream::CrasInputStream(const AudioParameters& params,
+ AudioManagerCrasBase* manager,
+ const std::string& device_id)
+ : audio_manager_(manager),
+ callback_(NULL),
+ client_(NULL),
+ params_(params),
+ started_(false),
+ stream_id_(0),
+ stream_direction_(CRAS_STREAM_INPUT),
+ pin_device_(NO_DEVICE),
+ is_loopback_(AudioDeviceDescription::IsLoopbackDevice(device_id)),
+ mute_system_audio_(device_id ==
+ AudioDeviceDescription::kLoopbackWithMuteDeviceId),
+ mute_done_(false),
+ input_volume_(1.0f) {
+ DCHECK(audio_manager_);
+ audio_bus_ = AudioBus::Create(params_);
+ if (!audio_manager_->IsDefault(device_id, true)) {
+ uint64_t cras_node_id;
+ base::StringToUint64(device_id, &cras_node_id);
+ pin_device_ = dev_index_of(cras_node_id);
+ }
+}
+
+CrasInputStream::~CrasInputStream() {
+ DCHECK(!client_);
+}
+
+AudioInputStream::OpenOutcome CrasInputStream::Open() {
+ if (client_) {
+ NOTREACHED() << "CrasInputStream already open";
+ return OpenOutcome::kAlreadyOpen;
+ }
+
+ // Sanity check input values.
+ if (params_.sample_rate() <= 0) {
+ DLOG(WARNING) << "Unsupported audio frequency.";
+ return OpenOutcome::kFailed;
+ }
+
+ if (AudioParameters::AUDIO_PCM_LINEAR != params_.format() &&
+ AudioParameters::AUDIO_PCM_LOW_LATENCY != params_.format()) {
+ DLOG(WARNING) << "Unsupported audio format.";
+ return OpenOutcome::kFailed;
+ }
+
+ // Create the client and connect to the CRAS server.
+ client_ = libcras_client_create();
+ if (!client_) {
+ DLOG(WARNING) << "Couldn't create CRAS client.\n";
+ client_ = NULL;
+ return OpenOutcome::kFailed;
+ }
+
+ if (libcras_client_connect(client_)) {
+ DLOG(WARNING) << "Couldn't connect CRAS client.\n";
+ libcras_client_destroy(client_);
+ client_ = NULL;
+ return OpenOutcome::kFailed;
+ }
+
+ // Then start running the client.
+ if (libcras_client_run_thread(client_)) {
+ DLOG(WARNING) << "Couldn't run CRAS client.\n";
+ libcras_client_destroy(client_);
+ client_ = NULL;
+ return OpenOutcome::kFailed;
+ }
+
+ if (is_loopback_) {
+ if (libcras_client_connected_wait(client_) < 0) {
+ DLOG(WARNING) << "Couldn't synchronize data.";
+ // TODO(chinyue): Add a DestroyClientOnError method to de-duplicate the
+ // cleanup code.
+ libcras_client_destroy(client_);
+ client_ = NULL;
+ return OpenOutcome::kFailed;
+ }
+
+ int rc = libcras_client_get_loopback_dev_idx(client_, &pin_device_);
+ if (rc < 0) {
+ DLOG(WARNING) << "Couldn't find CRAS loopback device.";
+ libcras_client_destroy(client_);
+ client_ = NULL;
+ return OpenOutcome::kFailed;
+ }
+ }
+
+ return OpenOutcome::kSuccess;
+}
+
+void CrasInputStream::Close() {
+ Stop();
+
+ if (client_) {
+ libcras_client_stop(client_);
+ libcras_client_destroy(client_);
+ client_ = NULL;
+ }
+
+ // Signal to the manager that we're closed and can be removed.
+ // Should be last call in the method as it deletes "this".
+ audio_manager_->ReleaseInputStream(this);
+}
+
+inline bool CrasInputStream::UseCrasAec() const {
+ return params_.effects() & AudioParameters::ECHO_CANCELLER;
+}
+
+inline bool CrasInputStream::UseCrasNs() const {
+ return params_.effects() & AudioParameters::NOISE_SUPPRESSION;
+}
+
+inline bool CrasInputStream::UseCrasAgc() const {
+ return params_.effects() & AudioParameters::AUTOMATIC_GAIN_CONTROL;
+}
+
+void CrasInputStream::Start(AudioInputCallback* callback) {
+ DCHECK(client_);
+ DCHECK(callback);
+
+ // Channel map to CRAS_CHANNEL, values in the same order of
+ // corresponding source in Chromium defined Channels.
+ static const int kChannelMap[] = {
+ CRAS_CH_FL,
+ CRAS_CH_FR,
+ CRAS_CH_FC,
+ CRAS_CH_LFE,
+ CRAS_CH_RL,
+ CRAS_CH_RR,
+ CRAS_CH_FLC,
+ CRAS_CH_FRC,
+ CRAS_CH_RC,
+ CRAS_CH_SL,
+ CRAS_CH_SR
+ };
+ static_assert(base::size(kChannelMap) == CHANNELS_MAX + 1,
+ "kChannelMap array size should match");
+
+ // If already playing, stop before re-starting.
+ if (started_)
+ return;
+
+ StartAgc();
+
+ callback_ = callback;
+
+ CRAS_STREAM_TYPE type = CRAS_STREAM_TYPE_DEFAULT;
+ uint32_t flags = 0;
+ if (params_.effects() & AudioParameters::PlatformEffectsMask::HOTWORD) {
+ flags = HOTWORD_STREAM;
+ type = CRAS_STREAM_TYPE_SPEECH_RECOGNITION;
+ }
+
+ unsigned int frames_per_packet = params_.frames_per_buffer();
+ struct libcras_stream_params* stream_params = libcras_stream_params_create();
+ if (!stream_params) {
+ DLOG(ERROR) << "Error creating stream params";
+ callback_->OnError();
+ callback_ = NULL;
+ return;
+ }
+
+ int rc = libcras_stream_params_set(
+ stream_params, stream_direction_, frames_per_packet, frames_per_packet,
+ type, audio_manager_->GetClientType(), flags, this,
+ CrasInputStream::SamplesReady, CrasInputStream::StreamError,
+ params_.sample_rate(), SND_PCM_FORMAT_S16, params_.channels());
+
+ if (rc) {
+ DLOG(WARNING) << "Error setting up stream parameters.";
+ callback_->OnError();
+ callback_ = NULL;
+ libcras_stream_params_destroy(stream_params);
+ return;
+ }
+
+ // Initialize channel layout to all -1 to indicate that none of
+ // the channels is set in the layout.
+ int8_t layout[CRAS_CH_MAX];
+ for (size_t i = 0; i < base::size(layout); ++i)
+ layout[i] = -1;
+
+ // Converts to CRAS defined channels. ChannelOrder will return -1
+ // for channels that are not present in params_.channel_layout().
+ for (size_t i = 0; i < base::size(kChannelMap); ++i) {
+ layout[kChannelMap[i]] = ChannelOrder(params_.channel_layout(),
+ static_cast<Channels>(i));
+ }
+
+ rc = libcras_stream_params_set_channel_layout(stream_params, CRAS_CH_MAX,
+ layout);
+ if (rc) {
+ DLOG(WARNING) << "Error setting up the channel layout.";
+ callback_->OnError();
+ callback_ = NULL;
+ libcras_stream_params_destroy(stream_params);
+ return;
+ }
+
+ if (UseCrasAec())
+ libcras_stream_params_enable_aec(stream_params);
+
+ if (UseCrasNs())
+ libcras_stream_params_enable_ns(stream_params);
+
+ if (UseCrasAgc())
+ libcras_stream_params_enable_agc(stream_params);
+
+ // Adding the stream will start the audio callbacks.
+ if (libcras_client_add_pinned_stream(client_, pin_device_, &stream_id_,
+ stream_params)) {
+ DLOG(WARNING) << "Failed to add the stream.";
+ callback_->OnError();
+ callback_ = NULL;
+ }
+
+ // Mute system audio if requested.
+ if (mute_system_audio_) {
+ int muted;
+ libcras_client_get_system_muted(client_, &muted);
+ if (!muted)
+ libcras_client_set_system_mute(client_, 1);
+ mute_done_ = true;
+ }
+
+ // Done with config params.
+ libcras_stream_params_destroy(stream_params);
+
+ started_ = true;
+}
+
+void CrasInputStream::Stop() {
+ if (!client_)
+ return;
+
+ if (!callback_ || !started_)
+ return;
+
+ if (mute_system_audio_ && mute_done_) {
+ libcras_client_set_system_mute(client_, 0);
+ mute_done_ = false;
+ }
+
+ StopAgc();
+
+ // Removing the stream from the client stops audio.
+ libcras_client_rm_stream(client_, stream_id_);
+
+ started_ = false;
+ callback_ = NULL;
+}
+
+// Static callback asking for samples. Run on high priority thread.
+int CrasInputStream::SamplesReady(struct libcras_stream_cb_data* data) {
+ unsigned int frames;
+ uint8_t* buf;
+ struct timespec latency;
+ void* usr_arg;
+ libcras_stream_cb_data_get_frames(data, &frames);
+ libcras_stream_cb_data_get_buf(data, &buf);
+ libcras_stream_cb_data_get_latency(data, &latency);
+ libcras_stream_cb_data_get_usr_arg(data, &usr_arg);
+ CrasInputStream* me = static_cast<CrasInputStream*>(usr_arg);
+ me->ReadAudio(frames, buf, &latency);
+ return frames;
+}
+
+// Static callback for stream errors.
+int CrasInputStream::StreamError(cras_client* client,
+ cras_stream_id_t stream_id,
+ int err,
+ void* arg) {
+ CrasInputStream* me = static_cast<CrasInputStream*>(arg);
+ me->NotifyStreamError(err);
+ return 0;
+}
+
+void CrasInputStream::ReadAudio(size_t frames,
+ uint8_t* buffer,
+ const timespec* latency_ts) {
+ DCHECK(callback_);
+
+ // Update the AGC volume level once every second. Note that, |volume| is
+ // also updated each time SetVolume() is called through IPC by the
+ // render-side AGC.
+ double normalized_volume = 0.0;
+ GetAgcVolume(&normalized_volume);
+
+ const base::TimeDelta delay =
+ std::max(base::TimeDelta::FromTimeSpec(*latency_ts), base::TimeDelta());
+
+ // The delay says how long ago the capture was, so we subtract the delay from
+ // Now() to find the capture time.
+ const base::TimeTicks capture_time = base::TimeTicks::Now() - delay;
+
+ audio_bus_->FromInterleaved<SignedInt16SampleTypeTraits>(
+ reinterpret_cast<int16_t*>(buffer), audio_bus_->frames());
+ callback_->OnData(audio_bus_.get(), capture_time, normalized_volume);
+}
+
+void CrasInputStream::NotifyStreamError(int err) {
+ if (callback_)
+ callback_->OnError();
+}
+
+double CrasInputStream::GetMaxVolume() {
+ return 1.0f;
+}
+
+void CrasInputStream::SetVolume(double volume) {
+ DCHECK(client_);
+
+ // Set the volume ratio to CRAS's softare and stream specific gain.
+ input_volume_ = volume;
+ libcras_client_set_stream_volume(client_, stream_id_, input_volume_);
+
+ // Update the AGC volume level based on the last setting above. Note that,
+ // the volume-level resolution is not infinite and it is therefore not
+ // possible to assume that the volume provided as input parameter can be
+ // used directly. Instead, a new query to the audio hardware is required.
+ // This method does nothing if AGC is disabled.
+ UpdateAgcVolume();
+}
+
+double CrasInputStream::GetVolume() {
+ if (!client_)
+ return 0.0;
+
+ return input_volume_;
+}
+
+bool CrasInputStream::IsMuted() {
+ return false;
+}
+
+void CrasInputStream::SetOutputDeviceForAec(
+ const std::string& output_device_id) {
+ // Not supported. Do nothing.
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/cras/cras_input.h b/third_party/chromium/media/audio/cras/cras_input.h
new file mode 100644
index 0000000..4acbc0c
--- /dev/null
+++ b/third_party/chromium/media/audio/cras/cras_input.h
@@ -0,0 +1,131 @@
+// Copyright (c) 2012 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 MEDIA_AUDIO_CRAS_CRAS_INPUT_H_
+#define MEDIA_AUDIO_CRAS_CRAS_INPUT_H_
+
+#include <cras_client.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "media/audio/agc_audio_stream.h"
+#include "media/audio/audio_io.h"
+#include "media/base/audio_parameters.h"
+#include "media/base/media_export.h"
+
+namespace media {
+
+class AudioManagerCrasBase;
+
+// Provides an input stream for audio capture based on CRAS, the ChromeOS Audio
+// Server. This object is not thread safe and all methods should be invoked in
+// the thread that created the object.
+class MEDIA_EXPORT CrasInputStream : public AgcAudioStream<AudioInputStream> {
+ public:
+ // The ctor takes all the usual parameters, plus |manager| which is the
+ // audio manager who is creating this object.
+ CrasInputStream(const AudioParameters& params,
+ AudioManagerCrasBase* manager,
+ const std::string& device_id);
+
+ CrasInputStream(const CrasInputStream&) = delete;
+ CrasInputStream& operator=(const CrasInputStream&) = delete;
+
+ // The dtor is typically called by the AudioManager only and it is usually
+ // triggered by calling AudioOutputStream::Close().
+ ~CrasInputStream() override;
+
+ // Implementation of AudioInputStream.
+ AudioInputStream::OpenOutcome Open() override;
+ void Start(AudioInputCallback* callback) override;
+ void Stop() override;
+ void Close() override;
+ double GetMaxVolume() override;
+ void SetVolume(double volume) override;
+ double GetVolume() override;
+ bool IsMuted() override;
+ void SetOutputDeviceForAec(const std::string& output_device_id) override;
+
+ private:
+ // Handles requests to get samples from the provided buffer. This will be
+ // called by the audio server when it has samples ready.
+ static int SamplesReady(struct libcras_stream_cb_data* data);
+
+ // Handles notification that there was an error with the playback stream.
+ static int StreamError(cras_client* client,
+ cras_stream_id_t stream_id,
+ int err,
+ void* arg);
+
+ // Reads one or more buffers of audio from the device, passes on to the
+ // registered callback. Called from SamplesReady().
+ void ReadAudio(size_t frames, uint8_t* buffer, const timespec* latency_ts);
+
+ // Deals with an error that occured in the stream. Called from StreamError().
+ void NotifyStreamError(int err);
+
+ // Convert from dB * 100 to a volume ratio.
+ double GetVolumeRatioFromDecibels(double dB) const;
+
+ // Convert from a volume ratio to dB.
+ double GetDecibelsFromVolumeRatio(double volume_ratio) const;
+
+ // Return true to use AEC in CRAS for this input stream.
+ inline bool UseCrasAec() const;
+
+ // Return true to use NS in CRAS for this input stream.
+ inline bool UseCrasNs() const;
+
+ // Return true to use AGC in CRAS for this input stream.
+ inline bool UseCrasAgc() const;
+
+ // Non-refcounted pointer back to the audio manager.
+ // The AudioManager indirectly holds on to stream objects, so we don't
+ // want circular references. Additionally, stream objects live on the audio
+ // thread, which is owned by the audio manager and we don't want to addref
+ // the manager from that thread.
+ AudioManagerCrasBase* const audio_manager_;
+
+ // Callback to pass audio samples too, valid while recording.
+ AudioInputCallback* callback_;
+
+ // The client used to communicate with the audio server.
+ struct libcras_client* client_;
+
+ // PCM parameters for the stream.
+ const AudioParameters params_;
+
+ // True if the stream has been started.
+ bool started_;
+
+ // ID of the playing stream.
+ cras_stream_id_t stream_id_;
+
+ // Direction of the stream.
+ const CRAS_STREAM_DIRECTION stream_direction_;
+
+ // Index of the CRAS device to stream input from.
+ int pin_device_;
+
+ // True if the stream is a system-wide loopback stream.
+ bool is_loopback_;
+
+ // True if we want to mute system audio during capturing.
+ bool mute_system_audio_;
+ bool mute_done_;
+
+ // Value of input stream volume, between 0.0 - 1.0.
+ double input_volume_;
+
+ std::unique_ptr<AudioBus> audio_bus_;
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_CRAS_CRAS_INPUT_H_
diff --git a/third_party/chromium/media/audio/cras/cras_input_unittest.cc b/third_party/chromium/media/audio/cras/cras_input_unittest.cc
new file mode 100644
index 0000000..5bda316
--- /dev/null
+++ b/third_party/chromium/media/audio/cras/cras_input_unittest.cc
@@ -0,0 +1,219 @@
+// Copyright (c) 2012 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 <stdint.h>
+
+#include <memory>
+#include <string>
+
+#include "ash/components/audio/cras_audio_handler.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/test/test_message_loop.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
+#include "chromeos/dbus/audio/fake_cras_audio_client.h"
+#include "media/audio/audio_device_description.h"
+#include "media/audio/cras/audio_manager_chromeos.h"
+#include "media/audio/fake_audio_log_factory.h"
+#include "media/audio/test_audio_thread.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// cras_util.h defines custom min/max macros which break compilation, so ensure
+// it's not included until last. #if avoids presubmit errors.
+#if defined(USE_CRAS)
+#include "media/audio/cras/cras_input.h"
+#endif
+
+using testing::_;
+using testing::AtLeast;
+using testing::Ge;
+using testing::InvokeWithoutArgs;
+using testing::StrictMock;
+
+namespace media {
+
+class MockAudioInputCallback : public AudioInputStream::AudioInputCallback {
+ public:
+ MOCK_METHOD3(OnData, void(const AudioBus*, base::TimeTicks, double));
+ MOCK_METHOD0(OnError, void());
+};
+
+class MockAudioManagerCrasInput : public AudioManagerChromeOS {
+ public:
+ MockAudioManagerCrasInput()
+ : AudioManagerChromeOS(std::make_unique<TestAudioThread>(),
+ &fake_audio_log_factory_) {}
+
+ // We need to override this function in order to skip checking the number
+ // of active output streams. It is because the number of active streams
+ // is managed inside MakeAudioInputStream, and we don't use
+ // MakeAudioInputStream to create the stream in the tests.
+ void ReleaseInputStream(AudioInputStream* stream) override {
+ DCHECK(stream);
+ delete stream;
+ }
+
+ private:
+ FakeAudioLogFactory fake_audio_log_factory_;
+};
+
+class CrasInputStreamTest : public testing::Test {
+ protected:
+ CrasInputStreamTest() {
+ chromeos::CrasAudioClient::InitializeFake();
+ ash::CrasAudioHandler::InitializeForTesting();
+ mock_manager_.reset(new StrictMock<MockAudioManagerCrasInput>());
+ base::RunLoop().RunUntilIdle();
+ }
+
+ ~CrasInputStreamTest() override {
+ mock_manager_->Shutdown();
+ ash::CrasAudioHandler::Shutdown();
+ chromeos::CrasAudioClient::Shutdown();
+ }
+
+ CrasInputStream* CreateStream(ChannelLayout layout) {
+ return CreateStream(layout, kTestFramesPerPacket);
+ }
+
+ CrasInputStream* CreateStream(ChannelLayout layout,
+ int32_t samples_per_packet) {
+ return CreateStream(layout, samples_per_packet,
+ AudioDeviceDescription::kDefaultDeviceId);
+ }
+
+ CrasInputStream* CreateStream(ChannelLayout layout,
+ int32_t samples_per_packet,
+ const std::string& device_id) {
+ AudioParameters params(kTestFormat,
+ layout,
+ kTestSampleRate,
+ samples_per_packet);
+ return new CrasInputStream(params, mock_manager_.get(), device_id);
+ }
+
+ void CaptureSomeFrames(const AudioParameters ¶ms,
+ unsigned int duration_ms) {
+ CrasInputStream* test_stream = new CrasInputStream(
+ params, mock_manager_.get(), AudioDeviceDescription::kDefaultDeviceId);
+
+ EXPECT_EQ(test_stream->Open(), AudioInputStream::OpenOutcome::kSuccess);
+
+ // Allow 8 frames variance for SRC in the callback. Different numbers of
+ // samples can be provided when doing non-integer SRC. For example
+ // converting from 192k to 44.1k is a ratio of 4.35 to 1.
+ MockAudioInputCallback mock_callback;
+ base::WaitableEvent event(base::WaitableEvent::ResetPolicy::AUTOMATIC,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+
+ EXPECT_CALL(mock_callback, OnData(_, _, _))
+ .WillOnce(InvokeWithoutArgs(&event, &base::WaitableEvent::Signal));
+
+ test_stream->Start(&mock_callback);
+
+ // Wait for samples to be captured.
+ EXPECT_TRUE(event.TimedWait(TestTimeouts::action_timeout()));
+
+ test_stream->Stop();
+ test_stream->Close();
+ }
+
+ static const unsigned int kTestCaptureDurationMs;
+ static const ChannelLayout kTestChannelLayout;
+ static const AudioParameters::Format kTestFormat;
+ static const uint32_t kTestFramesPerPacket;
+ static const int kTestSampleRate;
+
+ base::TestMessageLoop message_loop_;
+ std::unique_ptr<StrictMock<MockAudioManagerCrasInput>> mock_manager_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CrasInputStreamTest);
+};
+
+const unsigned int CrasInputStreamTest::kTestCaptureDurationMs = 250;
+const ChannelLayout CrasInputStreamTest::kTestChannelLayout =
+ CHANNEL_LAYOUT_STEREO;
+const AudioParameters::Format CrasInputStreamTest::kTestFormat =
+ AudioParameters::AUDIO_PCM_LINEAR;
+const uint32_t CrasInputStreamTest::kTestFramesPerPacket = 1000;
+const int CrasInputStreamTest::kTestSampleRate = 44100;
+
+TEST_F(CrasInputStreamTest, OpenMono) {
+ CrasInputStream* test_stream = CreateStream(CHANNEL_LAYOUT_MONO);
+ EXPECT_EQ(test_stream->Open(), AudioInputStream::OpenOutcome::kSuccess);
+ test_stream->Close();
+}
+
+TEST_F(CrasInputStreamTest, OpenStereo) {
+ CrasInputStream* test_stream = CreateStream(CHANNEL_LAYOUT_STEREO);
+ EXPECT_EQ(test_stream->Open(), AudioInputStream::OpenOutcome::kSuccess);
+ test_stream->Close();
+}
+
+TEST_F(CrasInputStreamTest, BadSampleRate) {
+ AudioParameters bad_rate_params(kTestFormat,
+ kTestChannelLayout,
+ 0,
+ kTestFramesPerPacket);
+ CrasInputStream* test_stream =
+ new CrasInputStream(bad_rate_params, mock_manager_.get(),
+ AudioDeviceDescription::kDefaultDeviceId);
+ EXPECT_EQ(test_stream->Open(), AudioInputStream::OpenOutcome::kFailed);
+ test_stream->Close();
+}
+
+TEST_F(CrasInputStreamTest, SetGetVolume) {
+ CrasInputStream* test_stream = CreateStream(CHANNEL_LAYOUT_MONO);
+ EXPECT_EQ(test_stream->Open(), AudioInputStream::OpenOutcome::kSuccess);
+
+ double max_volume = test_stream->GetMaxVolume();
+ EXPECT_GE(max_volume, 1.0);
+
+ test_stream->SetVolume(max_volume / 2);
+
+ double new_volume = test_stream->GetVolume();
+
+ EXPECT_GE(new_volume, 0.0);
+ EXPECT_LE(new_volume, max_volume);
+
+ test_stream->Close();
+}
+
+TEST_F(CrasInputStreamTest, CaptureFrames) {
+ const unsigned int rates[] =
+ {8000, 16000, 22050, 32000, 44100, 48000, 96000, 192000};
+
+ for (unsigned int i = 0; i < ARRAY_SIZE(rates); i++) {
+ SCOPED_TRACE(testing::Message() << "Mono " << rates[i] << "Hz");
+ AudioParameters params_mono(kTestFormat,
+ CHANNEL_LAYOUT_MONO,
+ rates[i],
+ kTestFramesPerPacket);
+ CaptureSomeFrames(params_mono, kTestCaptureDurationMs);
+ }
+
+ for (unsigned int i = 0; i < ARRAY_SIZE(rates); i++) {
+ SCOPED_TRACE(testing::Message() << "Stereo " << rates[i] << "Hz");
+ AudioParameters params_stereo(kTestFormat,
+ CHANNEL_LAYOUT_STEREO,
+ rates[i],
+ kTestFramesPerPacket);
+ CaptureSomeFrames(params_stereo, kTestCaptureDurationMs);
+ }
+}
+
+TEST_F(CrasInputStreamTest, CaptureLoopback) {
+ CrasInputStream* test_stream =
+ CreateStream(CHANNEL_LAYOUT_STEREO, kTestFramesPerPacket,
+ AudioDeviceDescription::kLoopbackInputDeviceId);
+ EXPECT_EQ(test_stream->Open(), AudioInputStream::OpenOutcome::kSuccess);
+ test_stream->Close();
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/cras/cras_unified.cc b/third_party/chromium/media/audio/cras/cras_unified.cc
new file mode 100644
index 0000000..d4a5069
--- /dev/null
+++ b/third_party/chromium/media/audio/cras/cras_unified.cc
@@ -0,0 +1,292 @@
+// Copyright 2013 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 "media/audio/cras/cras_unified.h"
+
+#include <algorithm>
+
+#include "base/cxx17_backports.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "media/audio/cras/audio_manager_cras_base.h"
+
+namespace media {
+
+namespace {
+
+int GetDevicePin(AudioManagerCrasBase* manager, const std::string& device_id) {
+ if (!manager->IsDefault(device_id, false)) {
+ uint64_t cras_node_id;
+ base::StringToUint64(device_id, &cras_node_id);
+ return dev_index_of(cras_node_id);
+ }
+ return NO_DEVICE;
+}
+
+} // namespace
+
+// Overview of operation:
+// 1) An object of CrasUnifiedStream is created by the AudioManager
+// factory: audio_man->MakeAudioStream().
+// 2) Next some thread will call Open(), at that point a client is created and
+// configured for the correct format and sample rate.
+// 3) Then Start(source) is called and a stream is added to the CRAS client
+// which will create its own thread that periodically calls the source for more
+// data as buffers are being consumed.
+// 4) When finished Stop() is called, which is handled by stopping the stream.
+// 5) Finally Close() is called. It cleans up and notifies the audio manager,
+// which likely will destroy this object.
+//
+// Simplified data flow for output only streams:
+//
+// +-------------+ +------------------+
+// | CRAS Server | | Chrome Client |
+// +------+------+ Add Stream +---------+--------+
+// |<----------------------------------|
+// | |
+// | Near out of samples, request more |
+// |---------------------------------->|
+// | | UnifiedCallback()
+// | | WriteAudio()
+// | |
+// | buffer_frames written to shm |
+// |<----------------------------------|
+// | |
+// ... Repeats for each block. ...
+// | |
+// | |
+// | Remove stream |
+// |<----------------------------------|
+// | |
+//
+// For Unified streams the Chrome client is notified whenever buffer_frames have
+// been captured. For Output streams the client is notified a few milliseconds
+// before the hardware buffer underruns and fills the buffer with another block
+// of audio.
+
+CrasUnifiedStream::CrasUnifiedStream(const AudioParameters& params,
+ AudioManagerCrasBase* manager,
+ const std::string& device_id)
+ : client_(NULL),
+ stream_id_(0),
+ params_(params),
+ is_playing_(false),
+ volume_(1.0),
+ manager_(manager),
+ source_callback_(NULL),
+ output_bus_(AudioBus::Create(params)),
+ stream_direction_(CRAS_STREAM_OUTPUT),
+ pin_device_(GetDevicePin(manager, device_id)) {
+ DCHECK(manager_);
+ DCHECK_GT(params_.channels(), 0);
+}
+
+CrasUnifiedStream::~CrasUnifiedStream() {
+ DCHECK(!is_playing_);
+}
+
+bool CrasUnifiedStream::Open() {
+ // Sanity check input values.
+ if (params_.sample_rate() <= 0) {
+ LOG(WARNING) << "Unsupported audio frequency.";
+ return false;
+ }
+
+ // Create the client and connect to the CRAS server.
+ client_ = libcras_client_create();
+ if (!client_) {
+ LOG(WARNING) << "Couldn't create CRAS client.\n";
+ client_ = NULL;
+ return false;
+ }
+
+ if (libcras_client_connect(client_)) {
+ LOG(WARNING) << "Couldn't connect CRAS client.\n";
+ libcras_client_destroy(client_);
+ client_ = NULL;
+ return false;
+ }
+
+ // Then start running the client.
+ if (libcras_client_run_thread(client_)) {
+ LOG(WARNING) << "Couldn't run CRAS client.\n";
+ libcras_client_destroy(client_);
+ client_ = NULL;
+ return false;
+ }
+
+ return true;
+}
+
+void CrasUnifiedStream::Close() {
+ if (client_) {
+ libcras_client_stop(client_);
+ libcras_client_destroy(client_);
+ client_ = NULL;
+ }
+
+ // Signal to the manager that we're closed and can be removed.
+ // Should be last call in the method as it deletes "this".
+ manager_->ReleaseOutputStream(this);
+}
+
+// This stream is always used with sub second buffer sizes, where it's
+// sufficient to simply always flush upon Start().
+void CrasUnifiedStream::Flush() {}
+
+void CrasUnifiedStream::Start(AudioSourceCallback* callback) {
+ CHECK(callback);
+
+ // Channel map to CRAS_CHANNEL, values in the same order of
+ // corresponding source in Chromium defined Channels.
+ static const int kChannelMap[] = {
+ CRAS_CH_FL,
+ CRAS_CH_FR,
+ CRAS_CH_FC,
+ CRAS_CH_LFE,
+ CRAS_CH_RL,
+ CRAS_CH_RR,
+ CRAS_CH_FLC,
+ CRAS_CH_FRC,
+ CRAS_CH_RC,
+ CRAS_CH_SL,
+ CRAS_CH_SR
+ };
+
+ source_callback_ = callback;
+
+ // Only start if we can enter the playing state.
+ if (is_playing_)
+ return;
+
+ struct libcras_stream_params* stream_params = libcras_stream_params_create();
+ if (!stream_params) {
+ DLOG(ERROR) << "Error creating stream params.";
+ callback->OnError(AudioSourceCallback::ErrorType::kUnknown);
+ }
+
+ unsigned int frames_per_packet = params_.frames_per_buffer();
+ int rc = libcras_stream_params_set(
+ stream_params, stream_direction_, frames_per_packet * 2,
+ frames_per_packet, CRAS_STREAM_TYPE_DEFAULT, manager_->GetClientType(), 0,
+ this, CrasUnifiedStream::UnifiedCallback, CrasUnifiedStream::StreamError,
+ params_.sample_rate(), SND_PCM_FORMAT_S16, params_.channels());
+
+ if (rc) {
+ LOG(WARNING) << "Error setting up stream parameters.";
+ callback->OnError(AudioSourceCallback::ErrorType::kUnknown);
+ libcras_stream_params_destroy(stream_params);
+ return;
+ }
+
+ // Initialize channel layout to all -1 to indicate that none of
+ // the channels is set in the layout.
+ int8_t layout[CRAS_CH_MAX] = {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1};
+
+ // Converts to CRAS defined channels. ChannelOrder will return -1
+ // for channels that does not present in params_.channel_layout().
+ for (size_t i = 0; i < base::size(kChannelMap); ++i)
+ layout[kChannelMap[i]] = ChannelOrder(params_.channel_layout(),
+ static_cast<Channels>(i));
+
+ rc = libcras_stream_params_set_channel_layout(stream_params, CRAS_CH_MAX,
+ layout);
+ if (rc) {
+ DLOG(WARNING) << "Error setting up the channel layout.";
+ callback->OnError(AudioSourceCallback::ErrorType::kUnknown);
+ libcras_stream_params_destroy(stream_params);
+ return;
+ }
+
+ // Adding the stream will start the audio callbacks requesting data.
+ if (libcras_client_add_pinned_stream(client_, pin_device_, &stream_id_,
+ stream_params)) {
+ LOG(WARNING) << "Failed to add the stream.";
+ callback->OnError(AudioSourceCallback::ErrorType::kUnknown);
+ libcras_stream_params_destroy(stream_params);
+ return;
+ }
+
+ // Set initial volume.
+ libcras_client_set_stream_volume(client_, stream_id_, volume_);
+
+ // Done with config params.
+ libcras_stream_params_destroy(stream_params);
+
+ is_playing_ = true;
+}
+
+void CrasUnifiedStream::Stop() {
+ if (!client_)
+ return;
+
+ // Removing the stream from the client stops audio.
+ libcras_client_rm_stream(client_, stream_id_);
+
+ is_playing_ = false;
+}
+
+void CrasUnifiedStream::SetVolume(double volume) {
+ if (!client_)
+ return;
+ volume_ = static_cast<float>(volume);
+ libcras_client_set_stream_volume(client_, stream_id_, volume_);
+}
+
+void CrasUnifiedStream::GetVolume(double* volume) {
+ *volume = volume_;
+}
+
+// Static callback asking for samples.
+int CrasUnifiedStream::UnifiedCallback(struct libcras_stream_cb_data* data) {
+ unsigned int frames;
+ uint8_t* buf;
+ struct timespec latency;
+ void* usr_arg;
+ libcras_stream_cb_data_get_frames(data, &frames);
+ libcras_stream_cb_data_get_buf(data, &buf);
+ libcras_stream_cb_data_get_latency(data, &latency);
+ libcras_stream_cb_data_get_usr_arg(data, &usr_arg);
+ CrasUnifiedStream* me = static_cast<CrasUnifiedStream*>(usr_arg);
+ return me->WriteAudio(frames, buf, &latency);
+}
+
+// Static callback for stream errors.
+int CrasUnifiedStream::StreamError(cras_client* client,
+ cras_stream_id_t stream_id,
+ int err,
+ void* arg) {
+ CrasUnifiedStream* me = static_cast<CrasUnifiedStream*>(arg);
+ me->NotifyStreamError(err);
+ return 0;
+}
+
+uint32_t CrasUnifiedStream::WriteAudio(size_t frames,
+ uint8_t* buffer,
+ const timespec* latency_ts) {
+ DCHECK_EQ(frames, static_cast<size_t>(output_bus_->frames()));
+
+ // Treat negative latency (if we are too slow to render) as 0.
+ const base::TimeDelta delay =
+ std::max(base::TimeDelta::FromTimeSpec(*latency_ts), base::TimeDelta());
+
+ int frames_filled = source_callback_->OnMoreData(
+ delay, base::TimeTicks::Now(), 0, output_bus_.get());
+
+ // Note: If this ever changes to output raw float the data must be clipped and
+ // sanitized since it may come from an untrusted source such as NaCl.
+ output_bus_->ToInterleaved<SignedInt16SampleTypeTraits>(
+ frames_filled, reinterpret_cast<int16_t*>(buffer));
+
+ return frames_filled;
+}
+
+void CrasUnifiedStream::NotifyStreamError(int err) {
+ // This will remove the stream from the client.
+ // TODO(dalecurtis): Consider sending a translated |err| code.
+ if (source_callback_)
+ source_callback_->OnError(AudioSourceCallback::ErrorType::kUnknown);
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/cras/cras_unified.h b/third_party/chromium/media/audio/cras/cras_unified.h
new file mode 100644
index 0000000..9aeca27
--- /dev/null
+++ b/third_party/chromium/media/audio/cras/cras_unified.h
@@ -0,0 +1,108 @@
+// Copyright 2013 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.
+//
+// Creates a unified stream based on the cras (ChromeOS audio server) interface.
+//
+// CrasUnifiedStream object is *not* thread-safe and should only be used
+// from the audio thread.
+
+#ifndef MEDIA_AUDIO_CRAS_CRAS_UNIFIED_H_
+#define MEDIA_AUDIO_CRAS_CRAS_UNIFIED_H_
+
+#include <cras_client.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "media/audio/audio_io.h"
+#include "media/base/audio_parameters.h"
+
+namespace media {
+
+class AudioManagerCrasBase;
+
+// Implementation of AudioOuputStream for Chrome OS using the Chrome OS audio
+// server.
+// TODO(dgreid): This class is used for only output, either remove all the
+// relevant input code and change the class to CrasOutputStream or merge
+// cras_input.cc into this unified implementation.
+class MEDIA_EXPORT CrasUnifiedStream : public AudioOutputStream {
+ public:
+ // The ctor takes all the usual parameters, plus |manager| which is the
+ // audio manager who is creating this object.
+ CrasUnifiedStream(const AudioParameters& params,
+ AudioManagerCrasBase* manager,
+ const std::string& device_id);
+
+ CrasUnifiedStream(const CrasUnifiedStream&) = delete;
+ CrasUnifiedStream& operator=(const CrasUnifiedStream&) = delete;
+
+ // The dtor is typically called by the AudioManager only and it is usually
+ // triggered by calling AudioUnifiedStream::Close().
+ ~CrasUnifiedStream() override;
+
+ // Implementation of AudioOutputStream.
+ bool Open() override;
+ void Close() override;
+ void Flush() override;
+ void Start(AudioSourceCallback* callback) override;
+ void Stop() override;
+ void SetVolume(double volume) override;
+ void GetVolume(double* volume) override;
+
+ private:
+ // Handles captured audio and fills the output with audio to be played.
+ static int UnifiedCallback(struct libcras_stream_cb_data* data);
+
+ // Handles notification that there was an error with the playback stream.
+ static int StreamError(cras_client* client,
+ cras_stream_id_t stream_id,
+ int err,
+ void* arg);
+
+ // Writes audio for a playback stream.
+ uint32_t WriteAudio(size_t frames,
+ uint8_t* buffer,
+ const timespec* latency_ts);
+
+ // Deals with an error that occured in the stream. Called from StreamError().
+ void NotifyStreamError(int err);
+
+ // The client used to communicate with the audio server.
+ struct libcras_client* client_;
+
+ // ID of the playing stream.
+ cras_stream_id_t stream_id_;
+
+ // PCM parameters for the stream.
+ AudioParameters params_;
+
+ // True if stream is playing.
+ bool is_playing_;
+
+ // Volume level from 0.0 to 1.0.
+ float volume_;
+
+ // Audio manager that created us. Used to report that we've been closed.
+ AudioManagerCrasBase* manager_;
+
+ // Callback to get audio samples.
+ AudioSourceCallback* source_callback_;
+
+ // Container for exchanging data with AudioSourceCallback::OnMoreData().
+ std::unique_ptr<AudioBus> output_bus_;
+
+ // Direciton of the stream.
+ CRAS_STREAM_DIRECTION stream_direction_;
+
+ // Index of the CRAS device to stream output to.
+ const int pin_device_;
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_CRAS_CRAS_UNIFIED_H_
diff --git a/third_party/chromium/media/audio/cras/cras_unified_unittest.cc b/third_party/chromium/media/audio/cras/cras_unified_unittest.cc
new file mode 100644
index 0000000..cd0c366
--- /dev/null
+++ b/third_party/chromium/media/audio/cras/cras_unified_unittest.cc
@@ -0,0 +1,160 @@
+// Copyright 2013 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 <stdint.h>
+
+#include <memory>
+#include <string>
+
+#include "ash/components/audio/cras_audio_handler.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/test/test_message_loop.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
+#include "chromeos/dbus/audio/cras_audio_client.h"
+#include "media/audio/audio_device_description.h"
+#include "media/audio/cras/audio_manager_chromeos.h"
+#include "media/audio/fake_audio_log_factory.h"
+#include "media/audio/mock_audio_source_callback.h"
+#include "media/audio/test_audio_thread.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// cras_util.h defines custom min/max macros which break compilation, so ensure
+// it's not included until last. #if avoids presubmit errors.
+#if defined(USE_CRAS)
+#include "media/audio/cras/cras_unified.h"
+#endif
+
+using testing::_;
+using testing::DoAll;
+using testing::InvokeWithoutArgs;
+using testing::Return;
+using testing::SetArgPointee;
+using testing::StrictMock;
+
+namespace media {
+
+class MockAudioManagerCras : public AudioManagerChromeOS {
+ public:
+ MockAudioManagerCras()
+ : AudioManagerChromeOS(std::make_unique<TestAudioThread>(),
+ &fake_audio_log_factory_) {}
+
+ // We need to override this function in order to skip the checking the number
+ // of active output streams. It is because the number of active streams
+ // is managed inside MakeAudioOutputStream, and we don't use
+ // MakeAudioOutputStream to create the stream in the tests.
+ void ReleaseOutputStream(AudioOutputStream* stream) override {
+ DCHECK(stream);
+ delete stream;
+ }
+
+ private:
+ FakeAudioLogFactory fake_audio_log_factory_;
+};
+
+class CrasUnifiedStreamTest : public testing::Test {
+ protected:
+ CrasUnifiedStreamTest() {
+ chromeos::CrasAudioClient::InitializeFake();
+ ash::CrasAudioHandler::InitializeForTesting();
+ mock_manager_.reset(new StrictMock<MockAudioManagerCras>());
+ base::RunLoop().RunUntilIdle();
+ }
+
+ ~CrasUnifiedStreamTest() override {
+ mock_manager_->Shutdown();
+ ash::CrasAudioHandler::Shutdown();
+ chromeos::CrasAudioClient::Shutdown();
+ }
+
+ CrasUnifiedStream* CreateStream(ChannelLayout layout) {
+ return CreateStream(layout, kTestFramesPerPacket);
+ }
+
+ CrasUnifiedStream* CreateStream(ChannelLayout layout,
+ int32_t samples_per_packet) {
+ AudioParameters params(kTestFormat, layout, kTestSampleRate,
+ samples_per_packet);
+ return new CrasUnifiedStream(params, mock_manager_.get(),
+ AudioDeviceDescription::kDefaultDeviceId);
+ }
+
+ MockAudioManagerCras& mock_manager() {
+ return *(mock_manager_.get());
+ }
+
+ static const ChannelLayout kTestChannelLayout;
+ static const int kTestSampleRate;
+ static const AudioParameters::Format kTestFormat;
+ static const uint32_t kTestFramesPerPacket;
+
+ base::TestMessageLoop message_loop_;
+ std::unique_ptr<StrictMock<MockAudioManagerCras>> mock_manager_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CrasUnifiedStreamTest);
+};
+
+const ChannelLayout CrasUnifiedStreamTest::kTestChannelLayout =
+ CHANNEL_LAYOUT_STEREO;
+const int CrasUnifiedStreamTest::kTestSampleRate =
+ AudioParameters::kAudioCDSampleRate;
+const AudioParameters::Format CrasUnifiedStreamTest::kTestFormat =
+ AudioParameters::AUDIO_PCM_LINEAR;
+const uint32_t CrasUnifiedStreamTest::kTestFramesPerPacket = 1000;
+
+TEST_F(CrasUnifiedStreamTest, ConstructedState) {
+ CrasUnifiedStream* test_stream = CreateStream(kTestChannelLayout);
+ EXPECT_TRUE(test_stream->Open());
+ test_stream->Close();
+
+ // Should support mono.
+ test_stream = CreateStream(CHANNEL_LAYOUT_MONO);
+ EXPECT_TRUE(test_stream->Open());
+ test_stream->Close();
+
+ // Should support multi-channel.
+ test_stream = CreateStream(CHANNEL_LAYOUT_SURROUND);
+ EXPECT_TRUE(test_stream->Open());
+ test_stream->Close();
+
+ // Bad sample rate.
+ AudioParameters bad_rate_params(kTestFormat, kTestChannelLayout, 0,
+ kTestFramesPerPacket);
+ test_stream = new CrasUnifiedStream(bad_rate_params, mock_manager_.get(),
+ AudioDeviceDescription::kDefaultDeviceId);
+ EXPECT_FALSE(test_stream->Open());
+ test_stream->Close();
+}
+
+TEST_F(CrasUnifiedStreamTest, RenderFrames) {
+ CrasUnifiedStream* test_stream = CreateStream(CHANNEL_LAYOUT_MONO);
+ MockAudioSourceCallback mock_callback;
+
+ ASSERT_TRUE(test_stream->Open());
+
+ base::WaitableEvent event(base::WaitableEvent::ResetPolicy::AUTOMATIC,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+
+ EXPECT_CALL(mock_callback, OnMoreData(_, _, 0, _))
+ .WillRepeatedly(
+ DoAll(InvokeWithoutArgs(&event, &base::WaitableEvent::Signal),
+ Return(kTestFramesPerPacket)));
+
+ test_stream->Start(&mock_callback);
+
+ // Wait for samples to be captured.
+ EXPECT_TRUE(event.TimedWait(TestTimeouts::action_timeout()));
+
+ test_stream->Stop();
+
+ test_stream->Close();
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/cras/cras_util.cc b/third_party/chromium/media/audio/cras/cras_util.cc
new file mode 100644
index 0000000..2198db3
--- /dev/null
+++ b/third_party/chromium/media/audio/cras/cras_util.cc
@@ -0,0 +1,270 @@
+// Copyright 2020 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 "media/audio/cras/cras_util.h"
+
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/time/time.h"
+#include "media/audio/audio_device_description.h"
+#include "media/audio/cras/audio_manager_cras_base.h"
+
+namespace media {
+
+namespace {
+
+constexpr char kInternalInputVirtualDevice[] = "Built-in mic";
+constexpr char kInternalOutputVirtualDevice[] = "Built-in speaker";
+constexpr char kHeadphoneLineOutVirtualDevice[] = "Headphone/Line Out";
+
+// Names below are from the node_type_to_str function in CRAS server.
+// https://chromium.googlesource.com/chromiumos/third_party/adhd/+/refs/heads/main/cras/src/server/cras_iodev_list.c
+constexpr char kInternalSpeaker[] = "INTERNAL_SPEAKER";
+constexpr char kHeadphone[] = "HEADPHONE";
+constexpr char kHDMI[] = "HDMI";
+constexpr char kLineout[] = "LINEOUT";
+constexpr char kMic[] = "MIC";
+constexpr char kInternalMic[] = "INTERNAL_MIC";
+constexpr char kFrontMic[] = "FRONT_MIC";
+constexpr char kRearMic[] = "REAR_MIC";
+constexpr char kKeyBoardMic[] = "KEYBOARD_MIC";
+constexpr char kBluetoothNBMic[] = "BLUETOOTH_NB_MIC";
+constexpr char kUSB[] = "USB";
+constexpr char kBluetooth[] = "BLUETOOTH";
+constexpr char kAlsaLoopback[] = "ALSA_LOOPBACK";
+
+// Returns if that an input or output audio device is for simple usage like
+// playback or recording for user. In contrast, audio device such as loopback,
+// always on keyword recognition (HOTWORD), and keyboard mic are not for simple
+// usage.
+// One special case is ALSA loopback device, which will only exist under
+// testing. We want it visible to users for e2e tests.
+bool IsForSimpleUsage(std::string type) {
+ return type == kInternalMic || type == kHeadphone || type == kHDMI ||
+ type == kLineout || type == kMic || type == kInternalMic ||
+ type == kFrontMic || type == kRearMic || type == kBluetoothNBMic ||
+ type == kUSB || type == kBluetooth || type == kAlsaLoopback;
+}
+
+bool IsInternalMic(std::string type) {
+ return type == kInternalMic || type == kFrontMic || type == kRearMic;
+}
+
+// Connects to the CRAS server.
+libcras_client* CrasConnect() {
+ libcras_client* client;
+
+ client = libcras_client_create();
+ if (!client) {
+ LOG(ERROR) << "Couldn't create CRAS client.\n";
+ return nullptr;
+ }
+ if (libcras_client_connect(client)) {
+ LOG(ERROR) << "Couldn't connect CRAS client.\n";
+ libcras_client_destroy(client);
+ return nullptr;
+ }
+ return client;
+}
+
+// Disconnects from the CRAS server.
+void CrasDisconnect(libcras_client** client) {
+ if (*client) {
+ libcras_client_stop(*client);
+ libcras_client_destroy(*client);
+ *client = nullptr;
+ }
+}
+
+} // namespace
+
+CrasDevice::CrasDevice() = default;
+
+CrasDevice::CrasDevice(struct libcras_node_info* node, DeviceType type)
+ : type(type) {
+ int rc;
+ rc = libcras_node_info_get_id(node, &id);
+ if (rc) {
+ LOG(ERROR) << "Failed to get the node id: " << rc;
+ id = 0;
+ }
+
+ rc = libcras_node_info_get_dev_idx(node, &dev_idx);
+ if (rc) {
+ LOG(ERROR) << "Failed to get the dev idx: " << rc;
+ dev_idx = 0;
+ }
+
+ rc = libcras_node_info_is_plugged(node, &plugged);
+ if (rc) {
+ LOG(ERROR) << "Failed to get if the node is plugged: " << rc;
+ plugged = false;
+ }
+
+ rc = libcras_node_info_is_active(node, &active);
+ if (rc) {
+ LOG(ERROR) << "Failed to get if the node is active: " << rc;
+ active = false;
+ }
+
+ char* type_str;
+ rc = libcras_node_info_get_type(node, &type_str);
+ if (rc) {
+ LOG(ERROR) << "Failed to get the node type: " << rc;
+ node_type = nullptr;
+ }
+ node_type = type_str;
+
+ char* node_name;
+ rc = libcras_node_info_get_node_name(node, &node_name);
+ if (rc) {
+ LOG(ERROR) << "Failed to get the node name: " << rc;
+ node_name = nullptr;
+ }
+
+ char* device_name;
+ rc = libcras_node_info_get_dev_name(node, &device_name);
+ if (rc) {
+ LOG(ERROR) << "Failed to get the dev name: " << rc;
+ device_name = nullptr;
+ }
+
+ name = std::string(node_name);
+ if (name.empty() || name == "(default)")
+ name = device_name;
+ dev_name = device_name;
+}
+
+void mergeDevices(CrasDevice& old_dev, CrasDevice& new_dev) {
+ if (old_dev.node_type == kLineout || new_dev.node_type == kLineout) {
+ old_dev.name = kHeadphoneLineOutVirtualDevice;
+ old_dev.node_type = "";
+ } else if (old_dev.node_type == kInternalSpeaker ||
+ new_dev.node_type == kInternalSpeaker) {
+ old_dev.name = kInternalOutputVirtualDevice;
+ old_dev.node_type = "";
+ } else if (IsInternalMic(old_dev.node_type) ||
+ IsInternalMic(new_dev.node_type)) {
+ old_dev.name = kInternalInputVirtualDevice;
+ old_dev.node_type = "";
+ } else {
+ LOG(WARNING) << "Failed to create virtual device for " << old_dev.name;
+ }
+ old_dev.active |= new_dev.active;
+}
+
+std::vector<CrasDevice> CrasGetAudioDevices(DeviceType type) {
+ std::vector<CrasDevice> devices;
+
+ libcras_client* client = CrasConnect();
+ if (!client)
+ return devices;
+
+ int rc;
+
+ struct libcras_node_info** nodes;
+ size_t num_nodes;
+
+ if (type == DeviceType::kInput) {
+ rc =
+ libcras_client_get_nodes(client, CRAS_STREAM_INPUT, &nodes, &num_nodes);
+ } else {
+ rc = libcras_client_get_nodes(client, CRAS_STREAM_OUTPUT, &nodes,
+ &num_nodes);
+ }
+
+ if (rc < 0) {
+ LOG(ERROR) << "Failed to get devices: " << std::strerror(rc);
+ CrasDisconnect(&client);
+ return devices;
+ }
+
+ for (size_t i = 0; i < num_nodes; i++) {
+ auto new_dev = CrasDevice(nodes[i], type);
+ if (!new_dev.plugged || !IsForSimpleUsage(new_dev.node_type))
+ continue;
+ bool added = false;
+ for (auto& dev : devices) {
+ if (dev.dev_idx == new_dev.dev_idx) {
+ mergeDevices(dev, new_dev);
+ added = true;
+ break;
+ }
+ }
+ if (!added)
+ devices.emplace_back(new_dev);
+ }
+
+ libcras_node_info_array_destroy(nodes, num_nodes);
+
+ CrasDisconnect(&client);
+ return devices;
+}
+
+bool CrasHasKeyboardMic() {
+ libcras_client* client = CrasConnect();
+ if (!client)
+ return false;
+
+ struct libcras_node_info** nodes;
+ size_t num_nodes;
+ int rc =
+ libcras_client_get_nodes(client, CRAS_STREAM_INPUT, &nodes, &num_nodes);
+ int ret = false;
+
+ if (rc < 0) {
+ LOG(ERROR) << "Failed to get devices: " << std::strerror(rc);
+ CrasDisconnect(&client);
+ return false;
+ }
+
+ for (size_t i = 0; i < num_nodes; i++) {
+ auto device = CrasDevice(nodes[i], DeviceType::kInput);
+ if (device.node_type == kKeyBoardMic)
+ ret = true;
+ }
+
+ libcras_node_info_array_destroy(nodes, num_nodes);
+
+ CrasDisconnect(&client);
+ return ret;
+}
+
+int CrasGetAecSupported() {
+ libcras_client* client = CrasConnect();
+ if (!client)
+ return 0;
+
+ int supported;
+ libcras_client_get_aec_supported(client, &supported);
+ CrasDisconnect(&client);
+
+ return supported;
+}
+
+int CrasGetAecGroupId() {
+ libcras_client* client = CrasConnect();
+ if (!client)
+ return -1;
+
+ int id;
+ int rc = libcras_client_get_aec_group_id(client, &id);
+ CrasDisconnect(&client);
+
+ return rc < 0 ? rc : id;
+}
+
+int CrasGetDefaultOutputBufferSize() {
+ libcras_client* client = CrasConnect();
+ if (!client)
+ return -1;
+
+ int size;
+ int rc = libcras_client_get_default_output_buffer_size(client, &size);
+ CrasDisconnect(&client);
+
+ return rc < 0 ? rc : size;
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/cras/cras_util.h b/third_party/chromium/media/audio/cras/cras_util.h
new file mode 100644
index 0000000..14d1d8f
--- /dev/null
+++ b/third_party/chromium/media/audio/cras/cras_util.h
@@ -0,0 +1,50 @@
+// Copyright 2020 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 MEDIA_AUDIO_CRAS_CRAS_UTIL_H_
+#define MEDIA_AUDIO_CRAS_CRAS_UTIL_H_
+
+#include <cras_client.h>
+
+#include <cstdint>
+#include <string>
+#include <vector>
+
+namespace media {
+
+enum class DeviceType { kInput, kOutput };
+
+struct CrasDevice {
+ CrasDevice();
+ explicit CrasDevice(struct libcras_node_info* node, DeviceType type);
+
+ DeviceType type;
+ uint64_t id;
+ uint32_t dev_idx;
+ bool plugged;
+ bool active;
+ std::string node_type;
+ std::string name;
+ std::string dev_name;
+};
+
+// Enumerates all devices of |type|.
+std::vector<CrasDevice> CrasGetAudioDevices(DeviceType type);
+
+// Returns if there is a keyboard mic in CRAS.
+bool CrasHasKeyboardMic();
+
+// Returns if system AEC is supported in CRAS.
+int CrasGetAecSupported();
+
+// Returns the system AEC group ID. If no group ID is specified, -1 is
+// returned.
+int CrasGetAecGroupId();
+
+// Returns the default output buffer size.
+int CrasGetDefaultOutputBufferSize();
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_CRAS_CRAS_UTIL_H_
diff --git a/third_party/chromium/media/audio/fake_audio_input_stream.cc b/third_party/chromium/media/audio/fake_audio_input_stream.cc
new file mode 100644
index 0000000..7d50030
--- /dev/null
+++ b/third_party/chromium/media/audio/fake_audio_input_stream.cc
@@ -0,0 +1,219 @@
+// Copyright (c) 2012 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 "media/audio/fake_audio_input_stream.h"
+
+#include <memory>
+#include <string>
+
+#include "base/atomicops.h"
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/string_split.h"
+#include "base/synchronization/lock.h"
+#include "base/thread_annotations.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/thread.h"
+#include "base/time/time.h"
+#include "media/audio/audio_manager_base.h"
+#include "media/audio/simple_sources.h"
+#include "media/base/audio_bus.h"
+#include "media/base/audio_parameters.h"
+#include "media/base/media_switches.h"
+
+namespace media {
+
+namespace {
+base::subtle::AtomicWord g_fake_input_streams_are_muted = 0;
+}
+
+AudioInputStream* FakeAudioInputStream::MakeFakeStream(
+ AudioManagerBase* manager,
+ const AudioParameters& params) {
+ return new FakeAudioInputStream(manager, params);
+}
+
+FakeAudioInputStream::FakeAudioInputStream(AudioManagerBase* manager,
+ const AudioParameters& params)
+ : audio_manager_(manager),
+ callback_(nullptr),
+ params_(params),
+ audio_bus_(AudioBus::Create(params)),
+ capture_thread_(
+ nullptr,
+ base::OnTaskRunnerDeleter(manager->GetWorkerTaskRunner())) {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+}
+
+FakeAudioInputStream::~FakeAudioInputStream() {
+ // |worker_| should be null as Stop() should have been called before.
+ DCHECK(!capture_thread_);
+ DCHECK(!callback_);
+ DCHECK(!fake_audio_worker_);
+}
+
+AudioInputStream::OpenOutcome FakeAudioInputStream::Open() {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+ audio_bus_->Zero();
+
+ return OpenOutcome::kSuccess;
+}
+
+void FakeAudioInputStream::Start(AudioInputCallback* callback) {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+ DCHECK(!capture_thread_);
+ DCHECK(callback);
+ DCHECK(!fake_audio_worker_);
+
+ capture_thread_.reset(new base::Thread("FakeAudioInput"));
+ base::Thread::Options options;
+ // REALTIME_AUDIO priority is needed to avoid audio playout delays.
+ // See crbug.com/971265
+ options.priority = base::ThreadPriority::REALTIME_AUDIO;
+ CHECK(capture_thread_->StartWithOptions(std::move(options)));
+
+ {
+ base::AutoLock lock(callback_lock_);
+ DCHECK(!callback_);
+ callback_ = callback;
+ }
+
+ fake_audio_worker_ = std::make_unique<FakeAudioWorker>(
+ capture_thread_->task_runner(), params_);
+ fake_audio_worker_->Start(base::BindRepeating(
+ &FakeAudioInputStream::ReadAudioFromSource, base::Unretained(this)));
+}
+
+void FakeAudioInputStream::Stop() {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+ // Start has not been called yet.
+ if (!capture_thread_) {
+ return;
+ }
+
+ {
+ base::AutoLock lock(callback_lock_);
+ DCHECK(callback_);
+ callback_ = nullptr;
+ }
+
+ DCHECK(fake_audio_worker_);
+ fake_audio_worker_->Stop();
+ fake_audio_worker_.reset();
+
+ capture_thread_.reset();
+}
+
+void FakeAudioInputStream::Close() {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+ Stop();
+ audio_manager_->ReleaseInputStream(this);
+}
+
+double FakeAudioInputStream::GetMaxVolume() {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+ return 1.0;
+}
+
+void FakeAudioInputStream::SetVolume(double volume) {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+}
+
+double FakeAudioInputStream::GetVolume() {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+ return 1.0;
+}
+
+bool FakeAudioInputStream::IsMuted() {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+ return base::subtle::NoBarrier_Load(&g_fake_input_streams_are_muted) != 0;
+}
+
+bool FakeAudioInputStream::SetAutomaticGainControl(bool enabled) {
+ return false;
+}
+
+bool FakeAudioInputStream::GetAutomaticGainControl() {
+ return false;
+}
+
+void FakeAudioInputStream::SetOutputDeviceForAec(
+ const std::string& output_device_id) {
+ // Not supported. Do nothing.
+}
+
+void FakeAudioInputStream::ReadAudioFromSource(base::TimeTicks ideal_time,
+ base::TimeTicks now) {
+ DCHECK(capture_thread_->task_runner()->BelongsToCurrentThread());
+
+ if (!audio_source_)
+ audio_source_ = ChooseSource();
+
+ // This OnMoreData()/OnData() timing would never happen in a real system:
+ //
+ // 1. Real AudioSources would never be asked to generate audio that should
+ // already be playing-out exactly at this very moment; they are asked to
+ // do so for audio to be played-out in the future.
+ // 2. Real AudioInputStreams could never provide audio that is striking a
+ // microphone element exactly at this very moment; they provide audio
+ // that happened in the recent past.
+ //
+ // However, it would be pointless to add a FIFO queue here to delay the signal
+ // in this "fake" implementation. So, just hack the timing and carry-on.
+ {
+ base::AutoLock lock(callback_lock_);
+ if (audio_bus_ && callback_) {
+ audio_source_->OnMoreData(base::TimeDelta(), ideal_time, 0,
+ audio_bus_.get());
+ callback_->OnData(audio_bus_.get(), ideal_time, 1.0);
+ }
+ }
+}
+
+using AudioSourceCallback = AudioOutputStream::AudioSourceCallback;
+std::unique_ptr<AudioSourceCallback> FakeAudioInputStream::ChooseSource() {
+ DCHECK(capture_thread_->task_runner()->BelongsToCurrentThread());
+
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kUseFileForFakeAudioCapture)) {
+ base::CommandLine::StringType switch_value =
+ base::CommandLine::ForCurrentProcess()->GetSwitchValueNative(
+ switches::kUseFileForFakeAudioCapture);
+ base::CommandLine::StringVector parameters =
+ base::SplitString(switch_value, FILE_PATH_LITERAL("%"),
+ base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+ CHECK(parameters.size() > 0) << "You must pass <file>[%noloop] to --"
+ << switches::kUseFileForFakeAudioCapture
+ << ".";
+ base::FilePath path_to_wav_file = base::FilePath(parameters[0]);
+ bool looping = true;
+ if (parameters.size() == 2) {
+ CHECK(parameters[1] == FILE_PATH_LITERAL("noloop"))
+ << "Unknown parameter " << parameters[1] << " to "
+ << switches::kUseFileForFakeAudioCapture << ".";
+ looping = false;
+ }
+ return std::make_unique<FileSource>(params_, path_to_wav_file, looping);
+ }
+ return std::make_unique<BeepingSource>(params_);
+}
+
+void FakeAudioInputStream::BeepOnce() {
+ BeepingSource::BeepOnce();
+}
+
+void FakeAudioInputStream::SetGlobalMutedState(bool is_muted) {
+ base::subtle::NoBarrier_Store(&g_fake_input_streams_are_muted,
+ (is_muted ? 1 : 0));
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/fake_audio_input_stream.h b/third_party/chromium/media/audio/fake_audio_input_stream.h
new file mode 100644
index 0000000..809edfb
--- /dev/null
+++ b/third_party/chromium/media/audio/fake_audio_input_stream.h
@@ -0,0 +1,91 @@
+// Copyright (c) 2012 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.
+//
+// A fake implementation of AudioInputStream, useful for testing purpose.
+
+#ifndef MEDIA_AUDIO_FAKE_AUDIO_INPUT_STREAM_H_
+#define MEDIA_AUDIO_FAKE_AUDIO_INPUT_STREAM_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/threading/thread.h"
+#include "media/audio/audio_io.h"
+#include "media/base/audio_parameters.h"
+#include "media/base/fake_audio_worker.h"
+
+namespace media {
+
+class AudioBus;
+class AudioManagerBase;
+
+// This class acts as a fake audio input stream. The default is to generate a
+// beeping sound unless --use-file-for-fake-audio-capture=<file> is specified,
+// in which case the indicated .wav file will be read and played into the
+// stream.
+class MEDIA_EXPORT FakeAudioInputStream : public AudioInputStream {
+ public:
+ static AudioInputStream* MakeFakeStream(AudioManagerBase* manager,
+ const AudioParameters& params);
+
+ OpenOutcome Open() override;
+ void Start(AudioInputCallback* callback) override;
+ void Stop() override;
+ void Close() override;
+ double GetMaxVolume() override;
+ void SetVolume(double volume) override;
+ double GetVolume() override;
+ bool IsMuted() override;
+ bool SetAutomaticGainControl(bool enabled) override;
+ bool GetAutomaticGainControl() override;
+ void SetOutputDeviceForAec(const std::string& output_device_id) override;
+
+ // Generate one beep sound. This method is called by FakeVideoCaptureDevice to
+ // test audio/video synchronization. This is a static method because
+ // FakeVideoCaptureDevice is disconnected from an audio device. This means
+ // only one instance of this class gets to respond, which is okay because we
+ // assume there's only one stream for this testing purpose. Furthermore this
+ // method will do nothing if --use-file-for-fake-audio-capture is specified
+ // since the input stream will be playing from a file instead of beeping.
+ // TODO(hclam): Make this non-static. To do this we'll need to fix
+ // crbug.com/159053 such that video capture device is aware of audio
+ // input stream.
+ static void BeepOnce();
+
+ // Set the muted state for _all_ FakeAudioInputStreams. The value is global,
+ // so it can be set before any FakeAudioInputStreams have been created.
+ static void SetGlobalMutedState(bool is_muted);
+
+ private:
+ FakeAudioInputStream(AudioManagerBase* manager,
+ const AudioParameters& params);
+ ~FakeAudioInputStream() override;
+
+ std::unique_ptr<AudioOutputStream::AudioSourceCallback> ChooseSource();
+ void ReadAudioFromSource(base::TimeTicks ideal_time, base::TimeTicks now);
+
+ AudioManagerBase* audio_manager_;
+ // |callback_| needs the lock as ReadAudioFromSource reads callback_
+ // on the capture thread, while callback_ is set on the audio thread.
+ base::Lock callback_lock_;
+ AudioInputCallback* callback_ GUARDED_BY(callback_lock_);
+ AudioParameters params_;
+
+ std::unique_ptr<FakeAudioWorker> fake_audio_worker_;
+ std::unique_ptr<AudioOutputStream::AudioSourceCallback> audio_source_;
+ std::unique_ptr<media::AudioBus> audio_bus_;
+ // We will delete the capture thread on the AudioManager worker task runner
+ // since the audio thread is the main UI thread on Mac.
+ std::unique_ptr<base::Thread, base::OnTaskRunnerDeleter> capture_thread_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeAudioInputStream);
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_FAKE_AUDIO_INPUT_STREAM_H_
diff --git a/third_party/chromium/media/audio/fake_audio_log_factory.cc b/third_party/chromium/media/audio/fake_audio_log_factory.cc
new file mode 100644
index 0000000..caf08f5
--- /dev/null
+++ b/third_party/chromium/media/audio/fake_audio_log_factory.cc
@@ -0,0 +1,35 @@
+// Copyright 2013 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 "media/audio/fake_audio_log_factory.h"
+
+#include <string>
+
+namespace media {
+
+class FakeAudioLogImpl : public AudioLog {
+ public:
+ FakeAudioLogImpl() = default;
+ ~FakeAudioLogImpl() override = default;
+ void OnCreated(const media::AudioParameters& params,
+ const std::string& device_id) override {}
+ void OnStarted() override {}
+ void OnStopped() override {}
+ void OnClosed() override {}
+ void OnError() override {}
+ void OnSetVolume(double volume) override {}
+ void OnProcessingStateChanged(const std::string& message) override {}
+ void OnLogMessage(const std::string& message) override {}
+};
+
+FakeAudioLogFactory::FakeAudioLogFactory() = default;
+FakeAudioLogFactory::~FakeAudioLogFactory() = default;
+
+std::unique_ptr<AudioLog> FakeAudioLogFactory::CreateAudioLog(
+ AudioComponent component,
+ int component_id) {
+ return std::make_unique<FakeAudioLogImpl>();
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/fake_audio_log_factory.h b/third_party/chromium/media/audio/fake_audio_log_factory.h
new file mode 100644
index 0000000..9e2eff8
--- /dev/null
+++ b/third_party/chromium/media/audio/fake_audio_log_factory.h
@@ -0,0 +1,32 @@
+// Copyright 2013 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 MEDIA_AUDIO_FAKE_AUDIO_LOG_FACTORY_H_
+#define MEDIA_AUDIO_FAKE_AUDIO_LOG_FACTORY_H_
+
+#include <memory>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "media/audio/audio_logging.h"
+#include "media/base/media_export.h"
+
+namespace media {
+
+// Creates stub AudioLog instances, for testing, which do nothing.
+class MEDIA_EXPORT FakeAudioLogFactory : public AudioLogFactory {
+ public:
+ FakeAudioLogFactory();
+
+ FakeAudioLogFactory(const FakeAudioLogFactory&) = delete;
+ FakeAudioLogFactory& operator=(const FakeAudioLogFactory&) = delete;
+
+ ~FakeAudioLogFactory() override;
+ std::unique_ptr<AudioLog> CreateAudioLog(AudioComponent component,
+ int component_id) override;
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_FAKE_AUDIO_LOG_FACTORY_H_
diff --git a/third_party/chromium/media/audio/fake_audio_manager.cc b/third_party/chromium/media/audio/fake_audio_manager.cc
new file mode 100644
index 0000000..cc6ea93
--- /dev/null
+++ b/third_party/chromium/media/audio/fake_audio_manager.cc
@@ -0,0 +1,87 @@
+// Copyright 2013 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 "media/audio/fake_audio_manager.h"
+
+#include <algorithm>
+#include <utility>
+
+namespace media {
+
+namespace {
+
+const int kDefaultInputBufferSize = 1024;
+const int kDefaultSampleRate = 48000;
+
+} // namespace
+
+FakeAudioManager::FakeAudioManager(std::unique_ptr<AudioThread> audio_thread,
+ AudioLogFactory* audio_log_factory)
+ : AudioManagerBase(std::move(audio_thread), audio_log_factory) {}
+
+FakeAudioManager::~FakeAudioManager() = default;
+
+// Implementation of AudioManager.
+bool FakeAudioManager::HasAudioOutputDevices() { return false; }
+
+bool FakeAudioManager::HasAudioInputDevices() { return false; }
+
+const char* FakeAudioManager::GetName() {
+ return "Fake";
+}
+
+// Implementation of AudioManagerBase.
+AudioOutputStream* FakeAudioManager::MakeLinearOutputStream(
+ const AudioParameters& params,
+ const LogCallback& log_callback) {
+ return FakeAudioOutputStream::MakeFakeStream(this, params);
+}
+
+AudioOutputStream* FakeAudioManager::MakeLowLatencyOutputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) {
+ return FakeAudioOutputStream::MakeFakeStream(this, params);
+}
+
+AudioInputStream* FakeAudioManager::MakeLinearInputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) {
+ return FakeAudioInputStream::MakeFakeStream(this, params);
+}
+
+AudioInputStream* FakeAudioManager::MakeLowLatencyInputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) {
+ return FakeAudioInputStream::MakeFakeStream(this, params);
+}
+
+AudioParameters FakeAudioManager::GetPreferredOutputStreamParameters(
+ const std::string& output_device_id,
+ const AudioParameters& input_params) {
+ static const int kDefaultOutputBufferSize = 2048;
+ static const int kDefaultSampleRate = 48000;
+ ChannelLayout channel_layout = CHANNEL_LAYOUT_STEREO;
+ int sample_rate = kDefaultSampleRate;
+ int buffer_size = kDefaultOutputBufferSize;
+ if (input_params.IsValid()) {
+ sample_rate = input_params.sample_rate();
+ channel_layout = input_params.channel_layout();
+ buffer_size = std::min(input_params.frames_per_buffer(), buffer_size);
+ }
+
+ return AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, channel_layout,
+ sample_rate, buffer_size);
+}
+
+AudioParameters FakeAudioManager::GetInputStreamParameters(
+ const std::string& device_id) {
+ return AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
+ CHANNEL_LAYOUT_STEREO, kDefaultSampleRate,
+ kDefaultInputBufferSize);
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/fake_audio_manager.h b/third_party/chromium/media/audio/fake_audio_manager.h
new file mode 100644
index 0000000..1979afa
--- /dev/null
+++ b/third_party/chromium/media/audio/fake_audio_manager.h
@@ -0,0 +1,59 @@
+// Copyright 2013 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 MEDIA_AUDIO_FAKE_AUDIO_MANAGER_H_
+#define MEDIA_AUDIO_FAKE_AUDIO_MANAGER_H_
+
+#include <string>
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "media/audio/audio_manager_base.h"
+#include "media/audio/fake_audio_input_stream.h"
+#include "media/audio/fake_audio_output_stream.h"
+
+namespace media {
+
+class MEDIA_EXPORT FakeAudioManager : public AudioManagerBase {
+ public:
+ FakeAudioManager(std::unique_ptr<AudioThread> audio_thread,
+ AudioLogFactory* audio_log_factory);
+
+ FakeAudioManager(const FakeAudioManager&) = delete;
+ FakeAudioManager& operator=(const FakeAudioManager&) = delete;
+
+ ~FakeAudioManager() override;
+
+ // Implementation of AudioManager.
+ bool HasAudioOutputDevices() override;
+ bool HasAudioInputDevices() override;
+ const char* GetName() override;
+
+ // Implementation of AudioManagerBase.
+ AudioOutputStream* MakeLinearOutputStream(
+ const AudioParameters& params,
+ const LogCallback& log_callback) override;
+ AudioOutputStream* MakeLowLatencyOutputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) override;
+ AudioInputStream* MakeLinearInputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) override;
+ AudioInputStream* MakeLowLatencyInputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) override;
+ AudioParameters GetInputStreamParameters(
+ const std::string& device_id) override;
+
+ protected:
+ AudioParameters GetPreferredOutputStreamParameters(
+ const std::string& output_device_id,
+ const AudioParameters& input_params) override;
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_FAKE_AUDIO_MANAGER_H_
diff --git a/third_party/chromium/media/audio/fake_audio_output_stream.cc b/third_party/chromium/media/audio/fake_audio_output_stream.cc
new file mode 100644
index 0000000..22282b3
--- /dev/null
+++ b/third_party/chromium/media/audio/fake_audio_output_stream.cc
@@ -0,0 +1,79 @@
+// Copyright (c) 2012 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 "media/audio/fake_audio_output_stream.h"
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/check.h"
+#include "base/single_thread_task_runner.h"
+#include "base/time/time.h"
+#include "media/audio/audio_manager_base.h"
+
+namespace media {
+
+// static
+AudioOutputStream* FakeAudioOutputStream::MakeFakeStream(
+ AudioManagerBase* manager, const AudioParameters& params) {
+ return new FakeAudioOutputStream(manager, params);
+}
+
+FakeAudioOutputStream::FakeAudioOutputStream(AudioManagerBase* manager,
+ const AudioParameters& params)
+ : audio_manager_(manager),
+ fixed_data_delay_(FakeAudioWorker::ComputeFakeOutputDelay(params)),
+ callback_(nullptr),
+ fake_worker_(manager->GetWorkerTaskRunner(), params),
+ audio_bus_(AudioBus::Create(params)) {}
+
+FakeAudioOutputStream::~FakeAudioOutputStream() {
+ DCHECK(!callback_);
+}
+
+bool FakeAudioOutputStream::Open() {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+ audio_bus_->Zero();
+ return true;
+}
+
+void FakeAudioOutputStream::Start(AudioSourceCallback* callback) {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+ callback_ = callback;
+ fake_worker_.Start(base::BindRepeating(&FakeAudioOutputStream::CallOnMoreData,
+ base::Unretained(this)));
+}
+
+void FakeAudioOutputStream::Stop() {
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+ fake_worker_.Stop();
+ callback_ = nullptr;
+}
+
+void FakeAudioOutputStream::Close() {
+ DCHECK(!callback_);
+ DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
+ audio_manager_->ReleaseOutputStream(this);
+}
+
+void FakeAudioOutputStream::Flush() {}
+
+void FakeAudioOutputStream::SetVolume(double volume) {}
+
+void FakeAudioOutputStream::GetVolume(double* volume) {
+ *volume = 0;
+}
+
+void FakeAudioOutputStream::CallOnMoreData(base::TimeTicks ideal_time,
+ base::TimeTicks now) {
+ DCHECK(audio_manager_->GetWorkerTaskRunner()->BelongsToCurrentThread());
+ // Real streams provide small tweaks to their delay values, alongside the
+ // current system time; and so the same is done here.
+ const auto delay =
+ fixed_data_delay_ + std::max(base::TimeDelta(), ideal_time - now);
+ callback_->OnMoreData(delay, now, 0, audio_bus_.get());
+}
+
+void FakeAudioOutputStream::SetMute(bool muted) {}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/fake_audio_output_stream.h b/third_party/chromium/media/audio/fake_audio_output_stream.h
new file mode 100644
index 0000000..3f97020
--- /dev/null
+++ b/third_party/chromium/media/audio/fake_audio_output_stream.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2012 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 MEDIA_AUDIO_FAKE_AUDIO_OUTPUT_STREAM_H_
+#define MEDIA_AUDIO_FAKE_AUDIO_OUTPUT_STREAM_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "media/audio/android/muteable_audio_output_stream.h"
+#include "media/audio/audio_io.h"
+#include "media/base/audio_parameters.h"
+#include "media/base/fake_audio_worker.h"
+
+namespace media {
+
+class AudioManagerBase;
+
+// A fake implementation of AudioOutputStream. Used for testing and when a real
+// audio output device is unavailable or refusing output (e.g. remote desktop).
+// Callbacks are driven on the AudioManager's message loop.
+class MEDIA_EXPORT FakeAudioOutputStream : public MuteableAudioOutputStream {
+ public:
+ static AudioOutputStream* MakeFakeStream(AudioManagerBase* manager,
+ const AudioParameters& params);
+
+ // AudioOutputStream implementation.
+ bool Open() override;
+ void Start(AudioSourceCallback* callback) override;
+ void Stop() override;
+ void SetVolume(double volume) override;
+ void GetVolume(double* volume) override;
+ void Close() override;
+ void Flush() override;
+ void SetMute(bool muted) override;
+
+ private:
+ FakeAudioOutputStream(AudioManagerBase* manager,
+ const AudioParameters& params);
+ ~FakeAudioOutputStream() override;
+
+ // Task that periodically calls OnMoreData() to consume audio data.
+ void CallOnMoreData(base::TimeTicks ideal_time, base::TimeTicks now);
+
+ AudioManagerBase* const audio_manager_;
+ const base::TimeDelta fixed_data_delay_;
+ AudioSourceCallback* callback_;
+ FakeAudioWorker fake_worker_;
+ const std::unique_ptr<AudioBus> audio_bus_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeAudioOutputStream);
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_FAKE_AUDIO_OUTPUT_STREAM_H_
diff --git a/third_party/chromium/media/audio/fuchsia/DIR_METADATA b/third_party/chromium/media/audio/fuchsia/DIR_METADATA
new file mode 100644
index 0000000..5b3985e
--- /dev/null
+++ b/third_party/chromium/media/audio/fuchsia/DIR_METADATA
@@ -0,0 +1,10 @@
+# Metadata information for this directory.
+#
+# For more information on DIR_METADATA files, see:
+# https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
+#
+# For the schema of this file, see Metadata message:
+# https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
+
+mixins: "//build/fuchsia/COMMON_METADATA"
+os: FUCHSIA
\ No newline at end of file
diff --git a/third_party/chromium/media/audio/fuchsia/OWNERS b/third_party/chromium/media/audio/fuchsia/OWNERS
new file mode 100644
index 0000000..e7034ea
--- /dev/null
+++ b/third_party/chromium/media/audio/fuchsia/OWNERS
@@ -0,0 +1 @@
+file://build/fuchsia/OWNERS
diff --git a/third_party/chromium/media/audio/fuchsia/audio_manager_fuchsia.cc b/third_party/chromium/media/audio/fuchsia/audio_manager_fuchsia.cc
new file mode 100644
index 0000000..4ff1fd9
--- /dev/null
+++ b/third_party/chromium/media/audio/fuchsia/audio_manager_fuchsia.cc
@@ -0,0 +1,143 @@
+// Copyright 2017 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 "media/audio/fuchsia/audio_manager_fuchsia.h"
+
+#include <memory>
+
+#include "base/command_line.h"
+#include "base/fuchsia/scheduler.h"
+#include "media/audio/fuchsia/audio_output_stream_fuchsia.h"
+#include "media/base/audio_timestamp_helper.h"
+#include "media/base/media_switches.h"
+
+namespace media {
+
+AudioManagerFuchsia::AudioManagerFuchsia(
+ std::unique_ptr<AudioThread> audio_thread,
+ AudioLogFactory* audio_log_factory)
+ : AudioManagerBase(std::move(audio_thread), audio_log_factory) {}
+
+AudioManagerFuchsia::~AudioManagerFuchsia() = default;
+
+bool AudioManagerFuchsia::HasAudioOutputDevices() {
+ // TODO(crbug.com/852834): Fuchsia currently doesn't provide an API for device
+ // enumeration. Update this method when that functionality is implemented.
+ return true;
+}
+
+bool AudioManagerFuchsia::HasAudioInputDevices() {
+ // TODO(crbug.com/852834): Fuchsia currently doesn't provide an API for device
+ // enumeration. Update this method when that functionality is implemented.
+ return true;
+}
+
+void AudioManagerFuchsia::GetAudioInputDeviceNames(
+ AudioDeviceNames* device_names) {
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kDisableAudioInput)) {
+ return;
+ }
+
+ // TODO(crbug.com/852834): Fuchsia currently doesn't provide an API for device
+ // enumeration. Update this method when that functionality is implemented.
+ *device_names = {AudioDeviceName::CreateDefault()};
+}
+
+void AudioManagerFuchsia::GetAudioOutputDeviceNames(
+ AudioDeviceNames* device_names) {
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kDisableAudioOutput)) {
+ return;
+ }
+
+ // TODO(crbug.com/852834): Fuchsia currently doesn't provide an API for device
+ // enumeration. Update this method when that functionality is implemented.
+ *device_names = {AudioDeviceName::CreateDefault()};
+}
+
+AudioParameters AudioManagerFuchsia::GetInputStreamParameters(
+ const std::string& device_id) {
+ // TODO(crbug.com/852834): Fuchsia currently doesn't provide an API to get
+ // device configuration and supported effects. Update this method when that
+ // functionality is implemented.
+ //
+ // Use 16kHz sample rate with 10ms buffer, which is consistent with
+ // the default configuration used in the AudioCapturer implementation.
+ // Assume that the system-provided AudioConsumer supports echo cancellation,
+ // noise suppression and automatic gain control.
+ const size_t kSampleRate = 16000;
+ const size_t kPeriodSamples = AudioTimestampHelper::TimeToFrames(
+ base::kAudioSchedulingPeriod, kSampleRate);
+ AudioParameters params(AudioParameters::AUDIO_PCM_LOW_LATENCY,
+ CHANNEL_LAYOUT_MONO, kSampleRate, kPeriodSamples);
+ params.set_effects(AudioParameters::ECHO_CANCELLER |
+ AudioParameters::NOISE_SUPPRESSION |
+ AudioParameters::AUTOMATIC_GAIN_CONTROL);
+
+ return params;
+}
+
+AudioParameters AudioManagerFuchsia::GetPreferredOutputStreamParameters(
+ const std::string& output_device_id,
+ const AudioParameters& input_params) {
+ // TODO(crbug.com/852834): Fuchsia currently doesn't provide an API to get
+ // device configuration. Update this method when that functionality is
+ // implemented.
+ const size_t kSampleRate = 48000;
+ const size_t kPeriodFrames = AudioTimestampHelper::TimeToFrames(
+ base::kAudioSchedulingPeriod, kSampleRate);
+ return AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
+ CHANNEL_LAYOUT_STEREO, kSampleRate, kPeriodFrames);
+}
+
+const char* AudioManagerFuchsia::GetName() {
+ return "Fuchsia";
+}
+
+AudioOutputStream* AudioManagerFuchsia::MakeLinearOutputStream(
+ const AudioParameters& params,
+ const LogCallback& log_callback) {
+ NOTREACHED();
+ return nullptr;
+}
+
+AudioOutputStream* AudioManagerFuchsia::MakeLowLatencyOutputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) {
+ DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format());
+
+ if (!device_id.empty() &&
+ device_id != AudioDeviceDescription::kDefaultDeviceId) {
+ return nullptr;
+ }
+
+ return new AudioOutputStreamFuchsia(this, params);
+}
+
+AudioInputStream* AudioManagerFuchsia::MakeLinearInputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) {
+ NOTREACHED();
+ return nullptr;
+}
+
+AudioInputStream* AudioManagerFuchsia::MakeLowLatencyInputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) {
+ NOTREACHED();
+ return nullptr;
+}
+
+std::unique_ptr<AudioManager> CreateAudioManager(
+ std::unique_ptr<AudioThread> audio_thread,
+ AudioLogFactory* audio_log_factory) {
+ return std::make_unique<AudioManagerFuchsia>(std::move(audio_thread),
+ audio_log_factory);
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/fuchsia/audio_manager_fuchsia.h b/third_party/chromium/media/audio/fuchsia/audio_manager_fuchsia.h
new file mode 100644
index 0000000..105aca4
--- /dev/null
+++ b/third_party/chromium/media/audio/fuchsia/audio_manager_fuchsia.h
@@ -0,0 +1,56 @@
+// Copyright 2017 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 MEDIA_AUDIO_FUCHSIA_AUDIO_MANAGER_FUCHSIA_H_
+#define MEDIA_AUDIO_FUCHSIA_AUDIO_MANAGER_FUCHSIA_H_
+
+#include "media/audio/audio_manager_base.h"
+
+namespace media {
+
+class AudioManagerFuchsia : public AudioManagerBase {
+ public:
+ AudioManagerFuchsia(std::unique_ptr<AudioThread> audio_thread,
+ AudioLogFactory* audio_log_factory);
+
+ AudioManagerFuchsia(const AudioManagerFuchsia&) = delete;
+ AudioManagerFuchsia& operator=(const AudioManagerFuchsia&) = delete;
+
+ ~AudioManagerFuchsia() override;
+
+ // Implementation of AudioManager.
+ bool HasAudioOutputDevices() override;
+ bool HasAudioInputDevices() override;
+ void GetAudioInputDeviceNames(AudioDeviceNames* device_names) override;
+ void GetAudioOutputDeviceNames(AudioDeviceNames* device_names) override;
+ AudioParameters GetInputStreamParameters(
+ const std::string& device_id) override;
+ const char* GetName() override;
+
+ // Implementation of AudioManagerBase.
+ AudioOutputStream* MakeLinearOutputStream(
+ const AudioParameters& params,
+ const LogCallback& log_callback) override;
+ AudioOutputStream* MakeLowLatencyOutputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) override;
+ AudioInputStream* MakeLinearInputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) override;
+ AudioInputStream* MakeLowLatencyInputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) override;
+
+ protected:
+ AudioParameters GetPreferredOutputStreamParameters(
+ const std::string& output_device_id,
+ const AudioParameters& input_params) override;
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_FUCHSIA_AUDIO_MANAGER_FUCHSIA_H_
diff --git a/third_party/chromium/media/audio/fuchsia/audio_output_stream_fuchsia.cc b/third_party/chromium/media/audio/fuchsia/audio_output_stream_fuchsia.cc
new file mode 100644
index 0000000..d872b08
--- /dev/null
+++ b/third_party/chromium/media/audio/fuchsia/audio_output_stream_fuchsia.cc
@@ -0,0 +1,301 @@
+// Copyright 2017 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 "media/audio/fuchsia/audio_output_stream_fuchsia.h"
+
+#include <lib/sys/cpp/component_context.h>
+#include <zircon/syscalls.h>
+
+#include "base/bind.h"
+#include "base/fuchsia/fuchsia_logging.h"
+#include "base/fuchsia/process_context.h"
+#include "base/logging.h"
+#include "base/memory/writable_shared_memory_region.h"
+#include "media/audio/fuchsia/audio_manager_fuchsia.h"
+#include "media/base/audio_sample_types.h"
+#include "media/base/audio_timestamp_helper.h"
+
+namespace media {
+
+namespace {
+
+// Current AudioRenderer implementation allows only one buffer with id=0.
+// TODO(crbug.com/1131179): Replace with an incrementing buffer id now that
+// AddPayloadBuffer() and RemovePayloadBuffer() are implemented properly in
+// AudioRenderer.
+const uint32_t kBufferId = 0;
+
+fuchsia::media::AudioRenderUsage GetStreamUsage(
+ const AudioParameters& parameters) {
+ if (parameters.latency_tag() == AudioLatency::LATENCY_RTC)
+ return fuchsia::media::AudioRenderUsage::COMMUNICATION;
+ return fuchsia::media::AudioRenderUsage::MEDIA;
+}
+
+} // namespace
+
+AudioOutputStreamFuchsia::AudioOutputStreamFuchsia(
+ AudioManagerFuchsia* manager,
+ const AudioParameters& parameters)
+ : manager_(manager),
+ parameters_(parameters),
+ audio_bus_(AudioBus::Create(parameters)) {}
+
+AudioOutputStreamFuchsia::~AudioOutputStreamFuchsia() {
+ // Close() must be called first.
+ DCHECK(!audio_renderer_);
+}
+
+bool AudioOutputStreamFuchsia::Open() {
+ DCHECK(!audio_renderer_);
+
+ // Connect |audio_renderer_| to the audio service.
+ fuchsia::media::AudioPtr audio_server =
+ base::ComponentContextForProcess()
+ ->svc()
+ ->Connect<fuchsia::media::Audio>();
+ audio_server->CreateAudioRenderer(audio_renderer_.NewRequest());
+ audio_renderer_.set_error_handler(
+ fit::bind_member(this, &AudioOutputStreamFuchsia::OnRendererError));
+
+ audio_renderer_->SetUsage(GetStreamUsage(parameters_));
+
+ // Inform the |audio_renderer_| of the format required by the caller.
+ fuchsia::media::AudioStreamType format;
+ format.sample_format = fuchsia::media::AudioSampleFormat::FLOAT;
+ format.channels = parameters_.channels();
+ format.frames_per_second = parameters_.sample_rate();
+ audio_renderer_->SetPcmStreamType(std::move(format));
+
+ // Use number of samples to specify media position.
+ audio_renderer_->SetPtsUnits(parameters_.sample_rate(), 1);
+
+ // Setup OnMinLeadTimeChanged event listener. This event is used to get
+ // |min_lead_time_|, which indicates how far ahead audio samples need to be
+ // sent to the renderer.
+ audio_renderer_.events().OnMinLeadTimeChanged =
+ fit::bind_member(this, &AudioOutputStreamFuchsia::OnMinLeadTimeChanged);
+ audio_renderer_->EnableMinLeadTimeEvents(true);
+
+ // The renderer may fail initialization asynchronously, which is handled in
+ // OnRendererError().
+ return true;
+}
+
+void AudioOutputStreamFuchsia::Start(AudioSourceCallback* callback) {
+ DCHECK(!callback_);
+ DCHECK(reference_time_.is_null());
+ DCHECK(!timer_.IsRunning());
+ callback_ = callback;
+
+ // Delay PumpSamples() until OnMinLeadTimeChanged is received and Pause() is
+ // not pending.
+ if (!min_lead_time_.has_value() || pause_pending_)
+ return;
+
+ PumpSamples();
+}
+
+void AudioOutputStreamFuchsia::Stop() {
+ callback_ = nullptr;
+ timer_.Stop();
+
+ // Nothing to do if playback is not started or being stopped.
+ if (reference_time_.is_null() || pause_pending_)
+ return;
+
+ reference_time_ = base::TimeTicks();
+ pause_pending_ = true;
+ audio_renderer_->Pause(
+ fit::bind_member(this, &AudioOutputStreamFuchsia::OnPauseComplete));
+ audio_renderer_->DiscardAllPacketsNoReply();
+}
+
+// This stream is always used with sub second buffer sizes, where it's
+// sufficient to simply always flush upon Start().
+void AudioOutputStreamFuchsia::Flush() {}
+
+void AudioOutputStreamFuchsia::SetVolume(double volume) {
+ DCHECK(0.0 <= volume && volume <= 1.0) << volume;
+ volume_ = volume;
+}
+
+void AudioOutputStreamFuchsia::GetVolume(double* volume) {
+ *volume = volume_;
+}
+
+void AudioOutputStreamFuchsia::Close() {
+ Stop();
+ audio_renderer_.Unbind();
+
+ // Signal to the manager that we're closed and can be removed. This should be
+ // the last call in the function as it deletes |this|.
+ manager_->ReleaseOutputStream(this);
+}
+
+base::TimeTicks AudioOutputStreamFuchsia::GetCurrentStreamTime() {
+ DCHECK(!reference_time_.is_null());
+ return reference_time_ +
+ AudioTimestampHelper::FramesToTime(stream_position_samples_,
+ parameters_.sample_rate());
+}
+
+size_t AudioOutputStreamFuchsia::GetMinBufferSize() {
+ // Ensure that |payload_buffer_| fits enough packets to cover min_lead_time_
+ // plus one extra packet.
+ int min_packets = (AudioTimestampHelper::TimeToFrames(
+ min_lead_time_.value(), parameters_.sample_rate()) +
+ parameters_.frames_per_buffer() - 1) /
+ parameters_.frames_per_buffer() +
+ 1;
+
+ return parameters_.GetBytesPerBuffer(kSampleFormatF32) * min_packets;
+}
+
+bool AudioOutputStreamFuchsia::InitializePayloadBuffer() {
+ size_t buffer_size = GetMinBufferSize();
+ auto region = base::WritableSharedMemoryRegion::Create(buffer_size);
+ payload_buffer_ = region.Map();
+ if (!payload_buffer_.IsValid()) {
+ LOG(WARNING) << "Failed to allocate VMO of size " << buffer_size;
+ return false;
+ }
+
+ payload_buffer_pos_ = 0;
+ audio_renderer_->AddPayloadBuffer(
+ kBufferId, base::WritableSharedMemoryRegion::TakeHandleForSerialization(
+ std::move(region))
+ .PassPlatformHandle());
+
+ return true;
+}
+
+void AudioOutputStreamFuchsia::OnMinLeadTimeChanged(int64_t min_lead_time) {
+ bool min_lead_time_was_unknown = !min_lead_time_.has_value();
+
+ min_lead_time_ = base::Nanoseconds(min_lead_time);
+
+ // When min_lead_time_ increases we may need to reallocate |payload_buffer_|.
+ // Code below just unmaps the current buffer. The new buffer will be allocated
+ // lated in PumpSamples(). This is necessary because VMO allocation may fail
+ // and it's not possible to report that error here - OnMinLeadTimeChanged()
+ // may be invoked before Start().
+ if (payload_buffer_.IsValid() &&
+ GetMinBufferSize() > payload_buffer_.size()) {
+ payload_buffer_ = {};
+
+ // Discard all packets currently in flight. This is required because
+ // AddPayloadBuffer() will fail if there are any packets in flight.
+ audio_renderer_->DiscardAllPacketsNoReply();
+ }
+
+ // If playback was started but we were waiting for MinLeadTime, then start
+ // pumping samples now.
+ if (is_started() && min_lead_time_was_unknown) {
+ DCHECK(!timer_.IsRunning());
+ PumpSamples();
+ }
+}
+
+void AudioOutputStreamFuchsia::OnRendererError(zx_status_t status) {
+ ZX_LOG(WARNING, status) << "AudioRenderer has failed";
+ ReportError();
+}
+
+void AudioOutputStreamFuchsia::ReportError() {
+ reference_time_ = base::TimeTicks();
+ timer_.Stop();
+ if (callback_)
+ callback_->OnError(AudioSourceCallback::ErrorType::kUnknown);
+}
+
+void AudioOutputStreamFuchsia::OnPauseComplete(int64_t reference_time,
+ int64_t media_time) {
+ DCHECK(pause_pending_);
+ pause_pending_ = false;
+
+ // If the stream was restarted while Pause() was pending then we can start
+ // pumping samples again.
+ if (is_started())
+ PumpSamples();
+}
+
+void AudioOutputStreamFuchsia::PumpSamples() {
+ DCHECK(is_started());
+ DCHECK(audio_renderer_);
+
+ // Allocate payload buffer if necessary.
+ if (!payload_buffer_.IsValid() && !InitializePayloadBuffer()) {
+ ReportError();
+ return;
+ }
+
+ base::TimeTicks now = base::TimeTicks::Now();
+
+ base::TimeDelta delay;
+ if (reference_time_.is_null()) {
+ delay = min_lead_time_.value() + parameters_.GetBufferDuration() / 2;
+ stream_position_samples_ = 0;
+ } else {
+ auto stream_time = GetCurrentStreamTime();
+
+ // Adjust stream position if we missed timer deadline.
+ if (now + min_lead_time_.value() > stream_time) {
+ stream_position_samples_ += AudioTimestampHelper::TimeToFrames(
+ now + min_lead_time_.value() - stream_time,
+ parameters_.sample_rate());
+ }
+
+ delay = stream_time - now;
+ }
+
+ // Request more samples from |callback_|.
+ int frames_filled = callback_->OnMoreData(delay, now, 0, audio_bus_.get());
+ DCHECK_EQ(frames_filled, audio_bus_->frames());
+
+ audio_bus_->Scale(volume_);
+
+ // Save samples to the |payload_buffer_|.
+ size_t packet_size = parameters_.GetBytesPerBuffer(kSampleFormatF32);
+ DCHECK_LE(payload_buffer_pos_ + packet_size, payload_buffer_.size());
+
+ // We skip clipping since that occurs at the shared memory boundary.
+ audio_bus_->ToInterleaved<Float32SampleTypeTraitsNoClip>(
+ audio_bus_->frames(),
+ reinterpret_cast<float*>(static_cast<uint8_t*>(payload_buffer_.memory()) +
+ payload_buffer_pos_));
+
+ // Send a new packet.
+ fuchsia::media::StreamPacket packet;
+ packet.pts = stream_position_samples_;
+ packet.payload_buffer_id = kBufferId;
+ packet.payload_offset = payload_buffer_pos_;
+ packet.payload_size = packet_size;
+ packet.flags = 0;
+ audio_renderer_->SendPacketNoReply(std::move(packet));
+
+ // Start playback if the stream was previously stopped.
+ if (reference_time_.is_null()) {
+ reference_time_ = now + delay;
+ audio_renderer_->PlayNoReply(reference_time_.ToZxTime(),
+ stream_position_samples_);
+ }
+
+ stream_position_samples_ += frames_filled;
+ payload_buffer_pos_ =
+ (payload_buffer_pos_ + packet_size) % payload_buffer_.size();
+
+ SchedulePumpSamples(now);
+}
+
+void AudioOutputStreamFuchsia::SchedulePumpSamples(base::TimeTicks now) {
+ base::TimeTicks next_pump_time = GetCurrentStreamTime() -
+ min_lead_time_.value() -
+ parameters_.GetBufferDuration() / 2;
+ timer_.Start(FROM_HERE, next_pump_time - now,
+ base::BindOnce(&AudioOutputStreamFuchsia::PumpSamples,
+ base::Unretained(this)));
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/fuchsia/audio_output_stream_fuchsia.h b/third_party/chromium/media/audio/fuchsia/audio_output_stream_fuchsia.h
new file mode 100644
index 0000000..5f5bfa8
--- /dev/null
+++ b/third_party/chromium/media/audio/fuchsia/audio_output_stream_fuchsia.h
@@ -0,0 +1,105 @@
+// Copyright 2017 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 MEDIA_AUDIO_FUCHSIA_AUDIO_OUTPUT_STREAM_FUCHSIA_H_
+#define MEDIA_AUDIO_FUCHSIA_AUDIO_OUTPUT_STREAM_FUCHSIA_H_
+
+#include <fuchsia/media/cpp/fidl.h>
+
+#include "base/memory/shared_memory_mapping.h"
+#include "base/timer/timer.h"
+#include "media/audio/audio_io.h"
+#include "media/base/audio_parameters.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace media {
+
+class AudioManagerFuchsia;
+
+class AudioOutputStreamFuchsia : public AudioOutputStream {
+ public:
+ // Caller must ensure that manager outlives the stream.
+ AudioOutputStreamFuchsia(AudioManagerFuchsia* manager,
+ const AudioParameters& parameters);
+
+ // AudioOutputStream interface.
+ bool Open() override;
+ void Start(AudioSourceCallback* callback) override;
+ void Stop() override;
+ void Flush() override;
+ void SetVolume(double volume) override;
+ void GetVolume(double* volume) override;
+ void Close() override;
+
+ private:
+ ~AudioOutputStreamFuchsia() override;
+
+ bool is_started() { return callback_ != nullptr; }
+
+ // Returns minimum |payload_buffer_| size for the current |min_lead_time_|.
+ size_t GetMinBufferSize();
+
+ // Allocates and maps |payload_buffer_|.
+ bool InitializePayloadBuffer();
+
+ base::TimeTicks GetCurrentStreamTime();
+
+ // Event handler for |audio_out_|.
+ void OnMinLeadTimeChanged(int64_t min_lead_time);
+
+ // Error handler for |audio_out_|.
+ void OnRendererError(zx_status_t status);
+
+ // Resets internal state and reports an error to |callback_|.
+ void ReportError();
+
+ // Callback for AudioRenderer::Pause().
+ void OnPauseComplete(int64_t reference_time, int64_t media_time);
+
+ // Requests data from AudioSourceCallback, passes it to the mixer and
+ // schedules |timer_| for the next call.
+ void PumpSamples();
+
+ // Schedules |timer_| to call PumpSamples() when appropriate for the next
+ // packet.
+ void SchedulePumpSamples(base::TimeTicks now);
+
+ AudioManagerFuchsia* manager_;
+ AudioParameters parameters_;
+
+ fuchsia::media::AudioRendererPtr audio_renderer_;
+
+ // |audio_bus_| is used only in PumpSamples(). It is kept here to avoid
+ // reallocating the memory every time.
+ std::unique_ptr<AudioBus> audio_bus_;
+
+ base::WritableSharedMemoryMapping payload_buffer_;
+ size_t payload_buffer_pos_ = 0;
+
+ AudioSourceCallback* callback_ = nullptr;
+
+ double volume_ = 1.0;
+
+ // Set to true when Pause() call is pending. AudioRenderer handles Pause()
+ // asynchronously, so Play() should not be called again until Pause() is
+ // complete.
+ bool pause_pending_ = false;
+
+ base::TimeTicks reference_time_;
+
+ int64_t stream_position_samples_;
+
+ // Current min lead time for the stream. This value is not set until the first
+ // AudioRenderer::OnMinLeadTimeChanged event.
+ absl::optional<base::TimeDelta> min_lead_time_;
+
+ // Timer that's scheduled to call PumpSamples().
+ base::OneShotTimer timer_;
+
+ DISALLOW_COPY_AND_ASSIGN(AudioOutputStreamFuchsia);
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_FUCHSIA_AUDIO_OUTPUT_STREAM_FUCHSIA_H_
diff --git a/third_party/chromium/media/audio/linux/audio_manager_linux.cc b/third_party/chromium/media/audio/linux/audio_manager_linux.cc
new file mode 100644
index 0000000..ed7a396
--- /dev/null
+++ b/third_party/chromium/media/audio/linux/audio_manager_linux.cc
@@ -0,0 +1,73 @@
+// Copyright (c) 2012 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 <memory>
+
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/metrics/histogram_macros.h"
+#include "build/chromeos_buildflags.h"
+#include "media/audio/fake_audio_manager.h"
+#include "media/base/media_switches.h"
+
+#if defined(USE_ALSA)
+#include "media/audio/alsa/audio_manager_alsa.h"
+#endif
+
+#if defined(USE_CRAS) && BUILDFLAG(IS_CHROMEOS_ASH)
+#include "media/audio/cras/audio_manager_chromeos.h"
+#elif defined(USE_CRAS)
+#include "media/audio/cras/audio_manager_cras.h"
+#endif
+
+#if defined(USE_PULSEAUDIO)
+#include "media/audio/pulse/audio_manager_pulse.h"
+#include "media/audio/pulse/pulse_util.h"
+#endif
+
+namespace media {
+
+std::unique_ptr<media::AudioManager> CreateAudioManager(
+ std::unique_ptr<AudioThread> audio_thread,
+ AudioLogFactory* audio_log_factory) {
+ // For testing allow audio output to be disabled.
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kDisableAudioOutput)) {
+ return std::make_unique<FakeAudioManager>(std::move(audio_thread),
+ audio_log_factory);
+ }
+
+#if defined(USE_CRAS)
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kUseCras)) {
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+ return std::make_unique<AudioManagerChromeOS>(std::move(audio_thread),
+ audio_log_factory);
+#else
+ return std::make_unique<AudioManagerCras>(std::move(audio_thread),
+ audio_log_factory);
+#endif
+ }
+#endif // defined(USE_CRAS)
+
+#if defined(USE_PULSEAUDIO)
+ pa_threaded_mainloop* pa_mainloop = nullptr;
+ pa_context* pa_context = nullptr;
+ if (pulse::InitPulse(&pa_mainloop, &pa_context)) {
+ return std::make_unique<AudioManagerPulse>(
+ std::move(audio_thread), audio_log_factory, pa_mainloop, pa_context);
+ }
+ LOG(WARNING) << "Falling back to ALSA for audio output. PulseAudio is not "
+ "available or could not be initialized.";
+#endif
+
+#if defined(USE_ALSA)
+ return std::make_unique<AudioManagerAlsa>(std::move(audio_thread),
+ audio_log_factory);
+#else
+ return std::make_unique<FakeAudioManager>(std::move(audio_thread),
+ audio_log_factory);
+#endif
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/mac/audio_auhal_mac.cc b/third_party/chromium/media/audio/mac/audio_auhal_mac.cc
new file mode 100644
index 0000000..0c7c8d3
--- /dev/null
+++ b/third_party/chromium/media/audio/mac/audio_auhal_mac.cc
@@ -0,0 +1,525 @@
+// Copyright 2013 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 "media/audio/mac/audio_auhal_mac.h"
+
+#include <CoreServices/CoreServices.h>
+
+#include <algorithm>
+#include <cstddef>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/logging.h"
+#include "base/mac/mac_logging.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/strings/stringprintf.h"
+#include "base/trace_event/trace_event.h"
+#include "media/audio/mac/audio_manager_mac.h"
+#include "media/base/audio_pull_fifo.h"
+#include "media/base/audio_timestamp_helper.h"
+
+namespace media {
+
+// Mapping from Chrome's channel layout to CoreAudio layout. This must match the
+// layout of the Channels enum in |channel_layout.h|
+static const AudioChannelLabel kCoreAudioChannelMapping[] = {
+ kAudioChannelLabel_Left,
+ kAudioChannelLabel_Right,
+ kAudioChannelLabel_Center,
+ kAudioChannelLabel_LFEScreen,
+ kAudioChannelLabel_LeftSurround,
+ kAudioChannelLabel_RightSurround,
+ kAudioChannelLabel_LeftCenter,
+ kAudioChannelLabel_RightCenter,
+ kAudioChannelLabel_CenterSurround,
+ kAudioChannelLabel_LeftSurroundDirect,
+ kAudioChannelLabel_RightSurroundDirect,
+};
+static_assert(0 == LEFT && 1 == RIGHT && 2 == CENTER && 3 == LFE &&
+ 4 == BACK_LEFT &&
+ 5 == BACK_RIGHT &&
+ 6 == LEFT_OF_CENTER &&
+ 7 == RIGHT_OF_CENTER &&
+ 8 == BACK_CENTER &&
+ 9 == SIDE_LEFT &&
+ 10 == SIDE_RIGHT &&
+ 10 == CHANNELS_MAX,
+ "Channel positions must match CoreAudio channel order.");
+
+static void WrapBufferList(AudioBufferList* buffer_list,
+ AudioBus* bus,
+ int frames) {
+ const int channels = bus->channels();
+ const int buffer_list_channels = buffer_list->mNumberBuffers;
+ CHECK_EQ(channels, buffer_list_channels);
+
+ // Copy pointers from AudioBufferList.
+ for (int i = 0; i < channels; ++i)
+ bus->SetChannelData(i, static_cast<float*>(buffer_list->mBuffers[i].mData));
+
+ // Finally set the actual length.
+ bus->set_frames(frames);
+}
+
+// Sets the stream format on the AUHAL to PCM Float32 non-interleaved for the
+// given number of channels on the given scope and element. The created stream
+// description will be stored in |desc|.
+static bool SetStreamFormat(int channels,
+ int sample_rate,
+ AudioUnit audio_unit,
+ AudioStreamBasicDescription* format) {
+ format->mSampleRate = sample_rate;
+ format->mFormatID = kAudioFormatLinearPCM;
+ format->mFormatFlags =
+ kAudioFormatFlagsNativeFloatPacked | kLinearPCMFormatFlagIsNonInterleaved;
+ format->mBytesPerPacket = sizeof(Float32);
+ format->mFramesPerPacket = 1;
+ format->mBytesPerFrame = sizeof(Float32);
+ format->mChannelsPerFrame = channels;
+ format->mBitsPerChannel = 32;
+ format->mReserved = 0;
+
+ // Set stream formats. See Apple's tech note for details on the peculiar way
+ // that inputs and outputs are handled in the AUHAL concerning scope and bus
+ // (element) numbers:
+ // http://developer.apple.com/library/mac/#technotes/tn2091/_index.html
+ return AudioUnitSetProperty(audio_unit, kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Input, AUElement::OUTPUT, format,
+ sizeof(*format)) == noErr;
+}
+
+// Converts |channel_layout| into CoreAudio format and sets up the AUHAL with
+// our layout information so it knows how to remap the channels.
+static void SetAudioChannelLayout(int channels,
+ ChannelLayout channel_layout,
+ AudioUnit audio_unit) {
+ DCHECK(audio_unit);
+ DCHECK_GT(channels, 0);
+ DCHECK_GT(channel_layout, CHANNEL_LAYOUT_UNSUPPORTED);
+
+ // AudioChannelLayout is structure ending in a variable length array, so we
+ // can't directly allocate one. Instead compute the size and and allocate one
+ // inside of a byte array.
+ //
+ // Code modeled after example from Apple documentation here:
+ // https://developer.apple.com/library/content/qa/qa1627/_index.html
+ const size_t layout_size =
+ offsetof(AudioChannelLayout, mChannelDescriptions[channels]);
+ std::unique_ptr<uint8_t[]> layout_storage(new uint8_t[layout_size]);
+ memset(layout_storage.get(), 0, layout_size);
+ AudioChannelLayout* coreaudio_layout =
+ reinterpret_cast<AudioChannelLayout*>(layout_storage.get());
+
+ coreaudio_layout->mNumberChannelDescriptions = channels;
+ coreaudio_layout->mChannelLayoutTag =
+ kAudioChannelLayoutTag_UseChannelDescriptions;
+ AudioChannelDescription* descriptions =
+ coreaudio_layout->mChannelDescriptions;
+
+ if (channel_layout == CHANNEL_LAYOUT_DISCRETE) {
+ // For the discrete case just assume common input mappings; once we run out
+ // of known channels mark them as unknown.
+ for (int ch = 0; ch < channels; ++ch) {
+ descriptions[ch].mChannelLabel = ch > CHANNELS_MAX
+ ? kAudioChannelLabel_Unknown
+ : kCoreAudioChannelMapping[ch];
+ descriptions[ch].mChannelFlags = kAudioChannelFlags_AllOff;
+ }
+ } else if (channel_layout == CHANNEL_LAYOUT_MONO) {
+ // CoreAudio has a special label for mono.
+ DCHECK_EQ(channels, 1);
+ descriptions[0].mChannelLabel = kAudioChannelLabel_Mono;
+ descriptions[0].mChannelFlags = kAudioChannelFlags_AllOff;
+ } else {
+ for (int ch = 0; ch <= CHANNELS_MAX; ++ch) {
+ const int order = ChannelOrder(channel_layout, static_cast<Channels>(ch));
+ if (order == -1)
+ continue;
+ descriptions[order].mChannelLabel = kCoreAudioChannelMapping[ch];
+ descriptions[order].mChannelFlags = kAudioChannelFlags_AllOff;
+ }
+ }
+
+ OSStatus result = AudioUnitSetProperty(
+ audio_unit, kAudioUnitProperty_AudioChannelLayout, kAudioUnitScope_Input,
+ AUElement::OUTPUT, coreaudio_layout, layout_size);
+ if (result != noErr) {
+ OSSTATUS_DLOG(ERROR, result)
+ << "Failed to set audio channel layout. Using default layout.";
+ }
+}
+
+AUHALStream::AUHALStream(AudioManagerMac* manager,
+ const AudioParameters& params,
+ AudioDeviceID device,
+ const AudioManager::LogCallback& log_callback)
+ : manager_(manager),
+ params_(params),
+ number_of_frames_(params_.frames_per_buffer()),
+ number_of_frames_requested_(0),
+ source_(NULL),
+ device_(device),
+ volume_(1),
+ stopped_(true),
+ current_lost_frames_(0),
+ last_sample_time_(0.0),
+ last_number_of_frames_(0),
+ total_lost_frames_(0),
+ largest_glitch_frames_(0),
+ glitches_detected_(0),
+ log_callback_(log_callback) {
+ // We must have a manager.
+ DCHECK(manager_);
+ DCHECK(params_.IsValid());
+ DCHECK_NE(device, kAudioObjectUnknown);
+}
+
+AUHALStream::~AUHALStream() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ CHECK(!audio_unit_);
+
+ base::AutoLock al(lock_);
+ ReportAndResetStats();
+}
+
+bool AUHALStream::Open() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!output_bus_);
+ DCHECK(!audio_unit_);
+
+ // The output bus will wrap the AudioBufferList given to us in
+ // the Render() callback.
+ output_bus_ = AudioBus::CreateWrapper(params_.channels());
+
+ bool configured = ConfigureAUHAL();
+ if (configured) {
+ DCHECK(audio_unit_);
+ DCHECK(audio_unit_->is_valid());
+ hardware_latency_ = AudioManagerMac::GetHardwareLatency(
+ audio_unit_->audio_unit(), device_, kAudioDevicePropertyScopeOutput,
+ params_.sample_rate());
+ }
+
+ return configured;
+}
+
+void AUHALStream::Close() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (audio_unit_) {
+ Stop();
+
+ // Clear the render callback to try and prevent any callbacks from coming
+ // in after we've called stop. https://crbug.com/737527.
+ AURenderCallbackStruct callback = {0};
+ auto result = AudioUnitSetProperty(
+ audio_unit_->audio_unit(), kAudioUnitProperty_SetRenderCallback,
+ kAudioUnitScope_Input, AUElement::OUTPUT, &callback, sizeof(callback));
+ OSSTATUS_DLOG_IF(ERROR, result != noErr, result)
+ << "Failed to clear input callback.";
+ }
+
+ audio_unit_.reset();
+ // Inform the audio manager that we have been closed. This will cause our
+ // destruction. Also include the device ID as a signal to the audio manager
+ // that it should try to increase the native I/O buffer size after the stream
+ // has been closed.
+ manager_->ReleaseOutputStreamUsingRealDevice(this, device_);
+}
+
+void AUHALStream::Start(AudioSourceCallback* callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(callback);
+ if (!audio_unit_) {
+ DLOG(ERROR) << "Open() has not been called successfully";
+ return;
+ }
+
+ if (!stopped_) {
+ base::AutoLock al(lock_);
+ CHECK_EQ(source_, callback);
+ return;
+ }
+
+ // Check if we should defer Start() for http://crbug.com/160920.
+ if (manager_->ShouldDeferStreamStart()) {
+ // Use a cancellable closure so that if Stop() is called before Start()
+ // actually runs, we can cancel the pending start.
+ deferred_start_cb_.Reset(
+ base::BindOnce(&AUHALStream::Start, base::Unretained(this), callback));
+ manager_->GetTaskRunner()->PostDelayedTask(
+ FROM_HERE, deferred_start_cb_.callback(),
+ base::Seconds(AudioManagerMac::kStartDelayInSecsForPowerEvents));
+ return;
+ }
+
+ stopped_ = false;
+
+ {
+ base::AutoLock al(lock_);
+ audio_fifo_.reset();
+ source_ = callback;
+ }
+
+ OSStatus result = AudioOutputUnitStart(audio_unit_->audio_unit());
+ if (result == noErr)
+ return;
+
+ Stop();
+ OSSTATUS_DLOG(ERROR, result) << "AudioOutputUnitStart() failed.";
+ callback->OnError(AudioSourceCallback::ErrorType::kUnknown);
+}
+
+// This stream is always used with sub second buffer sizes, where it's
+// sufficient to simply always flush upon Start().
+void AUHALStream::Flush() {}
+
+void AUHALStream::Stop() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ deferred_start_cb_.Cancel();
+ if (stopped_)
+ return;
+
+ OSStatus result = AudioOutputUnitStop(audio_unit_->audio_unit());
+ OSSTATUS_DLOG_IF(ERROR, result != noErr, result)
+ << "AudioOutputUnitStop() failed.";
+
+ {
+ base::AutoLock al(lock_);
+ if (result != noErr)
+ source_->OnError(AudioSourceCallback::ErrorType::kUnknown);
+
+ ReportAndResetStats();
+ source_ = nullptr;
+ }
+
+ stopped_ = true;
+}
+
+void AUHALStream::SetVolume(double volume) {
+ volume_ = static_cast<float>(volume);
+}
+
+void AUHALStream::GetVolume(double* volume) {
+ *volume = volume_;
+}
+
+// Pulls on our provider to get rendered audio stream.
+// Note to future hackers of this function: Do not add locks which can
+// be contended in the middle of stream processing here (starting and stopping
+// the stream are ok) because this is running on a real-time thread.
+OSStatus AUHALStream::Render(AudioUnitRenderActionFlags* flags,
+ const AudioTimeStamp* output_time_stamp,
+ UInt32 bus_number,
+ UInt32 number_of_frames,
+ AudioBufferList* data) {
+ TRACE_EVENT2("audio", "AUHALStream::Render", "input buffer size",
+ number_of_frames_, "output buffer size", number_of_frames);
+
+ base::AutoLock al(lock_);
+
+ // There's no documentation on what we should return here, but if we're here
+ // something is wrong so just return an AudioUnit error that looks reasonable.
+ if (!source_)
+ return kAudioUnitErr_Uninitialized;
+
+ UpdatePlayoutTimestamp(output_time_stamp);
+
+ // If the stream parameters change for any reason, we need to insert a FIFO
+ // since the OnMoreData() pipeline can't handle frame size changes.
+ if (number_of_frames != number_of_frames_) {
+ // Create a FIFO on the fly to handle any discrepancies in callback rates.
+ if (!audio_fifo_) {
+ // TODO(grunell): We'll only care about the first buffer size change,
+ // any further changes will be ignored. It would be nice to have all
+ // changes reflected in UMA stats.
+ number_of_frames_requested_ = number_of_frames;
+ DVLOG(1) << "Audio frame size changed from " << number_of_frames_
+ << " to " << number_of_frames << " adding FIFO to compensate.";
+ audio_fifo_ = std::make_unique<AudioPullFifo>(
+ params_.channels(), number_of_frames_,
+ base::BindRepeating(&AUHALStream::ProvideInput,
+ base::Unretained(this)));
+ }
+ }
+
+ // Make |output_bus_| wrap the output AudioBufferList.
+ WrapBufferList(data, output_bus_.get(), number_of_frames);
+
+ current_playout_time_ = GetPlayoutTime(output_time_stamp);
+
+ if (audio_fifo_)
+ audio_fifo_->Consume(output_bus_.get(), output_bus_->frames());
+ else
+ ProvideInput(0, output_bus_.get());
+
+ last_number_of_frames_ = number_of_frames;
+
+ return noErr;
+}
+
+void AUHALStream::ProvideInput(int frame_delay, AudioBus* dest) {
+ lock_.AssertAcquired();
+ DCHECK(source_);
+
+ const base::TimeTicks playout_time =
+ current_playout_time_ +
+ AudioTimestampHelper::FramesToTime(frame_delay, params_.sample_rate());
+ const base::TimeTicks now = base::TimeTicks::Now();
+ const base::TimeDelta delay = playout_time - now;
+
+ // Supply the input data and render the output data.
+ source_->OnMoreData(delay, now, current_lost_frames_, dest);
+ dest->Scale(volume_);
+ current_lost_frames_ = 0;
+}
+
+// AUHAL callback.
+OSStatus AUHALStream::InputProc(void* user_data,
+ AudioUnitRenderActionFlags* flags,
+ const AudioTimeStamp* output_time_stamp,
+ UInt32 bus_number,
+ UInt32 number_of_frames,
+ AudioBufferList* io_data) {
+ // Dispatch to our class method.
+ AUHALStream* audio_output = static_cast<AUHALStream*>(user_data);
+ if (!audio_output)
+ return -1;
+
+ return audio_output->Render(flags, output_time_stamp, bus_number,
+ number_of_frames, io_data);
+}
+
+base::TimeTicks AUHALStream::GetPlayoutTime(
+ const AudioTimeStamp* output_time_stamp) {
+ // A platform bug has been observed where the platform sometimes reports that
+ // the next frames will be output at an invalid time or a time in the past.
+ // Because the target playout time cannot be invalid or in the past, return
+ // "now" in these cases.
+ if ((output_time_stamp->mFlags & kAudioTimeStampHostTimeValid) == 0)
+ return base::TimeTicks::Now();
+
+ return std::max(base::TimeTicks::FromMachAbsoluteTime(
+ output_time_stamp->mHostTime),
+ base::TimeTicks::Now()) +
+ hardware_latency_;
+}
+
+void AUHALStream::UpdatePlayoutTimestamp(const AudioTimeStamp* timestamp) {
+ lock_.AssertAcquired();
+
+ if ((timestamp->mFlags & kAudioTimeStampSampleTimeValid) == 0)
+ return;
+
+ if (last_sample_time_) {
+ DCHECK_NE(0U, last_number_of_frames_);
+ UInt32 diff =
+ static_cast<UInt32>(timestamp->mSampleTime - last_sample_time_);
+ if (diff != last_number_of_frames_) {
+ DCHECK_GT(diff, last_number_of_frames_);
+ // We're being asked to render samples post what we expected. Update the
+ // glitch count etc and keep a record of the largest glitch.
+ auto lost_frames = diff - last_number_of_frames_;
+ total_lost_frames_ += lost_frames;
+ current_lost_frames_ += lost_frames;
+ if (lost_frames > largest_glitch_frames_)
+ largest_glitch_frames_ = lost_frames;
+ ++glitches_detected_;
+ }
+ }
+
+ // Store the last sample time for use next time we get called back.
+ last_sample_time_ = timestamp->mSampleTime;
+}
+
+void AUHALStream::ReportAndResetStats() {
+ lock_.AssertAcquired();
+
+ if (!last_sample_time_)
+ return; // No stats gathered to report.
+
+ // A value of 0 indicates that we got the buffer size we asked for.
+ UMA_HISTOGRAM_COUNTS_1M("Media.Audio.Render.FramesRequested",
+ number_of_frames_requested_);
+ // Even if there aren't any glitches, we want to record it to get a feel for
+ // how often we get no glitches vs the alternative.
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Media.Audio.Render.Glitches", glitches_detected_,
+ 1, 999999, 100);
+
+ auto lost_frames_ms = (total_lost_frames_ * 1000) / params_.sample_rate();
+
+ std::string log_message = base::StringPrintf(
+ "AU out: Total glitches=%d. Total frames lost=%d (%d ms).",
+ glitches_detected_, total_lost_frames_, lost_frames_ms);
+
+ if (!log_callback_.is_null())
+ log_callback_.Run(log_message);
+
+ if (glitches_detected_ != 0) {
+ UMA_HISTOGRAM_COUNTS_1M("Media.Audio.Render.LostFramesInMs",
+ lost_frames_ms);
+ auto largest_glitch_ms =
+ (largest_glitch_frames_ * 1000) / params_.sample_rate();
+ UMA_HISTOGRAM_COUNTS_1M("Media.Audio.Render.LargestGlitchMs",
+ largest_glitch_ms);
+ DLOG(WARNING) << log_message;
+ }
+
+ number_of_frames_requested_ = 0;
+ glitches_detected_ = 0;
+ last_sample_time_ = 0;
+ last_number_of_frames_ = 0;
+ total_lost_frames_ = 0;
+ largest_glitch_frames_ = 0;
+}
+
+bool AUHALStream::ConfigureAUHAL() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ std::unique_ptr<ScopedAudioUnit> local_audio_unit(
+ new ScopedAudioUnit(device_, AUElement::OUTPUT));
+ if (!local_audio_unit->is_valid())
+ return false;
+
+ if (!SetStreamFormat(params_.channels(), params_.sample_rate(),
+ local_audio_unit->audio_unit(), &output_format_)) {
+ return false;
+ }
+
+ bool size_was_changed = false;
+ size_t io_buffer_frame_size = 0;
+ if (!manager_->MaybeChangeBufferSize(device_, local_audio_unit->audio_unit(),
+ 0, number_of_frames_, &size_was_changed,
+ &io_buffer_frame_size)) {
+ return false;
+ }
+
+ // Setup callback.
+ AURenderCallbackStruct callback;
+ callback.inputProc = InputProc;
+ callback.inputProcRefCon = this;
+ OSStatus result = AudioUnitSetProperty(
+ local_audio_unit->audio_unit(), kAudioUnitProperty_SetRenderCallback,
+ kAudioUnitScope_Input, AUElement::OUTPUT, &callback, sizeof(callback));
+ if (result != noErr)
+ return false;
+
+ SetAudioChannelLayout(params_.channels(), params_.channel_layout(),
+ local_audio_unit->audio_unit());
+
+ result = AudioUnitInitialize(local_audio_unit->audio_unit());
+ if (result != noErr) {
+ OSSTATUS_DLOG(ERROR, result) << "AudioUnitInitialize() failed.";
+ return false;
+ }
+
+ audio_unit_ = std::move(local_audio_unit);
+ return true;
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/mac/audio_auhal_mac.h b/third_party/chromium/media/audio/mac/audio_auhal_mac.h
new file mode 100644
index 0000000..376ca46
--- /dev/null
+++ b/third_party/chromium/media/audio/mac/audio_auhal_mac.h
@@ -0,0 +1,221 @@
+// Copyright 2013 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.
+//
+// Implementation notes:
+//
+// - It is recommended to first acquire the native sample rate of the default
+// output device and then use the same rate when creating this object.
+// Use AudioManagerMac::HardwareSampleRate() to retrieve the sample rate.
+// - Calling Close() also leads to self destruction.
+// - The latency consists of two parts:
+// 1) Hardware latency, which includes Audio Unit latency, audio device
+// latency;
+// 2) The delay between the moment getting the callback and the scheduled time
+// stamp that tells when the data is going to be played out.
+//
+#ifndef MEDIA_AUDIO_MAC_AUDIO_AUHAL_MAC_H_
+#define MEDIA_AUDIO_MAC_AUDIO_AUHAL_MAC_H_
+
+#include <AudioUnit/AudioUnit.h>
+#include <CoreAudio/CoreAudio.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include <atomic>
+#include <memory>
+
+#include "base/cancelable_callback.h"
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/time.h"
+#include "media/audio/audio_io.h"
+#include "media/audio/audio_manager.h"
+#include "media/audio/mac/scoped_audio_unit.h"
+#include "media/base/audio_parameters.h"
+
+namespace media {
+
+class AudioManagerMac;
+class AudioPullFifo;
+
+// Implementation of AudioOuputStream for Mac OS X using the
+// AUHAL Audio Unit present in OS 10.4 and later.
+// It is useful for low-latency output.
+//
+// Overview of operation:
+// 1) An object of AUHALStream is created by the AudioManager factory on the
+// object's main thread via audio_man->MakeAudioStream(). Calls to the
+// control routines (Open/Close/Start/Stop), must be made on this thread.
+// 2) Next Open() will be called. At that point the underlying AUHAL Audio Unit
+// is created and configured to use the |device|.
+// 3) Then Start(source) is called and the device is started which creates its
+// own thread (or uses an existing background thread) on which the AUHAL's
+// callback will periodically ask for more data as buffers are being
+// consumed.
+// Note that all AUHAL instances receive callbacks on that very same
+// thread, so avoid any contention in the callback as to not cause delays for
+// other instances.
+// 4) At some point Stop() will be called, which we handle by stopping the
+// output Audio Unit.
+// 6) Lastly, Close() will be called where we cleanup and notify the audio
+// manager, which will delete the object.
+
+// TODO(tommi): Since the callback audio thread is shared for all instances of
+// AUHALStream, one stream blocking, can cause others to be delayed. Several
+// occurrances of this can cause a buildup of delay which forces the OS
+// to skip rendering frames. One known cause of this is the synchronzation
+// between the browser and render process in AudioSyncReader.
+// We need to fix this.
+
+class AUHALStream : public AudioOutputStream {
+ public:
+ // |manager| creates this object.
+ // |device| is the CoreAudio device to use for the stream.
+ // It will often be the default output device.
+ AUHALStream(AudioManagerMac* manager,
+ const AudioParameters& params,
+ AudioDeviceID device,
+ const AudioManager::LogCallback& log_callback);
+
+ AUHALStream(const AUHALStream&) = delete;
+ AUHALStream& operator=(const AUHALStream&) = delete;
+
+ // The dtor is typically called by the AudioManager only and it is usually
+ // triggered by calling AudioOutputStream::Close().
+ ~AUHALStream() override;
+
+ // Implementation of AudioOutputStream.
+ bool Open() override;
+ void Close() override;
+ void Start(AudioSourceCallback* callback) override;
+ void Stop() override;
+ void Flush() override;
+ void SetVolume(double volume) override;
+ void GetVolume(double* volume) override;
+
+ AudioDeviceID device_id() const { return device_; }
+ size_t requested_buffer_size() const { return number_of_frames_; }
+ AudioUnit audio_unit() const {
+ return audio_unit_ ? audio_unit_->audio_unit() : nullptr;
+ }
+
+ private:
+ // AUHAL callback.
+ static OSStatus InputProc(void* user_data,
+ AudioUnitRenderActionFlags* flags,
+ const AudioTimeStamp* time_stamp,
+ UInt32 bus_number,
+ UInt32 number_of_frames,
+ AudioBufferList* io_data);
+
+ OSStatus Render(AudioUnitRenderActionFlags* flags,
+ const AudioTimeStamp* output_time_stamp,
+ UInt32 bus_number,
+ UInt32 number_of_frames,
+ AudioBufferList* io_data);
+
+ // Called by either |audio_fifo_| or Render() to provide audio data.
+ void ProvideInput(int frame_delay, AudioBus* dest);
+
+ // Creates the AUHAL, sets its stream format, buffer-size, etc.
+ bool ConfigureAUHAL();
+
+ // Creates the input and output busses.
+ void CreateIOBusses();
+
+ // Returns the playout time for a given AudioTimeStamp.
+ base::TimeTicks GetPlayoutTime(const AudioTimeStamp* output_time_stamp);
+
+ // Updates playout timestamp, current lost frames, and total lost frames and
+ // glitches.
+ void UpdatePlayoutTimestamp(const AudioTimeStamp* timestamp);
+
+ // Called from the dtor and when the stream is reset.
+ void ReportAndResetStats();
+
+ // Our creator, the audio manager needs to be notified when we close.
+ AudioManagerMac* const manager_;
+
+ const AudioParameters params_;
+
+ // We may get some callbacks after AudioUnitStop() has been called.
+ base::Lock lock_;
+
+ // Size of audio buffer requested at construction. The actual buffer size
+ // is given by |actual_io_buffer_frame_size_| and it can differ from the
+ // requested size.
+ const size_t number_of_frames_;
+
+ // Stores the number of frames that we actually get callbacks for.
+ // This may be different from what we ask for, so we use this for stats in
+ // order to understand how often this happens and what are the typical values.
+ size_t number_of_frames_requested_ GUARDED_BY(lock_);
+
+ // Pointer to the object that will provide the audio samples.
+ AudioSourceCallback* source_ GUARDED_BY(lock_);
+
+ // Holds the stream format details such as bitrate.
+ AudioStreamBasicDescription output_format_;
+
+ // The audio device to use with the AUHAL.
+ // We can potentially handle both input and output with this device.
+ const AudioDeviceID device_;
+
+ // The AUHAL Audio Unit which talks to |device_|.
+ std::unique_ptr<ScopedAudioUnit> audio_unit_;
+
+ // Volume level from 0 to 1.
+ std::atomic<float> volume_;
+
+ // Fixed playout hardware latency.
+ base::TimeDelta hardware_latency_;
+
+ // This flag will be set to false while we're actively receiving callbacks.
+ bool stopped_;
+
+ // Container for retrieving data from AudioSourceCallback::OnMoreData().
+ std::unique_ptr<AudioBus> output_bus_;
+
+ // Dynamically allocated FIFO used when CoreAudio asks for unexpected frame
+ // sizes.
+ std::unique_ptr<AudioPullFifo> audio_fifo_ GUARDED_BY(lock_);
+
+ // Current playout time. Set by Render().
+ base::TimeTicks current_playout_time_;
+
+ // Lost frames not yet reported to the provider. Increased in
+ // UpdatePlayoutTimestamp() if any lost frame since last time. Forwarded to
+ // the provider and reset in ProvideInput().
+ uint32_t current_lost_frames_;
+
+ // Stores the timestamp of the previous audio buffer requested by the OS.
+ // We use this in combination with |last_number_of_frames_| to detect when
+ // the OS has decided to skip rendering frames (i.e. a glitch).
+ // This can happen in case of high CPU load or excessive blocking on the
+ // callback audio thread.
+ // These variables are only touched on the callback thread and then read
+ // in the dtor (when no longer receiving callbacks).
+ // NOTE: Float64 and UInt32 types are used for native API compatibility.
+ Float64 last_sample_time_ GUARDED_BY(lock_);
+ UInt32 last_number_of_frames_ GUARDED_BY(lock_);
+ UInt32 total_lost_frames_ GUARDED_BY(lock_);
+ UInt32 largest_glitch_frames_ GUARDED_BY(lock_);
+ int glitches_detected_ GUARDED_BY(lock_);
+
+ // Used to defer Start() to workaround http://crbug.com/160920.
+ base::CancelableOnceClosure deferred_start_cb_;
+
+ // Callback to send statistics info.
+ AudioManager::LogCallback log_callback_;
+
+ // Used to make sure control functions (Start(), Stop() etc) are called on the
+ // right thread.
+ base::ThreadChecker thread_checker_;
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_MAC_AUDIO_AUHAL_MAC_H_
diff --git a/third_party/chromium/media/audio/mac/audio_auhal_mac_unittest.cc b/third_party/chromium/media/audio/mac/audio_auhal_mac_unittest.cc
new file mode 100644
index 0000000..8ba6e39
--- /dev/null
+++ b/third_party/chromium/media/audio/mac/audio_auhal_mac_unittest.cc
@@ -0,0 +1,123 @@
+// Copyright 2013 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/macros.h"
+#include "base/message_loop/message_pump_type.h"
+#include "base/run_loop.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/test/test_message_loop.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "media/audio/audio_device_info_accessor_for_tests.h"
+#include "media/audio/audio_io.h"
+#include "media/audio/audio_manager.h"
+#include "media/audio/audio_unittest_util.h"
+#include "media/audio/mock_audio_source_callback.h"
+#include "media/audio/test_audio_thread.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::DoAll;
+using testing::Return;
+
+// TODO(crogers): Most of these tests can be made platform agnostic.
+// http://crbug.com/223242
+
+namespace media {
+
+ACTION(ZeroBuffer) {
+ arg3->Zero();
+}
+
+ACTION_P3(MaybeSignalEvent, counter, signal_at_count, event) {
+ if (++(*counter) == signal_at_count)
+ event->Signal();
+}
+
+class AUHALStreamTest : public testing::Test {
+ public:
+ AUHALStreamTest()
+ : message_loop_(base::MessagePumpType::UI),
+ manager_(AudioManager::CreateForTesting(
+ std::make_unique<TestAudioThread>())),
+ manager_device_info_(manager_.get()) {
+ // Wait for the AudioManager to finish any initialization on the audio loop.
+ base::RunLoop().RunUntilIdle();
+ }
+
+ AUHALStreamTest(const AUHALStreamTest&) = delete;
+ AUHALStreamTest& operator=(const AUHALStreamTest&) = delete;
+
+ ~AUHALStreamTest() override { manager_->Shutdown(); }
+
+ AudioOutputStream* Create() {
+ return manager_->MakeAudioOutputStream(
+ manager_device_info_.GetDefaultOutputStreamParameters(), "",
+ base::BindRepeating(&AUHALStreamTest::OnLogMessage,
+ base::Unretained(this)));
+ }
+
+ bool OutputDevicesAvailable() {
+ return manager_device_info_.HasAudioOutputDevices();
+ }
+
+ void OnLogMessage(const std::string& message) { log_message_ = message; }
+
+ protected:
+ base::TestMessageLoop message_loop_;
+ std::unique_ptr<AudioManager> manager_;
+ AudioDeviceInfoAccessorForTests manager_device_info_;
+ MockAudioSourceCallback source_;
+ std::string log_message_;
+};
+
+TEST_F(AUHALStreamTest, HardwareSampleRate) {
+ ABORT_AUDIO_TEST_IF_NOT(OutputDevicesAvailable());
+ const AudioParameters preferred_params =
+ manager_device_info_.GetDefaultOutputStreamParameters();
+ EXPECT_GE(preferred_params.sample_rate(), 16000);
+ EXPECT_LE(preferred_params.sample_rate(), 192000);
+}
+
+TEST_F(AUHALStreamTest, CreateClose) {
+ ABORT_AUDIO_TEST_IF_NOT(OutputDevicesAvailable());
+ Create()->Close();
+}
+
+TEST_F(AUHALStreamTest, CreateOpenClose) {
+ ABORT_AUDIO_TEST_IF_NOT(OutputDevicesAvailable());
+ AudioOutputStream* stream = Create();
+ EXPECT_TRUE(stream->Open());
+ stream->Close();
+}
+
+TEST_F(AUHALStreamTest, CreateOpenStartStopClose) {
+ ABORT_AUDIO_TEST_IF_NOT(OutputDevicesAvailable());
+
+ AudioOutputStream* stream = Create();
+ EXPECT_TRUE(stream->Open());
+
+ // Wait for the first two data callback from the OS.
+ base::WaitableEvent event(base::WaitableEvent::ResetPolicy::AUTOMATIC,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ int callback_counter = 0;
+ const int number_of_callbacks = 2;
+ EXPECT_CALL(source_, OnMoreData(_, _, _, _))
+ .Times(number_of_callbacks)
+ .WillRepeatedly(DoAll(
+ ZeroBuffer(),
+ MaybeSignalEvent(&callback_counter, number_of_callbacks, &event),
+ Return(0)));
+ EXPECT_CALL(source_, OnError(_)).Times(0);
+ stream->Start(&source_);
+ event.Wait();
+
+ stream->Stop();
+ stream->Close();
+
+ EXPECT_FALSE(log_message_.empty());
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/mac/audio_device_listener_mac.cc b/third_party/chromium/media/audio/mac/audio_device_listener_mac.cc
new file mode 100644
index 0000000..3476082
--- /dev/null
+++ b/third_party/chromium/media/audio/mac/audio_device_listener_mac.cc
@@ -0,0 +1,204 @@
+// Copyright (c) 2013 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 "media/audio/mac/audio_device_listener_mac.h"
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/mac/mac_logging.h"
+#include "base/single_thread_task_runner.h"
+#include "media/audio/audio_manager.h"
+#include "media/audio/mac/core_audio_util_mac.h"
+#include "media/base/bind_to_current_loop.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace media {
+
+const AudioObjectPropertyAddress
+ AudioDeviceListenerMac::kDefaultOutputDeviceChangePropertyAddress = {
+ kAudioHardwarePropertyDefaultOutputDevice,
+ kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster};
+
+const AudioObjectPropertyAddress
+ AudioDeviceListenerMac::kDefaultInputDeviceChangePropertyAddress = {
+ kAudioHardwarePropertyDefaultInputDevice,
+ kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster};
+
+const AudioObjectPropertyAddress
+ AudioDeviceListenerMac::kDevicesPropertyAddress = {
+ kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+
+const AudioObjectPropertyAddress kPropertyOutputSourceChanged = {
+ kAudioDevicePropertyDataSource, kAudioDevicePropertyScopeOutput,
+ kAudioObjectPropertyElementMaster};
+
+const AudioObjectPropertyAddress kPropertyInputSourceChanged = {
+ kAudioDevicePropertyDataSource, kAudioDevicePropertyScopeInput,
+ kAudioObjectPropertyElementMaster};
+
+class AudioDeviceListenerMac::PropertyListener {
+ public:
+ PropertyListener(AudioObjectID monitored_object,
+ const AudioObjectPropertyAddress* property,
+ base::RepeatingClosure callback)
+ : monitored_object_(monitored_object),
+ address_(property),
+ callback_(std::move(callback)) {}
+
+ AudioObjectID monitored_object() const { return monitored_object_; }
+ const base::RepeatingClosure& callback() const { return callback_; }
+ const AudioObjectPropertyAddress* property() const { return address_; }
+
+ private:
+ AudioObjectID monitored_object_;
+ const AudioObjectPropertyAddress* address_;
+ base::RepeatingClosure callback_;
+};
+
+// Callback from the system when an event occurs; this must be called on the
+// MessageLoop that created the AudioManager.
+// static
+OSStatus AudioDeviceListenerMac::OnEvent(
+ AudioObjectID object,
+ UInt32 num_addresses,
+ const AudioObjectPropertyAddress addresses[],
+ void* context) {
+ PropertyListener* listener = static_cast<PropertyListener*>(context);
+ if (object != listener->monitored_object())
+ return noErr;
+
+ for (UInt32 i = 0; i < num_addresses; ++i) {
+ if (addresses[i].mSelector == listener->property()->mSelector &&
+ addresses[i].mScope == listener->property()->mScope &&
+ addresses[i].mElement == listener->property()->mElement && context) {
+ listener->callback().Run();
+ break;
+ }
+ }
+
+ return noErr;
+}
+
+AudioDeviceListenerMac::AudioDeviceListenerMac(
+ const base::RepeatingClosure listener_cb,
+ bool monitor_default_input,
+ bool monitor_addition_removal,
+ bool monitor_sources)
+ : weak_factory_(this) {
+ listener_cb_ = std::move(listener_cb);
+
+ // Changes to the default output device are always monitored.
+ default_output_listener_ = std::make_unique<PropertyListener>(
+ kAudioObjectSystemObject, &kDefaultOutputDeviceChangePropertyAddress,
+ listener_cb_);
+ if (!AddPropertyListener(default_output_listener_.get()))
+ default_output_listener_.reset();
+
+ if (monitor_default_input) {
+ default_input_listener_ = std::make_unique<PropertyListener>(
+ kAudioObjectSystemObject, &kDefaultInputDeviceChangePropertyAddress,
+ listener_cb_);
+ if (!AddPropertyListener(default_input_listener_.get()))
+ default_input_listener_.reset();
+ }
+ if (monitor_addition_removal) {
+ addition_removal_listener_ = std::make_unique<PropertyListener>(
+ kAudioObjectSystemObject, &kDevicesPropertyAddress,
+ monitor_sources ? media::BindToCurrentLoop(base::BindRepeating(
+ &AudioDeviceListenerMac::OnDevicesAddedOrRemoved,
+ weak_factory_.GetWeakPtr()))
+ : listener_cb_);
+ if (!AddPropertyListener(addition_removal_listener_.get()))
+ addition_removal_listener_.reset();
+
+ // Sources can be monitored only if addition/removal is monitored.
+ if (monitor_sources)
+ UpdateSourceListeners();
+ }
+}
+
+AudioDeviceListenerMac::~AudioDeviceListenerMac() {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+ // Since we're running on the same CFRunLoop, there can be no outstanding
+ // callbacks in flight.
+ if (default_output_listener_)
+ RemovePropertyListener(default_output_listener_.get());
+ if (default_input_listener_)
+ RemovePropertyListener(default_input_listener_.get());
+ if (addition_removal_listener_)
+ RemovePropertyListener(addition_removal_listener_.get());
+ for (const auto& entry : source_listeners_)
+ RemovePropertyListener(entry.second.get());
+}
+
+bool AudioDeviceListenerMac::AddPropertyListener(
+ AudioDeviceListenerMac::PropertyListener* property_listener) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ OSStatus result = AudioObjectAddPropertyListener(
+ property_listener->monitored_object(), property_listener->property(),
+ &AudioDeviceListenerMac::OnEvent, property_listener);
+ bool success = result == noErr;
+ if (!success)
+ OSSTATUS_DLOG(ERROR, result) << "AudioObjectAddPropertyListener() failed!";
+
+ return success;
+}
+
+void AudioDeviceListenerMac::RemovePropertyListener(
+ AudioDeviceListenerMac::PropertyListener* property_listener) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ OSStatus result = AudioObjectRemovePropertyListener(
+ property_listener->monitored_object(), property_listener->property(),
+ &AudioDeviceListenerMac::OnEvent, property_listener);
+ OSSTATUS_DLOG_IF(ERROR, result != noErr, result)
+ << "AudioObjectRemovePropertyListener() failed!";
+}
+
+void AudioDeviceListenerMac::OnDevicesAddedOrRemoved() {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ UpdateSourceListeners();
+ listener_cb_.Run();
+}
+
+void AudioDeviceListenerMac::UpdateSourceListeners() {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ std::vector<AudioObjectID> device_ids =
+ core_audio_mac::GetAllAudioDeviceIDs();
+ for (bool is_input : {true, false}) {
+ for (auto device_id : device_ids) {
+ const AudioObjectPropertyAddress* property_address =
+ is_input ? &kPropertyInputSourceChanged
+ : &kPropertyOutputSourceChanged;
+ SourceListenerKey key = {device_id, is_input};
+ auto it_key = source_listeners_.find(key);
+ bool is_monitored = it_key != source_listeners_.end();
+ if (core_audio_mac::GetDeviceSource(device_id, is_input)) {
+ if (!is_monitored) {
+ // Start monitoring if the device has source and is not currently
+ // being monitored.
+ std::unique_ptr<PropertyListener> source_listener =
+ std::make_unique<PropertyListener>(device_id, property_address,
+ listener_cb_);
+ if (AddPropertyListener(source_listener.get())) {
+ source_listeners_[key] = std::move(source_listener);
+ } else {
+ source_listener.reset();
+ }
+ }
+ } else if (is_monitored) {
+ // Stop monitoring if the device has no source but is currently being
+ // monitored.
+ RemovePropertyListener(it_key->second.get());
+ source_listeners_.erase(it_key);
+ }
+ }
+ }
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/mac/audio_device_listener_mac.h b/third_party/chromium/media/audio/mac/audio_device_listener_mac.h
new file mode 100644
index 0000000..0c6dced
--- /dev/null
+++ b/third_party/chromium/media/audio/mac/audio_device_listener_mac.h
@@ -0,0 +1,78 @@
+// Copyright (c) 2012 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 MEDIA_AUDIO_MAC_AUDIO_DEVICE_LISTENER_MAC_H_
+#define MEDIA_AUDIO_MAC_AUDIO_DEVICE_LISTENER_MAC_H_
+
+#include <CoreAudio/AudioHardware.h>
+
+#include <map>
+#include <memory>
+#include <utility>
+
+#include "base/callback.h"
+#include "base/containers/flat_map.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "media/base/media_export.h"
+
+namespace media {
+
+// AudioDeviceListenerMac facilitates execution of device listener callbacks
+// issued via CoreAudio.
+class MEDIA_EXPORT AudioDeviceListenerMac {
+ public:
+ // |listener_cb| will be called when a device change occurs; it's a permanent
+ // callback and must outlive AudioDeviceListenerMac. Note that |listener_cb|
+ // might not be executed on the same thread as construction.
+ AudioDeviceListenerMac(base::RepeatingClosure listener_cb,
+ bool monitor_default_input = false,
+ bool monitor_addition_removal = false,
+ bool monitor_sources = false);
+
+ AudioDeviceListenerMac(const AudioDeviceListenerMac&) = delete;
+ AudioDeviceListenerMac& operator=(const AudioDeviceListenerMac&) = delete;
+
+ ~AudioDeviceListenerMac();
+
+ private:
+ friend class AudioDeviceListenerMacTest;
+ class PropertyListener;
+ static const AudioObjectPropertyAddress
+ kDefaultOutputDeviceChangePropertyAddress;
+ static const AudioObjectPropertyAddress
+ kDefaultInputDeviceChangePropertyAddress;
+ static const AudioObjectPropertyAddress kDevicesPropertyAddress;
+
+ static OSStatus OnEvent(AudioObjectID object,
+ UInt32 num_addresses,
+ const AudioObjectPropertyAddress addresses[],
+ void* context);
+
+ bool AddPropertyListener(PropertyListener* property_listener);
+ void RemovePropertyListener(PropertyListener* property_listener);
+ void OnDevicesAddedOrRemoved();
+ void UpdateSourceListeners();
+
+ base::RepeatingClosure listener_cb_;
+ std::unique_ptr<PropertyListener> default_output_listener_;
+ std::unique_ptr<PropertyListener> default_input_listener_;
+ std::unique_ptr<PropertyListener> addition_removal_listener_;
+
+ using SourceListenerKey = std::pair<AudioObjectID, bool>;
+ using SourceListenerMap =
+ base::flat_map<SourceListenerKey, std::unique_ptr<PropertyListener>>;
+ SourceListenerMap source_listeners_;
+
+ // AudioDeviceListenerMac must be constructed and destructed on the same
+ // thread.
+ THREAD_CHECKER(thread_checker_);
+
+ base::WeakPtrFactory<AudioDeviceListenerMac> weak_factory_;
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_MAC_AUDIO_DEVICE_LISTENER_MAC_H_
diff --git a/third_party/chromium/media/audio/mac/audio_device_listener_mac_unittest.cc b/third_party/chromium/media/audio/mac/audio_device_listener_mac_unittest.cc
new file mode 100644
index 0000000..0a4599b
--- /dev/null
+++ b/third_party/chromium/media/audio/mac/audio_device_listener_mac_unittest.cc
@@ -0,0 +1,125 @@
+// Copyright (c) 2013 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 "media/audio/mac/audio_device_listener_mac.h"
+
+#include <CoreAudio/AudioHardware.h>
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/test/task_environment.h"
+#include "media/base/bind_to_current_loop.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+
+class AudioDeviceListenerMacTest : public testing::Test {
+ public:
+ AudioDeviceListenerMacTest() {
+ // It's important to create the device listener from the message loop in
+ // order to ensure we don't end up with unbalanced TaskObserver calls.
+ task_environment_.GetMainThreadTaskRunner()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&AudioDeviceListenerMacTest::CreateDeviceListener,
+ base::Unretained(this)));
+ base::RunLoop().RunUntilIdle();
+ }
+
+ AudioDeviceListenerMacTest(const AudioDeviceListenerMacTest&) = delete;
+ AudioDeviceListenerMacTest& operator=(const AudioDeviceListenerMacTest&) =
+ delete;
+
+ virtual ~AudioDeviceListenerMacTest() {
+ // It's important to destroy the device listener from the message loop in
+ // order to ensure we don't end up with unbalanced TaskObserver calls.
+ task_environment_.GetMainThreadTaskRunner()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&AudioDeviceListenerMacTest::DestroyDeviceListener,
+ base::Unretained(this)));
+ base::RunLoop().RunUntilIdle();
+ }
+
+ void CreateDeviceListener() {
+ // Force a post task using BindToCurrentLoop() to ensure device listener
+ // internals are working correctly.
+ device_listener_ = std::make_unique<AudioDeviceListenerMac>(
+ BindToCurrentLoop(
+ base::BindRepeating(&AudioDeviceListenerMacTest::OnDeviceChange,
+ base::Unretained(this))),
+ true /* monitor_default_input */, true /* monitor_addition_removal */);
+ }
+
+ void DestroyDeviceListener() { device_listener_.reset(); }
+
+ bool ListenerIsValid() { return !device_listener_->listener_cb_.is_null(); }
+
+ bool SimulateEvent(const AudioObjectPropertyAddress& address) {
+ // Include multiple addresses to ensure only a single device change event
+ // occurs.
+ const AudioObjectPropertyAddress addresses[] = {
+ address,
+ {kAudioHardwarePropertySleepingIsAllowed,
+ kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster}};
+
+ OSStatus status = device_listener_->OnEvent(
+ kAudioObjectSystemObject, 2, addresses,
+ device_listener_->default_output_listener_.get());
+ if (status != noErr)
+ return false;
+
+ device_listener_->OnEvent(kAudioObjectSystemObject, 2, addresses,
+ device_listener_->default_input_listener_.get());
+ if (status != noErr)
+ return false;
+
+ device_listener_->OnEvent(
+ kAudioObjectSystemObject, 2, addresses,
+ device_listener_->addition_removal_listener_.get());
+ return status == noErr;
+ }
+
+ bool SimulateDefaultOutputDeviceChange() {
+ return SimulateEvent(
+ AudioDeviceListenerMac::kDefaultOutputDeviceChangePropertyAddress);
+ }
+
+ bool SimulateDefaultInputDeviceChange() {
+ return SimulateEvent(
+ AudioDeviceListenerMac::kDefaultInputDeviceChangePropertyAddress);
+ }
+
+ bool SimulateDeviceAdditionRemoval() {
+ return SimulateEvent(AudioDeviceListenerMac::kDevicesPropertyAddress);
+ }
+
+ MOCK_METHOD0(OnDeviceChange, void());
+
+ protected:
+ base::test::SingleThreadTaskEnvironment task_environment_;
+ std::unique_ptr<AudioDeviceListenerMac> device_listener_;
+};
+
+// Simulate a device change event and ensure we get the right callback.
+TEST_F(AudioDeviceListenerMacTest, Events) {
+ ASSERT_TRUE(ListenerIsValid());
+ EXPECT_CALL(*this, OnDeviceChange()).Times(1);
+ ASSERT_TRUE(SimulateDefaultOutputDeviceChange());
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_CALL(*this, OnDeviceChange()).Times(1);
+ ASSERT_TRUE(SimulateDefaultInputDeviceChange());
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_CALL(*this, OnDeviceChange()).Times(1);
+ ASSERT_TRUE(SimulateDeviceAdditionRemoval());
+ base::RunLoop().RunUntilIdle();
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/mac/audio_input_mac.cc b/third_party/chromium/media/audio/mac/audio_input_mac.cc
new file mode 100644
index 0000000..275d284
--- /dev/null
+++ b/third_party/chromium/media/audio/mac/audio_input_mac.cc
@@ -0,0 +1,328 @@
+// Copyright (c) 2012 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 "media/audio/mac/audio_input_mac.h"
+
+#include <CoreServices/CoreServices.h>
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/mac/mac_logging.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/metrics/sparse_histogram.h"
+#include "base/trace_event/trace_event.h"
+#include "media/audio/mac/audio_manager_mac.h"
+#include "media/base/audio_bus.h"
+
+namespace media {
+
+namespace {
+// A one-shot timer is created and started in Start() and it triggers
+// CheckInputStartupSuccess() after this amount of time. UMA stats marked
+// Media.Audio.InputStartupSuccessMacHighLatency is then updated where true is
+// added if input callbacks have started, and false otherwise. This constant
+// should ideally be set to about the same value as in
+// audio_low_latency_input_mac.cc, to make comparing them reasonable.
+const int kInputCallbackStartTimeoutInSeconds = 8;
+} // namespace
+
+PCMQueueInAudioInputStream::PCMQueueInAudioInputStream(
+ AudioManagerMac* manager,
+ const AudioParameters& params)
+ : manager_(manager),
+ callback_(NULL),
+ audio_queue_(NULL),
+ buffer_size_bytes_(0),
+ started_(false),
+ input_callback_is_active_(false),
+ audio_bus_(media::AudioBus::Create(params)) {
+ // We must have a manager.
+ DCHECK(manager_);
+
+ const SampleFormat kSampleFormat = kSampleFormatS16;
+
+ // A frame is one sample across all channels. In interleaved audio the per
+ // frame fields identify the set of n |channels|. In uncompressed audio, a
+ // packet is always one frame.
+ format_.mSampleRate = params.sample_rate();
+ format_.mFormatID = kAudioFormatLinearPCM;
+ format_.mFormatFlags =
+ kLinearPCMFormatFlagIsPacked | kLinearPCMFormatFlagIsSignedInteger;
+ format_.mBitsPerChannel = SampleFormatToBitsPerChannel(kSampleFormat);
+ format_.mChannelsPerFrame = params.channels();
+ format_.mFramesPerPacket = 1;
+ format_.mBytesPerPacket = format_.mBytesPerFrame =
+ params.GetBytesPerFrame(kSampleFormat);
+ format_.mReserved = 0;
+
+ buffer_size_bytes_ = params.GetBytesPerBuffer(kSampleFormat);
+}
+
+PCMQueueInAudioInputStream::~PCMQueueInAudioInputStream() {
+ DCHECK(!callback_);
+ DCHECK(!audio_queue_);
+}
+
+AudioInputStream::OpenOutcome PCMQueueInAudioInputStream::Open() {
+ OSStatus err = AudioQueueNewInput(&format_,
+ &HandleInputBufferStatic,
+ this,
+ NULL, // Use OS CFRunLoop for |callback|
+ kCFRunLoopCommonModes,
+ 0, // Reserved
+ &audio_queue_);
+ if (err != noErr) {
+ HandleError(err);
+ return AudioInputStream::OpenOutcome::kFailed;
+ }
+ return SetupBuffers() ? AudioInputStream::OpenOutcome::kSuccess
+ : AudioInputStream::OpenOutcome::kFailed;
+}
+
+void PCMQueueInAudioInputStream::Start(AudioInputCallback* callback) {
+ DCHECK(callback);
+ DLOG_IF(ERROR, !audio_queue_) << "Open() has not been called successfully";
+ if (callback_ || !audio_queue_)
+ return;
+
+ // Check if we should defer Start() for http://crbug.com/160920.
+ if (manager_->ShouldDeferStreamStart()) {
+ // Use a cancellable closure so that if Stop() is called before Start()
+ // actually runs, we can cancel the pending start.
+ deferred_start_cb_.Reset(base::BindOnce(&PCMQueueInAudioInputStream::Start,
+ base::Unretained(this), callback));
+ manager_->GetTaskRunner()->PostDelayedTask(
+ FROM_HERE, deferred_start_cb_.callback(),
+ base::Seconds(AudioManagerMac::kStartDelayInSecsForPowerEvents));
+ return;
+ }
+
+ callback_ = callback;
+ OSStatus err = AudioQueueStart(audio_queue_, NULL);
+ if (err != noErr) {
+ HandleError(err);
+ } else {
+ started_ = true;
+ }
+
+ // For UMA stat purposes, start a one-shot timer which detects when input
+ // callbacks starts indicating if input audio recording starts as intended.
+ // CheckInputStartupSuccess() will check if |input_callback_is_active_| is
+ // true when the timer expires.
+ input_callback_timer_ = std::make_unique<base::OneShotTimer>();
+ input_callback_timer_->Start(
+ FROM_HERE, base::Seconds(kInputCallbackStartTimeoutInSeconds), this,
+ &PCMQueueInAudioInputStream::CheckInputStartupSuccess);
+ DCHECK(input_callback_timer_->IsRunning());
+}
+
+void PCMQueueInAudioInputStream::Stop() {
+ deferred_start_cb_.Cancel();
+ if (input_callback_timer_ != nullptr) {
+ input_callback_timer_->Stop();
+ input_callback_timer_.reset();
+ }
+ if (!audio_queue_ || !started_)
+ return;
+
+ // We request a synchronous stop, so the next call can take some time. In
+ // the windows implementation we block here as well.
+ OSStatus err = AudioQueueStop(audio_queue_, true);
+ if (err != noErr)
+ HandleError(err);
+
+ SetInputCallbackIsActive(false);
+ started_ = false;
+ callback_ = NULL;
+}
+
+void PCMQueueInAudioInputStream::Close() {
+ Stop();
+
+ // It is valid to call Close() before calling Open() or Start(), thus
+ // |audio_queue_| and |callback_| might be NULL.
+ if (audio_queue_) {
+ OSStatus err = AudioQueueDispose(audio_queue_, true);
+ audio_queue_ = NULL;
+ if (err != noErr)
+ HandleError(err);
+ }
+
+ manager_->ReleaseInputStream(this);
+ // CARE: This object may now be destroyed.
+}
+
+double PCMQueueInAudioInputStream::GetMaxVolume() {
+ NOTREACHED() << "Only supported for low-latency mode.";
+ return 0.0;
+}
+
+void PCMQueueInAudioInputStream::SetVolume(double volume) {
+ NOTREACHED() << "Only supported for low-latency mode.";
+}
+
+double PCMQueueInAudioInputStream::GetVolume() {
+ NOTREACHED() << "Only supported for low-latency mode.";
+ return 0.0;
+}
+
+bool PCMQueueInAudioInputStream::IsMuted() {
+ NOTREACHED() << "Only supported for low-latency mode.";
+ return false;
+}
+
+bool PCMQueueInAudioInputStream::SetAutomaticGainControl(bool enabled) {
+ NOTREACHED() << "Only supported for low-latency mode.";
+ return false;
+}
+
+bool PCMQueueInAudioInputStream::GetAutomaticGainControl() {
+ NOTREACHED() << "Only supported for low-latency mode.";
+ return false;
+}
+
+void PCMQueueInAudioInputStream::SetOutputDeviceForAec(
+ const std::string& output_device_id) {
+ // Not supported. Do nothing.
+}
+
+void PCMQueueInAudioInputStream::HandleError(OSStatus err) {
+ if (callback_)
+ callback_->OnError();
+ // This point should never be reached.
+ OSSTATUS_DCHECK(0, err);
+}
+
+bool PCMQueueInAudioInputStream::SetupBuffers() {
+ DCHECK(buffer_size_bytes_);
+ for (int i = 0; i < kNumberBuffers; ++i) {
+ AudioQueueBufferRef buffer;
+ OSStatus err = AudioQueueAllocateBuffer(audio_queue_,
+ buffer_size_bytes_,
+ &buffer);
+ if (err == noErr)
+ err = QueueNextBuffer(buffer);
+ if (err != noErr) {
+ HandleError(err);
+ return false;
+ }
+ // |buffer| will automatically be freed when |audio_queue_| is released.
+ }
+ return true;
+}
+
+OSStatus PCMQueueInAudioInputStream::QueueNextBuffer(
+ AudioQueueBufferRef audio_buffer) {
+ // Only the first 2 params are needed for recording.
+ return AudioQueueEnqueueBuffer(audio_queue_, audio_buffer, 0, NULL);
+}
+
+// static
+void PCMQueueInAudioInputStream::HandleInputBufferStatic(
+ void* data,
+ AudioQueueRef audio_queue,
+ AudioQueueBufferRef audio_buffer,
+ const AudioTimeStamp* start_time,
+ UInt32 num_packets,
+ const AudioStreamPacketDescription* desc) {
+ reinterpret_cast<PCMQueueInAudioInputStream*>(data)->
+ HandleInputBuffer(audio_queue, audio_buffer, start_time,
+ num_packets, desc);
+}
+
+void PCMQueueInAudioInputStream::HandleInputBuffer(
+ AudioQueueRef audio_queue,
+ AudioQueueBufferRef audio_buffer,
+ const AudioTimeStamp* start_time,
+ UInt32 num_packets,
+ const AudioStreamPacketDescription* packet_desc) {
+ DCHECK_EQ(audio_queue_, audio_queue);
+ DCHECK(audio_buffer->mAudioData);
+ TRACE_EVENT0("audio", "PCMQueueInAudioInputStream::HandleInputBuffer");
+ if (!callback_) {
+ // This can happen if Stop() was called without start.
+ DCHECK_EQ(0U, audio_buffer->mAudioDataByteSize);
+ return;
+ }
+
+ // Indicate that input callbacks have started.
+ SetInputCallbackIsActive(true);
+
+ if (audio_buffer->mAudioDataByteSize) {
+ // The AudioQueue API may use a large internal buffer and repeatedly call us
+ // back to back once that internal buffer is filled. When this happens the
+ // renderer client does not have enough time to read data back from the
+ // shared memory before the next write comes along. If HandleInputBuffer()
+ // is called too frequently, Sleep() at least 5ms to ensure the shared
+ // memory doesn't get trampled.
+ // TODO(dalecurtis): This is a HACK. Long term the AudioQueue path is going
+ // away in favor of the AudioUnit based AUAudioInputStream(). Tracked by
+ // http://crbug.com/161383.
+ // TODO(dalecurtis): Delete all this. It shouldn't be necessary now that we
+ // have a ring buffer and FIFO on the actual shared memory.
+ base::TimeDelta elapsed = base::TimeTicks::Now() - last_fill_;
+ const base::TimeDelta kMinDelay = base::Milliseconds(5);
+ if (elapsed < kMinDelay) {
+ TRACE_EVENT0("audio",
+ "PCMQueueInAudioInputStream::HandleInputBuffer sleep");
+ base::PlatformThread::Sleep(kMinDelay - elapsed);
+ }
+
+ // TODO(dalecurtis): This should be updated to include the device latency,
+ // but really since Pepper (which ignores the delay value) is on the only
+ // one creating AUDIO_PCM_LINEAR input devices, it doesn't matter.
+ // https://lists.apple.com/archives/coreaudio-api/2017/Jul/msg00035.html
+ const base::TimeTicks capture_time =
+ start_time->mFlags & kAudioTimeStampHostTimeValid
+ ? base::TimeTicks::FromMachAbsoluteTime(start_time->mHostTime)
+ : base::TimeTicks::Now();
+
+ uint8_t* audio_data = reinterpret_cast<uint8_t*>(audio_buffer->mAudioData);
+ DCHECK_EQ(format_.mBitsPerChannel, 16u);
+ audio_bus_->FromInterleaved<SignedInt16SampleTypeTraits>(
+ reinterpret_cast<int16_t*>(audio_data), audio_bus_->frames());
+ callback_->OnData(audio_bus_.get(), capture_time, 0.0);
+
+ last_fill_ = base::TimeTicks::Now();
+ }
+ // Recycle the buffer.
+ OSStatus err = QueueNextBuffer(audio_buffer);
+ if (err != noErr) {
+ if (err == kAudioQueueErr_EnqueueDuringReset) {
+ // This is the error you get if you try to enqueue a buffer and the
+ // queue has been closed. Not really a problem if indeed the queue
+ // has been closed.
+ // TODO(joth): PCMQueueOutAudioOutputStream uses callback_ to provide an
+ // extra guard for this situation, but it seems to introduce more
+ // complications than it solves (memory barrier issues accessing it from
+ // multiple threads, looses the means to indicate OnClosed to client).
+ // Should determine if we need to do something equivalent here.
+ return;
+ }
+ HandleError(err);
+ }
+}
+
+void PCMQueueInAudioInputStream::SetInputCallbackIsActive(bool enabled) {
+ base::subtle::Release_Store(&input_callback_is_active_, enabled);
+}
+
+bool PCMQueueInAudioInputStream::GetInputCallbackIsActive() {
+ return (base::subtle::Acquire_Load(&input_callback_is_active_) != false);
+}
+
+void PCMQueueInAudioInputStream::CheckInputStartupSuccess() {
+ // Check if we have called Start() and input callbacks have actually
+ // started in time as they should. If that is not the case, we have a
+ // problem and the stream is considered dead.
+ const bool input_callback_is_active = GetInputCallbackIsActive();
+ UMA_HISTOGRAM_BOOLEAN("Media.Audio.InputStartupSuccessMac_HighLatency",
+ input_callback_is_active);
+ DVLOG(1) << "high_latency_input_callback_is_active: "
+ << input_callback_is_active;
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/mac/audio_input_mac.h b/third_party/chromium/media/audio/mac/audio_input_mac.h
new file mode 100644
index 0000000..7cab433
--- /dev/null
+++ b/third_party/chromium/media/audio/mac/audio_input_mac.h
@@ -0,0 +1,123 @@
+// Copyright (c) 2012 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 MEDIA_AUDIO_MAC_AUDIO_INPUT_MAC_H_
+#define MEDIA_AUDIO_MAC_AUDIO_INPUT_MAC_H_
+
+#include <AudioToolbox/AudioFormat.h>
+#include <AudioToolbox/AudioQueue.h>
+#include <stdint.h>
+
+#include <memory>
+
+#include "base/atomicops.h"
+#include "base/cancelable_callback.h"
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "media/audio/audio_io.h"
+#include "media/base/audio_parameters.h"
+
+namespace media {
+
+class AudioBus;
+class AudioManagerMac;
+
+// Implementation of AudioInputStream for Mac OS X using the audio queue service
+// present in OS 10.5 and later. Design reflects PCMQueueOutAudioOutputStream.
+class PCMQueueInAudioInputStream : public AudioInputStream {
+ public:
+ // Parameters as per AudioManager::MakeAudioInputStream.
+ PCMQueueInAudioInputStream(AudioManagerMac* manager,
+ const AudioParameters& params);
+
+ PCMQueueInAudioInputStream(const PCMQueueInAudioInputStream&) = delete;
+ PCMQueueInAudioInputStream& operator=(const PCMQueueInAudioInputStream&) =
+ delete;
+
+ ~PCMQueueInAudioInputStream() override;
+
+ // Implementation of AudioInputStream.
+ AudioInputStream::OpenOutcome Open() override;
+ void Start(AudioInputCallback* callback) override;
+ void Stop() override;
+ void Close() override;
+ double GetMaxVolume() override;
+ void SetVolume(double volume) override;
+ double GetVolume() override;
+ bool SetAutomaticGainControl(bool enabled) override;
+ bool GetAutomaticGainControl() override;
+ bool IsMuted() override;
+ void SetOutputDeviceForAec(const std::string& output_device_id) override;
+
+ private:
+ // Issue the OnError to |callback_|;
+ void HandleError(OSStatus err);
+
+ // Allocates and prepares the memory that will be used for recording.
+ bool SetupBuffers();
+
+ // Sends a buffer to the audio driver for recording.
+ OSStatus QueueNextBuffer(AudioQueueBufferRef audio_buffer);
+
+ // Callback from OS, delegates to non-static version below.
+ static void HandleInputBufferStatic(
+ void* data,
+ AudioQueueRef audio_queue,
+ AudioQueueBufferRef audio_buffer,
+ const AudioTimeStamp* start_time,
+ UInt32 num_packets,
+ const AudioStreamPacketDescription* desc);
+
+ // Handles callback from OS. Will be called on OS internal thread.
+ void HandleInputBuffer(AudioQueueRef audio_queue,
+ AudioQueueBufferRef audio_buffer,
+ const AudioTimeStamp* start_time,
+ UInt32 num_packets,
+ const AudioStreamPacketDescription* packet_desc);
+
+ static const int kNumberBuffers = 3;
+
+ // Helper methods to set and get atomic |input_callback_is_active_|.
+ void SetInputCallbackIsActive(bool active);
+ bool GetInputCallbackIsActive();
+
+ // Checks if a stream was started successfully and the audio unit also starts
+ // to call InputProc() as it should. This method is called once when a timer
+ // expires, a few seconds after calling Start().
+ void CheckInputStartupSuccess();
+
+ // Manager that owns this stream, used for closing down.
+ AudioManagerMac* manager_;
+ // We use the callback mostly to periodically supply the recorded audio data.
+ AudioInputCallback* callback_;
+ // Structure that holds the stream format details such as bitrate.
+ AudioStreamBasicDescription format_;
+ // Handle to the OS audio queue object.
+ AudioQueueRef audio_queue_;
+ // Size of each of the buffers in |audio_buffers_|
+ uint32_t buffer_size_bytes_;
+ // True iff Start() has been called successfully.
+ bool started_;
+ // Used to determine if we need to slow down |callback_| calls.
+ base::TimeTicks last_fill_;
+ // Used to defer Start() to workaround http://crbug.com/160920.
+ base::CancelableOnceClosure deferred_start_cb_;
+
+ // Is set to true on the internal AUHAL IO thread in the first input callback
+ // after Start() has bee called.
+ base::subtle::Atomic32 input_callback_is_active_;
+
+ // Timer which triggers CheckInputStartupSuccess() to verify that input
+ // callbacks have started as intended after a successful call to Start().
+ // This timer lives on the main browser thread.
+ std::unique_ptr<base::OneShotTimer> input_callback_timer_;
+
+ std::unique_ptr<media::AudioBus> audio_bus_;
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_MAC_AUDIO_INPUT_MAC_H_
diff --git a/third_party/chromium/media/audio/mac/audio_low_latency_input_mac.cc b/third_party/chromium/media/audio/mac/audio_low_latency_input_mac.cc
new file mode 100644
index 0000000..e28d374
--- /dev/null
+++ b/third_party/chromium/media/audio/mac/audio_low_latency_input_mac.cc
@@ -0,0 +1,1485 @@
+// Copyright (c) 2012 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 "media/audio/mac/audio_low_latency_input_mac.h"
+
+#include <CoreAudio/AudioHardware.h>
+#include <CoreServices/CoreServices.h>
+#include <dlfcn.h>
+#include <mach-o/loader.h>
+#include <mach/mach.h>
+#include <memory>
+#include <string>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/mac/foundation_util.h"
+#include "base/mac/mac_logging.h"
+#include "base/mac/mac_util.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/mac/scoped_mach_port.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/strings/strcat.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/system/sys_info.h"
+#include "base/time/time.h"
+#include "base/trace_event/trace_event.h"
+#include "media/audio/mac/core_audio_util_mac.h"
+#include "media/audio/mac/scoped_audio_unit.h"
+#include "media/base/audio_bus.h"
+#include "media/base/audio_timestamp_helper.h"
+#include "media/base/data_buffer.h"
+
+namespace {
+extern "C" {
+// See:
+// https://trac.webkit.org/browser/webkit/trunk/Source/WebCore/PAL/pal/spi/cf/CoreAudioSPI.h?rev=228264
+OSStatus AudioDeviceDuck(AudioDeviceID inDevice,
+ Float32 inDuckedLevel,
+ const AudioTimeStamp* __nullable inStartTime,
+ Float32 inRampDuration) __attribute__((weak_import));
+}
+
+void UndoDucking(AudioDeviceID output_device_id) {
+ if (AudioDeviceDuck != nullptr) {
+ // Ramp the volume back up over half a second.
+ AudioDeviceDuck(output_device_id, 1.0, nullptr, 0.5);
+ }
+}
+
+} // namespace
+
+namespace media {
+
+// Number of blocks of buffers used in the |fifo_|.
+const int kNumberOfBlocksBufferInFifo = 2;
+
+// Max length of sequence of TooManyFramesToProcessError errors.
+// The stream will be stopped as soon as this time limit is passed.
+const int kMaxErrorTimeoutInSeconds = 1;
+
+// A one-shot timer is created and started in Start() and it triggers
+// CheckInputStartupSuccess() after this amount of time. UMA stats marked
+// Media.Audio.InputStartupSuccessMac is then updated where true is added
+// if input callbacks have started, and false otherwise.
+const int kInputCallbackStartTimeoutInSeconds = 5;
+
+// Returns true if the format flags in |format_flags| has the "non-interleaved"
+// flag (kAudioFormatFlagIsNonInterleaved) cleared (set to 0).
+static bool FormatIsInterleaved(UInt32 format_flags) {
+ return !(format_flags & kAudioFormatFlagIsNonInterleaved);
+}
+
+// Converts the 32-bit non-terminated 4 byte string into an std::string.
+// Example: code=1735354734 <=> 'goin' <=> kAudioDevicePropertyDeviceIsRunning.
+static std::string FourCharFormatCodeToString(UInt32 code) {
+ char code_string[5];
+ // Converts a 32-bit integer from the host’s native byte order to big-endian.
+ UInt32 code_id = CFSwapInt32HostToBig(code);
+ bcopy(&code_id, code_string, 4);
+ code_string[4] = '\0';
+ return std::string(code_string);
+}
+
+static std::ostream& operator<<(std::ostream& os,
+ const AudioStreamBasicDescription& format) {
+ std::string format_string = FourCharFormatCodeToString(format.mFormatID);
+ os << "sample rate : " << format.mSampleRate << std::endl
+ << "format ID : " << format_string << std::endl
+ << "format flags : " << format.mFormatFlags << std::endl
+ << "bytes per packet : " << format.mBytesPerPacket << std::endl
+ << "frames per packet : " << format.mFramesPerPacket << std::endl
+ << "bytes per frame : " << format.mBytesPerFrame << std::endl
+ << "channels per frame: " << format.mChannelsPerFrame << std::endl
+ << "bits per channel : " << format.mBitsPerChannel << std::endl
+ << "reserved : " << format.mReserved << std::endl
+ << "interleaved : "
+ << (FormatIsInterleaved(format.mFormatFlags) ? "yes" : "no");
+ return os;
+}
+
+static OSStatus OnGetPlayoutData(void* in_ref_con,
+ AudioUnitRenderActionFlags* flags,
+ const AudioTimeStamp* time_stamp,
+ UInt32 bus_number,
+ UInt32 num_frames,
+ AudioBufferList* io_data) {
+ *flags |= kAudioUnitRenderAction_OutputIsSilence;
+ return noErr;
+}
+
+static OSStatus GetInputDeviceStreamFormat(
+ AudioUnit audio_unit,
+ AudioStreamBasicDescription* format) {
+ DCHECK(audio_unit);
+ UInt32 property_size = sizeof(*format);
+ // Get the audio stream data format on the input scope of the input element
+ // since it is connected to the current input device.
+ OSStatus result =
+ AudioUnitGetProperty(audio_unit, kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Input, 1, format, &property_size);
+ DVLOG(1) << "Input device stream format: " << *format;
+ return result;
+}
+
+// Returns the number of physical processors on the device.
+static int NumberOfPhysicalProcessors() {
+ base::mac::ScopedMachSendRight mach_host(mach_host_self());
+ host_basic_info hbi = {};
+ mach_msg_type_number_t info_count = HOST_BASIC_INFO_COUNT;
+ kern_return_t kr =
+ host_info(mach_host.get(), HOST_BASIC_INFO,
+ reinterpret_cast<host_info_t>(&hbi), &info_count);
+
+ int n_physical_cores = 0;
+ if (kr != KERN_SUCCESS) {
+ n_physical_cores = 1;
+ LOG(ERROR) << "Failed to determine number of physical cores, assuming 1";
+ } else {
+ n_physical_cores = hbi.physical_cpu;
+ }
+ DCHECK_EQ(HOST_BASIC_INFO_COUNT, info_count);
+ return n_physical_cores;
+}
+
+// Adds extra system information to Media.AudioXXXMac UMA statistics.
+// Only called when it has been detected that audio callbacks does not start
+// as expected.
+static void AddSystemInfoToUMA() {
+ // Number of logical processors/cores on the current machine.
+ UMA_HISTOGRAM_COUNTS_1M("Media.Audio.LogicalProcessorsMac",
+ base::SysInfo::NumberOfProcessors());
+ // Number of physical processors/cores on the current machine.
+ UMA_HISTOGRAM_COUNTS_1M("Media.Audio.PhysicalProcessorsMac",
+ NumberOfPhysicalProcessors());
+ DVLOG(1) << "logical processors: " << base::SysInfo::NumberOfProcessors();
+ DVLOG(1) << "physical processors: " << NumberOfPhysicalProcessors();
+}
+
+// Finds the first subdevice, in an aggregate device, with output streams.
+static AudioDeviceID FindFirstOutputSubdevice(
+ AudioDeviceID aggregate_device_id) {
+ const AudioObjectPropertyAddress property_address = {
+ kAudioAggregateDevicePropertyFullSubDeviceList,
+ kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster};
+ base::ScopedCFTypeRef<CFArrayRef> subdevices;
+ UInt32 size = sizeof(subdevices);
+ OSStatus result = AudioObjectGetPropertyData(
+ aggregate_device_id, &property_address, 0 /* inQualifierDataSize */,
+ nullptr /* inQualifierData */, &size, subdevices.InitializeInto());
+
+ if (result != noErr) {
+ OSSTATUS_LOG(WARNING, result)
+ << "Failed to read property "
+ << kAudioAggregateDevicePropertyFullSubDeviceList << " for device "
+ << aggregate_device_id;
+ return kAudioObjectUnknown;
+ }
+
+ AudioDeviceID output_subdevice_id = kAudioObjectUnknown;
+ DCHECK_EQ(CFGetTypeID(subdevices), CFArrayGetTypeID());
+ const CFIndex count = CFArrayGetCount(subdevices);
+ for (CFIndex i = 0; i != count; ++i) {
+ CFStringRef value =
+ base::mac::CFCast<CFStringRef>(CFArrayGetValueAtIndex(subdevices, i));
+ if (value) {
+ std::string uid = base::SysCFStringRefToUTF8(value);
+ output_subdevice_id = AudioManagerMac::GetAudioDeviceIdByUId(false, uid);
+ if (output_subdevice_id != kAudioObjectUnknown &&
+ core_audio_mac::GetNumStreams(output_subdevice_id, false) > 0) {
+ break;
+ }
+ }
+ }
+
+ return output_subdevice_id;
+}
+
+// See "Technical Note TN2091 - Device input using the HAL Output Audio Unit"
+// http://developer.apple.com/library/mac/#technotes/tn2091/_index.html
+// for more details and background regarding this implementation.
+
+AUAudioInputStream::AUAudioInputStream(
+ AudioManagerMac* manager,
+ const AudioParameters& input_params,
+ AudioDeviceID audio_device_id,
+ const AudioManager::LogCallback& log_callback,
+ AudioManagerBase::VoiceProcessingMode voice_processing_mode)
+ : manager_(manager),
+ input_params_(input_params),
+ number_of_frames_provided_(0),
+ io_buffer_frame_size_(0),
+ sink_(nullptr),
+ audio_unit_(0),
+ input_device_id_(audio_device_id),
+ number_of_channels_in_frame_(0),
+ fifo_(input_params.channels(),
+ input_params.frames_per_buffer(),
+ kNumberOfBlocksBufferInFifo),
+ got_input_callback_(false),
+ input_callback_is_active_(false),
+ buffer_size_was_changed_(false),
+ audio_unit_render_has_worked_(false),
+ noise_reduction_suppressed_(false),
+ use_voice_processing_(voice_processing_mode ==
+ AudioManagerBase::VoiceProcessingMode::kEnabled),
+ output_device_id_for_aec_(kAudioObjectUnknown),
+ last_sample_time_(0.0),
+ last_number_of_frames_(0),
+ total_lost_frames_(0),
+ largest_glitch_frames_(0),
+ glitches_detected_(0),
+ log_callback_(log_callback) {
+ DCHECK(manager_);
+ CHECK(log_callback_ != AudioManager::LogCallback());
+ if (use_voice_processing_) {
+ DCHECK(input_params.channels() == 1 || input_params.channels() == 2);
+ const bool got_default_device =
+ AudioManagerMac::GetDefaultOutputDevice(&output_device_id_for_aec_);
+ DCHECK(got_default_device);
+ }
+
+ const SampleFormat kSampleFormat = kSampleFormatS16;
+
+ // Set up the desired (output) format specified by the client.
+ format_.mSampleRate = input_params.sample_rate();
+ format_.mFormatID = kAudioFormatLinearPCM;
+ format_.mFormatFlags =
+ kLinearPCMFormatFlagIsPacked | kLinearPCMFormatFlagIsSignedInteger;
+ DCHECK(FormatIsInterleaved(format_.mFormatFlags));
+ format_.mBitsPerChannel = SampleFormatToBitsPerChannel(kSampleFormat);
+ format_.mChannelsPerFrame = input_params.channels();
+ format_.mFramesPerPacket = 1; // uncompressed audio
+ format_.mBytesPerPacket = format_.mBytesPerFrame =
+ input_params.GetBytesPerFrame(kSampleFormat);
+ format_.mReserved = 0;
+
+ DVLOG(1) << "ctor";
+ DVLOG(1) << "device ID: 0x" << std::hex << audio_device_id;
+ DVLOG(1) << "buffer size : " << input_params.frames_per_buffer();
+ DVLOG(1) << "channels : " << input_params.channels();
+ DVLOG(1) << "desired output format: " << format_;
+
+ // Derive size (in bytes) of the buffers that we will render to.
+ UInt32 data_byte_size =
+ input_params.frames_per_buffer() * format_.mBytesPerFrame;
+ DVLOG(1) << "size of data buffer in bytes : " << data_byte_size;
+
+ // Allocate AudioBuffers to be used as storage for the received audio.
+ // The AudioBufferList structure works as a placeholder for the
+ // AudioBuffer structure, which holds a pointer to the actual data buffer.
+ audio_data_buffer_.reset(new uint8_t[data_byte_size]);
+ // We ask for noninterleaved audio.
+ audio_buffer_list_.mNumberBuffers = 1;
+
+ AudioBuffer* audio_buffer = audio_buffer_list_.mBuffers;
+ audio_buffer->mNumberChannels = input_params.channels();
+ audio_buffer->mDataByteSize = data_byte_size;
+ audio_buffer->mData = audio_data_buffer_.get();
+}
+
+AUAudioInputStream::~AUAudioInputStream() {
+ DVLOG(1) << "~dtor";
+ ReportAndResetStats();
+}
+
+// Obtain and open the AUHAL AudioOutputUnit for recording.
+AudioInputStream::OpenOutcome AUAudioInputStream::Open() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DVLOG(1) << "Open";
+ DCHECK(!audio_unit_);
+
+ // Verify that we have a valid device. Send appropriate error code to
+ // HandleError() to ensure that the error type is added to UMA stats.
+ if (input_device_id_ == kAudioObjectUnknown) {
+ NOTREACHED() << "Device ID is unknown";
+ HandleError(kAudioUnitErr_InvalidElement);
+ return OpenOutcome::kFailed;
+ }
+
+ // The requested sample-rate must match the hardware sample-rate.
+ const int sample_rate =
+ AudioManagerMac::HardwareSampleRateForDevice(input_device_id_);
+ DCHECK_EQ(sample_rate, format_.mSampleRate);
+
+ log_callback_.Run(base::StrCat(
+ {"AU in: Open using ", use_voice_processing_ ? "VPAU" : "AUHAL"}));
+
+ const bool success =
+ use_voice_processing_ ? OpenVoiceProcessingAU() : OpenAUHAL();
+
+ if (!success)
+ return OpenOutcome::kFailed;
+
+ // The hardware latency is fixed and will not change during the call.
+ hardware_latency_ = AudioManagerMac::GetHardwareLatency(
+ audio_unit_, input_device_id_, kAudioDevicePropertyScopeInput,
+ format_.mSampleRate);
+
+ // The master channel is 0, Left and right are channels 1 and 2.
+ // And the master channel is not counted in |number_of_channels_in_frame_|.
+ number_of_channels_in_frame_ = GetNumberOfChannelsFromStream();
+
+ return OpenOutcome::kSuccess;
+}
+
+bool AUAudioInputStream::OpenAUHAL() {
+ // Start by obtaining an AudioOuputUnit using an AUHAL component description.
+
+ // Description for the Audio Unit we want to use (AUHAL in this case).
+ // The kAudioUnitSubType_HALOutput audio unit interfaces to any audio device.
+ // The user specifies which audio device to track. The audio unit can do
+ // input from the device as well as output to the device. Bus 0 is used for
+ // the output side, bus 1 is used to get audio input from the device.
+ AudioComponentDescription desc = {kAudioUnitType_Output,
+ kAudioUnitSubType_HALOutput,
+ kAudioUnitManufacturer_Apple, 0, 0};
+
+ // Find a component that meets the description in |desc|.
+ AudioComponent comp = AudioComponentFindNext(nullptr, &desc);
+ DCHECK(comp);
+ if (!comp) {
+ HandleError(kAudioUnitErr_NoConnection);
+ return false;
+ }
+
+ // Get access to the service provided by the specified Audio Unit.
+ OSStatus result = AudioComponentInstanceNew(comp, &audio_unit_);
+ if (result) {
+ HandleError(result);
+ return false;
+ }
+
+ // Initialize the AUHAL before making any changes or using it. The audio
+ // unit will be initialized once more as last operation in this method but
+ // that is intentional. This approach is based on a comment in the
+ // CAPlayThrough example from Apple, which states that "AUHAL needs to be
+ // initialized *before* anything is done to it".
+ // TODO(henrika): remove this extra call if we are unable to see any
+ // positive effects of it in our UMA stats.
+ result = AudioUnitInitialize(audio_unit_);
+ if (result != noErr) {
+ HandleError(result);
+ return false;
+ }
+
+ // Enable IO on the input scope of the Audio Unit.
+ // Note that, these changes must be done *before* setting the AUHAL's
+ // current device.
+
+ // After creating the AUHAL object, we must enable IO on the input scope
+ // of the Audio Unit to obtain the device input. Input must be explicitly
+ // enabled with the kAudioOutputUnitProperty_EnableIO property on Element 1
+ // of the AUHAL. Because the AUHAL can be used for both input and output,
+ // we must also disable IO on the output scope.
+
+ // kAudioOutputUnitProperty_EnableIO is not a writable property of the
+ // voice processing unit (we'd get kAudioUnitErr_PropertyNotWritable returned
+ // back to us). IO is always enabled.
+
+ // Enable input on the AUHAL.
+ {
+ const UInt32 enableIO = 1;
+ result = AudioUnitSetProperty(
+ audio_unit_, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input,
+ AUElement::INPUT, &enableIO, sizeof(enableIO));
+ if (result != noErr) {
+ HandleError(result);
+ return false;
+ }
+ }
+
+ // Disable output on the AUHAL.
+ {
+ const UInt32 disableIO = 0;
+ result = AudioUnitSetProperty(
+ audio_unit_, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output,
+ AUElement::OUTPUT, &disableIO, sizeof(disableIO));
+ if (result != noErr) {
+ HandleError(result);
+ return false;
+ }
+ }
+
+ // Next, set the audio device to be the Audio Unit's current device.
+ // Note that, devices can only be set to the AUHAL after enabling IO.
+ result =
+ AudioUnitSetProperty(audio_unit_, kAudioOutputUnitProperty_CurrentDevice,
+ kAudioUnitScope_Global, AUElement::OUTPUT,
+ &input_device_id_, sizeof(input_device_id_));
+
+ if (result != noErr) {
+ HandleError(result);
+ return false;
+ }
+
+ // Register the input procedure for the AUHAL. This procedure will be called
+ // when the AUHAL has received new data from the input device.
+ AURenderCallbackStruct callback;
+ callback.inputProc = &DataIsAvailable;
+ callback.inputProcRefCon = this;
+ result = AudioUnitSetProperty(
+ audio_unit_, kAudioOutputUnitProperty_SetInputCallback,
+ kAudioUnitScope_Global, AUElement::OUTPUT, &callback, sizeof(callback));
+
+ if (result != noErr) {
+ HandleError(result);
+ return false;
+ }
+
+ // Get the stream format for the selected input device and ensure that the
+ // sample rate of the selected input device matches the desired (given at
+ // construction) sample rate. We should not rely on sample rate conversion
+ // in the AUHAL, only *simple* conversions, e.g., 32-bit float to 16-bit
+ // signed integer format.
+ AudioStreamBasicDescription input_device_format = {0};
+ GetInputDeviceStreamFormat(audio_unit_, &input_device_format);
+ if (input_device_format.mSampleRate != format_.mSampleRate) {
+ LOG(ERROR) << "Input device's sample rate does not match the client's "
+ "sample rate; input_device_format="
+ << input_device_format;
+ result = kAudioUnitErr_FormatNotSupported;
+ HandleError(result);
+ return false;
+ }
+
+ // Modify the IO buffer size if not already set correctly for the selected
+ // device. The status of other active audio input and output streams is
+ // involved in the final setting.
+ // TODO(henrika): we could make io_buffer_frame_size a member and add it to
+ // the UMA stats tied to the Media.Audio.InputStartupSuccessMac record.
+ size_t io_buffer_frame_size = 0;
+ if (!manager_->MaybeChangeBufferSize(
+ input_device_id_, audio_unit_, 1, input_params_.frames_per_buffer(),
+ &buffer_size_was_changed_, &io_buffer_frame_size)) {
+ result = kAudioUnitErr_FormatNotSupported;
+ HandleError(result);
+ return false;
+ }
+
+ // Store current I/O buffer frame size for UMA stats stored in combination
+ // with failing input callbacks.
+ DCHECK(!io_buffer_frame_size_);
+ io_buffer_frame_size_ = io_buffer_frame_size;
+
+ // If the requested number of frames is out of range, the closest valid buffer
+ // size will be set instead. Check the current setting and log a warning for a
+ // non perfect match. Any such mismatch will be compensated for in
+ // OnDataIsAvailable().
+ UInt32 buffer_frame_size = 0;
+ UInt32 property_size = sizeof(buffer_frame_size);
+ result = AudioUnitGetProperty(
+ audio_unit_, kAudioDevicePropertyBufferFrameSize, kAudioUnitScope_Global,
+ AUElement::OUTPUT, &buffer_frame_size, &property_size);
+ LOG_IF(WARNING, buffer_frame_size !=
+ static_cast<UInt32>(input_params_.frames_per_buffer()))
+ << "AUHAL is using best match of IO buffer size: " << buffer_frame_size;
+
+ // Channel mapping should be supported but add a warning just in case.
+ // TODO(henrika): perhaps add to UMA stat to track if this can happen.
+ DLOG_IF(WARNING,
+ input_device_format.mChannelsPerFrame != format_.mChannelsPerFrame)
+ << "AUHAL's audio converter must do channel conversion";
+
+ // Set up the the desired (output) format.
+ // For obtaining input from a device, the device format is always expressed
+ // on the output scope of the AUHAL's Element 1.
+ result = AudioUnitSetProperty(audio_unit_, kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Output, 1, &format_,
+ sizeof(format_));
+ if (result != noErr) {
+ HandleError(result);
+ return false;
+ }
+
+ // Finally, initialize the audio unit and ensure that it is ready to render.
+ // Allocates memory according to the maximum number of audio frames
+ // it can produce in response to a single render call.
+ result = AudioUnitInitialize(audio_unit_);
+ if (result != noErr) {
+ HandleError(result);
+ return false;
+ }
+
+ return true;
+}
+
+bool AUAudioInputStream::OpenVoiceProcessingAU() {
+ // Start by obtaining an AudioOuputUnit using an AUHAL component description.
+
+ // Description for the Audio Unit we want to use (AUHAL in this case).
+ // The kAudioUnitSubType_HALOutput audio unit interfaces to any audio device.
+ // The user specifies which audio device to track. The audio unit can do
+ // input from the device as well as output to the device. Bus 0 is used for
+ // the output side, bus 1 is used to get audio input from the device.
+ AudioComponentDescription desc = {kAudioUnitType_Output,
+ kAudioUnitSubType_VoiceProcessingIO,
+ kAudioUnitManufacturer_Apple, 0, 0};
+
+ // Find a component that meets the description in |desc|.
+ AudioComponent comp = AudioComponentFindNext(nullptr, &desc);
+ DCHECK(comp);
+ if (!comp) {
+ HandleError(kAudioUnitErr_NoConnection);
+ return false;
+ }
+
+ // Get access to the service provided by the specified Audio Unit.
+ OSStatus result = AudioComponentInstanceNew(comp, &audio_unit_);
+ if (result) {
+ HandleError(result);
+ return false;
+ }
+
+ // Next, set the audio device to be the Audio Unit's input device.
+ result =
+ AudioUnitSetProperty(audio_unit_, kAudioOutputUnitProperty_CurrentDevice,
+ kAudioUnitScope_Global, AUElement::INPUT,
+ &input_device_id_, sizeof(input_device_id_));
+
+ if (result != noErr) {
+ HandleError(result);
+ return false;
+ }
+
+ // Followed by the audio device to be the Audio Unit's output device.
+ result = AudioUnitSetProperty(
+ audio_unit_, kAudioOutputUnitProperty_CurrentDevice,
+ kAudioUnitScope_Global, AUElement::OUTPUT, &output_device_id_for_aec_,
+ sizeof(output_device_id_for_aec_));
+
+ if (result != noErr) {
+ HandleError(result);
+ return false;
+ }
+
+ // Register the input procedure for the AUHAL. This procedure will be called
+ // when the AUHAL has received new data from the input device.
+ AURenderCallbackStruct callback;
+ callback.inputProc = &DataIsAvailable;
+ callback.inputProcRefCon = this;
+
+ result = AudioUnitSetProperty(
+ audio_unit_, kAudioOutputUnitProperty_SetInputCallback,
+ kAudioUnitScope_Global, AUElement::INPUT, &callback, sizeof(callback));
+ if (result != noErr) {
+ HandleError(result);
+ return false;
+ }
+
+ callback.inputProc = OnGetPlayoutData;
+ callback.inputProcRefCon = this;
+ result = AudioUnitSetProperty(
+ audio_unit_, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input,
+ AUElement::OUTPUT, &callback, sizeof(callback));
+ if (result != noErr) {
+ HandleError(result);
+ return false;
+ }
+
+ // Get the stream format for the selected input device and ensure that the
+ // sample rate of the selected input device matches the desired (given at
+ // construction) sample rate. We should not rely on sample rate conversion
+ // in the AUHAL, only *simple* conversions, e.g., 32-bit float to 16-bit
+ // signed integer format.
+ AudioStreamBasicDescription input_device_format = {0};
+ GetInputDeviceStreamFormat(audio_unit_, &input_device_format);
+ if (input_device_format.mSampleRate != format_.mSampleRate) {
+ LOG(ERROR)
+ << "Input device's sample rate does not match the client's sample rate";
+ result = kAudioUnitErr_FormatNotSupported;
+ HandleError(result);
+ return false;
+ }
+
+ // Modify the IO buffer size if not already set correctly for the selected
+ // device. The status of other active audio input and output streams is
+ // involved in the final setting.
+ // TODO(henrika): we could make io_buffer_frame_size a member and add it to
+ // the UMA stats tied to the Media.Audio.InputStartupSuccessMac record.
+ size_t io_buffer_frame_size = 0;
+ if (!manager_->MaybeChangeBufferSize(
+ input_device_id_, audio_unit_, 1, input_params_.frames_per_buffer(),
+ &buffer_size_was_changed_, &io_buffer_frame_size)) {
+ result = kAudioUnitErr_FormatNotSupported;
+ HandleError(result);
+ return false;
+ }
+
+ // Store current I/O buffer frame size for UMA stats stored in combination
+ // with failing input callbacks.
+ DCHECK(!io_buffer_frame_size_);
+ io_buffer_frame_size_ = io_buffer_frame_size;
+
+ // If the requested number of frames is out of range, the closest valid buffer
+ // size will be set instead. Check the current setting and log a warning for a
+ // non perfect match. Any such mismatch will be compensated for in
+ // OnDataIsAvailable().
+ UInt32 buffer_frame_size = 0;
+ UInt32 property_size = sizeof(buffer_frame_size);
+ result = AudioUnitGetProperty(
+ audio_unit_, kAudioDevicePropertyBufferFrameSize, kAudioUnitScope_Global,
+ AUElement::OUTPUT, &buffer_frame_size, &property_size);
+ LOG_IF(WARNING, buffer_frame_size !=
+ static_cast<UInt32>(input_params_.frames_per_buffer()))
+ << "AUHAL is using best match of IO buffer size: " << buffer_frame_size;
+
+ // The built-in device claims to be stereo. VPAU claims 5 channels (for me)
+ // but refuses to work in stereo. Just accept stero for now, use mono
+ // internally and upmix.
+ AudioStreamBasicDescription mono_format = format_;
+ if (format_.mChannelsPerFrame == 2) {
+ mono_format.mChannelsPerFrame = 1;
+ mono_format.mBytesPerPacket = mono_format.mBitsPerChannel / 8;
+ mono_format.mBytesPerFrame = mono_format.mBytesPerPacket;
+ }
+
+ // Set up the the desired (output) format.
+ // For obtaining input from a device, the device format is always expressed
+ // on the output scope of the AUHAL's Element 1.
+ result = AudioUnitSetProperty(audio_unit_, kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Output, AUElement::INPUT,
+ &mono_format, sizeof(mono_format));
+ if (result != noErr) {
+ HandleError(result);
+ return false;
+ }
+
+ result = AudioUnitSetProperty(audio_unit_, kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Input, AUElement::OUTPUT,
+ &mono_format, sizeof(mono_format));
+ if (result != noErr) {
+ HandleError(result);
+ return false;
+ }
+
+ // Finally, initialize the audio unit and ensure that it is ready to render.
+ // Allocates memory according to the maximum number of audio frames
+ // it can produce in response to a single render call.
+ result = AudioUnitInitialize(audio_unit_);
+ if (result != noErr) {
+ HandleError(result);
+ return false;
+ }
+
+ UndoDucking(output_device_id_for_aec_);
+
+ return true;
+}
+
+void AUAudioInputStream::Start(AudioInputCallback* callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DVLOG(1) << "Start";
+ DCHECK(callback);
+ DCHECK(!sink_);
+ DLOG_IF(ERROR, !audio_unit_) << "Open() has not been called successfully";
+ if (IsRunning())
+ return;
+
+ // Check if we should defer Start() for http://crbug.com/160920.
+ if (manager_->ShouldDeferStreamStart()) {
+ LOG(WARNING) << "Start of input audio is deferred";
+ // Use a cancellable closure so that if Stop() is called before Start()
+ // actually runs, we can cancel the pending start.
+ deferred_start_cb_.Reset(base::BindOnce(&AUAudioInputStream::Start,
+ base::Unretained(this), callback));
+ manager_->GetTaskRunner()->PostDelayedTask(
+ FROM_HERE, deferred_start_cb_.callback(),
+ base::Seconds(AudioManagerMac::kStartDelayInSecsForPowerEvents));
+ return;
+ }
+
+ sink_ = callback;
+ last_success_time_ = base::TimeTicks::Now();
+ audio_unit_render_has_worked_ = false;
+
+ // Don't disable built-in noise suppression when using VPAU.
+ if (!use_voice_processing_ &&
+ !(input_params_.effects() & AudioParameters::NOISE_SUPPRESSION) &&
+ manager_->DeviceSupportsAmbientNoiseReduction(input_device_id_)) {
+ noise_reduction_suppressed_ =
+ manager_->SuppressNoiseReduction(input_device_id_);
+ }
+ StartAgc();
+ OSStatus result = AudioOutputUnitStart(audio_unit_);
+ OSSTATUS_DLOG_IF(ERROR, result != noErr, result)
+ << "Failed to start acquiring data";
+ if (result != noErr) {
+ Stop();
+ return;
+ }
+ DCHECK(IsRunning()) << "Audio unit started OK but is not yet running";
+
+ // For UMA stat purposes, start a one-shot timer which detects when input
+ // callbacks starts indicating if input audio recording starts as intended.
+ // CheckInputStartupSuccess() will check if |input_callback_is_active_| is
+ // true when the timer expires.
+ input_callback_timer_ = std::make_unique<base::OneShotTimer>();
+ input_callback_timer_->Start(
+ FROM_HERE, base::Seconds(kInputCallbackStartTimeoutInSeconds), this,
+ &AUAudioInputStream::CheckInputStartupSuccess);
+ DCHECK(input_callback_timer_->IsRunning());
+}
+
+void AUAudioInputStream::Stop() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ deferred_start_cb_.Cancel();
+ DVLOG(1) << "Stop";
+ StopAgc();
+ if (noise_reduction_suppressed_) {
+ manager_->UnsuppressNoiseReduction(input_device_id_);
+ noise_reduction_suppressed_ = false;
+ }
+ if (input_callback_timer_ != nullptr) {
+ input_callback_timer_->Stop();
+ input_callback_timer_.reset();
+ }
+
+ if (audio_unit_ != nullptr) {
+ // Stop the I/O audio unit.
+ OSStatus result = AudioOutputUnitStop(audio_unit_);
+ DCHECK_EQ(result, noErr);
+ // Add a DCHECK here just in case. AFAIK, the call to AudioOutputUnitStop()
+ // seems to set this state synchronously, hence it should always report
+ // false after a successful call.
+ DCHECK(!IsRunning()) << "Audio unit is stopped but still running";
+
+ // Reset the audio unit’s render state. This function clears memory.
+ // It does not allocate or free memory resources.
+ result = AudioUnitReset(audio_unit_, kAudioUnitScope_Global, 0);
+ DCHECK_EQ(result, noErr);
+ OSSTATUS_DLOG_IF(ERROR, result != noErr, result)
+ << "Failed to stop acquiring data";
+ }
+
+ SetInputCallbackIsActive(false);
+ ReportAndResetStats();
+ sink_ = nullptr;
+ fifo_.Clear();
+ io_buffer_frame_size_ = 0;
+ got_input_callback_ = false;
+}
+
+void AUAudioInputStream::Close() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DVLOG(1) << "Close";
+
+ // It is valid to call Close() before calling open or Start().
+ // It is also valid to call Close() after Start() has been called.
+ if (IsRunning()) {
+ Stop();
+ }
+
+ // Uninitialize and dispose the audio unit.
+ CloseAudioUnit();
+
+ // Inform the audio manager that we have been closed. This will cause our
+ // destruction.
+ manager_->ReleaseInputStream(this);
+}
+
+double AUAudioInputStream::GetMaxVolume() {
+ // Verify that we have a valid device.
+ if (input_device_id_ == kAudioObjectUnknown) {
+ NOTREACHED() << "Device ID is unknown";
+ return 0.0;
+ }
+
+ // Query if any of the master, left or right channels has volume control.
+ for (int i = 0; i <= number_of_channels_in_frame_; ++i) {
+ // If the volume is settable, the valid volume range is [0.0, 1.0].
+ if (IsVolumeSettableOnChannel(i))
+ return 1.0;
+ }
+
+ // Volume control is not available for the audio stream.
+ return 0.0;
+}
+
+void AUAudioInputStream::SetVolume(double volume) {
+ DVLOG(1) << "SetVolume(volume=" << volume << ")";
+ DCHECK_GE(volume, 0.0);
+ DCHECK_LE(volume, 1.0);
+
+ // Verify that we have a valid device.
+ if (input_device_id_ == kAudioObjectUnknown) {
+ NOTREACHED() << "Device ID is unknown";
+ return;
+ }
+
+ Float32 volume_float32 = static_cast<Float32>(volume);
+ AudioObjectPropertyAddress property_address = {
+ kAudioDevicePropertyVolumeScalar, kAudioDevicePropertyScopeInput,
+ kAudioObjectPropertyElementMaster};
+
+ // Try to set the volume for master volume channel.
+ if (IsVolumeSettableOnChannel(kAudioObjectPropertyElementMaster)) {
+ OSStatus result = AudioObjectSetPropertyData(
+ input_device_id_, &property_address, 0, nullptr, sizeof(volume_float32),
+ &volume_float32);
+ if (result != noErr) {
+ DLOG(WARNING) << "Failed to set volume to " << volume_float32;
+ }
+ return;
+ }
+
+ // There is no master volume control, try to set volume for each channel.
+ int successful_channels = 0;
+ for (int i = 1; i <= number_of_channels_in_frame_; ++i) {
+ property_address.mElement = static_cast<UInt32>(i);
+ if (IsVolumeSettableOnChannel(i)) {
+ OSStatus result = AudioObjectSetPropertyData(
+ input_device_id_, &property_address, 0, NULL, sizeof(volume_float32),
+ &volume_float32);
+ if (result == noErr)
+ ++successful_channels;
+ }
+ }
+
+ DLOG_IF(WARNING, successful_channels == 0)
+ << "Failed to set volume to " << volume_float32;
+
+ // Update the AGC volume level based on the last setting above. Note that,
+ // the volume-level resolution is not infinite and it is therefore not
+ // possible to assume that the volume provided as input parameter can be
+ // used directly. Instead, a new query to the audio hardware is required.
+ // This method does nothing if AGC is disabled.
+ UpdateAgcVolume();
+}
+
+double AUAudioInputStream::GetVolume() {
+ // Verify that we have a valid device.
+ if (input_device_id_ == kAudioObjectUnknown) {
+ NOTREACHED() << "Device ID is unknown";
+ return 0.0;
+ }
+
+ AudioObjectPropertyAddress property_address = {
+ kAudioDevicePropertyVolumeScalar, kAudioDevicePropertyScopeInput,
+ kAudioObjectPropertyElementMaster};
+
+ if (AudioObjectHasProperty(input_device_id_, &property_address)) {
+ // The device supports master volume control, get the volume from the
+ // master channel.
+ Float32 volume_float32 = 0.0;
+ UInt32 size = sizeof(volume_float32);
+ OSStatus result =
+ AudioObjectGetPropertyData(input_device_id_, &property_address, 0,
+ nullptr, &size, &volume_float32);
+ if (result == noErr)
+ return static_cast<double>(volume_float32);
+ } else {
+ // There is no master volume control, try to get the average volume of
+ // all the channels.
+ Float32 volume_float32 = 0.0;
+ int successful_channels = 0;
+ for (int i = 1; i <= number_of_channels_in_frame_; ++i) {
+ property_address.mElement = static_cast<UInt32>(i);
+ if (AudioObjectHasProperty(input_device_id_, &property_address)) {
+ Float32 channel_volume = 0;
+ UInt32 size = sizeof(channel_volume);
+ OSStatus result =
+ AudioObjectGetPropertyData(input_device_id_, &property_address, 0,
+ nullptr, &size, &channel_volume);
+ if (result == noErr) {
+ volume_float32 += channel_volume;
+ ++successful_channels;
+ }
+ }
+ }
+
+ // Get the average volume of the channels.
+ if (successful_channels != 0)
+ return static_cast<double>(volume_float32 / successful_channels);
+ }
+
+ DLOG(WARNING) << "Failed to get volume";
+ return 0.0;
+}
+
+bool AUAudioInputStream::IsMuted() {
+ // Verify that we have a valid device.
+ DCHECK_NE(input_device_id_, kAudioObjectUnknown) << "Device ID is unknown";
+
+ AudioObjectPropertyAddress property_address = {
+ kAudioDevicePropertyMute, kAudioDevicePropertyScopeInput,
+ kAudioObjectPropertyElementMaster};
+
+ if (!AudioObjectHasProperty(input_device_id_, &property_address)) {
+ DLOG(ERROR) << "Device does not support checking master mute state";
+ return false;
+ }
+
+ UInt32 muted = 0;
+ UInt32 size = sizeof(muted);
+ OSStatus result = AudioObjectGetPropertyData(
+ input_device_id_, &property_address, 0, nullptr, &size, &muted);
+ DLOG_IF(WARNING, result != noErr) << "Failed to get mute state";
+ return result == noErr && muted != 0;
+}
+
+void AUAudioInputStream::SetOutputDeviceForAec(
+ const std::string& output_device_id) {
+ if (!use_voice_processing_)
+ return;
+
+ AudioDeviceID audio_device_id =
+ AudioManagerMac::GetAudioDeviceIdByUId(false, output_device_id);
+ if (audio_device_id == output_device_id_for_aec_)
+ return;
+
+ if (audio_device_id == kAudioObjectUnknown) {
+ log_callback_.Run(
+ base::StringPrintf("AU in: Unable to resolve output device id '%s'",
+ output_device_id.c_str()));
+ return;
+ }
+
+ // If the selected device is an aggregate device, try to use the first output
+ // device of the aggregate device instead.
+ if (core_audio_mac::GetDeviceTransportType(audio_device_id) ==
+ kAudioDeviceTransportTypeAggregate) {
+ const AudioDeviceID output_subdevice_id =
+ FindFirstOutputSubdevice(audio_device_id);
+
+ if (output_subdevice_id == kAudioObjectUnknown) {
+ log_callback_.Run(base::StringPrintf(
+ "AU in: Unable to find an output subdevice in aggregate device '%s'",
+ output_device_id.c_str()));
+ return;
+ }
+ audio_device_id = output_subdevice_id;
+ }
+
+ if (audio_device_id != output_device_id_for_aec_) {
+ output_device_id_for_aec_ = audio_device_id;
+ log_callback_.Run(base::StringPrintf(
+ "AU in: Output device for AEC changed to '%s' (%d)",
+ output_device_id.c_str(), output_device_id_for_aec_));
+ // Only restart the stream if it has previously been started.
+ if (audio_unit_)
+ ReinitializeVoiceProcessingAudioUnit();
+ }
+}
+
+void AUAudioInputStream::ReinitializeVoiceProcessingAudioUnit() {
+ DCHECK(use_voice_processing_);
+ DCHECK(audio_unit_);
+
+ const bool was_running = IsRunning();
+ OSStatus result = noErr;
+
+ if (was_running) {
+ result = AudioOutputUnitStop(audio_unit_);
+ DCHECK_EQ(result, noErr);
+ }
+
+ CloseAudioUnit();
+
+ // Reset things to a state similar to before the audio unit was opened.
+ // Most of these will be no-ops if the audio unit was opened but not started.
+ SetInputCallbackIsActive(false);
+ ReportAndResetStats();
+ io_buffer_frame_size_ = 0;
+ got_input_callback_ = false;
+
+ OpenVoiceProcessingAU();
+
+ if (was_running) {
+ result = AudioOutputUnitStart(audio_unit_);
+ if (result != noErr) {
+ OSSTATUS_DLOG(ERROR, result) << "Failed to start acquiring data";
+ Stop();
+ return;
+ }
+ }
+
+ log_callback_.Run(base::StringPrintf(
+ "AU in: Successfully reinitialized AEC for output device id=%d.",
+ output_device_id_for_aec_));
+}
+
+// static
+OSStatus AUAudioInputStream::DataIsAvailable(void* context,
+ AudioUnitRenderActionFlags* flags,
+ const AudioTimeStamp* time_stamp,
+ UInt32 bus_number,
+ UInt32 number_of_frames,
+ AudioBufferList* io_data) {
+ DCHECK(context);
+ // Recorded audio is always on the input bus (=1).
+ DCHECK_EQ(bus_number, 1u);
+ // No data buffer should be allocated at this stage.
+ DCHECK(!io_data);
+ AUAudioInputStream* self = reinterpret_cast<AUAudioInputStream*>(context);
+ // Propagate render action flags, time stamp, bus number and number
+ // of frames requested to the AudioUnitRender() call where the actual data
+ // is received from the input device via the output scope of the audio unit.
+ return self->OnDataIsAvailable(flags, time_stamp, bus_number,
+ number_of_frames);
+}
+
+OSStatus AUAudioInputStream::OnDataIsAvailable(
+ AudioUnitRenderActionFlags* flags,
+ const AudioTimeStamp* time_stamp,
+ UInt32 bus_number,
+ UInt32 number_of_frames) {
+ TRACE_EVENT0("audio", "AUAudioInputStream::OnDataIsAvailable");
+
+ // Indicate that input callbacks have started.
+ if (!got_input_callback_) {
+ got_input_callback_ = true;
+ SetInputCallbackIsActive(true);
+ }
+
+ // Update the |mDataByteSize| value in the audio_buffer_list() since
+ // |number_of_frames| can be changed on the fly.
+ // |mDataByteSize| needs to be exactly mapping to |number_of_frames|,
+ // otherwise it will put CoreAudio into bad state and results in
+ // AudioUnitRender() returning -50 for the new created stream.
+ // We have also seen kAudioUnitErr_TooManyFramesToProcess (-10874) and
+ // kAudioUnitErr_CannotDoInCurrentContext (-10863) as error codes.
+ // See crbug/428706 for details.
+ UInt32 new_size = number_of_frames * format_.mBytesPerFrame;
+ AudioBuffer* audio_buffer = audio_buffer_list_.mBuffers;
+ bool new_buffer_size_detected = false;
+ if (new_size != audio_buffer->mDataByteSize) {
+ new_buffer_size_detected = true;
+ DVLOG(1) << "New size of number_of_frames detected: " << number_of_frames;
+ io_buffer_frame_size_ = static_cast<size_t>(number_of_frames);
+ if (new_size > audio_buffer->mDataByteSize) {
+ // This can happen if the device is unplugged during recording. We
+ // allocate enough memory here to avoid depending on how CoreAudio
+ // handles it.
+ // See See http://www.crbug.com/434681 for one example when we can enter
+ // this scope.
+ audio_data_buffer_.reset(new uint8_t[new_size]);
+ audio_buffer->mData = audio_data_buffer_.get();
+ }
+
+ // Update the |mDataByteSize| to match |number_of_frames|.
+ audio_buffer->mDataByteSize = new_size;
+ }
+
+ // Obtain the recorded audio samples by initiating a rendering cycle.
+ // Since it happens on the input bus, the |&audio_buffer_list_| parameter is
+ // a reference to the preallocated audio buffer list that the audio unit
+ // renders into.
+ OSStatus result;
+ if (use_voice_processing_ && format_.mChannelsPerFrame != 1) {
+ // Use the first part of the output buffer for mono data...
+ AudioBufferList mono_buffer_list;
+ mono_buffer_list.mNumberBuffers = 1;
+ AudioBuffer* mono_buffer = mono_buffer_list.mBuffers;
+ mono_buffer->mNumberChannels = 1;
+ mono_buffer->mDataByteSize =
+ audio_buffer->mDataByteSize / audio_buffer->mNumberChannels;
+ mono_buffer->mData = audio_buffer->mData;
+
+ TRACE_EVENT_BEGIN0("audio", "AudioUnitRender");
+ result = AudioUnitRender(audio_unit_, flags, time_stamp, bus_number,
+ number_of_frames, &mono_buffer_list);
+ TRACE_EVENT_END0("audio", "AudioUnitRender");
+ // ... then upmix it by copying it out to two channels.
+ UpmixMonoToStereoInPlace(audio_buffer, format_.mBitsPerChannel / 8);
+ } else {
+ TRACE_EVENT_BEGIN0("audio", "AudioUnitRender");
+ result = AudioUnitRender(audio_unit_, flags, time_stamp, bus_number,
+ number_of_frames, &audio_buffer_list_);
+ TRACE_EVENT_END0("audio", "AudioUnitRender");
+ }
+
+ if (result == noErr) {
+ audio_unit_render_has_worked_ = true;
+ }
+ if (result) {
+ TRACE_EVENT_INSTANT0("audio", "AudioUnitRender error",
+ TRACE_EVENT_SCOPE_THREAD);
+ // Only upload UMA histograms for the case when AGC is enabled. The reason
+ // is that we want to compare these stats with others in this class and
+ // they are only stored for "AGC streams", e.g. WebRTC audio streams.
+ const bool add_uma_histogram = GetAutomaticGainControl();
+ if (add_uma_histogram) {
+ base::UmaHistogramSparse("Media.AudioInputCbErrorMac", result);
+ }
+ OSSTATUS_LOG(ERROR, result) << "AudioUnitRender() failed ";
+ if (result == kAudioUnitErr_TooManyFramesToProcess ||
+ result == kAudioUnitErr_CannotDoInCurrentContext) {
+ DCHECK(!last_success_time_.is_null());
+ // We delay stopping the stream for kAudioUnitErr_TooManyFramesToProcess
+ // since it has been observed that some USB headsets can cause this error
+ // but only for a few initial frames at startup and then then the stream
+ // returns to a stable state again. See b/19524368 for details.
+ // Instead, we measure time since last valid audio frame and call
+ // HandleError() only if a too long error sequence is detected. We do
+ // this to avoid ending up in a non recoverable bad core audio state.
+ // Also including kAudioUnitErr_CannotDoInCurrentContext since long
+ // sequences can be produced in combination with e.g. sample-rate changes
+ // for input devices.
+ base::TimeDelta time_since_last_success =
+ base::TimeTicks::Now() - last_success_time_;
+ if ((time_since_last_success >
+ base::Seconds(kMaxErrorTimeoutInSeconds))) {
+ const char* err = (result == kAudioUnitErr_TooManyFramesToProcess)
+ ? "kAudioUnitErr_TooManyFramesToProcess"
+ : "kAudioUnitErr_CannotDoInCurrentContext";
+ LOG(ERROR) << "Too long sequence of " << err << " errors!";
+ HandleError(result);
+ }
+
+ // Add some extra UMA stat to track down if we see this particular error
+ // in combination with a previous change of buffer size "on the fly".
+ if (result == kAudioUnitErr_CannotDoInCurrentContext &&
+ add_uma_histogram) {
+ UMA_HISTOGRAM_BOOLEAN("Media.Audio.RenderFailsWhenBufferSizeChangesMac",
+ new_buffer_size_detected);
+ UMA_HISTOGRAM_BOOLEAN("Media.Audio.AudioUnitRenderHasWorkedMac",
+ audio_unit_render_has_worked_);
+ }
+ } else {
+ // We have also seen kAudioUnitErr_NoConnection in some cases. Bailing
+ // out for this error for now.
+ HandleError(result);
+ }
+ return result;
+ }
+ // Update time of successful call to AudioUnitRender().
+ last_success_time_ = base::TimeTicks::Now();
+
+ // Deliver recorded data to the consumer as a callback.
+ return Provide(number_of_frames, &audio_buffer_list_, time_stamp);
+}
+
+OSStatus AUAudioInputStream::Provide(UInt32 number_of_frames,
+ AudioBufferList* io_data,
+ const AudioTimeStamp* time_stamp) {
+ TRACE_EVENT1("audio", "AUAudioInputStream::Provide", "number_of_frames",
+ number_of_frames);
+ UpdateCaptureTimestamp(time_stamp);
+ last_number_of_frames_ = number_of_frames;
+
+ // TODO(grunell): We'll only care about the first buffer size change, any
+ // further changes will be ignored. This is in line with output side stats.
+ // It would be nice to have all changes reflected in UMA stats.
+ if (number_of_frames !=
+ static_cast<UInt32>(input_params_.frames_per_buffer()) &&
+ number_of_frames_provided_ == 0)
+ number_of_frames_provided_ = number_of_frames;
+
+ base::TimeTicks capture_time = GetCaptureTime(time_stamp);
+
+ // The AGC volume level is updated once every second on a separate thread.
+ // Note that, |volume| is also updated each time SetVolume() is called
+ // through IPC by the render-side AGC.
+ double normalized_volume = 0.0;
+ GetAgcVolume(&normalized_volume);
+
+ AudioBuffer& buffer = io_data->mBuffers[0];
+ uint8_t* audio_data = reinterpret_cast<uint8_t*>(buffer.mData);
+ DCHECK(audio_data);
+ if (!audio_data)
+ return kAudioUnitErr_InvalidElement;
+
+ // Dynamically increase capacity of the FIFO to handle larger buffers from
+ // CoreAudio. This can happen in combination with Apple Thunderbolt Displays
+ // when the Display Audio is used as capture source and the cable is first
+ // remove and then inserted again.
+ // See http://www.crbug.com/434681 for details.
+ if (static_cast<int>(number_of_frames) > fifo_.GetUnfilledFrames()) {
+ // Derive required increase in number of FIFO blocks. The increase is
+ // typically one block.
+ const int blocks =
+ static_cast<int>((number_of_frames - fifo_.GetUnfilledFrames()) /
+ input_params_.frames_per_buffer()) +
+ 1;
+ DLOG(WARNING) << "Increasing FIFO capacity by " << blocks << " blocks";
+ TRACE_EVENT_INSTANT1("audio", "Increasing FIFO capacity",
+ TRACE_EVENT_SCOPE_THREAD, "increased by", blocks);
+ fifo_.IncreaseCapacity(blocks);
+ }
+
+ // Compensate the capture time for the FIFO before pushing an new frames.
+ capture_time -= AudioTimestampHelper::FramesToTime(fifo_.GetAvailableFrames(),
+ format_.mSampleRate);
+
+ // Copy captured (and interleaved) data into FIFO.
+ fifo_.Push(audio_data, number_of_frames, format_.mBitsPerChannel / 8);
+
+ // Consume and deliver the data when the FIFO has a block of available data.
+ while (fifo_.available_blocks()) {
+ const AudioBus* audio_bus = fifo_.Consume();
+ DCHECK_EQ(audio_bus->frames(),
+ static_cast<int>(input_params_.frames_per_buffer()));
+
+ sink_->OnData(audio_bus, capture_time, normalized_volume);
+
+ // Move the capture time forward for each vended block.
+ capture_time += AudioTimestampHelper::FramesToTime(audio_bus->frames(),
+ format_.mSampleRate);
+ }
+
+ return noErr;
+}
+
+int AUAudioInputStream::HardwareSampleRate() {
+ // Determine the default input device's sample-rate.
+ AudioDeviceID device_id = kAudioObjectUnknown;
+ UInt32 info_size = sizeof(device_id);
+
+ AudioObjectPropertyAddress default_input_device_address = {
+ kAudioHardwarePropertyDefaultInputDevice, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+ OSStatus result = AudioObjectGetPropertyData(kAudioObjectSystemObject,
+ &default_input_device_address, 0,
+ 0, &info_size, &device_id);
+ if (result != noErr)
+ return 0.0;
+
+ Float64 nominal_sample_rate;
+ info_size = sizeof(nominal_sample_rate);
+
+ AudioObjectPropertyAddress nominal_sample_rate_address = {
+ kAudioDevicePropertyNominalSampleRate, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+ result = AudioObjectGetPropertyData(device_id, &nominal_sample_rate_address,
+ 0, 0, &info_size, &nominal_sample_rate);
+ if (result != noErr)
+ return 0.0;
+
+ return static_cast<int>(nominal_sample_rate);
+}
+
+base::TimeTicks AUAudioInputStream::GetCaptureTime(
+ const AudioTimeStamp* input_time_stamp) {
+ // Total latency is composed by the dynamic latency and the fixed
+ // hardware latency.
+ // https://lists.apple.com/archives/coreaudio-api/2017/Jul/msg00035.html
+ return (input_time_stamp->mFlags & kAudioTimeStampHostTimeValid
+ ? base::TimeTicks::FromMachAbsoluteTime(
+ input_time_stamp->mHostTime)
+ : base::TimeTicks::Now()) -
+ hardware_latency_;
+}
+
+int AUAudioInputStream::GetNumberOfChannelsFromStream() {
+ // Get the stream format, to be able to read the number of channels.
+ AudioObjectPropertyAddress property_address = {
+ kAudioDevicePropertyStreamFormat, kAudioDevicePropertyScopeInput,
+ kAudioObjectPropertyElementMaster};
+ AudioStreamBasicDescription stream_format;
+ UInt32 size = sizeof(stream_format);
+ OSStatus result = AudioObjectGetPropertyData(
+ input_device_id_, &property_address, 0, nullptr, &size, &stream_format);
+ if (result != noErr) {
+ DLOG(WARNING) << "Could not get stream format";
+ return 0;
+ }
+
+ return static_cast<int>(stream_format.mChannelsPerFrame);
+}
+
+bool AUAudioInputStream::IsRunning() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (!audio_unit_)
+ return false;
+ UInt32 is_running = 0;
+ UInt32 size = sizeof(is_running);
+ OSStatus error =
+ AudioUnitGetProperty(audio_unit_, kAudioOutputUnitProperty_IsRunning,
+ kAudioUnitScope_Global, 0, &is_running, &size);
+ OSSTATUS_DLOG_IF(ERROR, error != noErr, error)
+ << "AudioUnitGetProperty(kAudioOutputUnitProperty_IsRunning) failed";
+ DVLOG(1) << "IsRunning: " << is_running;
+ return (error == noErr && is_running);
+}
+
+void AUAudioInputStream::HandleError(OSStatus err) {
+ // Log the latest OSStatus error message and also change the sign of the
+ // error if no callbacks are active. I.e., the sign of the error message
+ // carries one extra level of information.
+ base::UmaHistogramSparse("Media.InputErrorMac",
+ GetInputCallbackIsActive() ? err : (err * -1));
+ NOTREACHED() << "error " << logging::DescriptionFromOSStatus(err) << " ("
+ << err << ")";
+ if (sink_)
+ sink_->OnError();
+}
+
+bool AUAudioInputStream::IsVolumeSettableOnChannel(int channel) {
+ Boolean is_settable = false;
+ AudioObjectPropertyAddress property_address = {
+ kAudioDevicePropertyVolumeScalar, kAudioDevicePropertyScopeInput,
+ static_cast<UInt32>(channel)};
+ OSStatus result = AudioObjectIsPropertySettable(
+ input_device_id_, &property_address, &is_settable);
+ return (result == noErr) ? is_settable : false;
+}
+
+void AUAudioInputStream::SetInputCallbackIsActive(bool enabled) {
+ base::subtle::Release_Store(&input_callback_is_active_, enabled);
+}
+
+bool AUAudioInputStream::GetInputCallbackIsActive() {
+ return (base::subtle::Acquire_Load(&input_callback_is_active_) != false);
+}
+
+void AUAudioInputStream::CheckInputStartupSuccess() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(IsRunning());
+ // Only add UMA stat related to failing input audio for streams where
+ // the AGC has been enabled, e.g. WebRTC audio input streams.
+ if (GetAutomaticGainControl()) {
+ // Check if we have called Start() and input callbacks have actually
+ // started in time as they should. If that is not the case, we have a
+ // problem and the stream is considered dead.
+ const bool input_callback_is_active = GetInputCallbackIsActive();
+ UMA_HISTOGRAM_BOOLEAN("Media.Audio.InputStartupSuccessMac",
+ input_callback_is_active);
+ DVLOG(1) << "input_callback_is_active: " << input_callback_is_active;
+ if (!input_callback_is_active) {
+ // Now when we know that startup has failed for some reason, add extra
+ // UMA stats in an attempt to figure out the exact reason.
+ AddHistogramsForFailedStartup();
+ }
+ }
+}
+
+void AUAudioInputStream::CloseAudioUnit() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DVLOG(1) << "CloseAudioUnit";
+ if (!audio_unit_)
+ return;
+ OSStatus result = AudioUnitUninitialize(audio_unit_);
+ OSSTATUS_DLOG_IF(ERROR, result != noErr, result)
+ << "AudioUnitUninitialize() failed.";
+ result = AudioComponentInstanceDispose(audio_unit_);
+ OSSTATUS_DLOG_IF(ERROR, result != noErr, result)
+ << "AudioComponentInstanceDispose() failed.";
+ audio_unit_ = 0;
+}
+
+void AUAudioInputStream::AddHistogramsForFailedStartup() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ UMA_HISTOGRAM_BOOLEAN("Media.Audio.InputBufferSizeWasChangedMac",
+ buffer_size_was_changed_);
+ // |input_params_.frames_per_buffer()| is set at construction and corresponds
+ // to the requested (by the client) number of audio frames per I/O buffer
+ // connected to the selected input device. Ideally, this size will be the same
+ // as the native I/O buffer size given by |io_buffer_frame_size_|.
+ base::UmaHistogramSparse("Media.Audio.RequestedInputBufferFrameSizeMac",
+ input_params_.frames_per_buffer());
+ DVLOG(1) << "number_of_frames_: " << input_params_.frames_per_buffer();
+ // This value indicates the number of frames in the IO buffers connected to
+ // the selected input device. It has been set by the audio manger in Open()
+ // and can be the same as |input_params_.frames_per_buffer()|, which is the
+ // desired buffer size. These two values might differ if other streams are
+ // using the same device and any of these streams have asked for a smaller
+ // buffer size.
+ base::UmaHistogramSparse("Media.Audio.ActualInputBufferFrameSizeMac",
+ io_buffer_frame_size_);
+ DVLOG(1) << "io_buffer_frame_size_: " << io_buffer_frame_size_;
+ // Add information about things like number of logical processors etc.
+ AddSystemInfoToUMA();
+}
+
+void AUAudioInputStream::UpdateCaptureTimestamp(
+ const AudioTimeStamp* timestamp) {
+ if ((timestamp->mFlags & kAudioTimeStampSampleTimeValid) == 0)
+ return;
+
+ if (last_sample_time_) {
+ DCHECK_NE(0U, last_number_of_frames_);
+ UInt32 diff =
+ static_cast<UInt32>(timestamp->mSampleTime - last_sample_time_);
+ if (diff != last_number_of_frames_) {
+ DCHECK_GT(diff, last_number_of_frames_);
+ // We were given samples post what we expected. Update the glitch count
+ // etc. and keep a record of the largest glitch.
+ auto lost_frames = diff - last_number_of_frames_;
+ total_lost_frames_ += lost_frames;
+ if (lost_frames > largest_glitch_frames_)
+ largest_glitch_frames_ = lost_frames;
+ ++glitches_detected_;
+ }
+ }
+
+ // Store the last sample time for use next time we get called back.
+ last_sample_time_ = timestamp->mSampleTime;
+}
+
+void AUAudioInputStream::ReportAndResetStats() {
+ if (last_sample_time_ == 0)
+ return; // No stats gathered to report.
+
+ // A value of 0 indicates that we got the buffer size we asked for.
+ UMA_HISTOGRAM_COUNTS_10000("Media.Audio.Capture.FramesProvided",
+ number_of_frames_provided_);
+ // Even if there aren't any glitches, we want to record it to get a feel for
+ // how often we get no glitches vs the alternative.
+ UMA_HISTOGRAM_COUNTS_1M("Media.Audio.Capture.Glitches", glitches_detected_);
+
+ auto lost_frames_ms = (total_lost_frames_ * 1000) / format_.mSampleRate;
+ std::string log_message = base::StringPrintf(
+ "AU in: Total glitches=%d. Total frames lost=%d (%.0lf ms).",
+ glitches_detected_, total_lost_frames_, lost_frames_ms);
+ log_callback_.Run(log_message);
+
+ if (glitches_detected_ != 0) {
+ UMA_HISTOGRAM_LONG_TIMES("Media.Audio.Capture.LostFramesInMs",
+ base::Milliseconds(lost_frames_ms));
+ auto largest_glitch_ms =
+ (largest_glitch_frames_ * 1000) / format_.mSampleRate;
+ UMA_HISTOGRAM_CUSTOM_TIMES("Media.Audio.Capture.LargestGlitchMs",
+ base::Milliseconds(largest_glitch_ms),
+ base::Milliseconds(1), base::Minutes(1), 50);
+ DLOG(WARNING) << log_message;
+ }
+
+ number_of_frames_provided_ = 0;
+ glitches_detected_ = 0;
+ last_sample_time_ = 0;
+ last_number_of_frames_ = 0;
+ total_lost_frames_ = 0;
+ largest_glitch_frames_ = 0;
+}
+
+// TODO(ossu): Ideally, we'd just use the mono stream directly. However, since
+// mono or stereo (may) depend on if we want to run the echo canceller, and
+// since we can't provide two sets of AudioParameters for a device, this is the
+// best we can do right now.
+//
+// The algorithm works by copying a sample at offset N to 2*N and 2*N + 1, e.g.:
+// ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
+// | a1 | a2 | a3 | b1 | b2 | b3 | c1 | c2 | c3 | -- | -- | -- | -- | -- | ...
+// ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
+// into
+// ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
+// | a1 | a2 | a3 | a1 | a2 | a3 | b1 | b2 | b3 | b1 | b2 | b3 | c1 | c2 | ...
+// ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
+//
+// To support various different sample sizes, this is done byte-by-byte. Only
+// the first half of the buffer will be used as input. It is expected to contain
+// mono audio. The second half is output only. Since the data is expanding, the
+// algorithm starts copying from the last sample. Otherwise it would overwrite
+// data not already copied.
+void AUAudioInputStream::UpmixMonoToStereoInPlace(AudioBuffer* audio_buffer,
+ int bytes_per_sample) {
+ constexpr int channels = 2;
+ DCHECK_EQ(audio_buffer->mNumberChannels, static_cast<UInt32>(channels));
+ const int total_bytes = audio_buffer->mDataByteSize;
+ const int frames = total_bytes / bytes_per_sample / channels;
+ char* byte_ptr = reinterpret_cast<char*>(audio_buffer->mData);
+ for (int i = frames - 1; i >= 0; --i) {
+ int in_offset = (bytes_per_sample * i);
+ int out_offset = (channels * bytes_per_sample * i);
+ for (int b = 0; b < bytes_per_sample; ++b) {
+ const char byte = byte_ptr[in_offset + b];
+ byte_ptr[out_offset + b] = byte;
+ byte_ptr[out_offset + bytes_per_sample + b] = byte;
+ }
+ }
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/mac/audio_low_latency_input_mac.h b/third_party/chromium/media/audio/mac/audio_low_latency_input_mac.h
new file mode 100644
index 0000000..5ea6c83
--- /dev/null
+++ b/third_party/chromium/media/audio/mac/audio_low_latency_input_mac.h
@@ -0,0 +1,282 @@
+// Copyright (c) 2012 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.
+//
+// Implementation of AudioInputStream for Mac OS X using the special AUHAL
+// input Audio Unit present in OS 10.4 and later.
+// The AUHAL input Audio Unit is for low-latency audio I/O.
+//
+// Overview of operation:
+//
+// - An object of AUAudioInputStream is created by the AudioManager
+// factory: audio_man->MakeAudioInputStream().
+// - Next some thread will call Open(), at that point the underlying
+// AUHAL output Audio Unit is created and configured.
+// - Then some thread will call Start(sink).
+// Then the Audio Unit is started which creates its own thread which
+// periodically will provide the sink with more data as buffers are being
+// produced/recorded.
+// - At some point some thread will call Stop(), which we handle by directly
+// stopping the AUHAL output Audio Unit.
+// - The same thread that called stop will call Close() where we cleanup
+// and notify the audio manager, which likely will destroy this object.
+//
+// Implementation notes:
+//
+// - It is recommended to first acquire the native sample rate of the default
+// input device and then use the same rate when creating this object.
+// Use AUAudioInputStream::HardwareSampleRate() to retrieve the sample rate.
+// - Calling Close() also leads to self destruction.
+// - The latency consists of two parts:
+// 1) Hardware latency, which includes Audio Unit latency, audio device
+// latency;
+// 2) The delay between the actual recording instant and the time when the
+// data packet is provided as a callback.
+//
+#ifndef MEDIA_AUDIO_MAC_AUDIO_LOW_LATENCY_INPUT_MAC_H_
+#define MEDIA_AUDIO_MAC_AUDIO_LOW_LATENCY_INPUT_MAC_H_
+
+#include <AudioUnit/AudioUnit.h>
+#include <CoreAudio/CoreAudio.h>
+
+#include <memory>
+#include <vector>
+
+#include "base/atomicops.h"
+#include "base/cancelable_callback.h"
+#include "base/macros.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "media/audio/agc_audio_stream.h"
+#include "media/audio/audio_io.h"
+#include "media/audio/mac/audio_manager_mac.h"
+#include "media/base/audio_block_fifo.h"
+#include "media/base/audio_parameters.h"
+
+namespace media {
+
+class MEDIA_EXPORT AUAudioInputStream
+ : public AgcAudioStream<AudioInputStream> {
+ public:
+ // The ctor takes all the usual parameters, plus |manager| which is the
+ // the audio manager who is creating this object.
+ AUAudioInputStream(
+ AudioManagerMac* manager,
+ const AudioParameters& input_params,
+ AudioDeviceID audio_device_id,
+ const AudioManager::LogCallback& log_callback,
+ AudioManagerBase::VoiceProcessingMode voice_processing_mode);
+
+ AUAudioInputStream(const AUAudioInputStream&) = delete;
+ AUAudioInputStream& operator=(const AUAudioInputStream&) = delete;
+
+ // The dtor is typically called by the AudioManager only and it is usually
+ // triggered by calling AudioInputStream::Close().
+ ~AUAudioInputStream() override;
+
+ // Implementation of AudioInputStream.
+ AudioInputStream::OpenOutcome Open() override;
+ void Start(AudioInputCallback* callback) override;
+ void Stop() override;
+ void Close() override;
+ double GetMaxVolume() override;
+ void SetVolume(double volume) override;
+ double GetVolume() override;
+ bool IsMuted() override;
+ void SetOutputDeviceForAec(const std::string& output_device_id) override;
+
+ // Returns the current hardware sample rate for the default input device.
+ static int HardwareSampleRate();
+
+ // Returns true if the audio unit is active/running.
+ // The result is based on the kAudioOutputUnitProperty_IsRunning property
+ // which exists for output units.
+ bool IsRunning();
+
+ AudioDeviceID device_id() const { return input_device_id_; }
+ size_t requested_buffer_size() const {
+ return input_params_.frames_per_buffer();
+ }
+ AudioUnit audio_unit() const { return audio_unit_; }
+
+ // Fan out the data from the first half of audio_buffer into interleaved
+ // stereo across the whole of audio_buffer. Public for testing only.
+ static void UpmixMonoToStereoInPlace(AudioBuffer* audio_buffer,
+ int bytes_per_sample);
+
+ private:
+ bool OpenAUHAL();
+ bool OpenVoiceProcessingAU();
+
+ // Callback functions called on a real-time priority I/O thread from the audio
+ // unit. These methods are called when recorded audio is available.
+ static OSStatus DataIsAvailable(void* context,
+ AudioUnitRenderActionFlags* flags,
+ const AudioTimeStamp* time_stamp,
+ UInt32 bus_number,
+ UInt32 number_of_frames,
+ AudioBufferList* io_data);
+ OSStatus OnDataIsAvailable(AudioUnitRenderActionFlags* flags,
+ const AudioTimeStamp* time_stamp,
+ UInt32 bus_number,
+ UInt32 number_of_frames);
+
+ // Pushes recorded data to consumer of the input audio stream.
+ OSStatus Provide(UInt32 number_of_frames,
+ AudioBufferList* io_data,
+ const AudioTimeStamp* time_stamp);
+
+ // Gets the current capture time.
+ base::TimeTicks GetCaptureTime(const AudioTimeStamp* input_time_stamp);
+
+ // Gets the number of channels for a stream of audio data.
+ int GetNumberOfChannelsFromStream();
+
+ // Issues the OnError() callback to the |sink_|.
+ void HandleError(OSStatus err);
+
+ // Helper function to check if the volume control is avialable on specific
+ // channel.
+ bool IsVolumeSettableOnChannel(int channel);
+
+ // Helper methods to set and get atomic |input_callback_is_active_|.
+ void SetInputCallbackIsActive(bool active);
+ bool GetInputCallbackIsActive();
+
+ // Checks if a stream was started successfully and the audio unit also starts
+ // to call InputProc() as it should. This method is called once when a timer
+ // expires some time after calling Start().
+ void CheckInputStartupSuccess();
+
+ // Uninitializes the audio unit if needed.
+ void CloseAudioUnit();
+
+ // Reinitializes the AudioUnit to use a new output device.
+ void ReinitializeVoiceProcessingAudioUnit();
+
+ // Adds extra UMA stats when it has been detected that startup failed.
+ void AddHistogramsForFailedStartup();
+
+ // Updates capture timestamp, current lost frames, and total lost frames and
+ // glitches.
+ void UpdateCaptureTimestamp(const AudioTimeStamp* timestamp);
+
+ // Called from the dtor and when the stream is reset.
+ void ReportAndResetStats();
+
+ // Verifies that Open(), Start(), Stop() and Close() are all called on the
+ // creating thread which is the main browser thread (CrBrowserMain) on Mac.
+ base::ThreadChecker thread_checker_;
+
+ // Our creator, the audio manager needs to be notified when we close.
+ AudioManagerMac* const manager_;
+
+ // The audio parameters requested when creating the stream.
+ const AudioParameters input_params_;
+
+ // Stores the number of frames that we actually get callbacks for.
+ // This may be different from what we ask for, so we use this for stats in
+ // order to understand how often this happens and what are the typical values.
+ size_t number_of_frames_provided_;
+
+ // The actual I/O buffer size for the input device connected to the active
+ // AUHAL audio unit.
+ size_t io_buffer_frame_size_;
+
+ // Pointer to the object that will receive the recorded audio samples.
+ AudioInputCallback* sink_;
+
+ // Structure that holds the desired output format of the stream.
+ // Note that, this format can differ from the device(=input) format.
+ AudioStreamBasicDescription format_;
+
+ // The special Audio Unit called AUHAL, which allows us to pass audio data
+ // directly from a microphone, through the HAL, and to our application.
+ // The AUHAL also enables selection of non default devices.
+ AudioUnit audio_unit_;
+
+ // The UID refers to the current input audio device.
+ const AudioDeviceID input_device_id_;
+
+ // Provides a mechanism for encapsulating one or more buffers of audio data.
+ AudioBufferList audio_buffer_list_;
+
+ // Temporary storage for recorded data. The InputProc() renders into this
+ // array as soon as a frame of the desired buffer size has been recorded.
+ std::unique_ptr<uint8_t[]> audio_data_buffer_;
+
+ // Fixed capture hardware latency.
+ base::TimeDelta hardware_latency_;
+
+ // The number of channels in each frame of audio data, which is used
+ // when querying the volume of each channel.
+ int number_of_channels_in_frame_;
+
+ // FIFO used to accumulates recorded data.
+ media::AudioBlockFifo fifo_;
+
+ // Used to defer Start() to workaround http://crbug.com/160920.
+ base::CancelableOnceClosure deferred_start_cb_;
+
+ // Contains time of last successful call to AudioUnitRender().
+ // Initialized first time in Start() and then updated for each valid
+ // audio buffer. Used to detect long error sequences and to take actions
+ // if length of error sequence is above a certain limit.
+ base::TimeTicks last_success_time_;
+
+ // Flags to indicate if we have gotten an input callback.
+ // |got_input_callback_| is only accessed on the OS audio thread in
+ // OnDataIsAvailable() and is set to true when the first callback comes. It
+ // acts as a gate to only set |input_callback_is_active_| atomically once.
+ // |got_input_callback_| is reset to false in Stop() on the main thread. This
+ // is safe since after stopping the audio unit there is no current callback
+ // ongoing and no further callbacks coming.
+ bool got_input_callback_;
+ base::subtle::Atomic32 input_callback_is_active_;
+
+ // Timer which triggers CheckInputStartupSuccess() to verify that input
+ // callbacks have started as intended after a successful call to Start().
+ // This timer lives on the main browser thread.
+ std::unique_ptr<base::OneShotTimer> input_callback_timer_;
+
+ // Set to true if the audio unit's IO buffer was changed when Open() was
+ // called.
+ bool buffer_size_was_changed_;
+
+ // Set to true once when AudioUnitRender() succeeds for the first time.
+ bool audio_unit_render_has_worked_;
+
+ // Set to true when we've successfully called SuppressNoiseReduction to
+ // disable ambient noise reduction.
+ bool noise_reduction_suppressed_;
+
+ // Controls whether or not we use the kAudioUnitSubType_VoiceProcessingIO
+ // voice processing component that provides echo cancellation, ducking
+ // and gain control on Sierra and later.
+ const bool use_voice_processing_;
+
+ // The of the output device to cancel echo from.
+ AudioDeviceID output_device_id_for_aec_;
+
+ // Stores the timestamp of the previous audio buffer provided by the OS.
+ // We use this in combination with |last_number_of_frames_| to detect when
+ // the OS has decided to skip providing frames (i.e. a glitch).
+ // This can happen in case of high CPU load or excessive blocking on the
+ // callback audio thread.
+ // These variables are only touched on the callback thread and then read
+ // in the dtor (when no longer receiving callbacks).
+ // NOTE: Float64 and UInt32 types are used for native API compatibility.
+ Float64 last_sample_time_;
+ UInt32 last_number_of_frames_;
+ UInt32 total_lost_frames_;
+ UInt32 largest_glitch_frames_;
+ int glitches_detected_;
+
+ // Callback to send statistics info.
+ AudioManager::LogCallback log_callback_;
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_MAC_AUDIO_LOW_LATENCY_INPUT_MAC_H_
diff --git a/third_party/chromium/media/audio/mac/audio_low_latency_input_mac_unittest.cc b/third_party/chromium/media/audio/mac/audio_low_latency_input_mac_unittest.cc
new file mode 100644
index 0000000..96287c9
--- /dev/null
+++ b/third_party/chromium/media/audio/mac/audio_low_latency_input_mac_unittest.cc
@@ -0,0 +1,352 @@
+// Copyright (c) 2012 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 <stdint.h>
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/environment.h"
+#include "base/memory/ptr_util.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/test/task_environment.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/platform_thread.h"
+#include "build/build_config.h"
+#include "media/audio/audio_device_description.h"
+#include "media/audio/audio_device_info_accessor_for_tests.h"
+#include "media/audio/audio_io.h"
+#include "media/audio/audio_manager_base.h"
+#include "media/audio/audio_unittest_util.h"
+#include "media/audio/mac/audio_low_latency_input_mac.h"
+#include "media/audio/test_audio_thread.h"
+#include "media/base/seekable_buffer.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+using ::testing::AnyNumber;
+using ::testing::AtLeast;
+using ::testing::Ge;
+using ::testing::NotNull;
+
+namespace media {
+
+ACTION_P4(CheckCountAndPostQuitTask, count, limit, task_runner, closure) {
+ if (++*count >= limit) {
+ task_runner->PostTask(FROM_HERE, closure);
+ }
+}
+
+class MockAudioInputCallback : public AudioInputStream::AudioInputCallback {
+ public:
+ MOCK_METHOD3(OnData,
+ void(const AudioBus* src,
+ base::TimeTicks capture_time,
+ double volume));
+ MOCK_METHOD0(OnError, void());
+};
+
+// This audio sink implementation should be used for manual tests only since
+// the recorded data is stored on a raw binary data file.
+// The last test (WriteToFileAudioSink) - which is disabled by default -
+// can use this audio sink to store the captured data on a file for offline
+// analysis.
+class WriteToFileAudioSink : public AudioInputStream::AudioInputCallback {
+ public:
+ // Allocate space for ~10 seconds of data @ 48kHz in stereo:
+ // 2 bytes per sample, 2 channels, 10ms @ 48kHz, 10 seconds <=> 1920000 bytes.
+ static const int kMaxBufferSize = 2 * 2 * 480 * 100 * 10;
+
+ explicit WriteToFileAudioSink(const char* file_name)
+ : buffer_(0, kMaxBufferSize),
+ file_(fopen(file_name, "wb")),
+ bytes_to_write_(0) {
+ }
+
+ ~WriteToFileAudioSink() override {
+ int bytes_written = 0;
+ while (bytes_written < bytes_to_write_) {
+ const uint8_t* chunk;
+ int chunk_size;
+
+ // Stop writing if no more data is available.
+ if (!buffer_.GetCurrentChunk(&chunk, &chunk_size))
+ break;
+
+ // Write recorded data chunk to the file and prepare for next chunk.
+ fwrite(chunk, 1, chunk_size, file_);
+ buffer_.Seek(chunk_size);
+ bytes_written += chunk_size;
+ }
+ fclose(file_);
+ }
+
+ // AudioInputStream::AudioInputCallback implementation.
+ void OnData(const AudioBus* src,
+ base::TimeTicks capture_time,
+ double volume) override {
+ const int num_samples = src->frames() * src->channels();
+ std::unique_ptr<int16_t> interleaved(new int16_t[num_samples]);
+ src->ToInterleaved<SignedInt16SampleTypeTraits>(src->frames(),
+ interleaved.get());
+
+ // Store data data in a temporary buffer to avoid making blocking
+ // fwrite() calls in the audio callback. The complete buffer will be
+ // written to file in the destructor.
+ const int bytes_per_sample = sizeof(*interleaved);
+ const int size = bytes_per_sample * num_samples;
+ if (buffer_.Append((const uint8_t*)interleaved.get(), size)) {
+ bytes_to_write_ += size;
+ }
+ }
+
+ void OnError() override {}
+
+ private:
+ media::SeekableBuffer buffer_;
+ FILE* file_;
+ int bytes_to_write_;
+};
+
+class MacAudioInputTest : public testing::Test {
+ protected:
+ MacAudioInputTest()
+ : task_environment_(
+ base::test::SingleThreadTaskEnvironment::MainThreadType::UI),
+ audio_manager_(AudioManager::CreateForTesting(
+ std::make_unique<TestAudioThread>())) {
+ // Wait for the AudioManager to finish any initialization on the audio loop.
+ base::RunLoop().RunUntilIdle();
+ }
+
+ ~MacAudioInputTest() override { audio_manager_->Shutdown(); }
+
+ bool InputDevicesAvailable() {
+#if defined(OS_MAC) && defined(ARCH_CPU_ARM64)
+ // TODO(crbug.com/1128458): macOS on ARM64 says it has devices, but won't
+ // let any of them be opened or listed.
+ return false;
+#else
+ return AudioDeviceInfoAccessorForTests(audio_manager_.get())
+ .HasAudioInputDevices();
+#endif
+ }
+
+ // Convenience method which creates a default AudioInputStream object using
+ // a 10ms frame size and a sample rate which is set to the hardware sample
+ // rate.
+ AudioInputStream* CreateDefaultAudioInputStream() {
+ int fs = static_cast<int>(AUAudioInputStream::HardwareSampleRate());
+ int samples_per_packet = fs / 100;
+ AudioInputStream* ais = audio_manager_->MakeAudioInputStream(
+ AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
+ CHANNEL_LAYOUT_STEREO, fs, samples_per_packet),
+ AudioDeviceDescription::kDefaultDeviceId,
+ base::BindRepeating(&MacAudioInputTest::OnLogMessage,
+ base::Unretained(this)));
+ EXPECT_TRUE(ais);
+ return ais;
+ }
+
+ // Convenience method which creates an AudioInputStream object with a
+ // specified channel layout.
+ AudioInputStream* CreateAudioInputStream(ChannelLayout channel_layout) {
+ int fs = static_cast<int>(AUAudioInputStream::HardwareSampleRate());
+ int samples_per_packet = fs / 100;
+ AudioInputStream* ais = audio_manager_->MakeAudioInputStream(
+ AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, channel_layout,
+ fs, samples_per_packet),
+ AudioDeviceDescription::kDefaultDeviceId,
+ base::BindRepeating(&MacAudioInputTest::OnLogMessage,
+ base::Unretained(this)));
+ EXPECT_TRUE(ais);
+ return ais;
+ }
+
+ void OnLogMessage(const std::string& message) { log_message_ = message; }
+
+ base::test::SingleThreadTaskEnvironment task_environment_;
+ std::unique_ptr<AudioManager> audio_manager_;
+ std::string log_message_;
+};
+
+// Test Create(), Close().
+TEST_F(MacAudioInputTest, AUAudioInputStreamCreateAndClose) {
+ ABORT_AUDIO_TEST_IF_NOT(InputDevicesAvailable());
+ AudioInputStream* ais = CreateDefaultAudioInputStream();
+ ais->Close();
+}
+
+// Test Open(), Close().
+TEST_F(MacAudioInputTest, AUAudioInputStreamOpenAndClose) {
+ ABORT_AUDIO_TEST_IF_NOT(InputDevicesAvailable());
+ AudioInputStream* ais = CreateDefaultAudioInputStream();
+ EXPECT_EQ(ais->Open(), AudioInputStream::OpenOutcome::kSuccess);
+ ais->Close();
+}
+
+// Test Open(), Start(), Close().
+TEST_F(MacAudioInputTest, AUAudioInputStreamOpenStartAndClose) {
+ ABORT_AUDIO_TEST_IF_NOT(InputDevicesAvailable());
+ AudioInputStream* ais = CreateDefaultAudioInputStream();
+ EXPECT_EQ(ais->Open(), AudioInputStream::OpenOutcome::kSuccess);
+ MockAudioInputCallback sink;
+ ais->Start(&sink);
+ ais->Close();
+}
+
+// Test Open(), Start(), Stop(), Close().
+TEST_F(MacAudioInputTest, AUAudioInputStreamOpenStartStopAndClose) {
+ ABORT_AUDIO_TEST_IF_NOT(InputDevicesAvailable());
+ AudioInputStream* ais = CreateDefaultAudioInputStream();
+ EXPECT_EQ(ais->Open(), AudioInputStream::OpenOutcome::kSuccess);
+ MockAudioInputCallback sink;
+ ais->Start(&sink);
+ ais->Stop();
+ ais->Close();
+}
+
+// Verify that recording starts and stops correctly in mono using mocked sink.
+TEST_F(MacAudioInputTest, AUAudioInputStreamVerifyMonoRecording) {
+ ABORT_AUDIO_TEST_IF_NOT(InputDevicesAvailable());
+
+ int count = 0;
+
+ // Create an audio input stream which records in mono.
+ AudioInputStream* ais = CreateAudioInputStream(CHANNEL_LAYOUT_MONO);
+ EXPECT_EQ(ais->Open(), AudioInputStream::OpenOutcome::kSuccess);
+
+ MockAudioInputCallback sink;
+
+ // We use 10ms packets and will run the test until ten packets are received.
+ // All should contain valid packets of the same size and a valid delay
+ // estimate.
+ base::RunLoop run_loop;
+ EXPECT_CALL(sink, OnData(NotNull(), _, _))
+ .Times(AtLeast(10))
+ .WillRepeatedly(CheckCountAndPostQuitTask(
+ &count, 10, task_environment_.GetMainThreadTaskRunner(),
+ run_loop.QuitClosure()));
+ ais->Start(&sink);
+ run_loop.Run();
+ ais->Stop();
+ ais->Close();
+
+ EXPECT_FALSE(log_message_.empty());
+}
+
+// Verify that recording starts and stops correctly in mono using mocked sink.
+TEST_F(MacAudioInputTest, AUAudioInputStreamVerifyStereoRecording) {
+ ABORT_AUDIO_TEST_IF_NOT(InputDevicesAvailable());
+
+ int count = 0;
+
+ // Create an audio input stream which records in stereo.
+ AudioInputStream* ais = CreateAudioInputStream(CHANNEL_LAYOUT_STEREO);
+ EXPECT_EQ(ais->Open(), AudioInputStream::OpenOutcome::kSuccess);
+
+ MockAudioInputCallback sink;
+
+ // We use 10ms packets and will run the test until ten packets are received.
+ // All should contain valid packets of the same size and a valid delay
+ // estimate.
+ // TODO(henrika): http://crbug.com/154352 forced us to run the capture side
+ // using a native buffer size of 128 audio frames and combine it with a FIFO
+ // to match the requested size by the client. This change might also have
+ // modified the delay estimates since the existing Ge(bytes_per_packet) for
+ // parameter #4 does no longer pass. I am removing this restriction here to
+ // ensure that we can land the patch but will revisit this test again when
+ // more analysis of the delay estimates are done.
+ base::RunLoop run_loop;
+ EXPECT_CALL(sink, OnData(NotNull(), _, _))
+ .Times(AtLeast(10))
+ .WillRepeatedly(CheckCountAndPostQuitTask(
+ &count, 10, task_environment_.GetMainThreadTaskRunner(),
+ run_loop.QuitClosure()));
+ ais->Start(&sink);
+ run_loop.Run();
+ ais->Stop();
+ ais->Close();
+
+ EXPECT_FALSE(log_message_.empty());
+}
+
+// This test is intended for manual tests and should only be enabled
+// when it is required to store the captured data on a local file.
+// By default, GTest will print out YOU HAVE 1 DISABLED TEST.
+// To include disabled tests in test execution, just invoke the test program
+// with --gtest_also_run_disabled_tests or set the GTEST_ALSO_RUN_DISABLED_TESTS
+// environment variable to a value greater than 0.
+TEST_F(MacAudioInputTest, DISABLED_AUAudioInputStreamRecordToFile) {
+ ABORT_AUDIO_TEST_IF_NOT(InputDevicesAvailable());
+ const char* file_name = "out_stereo_10sec.pcm";
+
+ int fs = static_cast<int>(AUAudioInputStream::HardwareSampleRate());
+ AudioInputStream* ais = CreateDefaultAudioInputStream();
+ EXPECT_EQ(ais->Open(), AudioInputStream::OpenOutcome::kSuccess);
+
+ fprintf(stderr, " File name : %s\n", file_name);
+ fprintf(stderr, " Sample rate: %d\n", fs);
+ WriteToFileAudioSink file_sink(file_name);
+ fprintf(stderr, " >> Speak into the mic while recording...\n");
+ ais->Start(&file_sink);
+ base::PlatformThread::Sleep(TestTimeouts::action_timeout());
+ ais->Stop();
+ fprintf(stderr, " >> Recording has stopped.\n");
+ ais->Close();
+}
+
+TEST(MacAudioInputUpmixerTest, Upmix16bit) {
+ constexpr int kNumFrames = 512;
+ constexpr int kBytesPerSample = sizeof(int16_t);
+ int16_t mono[kNumFrames];
+ int16_t stereo[kNumFrames * 2];
+
+ // Fill the mono buffer and the first half of the stereo buffer with data
+ for (int i = 0; i != kNumFrames; ++i) {
+ mono[i] = i;
+ stereo[i] = i;
+ }
+
+ AudioBuffer audio_buffer;
+ audio_buffer.mNumberChannels = 2;
+ audio_buffer.mDataByteSize = kNumFrames * kBytesPerSample * 2;
+ audio_buffer.mData = stereo;
+ AUAudioInputStream::UpmixMonoToStereoInPlace(&audio_buffer, kBytesPerSample);
+
+ // Assert that the samples have been distributed properly
+ for (int i = 0; i != kNumFrames; ++i) {
+ ASSERT_EQ(mono[i], stereo[i * 2]);
+ ASSERT_EQ(mono[i], stereo[i * 2 + 1]);
+ }
+}
+
+TEST(MacAudioInputUpmixerTest, Upmix32bit) {
+ constexpr int kNumFrames = 512;
+ constexpr int kBytesPerSample = sizeof(int32_t);
+ int32_t mono[kNumFrames];
+ int32_t stereo[kNumFrames * 2];
+
+ // Fill the mono buffer and the first half of the stereo buffer with data
+ for (int i = 0; i != kNumFrames; ++i) {
+ mono[i] = i;
+ stereo[i] = i;
+ }
+
+ AudioBuffer audio_buffer;
+ audio_buffer.mNumberChannels = 2;
+ audio_buffer.mDataByteSize = kNumFrames * kBytesPerSample * 2;
+ audio_buffer.mData = stereo;
+ AUAudioInputStream::UpmixMonoToStereoInPlace(&audio_buffer, kBytesPerSample);
+
+ // Assert that the samples have been distributed properly
+ for (int i = 0; i != kNumFrames; ++i) {
+ ASSERT_EQ(mono[i], stereo[i * 2]);
+ ASSERT_EQ(mono[i], stereo[i * 2 + 1]);
+ }
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/mac/audio_manager_mac.cc b/third_party/chromium/media/audio/mac/audio_manager_mac.cc
new file mode 100644
index 0000000..2066a12
--- /dev/null
+++ b/third_party/chromium/media/audio/mac/audio_manager_mac.cc
@@ -0,0 +1,1252 @@
+// Copyright (c) 2012 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 "media/audio/mac/audio_manager_mac.h"
+
+#include <algorithm>
+#include <limits>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/containers/flat_set.h"
+#include "base/mac/mac_logging.h"
+#include "base/mac/mac_util.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/macros.h"
+#include "base/memory/free_deleter.h"
+#include "base/power_monitor/power_monitor.h"
+#include "base/power_monitor/power_observer.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/threading/thread_checker.h"
+#include "media/audio/audio_device_description.h"
+#include "media/audio/mac/audio_auhal_mac.h"
+#include "media/audio/mac/audio_input_mac.h"
+#include "media/audio/mac/audio_low_latency_input_mac.h"
+#include "media/audio/mac/core_audio_util_mac.h"
+#include "media/audio/mac/coreaudio_dispatch_override.h"
+#include "media/audio/mac/scoped_audio_unit.h"
+#include "media/base/audio_parameters.h"
+#include "media/base/audio_timestamp_helper.h"
+#include "media/base/bind_to_current_loop.h"
+#include "media/base/channel_layout.h"
+#include "media/base/limits.h"
+#include "media/base/mac/audio_latency_mac.h"
+#include "media/base/media_switches.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace media {
+
+// Maximum number of output streams that can be open simultaneously.
+static const int kMaxOutputStreams = 50;
+
+// Default sample-rate on most Apple hardware.
+static const int kFallbackSampleRate = 44100;
+
+static bool GetDeviceChannels(AudioUnit audio_unit,
+ AUElement element,
+ int* channels);
+
+// Helper method to construct AudioObjectPropertyAddress structure given
+// property selector and scope. The property element is always set to
+// kAudioObjectPropertyElementMaster.
+static AudioObjectPropertyAddress GetAudioObjectPropertyAddress(
+ AudioObjectPropertySelector selector,
+ bool is_input) {
+ AudioObjectPropertyScope scope = is_input ? kAudioObjectPropertyScopeInput
+ : kAudioObjectPropertyScopeOutput;
+ AudioObjectPropertyAddress property_address = {
+ selector, scope, kAudioObjectPropertyElementMaster};
+ return property_address;
+}
+
+static const AudioObjectPropertyAddress kNoiseReductionPropertyAddress = {
+ 'nzca', kAudioDevicePropertyScopeInput, kAudioObjectPropertyElementMaster};
+
+// Get IO buffer size range from HAL given device id and scope.
+static OSStatus GetIOBufferFrameSizeRange(AudioDeviceID device_id,
+ bool is_input,
+ UInt32* minimum,
+ UInt32* maximum) {
+ DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
+ AudioObjectPropertyAddress address = GetAudioObjectPropertyAddress(
+ kAudioDevicePropertyBufferFrameSizeRange, is_input);
+ AudioValueRange range = {0, 0};
+ UInt32 data_size = sizeof(AudioValueRange);
+ OSStatus result = AudioObjectGetPropertyData(device_id, &address, 0, NULL,
+ &data_size, &range);
+ if (result != noErr) {
+ OSSTATUS_DLOG(WARNING, result)
+ << "Failed to query IO buffer size range for device: " << std::hex
+ << device_id;
+ } else {
+ *minimum = range.mMinimum;
+ *maximum = range.mMaximum;
+ }
+ return result;
+}
+
+static bool HasAudioHardware(AudioObjectPropertySelector selector) {
+ DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
+ AudioDeviceID output_device_id = kAudioObjectUnknown;
+ const AudioObjectPropertyAddress property_address = {
+ selector,
+ kAudioObjectPropertyScopeGlobal, // mScope
+ kAudioObjectPropertyElementMaster // mElement
+ };
+ UInt32 output_device_id_size = static_cast<UInt32>(sizeof(output_device_id));
+ OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject,
+ &property_address,
+ 0, // inQualifierDataSize
+ NULL, // inQualifierData
+ &output_device_id_size,
+ &output_device_id);
+ return err == kAudioHardwareNoError &&
+ output_device_id != kAudioObjectUnknown;
+}
+
+static std::string GetAudioDeviceNameFromDeviceId(AudioDeviceID device_id,
+ bool is_input) {
+ DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
+ CFStringRef device_name = nullptr;
+ UInt32 data_size = sizeof(device_name);
+ AudioObjectPropertyAddress property_address = GetAudioObjectPropertyAddress(
+ kAudioDevicePropertyDeviceNameCFString, is_input);
+ OSStatus result = AudioObjectGetPropertyData(
+ device_id, &property_address, 0, nullptr, &data_size, &device_name);
+ std::string device;
+ if (result == noErr) {
+ device = base::SysCFStringRefToUTF8(device_name);
+ CFRelease(device_name);
+ }
+ return device;
+}
+
+// Retrieves information on audio devices, and prepends the default
+// device to the list if the list is non-empty.
+static void GetAudioDeviceInfo(bool is_input,
+ media::AudioDeviceNames* device_names) {
+ DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
+ std::vector<AudioObjectID> device_ids =
+ core_audio_mac::GetAllAudioDeviceIDs();
+ for (AudioObjectID device_id : device_ids) {
+ const bool is_valid_for_direction =
+ (is_input ? core_audio_mac::IsInputDevice(device_id)
+ : core_audio_mac::IsOutputDevice(device_id));
+
+ if (!is_valid_for_direction)
+ continue;
+
+ absl::optional<std::string> unique_id =
+ core_audio_mac::GetDeviceUniqueID(device_id);
+ if (!unique_id)
+ continue;
+
+ absl::optional<std::string> label =
+ core_audio_mac::GetDeviceLabel(device_id, is_input);
+ if (!label)
+ continue;
+
+ // Filter out aggregate devices, e.g. those that get created by using
+ // kAudioUnitSubType_VoiceProcessingIO.
+ if (core_audio_mac::IsPrivateAggregateDevice(device_id))
+ continue;
+
+ device_names->emplace_back(std::move(*label), std::move(*unique_id));
+ }
+
+ if (!device_names->empty()) {
+ // Prepend the default device to the list since we always want it to be
+ // on the top of the list for all platforms. There is no duplicate
+ // counting here since the default device has been abstracted out before.
+ device_names->push_front(media::AudioDeviceName::CreateDefault());
+ }
+}
+
+AudioDeviceID AudioManagerMac::GetAudioDeviceIdByUId(
+ bool is_input,
+ const std::string& device_id) {
+ DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
+ AudioObjectPropertyAddress property_address = {
+ kAudioHardwarePropertyDevices,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster
+ };
+ AudioDeviceID audio_device_id = kAudioObjectUnknown;
+ UInt32 device_size = sizeof(audio_device_id);
+ OSStatus result = -1;
+
+ if (AudioDeviceDescription::IsDefaultDevice(device_id)) {
+ // Default Device.
+ property_address.mSelector = is_input ?
+ kAudioHardwarePropertyDefaultInputDevice :
+ kAudioHardwarePropertyDefaultOutputDevice;
+
+ result = AudioObjectGetPropertyData(kAudioObjectSystemObject,
+ &property_address,
+ 0,
+ 0,
+ &device_size,
+ &audio_device_id);
+ } else {
+ // Non-default device.
+ base::ScopedCFTypeRef<CFStringRef> uid(
+ base::SysUTF8ToCFStringRef(device_id));
+ AudioValueTranslation value;
+ value.mInputData = &uid;
+ value.mInputDataSize = sizeof(CFStringRef);
+ value.mOutputData = &audio_device_id;
+ value.mOutputDataSize = device_size;
+ UInt32 translation_size = sizeof(AudioValueTranslation);
+
+ property_address.mSelector = kAudioHardwarePropertyDeviceForUID;
+ result = AudioObjectGetPropertyData(kAudioObjectSystemObject,
+ &property_address,
+ 0,
+ 0,
+ &translation_size,
+ &value);
+ }
+
+ if (result) {
+ OSSTATUS_DLOG(WARNING, result) << "Unable to query device " << device_id
+ << " for AudioDeviceID";
+ }
+
+ return audio_device_id;
+}
+
+static bool GetDefaultDevice(AudioDeviceID* device, bool input) {
+ DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
+ CHECK(device);
+
+ // Obtain the AudioDeviceID of the default input or output AudioDevice.
+ AudioObjectPropertyAddress pa;
+ pa.mSelector = input ? kAudioHardwarePropertyDefaultInputDevice
+ : kAudioHardwarePropertyDefaultOutputDevice;
+ pa.mScope = kAudioObjectPropertyScopeGlobal;
+ pa.mElement = kAudioObjectPropertyElementMaster;
+
+ UInt32 size = sizeof(*device);
+ OSStatus result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &pa, 0,
+ 0, &size, device);
+ if ((result != kAudioHardwareNoError) || (*device == kAudioDeviceUnknown)) {
+ DLOG(ERROR) << "Error getting default AudioDevice.";
+ return false;
+ }
+ return true;
+}
+
+bool AudioManagerMac::GetDefaultOutputDevice(AudioDeviceID* device) {
+ return GetDefaultDevice(device, false);
+}
+
+// Returns the total number of channels on a device; regardless of what the
+// device's preferred rendering layout looks like. Should only be used for the
+// channel count when a device has more than kMaxConcurrentChannels.
+static bool GetDeviceTotalChannelCount(AudioDeviceID device,
+ AudioObjectPropertyScope scope,
+ int* channels) {
+ DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
+ CHECK(channels);
+
+ // Get the stream configuration of the device in an AudioBufferList (with the
+ // buffer pointers set to nullptr) which describes the list of streams and the
+ // number of channels in each stream.
+ AudioObjectPropertyAddress pa = {kAudioDevicePropertyStreamConfiguration,
+ scope, kAudioObjectPropertyElementMaster};
+
+ UInt32 size;
+ OSStatus result = AudioObjectGetPropertyDataSize(device, &pa, 0, 0, &size);
+ if (result != noErr || !size)
+ return false;
+
+ std::unique_ptr<uint8_t[]> list_storage(new uint8_t[size]);
+ AudioBufferList* buffer_list =
+ reinterpret_cast<AudioBufferList*>(list_storage.get());
+
+ result = AudioObjectGetPropertyData(device, &pa, 0, 0, &size, buffer_list);
+ if (result != noErr)
+ return false;
+
+ // Determine number of channels based on the AudioBufferList.
+ // |mNumberBuffers] is the number of interleaved channels in the buffer.
+ // If the number is 1, the buffer is noninterleaved.
+ *channels = 0;
+ for (UInt32 i = 0; i < buffer_list->mNumberBuffers; ++i)
+ *channels += buffer_list->mBuffers[i].mNumberChannels;
+
+ DVLOG(1) << (scope == kAudioDevicePropertyScopeInput ? "Input" : "Output")
+ << " total channels: " << *channels;
+ return true;
+}
+
+// Returns the channel count from the |audio_unit|'s stream format for input
+// scope / input element or output scope / output element.
+static bool GetAudioUnitStreamFormatChannelCount(AudioUnit audio_unit,
+ AUElement element,
+ int* channels) {
+ AudioStreamBasicDescription stream_format;
+ UInt32 size = sizeof(stream_format);
+ OSStatus result =
+ AudioUnitGetProperty(audio_unit, kAudioUnitProperty_StreamFormat,
+ element == AUElement::OUTPUT ? kAudioUnitScope_Output
+ : kAudioUnitScope_Input,
+ element, &stream_format, &size);
+ if (result != noErr) {
+ OSSTATUS_DLOG(ERROR, result) << "Failed to get AudioUnit stream format.";
+ return false;
+ }
+
+ *channels = stream_format.mChannelsPerFrame;
+ return true;
+}
+
+// Returns the channel layout for |device| as provided by the AudioUnit attached
+// to that device matching |element|. Returns true if the count could be pulled
+// from the AudioUnit successfully, false otherwise.
+static bool GetDeviceChannels(AudioDeviceID device,
+ AUElement element,
+ int* channels) {
+ DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
+ CHECK(channels);
+
+ // For input, get the channel count directly from the AudioUnit's stream
+ // format.
+ // TODO(https://crbug.com/796163): Find out if we can use channel layout on
+ // input element, or confirm that we can't.
+ if (element == AUElement::INPUT) {
+ ScopedAudioUnit au(device, element);
+ if (!au.is_valid())
+ return false;
+
+ if (!GetAudioUnitStreamFormatChannelCount(au.audio_unit(), element,
+ channels)) {
+ return false;
+ }
+
+ DVLOG(1) << "Input channels: " << *channels;
+ return true;
+ }
+
+ // For output, use the channel layout to determine channel count.
+ DCHECK(element == AUElement::OUTPUT);
+
+ // If the device has more channels than possible for layouts to express, use
+ // the total count of channels on the device; as of this writing, macOS will
+ // only return up to 8 channels in any layout. To allow WebAudio to work with
+ // > 8 channel devices, we must use the total channel count instead of the
+ // channel count of the preferred layout.
+ int total_channel_count = 0;
+ if (GetDeviceTotalChannelCount(device,
+ element == AUElement::OUTPUT
+ ? kAudioDevicePropertyScopeOutput
+ : kAudioDevicePropertyScopeInput,
+ &total_channel_count) &&
+ total_channel_count > kMaxConcurrentChannels) {
+ *channels = total_channel_count;
+ return true;
+ }
+
+ ScopedAudioUnit au(device, element);
+ if (!au.is_valid())
+ return false;
+
+ return GetDeviceChannels(au.audio_unit(), element, channels);
+}
+
+static bool GetDeviceChannels(AudioUnit audio_unit,
+ AUElement element,
+ int* channels) {
+ // Attempt to retrieve the channel layout from the AudioUnit.
+ //
+ // Note: We don't use kAudioDevicePropertyPreferredChannelLayout on the device
+ // because it is not available on all devices.
+ UInt32 size;
+ Boolean writable;
+ OSStatus result = AudioUnitGetPropertyInfo(
+ audio_unit, kAudioUnitProperty_AudioChannelLayout, kAudioUnitScope_Output,
+ element, &size, &writable);
+ if (result != noErr) {
+ OSSTATUS_DLOG(ERROR, result)
+ << "Failed to get property info for AudioUnit channel layout.";
+ }
+
+ std::unique_ptr<uint8_t[]> layout_storage(new uint8_t[size]);
+ AudioChannelLayout* layout =
+ reinterpret_cast<AudioChannelLayout*>(layout_storage.get());
+
+ result =
+ AudioUnitGetProperty(audio_unit, kAudioUnitProperty_AudioChannelLayout,
+ kAudioUnitScope_Output, element, layout, &size);
+ if (result != noErr) {
+ OSSTATUS_LOG(ERROR, result) << "Failed to get AudioUnit channel layout.";
+ return false;
+ }
+
+ // We don't want to have to know about all channel layout tags, so force OSX
+ // to give us the channel descriptions from the bitmap or tag if necessary.
+ const AudioChannelLayoutTag tag = layout->mChannelLayoutTag;
+ if (tag != kAudioChannelLayoutTag_UseChannelDescriptions) {
+ const bool is_bitmap = tag == kAudioChannelLayoutTag_UseChannelBitmap;
+ const AudioFormatPropertyID fa =
+ is_bitmap ? kAudioFormatProperty_ChannelLayoutForBitmap
+ : kAudioFormatProperty_ChannelLayoutForTag;
+
+ if (is_bitmap) {
+ result = AudioFormatGetPropertyInfo(fa, sizeof(UInt32),
+ &layout->mChannelBitmap, &size);
+ } else {
+ result = AudioFormatGetPropertyInfo(fa, sizeof(AudioChannelLayoutTag),
+ &tag, &size);
+ }
+ if (result != noErr || !size) {
+ OSSTATUS_DLOG(ERROR, result)
+ << "Failed to get AudioFormat property info, size=" << size;
+ return false;
+ }
+
+ layout_storage.reset(new uint8_t[size]);
+ layout = reinterpret_cast<AudioChannelLayout*>(layout_storage.get());
+ if (is_bitmap) {
+ result = AudioFormatGetProperty(fa, sizeof(UInt32),
+ &layout->mChannelBitmap, &size, layout);
+ } else {
+ result = AudioFormatGetProperty(fa, sizeof(AudioChannelLayoutTag), &tag,
+ &size, layout);
+ }
+ if (result != noErr) {
+ OSSTATUS_DLOG(ERROR, result) << "Failed to get AudioFormat property.";
+ return false;
+ }
+ }
+
+ // There is no channel info for stereo, assume so for mono as well.
+ if (layout->mNumberChannelDescriptions <= 2) {
+ *channels = layout->mNumberChannelDescriptions;
+ } else {
+ *channels = 0;
+ for (UInt32 i = 0; i < layout->mNumberChannelDescriptions; ++i) {
+ if (layout->mChannelDescriptions[i].mChannelLabel !=
+ kAudioChannelLabel_Unknown)
+ (*channels)++;
+ }
+ }
+
+ DVLOG(1) << "Output channels: " << *channels;
+ return true;
+}
+
+class AudioManagerMac::AudioPowerObserver : public base::PowerSuspendObserver {
+ public:
+ AudioPowerObserver()
+ : is_suspending_(false),
+ is_monitoring_(base::PowerMonitor::IsInitialized()),
+ num_resume_notifications_(0) {
+ // The PowerMonitor requires significant setup (a CFRunLoop and preallocated
+ // IO ports) so it's not available under unit tests. See the OSX impl of
+ // base::PowerMonitorDeviceSource for more details.
+ if (!is_monitoring_)
+ return;
+ base::PowerMonitor::AddPowerSuspendObserver(this);
+ }
+
+ AudioPowerObserver(const AudioPowerObserver&) = delete;
+ AudioPowerObserver& operator=(const AudioPowerObserver&) = delete;
+
+ ~AudioPowerObserver() override {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (!is_monitoring_)
+ return;
+ base::PowerMonitor::RemovePowerSuspendObserver(this);
+ }
+
+ bool IsSuspending() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return is_suspending_;
+ }
+
+ size_t num_resume_notifications() const { return num_resume_notifications_; }
+
+ bool ShouldDeferStreamStart() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ // Start() should be deferred if the system is in the middle of a suspend or
+ // has recently started the process of resuming.
+ return is_suspending_ || base::TimeTicks::Now() < earliest_start_time_;
+ }
+
+ bool IsOnBatteryPower() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return base::PowerMonitor::IsOnBatteryPower();
+ }
+
+ private:
+ void OnSuspend() override {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DVLOG(1) << "OnSuspend";
+ is_suspending_ = true;
+ }
+
+ void OnResume() override {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DVLOG(1) << "OnResume";
+ ++num_resume_notifications_;
+ is_suspending_ = false;
+ earliest_start_time_ =
+ base::TimeTicks::Now() + base::Seconds(kStartDelayInSecsForPowerEvents);
+ }
+
+ bool is_suspending_;
+ const bool is_monitoring_;
+ base::TimeTicks earliest_start_time_;
+ base::ThreadChecker thread_checker_;
+ size_t num_resume_notifications_;
+};
+
+AudioManagerMac::AudioManagerMac(std::unique_ptr<AudioThread> audio_thread,
+ AudioLogFactory* audio_log_factory)
+ : AudioManagerBase(std::move(audio_thread), audio_log_factory),
+ current_sample_rate_(0),
+ current_output_device_(kAudioDeviceUnknown),
+ in_shutdown_(false),
+ weak_ptr_factory_(this) {
+ SetMaxOutputStreamsAllowed(kMaxOutputStreams);
+
+ // PostTask since AudioManager creation may be on the startup path and this
+ // may be slow.
+ GetTaskRunner()->PostTask(
+ FROM_HERE, base::BindOnce(&AudioManagerMac::InitializeOnAudioThread,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+AudioManagerMac::~AudioManagerMac() = default;
+
+void AudioManagerMac::ShutdownOnAudioThread() {
+ // We are now in shutdown mode. This flag disables MaybeChangeBufferSize()
+ // and IncreaseIOBufferSizeIfPossible() which both touches native Core Audio
+ // APIs and they can fail and disrupt tests during shutdown.
+ in_shutdown_ = true;
+
+ // Even if tasks to close the streams are enqueued, they would not run
+ // leading to CHECKs getting hit in the destructor about open streams. Close
+ // them explicitly here. crbug.com/608049.
+ CloseAllInputStreams();
+ CHECK(basic_input_streams_.empty());
+ CHECK(low_latency_input_streams_.empty());
+
+ // Deinitialize power observer on audio thread, since it's initialized on the
+ // audio thread. Typically, constructor/destructor and
+ // InitializeOnAudioThread/ShutdownOnAudioThread are all run on the main
+ // thread, but this might not be true in testing.
+ power_observer_.reset();
+
+ AudioManagerBase::ShutdownOnAudioThread();
+}
+
+bool AudioManagerMac::HasAudioOutputDevices() {
+ return HasAudioHardware(kAudioHardwarePropertyDefaultOutputDevice);
+}
+
+bool AudioManagerMac::HasAudioInputDevices() {
+ return HasAudioHardware(kAudioHardwarePropertyDefaultInputDevice);
+}
+
+// static
+int AudioManagerMac::HardwareSampleRateForDevice(AudioDeviceID device_id) {
+ DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
+ Float64 nominal_sample_rate;
+ UInt32 info_size = sizeof(nominal_sample_rate);
+
+ static const AudioObjectPropertyAddress kNominalSampleRateAddress = {
+ kAudioDevicePropertyNominalSampleRate,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster
+ };
+ OSStatus result = AudioObjectGetPropertyData(device_id,
+ &kNominalSampleRateAddress,
+ 0,
+ 0,
+ &info_size,
+ &nominal_sample_rate);
+ if (result != noErr) {
+ OSSTATUS_DLOG(WARNING, result)
+ << "Could not get default sample rate for device: " << device_id;
+ return 0;
+ }
+
+ return static_cast<int>(nominal_sample_rate);
+}
+
+// static
+int AudioManagerMac::HardwareSampleRate() {
+ // Determine the default output device's sample-rate.
+ AudioDeviceID device_id = kAudioObjectUnknown;
+ if (!GetDefaultOutputDevice(&device_id))
+ return kFallbackSampleRate;
+
+ return HardwareSampleRateForDevice(device_id);
+}
+
+void AudioManagerMac::GetAudioInputDeviceNames(
+ media::AudioDeviceNames* device_names) {
+ DCHECK(device_names->empty());
+ GetAudioDeviceInfo(true, device_names);
+}
+
+void AudioManagerMac::GetAudioOutputDeviceNames(
+ media::AudioDeviceNames* device_names) {
+ DCHECK(device_names->empty());
+ GetAudioDeviceInfo(false, device_names);
+}
+
+AudioParameters AudioManagerMac::GetInputStreamParameters(
+ const std::string& device_id) {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ AudioDeviceID device = GetAudioDeviceIdByUId(true, device_id);
+ if (device == kAudioObjectUnknown) {
+ DLOG(ERROR) << "Invalid device " << device_id;
+ return AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
+ CHANNEL_LAYOUT_STEREO, kFallbackSampleRate,
+ ChooseBufferSize(true, kFallbackSampleRate));
+ }
+
+ int channels = 0;
+ ChannelLayout channel_layout = CHANNEL_LAYOUT_STEREO;
+ if (GetDeviceChannels(device, AUElement::INPUT, &channels) && channels <= 2) {
+ channel_layout = GuessChannelLayout(channels);
+ } else {
+ DLOG(ERROR) << "Failed to get the device channels, use stereo as default "
+ << "for device " << device_id;
+ }
+
+ int sample_rate = HardwareSampleRateForDevice(device);
+ if (!sample_rate)
+ sample_rate = kFallbackSampleRate;
+
+ // Due to the sharing of the input and output buffer sizes, we need to choose
+ // the input buffer size based on the output sample rate. See
+ // http://crbug.com/154352.
+ const int buffer_size = ChooseBufferSize(true, sample_rate);
+
+ // TODO(grunell): query the native channel layout for the specific device.
+ AudioParameters params(
+ AudioParameters::AUDIO_PCM_LOW_LATENCY, channel_layout, sample_rate,
+ buffer_size,
+ AudioParameters::HardwareCapabilities(
+ GetMinAudioBufferSizeMacOS(limits::kMinAudioBufferSize, sample_rate),
+ limits::kMaxAudioBufferSize));
+
+ if (DeviceSupportsAmbientNoiseReduction(device)) {
+ params.set_effects(AudioParameters::NOISE_SUPPRESSION);
+ }
+
+ // VoiceProcessingIO is only supported on MacOS 10.12 and cannot be used on
+ // aggregate devices, since it creates an aggregate device itself. It also
+ // only runs in mono, but we allow upmixing to stereo since we can't claim a
+ // device works either in stereo without echo cancellation or mono with echo
+ // cancellation.
+ if (base::mac::IsAtLeastOS10_12() &&
+ (params.channel_layout() == CHANNEL_LAYOUT_MONO ||
+ params.channel_layout() == CHANNEL_LAYOUT_STEREO) &&
+ core_audio_mac::GetDeviceTransportType(device) !=
+ kAudioDeviceTransportTypeAggregate) {
+ params.set_effects(params.effects() |
+ AudioParameters::EXPERIMENTAL_ECHO_CANCELLER);
+ }
+
+ return params;
+}
+
+std::string AudioManagerMac::GetAssociatedOutputDeviceID(
+ const std::string& input_device_unique_id) {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ AudioObjectID input_device_id =
+ GetAudioDeviceIdByUId(true, input_device_unique_id);
+ if (input_device_id == kAudioObjectUnknown)
+ return std::string();
+
+ std::vector<AudioObjectID> related_device_ids =
+ core_audio_mac::GetRelatedDeviceIDs(input_device_id);
+
+ // Defined as a set as device IDs might be duplicated in
+ // GetRelatedDeviceIDs().
+ base::flat_set<AudioObjectID> related_output_device_ids;
+ for (AudioObjectID device_id : related_device_ids) {
+ if (core_audio_mac::GetNumStreams(device_id, false /* is_input */) > 0)
+ related_output_device_ids.insert(device_id);
+ }
+
+ // Return the device ID if there is only one associated device.
+ // When there are multiple associated devices, we currently do not have a way
+ // to detect if a device (e.g. a digital output device) is actually connected
+ // to an endpoint, so we cannot randomly pick a device.
+ if (related_output_device_ids.size() == 1) {
+ absl::optional<std::string> related_unique_id =
+ core_audio_mac::GetDeviceUniqueID(*related_output_device_ids.begin());
+ if (related_unique_id)
+ return std::move(*related_unique_id);
+ }
+
+ return std::string();
+}
+
+const char* AudioManagerMac::GetName() {
+ return "Mac";
+}
+
+AudioOutputStream* AudioManagerMac::MakeLinearOutputStream(
+ const AudioParameters& params,
+ const LogCallback& log_callback) {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ return MakeLowLatencyOutputStream(params, std::string(), log_callback);
+}
+
+AudioOutputStream* AudioManagerMac::MakeLowLatencyOutputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ bool device_listener_first_init = false;
+ // Lazily create the audio device listener on the first stream creation,
+ // even if getting an audio device fails. Otherwise, if we have 0 audio
+ // devices, the listener will never be initialized, and new valid devices
+ // will never be detected.
+ if (!output_device_listener_) {
+ // NOTE: Use BindToCurrentLoop() to ensure the callback is always PostTask'd
+ // even if OSX calls us on the right thread. Some CoreAudio drivers will
+ // fire the callbacks during stream creation, leading to re-entrancy issues
+ // otherwise. See http://crbug.com/349604
+ output_device_listener_ = std::make_unique<AudioDeviceListenerMac>(
+ BindToCurrentLoop(base::BindRepeating(
+ &AudioManagerMac::HandleDeviceChanges, base::Unretained(this))));
+ device_listener_first_init = true;
+ }
+
+ AudioDeviceID device = GetAudioDeviceIdByUId(false, device_id);
+ if (device == kAudioObjectUnknown) {
+ DLOG(ERROR) << "Failed to open output device: " << device_id;
+ return NULL;
+ }
+
+ // Only set the device and sample rate if we just initialized the device
+ // listener.
+ if (device_listener_first_init) {
+ // Only set the current output device for the default device.
+ if (AudioDeviceDescription::IsDefaultDevice(device_id))
+ current_output_device_ = device;
+ // Just use the current sample rate since we don't allow non-native sample
+ // rates on OSX.
+ current_sample_rate_ = params.sample_rate();
+ }
+
+ AUHALStream* stream = new AUHALStream(this, params, device, log_callback);
+ output_streams_.push_back(stream);
+ return stream;
+}
+
+std::string AudioManagerMac::GetDefaultOutputDeviceID() {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ return GetDefaultDeviceID(false /* is_input */);
+}
+
+std::string AudioManagerMac::GetDefaultInputDeviceID() {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ return GetDefaultDeviceID(true /* is_input */);
+}
+
+std::string AudioManagerMac::GetDefaultDeviceID(bool is_input) {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ AudioDeviceID device_id = kAudioObjectUnknown;
+ if (!GetDefaultDevice(&device_id, is_input))
+ return std::string();
+
+ const AudioObjectPropertyAddress property_address = {
+ kAudioDevicePropertyDeviceUID,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster
+ };
+ CFStringRef device_uid = NULL;
+ UInt32 size = sizeof(device_uid);
+ OSStatus status = AudioObjectGetPropertyData(device_id,
+ &property_address,
+ 0,
+ NULL,
+ &size,
+ &device_uid);
+ if (status != kAudioHardwareNoError || !device_uid)
+ return std::string();
+
+ std::string ret(base::SysCFStringRefToUTF8(device_uid));
+ CFRelease(device_uid);
+
+ return ret;
+}
+
+AudioInputStream* AudioManagerMac::MakeLinearInputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format());
+ AudioInputStream* stream = new PCMQueueInAudioInputStream(this, params);
+ basic_input_streams_.push_back(stream);
+ return stream;
+}
+
+AudioInputStream* AudioManagerMac::MakeLowLatencyInputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format());
+ // Gets the AudioDeviceID that refers to the AudioInputDevice with the device
+ // unique id. This AudioDeviceID is used to set the device for Audio Unit.
+ AudioDeviceID audio_device_id = GetAudioDeviceIdByUId(true, device_id);
+ if (audio_device_id == kAudioObjectUnknown) {
+ return nullptr;
+ }
+
+ VoiceProcessingMode voice_processing_mode =
+ (params.effects() & AudioParameters::ECHO_CANCELLER)
+ ? VoiceProcessingMode::kEnabled
+ : VoiceProcessingMode::kDisabled;
+
+ auto* stream = new AUAudioInputStream(this, params, audio_device_id,
+ log_callback, voice_processing_mode);
+ low_latency_input_streams_.push_back(stream);
+ return stream;
+}
+
+AudioParameters AudioManagerMac::GetPreferredOutputStreamParameters(
+ const std::string& output_device_id,
+ const AudioParameters& input_params) {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ const AudioDeviceID device = GetAudioDeviceIdByUId(false, output_device_id);
+ if (device == kAudioObjectUnknown) {
+ DLOG(ERROR) << "Invalid output device " << output_device_id;
+ return input_params.IsValid()
+ ? input_params
+ : AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
+ CHANNEL_LAYOUT_STEREO, kFallbackSampleRate,
+ ChooseBufferSize(false, kFallbackSampleRate));
+ }
+
+ const bool has_valid_input_params = input_params.IsValid();
+ const int hardware_sample_rate = HardwareSampleRateForDevice(device);
+
+ // Allow pass through buffer sizes. If concurrent input and output streams
+ // exist, they will use the smallest buffer size amongst them. As such, each
+ // stream must be able to FIFO requests appropriately when this happens.
+ int buffer_size;
+ if (has_valid_input_params) {
+ // Ensure the latency asked for is maintained, even if the sample rate is
+ // changed here.
+ const int scaled_buffer_size = input_params.frames_per_buffer() *
+ hardware_sample_rate /
+ input_params.sample_rate();
+ // If passed in via the input_params we allow buffer sizes to go as
+ // low as the the kMinAudioBufferSize, ignoring what
+ // ChooseBufferSize() normally returns.
+ buffer_size =
+ std::min(static_cast<int>(limits::kMaxAudioBufferSize),
+ std::max(scaled_buffer_size,
+ static_cast<int>(limits::kMinAudioBufferSize)));
+ } else {
+ buffer_size = ChooseBufferSize(false, hardware_sample_rate);
+ }
+
+ int hardware_channels;
+ if (!GetDeviceChannels(device, AUElement::OUTPUT, &hardware_channels))
+ hardware_channels = 2;
+
+ // Use the input channel count and channel layout if possible. Let OSX take
+ // care of remapping the channels; this lets user specified channel layouts
+ // work correctly.
+ int output_channels = input_params.channels();
+ ChannelLayout channel_layout = input_params.channel_layout();
+ if (!has_valid_input_params || output_channels > hardware_channels) {
+ output_channels = hardware_channels;
+ channel_layout = GuessChannelLayout(output_channels);
+ if (channel_layout == CHANNEL_LAYOUT_UNSUPPORTED)
+ channel_layout = CHANNEL_LAYOUT_DISCRETE;
+ }
+
+ AudioParameters params(
+ AudioParameters::AUDIO_PCM_LOW_LATENCY, channel_layout,
+ hardware_sample_rate, buffer_size,
+ AudioParameters::HardwareCapabilities(
+ GetMinAudioBufferSizeMacOS(limits::kMinAudioBufferSize,
+ hardware_sample_rate),
+ limits::kMaxAudioBufferSize));
+ params.set_channels_for_discrete(output_channels);
+ return params;
+}
+
+void AudioManagerMac::InitializeOnAudioThread() {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ InitializeCoreAudioDispatchOverride();
+ power_observer_ = std::make_unique<AudioPowerObserver>();
+}
+
+void AudioManagerMac::HandleDeviceChanges() {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ const int new_sample_rate = HardwareSampleRate();
+ AudioDeviceID new_output_device;
+ GetDefaultOutputDevice(&new_output_device);
+
+ if (current_sample_rate_ == new_sample_rate &&
+ current_output_device_ == new_output_device) {
+ return;
+ }
+
+ current_sample_rate_ = new_sample_rate;
+ current_output_device_ = new_output_device;
+ NotifyAllOutputDeviceChangeListeners();
+}
+
+int AudioManagerMac::ChooseBufferSize(bool is_input, int sample_rate) {
+ // kMinAudioBufferSize is too small for the output side because
+ // CoreAudio can get into under-run if the renderer fails delivering data
+ // to the browser within the allowed time by the OS. The workaround is to
+ // use 256 samples as the default output buffer size for sample rates
+ // smaller than 96KHz.
+ // TODO(xians): Remove this workaround after WebAudio supports user defined
+ // buffer size. See https://github.com/WebAudio/web-audio-api/issues/348
+ // for details.
+ int buffer_size =
+ is_input ? limits::kMinAudioBufferSize : 2 * limits::kMinAudioBufferSize;
+ const int user_buffer_size = GetUserBufferSize();
+ buffer_size = user_buffer_size
+ ? user_buffer_size
+ : GetMinAudioBufferSizeMacOS(buffer_size, sample_rate);
+ return buffer_size;
+}
+
+bool AudioManagerMac::IsSuspending() const {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ return power_observer_->IsSuspending();
+}
+
+bool AudioManagerMac::ShouldDeferStreamStart() const {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ return power_observer_->ShouldDeferStreamStart();
+}
+
+bool AudioManagerMac::IsOnBatteryPower() const {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ return power_observer_->IsOnBatteryPower();
+}
+
+size_t AudioManagerMac::GetNumberOfResumeNotifications() const {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ return power_observer_->num_resume_notifications();
+}
+
+bool AudioManagerMac::MaybeChangeBufferSize(AudioDeviceID device_id,
+ AudioUnit audio_unit,
+ AudioUnitElement element,
+ size_t desired_buffer_size,
+ bool* size_was_changed,
+ size_t* io_buffer_frame_size) {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ if (in_shutdown_) {
+ DVLOG(1) << "Disabled since we are shutting down";
+ return false;
+ }
+ const bool is_input = (element == 1);
+ DVLOG(1) << "MaybeChangeBufferSize(id=0x" << std::hex << device_id
+ << ", is_input=" << is_input << ", desired_buffer_size=" << std::dec
+ << desired_buffer_size << ")";
+
+ *size_was_changed = false;
+ *io_buffer_frame_size = 0;
+
+ // Log the device name (and id) for debugging purposes.
+ std::string device_name = GetAudioDeviceNameFromDeviceId(device_id, is_input);
+ DVLOG(1) << "name: " << device_name << " (ID: 0x" << std::hex << device_id
+ << ")";
+
+ // Get the current size of the I/O buffer for the specified device. The
+ // property is read on a global scope, hence using element 0. The default IO
+ // buffer size on Mac OSX for OS X 10.9 and later is 512 audio frames.
+ UInt32 buffer_size = 0;
+ UInt32 property_size = sizeof(buffer_size);
+ OSStatus result = AudioUnitGetProperty(
+ audio_unit, kAudioDevicePropertyBufferFrameSize, kAudioUnitScope_Global,
+ 0, &buffer_size, &property_size);
+ if (result != noErr) {
+ OSSTATUS_DLOG(ERROR, result)
+ << "AudioUnitGetProperty(kAudioDevicePropertyBufferFrameSize) failed.";
+ return false;
+ }
+ // Store the currently used (not changed yet) I/O buffer frame size.
+ *io_buffer_frame_size = buffer_size;
+
+ DVLOG(1) << "current IO buffer size: " << buffer_size;
+ DVLOG(1) << "#output streams: " << output_streams_.size();
+ DVLOG(1) << "#input streams: " << low_latency_input_streams_.size();
+
+ // Check if a buffer size change is required. If the caller asks for a
+ // reduced size (|desired_buffer_size| < |buffer_size|), the new lower size
+ // will be set. For larger buffer sizes, we have to perform some checks to
+ // see if the size can actually be changed. If there is any other active
+ // streams on the same device, either input or output, a larger size than
+ // their requested buffer size can't be set. The reason is that an existing
+ // stream can't handle buffer size larger than its requested buffer size.
+ // See http://crbug.com/428706 for a reason why.
+
+ if (buffer_size == desired_buffer_size)
+ return true;
+
+ if (desired_buffer_size > buffer_size) {
+ // Do NOT set the buffer size if there is another output stream using
+ // the same device with a smaller requested buffer size.
+ // Note, for the caller stream, its requested_buffer_size() will be the same
+ // as |desired_buffer_size|, so it won't return true due to comparing with
+ // itself.
+ for (auto* stream : output_streams_) {
+ if (stream->device_id() == device_id &&
+ stream->requested_buffer_size() < desired_buffer_size) {
+ return true;
+ }
+ }
+
+ // Do NOT set the buffer size if there is another input stream using
+ // the same device with a smaller buffer size.
+ for (auto* stream : low_latency_input_streams_) {
+ if (stream->device_id() == device_id &&
+ stream->requested_buffer_size() < desired_buffer_size) {
+ return true;
+ }
+ }
+ }
+
+ // In this scope we know that the IO buffer size should be modified. But
+ // first, verify that |desired_buffer_size| is within the valid range and
+ // modify the desired buffer size if it is outside this range.
+ // Note that, we have found that AudioUnitSetProperty(PropertyBufferFrameSize)
+ // does in fact do this limitation internally and report noErr even if the
+ // user tries to set an invalid size. As an example, asking for a size of
+ // 4410 will on most devices be limited to 4096 without any further notice.
+ UInt32 minimum = buffer_size;
+ UInt32 maximum = buffer_size;
+ result = GetIOBufferFrameSizeRange(device_id, is_input, &minimum, &maximum);
+ if (result != noErr) {
+ // OS error is logged in GetIOBufferFrameSizeRange().
+ return false;
+ }
+ DVLOG(1) << "valid IO buffer size range: [" << minimum << ", " << maximum
+ << "]";
+ buffer_size = desired_buffer_size;
+ if (buffer_size < minimum)
+ buffer_size = minimum;
+ else if (buffer_size > maximum)
+ buffer_size = maximum;
+ DVLOG(1) << "validated desired buffer size: " << buffer_size;
+
+ // Set new (and valid) I/O buffer size for the specified device. The property
+ // is set on a global scope, hence using element 0.
+ result = AudioUnitSetProperty(audio_unit, kAudioDevicePropertyBufferFrameSize,
+ kAudioUnitScope_Global, 0, &buffer_size,
+ sizeof(buffer_size));
+ OSSTATUS_DLOG_IF(ERROR, result != noErr, result)
+ << "AudioUnitSetProperty(kAudioDevicePropertyBufferFrameSize) failed. "
+ << "Size:: " << buffer_size;
+ *size_was_changed = (result == noErr);
+ DVLOG_IF(1, result == noErr) << "IO buffer size changed to: " << buffer_size;
+ // Store the currently used (after a change) I/O buffer frame size.
+ *io_buffer_frame_size = buffer_size;
+ return result == noErr;
+}
+
+// static
+base::TimeDelta AudioManagerMac::GetHardwareLatency(
+ AudioUnit audio_unit,
+ AudioDeviceID device_id,
+ AudioObjectPropertyScope scope,
+ int sample_rate) {
+ if (!audio_unit || device_id == kAudioObjectUnknown) {
+ DLOG(WARNING) << "Audio unit object is NULL or device ID is unknown";
+ return base::TimeDelta();
+ }
+
+ // Get audio unit latency.
+ Float64 audio_unit_latency_sec = 0.0;
+ UInt32 size = sizeof(audio_unit_latency_sec);
+ OSStatus result = AudioUnitGetProperty(audio_unit, kAudioUnitProperty_Latency,
+ kAudioUnitScope_Global, 0,
+ &audio_unit_latency_sec, &size);
+ OSSTATUS_DLOG_IF(WARNING, result != noErr, result)
+ << "Could not get audio unit latency";
+
+ // Get audio device latency.
+ AudioObjectPropertyAddress property_address = {
+ kAudioDevicePropertyLatency, scope, kAudioObjectPropertyElementMaster};
+ UInt32 device_latency_frames = 0;
+ size = sizeof(device_latency_frames);
+ result = AudioObjectGetPropertyData(device_id, &property_address, 0, nullptr,
+ &size, &device_latency_frames);
+ OSSTATUS_DLOG_IF(WARNING, result != noErr, result)
+ << "Could not get audio device latency.";
+
+ // Retrieve stream ids and take the stream latency from the first stream.
+ // There may be multiple streams with different latencies, but since we're
+ // likely using this delay information for a/v sync we must choose one of
+ // them; Apple recommends just taking the first entry.
+ //
+ // TODO(dalecurtis): Refactor all these "get data size" + "get data" calls
+ // into a common utility function that just returns a std::unique_ptr.
+ UInt32 stream_latency_frames = 0;
+ property_address.mSelector = kAudioDevicePropertyStreams;
+ result = AudioObjectGetPropertyDataSize(device_id, &property_address, 0,
+ nullptr, &size);
+ if (result == noErr && size >= sizeof(AudioStreamID)) {
+ std::unique_ptr<uint8_t[]> stream_id_storage(new uint8_t[size]);
+ AudioStreamID* stream_ids =
+ reinterpret_cast<AudioStreamID*>(stream_id_storage.get());
+ result = AudioObjectGetPropertyData(device_id, &property_address, 0,
+ nullptr, &size, stream_ids);
+ if (result == noErr) {
+ property_address.mSelector = kAudioStreamPropertyLatency;
+ size = sizeof(stream_latency_frames);
+ result =
+ AudioObjectGetPropertyData(stream_ids[0], &property_address, 0,
+ nullptr, &size, &stream_latency_frames);
+ OSSTATUS_DLOG_IF(WARNING, result != noErr, result)
+ << "Could not get stream latency for stream #0.";
+ } else {
+ OSSTATUS_DLOG(WARNING, result)
+ << "Could not get audio device stream ids.";
+ }
+ } else {
+ OSSTATUS_DLOG_IF(WARNING, result != noErr, result)
+ << "Could not get audio device stream ids size.";
+ }
+
+ return base::Seconds(audio_unit_latency_sec) +
+ AudioTimestampHelper::FramesToTime(
+ device_latency_frames + stream_latency_frames, sample_rate);
+}
+
+bool AudioManagerMac::DeviceSupportsAmbientNoiseReduction(
+ AudioDeviceID device_id) {
+ return AudioObjectHasProperty(device_id, &kNoiseReductionPropertyAddress);
+}
+
+bool AudioManagerMac::SuppressNoiseReduction(AudioDeviceID device_id) {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ DCHECK(DeviceSupportsAmbientNoiseReduction(device_id));
+ NoiseReductionState& state = device_noise_reduction_states_[device_id];
+ if (state.suppression_count == 0) {
+ UInt32 initially_enabled = 0;
+ UInt32 size = sizeof(initially_enabled);
+ OSStatus result =
+ AudioObjectGetPropertyData(device_id, &kNoiseReductionPropertyAddress,
+ 0, nullptr, &size, &initially_enabled);
+ if (result != noErr)
+ return false;
+
+ if (initially_enabled) {
+ const UInt32 disable = 0;
+ OSStatus result =
+ AudioObjectSetPropertyData(device_id, &kNoiseReductionPropertyAddress,
+ 0, nullptr, sizeof(disable), &disable);
+ if (result != noErr) {
+ OSSTATUS_DLOG(WARNING, result)
+ << "Failed to disable ambient noise reduction for device: "
+ << std::hex << device_id;
+ }
+ state.initial_state = NoiseReductionState::ENABLED;
+ } else {
+ state.initial_state = NoiseReductionState::DISABLED;
+ }
+ }
+
+ // Only increase the counter if suppression succeeded or is already active.
+ ++state.suppression_count;
+ return true;
+}
+
+void AudioManagerMac::UnsuppressNoiseReduction(AudioDeviceID device_id) {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ NoiseReductionState& state = device_noise_reduction_states_[device_id];
+ DCHECK_NE(state.suppression_count, 0);
+ --state.suppression_count;
+ if (state.suppression_count == 0) {
+ if (state.initial_state == NoiseReductionState::ENABLED) {
+ const UInt32 enable = 1;
+ OSStatus result =
+ AudioObjectSetPropertyData(device_id, &kNoiseReductionPropertyAddress,
+ 0, nullptr, sizeof(enable), &enable);
+ if (result != noErr) {
+ OSSTATUS_DLOG(WARNING, result)
+ << "Failed to re-enable ambient noise reduction for device: "
+ << std::hex << device_id;
+ }
+ }
+ }
+}
+
+bool AudioManagerMac::AudioDeviceIsUsedForInput(AudioDeviceID device_id) {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ if (!basic_input_streams_.empty()) {
+ // For Audio Queues and in the default case (Mac OS X), the audio comes
+ // from the system’s default audio input device as set by a user in System
+ // Preferences.
+ AudioDeviceID default_id;
+ GetDefaultDevice(&default_id, true);
+ if (default_id == device_id)
+ return true;
+ }
+
+ // Each low latency streams has its own device ID.
+ for (auto* stream : low_latency_input_streams_) {
+ if (stream->device_id() == device_id)
+ return true;
+ }
+ return false;
+}
+
+void AudioManagerMac::ReleaseOutputStream(AudioOutputStream* stream) {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ output_streams_.remove(static_cast<AUHALStream*>(stream));
+ AudioManagerBase::ReleaseOutputStream(stream);
+}
+
+void AudioManagerMac::ReleaseOutputStreamUsingRealDevice(
+ AudioOutputStream* stream,
+ AudioDeviceID device_id) {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ DVLOG(1) << "Closing output stream with id=0x" << std::hex << device_id;
+ DVLOG(1) << "requested_buffer_size: "
+ << static_cast<AUHALStream*>(stream)->requested_buffer_size();
+
+ // Start by closing down the specified output stream.
+ output_streams_.remove(static_cast<AUHALStream*>(stream));
+ AudioManagerBase::ReleaseOutputStream(stream);
+}
+
+void AudioManagerMac::ReleaseInputStream(AudioInputStream* stream) {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ auto stream_it = std::find(basic_input_streams_.begin(),
+ basic_input_streams_.end(),
+ stream);
+ if (stream_it == basic_input_streams_.end())
+ low_latency_input_streams_.remove(static_cast<AUAudioInputStream*>(stream));
+ else
+ basic_input_streams_.erase(stream_it);
+
+ AudioManagerBase::ReleaseInputStream(stream);
+}
+
+std::unique_ptr<AudioManager> CreateAudioManager(
+ std::unique_ptr<AudioThread> audio_thread,
+ AudioLogFactory* audio_log_factory) {
+ return std::make_unique<AudioManagerMac>(std::move(audio_thread),
+ audio_log_factory);
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/mac/audio_manager_mac.h b/third_party/chromium/media/audio/mac/audio_manager_mac.h
new file mode 100644
index 0000000..8abe189
--- /dev/null
+++ b/third_party/chromium/media/audio/mac/audio_manager_mac.h
@@ -0,0 +1,209 @@
+// Copyright (c) 2012 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 MEDIA_AUDIO_MAC_AUDIO_MANAGER_MAC_H_
+#define MEDIA_AUDIO_MAC_AUDIO_MANAGER_MAC_H_
+
+#include <AudioUnit/AudioUnit.h>
+#include <CoreAudio/AudioHardware.h>
+#include <stddef.h>
+
+#include <list>
+#include <map>
+#include <memory>
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "media/audio/audio_manager_base.h"
+#include "media/audio/mac/audio_device_listener_mac.h"
+
+namespace media {
+
+class AUAudioInputStream;
+class AUHALStream;
+
+// Mac OS X implementation of the AudioManager singleton. This class is internal
+// to the audio output and only internal users can call methods not exposed by
+// the AudioManager class.
+class MEDIA_EXPORT AudioManagerMac : public AudioManagerBase {
+ public:
+ AudioManagerMac(std::unique_ptr<AudioThread> audio_thread,
+ AudioLogFactory* audio_log_factory);
+
+ AudioManagerMac(const AudioManagerMac&) = delete;
+ AudioManagerMac& operator=(const AudioManagerMac&) = delete;
+
+ ~AudioManagerMac() override;
+
+ // Implementation of AudioManager.
+ bool HasAudioOutputDevices() override;
+ bool HasAudioInputDevices() override;
+ void GetAudioInputDeviceNames(AudioDeviceNames* device_names) override;
+ void GetAudioOutputDeviceNames(AudioDeviceNames* device_names) override;
+ AudioParameters GetInputStreamParameters(
+ const std::string& device_id) override;
+ std::string GetAssociatedOutputDeviceID(
+ const std::string& input_device_id) override;
+ const char* GetName() override;
+
+ // Implementation of AudioManagerBase.
+ AudioOutputStream* MakeLinearOutputStream(
+ const AudioParameters& params,
+ const LogCallback& log_callback) override;
+ AudioOutputStream* MakeLowLatencyOutputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) override;
+ AudioInputStream* MakeLinearInputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) override;
+ AudioInputStream* MakeLowLatencyInputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) override;
+
+ std::string GetDefaultInputDeviceID() override;
+ std::string GetDefaultOutputDeviceID() override;
+
+ // Used to track destruction of input and output streams.
+ void ReleaseOutputStream(AudioOutputStream* stream) override;
+ void ReleaseInputStream(AudioInputStream* stream) override;
+
+ // Called by AUHALStream::Close() before releasing the stream.
+ // This method is a special contract between the real stream and the audio
+ // manager and it ensures that we only try to increase the IO buffer size
+ // for real streams and not for fake or mocked streams.
+ void ReleaseOutputStreamUsingRealDevice(AudioOutputStream* stream,
+ AudioDeviceID device_id);
+
+ static int HardwareSampleRateForDevice(AudioDeviceID device_id);
+ static int HardwareSampleRate();
+ static bool GetDefaultOutputDevice(AudioDeviceID* device);
+ static AudioDeviceID GetAudioDeviceIdByUId(bool is_input,
+ const std::string& device_id);
+
+ // OSX has issues with starting streams as the system goes into suspend and
+ // immediately after it wakes up from resume. See http://crbug.com/160920.
+ // As a workaround we delay Start() when it occurs after suspend and for a
+ // small amount of time after resume.
+ //
+ // Streams should consult ShouldDeferStreamStart() and if true check the value
+ // again after |kStartDelayInSecsForPowerEvents| has elapsed. If false, the
+ // stream may be started immediately.
+ // TODO(henrika): track UMA statistics related to defer start to come up with
+ // a suitable delay value.
+ enum { kStartDelayInSecsForPowerEvents = 5 };
+ bool ShouldDeferStreamStart() const;
+
+ // True if the device is on battery power.
+ bool IsOnBatteryPower() const;
+
+ // Number of times the device has resumed from power suspension.
+ size_t GetNumberOfResumeNotifications() const;
+
+ // True if the device is suspending.
+ bool IsSuspending() const;
+
+ // Changes the I/O buffer size for |device_id| if |desired_buffer_size| is
+ // lower than the current device buffer size. The buffer size can also be
+ // modified under other conditions. See comments in the corresponding cc-file
+ // for more details.
+ // |size_was_changed| is set to true if the device's buffer size was changed
+ // and |io_buffer_frame_size| contains the new buffer size.
+ // Returns false if an error occurred.
+ bool MaybeChangeBufferSize(AudioDeviceID device_id,
+ AudioUnit audio_unit,
+ AudioUnitElement element,
+ size_t desired_buffer_size,
+ bool* size_was_changed,
+ size_t* io_buffer_frame_size);
+
+ // Returns the latency for the given audio unit and device. Total latency is
+ // the sum of the latency of the AudioUnit, device, and stream. If any one
+ // component of the latency can't be retrieved it is considered as zero.
+ static base::TimeDelta GetHardwareLatency(AudioUnit audio_unit,
+ AudioDeviceID device_id,
+ AudioObjectPropertyScope scope,
+ int sample_rate);
+
+ // Number of constructed output and input streams.
+ size_t output_streams() const { return output_streams_.size(); }
+ size_t low_latency_input_streams() const {
+ return low_latency_input_streams_.size();
+ }
+ size_t basic_input_streams() const { return basic_input_streams_.size(); }
+
+ bool DeviceSupportsAmbientNoiseReduction(AudioDeviceID device_id);
+ bool SuppressNoiseReduction(AudioDeviceID device_id);
+ void UnsuppressNoiseReduction(AudioDeviceID device_id);
+
+ // The state of a single device for which we've tried to disable Ambient Noise
+ // Reduction. If the device initially has ANR enabled, it will be turned off
+ // as the suppression count goes from 0 to 1 and turned on again as the count
+ // returns to 0.
+ struct NoiseReductionState {
+ enum State { DISABLED, ENABLED };
+ State initial_state = DISABLED;
+ int suppression_count = 0;
+ };
+
+ // Keep track of the devices that we've changed the Ambient Noise Reduction
+ // setting on.
+ std::map<AudioDeviceID, NoiseReductionState> device_noise_reduction_states_;
+
+ protected:
+ AudioParameters GetPreferredOutputStreamParameters(
+ const std::string& output_device_id,
+ const AudioParameters& input_params) override;
+ void ShutdownOnAudioThread() override;
+
+ private:
+ void InitializeOnAudioThread();
+
+ int ChooseBufferSize(bool is_input, int sample_rate);
+
+ // Notify streams of a device change if the default output device or its
+ // sample rate has changed, otherwise does nothing.
+ void HandleDeviceChanges();
+
+ // Returns true if any active input stream is using the specified |device_id|.
+ bool AudioDeviceIsUsedForInput(AudioDeviceID device_id);
+
+ std::string GetDefaultDeviceID(bool is_input);
+
+ std::unique_ptr<AudioDeviceListenerMac> output_device_listener_;
+
+ // Track the output sample-rate and the default output device
+ // so we can intelligently handle device notifications only when necessary.
+ int current_sample_rate_;
+ AudioDeviceID current_output_device_;
+
+ // Helper class which monitors power events to determine if output streams
+ // should defer Start() calls. Required to workaround an OSX bug. See
+ // http://crbug.com/160920 for more details.
+ class AudioPowerObserver;
+ std::unique_ptr<AudioPowerObserver> power_observer_;
+
+ // Tracks all constructed input and output streams.
+ // TODO(alokp): We used to track these streams to close before destruction.
+ // We no longer close the streams, so we may be able to get rid of these
+ // member variables. They are currently used by MaybeChangeBufferSize().
+ // Investigate if we can remove these.
+ std::list<AudioInputStream*> basic_input_streams_;
+ std::list<AUAudioInputStream*> low_latency_input_streams_;
+ std::list<AUHALStream*> output_streams_;
+
+ // Set to true in the destructor. Ensures that methods that touches native
+ // Core Audio APIs are not executed during shutdown.
+ bool in_shutdown_;
+
+ base::WeakPtrFactory<AudioManagerMac> weak_ptr_factory_;
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_MAC_AUDIO_MANAGER_MAC_H_
diff --git a/third_party/chromium/media/audio/mac/core_audio_util_mac.cc b/third_party/chromium/media/audio/mac/core_audio_util_mac.cc
new file mode 100644
index 0000000..8283c1a
--- /dev/null
+++ b/third_party/chromium/media/audio/mac/core_audio_util_mac.cc
@@ -0,0 +1,358 @@
+// Copyright 2018 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 "media/audio/mac/core_audio_util_mac.h"
+
+#include <IOKit/audio/IOAudioTypes.h>
+
+#include <utility>
+
+#include "base/mac/mac_logging.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/string_util.h"
+#include "base/strings/sys_string_conversions.h"
+
+namespace media {
+namespace core_audio_mac {
+
+namespace {
+
+AudioObjectPropertyScope InputOutputScope(bool is_input) {
+ return is_input ? kAudioObjectPropertyScopeInput
+ : kAudioObjectPropertyScopeOutput;
+}
+
+absl::optional<std::string> GetDeviceStringProperty(
+ AudioObjectID device_id,
+ AudioObjectPropertySelector property_selector) {
+ CFStringRef property_value = nullptr;
+ UInt32 size = sizeof(property_value);
+ AudioObjectPropertyAddress property_address = {
+ property_selector, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+
+ OSStatus result = AudioObjectGetPropertyData(
+ device_id, &property_address, 0 /* inQualifierDataSize */,
+ nullptr /* inQualifierData */, &size, &property_value);
+ if (result != noErr) {
+ OSSTATUS_DLOG(WARNING, result)
+ << "Failed to read string property " << property_selector
+ << " for device " << device_id;
+ return absl::nullopt;
+ }
+
+ if (!property_value)
+ return absl::nullopt;
+
+ std::string device_property = base::SysCFStringRefToUTF8(property_value);
+ CFRelease(property_value);
+
+ return device_property;
+}
+
+absl::optional<uint32_t> GetDeviceUint32Property(
+ AudioObjectID device_id,
+ AudioObjectPropertySelector property_selector,
+ AudioObjectPropertyScope property_scope) {
+ AudioObjectPropertyAddress property_address = {
+ property_selector, property_scope, kAudioObjectPropertyElementMaster};
+ UInt32 property_value;
+ UInt32 size = sizeof(property_value);
+ OSStatus result = AudioObjectGetPropertyData(
+ device_id, &property_address, 0 /* inQualifierDataSize */,
+ nullptr /* inQualifierData */, &size, &property_value);
+ if (result != noErr)
+ return absl::nullopt;
+
+ return property_value;
+}
+
+uint32_t GetDevicePropertySize(AudioObjectID device_id,
+ AudioObjectPropertySelector property_selector,
+ AudioObjectPropertyScope property_scope) {
+ AudioObjectPropertyAddress property_address = {
+ property_selector, property_scope, kAudioObjectPropertyElementMaster};
+ UInt32 size = 0;
+ OSStatus result = AudioObjectGetPropertyDataSize(
+ device_id, &property_address, 0 /* inQualifierDataSize */,
+ nullptr /* inQualifierData */, &size);
+ if (result != noErr) {
+ OSSTATUS_DLOG(WARNING, result)
+ << "Failed to read size of property " << property_selector
+ << " for device " << device_id;
+ return 0;
+ }
+ return size;
+}
+
+std::vector<AudioObjectID> GetAudioObjectIDs(
+ AudioObjectID audio_object_id,
+ AudioObjectPropertySelector property_selector) {
+ AudioObjectPropertyAddress property_address = {
+ property_selector, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+ UInt32 size = 0;
+ OSStatus result = AudioObjectGetPropertyDataSize(
+ audio_object_id, &property_address, 0 /* inQualifierDataSize */,
+ nullptr /* inQualifierData */, &size);
+ if (result != noErr) {
+ OSSTATUS_DLOG(WARNING, result)
+ << "Failed to read size of property " << property_selector
+ << " for device/object " << audio_object_id;
+ return {};
+ }
+
+ if (size == 0)
+ return {};
+
+ size_t device_count = size / sizeof(AudioObjectID);
+ // Get the array of device ids for all the devices, which includes both
+ // input devices and output devices.
+ std::vector<AudioObjectID> device_ids(device_count);
+ result = AudioObjectGetPropertyData(
+ audio_object_id, &property_address, 0 /* inQualifierDataSize */,
+ nullptr /* inQualifierData */, &size, device_ids.data());
+ if (result != noErr) {
+ OSSTATUS_DLOG(WARNING, result)
+ << "Failed to read object IDs from property " << property_selector
+ << " for device/object " << audio_object_id;
+ return {};
+ }
+
+ return device_ids;
+}
+
+absl::optional<std::string> GetDeviceName(AudioObjectID device_id) {
+ return GetDeviceStringProperty(device_id, kAudioObjectPropertyName);
+}
+
+absl::optional<std::string> GetDeviceModel(AudioObjectID device_id) {
+ return GetDeviceStringProperty(device_id, kAudioDevicePropertyModelUID);
+}
+
+bool ModelContainsVidPid(const std::string& model) {
+ return model.size() > 10 && model[model.size() - 5] == ':' &&
+ model[model.size() - 10] == ':';
+}
+
+std::string UsbVidPidFromModel(const std::string& model) {
+ return ModelContainsVidPid(model)
+ ? base::ToLowerASCII(model.substr(model.size() - 9))
+ : std::string();
+}
+
+std::string TransportTypeToString(uint32_t transport_type) {
+ switch (transport_type) {
+ case kAudioDeviceTransportTypeBuiltIn:
+ return "Built-in";
+ case kAudioDeviceTransportTypeAggregate:
+ return "Aggregate";
+ case kAudioDeviceTransportTypeAutoAggregate:
+ return "AutoAggregate";
+ case kAudioDeviceTransportTypeVirtual:
+ return "Virtual";
+ case kAudioDeviceTransportTypePCI:
+ return "PCI";
+ case kAudioDeviceTransportTypeUSB:
+ return "USB";
+ case kAudioDeviceTransportTypeFireWire:
+ return "FireWire";
+ case kAudioDeviceTransportTypeBluetooth:
+ return "Bluetooth";
+ case kAudioDeviceTransportTypeBluetoothLE:
+ return "Bluetooth LE";
+ case kAudioDeviceTransportTypeHDMI:
+ return "HDMI";
+ case kAudioDeviceTransportTypeDisplayPort:
+ return "DisplayPort";
+ case kAudioDeviceTransportTypeAirPlay:
+ return "AirPlay";
+ case kAudioDeviceTransportTypeAVB:
+ return "AVB";
+ case kAudioDeviceTransportTypeThunderbolt:
+ return "Thunderbolt";
+ case kAudioDeviceTransportTypeUnknown:
+ default:
+ return std::string();
+ }
+}
+
+absl::optional<std::string> TranslateDeviceSource(AudioObjectID device_id,
+ UInt32 source_id,
+ bool is_input) {
+ CFStringRef source_name = nullptr;
+ AudioValueTranslation translation;
+ translation.mInputData = &source_id;
+ translation.mInputDataSize = sizeof(source_id);
+ translation.mOutputData = &source_name;
+ translation.mOutputDataSize = sizeof(source_name);
+
+ UInt32 translation_size = sizeof(AudioValueTranslation);
+ AudioObjectPropertyAddress property_address = {
+ kAudioDevicePropertyDataSourceNameForIDCFString,
+ InputOutputScope(is_input), kAudioObjectPropertyElementMaster};
+
+ OSStatus result = AudioObjectGetPropertyData(
+ device_id, &property_address, 0 /* inQualifierDataSize */,
+ nullptr /* inQualifierData */, &translation_size, &translation);
+ if (result)
+ return absl::nullopt;
+
+ std::string ret = base::SysCFStringRefToUTF8(source_name);
+ CFRelease(source_name);
+
+ return ret;
+}
+
+} // namespace
+
+std::vector<AudioObjectID> GetAllAudioDeviceIDs() {
+ return GetAudioObjectIDs(kAudioObjectSystemObject,
+ kAudioHardwarePropertyDevices);
+}
+
+std::vector<AudioObjectID> GetRelatedDeviceIDs(AudioObjectID device_id) {
+ return GetAudioObjectIDs(device_id, kAudioDevicePropertyRelatedDevices);
+}
+
+absl::optional<std::string> GetDeviceUniqueID(AudioObjectID device_id) {
+ return GetDeviceStringProperty(device_id, kAudioDevicePropertyDeviceUID);
+}
+
+absl::optional<std::string> GetDeviceLabel(AudioObjectID device_id,
+ bool is_input) {
+ absl::optional<std::string> device_label;
+ absl::optional<uint32_t> source = GetDeviceSource(device_id, is_input);
+ if (source) {
+ device_label = TranslateDeviceSource(device_id, *source, is_input);
+ }
+
+ if (!device_label) {
+ device_label = GetDeviceName(device_id);
+ if (!device_label)
+ return absl::nullopt;
+ }
+
+ std::string suffix;
+ absl::optional<uint32_t> transport_type = GetDeviceTransportType(device_id);
+ if (transport_type) {
+ if (*transport_type == kAudioDeviceTransportTypeUSB) {
+ absl::optional<std::string> model = GetDeviceModel(device_id);
+ if (model) {
+ suffix = UsbVidPidFromModel(*model);
+ }
+ } else {
+ suffix = TransportTypeToString(*transport_type);
+ }
+ }
+
+ DCHECK(device_label);
+ if (!suffix.empty())
+ *device_label += " (" + suffix + ")";
+
+ return device_label;
+}
+
+uint32_t GetNumStreams(AudioObjectID device_id, bool is_input) {
+ return GetDevicePropertySize(device_id, kAudioDevicePropertyStreams,
+ InputOutputScope(is_input));
+}
+
+absl::optional<uint32_t> GetDeviceSource(AudioObjectID device_id,
+ bool is_input) {
+ return GetDeviceUint32Property(device_id, kAudioDevicePropertyDataSource,
+ InputOutputScope(is_input));
+}
+
+absl::optional<uint32_t> GetDeviceTransportType(AudioObjectID device_id) {
+ return GetDeviceUint32Property(device_id, kAudioDevicePropertyTransportType,
+ kAudioObjectPropertyScopeGlobal);
+}
+
+bool IsPrivateAggregateDevice(AudioObjectID device_id) {
+ // Don't try to access aggregate device properties unless |device_id| is
+ // really an aggregate device.
+ if (GetDeviceTransportType(device_id) != kAudioDeviceTransportTypeAggregate)
+ return false;
+
+ const AudioObjectPropertyAddress property_address = {
+ kAudioAggregateDevicePropertyComposition, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster};
+ CFDictionaryRef dictionary = nullptr;
+ UInt32 size = sizeof(dictionary);
+ OSStatus result = AudioObjectGetPropertyData(
+ device_id, &property_address, 0 /* inQualifierDataSize */,
+ nullptr /* inQualifierData */, &size, &dictionary);
+
+ if (result != noErr) {
+ OSSTATUS_LOG(WARNING, result) << "Failed to read property "
+ << kAudioAggregateDevicePropertyComposition
+ << " for device " << device_id;
+ return false;
+ }
+
+ DCHECK_EQ(CFGetTypeID(dictionary), CFDictionaryGetTypeID());
+ bool is_private = false;
+ CFTypeRef value = CFDictionaryGetValue(
+ dictionary, CFSTR(kAudioAggregateDeviceIsPrivateKey));
+
+ if (value && CFGetTypeID(value) == CFNumberGetTypeID()) {
+ int number = 0;
+ if (CFNumberGetValue(reinterpret_cast<CFNumberRef>(value), kCFNumberIntType,
+ &number)) {
+ is_private = number != 0;
+ }
+ }
+ CFRelease(dictionary);
+
+ return is_private;
+}
+
+bool IsInputDevice(AudioObjectID device_id) {
+ std::vector<AudioObjectID> streams =
+ GetAudioObjectIDs(device_id, kAudioDevicePropertyStreams);
+
+ int num_undefined_input_streams = 0;
+ int num_defined_input_streams = 0;
+ int num_output_streams = 0;
+
+ for (auto stream_id : streams) {
+ auto direction =
+ GetDeviceUint32Property(stream_id, kAudioStreamPropertyDirection,
+ kAudioObjectPropertyScopeGlobal);
+ DCHECK(direction.has_value());
+ const UInt32 kDirectionOutput = 0;
+ const UInt32 kDirectionInput = 1;
+ if (direction == kDirectionOutput) {
+ ++num_output_streams;
+ } else if (direction == kDirectionInput) {
+ // Filter input streams based on what terminal it claims to be attached
+ // to. Note that INPUT_UNDEFINED comes from a set of terminals declared
+ // in IOKit. CoreAudio defines a number of terminals in
+ // AudioHardwareBase.h but none of them match any of the values I've
+ // seen used in practice, though I've only tested a few devices.
+ auto terminal =
+ GetDeviceUint32Property(stream_id, kAudioStreamPropertyTerminalType,
+ kAudioObjectPropertyScopeGlobal);
+ if (terminal.has_value() && terminal == INPUT_UNDEFINED) {
+ ++num_undefined_input_streams;
+ } else {
+ ++num_defined_input_streams;
+ }
+ }
+ }
+
+ // I've only seen INPUT_UNDEFINED introduced by the VoiceProcessing AudioUnit,
+ // but to err on the side of caution, let's allow a device with only undefined
+ // input streams and no output streams as well.
+ return num_defined_input_streams > 0 ||
+ (num_undefined_input_streams > 0 && num_output_streams == 0);
+}
+
+bool IsOutputDevice(AudioObjectID device_id) {
+ return GetNumStreams(device_id, false) > 0;
+}
+
+} // namespace core_audio_mac
+} // namespace media
diff --git a/third_party/chromium/media/audio/mac/core_audio_util_mac.h b/third_party/chromium/media/audio/mac/core_audio_util_mac.h
new file mode 100644
index 0000000..b46243a
--- /dev/null
+++ b/third_party/chromium/media/audio/mac/core_audio_util_mac.h
@@ -0,0 +1,68 @@
+// Copyright 2018 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 MEDIA_AUDIO_MAC_CORE_AUDIO_UTIL_MAC_H_
+#define MEDIA_AUDIO_MAC_CORE_AUDIO_UTIL_MAC_H_
+
+#include <CoreAudio/AudioHardware.h>
+
+#include <string>
+#include <vector>
+
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace media {
+namespace core_audio_mac {
+
+// Returns a vector with the IDs of all audio devices in the system.
+// The vector is empty if there are no devices or if there is an error.
+std::vector<AudioObjectID> GetAllAudioDeviceIDs();
+
+// Returns a vector with the IDs of all devices related to the given
+// |device_id|. The vector is empty if there are no related devices or
+// if there is an error.
+std::vector<AudioObjectID> GetRelatedDeviceIDs(AudioObjectID device_id);
+
+// Returns a string with a unique device ID for the given |device_id|, or no
+// value if there is an error.
+absl::optional<std::string> GetDeviceUniqueID(AudioObjectID device_id);
+
+// Returns a string with a descriptive label for the given |device_id|, or no
+// value if there is an error. The returned label is based on several
+// characteristics of the device.
+absl::optional<std::string> GetDeviceLabel(AudioObjectID device_id,
+ bool is_input);
+
+// Returns the number of input or output streams associated with the given
+// |device_id|. Returns zero if there are no streams or if there is an error.
+uint32_t GetNumStreams(AudioObjectID device_id, bool is_input);
+
+// Returns the source associated with the given |device_id|, or no value if
+// |device_id| has no source or if there is an error.
+absl::optional<uint32_t> GetDeviceSource(AudioObjectID device_id,
+ bool is_input);
+
+// Returns the transport type of the given |device_id|, or no value if
+// |device_id| has no source or if there is an error.
+absl::optional<uint32_t> GetDeviceTransportType(AudioObjectID device_id);
+
+// Returns whether or not the |device_id| corresponds to a private, aggregate
+// device. Such a device gets created by instantiating a VoiceProcessingIO
+// AudioUnit.
+bool IsPrivateAggregateDevice(AudioObjectID device_id);
+
+// Returns whether or not the |device_id| corresponds to a device that has valid
+// input streams. When the VoiceProcessing AudioUnit is active, some output
+// devices get an input stream as well. This function tries to filter those out,
+// based on the value of the stream's kAudioStreamPropertyTerminalType value.
+bool IsInputDevice(AudioObjectID device_id);
+
+// Returns whether or not the |device_id| corresponds to a device with output
+// streams.
+bool IsOutputDevice(AudioObjectID device_id);
+
+} // namespace core_audio_mac
+} // namespace media
+
+#endif // MEDIA_AUDIO_MAC_CORE_AUDIO_UTIL_MAC_H_
diff --git a/third_party/chromium/media/audio/mac/coreaudio_dispatch_override.cc b/third_party/chromium/media/audio/mac/coreaudio_dispatch_override.cc
new file mode 100644
index 0000000..57a700e
--- /dev/null
+++ b/third_party/chromium/media/audio/mac/coreaudio_dispatch_override.cc
@@ -0,0 +1,207 @@
+// Copyright 2017 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 "media/audio/mac/coreaudio_dispatch_override.h"
+
+#include <dispatch/dispatch.h>
+#include <dlfcn.h>
+#include <mach-o/loader.h>
+
+#include "base/atomicops.h"
+#include "base/logging.h"
+#include "base/mac/mac_util.h"
+#include "base/metrics/histogram_macros.h"
+#include "build/build_config.h"
+
+namespace {
+struct dyld_interpose_tuple {
+ template <typename T>
+ dyld_interpose_tuple(T* replacement, T* replacee)
+ : replacement(reinterpret_cast<const void*>(replacement)),
+ replacee(reinterpret_cast<const void*>(replacee)) {}
+ const void* replacement;
+ const void* replacee;
+};
+
+using DispatchGetGlobalQueueFunc = dispatch_queue_t (*)(long id,
+ unsigned long flags);
+} // namespace
+
+// This method, and the tuple above, is defined in dyld_priv.h; see:
+// https://github.com/opensource-apple/dyld/blob/master/include/mach-o/dyld_priv.h
+extern "C" void dyld_dynamic_interpose(
+ const struct mach_header* mh,
+ const struct dyld_interpose_tuple array[],
+ size_t count) __attribute__((weak_import));
+
+namespace media {
+namespace {
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
+enum DispatchOverrideInitResult {
+ RESULT_NOT_SUPPORTED = 0,
+ RESULT_INITIALIZED = 1,
+ RESULT_DYNAMIC_INTERPOSE_NOT_FOUND = 2,
+ RESULT_COREAUDIO_DLOPEN_FAILED = 3,
+ RESULT_COREAUDIO_SYMBOL_NOT_FOUND = 4,
+ RESULT_COREAUDIO_MACH_HEADER_NOT_FOUND = 5,
+ RESULT_MAX = RESULT_COREAUDIO_MACH_HEADER_NOT_FOUND
+};
+
+void LogInitResult(DispatchOverrideInitResult result) {
+ UMA_HISTOGRAM_ENUMERATION("Media.Audio.CoreAudioDispatchOverrideInitResult",
+ result, RESULT_MAX + 1);
+}
+
+enum CallsiteLookupEvent {
+ LOOKUP_MISS = 0,
+ LOOKUP_RESUMEIO_CALLSITE_FOUND = 1,
+ LOOKUP_PAUSEIO_CALLSITE_FOUND = 2,
+ LOOKUP_MAX = LOOKUP_PAUSEIO_CALLSITE_FOUND
+};
+
+#if defined(ARCH_CPU_X86_64)
+void LogCallsiteLookupEvent(CallsiteLookupEvent event) {
+ UMA_HISTOGRAM_ENUMERATION("Media.Audio.CoreAudioDispatchOverrideLookupEvent",
+ event, LOOKUP_MAX + 1);
+}
+#endif
+
+const char kCoreAudioPath[] =
+ "/System/Library/Frameworks/CoreAudio.framework/Versions/A/CoreAudio";
+
+dispatch_queue_t g_pause_resume_queue = nullptr;
+bool g_dispatch_override_installed = false;
+
+#if defined(ARCH_CPU_X86_64)
+base::subtle::AtomicWord g_resumeio_callsite = 0;
+base::subtle::AtomicWord g_pauseio_callsite = 0;
+
+bool AddressIsPauseOrResume(intptr_t address) {
+ if (address == 0)
+ return false;
+
+ intptr_t resumeio_callsite =
+ base::subtle::NoBarrier_Load(&g_resumeio_callsite);
+
+ if (address == resumeio_callsite)
+ return true;
+
+ intptr_t pauseio_callsite = base::subtle::NoBarrier_Load(&g_pauseio_callsite);
+ if (address == pauseio_callsite)
+ return true;
+
+ if (resumeio_callsite && pauseio_callsite)
+ return false;
+
+ // We don't know both callsites yet, so try to look up the caller.
+ Dl_info info;
+ if (!dladdr(reinterpret_cast<const void*>(address), &info))
+ return false;
+
+ DCHECK_EQ(strcmp(info.dli_fname, kCoreAudioPath), 0);
+
+ // Before Mac OSX 10.10, this code is not applied because dyld is not
+ // available.
+ // From Mac OSX 10.10 to 10.15 (excluded) the target functions that trigger
+ // the interposition are HALC_IOContext_ResumeIO and HALC_IOContext_PauseIO
+ // for respectively resume and pause.
+ // With MacOSX 10.15 the target functions have changed to _XIOContext_ResumeIO
+ // and _XIOContext_PauseIO for respectively resume and pause.
+ if (!resumeio_callsite && info.dli_sname &&
+ (strcmp(info.dli_sname, "HALC_IOContext_ResumeIO") == 0 ||
+ strcmp(info.dli_sname, "_XIOContext_ResumeIO") == 0)) {
+ resumeio_callsite = address;
+ base::subtle::NoBarrier_CompareAndSwap(&g_resumeio_callsite, 0,
+ resumeio_callsite);
+ LogCallsiteLookupEvent(LOOKUP_RESUMEIO_CALLSITE_FOUND);
+ } else if (!pauseio_callsite && info.dli_sname &&
+ (strcmp(info.dli_sname, "HALC_IOContext_PauseIO") == 0 ||
+ strcmp(info.dli_sname, "_XIOContext_PauseIO") == 0)) {
+ pauseio_callsite = address;
+ base::subtle::NoBarrier_CompareAndSwap(&g_pauseio_callsite, 0,
+ pauseio_callsite);
+ LogCallsiteLookupEvent(LOOKUP_PAUSEIO_CALLSITE_FOUND);
+ } else {
+ LogCallsiteLookupEvent(LOOKUP_MISS);
+ }
+
+ return address == pauseio_callsite || address == resumeio_callsite;
+}
+
+dispatch_queue_t GetGlobalQueueOverride(long identifier, unsigned long flags) {
+ // Get the return address.
+ const intptr_t* rbp = 0;
+ asm("movq %%rbp, %0;" : "=r"(rbp));
+ const intptr_t caller = rbp[1];
+
+ // Check if it's one we should override.
+ if (identifier == DISPATCH_QUEUE_PRIORITY_HIGH &&
+ AddressIsPauseOrResume(caller)) {
+ return g_pause_resume_queue;
+ }
+
+ return dispatch_get_global_queue(identifier, flags);
+}
+#endif // defined(ARCH_CPU_X86_64)
+
+} // namespace
+
+bool InitializeCoreAudioDispatchOverride() {
+ if (g_dispatch_override_installed)
+ return true;
+
+ DCHECK_EQ(g_pause_resume_queue, nullptr);
+
+ if (dyld_dynamic_interpose == nullptr) {
+ LOG(ERROR) << "Unable to resolve dyld_dynamic_interpose()";
+ LogInitResult(RESULT_DYNAMIC_INTERPOSE_NOT_FOUND);
+ return false;
+ }
+ // Get CoreAudio handle
+ void* coreaudio = dlopen(kCoreAudioPath, RTLD_LAZY);
+ if (!coreaudio) {
+ LOG(ERROR) << "Could not load CoreAudio while trying to initialize "
+ "dispatch override";
+ LogInitResult(RESULT_COREAUDIO_DLOPEN_FAILED);
+ return false;
+ }
+ // Retrieve the base address (also address of Mach header). For this
+ // we need any external symbol to look up.
+ const void* symbol = dlsym(coreaudio, "AudioObjectGetPropertyData");
+ if (!symbol) {
+ LOG(ERROR) << "Unable to resolve AudioObjectGetPropertyData in "
+ "CoreAudio library";
+ LogInitResult(RESULT_COREAUDIO_SYMBOL_NOT_FOUND);
+ return false;
+ }
+ // From the address of that symbol, we can get the address of the library's
+ // header.
+ Dl_info info = {};
+ if (!dladdr(symbol, &info)) {
+ LOG(ERROR) << "Unable to find Mach header for CoreAudio library.";
+ LogInitResult(RESULT_COREAUDIO_MACH_HEADER_NOT_FOUND);
+ return false;
+ }
+
+#if defined(ARCH_CPU_X86_64)
+ const auto* header = reinterpret_cast<const mach_header*>(info.dli_fbase);
+ g_pause_resume_queue =
+ dispatch_queue_create("org.chromium.CoreAudioPauseResumeQueue", nullptr);
+ // The reinterpret_cast<> is needed because in the macOS 10.14 SDK, the return
+ // type of dispatch_get_global_queue changed to return a subtype of
+ // dispatch_queue_t* instead of dispatch_queue_t* itself, and T(*)(...) isn't
+ // automatically converted to U(*)(...) even if U is a superclass of T.
+ dyld_interpose_tuple interposition(
+ &GetGlobalQueueOverride,
+ reinterpret_cast<DispatchGetGlobalQueueFunc>(&dispatch_get_global_queue));
+ dyld_dynamic_interpose(header, &interposition, 1);
+#endif // defined(ARCH_CPU_X86_64)
+
+ g_dispatch_override_installed = true;
+ LogInitResult(RESULT_INITIALIZED);
+ return true;
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/mac/coreaudio_dispatch_override.h b/third_party/chromium/media/audio/mac/coreaudio_dispatch_override.h
new file mode 100644
index 0000000..efd9f8c
--- /dev/null
+++ b/third_party/chromium/media/audio/mac/coreaudio_dispatch_override.h
@@ -0,0 +1,29 @@
+// Copyright 2017 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 MEDIA_AUDIO_MAC_COREAUDIO_DISPATCH_OVERRIDE_H_
+#define MEDIA_AUDIO_MAC_COREAUDIO_DISPATCH_OVERRIDE_H_
+
+namespace media {
+// Initializes a CoreAudio hotfix, if supported (macOS >= 10.10).
+// See: http://crbug.com/772410
+// The hotfix overrides calls to dispatch_get_global_queue() from two CoreAudio
+// functions: HALC_IOContext_PauseIO and HALC_IOContext_ResumeIO. These dispatch
+// blocks that should execute in-order, but the global queue does not guarantee
+// this. When the calls execute out-of-order, we stop receiving callbacks for
+// audio streams on one or more devices.
+//
+// To circumvent this problem, these two functions get handed an internal serial
+// queue instead. For all other callers, the override will just defer to the
+// normal dispatch_get_global_queue() implementation.
+//
+// Calls to this function must be serialized. Will do nothing if called when
+// already initialized.
+//
+// Returns true if the hotfix is supported and initialization succeeded, or if
+// it was already initialized; false otherwise.
+bool InitializeCoreAudioDispatchOverride();
+} // namespace media
+
+#endif // MEDIA_AUDIO_MAC_COREAUDIO_DISPATCH_OVERRIDE_H_
diff --git a/third_party/chromium/media/audio/mac/scoped_audio_unit.cc b/third_party/chromium/media/audio/mac/scoped_audio_unit.cc
new file mode 100644
index 0000000..e063346
--- /dev/null
+++ b/third_party/chromium/media/audio/mac/scoped_audio_unit.cc
@@ -0,0 +1,76 @@
+// Copyright 2017 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 "media/audio/mac/scoped_audio_unit.h"
+
+#include "base/mac/mac_logging.h"
+
+namespace media {
+
+constexpr AudioComponentDescription desc = {kAudioUnitType_Output,
+ kAudioUnitSubType_HALOutput,
+ kAudioUnitManufacturer_Apple, 0, 0};
+
+static void DestroyAudioUnit(AudioUnit audio_unit) {
+ OSStatus result = AudioUnitUninitialize(audio_unit);
+ OSSTATUS_DLOG_IF(ERROR, result != noErr, result)
+ << "AudioUnitUninitialize() failed : " << audio_unit;
+ result = AudioComponentInstanceDispose(audio_unit);
+ OSSTATUS_DLOG_IF(ERROR, result != noErr, result)
+ << "AudioComponentInstanceDispose() failed : " << audio_unit;
+}
+
+ScopedAudioUnit::ScopedAudioUnit(AudioDeviceID device, AUElement element) {
+ AudioComponent comp = AudioComponentFindNext(0, &desc);
+ if (!comp)
+ return;
+
+ AudioUnit audio_unit;
+ OSStatus result = AudioComponentInstanceNew(comp, &audio_unit);
+ if (result != noErr) {
+ OSSTATUS_DLOG(ERROR, result) << "AudioComponentInstanceNew() failed.";
+ return;
+ }
+
+ const UInt32 enable_input_io = element == AUElement::INPUT ? 1 : 0;
+ result = AudioUnitSetProperty(audio_unit, kAudioOutputUnitProperty_EnableIO,
+ kAudioUnitScope_Input, AUElement::INPUT,
+ &enable_input_io, sizeof(enable_input_io));
+ if (result != noErr) {
+ OSSTATUS_DLOG(ERROR, result)
+ << "Failed to set input enable IO for audio unit.";
+ DestroyAudioUnit(audio_unit);
+ return;
+ }
+
+ const UInt32 enable_output_io = !enable_input_io;
+ result = AudioUnitSetProperty(audio_unit, kAudioOutputUnitProperty_EnableIO,
+ kAudioUnitScope_Output, AUElement::OUTPUT,
+ &enable_output_io, sizeof(enable_output_io));
+ if (result != noErr) {
+ OSSTATUS_DLOG(ERROR, result)
+ << "Failed to set output enable IO for audio unit.";
+ DestroyAudioUnit(audio_unit);
+ return;
+ }
+
+ result = AudioUnitSetProperty(
+ audio_unit, kAudioOutputUnitProperty_CurrentDevice,
+ kAudioUnitScope_Global, 0, &device, sizeof(AudioDeviceID));
+ if (result != noErr) {
+ OSSTATUS_DLOG(ERROR, result)
+ << "Failed to set current device for audio unit.";
+ DestroyAudioUnit(audio_unit);
+ return;
+ }
+
+ audio_unit_ = audio_unit;
+}
+
+ScopedAudioUnit::~ScopedAudioUnit() {
+ if (audio_unit_)
+ DestroyAudioUnit(audio_unit_);
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/mac/scoped_audio_unit.h b/third_party/chromium/media/audio/mac/scoped_audio_unit.h
new file mode 100644
index 0000000..d688399
--- /dev/null
+++ b/third_party/chromium/media/audio/mac/scoped_audio_unit.h
@@ -0,0 +1,42 @@
+// Copyright 2017 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 MEDIA_AUDIO_MAC_SCOPED_AUDIO_UNIT_H_
+#define MEDIA_AUDIO_MAC_SCOPED_AUDIO_UNIT_H_
+
+#include <AudioUnit/AudioUnit.h>
+#include <CoreAudio/CoreAudio.h>
+
+#include "base/macros.h"
+
+namespace media {
+
+// For whatever reason Apple doesn't have constants defined for these; per the
+// documentation, we use bus 0 for output and bus 1 for input:
+// http://developer.apple.com/library/mac/#technotes/tn2091/_index.html
+enum AUElement : AudioUnitElement { OUTPUT = 0, INPUT = 1 };
+
+// A helper class that ensures AudioUnits are properly disposed of.
+class ScopedAudioUnit {
+ public:
+ // Creates a new AudioUnit and sets its device for |element| to |device|. If
+ // the operation fails, is_valid() will return false and audio_unit() will
+ // return nullptr.
+ ScopedAudioUnit(AudioDeviceID device, AUElement element);
+
+ ScopedAudioUnit(const ScopedAudioUnit&) = delete;
+ ScopedAudioUnit& operator=(const ScopedAudioUnit&) = delete;
+
+ ~ScopedAudioUnit();
+
+ bool is_valid() const { return audio_unit_ != nullptr; }
+ AudioUnit audio_unit() const { return audio_unit_; }
+
+ private:
+ AudioUnit audio_unit_ = nullptr;
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_MAC_SCOPED_AUDIO_UNIT_H_
diff --git a/third_party/chromium/media/audio/mock_audio_debug_recording_manager.cc b/third_party/chromium/media/audio/mock_audio_debug_recording_manager.cc
new file mode 100644
index 0000000..1a38ba2
--- /dev/null
+++ b/third_party/chromium/media/audio/mock_audio_debug_recording_manager.cc
@@ -0,0 +1,17 @@
+// Copyright 2018 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 "media/audio/mock_audio_debug_recording_manager.h"
+
+#include <utility>
+
+namespace media {
+
+MockAudioDebugRecordingManager::MockAudioDebugRecordingManager(
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner)
+ : AudioDebugRecordingManager(std::move(task_runner)) {}
+
+MockAudioDebugRecordingManager::~MockAudioDebugRecordingManager() = default;
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/mock_audio_debug_recording_manager.h b/third_party/chromium/media/audio/mock_audio_debug_recording_manager.h
new file mode 100644
index 0000000..1ef5983
--- /dev/null
+++ b/third_party/chromium/media/audio/mock_audio_debug_recording_manager.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2018 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 MEDIA_AUDIO_MOCK_AUDIO_DEBUG_RECORDING_MANAGER_H_
+#define MEDIA_AUDIO_MOCK_AUDIO_DEBUG_RECORDING_MANAGER_H_
+
+#include "base/macros.h"
+#include "base/single_thread_task_runner.h"
+#include "media/audio/audio_debug_recording_manager.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace media {
+
+class MockAudioDebugRecordingManager : public AudioDebugRecordingManager {
+ public:
+ explicit MockAudioDebugRecordingManager(
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner);
+
+ MockAudioDebugRecordingManager(const MockAudioDebugRecordingManager&) =
+ delete;
+ MockAudioDebugRecordingManager& operator=(
+ const MockAudioDebugRecordingManager&) = delete;
+
+ ~MockAudioDebugRecordingManager() override;
+
+ MOCK_METHOD1(EnableDebugRecording,
+ void(AudioDebugRecordingManager::CreateWavFileCallback
+ create_file_callback));
+ MOCK_METHOD0(DisableDebugRecording, void());
+};
+
+} // namespace media.
+
+#endif // MEDIA_AUDIO_MOCK_AUDIO_DEBUG_RECORDING_MANAGER_H_
diff --git a/third_party/chromium/media/audio/mock_audio_manager.cc b/third_party/chromium/media/audio/mock_audio_manager.cc
new file mode 100644
index 0000000..a4056b2
--- /dev/null
+++ b/third_party/chromium/media/audio/mock_audio_manager.cc
@@ -0,0 +1,193 @@
+// Copyright (c) 2012 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 "media/audio/mock_audio_manager.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/check.h"
+#include "media/audio/mock_audio_debug_recording_manager.h"
+#include "media/base/audio_parameters.h"
+
+namespace media {
+
+MockAudioManager::MockAudioManager(std::unique_ptr<AudioThread> audio_thread)
+ : AudioManager(std::move(audio_thread)) {}
+
+MockAudioManager::~MockAudioManager() = default;
+
+void MockAudioManager::ShutdownOnAudioThread() {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+}
+
+bool MockAudioManager::HasAudioOutputDevices() {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ return has_output_devices_;
+}
+
+bool MockAudioManager::HasAudioInputDevices() {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ return has_input_devices_;
+}
+
+void MockAudioManager::GetAudioInputDeviceDescriptions(
+ AudioDeviceDescriptions* device_descriptions) {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ if (!get_input_device_descriptions_cb_)
+ return;
+ get_input_device_descriptions_cb_.Run(device_descriptions);
+}
+
+void MockAudioManager::GetAudioOutputDeviceDescriptions(
+ AudioDeviceDescriptions* device_descriptions) {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ if (!get_output_device_descriptions_cb_)
+ return;
+ get_output_device_descriptions_cb_.Run(device_descriptions);
+}
+
+media::AudioOutputStream* MockAudioManager::MakeAudioOutputStream(
+ const media::AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) {
+ return MakeAudioOutputStreamProxy(params, device_id);
+}
+
+media::AudioOutputStream* MockAudioManager::MakeAudioOutputStreamProxy(
+ const media::AudioParameters& params,
+ const std::string& device_id) {
+ return make_output_stream_cb_ ? make_output_stream_cb_.Run(params, device_id)
+ : nullptr;
+}
+
+media::AudioInputStream* MockAudioManager::MakeAudioInputStream(
+ const media::AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) {
+ return make_input_stream_cb_ ? make_input_stream_cb_.Run(params, device_id)
+ : nullptr;
+}
+
+void MockAudioManager::AddOutputDeviceChangeListener(
+ AudioDeviceListener* listener) {
+}
+
+void MockAudioManager::RemoveOutputDeviceChangeListener(
+ AudioDeviceListener* listener) {
+}
+
+AudioParameters MockAudioManager::GetDefaultOutputStreamParameters() {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ return default_output_params_;
+}
+
+AudioParameters MockAudioManager::GetOutputStreamParameters(
+ const std::string& device_id) {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ return output_params_;
+}
+
+AudioParameters MockAudioManager::GetInputStreamParameters(
+ const std::string& device_id) {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ return input_params_;
+}
+
+std::string MockAudioManager::GetAssociatedOutputDeviceID(
+ const std::string& input_device_id) {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ return get_associated_output_device_id_cb_
+ ? get_associated_output_device_id_cb_.Run(input_device_id)
+ : std::string();
+}
+
+std::string MockAudioManager::GetDefaultInputDeviceID() {
+ return std::string();
+}
+std::string MockAudioManager::GetDefaultOutputDeviceID() {
+ return std::string();
+}
+std::string MockAudioManager::GetCommunicationsInputDeviceID() {
+ return std::string();
+}
+std::string MockAudioManager::GetCommunicationsOutputDeviceID() {
+ return std::string();
+}
+
+std::unique_ptr<AudioLog> MockAudioManager::CreateAudioLog(
+ AudioLogFactory::AudioComponent component,
+ int component_id) {
+ return nullptr;
+}
+
+void MockAudioManager::InitializeDebugRecording() {
+ if (!GetTaskRunner()->BelongsToCurrentThread()) {
+ GetTaskRunner()->PostTask(
+ FROM_HERE, base::BindOnce(&MockAudioManager::InitializeDebugRecording,
+ base::Unretained(this)));
+ return;
+ }
+
+ DCHECK(!debug_recording_manager_);
+ debug_recording_manager_ =
+ std::make_unique<MockAudioDebugRecordingManager>(GetTaskRunner());
+}
+
+AudioDebugRecordingManager* MockAudioManager::GetAudioDebugRecordingManager() {
+ DCHECK(GetTaskRunner()->BelongsToCurrentThread());
+ return debug_recording_manager_.get();
+}
+
+const char* MockAudioManager::GetName() {
+ return nullptr;
+}
+
+void MockAudioManager::SetMakeOutputStreamCB(MakeOutputStreamCallback cb) {
+ make_output_stream_cb_ = std::move(cb);
+}
+
+void MockAudioManager::SetMakeInputStreamCB(MakeInputStreamCallback cb) {
+ make_input_stream_cb_ = std::move(cb);
+}
+
+void MockAudioManager::SetInputStreamParameters(const AudioParameters& params) {
+ input_params_ = params;
+}
+
+void MockAudioManager::SetOutputStreamParameters(
+ const AudioParameters& params) {
+ output_params_ = params;
+}
+
+void MockAudioManager::SetDefaultOutputStreamParameters(
+ const AudioParameters& params) {
+ default_output_params_ = params;
+}
+
+void MockAudioManager::SetHasInputDevices(bool has_input_devices) {
+ has_input_devices_ = has_input_devices;
+}
+
+void MockAudioManager::SetHasOutputDevices(bool has_output_devices) {
+ has_output_devices_ = has_output_devices;
+}
+
+void MockAudioManager::SetInputDeviceDescriptionsCallback(
+ GetDeviceDescriptionsCallback callback) {
+ get_input_device_descriptions_cb_ = std::move(callback);
+}
+
+void MockAudioManager::SetOutputDeviceDescriptionsCallback(
+ GetDeviceDescriptionsCallback callback) {
+ get_output_device_descriptions_cb_ = std::move(callback);
+}
+
+void MockAudioManager::SetAssociatedOutputDeviceIDCallback(
+ GetAssociatedOutputDeviceIDCallback callback) {
+ get_associated_output_device_id_cb_ = std::move(callback);
+}
+
+} // namespace media.
diff --git a/third_party/chromium/media/audio/mock_audio_manager.h b/third_party/chromium/media/audio/mock_audio_manager.h
new file mode 100644
index 0000000..b40bb43
--- /dev/null
+++ b/third_party/chromium/media/audio/mock_audio_manager.h
@@ -0,0 +1,127 @@
+// Copyright (c) 2012 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 MEDIA_AUDIO_MOCK_AUDIO_MANAGER_H_
+#define MEDIA_AUDIO_MOCK_AUDIO_MANAGER_H_
+
+#include <memory>
+#include <string>
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "media/audio/audio_debug_recording_manager.h"
+#include "media/audio/audio_manager.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace media {
+
+// This class is a simple mock around AudioManager, used exclusively for tests,
+// which avoids to use the actual (system and platform dependent) AudioManager.
+// Some bots do not have input devices, thus using the actual AudioManager
+// would causing failures on classes which expect that.
+class MockAudioManager : public AudioManager {
+ public:
+ using GetDeviceDescriptionsCallback =
+ base::RepeatingCallback<void(AudioDeviceDescriptions*)>;
+ using GetAssociatedOutputDeviceIDCallback =
+ base::RepeatingCallback<std::string(const std::string&)>;
+ using MakeOutputStreamCallback =
+ base::RepeatingCallback<media::AudioOutputStream*(
+ const media::AudioParameters& params,
+ const std::string& device_id)>;
+ using MakeInputStreamCallback =
+ base::RepeatingCallback<media::AudioInputStream*(
+ const media::AudioParameters& params,
+ const std::string& device_id)>;
+
+ explicit MockAudioManager(std::unique_ptr<AudioThread> audio_thread);
+
+ MockAudioManager(const MockAudioManager&) = delete;
+ MockAudioManager& operator=(const MockAudioManager&) = delete;
+
+ ~MockAudioManager() override;
+
+ AudioOutputStream* MakeAudioOutputStream(
+ const media::AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) override;
+
+ AudioOutputStream* MakeAudioOutputStreamProxy(
+ const media::AudioParameters& params,
+ const std::string& device_id) override;
+
+ AudioInputStream* MakeAudioInputStream(
+ const media::AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) override;
+
+ void AddOutputDeviceChangeListener(AudioDeviceListener* listener) override;
+ void RemoveOutputDeviceChangeListener(AudioDeviceListener* listener) override;
+
+ std::unique_ptr<AudioLog> CreateAudioLog(
+ AudioLogFactory::AudioComponent component,
+ int component_id) override;
+
+ void InitializeDebugRecording() override;
+ AudioDebugRecordingManager* GetAudioDebugRecordingManager() override;
+
+ const char* GetName() override;
+
+ // Setters to emulate desired in-test behavior.
+ void SetMakeOutputStreamCB(MakeOutputStreamCallback cb);
+ void SetMakeInputStreamCB(MakeInputStreamCallback cb);
+ void SetInputStreamParameters(const AudioParameters& params);
+ void SetOutputStreamParameters(const AudioParameters& params);
+ void SetDefaultOutputStreamParameters(const AudioParameters& params);
+ void SetHasInputDevices(bool has_input_devices);
+ void SetHasOutputDevices(bool has_output_devices);
+ void SetInputDeviceDescriptionsCallback(
+ GetDeviceDescriptionsCallback callback);
+ void SetOutputDeviceDescriptionsCallback(
+ GetDeviceDescriptionsCallback callback);
+ void SetAssociatedOutputDeviceIDCallback(
+ GetAssociatedOutputDeviceIDCallback callback);
+
+ protected:
+ void ShutdownOnAudioThread() override;
+
+ bool HasAudioOutputDevices() override;
+
+ bool HasAudioInputDevices() override;
+
+ void GetAudioInputDeviceDescriptions(
+ media::AudioDeviceDescriptions* device_descriptions) override;
+
+ void GetAudioOutputDeviceDescriptions(
+ media::AudioDeviceDescriptions* device_descriptions) override;
+
+ AudioParameters GetDefaultOutputStreamParameters() override;
+ AudioParameters GetOutputStreamParameters(
+ const std::string& device_id) override;
+ AudioParameters GetInputStreamParameters(
+ const std::string& device_id) override;
+ std::string GetAssociatedOutputDeviceID(
+ const std::string& input_device_id) override;
+ std::string GetDefaultInputDeviceID() override;
+ std::string GetDefaultOutputDeviceID() override;
+ std::string GetCommunicationsInputDeviceID() override;
+ std::string GetCommunicationsOutputDeviceID() override;
+
+ private:
+ AudioParameters input_params_;
+ AudioParameters output_params_;
+ AudioParameters default_output_params_;
+ bool has_input_devices_ = true;
+ bool has_output_devices_ = true;
+ MakeOutputStreamCallback make_output_stream_cb_;
+ MakeInputStreamCallback make_input_stream_cb_;
+ GetDeviceDescriptionsCallback get_input_device_descriptions_cb_;
+ GetDeviceDescriptionsCallback get_output_device_descriptions_cb_;
+ GetAssociatedOutputDeviceIDCallback get_associated_output_device_id_cb_;
+ std::unique_ptr<AudioDebugRecordingManager> debug_recording_manager_;
+};
+
+} // namespace media.
+
+#endif // MEDIA_AUDIO_MOCK_AUDIO_MANAGER_H_
diff --git a/third_party/chromium/media/audio/mock_audio_source_callback.cc b/third_party/chromium/media/audio/mock_audio_source_callback.cc
new file mode 100644
index 0000000..106b755
--- /dev/null
+++ b/third_party/chromium/media/audio/mock_audio_source_callback.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 "media/audio/mock_audio_source_callback.h"
+
+namespace media {
+
+MockAudioSourceCallback::MockAudioSourceCallback() = default;
+MockAudioSourceCallback::~MockAudioSourceCallback() = default;
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/mock_audio_source_callback.h b/third_party/chromium/media/audio/mock_audio_source_callback.h
new file mode 100644
index 0000000..a01e067
--- /dev/null
+++ b/third_party/chromium/media/audio/mock_audio_source_callback.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 MEDIA_AUDIO_MOCK_AUDIO_SOURCE_CALLBACK_H_
+#define MEDIA_AUDIO_MOCK_AUDIO_SOURCE_CALLBACK_H_
+
+#include <stdint.h>
+
+#include "base/macros.h"
+#include "base/time/time.h"
+#include "media/audio/audio_io.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace media {
+
+class MockAudioSourceCallback : public AudioOutputStream::AudioSourceCallback {
+ public:
+ MockAudioSourceCallback();
+
+ MockAudioSourceCallback(const MockAudioSourceCallback&) = delete;
+ MockAudioSourceCallback& operator=(const MockAudioSourceCallback&) = delete;
+
+ ~MockAudioSourceCallback() override;
+
+ MOCK_METHOD4(OnMoreData,
+ int(base::TimeDelta, base::TimeTicks, int, AudioBus*));
+ MOCK_METHOD1(OnError, void(ErrorType));
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_MOCK_AUDIO_SOURCE_CALLBACK_H_
diff --git a/third_party/chromium/media/audio/null_audio_sink.cc b/third_party/chromium/media/audio/null_audio_sink.cc
new file mode 100644
index 0000000..98c1acd
--- /dev/null
+++ b/third_party/chromium/media/audio/null_audio_sink.cc
@@ -0,0 +1,131 @@
+// Copyright (c) 2012 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 "media/audio/null_audio_sink.h"
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/single_thread_task_runner.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "media/base/audio_hash.h"
+#include "media/base/fake_audio_worker.h"
+
+namespace media {
+
+NullAudioSink::NullAudioSink(
+ const scoped_refptr<base::SingleThreadTaskRunner>& task_runner)
+ : initialized_(false),
+ started_(false),
+ playing_(false),
+ callback_(nullptr),
+ task_runner_(task_runner) {}
+
+NullAudioSink::~NullAudioSink() = default;
+
+void NullAudioSink::Initialize(const AudioParameters& params,
+ RenderCallback* callback) {
+ DCHECK(!started_);
+ fake_worker_ = std::make_unique<FakeAudioWorker>(task_runner_, params);
+ fixed_data_delay_ = FakeAudioWorker::ComputeFakeOutputDelay(params);
+ audio_bus_ = AudioBus::Create(params);
+ callback_ = callback;
+ initialized_ = true;
+}
+
+void NullAudioSink::Start() {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ DCHECK(initialized_);
+ DCHECK(!started_);
+ started_ = true;
+}
+
+void NullAudioSink::Stop() {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ started_ = false;
+ // Stop may be called at any time, so we have to check before stopping.
+ if (fake_worker_)
+ fake_worker_->Stop();
+}
+
+void NullAudioSink::Play() {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ DCHECK(started_);
+
+ if (playing_)
+ return;
+
+ fake_worker_->Start(
+ base::BindRepeating(&NullAudioSink::CallRender, base::Unretained(this)));
+
+ playing_ = true;
+}
+
+void NullAudioSink::Pause() {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ DCHECK(started_);
+
+ if (!playing_)
+ return;
+
+ fake_worker_->Stop();
+ playing_ = false;
+}
+
+void NullAudioSink::Flush() {}
+
+bool NullAudioSink::SetVolume(double volume) {
+ // Audio is always muted.
+ return volume == 0.0;
+}
+
+OutputDeviceInfo NullAudioSink::GetOutputDeviceInfo() {
+ return OutputDeviceInfo(OUTPUT_DEVICE_STATUS_OK);
+}
+
+void NullAudioSink::GetOutputDeviceInfoAsync(OutputDeviceInfoCB info_cb) {
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(info_cb), GetOutputDeviceInfo()));
+}
+
+bool NullAudioSink::IsOptimizedForHardwareParameters() {
+ return false;
+}
+
+bool NullAudioSink::CurrentThreadIsRenderingThread() {
+ return task_runner_->BelongsToCurrentThread();
+}
+
+void NullAudioSink::SwitchOutputDevice(const std::string& device_id,
+ OutputDeviceStatusCB callback) {
+ std::move(callback).Run(OUTPUT_DEVICE_STATUS_ERROR_INTERNAL);
+}
+
+void NullAudioSink::CallRender(base::TimeTicks ideal_time,
+ base::TimeTicks now) {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+
+ // Since NullAudioSink is only used for cases where a real audio sink was not
+ // available, provide "idealized" delay-timing arguments. This will drive the
+ // smoothest playback (since video is sync'ed to audio). See
+ // content::AudioRendererImpl and media::AudioClock for further details.
+ int frames_received =
+ callback_->Render(fixed_data_delay_, ideal_time, 0, audio_bus_.get());
+ if (!audio_hash_ || frames_received <= 0)
+ return;
+
+ audio_hash_->Update(audio_bus_.get(), frames_received);
+}
+
+void NullAudioSink::StartAudioHashForTesting() {
+ DCHECK(!initialized_);
+ audio_hash_ = std::make_unique<AudioHash>();
+}
+
+std::string NullAudioSink::GetAudioHashForTesting() {
+ return audio_hash_ ? audio_hash_->ToString() : std::string();
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/null_audio_sink.h b/third_party/chromium/media/audio/null_audio_sink.h
new file mode 100644
index 0000000..1db35b9
--- /dev/null
+++ b/third_party/chromium/media/audio/null_audio_sink.h
@@ -0,0 +1,75 @@
+// Copyright (c) 2012 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 MEDIA_AUDIO_NULL_AUDIO_SINK_H_
+#define MEDIA_AUDIO_NULL_AUDIO_SINK_H_
+
+#include <memory>
+#include <string>
+
+#include "base/macros.h"
+#include "media/base/audio_renderer_sink.h"
+
+namespace base {
+class SingleThreadTaskRunner;
+}
+
+namespace media {
+class AudioBus;
+class AudioHash;
+class FakeAudioWorker;
+
+class MEDIA_EXPORT NullAudioSink : public SwitchableAudioRendererSink {
+ public:
+ explicit NullAudioSink(
+ const scoped_refptr<base::SingleThreadTaskRunner>& task_runner);
+
+ // AudioRendererSink implementation.
+ void Initialize(const AudioParameters& params,
+ RenderCallback* callback) override;
+ void Start() override;
+ void Stop() override;
+ void Pause() override;
+ void Play() override;
+ void Flush() override;
+ bool SetVolume(double volume) override;
+ OutputDeviceInfo GetOutputDeviceInfo() override;
+ void GetOutputDeviceInfoAsync(OutputDeviceInfoCB info_cb) override;
+ bool IsOptimizedForHardwareParameters() override;
+ bool CurrentThreadIsRenderingThread() override;
+ void SwitchOutputDevice(const std::string& device_id,
+ OutputDeviceStatusCB callback) override;
+
+ // Enables audio frame hashing. Must be called prior to Initialize().
+ void StartAudioHashForTesting();
+
+ // Returns the hash of all audio frames seen since construction.
+ std::string GetAudioHashForTesting();
+
+ protected:
+ ~NullAudioSink() override;
+
+ private:
+ // Task that periodically calls Render() to consume audio data.
+ void CallRender(base::TimeTicks ideal_time, base::TimeTicks now);
+
+ bool initialized_;
+ bool started_;
+ bool playing_;
+ RenderCallback* callback_;
+
+ // Controls whether or not a running hash is computed for audio frames.
+ std::unique_ptr<AudioHash> audio_hash_;
+
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+ std::unique_ptr<FakeAudioWorker> fake_worker_;
+ base::TimeDelta fixed_data_delay_;
+ std::unique_ptr<AudioBus> audio_bus_;
+
+ DISALLOW_COPY_AND_ASSIGN(NullAudioSink);
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_NULL_AUDIO_SINK_H_
diff --git a/third_party/chromium/media/audio/power_observer_helper.cc b/third_party/chromium/media/audio/power_observer_helper.cc
new file mode 100644
index 0000000..187ca85
--- /dev/null
+++ b/third_party/chromium/media/audio/power_observer_helper.cc
@@ -0,0 +1,70 @@
+// Copyright 2017 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 "media/audio/power_observer_helper.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/power_monitor/power_monitor.h"
+
+namespace media {
+
+PowerObserverHelper::PowerObserverHelper(
+ scoped_refptr<base::SequencedTaskRunner> task_runner,
+ base::RepeatingClosure suspend_callback,
+ base::RepeatingClosure resume_callback)
+ : task_runner_(std::move(task_runner)),
+ suspend_callback_(std::move(suspend_callback)),
+ resume_callback_(std::move(resume_callback)) {
+ DCHECK(!suspend_callback_.is_null());
+ DCHECK(!resume_callback_.is_null());
+
+ // The PowerMonitor requires significant setup (a CFRunLoop and preallocated
+ // IO ports) so it's not available under unit tests. See the OSX impl of
+ // base::PowerMonitorDeviceSource for more details.
+ // TODO(grunell): We could be suspending when adding this as observer, and
+ // we won't be notified about that. See if we can add
+ // PowerMonitorSource::IsSuspending() so that this can be checked here.
+ base::PowerMonitor::AddPowerSuspendObserver(this);
+}
+
+PowerObserverHelper::~PowerObserverHelper() {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ base::PowerMonitor::RemovePowerSuspendObserver(this);
+}
+
+bool PowerObserverHelper::IsSuspending() const {
+ DCHECK(task_runner_->RunsTasksInCurrentSequence());
+ return is_suspending_;
+}
+
+void PowerObserverHelper::OnSuspend() {
+ DVLOG(1) << "OnSuspend";
+ if (!task_runner_->RunsTasksInCurrentSequence()) {
+ task_runner_->PostTask(FROM_HERE,
+ base::BindOnce(&PowerObserverHelper::OnSuspend,
+ weak_factory_.GetWeakPtr()));
+ return;
+ }
+
+ is_suspending_ = true;
+ suspend_callback_.Run();
+}
+
+void PowerObserverHelper::OnResume() {
+ DVLOG(1) << "OnResume";
+ if (!task_runner_->RunsTasksInCurrentSequence()) {
+ task_runner_->PostTask(FROM_HERE,
+ base::BindOnce(&PowerObserverHelper::OnResume,
+ weak_factory_.GetWeakPtr()));
+ return;
+ }
+
+ is_suspending_ = false;
+ resume_callback_.Run();
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/power_observer_helper.h b/third_party/chromium/media/audio/power_observer_helper.h
new file mode 100644
index 0000000..beb263c
--- /dev/null
+++ b/third_party/chromium/media/audio/power_observer_helper.h
@@ -0,0 +1,76 @@
+// Copyright 2017 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 MEDIA_AUDIO_POWER_OBSERVER_HELPER_H_
+#define MEDIA_AUDIO_POWER_OBSERVER_HELPER_H_
+
+#include "base/callback_forward.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/power_monitor/power_observer.h"
+#include "base/sequenced_task_runner.h"
+#include "media/base/media_export.h"
+
+namespace media {
+
+// Helper class that implements PowerSuspendObserver and handles threading. A
+// task runner is given, on which suspend and resume notification callbacks are
+// run. It also provides a function to check if we are suspending on the task
+// runner.
+// Note that on Linux suspend/resume information is not supported.
+class MEDIA_EXPORT PowerObserverHelper : public base::PowerSuspendObserver {
+ public:
+ PowerObserverHelper(scoped_refptr<base::SequencedTaskRunner> task_runner,
+ base::RepeatingClosure suspend_callback,
+ base::RepeatingClosure resume_callback);
+
+ PowerObserverHelper(const PowerObserverHelper&) = delete;
+ PowerObserverHelper& operator=(const PowerObserverHelper&) = delete;
+
+ ~PowerObserverHelper() override;
+
+ // Must be called on |task_runner|.
+ virtual bool IsSuspending() const;
+
+ protected:
+ base::SequencedTaskRunner* TaskRunnerForTesting() const {
+ return task_runner_.get();
+ }
+
+ base::RepeatingClosure* SuspendCallbackForTesting() {
+ return &suspend_callback_;
+ }
+
+ base::RepeatingClosure* ResumeCallbackForTesting() {
+ return &resume_callback_;
+ }
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(PowerObserverHelperTest,
+ SuspendAndResumeNotificationsTwice);
+ FRIEND_TEST_ALL_PREFIXES(PowerObserverHelperTest,
+ TwoSuspendAndTwoResumeNotifications);
+
+ // The task runner on which |is_suspending_| should live and the callbacks
+ // should be run on.
+ scoped_refptr<base::SequencedTaskRunner> task_runner_;
+
+ // Suspend and resume callbacks. Run on |task_runner_|.
+ base::RepeatingClosure suspend_callback_;
+ base::RepeatingClosure resume_callback_;
+
+ // base::PowerSuspendObserver implementation.
+ void OnSuspend() override;
+ void OnResume() override;
+
+ // Flag if we are suspending.
+ bool is_suspending_ = false;
+
+ base::WeakPtrFactory<PowerObserverHelper> weak_factory_{this};
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_POWER_OBSERVER_HELPER_H_
diff --git a/third_party/chromium/media/audio/power_observer_helper_unittest.cc b/third_party/chromium/media/audio/power_observer_helper_unittest.cc
new file mode 100644
index 0000000..0d68d4e
--- /dev/null
+++ b/third_party/chromium/media/audio/power_observer_helper_unittest.cc
@@ -0,0 +1,164 @@
+// Copyright 2017 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 <memory>
+
+#include "base/bind.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/test/task_environment.h"
+#include "base/threading/thread.h"
+#include "media/audio/power_observer_helper.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+
+class PowerObserverHelperTest : public testing::Test {
+ public:
+ PowerObserverHelperTest()
+ : power_observer_helper_thread_("AliveCheckerThread"),
+ suspend_event_(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED),
+ resume_event_(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED) {
+ power_observer_helper_thread_.StartAndWaitForTesting();
+ }
+
+ void OnSuspend() {
+ EXPECT_TRUE(
+ power_observer_helper_thread_.task_runner()->BelongsToCurrentThread());
+ suspend_event_.Signal();
+ }
+
+ void OnResume() {
+ EXPECT_TRUE(
+ power_observer_helper_thread_.task_runner()->BelongsToCurrentThread());
+ resume_event_.Signal();
+ }
+
+ protected:
+ ~PowerObserverHelperTest() override {
+ base::WaitableEvent done(base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ power_observer_helper_thread_.task_runner()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&PowerObserverHelperTest::
+ ResetPowerObserverHelperOnPowerObserverHelperThread,
+ base::Unretained(this), &done));
+ done.Wait();
+ }
+
+ void CreatePowerObserverHelper() {
+ DCHECK(!power_observer_helper_);
+ power_observer_helper_ = std::make_unique<PowerObserverHelper>(
+ power_observer_helper_thread_.task_runner(),
+ base::BindRepeating(&PowerObserverHelperTest::OnSuspend,
+ base::Unretained(this)),
+ base::BindRepeating(&PowerObserverHelperTest::OnResume,
+ base::Unretained(this)));
+ }
+
+ void WaitUntilSuspendNotification() {
+ suspend_event_.Wait();
+ suspend_event_.Reset();
+ }
+
+ void WaitUntilResumeNotification() {
+ resume_event_.Wait();
+ resume_event_.Reset();
+ }
+
+ bool IsSuspending() {
+ bool is_suspending = false;
+ base::WaitableEvent did_check(
+ base::WaitableEvent::ResetPolicy::MANUAL,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ power_observer_helper_thread_.task_runner()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&PowerObserverHelperTest::CheckIfSuspending,
+ base::Unretained(this), &is_suspending, &did_check));
+ did_check.Wait();
+ return is_suspending;
+ }
+
+ PowerObserverHelper* power_observer_helper() const {
+ return power_observer_helper_.get();
+ }
+
+ private:
+ void CheckIfSuspending(bool* is_suspending, base::WaitableEvent* done) {
+ EXPECT_TRUE(
+ power_observer_helper_thread_.task_runner()->BelongsToCurrentThread());
+ *is_suspending = power_observer_helper_->IsSuspending();
+ done->Signal();
+ }
+
+ void ResetPowerObserverHelperOnPowerObserverHelperThread(
+ base::WaitableEvent* done) {
+ EXPECT_TRUE(
+ power_observer_helper_thread_.task_runner()->BelongsToCurrentThread());
+ power_observer_helper_.reset();
+ done->Signal();
+ }
+
+ // The test task environment.
+ base::test::TaskEnvironment task_environment_;
+
+ // The thread the helper is run on.
+ base::Thread power_observer_helper_thread_;
+
+ // PowerObserverHelper under test.
+ std::unique_ptr<PowerObserverHelper> power_observer_helper_;
+
+ // Events to signal a notifications.
+ base::WaitableEvent suspend_event_;
+ base::WaitableEvent resume_event_;
+
+ DISALLOW_COPY_AND_ASSIGN(PowerObserverHelperTest);
+};
+
+// Suspend and resume notifications.
+TEST_F(PowerObserverHelperTest, SuspendAndResumeNotificationsTwice) {
+ CreatePowerObserverHelper();
+ EXPECT_FALSE(IsSuspending());
+
+ power_observer_helper()->OnSuspend();
+ WaitUntilSuspendNotification();
+ EXPECT_TRUE(IsSuspending());
+
+ power_observer_helper()->OnResume();
+ WaitUntilResumeNotification();
+ EXPECT_FALSE(IsSuspending());
+
+ power_observer_helper()->OnSuspend();
+ WaitUntilSuspendNotification();
+ EXPECT_TRUE(IsSuspending());
+
+ power_observer_helper()->OnResume();
+ WaitUntilResumeNotification();
+ EXPECT_FALSE(IsSuspending());
+}
+
+// Two suspend and two resume notifications.
+TEST_F(PowerObserverHelperTest, TwoSuspendAndTwoResumeNotifications) {
+ CreatePowerObserverHelper();
+ EXPECT_FALSE(IsSuspending());
+
+ power_observer_helper()->OnSuspend();
+ WaitUntilSuspendNotification();
+ EXPECT_TRUE(IsSuspending());
+
+ power_observer_helper()->OnSuspend();
+ WaitUntilSuspendNotification();
+ EXPECT_TRUE(IsSuspending());
+
+ power_observer_helper()->OnResume();
+ WaitUntilResumeNotification();
+ EXPECT_FALSE(IsSuspending());
+
+ power_observer_helper()->OnResume();
+ WaitUntilResumeNotification();
+ EXPECT_FALSE(IsSuspending());
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/pulse/audio_manager_pulse.cc b/third_party/chromium/media/audio/pulse/audio_manager_pulse.cc
new file mode 100644
index 0000000..147246b
--- /dev/null
+++ b/third_party/chromium/media/audio/pulse/audio_manager_pulse.cc
@@ -0,0 +1,349 @@
+// Copyright 2013 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 "media/audio/pulse/audio_manager_pulse.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "base/command_line.h"
+#include "base/environment.h"
+#include "base/logging.h"
+#include "base/nix/xdg_util.h"
+#include "build/chromeos_buildflags.h"
+#include "media/audio/audio_device_description.h"
+#include "media/audio/pulse/pulse_input.h"
+#include "media/audio/pulse/pulse_output.h"
+#include "media/audio/pulse/pulse_util.h"
+#include "media/base/audio_parameters.h"
+#include "media/base/channel_layout.h"
+
+namespace media {
+
+using pulse::AutoPulseLock;
+using pulse::WaitForOperationCompletion;
+
+// Maximum number of output streams that can be open simultaneously.
+constexpr int kMaxOutputStreams = 50;
+
+constexpr int kMinimumOutputBufferSize = 512;
+constexpr int kMaximumOutputBufferSize = 8192;
+constexpr int kDefaultInputBufferSize = 1024;
+constexpr int kDefaultSampleRate = 48000;
+constexpr int kDefaultChannelCount = 2;
+
+AudioManagerPulse::AudioManagerPulse(std::unique_ptr<AudioThread> audio_thread,
+ AudioLogFactory* audio_log_factory,
+ pa_threaded_mainloop* pa_mainloop,
+ pa_context* pa_context)
+ : AudioManagerBase(std::move(audio_thread), audio_log_factory),
+ input_mainloop_(pa_mainloop),
+ input_context_(pa_context),
+ devices_(nullptr),
+ native_input_sample_rate_(kDefaultSampleRate),
+ native_channel_count_(kDefaultChannelCount),
+ default_source_is_monitor_(false) {
+ DCHECK(input_mainloop_);
+ DCHECK(input_context_);
+ SetMaxOutputStreamsAllowed(kMaxOutputStreams);
+}
+
+AudioManagerPulse::~AudioManagerPulse() = default;
+
+void AudioManagerPulse::ShutdownOnAudioThread() {
+ AudioManagerBase::ShutdownOnAudioThread();
+ // The Pulse objects are the last things to be destroyed since
+ // AudioManagerBase::ShutdownOnAudioThread() needs them.
+ pulse::DestroyPulse(input_mainloop_, input_context_);
+}
+
+bool AudioManagerPulse::HasAudioOutputDevices() {
+ AudioDeviceNames devices;
+ GetAudioOutputDeviceNames(&devices);
+ return !devices.empty();
+}
+
+bool AudioManagerPulse::HasAudioInputDevices() {
+ AudioDeviceNames devices;
+ GetAudioInputDeviceNames(&devices);
+ return !devices.empty();
+}
+
+void AudioManagerPulse::GetAudioDeviceNames(
+ bool input, media::AudioDeviceNames* device_names) {
+ DCHECK(device_names->empty());
+ DCHECK(input_mainloop_);
+ DCHECK(input_context_);
+ AutoPulseLock auto_lock(input_mainloop_);
+ devices_ = device_names;
+ pa_operation* operation = NULL;
+ if (input) {
+ operation = pa_context_get_source_info_list(
+ input_context_, InputDevicesInfoCallback, this);
+ } else {
+ operation = pa_context_get_sink_info_list(
+ input_context_, OutputDevicesInfoCallback, this);
+ }
+ WaitForOperationCompletion(input_mainloop_, operation, input_context_);
+
+ // Prepend the default device if the list is not empty.
+ if (!device_names->empty())
+ device_names->push_front(AudioDeviceName::CreateDefault());
+}
+
+void AudioManagerPulse::GetAudioInputDeviceNames(
+ AudioDeviceNames* device_names) {
+ GetAudioDeviceNames(true, device_names);
+}
+
+void AudioManagerPulse::GetAudioOutputDeviceNames(
+ AudioDeviceNames* device_names) {
+ GetAudioDeviceNames(false, device_names);
+}
+
+AudioParameters AudioManagerPulse::GetInputStreamParameters(
+ const std::string& device_id) {
+ UpdateNativeAudioHardwareInfo();
+
+ {
+ AutoPulseLock auto_lock(input_mainloop_);
+ auto* operation = pa_context_get_source_info_by_name(
+ input_context_, default_source_name_.c_str(), DefaultSourceInfoCallback,
+ this);
+ WaitForOperationCompletion(input_mainloop_, operation, input_context_);
+ }
+
+ // We don't want to accidentally open a monitor device, so return invalid
+ // parameters for those. Note: The value of |default_source_is_monitor_|
+ // depends on the the call to pa_context_get_source_info_by_name() above.
+ if (device_id == AudioDeviceDescription::kDefaultDeviceId &&
+ default_source_is_monitor_) {
+ return AudioParameters();
+ }
+
+ const int user_buffer_size = GetUserBufferSize();
+ const int buffer_size =
+ user_buffer_size ? user_buffer_size : kDefaultInputBufferSize;
+ return AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
+ CHANNEL_LAYOUT_STEREO,
+ native_input_sample_rate_ ? native_input_sample_rate_
+ : kDefaultSampleRate,
+ buffer_size);
+}
+
+const char* AudioManagerPulse::GetName() {
+ return "PulseAudio";
+}
+
+AudioOutputStream* AudioManagerPulse::MakeLinearOutputStream(
+ const AudioParameters& params,
+ const LogCallback& log_callback) {
+ DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format());
+ return MakeOutputStream(params, AudioDeviceDescription::kDefaultDeviceId,
+ log_callback);
+}
+
+AudioOutputStream* AudioManagerPulse::MakeLowLatencyOutputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) {
+ DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format());
+ return MakeOutputStream(
+ params,
+ device_id.empty() ? AudioDeviceDescription::kDefaultDeviceId : device_id,
+ log_callback);
+}
+
+AudioInputStream* AudioManagerPulse::MakeLinearInputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) {
+ DCHECK_EQ(AudioParameters::AUDIO_PCM_LINEAR, params.format());
+ return MakeInputStream(params, device_id, log_callback);
+}
+
+AudioInputStream* AudioManagerPulse::MakeLowLatencyInputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) {
+ DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format());
+ return MakeInputStream(params, device_id, log_callback);
+}
+
+std::string AudioManagerPulse::GetDefaultInputDeviceID() {
+ // Do not use the real default input device since it is a fallback
+ // device rather than a default device. Using the default input device
+ // reported by Pulse Audio prevents, for example, input redirection
+ // using the PULSE_SOURCE environment variable.
+ return AudioManagerBase::GetDefaultInputDeviceID();
+}
+
+std::string AudioManagerPulse::GetDefaultOutputDeviceID() {
+ // Do not use the real default output device since it is a fallback
+ // device rather than a default device. Using the default output device
+ // reported by Pulse Audio prevents, for example, output redirection
+ // using the PULSE_SINK environment variable.
+ return AudioManagerBase::GetDefaultOutputDeviceID();
+}
+
+std::string AudioManagerPulse::GetAssociatedOutputDeviceID(
+ const std::string& input_device_id) {
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+ return AudioManagerBase::GetAssociatedOutputDeviceID(input_device_id);
+#else
+ DCHECK(AudioManager::Get()->GetTaskRunner()->BelongsToCurrentThread());
+ DCHECK(input_mainloop_);
+ DCHECK(input_context_);
+
+ if (input_device_id == AudioDeviceDescription::kDefaultDeviceId)
+ return std::string();
+
+ std::string input_bus =
+ pulse::GetBusOfInput(input_mainloop_, input_context_, input_device_id);
+ return input_bus.empty() ? std::string()
+ : pulse::GetOutputCorrespondingTo(
+ input_mainloop_, input_context_, input_bus);
+#endif
+}
+
+AudioParameters AudioManagerPulse::GetPreferredOutputStreamParameters(
+ const std::string& output_device_id,
+ const AudioParameters& input_params) {
+ // TODO(tommi): Support |output_device_id|.
+ VLOG_IF(0, !output_device_id.empty()) << "Not implemented!";
+
+ int buffer_size = kMinimumOutputBufferSize;
+
+ // Query native parameters where applicable; Pulse does not require these to
+ // be respected though, so prefer the input parameters for channel count.
+ UpdateNativeAudioHardwareInfo();
+ int sample_rate = native_input_sample_rate_ ? native_input_sample_rate_
+ : kDefaultSampleRate;
+ ChannelLayout channel_layout =
+ GuessChannelLayout(native_channel_count_ ? native_channel_count_ : 2);
+
+ if (input_params.IsValid()) {
+ // Use the system's output channel count for the DISCRETE layout. This is to
+ // avoid a crash due to the lack of support on the multi-channel beyond 8 in
+ // the PulseAudio layer.
+ if (input_params.channel_layout() != CHANNEL_LAYOUT_DISCRETE)
+ channel_layout = input_params.channel_layout();
+
+ buffer_size =
+ std::min(kMaximumOutputBufferSize,
+ std::max(buffer_size, input_params.frames_per_buffer()));
+ }
+
+ int user_buffer_size = GetUserBufferSize();
+ if (user_buffer_size)
+ buffer_size = user_buffer_size;
+
+ return AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, channel_layout,
+ sample_rate, buffer_size);
+}
+
+AudioOutputStream* AudioManagerPulse::MakeOutputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ LogCallback log_callback) {
+ DCHECK(!device_id.empty());
+ return new PulseAudioOutputStream(params, device_id, this,
+ std::move(log_callback));
+}
+
+AudioInputStream* AudioManagerPulse::MakeInputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ LogCallback log_callback) {
+ return new PulseAudioInputStream(this, device_id, params, input_mainloop_,
+ input_context_, std::move(log_callback));
+}
+
+void AudioManagerPulse::UpdateNativeAudioHardwareInfo() {
+ DCHECK(input_mainloop_);
+ DCHECK(input_context_);
+ AutoPulseLock auto_lock(input_mainloop_);
+ pa_operation* operation = pa_context_get_server_info(
+ input_context_, AudioHardwareInfoCallback, this);
+ WaitForOperationCompletion(input_mainloop_, operation, input_context_);
+
+ // Be careful about adding OS calls to this method.
+ // GetPreferredOutputStreamParameters() calls this method on a critical path.
+ // If the OS calls hang they will hang all device authorizations.
+}
+
+void AudioManagerPulse::InputDevicesInfoCallback(pa_context* context,
+ const pa_source_info* info,
+ int eol,
+ void* user_data) {
+ AudioManagerPulse* manager = reinterpret_cast<AudioManagerPulse*>(user_data);
+
+ if (eol) {
+ // Signal the pulse object that it is done.
+ pa_threaded_mainloop_signal(manager->input_mainloop_, 0);
+ return;
+ }
+
+ // Exclude output monitor (i.e. loopback) devices.
+ if (info->monitor_of_sink != PA_INVALID_INDEX)
+ return;
+
+ // If the device has ports, but none of them are available, skip it.
+ if (info->n_ports > 0) {
+ uint32_t port = 0;
+ for (; port != info->n_ports; ++port) {
+ if (info->ports[port]->available != PA_PORT_AVAILABLE_NO)
+ break;
+ }
+ if (port == info->n_ports)
+ return;
+ }
+
+ manager->devices_->push_back(AudioDeviceName(info->description, info->name));
+}
+
+void AudioManagerPulse::OutputDevicesInfoCallback(pa_context* context,
+ const pa_sink_info* info,
+ int eol,
+ void* user_data) {
+ AudioManagerPulse* manager = reinterpret_cast<AudioManagerPulse*>(user_data);
+
+ if (eol) {
+ // Signal the pulse object that it is done.
+ pa_threaded_mainloop_signal(manager->input_mainloop_, 0);
+ return;
+ }
+
+ manager->devices_->push_back(AudioDeviceName(info->description, info->name));
+}
+
+void AudioManagerPulse::AudioHardwareInfoCallback(pa_context* context,
+ const pa_server_info* info,
+ void* user_data) {
+ AudioManagerPulse* manager = reinterpret_cast<AudioManagerPulse*>(user_data);
+
+ manager->native_input_sample_rate_ = info->sample_spec.rate;
+ manager->native_channel_count_ = info->sample_spec.channels;
+ if (info->default_source_name)
+ manager->default_source_name_ = info->default_source_name;
+ pa_threaded_mainloop_signal(manager->input_mainloop_, 0);
+}
+
+void AudioManagerPulse::DefaultSourceInfoCallback(pa_context* context,
+ const pa_source_info* info,
+ int eol,
+ void* user_data) {
+ AudioManagerPulse* manager = reinterpret_cast<AudioManagerPulse*>(user_data);
+ if (eol) {
+ // Signal the pulse object that it is done.
+ pa_threaded_mainloop_signal(manager->input_mainloop_, 0);
+ return;
+ }
+
+ DCHECK(info);
+ manager->default_source_is_monitor_ =
+ info->monitor_of_sink != PA_INVALID_INDEX;
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/pulse/audio_manager_pulse.h b/third_party/chromium/media/audio/pulse/audio_manager_pulse.h
new file mode 100644
index 0000000..856f2ea
--- /dev/null
+++ b/third_party/chromium/media/audio/pulse/audio_manager_pulse.h
@@ -0,0 +1,118 @@
+// Copyright 2013 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 MEDIA_AUDIO_PULSE_AUDIO_MANAGER_PULSE_H_
+#define MEDIA_AUDIO_PULSE_AUDIO_MANAGER_PULSE_H_
+
+#include <pulse/pulseaudio.h>
+
+#include <memory>
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "media/audio/audio_manager_base.h"
+
+namespace media {
+
+class MEDIA_EXPORT AudioManagerPulse : public AudioManagerBase {
+ public:
+ AudioManagerPulse(std::unique_ptr<AudioThread> audio_thread,
+ AudioLogFactory* audio_log_factory,
+ pa_threaded_mainloop* pa_mainloop,
+ pa_context* pa_context);
+
+ AudioManagerPulse(const AudioManagerPulse&) = delete;
+ AudioManagerPulse& operator=(const AudioManagerPulse&) = delete;
+
+ ~AudioManagerPulse() override;
+
+ // Implementation of AudioManager.
+ bool HasAudioOutputDevices() override;
+ bool HasAudioInputDevices() override;
+ void GetAudioInputDeviceNames(AudioDeviceNames* device_names) override;
+ void GetAudioOutputDeviceNames(AudioDeviceNames* device_names) override;
+ AudioParameters GetInputStreamParameters(
+ const std::string& device_id) override;
+ const char* GetName() override;
+
+ // Implementation of AudioManagerBase.
+ AudioOutputStream* MakeLinearOutputStream(
+ const AudioParameters& params,
+ const LogCallback& log_callback) override;
+ AudioOutputStream* MakeLowLatencyOutputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) override;
+ AudioInputStream* MakeLinearInputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) override;
+ AudioInputStream* MakeLowLatencyInputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ const LogCallback& log_callback) override;
+ std::string GetDefaultInputDeviceID() override;
+ std::string GetDefaultOutputDeviceID() override;
+ std::string GetAssociatedOutputDeviceID(
+ const std::string& input_device_id) override;
+
+ bool DefaultSourceIsMonitor() const { return default_source_is_monitor_; }
+
+ protected:
+ void ShutdownOnAudioThread() override;
+ AudioParameters GetPreferredOutputStreamParameters(
+ const std::string& output_device_id,
+ const AudioParameters& input_params) override;
+
+ private:
+ void GetAudioDeviceNames(bool input, media::AudioDeviceNames* device_names);
+
+ // Callback to get the devices' info like names, used by GetInputDevices().
+ static void InputDevicesInfoCallback(pa_context* context,
+ const pa_source_info* info,
+ int eol,
+ void* user_data);
+ static void OutputDevicesInfoCallback(pa_context* context,
+ const pa_sink_info* info,
+ int eol,
+ void* user_data);
+
+ // Callback to get the native sample rate of PulseAudio, used by
+ // UpdateNativeAudioHardwareInfo().
+ static void AudioHardwareInfoCallback(pa_context* context,
+ const pa_server_info* info,
+ void* user_data);
+
+ static void DefaultSourceInfoCallback(pa_context* context,
+ const pa_source_info* info,
+ int eol,
+ void* user_data);
+
+ // Called by MakeLinearOutputStream and MakeLowLatencyOutputStream.
+ AudioOutputStream* MakeOutputStream(const AudioParameters& params,
+ const std::string& device_id,
+ LogCallback log_callback);
+
+ // Called by MakeLinearInputStream and MakeLowLatencyInputStream.
+ AudioInputStream* MakeInputStream(const AudioParameters& params,
+ const std::string& device_id,
+ LogCallback log_callback);
+
+ // Updates |native_input_sample_rate_| and |native_channel_count_|.
+ void UpdateNativeAudioHardwareInfo();
+
+ pa_threaded_mainloop* input_mainloop_;
+ pa_context* input_context_;
+ AudioDeviceNames* devices_;
+ int native_input_sample_rate_;
+ int native_channel_count_;
+ std::string default_source_name_;
+ bool default_source_is_monitor_;
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_PULSE_AUDIO_MANAGER_PULSE_H_
diff --git a/third_party/chromium/media/audio/pulse/pulse.sigs b/third_party/chromium/media/audio/pulse/pulse.sigs
new file mode 100644
index 0000000..85ff08a
--- /dev/null
+++ b/third_party/chromium/media/audio/pulse/pulse.sigs
@@ -0,0 +1,61 @@
+// Copyright 2013 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.
+
+//------------------------------------------------
+// Functions from pulse used in media code.
+//------------------------------------------------
+pa_mainloop_api* pa_threaded_mainloop_get_api(pa_threaded_mainloop* m);
+void pa_threaded_mainloop_free(pa_threaded_mainloop* m);
+pa_threaded_mainloop* pa_threaded_mainloop_new();
+void pa_threaded_mainloop_lock(pa_threaded_mainloop* m);
+int pa_threaded_mainloop_in_thread(pa_threaded_mainloop* m);
+void pa_threaded_mainloop_signal(pa_threaded_mainloop* m, int wait_for_accept);
+int pa_threaded_mainloop_start(pa_threaded_mainloop* m);
+void pa_threaded_mainloop_stop(pa_threaded_mainloop* m);
+void pa_threaded_mainloop_unlock(pa_threaded_mainloop* m);
+void pa_threaded_mainloop_wait(pa_threaded_mainloop* m);
+pa_channel_map* pa_channel_map_init(pa_channel_map* m);
+pa_channel_map* pa_channel_map_init_mono(pa_channel_map* m);
+int pa_context_connect(pa_context* c, const char* server, pa_context_flags_t flags, const pa_spawn_api* api);
+void pa_context_disconnect(pa_context* c);
+pa_operation* pa_context_get_server_info(pa_context* c, pa_server_info_cb_t cb, void* userdata);
+pa_operation* pa_context_get_source_info_by_index(pa_context* c, uint32_t idx, pa_source_info_cb_t cb, void* userdata);
+pa_operation* pa_context_get_source_info_by_name(pa_context* c, const char* name, pa_source_info_cb_t cb, void *userdata);
+pa_operation* pa_context_get_source_info_list(pa_context* c, pa_source_info_cb_t cb, void* userdata);
+pa_operation* pa_context_get_sink_info_list(pa_context* c, pa_sink_info_cb_t cb, void* userdata);
+pa_context_state_t pa_context_get_state(const_pa_context_ptr c);
+pa_context* pa_context_new(pa_mainloop_api* mainloop, const char* name);
+pa_operation* pa_context_set_source_volume_by_index(pa_context* c, uint32_t idx, const pa_cvolume* volume, pa_context_success_cb_t cb, void* userdata);
+void pa_context_set_state_callback(pa_context* c, pa_context_notify_cb_t cb, void* userdata);
+pa_operation_state_t pa_operation_get_state(const_pa_operation_ptr o);
+void pa_context_unref(pa_context* c);
+void pa_operation_cancel(pa_operation* o)
+void pa_operation_unref(pa_operation* o);
+int pa_stream_begin_write(pa_stream* p, void** data, size_t* nbytes);
+int pa_stream_connect_playback(pa_stream* s, const char* dev, const pa_buffer_attr* attr, pa_stream_flags_t flags, const pa_cvolume* volume,pa_stream* sync_stream);
+int pa_stream_connect_record(pa_stream* s, const char* dev, const pa_buffer_attr* attr, pa_stream_flags_t flags);
+pa_operation* pa_stream_cork(pa_stream* s, int b, pa_stream_success_cb_t cb, void* userdata);
+int pa_stream_disconnect(pa_stream* s);
+int pa_stream_drop(pa_stream *p);
+pa_operation* pa_stream_flush(pa_stream* s, pa_stream_success_cb_t cb, void* userdata);
+uint32_t pa_stream_get_device_index(const_pa_stream_ptr s);
+int pa_stream_get_latency(pa_stream* s, pa_usec_t* r_usec, int* negative);
+pa_stream_state_t pa_stream_get_state(const_pa_stream_ptr p);
+pa_stream* pa_stream_new(pa_context* c, const char* name, const pa_sample_spec* ss, const pa_channel_map * map);
+pa_stream* pa_stream_new_with_proplist(pa_context* c, const char* name, const pa_sample_spec* ss, const pa_channel_map* map, pa_proplist* p);
+pa_proplist* pa_proplist_new(void);
+int pa_proplist_contains(const_pa_proplist_ptr p, const char* key);
+void pa_proplist_free(pa_proplist* p);
+const char* pa_proplist_gets(const_pa_proplist_ptr p, const char* key);
+int pa_proplist_sets(pa_proplist* p, const char* key, const char* value);
+size_t pa_stream_readable_size(const_pa_stream_ptr p);
+int pa_stream_peek(pa_stream* p, const void** data, size_t* nbytes);
+void pa_stream_set_read_callback(pa_stream* p, pa_stream_request_cb_t cb, void* userdata);
+void pa_stream_set_state_callback(pa_stream* s, pa_stream_notify_cb_t cb, void* userdata);
+int pa_stream_write(pa_stream* p, const void* data, size_t nbytes, pa_free_cb_t free_cb, int64_t offset, pa_seek_mode_t seek);
+void pa_stream_set_write_callback(pa_stream *p, pa_stream_request_cb_t cb, void *userdata);
+void pa_stream_unref(pa_stream* s);
+int pa_context_errno(const_pa_context_ptr c);
+const char* pa_strerror(int error);
+pa_cvolume* pa_cvolume_set(pa_cvolume* a, unsigned channels, pa_volume_t v);
diff --git a/third_party/chromium/media/audio/pulse/pulse_input.cc b/third_party/chromium/media/audio/pulse/pulse_input.cc
new file mode 100644
index 0000000..1f7cca7
--- /dev/null
+++ b/third_party/chromium/media/audio/pulse/pulse_input.cc
@@ -0,0 +1,392 @@
+// Copyright (c) 2012 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 "media/audio/pulse/pulse_input.h"
+
+#include <stdint.h>
+
+#include "base/check.h"
+#include "base/strings/stringprintf.h"
+#include "media/audio/audio_device_description.h"
+#include "media/audio/pulse/audio_manager_pulse.h"
+#include "media/audio/pulse/pulse_util.h"
+#include "media/base/audio_timestamp_helper.h"
+
+namespace media {
+
+using pulse::AutoPulseLock;
+using pulse::WaitForOperationCompletion;
+
+// Number of blocks of buffers used in the |fifo_|.
+const int kNumberOfBlocksBufferInFifo = 2;
+
+PulseAudioInputStream::PulseAudioInputStream(
+ AudioManagerPulse* audio_manager,
+ const std::string& device_name,
+ const AudioParameters& params,
+ pa_threaded_mainloop* mainloop,
+ pa_context* context,
+ AudioManager::LogCallback log_callback)
+ : audio_manager_(audio_manager),
+ callback_(nullptr),
+ device_name_(device_name),
+ params_(params),
+ channels_(0),
+ volume_(0.0),
+ stream_started_(false),
+ muted_(false),
+ fifo_(params.channels(),
+ params.frames_per_buffer(),
+ kNumberOfBlocksBufferInFifo),
+ pa_mainloop_(mainloop),
+ pa_context_(context),
+ log_callback_(std::move(log_callback)),
+ handle_(nullptr) {
+ DCHECK(mainloop);
+ DCHECK(context);
+ CHECK(params_.IsValid());
+ SendLogMessage("%s({device_id=%s}, {params=[%s]})", __func__,
+ device_name.c_str(), params.AsHumanReadableString().c_str());
+}
+
+PulseAudioInputStream::~PulseAudioInputStream() {
+ // All internal structures should already have been freed in Close(),
+ // which calls AudioManagerPulse::Release which deletes this object.
+ DCHECK(!handle_);
+}
+
+AudioInputStream::OpenOutcome PulseAudioInputStream::Open() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ SendLogMessage("%s()", __func__);
+ if (device_name_ == AudioDeviceDescription::kDefaultDeviceId &&
+ audio_manager_->DefaultSourceIsMonitor()) {
+ SendLogMessage("%s => (ERROR: can't open monitor device)", __func__);
+ return OpenOutcome::kFailed;
+ }
+
+ AutoPulseLock auto_lock(pa_mainloop_);
+ if (!pulse::CreateInputStream(pa_mainloop_, pa_context_, &handle_, params_,
+ device_name_, &StreamNotifyCallback, this)) {
+ SendLogMessage("%s => (ERROR: failed to open PA stream)", __func__);
+ return OpenOutcome::kFailed;
+ }
+
+ DCHECK(handle_);
+
+ return OpenOutcome::kSuccess;
+}
+
+void PulseAudioInputStream::Start(AudioInputCallback* callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(callback);
+ DCHECK(handle_);
+ SendLogMessage("%s()", __func__);
+
+ // AGC needs to be started out of the lock.
+ StartAgc();
+
+ AutoPulseLock auto_lock(pa_mainloop_);
+
+ if (stream_started_)
+ return;
+
+ // Start the streaming.
+ callback_ = callback;
+ pa_stream_set_read_callback(handle_, &ReadCallback, this);
+ pa_stream_readable_size(handle_);
+ stream_started_ = true;
+
+ pa_operation* operation =
+ pa_stream_cork(handle_, 0, &pulse::StreamSuccessCallback, pa_mainloop_);
+
+ if (!WaitForOperationCompletion(pa_mainloop_, operation, pa_context_,
+ handle_)) {
+ callback_->OnError();
+ }
+}
+
+void PulseAudioInputStream::Stop() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ SendLogMessage("%s()", __func__);
+ AutoPulseLock auto_lock(pa_mainloop_);
+ if (!stream_started_)
+ return;
+
+ StopAgc();
+
+ // Set the flag to false to stop filling new data to soundcard.
+ stream_started_ = false;
+
+ // Clean up the old buffer.
+ pa_stream_drop(handle_);
+ fifo_.Clear();
+
+ pa_operation* operation =
+ pa_stream_flush(handle_, &pulse::StreamSuccessCallback, pa_mainloop_);
+ if (!WaitForOperationCompletion(pa_mainloop_, operation, pa_context_,
+ handle_)) {
+ callback_->OnError();
+ }
+
+ // Stop the stream.
+ pa_stream_set_read_callback(handle_, nullptr, nullptr);
+ operation =
+ pa_stream_cork(handle_, 1, &pulse::StreamSuccessCallback, pa_mainloop_);
+ if (!WaitForOperationCompletion(pa_mainloop_, operation, pa_context_,
+ handle_)) {
+ callback_->OnError();
+ }
+ callback_ = nullptr;
+}
+
+void PulseAudioInputStream::Close() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ SendLogMessage("%s()", __func__);
+ {
+ AutoPulseLock auto_lock(pa_mainloop_);
+ if (handle_) {
+ // Disable all the callbacks before disconnecting.
+ pa_stream_set_state_callback(handle_, nullptr, nullptr);
+ pa_operation* operation =
+ pa_stream_flush(handle_, &pulse::StreamSuccessCallback, pa_mainloop_);
+ WaitForOperationCompletion(pa_mainloop_, operation, pa_context_, handle_);
+
+ if (pa_stream_get_state(handle_) != PA_STREAM_UNCONNECTED)
+ pa_stream_disconnect(handle_);
+
+ // Release PulseAudio structures.
+ pa_stream_unref(handle_);
+ handle_ = nullptr;
+ }
+ }
+
+ // Signal to the manager that we're closed and can be removed.
+ // This should be the last call in the function as it deletes "this".
+ audio_manager_->ReleaseInputStream(this);
+}
+
+double PulseAudioInputStream::GetMaxVolume() {
+ return static_cast<double>(PA_VOLUME_NORM);
+}
+
+void PulseAudioInputStream::SetVolume(double volume) {
+ AutoPulseLock auto_lock(pa_mainloop_);
+ if (!handle_)
+ return;
+ SendLogMessage("%s({volume=%.2f})", __func__, volume);
+
+ size_t index = pa_stream_get_device_index(handle_);
+ pa_operation* operation = nullptr;
+ if (!channels_) {
+ // Get the number of channels for the source only when the |channels_| is 0.
+ // We are assuming the stream source is not changed on the fly here.
+ operation = pa_context_get_source_info_by_index(pa_context_, index,
+ &VolumeCallback, this);
+ if (!WaitForOperationCompletion(pa_mainloop_, operation, pa_context_,
+ handle_) ||
+ !channels_) {
+ SendLogMessage("%s => (WARNING: failed to read number of channels)",
+ __func__);
+ return;
+ }
+ }
+
+ pa_cvolume pa_volume;
+ pa_cvolume_set(&pa_volume, channels_, volume);
+ operation = pa_context_set_source_volume_by_index(
+ pa_context_, index, &pa_volume, nullptr, nullptr);
+
+ // Don't need to wait for this task to complete.
+ pa_operation_unref(operation);
+}
+
+double PulseAudioInputStream::GetVolume() {
+ if (pa_threaded_mainloop_in_thread(pa_mainloop_)) {
+ // When being called by the pulse thread, GetVolume() is asynchronous and
+ // called under AutoPulseLock.
+ if (!handle_)
+ return 0.0;
+
+ size_t index = pa_stream_get_device_index(handle_);
+ pa_operation* operation = pa_context_get_source_info_by_index(
+ pa_context_, index, &VolumeCallback, this);
+ // Do not wait for the operation since we can't block the pulse thread.
+ pa_operation_unref(operation);
+
+ // Return zero and the callback will asynchronously update the |volume_|.
+ return 0.0;
+ } else {
+ GetSourceInformation(&VolumeCallback);
+ return volume_;
+ }
+}
+
+bool PulseAudioInputStream::IsMuted() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ GetSourceInformation(&MuteCallback);
+ return muted_;
+}
+
+void PulseAudioInputStream::SetOutputDeviceForAec(
+ const std::string& output_device_id) {
+ // Not supported. Do nothing.
+}
+
+void PulseAudioInputStream::SendLogMessage(const char* format, ...) {
+ if (log_callback_.is_null())
+ return;
+ va_list args;
+ va_start(args, format);
+ log_callback_.Run("PAIS::" + base::StringPrintV(format, args));
+ va_end(args);
+}
+
+// static, used by pa_stream_set_read_callback.
+void PulseAudioInputStream::ReadCallback(pa_stream* handle,
+ size_t length,
+ void* user_data) {
+ PulseAudioInputStream* stream =
+ reinterpret_cast<PulseAudioInputStream*>(user_data);
+
+ stream->ReadData();
+}
+
+// static, used by pa_context_get_source_info_by_index.
+void PulseAudioInputStream::VolumeCallback(pa_context* context,
+ const pa_source_info* info,
+ int error, void* user_data) {
+ PulseAudioInputStream* stream =
+ reinterpret_cast<PulseAudioInputStream*>(user_data);
+
+ if (error) {
+ pa_threaded_mainloop_signal(stream->pa_mainloop_, 0);
+ return;
+ }
+
+ if (stream->channels_ != info->channel_map.channels)
+ stream->channels_ = info->channel_map.channels;
+
+ pa_volume_t volume = PA_VOLUME_MUTED; // Minimum possible value.
+ // Use the max volume of any channel as the volume.
+ for (int i = 0; i < stream->channels_; ++i) {
+ if (volume < info->volume.values[i])
+ volume = info->volume.values[i];
+ }
+
+ // It is safe to access |volume_| here since VolumeCallback() is running
+ // under PulseLock.
+ stream->volume_ = static_cast<double>(volume);
+}
+
+// static, used by pa_context_get_source_info_by_index.
+void PulseAudioInputStream::MuteCallback(pa_context* context,
+ const pa_source_info* info,
+ int error,
+ void* user_data) {
+ // Runs on PulseAudio callback thread. It might be possible to make this
+ // method more thread safe by passing a struct (or pair) of a local copy of
+ // |pa_mainloop_| and |muted_| instead.
+ PulseAudioInputStream* stream =
+ reinterpret_cast<PulseAudioInputStream*>(user_data);
+
+ // Avoid infinite wait loop in case of error.
+ if (error) {
+ pa_threaded_mainloop_signal(stream->pa_mainloop_, 0);
+ return;
+ }
+
+ stream->muted_ = info->mute != 0;
+}
+
+// static, used by pa_stream_set_state_callback.
+void PulseAudioInputStream::StreamNotifyCallback(pa_stream* s,
+ void* user_data) {
+ PulseAudioInputStream* stream =
+ reinterpret_cast<PulseAudioInputStream*>(user_data);
+
+ if (s && stream->callback_ &&
+ pa_stream_get_state(s) == PA_STREAM_FAILED) {
+ stream->callback_->OnError();
+ }
+
+ pa_threaded_mainloop_signal(stream->pa_mainloop_, 0);
+}
+
+void PulseAudioInputStream::ReadData() {
+ // Update the AGC volume level once every second. Note that,
+ // |volume| is also updated each time SetVolume() is called
+ // through IPC by the render-side AGC.
+ // We disregard the |normalized_volume| from GetAgcVolume()
+ // and use the value calculated by |volume_|.
+ double normalized_volume = 0.0;
+ GetAgcVolume(&normalized_volume);
+ normalized_volume = volume_ / GetMaxVolume();
+
+ // Compensate the audio delay caused by the FIFO.
+ // TODO(dalecurtis): This should probably use pa_stream_get_time() so we can
+ // get the capture time directly.
+ base::TimeTicks capture_time =
+ base::TimeTicks::Now() -
+ (pulse::GetHardwareLatency(handle_) +
+ AudioTimestampHelper::FramesToTime(fifo_.GetAvailableFrames(),
+ params_.sample_rate()));
+ do {
+ size_t length = 0;
+ const void* data = nullptr;
+ pa_stream_peek(handle_, &data, &length);
+ if (!data || length == 0)
+ break;
+
+ const int number_of_frames =
+ length / params_.GetBytesPerFrame(pulse::kInputSampleFormat);
+ if (number_of_frames > fifo_.GetUnfilledFrames()) {
+ // Dynamically increase capacity to the FIFO to handle larger buffer got
+ // from Pulse.
+ const int increase_blocks_of_buffer =
+ static_cast<int>((number_of_frames - fifo_.GetUnfilledFrames()) /
+ params_.frames_per_buffer()) +
+ 1;
+ fifo_.IncreaseCapacity(increase_blocks_of_buffer);
+ }
+
+ fifo_.Push(data, number_of_frames,
+ SampleFormatToBytesPerChannel(pulse::kInputSampleFormat));
+
+ // Checks if we still have data.
+ pa_stream_drop(handle_);
+ } while (pa_stream_readable_size(handle_) > 0);
+
+ while (fifo_.available_blocks()) {
+ const AudioBus* audio_bus = fifo_.Consume();
+
+ callback_->OnData(audio_bus, capture_time, normalized_volume);
+
+ // Move the capture time forward for each vended block.
+ capture_time += AudioTimestampHelper::FramesToTime(audio_bus->frames(),
+ params_.sample_rate());
+
+ // Sleep 5ms to wait until render consumes the data in order to avoid
+ // back to back OnData() method.
+ // TODO(dalecurtis): Delete all this. It shouldn't be necessary now that we
+ // have a ring buffer and FIFO on the actual shared memory.,
+ if (fifo_.available_blocks())
+ base::PlatformThread::Sleep(base::Milliseconds(5));
+ }
+
+ pa_threaded_mainloop_signal(pa_mainloop_, 0);
+}
+
+bool PulseAudioInputStream::GetSourceInformation(pa_source_info_cb_t callback) {
+ AutoPulseLock auto_lock(pa_mainloop_);
+ if (!handle_)
+ return false;
+
+ size_t index = pa_stream_get_device_index(handle_);
+ pa_operation* operation =
+ pa_context_get_source_info_by_index(pa_context_, index, callback, this);
+ return WaitForOperationCompletion(pa_mainloop_, operation, pa_context_,
+ handle_);
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/pulse/pulse_input.h b/third_party/chromium/media/audio/pulse/pulse_input.h
new file mode 100644
index 0000000..df3f28b
--- /dev/null
+++ b/third_party/chromium/media/audio/pulse/pulse_input.h
@@ -0,0 +1,100 @@
+// Copyright (c) 2012 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 MEDIA_AUDIO_PULSE_PULSE_INPUT_H_
+#define MEDIA_AUDIO_PULSE_PULSE_INPUT_H_
+
+#include <pulse/pulseaudio.h>
+#include <stddef.h>
+#include <string>
+
+#include "base/macros.h"
+#include "base/threading/thread_checker.h"
+#include "media/audio/agc_audio_stream.h"
+#include "media/audio/audio_device_name.h"
+#include "media/audio/audio_io.h"
+#include "media/audio/audio_manager.h"
+#include "media/base/audio_block_fifo.h"
+#include "media/base/audio_parameters.h"
+
+namespace media {
+
+class AudioManagerPulse;
+
+class PulseAudioInputStream : public AgcAudioStream<AudioInputStream> {
+ public:
+ PulseAudioInputStream(AudioManagerPulse* audio_manager,
+ const std::string& device_name,
+ const AudioParameters& params,
+ pa_threaded_mainloop* mainloop,
+ pa_context* context,
+ AudioManager::LogCallback log_callback);
+
+ PulseAudioInputStream(const PulseAudioInputStream&) = delete;
+ PulseAudioInputStream& operator=(const PulseAudioInputStream&) = delete;
+
+ ~PulseAudioInputStream() override;
+
+ // Implementation of AudioInputStream.
+ AudioInputStream::OpenOutcome Open() override;
+ void Start(AudioInputCallback* callback) override;
+ void Stop() override;
+ void Close() override;
+ double GetMaxVolume() override;
+ void SetVolume(double volume) override;
+ double GetVolume() override;
+ bool IsMuted() override;
+ void SetOutputDeviceForAec(const std::string& output_device_id) override;
+
+ private:
+ // Helper method used for sending native logs to the registered client.
+ void SendLogMessage(const char* format, ...) PRINTF_FORMAT(2, 3);
+
+ // PulseAudio Callbacks.
+ static void ReadCallback(pa_stream* handle, size_t length, void* user_data);
+ static void StreamNotifyCallback(pa_stream* stream, void* user_data);
+ static void VolumeCallback(pa_context* context, const pa_source_info* info,
+ int error, void* user_data);
+ static void MuteCallback(pa_context* context,
+ const pa_source_info* info,
+ int error,
+ void* user_data);
+
+ // Helper for the ReadCallback.
+ void ReadData();
+
+ // Utility method used by GetVolume() and IsMuted().
+ bool GetSourceInformation(pa_source_info_cb_t callback);
+
+ AudioManagerPulse* audio_manager_;
+ AudioInputCallback* callback_;
+ std::string device_name_;
+ AudioParameters params_;
+ int channels_;
+ double volume_;
+ bool stream_started_;
+
+ // Set to true in IsMuted() if user has muted the selected microphone in the
+ // sound settings UI.
+ bool muted_;
+
+ // Holds the data from the OS.
+ AudioBlockFifo fifo_;
+
+ // PulseAudio API structs.
+ pa_threaded_mainloop* pa_mainloop_; // Weak.
+
+ pa_context* pa_context_; // Weak.
+
+ // Callback to send log messages to registered clients.
+ AudioManager::LogCallback log_callback_;
+
+ pa_stream* handle_;
+
+ base::ThreadChecker thread_checker_;
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_PULSE_PULSE_INPUT_H_
diff --git a/third_party/chromium/media/audio/pulse/pulse_output.cc b/third_party/chromium/media/audio/pulse/pulse_output.cc
new file mode 100644
index 0000000..2b773a3
--- /dev/null
+++ b/third_party/chromium/media/audio/pulse/pulse_output.cc
@@ -0,0 +1,297 @@
+// Copyright (c) 2012 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 "media/audio/pulse/pulse_output.h"
+
+#include <pulse/pulseaudio.h>
+#include <stdint.h>
+
+#include "base/compiler_specific.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/stringprintf.h"
+#include "base/time/time.h"
+#include "media/audio/audio_device_description.h"
+#include "media/audio/audio_manager_base.h"
+#include "media/audio/pulse/pulse_util.h"
+#include "media/base/audio_sample_types.h"
+
+namespace media {
+
+using pulse::AutoPulseLock;
+using pulse::WaitForOperationCompletion;
+
+// static, pa_stream_notify_cb
+void PulseAudioOutputStream::StreamNotifyCallback(pa_stream* s, void* p_this) {
+ PulseAudioOutputStream* stream = static_cast<PulseAudioOutputStream*>(p_this);
+
+ // Forward unexpected failures to the AudioSourceCallback if available. All
+ // these variables are only modified under pa_threaded_mainloop_lock() so this
+ // should be thread safe.
+ if (s && stream->source_callback_ &&
+ pa_stream_get_state(s) == PA_STREAM_FAILED) {
+ stream->source_callback_->OnError(AudioSourceCallback::ErrorType::kUnknown);
+ }
+
+ pa_threaded_mainloop_signal(stream->pa_mainloop_, 0);
+}
+
+// static, pa_stream_request_cb_t
+void PulseAudioOutputStream::StreamRequestCallback(pa_stream* s, size_t len,
+ void* p_this) {
+ // Fulfill write request; must always result in a pa_stream_write() call.
+ static_cast<PulseAudioOutputStream*>(p_this)->FulfillWriteRequest(len);
+}
+
+PulseAudioOutputStream::PulseAudioOutputStream(
+ const AudioParameters& params,
+ const std::string& device_id,
+ AudioManagerBase* manager,
+ AudioManager::LogCallback log_callback)
+ : params_(AudioParameters(params.format(),
+ params.channel_layout(),
+ params.sample_rate(),
+ params.frames_per_buffer())),
+ device_id_(device_id),
+ manager_(manager),
+ log_callback_(std::move(log_callback)),
+ pa_context_(nullptr),
+ pa_mainloop_(nullptr),
+ pa_stream_(nullptr),
+ volume_(1.0f),
+ source_callback_(nullptr),
+ buffer_size_(params_.GetBytesPerBuffer(kSampleFormatF32)) {
+ CHECK(params_.IsValid());
+ SendLogMessage("%s({device_id=%s}, {params=[%s]})", __func__,
+ device_id.c_str(), params.AsHumanReadableString().c_str());
+ audio_bus_ = AudioBus::Create(params_);
+}
+
+PulseAudioOutputStream::~PulseAudioOutputStream() {
+ // All internal structures should already have been freed in Close(), which
+ // calls AudioManagerBase::ReleaseOutputStream() which deletes this object.
+ DCHECK(!pa_stream_);
+ DCHECK(!pa_context_);
+ DCHECK(!pa_mainloop_);
+}
+
+bool PulseAudioOutputStream::Open() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ SendLogMessage("%s()", __func__);
+ bool result = pulse::CreateOutputStream(
+ &pa_mainloop_, &pa_context_, &pa_stream_, params_, device_id_,
+ AudioManager::GetGlobalAppName(), &StreamNotifyCallback,
+ &StreamRequestCallback, this);
+ if (!result) {
+ SendLogMessage("%s => (ERROR: failed to open PA stream)", __func__);
+ }
+ return result;
+}
+
+void PulseAudioOutputStream::Reset() {
+ if (!pa_mainloop_) {
+ DCHECK(!pa_stream_);
+ DCHECK(!pa_context_);
+ return;
+ }
+
+ {
+ AutoPulseLock auto_lock(pa_mainloop_);
+
+ // Close the stream.
+ if (pa_stream_) {
+ // Ensure all samples are played out before shutdown.
+ pa_operation* operation = pa_stream_flush(
+ pa_stream_, &pulse::StreamSuccessCallback, pa_mainloop_);
+ WaitForOperationCompletion(pa_mainloop_, operation, pa_context_,
+ pa_stream_);
+
+ // Release PulseAudio structures.
+ pa_stream_disconnect(pa_stream_);
+ pa_stream_set_write_callback(pa_stream_, nullptr, nullptr);
+ pa_stream_set_state_callback(pa_stream_, nullptr, nullptr);
+ pa_stream_unref(pa_stream_);
+ pa_stream_ = nullptr;
+ }
+
+ if (pa_context_) {
+ pa_context_disconnect(pa_context_);
+ pa_context_set_state_callback(pa_context_, nullptr, nullptr);
+ pa_context_unref(pa_context_);
+ pa_context_ = nullptr;
+ }
+ }
+
+ pa_threaded_mainloop_stop(pa_mainloop_);
+ pa_threaded_mainloop_free(pa_mainloop_);
+ pa_mainloop_ = nullptr;
+}
+
+void PulseAudioOutputStream::Close() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ SendLogMessage("%s()", __func__);
+
+ Reset();
+
+ // Signal to the manager that we're closed and can be removed.
+ // This should be the last call in the function as it deletes "this".
+ manager_->ReleaseOutputStream(this);
+}
+
+// This stream is always used with sub second buffer sizes, where it's
+// sufficient to simply always flush upon Start().
+void PulseAudioOutputStream::Flush() {}
+
+void PulseAudioOutputStream::SendLogMessage(const char* format, ...) {
+ if (log_callback_.is_null())
+ return;
+ va_list args;
+ va_start(args, format);
+ log_callback_.Run("PAOS::" + base::StringPrintV(format, args) +
+ base::StringPrintf(" [this=%p]", this));
+ va_end(args);
+}
+
+void PulseAudioOutputStream::FulfillWriteRequest(size_t requested_bytes) {
+ int bytes_remaining = requested_bytes;
+ while (bytes_remaining > 0) {
+ void* pa_buffer = nullptr;
+ size_t pa_buffer_size = buffer_size_;
+ CHECK_GE(pa_stream_begin_write(pa_stream_, &pa_buffer, &pa_buffer_size), 0);
+
+ if (!source_callback_) {
+ memset(pa_buffer, 0, pa_buffer_size);
+ pa_stream_write(pa_stream_, pa_buffer, pa_buffer_size, nullptr, 0LL,
+ PA_SEEK_RELATIVE);
+ bytes_remaining -= pa_buffer_size;
+ continue;
+ }
+
+ size_t unwritten_frames_in_bus = audio_bus_->frames();
+ size_t frames_filled = source_callback_->OnMoreData(
+ pulse::GetHardwareLatency(pa_stream_), base::TimeTicks::Now(), 0,
+ audio_bus_.get());
+
+ // Zero any unfilled data so it plays back as silence.
+ if (frames_filled < unwritten_frames_in_bus) {
+ audio_bus_->ZeroFramesPartial(frames_filled,
+ unwritten_frames_in_bus - frames_filled);
+ }
+
+ audio_bus_->Scale(volume_);
+
+ size_t frame_size = buffer_size_ / unwritten_frames_in_bus;
+ size_t frames_to_copy = pa_buffer_size / frame_size;
+ size_t frame_offset_in_bus = 0;
+ do {
+ // Grab frames and get the count.
+ frames_to_copy =
+ std::min(audio_bus_->frames() - frame_offset_in_bus, frames_to_copy);
+
+ // We skip clipping since that occurs at the shared memory boundary.
+ audio_bus_->ToInterleavedPartial<Float32SampleTypeTraitsNoClip>(
+ frame_offset_in_bus, frames_to_copy,
+ reinterpret_cast<float*>(pa_buffer));
+ frame_offset_in_bus += frames_to_copy;
+ unwritten_frames_in_bus -= frames_to_copy;
+
+ if (pa_stream_write(pa_stream_, pa_buffer, pa_buffer_size, nullptr, 0LL,
+ PA_SEEK_RELATIVE) < 0) {
+ source_callback_->OnError(AudioSourceCallback::ErrorType::kUnknown);
+ return;
+ }
+ bytes_remaining -= pa_buffer_size;
+ if (unwritten_frames_in_bus) {
+ // Reset the buffer and the size:
+ // - If pa_buffer isn't nulled out, then it will get re-used, and
+ // there will be a race between PA reading and us writing.
+ // - If we don't shrink the pa_buffer_size to a small value, we get
+ // stuttering as the memory allocation can take far too long. This
+ // also means that we will never get more than we want, and we
+ // dont need to memset.
+ pa_buffer = nullptr;
+ pa_buffer_size = unwritten_frames_in_bus * frame_size;
+ CHECK_GE(pa_stream_begin_write(pa_stream_, &pa_buffer, &pa_buffer_size),
+ 0);
+ frames_to_copy = pa_buffer_size / frame_size;
+ }
+ } while (unwritten_frames_in_bus);
+ }
+}
+
+void PulseAudioOutputStream::Start(AudioSourceCallback* callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ CHECK(callback);
+ CHECK(pa_stream_);
+ SendLogMessage("%s()", __func__);
+
+ AutoPulseLock auto_lock(pa_mainloop_);
+
+ // Ensure the context and stream are ready.
+ if (pa_context_get_state(pa_context_) != PA_CONTEXT_READY &&
+ pa_stream_get_state(pa_stream_) != PA_STREAM_READY) {
+ callback->OnError(AudioSourceCallback::ErrorType::kUnknown);
+ return;
+ }
+
+ source_callback_ = callback;
+
+ // Uncork (resume) the stream.
+ pa_operation* operation = pa_stream_cork(
+ pa_stream_, 0, &pulse::StreamSuccessCallback, pa_mainloop_);
+ if (!WaitForOperationCompletion(pa_mainloop_, operation, pa_context_,
+ pa_stream_)) {
+ callback->OnError(AudioSourceCallback::ErrorType::kUnknown);
+ }
+}
+
+void PulseAudioOutputStream::Stop() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ SendLogMessage("%s()", __func__);
+
+ // Cork (pause) the stream. Waiting for the main loop lock will ensure
+ // outstanding callbacks have completed.
+ AutoPulseLock auto_lock(pa_mainloop_);
+
+ if (!source_callback_)
+ return;
+
+ // Set |source_callback_| to nullptr so all FulfillWriteRequest() calls which
+ // may occur while waiting on the flush and cork exit immediately.
+ auto* callback = source_callback_;
+ source_callback_ = nullptr;
+
+ // Flush the stream prior to cork, doing so after will cause hangs. Write
+ // callbacks are suspended while inside pa_threaded_mainloop_lock() so this
+ // is all thread safe.
+ pa_operation* operation =
+ pa_stream_flush(pa_stream_, &pulse::StreamSuccessCallback, pa_mainloop_);
+ if (!WaitForOperationCompletion(pa_mainloop_, operation, pa_context_,
+ pa_stream_)) {
+ callback->OnError(AudioSourceCallback::ErrorType::kUnknown);
+ }
+
+ operation = pa_stream_cork(pa_stream_, 1, &pulse::StreamSuccessCallback,
+ pa_mainloop_);
+ if (!WaitForOperationCompletion(pa_mainloop_, operation, pa_context_,
+ pa_stream_)) {
+ callback->OnError(AudioSourceCallback::ErrorType::kUnknown);
+ }
+}
+
+void PulseAudioOutputStream::SetVolume(double volume) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Waiting for the main loop lock will ensure outstanding callbacks have
+ // completed and |volume_| is not accessed from them.
+ AutoPulseLock auto_lock(pa_mainloop_);
+ volume_ = static_cast<float>(volume);
+}
+
+void PulseAudioOutputStream::GetVolume(double* volume) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ *volume = volume_;
+}
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/pulse/pulse_output.h b/third_party/chromium/media/audio/pulse/pulse_output.h
new file mode 100644
index 0000000..4dae18e
--- /dev/null
+++ b/third_party/chromium/media/audio/pulse/pulse_output.h
@@ -0,0 +1,115 @@
+// Copyright (c) 2012 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.
+//
+// Creates an audio output stream based on the PulseAudio asynchronous API;
+// specifically using the pa_threaded_mainloop model.
+//
+// If the stream is successfully opened, Close() must be called before the
+// stream is deleted as Close() is responsible for ensuring resource cleanup
+// occurs.
+//
+// This object is designed so that all AudioOutputStream methods will be called
+// on the same thread that created the object.
+//
+// WARNING: This object blocks on internal PulseAudio calls in Open() while
+// waiting for PulseAudio's context structure to be ready. It also blocks in
+// inside PulseAudio in Start() and repeated during playback, waiting for
+// PulseAudio write callbacks to occur.
+
+#ifndef MEDIA_AUDIO_PULSE_PULSE_OUTPUT_H_
+#define MEDIA_AUDIO_PULSE_PULSE_OUTPUT_H_
+
+#include <stddef.h>
+
+#include <memory>
+#include <string>
+
+#include "base/macros.h"
+#include "base/threading/thread_checker.h"
+#include "media/audio/audio_io.h"
+#include "media/audio/audio_manager.h"
+#include "media/base/audio_parameters.h"
+
+struct pa_context;
+struct pa_stream;
+struct pa_threaded_mainloop;
+
+namespace media {
+class AudioManagerBase;
+
+class PulseAudioOutputStream : public AudioOutputStream {
+ public:
+ PulseAudioOutputStream(const AudioParameters& params,
+ const std::string& device_id,
+ AudioManagerBase* manager,
+ AudioManager::LogCallback log_callback);
+
+ PulseAudioOutputStream(const PulseAudioOutputStream&) = delete;
+ PulseAudioOutputStream& operator=(const PulseAudioOutputStream&) = delete;
+
+ ~PulseAudioOutputStream() override;
+
+ // Implementation of AudioOutputStream.
+ bool Open() override;
+ void Close() override;
+ void Flush() override;
+ void Start(AudioSourceCallback* callback) override;
+ void Stop() override;
+ void SetVolume(double volume) override;
+ void GetVolume(double* volume) override;
+
+ private:
+ // Helper method used for sending native logs to the registered client.
+ void SendLogMessage(const char* format, ...) PRINTF_FORMAT(2, 3);
+
+ // Called by PulseAudio when |pa_stream_| change state. If an unexpected
+ // failure state change happens and |source_callback_| is set
+ // this method will forward the error via OnError().
+ static void StreamNotifyCallback(pa_stream* s, void* p_this);
+
+ // Called by PulseAudio when it needs more audio data.
+ static void StreamRequestCallback(pa_stream* s, size_t len, void* p_this);
+
+ // Fulfill a write request from the write request callback. Outputs silence
+ // if the request could not be fulfilled.
+ void FulfillWriteRequest(size_t requested_bytes);
+
+ // Close() helper function to free internal structs.
+ void Reset();
+
+ // AudioParameters from the constructor.
+ const AudioParameters params_;
+
+ // The device ID for the device to open.
+ const std::string device_id_;
+
+ // Audio manager that created us. Used to report that we've closed.
+ AudioManagerBase* manager_;
+
+ // Callback to send log messages to registered clients.
+ AudioManager::LogCallback log_callback_;
+
+ // PulseAudio API structs.
+ pa_context* pa_context_;
+ pa_threaded_mainloop* pa_mainloop_;
+ pa_stream* pa_stream_;
+
+ // Float representation of volume from 0.0 to 1.0.
+ float volume_;
+
+ // Callback to audio data source. Must only be modified while holding a lock
+ // on |pa_mainloop_| via pa_threaded_mainloop_lock().
+ AudioSourceCallback* source_callback_;
+
+ // Container for retrieving data from AudioSourceCallback::OnMoreData().
+ std::unique_ptr<AudioBus> audio_bus_;
+
+ const size_t buffer_size_;
+
+ base::ThreadChecker thread_checker_;
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_PULSE_PULSE_OUTPUT_H_
diff --git a/third_party/chromium/media/audio/pulse/pulse_stub_header.fragment b/third_party/chromium/media/audio/pulse/pulse_stub_header.fragment
new file mode 100644
index 0000000..cdaa841
--- /dev/null
+++ b/third_party/chromium/media/audio/pulse/pulse_stub_header.fragment
@@ -0,0 +1,19 @@
+// The extra include header needed in the generated stub file for defining
+// various Pulse types.
+
+extern "C" {
+
+#include <pulse/pulseaudio.h>
+
+#if PA_MAJOR > 12
+typedef const pa_context* const_pa_context_ptr;
+typedef const pa_operation* const_pa_operation_ptr;
+typedef const pa_proplist* const_pa_proplist_ptr;
+typedef const pa_stream* const_pa_stream_ptr;
+#else
+typedef pa_context* const_pa_context_ptr;
+typedef pa_operation* const_pa_operation_ptr;
+typedef pa_proplist* const_pa_proplist_ptr;
+typedef pa_stream* const_pa_stream_ptr;
+#endif
+}
diff --git a/third_party/chromium/media/audio/pulse/pulse_util.cc b/third_party/chromium/media/audio/pulse/pulse_util.cc
new file mode 100644
index 0000000..9d8eb23
--- /dev/null
+++ b/third_party/chromium/media/audio/pulse/pulse_util.cc
@@ -0,0 +1,645 @@
+// Copyright (c) 2012 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 "media/audio/pulse/pulse_util.h"
+
+#include <stdint.h>
+#include <string.h>
+
+#include <memory>
+
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/notreached.h"
+#include "base/synchronization/waitable_event.h"
+#include "build/branding_buildflags.h"
+#include "media/audio/audio_device_description.h"
+#include "media/base/audio_timestamp_helper.h"
+
+#if defined(DLOPEN_PULSEAUDIO)
+#include "media/audio/pulse/pulse_stubs.h"
+
+using media_audio_pulse::kModulePulse;
+using media_audio_pulse::InitializeStubs;
+using media_audio_pulse::StubPathMap;
+#endif // defined(DLOPEN_PULSEAUDIO)
+
+namespace media {
+
+namespace pulse {
+
+namespace {
+
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
+constexpr char kBrowserDisplayName[] = "google-chrome";
+#define PRODUCT_STRING "Google Chrome"
+#else
+constexpr char kBrowserDisplayName[] = "chromium-browser";
+#define PRODUCT_STRING "Chromium"
+#endif
+
+#if defined(DLOPEN_PULSEAUDIO)
+static const base::FilePath::CharType kPulseLib[] =
+ FILE_PATH_LITERAL("libpulse.so.0");
+#endif
+
+void DestroyMainloop(pa_threaded_mainloop* mainloop) {
+ pa_threaded_mainloop_stop(mainloop);
+ pa_threaded_mainloop_free(mainloop);
+}
+
+void DestroyContext(pa_context* context) {
+ pa_context_set_state_callback(context, nullptr, nullptr);
+ pa_context_disconnect(context);
+ pa_context_unref(context);
+}
+
+pa_channel_position ChromiumToPAChannelPosition(Channels channel) {
+ switch (channel) {
+ // PulseAudio does not differentiate between left/right and
+ // stereo-left/stereo-right, both translate to front-left/front-right.
+ case LEFT:
+ return PA_CHANNEL_POSITION_FRONT_LEFT;
+ case RIGHT:
+ return PA_CHANNEL_POSITION_FRONT_RIGHT;
+ case CENTER:
+ return PA_CHANNEL_POSITION_FRONT_CENTER;
+ case LFE:
+ return PA_CHANNEL_POSITION_LFE;
+ case BACK_LEFT:
+ return PA_CHANNEL_POSITION_REAR_LEFT;
+ case BACK_RIGHT:
+ return PA_CHANNEL_POSITION_REAR_RIGHT;
+ case LEFT_OF_CENTER:
+ return PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER;
+ case RIGHT_OF_CENTER:
+ return PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER;
+ case BACK_CENTER:
+ return PA_CHANNEL_POSITION_REAR_CENTER;
+ case SIDE_LEFT:
+ return PA_CHANNEL_POSITION_SIDE_LEFT;
+ case SIDE_RIGHT:
+ return PA_CHANNEL_POSITION_SIDE_RIGHT;
+ default:
+ NOTREACHED() << "Invalid channel: " << channel;
+ return PA_CHANNEL_POSITION_INVALID;
+ }
+}
+
+class ScopedPropertyList {
+ public:
+ ScopedPropertyList() : property_list_(pa_proplist_new()) {}
+
+ ScopedPropertyList(const ScopedPropertyList&) = delete;
+ ScopedPropertyList& operator=(const ScopedPropertyList&) = delete;
+
+ ~ScopedPropertyList() { pa_proplist_free(property_list_); }
+
+ pa_proplist* get() const { return property_list_; }
+
+ private:
+ pa_proplist* property_list_;
+};
+
+struct InputBusData {
+ InputBusData(pa_threaded_mainloop* loop, const std::string& name)
+ : loop_(loop), name_(name), bus_() {}
+
+ pa_threaded_mainloop* const loop_;
+ const std::string& name_;
+ std::string bus_;
+};
+
+struct OutputBusData {
+ OutputBusData(pa_threaded_mainloop* loop, const std::string& bus)
+ : loop_(loop), name_(), bus_(bus) {}
+
+ pa_threaded_mainloop* const loop_;
+ std::string name_;
+ const std::string& bus_;
+};
+
+void InputBusCallback(pa_context* context,
+ const pa_source_info* info,
+ int error,
+ void* user_data) {
+ InputBusData* data = static_cast<InputBusData*>(user_data);
+
+ if (error) {
+ // We have checked all the devices now.
+ pa_threaded_mainloop_signal(data->loop_, 0);
+ return;
+ }
+
+ if (strcmp(info->name, data->name_.c_str()) == 0 &&
+ pa_proplist_contains(info->proplist, PA_PROP_DEVICE_BUS)) {
+ data->bus_ = pa_proplist_gets(info->proplist, PA_PROP_DEVICE_BUS);
+ }
+}
+
+void OutputBusCallback(pa_context* context,
+ const pa_sink_info* info,
+ int error,
+ void* user_data) {
+ OutputBusData* data = static_cast<OutputBusData*>(user_data);
+
+ if (error) {
+ // We have checked all the devices now.
+ pa_threaded_mainloop_signal(data->loop_, 0);
+ return;
+ }
+
+ if (pa_proplist_contains(info->proplist, PA_PROP_DEVICE_BUS) &&
+ strcmp(pa_proplist_gets(info->proplist, PA_PROP_DEVICE_BUS),
+ data->bus_.c_str()) == 0) {
+ data->name_ = info->name;
+ }
+}
+
+struct DefaultDevicesData {
+ explicit DefaultDevicesData(pa_threaded_mainloop* loop) : loop_(loop) {}
+ std::string input_;
+ std::string output_;
+ pa_threaded_mainloop* const loop_;
+};
+
+void GetDefaultDeviceIdCallback(pa_context* c,
+ const pa_server_info* info,
+ void* userdata) {
+ DefaultDevicesData* data = static_cast<DefaultDevicesData*>(userdata);
+ if (info->default_source_name)
+ data->input_ = info->default_source_name;
+ if (info->default_sink_name)
+ data->output_ = info->default_sink_name;
+ pa_threaded_mainloop_signal(data->loop_, 0);
+}
+
+struct ContextStartupData {
+ base::WaitableEvent* context_wait;
+ pa_threaded_mainloop* pa_mainloop;
+};
+
+void SignalReadyOrErrorStateCallback(pa_context* context, void* context_data) {
+ auto context_state = pa_context_get_state(context);
+ auto* data = static_cast<ContextStartupData*>(context_data);
+ if (!PA_CONTEXT_IS_GOOD(context_state) || context_state == PA_CONTEXT_READY)
+ data->context_wait->Signal();
+ pa_threaded_mainloop_signal(data->pa_mainloop, 0);
+}
+
+} // namespace
+
+bool InitPulse(pa_threaded_mainloop** mainloop, pa_context** context) {
+#if defined(DLOPEN_PULSEAUDIO)
+ StubPathMap paths;
+
+ // Check if the pulse library is available.
+ paths[kModulePulse].push_back(kPulseLib);
+ if (!InitializeStubs(paths)) {
+ VLOG(1) << "Failed on loading the Pulse library and symbols";
+ return false;
+ }
+#endif // defined(DLOPEN_PULSEAUDIO)
+
+ // The setup order below follows the pattern used by pa_simple_new():
+ // https://github.com/pulseaudio/pulseaudio/blob/master/src/pulse/simple.c
+
+ // Create a mainloop API and connect to the default server.
+ // The mainloop is the internal asynchronous API event loop.
+ pa_threaded_mainloop* pa_mainloop = pa_threaded_mainloop_new();
+ if (!pa_mainloop)
+ return false;
+
+ pa_mainloop_api* pa_mainloop_api = pa_threaded_mainloop_get_api(pa_mainloop);
+ pa_context* pa_context =
+ pa_context_new(pa_mainloop_api, PRODUCT_STRING " input");
+ if (!pa_context) {
+ pa_threaded_mainloop_free(pa_mainloop);
+ return false;
+ }
+
+ // We can't rely on pa_threaded_mainloop_wait() for PulseAudio startup since
+ // it can hang indefinitely. Instead we use a WaitableEvent to time out the
+ // startup process if it takes too long.
+ base::WaitableEvent context_wait;
+ ContextStartupData data = {&context_wait, pa_mainloop};
+
+ pa_context_set_state_callback(pa_context, &SignalReadyOrErrorStateCallback,
+ &data);
+
+ if (pa_context_connect(pa_context, nullptr, PA_CONTEXT_NOAUTOSPAWN,
+ nullptr)) {
+ VLOG(1) << "Failed to connect to the context. Error: "
+ << pa_strerror(pa_context_errno(pa_context));
+ DestroyContext(pa_context);
+ pa_threaded_mainloop_free(pa_mainloop);
+ return false;
+ }
+
+ // Lock the event loop object, effectively blocking the event loop thread
+ // from processing events. This is necessary.
+ auto mainloop_lock = std::make_unique<AutoPulseLock>(pa_mainloop);
+
+ // Start the threaded mainloop after everything has been configured.
+ if (pa_threaded_mainloop_start(pa_mainloop)) {
+ DestroyContext(pa_context);
+ mainloop_lock.reset();
+ DestroyMainloop(pa_mainloop);
+ return false;
+ }
+
+ // Don't hold the mainloop lock while waiting for the context to become ready,
+ // or we'll never complete since PulseAudio can't continue working.
+ mainloop_lock.reset();
+
+ // Wait for up to 5 seconds for pa_context to become ready. We'll be signaled
+ // by the SignalReadyOrErrorStateCallback that we setup above.
+ //
+ // We've chosen a timeout value of 5 seconds because this can be executed at
+ // browser startup (other times it's during audio process startup). In the
+ // normal case, this should only take ~50ms, but we've seen some test bots
+ // hang indefinitely when the pulse daemon can't be started.
+ constexpr base::TimeDelta kStartupTimeout = base::Seconds(5);
+ const bool was_signaled = context_wait.TimedWait(kStartupTimeout);
+
+ // Require the mainloop lock before checking the context state.
+ mainloop_lock = std::make_unique<AutoPulseLock>(pa_mainloop);
+
+ auto context_state = pa_context_get_state(pa_context);
+ if (context_state != PA_CONTEXT_READY) {
+ if (!was_signaled)
+ VLOG(1) << "Timed out trying to connect to PulseAudio.";
+ else
+ VLOG(1) << "Failed to connect to PulseAudio: " << context_state;
+ DestroyContext(pa_context);
+ mainloop_lock.reset();
+ DestroyMainloop(pa_mainloop);
+ return false;
+ }
+
+ // Replace our function local state callback with a global appropriate one.
+ pa_context_set_state_callback(pa_context, &pulse::ContextStateCallback,
+ pa_mainloop);
+
+ *mainloop = pa_mainloop;
+ *context = pa_context;
+ return true;
+}
+
+void DestroyPulse(pa_threaded_mainloop* mainloop, pa_context* context) {
+ DCHECK(mainloop);
+ DCHECK(context);
+
+ {
+ AutoPulseLock auto_lock(mainloop);
+ DestroyContext(context);
+ }
+
+ DestroyMainloop(mainloop);
+}
+
+// static, pa_stream_success_cb_t
+void StreamSuccessCallback(pa_stream* s, int error, void* mainloop) {
+ pa_threaded_mainloop* pa_mainloop =
+ static_cast<pa_threaded_mainloop*>(mainloop);
+ pa_threaded_mainloop_signal(pa_mainloop, 0);
+}
+
+// |pa_context| and |pa_stream| state changed cb.
+void ContextStateCallback(pa_context* context, void* mainloop) {
+ pa_threaded_mainloop* pa_mainloop =
+ static_cast<pa_threaded_mainloop*>(mainloop);
+ pa_threaded_mainloop_signal(pa_mainloop, 0);
+}
+
+pa_channel_map ChannelLayoutToPAChannelMap(ChannelLayout channel_layout) {
+ pa_channel_map channel_map;
+ if (channel_layout == CHANNEL_LAYOUT_MONO) {
+ // CHANNEL_LAYOUT_MONO only specifies audio on the C channel, but we
+ // want PulseAudio to play single-channel audio on more than just that.
+ pa_channel_map_init_mono(&channel_map);
+ } else {
+ pa_channel_map_init(&channel_map);
+
+ channel_map.channels = ChannelLayoutToChannelCount(channel_layout);
+ for (Channels ch = LEFT; ch <= CHANNELS_MAX;
+ ch = static_cast<Channels>(ch + 1)) {
+ int channel_index = ChannelOrder(channel_layout, ch);
+ if (channel_index < 0)
+ continue;
+
+ channel_map.map[channel_index] = ChromiumToPAChannelPosition(ch);
+ }
+ }
+
+ return channel_map;
+}
+
+bool WaitForOperationCompletion(pa_threaded_mainloop* mainloop,
+ pa_operation* operation,
+ pa_context* optional_context,
+ pa_stream* optional_stream) {
+ if (!operation) {
+ LOG(ERROR) << "pa_operation is nullptr.";
+ return false;
+ }
+
+ while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING) {
+ if (optional_context) {
+ pa_context_state_t context_state = pa_context_get_state(optional_context);
+ if (!PA_CONTEXT_IS_GOOD(context_state)) {
+ LOG(ERROR) << "pa_context went bad while waiting: state="
+ << context_state << ", error="
+ << pa_strerror(pa_context_errno(optional_context));
+ pa_operation_cancel(operation);
+ pa_operation_unref(operation);
+ return false;
+ }
+ }
+
+ if (optional_stream) {
+ pa_stream_state_t stream_state = pa_stream_get_state(optional_stream);
+ if (!PA_STREAM_IS_GOOD(stream_state)) {
+ LOG(ERROR) << "pa_stream went bad while waiting: " << stream_state;
+ pa_operation_cancel(operation);
+ pa_operation_unref(operation);
+ return false;
+ }
+ }
+
+ pa_threaded_mainloop_wait(mainloop);
+ }
+
+ pa_operation_unref(operation);
+ return true;
+}
+
+base::TimeDelta GetHardwareLatency(pa_stream* stream) {
+ DCHECK(stream);
+ int negative = 0;
+ pa_usec_t latency_micros = 0;
+ if (pa_stream_get_latency(stream, &latency_micros, &negative) != 0)
+ return base::TimeDelta();
+
+ if (negative)
+ return base::TimeDelta();
+
+ return base::Microseconds(latency_micros);
+}
+
+// Helper macro for CreateInput/OutputStream() to avoid code spam and
+// string bloat.
+#define RETURN_ON_FAILURE(expression, message) do { \
+ if (!(expression)) { \
+ DLOG(ERROR) << message; \
+ return false; \
+ } \
+} while (0)
+
+bool CreateInputStream(pa_threaded_mainloop* mainloop,
+ pa_context* context,
+ pa_stream** stream,
+ const AudioParameters& params,
+ const std::string& device_id,
+ pa_stream_notify_cb_t stream_callback,
+ void* user_data) {
+ DCHECK(mainloop);
+ DCHECK(context);
+
+ // Set sample specifications.
+ pa_sample_spec sample_specifications;
+
+ // FIXME: This should be PA_SAMPLE_FLOAT32, but there is more work needed in
+ // PulseAudioInputStream to support this.
+ static_assert(kInputSampleFormat == kSampleFormatS16,
+ "Only 16-bit input supported.");
+ sample_specifications.format = PA_SAMPLE_S16LE;
+ sample_specifications.rate = params.sample_rate();
+ sample_specifications.channels = params.channels();
+
+ // Get channel mapping and open recording stream.
+ pa_channel_map source_channel_map = ChannelLayoutToPAChannelMap(
+ params.channel_layout());
+ pa_channel_map* map =
+ (source_channel_map.channels != 0) ? &source_channel_map : nullptr;
+
+ // Create a new recording stream and
+ // tells PulseAudio what the stream icon should be.
+ ScopedPropertyList property_list;
+ pa_proplist_sets(property_list.get(), PA_PROP_APPLICATION_ICON_NAME,
+ kBrowserDisplayName);
+ *stream = pa_stream_new_with_proplist(context, "RecordStream",
+ &sample_specifications, map,
+ property_list.get());
+ RETURN_ON_FAILURE(*stream, "failed to create PA recording stream");
+
+ pa_stream_set_state_callback(*stream, stream_callback, user_data);
+
+ // Set server-side capture buffer metrics. Detailed documentation on what
+ // values should be chosen can be found at
+ // freedesktop.org/software/pulseaudio/doxygen/structpa__buffer__attr.html.
+ pa_buffer_attr buffer_attributes;
+ const unsigned int buffer_size = params.GetBytesPerBuffer(kInputSampleFormat);
+ buffer_attributes.maxlength = static_cast<uint32_t>(-1);
+ buffer_attributes.tlength = buffer_size;
+ buffer_attributes.minreq = buffer_size;
+ buffer_attributes.prebuf = static_cast<uint32_t>(-1);
+ buffer_attributes.fragsize = buffer_size;
+ int flags = PA_STREAM_AUTO_TIMING_UPDATE |
+ PA_STREAM_INTERPOLATE_TIMING |
+ PA_STREAM_ADJUST_LATENCY |
+ PA_STREAM_START_CORKED;
+ RETURN_ON_FAILURE(
+ pa_stream_connect_record(
+ *stream,
+ device_id == AudioDeviceDescription::kDefaultDeviceId
+ ? nullptr
+ : device_id.c_str(),
+ &buffer_attributes, static_cast<pa_stream_flags_t>(flags)) == 0,
+ "pa_stream_connect_record FAILED ");
+
+ // Wait for the stream to be ready.
+ while (true) {
+ pa_stream_state_t stream_state = pa_stream_get_state(*stream);
+ RETURN_ON_FAILURE(
+ PA_STREAM_IS_GOOD(stream_state), "Invalid PulseAudio stream state");
+ if (stream_state == PA_STREAM_READY)
+ break;
+ pa_threaded_mainloop_wait(mainloop);
+ }
+
+ return true;
+}
+
+bool CreateOutputStream(pa_threaded_mainloop** mainloop,
+ pa_context** context,
+ pa_stream** stream,
+ const AudioParameters& params,
+ const std::string& device_id,
+ const std::string& app_name,
+ pa_stream_notify_cb_t stream_callback,
+ pa_stream_request_cb_t write_callback,
+ void* user_data) {
+ DCHECK(!*mainloop);
+ DCHECK(!*context);
+
+ *mainloop = pa_threaded_mainloop_new();
+ RETURN_ON_FAILURE(*mainloop, "Failed to create PulseAudio main loop.");
+
+ pa_mainloop_api* pa_mainloop_api = pa_threaded_mainloop_get_api(*mainloop);
+ *context = pa_context_new(
+ pa_mainloop_api, app_name.empty() ? PRODUCT_STRING : app_name.c_str());
+ RETURN_ON_FAILURE(*context, "Failed to create PulseAudio context.");
+
+ // A state callback must be set before calling pa_threaded_mainloop_lock() or
+ // pa_threaded_mainloop_wait() calls may lead to dead lock.
+ pa_context_set_state_callback(*context, &ContextStateCallback, *mainloop);
+
+ // Lock the main loop while setting up the context. Failure to do so may lead
+ // to crashes as the PulseAudio thread tries to run before things are ready.
+ AutoPulseLock auto_lock(*mainloop);
+
+ RETURN_ON_FAILURE(pa_threaded_mainloop_start(*mainloop) == 0,
+ "Failed to start PulseAudio main loop.");
+ RETURN_ON_FAILURE(pa_context_connect(*context, nullptr,
+ PA_CONTEXT_NOAUTOSPAWN, nullptr) == 0,
+ "Failed to connect PulseAudio context.");
+
+ // Wait until |pa_context_| is ready. pa_threaded_mainloop_wait() must be
+ // called after pa_context_get_state() in case the context is already ready,
+ // otherwise pa_threaded_mainloop_wait() will hang indefinitely.
+ while (true) {
+ pa_context_state_t context_state = pa_context_get_state(*context);
+ RETURN_ON_FAILURE(PA_CONTEXT_IS_GOOD(context_state),
+ "Invalid PulseAudio context state.");
+ if (context_state == PA_CONTEXT_READY)
+ break;
+ pa_threaded_mainloop_wait(*mainloop);
+ }
+
+ // Set sample specifications.
+ pa_sample_spec sample_specifications;
+ sample_specifications.format = PA_SAMPLE_FLOAT32;
+ sample_specifications.rate = params.sample_rate();
+ sample_specifications.channels = params.channels();
+
+ // Get channel mapping.
+ pa_channel_map* map = nullptr;
+ pa_channel_map source_channel_map = ChannelLayoutToPAChannelMap(
+ params.channel_layout());
+ if (source_channel_map.channels != 0) {
+ // The source data uses a supported channel map so we will use it rather
+ // than the default channel map (nullptr).
+ map = &source_channel_map;
+ }
+
+ // Open playback stream and
+ // tell PulseAudio what the stream icon should be.
+ ScopedPropertyList property_list;
+ pa_proplist_sets(property_list.get(), PA_PROP_APPLICATION_ICON_NAME,
+ kBrowserDisplayName);
+ *stream = pa_stream_new_with_proplist(
+ *context, "Playback", &sample_specifications, map, property_list.get());
+ RETURN_ON_FAILURE(*stream, "failed to create PA playback stream");
+
+ pa_stream_set_state_callback(*stream, stream_callback, user_data);
+
+ // Even though we start the stream corked above, PulseAudio will issue one
+ // stream request after setup. write_callback() must fulfill the write.
+ pa_stream_set_write_callback(*stream, write_callback, user_data);
+
+ // Pulse is very finicky with the small buffer sizes used by Chrome. The
+ // settings below are mostly found through trial and error. Essentially we
+ // want Pulse to auto size its internal buffers, but call us back nearly every
+ // |minreq| bytes. |tlength| should be a multiple of |minreq|; too low and
+ // Pulse will issue callbacks way too fast, too high and we don't get
+ // callbacks frequently enough.
+ //
+ // Setting |minreq| to the exact buffer size leads to more callbacks than
+ // necessary, so we've clipped it to half the buffer size. Regardless of the
+ // requested amount, we'll always fill |params.GetBytesPerBuffer()| though.
+ size_t buffer_size = params.GetBytesPerBuffer(kSampleFormatF32);
+ pa_buffer_attr pa_buffer_attributes;
+ pa_buffer_attributes.maxlength = static_cast<uint32_t>(-1);
+ pa_buffer_attributes.minreq = buffer_size / 2;
+ pa_buffer_attributes.prebuf = static_cast<uint32_t>(-1);
+ pa_buffer_attributes.tlength = buffer_size * 3;
+ pa_buffer_attributes.fragsize = static_cast<uint32_t>(-1);
+
+ // Connect playback stream. Like pa_buffer_attr, the pa_stream_flags have a
+ // huge impact on the performance of the stream and were chosen through trial
+ // and error.
+ RETURN_ON_FAILURE(
+ pa_stream_connect_playback(
+ *stream,
+ device_id == AudioDeviceDescription::kDefaultDeviceId
+ ? nullptr
+ : device_id.c_str(),
+ &pa_buffer_attributes,
+ static_cast<pa_stream_flags_t>(
+ PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_ADJUST_LATENCY |
+ PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_NOT_MONOTONIC |
+ PA_STREAM_START_CORKED),
+ nullptr, nullptr) == 0,
+ "pa_stream_connect_playback FAILED ");
+
+ // Wait for the stream to be ready.
+ while (true) {
+ pa_stream_state_t stream_state = pa_stream_get_state(*stream);
+ RETURN_ON_FAILURE(
+ PA_STREAM_IS_GOOD(stream_state), "Invalid PulseAudio stream state");
+ if (stream_state == PA_STREAM_READY)
+ break;
+ pa_threaded_mainloop_wait(*mainloop);
+ }
+
+ return true;
+}
+
+std::string GetBusOfInput(pa_threaded_mainloop* mainloop,
+ pa_context* context,
+ const std::string& name) {
+ DCHECK(mainloop);
+ DCHECK(context);
+ AutoPulseLock auto_lock(mainloop);
+ InputBusData data(mainloop, name);
+ pa_operation* operation =
+ pa_context_get_source_info_list(context, InputBusCallback, &data);
+ WaitForOperationCompletion(mainloop, operation, context);
+ return data.bus_;
+}
+
+std::string GetOutputCorrespondingTo(pa_threaded_mainloop* mainloop,
+ pa_context* context,
+ const std::string& bus) {
+ DCHECK(mainloop);
+ DCHECK(context);
+ AutoPulseLock auto_lock(mainloop);
+ OutputBusData data(mainloop, bus);
+ pa_operation* operation =
+ pa_context_get_sink_info_list(context, OutputBusCallback, &data);
+ WaitForOperationCompletion(mainloop, operation, context);
+ return data.name_;
+}
+
+std::string GetRealDefaultDeviceId(pa_threaded_mainloop* mainloop,
+ pa_context* context,
+ RequestType type) {
+ DCHECK(mainloop);
+ DCHECK(context);
+ AutoPulseLock auto_lock(mainloop);
+ DefaultDevicesData data(mainloop);
+ pa_operation* operation =
+ pa_context_get_server_info(context, &GetDefaultDeviceIdCallback, &data);
+ WaitForOperationCompletion(mainloop, operation, context);
+ return (type == RequestType::INPUT) ? data.input_ : data.output_;
+}
+
+#undef RETURN_ON_FAILURE
+
+} // namespace pulse
+
+} // namespace media
diff --git a/third_party/chromium/media/audio/pulse/pulse_util.h b/third_party/chromium/media/audio/pulse/pulse_util.h
new file mode 100644
index 0000000..0e80f81
--- /dev/null
+++ b/third_party/chromium/media/audio/pulse/pulse_util.h
@@ -0,0 +1,107 @@
+// Copyright (c) 2012 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 MEDIA_AUDIO_PULSE_PULSE_UTIL_H_
+#define MEDIA_AUDIO_PULSE_PULSE_UTIL_H_
+
+#include <pulse/pulseaudio.h>
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/time/time.h"
+#include "media/audio/audio_device_name.h"
+#include "media/base/audio_parameters.h"
+#include "media/base/channel_layout.h"
+
+namespace media {
+
+class AudioParameters;
+
+namespace pulse {
+
+enum class RequestType : int8_t { INPUT, OUTPUT };
+
+// A helper class that acquires pa_threaded_mainloop_lock() while in scope.
+class AutoPulseLock {
+ public:
+ explicit AutoPulseLock(pa_threaded_mainloop* pa_mainloop)
+ : pa_mainloop_(pa_mainloop) {
+ pa_threaded_mainloop_lock(pa_mainloop_);
+ }
+
+ AutoPulseLock(const AutoPulseLock&) = delete;
+ AutoPulseLock& operator=(const AutoPulseLock&) = delete;
+
+ ~AutoPulseLock() {
+ pa_threaded_mainloop_unlock(pa_mainloop_);
+ }
+
+ private:
+ pa_threaded_mainloop* pa_mainloop_;
+};
+
+bool MEDIA_EXPORT InitPulse(pa_threaded_mainloop** mainloop,
+ pa_context** context);
+void DestroyPulse(pa_threaded_mainloop* mainloop, pa_context* context);
+
+// Triggers pa_threaded_mainloop_signal() to avoid deadlocks.
+void StreamSuccessCallback(pa_stream* s, int error, void* mainloop);
+void ContextStateCallback(pa_context* context, void* mainloop);
+
+pa_channel_map ChannelLayoutToPAChannelMap(ChannelLayout channel_layout);
+
+// Blocks until pa_operation completes. If |optional_context| and/or
+// |optional_stream| are provided, the method will cancel |operation| and return
+// false if either the context or stream enter a bad state while waiting.
+bool WaitForOperationCompletion(pa_threaded_mainloop* mainloop,
+ pa_operation* operation,
+ pa_context* optional_context = nullptr,
+ pa_stream* optional_stream = nullptr);
+
+base::TimeDelta GetHardwareLatency(pa_stream* stream);
+
+constexpr SampleFormat kInputSampleFormat = kSampleFormatS16;
+
+// Create a recording stream for the threaded mainloop, return true if success,
+// otherwise false. |mainloop| and |context| have to be from a valid Pulse
+// threaded mainloop and the handle of the created stream will be returned by
+// |stream|.
+bool CreateInputStream(pa_threaded_mainloop* mainloop,
+ pa_context* context,
+ pa_stream** stream,
+ const AudioParameters& params,
+ const std::string& device_id,
+ pa_stream_notify_cb_t stream_callback,
+ void* user_data);
+
+// Create a playback stream for the threaded mainloop, return true if success,
+// otherwise false. This function will create a new Pulse threaded mainloop,
+// and the handles of the mainloop, context and stream will be returned by
+// |mainloop|, |context| and |stream|.
+bool CreateOutputStream(pa_threaded_mainloop** mainloop,
+ pa_context** context,
+ pa_stream** stream,
+ const AudioParameters& params,
+ const std::string& device_id,
+ const std::string& app_name,
+ pa_stream_notify_cb_t stream_callback,
+ pa_stream_request_cb_t write_callback,
+ void* user_data);
+
+// Utility functions to match up outputs and inputs.
+std::string GetBusOfInput(pa_threaded_mainloop* mainloop,
+ pa_context* context,
+ const std::string& name);
+std::string GetOutputCorrespondingTo(pa_threaded_mainloop* mainloop,
+ pa_context* context,
+ const std::string& bus);
+std::string GetRealDefaultDeviceId(pa_threaded_mainloop* mainloop,
+ pa_context* context,
+ RequestType type);
+} // namespace pulse
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_PULSE_PULSE_UTIL_H_
diff --git a/third_party/chromium/media/audio/scoped_task_runner_observer.cc b/third_party/chromium/media/audio/scoped_task_runner_observer.cc
new file mode 100644
index 0000000..b4b7f5b
--- /dev/null
+++ b/third_party/chromium/media/audio/scoped_task_runner_observer.cc
@@ -0,0 +1,50 @@
+// Copyright (c) 2012 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 "media/audio/scoped_task_runner_observer.h"
+
+#include "base/bind.h"
+#include "base/synchronization/waitable_event.h"
+
+namespace media {
+
+ScopedTaskRunnerObserver::ScopedTaskRunnerObserver(
+ const scoped_refptr<base::SingleThreadTaskRunner>& task_runner)
+ : task_runner_(task_runner) {
+ ObserveLoopDestruction(true, NULL);
+}
+
+ScopedTaskRunnerObserver::~ScopedTaskRunnerObserver() {
+ ObserveLoopDestruction(false, NULL);
+}
+
+void ScopedTaskRunnerObserver::ObserveLoopDestruction(
+ bool enable,
+ base::WaitableEvent* done) {
+ // Note: |done| may be NULL.
+ if (task_runner_->BelongsToCurrentThread()) {
+ base::CurrentThread loop = base::CurrentThread::Get();
+ if (enable) {
+ loop->AddDestructionObserver(this);
+ } else {
+ loop->RemoveDestructionObserver(this);
+ }
+ } else {
+ base::WaitableEvent event(base::WaitableEvent::ResetPolicy::AUTOMATIC,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+ if (task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&ScopedTaskRunnerObserver::ObserveLoopDestruction,
+ base::Unretained(this), enable, &event))) {
+ event.Wait();
+ } else {
+ // The message loop's thread has already terminated, so no need to wait.
+ }
+ }
+
+ if (done)
+ done->Signal();
+}
+
+} // namespace media.
diff --git a/third_party/chromium/media/audio/scoped_task_runner_observer.h b/third_party/chromium/media/audio/scoped_task_runner_observer.h
new file mode 100644
index 0000000..7e551dc
--- /dev/null
+++ b/third_party/chromium/media/audio/scoped_task_runner_observer.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2012 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 MEDIA_AUDIO_SCOPED_TASK_RUNNER_OBSERVER_H_
+#define MEDIA_AUDIO_SCOPED_TASK_RUNNER_OBSERVER_H_
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/task/current_thread.h"
+
+namespace base {
+class SingleThreadTaskRunner;
+class WaitableEvent;
+}
+
+namespace media {
+
+// A common base class for AudioOutputDevice and AudioInputDevice that manages
+// a task runner and monitors it for destruction. If the object goes out of
+// scope before the task runner, the object will automatically remove itself
+// from the task runner's list of destruction observers.
+// NOTE: The class that inherits from this class must implement the
+// WillDestroyCurrentMessageLoop virtual method from DestructionObserver.
+class ScopedTaskRunnerObserver
+ : public base::CurrentThread::DestructionObserver {
+ public:
+ explicit ScopedTaskRunnerObserver(
+ const scoped_refptr<base::SingleThreadTaskRunner>& task_runner);
+
+ protected:
+ ~ScopedTaskRunnerObserver() override;
+
+ // Accessor to the loop that's used by the derived class.
+ const scoped_refptr<base::SingleThreadTaskRunner>& task_runner() {
+ return task_runner_;
+ }
+
+ private:
+ // Call to add or remove ourselves from the list of destruction observers for
+ // the message loop.
+ void ObserveLoopDestruction(bool enable, base::WaitableEvent* done);
+
+ // A pointer to the task runner. In case it gets destroyed before this object
+ // goes out of scope, PostTask() etc will fail but not crash.
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedTaskRunnerObserver);
+};
+
+} // namespace media.
+
+#endif // MEDIA_AUDIO_SCOPED_TASK_RUNNER_OBSERVER_H_
diff --git a/third_party/chromium/media/audio/simple_sources.cc b/third_party/chromium/media/audio/simple_sources.cc
new file mode 100644
index 0000000..004df99
--- /dev/null
+++ b/third_party/chromium/media/audio/simple_sources.cc
@@ -0,0 +1,322 @@
+// Copyright (c) 2012 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 "media/audio/simple_sources.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <memory>
+
+#include "base/files/file.h"
+#include "base/logging.h"
+#include "base/numerics/math_constants.h"
+#include "base/thread_annotations.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "media/audio/wav_audio_handler.h"
+#include "media/base/audio_bus.h"
+
+namespace media {
+namespace {
+// Opens |wav_filename|, reads it and loads it as a wav file. This function will
+// return a null pointer if we can't read the file or if it's malformed. The
+// caller takes ownership of the returned data. The size of the data is stored
+// in |read_length|.
+std::unique_ptr<char[]> ReadWavFile(const base::FilePath& wav_filename,
+ size_t* read_length) {
+ base::File wav_file(
+ wav_filename, base::File::FLAG_OPEN | base::File::FLAG_READ);
+ if (!wav_file.IsValid()) {
+ LOG(ERROR) << "Failed to read " << wav_filename.value()
+ << " as input to the fake device."
+ " Try disabling the sandbox with --no-sandbox.";
+ return nullptr;
+ }
+
+ int64_t wav_file_length = wav_file.GetLength();
+ if (wav_file_length < 0) {
+ LOG(ERROR) << "Failed to get size of " << wav_filename.value();
+ return nullptr;
+ }
+ if (wav_file_length == 0) {
+ LOG(ERROR) << "Input file to fake device is empty: "
+ << wav_filename.value();
+ return nullptr;
+ }
+
+ std::unique_ptr<char[]> data(new char[wav_file_length]);
+ int read_bytes = wav_file.Read(0, data.get(), wav_file_length);
+ if (read_bytes != wav_file_length) {
+ LOG(ERROR) << "Failed to read all bytes of " << wav_filename.value();
+ return nullptr;
+ }
+ *read_length = wav_file_length;
+ return data;
+}
+
+// These values are based on experiments for local-to-local
+// PeerConnection to demonstrate audio/video synchronization.
+static const int kBeepDurationMilliseconds = 20;
+static const int kBeepFrequency = 400;
+
+// Intervals between two automatic beeps.
+static const int kAutomaticBeepIntervalInMs = 500;
+
+// Automatic beep will be triggered every |kAutomaticBeepIntervalInMs| unless
+// users explicitly call BeepOnce(), which will disable the automatic beep.
+class BeepContext {
+ public:
+ BeepContext() : beep_once_(false), automatic_beep_(true) {}
+
+ void SetBeepOnce(bool enable) {
+ base::AutoLock auto_lock(lock_);
+ beep_once_ = enable;
+
+ // Disable the automatic beep if users explicit set |beep_once_| to true.
+ if (enable)
+ automatic_beep_ = false;
+ }
+
+ bool beep_once() const {
+ base::AutoLock auto_lock(lock_);
+ return beep_once_;
+ }
+
+ bool automatic_beep() const {
+ base::AutoLock auto_lock(lock_);
+ return automatic_beep_;
+ }
+
+ private:
+ mutable base::Lock lock_;
+ bool beep_once_ GUARDED_BY(lock_);
+ bool automatic_beep_ GUARDED_BY(lock_);
+};
+
+BeepContext* GetBeepContext() {
+ static BeepContext* context = new BeepContext();
+ return context;
+}
+
+} // namespace
+
+//////////////////////////////////////////////////////////////////////////////
+// SineWaveAudioSource implementation.
+
+SineWaveAudioSource::SineWaveAudioSource(int channels,
+