Snap for 7550930 from 8cfb92904b40ff98c82dd5309698999a28c91388 to mainline-networkstack-release

Change-Id: Ic5b766e61d301498b3c358179e9b39e6c523438b
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
new file mode 100644
index 0000000..e8af785
--- /dev/null
+++ b/.github/workflows/codeql-analysis.yml
@@ -0,0 +1,65 @@
+# For most projects, this workflow file will not need changing; you simply need
+# to commit it to your repository.
+#
+# You may wish to alter this file to override the set of languages analyzed,
+# or to provide custom queries or build logic.
+name: "CodeQL"
+
+on:
+  push:
+    branches: [main]
+  pull_request:
+    # The branches below must be a subset of the branches above
+    branches: [main]
+  schedule:
+    - cron: '0 11 * * 1'
+
+jobs:
+  analyze:
+    name: Analyze
+    runs-on: ubuntu-latest
+
+    strategy:
+      fail-fast: false
+      matrix:
+        # Override automatic language detection by changing the below list
+        # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
+        language: ['cpp']
+        # Learn more...
+        # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
+
+    steps:
+    - name: Checkout repository
+      uses: actions/checkout@v2
+      with:
+        # We must fetch at least the immediate parents so that if this is
+        # a pull request then we can checkout the head.
+        fetch-depth: 2
+
+    # If this run was triggered by a pull request event, then checkout
+    # the head of the pull request instead of the merge commit.
+    - run: git checkout HEAD^2
+      if: ${{ github.event_name == 'pull_request' }}
+
+    # Initializes the CodeQL tools for scanning.
+    - name: Initialize CodeQL
+      uses: github/codeql-action/init@v1
+      with:
+        languages: ${{ matrix.language }}
+
+    # Command-line programs to run using the OS shell.
+    # https://git.io/JvXDl
+
+    - name: Install cras deps
+      working-directory: ./cras
+      run: sudo ./install_deps.sh
+
+    - name: Build
+      working-directory: ./cras
+      run: |
+        ./git_prepare.sh
+        ./configure
+        make
+
+    - name: Perform CodeQL Analysis
+      uses: github/codeql-action/analyze@v1
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..4cb9810
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,52 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// 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.
+
+package {
+    default_applicable_licenses: ["external_adhd_license"],
+}
+
+// Added automatically by a large-scale-change that took the approach of
+// 'apply every license found to every target'. While this makes sure we respect
+// every license restriction, it may not be entirely correct.
+//
+// e.g. GPL in an MIT project might only apply to the contrib/ directory.
+//
+// Please consider splitting the single license below into multiple licenses,
+// taking care not to lose any license_kind information, and overriding the
+// default license using the 'licenses: [...]' property on targets as needed.
+//
+// For unused files, consider creating a 'fileGroup' with "//visibility:private"
+// to attach the license to, and including a comment whether the files may be
+// used in the current project.
+//
+// large-scale-change included anything that looked like it might be a license
+// text as a license_text. e.g. LICENSE, NOTICE, COPYING etc.
+//
+// Please consider removing redundant or irrelevant files from 'license_text:'.
+// See: http://go/android-license-faq
+license {
+    name: "external_adhd_license",
+    visibility: [":__subpackages__"],
+    license_kinds: [
+        "SPDX-license-identifier-BSD",
+        "SPDX-license-identifier-LGPL",
+    ],
+    license_text: [
+        "LICENSE.SUPERFASTHASH",
+        "LICENSE.UTLIST",
+        "LICENSE.WEBKIT",
+        "NOTICE",
+    ],
+}
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..6d8601b
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,3 @@
+third_party {
+  license_type: RESTRICTED
+}
diff --git a/Makefile b/Makefile
index 798f576..9693cab 100644
--- a/Makefile
+++ b/Makefile
@@ -15,11 +15,11 @@
 
 cras-scripts:
 	$(ECHO) "Installing cras scripts"
-	$(INSTALL) --mode 755 -d $(DESTDIR)usr/bin/
+	$(INSTALL) --mode 755 -d $(DESTDIR)/usr/bin/
 	$(INSTALL) --mode 755 -D $(ADHD_DIR)/scripts/audio_diagnostics \
-		$(DESTDIR)usr/bin/
+		$(DESTDIR)/usr/bin/
 	$(INSTALL) --mode 755 -D $(ADHD_DIR)/scripts/asoc_dapm_graph \
-		$(DESTDIR)usr/bin/
+		$(DESTDIR)/usr/bin/
 
 cras_init_upstart:	$(ADHD_DIR)/init/cras.conf
 	$(ECHO) "Installing upstart file"
@@ -52,7 +52,7 @@
 
 endif
 
-$(DESTDIR)/etc/cras/device_blacklist:	$(ADHD_DIR)/cras-config/device_blacklist
+$(DESTDIR)/etc/cras/device_blocklist:	$(ADHD_DIR)/cras-config/device_blocklist
 	$(ECHO) "Installing '$<' to '$@'"
 	$(INSTALL) --mode 644 -D $< $@
 
@@ -83,7 +83,7 @@
 
 endif
 
-install:	$(DESTDIR)/etc/cras/device_blacklist \
+install:	$(DESTDIR)/etc/cras/device_blocklist \
 		cras-scripts \
 		cras_install \
 		cras_init
diff --git a/OWNERS.fuzz b/OWNERS.fuzz
new file mode 100644
index 0000000..8c7567f
--- /dev/null
+++ b/OWNERS.fuzz
@@ -0,0 +1,5 @@
+cychiang@chromium.org
+dgreid@chromium.org
+enshuo@chromium.org
+paulhsia@chromium.org
+chromeos-audio-bugs@google.com
diff --git a/PRESUBMIT.cfg b/PRESUBMIT.cfg
index c0e6ae9..e85e5dc 100644
--- a/PRESUBMIT.cfg
+++ b/PRESUBMIT.cfg
@@ -1,6 +1,7 @@
 [Hook Overrides]
 tab_check: false
 clang_format_check: true
+cargo_clippy_check: true
 
 # On by default, but required for options below.
 cros_license_check: true
@@ -10,3 +11,9 @@
 cros_license_check: --exclude_regex=HiFi\.conf$
 clang_format_check:
   cras/
+cargo_clippy_check:
+  --project=audio_streams
+  --project=cras/client/cras-sys
+  --project=cras/client/cras_tests
+  --project=cras/client/libcras
+  --project=cras/src/server/rust
diff --git a/alsa-module-config/alsa-cid.conf b/alsa-module-config/alsa-cid.conf
deleted file mode 100644
index 9254468..0000000
--- a/alsa-module-config/alsa-cid.conf
+++ /dev/null
@@ -1 +0,0 @@
-options snd_hda_intel model=cid patch=,cid_alsa.fw
diff --git a/alsa-module-config/alsa-falco.conf b/alsa-module-config/alsa-falco.conf
deleted file mode 100644
index 2a5fcda..0000000
--- a/alsa-module-config/alsa-falco.conf
+++ /dev/null
@@ -1 +0,0 @@
-options snd_hda_intel model=falco patch=,falco_alsa.fw
diff --git a/alsa-module-config/alsa-jecht.conf b/alsa-module-config/alsa-jecht.conf
deleted file mode 100644
index 550a387..0000000
--- a/alsa-module-config/alsa-jecht.conf
+++ /dev/null
@@ -1 +0,0 @@
-options snd_hda_intel model=jecht patch=,jecht_alsa.fw
diff --git a/alsa-module-config/alsa-leon.conf b/alsa-module-config/alsa-leon.conf
deleted file mode 100644
index d3a6af3..0000000
--- a/alsa-module-config/alsa-leon.conf
+++ /dev/null
@@ -1 +0,0 @@
-options snd_hda_intel model=leon patch=,leon_alsa.fw
diff --git a/alsa-module-config/alsa-mccloud.conf b/alsa-module-config/alsa-mccloud.conf
deleted file mode 100644
index f06a52a..0000000
--- a/alsa-module-config/alsa-mccloud.conf
+++ /dev/null
@@ -1 +0,0 @@
-options snd_hda_intel model=mccloud patch=,mccloud_alsa.fw
diff --git a/alsa-module-config/alsa-monroe.conf b/alsa-module-config/alsa-monroe.conf
deleted file mode 100644
index 620f8db..0000000
--- a/alsa-module-config/alsa-monroe.conf
+++ /dev/null
@@ -1 +0,0 @@
-options snd_hda_intel model=monroe patch=,monroe_alsa.fw
diff --git a/alsa-module-config/alsa-peppy.conf b/alsa-module-config/alsa-peppy.conf
deleted file mode 100644
index 482df73..0000000
--- a/alsa-module-config/alsa-peppy.conf
+++ /dev/null
@@ -1 +0,0 @@
-options snd_hda_intel model=peppy patch=,peppy_alsa.fw
diff --git a/alsa-module-config/alsa-rikku.conf b/alsa-module-config/alsa-rikku.conf
deleted file mode 100644
index 36dec7f..0000000
--- a/alsa-module-config/alsa-rikku.conf
+++ /dev/null
@@ -1 +0,0 @@
-options snd_hda_intel model=rikku patch=,rikku_alsa.fw
diff --git a/alsa-module-config/alsa-stout.conf b/alsa-module-config/alsa-stout.conf
deleted file mode 100644
index b117034..0000000
--- a/alsa-module-config/alsa-stout.conf
+++ /dev/null
@@ -1,2 +0,0 @@
-# Tell patch_realtek that it is running on a stout.
-options snd_hda_intel model=stout patch=stout_alsa.fw
diff --git a/alsa-module-config/alsa-stout32.conf b/alsa-module-config/alsa-stout32.conf
deleted file mode 100644
index 847501d..0000000
--- a/alsa-module-config/alsa-stout32.conf
+++ /dev/null
@@ -1,2 +0,0 @@
-# Tell patch_realtek that it is running on a stout.
-options snd_hda_intel model=stout patch=stout32_alsa.fw
diff --git a/alsa-module-config/alsa-tidus.conf b/alsa-module-config/alsa-tidus.conf
deleted file mode 100644
index 868d61e..0000000
--- a/alsa-module-config/alsa-tidus.conf
+++ /dev/null
@@ -1 +0,0 @@
-options snd_hda_intel model=tidus patch=,tidus_alsa.fw
diff --git a/alsa-module-config/alsa-tricky.conf b/alsa-module-config/alsa-tricky.conf
deleted file mode 100644
index ba28ce5..0000000
--- a/alsa-module-config/alsa-tricky.conf
+++ /dev/null
@@ -1 +0,0 @@
-options snd_hda_intel model=tricky patch=,tricky_alsa.fw
diff --git a/alsa-module-config/alsa-wolf.conf b/alsa-module-config/alsa-wolf.conf
deleted file mode 100644
index c7f4f0f..0000000
--- a/alsa-module-config/alsa-wolf.conf
+++ /dev/null
@@ -1 +0,0 @@
-options snd_hda_intel model=wolf patch=,wolf_alsa.fw
diff --git a/alsa-module-config/alsa-zako.conf b/alsa-module-config/alsa-zako.conf
deleted file mode 100644
index fcd6372..0000000
--- a/alsa-module-config/alsa-zako.conf
+++ /dev/null
@@ -1 +0,0 @@
-options snd_hda_intel model=zako patch=,zako_alsa.fw
diff --git a/alsa-module-config/cid_alsa.fw b/alsa-module-config/cid_alsa.fw
deleted file mode 100644
index 40ac817..0000000
--- a/alsa-module-config/cid_alsa.fw
+++ /dev/null
@@ -1,9 +0,0 @@
-[codec]
-0x10ec0283 0x10ec0283 0
-
-[model]
-alc283-dac-wcaps
-
-[hint]
-auto_mute = no
-auto_mic = no
diff --git a/alsa-module-config/falco_alsa.fw b/alsa-module-config/falco_alsa.fw
deleted file mode 100644
index 40ac817..0000000
--- a/alsa-module-config/falco_alsa.fw
+++ /dev/null
@@ -1,9 +0,0 @@
-[codec]
-0x10ec0283 0x10ec0283 0
-
-[model]
-alc283-dac-wcaps
-
-[hint]
-auto_mute = no
-auto_mic = no
diff --git a/alsa-module-config/jecht_alsa.fw b/alsa-module-config/jecht_alsa.fw
deleted file mode 100644
index 40ac817..0000000
--- a/alsa-module-config/jecht_alsa.fw
+++ /dev/null
@@ -1,9 +0,0 @@
-[codec]
-0x10ec0283 0x10ec0283 0
-
-[model]
-alc283-dac-wcaps
-
-[hint]
-auto_mute = no
-auto_mic = no
diff --git a/alsa-module-config/leon_alsa.fw b/alsa-module-config/leon_alsa.fw
deleted file mode 100644
index 40ac817..0000000
--- a/alsa-module-config/leon_alsa.fw
+++ /dev/null
@@ -1,9 +0,0 @@
-[codec]
-0x10ec0283 0x10ec0283 0
-
-[model]
-alc283-dac-wcaps
-
-[hint]
-auto_mute = no
-auto_mic = no
diff --git a/alsa-module-config/mccloud_alsa.fw b/alsa-module-config/mccloud_alsa.fw
deleted file mode 100644
index 40ac817..0000000
--- a/alsa-module-config/mccloud_alsa.fw
+++ /dev/null
@@ -1,9 +0,0 @@
-[codec]
-0x10ec0283 0x10ec0283 0
-
-[model]
-alc283-dac-wcaps
-
-[hint]
-auto_mute = no
-auto_mic = no
diff --git a/alsa-module-config/monroe_alsa.fw b/alsa-module-config/monroe_alsa.fw
deleted file mode 100644
index 40ac817..0000000
--- a/alsa-module-config/monroe_alsa.fw
+++ /dev/null
@@ -1,9 +0,0 @@
-[codec]
-0x10ec0283 0x10ec0283 0
-
-[model]
-alc283-dac-wcaps
-
-[hint]
-auto_mute = no
-auto_mic = no
diff --git a/alsa-module-config/peppy_alsa.fw b/alsa-module-config/peppy_alsa.fw
deleted file mode 100644
index 40ac817..0000000
--- a/alsa-module-config/peppy_alsa.fw
+++ /dev/null
@@ -1,9 +0,0 @@
-[codec]
-0x10ec0283 0x10ec0283 0
-
-[model]
-alc283-dac-wcaps
-
-[hint]
-auto_mute = no
-auto_mic = no
diff --git a/alsa-module-config/rikku_alsa.fw b/alsa-module-config/rikku_alsa.fw
deleted file mode 100644
index 40ac817..0000000
--- a/alsa-module-config/rikku_alsa.fw
+++ /dev/null
@@ -1,9 +0,0 @@
-[codec]
-0x10ec0283 0x10ec0283 0
-
-[model]
-alc283-dac-wcaps
-
-[hint]
-auto_mute = no
-auto_mic = no
diff --git a/alsa-module-config/stout32_alsa.fw b/alsa-module-config/stout32_alsa.fw
deleted file mode 100644
index 2ba7aa2..0000000
--- a/alsa-module-config/stout32_alsa.fw
+++ /dev/null
@@ -1,9 +0,0 @@
-[codec]
-0x10ec0269 0x17aa21fe 0
-
-[model]
-stout
-
-[hint]
-auto_mute = no
-auto_mic = no
diff --git a/alsa-module-config/stout_alsa.fw b/alsa-module-config/stout_alsa.fw
deleted file mode 100644
index 2ba7aa2..0000000
--- a/alsa-module-config/stout_alsa.fw
+++ /dev/null
@@ -1,9 +0,0 @@
-[codec]
-0x10ec0269 0x17aa21fe 0
-
-[model]
-stout
-
-[hint]
-auto_mute = no
-auto_mic = no
diff --git a/alsa-module-config/tidus_alsa.fw b/alsa-module-config/tidus_alsa.fw
deleted file mode 100644
index 40ac817..0000000
--- a/alsa-module-config/tidus_alsa.fw
+++ /dev/null
@@ -1,9 +0,0 @@
-[codec]
-0x10ec0283 0x10ec0283 0
-
-[model]
-alc283-dac-wcaps
-
-[hint]
-auto_mute = no
-auto_mic = no
diff --git a/alsa-module-config/tricky_alsa.fw b/alsa-module-config/tricky_alsa.fw
deleted file mode 100644
index 40ac817..0000000
--- a/alsa-module-config/tricky_alsa.fw
+++ /dev/null
@@ -1,9 +0,0 @@
-[codec]
-0x10ec0283 0x10ec0283 0
-
-[model]
-alc283-dac-wcaps
-
-[hint]
-auto_mute = no
-auto_mic = no
diff --git a/alsa-module-config/wolf_alsa.fw b/alsa-module-config/wolf_alsa.fw
deleted file mode 100644
index 40ac817..0000000
--- a/alsa-module-config/wolf_alsa.fw
+++ /dev/null
@@ -1,9 +0,0 @@
-[codec]
-0x10ec0283 0x10ec0283 0
-
-[model]
-alc283-dac-wcaps
-
-[hint]
-auto_mute = no
-auto_mic = no
diff --git a/alsa-module-config/zako_alsa.fw b/alsa-module-config/zako_alsa.fw
deleted file mode 100644
index 40ac817..0000000
--- a/alsa-module-config/zako_alsa.fw
+++ /dev/null
@@ -1,9 +0,0 @@
-[codec]
-0x10ec0283 0x10ec0283 0
-
-[model]
-alc283-dac-wcaps
-
-[hint]
-auto_mute = no
-auto_mic = no
diff --git a/audio_streams/.gitignore b/audio_streams/.gitignore
new file mode 100644
index 0000000..fa8d85a
--- /dev/null
+++ b/audio_streams/.gitignore
@@ -0,0 +1,2 @@
+Cargo.lock
+target
diff --git a/audio_streams/Android.bp b/audio_streams/Android.bp
index cbb6743..2909b54 100644
--- a/audio_streams/Android.bp
+++ b/audio_streams/Android.bp
@@ -1,18 +1,61 @@
-// This file is generated by cargo2android.py, added defaults.
+// This file is generated by cargo2android.py --run --device --test --global_defaults=crosvm_defaults --dependencies.
 
-rust_test_host {
-    name: "audio_streams_tests_audio_streams",
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "external_adhd_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-BSD
+    default_applicable_licenses: ["external_adhd_license"],
+}
+
+rust_defaults {
+    name: "audio_streams_defaults",
     defaults: ["crosvm_defaults"],
     crate_name: "audio_streams",
     srcs: ["src/audio_streams.rs"],
-    relative_install_path: "audio_streams_tests",
     test_suites: ["general-tests"],
     auto_gen_config: true,
+    edition: "2018",
+    rustlibs: [
+        "libsync_rust",
+        "libsys_util",
+    ],
 }
 
-rust_library_host_rlib {
+rust_test_host {
+    name: "audio_streams_host_test_src_audio_streams",
+    defaults: ["audio_streams_defaults"],
+}
+
+rust_test {
+    name: "audio_streams_device_test_src_audio_streams",
+    defaults: ["audio_streams_defaults"],
+}
+
+rust_library {
     name: "libaudio_streams",
     defaults: ["crosvm_defaults"],
+    host_supported: true,
     crate_name: "audio_streams",
     srcs: ["src/audio_streams.rs"],
+    edition: "2018",
+    rustlibs: [
+        "libsync_rust",
+        "libsys_util",
+    ],
 }
+
+// dependent_library ["feature_list"]
+//   ../../crosvm/assertions/src/lib.rs
+//   ../../crosvm/data_model/src/lib.rs
+//   ../../crosvm/sync/src/lib.rs
+//   ../../crosvm/sys_util/poll_token_derive/poll_token_derive.rs
+//   ../../crosvm/sys_util/src/lib.rs
+//   ../../crosvm/syscall_defines/src/lib.rs
+//   ../../crosvm/tempfile/src/lib.rs
+//   libc-0.2.76 "default,std"
+//   proc-macro2-1.0.19 "default,proc-macro"
+//   quote-1.0.7 "default,proc-macro"
+//   syn-1.0.39 "clone-impls,default,derive,parsing,printing,proc-macro,quote"
+//   unicode-xid-0.2.1 "default"
diff --git a/audio_streams/Cargo.toml b/audio_streams/Cargo.toml
index 54e6af6..dd169c7 100644
--- a/audio_streams/Cargo.toml
+++ b/audio_streams/Cargo.toml
@@ -8,3 +8,5 @@
 path = "src/audio_streams.rs"
 
 [dependencies]
+sync = { path = "../../crosvm/sync" } # provided by ebuild
+sys_util = { path = "../../crosvm/sys_util" } # provided by ebuild
diff --git a/audio_streams/README.md b/audio_streams/README.md
index b62c294..d3a02e8 100644
--- a/audio_streams/README.md
+++ b/audio_streams/README.md
@@ -2,5 +2,5 @@
 
 The `audio_streams` crate provides a basic interface for playing audio.
 This will be used to enable playback to various audio subsystems such as
-Alsa and cras. To start, an empty playback example `DummyStreamSource`
+Alsa and cras. To start, an empty playback example `NoopStreamSource`
 is provided.
diff --git a/audio_streams/src/audio_streams.rs b/audio_streams/src/audio_streams.rs
index 672034b..e5fc83c 100644
--- a/audio_streams/src/audio_streams.rs
+++ b/audio_streams/src/audio_streams.rs
@@ -13,67 +13,158 @@
 //! the samples written to it are committed to the `PlaybackBufferStream` it came from.
 //!
 //! ```
-//! use audio_streams::{StreamSource, DummyStreamSource};
+//! use audio_streams::{BoxError, SampleFormat, StreamSource, NoopStreamSource};
 //! use std::io::Write;
 //!
 //! const buffer_size: usize = 120;
 //! const num_channels: usize = 2;
-//! const frame_size: usize = num_channels * 2; // 16-bit samples are two bytes.
 //!
-//! # fn main() -> std::result::Result<(), Box<std::error::Error>> {
-//! let mut stream_source = DummyStreamSource::new();
+//! # fn main() -> std::result::Result<(), BoxError> {
+//! let mut stream_source = NoopStreamSource::new();
+//! let sample_format = SampleFormat::S16LE;
+//! let frame_size = num_channels * sample_format.sample_bytes();
 //!
 //! let (_, mut stream) = stream_source
-//!     .new_playback_stream(num_channels, 48000, buffer_size)?;
+//!     .new_playback_stream(num_channels, sample_format, 48000, buffer_size)?;
 //! // Play 10 buffers of DC.
-//! let pb_bufs = [[0xa5u8; buffer_size * frame_size]; 10];
-//! for pb_buf in &pb_bufs {
+//! let mut buf = Vec::new();
+//! buf.resize(buffer_size * frame_size, 0xa5u8);
+//! for _ in 0..10 {
 //!     let mut stream_buffer = stream.next_playback_buffer()?;
-//!     assert_eq!(stream_buffer.write(pb_buf)?, buffer_size * frame_size);
+//!     assert_eq!(stream_buffer.write(&buf)?, buffer_size * frame_size);
 //! }
 //! # Ok (())
 //! # }
 //! ```
 
+use std::cmp::min;
 use std::error;
 use std::fmt::{self, Display};
 use std::io::{self, Write};
 use std::os::unix::io::RawFd;
 use std::result::Result;
+use std::str::FromStr;
 use std::time::{Duration, Instant};
 
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub enum SampleFormat {
+    U8,
+    S16LE,
+    S24LE,
+    S32LE,
+}
+
+impl SampleFormat {
+    pub fn sample_bytes(self) -> usize {
+        use SampleFormat::*;
+        match self {
+            U8 => 1,
+            S16LE => 2,
+            S24LE => 4, // Not a typo, S24_LE samples are stored in 4 byte chunks.
+            S32LE => 4,
+        }
+    }
+}
+
+impl Display for SampleFormat {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        use SampleFormat::*;
+        match self {
+            U8 => write!(f, "Unsigned 8 bit"),
+            S16LE => write!(f, "Signed 16 bit Little Endian"),
+            S24LE => write!(f, "Signed 24 bit Little Endian"),
+            S32LE => write!(f, "Signed 32 bit Little Endian"),
+        }
+    }
+}
+
+/// Valid directions of an audio stream.
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub enum StreamDirection {
+    Playback,
+    Capture,
+}
+
+/// Valid effects for an audio stream.
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub enum StreamEffect {
+    NoEffect,
+    EchoCancellation,
+}
+
 pub mod capture;
+pub mod shm_streams;
+
+impl Default for StreamEffect {
+    fn default() -> Self {
+        StreamEffect::NoEffect
+    }
+}
+
+/// Errors that can pass across threads.
+pub type BoxError = Box<dyn error::Error + Send + Sync>;
+
+/// Errors that are possible from a `StreamEffect`.
+#[derive(Debug)]
+pub enum StreamEffectError {
+    InvalidEffect,
+}
+
+impl error::Error for StreamEffectError {}
+
+impl Display for StreamEffectError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            StreamEffectError::InvalidEffect => write!(f, "Must be in [EchoCancellation, aec]"),
+        }
+    }
+}
+
+impl FromStr for StreamEffect {
+    type Err = StreamEffectError;
+    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
+        match s {
+            "EchoCancellation" | "aec" => Ok(StreamEffect::EchoCancellation),
+            _ => Err(StreamEffectError::InvalidEffect),
+        }
+    }
+}
 
 /// `StreamSource` creates streams for playback or capture of audio.
 pub trait StreamSource: Send {
     /// Returns a stream control and buffer generator object. These are separate as the buffer
     /// generator might want to be passed to the audio stream.
+    #[allow(clippy::type_complexity)]
     fn new_playback_stream(
         &mut self,
         num_channels: usize,
-        frame_rate: usize,
+        format: SampleFormat,
+        frame_rate: u32,
         buffer_size: usize,
-    ) -> Result<(Box<dyn StreamControl>, Box<dyn PlaybackBufferStream>), Box<dyn error::Error>>;
+    ) -> Result<(Box<dyn StreamControl>, Box<dyn PlaybackBufferStream>), BoxError>;
 
     /// Returns a stream control and buffer generator object. These are separate as the buffer
     /// generator might want to be passed to the audio stream.
-    /// Default implementation returns `DummyStreamControl` and `DummyCaptureStream`.
+    /// Default implementation returns `NoopStreamControl` and `NoopCaptureStream`.
+    #[allow(clippy::type_complexity)]
     fn new_capture_stream(
         &mut self,
         num_channels: usize,
-        frame_rate: usize,
+        format: SampleFormat,
+        frame_rate: u32,
         buffer_size: usize,
     ) -> Result<
         (
             Box<dyn StreamControl>,
             Box<dyn capture::CaptureBufferStream>,
         ),
-        Box<dyn error::Error>,
+        BoxError,
     > {
         Ok((
-            Box::new(DummyStreamControl::new()),
-            Box::new(capture::DummyCaptureStream::new(
+            Box::new(NoopStreamControl::new()),
+            Box::new(capture::NoopCaptureStream::new(
                 num_channels,
+                format,
                 frame_rate,
                 buffer_size,
             )),
@@ -89,7 +180,7 @@
 
 /// `PlaybackBufferStream` provides `PlaybackBuffer`s to fill with audio samples for playback.
 pub trait PlaybackBufferStream: Send {
-    fn next_playback_buffer<'a>(&'a mut self) -> Result<PlaybackBuffer<'a>, Box<dyn error::Error>>;
+    fn next_playback_buffer(&mut self) -> Result<PlaybackBuffer, BoxError>;
 }
 
 /// `StreamControl` provides a way to set the volume and mute states of a stream. `StreamControl`
@@ -172,7 +263,10 @@
     /// Writes up to `size` bytes directly to this buffer inside of the given callback function.
     pub fn copy_cb<F: FnOnce(&mut [u8])>(&mut self, size: usize, cb: F) {
         // only write complete frames.
-        let len = size / self.buffer.frame_size * self.buffer.frame_size;
+        let len = min(
+            size / self.buffer.frame_size * self.buffer.frame_size,
+            self.buffer.buffer.len() - self.buffer.offset,
+        );
         cb(&mut self.buffer.buffer[self.buffer.offset..(self.buffer.offset + len)]);
         self.buffer.offset += len;
     }
@@ -201,51 +295,55 @@
 }
 
 /// Stream that accepts playback samples but drops them.
-pub struct DummyStream {
+pub struct NoopStream {
     buffer: Vec<u8>,
     frame_size: usize,
     interval: Duration,
     next_frame: Duration,
     start_time: Option<Instant>,
-    buffer_drop: DummyBufferDrop,
+    buffer_drop: NoopBufferDrop,
 }
 
-/// DummyStream data that is needed from the buffer complete callback.
-struct DummyBufferDrop {
+/// NoopStream data that is needed from the buffer complete callback.
+struct NoopBufferDrop {
     which_buffer: bool,
 }
 
-impl BufferDrop for DummyBufferDrop {
+impl BufferDrop for NoopBufferDrop {
     fn trigger(&mut self, _nwritten: usize) {
         // When a buffer completes, switch to the other one.
         self.which_buffer ^= true;
     }
 }
 
-impl DummyStream {
-    // TODO(allow other formats)
-    pub fn new(num_channels: usize, frame_rate: usize, buffer_size: usize) -> Self {
-        const S16LE_SIZE: usize = 2;
-        let frame_size = S16LE_SIZE * num_channels;
+impl NoopStream {
+    pub fn new(
+        num_channels: usize,
+        format: SampleFormat,
+        frame_rate: u32,
+        buffer_size: usize,
+    ) -> Self {
+        let frame_size = format.sample_bytes() * num_channels;
         let interval = Duration::from_millis(buffer_size as u64 * 1000 / frame_rate as u64);
-        DummyStream {
+        NoopStream {
             buffer: vec![0; buffer_size * frame_size],
             frame_size,
             interval,
             next_frame: interval,
             start_time: None,
-            buffer_drop: DummyBufferDrop {
+            buffer_drop: NoopBufferDrop {
                 which_buffer: false,
             },
         }
     }
 }
 
-impl PlaybackBufferStream for DummyStream {
-    fn next_playback_buffer<'a>(&'a mut self) -> Result<PlaybackBuffer<'a>, Box<dyn error::Error>> {
+impl PlaybackBufferStream for NoopStream {
+    fn next_playback_buffer(&mut self) -> Result<PlaybackBuffer, BoxError> {
         if let Some(start_time) = self.start_time {
-            if start_time.elapsed() < self.next_frame {
-                std::thread::sleep(self.next_frame - start_time.elapsed());
+            let elapsed = start_time.elapsed();
+            if elapsed < self.next_frame {
+                std::thread::sleep(self.next_frame - elapsed);
             }
             self.next_frame += self.interval;
         } else {
@@ -260,39 +358,45 @@
     }
 }
 
-/// No-op control for `DummyStream`s.
+/// No-op control for `NoopStream`s.
 #[derive(Default)]
-pub struct DummyStreamControl;
+pub struct NoopStreamControl;
 
-impl DummyStreamControl {
+impl NoopStreamControl {
     pub fn new() -> Self {
-        DummyStreamControl {}
+        NoopStreamControl {}
     }
 }
 
-impl StreamControl for DummyStreamControl {}
+impl StreamControl for NoopStreamControl {}
 
-/// Source of `DummyStream` and `DummyStreamControl` objects.
+/// Source of `NoopStream` and `NoopStreamControl` objects.
 #[derive(Default)]
-pub struct DummyStreamSource;
+pub struct NoopStreamSource;
 
-impl DummyStreamSource {
+impl NoopStreamSource {
     pub fn new() -> Self {
-        DummyStreamSource {}
+        NoopStreamSource {}
     }
 }
 
-impl StreamSource for DummyStreamSource {
+impl StreamSource for NoopStreamSource {
+    #[allow(clippy::type_complexity)]
     fn new_playback_stream(
         &mut self,
         num_channels: usize,
-        frame_rate: usize,
+        format: SampleFormat,
+        frame_rate: u32,
         buffer_size: usize,
-    ) -> Result<(Box<dyn StreamControl>, Box<dyn PlaybackBufferStream>), Box<dyn error::Error>>
-    {
+    ) -> Result<(Box<dyn StreamControl>, Box<dyn PlaybackBufferStream>), BoxError> {
         Ok((
-            Box::new(DummyStreamControl::new()),
-            Box::new(DummyStream::new(num_channels, frame_rate, buffer_size)),
+            Box::new(NoopStreamControl::new()),
+            Box::new(NoopStream::new(
+                num_channels,
+                format,
+                frame_rate,
+                buffer_size,
+            )),
         ))
     }
 }
@@ -305,7 +409,7 @@
     fn invalid_buffer_length() {
         // Playback buffers can't be created with a size that isn't divisible by the frame size.
         let mut pb_buf = [0xa5u8; 480 * 2 * 2 + 1];
-        let mut buffer_drop = DummyBufferDrop {
+        let mut buffer_drop = NoopBufferDrop {
             which_buffer: false,
         };
         assert!(PlaybackBuffer::new(2, &mut pb_buf, &mut buffer_drop).is_err());
@@ -326,15 +430,17 @@
             const FRAME_SIZE: usize = 4;
             let mut buf = [0u8; 480 * FRAME_SIZE];
             let mut pb_buf = PlaybackBuffer::new(FRAME_SIZE, &mut buf, &mut test_drop).unwrap();
-            pb_buf.write(&[0xa5u8; 480 * FRAME_SIZE]).unwrap();
+            pb_buf.write_all(&[0xa5u8; 480 * FRAME_SIZE]).unwrap();
         }
         assert_eq!(test_drop.frame_count, 480);
     }
 
     #[test]
     fn sixteen_bit_stereo() {
-        let mut server = DummyStreamSource::new();
-        let (_, mut stream) = server.new_playback_stream(2, 48000, 480).unwrap();
+        let mut server = NoopStreamSource::new();
+        let (_, mut stream) = server
+            .new_playback_stream(2, SampleFormat::S16LE, 48000, 480)
+            .unwrap();
         let mut stream_buffer = stream.next_playback_buffer().unwrap();
         assert_eq!(stream_buffer.frame_capacity(), 480);
         let pb_buf = [0xa5u8; 480 * 2 * 2];
@@ -343,8 +449,10 @@
 
     #[test]
     fn consumption_rate() {
-        let mut server = DummyStreamSource::new();
-        let (_, mut stream) = server.new_playback_stream(2, 48000, 480).unwrap();
+        let mut server = NoopStreamSource::new();
+        let (_, mut stream) = server
+            .new_playback_stream(2, SampleFormat::S16LE, 48000, 480)
+            .unwrap();
         let start = Instant::now();
         {
             let mut stream_buffer = stream.next_playback_buffer().unwrap();
diff --git a/audio_streams/src/capture.rs b/audio_streams/src/capture.rs
index 0e58dc8..6a32cf1 100644
--- a/audio_streams/src/capture.rs
+++ b/audio_streams/src/capture.rs
@@ -2,40 +2,43 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 //! ```
-//! use audio_streams::{StreamSource, DummyStreamSource};
+//! use audio_streams::{BoxError, SampleFormat, StreamSource, NoopStreamSource};
 //! use std::io::Read;
 //!
 //! const buffer_size: usize = 120;
 //! const num_channels: usize = 2;
-//! const frame_size: usize = num_channels * 2; // 16-bit samples are two bytes.
 //!
-//! # fn main() -> std::result::Result<(), Box<std::error::Error>> {
-//! let mut stream_source = DummyStreamSource::new();
+//! # fn main() -> std::result::Result<(),BoxError> {
+//! let mut stream_source = NoopStreamSource::new();
+//! let sample_format = SampleFormat::S16LE;
+//! let frame_size = num_channels * sample_format.sample_bytes();
 //!
 //! let (_, mut stream) = stream_source
-//!     .new_capture_stream(num_channels, 48000, buffer_size)?;
+//!     .new_capture_stream(num_channels, sample_format, 48000, buffer_size)?;
 //! // Capture 10 buffers of zeros.
-//! let mut cp_bufs = [[0xa5u8; buffer_size * frame_size]; 10];
-//! for cp_buf in &mut cp_bufs {
+//! let mut buf = Vec::new();
+//! buf.resize(buffer_size * frame_size, 0xa5u8);
+//! for _ in 0..10 {
 //!     let mut stream_buffer = stream.next_capture_buffer()?;
-//!     assert_eq!(stream_buffer.read(cp_buf)?, buffer_size * frame_size);
+//!     assert_eq!(stream_buffer.read(&mut buf)?, buffer_size * frame_size);
 //! }
 //! # Ok (())
 //! # }
 //! ```
 
 use std::{
+    cmp::min,
     error,
     fmt::{self, Display},
     io::{self, Read, Write},
     time::{Duration, Instant},
 };
 
-use super::{AudioBuffer, BufferDrop, DummyBufferDrop};
+use super::{AudioBuffer, BoxError, BufferDrop, NoopBufferDrop, SampleFormat};
 
 /// `CaptureBufferStream` provides `CaptureBuffer`s to read with audio samples from capture.
 pub trait CaptureBufferStream: Send {
-    fn next_capture_buffer<'a>(&'a mut self) -> Result<CaptureBuffer<'a>, Box<dyn error::Error>>;
+    fn next_capture_buffer(&mut self) -> Result<CaptureBuffer, BoxError>;
 }
 
 /// `CaptureBuffer` contains a block of audio samples got from capture stream. It provides
@@ -94,7 +97,10 @@
 
     /// Reads up to `size` bytes directly from this buffer inside of the given callback function.
     pub fn copy_cb<F: FnOnce(&[u8])>(&mut self, size: usize, cb: F) {
-        let len = size / self.buffer.frame_size * self.buffer.frame_size;
+        let len = min(
+            size / self.buffer.frame_size * self.buffer.frame_size,
+            self.buffer.buffer.len() - self.buffer.offset,
+        );
         cb(&self.buffer.buffer[self.buffer.offset..(self.buffer.offset + len)]);
         self.buffer.offset += len;
     }
@@ -117,39 +123,43 @@
 }
 
 /// Stream that provides null capture samples.
-pub struct DummyCaptureStream {
+pub struct NoopCaptureStream {
     buffer: Vec<u8>,
     frame_size: usize,
     interval: Duration,
     next_frame: Duration,
     start_time: Option<Instant>,
-    buffer_drop: DummyBufferDrop,
+    buffer_drop: NoopBufferDrop,
 }
 
-impl DummyCaptureStream {
-    // TODO(allow other formats)
-    pub fn new(num_channels: usize, frame_rate: usize, buffer_size: usize) -> Self {
-        const S16LE_SIZE: usize = 2;
-        let frame_size = S16LE_SIZE * num_channels;
+impl NoopCaptureStream {
+    pub fn new(
+        num_channels: usize,
+        format: SampleFormat,
+        frame_rate: u32,
+        buffer_size: usize,
+    ) -> Self {
+        let frame_size = format.sample_bytes() * num_channels;
         let interval = Duration::from_millis(buffer_size as u64 * 1000 / frame_rate as u64);
-        DummyCaptureStream {
+        NoopCaptureStream {
             buffer: vec![0; buffer_size * frame_size],
             frame_size,
             interval,
             next_frame: interval,
             start_time: None,
-            buffer_drop: DummyBufferDrop {
+            buffer_drop: NoopBufferDrop {
                 which_buffer: false,
             },
         }
     }
 }
 
-impl CaptureBufferStream for DummyCaptureStream {
-    fn next_capture_buffer(&mut self) -> Result<CaptureBuffer, Box<dyn error::Error>> {
+impl CaptureBufferStream for NoopCaptureStream {
+    fn next_capture_buffer(&mut self) -> Result<CaptureBuffer, BoxError> {
         if let Some(start_time) = self.start_time {
-            if start_time.elapsed() < self.next_frame {
-                std::thread::sleep(self.next_frame - start_time.elapsed());
+            let elapsed = start_time.elapsed();
+            if elapsed < self.next_frame {
+                std::thread::sleep(self.next_frame - elapsed);
             }
             self.next_frame += self.interval;
         } else {
@@ -173,7 +183,7 @@
     fn invalid_buffer_length() {
         // Capture buffers can't be created with a size that isn't divisible by the frame size.
         let mut cp_buf = [0xa5u8; 480 * 2 * 2 + 1];
-        let mut buffer_drop = DummyBufferDrop {
+        let mut buffer_drop = NoopBufferDrop {
             which_buffer: false,
         };
         assert!(CaptureBuffer::new(2, &mut cp_buf, &mut buffer_drop).is_err());
@@ -203,8 +213,10 @@
 
     #[test]
     fn sixteen_bit_stereo() {
-        let mut server = DummyStreamSource::new();
-        let (_, mut stream) = server.new_capture_stream(2, 48000, 480).unwrap();
+        let mut server = NoopStreamSource::new();
+        let (_, mut stream) = server
+            .new_capture_stream(2, SampleFormat::S16LE, 48000, 480)
+            .unwrap();
         let mut stream_buffer = stream.next_capture_buffer().unwrap();
         assert_eq!(stream_buffer.frame_capacity(), 480);
         let mut pb_buf = [0xa5u8; 480 * 2 * 2];
@@ -213,15 +225,17 @@
 
     #[test]
     fn consumption_rate() {
-        let mut server = DummyStreamSource::new();
-        let (_, mut stream) = server.new_capture_stream(2, 48000, 480).unwrap();
+        let mut server = NoopStreamSource::new();
+        let (_, mut stream) = server
+            .new_capture_stream(2, SampleFormat::S16LE, 48000, 480)
+            .unwrap();
         let start = Instant::now();
         {
             let mut stream_buffer = stream.next_capture_buffer().unwrap();
             let mut cp_buf = [0xa5u8; 480 * 2 * 2];
             assert_eq!(stream_buffer.read(&mut cp_buf).unwrap(), 480 * 2 * 2);
-            for i in 0..cp_buf.len() {
-                assert_eq!(cp_buf[i], 0, "Read samples should all be zeros.");
+            for buf in cp_buf.iter() {
+                assert_eq!(*buf, 0, "Read samples should all be zeros.");
             }
         }
         // The second call should block until the first buffer is consumed.
@@ -233,5 +247,4 @@
             elapsed.subsec_millis()
         );
     }
-
 }
diff --git a/audio_streams/src/shm_streams.rs b/audio_streams/src/shm_streams.rs
new file mode 100644
index 0000000..b11626f
--- /dev/null
+++ b/audio_streams/src/shm_streams.rs
@@ -0,0 +1,568 @@
+// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+use std::error;
+use std::fmt;
+use std::os::unix::io::RawFd;
+use std::sync::Arc;
+use std::time::{Duration, Instant};
+
+use sync::{Condvar, Mutex};
+use sys_util::SharedMemory;
+
+use crate::{BoxError, SampleFormat, StreamDirection, StreamEffect};
+
+type GenericResult<T> = std::result::Result<T, BoxError>;
+
+/// `BufferSet` is used as a callback mechanism for `ServerRequest` objects.
+/// It is meant to be implemented by the audio stream, allowing arbitrary code
+/// to be run after a buffer offset and length is set.
+pub trait BufferSet {
+    /// Called when the client sets a buffer offset and length.
+    ///
+    /// `offset` is the offset within shared memory of the buffer and `frames`
+    /// indicates the number of audio frames that can be read from or written to
+    /// the buffer.
+    fn callback(&mut self, offset: usize, frames: usize) -> GenericResult<()>;
+
+    /// Called when the client ignores a request from the server.
+    fn ignore(&mut self) -> GenericResult<()>;
+}
+
+#[derive(Debug)]
+pub enum Error {
+    TooManyFrames(usize, usize),
+}
+
+impl error::Error for Error {}
+
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            Error::TooManyFrames(provided, requested) => write!(
+                f,
+                "Provided number of frames {} exceeds requested number of frames {}",
+                provided, requested
+            ),
+        }
+    }
+}
+
+/// `ServerRequest` represents an active request from the server for the client
+/// to provide a buffer in shared memory to playback from or capture to.
+pub struct ServerRequest<'a> {
+    requested_frames: usize,
+    buffer_set: &'a mut dyn BufferSet,
+}
+
+impl<'a> ServerRequest<'a> {
+    /// Create a new ServerRequest object
+    ///
+    /// Create a ServerRequest object representing a request from the server
+    /// for a buffer `requested_frames` in size.
+    ///
+    /// When the client responds to this request by calling
+    /// [`set_buffer_offset_and_frames`](ServerRequest::set_buffer_offset_and_frames),
+    /// BufferSet::callback will be called on `buffer_set`.
+    ///
+    /// # Arguments
+    /// * `requested_frames` - The requested buffer size in frames.
+    /// * `buffer_set` - The object implementing the callback for when a buffer is provided.
+    pub fn new<D: BufferSet>(requested_frames: usize, buffer_set: &'a mut D) -> Self {
+        Self {
+            requested_frames,
+            buffer_set,
+        }
+    }
+
+    /// Get the number of frames of audio data requested by the server.
+    ///
+    /// The returned value should never be greater than the `buffer_size`
+    /// given in [`new_stream`](ShmStreamSource::new_stream).
+    pub fn requested_frames(&self) -> usize {
+        self.requested_frames
+    }
+
+    /// Sets the buffer offset and length for the requested buffer.
+    ///
+    /// Sets the buffer offset and length of the buffer that fulfills this
+    /// server request to `offset` and `length`, respectively. This means that
+    /// `length` bytes of audio samples may be read from/written to that
+    /// location in `client_shm` for a playback/capture stream, respectively.
+    /// This function may only be called once for a `ServerRequest`, at which
+    /// point the ServerRequest is dropped and no further calls are possible.
+    ///
+    /// # Arguments
+    ///
+    /// * `offset` - The value to use as the new buffer offset for the next buffer.
+    /// * `frames` - The length of the next buffer in frames.
+    ///
+    /// # Errors
+    ///
+    /// * If `frames` is greater than `requested_frames`.
+    pub fn set_buffer_offset_and_frames(self, offset: usize, frames: usize) -> GenericResult<()> {
+        if frames > self.requested_frames {
+            return Err(Box::new(Error::TooManyFrames(
+                frames,
+                self.requested_frames,
+            )));
+        }
+
+        self.buffer_set.callback(offset, frames)
+    }
+
+    /// Ignore this request
+    ///
+    /// If the client does not intend to respond to this ServerRequest with a
+    /// buffer, they should call this function. The stream will be notified that
+    /// the request has been ignored and will handle it properly.
+    pub fn ignore_request(self) -> GenericResult<()> {
+        self.buffer_set.ignore()
+    }
+}
+
+/// `ShmStream` allows a client to interact with an active CRAS stream.
+pub trait ShmStream: Send {
+    /// Get the size of a frame of audio data for this stream.
+    fn frame_size(&self) -> usize;
+
+    /// Get the number of channels of audio data for this stream.
+    fn num_channels(&self) -> usize;
+
+    /// Get the frame rate of audio data for this stream.
+    fn frame_rate(&self) -> u32;
+
+    /// Waits until the next server message indicating action is required.
+    ///
+    /// For playback streams, this will be `AUDIO_MESSAGE_REQUEST_DATA`, meaning
+    /// that we must set the buffer offset to the next location where playback
+    /// data can be found.
+    /// For capture streams, this will be `AUDIO_MESSAGE_DATA_READY`, meaning
+    /// that we must set the buffer offset to the next location where captured
+    /// data can be written to.
+    /// Will return early if `timeout` elapses before a message is received.
+    ///
+    /// # Arguments
+    ///
+    /// * `timeout` - The amount of time to wait until a message is received.
+    ///
+    /// # Return value
+    ///
+    /// Returns `Some(request)` where `request` is an object that implements the
+    /// [`ServerRequest`](ServerRequest) trait and which can be used to get the
+    /// number of bytes requested for playback streams or that have already been
+    /// written to shm for capture streams.
+    ///
+    /// If the timeout occurs before a message is received, returns `None`.
+    ///
+    /// # Errors
+    ///
+    /// * If an invalid message type is received for the stream.
+    fn wait_for_next_action_with_timeout(
+        &mut self,
+        timeout: Duration,
+    ) -> GenericResult<Option<ServerRequest>>;
+}
+
+/// `ShmStreamSource` creates streams for playback or capture of audio.
+pub trait ShmStreamSource: Send {
+    /// Creates a new [`ShmStream`](ShmStream)
+    ///
+    /// Creates a new `ShmStream` object, which allows:
+    /// * Waiting until the server has communicated that data is ready or
+    ///   requested that we make more data available.
+    /// * Setting the location and length of buffers for reading/writing audio data.
+    ///
+    /// # Arguments
+    ///
+    /// * `direction` - The direction of the stream, either `Playback` or `Capture`.
+    /// * `num_channels` - The number of audio channels for the stream.
+    /// * `format` - The audio format to use for audio samples.
+    /// * `frame_rate` - The stream's frame rate in Hz.
+    /// * `buffer_size` - The maximum size of an audio buffer. This will be the
+    ///                   size used for transfers of audio data between client
+    ///                   and server.
+    /// * `effects` - Audio effects to use for the stream, such as echo-cancellation.
+    /// * `client_shm` - The shared memory area that will contain samples.
+    /// * `buffer_offsets` - The two initial values to use as buffer offsets
+    ///                      for streams. This way, the server will not write
+    ///                      audio data to an arbitrary offset in `client_shm`
+    ///                      if the client fails to update offsets in time.
+    ///
+    /// # Errors
+    ///
+    /// * If sending the connect stream message to the server fails.
+    #[allow(clippy::too_many_arguments)]
+    fn new_stream(
+        &mut self,
+        direction: StreamDirection,
+        num_channels: usize,
+        format: SampleFormat,
+        frame_rate: u32,
+        buffer_size: usize,
+        effects: &[StreamEffect],
+        client_shm: &SharedMemory,
+        buffer_offsets: [u64; 2],
+    ) -> GenericResult<Box<dyn ShmStream>>;
+
+    /// Get a list of file descriptors used by the implementation.
+    ///
+    /// Returns any open file descriptors needed by the implementation.
+    /// This list helps users of the ShmStreamSource enter Linux jails without
+    /// closing needed file descriptors.
+    fn keep_fds(&self) -> Vec<RawFd> {
+        Vec::new()
+    }
+}
+
+/// Class that implements ShmStream trait but does nothing with the samples
+pub struct NullShmStream {
+    num_channels: usize,
+    frame_rate: u32,
+    buffer_size: usize,
+    frame_size: usize,
+    interval: Duration,
+    next_frame: Duration,
+    start_time: Instant,
+}
+
+impl NullShmStream {
+    /// Attempt to create a new NullShmStream with the given number of channels,
+    /// format, frame_rate, and buffer_size.
+    pub fn new(
+        buffer_size: usize,
+        num_channels: usize,
+        format: SampleFormat,
+        frame_rate: u32,
+    ) -> Self {
+        let interval = Duration::from_millis(buffer_size as u64 * 1000 / frame_rate as u64);
+        Self {
+            num_channels,
+            frame_rate,
+            buffer_size,
+            frame_size: format.sample_bytes() * num_channels,
+            interval,
+            next_frame: interval,
+            start_time: Instant::now(),
+        }
+    }
+}
+
+impl BufferSet for NullShmStream {
+    fn callback(&mut self, _offset: usize, _frames: usize) -> GenericResult<()> {
+        Ok(())
+    }
+
+    fn ignore(&mut self) -> GenericResult<()> {
+        Ok(())
+    }
+}
+
+impl ShmStream for NullShmStream {
+    fn frame_size(&self) -> usize {
+        self.frame_size
+    }
+
+    fn num_channels(&self) -> usize {
+        self.num_channels
+    }
+
+    fn frame_rate(&self) -> u32 {
+        self.frame_rate
+    }
+
+    fn wait_for_next_action_with_timeout(
+        &mut self,
+        timeout: Duration,
+    ) -> GenericResult<Option<ServerRequest>> {
+        let elapsed = self.start_time.elapsed();
+        if elapsed < self.next_frame {
+            if timeout < self.next_frame - elapsed {
+                std::thread::sleep(timeout);
+                return Ok(None);
+            } else {
+                std::thread::sleep(self.next_frame - elapsed);
+            }
+        }
+        self.next_frame += self.interval;
+        Ok(Some(ServerRequest::new(self.buffer_size, self)))
+    }
+}
+
+/// Source of `NullShmStream` objects.
+#[derive(Default)]
+pub struct NullShmStreamSource;
+
+impl NullShmStreamSource {
+    pub fn new() -> Self {
+        Self::default()
+    }
+}
+
+impl ShmStreamSource for NullShmStreamSource {
+    fn new_stream(
+        &mut self,
+        _direction: StreamDirection,
+        num_channels: usize,
+        format: SampleFormat,
+        frame_rate: u32,
+        buffer_size: usize,
+        _effects: &[StreamEffect],
+        _client_shm: &SharedMemory,
+        _buffer_offsets: [u64; 2],
+    ) -> GenericResult<Box<dyn ShmStream>> {
+        let new_stream = NullShmStream::new(buffer_size, num_channels, format, frame_rate);
+        Ok(Box::new(new_stream))
+    }
+}
+
+#[derive(Clone)]
+pub struct MockShmStream {
+    num_channels: usize,
+    frame_rate: u32,
+    request_size: usize,
+    frame_size: usize,
+    request_notifier: Arc<(Mutex<bool>, Condvar)>,
+}
+
+impl MockShmStream {
+    /// Attempt to create a new MockShmStream with the given number of
+    /// channels, frame_rate, format, and buffer_size.
+    pub fn new(
+        num_channels: usize,
+        frame_rate: u32,
+        format: SampleFormat,
+        buffer_size: usize,
+    ) -> Self {
+        Self {
+            num_channels,
+            frame_rate,
+            request_size: buffer_size,
+            frame_size: format.sample_bytes() * num_channels,
+            request_notifier: Arc::new((Mutex::new(false), Condvar::new())),
+        }
+    }
+
+    /// Call to request data from the stream, causing it to return from
+    /// `wait_for_next_action_with_timeout`. Will block until
+    /// `set_buffer_offset_and_frames` is called on the ServerRequest returned
+    /// from `wait_for_next_action_with_timeout`, or until `timeout` elapses.
+    /// Returns true if a response was successfully received.
+    pub fn trigger_callback_with_timeout(&mut self, timeout: Duration) -> bool {
+        let &(ref lock, ref cvar) = &*self.request_notifier;
+        let mut requested = lock.lock();
+        *requested = true;
+        cvar.notify_one();
+        let start_time = Instant::now();
+        while *requested {
+            requested = cvar.wait_timeout(requested, timeout).0;
+            if start_time.elapsed() > timeout {
+                // We failed to get a callback in time, mark this as false.
+                *requested = false;
+                return false;
+            }
+        }
+
+        true
+    }
+
+    fn notify_request(&mut self) {
+        let &(ref lock, ref cvar) = &*self.request_notifier;
+        let mut requested = lock.lock();
+        *requested = false;
+        cvar.notify_one();
+    }
+}
+
+impl BufferSet for MockShmStream {
+    fn callback(&mut self, _offset: usize, _frames: usize) -> GenericResult<()> {
+        self.notify_request();
+        Ok(())
+    }
+
+    fn ignore(&mut self) -> GenericResult<()> {
+        self.notify_request();
+        Ok(())
+    }
+}
+
+impl ShmStream for MockShmStream {
+    fn frame_size(&self) -> usize {
+        self.frame_size
+    }
+
+    fn num_channels(&self) -> usize {
+        self.num_channels
+    }
+
+    fn frame_rate(&self) -> u32 {
+        self.frame_rate
+    }
+
+    fn wait_for_next_action_with_timeout(
+        &mut self,
+        timeout: Duration,
+    ) -> GenericResult<Option<ServerRequest>> {
+        {
+            let start_time = Instant::now();
+            let &(ref lock, ref cvar) = &*self.request_notifier;
+            let mut requested = lock.lock();
+            while !*requested {
+                requested = cvar.wait_timeout(requested, timeout).0;
+                if start_time.elapsed() > timeout {
+                    return Ok(None);
+                }
+            }
+        }
+
+        Ok(Some(ServerRequest::new(self.request_size, self)))
+    }
+}
+
+/// Source of `MockShmStream` objects.
+#[derive(Clone, Default)]
+pub struct MockShmStreamSource {
+    last_stream: Arc<(Mutex<Option<MockShmStream>>, Condvar)>,
+}
+
+impl MockShmStreamSource {
+    pub fn new() -> Self {
+        Default::default()
+    }
+
+    /// Get the last stream that has been created from this source. If no stream
+    /// has been created, block until one has.
+    pub fn get_last_stream(&self) -> MockShmStream {
+        let &(ref last_stream, ref cvar) = &*self.last_stream;
+        let mut stream = last_stream.lock();
+        loop {
+            match &*stream {
+                None => stream = cvar.wait(stream),
+                Some(ref s) => return s.clone(),
+            };
+        }
+    }
+}
+
+impl ShmStreamSource for MockShmStreamSource {
+    fn new_stream(
+        &mut self,
+        _direction: StreamDirection,
+        num_channels: usize,
+        format: SampleFormat,
+        frame_rate: u32,
+        buffer_size: usize,
+        _effects: &[StreamEffect],
+        _client_shm: &SharedMemory,
+        _buffer_offsets: [u64; 2],
+    ) -> GenericResult<Box<dyn ShmStream>> {
+        let &(ref last_stream, ref cvar) = &*self.last_stream;
+        let mut stream = last_stream.lock();
+
+        let new_stream = MockShmStream::new(num_channels, frame_rate, format, buffer_size);
+        *stream = Some(new_stream.clone());
+        cvar.notify_one();
+        Ok(Box::new(new_stream))
+    }
+}
+
+#[cfg(test)]
+pub mod tests {
+    use super::*;
+
+    #[test]
+    fn mock_trigger_callback() {
+        let stream_source = MockShmStreamSource::new();
+        let mut thread_stream_source = stream_source.clone();
+
+        let buffer_size = 480;
+        let num_channels = 2;
+        let format = SampleFormat::S24LE;
+        let shm = SharedMemory::anon().expect("Failed to create shm");
+
+        let handle = std::thread::spawn(move || {
+            let mut stream = thread_stream_source
+                .new_stream(
+                    StreamDirection::Playback,
+                    num_channels,
+                    format,
+                    44100,
+                    buffer_size,
+                    &[],
+                    &shm,
+                    [400, 8000],
+                )
+                .expect("Failed to create stream");
+
+            let request = stream
+                .wait_for_next_action_with_timeout(Duration::from_secs(5))
+                .expect("Failed to wait for next action");
+            match request {
+                Some(r) => {
+                    let requested = r.requested_frames();
+                    r.set_buffer_offset_and_frames(872, requested)
+                        .expect("Failed to set buffer offset and frames");
+                    requested
+                }
+                None => 0,
+            }
+        });
+
+        let mut stream = stream_source.get_last_stream();
+        assert!(stream.trigger_callback_with_timeout(Duration::from_secs(1)));
+
+        let requested_frames = handle.join().expect("Failed to join thread");
+        assert_eq!(requested_frames, buffer_size);
+    }
+
+    #[test]
+    fn null_consumption_rate() {
+        let frame_rate = 44100;
+        let buffer_size = 480;
+        let interval = Duration::from_millis(buffer_size as u64 * 1000 / frame_rate as u64);
+
+        let shm = SharedMemory::anon().expect("Failed to create shm");
+
+        let start = Instant::now();
+
+        let mut stream_source = NullShmStreamSource::new();
+        let mut stream = stream_source
+            .new_stream(
+                StreamDirection::Playback,
+                2,
+                SampleFormat::S24LE,
+                frame_rate,
+                buffer_size,
+                &[],
+                &shm,
+                [400, 8000],
+            )
+            .expect("Failed to create stream");
+
+        let timeout = Duration::from_secs(5);
+        let request = stream
+            .wait_for_next_action_with_timeout(timeout)
+            .expect("Failed to wait for first request")
+            .expect("First request should not have timed out");
+        request
+            .set_buffer_offset_and_frames(276, 480)
+            .expect("Failed to set buffer offset and length");
+
+        // The second call should block until the first buffer is consumed.
+        let _request = stream
+            .wait_for_next_action_with_timeout(timeout)
+            .expect("Failed to wait for second request");
+        let elapsed = start.elapsed();
+        assert!(
+            elapsed > interval,
+            "wait_for_next_action_with_timeout didn't block long enough: {:?}",
+            elapsed
+        );
+
+        assert!(
+            elapsed < timeout,
+            "wait_for_next_action_with_timeout blocked for too long: {:?}",
+            elapsed
+        );
+    }
+}
diff --git a/cras-config/bolt/HDA Intel PCH b/cras-config/bolt/HDA Intel PCH
deleted file mode 100644
index b3865e6..0000000
--- a/cras-config/bolt/HDA Intel PCH
+++ /dev/null
@@ -1,107 +0,0 @@
-[Default]
-  volume_curve = explicit
-  db_at_100 = 0
-  db_at_99 = -100
-  db_at_98 = -100
-  db_at_97 = -100
-  db_at_96 = -100
-  db_at_95 = -200
-  db_at_94 = -200
-  db_at_93 = -200
-  db_at_92 = -200
-  db_at_91 = -300
-  db_at_90 = -300
-  db_at_89 = -300
-  db_at_88 = -300
-  db_at_87 = -400
-  db_at_86 = -400
-  db_at_85 = -400
-  db_at_84 = -400
-  db_at_83 = -500
-  db_at_82 = -500
-  db_at_81 = -500
-  db_at_80 = -500
-  db_at_79 = -600
-  db_at_78 = -600
-  db_at_77 = -600
-  db_at_76 = -600
-  db_at_75 = -700
-  db_at_74 = -700
-  db_at_73 = -700
-  db_at_72 = -700
-  db_at_71 = -800
-  db_at_70 = -800
-  db_at_69 = -800
-  db_at_68 = -800
-  db_at_67 = -900
-  db_at_66 = -900
-  db_at_65 = -900
-  db_at_64 = -900
-  db_at_63 = -900
-  db_at_62 = -900
-  db_at_61 = -900
-  db_at_60 = -1000
-  db_at_59 = -1000
-  db_at_58 = -1000
-  db_at_57 = -1000
-  db_at_56 = -1100
-  db_at_55 = -1100
-  db_at_54 = -1100
-  db_at_53 = -1100
-  db_at_52 = -1200
-  db_at_51 = -1200
-  db_at_50 = -1200
-  db_at_49 = -1200
-  db_at_48 = -1300
-  db_at_47 = -1300
-  db_at_46 = -1300
-  db_at_45 = -1300
-  db_at_44 = -1400
-  db_at_43 = -1400
-  db_at_42 = -1400
-  db_at_41 = -1400
-  db_at_40 = -1500
-  db_at_39 = -1600
-  db_at_38 = -1600
-  db_at_37 = -1700
-  db_at_36 = -1700
-  db_at_35 = -1800
-  db_at_34 = -1900
-  db_at_33 = -2000
-  db_at_32 = -2100
-  db_at_31 = -2200
-  db_at_30 = -2300
-  db_at_29 = -2400
-  db_at_28 = -2500
-  db_at_27 = -2600
-  db_at_26 = -2700
-  db_at_25 = -2800
-  db_at_24 = -2900
-  db_at_23 = -3000
-  db_at_22 = -3100
-  db_at_21 = -3200
-  db_at_20 = -3300
-  db_at_19 = -3400
-  db_at_18 = -3500
-  db_at_17 = -3600
-  db_at_16 = -3700
-  db_at_15 = -3800
-  db_at_14 = -3900
-  db_at_13 = -4000
-  db_at_12 = -4100
-  db_at_11 = -4200
-  db_at_10 = -4300
-  db_at_9 = -4400
-  db_at_8 = -4500
-  db_at_7 = -4600
-  db_at_6 = -4800
-  db_at_5 = -5000
-  db_at_4 = -5200
-  db_at_3 = -5400
-  db_at_2 = -5600
-  db_at_1 = -5800
-  db_at_0 = -6000
-[Front Headphone Jack]
-  volume_curve = simple_step
-  volume_step = 75
-  max_volume = -900
diff --git a/cras-config/cid/HDA Intel PCH b/cras-config/cid/HDA Intel PCH
deleted file mode 100644
index e0adc3e..0000000
--- a/cras-config/cid/HDA Intel PCH
+++ /dev/null
@@ -1,107 +0,0 @@
-[Speaker]
-  volume_curve = explicit
-  db_at_100 = 0
-  db_at_99 = -75
-  db_at_98 = -75
-  db_at_97 = -75
-  db_at_96 = -75
-  db_at_95 = -75
-  db_at_94 = -150
-  db_at_93 = -150
-  db_at_92 = -150
-  db_at_91 = -150
-  db_at_90 = -225
-  db_at_89 = -225
-  db_at_88 = -225
-  db_at_87 = -225
-  db_at_86 = -300
-  db_at_85 = -300
-  db_at_84 = -300
-  db_at_83 = -300
-  db_at_82 = -375
-  db_at_81 = -375
-  db_at_80 = -450
-  db_at_79 = -450
-  db_at_78 = -450
-  db_at_77 = -450
-  db_at_76 = -525
-  db_at_75 = -525
-  db_at_74 = -600
-  db_at_73 = -600
-  db_at_72 = -600
-  db_at_71 = -600
-  db_at_70 = -675
-  db_at_69 = -675
-  db_at_68 = -675
-  db_at_67 = -675
-  db_at_66 = -750
-  db_at_65 = -750
-  db_at_64 = -825
-  db_at_63 = -825
-  db_at_62 = -825
-  db_at_61 = -825
-  db_at_60 = -900
-  db_at_59 = -900
-  db_at_58 = -900
-  db_at_57 = -900
-  db_at_56 = -975
-  db_at_55 = -975
-  db_at_54 = -1050
-  db_at_53 = -1050
-  db_at_52 = -1050
-  db_at_51 = -1050
-  db_at_50 = -1125
-  db_at_49 = -1125
-  db_at_48 = -1200
-  db_at_47 = -1200
-  db_at_46 = -1200
-  db_at_45 = -1200
-  db_at_44 = -1275
-  db_at_43 = -1275
-  db_at_42 = -1275
-  db_at_41 = -1275
-  db_at_40 = -1350
-  db_at_39 = -1425
-  db_at_38 = -1425
-  db_at_37 = -1500
-  db_at_36 = -1500
-  db_at_35 = -1575
-  db_at_34 = -1650
-  db_at_33 = -1650
-  db_at_32 = -1725
-  db_at_31 = -1800
-  db_at_30 = -1800
-  db_at_29 = -1800
-  db_at_28 = -1875
-  db_at_27 = -2025
-  db_at_26 = -2100
-  db_at_25 = -2100
-  db_at_24 = -2175
-  db_at_23 = -2250
-  db_at_22 = -2400
-  db_at_21 = -2475
-  db_at_20 = -2550
-  db_at_19 = -2625
-  db_at_18 = -2700
-  db_at_17 = -2850
-  db_at_16 = -2925
-  db_at_15 = -3000
-  db_at_14 = -3075
-  db_at_13 = -3225
-  db_at_12 = -3300
-  db_at_11 = -3375
-  db_at_10 = -3450
-  db_at_9 = -3600
-  db_at_8 = -3675
-  db_at_7 = -3750
-  db_at_6 = -3825
-  db_at_5 = -3900
-  db_at_4 = -4050
-  db_at_3 = -4050
-  db_at_2 = -4275
-  db_at_1 = -4500
-  db_at_0 = -4800
-[Headphone]
-  volume_curve = simple_step
-  volume_step = 50
-  max_volume = 0
diff --git a/cras-config/daisy/DAISY-I2S b/cras-config/daisy/DAISY-I2S
deleted file mode 100755
index 949658d..0000000
--- a/cras-config/daisy/DAISY-I2S
+++ /dev/null
@@ -1,107 +0,0 @@
-[Speaker]
-  volume_curve = explicit
-  db_at_100 = -900
-  db_at_99 = -900
-  db_at_98 = -900
-  db_at_97 = -950
-  db_at_96 = -950
-  db_at_95 = -950
-  db_at_94 = -1000
-  db_at_93 = -1000
-  db_at_92 = -1000
-  db_at_91 = -1050
-  db_at_90 = -1050
-  db_at_89 = -1050
-  db_at_88 = -1100
-  db_at_87 = -1100
-  db_at_86 = -1100
-  db_at_85 = -1150
-  db_at_84 = -1150
-  db_at_83 = -1150
-  db_at_82 = -1200
-  db_at_81 = -1200
-  db_at_80 = -1200
-  db_at_79 = -1250
-  db_at_78 = -1250
-  db_at_77 = -1250
-  db_at_76 = -1300
-  db_at_75 = -1300
-  db_at_74 = -1300
-  db_at_73 = -1350
-  db_at_72 = -1350
-  db_at_71 = -1350
-  db_at_70 = -1425
-  db_at_69 = -1425
-  db_at_68 = -1425
-  db_at_67 = -1500
-  db_at_66 = -1500
-  db_at_65 = -1500
-  db_at_64 = -1575
-  db_at_63 = -1575
-  db_at_62 = -1575
-  db_at_61 = -1650
-  db_at_60 = -1650
-  db_at_59 = -1650
-  db_at_58 = -1725
-  db_at_57 = -1725
-  db_at_56 = -1725
-  db_at_55 = -1800
-  db_at_54 = -1800
-  db_at_53 = -1800
-  db_at_52 = -1900
-  db_at_51 = -1900
-  db_at_50 = -1900
-  db_at_49 = -2000
-  db_at_48 = -2000
-  db_at_47 = -2000
-  db_at_46 = -2100
-  db_at_45 = -2100
-  db_at_44 = -2100
-  db_at_43 = -2200
-  db_at_42 = -2200
-  db_at_41 = -2200
-  db_at_40 = -2350
-  db_at_39 = -2350
-  db_at_38 = -2350
-  db_at_37 = -2500
-  db_at_36 = -2500
-  db_at_35 = -2500
-  db_at_34 = -2650
-  db_at_33 = -2650
-  db_at_32 = -2650
-  db_at_31 = -2725
-  db_at_30 = -2725
-  db_at_29 = -2800
-  db_at_28 = -3000
-  db_at_27 = -3000
-  db_at_26 = -3000
-  db_at_25 = -3200
-  db_at_24 = -3200
-  db_at_23 = -3200
-  db_at_22 = -3200
-  db_at_21 = -3600
-  db_at_20 = -3600
-  db_at_19 = -4000
-  db_at_18 = -4000
-  db_at_17 = -4000
-  db_at_16 = -4000
-  db_at_15 = -4400
-  db_at_14 = -4400
-  db_at_13 = -4400
-  db_at_12 = -4400
-  db_at_11 = -4800
-  db_at_10 = -4800
-  db_at_9 = -4800
-  db_at_8 = -4800
-  db_at_7 = -5000
-  db_at_6 = -5000
-  db_at_5 = -5200
-  db_at_4 = -5200
-  db_at_3 = -5200
-  db_at_2 = -5200
-  db_at_1 = -5600
-  db_at_0 = -6000
-[Headphone]
-  volume_curve = simple_step ; Headphones allowed full range, 0.5dB per step, 50dB total range.
-  volume_step = 50
-  max_volume = -200
diff --git a/cras-config/daisy/DAISY-I2S-98090 b/cras-config/daisy/DAISY-I2S-98090
deleted file mode 100755
index f642023..0000000
--- a/cras-config/daisy/DAISY-I2S-98090
+++ /dev/null
@@ -1,206 +0,0 @@
-[Speaker]
-  volume_curve = explicit
-  db_at_100 = -500
-  db_at_99 = -500
-  db_at_98 = -500
-  db_at_97 = -500
-  db_at_96 = -600
-  db_at_95 = -600
-  db_at_94 = -600
-  db_at_93 = -600
-  db_at_92 = -700
-  db_at_91 = -700
-  db_at_90 = -700
-  db_at_89 = -700
-  db_at_88 = -800
-  db_at_87 = -800
-  db_at_86 = -800
-  db_at_85 = -800
-  db_at_84 = -900
-  db_at_83 = -900
-  db_at_82 = -900
-  db_at_81 = -900
-  db_at_80 = -1000
-  db_at_79 = -1000
-  db_at_78 = -1000
-  db_at_77 = -1000
-  db_at_76 = -1100
-  db_at_75 = -1100
-  db_at_74 = -1100
-  db_at_73 = -1100
-  db_at_72 = -1200
-  db_at_71 = -1200
-  db_at_70 = -1200
-  db_at_69 = -1200
-  db_at_68 = -1300
-  db_at_67 = -1300
-  db_at_66 = -1300
-  db_at_65 = -1300
-  db_at_64 = -1400
-  db_at_63 = -1400
-  db_at_62 = -1400
-  db_at_61 = -1400
-  db_at_60 = -1500
-  db_at_59 = -1500
-  db_at_58 = -1500
-  db_at_57 = -1500
-  db_at_56 = -1600
-  db_at_55 = -1600
-  db_at_54 = -1600
-  db_at_53 = -1600
-  db_at_52 = -1700
-  db_at_51 = -1700
-  db_at_50 = -1700
-  db_at_49 = -1700
-  db_at_48 = -1800
-  db_at_47 = -1800
-  db_at_46 = -1800
-  db_at_45 = -1800
-  db_at_44 = -1900
-  db_at_43 = -1900
-  db_at_42 = -1900
-  db_at_41 = -1900
-  db_at_40 = -2100
-  db_at_39 = -2100
-  db_at_38 = -2100
-  db_at_37 = -2100
-  db_at_36 = -2300
-  db_at_35 = -2300
-  db_at_34 = -2300
-  db_at_33 = -2300
-  db_at_32 = -2500
-  db_at_31 = -2500
-  db_at_30 = -2500
-  db_at_29 = -2500
-  db_at_28 = -2700
-  db_at_27 = -2700
-  db_at_26 = -2700
-  db_at_25 = -2700
-  db_at_24 = -2900
-  db_at_23 = -2900
-  db_at_22 = -2900
-  db_at_21 = -2900
-  db_at_20 = -3300
-  db_at_19 = -3300
-  db_at_18 = -3300
-  db_at_17 = -3300
-  db_at_16 = -3600
-  db_at_15 = -3600
-  db_at_14 = -3600
-  db_at_13 = -3600
-  db_at_12 = -4300
-  db_at_11 = -4300
-  db_at_10 = -4300
-  db_at_9 = -4300
-  db_at_8 = -4900
-  db_at_7 = -4900
-  db_at_6 = -4900
-  db_at_5 = -4900
-  db_at_4 = -5500
-  db_at_3 = -5500
-  db_at_2 = -5500
-  db_at_1 = -5500
-  db_at_0 = -6500
-[Headphone]
-  volume_curve = explicit
-  db_at_100 = -1000
-  db_at_99 = -1000
-  db_at_98 = -1000
-  db_at_97 = -1000
-  db_at_96 = -1200
-  db_at_95 = -1200
-  db_at_94 = -1200
-  db_at_93 = -1200
-  db_at_92 = -1400
-  db_at_91 = -1400
-  db_at_90 = -1400
-  db_at_89 = -1400
-  db_at_88 = -1600
-  db_at_87 = -1600
-  db_at_86 = -1600
-  db_at_85 = -1600
-  db_at_84 = -1800
-  db_at_83 = -1800
-  db_at_82 = -1800
-  db_at_81 = -1800
-  db_at_80 = -2000
-  db_at_79 = -2000
-  db_at_78 = -2000
-  db_at_77 = -2000
-  db_at_76 = -2200
-  db_at_75 = -2200
-  db_at_74 = -2200
-  db_at_73 = -2200
-  db_at_72 = -2300
-  db_at_71 = -2300
-  db_at_70 = -2300
-  db_at_69 = -2300
-  db_at_68 = -2500
-  db_at_67 = -2500
-  db_at_66 = -2500
-  db_at_65 = -2500
-  db_at_64 = -2700
-  db_at_63 = -2700
-  db_at_62 = -2700
-  db_at_61 = -2700
-  db_at_60 = -2900
-  db_at_59 = -2900
-  db_at_58 = -2900
-  db_at_57 = -2900
-  db_at_56 = -3100
-  db_at_55 = -3100
-  db_at_54 = -3100
-  db_at_53 = -3100
-  db_at_52 = -3300
-  db_at_51 = -3300
-  db_at_50 = -3300
-  db_at_49 = -3300
-  db_at_48 = -3500
-  db_at_47 = -3500
-  db_at_46 = -3500
-  db_at_45 = -3500
-  db_at_44 = -3700
-  db_at_43 = -3700
-  db_at_42 = -3700
-  db_at_41 = -3700
-  db_at_40 = -4000
-  db_at_39 = -4000
-  db_at_38 = -4000
-  db_at_37 = -4000
-  db_at_36 = -4300
-  db_at_35 = -4300
-  db_at_34 = -4300
-  db_at_33 = -4300
-  db_at_32 = -4600
-  db_at_31 = -4600
-  db_at_30 = -4600
-  db_at_29 = -4600
-  db_at_28 = -4900
-  db_at_27 = -4900
-  db_at_26 = -4900
-  db_at_25 = -4900
-  db_at_24 = -5200
-  db_at_23 = -5200
-  db_at_22 = -5200
-  db_at_21 = -5200
-  db_at_20 = -5500
-  db_at_19 = -5500
-  db_at_18 = -5500
-  db_at_17 = -5500
-  db_at_16 = -5800
-  db_at_15 = -5800
-  db_at_14 = -5800
-  db_at_13 = -5800
-  db_at_12 = -6200
-  db_at_11 = -6200
-  db_at_10 = -6200
-  db_at_9 = -6200
-  db_at_8 = -6600
-  db_at_7 = -6600
-  db_at_6 = -6600
-  db_at_5 = -6600
-  db_at_4 = -7000
-  db_at_3 = -7000
-  db_at_2 = -7000
-  db_at_1 = -7000
-  db_at_0 = -7400
diff --git a/cras-config/daisy/dsp.ini b/cras-config/daisy/dsp.ini
deleted file mode 100644
index e675d55..0000000
--- a/cras-config/daisy/dsp.ini
+++ /dev/null
@@ -1,46 +0,0 @@
-[output_source]
-library=builtin
-label=source
-purpose=playback
-disable=(not (equal? dsp_name "speaker_eq"))
-output_0={src:0}
-output_1={src:1}
-
-[output_sink]
-library=builtin
-label=sink
-purpose=playback
-input_0={dst:0}
-input_1={dst:1}
-
-[eq2]
-library=builtin
-label=eq2
-input_0={src:0}
-input_1={src:1}
-output_2={dst:0}
-output_3={dst:1}
-input_4=2       ; highpass
-input_5=150     ; freq
-input_6=0       ; Q
-input_7=0       ; gain
-input_8=2       ; highpass
-input_9=150     ; freq
-input_10=0       ; Q
-input_11=0       ; gain
-input_12=6       ; peaking
-input_13=1051    ; freq
-input_14=2       ; Q
-input_15=-3      ; gain
-input_16=6       ; peaking
-input_17=1051    ; freq
-input_18=2       ; Q
-input_19=-3      ; gain
-input_20=6       ; peaking
-input_21=4146    ; freq
-input_22=3       ; Q
-input_23=-3      ; gain
-input_24=6       ; peaking
-input_25=4146    ; freq
-input_26=3       ; Q
-input_27=-3      ; gain
diff --git a/cras-config/daisy_skate/dsp.ini b/cras-config/daisy_skate/dsp.ini
deleted file mode 100644
index 7c94fc0..0000000
--- a/cras-config/daisy_skate/dsp.ini
+++ /dev/null
@@ -1,111 +0,0 @@
-[output_source]
-library=builtin
-label=source
-purpose=playback
-disable=(not (equal? dsp_name "speaker_eq"))
-output_0={src:0}
-output_1={src:1}
-
-[output_sink]
-library=builtin
-label=sink
-purpose=playback
-input_0={dst:0}
-input_1={dst:1}
-
-[drc]
-library=builtin
-label=drc
-input_0={src:0}
-input_1={src:1}
-output_2={intermediate:0}
-output_3={intermediate:1}
-input_4=1         ; emphasis_disabled
-input_5=0         ; f
-input_6=1         ; enable
-input_7=-24       ; threshold
-input_8=30        ; knee
-input_9=12        ; ratio
-input_10=0.003     ; attack
-input_11=0.25      ; release
-input_12=2         ; boost
-input_13=200       ; f
-input_14=1         ; enable
-input_15=-24       ; threshold
-input_16=30        ; knee
-input_17=12        ; ratio
-input_18=0.003     ; attack
-input_19=0.25      ; release
-input_20=2         ; boost
-input_21=2000      ; f
-input_22=1         ; enable
-input_23=-24       ; threshold
-input_24=30        ; knee
-input_25=12        ; ratio
-input_26=0.003     ; attack
-input_27=0.25      ; release
-input_28=2         ; boost
-
-[eq2]
-library=builtin
-label=eq2
-input_0={intermediate:0}
-input_1={intermediate:1}
-output_2={dst:0}
-output_3={dst:1}
-input_4=2       ; highpass
-input_5=200     ; freq
-input_6=0       ; Q
-input_7=0       ; gain
-input_8=2       ; highpass
-input_9=200     ; freq
-input_10=0       ; Q
-input_11=0       ; gain
-input_12=6       ; peaking
-input_13=350     ; freq
-input_14=1       ; Q
-input_15=3       ; gain
-input_16=6       ; peaking
-input_17=350     ; freq
-input_18=1       ; Q
-input_19=3       ; gain
-input_20=6       ; peaking
-input_21=1000    ; freq
-input_22=1       ; Q
-input_23=-8      ; gain
-input_24=6       ; peaking
-input_25=1000    ; freq
-input_26=1       ; Q
-input_27=-8      ; gain
-input_28=6       ; peaking
-input_29=5000    ; freq
-input_30=1       ; Q
-input_31=3       ; gain
-input_32=6       ; peaking
-input_33=5000    ; freq
-input_34=1       ; Q
-input_35=3       ; gain
-input_36=6       ; peaking
-input_37=8000    ; freq
-input_38=1       ; Q
-input_39=3       ; gain
-input_40=6       ; peaking
-input_41=8000    ; freq
-input_42=1       ; Q
-input_43=3       ; gain
-input_44=6       ; peaking
-input_45=10000   ; freq
-input_46=1       ; Q
-input_47=3       ; gain
-input_48=6       ; peaking
-input_49=10000   ; freq
-input_50=1       ; Q
-input_51=3       ; gain
-input_52=6       ; peaking
-input_53=2000    ; freq
-input_54=1       ; Q
-input_55=-2      ; gain
-input_56=6       ; peaking
-input_57=2000    ; freq
-input_58=1       ; Q
-input_59=-2      ; gain
diff --git a/cras-config/daisy_spring/DAISY-I2S b/cras-config/daisy_spring/DAISY-I2S
deleted file mode 100755
index ae9801a..0000000
--- a/cras-config/daisy_spring/DAISY-I2S
+++ /dev/null
@@ -1,206 +0,0 @@
-[Speaker]
-  volume_curve = explicit
-  db_at_100 = 0
-  db_at_99 = -100
-  db_at_98 = -100
-  db_at_97 = -100
-  db_at_96 = -100
-  db_at_95 = -200
-  db_at_94 = -200
-  db_at_93 = -200
-  db_at_92 = -200
-  db_at_91 = -300
-  db_at_90 = -300
-  db_at_89 = -300
-  db_at_88 = -300
-  db_at_87 = -400
-  db_at_86 = -400
-  db_at_85 = -400
-  db_at_84 = -400
-  db_at_83 = -500
-  db_at_82 = -500
-  db_at_81 = -500
-  db_at_80 = -500
-  db_at_79 = -600
-  db_at_78 = -600
-  db_at_77 = -600
-  db_at_76 = -600
-  db_at_75 = -700
-  db_at_74 = -700
-  db_at_73 = -700
-  db_at_72 = -700
-  db_at_71 = -800
-  db_at_70 = -800
-  db_at_69 = -800
-  db_at_68 = -800
-  db_at_67 = -1000
-  db_at_66 = -1000
-  db_at_65 = -1000
-  db_at_64 = -1000
-  db_at_63 = -1000
-  db_at_62 = -1000
-  db_at_61 = -1000
-  db_at_60 = -1000
-  db_at_59 = -1200
-  db_at_58 = -1200
-  db_at_57 = -1200
-  db_at_56 = -1200
-  db_at_55 = -1200
-  db_at_54 = -1200
-  db_at_53 = -1200
-  db_at_52 = -1400
-  db_at_51 = -1400
-  db_at_50 = -1400
-  db_at_49 = -1400
-  db_at_48 = -1400
-  db_at_47 = -1400
-  db_at_46 = -1400
-  db_at_45 = -1400
-  db_at_44 = -1600
-  db_at_43 = -1600
-  db_at_42 = -1600
-  db_at_41 = -1600
-  db_at_40 = -1600
-  db_at_39 = -1600
-  db_at_38 = -1600
-  db_at_37 = -1600
-  db_at_36 = -1800
-  db_at_35 = -1800
-  db_at_34 = -1800
-  db_at_33 = -2000
-  db_at_32 = -2000
-  db_at_31 = -2200
-  db_at_30 = -2200
-  db_at_29 = -2500
-  db_at_28 = -2500
-  db_at_27 = -2500
-  db_at_26 = -2500
-  db_at_25 = -2800
-  db_at_24 = -2800
-  db_at_23 = -2800
-  db_at_22 = -3100
-  db_at_21 = -3100
-  db_at_20 = -3100
-  db_at_19 = -3400
-  db_at_18 = -3400
-  db_at_17 = -3400
-  db_at_16 = -3400
-  db_at_15 = -3700
-  db_at_14 = -3700
-  db_at_13 = -4000
-  db_at_12 = -4000
-  db_at_11 = -4300
-  db_at_10 = -4300
-  db_at_9 = -4600
-  db_at_8 = -4600
-  db_at_7 = -4600
-  db_at_6 = -5000
-  db_at_5 = -5000
-  db_at_4 = -5400
-  db_at_3 = -5400
-  db_at_2 = -5800
-  db_at_1 = -5800
-  db_at_0 = -6200
-[Headphone]
-  volume_curve = explicit
-  db_at_100 = -300
-  db_at_99 = -300
-  db_at_98 = -300
-  db_at_97 = -300
-  db_at_96 = -400
-  db_at_95 = -400
-  db_at_94 = -400
-  db_at_93 = -400
-  db_at_92 = -600
-  db_at_91 = -600
-  db_at_90 = -600
-  db_at_89 = -600
-  db_at_88 = -700
-  db_at_87 = -700
-  db_at_86 = -700
-  db_at_85 = -700
-  db_at_84 = -800
-  db_at_83 = -800
-  db_at_82 = -800
-  db_at_81 = -800
-  db_at_80 = -1000
-  db_at_79 = -1000
-  db_at_78 = -1000
-  db_at_77 = -1000
-  db_at_76 = -1200
-  db_at_75 = -1200
-  db_at_74 = -1200
-  db_at_73 = -1200
-  db_at_72 = -1400
-  db_at_71 = -1400
-  db_at_70 = -1400
-  db_at_69 = -1400
-  db_at_68 = -1600
-  db_at_67 = -1600
-  db_at_66 = -1600
-  db_at_65 = -1600
-  db_at_64 = -1800
-  db_at_63 = -1800
-  db_at_62 = -1800
-  db_at_61 = -1800
-  db_at_60 = -2000
-  db_at_59 = -2000
-  db_at_58 = -2000
-  db_at_57 = -2000
-  db_at_56 = -2200
-  db_at_55 = -2200
-  db_at_54 = -2200
-  db_at_53 = -2200
-  db_at_52 = -2500
-  db_at_51 = -2500
-  db_at_50 = -2500
-  db_at_49 = -2500
-  db_at_48 = -2800
-  db_at_47 = -2800
-  db_at_46 = -2800
-  db_at_45 = -2800
-  db_at_44 = -3100
-  db_at_43 = -3100
-  db_at_42 = -3100
-  db_at_41 = -3100
-  db_at_40 = -3400
-  db_at_39 = -3400
-  db_at_38 = -3400
-  db_at_37 = -3400
-  db_at_36 = -3700
-  db_at_35 = -3700
-  db_at_34 = -3700
-  db_at_33 = -3700
-  db_at_32 = -4000
-  db_at_31 = -4000
-  db_at_30 = -4000
-  db_at_29 = -4000
-  db_at_28 = -4300
-  db_at_27 = -4300
-  db_at_26 = -4300
-  db_at_25 = -4300
-  db_at_24 = -4700
-  db_at_23 = -4700
-  db_at_22 = -4700
-  db_at_21 = -4700
-  db_at_20 = -5100
-  db_at_19 = -5100
-  db_at_18 = -5100
-  db_at_17 = -5100
-  db_at_16 = -5500
-  db_at_15 = -5500
-  db_at_14 = -5500
-  db_at_13 = -5500
-  db_at_12 = -5900
-  db_at_11 = -5900
-  db_at_10 = -5900
-  db_at_9 = -5900
-  db_at_8 = -6300
-  db_at_7 = -6300
-  db_at_6 = -6300
-  db_at_5 = -6300
-  db_at_4 = -6700
-  db_at_3 = -6700
-  db_at_2 = -6700
-  db_at_1 = -6700
-  db_at_0 = -6700
diff --git a/cras-config/daisy_spring/dsp.ini b/cras-config/daisy_spring/dsp.ini
deleted file mode 100644
index 19ba945..0000000
--- a/cras-config/daisy_spring/dsp.ini
+++ /dev/null
@@ -1,95 +0,0 @@
-[output_source]
-library=builtin
-label=source
-purpose=playback
-disable=(not (equal? dsp_name "speaker_eq"))
-output_0={src:0}
-output_1={src:1}
-
-[output_sink]
-library=builtin
-label=sink
-purpose=playback
-input_0={dst:0}
-input_1={dst:1}
-
-[drc]
-library=builtin
-label=drc
-input_0={src:0}
-input_1={src:1}
-output_2={intermediate:0}
-output_3={intermediate:1}
-input_4=0       ; emphasis_disabled
-input_5=0       ; f
-input_6=1       ; enable
-input_7=-30     ; threshold
-input_8=12      ; knee
-input_9=10.384  ; ratio
-input_10=0.02    ; attack
-input_11=1       ; release
-input_12=0       ; boost
-input_13=600     ; f
-input_14=1       ; enable
-input_15=-32     ; threshold
-input_16=21      ; knee
-input_17=12      ; ratio
-input_18=0.02    ; attack
-input_19=0.2     ; release
-input_20=1       ; boost
-input_21=2000    ; f
-input_22=1       ; enable
-input_23=-24     ; threshold
-input_24=21      ; knee
-input_25=6.329   ; ratio
-input_26=0.02    ; attack
-input_27=0.2     ; release
-input_28=-1      ; boost
-
-[eq2]
-library=builtin
-label=eq2
-input_0={intermediate:0}
-input_1={intermediate:1}
-output_2={dst:0}
-output_3={dst:1}
-input_4=6       ; peaking
-input_5=380     ; freq
-input_6=3       ; Q
-input_7=-10     ; gain
-input_8=6       ; peaking
-input_9=450     ; freq
-input_10=3       ; Q
-input_11=-12     ; gain
-input_12=6       ; peaking
-input_13=720     ; freq
-input_14=3       ; Q
-input_15=-12     ; gain
-input_16=6       ; peaking
-input_17=721     ; freq
-input_18=3       ; Q
-input_19=-12     ; gain
-input_20=6       ; peaking
-input_21=1705    ; freq
-input_22=3       ; Q
-input_23=-8      ; gain
-input_24=6       ; peaking
-input_25=1800    ; freq
-input_26=8       ; Q
-input_27=-10.2   ; gain
-input_28=6       ; peaking
-input_29=580     ; freq
-input_30=6       ; Q
-input_31=-8      ; gain
-input_32=6       ; peaking
-input_33=580     ; freq
-input_34=6       ; Q
-input_35=-8      ; gain
-input_36=2       ; highpass
-input_37=218     ; freq
-input_38=0.7     ; Q
-input_39=2       ; gain
-input_40=2       ; highpass
-input_41=250     ; freq
-input_42=0.7     ; Q
-input_43=0.6578  ; gain
diff --git a/cras-config/device_blacklist b/cras-config/device_blocklist
similarity index 100%
rename from cras-config/device_blacklist
rename to cras-config/device_blocklist
diff --git a/cras-config/falco/HDA Intel PCH b/cras-config/falco/HDA Intel PCH
deleted file mode 100644
index e0adc3e..0000000
--- a/cras-config/falco/HDA Intel PCH
+++ /dev/null
@@ -1,107 +0,0 @@
-[Speaker]
-  volume_curve = explicit
-  db_at_100 = 0
-  db_at_99 = -75
-  db_at_98 = -75
-  db_at_97 = -75
-  db_at_96 = -75
-  db_at_95 = -75
-  db_at_94 = -150
-  db_at_93 = -150
-  db_at_92 = -150
-  db_at_91 = -150
-  db_at_90 = -225
-  db_at_89 = -225
-  db_at_88 = -225
-  db_at_87 = -225
-  db_at_86 = -300
-  db_at_85 = -300
-  db_at_84 = -300
-  db_at_83 = -300
-  db_at_82 = -375
-  db_at_81 = -375
-  db_at_80 = -450
-  db_at_79 = -450
-  db_at_78 = -450
-  db_at_77 = -450
-  db_at_76 = -525
-  db_at_75 = -525
-  db_at_74 = -600
-  db_at_73 = -600
-  db_at_72 = -600
-  db_at_71 = -600
-  db_at_70 = -675
-  db_at_69 = -675
-  db_at_68 = -675
-  db_at_67 = -675
-  db_at_66 = -750
-  db_at_65 = -750
-  db_at_64 = -825
-  db_at_63 = -825
-  db_at_62 = -825
-  db_at_61 = -825
-  db_at_60 = -900
-  db_at_59 = -900
-  db_at_58 = -900
-  db_at_57 = -900
-  db_at_56 = -975
-  db_at_55 = -975
-  db_at_54 = -1050
-  db_at_53 = -1050
-  db_at_52 = -1050
-  db_at_51 = -1050
-  db_at_50 = -1125
-  db_at_49 = -1125
-  db_at_48 = -1200
-  db_at_47 = -1200
-  db_at_46 = -1200
-  db_at_45 = -1200
-  db_at_44 = -1275
-  db_at_43 = -1275
-  db_at_42 = -1275
-  db_at_41 = -1275
-  db_at_40 = -1350
-  db_at_39 = -1425
-  db_at_38 = -1425
-  db_at_37 = -1500
-  db_at_36 = -1500
-  db_at_35 = -1575
-  db_at_34 = -1650
-  db_at_33 = -1650
-  db_at_32 = -1725
-  db_at_31 = -1800
-  db_at_30 = -1800
-  db_at_29 = -1800
-  db_at_28 = -1875
-  db_at_27 = -2025
-  db_at_26 = -2100
-  db_at_25 = -2100
-  db_at_24 = -2175
-  db_at_23 = -2250
-  db_at_22 = -2400
-  db_at_21 = -2475
-  db_at_20 = -2550
-  db_at_19 = -2625
-  db_at_18 = -2700
-  db_at_17 = -2850
-  db_at_16 = -2925
-  db_at_15 = -3000
-  db_at_14 = -3075
-  db_at_13 = -3225
-  db_at_12 = -3300
-  db_at_11 = -3375
-  db_at_10 = -3450
-  db_at_9 = -3600
-  db_at_8 = -3675
-  db_at_7 = -3750
-  db_at_6 = -3825
-  db_at_5 = -3900
-  db_at_4 = -4050
-  db_at_3 = -4050
-  db_at_2 = -4275
-  db_at_1 = -4500
-  db_at_0 = -4800
-[Headphone]
-  volume_curve = simple_step
-  volume_step = 50
-  max_volume = 0
diff --git a/cras-config/falco/dsp.ini b/cras-config/falco/dsp.ini
deleted file mode 100644
index a2b1109..0000000
--- a/cras-config/falco/dsp.ini
+++ /dev/null
@@ -1,111 +0,0 @@
-[output_source]
-library=builtin
-label=source
-purpose=playback
-disable=(not (equal? dsp_name "speaker_eq"))
-output_0={src:0}
-output_1={src:1}
-
-[output_sink]
-library=builtin
-label=sink
-purpose=playback
-input_0={dst:0}
-input_1={dst:1}
-
-[drc]
-library=builtin
-label=drc
-input_0={src:0}
-input_1={src:1}
-output_2={intermediate:0}
-output_3={intermediate:1}
-input_4=0       ; emphasis_disabled
-input_5=0       ; f
-input_6=1       ; enable
-input_7=-24     ; threshold
-input_8=30      ; knee
-input_9=12      ; ratio
-input_10=0.003   ; attack
-input_11=0.25    ; release
-input_12=2       ; boost
-input_13=200     ; f
-input_14=1       ; enable
-input_15=-24     ; threshold
-input_16=30      ; knee
-input_17=12      ; ratio
-input_18=0.003   ; attack
-input_19=0.25    ; release
-input_20=2       ; boost
-input_21=2000    ; f
-input_22=1       ; enable
-input_23=-24     ; threshold
-input_24=30      ; knee
-input_25=12      ; ratio
-input_26=0.003   ; attack
-input_27=0.25    ; release
-input_28=2       ; boost
-
-[eq2]
-library=builtin
-label=eq2
-input_0={intermediate:0}
-input_1={intermediate:1}
-output_2={dst:0}
-output_3={dst:1}
-input_4=2       ; highpass
-input_5=200     ; freq
-input_6=0       ; Q
-input_7=0       ; gain
-input_8=2       ; highpass
-input_9=200     ; freq
-input_10=0       ; Q
-input_11=0       ; gain
-input_12=6       ; peaking
-input_13=350     ; freq
-input_14=1       ; Q
-input_15=3       ; gain
-input_16=6       ; peaking
-input_17=350     ; freq
-input_18=1       ; Q
-input_19=3       ; gain
-input_20=6       ; peaking
-input_21=1000    ; freq
-input_22=1       ; Q
-input_23=-8      ; gain
-input_24=6       ; peaking
-input_25=1000    ; freq
-input_26=1       ; Q
-input_27=-8      ; gain
-input_28=6       ; peaking
-input_29=5000    ; freq
-input_30=1       ; Q
-input_31=3       ; gain
-input_32=6       ; peaking
-input_33=5000    ; freq
-input_34=1       ; Q
-input_35=3       ; gain
-input_36=6       ; peaking
-input_37=8000    ; freq
-input_38=1       ; Q
-input_39=3       ; gain
-input_40=6       ; peaking
-input_41=8000    ; freq
-input_42=1       ; Q
-input_43=3       ; gain
-input_44=6       ; peaking
-input_45=10000   ; freq
-input_46=1       ; Q
-input_47=3       ; gain
-input_48=6       ; peaking
-input_49=10000   ; freq
-input_50=1       ; Q
-input_51=3       ; gain
-input_52=6       ; peaking
-input_53=2000    ; freq
-input_54=1       ; Q
-input_55=-2      ; gain
-input_56=6       ; peaking
-input_57=2000    ; freq
-input_58=1       ; Q
-input_59=-2      ; gain
diff --git a/cras-config/leon/HDA Intel PCH b/cras-config/leon/HDA Intel PCH
deleted file mode 100644
index e0adc3e..0000000
--- a/cras-config/leon/HDA Intel PCH
+++ /dev/null
@@ -1,107 +0,0 @@
-[Speaker]
-  volume_curve = explicit
-  db_at_100 = 0
-  db_at_99 = -75
-  db_at_98 = -75
-  db_at_97 = -75
-  db_at_96 = -75
-  db_at_95 = -75
-  db_at_94 = -150
-  db_at_93 = -150
-  db_at_92 = -150
-  db_at_91 = -150
-  db_at_90 = -225
-  db_at_89 = -225
-  db_at_88 = -225
-  db_at_87 = -225
-  db_at_86 = -300
-  db_at_85 = -300
-  db_at_84 = -300
-  db_at_83 = -300
-  db_at_82 = -375
-  db_at_81 = -375
-  db_at_80 = -450
-  db_at_79 = -450
-  db_at_78 = -450
-  db_at_77 = -450
-  db_at_76 = -525
-  db_at_75 = -525
-  db_at_74 = -600
-  db_at_73 = -600
-  db_at_72 = -600
-  db_at_71 = -600
-  db_at_70 = -675
-  db_at_69 = -675
-  db_at_68 = -675
-  db_at_67 = -675
-  db_at_66 = -750
-  db_at_65 = -750
-  db_at_64 = -825
-  db_at_63 = -825
-  db_at_62 = -825
-  db_at_61 = -825
-  db_at_60 = -900
-  db_at_59 = -900
-  db_at_58 = -900
-  db_at_57 = -900
-  db_at_56 = -975
-  db_at_55 = -975
-  db_at_54 = -1050
-  db_at_53 = -1050
-  db_at_52 = -1050
-  db_at_51 = -1050
-  db_at_50 = -1125
-  db_at_49 = -1125
-  db_at_48 = -1200
-  db_at_47 = -1200
-  db_at_46 = -1200
-  db_at_45 = -1200
-  db_at_44 = -1275
-  db_at_43 = -1275
-  db_at_42 = -1275
-  db_at_41 = -1275
-  db_at_40 = -1350
-  db_at_39 = -1425
-  db_at_38 = -1425
-  db_at_37 = -1500
-  db_at_36 = -1500
-  db_at_35 = -1575
-  db_at_34 = -1650
-  db_at_33 = -1650
-  db_at_32 = -1725
-  db_at_31 = -1800
-  db_at_30 = -1800
-  db_at_29 = -1800
-  db_at_28 = -1875
-  db_at_27 = -2025
-  db_at_26 = -2100
-  db_at_25 = -2100
-  db_at_24 = -2175
-  db_at_23 = -2250
-  db_at_22 = -2400
-  db_at_21 = -2475
-  db_at_20 = -2550
-  db_at_19 = -2625
-  db_at_18 = -2700
-  db_at_17 = -2850
-  db_at_16 = -2925
-  db_at_15 = -3000
-  db_at_14 = -3075
-  db_at_13 = -3225
-  db_at_12 = -3300
-  db_at_11 = -3375
-  db_at_10 = -3450
-  db_at_9 = -3600
-  db_at_8 = -3675
-  db_at_7 = -3750
-  db_at_6 = -3825
-  db_at_5 = -3900
-  db_at_4 = -4050
-  db_at_3 = -4050
-  db_at_2 = -4275
-  db_at_1 = -4500
-  db_at_0 = -4800
-[Headphone]
-  volume_curve = simple_step
-  volume_step = 50
-  max_volume = 0
diff --git a/cras-config/leon/dsp.ini b/cras-config/leon/dsp.ini
deleted file mode 100644
index 1447647..0000000
--- a/cras-config/leon/dsp.ini
+++ /dev/null
@@ -1,79 +0,0 @@
-[output_source]
-library=builtin
-label=source
-purpose=playback
-disable=(not (equal? dsp_name "speaker_eq"))
-output_0={src:0}
-output_1={src:1}
-
-[output_sink]
-library=builtin
-label=sink
-purpose=playback
-input_0={dst:0}
-input_1={dst:1}
-
-[drc]
-library=builtin
-label=drc
-input_0={src:0}
-input_1={src:1}
-output_2={intermediate:0}
-output_3={intermediate:1}
-input_4=0       ; emphasis_disabled
-input_5=0       ; f
-input_6=1       ; enable
-input_7=-20     ; threshold
-input_8=25      ; knee
-input_9=12      ; ratio
-input_10=0.003   ; attack
-input_11=0.25    ; release
-input_12=4       ; boost
-input_13=579     ; f
-input_14=1       ; enable
-input_15=-24     ; threshold
-input_16=31      ; knee
-input_17=12      ; ratio
-input_18=0.003   ; attack
-input_19=0.25    ; release
-input_20=-3      ; boost
-input_21=2164    ; f
-input_22=1       ; enable
-input_23=-25     ; threshold
-input_24=30      ; knee
-input_25=12      ; ratio
-input_26=0.003   ; attack
-input_27=0.25    ; release
-input_28=4       ; boost
-
-[eq2]
-library=builtin
-label=eq2
-input_0={intermediate:0}
-input_1={intermediate:1}
-output_2={dst:0}
-output_3={dst:1}
-input_4=2       ; highpass
-input_5=231     ; freq
-input_6=0       ; Q
-input_7=0       ; gain
-input_8=2       ; highpass
-input_9=218     ; freq
-input_10=0       ; Q
-input_11=0       ; gain
-input_12=6       ; peaking
-input_13=728     ; freq
-input_14=1.5264  ; Q
-input_15=-7.8    ; gain
-input_16=6       ; peaking
-input_17=728     ; freq
-input_18=1.6351  ; Q
-input_19=-8.8    ; gain
-input_20=2       ; highpass
-input_21=164     ; freq
-input_22=3.3685  ; Q
-input_23=0       ; gain
-input_24=2       ; highpass
-input_25=164     ; freq
-input_26=3.3685  ; Q
-input_27=0       ; gain
diff --git a/cras-config/link/HDA Intel PCH b/cras-config/link/HDA Intel PCH
deleted file mode 100644
index 5344a08..0000000
--- a/cras-config/link/HDA Intel PCH
+++ /dev/null
@@ -1,107 +0,0 @@
-[Default]
-  volume_curve = explicit
-  db_at_100 = 0
-  db_at_99 = -100
-  db_at_98 = -100
-  db_at_97 = -100
-  db_at_96 = -100
-  db_at_95 = -200
-  db_at_94 = -200
-  db_at_93 = -200
-  db_at_92 = -200
-  db_at_91 = -300
-  db_at_90 = -300
-  db_at_89 = -300
-  db_at_88 = -300
-  db_at_87 = -400
-  db_at_86 = -400
-  db_at_85 = -400
-  db_at_84 = -400
-  db_at_83 = -500
-  db_at_82 = -500
-  db_at_81 = -500
-  db_at_80 = -500
-  db_at_79 = -600
-  db_at_78 = -600
-  db_at_77 = -600
-  db_at_76 = -600
-  db_at_75 = -700
-  db_at_74 = -700
-  db_at_73 = -700
-  db_at_72 = -700
-  db_at_71 = -800
-  db_at_70 = -800
-  db_at_69 = -800
-  db_at_68 = -800
-  db_at_67 = -900
-  db_at_66 = -900
-  db_at_65 = -900
-  db_at_64 = -900
-  db_at_63 = -900
-  db_at_62 = -900
-  db_at_61 = -900
-  db_at_60 = -1000
-  db_at_59 = -1000
-  db_at_58 = -1000
-  db_at_57 = -1000
-  db_at_56 = -1100
-  db_at_55 = -1100
-  db_at_54 = -1100
-  db_at_53 = -1100
-  db_at_52 = -1200
-  db_at_51 = -1200
-  db_at_50 = -1200
-  db_at_49 = -1200
-  db_at_48 = -1300
-  db_at_47 = -1300
-  db_at_46 = -1300
-  db_at_45 = -1300
-  db_at_44 = -1400
-  db_at_43 = -1400
-  db_at_42 = -1400
-  db_at_41 = -1400
-  db_at_40 = -1500
-  db_at_39 = -1600
-  db_at_38 = -1600
-  db_at_37 = -1700
-  db_at_36 = -1700
-  db_at_35 = -1800
-  db_at_34 = -1900
-  db_at_33 = -2000
-  db_at_32 = -2100
-  db_at_31 = -2200
-  db_at_30 = -2300
-  db_at_29 = -2400
-  db_at_28 = -2500
-  db_at_27 = -2600
-  db_at_26 = -2700
-  db_at_25 = -2800
-  db_at_24 = -2900
-  db_at_23 = -3000
-  db_at_22 = -3100
-  db_at_21 = -3200
-  db_at_20 = -3300
-  db_at_19 = -3400
-  db_at_18 = -3500
-  db_at_17 = -3600
-  db_at_16 = -3700
-  db_at_15 = -3800
-  db_at_14 = -3900
-  db_at_13 = -4000
-  db_at_12 = -4100
-  db_at_11 = -4200
-  db_at_10 = -4300
-  db_at_9 = -4400
-  db_at_8 = -4500
-  db_at_7 = -4600
-  db_at_6 = -4800
-  db_at_5 = -5000
-  db_at_4 = -5200
-  db_at_3 = -5400
-  db_at_2 = -5600
-  db_at_1 = -5800
-  db_at_0 = -6000
-[Headphone Jack]
-  volume_curve = simple_step
-  volume_step = 75
-  max_volume = -900
diff --git a/cras-config/peppy/HDA Intel PCH b/cras-config/peppy/HDA Intel PCH
deleted file mode 100644
index e0adc3e..0000000
--- a/cras-config/peppy/HDA Intel PCH
+++ /dev/null
@@ -1,107 +0,0 @@
-[Speaker]
-  volume_curve = explicit
-  db_at_100 = 0
-  db_at_99 = -75
-  db_at_98 = -75
-  db_at_97 = -75
-  db_at_96 = -75
-  db_at_95 = -75
-  db_at_94 = -150
-  db_at_93 = -150
-  db_at_92 = -150
-  db_at_91 = -150
-  db_at_90 = -225
-  db_at_89 = -225
-  db_at_88 = -225
-  db_at_87 = -225
-  db_at_86 = -300
-  db_at_85 = -300
-  db_at_84 = -300
-  db_at_83 = -300
-  db_at_82 = -375
-  db_at_81 = -375
-  db_at_80 = -450
-  db_at_79 = -450
-  db_at_78 = -450
-  db_at_77 = -450
-  db_at_76 = -525
-  db_at_75 = -525
-  db_at_74 = -600
-  db_at_73 = -600
-  db_at_72 = -600
-  db_at_71 = -600
-  db_at_70 = -675
-  db_at_69 = -675
-  db_at_68 = -675
-  db_at_67 = -675
-  db_at_66 = -750
-  db_at_65 = -750
-  db_at_64 = -825
-  db_at_63 = -825
-  db_at_62 = -825
-  db_at_61 = -825
-  db_at_60 = -900
-  db_at_59 = -900
-  db_at_58 = -900
-  db_at_57 = -900
-  db_at_56 = -975
-  db_at_55 = -975
-  db_at_54 = -1050
-  db_at_53 = -1050
-  db_at_52 = -1050
-  db_at_51 = -1050
-  db_at_50 = -1125
-  db_at_49 = -1125
-  db_at_48 = -1200
-  db_at_47 = -1200
-  db_at_46 = -1200
-  db_at_45 = -1200
-  db_at_44 = -1275
-  db_at_43 = -1275
-  db_at_42 = -1275
-  db_at_41 = -1275
-  db_at_40 = -1350
-  db_at_39 = -1425
-  db_at_38 = -1425
-  db_at_37 = -1500
-  db_at_36 = -1500
-  db_at_35 = -1575
-  db_at_34 = -1650
-  db_at_33 = -1650
-  db_at_32 = -1725
-  db_at_31 = -1800
-  db_at_30 = -1800
-  db_at_29 = -1800
-  db_at_28 = -1875
-  db_at_27 = -2025
-  db_at_26 = -2100
-  db_at_25 = -2100
-  db_at_24 = -2175
-  db_at_23 = -2250
-  db_at_22 = -2400
-  db_at_21 = -2475
-  db_at_20 = -2550
-  db_at_19 = -2625
-  db_at_18 = -2700
-  db_at_17 = -2850
-  db_at_16 = -2925
-  db_at_15 = -3000
-  db_at_14 = -3075
-  db_at_13 = -3225
-  db_at_12 = -3300
-  db_at_11 = -3375
-  db_at_10 = -3450
-  db_at_9 = -3600
-  db_at_8 = -3675
-  db_at_7 = -3750
-  db_at_6 = -3825
-  db_at_5 = -3900
-  db_at_4 = -4050
-  db_at_3 = -4050
-  db_at_2 = -4275
-  db_at_1 = -4500
-  db_at_0 = -4800
-[Headphone]
-  volume_curve = simple_step
-  volume_step = 50
-  max_volume = 0
diff --git a/cras-config/peppy/dsp.ini b/cras-config/peppy/dsp.ini
deleted file mode 100644
index e229b19..0000000
--- a/cras-config/peppy/dsp.ini
+++ /dev/null
@@ -1,119 +0,0 @@
-[output_source]
-library=builtin
-label=source
-purpose=playback
-disable=(not (equal? dsp_name "speaker_eq"))
-output_0={src:0}
-output_1={src:1}
-
-[output_sink]
-library=builtin
-label=sink
-purpose=playback
-input_0={dst:0}
-input_1={dst:1}
-
-[drc]
-library=builtin
-label=drc
-input_0={src:0}
-input_1={src:1}
-output_2={intermediate:0}
-output_3={intermediate:1}
-input_4=0       ; emphasis_disabled
-input_5=0       ; f
-input_6=1       ; enable
-input_7=-30     ; threshold
-input_8=24      ; knee
-input_9=6.032   ; ratio
-input_10=0.02    ; attack
-input_11=0.25    ; release
-input_12=2       ; boost
-input_13=326     ; f
-input_14=1       ; enable
-input_15=-27     ; threshold
-input_16=23      ; knee
-input_17=5.634   ; ratio
-input_18=0.003   ; attack
-input_19=0.25    ; release
-input_20=2       ; boost
-input_21=1842    ; f
-input_22=1       ; enable
-input_23=-40     ; threshold
-input_24=37      ; knee
-input_25=5.67    ; ratio
-input_26=0.003   ; attack
-input_27=0.25    ; release
-input_28=3       ; boost
-
-[eq2]
-library=builtin
-label=eq2
-input_0={intermediate:0}
-input_1={intermediate:1}
-output_2={dst:0}
-output_3={dst:1}
-input_4=6       ; peaking
-input_5=248     ; freq
-input_6=4.8668  ; Q
-input_7=-7.8    ; gain
-input_8=6       ; peaking
-input_9=688     ; freq
-input_10=2.6911  ; Q
-input_11=-3.9    ; gain
-input_12=6       ; peaking
-input_13=410     ; freq
-input_14=8.5     ; Q
-input_15=-7.3    ; gain
-input_16=6       ; peaking
-input_17=817     ; freq
-input_18=4.1703  ; Q
-input_19=-15     ; gain
-input_20=6       ; peaking
-input_21=5112    ; freq
-input_22=3.957   ; Q
-input_23=-4.4    ; gain
-input_24=6       ; peaking
-input_25=4827    ; freq
-input_26=3.3685  ; Q
-input_27=-6.3    ; gain
-input_28=6       ; peaking
-input_29=819     ; freq
-input_30=2.2529  ; Q
-input_31=-14.1   ; gain
-input_32=6       ; peaking
-input_33=4065    ; freq
-input_34=2.5388  ; Q
-input_35=3       ; gain
-input_36=6       ; peaking
-input_37=4065    ; freq
-input_38=5.9436  ; Q
-input_39=3       ; gain
-input_40=6       ; peaking
-input_41=2292    ; freq
-input_42=2.6     ; Q
-input_43=1.5     ; gain
-input_44=6       ; peaking
-input_45=2292    ; freq
-input_46=2.6911  ; Q
-input_47=1.5     ; gain
-input_48=5       ; highshelf
-input_49=8960    ; freq
-input_50=2.1187  ; Q
-input_51=2.6     ; gain
-input_52=2       ; highpass
-input_53=293     ; freq
-input_54=1.0465  ; Q
-input_55=0       ; gain
-input_56=2       ; highpass
-input_57=343     ; freq
-input_58=1.4222  ; Q
-input_59=0       ; gain
-input_60=5       ; highshelf
-input_61=8960    ; freq
-input_62=1       ; Q
-input_63=2.6     ; gain
-input_64=0       ; none
-input_65=0       ; freq
-input_66=0       ; Q
-input_67=0       ; gain
diff --git a/cras-config/stout/HDA Intel PCH b/cras-config/stout/HDA Intel PCH
deleted file mode 100644
index 884c006..0000000
--- a/cras-config/stout/HDA Intel PCH
+++ /dev/null
@@ -1,107 +0,0 @@
-[Speaker]
-  volume_curve = explicit
-  db_at_100 = 0
-  db_at_99 = -75
-  db_at_98 = -75
-  db_at_97 = -75
-  db_at_96 = -150
-  db_at_95 = -150
-  db_at_94 = -150
-  db_at_93 = -225
-  db_at_92 = -225
-  db_at_91 = -300
-  db_at_90 = -300
-  db_at_89 = -300
-  db_at_88 = -375
-  db_at_87 = -375
-  db_at_86 = -375
-  db_at_85 = -450
-  db_at_84 = -450
-  db_at_83 = -450
-  db_at_82 = -525
-  db_at_81 = -525
-  db_at_80 = -600
-  db_at_79 = -600
-  db_at_78 = -675
-  db_at_77 = -675
-  db_at_76 = -750
-  db_at_75 = -750
-  db_at_74 = -750
-  db_at_73 = -825
-  db_at_72 = -825
-  db_at_71 = -825
-  db_at_70 = -900
-  db_at_69 = -900
-  db_at_68 = -975
-  db_at_67 = -975
-  db_at_66 = -1050
-  db_at_65 = -1050
-  db_at_64 = -1125
-  db_at_63 = -1125
-  db_at_62 = -1125
-  db_at_61 = -1200
-  db_at_60 = -1200
-  db_at_59 = -1200
-  db_at_58 = -1275
-  db_at_57 = -1275
-  db_at_56 = -1350
-  db_at_55 = -1350
-  db_at_54 = -1425
-  db_at_53 = -1425
-  db_at_52 = -1500
-  db_at_51 = -1500
-  db_at_50 = -1500
-  db_at_49 = -1500
-  db_at_48 = -1575
-  db_at_47 = -1575
-  db_at_46 = -1650
-  db_at_45 = -1650
-  db_at_44 = -1725
-  db_at_43 = -1725
-  db_at_42 = -1800
-  db_at_41 = -1800
-  db_at_40 = -1875
-  db_at_39 = -1875
-  db_at_38 = -1950
-  db_at_37 = -2025
-  db_at_36 = -2100
-  db_at_35 = -2175
-  db_at_34 = -2250
-  db_at_33 = -2250
-  db_at_32 = -2325
-  db_at_31 = -2400
-  db_at_30 = -2475
-  db_at_29 = -2475
-  db_at_28 = -2625
-  db_at_27 = -2700
-  db_at_26 = -2850
-  db_at_25 = -2850
-  db_at_24 = -3000
-  db_at_23 = -3075
-  db_at_22 = -3225
-  db_at_21 = -3375
-  db_at_20 = -3450
-  db_at_19 = -3600
-  db_at_18 = -3750
-  db_at_17 = -3825
-  db_at_16 = -3975
-  db_at_15 = -4125
-  db_at_14 = -4200
-  db_at_13 = -4350
-  db_at_12 = -4500
-  db_at_11 = -4575
-  db_at_10 = -4725
-  db_at_9 = -4875
-  db_at_8 = -4950
-  db_at_7 = -5100
-  db_at_6 = -5250
-  db_at_5 = -5325
-  db_at_4 = -5475
-  db_at_3 = -5550
-  db_at_2 = -5850
-  db_at_1 = -6150
-  db_at_0 = -6525
-[Headphone]
-  volume_curve = simple_step
-  volume_step = 65
-  max_volume = 0
diff --git a/cras-config/veryon_jerry-kernelnext b/cras-config/veryon_jerry-kernelnext
deleted file mode 120000
index 0462d3a..0000000
--- a/cras-config/veryon_jerry-kernelnext
+++ /dev/null
@@ -1 +0,0 @@
-veyron_jerry
\ No newline at end of file
diff --git a/cras-config/veyron_jaq/ROCKCHIP-I2S b/cras-config/veyron_jaq/ROCKCHIP-I2S
deleted file mode 100644
index 2b946ca..0000000
--- a/cras-config/veyron_jaq/ROCKCHIP-I2S
+++ /dev/null
@@ -1,107 +0,0 @@
-[Speaker]
-  volume_curve = explicit
-  db_at_100 = -100
-  db_at_99 = -100
-  db_at_98 = -100
-  db_at_97 = -100
-  db_at_96 = -200
-  db_at_95 = -200
-  db_at_94 = -200
-  db_at_93 = -200
-  db_at_92 = -200
-  db_at_91 = -200
-  db_at_90 = -200
-  db_at_89 = -200
-  db_at_88 = -300
-  db_at_87 = -300
-  db_at_86 = -300
-  db_at_85 = -300
-  db_at_84 = -300
-  db_at_83 = -300
-  db_at_82 = -300
-  db_at_81 = -400
-  db_at_80 = -400
-  db_at_79 = -400
-  db_at_78 = -400
-  db_at_77 = -400
-  db_at_76 = -400
-  db_at_75 = -400
-  db_at_74 = -500
-  db_at_73 = -500
-  db_at_72 = -500
-  db_at_71 = -500
-  db_at_70 = -500
-  db_at_69 = -500
-  db_at_68 = -500
-  db_at_67 = -500
-  db_at_66 = -600
-  db_at_65 = -600
-  db_at_64 = -600
-  db_at_63 = -600
-  db_at_62 = -600
-  db_at_61 = -600
-  db_at_60 = -600
-  db_at_59 = -700
-  db_at_58 = -700
-  db_at_57 = -700
-  db_at_56 = -700
-  db_at_55 = -700
-  db_at_54 = -800
-  db_at_53 = -800
-  db_at_52 = -800
-  db_at_51 = -900
-  db_at_50 = -900
-  db_at_49 = -900
-  db_at_48 = -900
-  db_at_47 = -1000
-  db_at_46 = -1000
-  db_at_45 = -1000
-  db_at_44 = -1100
-  db_at_43 = -1100
-  db_at_42 = -1100
-  db_at_41 = -1200
-  db_at_40 = -1200
-  db_at_39 = -1200
-  db_at_38 = -1200
-  db_at_37 = -1300
-  db_at_36 = -1300
-  db_at_35 = -1300
-  db_at_34 = -1400
-  db_at_33 = -1400
-  db_at_32 = -1400
-  db_at_31 = -1500
-  db_at_30 = -1500
-  db_at_29 = -1500
-  db_at_28 = -1500
-  db_at_27 = -1600
-  db_at_26 = -1600
-  db_at_25 = -1600
-  db_at_24 = -1700
-  db_at_23 = -1700
-  db_at_22 = -1700
-  db_at_21 = -1800
-  db_at_20 = -1800
-  db_at_19 = -1800
-  db_at_18 = -1900
-  db_at_17 = -1900
-  db_at_16 = -1900
-  db_at_15 = -1900
-  db_at_14 = -2000
-  db_at_13 = -2000
-  db_at_12 = -2000
-  db_at_11 = -2100
-  db_at_10 = -2100
-  db_at_9 = -2100
-  db_at_8 = -2200
-  db_at_7 = -2200
-  db_at_6 = -2200
-  db_at_5 = -2200
-  db_at_4 = -2300
-  db_at_3 = -2300
-  db_at_2 = -2300
-  db_at_1 = -2400
-  db_at_0 = -2400
-[Headphone]
-  volume_curve = simple_step
-  volume_step = 70
-  max_volume = 0
diff --git a/cras-config/veyron_jaq/VEYRON-I2S b/cras-config/veyron_jaq/VEYRON-I2S
deleted file mode 100644
index 2b946ca..0000000
--- a/cras-config/veyron_jaq/VEYRON-I2S
+++ /dev/null
@@ -1,107 +0,0 @@
-[Speaker]
-  volume_curve = explicit
-  db_at_100 = -100
-  db_at_99 = -100
-  db_at_98 = -100
-  db_at_97 = -100
-  db_at_96 = -200
-  db_at_95 = -200
-  db_at_94 = -200
-  db_at_93 = -200
-  db_at_92 = -200
-  db_at_91 = -200
-  db_at_90 = -200
-  db_at_89 = -200
-  db_at_88 = -300
-  db_at_87 = -300
-  db_at_86 = -300
-  db_at_85 = -300
-  db_at_84 = -300
-  db_at_83 = -300
-  db_at_82 = -300
-  db_at_81 = -400
-  db_at_80 = -400
-  db_at_79 = -400
-  db_at_78 = -400
-  db_at_77 = -400
-  db_at_76 = -400
-  db_at_75 = -400
-  db_at_74 = -500
-  db_at_73 = -500
-  db_at_72 = -500
-  db_at_71 = -500
-  db_at_70 = -500
-  db_at_69 = -500
-  db_at_68 = -500
-  db_at_67 = -500
-  db_at_66 = -600
-  db_at_65 = -600
-  db_at_64 = -600
-  db_at_63 = -600
-  db_at_62 = -600
-  db_at_61 = -600
-  db_at_60 = -600
-  db_at_59 = -700
-  db_at_58 = -700
-  db_at_57 = -700
-  db_at_56 = -700
-  db_at_55 = -700
-  db_at_54 = -800
-  db_at_53 = -800
-  db_at_52 = -800
-  db_at_51 = -900
-  db_at_50 = -900
-  db_at_49 = -900
-  db_at_48 = -900
-  db_at_47 = -1000
-  db_at_46 = -1000
-  db_at_45 = -1000
-  db_at_44 = -1100
-  db_at_43 = -1100
-  db_at_42 = -1100
-  db_at_41 = -1200
-  db_at_40 = -1200
-  db_at_39 = -1200
-  db_at_38 = -1200
-  db_at_37 = -1300
-  db_at_36 = -1300
-  db_at_35 = -1300
-  db_at_34 = -1400
-  db_at_33 = -1400
-  db_at_32 = -1400
-  db_at_31 = -1500
-  db_at_30 = -1500
-  db_at_29 = -1500
-  db_at_28 = -1500
-  db_at_27 = -1600
-  db_at_26 = -1600
-  db_at_25 = -1600
-  db_at_24 = -1700
-  db_at_23 = -1700
-  db_at_22 = -1700
-  db_at_21 = -1800
-  db_at_20 = -1800
-  db_at_19 = -1800
-  db_at_18 = -1900
-  db_at_17 = -1900
-  db_at_16 = -1900
-  db_at_15 = -1900
-  db_at_14 = -2000
-  db_at_13 = -2000
-  db_at_12 = -2000
-  db_at_11 = -2100
-  db_at_10 = -2100
-  db_at_9 = -2100
-  db_at_8 = -2200
-  db_at_7 = -2200
-  db_at_6 = -2200
-  db_at_5 = -2200
-  db_at_4 = -2300
-  db_at_3 = -2300
-  db_at_2 = -2300
-  db_at_1 = -2400
-  db_at_0 = -2400
-[Headphone]
-  volume_curve = simple_step
-  volume_step = 70
-  max_volume = 0
diff --git a/cras-config/veyron_jaq/dsp.ini b/cras-config/veyron_jaq/dsp.ini
deleted file mode 100644
index 8f7c628..0000000
--- a/cras-config/veyron_jaq/dsp.ini
+++ /dev/null
@@ -1,119 +0,0 @@
-[output_source]
-library=builtin
-label=source
-purpose=playback
-disable=(not (equal? dsp_name "speaker_eq"))
-output_0={src:0}
-output_1={src:1}
-
-[output_sink]
-library=builtin
-label=sink
-purpose=playback
-input_0={dst:0}
-input_1={dst:1}
-
-[drc]
-library=builtin
-label=drc
-input_0={src:0}
-input_1={src:1}
-output_2={intermediate:0}
-output_3={intermediate:1}
-input_4=1         ; emphasis_disabled
-input_5=0         ; f
-input_6=1         ; enable
-input_7=-24       ; threshold
-input_8=30        ; knee
-input_9=12        ; ratio
-input_10=0.003     ; attack
-input_11=0.25      ; release
-input_12=0         ; boost
-input_13=200       ; f
-input_14=1         ; enable
-input_15=-24       ; threshold
-input_16=30        ; knee
-input_17=12        ; ratio
-input_18=0.003     ; attack
-input_19=0.25      ; release
-input_20=0         ; boost
-input_21=2000      ; f
-input_22=1         ; enable
-input_23=-24       ; threshold
-input_24=30        ; knee
-input_25=12        ; ratio
-input_26=0.003     ; attack
-input_27=0.25      ; release
-input_28=0         ; boost
-
-[eq2]
-library=builtin
-label=eq2
-input_0={intermediate:0}
-input_1={intermediate:1}
-output_2={dst:0}
-output_3={dst:1}
-input_4=2       ; highpass
-input_5=250     ; freq
-input_6=3       ; Q
-input_7=0       ; gain
-input_8=2       ; highpass
-input_9=250     ; freq
-input_10=3       ; Q
-input_11=0       ; gain
-input_12=6       ; peaking
-input_13=400     ; freq
-input_14=1       ; Q
-input_15=3       ; gain
-input_16=6       ; peaking
-input_17=400     ; freq
-input_18=1       ; Q
-input_19=3       ; gain
-input_20=6       ; peaking
-input_21=630     ; freq
-input_22=1       ; Q
-input_23=1       ; gain
-input_24=6       ; peaking
-input_25=630     ; freq
-input_26=1       ; Q
-input_27=1       ; gain
-input_28=6       ; peaking
-input_29=1500    ; freq
-input_30=1       ; Q
-input_31=-2      ; gain
-input_32=6       ; peaking
-input_33=1500    ; freq
-input_34=1       ; Q
-input_35=-2      ; gain
-input_36=6       ; peaking
-input_37=2200    ; freq
-input_38=1       ; Q
-input_39=1       ; gain
-input_40=6       ; peaking
-input_41=2200    ; freq
-input_42=1       ; Q
-input_43=1       ; gain
-input_44=6       ; peaking
-input_45=3300    ; freq
-input_46=0.7     ; Q
-input_47=-6      ; gain
-input_48=6       ; peaking
-input_49=3300    ; freq
-input_50=0.7     ; Q
-input_51=-6      ; gain
-input_52=6       ; peaking
-input_53=6000    ; freq
-input_54=1       ; Q
-input_55=-3      ; gain
-input_56=6       ; peaking
-input_57=6000    ; freq
-input_58=1       ; Q
-input_59=-3      ; gain
-input_60=5       ; highshelf
-input_61=12000   ; freq
-input_62=1       ; Q
-input_63=1.5     ; gain
-input_64=5       ; highshelf
-input_65=12000   ; freq
-input_66=1       ; Q
-input_67=1.5     ; gain
diff --git a/cras-config/veyron_jerry/ROCKCHIP-I2S b/cras-config/veyron_jerry/ROCKCHIP-I2S
deleted file mode 100644
index d59988f..0000000
--- a/cras-config/veyron_jerry/ROCKCHIP-I2S
+++ /dev/null
@@ -1,107 +0,0 @@
-[Speaker]
-  volume_curve = explicit
-  db_at_100 = -500
-  db_at_99 = -520
-  db_at_98 = -520
-  db_at_97 = -540
-  db_at_96 = -540
-  db_at_95 = -560
-  db_at_94 = -580
-  db_at_93 = -580
-  db_at_92 = -600
-  db_at_91 = -620
-  db_at_90 = -620
-  db_at_89 = -640
-  db_at_88 = -640
-  db_at_87 = -660
-  db_at_86 = -680
-  db_at_85 = -680
-  db_at_84 = -700
-  db_at_83 = -720
-  db_at_82 = -740
-  db_at_81 = -760
-  db_at_80 = -780
-  db_at_79 = -800
-  db_at_78 = -820
-  db_at_77 = -840
-  db_at_76 = -860
-  db_at_75 = -880
-  db_at_74 = -880
-  db_at_73 = -880
-  db_at_72 = -900
-  db_at_71 = -920
-  db_at_70 = -940
-  db_at_69 = -980
-  db_at_68 = -1000
-  db_at_67 = -1020
-  db_at_66 = -1040
-  db_at_65 = -1080
-  db_at_64 = -1100
-  db_at_63 = -1120
-  db_at_62 = -1140
-  db_at_61 = -1180
-  db_at_60 = -1200
-  db_at_59 = -1220
-  db_at_58 = -1240
-  db_at_57 = -1280
-  db_at_56 = -1300
-  db_at_55 = -1320
-  db_at_54 = -1340
-  db_at_53 = -1380
-  db_at_52 = -1400
-  db_at_51 = -1460
-  db_at_50 = -1540
-  db_at_49 = -1600
-  db_at_48 = -1660
-  db_at_47 = -1720
-  db_at_46 = -1800
-  db_at_45 = -1860
-  db_at_44 = -1920
-  db_at_43 = -1980
-  db_at_42 = -2060
-  db_at_41 = -2120
-  db_at_40 = -2180
-  db_at_39 = -2240
-  db_at_38 = -2320
-  db_at_37 = -2380
-  db_at_36 = -2440
-  db_at_35 = -2520
-  db_at_34 = -2580
-  db_at_33 = -2640
-  db_at_32 = -2700
-  db_at_31 = -2780
-  db_at_30 = -2840
-  db_at_29 = -2900
-  db_at_28 = -2960
-  db_at_27 = -3040
-  db_at_26 = -3100
-  db_at_25 = -3160
-  db_at_24 = -3240
-  db_at_23 = -3300
-  db_at_22 = -3360
-  db_at_21 = -3420
-  db_at_20 = -3500
-  db_at_19 = -3560
-  db_at_18 = -3620
-  db_at_17 = -3680
-  db_at_16 = -3760
-  db_at_15 = -3820
-  db_at_14 = -3880
-  db_at_13 = -3940
-  db_at_12 = -4020
-  db_at_11 = -4080
-  db_at_10 = -4140
-  db_at_9 = -4220
-  db_at_8 = -4280
-  db_at_7 = -4340
-  db_at_6 = -4400
-  db_at_5 = -4480
-  db_at_4 = -4540
-  db_at_3 = -4600
-  db_at_2 = -4660
-  db_at_1 = -4740
-  db_at_0 = -4800
-[Headphone]
-  volume_curve = simple_step
-  volume_step = 70
-  max_volume = 0
diff --git a/cras-config/veyron_jerry/VEYRON-I2S b/cras-config/veyron_jerry/VEYRON-I2S
deleted file mode 100644
index d59988f..0000000
--- a/cras-config/veyron_jerry/VEYRON-I2S
+++ /dev/null
@@ -1,107 +0,0 @@
-[Speaker]
-  volume_curve = explicit
-  db_at_100 = -500
-  db_at_99 = -520
-  db_at_98 = -520
-  db_at_97 = -540
-  db_at_96 = -540
-  db_at_95 = -560
-  db_at_94 = -580
-  db_at_93 = -580
-  db_at_92 = -600
-  db_at_91 = -620
-  db_at_90 = -620
-  db_at_89 = -640
-  db_at_88 = -640
-  db_at_87 = -660
-  db_at_86 = -680
-  db_at_85 = -680
-  db_at_84 = -700
-  db_at_83 = -720
-  db_at_82 = -740
-  db_at_81 = -760
-  db_at_80 = -780
-  db_at_79 = -800
-  db_at_78 = -820
-  db_at_77 = -840
-  db_at_76 = -860
-  db_at_75 = -880
-  db_at_74 = -880
-  db_at_73 = -880
-  db_at_72 = -900
-  db_at_71 = -920
-  db_at_70 = -940
-  db_at_69 = -980
-  db_at_68 = -1000
-  db_at_67 = -1020
-  db_at_66 = -1040
-  db_at_65 = -1080
-  db_at_64 = -1100
-  db_at_63 = -1120
-  db_at_62 = -1140
-  db_at_61 = -1180
-  db_at_60 = -1200
-  db_at_59 = -1220
-  db_at_58 = -1240
-  db_at_57 = -1280
-  db_at_56 = -1300
-  db_at_55 = -1320
-  db_at_54 = -1340
-  db_at_53 = -1380
-  db_at_52 = -1400
-  db_at_51 = -1460
-  db_at_50 = -1540
-  db_at_49 = -1600
-  db_at_48 = -1660
-  db_at_47 = -1720
-  db_at_46 = -1800
-  db_at_45 = -1860
-  db_at_44 = -1920
-  db_at_43 = -1980
-  db_at_42 = -2060
-  db_at_41 = -2120
-  db_at_40 = -2180
-  db_at_39 = -2240
-  db_at_38 = -2320
-  db_at_37 = -2380
-  db_at_36 = -2440
-  db_at_35 = -2520
-  db_at_34 = -2580
-  db_at_33 = -2640
-  db_at_32 = -2700
-  db_at_31 = -2780
-  db_at_30 = -2840
-  db_at_29 = -2900
-  db_at_28 = -2960
-  db_at_27 = -3040
-  db_at_26 = -3100
-  db_at_25 = -3160
-  db_at_24 = -3240
-  db_at_23 = -3300
-  db_at_22 = -3360
-  db_at_21 = -3420
-  db_at_20 = -3500
-  db_at_19 = -3560
-  db_at_18 = -3620
-  db_at_17 = -3680
-  db_at_16 = -3760
-  db_at_15 = -3820
-  db_at_14 = -3880
-  db_at_13 = -3940
-  db_at_12 = -4020
-  db_at_11 = -4080
-  db_at_10 = -4140
-  db_at_9 = -4220
-  db_at_8 = -4280
-  db_at_7 = -4340
-  db_at_6 = -4400
-  db_at_5 = -4480
-  db_at_4 = -4540
-  db_at_3 = -4600
-  db_at_2 = -4660
-  db_at_1 = -4740
-  db_at_0 = -4800
-[Headphone]
-  volume_curve = simple_step
-  volume_step = 70
-  max_volume = 0
diff --git a/cras-config/veyron_jerry/dsp.ini b/cras-config/veyron_jerry/dsp.ini
deleted file mode 100644
index 4973a55..0000000
--- a/cras-config/veyron_jerry/dsp.ini
+++ /dev/null
@@ -1,79 +0,0 @@
-[output_source]
-library=builtin
-label=source
-purpose=playback
-disable=(not (equal? dsp_name "speaker_eq"))
-output_0={src:0}
-output_1={src:1}
-
-[output_sink]
-library=builtin
-label=sink
-purpose=playback
-input_0={dst:0}
-input_1={dst:1}
-
-[drc]
-library=builtin
-label=drc
-input_0={src:0}
-input_1={src:1}
-output_2={intermediate:0}
-output_3={intermediate:1}
-input_4=1         ; emphasis_disabled
-input_5=0         ; f
-input_6=1         ; enable
-input_7=-30       ; threshold
-input_8=25        ; knee
-input_9=4         ; ratio
-input_10=0.003     ; attack
-input_11=0.25      ; release
-input_12=0         ; boost
-input_13=200       ; f
-input_14=1         ; enable
-input_15=-30       ; threshold
-input_16=25        ; knee
-input_17=4         ; ratio
-input_18=0.003     ; attack
-input_19=0.25      ; release
-input_20=0         ; boost
-input_21=2000      ; f
-input_22=1         ; enable
-input_23=-30       ; threshold
-input_24=25        ; knee
-input_25=4         ; ratio
-input_26=0.003     ; attack
-input_27=0.25      ; release
-input_28=0         ; boost
-
-[eq2]
-library=builtin
-label=eq2
-input_0={intermediate:0}
-input_1={intermediate:1}
-output_2={dst:0}
-output_3={dst:1}
-input_4=2       ; highpass
-input_5=500     ; freq
-input_6=0       ; Q
-input_7=0       ; gain
-input_8=2       ; highpass
-input_9=500     ; freq
-input_10=0       ; Q
-input_11=0       ; gain
-input_12=6       ; peaking
-input_13=350     ; freq
-input_14=4       ; Q
-input_15=-6      ; gain
-input_16=6       ; peaking
-input_17=350     ; freq
-input_18=4       ; Q
-input_19=-6      ; gain
-input_20=6       ; peaking
-input_21=750     ; freq
-input_22=4       ; Q
-input_23=-6      ; gain
-input_24=6       ; peaking
-input_25=750     ; freq
-input_26=4       ; Q
-input_27=-6      ; gain
diff --git a/cras-config/veyron_mighty/ROCKCHIP-I2S b/cras-config/veyron_mighty/ROCKCHIP-I2S
deleted file mode 100644
index 2b946ca..0000000
--- a/cras-config/veyron_mighty/ROCKCHIP-I2S
+++ /dev/null
@@ -1,107 +0,0 @@
-[Speaker]
-  volume_curve = explicit
-  db_at_100 = -100
-  db_at_99 = -100
-  db_at_98 = -100
-  db_at_97 = -100
-  db_at_96 = -200
-  db_at_95 = -200
-  db_at_94 = -200
-  db_at_93 = -200
-  db_at_92 = -200
-  db_at_91 = -200
-  db_at_90 = -200
-  db_at_89 = -200
-  db_at_88 = -300
-  db_at_87 = -300
-  db_at_86 = -300
-  db_at_85 = -300
-  db_at_84 = -300
-  db_at_83 = -300
-  db_at_82 = -300
-  db_at_81 = -400
-  db_at_80 = -400
-  db_at_79 = -400
-  db_at_78 = -400
-  db_at_77 = -400
-  db_at_76 = -400
-  db_at_75 = -400
-  db_at_74 = -500
-  db_at_73 = -500
-  db_at_72 = -500
-  db_at_71 = -500
-  db_at_70 = -500
-  db_at_69 = -500
-  db_at_68 = -500
-  db_at_67 = -500
-  db_at_66 = -600
-  db_at_65 = -600
-  db_at_64 = -600
-  db_at_63 = -600
-  db_at_62 = -600
-  db_at_61 = -600
-  db_at_60 = -600
-  db_at_59 = -700
-  db_at_58 = -700
-  db_at_57 = -700
-  db_at_56 = -700
-  db_at_55 = -700
-  db_at_54 = -800
-  db_at_53 = -800
-  db_at_52 = -800
-  db_at_51 = -900
-  db_at_50 = -900
-  db_at_49 = -900
-  db_at_48 = -900
-  db_at_47 = -1000
-  db_at_46 = -1000
-  db_at_45 = -1000
-  db_at_44 = -1100
-  db_at_43 = -1100
-  db_at_42 = -1100
-  db_at_41 = -1200
-  db_at_40 = -1200
-  db_at_39 = -1200
-  db_at_38 = -1200
-  db_at_37 = -1300
-  db_at_36 = -1300
-  db_at_35 = -1300
-  db_at_34 = -1400
-  db_at_33 = -1400
-  db_at_32 = -1400
-  db_at_31 = -1500
-  db_at_30 = -1500
-  db_at_29 = -1500
-  db_at_28 = -1500
-  db_at_27 = -1600
-  db_at_26 = -1600
-  db_at_25 = -1600
-  db_at_24 = -1700
-  db_at_23 = -1700
-  db_at_22 = -1700
-  db_at_21 = -1800
-  db_at_20 = -1800
-  db_at_19 = -1800
-  db_at_18 = -1900
-  db_at_17 = -1900
-  db_at_16 = -1900
-  db_at_15 = -1900
-  db_at_14 = -2000
-  db_at_13 = -2000
-  db_at_12 = -2000
-  db_at_11 = -2100
-  db_at_10 = -2100
-  db_at_9 = -2100
-  db_at_8 = -2200
-  db_at_7 = -2200
-  db_at_6 = -2200
-  db_at_5 = -2200
-  db_at_4 = -2300
-  db_at_3 = -2300
-  db_at_2 = -2300
-  db_at_1 = -2400
-  db_at_0 = -2400
-[Headphone]
-  volume_curve = simple_step
-  volume_step = 70
-  max_volume = 0
diff --git a/cras-config/veyron_mighty/VEYRON-I2S b/cras-config/veyron_mighty/VEYRON-I2S
deleted file mode 100644
index 2b946ca..0000000
--- a/cras-config/veyron_mighty/VEYRON-I2S
+++ /dev/null
@@ -1,107 +0,0 @@
-[Speaker]
-  volume_curve = explicit
-  db_at_100 = -100
-  db_at_99 = -100
-  db_at_98 = -100
-  db_at_97 = -100
-  db_at_96 = -200
-  db_at_95 = -200
-  db_at_94 = -200
-  db_at_93 = -200
-  db_at_92 = -200
-  db_at_91 = -200
-  db_at_90 = -200
-  db_at_89 = -200
-  db_at_88 = -300
-  db_at_87 = -300
-  db_at_86 = -300
-  db_at_85 = -300
-  db_at_84 = -300
-  db_at_83 = -300
-  db_at_82 = -300
-  db_at_81 = -400
-  db_at_80 = -400
-  db_at_79 = -400
-  db_at_78 = -400
-  db_at_77 = -400
-  db_at_76 = -400
-  db_at_75 = -400
-  db_at_74 = -500
-  db_at_73 = -500
-  db_at_72 = -500
-  db_at_71 = -500
-  db_at_70 = -500
-  db_at_69 = -500
-  db_at_68 = -500
-  db_at_67 = -500
-  db_at_66 = -600
-  db_at_65 = -600
-  db_at_64 = -600
-  db_at_63 = -600
-  db_at_62 = -600
-  db_at_61 = -600
-  db_at_60 = -600
-  db_at_59 = -700
-  db_at_58 = -700
-  db_at_57 = -700
-  db_at_56 = -700
-  db_at_55 = -700
-  db_at_54 = -800
-  db_at_53 = -800
-  db_at_52 = -800
-  db_at_51 = -900
-  db_at_50 = -900
-  db_at_49 = -900
-  db_at_48 = -900
-  db_at_47 = -1000
-  db_at_46 = -1000
-  db_at_45 = -1000
-  db_at_44 = -1100
-  db_at_43 = -1100
-  db_at_42 = -1100
-  db_at_41 = -1200
-  db_at_40 = -1200
-  db_at_39 = -1200
-  db_at_38 = -1200
-  db_at_37 = -1300
-  db_at_36 = -1300
-  db_at_35 = -1300
-  db_at_34 = -1400
-  db_at_33 = -1400
-  db_at_32 = -1400
-  db_at_31 = -1500
-  db_at_30 = -1500
-  db_at_29 = -1500
-  db_at_28 = -1500
-  db_at_27 = -1600
-  db_at_26 = -1600
-  db_at_25 = -1600
-  db_at_24 = -1700
-  db_at_23 = -1700
-  db_at_22 = -1700
-  db_at_21 = -1800
-  db_at_20 = -1800
-  db_at_19 = -1800
-  db_at_18 = -1900
-  db_at_17 = -1900
-  db_at_16 = -1900
-  db_at_15 = -1900
-  db_at_14 = -2000
-  db_at_13 = -2000
-  db_at_12 = -2000
-  db_at_11 = -2100
-  db_at_10 = -2100
-  db_at_9 = -2100
-  db_at_8 = -2200
-  db_at_7 = -2200
-  db_at_6 = -2200
-  db_at_5 = -2200
-  db_at_4 = -2300
-  db_at_3 = -2300
-  db_at_2 = -2300
-  db_at_1 = -2400
-  db_at_0 = -2400
-[Headphone]
-  volume_curve = simple_step
-  volume_step = 70
-  max_volume = 0
diff --git a/cras-config/veyron_mighty/dsp.ini b/cras-config/veyron_mighty/dsp.ini
deleted file mode 100644
index 94c7ad2..0000000
--- a/cras-config/veyron_mighty/dsp.ini
+++ /dev/null
@@ -1,119 +0,0 @@
-[output_source]
-library=builtin
-label=source
-purpose=playback
-disable=(not (equal? dsp_name "speaker_eq"))
-output_0={src:0}
-output_1={src:1}
-
-[output_sink]
-library=builtin
-label=sink
-purpose=playback
-input_0={dst:0}
-input_1={dst:1}
-
-[drc]
-library=builtin
-label=drc
-input_0={src:0}
-input_1={src:1}
-output_2={intermediate:0}
-output_3={intermediate:1}
-input_4=1         ; emphasis_disabled
-input_5=0         ; f
-input_6=1         ; enable
-input_7=-24       ; threshold
-input_8=30        ; knee
-input_9=12        ; ratio
-input_10=0.003     ; attack
-input_11=0.25      ; release
-input_12=0         ; boost
-input_13=200       ; f
-input_14=1         ; enable
-input_15=-24       ; threshold
-input_16=30        ; knee
-input_17=12        ; ratio
-input_18=0.003     ; attack
-input_19=0.25      ; release
-input_20=0         ; boost
-input_21=2000      ; f
-input_22=1         ; enable
-input_23=-24       ; threshold
-input_24=30        ; knee
-input_25=12        ; ratio
-input_26=0.003     ; attack
-input_27=0.25      ; release
-input_28=0         ; boost
-
-[eq2]
-library=builtin
-label=eq2
-input_0={intermediate:0}
-input_1={intermediate:1}
-output_2={dst:0}
-output_3={dst:1}
-input_4=2       ; highpass
-input_5=250     ; freq
-input_6=3       ; Q
-input_7=0       ; gain
-input_8=2       ; highpass
-input_9=300     ; freq
-input_10=3       ; Q
-input_11=0       ; gain
-input_12=6       ; peaking
-input_13=500     ; freq
-input_14=1       ; Q
-input_15=4.5     ; gain
-input_16=6       ; peaking
-input_17=500     ; freq
-input_18=1       ; Q
-input_19=4.5     ; gain
-input_20=6       ; peaking
-input_21=800     ; freq
-input_22=3       ; Q
-input_23=-8      ; gain
-input_24=6       ; peaking
-input_25=800     ; freq
-input_26=3       ; Q
-input_27=-8      ; gain
-input_28=6       ; peaking
-input_29=1000    ; freq
-input_30=1       ; Q
-input_31=-2      ; gain
-input_32=6       ; peaking
-input_33=1000    ; freq
-input_34=1       ; Q
-input_35=-2      ; gain
-input_36=6       ; peaking
-input_37=2600    ; freq
-input_38=1       ; Q
-input_39=-2      ; gain
-input_40=6       ; peaking
-input_41=2600    ; freq
-input_42=1       ; Q
-input_43=-2      ; gain
-input_44=6       ; peaking
-input_45=3500    ; freq
-input_46=2.5     ; Q
-input_47=-8      ; gain
-input_48=6       ; peaking
-input_49=3500    ; freq
-input_50=2.5     ; Q
-input_51=-8      ; gain
-input_52=6       ; peaking
-input_53=6000    ; freq
-input_54=1       ; Q
-input_55=-2      ; gain
-input_56=6       ; peaking
-input_57=6000    ; freq
-input_58=1       ; Q
-input_59=-2      ; gain
-input_60=5       ; highshelf
-input_61=8000    ; freq
-input_62=1       ; Q
-input_63=1.5     ; gain
-input_64=5       ; highshelf
-input_65=8000    ; freq
-input_66=1       ; Q
-input_67=1.5     ; gain
diff --git a/cras-config/veyron_minnie-kernelnext b/cras-config/veyron_minnie-kernelnext
deleted file mode 120000
index 5fea6bb..0000000
--- a/cras-config/veyron_minnie-kernelnext
+++ /dev/null
@@ -1 +0,0 @@
-veyron_minnie
\ No newline at end of file
diff --git a/cras-config/veyron_minnie/ROCKCHIP-I2S b/cras-config/veyron_minnie/ROCKCHIP-I2S
deleted file mode 100644
index 297ac51..0000000
--- a/cras-config/veyron_minnie/ROCKCHIP-I2S
+++ /dev/null
@@ -1,107 +0,0 @@
-[Speaker]
-  volume_curve = explicit
-  db_at_100 = -500
-  db_at_99 = -500
-  db_at_98 = -500
-  db_at_97 = -600
-  db_at_96 = -600
-  db_at_95 = -600
-  db_at_94 = -600
-  db_at_93 = -700
-  db_at_92 = -700
-  db_at_91 = -700
-  db_at_90 = -700
-  db_at_89 = -800
-  db_at_88 = -800
-  db_at_87 = -800
-  db_at_86 = -800
-  db_at_85 = -900
-  db_at_84 = -900
-  db_at_83 = -900
-  db_at_82 = -900
-  db_at_81 = -1000
-  db_at_80 = -1000
-  db_at_79 = -1000
-  db_at_78 = -1000
-  db_at_77 = -1100
-  db_at_76 = -1100
-  db_at_75 = -1100
-  db_at_74 = -1100
-  db_at_73 = -1200
-  db_at_72 = -1200
-  db_at_71 = -1200
-  db_at_70 = -1200
-  db_at_69 = -1300
-  db_at_68 = -1300
-  db_at_67 = -1300
-  db_at_66 = -1300
-  db_at_65 = -1400
-  db_at_64 = -1400
-  db_at_63 = -1400
-  db_at_62 = -1400
-  db_at_61 = -1500
-  db_at_60 = -1500
-  db_at_59 = -1500
-  db_at_58 = -1500
-  db_at_57 = -1600
-  db_at_56 = -1600
-  db_at_55 = -1600
-  db_at_54 = -1600
-  db_at_53 = -1700
-  db_at_52 = -1700
-  db_at_51 = -1700
-  db_at_50 = -1700
-  db_at_49 = -1800
-  db_at_48 = -1800
-  db_at_47 = -1800
-  db_at_46 = -1800
-  db_at_45 = -1900
-  db_at_44 = -1900
-  db_at_43 = -1900
-  db_at_42 = -1900
-  db_at_41 = -2000
-  db_at_40 = -2000
-  db_at_39 = -2000
-  db_at_38 = -2100
-  db_at_37 = -2100
-  db_at_36 = -2200
-  db_at_35 = -2200
-  db_at_34 = -2300
-  db_at_33 = -2300
-  db_at_32 = -2400
-  db_at_31 = -2400
-  db_at_30 = -2500
-  db_at_29 = -2500
-  db_at_28 = -2600
-  db_at_27 = -2600
-  db_at_26 = -2700
-  db_at_25 = -2700
-  db_at_24 = -2800
-  db_at_23 = -2900
-  db_at_22 = -2900
-  db_at_21 = -3000
-  db_at_20 = -3100
-  db_at_19 = -3200
-  db_at_18 = -3200
-  db_at_17 = -3300
-  db_at_16 = -3400
-  db_at_15 = -3500
-  db_at_14 = -3500
-  db_at_13 = -3600
-  db_at_12 = -3700
-  db_at_11 = -3800
-  db_at_10 = -3800
-  db_at_9 = -3900
-  db_at_8 = -4000
-  db_at_7 = -4100
-  db_at_6 = -4100
-  db_at_5 = -4200
-  db_at_4 = -4300
-  db_at_3 = -4400
-  db_at_2 = -4400
-  db_at_1 = -4500
-  db_at_0 = -4600
-[Headphone]
-  volume_curve = simple_step
-  volume_step = 70
-  max_volume = 0
diff --git a/cras-config/veyron_minnie/VEYRON-I2S b/cras-config/veyron_minnie/VEYRON-I2S
deleted file mode 100644
index 297ac51..0000000
--- a/cras-config/veyron_minnie/VEYRON-I2S
+++ /dev/null
@@ -1,107 +0,0 @@
-[Speaker]
-  volume_curve = explicit
-  db_at_100 = -500
-  db_at_99 = -500
-  db_at_98 = -500
-  db_at_97 = -600
-  db_at_96 = -600
-  db_at_95 = -600
-  db_at_94 = -600
-  db_at_93 = -700
-  db_at_92 = -700
-  db_at_91 = -700
-  db_at_90 = -700
-  db_at_89 = -800
-  db_at_88 = -800
-  db_at_87 = -800
-  db_at_86 = -800
-  db_at_85 = -900
-  db_at_84 = -900
-  db_at_83 = -900
-  db_at_82 = -900
-  db_at_81 = -1000
-  db_at_80 = -1000
-  db_at_79 = -1000
-  db_at_78 = -1000
-  db_at_77 = -1100
-  db_at_76 = -1100
-  db_at_75 = -1100
-  db_at_74 = -1100
-  db_at_73 = -1200
-  db_at_72 = -1200
-  db_at_71 = -1200
-  db_at_70 = -1200
-  db_at_69 = -1300
-  db_at_68 = -1300
-  db_at_67 = -1300
-  db_at_66 = -1300
-  db_at_65 = -1400
-  db_at_64 = -1400
-  db_at_63 = -1400
-  db_at_62 = -1400
-  db_at_61 = -1500
-  db_at_60 = -1500
-  db_at_59 = -1500
-  db_at_58 = -1500
-  db_at_57 = -1600
-  db_at_56 = -1600
-  db_at_55 = -1600
-  db_at_54 = -1600
-  db_at_53 = -1700
-  db_at_52 = -1700
-  db_at_51 = -1700
-  db_at_50 = -1700
-  db_at_49 = -1800
-  db_at_48 = -1800
-  db_at_47 = -1800
-  db_at_46 = -1800
-  db_at_45 = -1900
-  db_at_44 = -1900
-  db_at_43 = -1900
-  db_at_42 = -1900
-  db_at_41 = -2000
-  db_at_40 = -2000
-  db_at_39 = -2000
-  db_at_38 = -2100
-  db_at_37 = -2100
-  db_at_36 = -2200
-  db_at_35 = -2200
-  db_at_34 = -2300
-  db_at_33 = -2300
-  db_at_32 = -2400
-  db_at_31 = -2400
-  db_at_30 = -2500
-  db_at_29 = -2500
-  db_at_28 = -2600
-  db_at_27 = -2600
-  db_at_26 = -2700
-  db_at_25 = -2700
-  db_at_24 = -2800
-  db_at_23 = -2900
-  db_at_22 = -2900
-  db_at_21 = -3000
-  db_at_20 = -3100
-  db_at_19 = -3200
-  db_at_18 = -3200
-  db_at_17 = -3300
-  db_at_16 = -3400
-  db_at_15 = -3500
-  db_at_14 = -3500
-  db_at_13 = -3600
-  db_at_12 = -3700
-  db_at_11 = -3800
-  db_at_10 = -3800
-  db_at_9 = -3900
-  db_at_8 = -4000
-  db_at_7 = -4100
-  db_at_6 = -4100
-  db_at_5 = -4200
-  db_at_4 = -4300
-  db_at_3 = -4400
-  db_at_2 = -4400
-  db_at_1 = -4500
-  db_at_0 = -4600
-[Headphone]
-  volume_curve = simple_step
-  volume_step = 70
-  max_volume = 0
diff --git a/cras-config/veyron_minnie/dsp.ini b/cras-config/veyron_minnie/dsp.ini
deleted file mode 100644
index 148b098..0000000
--- a/cras-config/veyron_minnie/dsp.ini
+++ /dev/null
@@ -1,79 +0,0 @@
-[output_source]
-library=builtin
-label=source
-purpose=playback
-disable=(not (equal? dsp_name "speaker_eq"))
-output_0={src:0}
-output_1={src:1}
-
-[output_sink]
-library=builtin
-label=sink
-purpose=playback
-input_0={dst:0}
-input_1={dst:1}
-
-[drc]
-library=builtin
-label=drc
-input_0={src:0}
-input_1={src:1}
-output_2={intermediate:0}
-output_3={intermediate:1}
-input_4=1         ; emphasis_disabled
-input_5=0         ; f
-input_6=0         ; enable
-input_7=-24       ; threshold
-input_8=30        ; knee
-input_9=12        ; ratio
-input_10=0.003     ; attack
-input_11=0.25      ; release
-input_12=0         ; boost
-input_13=100       ; f
-input_14=1         ; enable
-input_15=-24       ; threshold
-input_16=24        ; knee
-input_17=12        ; ratio
-input_18=0.003     ; attack
-input_19=0.5       ; release
-input_20=1         ; boost
-input_21=2000      ; f
-input_22=0         ; enable
-input_23=-24       ; threshold
-input_24=30        ; knee
-input_25=12        ; ratio
-input_26=0.003     ; attack
-input_27=0.25      ; release
-input_28=0         ; boost
-
-[eq2]
-library=builtin
-label=eq2
-input_0={intermediate:0}
-input_1={intermediate:1}
-output_2={dst:0}
-output_3={dst:1}
-input_4=2       ; highpass
-input_5=200     ; freq
-input_6=0       ; Q
-input_7=0       ; gain
-input_8=2       ; highpass
-input_9=200     ; freq
-input_10=0       ; Q
-input_11=0       ; gain
-input_12=6       ; peaking
-input_13=700     ; freq
-input_14=3       ; Q
-input_15=-12     ; gain
-input_16=6       ; peaking
-input_17=700     ; freq
-input_18=3       ; Q
-input_19=-12     ; gain
-input_20=6       ; peaking
-input_21=930     ; freq
-input_22=3       ; Q
-input_23=-12     ; gain
-input_24=6       ; peaking
-input_25=980     ; freq
-input_26=3       ; Q
-input_27=-12     ; gain
diff --git a/cras-config/veyron_speedy/ROCKCHIP-I2S b/cras-config/veyron_speedy/ROCKCHIP-I2S
deleted file mode 100644
index 297ac51..0000000
--- a/cras-config/veyron_speedy/ROCKCHIP-I2S
+++ /dev/null
@@ -1,107 +0,0 @@
-[Speaker]
-  volume_curve = explicit
-  db_at_100 = -500
-  db_at_99 = -500
-  db_at_98 = -500
-  db_at_97 = -600
-  db_at_96 = -600
-  db_at_95 = -600
-  db_at_94 = -600
-  db_at_93 = -700
-  db_at_92 = -700
-  db_at_91 = -700
-  db_at_90 = -700
-  db_at_89 = -800
-  db_at_88 = -800
-  db_at_87 = -800
-  db_at_86 = -800
-  db_at_85 = -900
-  db_at_84 = -900
-  db_at_83 = -900
-  db_at_82 = -900
-  db_at_81 = -1000
-  db_at_80 = -1000
-  db_at_79 = -1000
-  db_at_78 = -1000
-  db_at_77 = -1100
-  db_at_76 = -1100
-  db_at_75 = -1100
-  db_at_74 = -1100
-  db_at_73 = -1200
-  db_at_72 = -1200
-  db_at_71 = -1200
-  db_at_70 = -1200
-  db_at_69 = -1300
-  db_at_68 = -1300
-  db_at_67 = -1300
-  db_at_66 = -1300
-  db_at_65 = -1400
-  db_at_64 = -1400
-  db_at_63 = -1400
-  db_at_62 = -1400
-  db_at_61 = -1500
-  db_at_60 = -1500
-  db_at_59 = -1500
-  db_at_58 = -1500
-  db_at_57 = -1600
-  db_at_56 = -1600
-  db_at_55 = -1600
-  db_at_54 = -1600
-  db_at_53 = -1700
-  db_at_52 = -1700
-  db_at_51 = -1700
-  db_at_50 = -1700
-  db_at_49 = -1800
-  db_at_48 = -1800
-  db_at_47 = -1800
-  db_at_46 = -1800
-  db_at_45 = -1900
-  db_at_44 = -1900
-  db_at_43 = -1900
-  db_at_42 = -1900
-  db_at_41 = -2000
-  db_at_40 = -2000
-  db_at_39 = -2000
-  db_at_38 = -2100
-  db_at_37 = -2100
-  db_at_36 = -2200
-  db_at_35 = -2200
-  db_at_34 = -2300
-  db_at_33 = -2300
-  db_at_32 = -2400
-  db_at_31 = -2400
-  db_at_30 = -2500
-  db_at_29 = -2500
-  db_at_28 = -2600
-  db_at_27 = -2600
-  db_at_26 = -2700
-  db_at_25 = -2700
-  db_at_24 = -2800
-  db_at_23 = -2900
-  db_at_22 = -2900
-  db_at_21 = -3000
-  db_at_20 = -3100
-  db_at_19 = -3200
-  db_at_18 = -3200
-  db_at_17 = -3300
-  db_at_16 = -3400
-  db_at_15 = -3500
-  db_at_14 = -3500
-  db_at_13 = -3600
-  db_at_12 = -3700
-  db_at_11 = -3800
-  db_at_10 = -3800
-  db_at_9 = -3900
-  db_at_8 = -4000
-  db_at_7 = -4100
-  db_at_6 = -4100
-  db_at_5 = -4200
-  db_at_4 = -4300
-  db_at_3 = -4400
-  db_at_2 = -4400
-  db_at_1 = -4500
-  db_at_0 = -4600
-[Headphone]
-  volume_curve = simple_step
-  volume_step = 70
-  max_volume = 0
diff --git a/cras-config/veyron_speedy/VEYRON-I2S b/cras-config/veyron_speedy/VEYRON-I2S
deleted file mode 100644
index 297ac51..0000000
--- a/cras-config/veyron_speedy/VEYRON-I2S
+++ /dev/null
@@ -1,107 +0,0 @@
-[Speaker]
-  volume_curve = explicit
-  db_at_100 = -500
-  db_at_99 = -500
-  db_at_98 = -500
-  db_at_97 = -600
-  db_at_96 = -600
-  db_at_95 = -600
-  db_at_94 = -600
-  db_at_93 = -700
-  db_at_92 = -700
-  db_at_91 = -700
-  db_at_90 = -700
-  db_at_89 = -800
-  db_at_88 = -800
-  db_at_87 = -800
-  db_at_86 = -800
-  db_at_85 = -900
-  db_at_84 = -900
-  db_at_83 = -900
-  db_at_82 = -900
-  db_at_81 = -1000
-  db_at_80 = -1000
-  db_at_79 = -1000
-  db_at_78 = -1000
-  db_at_77 = -1100
-  db_at_76 = -1100
-  db_at_75 = -1100
-  db_at_74 = -1100
-  db_at_73 = -1200
-  db_at_72 = -1200
-  db_at_71 = -1200
-  db_at_70 = -1200
-  db_at_69 = -1300
-  db_at_68 = -1300
-  db_at_67 = -1300
-  db_at_66 = -1300
-  db_at_65 = -1400
-  db_at_64 = -1400
-  db_at_63 = -1400
-  db_at_62 = -1400
-  db_at_61 = -1500
-  db_at_60 = -1500
-  db_at_59 = -1500
-  db_at_58 = -1500
-  db_at_57 = -1600
-  db_at_56 = -1600
-  db_at_55 = -1600
-  db_at_54 = -1600
-  db_at_53 = -1700
-  db_at_52 = -1700
-  db_at_51 = -1700
-  db_at_50 = -1700
-  db_at_49 = -1800
-  db_at_48 = -1800
-  db_at_47 = -1800
-  db_at_46 = -1800
-  db_at_45 = -1900
-  db_at_44 = -1900
-  db_at_43 = -1900
-  db_at_42 = -1900
-  db_at_41 = -2000
-  db_at_40 = -2000
-  db_at_39 = -2000
-  db_at_38 = -2100
-  db_at_37 = -2100
-  db_at_36 = -2200
-  db_at_35 = -2200
-  db_at_34 = -2300
-  db_at_33 = -2300
-  db_at_32 = -2400
-  db_at_31 = -2400
-  db_at_30 = -2500
-  db_at_29 = -2500
-  db_at_28 = -2600
-  db_at_27 = -2600
-  db_at_26 = -2700
-  db_at_25 = -2700
-  db_at_24 = -2800
-  db_at_23 = -2900
-  db_at_22 = -2900
-  db_at_21 = -3000
-  db_at_20 = -3100
-  db_at_19 = -3200
-  db_at_18 = -3200
-  db_at_17 = -3300
-  db_at_16 = -3400
-  db_at_15 = -3500
-  db_at_14 = -3500
-  db_at_13 = -3600
-  db_at_12 = -3700
-  db_at_11 = -3800
-  db_at_10 = -3800
-  db_at_9 = -3900
-  db_at_8 = -4000
-  db_at_7 = -4100
-  db_at_6 = -4100
-  db_at_5 = -4200
-  db_at_4 = -4300
-  db_at_3 = -4400
-  db_at_2 = -4400
-  db_at_1 = -4500
-  db_at_0 = -4600
-[Headphone]
-  volume_curve = simple_step
-  volume_step = 70
-  max_volume = 0
diff --git a/cras-config/veyron_speedy/dsp.ini b/cras-config/veyron_speedy/dsp.ini
deleted file mode 100644
index f95096d..0000000
--- a/cras-config/veyron_speedy/dsp.ini
+++ /dev/null
@@ -1,79 +0,0 @@
-[output_source]
-library=builtin
-label=source
-purpose=playback
-disable=(not (equal? dsp_name "speaker_eq"))
-output_0={src:0}
-output_1={src:1}
-
-[output_sink]
-library=builtin
-label=sink
-purpose=playback
-input_0={dst:0}
-input_1={dst:1}
-
-[drc]
-library=builtin
-label=drc
-input_0={src:0}
-input_1={src:1}
-output_2={intermediate:0}
-output_3={intermediate:1}
-input_4=1         ; emphasis_disabled
-input_5=0         ; f
-input_6=0         ; enable
-input_7=-24       ; threshold
-input_8=30        ; knee
-input_9=12        ; ratio
-input_10=0.003     ; attack
-input_11=0.25      ; release
-input_12=-6        ; boost
-input_13=50        ; f
-input_14=1         ; enable
-input_15=-24       ; threshold
-input_16=30        ; knee
-input_17=12        ; ratio
-input_18=0.003     ; attack
-input_19=0.25      ; release
-input_20=-4.5      ; boost
-input_21=2000      ; f
-input_22=0         ; enable
-input_23=-24       ; threshold
-input_24=30        ; knee
-input_25=12        ; ratio
-input_26=0.003     ; attack
-input_27=0.25      ; release
-input_28=0         ; boost
-
-[eq2]
-library=builtin
-label=eq2
-input_0={intermediate:0}
-input_1={intermediate:1}
-output_2={dst:0}
-output_3={dst:1}
-input_4=2       ; highpass
-input_5=300     ; freq
-input_6=0       ; Q
-input_7=0       ; gain
-input_8=2       ; highpass
-input_9=300     ; freq
-input_10=0       ; Q
-input_11=0       ; gain
-input_12=6       ; peaking
-input_13=600     ; freq
-input_14=4       ; Q
-input_15=-6      ; gain
-input_16=6       ; peaking
-input_17=600     ; freq
-input_18=4       ; Q
-input_19=-6      ; gain
-input_20=6       ; peaking
-input_21=800     ; freq
-input_22=4       ; Q
-input_23=-6      ; gain
-input_24=6       ; peaking
-input_25=800     ; freq
-input_26=4       ; Q
-input_27=-6      ; gain
diff --git a/cras-config/whirlwind/dsp.ini b/cras-config/whirlwind/dsp.ini
deleted file mode 100644
index 9b1badd..0000000
--- a/cras-config/whirlwind/dsp.ini
+++ /dev/null
@@ -1,95 +0,0 @@
-[output_source]
-library=builtin
-label=source
-purpose=playback
-disable=(not (equal? dsp_name "speaker_eq"))
-output_0={src:0}
-output_1={src:1}
-
-[output_sink]
-library=builtin
-label=sink
-purpose=playback
-input_0={dst:0}
-input_1={dst:1}
-
-[drc]
-library=builtin
-label=drc
-input_0={src:0}
-input_1={src:1}
-output_2={intermediate:0}
-output_3={intermediate:1}
-input_4=1         ; emphasis_disabled
-input_5=0         ; f
-input_6=1         ; enable
-input_7=-35       ; threshold
-input_8=24        ; knee
-input_9=8         ; ratio
-input_10=0.003     ; attack
-input_11=0.3       ; release
-input_12=0         ; boost
-input_13=450       ; f
-input_14=1         ; enable
-input_15=-16       ; threshold
-input_16=10        ; knee
-input_17=6         ; ratio
-input_18=0.003     ; attack
-input_19=0.3       ; release
-input_20=0         ; boost
-input_21=1500      ; f
-input_22=1         ; enable
-input_23=0         ; threshold
-input_24=0         ; knee
-input_25=1         ; ratio
-input_26=0.003     ; attack
-input_27=0.25      ; release
-input_28=0         ; boost
-
-[eq2]
-library=builtin
-label=eq2
-input_0={intermediate:0}
-input_1={intermediate:1}
-output_2={dst:0}
-output_3={dst:1}
-input_4=2       ; highpass
-input_5=150     ; freq
-input_6=0       ; Q
-input_7=0       ; gain
-input_8=2       ; highpass
-input_9=150     ; freq
-input_10=0       ; Q
-input_11=0       ; gain
-input_12=6       ; peaking
-input_13=350     ; freq
-input_14=5       ; Q
-input_15=-6      ; gain
-input_16=6       ; peaking
-input_17=350     ; freq
-input_18=5       ; Q
-input_19=-6      ; gain
-input_20=6       ; peaking
-input_21=700     ; freq
-input_22=2       ; Q
-input_23=-3      ; gain
-input_24=6       ; peaking
-input_25=700     ; freq
-input_26=2       ; Q
-input_27=-3      ; gain
-input_28=6       ; peaking
-input_29=3300    ; freq
-input_30=3       ; Q
-input_31=-4      ; gain
-input_32=6       ; peaking
-input_33=3300    ; freq
-input_34=3       ; Q
-input_35=-4      ; gain
-input_36=5       ; highshelf
-input_37=2000    ; freq
-input_38=1       ; Q
-input_39=2       ; gain
-input_40=5       ; highshelf
-input_41=2000    ; freq
-input_42=1       ; Q
-input_43=2       ; gain
diff --git a/cras-config/wolf/HDA Intel PCH b/cras-config/wolf/HDA Intel PCH
deleted file mode 100644
index e0adc3e..0000000
--- a/cras-config/wolf/HDA Intel PCH
+++ /dev/null
@@ -1,107 +0,0 @@
-[Speaker]
-  volume_curve = explicit
-  db_at_100 = 0
-  db_at_99 = -75
-  db_at_98 = -75
-  db_at_97 = -75
-  db_at_96 = -75
-  db_at_95 = -75
-  db_at_94 = -150
-  db_at_93 = -150
-  db_at_92 = -150
-  db_at_91 = -150
-  db_at_90 = -225
-  db_at_89 = -225
-  db_at_88 = -225
-  db_at_87 = -225
-  db_at_86 = -300
-  db_at_85 = -300
-  db_at_84 = -300
-  db_at_83 = -300
-  db_at_82 = -375
-  db_at_81 = -375
-  db_at_80 = -450
-  db_at_79 = -450
-  db_at_78 = -450
-  db_at_77 = -450
-  db_at_76 = -525
-  db_at_75 = -525
-  db_at_74 = -600
-  db_at_73 = -600
-  db_at_72 = -600
-  db_at_71 = -600
-  db_at_70 = -675
-  db_at_69 = -675
-  db_at_68 = -675
-  db_at_67 = -675
-  db_at_66 = -750
-  db_at_65 = -750
-  db_at_64 = -825
-  db_at_63 = -825
-  db_at_62 = -825
-  db_at_61 = -825
-  db_at_60 = -900
-  db_at_59 = -900
-  db_at_58 = -900
-  db_at_57 = -900
-  db_at_56 = -975
-  db_at_55 = -975
-  db_at_54 = -1050
-  db_at_53 = -1050
-  db_at_52 = -1050
-  db_at_51 = -1050
-  db_at_50 = -1125
-  db_at_49 = -1125
-  db_at_48 = -1200
-  db_at_47 = -1200
-  db_at_46 = -1200
-  db_at_45 = -1200
-  db_at_44 = -1275
-  db_at_43 = -1275
-  db_at_42 = -1275
-  db_at_41 = -1275
-  db_at_40 = -1350
-  db_at_39 = -1425
-  db_at_38 = -1425
-  db_at_37 = -1500
-  db_at_36 = -1500
-  db_at_35 = -1575
-  db_at_34 = -1650
-  db_at_33 = -1650
-  db_at_32 = -1725
-  db_at_31 = -1800
-  db_at_30 = -1800
-  db_at_29 = -1800
-  db_at_28 = -1875
-  db_at_27 = -2025
-  db_at_26 = -2100
-  db_at_25 = -2100
-  db_at_24 = -2175
-  db_at_23 = -2250
-  db_at_22 = -2400
-  db_at_21 = -2475
-  db_at_20 = -2550
-  db_at_19 = -2625
-  db_at_18 = -2700
-  db_at_17 = -2850
-  db_at_16 = -2925
-  db_at_15 = -3000
-  db_at_14 = -3075
-  db_at_13 = -3225
-  db_at_12 = -3300
-  db_at_11 = -3375
-  db_at_10 = -3450
-  db_at_9 = -3600
-  db_at_8 = -3675
-  db_at_7 = -3750
-  db_at_6 = -3825
-  db_at_5 = -3900
-  db_at_4 = -4050
-  db_at_3 = -4050
-  db_at_2 = -4275
-  db_at_1 = -4500
-  db_at_0 = -4800
-[Headphone]
-  volume_curve = simple_step
-  volume_step = 50
-  max_volume = 0
diff --git a/cras-config/wolf/dsp.ini b/cras-config/wolf/dsp.ini
deleted file mode 100644
index 6afaf61..0000000
--- a/cras-config/wolf/dsp.ini
+++ /dev/null
@@ -1,103 +0,0 @@
-[output_source]
-library=builtin
-label=source
-purpose=playback
-disable=(not (equal? dsp_name "speaker_eq"))
-output_0={src:0}
-output_1={src:1}
-
-[output_sink]
-library=builtin
-label=sink
-purpose=playback
-input_0={dst:0}
-input_1={dst:1}
-
-[drc]
-library=builtin
-label=drc
-input_0={src:0}
-input_1={src:1}
-output_2={intermediate:0}
-output_3={intermediate:1}
-input_4=0       ; emphasis_disabled
-input_5=0       ; f
-input_6=1       ; enable
-input_7=-16     ; threshold
-input_8=11      ; knee
-input_9=4.476   ; ratio
-input_10=0.003   ; attack
-input_11=0.427   ; release
-input_12=3       ; boost
-input_13=2292    ; f
-input_14=1       ; enable
-input_15=-17     ; threshold
-input_16=13      ; knee
-input_17=3.78    ; ratio
-input_18=0.003   ; attack
-input_19=0.421   ; release
-input_20=4       ; boost
-input_21=3000    ; f
-input_22=0       ; enable
-input_23=-24     ; threshold
-input_24=30      ; knee
-input_25=12      ; ratio
-input_26=0.003   ; attack
-input_27=0.25    ; release
-input_28=0       ; boost
-
-[eq2]
-library=builtin
-label=eq2
-input_0={intermediate:0}
-input_1={intermediate:1}
-output_2={dst:0}
-output_3={dst:1}
-input_4=2       ; highpass
-input_5=200     ; freq
-input_6=0       ; Q
-input_7=0       ; gain
-input_8=2       ; highpass
-input_9=200     ; freq
-input_10=0       ; Q
-input_11=0       ; gain
-input_12=6       ; peaking
-input_13=794     ; freq
-input_14=2       ; Q
-input_15=-6.6    ; gain
-input_16=6       ; peaking
-input_17=794     ; freq
-input_18=2       ; Q
-input_19=-6.6    ; gain
-input_20=6       ; peaking
-input_21=1491    ; freq
-input_22=3.8537  ; Q
-input_23=-4.6    ; gain
-input_24=6       ; peaking
-input_25=1491    ; freq
-input_26=3.8537  ; Q
-input_27=-4.6    ; gain
-input_28=6       ; peaking
-input_29=399     ; freq
-input_30=5.2495  ; Q
-input_31=-5.1    ; gain
-input_32=6       ; peaking
-input_33=399     ; freq
-input_34=5.2495  ; Q
-input_35=-5.1    ; gain
-input_36=6       ; peaking
-input_37=2645    ; freq
-input_38=1       ; Q
-input_39=-1.7    ; gain
-input_40=6       ; peaking
-input_41=2645    ; freq
-input_42=1       ; Q
-input_43=-1.7    ; gain
-input_44=6       ; peaking
-input_45=2966    ; freq
-input_46=5.5184  ; Q
-input_47=4.1     ; gain
-input_48=6       ; peaking
-input_49=2966    ; freq
-input_50=5.5184  ; Q
-input_51=4.1     ; gain
diff --git a/cras/Makefile.am b/cras/Makefile.am
index 6ecfbae..6c89bdc 100644
--- a/cras/Makefile.am
+++ b/cras/Makefile.am
@@ -5,3 +5,10 @@
 SUBDIRS = src
 pkgconfigdir = $(libdir)/pkgconfig
 pkgconfig_DATA = libcras.pc
+
+compile_commands.json:
+	which bear || (echo "Please install 'bear' first." && exit 1)
+	bear make check -j$(nproc)
+
+clean-local:
+	rm -f compile_commands.json
diff --git a/cras/README.dbus-api b/cras/README.dbus-api
index cac2f03..f347358 100644
--- a/cras/README.dbus-api
+++ b/cras/README.dbus-api
@@ -30,19 +30,12 @@
 
 			Sets the system output mute from user action.
 
-		void SetInputGain(int32 gain)
-
-			Sets the capture gain of the system. Gain is specified
-			in dBFS * 100.  For example 5dB of gain would be
-			specified with an argument of 500, while -10 would be
-			specified with -1000, and 11.5 maps to 1150.
-
 		void SetInputNodeGain(uint64 node_id, int32 gain)
 
-			Sets the capture gain of the node. Gain is specified
-			in dBFS * 100.  For example 5dB of gain would be
-			specified with an argument of 500, while -10 would be
-			specified with -1000, and 11.5 maps to 1150.
+			Sets the capture gain of the node. gain is a 0-100
+			integer which linearly maps [0, 50] to range [-40dB, 0dB]
+			and [50, 100] to [0dB, 20dB],
+			Default gain value is 50, which is 0dB.
 
 		void SetInputMute(boolean mute_on)
 
@@ -112,9 +105,6 @@
 				uint64 PluggedTime
 					The time that this device was plugged
 					in. This value is in microseconds.
-				string MicPositions
-					The string formed by floating numbers
-					describing the position of mic array.
 				unit64 NodeVolume
 					The node volume indexed from 0 to 100.
 				unit64 NodeCaptureGain
@@ -161,9 +151,9 @@
 		int32 IsAudioOutputActive()
 
 			Returns 1 if there are currently any active output streams,
-			excluding 'dummy' streams that are not actually outputting any
+			excluding 'fake' streams that are not actually outputting any
 			audio. Returns 0 if there are no active streams, or all active
-			streams are 'dummy' streams.
+			streams are 'fake' streams.
 
 		void SetGlobalOutputChannelRemix(int32 num_channels,
 						 array:double coefficient)
diff --git a/cras/README b/cras/README.md
similarity index 61%
rename from cras/README
rename to cras/README.md
index bb7004a..e0ef0ec 100644
--- a/cras/README
+++ b/cras/README.md
@@ -1,59 +1,88 @@
 CRAS = ChromeOS Audio Server
+===
 
-Directories
-src/server - the source for the sound server
-src/libcras - client library for interacting with cras
-src/common - files common to both the server and library
-src/tests - tests for cras and libcras
+# Directories
+- [src/server](src/server) - the source for the sound server
+- [src/libcras](src/libcras) - client library for interacting with cras
+- [src/common](src/common) - files common to both the server and library
+- [src/tests](src/tests) - tests for cras and libcras
+- [src/fuzz](src/fuzz) - source code and build scripts for coverage-guided
+  fuzzers for CRAS
 
-Building from source:
+# Building from source:
+```
+# Generate install-sh
 ./git_prepare.sh
-./configure
-make
+
+# Configure
+CC=clang \
+CXX=clang++ \
+CXXFLAGS="-g -O2 -std=gnu++11 -Wall" \
+CFLAGS="-g -O2 -Wall" \
+./configure --disable-alsa-plugin
+
+# Compile
+make -j$(nproc)
+
+# Compile with unit tests
+make -j$(nproc) check
+
+# Install binaries to /usr/bin
 sudo make install
+```
 
----------------------
-Configuration:
----------------------
+## Code complete for for editors
+You need to install [bear] first and generate [compile commands] for
+[language server plugins in editors] by
+```
+make clean && make compile_commands.json
+```
+Then you'll get `compile_commands.json` for editor.
+Import the JSON file to your editor and you'll get useful code complete
+features for CRAS and its unit tests.
 
-Device Blacklisting:
---------------------
-Blacklist of certain USB output device(s) is possible by modifying the config
-file /etc/cras/device_blacklist.
+# Configuration:
+
+## Device Blocklisting:
+
+Blocklist of certain USB output device(s) is possible by modifying the config
+file `/etc/cras/device_blocklist`.
 
 The format of this file is as follows:
-
+```
 [USB_Outputs]
   <vendor_id>_<product_id>_<checksum>_<device_index> = 1
-
+```
 Where vendor_id and product id are the USB identifiers for the card to
-blacklist. The checksum is the output of "cksum" command applied to the
+blocklist. The checksum is the output of "cksum" command applied to the
 sysfs "descriptors" file of the device. The device index specifies the
-index of the output device in the card to blacklist.  This is a bool
+index of the output device in the card to blocklist.  This is a bool
 parameter, so '= 1' enables the option.
 
-Example, blacklisting the non-functional output device reported by the C-Media
+Example, blocklisting the non-functional output device reported by the C-Media
 based CAD-u1 mic:
-
+```
 [USB_Outputs]
   0d8c_0008_00000000_0 = 1
+```
 
-Card Configuration:
--------------------
+## Card Configuration:
+
 There can be a config file for each sound alsa card on the system.  This file
-lives in /etc/cras/.  The file should be named with the card name returned by
+lives in `/etc/cras/`.  The file should be named with the card name returned by
 ALSA, the string in the second set of '[]' in the aplay -l output.  The ini file
 has the following format.
 
+```
 [<output-node-name>] ; Name of the mixer control for this output.
   <config-option> = <config-value>
-
+```
 output-node-name can be speficied in a few ways to link with the real node:
-  UCM device name - The name string following the SectionDevice label in UCM
+- UCM device name - The name string following the SectionDevice label in UCM
     config, i.e. HiFi.conf
-  Jack name - Name of the mixer control for mixer jack, or the gpio jack name
+- Jack name - Name of the mixer control for mixer jack, or the gpio jack name
     listed by 'evtest' command.
-  Mixer control name - e.g. "Headphone" or "Speaker", listed by
+- Mixer control name - e.g. "Headphone" or "Speaker", listed by
     'amixer scontrols' command.
 
 Note that an output node matches to the output-node-name label in card config by
@@ -62,12 +91,12 @@
 be used for searching, and lastly the mixer output control name.
 
 config-option can be the following:
-  volume_curve - The type of volume curve, "simple_step" or "explicit".
-  Options valid and mandatory when volume_curve = simple_step:
-    max_volume - The maximum volume for this output specified in dBFS * 100.
-    volume_step - Number of dB per volume 'tick' specified in  dBFS * 100.
-  Options valid and mandatory when volume_curve = explicit:
-    dB_at_N - The value in dB*100 that should be used for the volume at step
+- volume_curve - The type of volume curve, "simple_step" or "explicit".
+- Options valid and mandatory when volume_curve = simple_step:
+  - max_volume - The maximum volume for this output specified in dBFS * 100.
+  - volume_step - Number of dB per volume 'tick' specified in  dBFS * 100.
+- Options valid and mandatory when volume_curve = explicit:
+  - dB_at_N - The value in dB*100 that should be used for the volume at step
       "N".  There must be one of these for each setting from N=0 to 100
       inclusive.
 
@@ -78,6 +107,7 @@
 given, which is a 1dBFS per step curve from max = +0.5dBFS to min = -99.5dBFS
 (volume step 10 is -89.5dBFS).
 
+```
 [Headphone]
   volume_curve = simple_step
   volume_step = 75
@@ -185,3 +215,8 @@
   dB_at_98 = -150
   dB_at_99 = -50
   dB_at_100 = 50
+```
+
+[bear]: https://github.com/rizsotto/Bear
+[compile commands]: https://clang.llvm.org/extra/clangd/Installation.html#compile-commands-json
+[language server plugins in editors]: https://clang.llvm.org/extra/clangd/Installation.html#editor-plugins
diff --git a/cras/client/cras-sys/.gitignore b/cras/client/cras-sys/.gitignore
index 5245cb5..fa8d85a 100644
--- a/cras/client/cras-sys/.gitignore
+++ b/cras/client/cras-sys/.gitignore
@@ -1,2 +1,2 @@
-target/
-lib_gen.rs
+Cargo.lock
+target
diff --git a/cras/client/cras-sys/Android.bp b/cras/client/cras-sys/Android.bp
index 2a90f8f..d6482b2 100644
--- a/cras/client/cras-sys/Android.bp
+++ b/cras/client/cras-sys/Android.bp
@@ -1,26 +1,62 @@
-// This file is generated by cargo2android.py, added defaults.
+// This file is generated by cargo2android.py --run --device --test --global_defaults=crosvm_defaults --dependencies.
 
-rust_test_host {
-    name: "cras-sys_tests_cras_sys",
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "external_adhd_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-BSD
+    default_applicable_licenses: ["external_adhd_license"],
+}
+
+rust_defaults {
+    name: "cras-sys_defaults",
     defaults: ["crosvm_defaults"],
     crate_name: "cras_sys",
     srcs: ["src/lib.rs"],
-    relative_install_path: "cras-sys_tests",
     test_suites: ["general-tests"],
     auto_gen_config: true,
     edition: "2015",
-    rlibs: [
+    rustlibs: [
+        "libaudio_streams",
         "libdata_model",
     ],
 }
 
-rust_library_host_rlib {
+rust_test_host {
+    name: "cras-sys_host_test_src_lib",
+    defaults: ["cras-sys_defaults"],
+}
+
+rust_test {
+    name: "cras-sys_device_test_src_lib",
+    defaults: ["cras-sys_defaults"],
+}
+
+rust_library {
     name: "libcras_sys",
     defaults: ["crosvm_defaults"],
+    host_supported: true,
     crate_name: "cras_sys",
     srcs: ["src/lib.rs"],
     edition: "2015",
-    rlibs: [
+    rustlibs: [
+        "libaudio_streams",
         "libdata_model",
     ],
 }
+
+// dependent_library ["feature_list"]
+//   ../../../../crosvm/assertions/src/lib.rs
+//   ../../../../crosvm/data_model/src/lib.rs
+//   ../../../../crosvm/sync/src/lib.rs
+//   ../../../../crosvm/sys_util/poll_token_derive/poll_token_derive.rs
+//   ../../../../crosvm/sys_util/src/lib.rs
+//   ../../../../crosvm/syscall_defines/src/lib.rs
+//   ../../../../crosvm/tempfile/src/lib.rs
+//   ../../../audio_streams/src/audio_streams.rs
+//   libc-0.2.76 "default,std"
+//   proc-macro2-1.0.19 "default,proc-macro"
+//   quote-1.0.7 "default,proc-macro"
+//   syn-1.0.39 "clone-impls,default,derive,parsing,printing,proc-macro,quote"
+//   unicode-xid-0.2.1 "default"
diff --git a/cras/client/cras-sys/Cargo.toml b/cras/client/cras-sys/Cargo.toml
index f71e540..1ac1857 100644
--- a/cras/client/cras-sys/Cargo.toml
+++ b/cras/client/cras-sys/Cargo.toml
@@ -4,4 +4,5 @@
 authors = ["The Chromium OS Authors"]
 
 [dependencies]
-data_model = { path = "../../../../../platform/crosvm/data_model" } # provided by ebuild
+audio_streams = { path = "../../../audio_streams" } # provided by ebuild
+data_model = { path = "../../../../crosvm/data_model" } # provided by ebuild
diff --git a/cras/client/cras-sys/generator/src/main.rs b/cras/client/cras-sys/generator/src/main.rs
index 7b47f02..e562691 100644
--- a/cras/client/cras-sys/generator/src/main.rs
+++ b/cras/client/cras-sys/generator/src/main.rs
@@ -36,6 +36,7 @@
         "cras_shm.h",
         "cras_types.h",
         "cras_util.h",
+        "packet_status_logger.h",
     ];
 
     for header in &header_files {
@@ -110,8 +111,10 @@
         .whitelist_type("CRAS_.*")
         .whitelist_var("CRAS_.*")
         .whitelist_type("audio_message")
+        .whitelist_var("MAX_DEBUG_.*")
         .rustified_enum("CRAS_.*")
         .rustified_enum("_snd_pcm_.*")
+        .bitfield_enum("CRAS_STREAM_EFFECT")
         .generate()
         .expect(format!("Unable to generate {} code", name).as_str());
 
@@ -131,7 +134,11 @@
  * cras_shm.h
  * cras_types.h
  * cras_util.h
+ * packet_status_logger.h
  */
+
+#![allow(clippy::unreadable_literal)]
+#![allow(clippy::cognitive_complexity)]
 ";
 
     let mut output_file = File::create(output_path)?;
diff --git a/cras/client/cras-sys/src/gen.rs b/cras/client/cras-sys/src/gen.rs
index 59d146a..6fb4cdf 100644
--- a/cras/client/cras-sys/src/gen.rs
+++ b/cras/client/cras-sys/src/gen.rs
@@ -10,7 +10,11 @@
  * cras_shm.h
  * cras_types.h
  * cras_util.h
+ * packet_status_logger.h
  */
+
+#![allow(clippy::unreadable_literal)]
+#![allow(clippy::cognitive_complexity)]
 /* automatically generated by rust-bindgen */
 
 pub const CRAS_IODEV_NAME_BUFFER_SIZE: u32 = 64;
@@ -23,13 +27,15 @@
 pub const CRAS_MAX_ATTACHED_CLIENTS: u32 = 20;
 pub const CRAS_MAX_AUDIO_THREAD_SNAPSHOTS: u32 = 10;
 pub const CRAS_MAX_HOTWORD_MODEL_NAME_SIZE: u32 = 12;
+pub const MAX_DEBUG_DEVS: u32 = 4;
+pub const MAX_DEBUG_STREAMS: u32 = 8;
 pub const CRAS_BT_EVENT_LOG_SIZE: u32 = 1024;
 pub const CRAS_SERVER_STATE_VERSION: u32 = 2;
-pub const CRAS_PROTO_VER: u32 = 5;
+pub const CRAS_PROTO_VER: u32 = 7;
 pub const CRAS_SERV_MAX_MSG_SIZE: u32 = 256;
 pub const CRAS_CLIENT_MAX_MSG_SIZE: u32 = 256;
-pub const CRAS_MAX_HOTWORD_MODELS: u32 = 244;
-pub const CRAS_MAX_REMIX_CHANNELS: u32 = 32;
+pub const CRAS_MAX_HOTWORD_MODELS: u32 = 243;
+pub const CRAS_MAX_REMIX_CHANNELS: u32 = 8;
 pub const CRAS_MAX_TEST_DATA_LEN: u32 = 224;
 pub const CRAS_AEC_DUMP_FILE_NAME_LEN: u32 = 128;
 pub const CRAS_NUM_SHM_BUFFERS: u32 = 2;
@@ -48,7 +54,7 @@
     pub idx: u32,
     pub name: [::std::os::raw::c_char; 64usize],
     pub stable_id: u32,
-    pub stable_id_new: u32,
+    pub max_supported_channels: u32,
 }
 #[test]
 fn bindgen_test_layout_cras_iodev_info() {
@@ -93,13 +99,15 @@
         )
     );
     assert_eq!(
-        unsafe { &(*(::std::ptr::null::<cras_iodev_info>())).stable_id_new as *const _ as usize },
+        unsafe {
+            &(*(::std::ptr::null::<cras_iodev_info>())).max_supported_channels as *const _ as usize
+        },
         72usize,
         concat!(
             "Offset of field: ",
             stringify!(cras_iodev_info),
             "::",
-            stringify!(stable_id_new)
+            stringify!(max_supported_channels)
         )
     );
 }
@@ -113,11 +121,10 @@
     pub plugged_time: cras_ionode_info__bindgen_ty_1,
     pub volume: u32,
     pub capture_gain: i32,
+    pub ui_gain_scaler: f32,
     pub left_right_swapped: i32,
     pub type_enum: u32,
     pub stable_id: u32,
-    pub stable_id_new: u32,
-    pub mic_positions: [::std::os::raw::c_char; 128usize],
     pub type_: [::std::os::raw::c_char; 32usize],
     pub name: [::std::os::raw::c_char; 64usize],
     pub active_hotword_model: [::std::os::raw::c_char; 16usize],
@@ -169,7 +176,7 @@
 fn bindgen_test_layout_cras_ionode_info() {
     assert_eq!(
         ::std::mem::size_of::<cras_ionode_info>(),
-        296usize,
+        168usize,
         concat!("Size of: ", stringify!(cras_ionode_info))
     );
     assert_eq!(
@@ -248,10 +255,20 @@
         )
     );
     assert_eq!(
+        unsafe { &(*(::std::ptr::null::<cras_ionode_info>())).ui_gain_scaler as *const _ as usize },
+        40usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(cras_ionode_info),
+            "::",
+            stringify!(ui_gain_scaler)
+        )
+    );
+    assert_eq!(
         unsafe {
             &(*(::std::ptr::null::<cras_ionode_info>())).left_right_swapped as *const _ as usize
         },
-        40usize,
+        44usize,
         concat!(
             "Offset of field: ",
             stringify!(cras_ionode_info),
@@ -261,7 +278,7 @@
     );
     assert_eq!(
         unsafe { &(*(::std::ptr::null::<cras_ionode_info>())).type_enum as *const _ as usize },
-        44usize,
+        48usize,
         concat!(
             "Offset of field: ",
             stringify!(cras_ionode_info),
@@ -271,7 +288,7 @@
     );
     assert_eq!(
         unsafe { &(*(::std::ptr::null::<cras_ionode_info>())).stable_id as *const _ as usize },
-        48usize,
+        52usize,
         concat!(
             "Offset of field: ",
             stringify!(cras_ionode_info),
@@ -280,28 +297,8 @@
         )
     );
     assert_eq!(
-        unsafe { &(*(::std::ptr::null::<cras_ionode_info>())).stable_id_new as *const _ as usize },
-        52usize,
-        concat!(
-            "Offset of field: ",
-            stringify!(cras_ionode_info),
-            "::",
-            stringify!(stable_id_new)
-        )
-    );
-    assert_eq!(
-        unsafe { &(*(::std::ptr::null::<cras_ionode_info>())).mic_positions as *const _ as usize },
-        56usize,
-        concat!(
-            "Offset of field: ",
-            stringify!(cras_ionode_info),
-            "::",
-            stringify!(mic_positions)
-        )
-    );
-    assert_eq!(
         unsafe { &(*(::std::ptr::null::<cras_ionode_info>())).type_ as *const _ as usize },
-        184usize,
+        56usize,
         concat!(
             "Offset of field: ",
             stringify!(cras_ionode_info),
@@ -311,7 +308,7 @@
     );
     assert_eq!(
         unsafe { &(*(::std::ptr::null::<cras_ionode_info>())).name as *const _ as usize },
-        216usize,
+        88usize,
         concat!(
             "Offset of field: ",
             stringify!(cras_ionode_info),
@@ -323,7 +320,7 @@
         unsafe {
             &(*(::std::ptr::null::<cras_ionode_info>())).active_hotword_model as *const _ as usize
         },
-        280usize,
+        152usize,
         concat!(
             "Offset of field: ",
             stringify!(cras_ionode_info),
@@ -616,6 +613,78 @@
         )
     );
 }
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct packet_status_logger {
+    pub data: [u8; 64usize],
+    pub size: ::std::os::raw::c_int,
+    pub wp: ::std::os::raw::c_int,
+    pub num_wraps: ::std::os::raw::c_int,
+    pub ts: timespec,
+}
+#[test]
+fn bindgen_test_layout_packet_status_logger() {
+    assert_eq!(
+        ::std::mem::size_of::<packet_status_logger>(),
+        96usize,
+        concat!("Size of: ", stringify!(packet_status_logger))
+    );
+    assert_eq!(
+        ::std::mem::align_of::<packet_status_logger>(),
+        8usize,
+        concat!("Alignment of ", stringify!(packet_status_logger))
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<packet_status_logger>())).data as *const _ as usize },
+        0usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(packet_status_logger),
+            "::",
+            stringify!(data)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<packet_status_logger>())).size as *const _ as usize },
+        64usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(packet_status_logger),
+            "::",
+            stringify!(size)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<packet_status_logger>())).wp as *const _ as usize },
+        68usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(packet_status_logger),
+            "::",
+            stringify!(wp)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<packet_status_logger>())).num_wraps as *const _ as usize },
+        72usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(packet_status_logger),
+            "::",
+            stringify!(num_wraps)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<packet_status_logger>())).ts as *const _ as usize },
+        80usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(packet_status_logger),
+            "::",
+            stringify!(ts)
+        )
+    );
+}
 #[repr(C, packed)]
 #[derive(Debug, Copy, Clone)]
 pub struct cras_timespec {
@@ -673,6 +742,18 @@
 }
 #[repr(u32)]
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+pub enum CRAS_CONNECTION_TYPE {
+    CRAS_CONTROL = 0,
+    CRAS_PLAYBACK = 1,
+    CRAS_CAPTURE = 2,
+    CRAS_VMS_LEGACY = 3,
+    CRAS_VMS_UNIFIED = 4,
+    CRAS_PLUGIN_PLAYBACK = 5,
+    CRAS_PLUGIN_UNIFIED = 6,
+    CRAS_NUM_CONN_TYPE = 7,
+}
+#[repr(u32)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
 pub enum CRAS_STREAM_DIRECTION {
     CRAS_STREAM_OUTPUT = 0,
     CRAS_STREAM_INPUT = 1,
@@ -717,15 +798,53 @@
     CRAS_CLIENT_TYPE_CHROME = 4,
     CRAS_CLIENT_TYPE_ARC = 5,
     CRAS_CLIENT_TYPE_CROSVM = 6,
+    CRAS_CLIENT_TYPE_SERVER_STREAM = 7,
+    CRAS_CLIENT_TYPE_LACROS = 8,
+    CRAS_CLIENT_TYPE_PLUGIN = 9,
+    CRAS_CLIENT_TYPE_ARCVM = 10,
+    CRAS_NUM_CLIENT_TYPE = 11,
 }
-#[repr(u32)]
+impl CRAS_STREAM_EFFECT {
+    pub const APM_ECHO_CANCELLATION: CRAS_STREAM_EFFECT = CRAS_STREAM_EFFECT(1);
+}
+impl CRAS_STREAM_EFFECT {
+    pub const APM_NOISE_SUPRESSION: CRAS_STREAM_EFFECT = CRAS_STREAM_EFFECT(2);
+}
+impl CRAS_STREAM_EFFECT {
+    pub const APM_GAIN_CONTROL: CRAS_STREAM_EFFECT = CRAS_STREAM_EFFECT(4);
+}
+impl CRAS_STREAM_EFFECT {
+    pub const APM_VOICE_DETECTION: CRAS_STREAM_EFFECT = CRAS_STREAM_EFFECT(8);
+}
+impl ::std::ops::BitOr<CRAS_STREAM_EFFECT> for CRAS_STREAM_EFFECT {
+    type Output = Self;
+    #[inline]
+    fn bitor(self, other: Self) -> Self {
+        CRAS_STREAM_EFFECT(self.0 | other.0)
+    }
+}
+impl ::std::ops::BitOrAssign for CRAS_STREAM_EFFECT {
+    #[inline]
+    fn bitor_assign(&mut self, rhs: CRAS_STREAM_EFFECT) {
+        self.0 |= rhs.0;
+    }
+}
+impl ::std::ops::BitAnd<CRAS_STREAM_EFFECT> for CRAS_STREAM_EFFECT {
+    type Output = Self;
+    #[inline]
+    fn bitand(self, other: Self) -> Self {
+        CRAS_STREAM_EFFECT(self.0 & other.0)
+    }
+}
+impl ::std::ops::BitAndAssign for CRAS_STREAM_EFFECT {
+    #[inline]
+    fn bitand_assign(&mut self, rhs: CRAS_STREAM_EFFECT) {
+        self.0 &= rhs.0;
+    }
+}
+#[repr(C)]
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
-pub enum CRAS_STREAM_EFFECT {
-    APM_ECHO_CANCELLATION = 1,
-    APM_NOISE_SUPRESSION = 2,
-    APM_GAIN_CONTROL = 4,
-    APM_VOICE_DETECTION = 8,
-}
+pub struct CRAS_STREAM_EFFECT(pub u32);
 #[repr(C, packed)]
 #[derive(Debug, Copy, Clone)]
 pub struct cras_attached_client_info {
@@ -800,19 +919,25 @@
     BT_A2DP_START = 6,
     BT_A2DP_SUSPENDED = 7,
     BT_CODEC_SELECTION = 8,
-    BT_DEV_CONNECTED_CHANGE = 9,
-    BT_DEV_CONN_WATCH_CB = 10,
-    BT_DEV_SUSPEND_CB = 11,
-    BT_HFP_NEW_CONNECTION = 12,
-    BT_HFP_REQUEST_DISCONNECT = 13,
-    BT_HFP_SUPPORTED_FEATURES = 14,
-    BT_HSP_NEW_CONNECTION = 15,
-    BT_HSP_REQUEST_DISCONNECT = 16,
-    BT_NEW_AUDIO_PROFILE_AFTER_CONNECT = 17,
-    BT_RESET = 18,
-    BT_SCO_CONNECT = 19,
-    BT_TRANSPORT_ACQUIRE = 20,
-    BT_TRANSPORT_RELEASE = 21,
+    BT_DEV_CONNECTED = 9,
+    BT_DEV_DISCONNECTED = 10,
+    BT_DEV_CONN_WATCH_CB = 11,
+    BT_DEV_SUSPEND_CB = 12,
+    BT_HFP_NEW_CONNECTION = 13,
+    BT_HFP_REQUEST_DISCONNECT = 14,
+    BT_HFP_SUPPORTED_FEATURES = 15,
+    BT_HFP_HF_INDICATOR = 16,
+    BT_HFP_SET_SPEAKER_GAIN = 17,
+    BT_HFP_UPDATE_SPEAKER_GAIN = 18,
+    BT_HSP_NEW_CONNECTION = 19,
+    BT_HSP_REQUEST_DISCONNECT = 20,
+    BT_NEW_AUDIO_PROFILE_AFTER_CONNECT = 21,
+    BT_RESET = 22,
+    BT_SCO_CONNECT = 23,
+    BT_TRANSPORT_ACQUIRE = 24,
+    BT_TRANSPORT_RELEASE = 25,
+    BT_TRANSPORT_SET_VOLUME = 26,
+    BT_TRANSPORT_UPDATE_VOLUME = 27,
 }
 #[repr(C, packed)]
 #[derive(Debug, Copy, Clone)]
@@ -889,7 +1014,8 @@
 #[repr(C, packed)]
 #[derive(Copy, Clone)]
 pub struct audio_thread_event_log {
-    pub write_pos: u32,
+    pub write_pos: u64,
+    pub sync_write_pos: u64,
     pub len: u32,
     pub log: [audio_thread_event; 6144usize],
 }
@@ -897,7 +1023,7 @@
 fn bindgen_test_layout_audio_thread_event_log() {
     assert_eq!(
         ::std::mem::size_of::<audio_thread_event_log>(),
-        122888usize,
+        122900usize,
         concat!("Size of: ", stringify!(audio_thread_event_log))
     );
     assert_eq!(
@@ -918,8 +1044,20 @@
         )
     );
     assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<audio_thread_event_log>())).sync_write_pos as *const _ as usize
+        },
+        8usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(audio_thread_event_log),
+            "::",
+            stringify!(sync_write_pos)
+        )
+    );
+    assert_eq!(
         unsafe { &(*(::std::ptr::null::<audio_thread_event_log>())).len as *const _ as usize },
-        4usize,
+        16usize,
         concat!(
             "Offset of field: ",
             stringify!(audio_thread_event_log),
@@ -929,7 +1067,7 @@
     );
     assert_eq!(
         unsafe { &(*(::std::ptr::null::<audio_thread_event_log>())).log as *const _ as usize },
-        8usize,
+        20usize,
         concat!(
             "Offset of field: ",
             stringify!(audio_thread_event_log),
@@ -955,13 +1093,15 @@
     pub highest_hw_level: u32,
     pub runtime_sec: u32,
     pub runtime_nsec: u32,
+    pub longest_wake_sec: u32,
+    pub longest_wake_nsec: u32,
     pub software_gain_scaler: f64,
 }
 #[test]
 fn bindgen_test_layout_audio_dev_debug_info() {
     assert_eq!(
         ::std::mem::size_of::<audio_dev_debug_info>(),
-        125usize,
+        133usize,
         concat!("Size of: ", stringify!(audio_dev_debug_info))
     );
     assert_eq!(
@@ -1134,10 +1274,34 @@
     );
     assert_eq!(
         unsafe {
+            &(*(::std::ptr::null::<audio_dev_debug_info>())).longest_wake_sec as *const _ as usize
+        },
+        117usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(audio_dev_debug_info),
+            "::",
+            stringify!(longest_wake_sec)
+        )
+    );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<audio_dev_debug_info>())).longest_wake_nsec as *const _ as usize
+        },
+        121usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(audio_dev_debug_info),
+            "::",
+            stringify!(longest_wake_nsec)
+        )
+    );
+    assert_eq!(
+        unsafe {
             &(*(::std::ptr::null::<audio_dev_debug_info>())).software_gain_scaler as *const _
                 as usize
         },
-        117usize,
+        125usize,
         concat!(
             "Offset of field: ",
             stringify!(audio_dev_debug_info),
@@ -1445,7 +1609,7 @@
 fn bindgen_test_layout_audio_debug_info() {
     assert_eq!(
         ::std::mem::size_of::<audio_debug_info>(),
-        124220usize,
+        124264usize,
         concat!("Size of: ", stringify!(audio_debug_info))
     );
     assert_eq!(
@@ -1485,7 +1649,7 @@
     );
     assert_eq!(
         unsafe { &(*(::std::ptr::null::<audio_debug_info>())).streams as *const _ as usize },
-        508usize,
+        540usize,
         concat!(
             "Offset of field: ",
             stringify!(audio_debug_info),
@@ -1495,7 +1659,7 @@
     );
     assert_eq!(
         unsafe { &(*(::std::ptr::null::<audio_debug_info>())).log as *const _ as usize },
-        1332usize,
+        1364usize,
         concat!(
             "Offset of field: ",
             stringify!(audio_debug_info),
@@ -1506,6 +1670,156 @@
 }
 #[repr(C, packed)]
 #[derive(Debug, Copy, Clone)]
+pub struct main_thread_event {
+    pub tag_sec: u32,
+    pub nsec: u32,
+    pub data1: u32,
+    pub data2: u32,
+    pub data3: u32,
+}
+#[test]
+fn bindgen_test_layout_main_thread_event() {
+    assert_eq!(
+        ::std::mem::size_of::<main_thread_event>(),
+        20usize,
+        concat!("Size of: ", stringify!(main_thread_event))
+    );
+    assert_eq!(
+        ::std::mem::align_of::<main_thread_event>(),
+        1usize,
+        concat!("Alignment of ", stringify!(main_thread_event))
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<main_thread_event>())).tag_sec as *const _ as usize },
+        0usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(main_thread_event),
+            "::",
+            stringify!(tag_sec)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<main_thread_event>())).nsec as *const _ as usize },
+        4usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(main_thread_event),
+            "::",
+            stringify!(nsec)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<main_thread_event>())).data1 as *const _ as usize },
+        8usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(main_thread_event),
+            "::",
+            stringify!(data1)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<main_thread_event>())).data2 as *const _ as usize },
+        12usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(main_thread_event),
+            "::",
+            stringify!(data2)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<main_thread_event>())).data3 as *const _ as usize },
+        16usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(main_thread_event),
+            "::",
+            stringify!(data3)
+        )
+    );
+}
+#[repr(C, packed)]
+#[derive(Copy, Clone)]
+pub struct main_thread_event_log {
+    pub write_pos: u32,
+    pub len: u32,
+    pub log: [main_thread_event; 1024usize],
+}
+#[test]
+fn bindgen_test_layout_main_thread_event_log() {
+    assert_eq!(
+        ::std::mem::size_of::<main_thread_event_log>(),
+        20488usize,
+        concat!("Size of: ", stringify!(main_thread_event_log))
+    );
+    assert_eq!(
+        ::std::mem::align_of::<main_thread_event_log>(),
+        1usize,
+        concat!("Alignment of ", stringify!(main_thread_event_log))
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<main_thread_event_log>())).write_pos as *const _ as usize },
+        0usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(main_thread_event_log),
+            "::",
+            stringify!(write_pos)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<main_thread_event_log>())).len as *const _ as usize },
+        4usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(main_thread_event_log),
+            "::",
+            stringify!(len)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<main_thread_event_log>())).log as *const _ as usize },
+        8usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(main_thread_event_log),
+            "::",
+            stringify!(log)
+        )
+    );
+}
+#[repr(C, packed)]
+#[derive(Copy, Clone)]
+pub struct main_thread_debug_info {
+    pub main_log: main_thread_event_log,
+}
+#[test]
+fn bindgen_test_layout_main_thread_debug_info() {
+    assert_eq!(
+        ::std::mem::size_of::<main_thread_debug_info>(),
+        20488usize,
+        concat!("Size of: ", stringify!(main_thread_debug_info))
+    );
+    assert_eq!(
+        ::std::mem::align_of::<main_thread_debug_info>(),
+        1usize,
+        concat!("Alignment of ", stringify!(main_thread_debug_info))
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<main_thread_debug_info>())).main_log as *const _ as usize },
+        0usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(main_thread_debug_info),
+            "::",
+            stringify!(main_log)
+        )
+    );
+}
+#[repr(C, packed)]
+#[derive(Debug, Copy, Clone)]
 pub struct cras_bt_event {
     pub tag_sec: u32,
     pub nsec: u32,
@@ -1619,12 +1933,13 @@
 #[derive(Copy, Clone)]
 pub struct cras_bt_debug_info {
     pub bt_log: cras_bt_event_log,
+    pub wbs_logger: packet_status_logger,
 }
 #[test]
 fn bindgen_test_layout_cras_bt_debug_info() {
     assert_eq!(
         ::std::mem::size_of::<cras_bt_debug_info>(),
-        16392usize,
+        16488usize,
         concat!("Size of: ", stringify!(cras_bt_debug_info))
     );
     assert_eq!(
@@ -1642,15 +1957,29 @@
             stringify!(bt_log)
         )
     );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<cras_bt_debug_info>())).wbs_logger as *const _ as usize },
+        16392usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(cras_bt_debug_info),
+            "::",
+            stringify!(wbs_logger)
+        )
+    );
 }
 #[repr(u32)]
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
 pub enum CRAS_AUDIO_THREAD_EVENT_TYPE {
-    AUDIO_THREAD_EVENT_BUSYLOOP = 0,
-    AUDIO_THREAD_EVENT_DEBUG = 1,
-    AUDIO_THREAD_EVENT_SEVERE_UNDERRUN = 2,
-    AUDIO_THREAD_EVENT_UNDERRUN = 3,
-    AUDIO_THREAD_EVENT_TYPE_COUNT = 4,
+    AUDIO_THREAD_EVENT_A2DP_OVERRUN = 0,
+    AUDIO_THREAD_EVENT_A2DP_THROTTLE = 1,
+    AUDIO_THREAD_EVENT_BUSYLOOP = 2,
+    AUDIO_THREAD_EVENT_DEBUG = 3,
+    AUDIO_THREAD_EVENT_SEVERE_UNDERRUN = 4,
+    AUDIO_THREAD_EVENT_UNDERRUN = 5,
+    AUDIO_THREAD_EVENT_DROP_SAMPLES = 6,
+    AUDIO_THREAD_EVENT_DEV_OVERRUN = 7,
+    AUDIO_THREAD_EVENT_TYPE_COUNT = 8,
 }
 #[repr(C, packed)]
 #[derive(Copy, Clone)]
@@ -1663,7 +1992,7 @@
 fn bindgen_test_layout_cras_audio_thread_snapshot() {
     assert_eq!(
         ::std::mem::size_of::<cras_audio_thread_snapshot>(),
-        124240usize,
+        124284usize,
         concat!("Size of: ", stringify!(cras_audio_thread_snapshot))
     );
     assert_eq!(
@@ -1719,7 +2048,7 @@
 fn bindgen_test_layout_cras_audio_thread_snapshot_buffer() {
     assert_eq!(
         ::std::mem::size_of::<cras_audio_thread_snapshot_buffer>(),
-        1242404usize,
+        1242844usize,
         concat!("Size of: ", stringify!(cras_audio_thread_snapshot_buffer))
     );
     assert_eq!(
@@ -1747,7 +2076,7 @@
         unsafe {
             &(*(::std::ptr::null::<cras_audio_thread_snapshot_buffer>())).pos as *const _ as usize
         },
-        1242400usize,
+        1242840usize,
         concat!(
             "Offset of field: ",
             stringify!(cras_audio_thread_snapshot_buffer),
@@ -1768,11 +2097,8 @@
     pub mute_locked: i32,
     pub suspended: i32,
     pub capture_gain: i32,
-    pub capture_gain_target: i32,
     pub capture_mute: i32,
     pub capture_mute_locked: i32,
-    pub min_capture_gain: i32,
-    pub max_capture_gain: i32,
     pub num_streams_attached: u32,
     pub num_output_devs: u32,
     pub num_input_devs: u32,
@@ -1795,12 +2121,17 @@
     pub snapshot_buffer: cras_audio_thread_snapshot_buffer,
     pub bt_debug_info: cras_bt_debug_info,
     pub bt_wbs_enabled: i32,
+    pub deprioritize_bt_wbs_mic: i32,
+    pub main_thread_debug_info: main_thread_debug_info,
+    pub num_input_streams_with_permission: [u32; 11usize],
+    pub noise_cancellation_enabled: i32,
+    pub hotword_pause_at_suspend: i32,
 }
 #[test]
 fn bindgen_test_layout_cras_server_state() {
     assert_eq!(
         ::std::mem::size_of::<cras_server_state>(),
-        1398352usize,
+        1414344usize,
         concat!("Size of: ", stringify!(cras_server_state))
     );
     assert_eq!(
@@ -1903,20 +2234,8 @@
         )
     );
     assert_eq!(
-        unsafe {
-            &(*(::std::ptr::null::<cras_server_state>())).capture_gain_target as *const _ as usize
-        },
-        36usize,
-        concat!(
-            "Offset of field: ",
-            stringify!(cras_server_state),
-            "::",
-            stringify!(capture_gain_target)
-        )
-    );
-    assert_eq!(
         unsafe { &(*(::std::ptr::null::<cras_server_state>())).capture_mute as *const _ as usize },
-        40usize,
+        36usize,
         concat!(
             "Offset of field: ",
             stringify!(cras_server_state),
@@ -1928,7 +2247,7 @@
         unsafe {
             &(*(::std::ptr::null::<cras_server_state>())).capture_mute_locked as *const _ as usize
         },
-        44usize,
+        40usize,
         concat!(
             "Offset of field: ",
             stringify!(cras_server_state),
@@ -1938,33 +2257,9 @@
     );
     assert_eq!(
         unsafe {
-            &(*(::std::ptr::null::<cras_server_state>())).min_capture_gain as *const _ as usize
-        },
-        48usize,
-        concat!(
-            "Offset of field: ",
-            stringify!(cras_server_state),
-            "::",
-            stringify!(min_capture_gain)
-        )
-    );
-    assert_eq!(
-        unsafe {
-            &(*(::std::ptr::null::<cras_server_state>())).max_capture_gain as *const _ as usize
-        },
-        52usize,
-        concat!(
-            "Offset of field: ",
-            stringify!(cras_server_state),
-            "::",
-            stringify!(max_capture_gain)
-        )
-    );
-    assert_eq!(
-        unsafe {
             &(*(::std::ptr::null::<cras_server_state>())).num_streams_attached as *const _ as usize
         },
-        56usize,
+        44usize,
         concat!(
             "Offset of field: ",
             stringify!(cras_server_state),
@@ -1976,7 +2271,7 @@
         unsafe {
             &(*(::std::ptr::null::<cras_server_state>())).num_output_devs as *const _ as usize
         },
-        60usize,
+        48usize,
         concat!(
             "Offset of field: ",
             stringify!(cras_server_state),
@@ -1988,7 +2283,7 @@
         unsafe {
             &(*(::std::ptr::null::<cras_server_state>())).num_input_devs as *const _ as usize
         },
-        64usize,
+        52usize,
         concat!(
             "Offset of field: ",
             stringify!(cras_server_state),
@@ -1998,7 +2293,7 @@
     );
     assert_eq!(
         unsafe { &(*(::std::ptr::null::<cras_server_state>())).output_devs as *const _ as usize },
-        68usize,
+        56usize,
         concat!(
             "Offset of field: ",
             stringify!(cras_server_state),
@@ -2008,7 +2303,7 @@
     );
     assert_eq!(
         unsafe { &(*(::std::ptr::null::<cras_server_state>())).input_devs as *const _ as usize },
-        1588usize,
+        1576usize,
         concat!(
             "Offset of field: ",
             stringify!(cras_server_state),
@@ -2020,7 +2315,7 @@
         unsafe {
             &(*(::std::ptr::null::<cras_server_state>())).num_output_nodes as *const _ as usize
         },
-        3108usize,
+        3096usize,
         concat!(
             "Offset of field: ",
             stringify!(cras_server_state),
@@ -2032,7 +2327,7 @@
         unsafe {
             &(*(::std::ptr::null::<cras_server_state>())).num_input_nodes as *const _ as usize
         },
-        3112usize,
+        3100usize,
         concat!(
             "Offset of field: ",
             stringify!(cras_server_state),
@@ -2042,7 +2337,7 @@
     );
     assert_eq!(
         unsafe { &(*(::std::ptr::null::<cras_server_state>())).output_nodes as *const _ as usize },
-        3116usize,
+        3104usize,
         concat!(
             "Offset of field: ",
             stringify!(cras_server_state),
@@ -2052,7 +2347,7 @@
     );
     assert_eq!(
         unsafe { &(*(::std::ptr::null::<cras_server_state>())).input_nodes as *const _ as usize },
-        9036usize,
+        6464usize,
         concat!(
             "Offset of field: ",
             stringify!(cras_server_state),
@@ -2064,7 +2359,7 @@
         unsafe {
             &(*(::std::ptr::null::<cras_server_state>())).num_attached_clients as *const _ as usize
         },
-        14956usize,
+        9824usize,
         concat!(
             "Offset of field: ",
             stringify!(cras_server_state),
@@ -2074,7 +2369,7 @@
     );
     assert_eq!(
         unsafe { &(*(::std::ptr::null::<cras_server_state>())).client_info as *const _ as usize },
-        14960usize,
+        9828usize,
         concat!(
             "Offset of field: ",
             stringify!(cras_server_state),
@@ -2084,7 +2379,7 @@
     );
     assert_eq!(
         unsafe { &(*(::std::ptr::null::<cras_server_state>())).update_count as *const _ as usize },
-        15280usize,
+        10148usize,
         concat!(
             "Offset of field: ",
             stringify!(cras_server_state),
@@ -2096,7 +2391,7 @@
         unsafe {
             &(*(::std::ptr::null::<cras_server_state>())).num_active_streams as *const _ as usize
         },
-        15284usize,
+        10152usize,
         concat!(
             "Offset of field: ",
             stringify!(cras_server_state),
@@ -2109,7 +2404,7 @@
             &(*(::std::ptr::null::<cras_server_state>())).last_active_stream_time as *const _
                 as usize
         },
-        15300usize,
+        10168usize,
         concat!(
             "Offset of field: ",
             stringify!(cras_server_state),
@@ -2121,7 +2416,7 @@
         unsafe {
             &(*(::std::ptr::null::<cras_server_state>())).audio_debug_info as *const _ as usize
         },
-        15316usize,
+        10184usize,
         concat!(
             "Offset of field: ",
             stringify!(cras_server_state),
@@ -2134,7 +2429,7 @@
             &(*(::std::ptr::null::<cras_server_state>())).default_output_buffer_size as *const _
                 as usize
         },
-        139536usize,
+        134448usize,
         concat!(
             "Offset of field: ",
             stringify!(cras_server_state),
@@ -2146,7 +2441,7 @@
         unsafe {
             &(*(::std::ptr::null::<cras_server_state>())).non_empty_status as *const _ as usize
         },
-        139540usize,
+        134452usize,
         concat!(
             "Offset of field: ",
             stringify!(cras_server_state),
@@ -2156,7 +2451,7 @@
     );
     assert_eq!(
         unsafe { &(*(::std::ptr::null::<cras_server_state>())).aec_supported as *const _ as usize },
-        139544usize,
+        134456usize,
         concat!(
             "Offset of field: ",
             stringify!(cras_server_state),
@@ -2166,7 +2461,7 @@
     );
     assert_eq!(
         unsafe { &(*(::std::ptr::null::<cras_server_state>())).aec_group_id as *const _ as usize },
-        139548usize,
+        134460usize,
         concat!(
             "Offset of field: ",
             stringify!(cras_server_state),
@@ -2178,7 +2473,7 @@
         unsafe {
             &(*(::std::ptr::null::<cras_server_state>())).snapshot_buffer as *const _ as usize
         },
-        139552usize,
+        134464usize,
         concat!(
             "Offset of field: ",
             stringify!(cras_server_state),
@@ -2188,7 +2483,7 @@
     );
     assert_eq!(
         unsafe { &(*(::std::ptr::null::<cras_server_state>())).bt_debug_info as *const _ as usize },
-        1381956usize,
+        1377308usize,
         concat!(
             "Offset of field: ",
             stringify!(cras_server_state),
@@ -2200,7 +2495,7 @@
         unsafe {
             &(*(::std::ptr::null::<cras_server_state>())).bt_wbs_enabled as *const _ as usize
         },
-        1398348usize,
+        1393796usize,
         concat!(
             "Offset of field: ",
             stringify!(cras_server_state),
@@ -2208,6 +2503,71 @@
             stringify!(bt_wbs_enabled)
         )
     );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<cras_server_state>())).deprioritize_bt_wbs_mic as *const _
+                as usize
+        },
+        1393800usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(cras_server_state),
+            "::",
+            stringify!(deprioritize_bt_wbs_mic)
+        )
+    );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<cras_server_state>())).main_thread_debug_info as *const _
+                as usize
+        },
+        1393804usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(cras_server_state),
+            "::",
+            stringify!(main_thread_debug_info)
+        )
+    );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<cras_server_state>())).num_input_streams_with_permission
+                as *const _ as usize
+        },
+        1414292usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(cras_server_state),
+            "::",
+            stringify!(num_input_streams_with_permission)
+        )
+    );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<cras_server_state>())).noise_cancellation_enabled as *const _
+                as usize
+        },
+        1414336usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(cras_server_state),
+            "::",
+            stringify!(noise_cancellation_enabled)
+        )
+    );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<cras_server_state>())).hotword_pause_at_suspend as *const _
+                as usize
+        },
+        1414340usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(cras_server_state),
+            "::",
+            stringify!(hotword_pause_at_suspend)
+        )
+    );
 }
 pub const cras_notify_device_action_CRAS_DEVICE_ACTION_ADD: cras_notify_device_action = 0;
 pub const cras_notify_device_action_CRAS_DEVICE_ACTION_REMOVE: cras_notify_device_action = 1;
@@ -2323,9 +2683,14 @@
     CRAS_NODE_TYPE_HOTWORD = 6,
     CRAS_NODE_TYPE_POST_MIX_PRE_DSP = 7,
     CRAS_NODE_TYPE_POST_DSP = 8,
-    CRAS_NODE_TYPE_USB = 9,
-    CRAS_NODE_TYPE_BLUETOOTH = 10,
-    CRAS_NODE_TYPE_UNKNOWN = 11,
+    CRAS_NODE_TYPE_BLUETOOTH_NB_MIC = 9,
+    CRAS_NODE_TYPE_USB = 10,
+    CRAS_NODE_TYPE_BLUETOOTH = 11,
+    CRAS_NODE_TYPE_FALLBACK_NORMAL = 12,
+    CRAS_NODE_TYPE_FALLBACK_ABNORMAL = 13,
+    CRAS_NODE_TYPE_UNKNOWN = 14,
+    CRAS_NODE_TYPE_ECHO_REFERENCE = 15,
+    CRAS_NODE_TYPE_ALSA_LOOPBACK = 16,
 }
 #[repr(u32)]
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
@@ -2369,6 +2734,8 @@
     CRAS_SERVER_RELOAD_AEC_CONFIG = 27,
     CRAS_SERVER_DUMP_BT = 28,
     CRAS_SERVER_SET_BT_WBS_ENABLED = 29,
+    CRAS_SERVER_GET_ATLOG_FD = 30,
+    CRAS_SERVER_DUMP_MAIN = 31,
 }
 #[repr(u32)]
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
@@ -2387,6 +2754,7 @@
     CRAS_CLIENT_NODE_LEFT_RIGHT_SWAPPED_CHANGED = 11,
     CRAS_CLIENT_INPUT_NODE_GAIN_CHANGED = 12,
     CRAS_CLIENT_NUM_ACTIVE_STREAMS_CHANGED = 13,
+    CRAS_CLIENT_ATLOG_FD_READY = 14,
 }
 #[repr(C, packed)]
 #[derive(Debug, Copy, Clone)]
@@ -2481,13 +2849,14 @@
     pub dev_idx: u32,
     pub effects: u64,
     pub client_type: CRAS_CLIENT_TYPE,
-    pub client_shm_size: u32,
+    pub client_shm_size: u64,
+    pub buffer_offsets: [u64; 2usize],
 }
 #[test]
 fn bindgen_test_layout_cras_connect_message() {
     assert_eq!(
         ::std::mem::size_of::<cras_connect_message>(),
-        79usize,
+        99usize,
         concat!("Size of: ", stringify!(cras_connect_message))
     );
     assert_eq!(
@@ -2637,158 +3006,16 @@
             stringify!(client_shm_size)
         )
     );
-}
-#[repr(C, packed)]
-#[derive(Debug, Copy, Clone)]
-pub struct cras_connect_message_old {
-    pub header: cras_server_message,
-    pub proto_version: u32,
-    pub direction: CRAS_STREAM_DIRECTION,
-    pub stream_id: cras_stream_id_t,
-    pub stream_type: CRAS_STREAM_TYPE,
-    pub buffer_frames: u32,
-    pub cb_threshold: u32,
-    pub flags: u32,
-    pub format: cras_audio_format_packed,
-    pub dev_idx: u32,
-    pub effects: u64,
-}
-#[test]
-fn bindgen_test_layout_cras_connect_message_old() {
-    assert_eq!(
-        ::std::mem::size_of::<cras_connect_message_old>(),
-        71usize,
-        concat!("Size of: ", stringify!(cras_connect_message_old))
-    );
-    assert_eq!(
-        ::std::mem::align_of::<cras_connect_message_old>(),
-        1usize,
-        concat!("Alignment of ", stringify!(cras_connect_message_old))
-    );
-    assert_eq!(
-        unsafe { &(*(::std::ptr::null::<cras_connect_message_old>())).header as *const _ as usize },
-        0usize,
-        concat!(
-            "Offset of field: ",
-            stringify!(cras_connect_message_old),
-            "::",
-            stringify!(header)
-        )
-    );
     assert_eq!(
         unsafe {
-            &(*(::std::ptr::null::<cras_connect_message_old>())).proto_version as *const _ as usize
+            &(*(::std::ptr::null::<cras_connect_message>())).buffer_offsets as *const _ as usize
         },
-        8usize,
+        83usize,
         concat!(
             "Offset of field: ",
-            stringify!(cras_connect_message_old),
+            stringify!(cras_connect_message),
             "::",
-            stringify!(proto_version)
-        )
-    );
-    assert_eq!(
-        unsafe {
-            &(*(::std::ptr::null::<cras_connect_message_old>())).direction as *const _ as usize
-        },
-        12usize,
-        concat!(
-            "Offset of field: ",
-            stringify!(cras_connect_message_old),
-            "::",
-            stringify!(direction)
-        )
-    );
-    assert_eq!(
-        unsafe {
-            &(*(::std::ptr::null::<cras_connect_message_old>())).stream_id as *const _ as usize
-        },
-        16usize,
-        concat!(
-            "Offset of field: ",
-            stringify!(cras_connect_message_old),
-            "::",
-            stringify!(stream_id)
-        )
-    );
-    assert_eq!(
-        unsafe {
-            &(*(::std::ptr::null::<cras_connect_message_old>())).stream_type as *const _ as usize
-        },
-        20usize,
-        concat!(
-            "Offset of field: ",
-            stringify!(cras_connect_message_old),
-            "::",
-            stringify!(stream_type)
-        )
-    );
-    assert_eq!(
-        unsafe {
-            &(*(::std::ptr::null::<cras_connect_message_old>())).buffer_frames as *const _ as usize
-        },
-        24usize,
-        concat!(
-            "Offset of field: ",
-            stringify!(cras_connect_message_old),
-            "::",
-            stringify!(buffer_frames)
-        )
-    );
-    assert_eq!(
-        unsafe {
-            &(*(::std::ptr::null::<cras_connect_message_old>())).cb_threshold as *const _ as usize
-        },
-        28usize,
-        concat!(
-            "Offset of field: ",
-            stringify!(cras_connect_message_old),
-            "::",
-            stringify!(cb_threshold)
-        )
-    );
-    assert_eq!(
-        unsafe { &(*(::std::ptr::null::<cras_connect_message_old>())).flags as *const _ as usize },
-        32usize,
-        concat!(
-            "Offset of field: ",
-            stringify!(cras_connect_message_old),
-            "::",
-            stringify!(flags)
-        )
-    );
-    assert_eq!(
-        unsafe { &(*(::std::ptr::null::<cras_connect_message_old>())).format as *const _ as usize },
-        36usize,
-        concat!(
-            "Offset of field: ",
-            stringify!(cras_connect_message_old),
-            "::",
-            stringify!(format)
-        )
-    );
-    assert_eq!(
-        unsafe {
-            &(*(::std::ptr::null::<cras_connect_message_old>())).dev_idx as *const _ as usize
-        },
-        59usize,
-        concat!(
-            "Offset of field: ",
-            stringify!(cras_connect_message_old),
-            "::",
-            stringify!(dev_idx)
-        )
-    );
-    assert_eq!(
-        unsafe {
-            &(*(::std::ptr::null::<cras_connect_message_old>())).effects as *const _ as usize
-        },
-        63usize,
-        concat!(
-            "Offset of field: ",
-            stringify!(cras_connect_message_old),
-            "::",
-            stringify!(effects)
+            stringify!(buffer_offsets)
         )
     );
 }
@@ -2934,49 +3161,6 @@
 }
 #[repr(C, packed)]
 #[derive(Debug, Copy, Clone)]
-pub struct cras_set_system_capture_gain {
-    pub header: cras_server_message,
-    pub gain: i32,
-}
-#[test]
-fn bindgen_test_layout_cras_set_system_capture_gain() {
-    assert_eq!(
-        ::std::mem::size_of::<cras_set_system_capture_gain>(),
-        12usize,
-        concat!("Size of: ", stringify!(cras_set_system_capture_gain))
-    );
-    assert_eq!(
-        ::std::mem::align_of::<cras_set_system_capture_gain>(),
-        1usize,
-        concat!("Alignment of ", stringify!(cras_set_system_capture_gain))
-    );
-    assert_eq!(
-        unsafe {
-            &(*(::std::ptr::null::<cras_set_system_capture_gain>())).header as *const _ as usize
-        },
-        0usize,
-        concat!(
-            "Offset of field: ",
-            stringify!(cras_set_system_capture_gain),
-            "::",
-            stringify!(header)
-        )
-    );
-    assert_eq!(
-        unsafe {
-            &(*(::std::ptr::null::<cras_set_system_capture_gain>())).gain as *const _ as usize
-        },
-        8usize,
-        concat!(
-            "Offset of field: ",
-            stringify!(cras_set_system_capture_gain),
-            "::",
-            stringify!(gain)
-        )
-    );
-}
-#[repr(C, packed)]
-#[derive(Debug, Copy, Clone)]
 pub struct cras_set_system_mute {
     pub header: cras_server_message,
     pub mute: i32,
@@ -3311,6 +3495,62 @@
 }
 #[repr(C, packed)]
 #[derive(Debug, Copy, Clone)]
+pub struct cras_get_atlog_fd {
+    pub header: cras_server_message,
+}
+#[test]
+fn bindgen_test_layout_cras_get_atlog_fd() {
+    assert_eq!(
+        ::std::mem::size_of::<cras_get_atlog_fd>(),
+        8usize,
+        concat!("Size of: ", stringify!(cras_get_atlog_fd))
+    );
+    assert_eq!(
+        ::std::mem::align_of::<cras_get_atlog_fd>(),
+        1usize,
+        concat!("Alignment of ", stringify!(cras_get_atlog_fd))
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<cras_get_atlog_fd>())).header as *const _ as usize },
+        0usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(cras_get_atlog_fd),
+            "::",
+            stringify!(header)
+        )
+    );
+}
+#[repr(C, packed)]
+#[derive(Debug, Copy, Clone)]
+pub struct cras_dump_main {
+    pub header: cras_server_message,
+}
+#[test]
+fn bindgen_test_layout_cras_dump_main() {
+    assert_eq!(
+        ::std::mem::size_of::<cras_dump_main>(),
+        8usize,
+        concat!("Size of: ", stringify!(cras_dump_main))
+    );
+    assert_eq!(
+        ::std::mem::align_of::<cras_dump_main>(),
+        1usize,
+        concat!("Alignment of ", stringify!(cras_dump_main))
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<cras_dump_main>())).header as *const _ as usize },
+        0usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(cras_dump_main),
+            "::",
+            stringify!(header)
+        )
+    );
+}
+#[repr(C, packed)]
+#[derive(Debug, Copy, Clone)]
 pub struct cras_dump_bt {
     pub header: cras_server_message,
 }
@@ -3477,17 +3717,17 @@
     );
 }
 #[repr(C, packed)]
-#[derive(Debug, Copy, Clone)]
+#[derive(Copy, Clone)]
 pub struct cras_config_global_remix {
     pub header: cras_server_message,
     pub num_channels: ::std::os::raw::c_uint,
-    pub coefficient: [f32; 32usize],
+    pub coefficient: [f32; 64usize],
 }
 #[test]
 fn bindgen_test_layout_cras_config_global_remix() {
     assert_eq!(
         ::std::mem::size_of::<cras_config_global_remix>(),
-        140usize,
+        268usize,
         concat!("Size of: ", stringify!(cras_config_global_remix))
     );
     assert_eq!(
@@ -3929,7 +4169,7 @@
         )
     );
 }
-#[repr(C)]
+#[repr(C, packed)]
 #[derive(Debug, Copy, Clone)]
 pub struct cras_client_audio_debug_info_ready {
     pub header: cras_client_message,
@@ -3963,7 +4203,37 @@
         )
     );
 }
-#[repr(C)]
+#[repr(C, packed)]
+#[derive(Debug, Copy, Clone)]
+pub struct cras_client_atlog_fd_ready {
+    pub header: cras_client_message,
+}
+#[test]
+fn bindgen_test_layout_cras_client_atlog_fd_ready() {
+    assert_eq!(
+        ::std::mem::size_of::<cras_client_atlog_fd_ready>(),
+        8usize,
+        concat!("Size of: ", stringify!(cras_client_atlog_fd_ready))
+    );
+    assert_eq!(
+        ::std::mem::align_of::<cras_client_atlog_fd_ready>(),
+        1usize,
+        concat!("Alignment of ", stringify!(cras_client_atlog_fd_ready))
+    );
+    assert_eq!(
+        unsafe {
+            &(*(::std::ptr::null::<cras_client_atlog_fd_ready>())).header as *const _ as usize
+        },
+        0usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(cras_client_atlog_fd_ready),
+            "::",
+            stringify!(header)
+        )
+    );
+}
+#[repr(C, packed)]
 #[derive(Copy, Clone)]
 pub struct cras_client_get_hotword_models_ready {
     pub header: cras_client_message,
@@ -3982,7 +4252,7 @@
     );
     assert_eq!(
         ::std::mem::align_of::<cras_client_get_hotword_models_ready>(),
-        4usize,
+        1usize,
         concat!(
             "Alignment of ",
             stringify!(cras_client_get_hotword_models_ready)
@@ -4457,13 +4727,13 @@
     pub callback_pending: i32,
     pub num_overruns: u32,
     pub ts: cras_timespec,
-    pub buffer_offset: [u32; 2usize],
+    pub buffer_offset: [u64; 2usize],
 }
 #[test]
 fn bindgen_test_layout_cras_audio_shm_header() {
     assert_eq!(
         ::std::mem::size_of::<cras_audio_shm_header>(),
-        80usize,
+        88usize,
         concat!("Size of: ", stringify!(cras_audio_shm_header))
     );
     assert_eq!(
diff --git a/cras/client/cras-sys/src/lib.rs b/cras/client/cras-sys/src/lib.rs
index 4056162..2b3d21e 100644
--- a/cras/client/cras-sys/src/lib.rs
+++ b/cras/client/cras-sys/src/lib.rs
@@ -1,49 +1,168 @@
 // Copyright 2019 The Chromium OS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
+extern crate audio_streams;
 extern crate data_model;
 
+use std::cmp::min;
+use std::convert::{TryFrom, TryInto};
+use std::error;
+use std::fmt;
+use std::iter::FromIterator;
+use std::os::raw::c_char;
+use std::str::FromStr;
+use std::time::Duration;
+
 #[allow(dead_code)]
 #[allow(non_upper_case_globals)]
 #[allow(non_camel_case_types)]
 #[allow(non_snake_case)]
 pub mod gen;
 use gen::{
-    _snd_pcm_format, audio_message, cras_audio_format_packed, CRAS_AUDIO_MESSAGE_ID, CRAS_CHANNEL,
+    _snd_pcm_format, audio_dev_debug_info, audio_message, audio_stream_debug_info,
+    cras_audio_format_packed, cras_iodev_info, cras_ionode_info, cras_ionode_info__bindgen_ty_1,
+    cras_timespec, snd_pcm_format_t, CRAS_AUDIO_MESSAGE_ID, CRAS_CHANNEL, CRAS_CLIENT_TYPE,
+    CRAS_NODE_TYPE, CRAS_STREAM_DIRECTION, CRAS_STREAM_EFFECT, CRAS_STREAM_TYPE,
 };
 
+use audio_streams::{SampleFormat, StreamDirection, StreamEffect};
+
 unsafe impl data_model::DataInit for gen::audio_message {}
+unsafe impl data_model::DataInit for gen::audio_debug_info {}
+unsafe impl data_model::DataInit for gen::audio_dev_debug_info {}
+unsafe impl data_model::DataInit for gen::audio_stream_debug_info {}
 unsafe impl data_model::DataInit for gen::cras_client_connected {}
 unsafe impl data_model::DataInit for gen::cras_client_stream_connected {}
 unsafe impl data_model::DataInit for gen::cras_connect_message {}
 unsafe impl data_model::DataInit for gen::cras_disconnect_stream_message {}
+unsafe impl data_model::DataInit for gen::cras_dump_audio_thread {}
+unsafe impl data_model::DataInit for gen::cras_iodev_info {}
+unsafe impl data_model::DataInit for gen::cras_ionode_info {}
 unsafe impl data_model::DataInit for gen::cras_server_state {}
+unsafe impl data_model::DataInit for gen::cras_set_system_mute {}
+unsafe impl data_model::DataInit for gen::cras_set_system_volume {}
+
+/// An enumeration of errors that can occur when converting the packed C
+/// structs into Rust-style structs.
+#[derive(Debug)]
+pub enum Error {
+    InvalidChannel(i8),
+    InvalidClientType(u32),
+    InvalidClientTypeStr,
+    InvalidStreamType(u32),
+}
+
+impl error::Error for Error {}
+
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        use Error::*;
+        match self {
+            InvalidChannel(c) => write!(
+                f,
+                "Channel value {} is not within valid range [0, {})",
+                c,
+                CRAS_CHANNEL::CRAS_CH_MAX as u32
+            ),
+            InvalidClientType(t) => write!(
+                f,
+                "Client type {} is not within valid range [0, {})",
+                t,
+                CRAS_CLIENT_TYPE::CRAS_CLIENT_TYPE_SERVER_STREAM as u32 + 1
+            ),
+            InvalidClientTypeStr => write!(f, "Invalid client type string"),
+            InvalidStreamType(t) => write!(
+                f,
+                "Stream type {} is not within valid range [0, {})",
+                t,
+                CRAS_STREAM_TYPE::CRAS_STREAM_NUM_TYPES as u32
+            ),
+        }
+    }
+}
 
 impl cras_audio_format_packed {
     /// Initializes `cras_audio_format_packed` from input parameters.
+    /// Field `channel_layout` will be assigned with default channel layout defined in
+    /// `Self::default_channel_layout`.
     ///
     /// # Arguments
     /// * `format` - Format in used.
     /// * `rate` - Rate in used.
     /// * `num_channels` - Number of channels in used.
+    /// * `direction` - Stream direction enumeration.
     ///
     /// # Returns
     /// Structure `cras_audio_format_packed`
-    pub fn new(format: _snd_pcm_format, rate: usize, num_channels: usize) -> Self {
-        let mut audio_format = Self {
+    pub fn new(
+        format: _snd_pcm_format,
+        rate: u32,
+        num_channels: usize,
+        direction: CRAS_STREAM_DIRECTION,
+    ) -> Self {
+        Self {
             format: format as i32,
-            frame_rate: rate as u32,
+            frame_rate: rate,
             num_channels: num_channels as u32,
-            channel_layout: [-1; CRAS_CHANNEL::CRAS_CH_MAX as usize],
-        };
-        for i in 0..CRAS_CHANNEL::CRAS_CH_MAX as usize {
-            if i < num_channels {
-                audio_format.channel_layout[i] = i as i8;
-            } else {
-                break;
+            channel_layout: Self::default_channel_layout(num_channels, direction),
+        }
+    }
+
+    /// Generates default channel layout by given number of channels and stream direction.
+    /// ```
+    /// use cras_sys::gen::{
+    ///     _snd_pcm_format,
+    ///     cras_audio_format_packed,
+    ///     CRAS_STREAM_DIRECTION::*
+    /// };
+    /// let test_one = | num_channels, direction, expected_results | {
+    ///     let default_channel_fmt = cras_audio_format_packed::new(
+    ///         _snd_pcm_format::SND_PCM_FORMAT_S16,
+    ///         48000,
+    ///         num_channels,
+    ///         direction
+    ///     );
+    ///     assert_eq!(default_channel_fmt.channel_layout, expected_results);
+    /// };
+    /// test_one(2, CRAS_STREAM_OUTPUT, [0, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1]);
+    /// test_one(4, CRAS_STREAM_OUTPUT, [0, 1, 2, 3, -1, -1, -1, -1, -1, -1, -1]);
+    /// test_one(6, CRAS_STREAM_OUTPUT, [0, 1, 4, 5, 2, 3, -1, -1, -1, -1, -1]);
+    /// test_one(2, CRAS_STREAM_INPUT, [0, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1]);
+    /// test_one(4, CRAS_STREAM_INPUT, [0, 1, 2, 3, -1, -1, -1, -1, -1, -1, -1]);
+    /// test_one(6, CRAS_STREAM_INPUT, [0, 1, 2, 3, 4, 5, -1, -1, -1, -1, -1]);
+    /// ```
+    fn default_channel_layout(
+        num_channels: usize,
+        direction: CRAS_STREAM_DIRECTION,
+    ) -> [i8; CRAS_CHANNEL::CRAS_CH_MAX as usize] {
+        use {CRAS_CHANNEL::*, CRAS_STREAM_DIRECTION::*};
+
+        let mut channel_layout = [-1; CRAS_CH_MAX as usize];
+        match (num_channels, direction) {
+            (6, CRAS_STREAM_OUTPUT) => {
+                [
+                    CRAS_CH_FL,
+                    CRAS_CH_FR,
+                    CRAS_CH_FC,
+                    CRAS_CH_LFE,
+                    CRAS_CH_RL,
+                    CRAS_CH_RR,
+                ]
+                .iter()
+                .enumerate()
+                .for_each(|(idx, &channel)| channel_layout[channel as usize] = idx as i8);
+            }
+            _ => {
+                for (i, channel) in channel_layout
+                    .iter_mut()
+                    .enumerate()
+                    .take(min(num_channels, CRAS_CH_MAX as usize))
+                {
+                    *channel = i as i8;
+                }
             }
         }
-        audio_format
+        channel_layout
     }
 }
 
@@ -56,3 +175,483 @@
         }
     }
 }
+
+impl Default for cras_iodev_info {
+    fn default() -> Self {
+        Self {
+            idx: 0,
+            name: [0; 64usize],
+            stable_id: 0,
+            max_supported_channels: 0,
+        }
+    }
+}
+
+#[derive(Debug)]
+pub struct CrasIodevInfo {
+    pub index: u32,
+    pub name: String,
+}
+
+fn cstring_to_string(cstring: &[c_char]) -> String {
+    let null_idx = match cstring.iter().enumerate().find(|(_, &c)| c == 0) {
+        Some((i, _)) => i,
+        None => return "".to_owned(),
+    };
+
+    let ptr = cstring.as_ptr() as *const u8;
+    let slice = unsafe { core::slice::from_raw_parts(ptr, null_idx) };
+    String::from_utf8_lossy(slice).to_string()
+}
+
+impl From<cras_iodev_info> for CrasIodevInfo {
+    fn from(info: cras_iodev_info) -> Self {
+        Self {
+            index: info.idx,
+            name: cstring_to_string(&info.name),
+        }
+    }
+}
+
+impl Default for cras_ionode_info {
+    fn default() -> Self {
+        Self {
+            iodev_idx: 0,
+            ionode_idx: 0,
+            plugged: 0,
+            active: 0,
+            plugged_time: cras_ionode_info__bindgen_ty_1 {
+                tv_sec: 0,
+                tv_usec: 0,
+            },
+            volume: 0,
+            ui_gain_scaler: 0.0,
+            capture_gain: 0,
+            left_right_swapped: 0,
+            type_enum: 0,
+            stable_id: 0,
+            type_: [0; 32usize],
+            name: [0; 64usize],
+            active_hotword_model: [0; 16usize],
+        }
+    }
+}
+
+impl From<u32> for CRAS_NODE_TYPE {
+    fn from(node_type: u32) -> CRAS_NODE_TYPE {
+        use CRAS_NODE_TYPE::*;
+        match node_type {
+            0 => CRAS_NODE_TYPE_INTERNAL_SPEAKER,
+            1 => CRAS_NODE_TYPE_HEADPHONE,
+            2 => CRAS_NODE_TYPE_HDMI,
+            3 => CRAS_NODE_TYPE_HAPTIC,
+            4 => CRAS_NODE_TYPE_LINEOUT,
+            5 => CRAS_NODE_TYPE_MIC,
+            6 => CRAS_NODE_TYPE_HOTWORD,
+            7 => CRAS_NODE_TYPE_POST_MIX_PRE_DSP,
+            8 => CRAS_NODE_TYPE_POST_DSP,
+            9 => CRAS_NODE_TYPE_USB,
+            10 => CRAS_NODE_TYPE_BLUETOOTH,
+            _ => CRAS_NODE_TYPE_UNKNOWN,
+        }
+    }
+}
+
+#[derive(Debug)]
+pub struct CrasIonodeInfo {
+    pub name: String,
+    pub iodev_index: u32,
+    pub ionode_index: u32,
+    pub stable_id: u32,
+    pub plugged: bool,
+    pub active: bool,
+    pub node_type: CRAS_NODE_TYPE,
+    pub type_name: String,
+    pub volume: u32,
+    pub capture_gain: i32,
+    pub plugged_time: cras_timespec,
+}
+
+impl From<cras_ionode_info> for CrasIonodeInfo {
+    fn from(info: cras_ionode_info) -> Self {
+        Self {
+            name: cstring_to_string(&info.name),
+            iodev_index: info.iodev_idx,
+            ionode_index: info.ionode_idx,
+            stable_id: info.stable_id,
+            plugged: info.plugged != 0,
+            active: info.active != 0,
+            node_type: CRAS_NODE_TYPE::from(info.type_enum),
+            type_name: cstring_to_string(&info.type_),
+            volume: info.volume,
+            capture_gain: info.capture_gain,
+            plugged_time: cras_timespec {
+                tv_sec: info.plugged_time.tv_sec,
+                tv_nsec: info.plugged_time.tv_usec * 1000,
+            },
+        }
+    }
+}
+
+impl From<u32> for CRAS_STREAM_DIRECTION {
+    fn from(node_type: u32) -> CRAS_STREAM_DIRECTION {
+        use CRAS_STREAM_DIRECTION::*;
+        match node_type {
+            0 => CRAS_STREAM_OUTPUT,
+            1 => CRAS_STREAM_INPUT,
+            2 => CRAS_STREAM_UNDEFINED,
+            3 => CRAS_STREAM_POST_MIX_PRE_DSP,
+            _ => CRAS_STREAM_UNDEFINED,
+        }
+    }
+}
+
+impl Default for audio_dev_debug_info {
+    fn default() -> Self {
+        Self {
+            dev_name: [0; 64],
+            buffer_size: 0,
+            min_buffer_level: 0,
+            min_cb_level: 0,
+            max_cb_level: 0,
+            frame_rate: 0,
+            num_channels: 0,
+            est_rate_ratio: 0.0,
+            direction: 0,
+            num_underruns: 0,
+            num_severe_underruns: 0,
+            highest_hw_level: 0,
+            runtime_sec: 0,
+            runtime_nsec: 0,
+            longest_wake_sec: 0,
+            longest_wake_nsec: 0,
+            software_gain_scaler: 0.0,
+        }
+    }
+}
+
+/// A rust-style representation of the server's packed audio_dev_debug_info
+/// struct.
+#[derive(Debug)]
+pub struct AudioDevDebugInfo {
+    pub dev_name: String,
+    pub buffer_size: u32,
+    pub min_buffer_level: u32,
+    pub min_cb_level: u32,
+    pub max_cb_level: u32,
+    pub frame_rate: u32,
+    pub num_channels: u32,
+    pub est_rate_ratio: f64,
+    pub direction: CRAS_STREAM_DIRECTION,
+    pub num_underruns: u32,
+    pub num_severe_underruns: u32,
+    pub highest_hw_level: u32,
+    pub runtime: Duration,
+    pub longest_wake: Duration,
+    pub software_gain_scaler: f64,
+}
+
+impl From<audio_dev_debug_info> for AudioDevDebugInfo {
+    fn from(info: audio_dev_debug_info) -> Self {
+        Self {
+            dev_name: cstring_to_string(&info.dev_name),
+            buffer_size: info.buffer_size,
+            min_buffer_level: info.min_buffer_level,
+            min_cb_level: info.min_cb_level,
+            max_cb_level: info.max_cb_level,
+            frame_rate: info.frame_rate,
+            num_channels: info.num_channels,
+            est_rate_ratio: info.est_rate_ratio,
+            direction: CRAS_STREAM_DIRECTION::from(u32::from(info.direction)),
+            num_underruns: info.num_underruns,
+            num_severe_underruns: info.num_severe_underruns,
+            highest_hw_level: info.highest_hw_level,
+            runtime: Duration::new(info.runtime_sec.into(), info.runtime_nsec),
+            longest_wake: Duration::new(info.longest_wake_sec.into(), info.longest_wake_nsec),
+            software_gain_scaler: info.software_gain_scaler,
+        }
+    }
+}
+
+impl fmt::Display for AudioDevDebugInfo {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        writeln!(f, "Device: {}", self.dev_name)?;
+        writeln!(f, "  Direction: {:?}", self.direction)?;
+        writeln!(f, "  Buffer size: {}", self.buffer_size)?;
+        writeln!(f, "  Minimum buffer level: {}", self.min_buffer_level)?;
+        writeln!(f, "  Minimum callback level: {}", self.min_cb_level)?;
+        writeln!(f, "  Max callback level: {}", self.max_cb_level)?;
+        writeln!(f, "  Frame rate: {}", self.frame_rate)?;
+        writeln!(f, "  Number of channels: {}", self.num_channels)?;
+        writeln!(f, "  Estimated rate ratio: {:.2}", self.est_rate_ratio)?;
+        writeln!(f, "  Underrun count: {}", self.num_underruns)?;
+        writeln!(f, "  Severe underrun count: {}", self.num_severe_underruns)?;
+        writeln!(f, "  Highest hardware level: {}", self.highest_hw_level)?;
+        writeln!(f, "  Runtime: {:?}", self.runtime)?;
+        writeln!(f, "  Longest wake: {:?}", self.longest_wake)?;
+        writeln!(f, "  Software gain scaler: {}", self.software_gain_scaler)?;
+        Ok(())
+    }
+}
+
+impl TryFrom<u32> for CRAS_STREAM_TYPE {
+    type Error = Error;
+    fn try_from(stream_type: u32) -> Result<Self, Self::Error> {
+        use CRAS_STREAM_TYPE::*;
+        match stream_type {
+            0 => Ok(CRAS_STREAM_TYPE_DEFAULT),
+            1 => Ok(CRAS_STREAM_TYPE_MULTIMEDIA),
+            2 => Ok(CRAS_STREAM_TYPE_VOICE_COMMUNICATION),
+            3 => Ok(CRAS_STREAM_TYPE_SPEECH_RECOGNITION),
+            4 => Ok(CRAS_STREAM_TYPE_PRO_AUDIO),
+            5 => Ok(CRAS_STREAM_TYPE_ACCESSIBILITY),
+            _ => Err(Error::InvalidStreamType(stream_type)),
+        }
+    }
+}
+
+impl TryFrom<u32> for CRAS_CLIENT_TYPE {
+    type Error = Error;
+    fn try_from(client_type: u32) -> Result<Self, Self::Error> {
+        use CRAS_CLIENT_TYPE::*;
+        match client_type {
+            0 => Ok(CRAS_CLIENT_TYPE_UNKNOWN),
+            1 => Ok(CRAS_CLIENT_TYPE_LEGACY),
+            2 => Ok(CRAS_CLIENT_TYPE_TEST),
+            3 => Ok(CRAS_CLIENT_TYPE_PCM),
+            4 => Ok(CRAS_CLIENT_TYPE_CHROME),
+            5 => Ok(CRAS_CLIENT_TYPE_ARC),
+            6 => Ok(CRAS_CLIENT_TYPE_CROSVM),
+            7 => Ok(CRAS_CLIENT_TYPE_SERVER_STREAM),
+            8 => Ok(CRAS_CLIENT_TYPE_LACROS),
+            _ => Err(Error::InvalidClientType(client_type)),
+        }
+    }
+}
+
+impl FromStr for CRAS_CLIENT_TYPE {
+    type Err = Error;
+    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
+        use CRAS_CLIENT_TYPE::*;
+        match s {
+            "crosvm" => Ok(CRAS_CLIENT_TYPE_CROSVM),
+            "arcvm" => Ok(CRAS_CLIENT_TYPE_ARCVM),
+            _ => Err(Error::InvalidClientTypeStr),
+        }
+    }
+}
+
+impl Default for audio_stream_debug_info {
+    fn default() -> Self {
+        Self {
+            stream_id: 0,
+            dev_idx: 0,
+            direction: 0,
+            stream_type: 0,
+            client_type: 0,
+            buffer_frames: 0,
+            cb_threshold: 0,
+            effects: 0,
+            flags: 0,
+            frame_rate: 0,
+            num_channels: 0,
+            longest_fetch_sec: 0,
+            longest_fetch_nsec: 0,
+            num_missed_cb: 0,
+            num_overruns: 0,
+            is_pinned: 0,
+            pinned_dev_idx: 0,
+            runtime_sec: 0,
+            runtime_nsec: 0,
+            stream_volume: 0.0,
+            channel_layout: [0; 11],
+        }
+    }
+}
+
+impl TryFrom<i8> for CRAS_CHANNEL {
+    type Error = Error;
+    fn try_from(channel: i8) -> Result<Self, Self::Error> {
+        use CRAS_CHANNEL::*;
+        match channel {
+            0 => Ok(CRAS_CH_FL),
+            1 => Ok(CRAS_CH_FR),
+            2 => Ok(CRAS_CH_RL),
+            3 => Ok(CRAS_CH_RR),
+            4 => Ok(CRAS_CH_FC),
+            5 => Ok(CRAS_CH_LFE),
+            6 => Ok(CRAS_CH_SL),
+            7 => Ok(CRAS_CH_SR),
+            8 => Ok(CRAS_CH_RC),
+            9 => Ok(CRAS_CH_FLC),
+            10 => Ok(CRAS_CH_FRC),
+            _ => Err(Error::InvalidChannel(channel)),
+        }
+    }
+}
+
+/// A rust-style representation of the server's packed audio_stream_debug_info
+/// struct.
+#[derive(Debug)]
+pub struct AudioStreamDebugInfo {
+    pub stream_id: u64,
+    pub dev_idx: u32,
+    pub direction: CRAS_STREAM_DIRECTION,
+    pub stream_type: CRAS_STREAM_TYPE,
+    pub client_type: CRAS_CLIENT_TYPE,
+    pub buffer_frames: u32,
+    pub cb_threshold: u32,
+    pub effects: u64,
+    pub flags: u32,
+    pub frame_rate: u32,
+    pub num_channels: u32,
+    pub longest_fetch: Duration,
+    pub num_missed_cb: u32,
+    pub num_overruns: u32,
+    pub is_pinned: bool,
+    pub pinned_dev_idx: u32,
+    pub runtime: Duration,
+    pub stream_volume: f64,
+    pub channel_layout: Vec<CRAS_CHANNEL>,
+}
+
+impl TryFrom<audio_stream_debug_info> for AudioStreamDebugInfo {
+    type Error = Error;
+    fn try_from(info: audio_stream_debug_info) -> Result<Self, Self::Error> {
+        let channel_layout = info
+            .channel_layout
+            .iter()
+            .cloned()
+            .take_while(|&c| c != -1)
+            .map(TryInto::try_into)
+            .collect::<Result<Vec<_>, _>>()?;
+        Ok(Self {
+            stream_id: info.stream_id,
+            dev_idx: info.dev_idx,
+            direction: info.direction.into(),
+            stream_type: info.stream_type.try_into()?,
+            client_type: info.client_type.try_into()?,
+            buffer_frames: info.buffer_frames,
+            cb_threshold: info.cb_threshold,
+            effects: info.effects,
+            flags: info.flags,
+            frame_rate: info.frame_rate,
+            num_channels: info.num_channels,
+            longest_fetch: Duration::new(info.longest_fetch_sec.into(), info.longest_fetch_nsec),
+            num_missed_cb: info.num_missed_cb,
+            num_overruns: info.num_overruns,
+            is_pinned: info.is_pinned != 0,
+            pinned_dev_idx: info.pinned_dev_idx,
+            runtime: Duration::new(info.runtime_sec.into(), info.runtime_nsec),
+            stream_volume: info.stream_volume,
+            channel_layout,
+        })
+    }
+}
+
+impl fmt::Display for AudioStreamDebugInfo {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        writeln!(
+            f,
+            "Stream: {}, Device index: {}",
+            self.stream_id, self.dev_idx
+        )?;
+        writeln!(f, "  Direction: {:?}", self.direction)?;
+        writeln!(f, "  Stream type: {:?}", self.stream_type)?;
+        writeln!(f, "  Client type: {:?}", self.client_type)?;
+        writeln!(f, "  Buffer frames: {}", self.buffer_frames)?;
+        writeln!(f, "  Callback threshold: {}", self.cb_threshold)?;
+        writeln!(f, "  Effects: {:#x}", self.effects)?;
+        writeln!(f, "  Frame rate: {}", self.frame_rate)?;
+        writeln!(f, "  Number of channels: {}", self.num_channels)?;
+        writeln!(f, "  Longest fetch: {:?}", self.longest_fetch)?;
+        writeln!(f, "  Overrun count: {}", self.num_overruns)?;
+        writeln!(f, "  Pinned: {}", self.is_pinned)?;
+        writeln!(f, "  Pinned device index: {}", self.pinned_dev_idx)?;
+        writeln!(f, "  Missed callbacks: {}", self.num_missed_cb)?;
+        match self.direction {
+            CRAS_STREAM_DIRECTION::CRAS_STREAM_OUTPUT => {
+                writeln!(f, "  Volume: {:.2}", self.stream_volume)?
+            }
+            CRAS_STREAM_DIRECTION::CRAS_STREAM_INPUT => {
+                writeln!(f, "  Gain: {:.2}", self.stream_volume)?
+            }
+            _ => (),
+        };
+        writeln!(f, "  Runtime: {:?}", self.runtime)?;
+        write!(f, "  Channel map:")?;
+        for channel in &self.channel_layout {
+            write!(f, " {:?}", channel)?;
+        }
+        writeln!(f)?;
+        Ok(())
+    }
+}
+
+/// A rust-style representation of the server's audio debug info.
+pub struct AudioDebugInfo {
+    pub devices: Vec<AudioDevDebugInfo>,
+    pub streams: Vec<AudioStreamDebugInfo>,
+}
+
+impl AudioDebugInfo {
+    pub fn new(devices: Vec<AudioDevDebugInfo>, streams: Vec<AudioStreamDebugInfo>) -> Self {
+        Self { devices, streams }
+    }
+}
+
+impl Into<u64> for CRAS_STREAM_EFFECT {
+    fn into(self) -> u64 {
+        u64::from(self.0)
+    }
+}
+
+impl CRAS_STREAM_EFFECT {
+    pub fn empty() -> Self {
+        CRAS_STREAM_EFFECT(0)
+    }
+}
+
+impl From<StreamDirection> for CRAS_STREAM_DIRECTION {
+    /// Convert an audio_streams StreamDirection into the corresponding CRAS_STREAM_DIRECTION.
+    fn from(direction: StreamDirection) -> Self {
+        match direction {
+            StreamDirection::Playback => CRAS_STREAM_DIRECTION::CRAS_STREAM_OUTPUT,
+            StreamDirection::Capture => CRAS_STREAM_DIRECTION::CRAS_STREAM_INPUT,
+        }
+    }
+}
+
+impl From<StreamEffect> for CRAS_STREAM_EFFECT {
+    /// Convert an audio_streams StreamEffect into the corresponding CRAS_STREAM_EFFECT.
+    fn from(effect: StreamEffect) -> Self {
+        match effect {
+            StreamEffect::NoEffect => CRAS_STREAM_EFFECT::empty(),
+            StreamEffect::EchoCancellation => CRAS_STREAM_EFFECT::APM_ECHO_CANCELLATION,
+        }
+    }
+}
+
+impl<'a> FromIterator<&'a StreamEffect> for CRAS_STREAM_EFFECT {
+    fn from_iter<I>(iter: I) -> Self
+    where
+        I: IntoIterator<Item = &'a StreamEffect>,
+    {
+        iter.into_iter().fold(
+            CRAS_STREAM_EFFECT::empty(),
+            |cras_effect, &stream_effect| cras_effect | stream_effect.into(),
+        )
+    }
+}
+
+/// Convert an audio_streams SampleFormat into the corresponding pcm_format.
+impl From<SampleFormat> for snd_pcm_format_t {
+    fn from(format: SampleFormat) -> Self {
+        match format {
+            SampleFormat::U8 => snd_pcm_format_t::SND_PCM_FORMAT_U8,
+            SampleFormat::S16LE => snd_pcm_format_t::SND_PCM_FORMAT_S16_LE,
+            SampleFormat::S24LE => snd_pcm_format_t::SND_PCM_FORMAT_S24_LE,
+            SampleFormat::S32LE => snd_pcm_format_t::SND_PCM_FORMAT_S32_LE,
+        }
+    }
+}
diff --git a/cras/client/cras_tests/Cargo.toml b/cras/client/cras_tests/Cargo.toml
index 94453dd..108fe6c 100644
--- a/cras/client/cras_tests/Cargo.toml
+++ b/cras/client/cras_tests/Cargo.toml
@@ -7,5 +7,11 @@
 [dependencies]
 audio_streams = { path = "../../../audio_streams" } # provided by ebuild
 getopts = "0.2.18"
+hound = "3.4.0"
 libcras = { path = "../libcras" } # provided by ebuild
-sys_util = { path = "../../../../../platform/crosvm/sys_util" } # provided by ebuild
+sys_util = { path = "../../../../crosvm/sys_util" } # provided by ebuild
+
+[profile.release]
+lto = true
+panic = 'abort'
+overflow-checks = true
diff --git a/cras/client/cras_tests/src/arguments.rs b/cras/client/cras_tests/src/arguments.rs
new file mode 100644
index 0000000..59e9ec2
--- /dev/null
+++ b/cras/client/cras_tests/src/arguments.rs
@@ -0,0 +1,462 @@
+// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+use std::error;
+use std::fmt;
+use std::path::PathBuf;
+
+use audio_streams::SampleFormat;
+use getopts::{self, Matches, Options};
+
+#[derive(Debug)]
+pub enum Error {
+    GetOpts(getopts::Fail),
+    InvalidArgument(String, String, String),
+    InvalidFiletype(String),
+    MissingArgument(String),
+    MissingCommand,
+    MissingFilename,
+    UnknownCommand(String),
+}
+
+impl error::Error for Error {}
+
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        use Error::*;
+        match self {
+            GetOpts(e) => write!(f, "Getopts Error: {}", e),
+            InvalidArgument(flag, value, error_msg) => {
+                write!(f, "Invalid {} argument '{}': {}", flag, value, error_msg)
+            }
+            InvalidFiletype(extension) => write!(
+                f,
+                "Invalid file extension '{}'. Supported types are 'wav' and 'raw'",
+                extension
+            ),
+            MissingArgument(subcommand) => write!(f, "Missing argument for {}", subcommand),
+            MissingCommand => write!(f, "A command must be provided"),
+            MissingFilename => write!(f, "A file name must be provided"),
+            UnknownCommand(s) => write!(f, "Unknown command '{}'", s),
+        }
+    }
+}
+
+type Result<T> = std::result::Result<T, Error>;
+
+/// The different types of commands that can be given to cras_tests.
+/// Any options for those commands are passed as parameters to the enum values.
+#[derive(Debug, PartialEq)]
+pub enum Command {
+    Capture(AudioOptions),
+    Playback(AudioOptions),
+    Control(ControlCommand),
+}
+
+impl Command {
+    pub fn parse<T: AsRef<str>>(args: &[T]) -> Result<Option<Self>> {
+        let program_name = args.get(0).map(|s| s.as_ref()).unwrap_or("cras_tests");
+        let remaining_args = args.get(2..).unwrap_or(&[]);
+        match args.get(1).map(|s| s.as_ref()) {
+            None => {
+                show_usage(program_name);
+                Err(Error::MissingCommand)
+            }
+            Some("help") => {
+                show_usage(program_name);
+                Ok(None)
+            }
+            Some("capture") => Ok(
+                AudioOptions::parse(program_name, "capture", remaining_args)?.map(Command::Capture),
+            ),
+            Some("playback") => Ok(
+                AudioOptions::parse(program_name, "playback", remaining_args)?
+                    .map(Command::Playback),
+            ),
+            Some("control") => {
+                Ok(ControlCommand::parse(program_name, remaining_args)?.map(Command::Control))
+            }
+            Some(s) => {
+                show_usage(program_name);
+                Err(Error::UnknownCommand(s.to_string()))
+            }
+        }
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub enum FileType {
+    Raw,
+    Wav,
+}
+
+impl fmt::Display for FileType {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            FileType::Raw => write!(f, "raw data"),
+            FileType::Wav => write!(f, "WAVE"),
+        }
+    }
+}
+
+fn show_usage(program_name: &str) {
+    eprintln!("Usage: {} [command] <command args>", program_name);
+    eprintln!("\nCommands:\n");
+    eprintln!("capture - Capture to a file from CRAS");
+    eprintln!("playback - Playback to CRAS from a file");
+    eprintln!("control - Get and set server settings");
+    eprintln!("\nhelp - Print help message");
+}
+
+fn show_audio_command_usage(program_name: &str, command: &str, opts: &Options) {
+    let brief = format!("Usage: {} {} [options] [filename]", program_name, command);
+    eprint!("{}", opts.usage(&brief));
+}
+
+/// The possible command line options that can be passed to the 'playback' and
+/// 'capture' commands. Optional values will be `Some(_)` only if a value was
+/// explicitly provided by the user.
+///
+/// This struct will be passed to `playback()` and `capture()`.
+#[derive(Debug, PartialEq)]
+pub enum LoopbackType {
+    PreDsp,
+    PostDsp,
+}
+
+#[derive(Debug, PartialEq)]
+pub struct AudioOptions {
+    pub file_name: PathBuf,
+    pub loopback_type: Option<LoopbackType>,
+    pub file_type: FileType,
+    pub buffer_size: Option<usize>,
+    pub num_channels: Option<usize>,
+    pub format: Option<SampleFormat>,
+    pub frame_rate: Option<u32>,
+}
+
+fn get_u32_param(matches: &Matches, option_name: &str) -> Result<Option<u32>> {
+    matches.opt_get::<u32>(option_name).map_err(|e| {
+        let argument = matches.opt_str(option_name).unwrap_or_default();
+        Error::InvalidArgument(option_name.to_string(), argument, e.to_string())
+    })
+}
+
+fn get_usize_param(matches: &Matches, option_name: &str) -> Result<Option<usize>> {
+    matches.opt_get::<usize>(option_name).map_err(|e| {
+        let argument = matches.opt_str(option_name).unwrap_or_default();
+        Error::InvalidArgument(option_name.to_string(), argument, e.to_string())
+    })
+}
+
+impl AudioOptions {
+    fn parse<T: AsRef<str>>(
+        program_name: &str,
+        command_name: &str,
+        args: &[T],
+    ) -> Result<Option<Self>> {
+        let mut opts = Options::new();
+        opts.optopt("b", "buffer_size", "Buffer size in frames", "SIZE")
+            .optopt("c", "channels", "Number of channels", "NUM")
+            .optopt(
+                "f",
+                "format",
+                "Sample format (U8, S16_LE, S24_LE, or S32_LE)",
+                "FORMAT",
+            )
+            .optopt("r", "rate", "Audio frame rate (Hz)", "RATE")
+            .optflag("h", "help", "Print help message");
+
+        if command_name == "capture" {
+            opts.optopt(
+                "",
+                "loopback",
+                "Capture from loopback device ('pre_dsp' or 'post_dsp')",
+                "DEVICE",
+            );
+        }
+
+        let args = args.iter().map(|s| s.as_ref());
+        let matches = match opts.parse(args) {
+            Ok(m) => m,
+            Err(e) => {
+                show_audio_command_usage(program_name, command_name, &opts);
+                return Err(Error::GetOpts(e));
+            }
+        };
+        if matches.opt_present("h") {
+            show_audio_command_usage(program_name, command_name, &opts);
+            return Ok(None);
+        }
+
+        let loopback_type = if matches.opt_defined("loopback") {
+            match matches.opt_str("loopback").as_deref() {
+                Some("pre_dsp") => Some(LoopbackType::PreDsp),
+                Some("post_dsp") => Some(LoopbackType::PostDsp),
+                Some(s) => {
+                    return Err(Error::InvalidArgument(
+                        "loopback".to_string(),
+                        s.to_string(),
+                        "Loopback type must be 'pre_dsp' or 'post_dsp'".to_string(),
+                    ))
+                }
+                None => None,
+            }
+        } else {
+            None
+        };
+
+        let file_name = match matches.free.get(0) {
+            None => {
+                show_audio_command_usage(program_name, command_name, &opts);
+                return Err(Error::MissingFilename);
+            }
+            Some(file_name) => PathBuf::from(file_name),
+        };
+
+        let extension = file_name
+            .extension()
+            .map(|s| s.to_string_lossy().into_owned());
+        let file_type = match extension.as_deref() {
+            Some("wav") | Some("wave") => FileType::Wav,
+            Some("raw") | None => FileType::Raw,
+            Some(extension) => return Err(Error::InvalidFiletype(extension.to_string())),
+        };
+
+        let buffer_size = get_usize_param(&matches, "buffer_size")?;
+        let num_channels = get_usize_param(&matches, "channels")?;
+        let frame_rate = get_u32_param(&matches, "rate")?;
+        let format = match matches.opt_str("format").as_deref() {
+            Some("U8") => Some(SampleFormat::U8),
+            Some("S16_LE") => Some(SampleFormat::S16LE),
+            Some("S24_LE") => Some(SampleFormat::S24LE),
+            Some("S32_LE") => Some(SampleFormat::S32LE),
+            Some(s) => {
+                show_audio_command_usage(program_name, command_name, &opts);
+                return Err(Error::InvalidArgument(
+                    "format".to_string(),
+                    s.to_string(),
+                    "Format must be 'U8', 'S16_LE', 'S24_LE', or 'S32_LE'".to_string(),
+                ));
+            }
+            None => None,
+        };
+
+        Ok(Some(AudioOptions {
+            loopback_type,
+            file_name,
+            file_type,
+            buffer_size,
+            num_channels,
+            format,
+            frame_rate,
+        }))
+    }
+}
+
+fn show_control_command_usage(program_name: &str) {
+    eprintln!("Usage: {} control [command] <command args>", program_name);
+    eprintln!("");
+    eprintln!("Commands:");
+    let commands = [
+        ("help", "", "Print help message"),
+        ("", "", ""),
+        ("get_volume", "", "Get the system volume (0 - 100)"),
+        (
+            "set_volume",
+            "VOLUME",
+            "Set the system volume to VOLUME (0 - 100)",
+        ),
+        ("get_mute", "", "Get the system mute state (true or false)"),
+        (
+            "set_mute",
+            "MUTE",
+            "Set the system mute state to MUTE (true or false)",
+        ),
+        ("", "", ""),
+        ("list_output_devices", "", "Print list of output devices"),
+        ("list_input_devices", "", "Print list of input devices"),
+        ("list_output_nodes", "", "Print list of output nodes"),
+        ("list_input_nodes", "", "Print list of input nodes"),
+        (
+            "dump_audio_debug_info",
+            "",
+            "Print stream info, device info, and audio thread log.",
+        ),
+    ];
+    for command in &commands {
+        let command_string = format!("{} {}", command.0, command.1);
+        eprintln!("\t{: <23} {}", command_string, command.2);
+    }
+}
+
+#[derive(Debug, PartialEq)]
+pub enum ControlCommand {
+    GetSystemVolume,
+    SetSystemVolume(u32),
+    GetSystemMute,
+    SetSystemMute(bool),
+    ListOutputDevices,
+    ListInputDevices,
+    ListOutputNodes,
+    ListInputNodes,
+    DumpAudioDebugInfo,
+}
+
+impl ControlCommand {
+    fn parse<T: AsRef<str>>(program_name: &str, args: &[T]) -> Result<Option<Self>> {
+        let mut args = args.iter().map(|s| s.as_ref());
+        match args.next() {
+            Some("help") => {
+                show_control_command_usage(program_name);
+                Ok(None)
+            }
+            Some("get_volume") => Ok(Some(ControlCommand::GetSystemVolume)),
+            Some("set_volume") => {
+                let volume_str = args
+                    .next()
+                    .ok_or_else(|| Error::MissingArgument("set_volume".to_string()))?;
+
+                let volume = volume_str.parse::<u32>().map_err(|e| {
+                    Error::InvalidArgument(
+                        "set_volume".to_string(),
+                        volume_str.to_string(),
+                        e.to_string(),
+                    )
+                })?;
+
+                Ok(Some(ControlCommand::SetSystemVolume(volume)))
+            }
+            Some("get_mute") => Ok(Some(ControlCommand::GetSystemMute)),
+            Some("set_mute") => {
+                let mute_str = args
+                    .next()
+                    .ok_or_else(|| Error::MissingArgument("set_mute".to_string()))?;
+
+                let mute = mute_str.parse::<bool>().map_err(|e| {
+                    Error::InvalidArgument(
+                        "set_mute".to_string(),
+                        mute_str.to_string(),
+                        e.to_string(),
+                    )
+                })?;
+                Ok(Some(ControlCommand::SetSystemMute(mute)))
+            }
+            Some("list_output_devices") => Ok(Some(ControlCommand::ListOutputDevices)),
+            Some("list_input_devices") => Ok(Some(ControlCommand::ListInputDevices)),
+            Some("list_output_nodes") => Ok(Some(ControlCommand::ListOutputNodes)),
+            Some("list_input_nodes") => Ok(Some(ControlCommand::ListInputNodes)),
+            Some("dump_audio_debug_info") => Ok(Some(ControlCommand::DumpAudioDebugInfo)),
+            Some(s) => {
+                show_control_command_usage(program_name);
+                Err(Error::UnknownCommand(s.to_string()))
+            }
+            None => {
+                show_control_command_usage(program_name);
+                Err(Error::MissingCommand)
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn parse_command() {
+        let command = Command::parse(&["cras_tests", "playback", "output.wav"])
+            .unwrap()
+            .unwrap();
+        assert_eq!(
+            command,
+            Command::Playback(AudioOptions {
+                file_name: PathBuf::from("output.wav"),
+                loopback_type: None,
+                file_type: FileType::Wav,
+                frame_rate: None,
+                num_channels: None,
+                format: None,
+                buffer_size: None,
+            })
+        );
+        let command = Command::parse(&["cras_tests", "capture", "input.raw"])
+            .unwrap()
+            .unwrap();
+        assert_eq!(
+            command,
+            Command::Capture(AudioOptions {
+                file_name: PathBuf::from("input.raw"),
+                loopback_type: None,
+                file_type: FileType::Raw,
+                frame_rate: None,
+                num_channels: None,
+                format: None,
+                buffer_size: None,
+            })
+        );
+
+        let command = Command::parse(&[
+            "cras_tests",
+            "playback",
+            "-r",
+            "44100",
+            "output.wave",
+            "-c",
+            "2",
+        ])
+        .unwrap()
+        .unwrap();
+        assert_eq!(
+            command,
+            Command::Playback(AudioOptions {
+                file_name: PathBuf::from("output.wave"),
+                loopback_type: None,
+                file_type: FileType::Wav,
+                frame_rate: Some(44100),
+                num_channels: Some(2),
+                format: None,
+                buffer_size: None,
+            })
+        );
+
+        let command =
+            Command::parse(&["cras_tests", "playback", "-r", "44100", "output", "-c", "2"])
+                .unwrap()
+                .unwrap();
+        assert_eq!(
+            command,
+            Command::Playback(AudioOptions {
+                file_name: PathBuf::from("output"),
+                loopback_type: None,
+                file_type: FileType::Raw,
+                frame_rate: Some(44100),
+                num_channels: Some(2),
+                format: None,
+                buffer_size: None,
+            })
+        );
+
+        assert!(Command::parse(&["cras_tests"]).is_err());
+        assert!(Command::parse(&["cras_tests", "capture"]).is_err());
+        assert!(Command::parse(&["cras_tests", "capture", "input.mp3"]).is_err());
+        assert!(Command::parse(&["cras_tests", "capture", "input.ogg"]).is_err());
+        assert!(Command::parse(&["cras_tests", "capture", "input.flac"]).is_err());
+        assert!(Command::parse(&["cras_tests", "playback"]).is_err());
+        assert!(Command::parse(&["cras_tests", "loopback"]).is_err());
+        assert!(Command::parse(&["cras_tests", "loopback", "file.ogg"]).is_err());
+        assert!(Command::parse(&["cras_tests", "filename.wav"]).is_err());
+        assert!(Command::parse(&["cras_tests", "filename.wav", "capture"]).is_err());
+        assert!(Command::parse(&["cras_tests", "help"]).is_ok());
+        assert!(Command::parse(&[
+            "cras_tests",
+            "-c",
+            "2",
+            "playback",
+            "output.wav",
+            "-r",
+            "44100"
+        ])
+        .is_err());
+    }
+}
diff --git a/cras/client/cras_tests/src/audio.rs b/cras/client/cras_tests/src/audio.rs
new file mode 100644
index 0000000..23018fd
--- /dev/null
+++ b/cras/client/cras_tests/src/audio.rs
@@ -0,0 +1,414 @@
+// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::error;
+use std::fmt;
+use std::fs::File;
+use std::io::{self, BufReader, BufWriter, Read, Write};
+use std::os::raw::c_int;
+use std::path::Path;
+use std::sync::atomic::{AtomicBool, Ordering};
+
+use audio_streams::{SampleFormat, StreamSource};
+use hound::{WavReader, WavSpec, WavWriter};
+use libcras::{BoxError, CrasClient, CrasNodeType};
+use sys_util::{register_signal_handler, set_rt_prio_limit, set_rt_round_robin};
+
+use crate::arguments::{AudioOptions, FileType, LoopbackType};
+
+#[derive(Debug)]
+pub enum Error {
+    CreateStream(BoxError),
+    FetchStream(BoxError),
+    FloatingPointSamples,
+    InvalidWavFile(hound::Error),
+    Io(io::Error),
+    Libcras(libcras::Error),
+    NoLoopbackNode(CrasNodeType),
+    OpenFile(hound::Error),
+    SampleBits(u16),
+    SysUtil(sys_util::Error),
+}
+
+impl error::Error for Error {}
+
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        use Error::*;
+        match self {
+            CreateStream(e) => write!(f, "Failed to create stream: {}", e),
+            FetchStream(e) => write!(f, "Failed to fetch buffer from stream: {}", e),
+            FloatingPointSamples => write!(f, "Floating point audio samples are not supported"),
+            InvalidWavFile(e) => write!(f, "Could not open file as WAV file: {}", e),
+            Io(e) => write!(f, "IO Error: {}", e),
+            Libcras(e) => write!(f, "Libcras Error: {}", e),
+            NoLoopbackNode(typ) => write!(f, "No loopback node found with type {:?}", typ),
+            OpenFile(e) => write!(f, "Could not open WAV file for writing: {}", e),
+            SampleBits(bits) => write!(
+                f,
+                "Sample size {} is not supported, only 8, 16, 24, and 32 bit samples are supported",
+                bits
+            ),
+            SysUtil(e) => write!(f, "SysUtil Error: {}", e),
+        }
+    }
+}
+
+type Result<T> = std::result::Result<T, Error>;
+
+static INTERRUPTED: AtomicBool = AtomicBool::new(false);
+
+extern "C" fn sigint_handler(_: c_int) {
+    // Check if we've already received one SIGINT. If we have, the program may
+    // be misbehaving and not terminating, so to be safe we'll forcefully exit.
+    if INTERRUPTED.load(Ordering::Acquire) {
+        std::process::exit(1);
+    }
+    INTERRUPTED.store(true, Ordering::Release);
+}
+
+fn add_sigint_handler() -> Result<()> {
+    const SIGINT: c_int = 2;
+    let result = unsafe { register_signal_handler(SIGINT, sigint_handler) };
+    result.map_err(Error::SysUtil)
+}
+
+fn set_priority_to_realtime() {
+    const AUDIO_THREAD_RTPRIO: u16 = 10;
+    if set_rt_prio_limit(AUDIO_THREAD_RTPRIO as u64).is_err()
+        || set_rt_round_robin(AUDIO_THREAD_RTPRIO as i32).is_err()
+    {
+        println!("Attempt to use real-time priority failed, running with default scheduler.");
+    }
+}
+
+fn channel_string(num_channels: usize) -> String {
+    match num_channels {
+        1 => "Mono".to_string(),
+        2 => "Stereo".to_string(),
+        _ => format!("{} Channels", num_channels),
+    }
+}
+
+struct WavSource {
+    wav_reader: WavReader<BufReader<File>>,
+    format: SampleFormat,
+    num_channels: usize,
+    frame_rate: u32,
+}
+
+impl WavSource {
+    fn try_new(opts: &AudioOptions) -> Result<Self> {
+        let wav_reader = WavReader::open(&opts.file_name).map_err(Error::InvalidWavFile)?;
+        let spec = wav_reader.spec();
+        if spec.sample_format == hound::SampleFormat::Float {
+            return Err(Error::FloatingPointSamples);
+        }
+
+        let format = match spec.bits_per_sample {
+            8 => SampleFormat::U8,
+            16 => SampleFormat::S16LE,
+            24 => SampleFormat::S24LE,
+            32 => SampleFormat::S32LE,
+            s => return Err(Error::SampleBits(s)),
+        };
+        if opts.format.is_some() && Some(format) != opts.format {
+            eprintln!("Warning: format changed to {:?}", format);
+        }
+
+        let num_channels = spec.channels as usize;
+        if opts.num_channels.is_some() && Some(num_channels) != opts.num_channels {
+            eprintln!("Warning: number of channels changed to {}", num_channels);
+        }
+
+        let frame_rate = spec.sample_rate;
+        if opts.frame_rate.is_some() && Some(frame_rate) != opts.frame_rate {
+            eprintln!("Warning: frame rate changed to {}", frame_rate);
+        }
+
+        Ok(Self {
+            wav_reader,
+            format,
+            num_channels,
+            frame_rate,
+        })
+    }
+
+    fn format(&self) -> SampleFormat {
+        self.format
+    }
+
+    fn num_channels(&self) -> usize {
+        self.num_channels
+    }
+
+    fn frame_rate(&self) -> u32 {
+        self.frame_rate
+    }
+}
+
+impl Read for WavSource {
+    fn read(&mut self, mut buf: &mut [u8]) -> io::Result<usize> {
+        let frame_size = self.format.sample_bytes() * self.num_channels;
+        let read_len = buf.len() - buf.len() % frame_size;
+        let num_samples = read_len / self.format.sample_bytes();
+        let samples = self.wav_reader.samples::<i32>();
+        let mut read = 0;
+        for s in samples.take(num_samples) {
+            match s {
+                Ok(sample) => {
+                    let result = match self.format {
+                        SampleFormat::U8 => buf.write_all(&((sample + 128) as u8).to_le_bytes()),
+                        SampleFormat::S16LE => buf.write_all(&(sample as i16).to_le_bytes()),
+                        SampleFormat::S24LE | SampleFormat::S32LE => {
+                            buf.write_all(&sample.to_le_bytes())
+                        }
+                    };
+
+                    match result {
+                        Ok(()) => read += self.format.sample_bytes(),
+                        Err(_) => return Ok(read),
+                    };
+                }
+                Err(_) => return Ok(read),
+            };
+        }
+        Ok(read)
+    }
+}
+
+pub fn playback(opts: AudioOptions) -> Result<()> {
+    let num_channels;
+    let frame_rate;
+    let format;
+    let mut sample_source: Box<dyn Read> = match opts.file_type {
+        FileType::Wav => {
+            let wav_source = WavSource::try_new(&opts)?;
+            num_channels = wav_source.num_channels();
+            frame_rate = wav_source.frame_rate();
+            format = wav_source.format();
+            Box::new(wav_source)
+        }
+        FileType::Raw => {
+            num_channels = opts.num_channels.unwrap_or(2);
+            frame_rate = opts.frame_rate.unwrap_or(48000);
+            format = opts.format.unwrap_or(SampleFormat::S16LE);
+            Box::new(BufReader::new(
+                File::open(&opts.file_name).map_err(Error::Io)?,
+            ))
+        }
+    };
+
+    println!(
+        "Playing {} '{}' : {}, Rate {} Hz, {}",
+        opts.file_type,
+        opts.file_name.display(),
+        format,
+        frame_rate,
+        channel_string(num_channels)
+    );
+
+    let mut cras_client = CrasClient::new().map_err(Error::Libcras)?;
+    let (_control, mut stream) = cras_client
+        .new_playback_stream(
+            num_channels,
+            format,
+            frame_rate,
+            opts.buffer_size.unwrap_or(256),
+        )
+        .map_err(Error::CreateStream)?;
+    set_priority_to_realtime();
+
+    add_sigint_handler()?;
+    while !INTERRUPTED.load(Ordering::Acquire) {
+        let mut buffer = stream.next_playback_buffer().map_err(Error::FetchStream)?;
+
+        let frame_size = num_channels * format.sample_bytes();
+        let frames = buffer.frame_capacity();
+
+        let mut chunk = (&mut sample_source).take((frames * frame_size) as u64);
+        let transferred = io::copy(&mut chunk, &mut buffer).map_err(Error::Io)?;
+        if transferred == 0 {
+            break;
+        }
+    }
+    // Stream and client should gracefully be closed out of this scope
+
+    Ok(())
+}
+
+struct WavSink {
+    wav_writer: WavWriter<BufWriter<File>>,
+    format: SampleFormat,
+}
+
+impl WavSink {
+    fn try_new<P: AsRef<Path>>(
+        path: P,
+        num_channels: usize,
+        format: SampleFormat,
+        frame_rate: u32,
+    ) -> Result<Self> {
+        let spec = WavSpec {
+            channels: num_channels as u16,
+            sample_rate: frame_rate,
+            bits_per_sample: (format.sample_bytes() * 8) as u16,
+            sample_format: hound::SampleFormat::Int,
+        };
+        let wav_writer = WavWriter::create(path, spec).map_err(Error::OpenFile)?;
+        Ok(Self { wav_writer, format })
+    }
+}
+
+impl Write for WavSink {
+    fn write(&mut self, samples: &[u8]) -> io::Result<usize> {
+        let sample_bytes = self.format.sample_bytes();
+        if samples.len() % sample_bytes != 0 {
+            return Err(io::Error::new(
+                io::ErrorKind::InvalidInput,
+                format!(
+                    "u8 samples vector of length {} cannot be interpreted as {:?} samples",
+                    samples.len(),
+                    self.format
+                ),
+            ));
+        }
+        let num_samples = samples.len() / sample_bytes;
+        match self.format {
+            SampleFormat::U8 => {
+                for sample in samples {
+                    self.wav_writer.write_sample(*sample as i8).map_err(|e| {
+                        io::Error::new(
+                            io::ErrorKind::Other,
+                            format!("Failed to write sample: {}", e),
+                        )
+                    })?;
+                }
+            }
+            SampleFormat::S16LE => {
+                // hound offers an optimized i16 writer, so special case here.
+                let mut writer = self.wav_writer.get_i16_writer(num_samples as u32);
+                for i in 0..num_samples {
+                    let sample = i16::from_le_bytes([
+                        samples[sample_bytes * i],
+                        samples[sample_bytes * i + 1],
+                    ]);
+                    writer.write_sample(sample);
+                }
+                // I16Writer buffers internally and must be explicitly flushed to write
+                // samples to the backing writer. Flush is not called automatically
+                // on drop.
+                // The flush method only writes data from the i16_writer to the underlying
+                // WavWriter, it does not actually guarantee a flush to disk.
+                writer.flush().map_err(|e| {
+                    io::Error::new(
+                        io::ErrorKind::Other,
+                        format!("Failed to flush SampleWriter: {}", e),
+                    )
+                })?;
+            }
+            SampleFormat::S24LE | SampleFormat::S32LE => {
+                for i in 0..num_samples {
+                    let mut sample = i32::from_le_bytes([
+                        samples[sample_bytes * i],
+                        samples[sample_bytes * i + 1],
+                        samples[sample_bytes * i + 2],
+                        samples[sample_bytes * i + 3],
+                    ]);
+
+                    // Upsample to 32 bit since CRAS doesn't support S24_3LE.
+                    // Our wav encoder/decoder, hound, does have support for
+                    // S24_LE, but it hasn't released a new version since the
+                    // support was added. If getting that support is an issue,
+                    // push upstream to cut a new a release.
+                    if self.format == SampleFormat::S24LE {
+                        sample <<= 8;
+                    }
+
+                    self.wav_writer.write_sample(sample).map_err(|e| {
+                        io::Error::new(
+                            io::ErrorKind::Other,
+                            format!("Failed to write sample: {}", e),
+                        )
+                    })?;
+                }
+            }
+        }
+
+        Ok(samples.len())
+    }
+
+    fn flush(&mut self) -> io::Result<()> {
+        self.wav_writer.flush().map_err(|e| {
+            io::Error::new(
+                io::ErrorKind::Other,
+                format!("Failed to flush WavWriter: {}", e),
+            )
+        })
+    }
+}
+
+pub fn capture(opts: AudioOptions) -> Result<()> {
+    let num_channels = opts.num_channels.unwrap_or(2);
+    let format = opts.format.unwrap_or(SampleFormat::S16LE);
+    let frame_rate = opts.frame_rate.unwrap_or(48000);
+    let buffer_size = opts.buffer_size.unwrap_or(256);
+
+    let mut sample_sink: Box<dyn Write> = match opts.file_type {
+        FileType::Raw => Box::new(BufWriter::new(
+            File::create(&opts.file_name).map_err(Error::Io)?,
+        )),
+        FileType::Wav => Box::new(WavSink::try_new(
+            &opts.file_name,
+            num_channels,
+            format,
+            frame_rate,
+        )?),
+    };
+
+    println!(
+        "Recording {} '{}' : {}, Rate {} Hz, {}",
+        opts.file_type,
+        opts.file_name.display(),
+        format,
+        frame_rate,
+        channel_string(num_channels)
+    );
+
+    let mut cras_client = CrasClient::new().map_err(Error::Libcras)?;
+    cras_client.enable_cras_capture();
+    let (_control, mut stream) = match opts.loopback_type {
+        Some(loopback_type) => {
+            let node_type = match loopback_type {
+                LoopbackType::PreDsp => CrasNodeType::CRAS_NODE_TYPE_POST_MIX_PRE_DSP,
+                LoopbackType::PostDsp => CrasNodeType::CRAS_NODE_TYPE_POST_DSP,
+            };
+
+            let loopback_node = cras_client
+                .input_nodes()
+                .find(|node| node.node_type == node_type)
+                .ok_or(Error::NoLoopbackNode(node_type))?;
+
+            cras_client
+                .new_pinned_capture_stream(
+                    loopback_node.iodev_index,
+                    num_channels,
+                    format,
+                    frame_rate,
+                    buffer_size,
+                )
+                .map_err(Error::CreateStream)?
+        }
+        None => cras_client
+            .new_capture_stream(num_channels, format, frame_rate, buffer_size)
+            .map_err(Error::CreateStream)?,
+    };
+    set_priority_to_realtime();
+    add_sigint_handler()?;
+    while !INTERRUPTED.load(Ordering::Acquire) {
+        let mut buf = stream.next_capture_buffer().map_err(Error::FetchStream)?;
+        io::copy(&mut buf, &mut sample_sink).map_err(Error::Io)?;
+    }
+    Ok(())
+}
diff --git a/cras/client/cras_tests/src/audio_options.rs b/cras/client/cras_tests/src/audio_options.rs
deleted file mode 100644
index d71aac5..0000000
--- a/cras/client/cras_tests/src/audio_options.rs
+++ /dev/null
@@ -1,194 +0,0 @@
-// Copyright 2019 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-use std::fmt;
-use std::io;
-use std::path::PathBuf;
-
-use getopts::Options;
-
-type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
-
-#[derive(Debug, PartialEq)]
-pub enum Subcommand {
-    Capture,
-    Playback,
-}
-
-impl fmt::Display for Subcommand {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            Subcommand::Capture => write!(f, "capture"),
-            Subcommand::Playback => write!(f, "playback"),
-        }
-    }
-}
-
-fn show_usage<T: AsRef<str>>(program_name: T) {
-    println!(
-        "Usage: {} [subcommand] <subcommand args>",
-        program_name.as_ref()
-    );
-    println!("\nSubcommands:\n");
-    println!("capture - Capture to a file from CRAS");
-    println!("playback - Playback to CRAS from a file");
-    println!("\nhelp - Print help message");
-}
-
-fn show_subcommand_usage<T: AsRef<str>>(program_name: T, subcommand: &Subcommand, opts: &Options) {
-    let brief = format!(
-        "Usage: {} {} [options] [filename]",
-        program_name.as_ref(),
-        subcommand
-    );
-    print!("{}", opts.usage(&brief));
-}
-
-pub struct AudioOptions {
-    pub subcommand: Subcommand,
-    pub file_name: PathBuf,
-    pub buffer_size: Option<usize>,
-    pub num_channels: Option<usize>,
-    pub frame_rate: Option<usize>,
-}
-
-impl AudioOptions {
-    pub fn parse_from_args<T: AsRef<str>>(args: &[T]) -> Result<Option<Self>> {
-        let mut opts = Options::new();
-        opts.optopt("b", "buffer_size", "Buffer size in frames", "SIZE")
-            .optopt("c", "", "Number of channels", "NUM")
-            .optopt("r", "rate", "Audio frame rate (Hz)", "RATE")
-            .optflag("h", "help", "Print help message");
-
-        let mut args = args.into_iter().map(|s| s.as_ref());
-
-        let program_name = args.next().ok_or_else(|| {
-            Box::new(io::Error::new(
-                std::io::ErrorKind::InvalidInput,
-                "Program name must be specified",
-            ))
-        })?;
-
-        let subcommand = match args.next() {
-            None => {
-                println!("Must specify a subcommand.");
-                show_usage(program_name);
-                return Err(Box::new(std::io::Error::new(
-                    std::io::ErrorKind::InvalidInput,
-                    "No subcommand",
-                )));
-            }
-            Some("help") => {
-                show_usage(&program_name);
-                return Ok(None);
-            }
-            Some("capture") => Subcommand::Capture,
-            Some("playback") => Subcommand::Playback,
-            Some(s) => {
-                println!("Subcommand \"{}\" does not exist.", s);
-                show_usage(&program_name);
-                return Err(Box::new(std::io::Error::new(
-                    std::io::ErrorKind::InvalidInput,
-                    "Subcommand does not exist",
-                )));
-            }
-        };
-
-        let matches = match opts.parse(args) {
-            Ok(m) => m,
-            Err(e) => {
-                show_subcommand_usage(&program_name, &subcommand, &opts);
-                return Err(Box::new(e));
-            }
-        };
-        if matches.opt_present("h") {
-            show_subcommand_usage(&program_name, &subcommand, &opts);
-            return Ok(None);
-        }
-        let file_name = match matches.free.get(0) {
-            None => {
-                println!("Must provide file name.");
-                show_subcommand_usage(&program_name, &subcommand, &opts);
-                return Err(Box::new(std::io::Error::new(
-                    std::io::ErrorKind::InvalidInput,
-                    "Must provide file name.",
-                )));
-            }
-            Some(file_name) => PathBuf::from(file_name),
-        };
-        let buffer_size = matches.opt_get::<usize>("b")?;
-        let num_channels = matches.opt_get::<usize>("c")?;
-        let frame_rate = matches.opt_get::<usize>("r")?;
-
-        Ok(Some(AudioOptions {
-            subcommand,
-            file_name,
-            buffer_size,
-            num_channels,
-            frame_rate,
-        }))
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use std::ffi::OsString;
-
-    #[test]
-    fn parse_from_args() {
-        let opts = AudioOptions::parse_from_args(&["cras_tests", "playback", "output.wav"])
-            .unwrap()
-            .unwrap();
-        assert_eq!(opts.subcommand, Subcommand::Playback);
-        assert_eq!(opts.file_name, OsString::from("output.wav"));
-        assert_eq!(opts.frame_rate, None);
-        assert_eq!(opts.num_channels, None);
-        assert_eq!(opts.buffer_size, None);
-
-        let opts = AudioOptions::parse_from_args(&["cras_tests", "capture", "input.flac"])
-            .unwrap()
-            .unwrap();
-        assert_eq!(opts.subcommand, Subcommand::Capture);
-        assert_eq!(opts.file_name, OsString::from("input.flac"));
-        assert_eq!(opts.frame_rate, None);
-        assert_eq!(opts.num_channels, None);
-        assert_eq!(opts.buffer_size, None);
-
-        let opts = AudioOptions::parse_from_args(&[
-            "cras_tests",
-            "playback",
-            "-r",
-            "44100",
-            "output.wav",
-            "-c",
-            "2",
-        ])
-        .unwrap()
-        .unwrap();
-        assert_eq!(opts.subcommand, Subcommand::Playback);
-        assert_eq!(opts.file_name, OsString::from("output.wav"));
-        assert_eq!(opts.frame_rate, Some(44100));
-        assert_eq!(opts.num_channels, Some(2));
-        assert_eq!(opts.buffer_size, None);
-
-        assert!(AudioOptions::parse_from_args(&["cras_tests"]).is_err());
-        assert!(AudioOptions::parse_from_args(&["cras_tests", "capture"]).is_err());
-        assert!(AudioOptions::parse_from_args(&["cras_tests", "playback"]).is_err());
-        assert!(AudioOptions::parse_from_args(&["cras_tests", "loopback"]).is_err());
-        assert!(AudioOptions::parse_from_args(&["cras_tests", "loopback", "file.ogg"]).is_err());
-        assert!(AudioOptions::parse_from_args(&["cras_tests", "filename.wav"]).is_err());
-        assert!(AudioOptions::parse_from_args(&["cras_tests", "filename.wav", "capture"]).is_err());
-        assert!(AudioOptions::parse_from_args(&["cras_tests", "help"]).is_ok());
-        assert!(AudioOptions::parse_from_args(&[
-            "cras_tests",
-            "-c",
-            "2",
-            "playback",
-            "output.wav",
-            "-r",
-            "44100"
-        ])
-        .is_err());
-    }
-}
diff --git a/cras/client/cras_tests/src/control.rs b/cras/client/cras_tests/src/control.rs
new file mode 100644
index 0000000..3a98ec9
--- /dev/null
+++ b/cras/client/cras_tests/src/control.rs
@@ -0,0 +1,106 @@
+// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::error;
+use std::fmt;
+
+use libcras::{AudioDebugInfo, CrasClient, CrasIonodeInfo};
+
+use crate::arguments::ControlCommand;
+
+/// An enumeration of errors that can occur when running `ControlCommand` using
+/// the `control()` function.
+#[derive(Debug)]
+pub enum Error {
+    Libcras(libcras::Error),
+}
+
+impl error::Error for Error {}
+
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        use Error::*;
+        match self {
+            Libcras(e) => write!(f, "Libcras Error: {}", e),
+        }
+    }
+}
+
+type Result<T> = std::result::Result<T, Error>;
+
+fn print_nodes(nodes: impl Iterator<Item = CrasIonodeInfo>) {
+    println!(
+        "{: <13}{: <7}{: <6}{: <10}{: <13}{: <20} {: <10}",
+        "Stable ID", "ID", "Vol", "Plugged", "Time", "Type", "Name"
+    );
+    for node in nodes {
+        let id = format!("{}:{}", node.iodev_index, node.ionode_index);
+        let stable_id = format!("({:08x})", node.stable_id);
+        let plugged_time = node.plugged_time.tv_sec;
+        let active = if node.active { "*" } else { " " };
+        println!(
+            "{: <13}{: <7}{: <6}{: <10}{: <13}{: <20}{}{: <10}",
+            stable_id,
+            id,
+            node.volume,
+            node.plugged,
+            plugged_time,
+            node.type_name,
+            active,
+            node.name
+        );
+    }
+}
+
+fn print_audio_debug_info(info: &AudioDebugInfo) {
+    println!("Audio Debug Stats:");
+    println!("-------------devices------------");
+    for device in &info.devices {
+        println!("{}", device);
+        println!();
+    }
+
+    println!("-------------stream_dump------------");
+    for stream in &info.streams {
+        println!("{}", stream);
+        println!();
+    }
+}
+
+/// Connect to CRAS and run the given `ControlCommand`.
+pub fn control(command: ControlCommand) -> Result<()> {
+    use ControlCommand::*;
+    let mut cras_client = CrasClient::new().map_err(Error::Libcras)?;
+    match command {
+        GetSystemVolume => println!("{}", cras_client.get_system_volume()),
+        SetSystemVolume(volume) => {
+            cras_client
+                .set_system_volume(volume)
+                .map_err(Error::Libcras)?;
+        }
+        GetSystemMute => println!("{}", cras_client.get_system_mute()),
+        SetSystemMute(mute) => {
+            cras_client.set_system_mute(mute).map_err(Error::Libcras)?;
+        }
+        ListOutputDevices => {
+            println!("{: <5}{: <10}", "ID", "Name");
+            for dev in cras_client.output_devices() {
+                println!("{: <5}{: <10}", dev.index, dev.name);
+            }
+        }
+        ListInputDevices => {
+            println!("{: <5}{: <10}", "ID", "Name");
+            for dev in cras_client.input_devices() {
+                println!("{: <5}{: <10}", dev.index, dev.name);
+            }
+        }
+        ListOutputNodes => print_nodes(cras_client.output_nodes()),
+        ListInputNodes => print_nodes(cras_client.input_nodes()),
+        DumpAudioDebugInfo => {
+            let debug_info = cras_client.get_audio_debug_info().map_err(Error::Libcras)?;
+            print_audio_debug_info(&debug_info);
+        }
+    };
+    Ok(())
+}
diff --git a/cras/client/cras_tests/src/main.rs b/cras/client/cras_tests/src/main.rs
index 9709404..50ffd09 100644
--- a/cras/client/cras_tests/src/main.rs
+++ b/cras/client/cras_tests/src/main.rs
@@ -2,130 +2,57 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-mod audio_options;
+mod arguments;
+mod audio;
+mod control;
 
-use std::fs::File;
-use std::io::{self, BufRead, BufReader, Write};
-use std::thread::spawn;
-use sys_util::{set_rt_prio_limit, set_rt_round_robin};
-type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
+use std::error;
+use std::fmt;
 
-use audio_streams::StreamSource;
-use libcras::CrasClient;
+use crate::arguments::Command;
+use crate::audio::{capture, playback};
+use crate::control::control;
 
-use crate::audio_options::{AudioOptions, Subcommand};
-
-fn set_priority_to_realtime() {
-    const AUDIO_THREAD_RTPRIO: u16 = 10;
-    if set_rt_prio_limit(AUDIO_THREAD_RTPRIO as u64).is_err()
-        || set_rt_round_robin(AUDIO_THREAD_RTPRIO as i32).is_err()
-    {
-        println!("Attempt to use real-time priority failed, running with default scheduler.");
-    }
+#[derive(Debug)]
+pub enum Error {
+    Audio(audio::Error),
+    ParseArgs(arguments::Error),
+    Control(control::Error),
 }
 
-fn channel_string(num_channels: usize) -> String {
-    match num_channels {
-        1 => "Mono".to_string(),
-        2 => "Stereo".to_string(),
-        _ => format!("{} Channels", num_channels),
-    }
-}
+impl error::Error for Error {}
 
-fn playback(opts: AudioOptions) -> Result<()> {
-    let file = File::open(&opts.file_name).expect("failed to open file");
-    let mut buffered_file = BufReader::new(file);
-
-    let num_channels = opts.num_channels.unwrap_or(2);
-    let frame_rate = opts.frame_rate.unwrap_or(48000);
-
-    println!(
-        "Playing raw data '{}' : Signed 16 bit Little Endian, Rate {} Hz, {}",
-        opts.file_name.display(),
-        frame_rate,
-        channel_string(num_channels)
-    );
-
-    let mut cras_client = CrasClient::new()?;
-    let (_control, mut stream) = cras_client.new_playback_stream(
-        num_channels,
-        frame_rate,
-        opts.buffer_size.unwrap_or(256),
-    )?;
-    let thread = spawn(move || {
-        set_priority_to_realtime();
-        loop {
-            let local_buffer = buffered_file
-                .fill_buf()
-                .expect("failed to read from input file");
-
-            // Reached EOF
-            if local_buffer.len() == 0 {
-                break;
-            }
-
-            // Gets writable buffer from stream
-            let mut buffer = stream
-                .next_playback_buffer()
-                .expect("failed to get next playback buffer");
-
-            // Writes data to stream buffer
-            let write_frames = buffer
-                .write(&local_buffer)
-                .expect("failed to write output data to buffer");
-
-            // Mark the file data as written
-            buffered_file.consume(write_frames);
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        use Error::*;
+        match self {
+            Audio(e) => e.fmt(f),
+            ParseArgs(e) => write!(f, "Failed to parse arguments: {}", e),
+            Control(e) => e.fmt(f),
         }
-    });
-    thread.join().expect("Failed to join playback thread");
-    // Stream and client should gracefully be closed out of this scope
-
-    Ok(())
-}
-
-fn capture(opts: AudioOptions) -> Result<()> {
-    let num_channels = opts.num_channels.unwrap_or(2);
-    let frame_rate = opts.frame_rate.unwrap_or(48000);
-
-    println!(
-        "Recording raw data '{}' : Signed 16 bit Little Endian, Rate {} Hz, {}",
-        opts.file_name.display(),
-        frame_rate,
-        channel_string(num_channels)
-    );
-
-    let mut cras_client = CrasClient::new()?;
-    cras_client.enable_cras_capture();
-    let (_control, mut stream) = cras_client.new_capture_stream(
-        num_channels,
-        frame_rate,
-        opts.buffer_size.unwrap_or(256),
-    )?;
-    let mut file = File::create(&opts.file_name).unwrap();
-    loop {
-        let _frames = match stream.next_capture_buffer() {
-            Err(e) => {
-                return Err(e.into());
-            }
-            Ok(mut buf) => {
-                let written = io::copy(&mut buf, &mut file)?;
-                written
-            }
-        };
     }
 }
 
-fn main() -> Result<()> {
+type Result<T> = std::result::Result<T, Error>;
+
+fn run() -> Result<()> {
     let args: Vec<String> = std::env::args().collect();
-    let opts = match AudioOptions::parse_from_args(&args)? {
+    let command = match Command::parse(&args).map_err(Error::ParseArgs)? {
         None => return Ok(()),
         Some(v) => v,
     };
 
-    match opts.subcommand {
-        Subcommand::Capture => capture(opts)?,
-        Subcommand::Playback => playback(opts)?,
-    };
-    Ok(())
+    match command {
+        Command::Capture(audio_opts) => capture(audio_opts).map_err(Error::Audio),
+        Command::Control(command) => control(command).map_err(Error::Control),
+        Command::Playback(audio_opts) => playback(audio_opts).map_err(Error::Audio),
+    }
+}
+
+fn main() {
+    // Use run() instead of returning a Result from main() so that we can print
+    // errors using Display instead of Debug.
+    if let Err(e) = run() {
+        eprintln!("{}", e);
+    }
 }
diff --git a/cras/client/libcras/.gitignore b/cras/client/libcras/.gitignore
index 41cebfd..fa8d85a 100644
--- a/cras/client/libcras/.gitignore
+++ b/cras/client/libcras/.gitignore
@@ -1,3 +1,2 @@
-target/
-.rustfmt.toml
-.*.rustfmt.toml
+Cargo.lock
+target
diff --git a/cras/client/libcras/Android.bp b/cras/client/libcras/Android.bp
index 33c4ddb..aecb27a 100644
--- a/cras/client/libcras/Android.bp
+++ b/cras/client/libcras/Android.bp
@@ -1,15 +1,23 @@
-// This file is generated by cargo2android.py, added defaults.
+// This file is generated by cargo2android.py --run --device --test --global_defaults=crosvm_defaults --dependencies.
 
-rust_test_host {
-    name: "libcras_tests_libcras",
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "external_adhd_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-BSD
+    default_applicable_licenses: ["external_adhd_license"],
+}
+
+rust_defaults {
+    name: "libcras_defaults",
     defaults: ["crosvm_defaults"],
-    deny_warnings: false,
     crate_name: "libcras",
     srcs: ["src/libcras.rs"],
-    relative_install_path: "libcras_tests",
     test_suites: ["general-tests"],
     auto_gen_config: true,
-    rlibs: [
+    edition: "2018",
+    rustlibs: [
         "libaudio_streams",
         "libcras_sys",
         "libdata_model",
@@ -18,13 +26,24 @@
     ],
 }
 
-rust_library_host_rlib {
+rust_test_host {
+    name: "libcras_host_test_src_libcras",
+    defaults: ["libcras_defaults"],
+}
+
+rust_test {
+    name: "libcras_device_test_src_libcras",
+    defaults: ["libcras_defaults"],
+}
+
+rust_library {
     name: "liblibcras",
     defaults: ["crosvm_defaults"],
-    deny_warnings: false,
+    host_supported: true,
     crate_name: "libcras",
     srcs: ["src/libcras.rs"],
-    rlibs: [
+    edition: "2018",
+    rustlibs: [
         "libaudio_streams",
         "libcras_sys",
         "libdata_model",
@@ -32,3 +51,19 @@
         "libsys_util",
     ],
 }
+
+// dependent_library ["feature_list"]
+//   ../../../../crosvm/assertions/src/lib.rs
+//   ../../../../crosvm/data_model/src/lib.rs
+//   ../../../../crosvm/sync/src/lib.rs
+//   ../../../../crosvm/sys_util/poll_token_derive/poll_token_derive.rs
+//   ../../../../crosvm/sys_util/src/lib.rs
+//   ../../../../crosvm/syscall_defines/src/lib.rs
+//   ../../../../crosvm/tempfile/src/lib.rs
+//   ../../../audio_streams/src/audio_streams.rs
+//   ../cras-sys/src/lib.rs
+//   libc-0.2.76 "default,std"
+//   proc-macro2-1.0.19 "default,proc-macro"
+//   quote-1.0.7 "default,proc-macro"
+//   syn-1.0.39 "clone-impls,default,derive,parsing,printing,proc-macro,quote"
+//   unicode-xid-0.2.1 "default"
diff --git a/cras/client/libcras/Cargo.toml b/cras/client/libcras/Cargo.toml
index cc2a3e1..b52a261 100644
--- a/cras/client/libcras/Cargo.toml
+++ b/cras/client/libcras/Cargo.toml
@@ -11,5 +11,5 @@
 audio_streams = { path = "../../../audio_streams" } # provided by ebuild
 libc = "*"
 cras-sys = { path = "../cras-sys" } # provided by ebuild
-data_model = { path = "../../../../../platform/crosvm/data_model" } # provided by ebuild
-sys_util = { path = "../../../../../platform/crosvm/sys_util" } # provided by ebuild
+data_model = { path = "../../../../crosvm/data_model" } # provided by ebuild
+sys_util = { path = "../../../../crosvm/sys_util" } # provided by ebuild
diff --git a/cras/client/libcras/src/cras_client_message.rs b/cras/client/libcras/src/cras_client_message.rs
index 0921c5f..c1c5ec5 100644
--- a/cras/client/libcras/src/cras_client_message.rs
+++ b/cras/client/libcras/src/cras_client_message.rs
@@ -16,7 +16,7 @@
 use crate::cras_stream;
 
 #[derive(Debug)]
-enum ErrorType {
+pub enum Error {
     IoError(io::Error),
     SysUtilError(sys_util::Error),
     CrasStreamError(cras_stream::Error),
@@ -29,32 +29,21 @@
     MessageFromSliceError,
 }
 
-#[derive(Debug)]
-pub struct Error {
-    error_type: ErrorType,
-}
-
-impl Error {
-    fn new(error_type: ErrorType) -> Error {
-        Error { error_type }
-    }
-}
-
 impl error::Error for Error {}
 
 impl fmt::Display for Error {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self.error_type {
-            ErrorType::IoError(ref err) => err.fmt(f),
-            ErrorType::SysUtilError(ref err) => err.fmt(f),
-            ErrorType::MessageTypeError => write!(f, "Message type error"),
-            ErrorType::CrasStreamError(ref err) => err.fmt(f),
-            ErrorType::ArrayTryFromSliceError(ref err) => err.fmt(f),
-            ErrorType::MessageNumFdError => write!(f, "Message the number of fds is not matched"),
-            ErrorType::MessageTruncated => write!(f, "Read truncated message"),
-            ErrorType::MessageIdError => write!(f, "No such id"),
-            ErrorType::MessageFromSliceError => write!(f, "Message from slice error"),
-            ErrorType::InvalidSize => write!(f, "Invalid data size"),
+        match self {
+            Error::IoError(ref err) => err.fmt(f),
+            Error::SysUtilError(ref err) => err.fmt(f),
+            Error::MessageTypeError => write!(f, "Message type error"),
+            Error::CrasStreamError(ref err) => err.fmt(f),
+            Error::ArrayTryFromSliceError(ref err) => err.fmt(f),
+            Error::MessageNumFdError => write!(f, "Message the number of fds is not matched"),
+            Error::MessageTruncated => write!(f, "Read truncated message"),
+            Error::MessageIdError => write!(f, "No such id"),
+            Error::MessageFromSliceError => write!(f, "Message from slice error"),
+            Error::InvalidSize => write!(f, "Invalid data size"),
         }
     }
 }
@@ -63,25 +52,25 @@
 
 impl From<io::Error> for Error {
     fn from(io_err: io::Error) -> Self {
-        Self::new(ErrorType::IoError(io_err))
+        Error::IoError(io_err)
     }
 }
 
 impl From<sys_util::Error> for Error {
     fn from(sys_util_err: sys_util::Error) -> Self {
-        Self::new(ErrorType::SysUtilError(sys_util_err))
+        Error::SysUtilError(sys_util_err)
     }
 }
 
 impl From<cras_stream::Error> for Error {
     fn from(err: cras_stream::Error) -> Self {
-        Self::new(ErrorType::CrasStreamError(err))
+        Error::CrasStreamError(err)
     }
 }
 
 impl From<TryFromSliceError> for Error {
     fn from(err: TryFromSliceError) -> Self {
-        Self::new(ErrorType::ArrayTryFromSliceError(err))
+        Error::ArrayTryFromSliceError(err)
     }
 }
 
@@ -91,6 +80,7 @@
     Connected(u32, CrasServerStateShmFd),
     /// stream_id, header_fd, samples_fd
     StreamConnected(u32, CrasAudioShmHeaderFd, CrasShmFd),
+    DebugInfoReady,
 }
 
 impl ServerResult {
@@ -122,7 +112,10 @@
                     unsafe { CrasShmFd::new(message.fds[1], cmsg.samples_shm_size as usize) },
                 ))
             }
-            _ => Err(Error::new(ErrorType::MessageTypeError)),
+            CRAS_CLIENT_MESSAGE_ID::CRAS_CLIENT_AUDIO_DEBUG_INFO_READY => {
+                Ok(ServerResult::DebugInfoReady)
+            }
+            _ => Err(Error::MessageTypeError),
         }
     }
 }
@@ -154,7 +147,7 @@
         let (len, fd_nums) = server_socket.recv_with_fds(&mut message.data, &mut message.fds)?;
 
         if len < mem::size_of::<cras_client_message>() {
-            Err(Error::new(ErrorType::MessageTruncated))
+            Err(Error::MessageTruncated)
         } else {
             message.len = len;
             message.check_fd_nums(fd_nums)?;
@@ -167,16 +160,20 @@
         match self.get_id()? {
             CRAS_CLIENT_CONNECTED => match fd_nums {
                 1 => Ok(()),
-                _ => Err(Error::new(ErrorType::MessageNumFdError)),
+                _ => Err(Error::MessageNumFdError),
             },
             CRAS_CLIENT_STREAM_CONNECTED => match fd_nums {
                 // CRAS should return two shared memory areas the first which has
                 // mem::size_of::<cras_audio_shm_header>() bytes, and the second which has
                 // `samples_shm_size` bytes.
                 2 => Ok(()),
-                _ => Err(Error::new(ErrorType::MessageNumFdError)),
+                _ => Err(Error::MessageNumFdError),
             },
-            _ => Err(Error::new(ErrorType::MessageTypeError)),
+            CRAS_CLIENT_AUDIO_DEBUG_INFO_READY => match fd_nums {
+                0 => Ok(()),
+                _ => Err(Error::MessageNumFdError),
+            },
+            _ => Err(Error::MessageTypeError),
         }
     }
 
@@ -186,16 +183,18 @@
         match u32::from_le_bytes(self.data[offset..offset + 4].try_into()?) {
             id if id == (CRAS_CLIENT_CONNECTED as u32) => Ok(CRAS_CLIENT_CONNECTED),
             id if id == (CRAS_CLIENT_STREAM_CONNECTED as u32) => Ok(CRAS_CLIENT_STREAM_CONNECTED),
-            _ => Err(Error::new(ErrorType::MessageIdError)),
+            id if id == (CRAS_CLIENT_AUDIO_DEBUG_INFO_READY as u32) => {
+                Ok(CRAS_CLIENT_AUDIO_DEBUG_INFO_READY)
+            }
+            _ => Err(Error::MessageIdError),
         }
     }
 
     // Gets a reference to the message content
     fn get_message<T: DataInit>(&self) -> Result<&T> {
         if self.len != mem::size_of::<T>() {
-            return Err(Error::new(ErrorType::InvalidSize));
+            return Err(Error::InvalidSize);
         }
-        T::from_slice(&self.data[..mem::size_of::<T>()])
-            .ok_or_else(|| Error::new(ErrorType::MessageFromSliceError))
+        T::from_slice(&self.data[..mem::size_of::<T>()]).ok_or(Error::MessageFromSliceError)
     }
 }
diff --git a/cras/client/libcras/src/cras_server_socket.rs b/cras/client/libcras/src/cras_server_socket.rs
index 139382a..4a7d915 100644
--- a/cras/client/libcras/src/cras_server_socket.rs
+++ b/cras/client/libcras/src/cras_server_socket.rs
@@ -1,14 +1,31 @@
 // Copyright 2019 The Chromium OS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
-use std::io;
 use std::os::unix::io::{AsRawFd, RawFd};
+use std::{io, mem};
 
+use cras_sys::gen::{cras_disconnect_stream_message, cras_server_message, CRAS_SERVER_MESSAGE_ID};
 use sys_util::{net::UnixSeqpacket, ScmSocket};
 
 use data_model::DataInit;
 
-const CRAS_SERVER_SOCKET_PATH: &str = "/run/cras/.cras_socket";
+/// Server socket type to connect.
+pub enum CrasSocketType {
+    /// A server socket type supports only playback function.
+    Legacy,
+    /// A server socket type supports both playback and capture functions.
+    Unified,
+}
+
+impl CrasSocketType {
+    fn sock_path(&self) -> &str {
+        match self {
+            Self::Legacy => "/run/cras/.cras_socket",
+            Self::Unified => "/run/cras/.cras_unified",
+        }
+    }
+}
+
 /// A socket connecting to the CRAS audio server.
 pub struct CrasServerSocket {
     socket: UnixSeqpacket,
@@ -16,8 +33,18 @@
 
 impl CrasServerSocket {
     pub fn new() -> io::Result<CrasServerSocket> {
-        let socket = UnixSeqpacket::connect(CRAS_SERVER_SOCKET_PATH)?;
-        Ok(CrasServerSocket { socket })
+        Self::with_type(CrasSocketType::Legacy)
+    }
+
+    /// Creates a `CrasServerSocket` with given `CrasSocketType`.
+    ///
+    /// # Errors
+    ///
+    /// Returns the `io::Error` generated when connecting to the socket on failure.
+    pub fn with_type(socket_type: CrasSocketType) -> io::Result<CrasServerSocket> {
+        Ok(CrasServerSocket {
+            socket: UnixSeqpacket::connect(socket_type.sock_path())?,
+        })
     }
 
     /// Sends a sized and packed server messge to the server socket. The message
@@ -38,10 +65,13 @@
     ) -> io::Result<usize> {
         match fds.len() {
             0 => self.socket.send(message.as_slice()),
-            _ => match self.send_with_fds(message.as_slice(), fds) {
-                Ok(len) => Ok(len),
-                Err(err) => Err(io::Error::new(io::ErrorKind::Other, format!("{}", err))),
-            },
+            _ => {
+                let ioslice = io::IoSlice::new(message.as_slice());
+                match self.send_with_fds(&[ioslice], fds) {
+                    Ok(len) => Ok(len),
+                    Err(err) => Err(io::Error::new(io::ErrorKind::Other, format!("{}", err))),
+                }
+            }
         }
     }
 
@@ -51,6 +81,32 @@
         let new_sock = self.socket.try_clone()?;
         Ok(CrasServerSocket { socket: new_sock })
     }
+
+    /// Send a message to request disconnection of the given stream.
+    ///
+    /// Builds a `cras_disconnect_stream_message` containing `stream_id` and
+    /// sends it to the server.
+    /// No response is expected.
+    ///
+    /// # Arguments
+    ///
+    /// * `stream_id` - The id of the stream that should be disconnected.
+    ///
+    /// # Errors
+    ///
+    /// * If the message was not written to the server socket successfully.
+    pub fn disconnect_stream(&self, stream_id: u32) -> io::Result<()> {
+        let msg_header = cras_server_message {
+            length: mem::size_of::<cras_disconnect_stream_message>() as u32,
+            id: CRAS_SERVER_MESSAGE_ID::CRAS_SERVER_DISCONNECT_STREAM,
+        };
+        let server_cmsg = cras_disconnect_stream_message {
+            header: msg_header,
+            stream_id,
+        };
+        self.send_server_message_with_fds(&server_cmsg, &[])
+            .map(|_| ())
+    }
 }
 
 // For using `recv_with_fds` and `send_with_fds`.
diff --git a/cras/client/libcras/src/cras_shm.rs b/cras/client/libcras/src/cras_shm.rs
index 5547497..0553375 100644
--- a/cras/client/libcras/src/cras_shm.rs
+++ b/cras/client/libcras/src/cras_shm.rs
@@ -1,19 +1,26 @@
 // Copyright 2019 The Chromium OS Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
+use std::convert::TryFrom;
 use std::io;
 use std::mem;
 use std::os::unix::io::{AsRawFd, RawFd};
 use std::ptr;
 use std::ptr::NonNull;
 use std::slice;
-
-use libc;
+use std::sync::atomic::{self, Ordering};
+use std::thread;
 
 use cras_sys::gen::{
-    cras_audio_shm_header, cras_server_state, CRAS_NUM_SHM_BUFFERS, CRAS_SHM_BUFFERS_MASK,
+    audio_dev_debug_info, audio_stream_debug_info, cras_audio_shm_header, cras_iodev_info,
+    cras_ionode_info, cras_server_state, CRAS_MAX_IODEVS, CRAS_MAX_IONODES, CRAS_NUM_SHM_BUFFERS,
+    CRAS_SERVER_STATE_VERSION, CRAS_SHM_BUFFERS_MASK, MAX_DEBUG_DEVS, MAX_DEBUG_STREAMS,
 };
-use data_model::VolatileRef;
+use cras_sys::{
+    AudioDebugInfo, AudioDevDebugInfo, AudioStreamDebugInfo, CrasIodevInfo, CrasIonodeInfo,
+};
+use data_model::{VolatileRef, VolatileSlice};
+use sys_util::warn;
 
 /// A structure wrapping a fd which contains a shared `cras_audio_shm_header`.
 /// * `shm_fd` - A shared memory fd contains a `cras_audio_shm_header`
@@ -45,7 +52,6 @@
 /// A wrapper for the raw structure `cras_audio_shm_header` with
 /// size information for the separate audio samples shm area and several
 /// `VolatileRef` to sub fields for safe access to the header.
-#[allow(dead_code)]
 pub struct CrasAudioHeader<'a> {
     addr: *mut libc::c_void,
     /// Size of the buffer for samples in CrasAudioBuffer
@@ -56,7 +62,7 @@
     write_buf_idx: VolatileRef<'a, u32>,
     read_offset: [VolatileRef<'a, u32>; CRAS_NUM_SHM_BUFFERS as usize],
     write_offset: [VolatileRef<'a, u32>; CRAS_NUM_SHM_BUFFERS as usize],
-    buffer_offset: [VolatileRef<'a, u32>; CRAS_NUM_SHM_BUFFERS as usize],
+    buffer_offset: [VolatileRef<'a, u64>; CRAS_NUM_SHM_BUFFERS as usize],
 }
 
 // It is safe to send audio buffers between threads as this struct has exclusive ownership of the
@@ -226,8 +232,13 @@
         self.frame_size.load() as usize
     }
 
-    /// Gets the size in bytes of the shared memory buffer.
-    fn get_used_size(&self) -> usize {
+    /// Gets the max size in bytes of each shared memory buffer within
+    /// the samples area.
+    ///
+    /// # Returns
+    ///
+    /// * `usize` - Value of `used_size` fetched from the shared memory header.
+    pub fn get_used_size(&self) -> usize {
         self.used_size.load() as usize
     }
 
@@ -299,7 +310,7 @@
         Ok(())
     }
 
-    /// Sets `read_offset[idx]` of to count of written bytes.
+    /// Sets `read_offset[idx]` to count of written bytes.
     ///
     /// # Arguments
     /// `idx` - 0 <= `idx` < `CRAS_NUM_SHM_BUFFERS`
@@ -334,13 +345,23 @@
             .load() as usize;
         let other_end = other_start + self.buffer_len_from_offset(other_start)?;
         if start < other_end && other_start < end {
-            return Err(io::Error::new(
-                io::ErrorKind::InvalidInput,
-                format!(
-                    "Setting buffer {} to [{}, {}) overlaps buffer {} at [{}, {})",
-                    idx, start, end, other_idx, other_start, other_end,
-                ),
-            ));
+            // Special case: occasionally we get the same buffer offset twice
+            // from the intel8x0 kernel driver in crosvm's AC97 device, and we
+            // don't want to crash in that case.
+            if start == other_start && end == other_end {
+                warn!(
+                    "Setting buffer {} to same index/offset as buffer {}, [{}, {})",
+                    idx, other_idx, other_start, other_end
+                );
+            } else {
+                return Err(io::Error::new(
+                    io::ErrorKind::InvalidInput,
+                    format!(
+                        "Setting buffer {} to [{}, {}) overlaps buffer {} at [{}, {})",
+                        idx, start, end, other_idx, other_start, other_end,
+                    ),
+                ));
+            }
         }
         Ok(())
     }
@@ -360,11 +381,11 @@
     ///  * overlaps some other buffer `[other_offset, other_offset + used_size)`
     ///  * is close enough to the end of the samples area that the buffer would
     ///    be shorter than `frame_size`.
-    fn set_buffer_offset(&mut self, idx: usize, offset: usize) -> io::Result<()> {
+    pub fn set_buffer_offset(&mut self, idx: usize, offset: usize) -> io::Result<()> {
         self.check_buffer_offset(idx, offset)?;
 
         let buffer_offset = self.buffer_offset.get(idx).ok_or_else(index_out_of_range)?;
-        buffer_offset.store(offset as u32);
+        buffer_offset.store(offset as u64);
         Ok(())
     }
 
@@ -499,45 +520,252 @@
     cras_mmap_offset(len, prot, fd, 0)
 }
 
-/// A structure that points to RO shared memory area - `cras_server_state`
-/// The structure is created from a shared memory fd which contains the structure.
-#[allow(dead_code)]
-pub struct CrasServerState {
-    addr: *mut libc::c_void,
-    size: usize,
+/// An unsafe macro for getting a `VolatileSlice` representing an entire array
+/// field from a given NonNull pointer.
+///
+/// To use this macro safely, we need to
+/// - Make sure the pointer address is readable and writeable for its struct.
+/// - Make sure all `VolatileSlice`s generated from this macro have exclusive ownership for the same
+/// pointer.
+/// - Make sure the length of the array field is non-zero.
+#[macro_export]
+macro_rules! vslice_from_addr {
+    ($addr:ident, $($field:ident).*) => {{
+        let ptr = &mut $addr.as_mut().$($field).* as *mut _ as *mut u8;
+        let size = std::mem::size_of_val(&$addr.as_mut().$($field).*);
+        VolatileSlice::from_raw_parts(ptr, size)
+    }};
 }
 
-impl CrasServerState {
-    /// An unsafe function for creating `CrasServerState`. To use this function safely, we need to
-    /// - Make sure that the `shm_fd` must come from the server's message that provides the shared
-    /// memory region. The Id for the message is `CRAS_CLIENT_MESSAGE_ID::CRAS_CLIENT_CONNECTED`.
-    #[allow(dead_code)]
-    pub unsafe fn new(shm_fd: CrasShmFd) -> io::Result<Self> {
-        let size = mem::size_of::<cras_server_state>();
-        if size > shm_fd.size {
-            Err(io::Error::new(
-                io::ErrorKind::InvalidInput,
-                "Invalid shared memory size.",
-            ))
-        } else {
-            let addr = cras_mmap(size, libc::PROT_READ, shm_fd.as_raw_fd())?;
-            Ok(CrasServerState { addr, size })
+/// A structure that points to RO shared memory area - `cras_server_state`
+/// The structure is created from a shared memory fd which contains the structure.
+#[derive(Debug)]
+pub struct CrasServerState<'a> {
+    addr: *mut libc::c_void,
+    volume: VolatileRef<'a, u32>,
+    mute: VolatileRef<'a, i32>,
+    num_output_devs: VolatileRef<'a, u32>,
+    output_devs: VolatileSlice<'a>,
+    num_input_devs: VolatileRef<'a, u32>,
+    input_devs: VolatileSlice<'a>,
+    num_output_nodes: VolatileRef<'a, u32>,
+    num_input_nodes: VolatileRef<'a, u32>,
+    output_nodes: VolatileSlice<'a>,
+    input_nodes: VolatileSlice<'a>,
+    update_count: VolatileRef<'a, u32>,
+    debug_info_num_devs: VolatileRef<'a, u32>,
+    debug_info_devs: VolatileSlice<'a>,
+    debug_info_num_streams: VolatileRef<'a, u32>,
+    debug_info_streams: VolatileSlice<'a>,
+}
+
+// It is safe to send server_state between threads as this struct has exclusive
+// ownership of the shared memory area contained in it.
+unsafe impl<'a> Send for CrasServerState<'a> {}
+
+impl<'a> CrasServerState<'a> {
+    /// Create a CrasServerState
+    pub fn try_new(state_fd: CrasServerStateShmFd) -> io::Result<Self> {
+        // Safe because the creator of CrasServerStateShmFd already
+        // ensured that state_fd contains a cras_server_state.
+        let mmap_addr =
+            unsafe { cras_mmap(state_fd.fd.size, libc::PROT_READ, state_fd.fd.as_raw_fd())? };
+
+        let mut addr = NonNull::new(mmap_addr as *mut cras_server_state).ok_or_else(|| {
+            io::Error::new(io::ErrorKind::Other, "Failed to create CrasServerState.")
+        })?;
+
+        // Safe because we know that addr is a non-null pointer to cras_server_state.
+        let state_version = unsafe { vref_from_addr!(addr, state_version) };
+        if state_version.load() != CRAS_SERVER_STATE_VERSION {
+            return Err(io::Error::new(
+                io::ErrorKind::Other,
+                format!(
+                    "CrasServerState version {} does not match expected version {}",
+                    state_version.load(),
+                    CRAS_SERVER_STATE_VERSION
+                ),
+            ));
+        }
+
+        // Safe because we know that mmap_addr (contained in addr) contains a
+        // cras_server_state, and the mapped area will be exclusively
+        // owned by this struct.
+        unsafe {
+            Ok(CrasServerState {
+                addr: addr.as_ptr() as *mut libc::c_void,
+                volume: vref_from_addr!(addr, volume),
+                mute: vref_from_addr!(addr, mute),
+                num_output_devs: vref_from_addr!(addr, num_output_devs),
+                num_input_devs: vref_from_addr!(addr, num_input_devs),
+                output_devs: vslice_from_addr!(addr, output_devs),
+                input_devs: vslice_from_addr!(addr, input_devs),
+                num_output_nodes: vref_from_addr!(addr, num_output_nodes),
+                num_input_nodes: vref_from_addr!(addr, num_input_nodes),
+                output_nodes: vslice_from_addr!(addr, output_nodes),
+                input_nodes: vslice_from_addr!(addr, input_nodes),
+                update_count: vref_from_addr!(addr, update_count),
+                debug_info_num_devs: vref_from_addr!(addr, audio_debug_info.num_devs),
+                debug_info_devs: vslice_from_addr!(addr, audio_debug_info.devs),
+                debug_info_num_streams: vref_from_addr!(addr, audio_debug_info.num_streams),
+                debug_info_streams: vslice_from_addr!(addr, audio_debug_info.streams),
+            })
         }
     }
 
-    // Gets `cras_server_state` reference from the structure.
-    #[allow(dead_code)]
-    fn get_ref(&self) -> VolatileRef<cras_server_state> {
-        unsafe { VolatileRef::new(self.addr as *mut _) }
+    /// Gets the system volume.
+    ///
+    /// Read the current value for system volume from shared memory.
+    pub fn get_system_volume(&self) -> u32 {
+        self.volume.load()
+    }
+
+    /// Gets the system mute.
+    ///
+    /// Read the current value for system mute from shared memory.
+    pub fn get_system_mute(&self) -> bool {
+        self.mute.load() != 0
+    }
+
+    /// Runs a closure safely such that it can be sure that the server state
+    /// was not updated during the read.
+    /// This can be used for an "atomic" read of non-atomic data from the
+    /// state shared memory.
+    fn synchronized_state_read<F, T>(&self, mut func: F) -> T
+    where
+        F: FnMut() -> T,
+    {
+        // Waits until the server has completed a state update before returning
+        // the current update count.
+        let begin_server_state_read = || -> u32 {
+            loop {
+                let update_count = self.update_count.load();
+                if update_count % 2 == 0 {
+                    atomic::fence(Ordering::Acquire);
+                    return update_count;
+                } else {
+                    thread::yield_now();
+                }
+            }
+        };
+
+        // Checks that the update count has not changed since the start
+        // of the server state read.
+        let end_server_state_read = |count: u32| -> bool {
+            let result = count == self.update_count.load();
+            atomic::fence(Ordering::Release);
+            result
+        };
+
+        // Get the state's update count and run the provided closure.
+        // If the update count has not changed once the closure is finished,
+        // return the result, otherwise repeat the process.
+        loop {
+            let update_count = begin_server_state_read();
+            let result = func();
+            if end_server_state_read(update_count) {
+                return result;
+            }
+        }
+    }
+
+    /// Gets a list of output devices
+    ///
+    /// Read a list of the currently attached output devices from shared memory.
+    pub fn output_devices(&self) -> impl Iterator<Item = CrasIodevInfo> {
+        let mut devs: Vec<cras_iodev_info> = vec![Default::default(); CRAS_MAX_IODEVS as usize];
+        let num_devs = self.synchronized_state_read(|| {
+            self.output_devs.copy_to(&mut devs);
+            self.num_output_devs.load()
+        });
+        devs.into_iter()
+            .take(num_devs as usize)
+            .map(CrasIodevInfo::from)
+    }
+
+    /// Gets a list of input devices
+    ///
+    /// Read a list of the currently attached input devices from shared memory.
+    pub fn input_devices(&self) -> impl Iterator<Item = CrasIodevInfo> {
+        let mut devs: Vec<cras_iodev_info> = vec![Default::default(); CRAS_MAX_IODEVS as usize];
+        let num_devs = self.synchronized_state_read(|| {
+            self.input_devs.copy_to(&mut devs);
+            self.num_input_devs.load()
+        });
+        devs.into_iter()
+            .take(num_devs as usize)
+            .map(CrasIodevInfo::from)
+    }
+
+    /// Gets a list of output nodes
+    ///
+    /// Read a list of the currently attached output nodes from shared memory.
+    pub fn output_nodes(&self) -> impl Iterator<Item = CrasIonodeInfo> {
+        let mut nodes: Vec<cras_ionode_info> = vec![Default::default(); CRAS_MAX_IONODES as usize];
+        let num_nodes = self.synchronized_state_read(|| {
+            self.output_nodes.copy_to(&mut nodes);
+            self.num_output_nodes.load()
+        });
+        nodes
+            .into_iter()
+            .take(num_nodes as usize)
+            .map(CrasIonodeInfo::from)
+    }
+
+    /// Gets a list of input nodes
+    ///
+    /// Read a list of the currently attached input nodes from shared memory.
+    pub fn input_nodes(&self) -> impl Iterator<Item = CrasIonodeInfo> {
+        let mut nodes: Vec<cras_ionode_info> = vec![Default::default(); CRAS_MAX_IONODES as usize];
+        let num_nodes = self.synchronized_state_read(|| {
+            self.input_nodes.copy_to(&mut nodes);
+            self.num_input_nodes.load()
+        });
+        nodes
+            .into_iter()
+            .take(num_nodes as usize)
+            .map(CrasIonodeInfo::from)
+    }
+
+    /// Get audio debug info
+    ///
+    /// Loads the server's audio_debug_info struct and converts it into an
+    /// idiomatic rust representation.
+    ///
+    /// # Errors
+    /// * If any of the stream debug information structs are invalid.
+    pub fn get_audio_debug_info(&self) -> Result<AudioDebugInfo, cras_sys::Error> {
+        let mut devs: Vec<audio_dev_debug_info> = vec![Default::default(); MAX_DEBUG_DEVS as usize];
+        let mut streams: Vec<audio_stream_debug_info> =
+            vec![Default::default(); MAX_DEBUG_STREAMS as usize];
+        let (num_devs, num_streams) = self.synchronized_state_read(|| {
+            self.debug_info_devs.copy_to(&mut devs);
+            self.debug_info_streams.copy_to(&mut streams);
+            (
+                self.debug_info_num_devs.load(),
+                self.debug_info_num_streams.load(),
+            )
+        });
+        let dev_info = devs
+            .into_iter()
+            .take(num_devs as usize)
+            .map(AudioDevDebugInfo::from)
+            .collect();
+        let stream_info = streams
+            .into_iter()
+            .take(num_streams as usize)
+            .map(AudioStreamDebugInfo::try_from)
+            .collect::<Result<Vec<_>, _>>()?;
+        Ok(AudioDebugInfo::new(dev_info, stream_info))
     }
 }
 
-impl Drop for CrasServerState {
+impl<'a> Drop for CrasServerState<'a> {
     /// Call `munmap` for `addr`.
     fn drop(&mut self) {
         unsafe {
             // Safe because all references must be gone by the time drop is called.
-            libc::munmap(self.addr, self.size);
+            libc::munmap(self.addr, mem::size_of::<cras_server_state>());
         }
     }
 }
@@ -602,6 +830,16 @@
     Ok((header, buffer))
 }
 
+/// Creates header from header shared memory fds. Use this function
+/// when mapping the samples shm is not necessary, for instance with a
+/// client-provided shm stream.
+pub fn create_header<'a>(
+    header_fd: CrasAudioShmHeaderFd,
+    samples_len: usize,
+) -> io::Result<CrasAudioHeader<'a>> {
+    Ok(CrasAudioHeader::new(header_fd, samples_len)?)
+}
+
 /// A structure wrapping a fd which contains a shared memory area and its size.
 /// * `fd` - The shared memory file descriptor, a `libc::c_int`.
 /// * `size` - Size of the shared memory area.
@@ -650,8 +888,7 @@
 /// A structure wrapping a fd which contains a shared `cras_server_state`.
 /// * `shm_fd` - A shared memory fd contains a `cras_server_state`
 pub struct CrasServerStateShmFd {
-    #[allow(dead_code)]
-    shm_fd: CrasShmFd,
+    fd: CrasShmFd,
 }
 
 impl CrasServerStateShmFd {
@@ -670,7 +907,7 @@
     /// - The shared memory area in the input fd contains a `cras_server_state`.
     pub unsafe fn new(fd: libc::c_int) -> Self {
         Self {
-            shm_fd: CrasShmFd::new(fd, mem::size_of::<cras_server_state>()),
+            fd: CrasShmFd::new(fd, mem::size_of::<cras_server_state>()),
         }
     }
 }
@@ -680,6 +917,8 @@
     use super::*;
     use std::fs::File;
     use std::os::unix::io::IntoRawFd;
+    use std::sync::{Arc, Mutex};
+    use std::thread;
     use sys_util::{kernel_has_memfd, SharedMemory};
 
     #[test]
@@ -885,13 +1124,16 @@
         header.write_offset[1].store(0);
         header.buffer_offset[1].store(10);
 
-        // Setting buffer_offset to overlap with other buffer is not okay
-        assert!(header.set_buffer_offset(0, 10).is_err());
+        // Setting buffer_offset to exactly overlap with other buffer is okay
+        assert!(header.set_buffer_offset(0, 10).is_ok());
+
+        // Setting buffer_offset to partially overlap other buffer is not okay
+        assert!(header.set_buffer_offset(0, 9).is_err());
 
         header.buffer_offset[0].store(0);
         header.write_offset[1].store(8);
         // With samples, it's still an error.
-        assert!(header.set_buffer_offset(0, 10).is_err());
+        assert!(header.set_buffer_offset(0, 9).is_err());
 
         // Setting the offset past the end of the other buffer is okay
         assert!(header.set_buffer_offset(0, 20).is_ok());
@@ -906,9 +1148,9 @@
         assert!(header.set_buffer_offset(0, 30).is_err());
 
         // If we try to overlap another buffer with that other buffer at the end,
-        // it's not okay.
+        // it's not okay, unless it's the exact same index.
         assert!(header.set_buffer_offset(1, 25).is_err());
-        assert!(header.set_buffer_offset(1, 27).is_err());
+        assert!(header.set_buffer_offset(1, 27).is_ok());
         assert!(header.set_buffer_offset(1, 28).is_err());
 
         // Setting buffer offset past the end of samples is an error.
@@ -966,4 +1208,101 @@
         let rc = unsafe { cras_mmap(10, libc::PROT_READ, -1) };
         assert!(rc.is_err());
     }
+
+    #[test]
+    fn cras_server_state() {
+        let size = mem::size_of::<cras_server_state>();
+        let shm = create_shm(size);
+        unsafe {
+            let addr = cras_mmap(size, libc::PROT_WRITE, shm.as_raw_fd())
+                .expect("failed to mmap state shm");
+            {
+                let state: &mut cras_server_state = &mut *(addr as *mut cras_server_state);
+                state.state_version = CRAS_SERVER_STATE_VERSION;
+                state.volume = 47;
+                state.mute = 1;
+            }
+            libc::munmap(addr, size);
+        };
+        let state_fd = unsafe { CrasServerStateShmFd::new(shm.into_raw_fd()) };
+        let state =
+            CrasServerState::try_new(state_fd).expect("try_new failed for valid server_state fd");
+        assert_eq!(state.get_system_volume(), 47);
+        assert_eq!(state.get_system_mute(), true);
+    }
+
+    #[test]
+    fn cras_server_state_old_version() {
+        let size = mem::size_of::<cras_server_state>();
+        let shm = create_shm(size);
+        unsafe {
+            let addr = cras_mmap(size, libc::PROT_WRITE, shm.as_raw_fd())
+                .expect("failed to mmap state shm");
+            {
+                let state: &mut cras_server_state = &mut *(addr as *mut cras_server_state);
+                state.state_version = CRAS_SERVER_STATE_VERSION - 1;
+                state.volume = 29;
+                state.mute = 0;
+            }
+            libc::munmap(addr, size);
+        };
+        let state_fd = unsafe { CrasServerStateShmFd::new(shm.into_raw_fd()) };
+        CrasServerState::try_new(state_fd)
+            .expect_err("try_new succeeded for invalid state version");
+    }
+
+    #[test]
+    fn cras_server_sync_state_read() {
+        let size = mem::size_of::<cras_server_state>();
+        let shm = create_shm(size);
+        let addr = unsafe { cras_mmap(size, libc::PROT_WRITE, shm.as_raw_fd()).unwrap() };
+        let state: &mut cras_server_state = unsafe { &mut *(addr as *mut cras_server_state) };
+        state.state_version = CRAS_SERVER_STATE_VERSION;
+        state.update_count = 14;
+        state.volume = 12;
+
+        let state_fd = unsafe { CrasServerStateShmFd::new(shm.into_raw_fd()) };
+        let state_struct = CrasServerState::try_new(state_fd).unwrap();
+
+        // Create a lock so that we can block the reader while we change the
+        // update_count;
+        let lock = Arc::new(Mutex::new(()));
+        let thread_lock = lock.clone();
+        let reader_thread = {
+            let _guard = lock.lock().unwrap();
+
+            // Create reader thread that will get the value of volume. Since we
+            // hold the lock currently, this will block until we release the lock.
+            let reader_thread = thread::spawn(move || {
+                state_struct.synchronized_state_read(|| {
+                    let _guard = thread_lock.lock().unwrap();
+                    state_struct.volume.load()
+                })
+            });
+
+            // Update volume and change update count so that the synchronized read
+            // will not return (odd update count means update in progress).
+            state.volume = 27;
+            state.update_count = 15;
+
+            reader_thread
+        };
+
+        // The lock has been released, but the reader thread should still not
+        // terminate, because of the update in progress.
+
+        // Yield thread to give reader_thread a chance to get scheduled.
+        thread::yield_now();
+        {
+            let _guard = lock.lock().unwrap();
+
+            // Update volume and change update count to indicate the write has
+            // finished.
+            state.volume = 42;
+            state.update_count = 16;
+        }
+
+        let read_value = reader_thread.join().unwrap();
+        assert_eq!(read_value, 42);
+    }
 }
diff --git a/cras/client/libcras/src/cras_shm_stream.rs b/cras/client/libcras/src/cras_shm_stream.rs
new file mode 100644
index 0000000..f72cc07
--- /dev/null
+++ b/cras/client/libcras/src/cras_shm_stream.rs
@@ -0,0 +1,191 @@
+// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+use std::time::Duration;
+use std::{error, fmt};
+
+use audio_streams::{
+    shm_streams::{BufferSet, ServerRequest, ShmStream},
+    BoxError, SampleFormat, StreamDirection,
+};
+use cras_sys::gen::CRAS_AUDIO_MESSAGE_ID;
+use sys_util::error;
+
+use crate::audio_socket::{AudioMessage, AudioSocket};
+use crate::cras_server_socket::CrasServerSocket;
+use crate::cras_shm::{self, CrasAudioHeader, CrasAudioShmHeaderFd};
+
+#[derive(Debug)]
+pub enum Error {
+    MessageTypeError,
+    CaptureBufferTooSmall,
+}
+
+impl error::Error for Error {}
+
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            Error::MessageTypeError => write!(f, "Message type error"),
+            Error::CaptureBufferTooSmall => write!(
+                f,
+                "Capture buffer too small, must have size at least 'used_size'."
+            ),
+        }
+    }
+}
+
+/// An object that handles interactions with CRAS for a shm stream.
+/// The object implements `ShmStream` and so can be used to wait for
+/// `ServerRequest` and `BufferComplete` messages.
+pub struct CrasShmStream<'a> {
+    stream_id: u32,
+    server_socket: CrasServerSocket,
+    audio_socket: AudioSocket,
+    direction: StreamDirection,
+    header: CrasAudioHeader<'a>,
+    frame_size: usize,
+    num_channels: usize,
+    frame_rate: u32,
+    // The index of the next buffer within SHM to set the buffer offset for.
+    next_buffer_idx: usize,
+}
+
+impl<'a> CrasShmStream<'a> {
+    /// Attempt to creates a CrasShmStream with the given arguments.
+    ///
+    /// # Arguments
+    ///
+    /// * `stream_id` - The server's ID for the stream.
+    /// * `server_socket` - The socket that is connected to the server.
+    /// * `audio_socket` - The socket for audio request and audio available messages.
+    /// * `direction` - The direction of the stream, `Playback` or `Capture`.
+    /// * `num_channels` - The number of audio channels for the stream.
+    /// * `format` - The format to use for the stream's samples.
+    /// * `header_fd` - The file descriptor for the audio header shm area.
+    /// * `samples_len` - The size of the audio samples shm area.
+    ///
+    /// # Returns
+    ///
+    /// `CrasShmStream` - CRAS client stream.
+    ///
+    /// # Errors
+    ///
+    /// * If `header_fd` could not be successfully mmapped.
+    #[allow(clippy::too_many_arguments)]
+    pub fn try_new(
+        stream_id: u32,
+        server_socket: CrasServerSocket,
+        audio_socket: AudioSocket,
+        direction: StreamDirection,
+        num_channels: usize,
+        frame_rate: u32,
+        format: SampleFormat,
+        header_fd: CrasAudioShmHeaderFd,
+        samples_len: usize,
+    ) -> Result<Self, BoxError> {
+        let header = cras_shm::create_header(header_fd, samples_len)?;
+        Ok(Self {
+            stream_id,
+            server_socket,
+            audio_socket,
+            direction,
+            header,
+            frame_size: format.sample_bytes() * num_channels,
+            num_channels,
+            frame_rate,
+            // We have either sent zero or two offsets to the server, so we will
+            // need to update index 0 next.
+            next_buffer_idx: 0,
+        })
+    }
+}
+
+impl<'a> Drop for CrasShmStream<'a> {
+    /// Send the disconnect stream message and log an error if sending fails.
+    fn drop(&mut self) {
+        if let Err(e) = self.server_socket.disconnect_stream(self.stream_id) {
+            error!("CrasShmStream::drop error: {}", e);
+        }
+    }
+}
+
+impl<'a> ShmStream for CrasShmStream<'a> {
+    fn frame_size(&self) -> usize {
+        self.frame_size
+    }
+
+    fn num_channels(&self) -> usize {
+        self.num_channels
+    }
+
+    fn frame_rate(&self) -> u32 {
+        self.frame_rate
+    }
+
+    fn wait_for_next_action_with_timeout(
+        &mut self,
+        timeout: Duration,
+    ) -> Result<Option<ServerRequest>, BoxError> {
+        let expected_id = match self.direction {
+            StreamDirection::Playback => CRAS_AUDIO_MESSAGE_ID::AUDIO_MESSAGE_REQUEST_DATA,
+            StreamDirection::Capture => CRAS_AUDIO_MESSAGE_ID::AUDIO_MESSAGE_DATA_READY,
+        };
+
+        match self
+            .audio_socket
+            .read_audio_message_with_timeout(Some(timeout))?
+        {
+            Some(AudioMessage::Success { id, frames }) if id == expected_id => {
+                Ok(Some(ServerRequest::new(frames as usize, self)))
+            }
+            None => Ok(None),
+            _ => Err(Box::new(Error::MessageTypeError)),
+        }
+    }
+}
+
+impl BufferSet for CrasShmStream<'_> {
+    fn callback(&mut self, offset: usize, frames: usize) -> Result<(), BoxError> {
+        self.header
+            .set_buffer_offset(self.next_buffer_idx, offset)?;
+        self.next_buffer_idx ^= 1;
+        let frames = frames as u32;
+
+        match self.direction {
+            StreamDirection::Playback => {
+                self.header.commit_written_frames(frames)?;
+
+                // Notify CRAS that we've made playback data available.
+                self.audio_socket.data_ready(frames)?
+            }
+            StreamDirection::Capture => {
+                let used_size = self.header.get_used_size();
+                // Because CRAS doesn't know how long our buffer in shm is, we
+                // must make sure that there are always at least buffer_size
+                // frames available so that it doesn't write outside the buffer.
+                if frames < (used_size / self.frame_size) as u32 {
+                    return Err(Box::new(Error::CaptureBufferTooSmall));
+                }
+
+                self.header.commit_read_frames(frames)?;
+                self.audio_socket.capture_ready(frames)?;
+            }
+        }
+
+        Ok(())
+    }
+
+    fn ignore(&mut self) -> Result<(), BoxError> {
+        // We send an empty buffer for an ignored playback request since the
+        // server will not read from a 0-length buffer. We don't do anything for
+        // an ignored capture request, since we don't have a way to communicate
+        // buffer length to the server, and we don't want the server writing
+        // data to offsets within the SHM area that aren't audio buffers.
+        if self.direction == StreamDirection::Playback {
+            self.callback(0, 0)?;
+        }
+
+        Ok(())
+    }
+}
diff --git a/cras/client/libcras/src/cras_stream.rs b/cras/client/libcras/src/cras_stream.rs
index bd9520a..f600480 100644
--- a/cras/client/libcras/src/cras_stream.rs
+++ b/cras/client/libcras/src/cras_stream.rs
@@ -4,17 +4,13 @@
 use std::cmp::min;
 use std::io;
 use std::marker::PhantomData;
-use std::mem;
 use std::{error, fmt};
 
 use audio_streams::{
     capture::{CaptureBuffer, CaptureBufferStream},
-    BufferDrop, PlaybackBuffer, PlaybackBufferStream,
+    BoxError, BufferDrop, PlaybackBuffer, PlaybackBufferStream,
 };
-use cras_sys::gen::{
-    cras_disconnect_stream_message, cras_server_message, snd_pcm_format_t, CRAS_AUDIO_MESSAGE_ID,
-    CRAS_SERVER_MESSAGE_ID, CRAS_STREAM_DIRECTION,
-};
+use cras_sys::gen::{snd_pcm_format_t, CRAS_AUDIO_MESSAGE_ID, CRAS_STREAM_DIRECTION};
 use sys_util::error;
 
 use crate::audio_socket::{AudioMessage, AudioSocket};
@@ -22,38 +18,25 @@
 use crate::cras_shm::*;
 
 #[derive(Debug)]
-pub enum ErrorType {
+pub enum Error {
     IoError(io::Error),
     MessageTypeError,
-    NoShmError,
-}
-
-#[derive(Debug)]
-pub struct Error {
-    error_type: ErrorType,
-}
-
-impl Error {
-    fn new(error_type: ErrorType) -> Error {
-        Error { error_type }
-    }
 }
 
 impl error::Error for Error {}
 
 impl fmt::Display for Error {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self.error_type {
-            ErrorType::IoError(ref err) => err.fmt(f),
-            ErrorType::MessageTypeError => write!(f, "Message type error"),
-            ErrorType::NoShmError => write!(f, "Shared memory area is not created"),
+        match self {
+            Error::IoError(ref err) => err.fmt(f),
+            Error::MessageTypeError => write!(f, "Message type error"),
         }
     }
 }
 
 impl From<io::Error> for Error {
     fn from(io_err: io::Error) -> Error {
-        Error::new(ErrorType::IoError(io_err))
+        Error::IoError(io_err)
     }
 }
 
@@ -61,31 +44,23 @@
 /// interacts with server's audio thread through `AudioSocket`.
 pub trait CrasStreamData<'a>: Send {
     // Creates `CrasStreamData` with only `AudioSocket`.
-    fn new(audio_sock: AudioSocket) -> Self;
-    fn set_header(&mut self, header: CrasAudioHeader<'a>);
-    fn header_mut(&mut self) -> &mut Option<CrasAudioHeader<'a>>;
+    fn new(audio_sock: AudioSocket, header: CrasAudioHeader<'a>) -> Self;
+    fn header_mut(&mut self) -> &mut CrasAudioHeader<'a>;
     fn audio_sock_mut(&mut self) -> &mut AudioSocket;
 }
 
 /// `CrasStreamData` implementation for `PlaybackBufferStream`.
 pub struct CrasPlaybackData<'a> {
     audio_sock: AudioSocket,
-    header: Option<CrasAudioHeader<'a>>,
+    header: CrasAudioHeader<'a>,
 }
 
 impl<'a> CrasStreamData<'a> for CrasPlaybackData<'a> {
-    fn new(audio_sock: AudioSocket) -> Self {
-        Self {
-            audio_sock,
-            header: None,
-        }
+    fn new(audio_sock: AudioSocket, header: CrasAudioHeader<'a>) -> Self {
+        Self { audio_sock, header }
     }
 
-    fn set_header(&mut self, header: CrasAudioHeader<'a>) {
-        self.header = Some(header);
-    }
-
-    fn header_mut(&mut self) -> &mut Option<CrasAudioHeader<'a>> {
+    fn header_mut(&mut self) -> &mut CrasAudioHeader<'a> {
         &mut self.header
     }
 
@@ -97,10 +72,8 @@
 impl<'a> BufferDrop for CrasPlaybackData<'a> {
     fn trigger(&mut self, nframes: usize) {
         let log_err = |e| error!("BufferDrop error: {}", e);
-        if let Some(header) = &mut self.header {
-            if let Err(e) = header.commit_written_frames(nframes as u32) {
-                log_err(e);
-            }
+        if let Err(e) = self.header.commit_written_frames(nframes as u32) {
+            log_err(e);
         }
         if let Err(e) = self.audio_sock.data_ready(nframes as u32) {
             log_err(e);
@@ -111,22 +84,15 @@
 /// `CrasStreamData` implementation for `CaptureBufferStream`.
 pub struct CrasCaptureData<'a> {
     audio_sock: AudioSocket,
-    header: Option<CrasAudioHeader<'a>>,
+    header: CrasAudioHeader<'a>,
 }
 
 impl<'a> CrasStreamData<'a> for CrasCaptureData<'a> {
-    fn new(audio_sock: AudioSocket) -> Self {
-        Self {
-            audio_sock,
-            header: None,
-        }
+    fn new(audio_sock: AudioSocket, header: CrasAudioHeader<'a>) -> Self {
+        Self { audio_sock, header }
     }
 
-    fn set_header(&mut self, header: CrasAudioHeader<'a>) {
-        self.header = Some(header);
-    }
-
-    fn header_mut(&mut self) -> &mut Option<CrasAudioHeader<'a>> {
+    fn header_mut(&mut self) -> &mut CrasAudioHeader<'a> {
         &mut self.header
     }
 
@@ -138,10 +104,8 @@
 impl<'a> BufferDrop for CrasCaptureData<'a> {
     fn trigger(&mut self, nframes: usize) {
         let log_err = |e| error!("BufferDrop error: {}", e);
-        if let Some(header) = &mut self.header {
-            if let Err(e) = header.commit_read_frames(nframes as u32) {
-                log_err(e);
-            }
+        if let Err(e) = self.header.commit_read_frames(nframes as u32) {
+            log_err(e);
         }
         if let Err(e) = self.audio_sock.capture_ready(nframes as u32) {
             log_err(e);
@@ -155,14 +119,14 @@
     server_socket: CrasServerSocket,
     block_size: u32,
     direction: CRAS_STREAM_DIRECTION,
-    rate: usize,
+    rate: u32,
     num_channels: usize,
     format: snd_pcm_format_t,
     /// A structure for stream to interact with server audio thread.
     controls: T,
     /// The `PhantomData` is used by `controls: T`
     phantom: PhantomData<CrasAudioHeader<'a>>,
-    audio_buffer: Option<CrasAudioBuffer>,
+    audio_buffer: CrasAudioBuffer,
 }
 
 impl<'a, T: CrasStreamData<'a> + BufferDrop> CrasStream<'a, T> {
@@ -170,17 +134,22 @@
     ///
     /// # Returns
     /// `CrasStream` - CRAS client stream.
-    pub fn new(
+    #[allow(clippy::too_many_arguments)]
+    pub fn try_new(
         stream_id: u32,
         server_socket: CrasServerSocket,
         block_size: u32,
         direction: CRAS_STREAM_DIRECTION,
-        rate: usize,
+        rate: u32,
         num_channels: usize,
         format: snd_pcm_format_t,
         audio_sock: AudioSocket,
-    ) -> Self {
-        Self {
+        header_fd: CrasAudioShmHeaderFd,
+        samples_fd: CrasShmFd,
+    ) -> Result<Self, Error> {
+        let (header, audio_buffer) = create_header_and_buffers(header_fd, samples_fd)?;
+
+        Ok(Self {
             stream_id,
             server_socket,
             block_size,
@@ -188,41 +157,29 @@
             rate,
             num_channels,
             format,
-            controls: T::new(audio_sock),
+            controls: T::new(audio_sock, header),
             phantom: PhantomData,
-            audio_buffer: None,
-        }
-    }
-
-    /// Receives shared memory fd and initialize stream audio shared memory area
-    pub fn init_shm(
-        &mut self,
-        header_fd: CrasAudioShmHeaderFd,
-        samples_fd: CrasShmFd,
-    ) -> Result<(), Error> {
-        let (header, buffer) = create_header_and_buffers(header_fd, samples_fd)?;
-        self.controls.set_header(header);
-        self.audio_buffer = Some(buffer);
-        Ok(())
+            audio_buffer,
+        })
     }
 
     fn wait_request_data(&mut self) -> Result<(), Error> {
         match self.controls.audio_sock_mut().read_audio_message()? {
-            AudioMessage::Success { id, .. } => match id {
-                CRAS_AUDIO_MESSAGE_ID::AUDIO_MESSAGE_REQUEST_DATA => Ok(()),
-                _ => Err(Error::new(ErrorType::MessageTypeError)),
-            },
-            _ => Err(Error::new(ErrorType::MessageTypeError)),
+            AudioMessage::Success {
+                id: CRAS_AUDIO_MESSAGE_ID::AUDIO_MESSAGE_REQUEST_DATA,
+                ..
+            } => Ok(()),
+            _ => Err(Error::MessageTypeError),
         }
     }
 
     fn wait_data_ready(&mut self) -> Result<u32, Error> {
         match self.controls.audio_sock_mut().read_audio_message()? {
-            AudioMessage::Success { id, frames } => match id {
-                CRAS_AUDIO_MESSAGE_ID::AUDIO_MESSAGE_DATA_READY => Ok(frames),
-                _ => Err(Error::new(ErrorType::MessageTypeError)),
-            },
-            _ => Err(Error::new(ErrorType::MessageTypeError)),
+            AudioMessage::Success {
+                id: CRAS_AUDIO_MESSAGE_ID::AUDIO_MESSAGE_DATA_READY,
+                frames,
+            } => Ok(frames),
+            _ => Err(Error::MessageTypeError),
         }
     }
 }
@@ -232,57 +189,36 @@
     /// the return message.
     /// Logs an error message to stderr if the method fails.
     fn drop(&mut self) {
-        // Send stream disconnect message
-        let msg_header = cras_server_message {
-            length: mem::size_of::<cras_disconnect_stream_message>() as u32,
-            id: CRAS_SERVER_MESSAGE_ID::CRAS_SERVER_DISCONNECT_STREAM,
-        };
-        let server_cmsg = cras_disconnect_stream_message {
-            header: msg_header,
-            stream_id: self.stream_id,
-        };
-        if let Err(e) = self
-            .server_socket
-            .send_server_message_with_fds(&server_cmsg, &[])
-        {
+        if let Err(e) = self.server_socket.disconnect_stream(self.stream_id) {
             error!("CrasStream::Drop error: {}", e);
         }
     }
 }
 
 impl<'a, T: CrasStreamData<'a> + BufferDrop> PlaybackBufferStream for CrasStream<'a, T> {
-    fn next_playback_buffer(&mut self) -> Result<PlaybackBuffer, Box<dyn error::Error>> {
+    fn next_playback_buffer(&mut self) -> Result<PlaybackBuffer, BoxError> {
         // Wait for request audio message
         self.wait_request_data()?;
-        let (frame_size, (offset, len)) = match self.controls.header_mut() {
-            None => return Err(Error::new(ErrorType::NoShmError).into()),
-            Some(header) => (header.get_frame_size(), header.get_write_offset_and_len()?),
-        };
-        let buf = match self.audio_buffer.as_mut() {
-            None => return Err(Error::new(ErrorType::NoShmError).into()),
-            Some(audio_buffer) => &mut audio_buffer.get_buffer()[offset..offset + len],
-        };
+        let header = self.controls.header_mut();
+        let frame_size = header.get_frame_size();
+        let (offset, len) = header.get_write_offset_and_len()?;
+        let buf = &mut self.audio_buffer.get_buffer()[offset..offset + len];
+
         PlaybackBuffer::new(frame_size, buf, &mut self.controls).map_err(Box::from)
     }
 }
 
 impl<'a, T: CrasStreamData<'a> + BufferDrop> CaptureBufferStream for CrasStream<'a, T> {
-    fn next_capture_buffer(&mut self) -> Result<CaptureBuffer, Box<dyn error::Error>> {
+    fn next_capture_buffer(&mut self) -> Result<CaptureBuffer, BoxError> {
         // Wait for data ready message
         let frames = self.wait_data_ready()?;
-        let (frame_size, shm_frames, offset) = match self.controls.header_mut() {
-            None => return Err(Error::new(ErrorType::NoShmError).into()),
-            Some(header) => (
-                header.get_frame_size(),
-                header.get_readable_frames()?,
-                header.get_read_buffer_offset()?,
-            ),
-        };
+        let header = self.controls.header_mut();
+        let frame_size = header.get_frame_size();
+        let shm_frames = header.get_readable_frames()?;
         let len = min(shm_frames, frames as usize) * frame_size;
-        let buf = match self.audio_buffer.as_mut() {
-            None => return Err(Error::new(ErrorType::NoShmError).into()),
-            Some(audio_buffer) => &mut audio_buffer.get_buffer()[offset..offset + len],
-        };
+        let offset = header.get_read_buffer_offset()?;
+        let buf = &mut self.audio_buffer.get_buffer()[offset..offset + len];
+
         CaptureBuffer::new(frame_size, buf, &mut self.controls).map_err(Box::from)
     }
 }
diff --git a/cras/client/libcras/src/libcras.rs b/cras/client/libcras/src/libcras.rs
index c81e540..402a4a2 100644
--- a/cras/client/libcras/src/libcras.rs
+++ b/cras/client/libcras/src/libcras.rs
@@ -23,14 +23,15 @@
 //! use std::fs::File;
 //! use std::io::{Read, Write};
 //! use std::thread::{spawn, JoinHandle};
-//! type Result<T> = std::result::Result<T, Box<std::error::Error>>;
+//! type Result<T> = std::result::Result<T, BoxError>;
 //!
-//! use libcras::{CrasClient, CrasClientType};
-//! use audio_streams::StreamSource;
+//! use libcras::{BoxError, CrasClient, CrasClientType};
+//! use audio_streams::{SampleFormat, StreamSource};
 //!
 //! const BUFFER_SIZE: usize = 256;
-//! const FRAME_RATE: usize = 44100;
+//! const FRAME_RATE: u32 = 44100;
 //! const NUM_CHANNELS: usize = 2;
+//! const FORMAT: SampleFormat = SampleFormat::S16LE;
 //!
 //! # fn main() -> Result<()> {
 //! #    let args: Vec<String> = env::args().collect();
@@ -39,7 +40,7 @@
 //!              let mut cras_client = CrasClient::new()?;
 //!              cras_client.set_client_type(CrasClientType::CRAS_CLIENT_TYPE_TEST);
 //!              let (_control, mut stream) = cras_client
-//!                  .new_playback_stream(NUM_CHANNELS, FRAME_RATE, BUFFER_SIZE)?;
+//!                  .new_playback_stream(NUM_CHANNELS, FORMAT, FRAME_RATE, BUFFER_SIZE)?;
 //!
 //!              // Plays 1000 * BUFFER_SIZE samples from the given file
 //!              let mut file = File::open(&args[1])?;
@@ -75,14 +76,15 @@
 //! use std::fs::File;
 //! use std::io::{Read, Write};
 //! use std::thread::{spawn, JoinHandle};
-//! type Result<T> = std::result::Result<T, Box<std::error::Error>>;
+//! type Result<T> = std::result::Result<T, BoxError>;
 //!
-//! use libcras::{CrasClient, CrasClientType};
-//! use audio_streams::StreamSource;
+//! use libcras::{BoxError, CrasClient, CrasClientType};
+//! use audio_streams::{SampleFormat, StreamSource};
 //!
 //! const BUFFER_SIZE: usize = 256;
-//! const FRAME_RATE: usize = 44100;
+//! const FRAME_RATE: u32 = 44100;
 //! const NUM_CHANNELS: usize = 2;
+//! const FORMAT: SampleFormat = SampleFormat::S16LE;
 //!
 //! # fn main() -> Result<()> {
 //! #    let args: Vec<String> = env::args().collect();
@@ -91,7 +93,7 @@
 //!              let mut cras_client = CrasClient::new()?;
 //!              cras_client.set_client_type(CrasClientType::CRAS_CLIENT_TYPE_TEST);
 //!              let (_control, mut stream) = cras_client
-//!                  .new_capture_stream(NUM_CHANNELS, FRAME_RATE, BUFFER_SIZE)?;
+//!                  .new_capture_stream(NUM_CHANNELS, FORMAT, FRAME_RATE, BUFFER_SIZE)?;
 //!
 //!              // Capture 1000 * BUFFER_SIZE samples to the given file
 //!              let mut file = File::create(&args[1])?;
@@ -122,56 +124,58 @@
 };
 use std::{error, fmt};
 
+pub use audio_streams::BoxError;
 use audio_streams::{
-    capture::{CaptureBufferStream, DummyCaptureStream},
-    BufferDrop, DummyStreamControl, PlaybackBufferStream, StreamControl, StreamSource,
+    capture::{CaptureBufferStream, NoopCaptureStream},
+    shm_streams::{NullShmStream, ShmStream, ShmStreamSource},
+    BufferDrop, NoopStreamControl, PlaybackBufferStream, SampleFormat, StreamControl,
+    StreamDirection, StreamEffect, StreamSource,
 };
-pub use cras_sys::gen::CRAS_CLIENT_TYPE as CrasClientType;
 use cras_sys::gen::*;
-use sys_util::{PollContext, PollToken};
+pub use cras_sys::gen::{
+    CRAS_CLIENT_TYPE as CrasClientType, CRAS_NODE_TYPE as CrasNodeType,
+    CRAS_STREAM_EFFECT as CrasStreamEffect,
+};
+pub use cras_sys::{AudioDebugInfo, CrasIodevInfo, CrasIonodeInfo, Error as CrasSysError};
+use sys_util::{PollContext, PollToken, SharedMemory};
 
 mod audio_socket;
 use crate::audio_socket::AudioSocket;
 mod cras_server_socket;
 use crate::cras_server_socket::CrasServerSocket;
+pub use crate::cras_server_socket::CrasSocketType;
 mod cras_shm;
+use crate::cras_shm::CrasServerState;
+pub mod cras_shm_stream;
+use crate::cras_shm_stream::CrasShmStream;
 mod cras_stream;
 use crate::cras_stream::{CrasCaptureData, CrasPlaybackData, CrasStream, CrasStreamData};
 mod cras_client_message;
 use crate::cras_client_message::*;
 
 #[derive(Debug)]
-pub enum ErrorType {
+pub enum Error {
     CrasClientMessageError(cras_client_message::Error),
     CrasStreamError(cras_stream::Error),
+    CrasSysError(cras_sys::Error),
     IoError(io::Error),
     SysUtilError(sys_util::Error),
     MessageTypeError,
     UnexpectedExit,
 }
 
-#[derive(Debug)]
-pub struct Error {
-    error_type: ErrorType,
-}
-
-impl Error {
-    fn new(error_type: ErrorType) -> Self {
-        Self { error_type }
-    }
-}
-
 impl error::Error for Error {}
 
 impl fmt::Display for Error {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self.error_type {
-            ErrorType::CrasClientMessageError(ref err) => err.fmt(f),
-            ErrorType::CrasStreamError(ref err) => err.fmt(f),
-            ErrorType::IoError(ref err) => err.fmt(f),
-            ErrorType::SysUtilError(ref err) => err.fmt(f),
-            ErrorType::MessageTypeError => write!(f, "Message type error"),
-            ErrorType::UnexpectedExit => write!(f, "Unexpected exit"),
+        match self {
+            Error::CrasClientMessageError(ref err) => err.fmt(f),
+            Error::CrasStreamError(ref err) => err.fmt(f),
+            Error::CrasSysError(ref err) => err.fmt(f),
+            Error::IoError(ref err) => err.fmt(f),
+            Error::SysUtilError(ref err) => err.fmt(f),
+            Error::MessageTypeError => write!(f, "Message type error"),
+            Error::UnexpectedExit => write!(f, "Unexpected exit"),
         }
     }
 }
@@ -180,39 +184,40 @@
 
 impl From<io::Error> for Error {
     fn from(io_err: io::Error) -> Self {
-        Self::new(ErrorType::IoError(io_err))
+        Error::IoError(io_err)
     }
 }
 
 impl From<sys_util::Error> for Error {
     fn from(sys_util_err: sys_util::Error) -> Self {
-        Self::new(ErrorType::SysUtilError(sys_util_err))
+        Error::SysUtilError(sys_util_err)
     }
 }
 
 impl From<cras_stream::Error> for Error {
     fn from(err: cras_stream::Error) -> Self {
-        Self::new(ErrorType::CrasStreamError(err))
+        Error::CrasStreamError(err)
     }
 }
 
 impl From<cras_client_message::Error> for Error {
     fn from(err: cras_client_message::Error) -> Self {
-        Self::new(ErrorType::CrasClientMessageError(err))
+        Error::CrasClientMessageError(err)
     }
 }
 
-/// A CRAS server client, which implements StreamSource. It can create audio streams connecting
-/// to CRAS server.
-pub struct CrasClient {
+/// A CRAS server client, which implements StreamSource and ShmStreamSource.
+/// It can create audio streams connecting to CRAS server.
+pub struct CrasClient<'a> {
     server_socket: CrasServerSocket,
+    server_state: CrasServerState<'a>,
     client_id: u32,
     next_stream_id: u32,
     cras_capture: bool,
     client_type: CRAS_CLIENT_TYPE,
 }
 
-impl CrasClient {
+impl<'a> CrasClient<'a> {
     /// Blocks creating a `CrasClient` with registered `client_id`
     ///
     /// # Results
@@ -224,26 +229,33 @@
     /// Returns error if error occurs while handling server message or message
     /// type is incorrect
     pub fn new() -> Result<Self> {
+        Self::with_type(CrasSocketType::Legacy)
+    }
+
+    /// Tries to create a `CrasClient` with a given `CrasSocketType`.
+    ///
+    /// # Errors
+    ///
+    /// Returns error if error occurs while handling server message or message
+    /// type is incorrect.
+    pub fn with_type(socket_type: CrasSocketType) -> Result<Self> {
         // Create a connection to the server.
-        let mut server_socket = CrasServerSocket::new()?;
-
-        // Gets client ID from server
-        let client_id = {
-            match CrasClient::wait_for_message(&mut server_socket)? {
-                ServerResult::Connected(res, _server_state_fd) => res as u32,
-                _ => {
-                    return Err(Error::new(ErrorType::MessageTypeError));
-                }
-            }
-        };
-
-        Ok(Self {
-            server_socket,
-            client_id,
-            next_stream_id: 0,
-            cras_capture: false,
-            client_type: CRAS_CLIENT_TYPE::CRAS_CLIENT_TYPE_UNKNOWN,
-        })
+        let mut server_socket = CrasServerSocket::with_type(socket_type)?;
+        // Gets client ID and server state fd from server
+        if let ServerResult::Connected(client_id, server_state_fd) =
+            CrasClient::wait_for_message(&mut server_socket)?
+        {
+            Ok(Self {
+                server_socket,
+                server_state: CrasServerState::try_new(server_state_fd)?,
+                client_id,
+                next_stream_id: 0,
+                cras_capture: false,
+                client_type: CRAS_CLIENT_TYPE::CRAS_CLIENT_TYPE_UNKNOWN,
+            })
+        } else {
+            Err(Error::MessageTypeError)
+        }
     }
 
     /// Enables capturing audio through CRAS server.
@@ -256,31 +268,143 @@
         self.client_type = client_type;
     }
 
+    /// Sets the system volume to `volume`.
+    ///
+    /// Send a message to the server to request setting the system volume
+    /// to `volume`. No response is returned from the server.
+    ///
+    /// # Errors
+    ///
+    /// If writing the message to the server socket failed.
+    pub fn set_system_volume(&mut self, volume: u32) -> Result<()> {
+        let header = cras_server_message {
+            length: mem::size_of::<cras_set_system_volume>() as u32,
+            id: CRAS_SERVER_MESSAGE_ID::CRAS_SERVER_SET_SYSTEM_VOLUME,
+        };
+        let msg = cras_set_system_volume { header, volume };
+
+        self.server_socket.send_server_message_with_fds(&msg, &[])?;
+        Ok(())
+    }
+
+    /// Sets the system mute status to `mute`.
+    ///
+    /// Send a message to the server to request setting the system mute
+    /// to `mute`. No response is returned from the server.
+    ///
+    /// # Errors
+    ///
+    /// If writing the message to the server socket failed.
+    pub fn set_system_mute(&mut self, mute: bool) -> Result<()> {
+        let header = cras_server_message {
+            length: mem::size_of::<cras_set_system_mute>() as u32,
+            id: CRAS_SERVER_MESSAGE_ID::CRAS_SERVER_SET_SYSTEM_MUTE,
+        };
+        let msg = cras_set_system_mute {
+            header,
+            mute: mute as i32,
+        };
+
+        self.server_socket.send_server_message_with_fds(&msg, &[])?;
+        Ok(())
+    }
+
+    /// Gets the system volume.
+    ///
+    /// Read the current value for system volume from the server shared memory.
+    pub fn get_system_volume(&self) -> u32 {
+        self.server_state.get_system_volume()
+    }
+
+    /// Gets the system mute.
+    ///
+    /// Read the current value for system mute from the server shared memory.
+    pub fn get_system_mute(&self) -> bool {
+        self.server_state.get_system_mute()
+    }
+
+    /// Gets a list of output devices
+    ///
+    /// Read a list of the currently attached output devices from the server shared memory.
+    pub fn output_devices(&self) -> impl Iterator<Item = CrasIodevInfo> {
+        self.server_state.output_devices()
+    }
+
+    /// Gets a list of input devices
+    ///
+    /// Read a list of the currently attached input devices from the server shared memory.
+    pub fn input_devices(&self) -> impl Iterator<Item = CrasIodevInfo> {
+        self.server_state.input_devices()
+    }
+
+    /// Gets a list of output nodes
+    ///
+    /// Read a list of the currently attached output nodes from the server shared memory.
+    pub fn output_nodes(&self) -> impl Iterator<Item = CrasIonodeInfo> {
+        self.server_state.output_nodes()
+    }
+
+    /// Gets a list of input nodes
+    ///
+    /// Read a list of the currently attached input nodes from the server shared memory.
+    pub fn input_nodes(&self) -> impl Iterator<Item = CrasIonodeInfo> {
+        self.server_state.input_nodes()
+    }
+
+    /// Gets the server's audio debug info.
+    ///
+    /// Sends a message to the server requesting an update of audio debug info,
+    /// waits for the response, and then reads the info from the server state.
+    ///
+    /// # Errors
+    ///
+    /// * If sending the message to the server failed.
+    /// * If an unexpected response message is received.
+    pub fn get_audio_debug_info(&mut self) -> Result<AudioDebugInfo> {
+        let header = cras_server_message {
+            length: mem::size_of::<cras_dump_audio_thread>() as u32,
+            id: CRAS_SERVER_MESSAGE_ID::CRAS_SERVER_DUMP_AUDIO_THREAD,
+        };
+        let msg = cras_dump_audio_thread { header };
+
+        self.server_socket.send_server_message_with_fds(&msg, &[])?;
+
+        match CrasClient::wait_for_message(&mut self.server_socket)? {
+            ServerResult::DebugInfoReady => Ok(self
+                .server_state
+                .get_audio_debug_info()
+                .map_err(Error::CrasSysError)?),
+            _ => Err(Error::MessageTypeError),
+        }
+    }
+
     // Gets next server_stream_id from client and increment stream_id counter.
-    fn next_server_stream_id(&mut self) -> Result<u32> {
+    fn next_server_stream_id(&mut self) -> u32 {
         let res = self.next_stream_id;
         self.next_stream_id += 1;
-        self.server_stream_id(&res)
+        self.server_stream_id(res)
     }
 
     // Gets server_stream_id from given stream_id
-    fn server_stream_id(&self, stream_id: &u32) -> Result<u32> {
-        Ok((self.client_id << 16) | stream_id)
+    fn server_stream_id(&self, stream_id: u32) -> u32 {
+        (self.client_id << 16) | stream_id
     }
 
     // Creates general stream with given parameters
-    fn create_stream<'a, T: BufferDrop + CrasStreamData<'a>>(
+    fn create_stream<'b, T: BufferDrop + CrasStreamData<'b>>(
         &mut self,
+        device_index: Option<u32>,
         block_size: u32,
         direction: CRAS_STREAM_DIRECTION,
-        rate: usize,
+        rate: u32,
         channel_num: usize,
-        format: snd_pcm_format_t,
-    ) -> Result<CrasStream<'a, T>> {
-        let stream_id = self.next_server_stream_id()?;
+        format: SampleFormat,
+    ) -> Result<CrasStream<'b, T>> {
+        let stream_id = self.next_server_stream_id();
 
         // Prepares server message
-        let audio_format = cras_audio_format_packed::new(format, rate, channel_num);
+        let audio_format =
+            cras_audio_format_packed::new(format.into(), rate, channel_num, direction);
         let msg_header = cras_server_message {
             length: mem::size_of::<cras_connect_message>() as u32,
             id: CRAS_SERVER_MESSAGE_ID::CRAS_SERVER_CONNECT_STREAM,
@@ -295,10 +419,11 @@
             cb_threshold: block_size,
             flags: 0,
             format: audio_format,
-            dev_idx: CRAS_SPECIAL_DEVICE::NO_DEVICE as u32,
+            dev_idx: device_index.unwrap_or(CRAS_SPECIAL_DEVICE::NO_DEVICE as u32),
             effects: 0,
             client_type: self.client_type,
             client_shm_size: 0,
+            buffer_offsets: [0, 0],
         };
 
         // Creates AudioSocket pair
@@ -310,26 +435,90 @@
             .send_server_message_with_fds(&server_cmsg, &socks)?;
 
         let audio_socket = AudioSocket::new(sock1);
-        let mut stream = CrasStream::new(
-            stream_id,
-            self.server_socket.try_clone()?,
-            block_size,
-            direction,
-            rate,
-            channel_num,
-            format,
-            audio_socket,
-        );
-
         loop {
             let result = CrasClient::wait_for_message(&mut self.server_socket)?;
             if let ServerResult::StreamConnected(_stream_id, header_fd, samples_fd) = result {
-                stream.init_shm(header_fd, samples_fd)?;
-                break;
+                return CrasStream::try_new(
+                    stream_id,
+                    self.server_socket.try_clone()?,
+                    block_size,
+                    direction,
+                    rate,
+                    channel_num,
+                    format.into(),
+                    audio_socket,
+                    header_fd,
+                    samples_fd,
+                )
+                .map_err(Error::CrasStreamError);
             }
         }
+    }
 
-        Ok(stream)
+    /// Creates a new playback stream pinned to the device at `device_index`.
+    ///
+    /// # Arguments
+    ///
+    /// * `device_index` - The device to which the stream will be attached.
+    /// * `num_channels` - The count of audio channels for the stream.
+    /// * `format` - The format to use for stream audio samples.
+    /// * `frame_rate` - The sample rate of the stream.
+    /// * `buffer_size` - The transfer size granularity in frames.
+    #[allow(clippy::type_complexity)]
+    pub fn new_pinned_playback_stream(
+        &mut self,
+        device_index: u32,
+        num_channels: usize,
+        format: SampleFormat,
+        frame_rate: u32,
+        buffer_size: usize,
+    ) -> std::result::Result<(Box<dyn StreamControl>, Box<dyn PlaybackBufferStream>), BoxError>
+    {
+        Ok((
+            Box::new(NoopStreamControl::new()),
+            Box::new(self.create_stream::<CrasPlaybackData>(
+                Some(device_index),
+                buffer_size as u32,
+                CRAS_STREAM_DIRECTION::CRAS_STREAM_OUTPUT,
+                frame_rate,
+                num_channels,
+                format,
+            )?),
+        ))
+    }
+
+    /// Creates a new capture stream pinned to the device at `device_index`.
+    ///
+    /// This is useful for, among other things, capturing from a loopback
+    /// device.
+    ///
+    /// # Arguments
+    ///
+    /// * `device_index` - The device to which the stream will be attached.
+    /// * `num_channels` - The count of audio channels for the stream.
+    /// * `format` - The format to use for stream audio samples.
+    /// * `frame_rate` - The sample rate of the stream.
+    /// * `buffer_size` - The transfer size granularity in frames.
+    #[allow(clippy::type_complexity)]
+    pub fn new_pinned_capture_stream(
+        &mut self,
+        device_index: u32,
+        num_channels: usize,
+        format: SampleFormat,
+        frame_rate: u32,
+        buffer_size: usize,
+    ) -> std::result::Result<(Box<dyn StreamControl>, Box<dyn CaptureBufferStream>), BoxError> {
+        Ok((
+            Box::new(NoopStreamControl::new()),
+            Box::new(self.create_stream::<CrasCaptureData>(
+                Some(device_index),
+                buffer_size as u32,
+                CRAS_STREAM_DIRECTION::CRAS_STREAM_INPUT,
+                frame_rate,
+                num_channels,
+                format,
+            )?),
+        ))
     }
 
     // Blocks handling the first server message received from `socket`.
@@ -346,7 +535,7 @@
         let tokens: Vec<Token> = events.iter_readable().map(|e| e.token()).collect();
         tokens
             .get(0)
-            .ok_or_else(|| Error::new(ErrorType::UnexpectedExit))
+            .ok_or(Error::UnexpectedExit)
             .and_then(|ref token| {
                 match token {
                     Token::ServerMsg => ServerResult::handle_server_message(socket),
@@ -354,55 +543,63 @@
                 .map_err(Into::into)
             })
     }
+
+    /// Returns any open file descriptors needed by CrasClient.
+    /// This function is shared between StreamSource and ShmStreamSource.
+    fn keep_fds(&self) -> Vec<RawFd> {
+        vec![self.server_socket.as_raw_fd()]
+    }
 }
 
-impl StreamSource for CrasClient {
+impl<'a> StreamSource for CrasClient<'a> {
+    #[allow(clippy::type_complexity)]
     fn new_playback_stream(
         &mut self,
         num_channels: usize,
-        frame_rate: usize,
+        format: SampleFormat,
+        frame_rate: u32,
         buffer_size: usize,
-    ) -> std::result::Result<
-        (Box<dyn StreamControl>, Box<dyn PlaybackBufferStream>),
-        Box<dyn error::Error>,
-    > {
+    ) -> std::result::Result<(Box<dyn StreamControl>, Box<dyn PlaybackBufferStream>), BoxError>
+    {
         Ok((
-            Box::new(DummyStreamControl::new()),
+            Box::new(NoopStreamControl::new()),
             Box::new(self.create_stream::<CrasPlaybackData>(
+                None,
                 buffer_size as u32,
                 CRAS_STREAM_DIRECTION::CRAS_STREAM_OUTPUT,
                 frame_rate,
                 num_channels,
-                _snd_pcm_format::SND_PCM_FORMAT_S16_LE,
+                format,
             )?),
         ))
     }
 
+    #[allow(clippy::type_complexity)]
     fn new_capture_stream(
         &mut self,
         num_channels: usize,
-        frame_rate: usize,
+        format: SampleFormat,
+        frame_rate: u32,
         buffer_size: usize,
-    ) -> std::result::Result<
-        (Box<dyn StreamControl>, Box<dyn CaptureBufferStream>),
-        Box<dyn error::Error>,
-    > {
+    ) -> std::result::Result<(Box<dyn StreamControl>, Box<dyn CaptureBufferStream>), BoxError> {
         if self.cras_capture {
             Ok((
-                Box::new(DummyStreamControl::new()),
+                Box::new(NoopStreamControl::new()),
                 Box::new(self.create_stream::<CrasCaptureData>(
+                    None,
                     buffer_size as u32,
                     CRAS_STREAM_DIRECTION::CRAS_STREAM_INPUT,
                     frame_rate,
                     num_channels,
-                    _snd_pcm_format::SND_PCM_FORMAT_S16_LE,
+                    format,
                 )?),
             ))
         } else {
             Ok((
-                Box::new(DummyStreamControl::new()),
-                Box::new(DummyCaptureStream::new(
+                Box::new(NoopStreamControl::new()),
+                Box::new(NoopCaptureStream::new(
                     num_channels,
+                    format,
                     frame_rate,
                     buffer_size,
                 )),
@@ -411,6 +608,92 @@
     }
 
     fn keep_fds(&self) -> Option<Vec<RawFd>> {
-        Some(vec![self.server_socket.as_raw_fd()])
+        Some(CrasClient::keep_fds(self))
+    }
+}
+
+impl<'a> ShmStreamSource for CrasClient<'a> {
+    fn new_stream(
+        &mut self,
+        direction: StreamDirection,
+        num_channels: usize,
+        format: SampleFormat,
+        frame_rate: u32,
+        buffer_size: usize,
+        effects: &[StreamEffect],
+        client_shm: &SharedMemory,
+        buffer_offsets: [u64; 2],
+    ) -> std::result::Result<Box<dyn ShmStream>, BoxError> {
+        if direction == StreamDirection::Capture && !self.cras_capture {
+            return Ok(Box::new(NullShmStream::new(
+                buffer_size,
+                num_channels,
+                format,
+                frame_rate,
+            )));
+        }
+
+        let buffer_size = buffer_size as u32;
+
+        // Prepares server message
+        let stream_id = self.next_server_stream_id();
+        let audio_format = cras_audio_format_packed::new(
+            format.into(),
+            frame_rate,
+            num_channels,
+            direction.into(),
+        );
+        let msg_header = cras_server_message {
+            length: mem::size_of::<cras_connect_message>() as u32,
+            id: CRAS_SERVER_MESSAGE_ID::CRAS_SERVER_CONNECT_STREAM,
+        };
+
+        let server_cmsg = cras_connect_message {
+            header: msg_header,
+            proto_version: CRAS_PROTO_VER,
+            direction: direction.into(),
+            stream_id,
+            stream_type: CRAS_STREAM_TYPE::CRAS_STREAM_TYPE_DEFAULT,
+            buffer_frames: buffer_size,
+            cb_threshold: buffer_size,
+            flags: 0,
+            format: audio_format,
+            dev_idx: CRAS_SPECIAL_DEVICE::NO_DEVICE as u32,
+            effects: effects.iter().collect::<CrasStreamEffect>().into(),
+            client_type: self.client_type,
+            client_shm_size: client_shm.size(),
+            buffer_offsets,
+        };
+
+        // Creates AudioSocket pair
+        let (sock1, sock2) = UnixStream::pair()?;
+
+        // Sends `CRAS_SERVER_CONNECT_STREAM` message
+        let fds = [sock2.as_raw_fd(), client_shm.as_raw_fd()];
+        self.server_socket
+            .send_server_message_with_fds(&server_cmsg, &fds)?;
+
+        loop {
+            let result = CrasClient::wait_for_message(&mut self.server_socket)?;
+            if let ServerResult::StreamConnected(_stream_id, header_fd, _samples_fd) = result {
+                let audio_socket = AudioSocket::new(sock1);
+                let stream = CrasShmStream::try_new(
+                    stream_id,
+                    self.server_socket.try_clone()?,
+                    audio_socket,
+                    direction,
+                    num_channels,
+                    frame_rate,
+                    format,
+                    header_fd,
+                    client_shm.size() as usize,
+                )?;
+                return Ok(Box::new(stream));
+            }
+        }
+    }
+
+    fn keep_fds(&self) -> Vec<RawFd> {
+        CrasClient::keep_fds(self)
     }
 }
diff --git a/cras/configure.ac b/cras/configure.ac
index f00011c..f39a14a 100644
--- a/cras/configure.ac
+++ b/cras/configure.ac
@@ -10,6 +10,9 @@
 AM_INIT_AUTOMAKE([1.10 -Wall no-define])
 #AC_CONFIG_HEADERS([config.h])
 
+# To compile with full logs, use V=1 with make instead.
+AM_SILENT_RULES([yes])
+
 m4_ifdef([AM_PROG_AR], [AM_PROG_AR])
 AC_PROG_LIBTOOL
 AC_PROG_CC
@@ -23,6 +26,8 @@
 PKG_CHECK_MODULES([LIBSPEEX], [ speexdsp >= 1.2 ])
 PKG_CHECK_MODULES([ASOUNDLIB], [ alsa >= 1.1.0 ])
 
+AC_CHECK_HEADERS([iniparser/iniparser.h])
+
 AC_ARG_ENABLE([dbus], AS_HELP_STRING([--disable-dbus], [Disable all DBUS uses]), have_dbus=$enableval, have_dbus=yes)
 AM_CONDITIONAL(HAVE_DBUS, test "$have_dbus" = "yes")
 if test "$have_dbus" = "yes"; then
@@ -48,7 +53,7 @@
 AC_SUBST(SELINUX_LIBS)
 
 # WEBRTC APM support
-AC_ARG_ENABLE([webrtc-apm], AS_HELP_STRING([--disable-webrtc-apm], [Disable webrtc-apm uses]), have_webrtc_apm=$enableval, have_webrtc_apm=yes)
+AC_ARG_ENABLE([webrtc-apm], AS_HELP_STRING([--enable-webrtc-apm], [Enable webrtc-apm uses]), have_webrtc_apm=$enableval, have_webrtc_apm=no)
 AM_CONDITIONAL(HAVE_WEBRTC_APM, test "$have_webrtc_apm" = "yes")
 if test "$have_webrtc_apm" = "yes"; then
     PKG_CHECK_MODULES([WEBRTC_APM], [ libwebrtc_apm ])
@@ -58,7 +63,23 @@
 fi
 AC_SUBST(WEBRTC_APM_LIBS)
 
+# Build fuzzer binaries
+AC_ARG_ENABLE([fuzzer], AS_HELP_STRING([--enable-fuzzer], [Enable fuzzer build]), have_fuzzer=$enableval, have_fuzzer=no)
+AM_CONDITIONAL(HAVE_FUZZER, test "$have_fuzzer" = "yes")
+if test "$have_fuzzer" = "yes"; then
+    AC_DEFINE(HAVE_FUZZER, 1, [Define to build fuzzers.])
+fi
+
 PKG_CHECK_MODULES([SBC], [ sbc >= 1.0 ])
+AC_CHECK_HEADERS([iniparser/iniparser.h iniparser.h], [FOUND_INIPARSER=1;break])
+test [$FOUND_INIPARSER] || AC_MSG_ERROR([Missing iniparser, please install.])
+AC_SEARCH_LIBS([LADSPA], [ladspa-sdk], [], [
+	AC_CHECK_HEADERS([ladspa.h], [], [
+		AC_MSG_ERROR([Missing ladspa-sdk, please install.])
+	])
+])
+PKG_CHECK_MODULES([UDEV], [ libudev >= 1.0 ])
+PKG_CHECK_MODULES([GTEST], [ gtest >= 1.0 ])
 AC_CHECK_LIB(asound, snd_pcm_ioplug_create,,
 	     AC_ERROR([*** libasound has no external plugin SDK]), -ldl)
 
@@ -68,12 +89,29 @@
 AC_ARG_ENABLE([metrics], AS_HELP_STRING([--enable-metrics], [Enable metrics uses]), have_metrics=$enableval, have_metrics=no)
 if test "$have_metrics" = "yes"; then
     AC_DEFINE(HAVE_LIB_METRICS, 1, [Define to use libmetrics])
-    METRICS_LIBS=-lmetrics-${BASE_VER}
+    METRICS_LIBS=-lmetrics
 else
     METRICS_LIBS=
 fi
 AC_SUBST(METRICS_LIBS)
 
+# Check if the system copy of the cras rust library should be used. If not, make sure cargo and rustc are present to build it.
+AC_ARG_WITH([system-cras-rust],
+	    AS_HELP_STRING([--with-system-cras-rust], [Use the system provided cras_rust library]),
+	    with_system_rust=$enableval,
+	    with_system_rust=no)
+AM_CONDITIONAL(WITH_SYSTEM_RUST, test "$with_system_rust" = "yes")
+if test "$with_system_rust" = "no"; then
+    AC_CHECK_PROG(CARGO, [cargo], [yes], [no])
+    AS_IF(test x$CARGO = xno,
+        AC_MSG_ERROR([cargo is required to build cras rust lib.])
+    )
+    AC_CHECK_PROG(RUSTC, [rustc], [yes], [no])
+    AS_IF(test x$RUSTC = xno,
+        AC_MSG_ERROR([rustc is required to build cras rust lib.])
+    )
+fi
+
 # Determine ALSA plugin directory.
 test "x$prefix" = xNONE && prefix=$ac_default_prefix
 test "x$exec_prefix" = xNONE && exec_prefix=$prefix
diff --git a/cras/install_deps.sh b/cras/install_deps.sh
new file mode 100755
index 0000000..6eac01a
--- /dev/null
+++ b/cras/install_deps.sh
@@ -0,0 +1,51 @@
+#!/bin/sh
+apt-get install -y \
+  automake \
+  build-essential \
+  cmake \
+  g++ \
+  gdb \
+  git \
+  ladspa-sdk \
+  libasound-dev \
+  libdbus-1-dev \
+  libncurses5-dev \
+  libsbc-dev \
+  libsndfile-dev \
+  libspeexdsp-dev \
+  libtool \
+  libudev-dev \
+  wget \
+  zip
+cd /tmp
+git clone https://github.com/ndevilla/iniparser.git
+cd iniparser
+make
+cp libiniparser.* /usr/local/lib
+cp src/dictionary.h src/iniparser.h /usr/local/include
+chmod 644 /usr/local/include/dictionary.h /usr/local/include/iniparser.h
+chmod 644 /usr/local/lib/libiniparser.a
+chmod 755 /usr/local/lib/libiniparser.so.*
+
+cd /tmp
+git clone https://github.com/google/googletest.git -b v1.8.x
+cd googletest
+mkdir build
+cd build
+cmake .. -DBUILD_SHARED_LIBS=ON \
+  -DINSTALL_GTEST=ON \
+  -DCMAKE_INSTALL_PREFIX:PATH=/usr
+make
+make install
+
+# Need to build and install alsa so there is a static lib.
+mkdir -p /tmp/alsa-build &&
+  cd /tmp/alsa-build && \
+  wget ftp://ftp.alsa-project.org/pub/lib/alsa-lib-1.1.4.1.tar.bz2 && \
+  bzip2 -f -d alsa-lib-* && \
+  tar xf alsa-lib-* && \
+  cd alsa-lib-* && \
+  ./configure --enable-static --disable-shared && \
+  make clean && \
+  make -j$(nproc) all && \
+  make install
diff --git a/cras/src/Android.bp b/cras/src/Android.bp
index 070e6ca..17b9919 100644
--- a/cras/src/Android.bp
+++ b/cras/src/Android.bp
@@ -1,3 +1,13 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "external_adhd_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-BSD
+    //   SPDX-license-identifier-LGPL
+    default_applicable_licenses: ["external_adhd_license"],
+}
+
 cc_library_static {
     name: "libcras",
 
diff --git a/cras/src/Makefile.am b/cras/src/Makefile.am
index acc62d4..1e89f81 100644
--- a/cras/src/Makefile.am
+++ b/cras/src/Makefile.am
@@ -41,10 +41,12 @@
 COMMON_SIMD_CPPFLAGS = -O3 -Wall -Werror -Wno-error=cpp
 
 bin_PROGRAMS = cras cras_test_client cras_monitor cras_router
+noinst_PROGRAMS =
 
 if HAVE_DBUS
 CRAS_DBUS_SOURCES = \
 	common/cras_sbc_codec.c \
+	common/packet_status_logger.c \
 	server/cras_bt_manager.c \
 	server/cras_bt_adapter.c \
 	server/cras_bt_device.c \
@@ -53,6 +55,7 @@
 	server/cras_bt_player.c \
 	server/cras_bt_io.c \
 	server/cras_bt_profile.c \
+	server/cras_bt_battery_provider.c \
 	server/cras_dbus.c \
 	server/cras_dbus_util.c \
 	server/cras_dbus_control.c \
@@ -106,7 +109,7 @@
 	server/buffer_share.c \
 	server/config/cras_board_config.c \
 	server/config/cras_card_config.c \
-	server/config/cras_device_blacklist.c \
+	server/config/cras_device_blocklist.c \
 	server/cras_alert.c \
 	server/cras_alsa_card.c \
 	server/cras_alsa_helpers.c \
@@ -114,6 +117,7 @@
 	server/cras_alsa_jack.c \
 	server/cras_alsa_mixer.c \
 	server/cras_alsa_mixer_name.c \
+	server/cras_alsa_plugin_io.c \
 	server/cras_alsa_ucm.c \
 	server/cras_alsa_ucm_section.c \
 	server/cras_audio_area.c \
@@ -143,7 +147,9 @@
 	server/cras_control_rclient.c \
 	server/cras_playback_rclient.c \
 	server/cras_capture_rclient.c \
+	server/cras_unified_rclient.c \
 	server/cras_rstream.c \
+	server/cras_rstream_config.c \
 	server/cras_server_metrics.c \
 	server/cras_system_state.c \
 	server/cras_tm.c \
@@ -151,22 +157,26 @@
 	server/cras_volume_curve.c \
 	server/dev_io.c \
 	server/dev_stream.c \
+	server/ewma_power.c \
 	server/input_data.c \
 	server/linear_resampler.c \
 	server/polled_interval_checker.c \
 	server/server_stream.c \
 	server/stream_list.c \
 	server/test_iodev.c \
-	server/rate_estimator.c \
 	server/softvol_curve.c
 
+SERVER_RUST_SRCDIR = $(top_srcdir)/src/server/rust
+
 libcrasserver_la_SOURCES = \
 	$(cras_server_SOURCES)
 libcrasserver_la_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \
 	-I$(top_srcdir)/src/dsp -I$(top_srcdir)/src/server \
 	-I$(top_srcdir)/src/server/config -I$(top_srcdir)/src/plc \
+	-I$(SERVER_RUST_SRCDIR)/src/headers \
 	$(DBUS_CFLAGS) $(SBC_CFLAGS) $(SELINUX_CFLAGS)
 libcrasserver_la_LIBADD = \
+	$(CRAS_RUST) \
 	libcrasmix.la \
 	$(CRAS_SSE4_2) \
 	$(CRAS_AVX) \
@@ -193,6 +203,7 @@
 	$(CRAS_AVX) \
 	$(CRAS_AVX2) \
 	$(CRAS_FMA) \
+	$(CRAS_RUST) \
 	-lpthread -lasound -lrt -liniparser -ludev -ldl -lm -lspeexdsp \
 	$(METRICS_LIBS) \
 	$(SBC_LIBS) \
@@ -272,6 +283,7 @@
 	common/cras_types.h \
 	common/cras_util.h \
 	common/edid_utils.h \
+	common/packet_status_logger.h \
 	common/utlist.h \
 	libcras/cras_client.h \
 	libcras/cras_helpers.h
@@ -298,6 +310,28 @@
 libasound_module_ctl_cras_la_SOURCES = alsa_plugin/ctl_cras.c
 libasound_module_ctl_cras_la_LIBADD = -lasound libcras.la
 
+if !WITH_SYSTEM_RUST
+RUST_FILES = \
+	$(SERVER_RUST_SRCDIR)/Cargo.toml \
+	$(SERVER_RUST_SRCDIR)/src/rate_estimator_bindings.rs \
+	$(SERVER_RUST_SRCDIR)/src/rate_estimator.rs
+
+CRAS_RUST_TARGET_DIR = $(top_builddir)/src/server/rust/target
+CRAS_RUST = $(CRAS_RUST_TARGET_DIR)/release/libcras_rust.a
+$(CRAS_RUST): $(RUST_FILES)
+	cargo build --release \
+		--manifest-path $(SERVER_RUST_SRCDIR)/Cargo.toml \
+		--target-dir $(CRAS_RUST_TARGET_DIR)
+
+clean-local:
+	cargo clean --release \
+		--manifest-path $(SERVER_RUST_SRCDIR)/Cargo.toml \
+		--target-dir $(CRAS_RUST_TARGET_DIR)
+
+else
+CRAS_RUST = -lcras_rust
+endif
+
 # Inject a dependency between the installation rules of libcras and its modules.
 # This avoids a race when the modules are relinked before libcras is actually
 # installed.
@@ -309,6 +343,50 @@
 $(hide_install)-asound_module_pcm_crasLTLIBRARIES: install-libLTLIBRARIES
 $(hide_install)-asound_module_ctl_crasLTLIBRARIES: install-libLTLIBRARIES
 
+# ==== Fuzzer section
+if HAVE_FUZZER
+FUZZERS = \
+	cras_rclient_message_fuzzer \
+	cras_hfp_slc_fuzzer
+
+noinst_PROGRAMS += $(FUZZERS)
+
+FUZZER_CPPFLAGS = $(COMMON_CPPFLAGS) \
+	-I$(top_srcdir)/src/common \
+	-I$(top_srcdir)/src/dsp -I$(top_srcdir)/src/server \
+	-I$(top_srcdir)/src/server/config -I$(top_srcdir)/src/plc \
+	$(DBUS_CFLAGS) $(SBC_CFLAGS)
+
+FUZZER_LDADD = \
+	libcrasmix.la \
+	libcrasserver.la \
+	$(CRAS_SSE4_2) \
+	$(CRAS_AVX) \
+	$(CRAS_AVX2) \
+	$(CRAS_FMA) \
+	$(CRAS_RUST) \
+	-lpthread -lasound -lrt -liniparser -ludev -ldl -lm -lspeexdsp \
+	$(METRICS_LIBS) \
+	$(SBC_LIBS) \
+	$(DBUS_LIBS) \
+	$(WEBRTC_APM_LIBS)
+
+cras_rclient_message_fuzzer_SOURCES = \
+	fuzz/rclient_message.cc
+
+cras_rclient_message_fuzzer_CPPFLAGS = $(FUZZER_CPPFLAGS)
+cras_rclient_message_fuzzer_LDFLAGS = $(FUZZER_LDFLAGS)
+cras_rclient_message_fuzzer_LDADD = $(FUZZER_LDADD)
+
+cras_hfp_slc_fuzzer_SOURCES = \
+	fuzz/cras_hfp_slc.cc
+
+cras_hfp_slc_fuzzer_CPPFLAGS = $(FUZZER_CPPFLAGS)
+cras_hfp_slc_fuzzer_LDFLAGS = $(FUZZER_LDFLAGS)
+cras_hfp_slc_fuzzer_LDADD = $(FUZZER_LDADD)
+endif
+
+# ==== Tests section
 if HAVE_DBUS
 DBUS_TESTS = \
 	a2dp_info_unittest \
@@ -349,12 +427,13 @@
 	byte_buffer_unittest \
 	card_config_unittest \
 	checksum_unittest \
+	cras_abi_unittest \
 	cras_client_unittest \
 	cras_tm_unittest \
 	device_monitor_unittest \
 	dev_io_unittest \
 	dev_stream_unittest \
-	device_blacklist_unittest \
+	device_blocklist_unittest \
 	dsp_core_unittest \
 	dsp_ini_unittest \
 	dsp_pipeline_unittest \
@@ -363,6 +442,7 @@
 	edid_utils_unittest \
 	empty_iodev_unittest \
 	expr_unittest \
+	ewma_power_unittest \
 	file_wait_unittest \
 	float_buffer_unittest \
 	fmt_conv_unittest \
@@ -535,8 +615,7 @@
 	-I$(top_srcdir)/src/common
 a2dp_info_unittest_LDADD = -lgtest -lpthread
 
-a2dp_iodev_unittest_SOURCES = tests/a2dp_iodev_unittest.cc \
-	server/cras_a2dp_iodev.c common/sfh.c
+a2dp_iodev_unittest_SOURCES = tests/a2dp_iodev_unittest.cc
 a2dp_iodev_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/server \
 	-I$(top_srcdir)/src/common $(DBUS_CFLAGS)
 a2dp_iodev_unittest_LDADD = -lgtest -lpthread $(DBUS_LIBS)
@@ -597,7 +676,8 @@
 	server/dev_io.c tests/empty_audio_stub.cc tests/metrics_stub.cc \
 	common/cras_shm.c
 audio_thread_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) \
-	-I$(top_srcdir)/src/common -I$(top_srcdir)/src/server
+	-I$(top_srcdir)/src/common -I$(top_srcdir)/src/server \
+	-I$(SERVER_RUST_SRCDIR)/src/headers
 audio_thread_unittest_LDADD = -lgtest -lpthread -lrt
 
 audio_thread_monitor_unittest_SOURCES = tests/audio_thread_monitor_unittest.cc
@@ -607,7 +687,8 @@
 
 if HAVE_DBUS
 bt_device_unittest_SOURCES = tests/bt_device_unittest.cc \
-	server/cras_bt_device.c
+	server/cras_bt_device.c \
+	tests/metrics_stub.cc common/sfh.c
 bt_device_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/server \
 	-I$(top_srcdir)/src/common $(DBUS_CFLAGS)
 bt_device_unittest_LDADD = -lgtest -lpthread $(DBUS_LIBS)
@@ -639,6 +720,13 @@
 checksum_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common
 checksum_unittest_LDADD = -lgtest -lpthread
 
+cras_abi_unittest_SOURCES = tests/cras_abi_unittest.cc \
+	common/cras_config.c common/cras_shm.c common/cras_util.c \
+	common/cras_file_wait.c common/cras_audio_format.c
+cras_abi_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \
+	-I$(top_srcdir)/src/libcras
+cras_abi_unittest_LDADD = -lgtest -lpthread -lrt -lspeexdsp
+
 cras_client_unittest_SOURCES = tests/cras_client_unittest.cc \
 	common/cras_config.c common/cras_shm.c common/cras_util.c \
 	common/cras_file_wait.c
@@ -668,6 +756,7 @@
 	-I$(top_srcdir)/src/common \
 	-I$(top_srcdir)/src/server \
 	-I$(top_srcdir)/src/server/config \
+	-I$(SERVER_RUST_SRCDIR)/src/headers \
 	$(SELINUX_CFLAGS)
 dev_io_unittest_LDADD = \
 	libcrasmix.la \
@@ -684,12 +773,12 @@
 	-I$(top_srcdir)/src/common -I$(top_srcdir)/src/server
 dev_stream_unittest_LDADD = -lgtest -liniparser -lpthread -lrt
 
-device_blacklist_unittest_SOURCES = tests/device_blacklist_unittest.cc \
-	server/config/cras_device_blacklist.c
-device_blacklist_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) \
+device_blocklist_unittest_SOURCES = tests/device_blocklist_unittest.cc \
+	server/config/cras_device_blocklist.c
+device_blocklist_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) \
 	-I$(top_srcdir)/src/common -I$(top_srcdir)/src/server \
 	-I$(top_srcdir)/src/server/config $(CRAS_UT_TMPDIR_CFLAGS)
-device_blacklist_unittest_LDADD = -lgtest -liniparser -lpthread
+device_blocklist_unittest_LDADD = -lgtest -liniparser -lpthread
 
 device_monitor_unittest_SOURCES = tests/device_monitor_unittest.cc
 device_monitor_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \
@@ -774,13 +863,13 @@
 
 if HAVE_DBUS
 hfp_iodev_unittest_SOURCES = tests/hfp_iodev_unittest.cc \
-	server/cras_hfp_iodev.c common/sfh.c
+	server/cras_hfp_iodev.c
 hfp_iodev_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \
 	-I$(top_srcdir)/src/server $(DBUS_CFLAGS)
 hfp_iodev_unittest_LDADD = -lgtest -lpthread $(DBUS_LIBS)
 
 hfp_alsa_iodev_unittest_SOURCES = tests/hfp_alsa_iodev_unittest.cc \
-	server/cras_hfp_alsa_iodev.c common/sfh.c
+	server/cras_hfp_alsa_iodev.c
 hfp_alsa_iodev_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) \
 	-I$(top_srcdir)/src/common -I$(top_srcdir)/src/server $(DBUS_CFLAGS)
 hfp_alsa_iodev_unittest_LDADD = -lgtest -lpthread $(DBUS_LIBS)
@@ -792,7 +881,7 @@
 hfp_ag_profile_unittest_LDADD = -lgtest -lpthread $(DBUS_LIBS)
 
 hfp_slc_unittest_SOURCES = tests/hfp_slc_unittest.cc \
-	server/cras_hfp_slc.c
+	server/cras_hfp_slc.c tests/metrics_stub.cc
 hfp_slc_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \
 	-I$(top_srcdir)/src/server $(DBUS_CFLAGS)
 hfp_slc_unittest_LDADD = -lgtest -lpthread $(DBUS_LIBS)
@@ -804,6 +893,14 @@
 	-I$(top_srcdir)/src/common -I$(top_srcdir)/src/server
 buffer_share_unittest_LDADD = -lgtest -liniparser -lpthread
 
+ewma_power_unittest_SOURCES = tests/ewma_power_unittest.cc \
+	common/cras_audio_format.c server/cras_audio_area.c \
+	server/ewma_power.c
+
+ewma_power_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) \
+	-I$(top_srcdir)/src/common -I$(top_srcdir)/src/server
+ewma_power_unittest_LDADD = -lgtest
+
 iodev_list_unittest_SOURCES = tests/iodev_list_unittest.cc \
 	server/cras_iodev_list.c
 iodev_list_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \
@@ -811,11 +908,11 @@
 iodev_list_unittest_LDADD = -lgtest -lpthread
 
 loopback_iodev_unittest_SOURCES = tests/loopback_iodev_unittest.cc \
-	server/cras_loopback_iodev.c common/sfh.c
+	server/cras_loopback_iodev.c common/cras_shm.c common/sfh.c
 loopback_iodev_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) \
 	-I$(top_srcdir)/src/common \
 	-I$(top_srcdir)/src/server
-loopback_iodev_unittest_LDADD = -lgtest -lpthread
+loopback_iodev_unittest_LDADD = -lgtest -lpthread -lrt
 
 input_data_unittest_SOURCES = tests/input_data_unittest.cc \
 	server/input_data.c
@@ -826,7 +923,8 @@
 iodev_unittest_SOURCES = tests/iodev_unittest.cc \
 	server/cras_iodev.c common/cras_shm.c
 iodev_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \
-	 -I$(top_srcdir)/src/server
+	-I$(top_srcdir)/src/server \
+	-I$(SERVER_RUST_SRCDIR)/src/headers
 iodev_unittest_LDADD = -lgtest -lpthread -lrt
 
 mix_unittest_SOURCES = tests/mix_unittest.cc server/cras_mix.c
@@ -862,31 +960,37 @@
 	-I$(top_srcdir)/src/server
 ramp_unittest_LDADD = -lgtest -lpthread
 
-rate_estimator_unittest_SOURCES = tests/rate_estimator_unittest.cc server/rate_estimator.c
+rate_estimator_unittest_SOURCES = tests/rate_estimator_unittest.cc
 rate_estimator_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \
-	 -I$(top_srcdir)/src/server
-rate_estimator_unittest_LDADD = -lgtest -lpthread
+	 -I$(top_srcdir)/src/server \
+	 -I$(SERVER_RUST_SRCDIR)/src/headers
+rate_estimator_unittest_LDADD = $(CRAS_RUST) -lgtest -ldl -lpthread
 
-control_rclient_unittest_SOURCES = tests/control_rclient_unittest.cc
+control_rclient_unittest_SOURCES = tests/control_rclient_unittest.cc \
+				   server/cras_rstream_config.c
 control_rclient_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) \
 	-I$(top_srcdir)/src/common \
-	-I$(top_srcdir)/src/server $(CRAS_UT_TMPDIR_CFLAGS)
-control_rclient_unittest_LDADD = -lgtest -lpthread
+	-I$(top_srcdir)/src/server $(CRAS_UT_TMPDIR_CFLAGS) \
+	$(DBUS_CFLAGS)
+control_rclient_unittest_LDADD = -lgtest -lpthread $(DBUS_LIBS)
 
-playback_rclient_unittest_SOURCES = tests/playback_rclient_unittest.cc
+playback_rclient_unittest_SOURCES = tests/playback_rclient_unittest.cc \
+				    server/cras_rstream_config.c
 playback_rclient_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) \
 	-I$(top_srcdir)/src/common \
 	-I$(top_srcdir)/src/server $(CRAS_UT_TMPDIR_CFLAGS)
 playback_rclient_unittest_LDADD = -lgtest -lpthread
 
-capture_rclient_unittest_SOURCES = tests/capture_rclient_unittest.cc
+capture_rclient_unittest_SOURCES = tests/capture_rclient_unittest.cc \
+				   server/cras_rstream_config.c
 capture_rclient_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) \
 	-I$(top_srcdir)/src/common \
 	-I$(top_srcdir)/src/server $(CRAS_UT_TMPDIR_CFLAGS)
 capture_rclient_unittest_LDADD = -lgtest -lpthread
 
 rstream_unittest_SOURCES = tests/rstream_unittest.cc server/cras_rstream.c \
-	common/cras_shm.c $(CRAS_SELINUX_UNITTEST_SOURCES)
+	common/cras_shm.c tests/metrics_stub.cc \
+	server/cras_rstream_config.c $(CRAS_SELINUX_UNITTEST_SOURCES)
 rstream_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \
 	 -I$(top_srcdir)/src/server $(SELINUX_CFLAGS)
 rstream_unittest_LDADD = $(SELINUX_LIBS) \
@@ -914,7 +1018,7 @@
 
 system_state_unittest_SOURCES = tests/system_state_unittest.cc \
 	server/cras_system_state.c common/cras_shm.c \
-	server/config/cras_board_config.c $(CRAS_SELINUX_UNITTEST_SOURCES)
+       	$(CRAS_SELINUX_UNITTEST_SOURCES)
 system_state_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) \
 	-I$(top_srcdir)/src/common -I$(top_srcdir)/src/server \
 	-I$(top_srcdir)/src/server/config $(SELINUX_CFLAGS)
@@ -946,6 +1050,7 @@
 	-I$(top_srcdir)/src/common \
 	-I$(top_srcdir)/src/server \
 	-I$(top_srcdir)/src/server/config \
+	-I$(SERVER_RUST_SRCDIR)/src/headers \
 	$(SELINUX_CFLAGS)
 timing_unittest_LDADD = \
 	libcrasmix.la \
diff --git a/cras/src/alsa_plugin/ctl_cras.c b/cras/src/alsa_plugin/ctl_cras.c
index 822b63a..76b0c03 100644
--- a/cras/src/alsa_plugin/ctl_cras.c
+++ b/cras/src/alsa_plugin/ctl_cras.c
@@ -14,8 +14,6 @@
 enum CTL_CRAS_MIXER_CONTROLS {
 	CTL_CRAS_MIXER_PLAYBACK_SWITCH,
 	CTL_CRAS_MIXER_PLAYBACK_VOLUME,
-	CTL_CRAS_MIXER_CAPTURE_SWITCH,
-	CTL_CRAS_MIXER_CAPTURE_VOLUME,
 	NUM_CTL_CRAS_MIXER_ELEMS
 };
 
@@ -33,10 +31,6 @@
 	  SND_CTL_EXT_ACCESS_READWRITE, 1 },
 	{ "Master Playback Volume", SND_CTL_ELEM_TYPE_INTEGER,
 	  SND_CTL_EXT_ACCESS_READWRITE, 1 },
-	{ "Capture Switch", SND_CTL_ELEM_TYPE_BOOLEAN,
-	  SND_CTL_EXT_ACCESS_READWRITE, 1 },
-	{ "Capture Volume", SND_CTL_ELEM_TYPE_INTEGER,
-	  SND_CTL_EXT_ACCESS_READWRITE, 1 },
 };
 
 /* Holds the client and ctl plugin pointers. */
@@ -119,46 +113,6 @@
 	return 0;
 }
 
-static long capture_index_to_gain(struct cras_client *client, long index)
-{
-	long min;
-	long max;
-	long dB_step;
-
-	min = cras_client_get_system_min_capture_gain(client);
-	max = cras_client_get_system_max_capture_gain(client);
-	if (min >= max)
-		return min;
-
-	dB_step = (max - min) / 100;
-
-	if (index <= 0)
-		return min;
-	if (index >= 100)
-		return max;
-	return index * dB_step + min;
-}
-
-static long capture_gain_to_index(struct cras_client *client, long gain)
-{
-	long min;
-	long max;
-	long dB_step;
-
-	min = cras_client_get_system_min_capture_gain(client);
-	max = cras_client_get_system_max_capture_gain(client);
-	if (min >= max)
-		return 0;
-
-	dB_step = (max - min) / 100;
-
-	if (gain <= min)
-		return 0;
-	if (gain >= max)
-		return 100;
-	return (gain - min) / dB_step;
-}
-
 static int get_nodes(struct cras_client *client, enum CRAS_STREAM_DIRECTION dir,
 		     struct cras_ionode_info *nodes, size_t num_nodes)
 {
@@ -199,20 +153,6 @@
 			break;
 		}
 		break;
-	case CTL_CRAS_MIXER_CAPTURE_SWITCH:
-		*value = !cras_client_get_system_capture_muted(cras->client);
-		break;
-	case CTL_CRAS_MIXER_CAPTURE_VOLUME:
-		num_nodes = get_nodes(cras->client, CRAS_STREAM_INPUT, nodes,
-				      MAX_IONODES);
-		for (i = 0; i < num_nodes; i++) {
-			if (!nodes[i].active)
-				continue;
-			*value = capture_gain_to_index(cras->client,
-						       nodes[i].capture_gain);
-			break;
-		}
-		break;
 	default:
 		return -EINVAL;
 	}
@@ -227,7 +167,6 @@
 	struct ctl_cras *cras = (struct ctl_cras *)ext_ctl->private_data;
 	struct cras_ionode_info nodes[MAX_IONODES];
 	int num_nodes, i;
-	long gain;
 
 	switch (key) {
 	case CTL_CRAS_MIXER_PLAYBACK_SWITCH:
@@ -246,23 +185,6 @@
 				*value);
 		}
 		break;
-	case CTL_CRAS_MIXER_CAPTURE_SWITCH:
-		cras_client_set_system_capture_mute(cras->client, !(*value));
-		break;
-	case CTL_CRAS_MIXER_CAPTURE_VOLUME:
-		gain = capture_index_to_gain(cras->client, *value);
-		num_nodes = get_nodes(cras->client, CRAS_STREAM_INPUT, nodes,
-				      MAX_IONODES);
-		for (i = 0; i < num_nodes; i++) {
-			if (!nodes[i].active)
-				continue;
-			cras_client_set_node_capture_gain(
-				cras->client,
-				cras_make_node_id(nodes[i].iodev_idx,
-						  nodes[i].ionode_idx),
-				gain);
-		}
-		break;
 	default:
 		return -EINVAL;
 	}
diff --git a/cras/src/alsa_plugin/pcm_cras.c b/cras/src/alsa_plugin/pcm_cras.c
index 715db2c..7bc960b 100644
--- a/cras/src/alsa_plugin/pcm_cras.c
+++ b/cras/src/alsa_plugin/pcm_cras.c
@@ -124,7 +124,7 @@
 	struct snd_pcm_cras *pcm_cras;
 	const snd_pcm_channel_area_t *areas;
 	snd_pcm_uframes_t copied_frames;
-	char dummy_byte;
+	char empty_byte;
 	size_t chan, frame_bytes, sample_bytes;
 	int rc;
 	uint8_t *samples;
@@ -196,7 +196,7 @@
 		copied_frames += frames;
 	}
 
-	rc = write(pcm_cras->fd, &dummy_byte, 1); /* Wake up polling clients. */
+	rc = write(pcm_cras->fd, &empty_byte, 1); /* Wake up polling clients. */
 	if (rc < 0 && errno != EWOULDBLOCK && errno != EAGAIN)
 		fprintf(stderr, "%s write failed %d\n", __func__, errno);
 
diff --git a/cras/src/common/bluetooth.h b/cras/src/common/bluetooth.h
index 155b5e0..66beada 100644
--- a/cras/src/common/bluetooth.h
+++ b/cras/src/common/bluetooth.h
@@ -74,3 +74,11 @@
 };
 
 #define BT_VOICE_TRANSPARENT 0x0003
+
+#define BT_SNDMTU 12
+
+#define BT_RCVMTU 13
+
+#define BT_PKT_STATUS 16
+
+#define BT_SCM_PKT_STATUS 0x03
diff --git a/cras/src/common/cras_audio_format.c b/cras/src/common/cras_audio_format.c
index f504dfc..8bd4865 100644
--- a/cras/src/common/cras_audio_format.c
+++ b/cras/src/common/cras_audio_format.c
@@ -62,7 +62,7 @@
 	 * channel count set in format.
 	 */
 	for (i = 0; i < CRAS_CH_MAX; i++)
-		if (layout[i] >= (int)format->num_channels)
+		if (layout[i] < -1 || layout[i] >= (int)format->num_channels)
 			return -EINVAL;
 
 	for (i = 0; i < CRAS_CH_MAX; i++)
@@ -71,6 +71,19 @@
 	return 0;
 }
 
+/* Verifies if all channel_layout[i] are in [-1, fmt->num_channels). */
+bool cras_audio_format_valid(const struct cras_audio_format *fmt)
+{
+	int i;
+	for (i = 0; i < CRAS_CH_MAX; i++) {
+		if (fmt->channel_layout[i] < -1 ||
+		    fmt->channel_layout[i] >= (int)fmt->num_channels) {
+			return false;
+		}
+	}
+	return true;
+}
+
 /* Destroy an audio format struct created with cras_audio_format_crate. */
 void cras_audio_format_destroy(struct cras_audio_format *fmt)
 {
diff --git a/cras/src/common/cras_audio_format.h b/cras/src/common/cras_audio_format.h
index 47bb5c5..f0cc94f 100644
--- a/cras/src/common/cras_audio_format.h
+++ b/cras/src/common/cras_audio_format.h
@@ -10,6 +10,7 @@
 extern "C" {
 #endif
 
+#include <stdbool.h>
 #include <stdint.h>
 #include <string.h>
 
@@ -105,15 +106,16 @@
 	       sizeof(src->channel_layout));
 }
 
-static inline void
-unpack_cras_audio_format(struct cras_audio_format *dest,
-			 const struct cras_audio_format_packed *src)
+static inline struct cras_audio_format
+unpack_cras_audio_format(const struct cras_audio_format_packed *src)
 {
-	dest->format = (snd_pcm_format_t)src->format;
-	dest->frame_rate = src->frame_rate;
-	dest->num_channels = src->num_channels;
-	memcpy(dest->channel_layout, src->channel_layout,
+	struct cras_audio_format dest;
+	dest.format = (snd_pcm_format_t)src->format;
+	dest.frame_rate = src->frame_rate;
+	dest.num_channels = src->num_channels;
+	memcpy(dest.channel_layout, src->channel_layout,
 	       sizeof(src->channel_layout));
+	return dest;
 }
 
 /* Returns the number of bytes per sample.
@@ -143,6 +145,9 @@
 /* Destroy an audio format struct created with cras_audio_format_crate. */
 void cras_audio_format_destroy(struct cras_audio_format *fmt);
 
+/* Returns true if the audio format is valid */
+bool cras_audio_format_valid(const struct cras_audio_format *fmt);
+
 /* Sets the channel layout for given format.
  *    format - The format structure to carry channel layout info
  *    layout - An integer array representing the position of each
diff --git a/cras/src/common/cras_config.c b/cras/src/common/cras_config.c
index 335b784..75fa24e 100644
--- a/cras/src/common/cras_config.c
+++ b/cras/src/common/cras_config.c
@@ -38,6 +38,18 @@
 	case CRAS_CAPTURE:
 		sock_file = CRAS_CAPTURE_SOCKET_FILE;
 		break;
+	case CRAS_VMS_LEGACY:
+		sock_file = CRAS_VMS_LEGACY_SOCKET_FILE;
+		break;
+	case CRAS_VMS_UNIFIED:
+		sock_file = CRAS_VMS_UNIFIED_SOCKET_FILE;
+		break;
+	case CRAS_PLUGIN_PLAYBACK:
+		sock_file = CRAS_PLUGIN_PLAYBACK_SOCKET_FILE;
+		break;
+	case CRAS_PLUGIN_UNIFIED:
+		sock_file = CRAS_PLUGIN_UNIFIED_SOCKET_FILE;
+		break;
 	default:
 		return -EINVAL;
 	}
diff --git a/cras/src/common/cras_config.h b/cras/src/common/cras_config.h
index 8b1e613..1c8e55f 100644
--- a/cras/src/common/cras_config.h
+++ b/cras/src/common/cras_config.h
@@ -9,6 +9,7 @@
 #include "cras_types.h"
 
 #define CRAS_MIN_BUFFER_TIME_IN_US 1000 /* 1 milliseconds */
+#define CRAS_MAX_BUFFER_TIME_IN_S 10 /* 10 seconds */
 
 #define CRAS_SERVER_RT_THREAD_PRIORITY 12
 #define CRAS_CLIENT_RT_THREAD_PRIORITY 10
@@ -16,6 +17,12 @@
 #define CRAS_SOCKET_FILE ".cras_socket"
 #define CRAS_PLAYBACK_SOCKET_FILE ".cras_playback"
 #define CRAS_CAPTURE_SOCKET_FILE ".cras_capture"
+/* Socket file paths for VMs. */
+#define CRAS_VMS_LEGACY_SOCKET_FILE "vms/.cras_socket"
+#define CRAS_VMS_UNIFIED_SOCKET_FILE "vms/.cras_unified"
+/* Socket file paths for pluginVM. */
+#define CRAS_PLUGIN_PLAYBACK_SOCKET_FILE "vms/plugin/playback/.cras_socket"
+#define CRAS_PLUGIN_UNIFIED_SOCKET_FILE "vms/plugin/unified/.cras_socket"
 
 /* Maximum socket_path size, which is equals to sizeof(sun_path) in sockaddr_un
  * structure.
diff --git a/cras/src/common/cras_file_wait.c b/cras/src/common/cras_file_wait.c
index 9ad9448..190a5e1 100644
--- a/cras/src/common/cras_file_wait.c
+++ b/cras/src/common/cras_file_wait.c
@@ -190,7 +190,7 @@
 	strcpy(file_wait->watch_dir, file_wait->file_path);
 	watch_dir_len = file_wait->file_path_len;
 
-	while (rc == -ENOENT) {
+	while (rc == -ENOENT || rc == -EACCES) {
 		strcpy(file_wait->watch_path, file_wait->watch_dir);
 		watch_path_len = watch_dir_len;
 
diff --git a/cras/src/common/cras_iodev_info.h b/cras/src/common/cras_iodev_info.h
index 5317dde..85d20f9 100644
--- a/cras/src/common/cras_iodev_info.h
+++ b/cras/src/common/cras_iodev_info.h
@@ -19,11 +19,13 @@
  *    idx - iodev index.
  *    name - Name displayed to the user.
  *    stable_id - ID that does not change due to device plug/unplug or reboot.
+ *    max_supported_channels - Max supported channel count of this device.
  */
 struct __attribute__((__packed__)) cras_iodev_info {
 	uint32_t idx;
 	char name[CRAS_IODEV_NAME_BUFFER_SIZE];
 	uint32_t stable_id;
+	uint32_t max_supported_channels;
 };
 
 /* Identifying information about an ionode on an iodev.
@@ -34,9 +36,9 @@
  *    active - If this is the node currently being used.
  *    volume - per-node volume (0-100)
  *    capture_gain - per-node capture gain/attenuation (in 100*dBFS)
+ *    ui_gain_scaler - Adjustable gain scaler set by Chrome.
  *    left_right_swapped - Set true if left and right channels are swapped.
  *    stable_id - ID that does not change due to device plug/unplug or reboot.
- *    mic_positions - Positions of the mic array.
  *    type - Type displayed to the user.
  *    name - Name displayed to the user.
  *    active_hotword_model - name of the currently selected hotword model.
@@ -52,10 +54,10 @@
 	} plugged_time;
 	uint32_t volume;
 	int32_t capture_gain;
+	float ui_gain_scaler;
 	int32_t left_right_swapped;
 	uint32_t type_enum;
 	uint32_t stable_id;
-	char mic_positions[CRAS_NODE_MIC_POS_BUFFER_SIZE];
 	char type[CRAS_NODE_TYPE_BUFFER_SIZE];
 	char name[CRAS_NODE_NAME_BUFFER_SIZE];
 	char active_hotword_model[CRAS_NODE_HOTWORD_MODEL_BUFFER_SIZE];
diff --git a/cras/src/common/cras_messages.h b/cras/src/common/cras_messages.h
index 195965b..50cbe7c 100644
--- a/cras/src/common/cras_messages.h
+++ b/cras/src/common/cras_messages.h
@@ -16,11 +16,11 @@
 
 /* Rev when message format changes. If new messages are added, or message ID
  * values change. */
-#define CRAS_PROTO_VER 5
+#define CRAS_PROTO_VER 7
 #define CRAS_SERV_MAX_MSG_SIZE 256
 #define CRAS_CLIENT_MAX_MSG_SIZE 256
 #define CRAS_MAX_HOTWORD_MODELS 243
-#define CRAS_MAX_REMIX_CHANNELS 32
+#define CRAS_MAX_REMIX_CHANNELS 8
 #define CRAS_MAX_TEST_DATA_LEN 224
 #define CRAS_AEC_DUMP_FILE_NAME_LEN 128
 
@@ -34,7 +34,7 @@
 	CRAS_SERVER_SET_SYSTEM_MUTE,
 	CRAS_SERVER_SET_USER_MUTE,
 	CRAS_SERVER_SET_SYSTEM_MUTE_LOCKED,
-	CRAS_SERVER_SET_SYSTEM_CAPTURE_GAIN,
+	CRAS_SERVER_SET_SYSTEM_CAPTURE_GAIN, /* Deprecated */
 	CRAS_SERVER_SET_SYSTEM_CAPTURE_MUTE,
 	CRAS_SERVER_SET_SYSTEM_CAPTURE_MUTE_LOCKED,
 	CRAS_SERVER_SET_NODE_ATTR,
@@ -58,6 +58,7 @@
 	CRAS_SERVER_DUMP_BT,
 	CRAS_SERVER_SET_BT_WBS_ENABLED,
 	CRAS_SERVER_GET_ATLOG_FD,
+	CRAS_SERVER_DUMP_MAIN,
 };
 
 enum CRAS_CLIENT_MESSAGE_ID {
@@ -113,28 +114,10 @@
 	uint32_t dev_idx; /* device to attach stream, 0 if none */
 	uint64_t effects; /* Bit map of requested effects. */
 	enum CRAS_CLIENT_TYPE client_type; /* chrome, or arc, etc. */
-	uint32_t client_shm_size; /* Size of client-provided samples shm, if any */
-};
-
-/*
- * Old version of connect message without 'cras_type' and 'client_shm_size'
- * defined.
- * Used to check against when receiving invalid size of connect message.
- * Expected to have proto_version set to 3.
- * TODO(yuhsuan): remove when all clients migrate to latest libcras.
- */
-struct __attribute__((__packed__)) cras_connect_message_old {
-	struct cras_server_message header;
-	uint32_t proto_version;
-	enum CRAS_STREAM_DIRECTION direction; /* input/output/loopback */
-	cras_stream_id_t stream_id; /* unique id for this stream */
-	enum CRAS_STREAM_TYPE stream_type; /* media, or call, etc. */
-	uint32_t buffer_frames; /* Buffer size in frames. */
-	uint32_t cb_threshold; /* callback client when this much is left */
-	uint32_t flags;
-	struct cras_audio_format_packed format; /* rate, channel, sample size */
-	uint32_t dev_idx; /* device to attach stream, 0 if none */
-	uint64_t effects; /* Bit map of requested effects. */
+	uint64_t client_shm_size; /* Size of client-provided samples shm, if any */
+	/* Initial values for shm samples buffer offsets. These will be 0 for
+	 * streams that do not use client-provided shm */
+	uint64_t buffer_offsets[2];
 };
 
 static inline void cras_fill_connect_message(
@@ -142,8 +125,7 @@
 	cras_stream_id_t stream_id, enum CRAS_STREAM_TYPE stream_type,
 	enum CRAS_CLIENT_TYPE client_type, size_t buffer_frames,
 	size_t cb_threshold, uint32_t flags, uint64_t effects,
-	struct cras_audio_format format, uint32_t dev_idx,
-	uint32_t client_shm_size)
+	struct cras_audio_format format, uint32_t dev_idx)
 {
 	m->proto_version = CRAS_PROTO_VER;
 	m->direction = direction;
@@ -156,7 +138,9 @@
 	pack_cras_audio_format(&m->format, &format);
 	m->dev_idx = dev_idx;
 	m->client_type = client_type;
-	m->client_shm_size = client_shm_size;
+	m->client_shm_size = 0;
+	m->buffer_offsets[0] = 0;
+	m->buffer_offsets[1] = 0;
 	m->header.id = CRAS_SERVER_CONNECT_STREAM;
 	m->header.length = sizeof(struct cras_connect_message);
 }
@@ -195,20 +179,6 @@
 	m->header.length = sizeof(*m);
 }
 
-/* Sets the capture gain. */
-struct __attribute__((__packed__)) cras_set_system_capture_gain {
-	struct cras_server_message header;
-	int32_t gain;
-};
-static inline void
-cras_fill_set_system_capture_gain(struct cras_set_system_capture_gain *m,
-				  long gain)
-{
-	m->gain = gain;
-	m->header.id = CRAS_SERVER_SET_SYSTEM_CAPTURE_GAIN;
-	m->header.length = sizeof(*m);
-}
-
 /* Set the system mute state. */
 struct __attribute__((__packed__)) cras_set_system_mute {
 	struct cras_server_message header;
@@ -361,6 +331,17 @@
 	m->header.length = sizeof(*m);
 }
 
+/* Dump events in CRAS main thread. */
+struct __attribute__((__packed__)) cras_dump_main {
+	struct cras_server_message header;
+};
+
+static inline void cras_fill_dump_main(struct cras_dump_main *m)
+{
+	m->header.id = CRAS_SERVER_DUMP_MAIN;
+	m->header.length = sizeof(*m);
+}
+
 /* Dump bluetooth events and state changes. */
 struct __attribute__((__packed__)) cras_dump_bt {
 	struct cras_server_message header;
@@ -434,7 +415,7 @@
 struct __attribute__((__packed__)) cras_config_global_remix {
 	struct cras_server_message header;
 	unsigned int num_channels;
-	float coefficient[CRAS_MAX_REMIX_CHANNELS];
+	float coefficient[CRAS_MAX_REMIX_CHANNELS * CRAS_MAX_REMIX_CHANNELS];
 };
 
 static inline void
@@ -559,8 +540,8 @@
  * Reply from server that a stream has been successfully added.
  * Two file descriptors are added, input shm followed by out shm.
  *
- * samples_shm_size is shm_max_size for old clients.
- * TODO(fletcherw) remove comment once all clients are on CRAS_PROTO_VER >= 3.
+ * |samples_shm_size| is valid for normal streams, not client-provided
+ * shm streams.
  */
 struct __attribute__((__packed__)) cras_client_stream_connected {
 	struct cras_client_message header;
@@ -580,6 +561,9 @@
 	m->err = err;
 	m->stream_id = stream_id;
 	pack_cras_audio_format(&m->format, format);
+	if (samples_shm_size > UINT32_MAX) {
+		samples_shm_size = UINT32_MAX;
+	}
 	m->samples_shm_size = samples_shm_size;
 	m->effects = effects;
 	m->header.id = CRAS_CLIENT_STREAM_CONNECTED;
diff --git a/cras/src/common/cras_observer_ops.h b/cras/src/common/cras_observer_ops.h
index 70dd513..e73845c 100644
--- a/cras/src/common/cras_observer_ops.h
+++ b/cras/src/common/cras_observer_ops.h
@@ -47,12 +47,19 @@
 	void (*num_active_streams_changed)(void *context,
 					   enum CRAS_STREAM_DIRECTION dir,
 					   uint32_t num_active_streams);
+	/* Number of input streams with permission changed. */
+	void (*num_input_streams_with_permission_changed)(
+		void *context,
+		uint32_t num_input_streams[CRAS_NUM_CLIENT_TYPE]);
 	/* Hotword triggered. */
 	void (*hotword_triggered)(void *context, int64_t tv_sec,
 				  int64_t tv_nsec);
 	/* State regarding whether non-empty audio is being played/captured has
 	 * changed. */
 	void (*non_empty_audio_state_changed)(void *context, int non_empty);
+	/* Bluetooth headset battery level changed. */
+	void (*bt_battery_changed)(void *context, const char *address,
+				   uint32_t level);
 };
 
 #endif /* CRAS_OBSERVER_OPS_H */
diff --git a/cras/src/common/cras_shm.c b/cras/src/common/cras_shm.c
index 3e6c2b7..ecb6169 100644
--- a/cras/src/common/cras_shm.c
+++ b/cras/src/common/cras_shm.c
@@ -116,35 +116,31 @@
 	 * The parameters are cleared, and the owner of cras_audio_shm is now
 	 * responsible for closing the fds and unlinking any associated shm
 	 * files using cras_audio_shm_destroy.
-	 *
-	 * The source pointers are updated to point to the moved structs so that
-	 * they will be properly cleaned up in the error case.
 	 */
 	ret = cras_shm_info_move(header_info, &shm->header_info);
 	if (ret)
 		goto free_shm;
-	header_info = &shm->header_info;
 
 	ret = cras_shm_info_move(samples_info, &shm->samples_info);
 	if (ret)
 		goto free_shm;
-	samples_info = &shm->samples_info;
 
-	shm->header = mmap(NULL, header_info->length, PROT_READ | PROT_WRITE,
-			   MAP_SHARED, header_info->fd, 0);
+	shm->header =
+		mmap(NULL, shm->header_info.length, PROT_READ | PROT_WRITE,
+		     MAP_SHARED, shm->header_info.fd, 0);
 	if (shm->header == (struct cras_audio_shm_header *)-1) {
-		ret = errno;
+		ret = -errno;
 		syslog(LOG_ERR, "cras_shm: mmap failed to map shm for header.");
 		goto free_shm;
 	}
 
-	shm->samples = mmap(NULL, samples_info->length, samples_prot,
-			    MAP_SHARED, samples_info->fd, 0);
+	shm->samples = mmap(NULL, shm->samples_info.length, samples_prot,
+			    MAP_SHARED, shm->samples_info.fd, 0);
 	if (shm->samples == (uint8_t *)-1) {
-		ret = errno;
+		ret = -errno;
 		syslog(LOG_ERR,
 		       "cras_shm: mmap failed to map shm for samples.");
-		goto unmap_header;
+		goto free_shm;
 	}
 
 	cras_shm_set_volume_scaler(shm, 1.0);
@@ -152,10 +148,8 @@
 	*shm_out = shm;
 	return 0;
 
-unmap_header:
-	munmap(shm->header, shm->header_info.length);
 free_shm:
-	free(shm);
+	cras_audio_shm_destroy(shm);
 cleanup_info:
 	cras_shm_info_cleanup(samples_info);
 	cras_shm_info_cleanup(header_info);
@@ -167,9 +161,12 @@
 	if (!shm)
 		return;
 
-	munmap(shm->samples, shm->samples_info.length);
+	if (shm->samples != NULL && shm->samples != (uint8_t *)-1)
+		munmap(shm->samples, shm->samples_info.length);
 	cras_shm_info_cleanup(&shm->samples_info);
-	munmap(shm->header, shm->header_info.length);
+	if (shm->header != NULL &&
+	    shm->header != (struct cras_audio_shm_header *)-1)
+		munmap(shm->header, shm->header_info.length);
 	cras_shm_info_cleanup(&shm->header_info);
 	free(shm);
 }
@@ -253,7 +250,7 @@
 		       strerror(-fd));
 		return fd;
 	}
-	rc = ftruncate(fd, size);
+	rc = posix_fallocate(fd, 0, size);
 	if (rc) {
 		rc = -errno;
 		syslog(LOG_ERR, "failed to set size of shm %s: %s\n", name,
diff --git a/cras/src/common/cras_shm.h b/cras/src/common/cras_shm.h
index f9c9392..47786c3 100644
--- a/cras/src/common/cras_shm.h
+++ b/cras/src/common/cras_shm.h
@@ -59,7 +59,7 @@
 	int32_t callback_pending;
 	uint32_t num_overruns;
 	struct cras_timespec ts;
-	uint32_t buffer_offset[CRAS_NUM_SHM_BUFFERS];
+	uint64_t buffer_offset[CRAS_NUM_SHM_BUFFERS];
 };
 
 /* Returns the number of bytes needed to hold a cras_audio_shm_header. */
@@ -146,6 +146,8 @@
  * samples_prot - the mapping protections to use when mapping samples. Allowed
  *                values are PROT_READ or PROT_WRITE.
  * shm_out - pointer where the created cras_audio_shm will be stored.
+ *
+ * Returns 0 on success or a negative error code on failure.
  */
 int cras_audio_shm_create(struct cras_shm_info *header_info,
 			  struct cras_shm_info *samples_info, int samples_prot,
@@ -562,11 +564,12 @@
 	uint32_t i;
 
 	shm->config.used_size = used_size;
-	if (shm->header)
+	if (shm->header) {
 		shm->header->config.used_size = used_size;
 
-	for (i = 0; i < CRAS_NUM_SHM_BUFFERS; i++)
-		cras_shm_set_buffer_offset(shm, i, i * used_size);
+		for (i = 0; i < CRAS_NUM_SHM_BUFFERS; i++)
+			cras_shm_set_buffer_offset(shm, i, i * used_size);
+	}
 }
 
 /* Returns the used size of the shm region in bytes. */
@@ -582,7 +585,7 @@
 }
 
 /* Returns the size of the samples shm region. */
-static inline unsigned cras_shm_samples_size(const struct cras_audio_shm *shm)
+static inline uint64_t cras_shm_samples_size(const struct cras_audio_shm *shm)
 {
 	return shm->samples_info.length;
 }
diff --git a/cras/src/common/cras_types.h b/cras/src/common/cras_types.h
index 3dd9413..544ba02 100644
--- a/cras/src/common/cras_types.h
+++ b/cras/src/common/cras_types.h
@@ -15,6 +15,7 @@
 
 #include "cras_audio_format.h"
 #include "cras_iodev_info.h"
+#include "packet_status_logger.h"
 
 /* Architecture independent timespec */
 struct __attribute__((__packed__)) cras_timespec {
@@ -48,6 +49,10 @@
 	CRAS_CONTROL, // For legacy client.
 	CRAS_PLAYBACK, // For playback client.
 	CRAS_CAPTURE, // For capture client.
+	CRAS_VMS_LEGACY, // For legacy client in vms.
+	CRAS_VMS_UNIFIED, // For unified client in vms.
+	CRAS_PLUGIN_PLAYBACK, // For playback client in vms/plugin.
+	CRAS_PLUGIN_UNIFIED, // For unified client in vms/plugin.
 	CRAS_NUM_CONN_TYPE,
 };
 
@@ -162,8 +167,17 @@
 	CRAS_CLIENT_TYPE_ARC, /* ARC++ */
 	CRAS_CLIENT_TYPE_CROSVM, /* CROSVM */
 	CRAS_CLIENT_TYPE_SERVER_STREAM, /* Server stream */
+	CRAS_CLIENT_TYPE_LACROS, /* LaCrOS */
+	CRAS_CLIENT_TYPE_PLUGIN, /* PluginVM */
+	CRAS_CLIENT_TYPE_ARCVM, /* ARCVM */
+	CRAS_NUM_CLIENT_TYPE, /* numbers of CRAS_CLIENT_TYPE */
 };
 
+static inline bool cras_validate_client_type(enum CRAS_CLIENT_TYPE client_type)
+{
+	return 0 <= client_type && client_type < CRAS_NUM_CLIENT_TYPE;
+}
+
 #define ENUM_STR(x)                                                            \
 	case x:                                                                \
 		return #x;
@@ -198,6 +212,9 @@
 	ENUM_STR(CRAS_CLIENT_TYPE_ARC)
 	ENUM_STR(CRAS_CLIENT_TYPE_CROSVM)
 	ENUM_STR(CRAS_CLIENT_TYPE_SERVER_STREAM)
+	ENUM_STR(CRAS_CLIENT_TYPE_LACROS)
+	ENUM_STR(CRAS_CLIENT_TYPE_PLUGIN)
+	ENUM_STR(CRAS_CLIENT_TYPE_ARCVM)
 	default:
 		return "INVALID_CLIENT_TYPE";
 	}
@@ -250,6 +267,7 @@
 #define MAX_DEBUG_STREAMS 8
 #define AUDIO_THREAD_EVENT_LOG_SIZE (1024 * 6)
 #define CRAS_BT_EVENT_LOG_SIZE 1024
+#define MAIN_THREAD_EVENT_LOG_SIZE 1024
 
 /* There are 8 bits of space for events. */
 enum AUDIO_THREAD_LOG_EVENTS {
@@ -270,7 +288,8 @@
 	AUDIO_THREAD_FETCH_STREAM,
 	AUDIO_THREAD_STREAM_ADDED,
 	AUDIO_THREAD_STREAM_REMOVED,
-	AUDIO_THREAD_A2DP_ENCODE,
+	AUDIO_THREAD_A2DP_FLUSH,
+	AUDIO_THREAD_A2DP_THROTTLE_TIME,
 	AUDIO_THREAD_A2DP_WRITE,
 	AUDIO_THREAD_DEV_STREAM_MIX,
 	AUDIO_THREAD_CAPTURE_POST,
@@ -296,6 +315,48 @@
 	AUDIO_THREAD_SEVERE_UNDERRUN,
 	AUDIO_THREAD_CAPTURE_DROP_TIME,
 	AUDIO_THREAD_DEV_DROP_FRAMES,
+	AUDIO_THREAD_LOOPBACK_PUT,
+	AUDIO_THREAD_LOOPBACK_GET,
+	AUDIO_THREAD_LOOPBACK_SAMPLE_HOOK,
+	AUDIO_THREAD_DEV_OVERRUN,
+};
+
+/* Important events in main thread.
+ * MAIN_THREAD_DEV_CLOSE - When an iodev closes at stream removal.
+ * MAIN_THREAD_DEV_DISABLE - When an iodev is removed from active dev list.
+ * MAIN_THREAD_DEV_INIT - When an iodev opens when stream attachs.
+ * MAIN_THREAD_DEV_REOPEN - When an iodev reopens for format change.
+ * MAIN_THREAD_ADD_ACTIVE_NODE - When an iodev is set as an additional
+ *    active device.
+ * MAIN_THREAD_SELECT_NODE - When UI selects an iodev as active.
+ * MAIN_THREAD_NODE_PLUGGED - When a jack of iodev is plugged/unplugged.
+ * MAIN_THREAD_ADD_TO_DEV_LIST - When iodev is added to list.
+ * MAIN_THREAD_INPUT_NODE_GAIN - When input node gain changes.
+ * MAIN_THREAD_OUTPUT_NODE_VOLUME - When output node volume changes.
+ * MAIN_THREAD_SET_OUTPUT_USER_MUTE - When output mute state is set.
+ * MAIN_THREAD_RESUME_DEVS - When system resumes and notifies CRAS.
+ * MAIN_THREAD_SUSPEND_DEVS - When system suspends and notifies CRAS.
+ * MAIN_THREAD_STREAM_ADDED - When an audio stream is added.
+ * MAIN_THREAD_STREAM_REMOVED - When an audio stream is removed.
+ */
+enum MAIN_THREAD_LOG_EVENTS {
+	/* iodev related */
+	MAIN_THREAD_DEV_CLOSE,
+	MAIN_THREAD_DEV_DISABLE,
+	MAIN_THREAD_DEV_INIT,
+	MAIN_THREAD_DEV_REOPEN,
+	MAIN_THREAD_ADD_ACTIVE_NODE,
+	MAIN_THREAD_SELECT_NODE,
+	MAIN_THREAD_NODE_PLUGGED,
+	MAIN_THREAD_ADD_TO_DEV_LIST,
+	MAIN_THREAD_INPUT_NODE_GAIN,
+	MAIN_THREAD_OUTPUT_NODE_VOLUME,
+	MAIN_THREAD_SET_OUTPUT_USER_MUTE,
+	MAIN_THREAD_RESUME_DEVS,
+	MAIN_THREAD_SUSPEND_DEVS,
+	/* stream related */
+	MAIN_THREAD_STREAM_ADDED,
+	MAIN_THREAD_STREAM_REMOVED,
 };
 
 /* There are 8 bits of space for events. */
@@ -309,12 +370,16 @@
 	BT_A2DP_START,
 	BT_A2DP_SUSPENDED,
 	BT_CODEC_SELECTION,
-	BT_DEV_CONNECTED_CHANGE,
+	BT_DEV_CONNECTED,
+	BT_DEV_DISCONNECTED,
 	BT_DEV_CONN_WATCH_CB,
 	BT_DEV_SUSPEND_CB,
 	BT_HFP_NEW_CONNECTION,
 	BT_HFP_REQUEST_DISCONNECT,
 	BT_HFP_SUPPORTED_FEATURES,
+	BT_HFP_HF_INDICATOR,
+	BT_HFP_SET_SPEAKER_GAIN,
+	BT_HFP_UPDATE_SPEAKER_GAIN,
 	BT_HSP_NEW_CONNECTION,
 	BT_HSP_REQUEST_DISCONNECT,
 	BT_NEW_AUDIO_PROFILE_AFTER_CONNECT,
@@ -322,6 +387,8 @@
 	BT_SCO_CONNECT,
 	BT_TRANSPORT_ACQUIRE,
 	BT_TRANSPORT_RELEASE,
+	BT_TRANSPORT_SET_VOLUME,
+	BT_TRANSPORT_UPDATE_VOLUME,
 };
 
 struct __attribute__((__packed__)) audio_thread_event {
@@ -393,6 +460,24 @@
 	struct audio_thread_event_log log;
 };
 
+struct __attribute__((__packed__)) main_thread_event {
+	uint32_t tag_sec;
+	uint32_t nsec;
+	uint32_t data1;
+	uint32_t data2;
+	uint32_t data3;
+};
+
+struct __attribute__((__packed__)) main_thread_event_log {
+	uint32_t write_pos;
+	uint32_t len;
+	struct main_thread_event log[MAIN_THREAD_EVENT_LOG_SIZE];
+};
+
+struct __attribute__((__packed__)) main_thread_debug_info {
+	struct main_thread_event_log main_log;
+};
+
 struct __attribute__((__packed__)) cras_bt_event {
 	uint32_t tag_sec;
 	uint32_t nsec;
@@ -408,6 +493,7 @@
 
 struct __attribute__((__packed__)) cras_bt_debug_info {
 	struct cras_bt_event_log bt_log;
+	struct packet_status_logger wbs_logger;
 };
 
 /*
@@ -415,11 +501,14 @@
  * or they will be ignored by the handler.
  */
 enum CRAS_AUDIO_THREAD_EVENT_TYPE {
+	AUDIO_THREAD_EVENT_A2DP_OVERRUN,
+	AUDIO_THREAD_EVENT_A2DP_THROTTLE,
 	AUDIO_THREAD_EVENT_BUSYLOOP,
 	AUDIO_THREAD_EVENT_DEBUG,
 	AUDIO_THREAD_EVENT_SEVERE_UNDERRUN,
 	AUDIO_THREAD_EVENT_UNDERRUN,
 	AUDIO_THREAD_EVENT_DROP_SAMPLES,
+	AUDIO_THREAD_EVENT_DEV_OVERRUN,
 	AUDIO_THREAD_EVENT_TYPE_COUNT,
 };
 
@@ -451,16 +540,8 @@
  *    mute_locked - 0 = unlocked, 1 = locked.
  *    suspended - 1 = suspended, 0 = resumed.
  *    capture_gain - Capture gain in dBFS * 100.
- *    capture_gain_target - Target capture gain in dBFS * 100. The actual
- *                          capture gain will be subjected to current
- *                          supported range. When active device/node changes,
- *                          supported range changes accordingly. System state
- *                          should try to re-apply target gain subjected to new
- *                          range.
  *    capture_mute - 0 = unmuted, 1 = muted.
  *    capture_mute_locked - 0 = unlocked, 1 = locked.
- *    min_capture_gain - Min allowed capture gain in dBFS * 100.
- *    max_capture_gain - Max allowed capture gain in dBFS * 100.
  *    num_streams_attached - Total number of streams since server started.
  *    num_output_devs - Number of available output devices.
  *    num_input_devs - Number of available input devices.
@@ -490,6 +571,17 @@
  *    snapshot_buffer - ring buffer for storing audio thread snapshots.
  *    bt_debug_info - ring buffer for storing bluetooth event logs.
  *    bt_wbs_enabled - Whether or not bluetooth wideband speech is enabled.
+ *    deprioritize_bt_wbs_mic - Whether Bluetooth wideband speech mic
+ *        should be deprioritized for selecting as default audio input.
+ *    main_thread_debug_info - ring buffer for storing main thread event logs.
+ *    num_input_streams_with_permission - An array containing numbers of input
+ *        streams with permission in each client type.
+ *    noise_cancellation_enabled - Whether or not Noise Cancellation is enabled.
+ *    hotword_pause_at_suspend - 1 = Pause hotword detection when the system
+ *        suspends. Hotword detection is resumed after system resumes.
+ *        0 - Hotword detection is allowed to continue running after system
+ *        suspends, so a detected hotword can wake up the device.
+ *
  */
 #define CRAS_SERVER_STATE_VERSION 2
 struct __attribute__((packed, aligned(4))) cras_server_state {
@@ -502,11 +594,8 @@
 	int32_t mute_locked;
 	int32_t suspended;
 	int32_t capture_gain;
-	int32_t capture_gain_target;
 	int32_t capture_mute;
 	int32_t capture_mute_locked;
-	int32_t min_capture_gain;
-	int32_t max_capture_gain;
 	uint32_t num_streams_attached;
 	uint32_t num_output_devs;
 	uint32_t num_input_devs;
@@ -529,6 +618,11 @@
 	struct cras_audio_thread_snapshot_buffer snapshot_buffer;
 	struct cras_bt_debug_info bt_debug_info;
 	int32_t bt_wbs_enabled;
+	int32_t deprioritize_bt_wbs_mic;
+	struct main_thread_debug_info main_thread_debug_info;
+	uint32_t num_input_streams_with_permission[CRAS_NUM_CLIENT_TYPE];
+	int32_t noise_cancellation_enabled;
+	int32_t hotword_pause_at_suspend;
 };
 
 /* Actions for card add/remove/change. */
@@ -595,12 +689,16 @@
 	CRAS_NODE_TYPE_HOTWORD,
 	CRAS_NODE_TYPE_POST_MIX_PRE_DSP,
 	CRAS_NODE_TYPE_POST_DSP,
+	/* Type for the legacy BT narrow band mic .*/
+	CRAS_NODE_TYPE_BLUETOOTH_NB_MIC,
 	/* These value can be used for both output and input nodes. */
 	CRAS_NODE_TYPE_USB,
 	CRAS_NODE_TYPE_BLUETOOTH,
 	CRAS_NODE_TYPE_FALLBACK_NORMAL,
 	CRAS_NODE_TYPE_FALLBACK_ABNORMAL,
 	CRAS_NODE_TYPE_UNKNOWN,
+	CRAS_NODE_TYPE_ECHO_REFERENCE,
+	CRAS_NODE_TYPE_ALSA_LOOPBACK,
 };
 
 /* Position values to described where a node locates on the system.
diff --git a/cras/src/common/cras_util.h b/cras/src/common/cras_util.h
index 0103424..96985ab 100644
--- a/cras/src/common/cras_util.h
+++ b/cras/src/common/cras_util.h
@@ -187,6 +187,33 @@
 	return cras_time_to_frames(&time_since, rate);
 }
 
+/* Calculates frames until time end. */
+static inline uint64_t cras_frames_until_time(const struct timespec *end,
+					      unsigned int rate)
+{
+	struct timespec now, time_until;
+
+	clock_gettime(CLOCK_MONOTONIC_RAW, &now);
+	if (!timespec_after(end, &now))
+		return 0;
+
+	subtract_timespecs(end, &now, &time_until);
+	return cras_time_to_frames(&time_until, rate);
+}
+
+/* Returns true if the difference between a and b is  shorter than t. */
+static inline bool timespec_diff_shorter_than(const struct timespec *a,
+					      const struct timespec *b,
+					      const struct timespec *t)
+{
+	struct timespec diff;
+	if (timespec_after(a, b))
+		subtract_timespecs(a, b, &diff);
+	else
+		subtract_timespecs(b, a, &diff);
+	return timespec_after(t, &diff);
+}
+
 /* Poll on the given file descriptors.
  *
  * See ppoll(). This implementation changes the value of timeout to the
diff --git a/cras/src/common/dumper.c b/cras/src/common/dumper.c
index 50789e8..5da16df 100644
--- a/cras/src/common/dumper.c
+++ b/cras/src/common/dumper.c
@@ -108,18 +108,23 @@
 	struct dumper *dumper = calloc(1, sizeof(struct dumper));
 	struct mem_data *data = calloc(1, sizeof(struct mem_data));
 	if (!dumper || !data)
-		return NULL;
+		goto error;
 	data->size = 0;
 	data->capacity = 80;
 	data->buf = malloc(data->capacity);
-	if (!data->buf) {
-		free(data);
-		return NULL;
-	}
+	if (!data->buf)
+		goto error;
 	data->buf[0] = '\0';
 	dumper->data = data;
 	dumper->vprintf = &mem_vprintf;
 	return dumper;
+
+error:
+	if (dumper)
+		free(dumper);
+	if (data)
+		free(data);
+	return NULL;
 }
 
 void mem_dumper_free(struct dumper *dumper)
diff --git a/cras/src/common/packet_status_logger.c b/cras/src/common/packet_status_logger.c
new file mode 100644
index 0000000..f1be696
--- /dev/null
+++ b/cras/src/common/packet_status_logger.c
@@ -0,0 +1,35 @@
+/* Copyright 2020 The Chromium OS 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 <string.h>
+#include <time.h>
+
+#include "cras_util.h"
+#include "packet_status_logger.h"
+
+void packet_status_logger_init(struct packet_status_logger *logger)
+{
+	memset(logger->data, 0, PACKET_STATUS_LEN_BYTES);
+	logger->size = PACKET_STATUS_LEN_BYTES * 8;
+	logger->wp = 0;
+	logger->num_wraps = 0;
+	clock_gettime(CLOCK_MONOTONIC_RAW, &logger->ts);
+}
+
+void packet_status_logger_update(struct packet_status_logger *logger, bool val)
+{
+	if (val) {
+		logger->data[logger->wp / 8] |= 1UL << (logger->wp % 8);
+	} else {
+		logger->data[logger->wp / 8] &= ~(1UL << (logger->wp % 8));
+	}
+	logger->wp++;
+	if (logger->wp >= logger->size) {
+		logger->wp %= logger->size;
+		logger->num_wraps += 1;
+	}
+	if (logger->wp == 0 || (logger->num_wraps == 0 && logger->wp == 1))
+		clock_gettime(CLOCK_MONOTONIC_RAW, &logger->ts);
+}
diff --git a/cras/src/common/packet_status_logger.h b/cras/src/common/packet_status_logger.h
new file mode 100644
index 0000000..3bc9004
--- /dev/null
+++ b/cras/src/common/packet_status_logger.h
@@ -0,0 +1,127 @@
+/* Copyright 2020 The Chromium OS 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 PACKET_STATUS_LOGGER_
+#define PACKET_STATUS_LOGGER_
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#define PACKET_STATUS_LEN_BYTES 64
+#define WBS_FRAME_NS 7500000
+
+/* Avoid 32, 40, 64 consecutive hex characters so CrOS feedback redact
+ * tool doesn't trim our dump. */
+#define PACKET_STATUS_LOG_LINE_WRAP 50
+
+/*
+ * Object to log consecutive packets' status.
+ * Members:
+ *    data - Bytes to store packets' status.
+ *    size - Total number of bits in |data|.
+ *    wp - Position of the next bit to log packet status.
+ *    num_wraps - Number of times the ring buffer has wrapped.
+ *    ts - The timestamp of the last time when the first bit of |data| updated.
+ */
+struct packet_status_logger {
+	uint8_t data[PACKET_STATUS_LEN_BYTES];
+	int size;
+	int wp;
+	int num_wraps;
+	struct timespec ts;
+};
+
+/* Initializes the packet status logger. */
+void packet_status_logger_init(struct packet_status_logger *logger);
+
+/* Updates the next packet status to logger. */
+void packet_status_logger_update(struct packet_status_logger *logger, bool val);
+
+/* Rewinds logger's time stamp to calculate the beginning.
+ * If logger's ring buffer hasn't wrapped, simply return logger_ts.
+ * Otherwise beginning_ts = logger_ts - WBS_FRAME_NS * (size - wp)
+ */
+static inline void
+packet_status_logger_begin_ts(const struct packet_status_logger *logger,
+			      struct timespec *ts)
+{
+	long nsec = WBS_FRAME_NS * (logger->size - logger->wp);
+
+	*ts = logger->ts;
+	if (logger->num_wraps == 0)
+		return;
+	while (nsec > 1000000000L) {
+		ts->tv_sec--;
+		nsec -= 1000000000L;
+	}
+	ts->tv_nsec -= nsec;
+	if (ts->tv_nsec < 0) {
+		ts->tv_sec--;
+		ts->tv_nsec += 1000000000L;
+	}
+}
+
+/* Fast-forwards the logger's time stamp to calculate the end.
+ * In other words, end_ts = logger_ts + WBS_FRAME_NS * wp
+ */
+static inline void
+packet_status_logger_end_ts(const struct packet_status_logger *logger,
+			    struct timespec *ts)
+{
+	*ts = logger->ts;
+	ts->tv_nsec += WBS_FRAME_NS * logger->wp;
+	while (ts->tv_nsec > 1000000000L) {
+		ts->tv_sec++;
+		ts->tv_nsec -= 1000000000L;
+	}
+}
+
+/* Prints the logger data in hex format */
+static inline void
+packet_status_logger_dump_hex(const struct packet_status_logger *logger)
+{
+	int i = logger->wp / 8;
+
+	/* Print the bits after wp only if buffer has wrapped. */
+	if (logger->num_wraps) {
+		if (logger->wp % 8)
+			printf("%.2x",
+			       logger->data[i] & (0xff << (logger->wp % 8)));
+		for (; i < PACKET_STATUS_LEN_BYTES; i++)
+			printf("%.2x", logger->data[i]);
+	}
+	for (i = 0; i < logger->wp / 8; i++)
+		printf("%.2x", logger->data[i]);
+	if (logger->wp % 8)
+		printf("%.2x", logger->data[i] & (~(0xff << (logger->wp % 8))));
+	printf("\n");
+}
+
+/* Prints the logger data in binary format */
+static inline void
+packet_status_logger_dump_binary(const struct packet_status_logger *logger)
+{
+	/* Don't print the bits after wp if buffer hasn't wrapped. */
+	int head = logger->num_wraps ? logger->wp : 0;
+	int len = logger->num_wraps ? logger->size : logger->wp;
+	int i, j;
+
+	for (i = 0; i < len; ++i) {
+		j = (head + i) % logger->size;
+		printf("%d", (logger->data[j / 8] >> (j % 8)) & 1U);
+		if ((i + 1) % PACKET_STATUS_LOG_LINE_WRAP == 0)
+			printf("\n");
+	}
+	/* Fill indicator digit 'D' until the last line wraps. */
+	if (len % PACKET_STATUS_LOG_LINE_WRAP) {
+		while (len % PACKET_STATUS_LOG_LINE_WRAP) {
+			printf("D");
+			++len;
+		}
+		printf("\n");
+	}
+}
+
+#endif /* PACKET_STATUS_LOGGER_ */
diff --git a/cras/src/common/utlist.h b/cras/src/common/utlist.h
index e3ee630..6c7f1e3 100644
--- a/cras/src/common/utlist.h
+++ b/cras/src/common/utlist.h
@@ -194,6 +194,7 @@
 
 #define DL_DELETE(head, del)                                                   \
 	do {                                                                   \
+		assert((head) != NULL);                                        \
 		assert((del)->prev != NULL);                                   \
 		if ((del)->prev == (del)) {                                    \
 			(head) = NULL;                                         \
diff --git a/cras/src/dsp/drc.c b/cras/src/dsp/drc.c
index 1b2639a..e609841 100644
--- a/cras/src/dsp/drc.c
+++ b/cras/src/dsp/drc.c
@@ -104,7 +104,7 @@
 		param[PARAM_RELEASE_ZONE3] = 0.42f;
 		param[PARAM_RELEASE_ZONE4] = 0.98f;
 
-		/* This is effectively a master volume on the compressed
+		/* This is effectively a main volume on the compressed
 		 * signal */
 		param[PARAM_POST_GAIN] = 0; /* dB */
 		param[PARAM_ENABLED] = 0;
diff --git a/cras/src/dsp/drc_kernel.c b/cras/src/dsp/drc_kernel.c
index c0eb100..8c3404f 100644
--- a/cras/src/dsp/drc_kernel.c
+++ b/cras/src/dsp/drc_kernel.c
@@ -257,7 +257,7 @@
 	/* Empirical/perceptual tuning. */
 	full_range_makeup_gain = powf(full_range_makeup_gain, 0.6f);
 
-	dk->master_linear_gain =
+	dk->main_linear_gain =
 		decibels_to_linear(db_post_gain) * full_range_makeup_gain;
 
 	/* Attack parameters. */
@@ -566,7 +566,7 @@
 #include <arm_neon.h>
 static void dk_compress_output(struct drc_kernel *dk)
 {
-	const float master_linear_gain = dk->master_linear_gain;
+	const float main_linear_gain = dk->main_linear_gain;
 	const float envelope_rate = dk->envelope_rate;
 	const float scaled_desired_gain = dk->scaled_desired_gain;
 	const float compressor_gain = dk->compressor_gain;
@@ -638,7 +638,7 @@
 			  [A7]"w"(A7),
 			  [base]"w"(vdupq_n_f32(scaled_desired_gain)),
 			  [r4]"w"(vdupq_n_f32(r*r*r*r)),
-			  [g]"w"(vdupq_n_f32(master_linear_gain))
+			  [g]"w"(vdupq_n_f32(main_linear_gain))
 			: /* clobber */
 			  "memory", "cc");
 		// clang-format on
@@ -698,7 +698,7 @@
 			  [A7]"w"(A7),
 			  [one]"w"(vdupq_n_f32(1)),
 			  [r4]"w"(vdupq_n_f32(r*r*r*r)),
-			  [g]"w"(vdupq_n_f32(master_linear_gain))
+			  [g]"w"(vdupq_n_f32(main_linear_gain))
 			: /* clobber */
 			  "memory", "cc");
 		// clang-format on
@@ -709,7 +709,7 @@
 #include <emmintrin.h>
 static void dk_compress_output(struct drc_kernel *dk)
 {
-	const float master_linear_gain = dk->master_linear_gain;
+	const float main_linear_gain = dk->main_linear_gain;
 	const float envelope_rate = dk->envelope_rate;
 	const float scaled_desired_gain = dk->scaled_desired_gain;
 	const float compressor_gain = dk->compressor_gain;
@@ -789,7 +789,7 @@
 			  [A7]"x"(A7),
 			  [base]"x"(_mm_set1_ps(scaled_desired_gain)),
 			  [r4]"x"(_mm_set1_ps(r*r*r*r)),
-			  [g]"x"(_mm_set1_ps(master_linear_gain))
+			  [g]"x"(_mm_set1_ps(main_linear_gain))
 			: /* clobber */
 			  "memory", "cc");
 		// clang-format on
@@ -862,7 +862,7 @@
 			  [A7]"x"(A7),
 			  [one]"x"(_mm_set1_ps(1)),
 			  [r4]"x"(_mm_set1_ps(r*r*r*r)),
-			  [g]"x"(_mm_set1_ps(master_linear_gain))
+			  [g]"x"(_mm_set1_ps(main_linear_gain))
 			: /* clobber */
 			  "memory", "cc");
 		// clang-format on
@@ -872,7 +872,7 @@
 #else
 static void dk_compress_output(struct drc_kernel *dk)
 {
-	const float master_linear_gain = dk->master_linear_gain;
+	const float main_linear_gain = dk->main_linear_gain;
 	const float envelope_rate = dk->envelope_rate;
 	const float scaled_desired_gain = dk->scaled_desired_gain;
 	const float compressor_gain = dk->compressor_gain;
@@ -902,8 +902,8 @@
 				float post_warp_compressor_gain =
 					warp_sinf(x[j] + base);
 
-				/* Calculate total gain using master gain. */
-				float total_gain = master_linear_gain *
+				/* Calculate total gain using main gain. */
+				float total_gain = main_linear_gain *
 						   post_warp_compressor_gain;
 
 				/* Apply final gain. */
@@ -936,8 +936,8 @@
 				float post_warp_compressor_gain =
 					warp_sinf(x[j]);
 
-				/* Calculate total gain using master gain. */
-				float total_gain = master_linear_gain *
+				/* Calculate total gain using main gain. */
+				float total_gain = main_linear_gain *
 						   post_warp_compressor_gain;
 
 				/* Apply final gain. */
diff --git a/cras/src/dsp/drc_kernel.h b/cras/src/dsp/drc_kernel.h
index 1157f22..2ed9956 100644
--- a/cras/src/dsp/drc_kernel.h
+++ b/cras/src/dsp/drc_kernel.h
@@ -67,7 +67,7 @@
 	float kA, kB, kC, kD, kE;
 
 	/* Calculated parameters */
-	float master_linear_gain;
+	float main_linear_gain;
 	float attack_frames;
 	float sat_release_frames_inv_neg;
 	float sat_release_rate_at_neg_two_db;
diff --git a/cras/src/fuzz/Dockerfile b/cras/src/fuzz/Dockerfile
index cf08a6e..caffa99 100644
--- a/cras/src/fuzz/Dockerfile
+++ b/cras/src/fuzz/Dockerfile
@@ -1,57 +1,13 @@
-# Copyright 2017 The Chromium Authors. All rights reserved.
+# Copyright 2017 The Chromium OS Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 #
 # Defines a docker image that can build cras fuzzers.
 #
 FROM gcr.io/oss-fuzz-base/base-builder
+LABEL maintainer="dgreid@chromium.org"
 
-RUN apt-get -y update && \
-      apt-get install -y \
-      automake \
-      build-essential \
-      cmake \
-      ctags \
-      g++ \
-      gdb \
-      git \
-      ladspa-sdk \
-      libasound-dev \
-      libdbus-1-dev \
-      libgtest-dev \
-      libncurses5-dev \
-      libsbc-dev \
-      libsndfile-dev \
-      libspeexdsp-dev \
-      libtool \
-      libudev-dev \
-      wget
-RUN apt-get clean
-RUN cd /tmp && git clone https://github.com/ndevilla/iniparser.git && \
-      cd iniparser && \
-      make && \
-      cp libiniparser.* /usr/local/lib && \
-      cp src/dictionary.h src/iniparser.h /usr/local/include && \
-      chmod 644 /usr/local/include/dictionary.h /usr/local/include/iniparser.h && \
-      chmod 644 /usr/local/lib/libiniparser.a && \
-      chmod 755 /usr/local/lib/libiniparser.so.*
-RUN cd /usr/src/gtest && \
-      cmake . && \
-      make && \
-      chmod 644 *.a && \
-      cp *.a /usr/local/lib
-
-# Need to build and install alsa so there is a static lib.
-RUN mkdir -p /tmp/alsa-build && cd /tmp/alsa-build && \
-      wget ftp://ftp.alsa-project.org/pub/lib/alsa-lib-1.1.4.1.tar.bz2 && \
-      bzip2 -f -d alsa-lib-* && \
-      tar xf alsa-lib-* && \
-      cd alsa-lib-* && \
-      ./configure --enable-static --disable-shared && \
-      make clean && \
-      make -j$(nproc) all && \
-      make install
-
-
-COPY . /src/cras/
-COPY src/fuzz/build.sh /src/
+COPY . "${SRC}/adhd"
+COPY cras/src/fuzz/build.sh "${SRC}/build.sh"
+RUN "${SRC}/adhd/cras/install_deps.sh"
+RUN mkdir -p /etc/cras && cp "${SRC}/adhd/cras-config/dsp.ini.sample" /etc/cras
diff --git a/cras/src/fuzz/README.md b/cras/src/fuzz/README.md
index 7271602..0d235ce 100644
--- a/cras/src/fuzz/README.md
+++ b/cras/src/fuzz/README.md
@@ -11,22 +11,26 @@
 ```
 sudo adduser $USER docker
 ```
+### Sync to the latest base-builder
+```
+docker pull gcr.io/oss-fuzz-base/base-builder
+```
 
-### Build a container from the cras directory
+### Build a container from the adhd directory
 ```
-docker build -t ossfuzz/cras -f src/fuzz/Dockerfile .
+docker build -t ossfuzz/cras -f cras/src/fuzz/Dockerfile .
 ```
+Add `--no-cache` if you want a complete rebuild.
 
 ### Build fuzzers
 ```
-docker run --cap-add=SYS_PTRACE -ti --rm -v $(pwd):/src/cras -v /tmp/fuzzers:/out \
-    ossfuzz/cras
+docker run --cap-add=SYS_PTRACE -ti --rm -v /tmp/fuzzers:/out ossfuzz/cras
 ```
 
 ### Look in /tmp/fuzzers to see the executables. Run them like so:
 ```
-docker run --cap-add=SYS_PTRACE -ti -v $(pwd)/src/fuzz/corpus:/corpus \
-    -v /tmp/fuzzers:/out ossfuzz/base-runner /out/rclient_message \
+docker run --cap-add=SYS_PTRACE -ti -v $(pwd)/cras/src/fuzz/corpus:/corpus \
+    -v /tmp/fuzzers:/out ossfuzz/cras /out/rclient_message \
     /corpus -runs=100
 ```
 
@@ -34,7 +38,7 @@
 
 Go into docker console by
 ```
-docker run --cap-add=SYS_PTRACE -ti -v $(pwd)/src/fuzz/corpus:/corpus \
-    -v /tmp/fuzzers:/out ossfuzz/base-runner /bin/bash
+docker run --cap-add=SYS_PTRACE -ti -v $(pwd)/cras/src/fuzz/corpus:/corpus \
+    -v /tmp/fuzzers:/out ossfuzz/cras /bin/bash
 ```
 and start debugging.
diff --git a/cras/src/fuzz/build.sh b/cras/src/fuzz/build.sh
index ac4ad2f..44413a7 100755
--- a/cras/src/fuzz/build.sh
+++ b/cras/src/fuzz/build.sh
@@ -1,22 +1,24 @@
 #!/bin/bash -eux
-# Copyright 2017 The Chromium Authors. All rights reserved.
+
+# Copyright 2017 The Chromium OS Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 #
-# Builds fuzzers from within a container into /out/ directory.
-# Expects /src/cras to contain a cras checkout.
+# Builds fuzzers from within a container into ${OUT} directory.
+# Expects "${SRC}/adhd" to contain an adhd checkout.
 
-mkdir $WORK/cras
-cd $SRC/cras
+cd "${SRC}/adhd/cras"
 ./git_prepare.sh
-./configure --disable-dbus --disable-webrtc-apm
+
+FUZZER_LDFLAGS="${FUZZER_LDFLAGS} ${LIB_FUZZING_ENGINE}"
+./configure --enable-fuzzer
+
+# Compile fuzzers
 make -j$(nproc)
 
-$CXX $CXXFLAGS $FUZZER_LDFLAGS \
-  $SRC/cras/src/fuzz/rclient_message.cc -o $OUT/rclient_message \
-  -I $SRC/cras/src/server \
-  -I $SRC/cras/src/common \
-  $SRC/cras/src/.libs/libcrasserver.a \
-  -lpthread -lrt -ludev -ldl -lm \
-  -lFuzzingEngine \
-  -Wl,-Bstatic -liniparser -lasound -lspeexdsp -Wl,-Bdynamic
+# Copy fuzzers and dependencies to "${OUT}" directory
+cp "${SRC}/adhd/cras/src/cras_rclient_message_fuzzer" "${OUT}/rclient_message"
+zip -j "${OUT}/rclient_message_corpus.zip" ./src/fuzz/corpus/*
+
+cp "${SRC}/adhd/cras/src/cras_hfp_slc_fuzzer" "${OUT}/cras_hfp_slc"
+cp "${SRC}/adhd/cras/src/fuzz/cras_hfp_slc.dict" "${OUT}/cras_hfp_slc.dict"
diff --git a/cras/src/fuzz/cras_hfp_slc.cc b/cras/src/fuzz/cras_hfp_slc.cc
new file mode 100644
index 0000000..4a76ea4
--- /dev/null
+++ b/cras/src/fuzz/cras_hfp_slc.cc
@@ -0,0 +1,66 @@
+/* Copyright 2020 The Chromium OS 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 <assert.h>
+#include <fuzzer/FuzzedDataProvider.h>
+#include <stddef.h>
+#include <stdint.h>
+
+extern "C" {
+#include "cras_bt_device.h"
+#include "cras_bt_log.h"
+#include "cras_hfp_slc.h"
+#include "cras_iodev_list.h"
+#include "cras_mix.h"
+#include "cras_observer.h"
+#include "cras_shm.h"
+#include "cras_system_state.h"
+
+struct cras_bt_event_log* btlog;
+}
+
+int disconnect_cb(struct hfp_slc_handle*) {
+  return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  FuzzedDataProvider data_provider(data, size);
+  bool is_hsp = data_provider.ConsumeIntegralInRange(0, 1);
+  int ag_supported_features = data_provider.ConsumeIntegral<int>();
+  std::string command = data_provider.ConsumeRemainingBytesAsString();
+  int fd = open("/dev/null", O_RDWR);
+
+  struct cras_bt_device* bt_dev = cras_bt_device_create(NULL, "");
+  struct hfp_slc_handle* handle = hfp_slc_create(
+      fd, is_hsp, ag_supported_features, bt_dev, NULL, &disconnect_cb);
+  if (!handle)
+    return 0;
+
+  handle_at_command_for_test(handle, command.c_str());
+
+  hfp_slc_destroy(handle);
+  cras_bt_device_remove(bt_dev);
+  return 0;
+}
+
+extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) {
+  char* shm_name;
+  if (asprintf(&shm_name, "/cras-%d", getpid()) < 0)
+    exit(-ENOMEM);
+  struct cras_server_state* exp_state =
+      (struct cras_server_state*)calloc(1, sizeof(*exp_state));
+  if (!exp_state)
+    exit(-1);
+  int rw_shm_fd = open("/dev/null", O_RDWR);
+  int ro_shm_fd = open("/dev/null", O_RDONLY);
+  cras_system_state_init("/tmp", shm_name, rw_shm_fd, ro_shm_fd, exp_state,
+                         sizeof(*exp_state));
+  free(shm_name);
+  cras_observer_server_init();
+  cras_mix_init(0);
+  cras_iodev_list_init();
+  btlog = cras_bt_event_log_init();
+  return 0;
+}
diff --git a/cras/src/fuzz/cras_hfp_slc.dict b/cras/src/fuzz/cras_hfp_slc.dict
new file mode 100644
index 0000000..cfc4989
--- /dev/null
+++ b/cras/src/fuzz/cras_hfp_slc.dict
@@ -0,0 +1,23 @@
+"ATA"
+"ATD"
+"AT+BAC"
+"AT+BCS"
+"AT+BIA"
+"AT+BIEV"
+"AT+BIND"
+"AT+BLDN"
+"AT+BRSF"
+"AT+CCWA"
+"AT+CHUP"
+"AT+CIND"
+"AT+CKPD"
+"AT+CLCC"
+"AT+CLIP"
+"AT+CMEE"
+"AT+CMER"
+"AT+CNUM"
+"AT+COPS"
+"AT+IPHONEACCEV"
+"AT+VG"
+"AT+VTS"
+"AT+XAPL"
diff --git a/cras/src/fuzz/rclient_message.cc b/cras/src/fuzz/rclient_message.cc
index a2a5650..eacf9da 100644
--- a/cras/src/fuzz/rclient_message.cc
+++ b/cras/src/fuzz/rclient_message.cc
@@ -4,21 +4,37 @@
  */
 
 #include <assert.h>
+#include <fuzzer/FuzzedDataProvider.h>
 #include <stddef.h>
 #include <stdint.h>
 
 extern "C" {
+#include "cras_apm_list.h"
+#include "cras_bt_log.h"
+#include "cras_dsp.h"
 #include "cras_iodev_list.h"
 #include "cras_mix.h"
 #include "cras_observer.h"
 #include "cras_rclient.h"
 #include "cras_shm.h"
 #include "cras_system_state.h"
+
+struct cras_bt_event_log* btlog;
 }
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
   cras_rclient* client = cras_rclient_create(0, 0, CRAS_CONTROL);
-  cras_rclient_buffer_from_client(client, data, size, NULL, 0);
+  if (size < 300) {
+    /* Feeds input data directly if the given bytes is too short. */
+    cras_rclient_buffer_from_client(client, data, size, NULL, 0);
+  } else {
+    FuzzedDataProvider data_provider(data, size);
+    int fds[1] = {0};
+    int num_fds = data_provider.ConsumeIntegralInRange(0, 1);
+    std::vector<uint8_t> msg = data_provider.ConsumeRemainingBytes<uint8_t>();
+    cras_rclient_buffer_from_client(client, msg.data(), msg.size(), fds,
+                                    num_fds);
+  }
   cras_rclient_destroy(client);
 
   return 0;
@@ -40,7 +56,13 @@
 
   cras_observer_server_init();
   cras_mix_init(0);
+  cras_apm_list_init("/etc/cras");
   cras_iodev_list_init();
-
+  /* For cros fuzz, emerge adhd with USE=fuzzer will copy dsp.ini.sample to
+   * etc/cras. For OSS-Fuzz the Dockerfile will be responsible for copying the
+   * file. This shouldn't crash CRAS even if the dsp file does not exist. */
+  cras_dsp_init("/etc/cras/dsp.ini.sample");
+  /* Initializes btlog for CRAS_SERVER_DUMP_BT path with CRAS_DBUS defined. */
+  btlog = cras_bt_event_log_init();
   return 0;
 }
diff --git a/cras/src/libcras/cras_client.c b/cras/src/libcras/cras_client.c
index fe54e0e..8420db1 100644
--- a/cras/src/libcras/cras_client.c
+++ b/cras/src/libcras/cras_client.c
@@ -119,7 +119,8 @@
 };
 
 /* Parameters used when setting up a capture or playback stream. See comment
- * above cras_client_create_stream_params in the header for descriptions. */
+ * above cras_client_stream_params_create or libcras_stream_params_set in the
+ * header for descriptions. */
 struct cras_stream_params {
 	enum CRAS_STREAM_DIRECTION direction;
 	size_t buffer_frames;
@@ -133,15 +134,14 @@
 	cras_unified_cb_t unified_cb;
 	cras_error_cb_t err_cb;
 	struct cras_audio_format format;
-	int client_shm_fd;
-	size_t client_shm_size;
+	libcras_stream_cb_t stream_cb;
 };
 
 /* Represents an attached audio stream.
  * id - Unique stream identifier.
  * aud_fd - After server connects audio messages come in here.
  * direction - playback, capture, or loopback (see CRAS_STREAM_DIRECTION).
- * flags - Currently not used.
+ * flags - Currently only used for CRAS_INPUT_STREAM_FLAG.
  * volume_scaler - Amount to scale the stream by, 0.0 to 1.0. Client could
  *    change this scaler value before stream actually connected, so we need
  *    to cache it until shm is prepared and apply it.
@@ -276,6 +276,92 @@
 	void *user_data;
 };
 
+struct cras_stream_cb_data {
+	cras_stream_id_t stream_id;
+	enum CRAS_STREAM_DIRECTION direction;
+	uint8_t *buf;
+	unsigned int frames;
+	struct timespec sample_ts;
+	void *user_arg;
+};
+
+int stream_cb_get_stream_id(struct cras_stream_cb_data *data,
+			    cras_stream_id_t *id)
+{
+	*id = data->stream_id;
+	return 0;
+}
+
+int stream_cb_get_buf(struct cras_stream_cb_data *data, uint8_t **buf)
+{
+	*buf = data->buf;
+	return 0;
+}
+
+int stream_cb_get_frames(struct cras_stream_cb_data *data, unsigned int *frames)
+{
+	*frames = data->frames;
+	return 0;
+}
+
+int stream_cb_get_latency(struct cras_stream_cb_data *data,
+			  struct timespec *latency)
+{
+	if (data->direction == CRAS_STREAM_INPUT)
+		cras_client_calc_capture_latency(&data->sample_ts, latency);
+	else
+		cras_client_calc_playback_latency(&data->sample_ts, latency);
+	return 0;
+}
+
+int stream_cb_get_user_arg(struct cras_stream_cb_data *data, void **user_arg)
+{
+	*user_arg = data->user_arg;
+	return 0;
+}
+
+struct libcras_stream_cb_data *
+libcras_stream_cb_data_create(cras_stream_id_t stream_id,
+			      enum CRAS_STREAM_DIRECTION direction,
+			      uint8_t *buf, unsigned int frames,
+			      struct timespec sample_ts, void *user_arg)
+{
+	struct libcras_stream_cb_data *data =
+		(struct libcras_stream_cb_data *)calloc(
+			1, sizeof(struct libcras_stream_cb_data));
+	if (!data) {
+		syslog(LOG_ERR, "cras_client: calloc: %s", strerror(errno));
+		return NULL;
+	}
+	data->data_ = (struct cras_stream_cb_data *)calloc(
+		1, sizeof(struct cras_stream_cb_data));
+	if (!data->data_) {
+		syslog(LOG_ERR, "cras_client: calloc: %s", strerror(errno));
+		free(data);
+		return NULL;
+	}
+	data->api_version = CRAS_API_VERSION;
+	data->get_stream_id = stream_cb_get_stream_id;
+	data->get_buf = stream_cb_get_buf;
+	data->get_frames = stream_cb_get_frames;
+	data->get_latency = stream_cb_get_latency;
+	data->get_user_arg = stream_cb_get_user_arg;
+	data->data_->stream_id = stream_id;
+	data->data_->direction = direction;
+	data->data_->buf = buf;
+	data->data_->frames = frames;
+	data->data_->sample_ts = sample_ts;
+	data->data_->user_arg = user_arg;
+	return data;
+}
+
+void libcras_stream_cb_data_destroy(struct libcras_stream_cb_data *data)
+{
+	if (data)
+		free(data->data_);
+	free(data);
+}
+
 /*
  * Local Helpers
  */
@@ -285,6 +371,10 @@
 static int handle_message_from_server(struct cras_client *client);
 static int reregister_notifications(struct cras_client *client);
 
+static struct libcras_node_info *
+libcras_node_info_create(struct cras_iodev_info *iodev,
+			 struct cras_ionode_info *ionode);
+
 /*
  * Unlock the server_state_rwlock if lock_rc is 0.
  *
@@ -1086,6 +1176,7 @@
 	uint8_t *captured_frames;
 	struct timespec ts;
 	int rc = 0;
+	struct libcras_stream_cb_data *data;
 
 	config = stream->config;
 	/* If this message is for an output stream, log error and drop it. */
@@ -1100,14 +1191,24 @@
 
 	cras_timespec_to_timespec(&ts, &stream->shm->header->ts);
 
-	if (config->unified_cb)
+	if (config->stream_cb) {
+		data = libcras_stream_cb_data_create(
+			stream->id, stream->direction, captured_frames,
+			num_frames, ts, config->user_data);
+		if (!data)
+			return -errno;
+		frames = config->stream_cb(data);
+		libcras_stream_cb_data_destroy(data);
+		data = NULL;
+	} else if (config->unified_cb) {
 		frames = config->unified_cb(stream->client, stream->id,
 					    captured_frames, NULL, num_frames,
 					    &ts, NULL, config->user_data);
-	else
+	} else {
 		frames = config->aud_cb(stream->client, stream->id,
 					captured_frames, num_frames, &ts,
 					config->user_data);
+	}
 	if (frames < 0) {
 		send_stream_message(stream, CLIENT_STREAM_EOF);
 		rc = frames;
@@ -1154,6 +1255,7 @@
 	struct cras_stream_params *config;
 	struct cras_audio_shm *shm = stream->shm;
 	struct timespec ts;
+	struct libcras_stream_cb_data *data;
 
 	config = stream->config;
 
@@ -1171,13 +1273,24 @@
 	cras_timespec_to_timespec(&ts, &shm->header->ts);
 
 	/* Get samples from the user */
-	if (config->unified_cb)
+	if (config->stream_cb) {
+		data = libcras_stream_cb_data_create(stream->id,
+						     stream->direction, buf,
+						     num_frames, ts,
+						     config->user_data);
+		if (!data)
+			return -errno;
+		frames = config->stream_cb(data);
+		libcras_stream_cb_data_destroy(data);
+		data = NULL;
+	} else if (config->unified_cb) {
 		frames = config->unified_cb(stream->client, stream->id, NULL,
 					    buf, num_frames, NULL, &ts,
 					    config->user_data);
-	else
+	} else {
 		frames = config->aud_cb(stream->client, stream->id, buf,
 					num_frames, &ts, config->user_data);
+	}
 	if (frames < 0) {
 		send_stream_message(stream, CLIENT_STREAM_EOF);
 		rc = frames;
@@ -1384,7 +1497,6 @@
 {
 	int rc, samples_prot;
 	unsigned int i;
-	struct cras_audio_format mfmt;
 	struct cras_shm_info header_info, samples_info;
 
 	if (msg->err || num_fds != 2) {
@@ -1394,8 +1506,6 @@
 		goto err_ret;
 	}
 
-	unpack_cras_audio_format(&mfmt, &msg->format);
-
 	rc = cras_shm_info_init_with_fd(stream_fds[0], cras_shm_header_size(),
 					&header_info);
 	if (rc < 0)
@@ -1408,7 +1518,6 @@
 		goto err_ret;
 	}
 
-	samples_prot = 0;
 	if (stream->direction == CRAS_STREAM_OUTPUT)
 		samples_prot = PROT_WRITE;
 	else
@@ -1443,8 +1552,6 @@
 	int rc;
 	struct cras_connect_message serv_msg;
 	int sock[2] = { -1, -1 };
-	int fds[2] = { -1, -1 };
-	unsigned int num_fds;
 
 	/* Create a socket pair for the server to notify of audio events. */
 	rc = socketpair(AF_UNIX, SOCK_STREAM, 0, sock);
@@ -1454,21 +1561,16 @@
 		goto fail;
 	}
 
-	cras_fill_connect_message(
-		&serv_msg, stream->config->direction, stream->id,
-		stream->config->stream_type, stream->config->client_type,
-		stream->config->buffer_frames, stream->config->cb_threshold,
-		stream->flags, stream->config->effects, stream->config->format,
-		dev_idx, stream->config->client_shm_size);
+	cras_fill_connect_message(&serv_msg, stream->config->direction,
+				  stream->id, stream->config->stream_type,
+				  stream->config->client_type,
+				  stream->config->buffer_frames,
+				  stream->config->cb_threshold, stream->flags,
+				  stream->config->effects,
+				  stream->config->format, dev_idx);
 
-	fds[0] = sock[1];
-	num_fds = 1;
-	if (stream->config->client_shm_fd >= 0) {
-		fds[1] = stream->config->client_shm_fd;
-		num_fds++;
-	}
 	rc = cras_send_with_fds(client->server_fd, &serv_msg, sizeof(serv_msg),
-				fds, num_fds);
+				&sock[1], 1);
 	if (rc != sizeof(serv_msg)) {
 		rc = EIO;
 		syslog(LOG_ERR,
@@ -1478,8 +1580,6 @@
 
 	stream->aud_fd = sock[0];
 	close(sock[1]);
-	if (stream->config->client_shm_fd != -1)
-		close(stream->config->client_shm_fd);
 	return 0;
 
 fail:
@@ -1487,8 +1587,6 @@
 		close(sock[0]);
 	if (sock[1] != -1)
 		close(sock[1]);
-	if (stream->config->client_shm_fd != -1)
-		close(stream->config->client_shm_fd);
 	return rc;
 }
 
@@ -1504,18 +1602,30 @@
 	cras_stream_id_t new_id;
 	struct client_stream *out;
 
-	/* Find the hotword device index. */
-	if ((stream->flags & HOTWORD_STREAM) == HOTWORD_STREAM &&
-	    dev_idx == NO_DEVICE) {
+	if ((stream->flags & HOTWORD_STREAM) == HOTWORD_STREAM) {
 		int hotword_idx;
 		hotword_idx = cras_client_get_first_dev_type_idx(
 			client, CRAS_NODE_TYPE_HOTWORD, CRAS_STREAM_INPUT);
-		if (hotword_idx < 0) {
-			syslog(LOG_ERR,
-			       "cras_client: add_stream: Finding hotword dev");
-			return hotword_idx;
+
+		/* Find the hotword device index. */
+		if (dev_idx == NO_DEVICE) {
+			if (hotword_idx < 0) {
+				syslog(LOG_ERR,
+				       "cras_client: add_stream: No hotword dev");
+				return hotword_idx;
+			} else {
+				dev_idx = (uint32_t)hotword_idx;
+			}
 		}
-		dev_idx = hotword_idx;
+		/* A known Use case for client to pin hotword stream on a not
+		 * hotword device is to use internal mic for Assistant to work
+		 * on board without usable DSP hotwording. We assume there will
+		 * be only one hotword device exists. */
+		else if (dev_idx != (uint32_t)hotword_idx) {
+			/* Unmask the flag to fallback to normal pinned stream
+			 * on specified device. */
+			stream->flags &= ~HOTWORD_STREAM;
+		}
 	}
 
 	/* Find an available stream id. */
@@ -2142,7 +2252,7 @@
 
 	rc = fill_socket_file((*client), conn_type);
 	if (rc < 0) {
-		goto free_error;
+		goto free_server_event_fd;
 	}
 
 	rc = cras_file_wait_create((*client)->sock_file,
@@ -2175,10 +2285,11 @@
 
 	return 0;
 free_error:
-	if ((*client)->server_event_fd >= 0)
-		close((*client)->server_event_fd);
 	cras_file_wait_destroy((*client)->sock_file_wait);
 	free((void *)(*client)->sock_file);
+free_server_event_fd:
+	if ((*client)->server_event_fd >= 0)
+		close((*client)->server_event_fd);
 free_cond:
 	pthread_cond_destroy(&(*client)->stream_start_cond);
 free_lock:
@@ -2259,9 +2370,8 @@
 	params->user_data = user_data;
 	params->aud_cb = aud_cb;
 	params->unified_cb = 0;
+	params->stream_cb = 0;
 	params->err_cb = err_cb;
-	params->client_shm_fd = -1;
-	params->client_shm_size = 0;
 	memcpy(&(params->format), format, sizeof(*format));
 	return params;
 }
@@ -2312,14 +2422,6 @@
 	params->effects &= ~APM_VOICE_DETECTION;
 }
 
-void cras_client_stream_params_configure_client_shm(
-	struct cras_stream_params *params, int client_shm_fd,
-	size_t client_shm_size)
-{
-	params->client_shm_fd = client_shm_fd;
-	params->client_shm_size = client_shm_size;
-}
-
 struct cras_stream_params *cras_client_unified_params_create(
 	enum CRAS_STREAM_DIRECTION direction, unsigned int block_size,
 	enum CRAS_STREAM_TYPE stream_type, uint32_t flags, void *user_data,
@@ -2342,9 +2444,8 @@
 	params->user_data = user_data;
 	params->aud_cb = 0;
 	params->unified_cb = unified_cb;
+	params->stream_cb = 0;
 	params->err_cb = err_cb;
-	params->client_shm_fd = -1;
-	params->client_shm_size = 0;
 	memcpy(&(params->format), format, sizeof(*format));
 
 	return params;
@@ -2366,7 +2467,8 @@
 	if (client == NULL || config == NULL || stream_id_out == NULL)
 		return -EINVAL;
 
-	if (config->aud_cb == NULL && config->unified_cb == NULL)
+	if (config->stream_cb == NULL && config->aud_cb == NULL &&
+	    config->unified_cb == NULL)
 		return -EINVAL;
 
 	if (config->err_cb == NULL)
@@ -2464,17 +2566,6 @@
 	return write_message_to_server(client, &msg.header);
 }
 
-int cras_client_set_system_capture_gain(struct cras_client *client, long gain)
-{
-	struct cras_set_system_capture_gain msg;
-
-	if (client == NULL)
-		return -EINVAL;
-
-	cras_fill_set_system_capture_gain(&msg, gain);
-	return write_message_to_server(client, &msg.header);
-}
-
 int cras_client_set_system_mute(struct cras_client *client, int mute)
 {
 	struct cras_set_system_mute msg;
@@ -2629,32 +2720,19 @@
 	return max_volume;
 }
 
-long cras_client_get_system_min_capture_gain(const struct cras_client *client)
+int cras_client_get_default_output_buffer_size(struct cras_client *client)
 {
-	long min_gain;
+	int default_output_buffer_size;
 	int lock_rc;
 
 	lock_rc = server_state_rdlock(client);
 	if (lock_rc)
-		return 0;
+		return -EINVAL;
 
-	min_gain = client->server_state->min_capture_gain;
+	default_output_buffer_size =
+		client->server_state->default_output_buffer_size;
 	server_state_unlock(client, lock_rc);
-	return min_gain;
-}
-
-long cras_client_get_system_max_capture_gain(const struct cras_client *client)
-{
-	long max_gain;
-	int lock_rc;
-
-	lock_rc = server_state_rdlock(client);
-	if (lock_rc)
-		return 0;
-
-	max_gain = client->server_state->max_capture_gain;
-	server_state_unlock(client, lock_rc);
-	return max_gain;
+	return default_output_buffer_size;
 }
 
 const struct audio_debug_info *
@@ -2672,6 +2750,21 @@
 	return debug_info;
 }
 
+const struct main_thread_debug_info *
+cras_client_get_main_thread_debug_info(const struct cras_client *client)
+{
+	const struct main_thread_debug_info *debug_info;
+	int lock_rc;
+
+	lock_rc = server_state_rdlock(client);
+	if (lock_rc)
+		return 0;
+
+	debug_info = &client->server_state->main_thread_debug_info;
+	server_state_unlock(client, lock_rc);
+	return debug_info;
+}
+
 const struct cras_bt_debug_info *
 cras_client_get_bt_debug_info(const struct cras_client *client)
 {
@@ -3208,6 +3301,20 @@
 	return len;
 }
 
+int cras_client_update_main_thread_debug_info(
+	struct cras_client *client, void (*debug_info_cb)(struct cras_client *))
+{
+	struct cras_dump_main msg;
+
+	if (client == NULL)
+		return -EINVAL;
+	if (client->debug_info_callback != NULL)
+		return -EINVAL;
+	client->debug_info_callback = debug_info_cb;
+	cras_fill_dump_main(&msg);
+	return write_message_to_server(client, &msg.header);
+}
+
 int cras_client_update_bt_debug_info(
 	struct cras_client *client, void (*debug_info_cb)(struct cras_client *))
 {
@@ -3240,6 +3347,71 @@
 	return write_message_to_server(client, &msg.header);
 }
 
+int cras_client_get_max_supported_channels(const struct cras_client *client,
+					   cras_node_id_t node_id,
+					   uint32_t *max_channels)
+{
+	size_t ndevs, nnodes;
+	struct cras_iodev_info *devs = NULL;
+	struct cras_ionode_info *nodes = NULL;
+	int rc = -EINVAL;
+	unsigned i;
+
+	if (!client) {
+		rc = -EINVAL;
+		goto quit;
+	}
+
+	devs = (struct cras_iodev_info *)malloc(CRAS_MAX_IODEVS *
+						sizeof(*devs));
+	if (!devs) {
+		rc = -ENOMEM;
+		goto quit;
+	}
+
+	nodes = (struct cras_ionode_info *)malloc(CRAS_MAX_IONODES *
+						  sizeof(*nodes));
+	if (!nodes) {
+		rc = -ENOMEM;
+		goto quit;
+	}
+
+	ndevs = CRAS_MAX_IODEVS;
+	nnodes = CRAS_MAX_IONODES;
+	rc = cras_client_get_output_devices(client, devs, nodes, &ndevs,
+					    &nnodes);
+	if (rc < 0)
+		goto quit;
+
+	rc = -ENOENT;
+	uint32_t iodev_idx;
+	for (i = 0; i < nnodes; i++) {
+		if (node_id == cras_make_node_id(nodes[i].iodev_idx,
+						 nodes[i].ionode_idx)) {
+			iodev_idx = nodes[i].iodev_idx;
+			rc = 0;
+			break;
+		}
+	}
+
+	if (rc < 0)
+		goto quit;
+
+	rc = -ENOENT;
+	for (i = 0; i < ndevs; i++) {
+		if (iodev_idx == devs[i].idx) {
+			*max_channels = devs[i].max_supported_channels;
+			rc = 0;
+			break;
+		}
+	}
+
+quit:
+	free(devs);
+	free(nodes);
+	return rc;
+}
+
 int cras_client_set_node_volume(struct cras_client *client,
 				cras_node_id_t node_id, uint8_t volume)
 {
@@ -3308,10 +3480,10 @@
 {
 	struct cras_config_global_remix *msg;
 	int rc;
+	size_t nchan = (size_t)num_channels;
 
 	msg = (struct cras_config_global_remix *)malloc(
-		sizeof(*msg) +
-		num_channels * num_channels * sizeof(*coefficient));
+		sizeof(*msg) + nchan * nchan * sizeof(*coefficient));
 	cras_fill_config_global_remix_command(msg, num_channels, coefficient,
 					      num_channels * num_channels);
 	rc = write_message_to_server(client, &msg->header);
@@ -3761,3 +3933,317 @@
 	free(handle);
 	return 0;
 }
+
+int get_nodes(struct cras_client *client, enum CRAS_STREAM_DIRECTION direction,
+	      struct libcras_node_info ***nodes, size_t *num)
+{
+	struct cras_iodev_info iodevs[CRAS_MAX_IODEVS];
+	struct cras_ionode_info ionodes[CRAS_MAX_IONODES];
+	size_t num_devs = CRAS_MAX_IODEVS, num_nodes = CRAS_MAX_IONODES;
+	int rc, i, j;
+
+	*num = 0;
+	if (direction == CRAS_STREAM_INPUT) {
+		rc = cras_client_get_input_devices(client, iodevs, ionodes,
+						   &num_devs, &num_nodes);
+	} else {
+		rc = cras_client_get_output_devices(client, iodevs, ionodes,
+						    &num_devs, &num_nodes);
+	}
+
+	if (rc < 0) {
+		syslog(LOG_ERR, "Failed to get devices: %d", rc);
+		return rc;
+	}
+
+	*nodes = (struct libcras_node_info **)calloc(
+		num_nodes, sizeof(struct libcras_node_info *));
+
+	for (i = 0; i < num_devs; i++) {
+		for (j = 0; j < num_nodes; j++) {
+			if (iodevs[i].idx != ionodes[j].iodev_idx)
+				continue;
+			(*nodes)[*num] = libcras_node_info_create(&iodevs[i],
+								  &ionodes[j]);
+			if ((*nodes)[*num] == NULL) {
+				rc = -errno;
+				goto clean;
+			}
+			(*num)++;
+		}
+	}
+	return 0;
+clean:
+	for (i = 0; i < *num; i++)
+		libcras_node_info_destroy((*nodes)[i]);
+	free(*nodes);
+	*nodes = NULL;
+	*num = 0;
+	return rc;
+}
+
+int get_default_output_buffer_size(struct cras_client *client, int *size)
+{
+	int rc = cras_client_get_default_output_buffer_size(client);
+	if (rc < 0)
+		return rc;
+	*size = rc;
+	return 0;
+}
+
+int get_aec_group_id(struct cras_client *client, int *id)
+{
+	int rc = cras_client_get_aec_group_id(client);
+	if (rc < 0)
+		return rc;
+	*id = rc;
+	return 0;
+}
+
+int get_aec_supported(struct cras_client *client, int *supported)
+{
+	*supported = cras_client_get_aec_supported(client);
+	return 0;
+}
+
+int get_system_muted(struct cras_client *client, int *muted)
+{
+	*muted = cras_client_get_system_muted(client);
+	return 0;
+}
+
+int get_loopback_dev_idx(struct cras_client *client, int *idx)
+{
+	int rc = cras_client_get_first_dev_type_idx(
+		client, CRAS_NODE_TYPE_POST_MIX_PRE_DSP, CRAS_STREAM_INPUT);
+	if (rc < 0)
+		return rc;
+	*idx = rc;
+	return 0;
+}
+
+struct libcras_client *libcras_client_create()
+{
+	struct libcras_client *client = (struct libcras_client *)calloc(
+		1, sizeof(struct libcras_client));
+	if (!client) {
+		syslog(LOG_ERR, "cras_client: calloc failed");
+		return NULL;
+	}
+	if (cras_client_create(&client->client_)) {
+		libcras_client_destroy(client);
+		return NULL;
+	}
+	client->api_version = CRAS_API_VERSION;
+	client->connect = cras_client_connect;
+	client->connect_timeout = cras_client_connect_timeout;
+	client->connected_wait = cras_client_connected_wait;
+	client->run_thread = cras_client_run_thread;
+	client->stop = cras_client_stop;
+	client->add_pinned_stream = cras_client_add_pinned_stream;
+	client->rm_stream = cras_client_rm_stream;
+	client->set_stream_volume = cras_client_set_stream_volume;
+	client->get_nodes = get_nodes;
+	client->get_default_output_buffer_size = get_default_output_buffer_size;
+	client->get_aec_group_id = get_aec_group_id;
+	client->get_aec_supported = get_aec_supported;
+	client->get_system_muted = get_system_muted;
+	client->set_system_mute = cras_client_set_system_mute;
+	client->get_loopback_dev_idx = get_loopback_dev_idx;
+	return client;
+}
+
+void libcras_client_destroy(struct libcras_client *client)
+{
+	cras_client_destroy(client->client_);
+	free(client);
+}
+
+int stream_params_set(struct cras_stream_params *params,
+		      enum CRAS_STREAM_DIRECTION direction,
+		      size_t buffer_frames, size_t cb_threshold,
+		      enum CRAS_STREAM_TYPE stream_type,
+		      enum CRAS_CLIENT_TYPE client_type, uint32_t flags,
+		      void *user_data, libcras_stream_cb_t stream_cb,
+		      cras_error_cb_t err_cb, size_t rate,
+		      snd_pcm_format_t format, size_t num_channels)
+{
+	params->direction = direction;
+	params->buffer_frames = buffer_frames;
+	params->cb_threshold = cb_threshold;
+	params->stream_type = stream_type;
+	params->client_type = client_type;
+	params->flags = flags;
+	params->user_data = user_data;
+	params->stream_cb = stream_cb;
+	params->err_cb = err_cb;
+	params->format.frame_rate = rate;
+	params->format.format = format;
+	params->format.num_channels = num_channels;
+	return 0;
+}
+
+int stream_params_set_channel_layout(struct cras_stream_params *params,
+				     int length, const int8_t *layout)
+{
+	if (length != CRAS_CH_MAX)
+		return -EINVAL;
+	return cras_audio_format_set_channel_layout(&params->format, layout);
+}
+
+struct libcras_stream_params *libcras_stream_params_create()
+{
+	struct libcras_stream_params *params =
+		(struct libcras_stream_params *)calloc(
+			1, sizeof(struct libcras_stream_params));
+	if (!params) {
+		syslog(LOG_ERR, "cras_client: calloc failed");
+		return NULL;
+	}
+	params->params_ = (struct cras_stream_params *)calloc(
+		1, sizeof(struct cras_stream_params));
+	if (params->params_ == NULL) {
+		syslog(LOG_ERR, "cras_client: calloc failed");
+		free(params->params_);
+		return NULL;
+	}
+	params->api_version = CRAS_API_VERSION;
+	params->set = stream_params_set;
+	params->set_channel_layout = stream_params_set_channel_layout;
+	params->enable_aec = cras_client_stream_params_enable_aec;
+	return params;
+}
+
+void libcras_stream_params_destroy(struct libcras_stream_params *params)
+{
+	free(params->params_);
+	free(params);
+}
+
+struct cras_node_info {
+	uint64_t id;
+	uint32_t dev_idx;
+	uint32_t node_idx;
+	uint32_t max_supported_channels;
+	bool plugged;
+	bool active;
+	char type[CRAS_NODE_TYPE_BUFFER_SIZE];
+	char node_name[CRAS_NODE_NAME_BUFFER_SIZE];
+	char dev_name[CRAS_IODEV_NAME_BUFFER_SIZE];
+};
+
+int cras_node_info_get_id(struct cras_node_info *node, uint64_t *id)
+{
+	(*id) = node->id;
+	return 0;
+}
+
+int cras_node_info_get_dev_idx(struct cras_node_info *node, uint32_t *dev_idx)
+{
+	(*dev_idx) = node->dev_idx;
+	return 0;
+}
+
+int cras_node_info_get_node_idx(struct cras_node_info *node, uint32_t *node_idx)
+{
+	(*node_idx) = node->node_idx;
+	return 0;
+}
+
+int cras_node_info_get_max_supported_channels(struct cras_node_info *node,
+					      uint32_t *max_supported_channels)
+{
+	(*max_supported_channels) = node->max_supported_channels;
+	return 0;
+}
+
+int cras_node_info_is_plugged(struct cras_node_info *node, bool *is_plugged)
+{
+	(*is_plugged) = node->plugged;
+	return 0;
+}
+
+int cras_node_info_is_active(struct cras_node_info *node, bool *is_active)
+{
+	(*is_active) = node->active;
+	return 0;
+}
+
+int cras_node_info_get_type(struct cras_node_info *node, char **type)
+{
+	(*type) = node->type;
+	return 0;
+}
+
+int cras_node_info_get_node_name(struct cras_node_info *node, char **node_name)
+{
+	(*node_name) = node->node_name;
+	return 0;
+}
+
+int cras_node_info_get_dev_name(struct cras_node_info *node, char **dev_name)
+{
+	(*dev_name) = node->dev_name;
+	return 0;
+}
+
+struct libcras_node_info *
+libcras_node_info_create(struct cras_iodev_info *iodev,
+			 struct cras_ionode_info *ionode)
+{
+	struct libcras_node_info *node = (struct libcras_node_info *)calloc(
+		1, sizeof(struct libcras_node_info));
+	if (!node) {
+		syslog(LOG_ERR, "cras_client: calloc failed");
+		return NULL;
+	}
+	node->node_ = (struct cras_node_info *)calloc(
+		1, sizeof(struct cras_node_info));
+	if (node->node_ == NULL) {
+		syslog(LOG_ERR, "cras_client: calloc failed");
+		free(node);
+		return NULL;
+	}
+	node->api_version = CRAS_API_VERSION;
+	node->node_->id =
+		cras_make_node_id(ionode->iodev_idx, ionode->ionode_idx);
+	node->node_->dev_idx = ionode->iodev_idx;
+	node->node_->node_idx = ionode->ionode_idx;
+	node->node_->max_supported_channels = iodev->max_supported_channels;
+	node->node_->plugged = ionode->plugged;
+	node->node_->active = ionode->active;
+	strncpy(node->node_->type, ionode->type, CRAS_NODE_TYPE_BUFFER_SIZE);
+	node->node_->type[CRAS_NODE_TYPE_BUFFER_SIZE - 1] = '\0';
+	strncpy(node->node_->node_name, ionode->name,
+		CRAS_NODE_NAME_BUFFER_SIZE);
+	node->node_->node_name[CRAS_NODE_NAME_BUFFER_SIZE - 1] = '\0';
+	strncpy(node->node_->dev_name, iodev->name,
+		CRAS_IODEV_NAME_BUFFER_SIZE);
+	node->node_->dev_name[CRAS_IODEV_NAME_BUFFER_SIZE - 1] = '\0';
+	node->get_id = cras_node_info_get_id;
+	node->get_dev_idx = cras_node_info_get_dev_idx;
+	node->get_node_idx = cras_node_info_get_node_idx;
+	node->get_max_supported_channels =
+		cras_node_info_get_max_supported_channels;
+	node->is_plugged = cras_node_info_is_plugged;
+	node->is_active = cras_node_info_is_active;
+	node->get_type = cras_node_info_get_type;
+	node->get_node_name = cras_node_info_get_node_name;
+	node->get_dev_name = cras_node_info_get_dev_name;
+	return node;
+}
+
+void libcras_node_info_destroy(struct libcras_node_info *node)
+{
+	free(node->node_);
+	free(node);
+}
+
+void libcras_node_info_array_destroy(struct libcras_node_info **nodes,
+				     size_t num)
+{
+	int i;
+	for (i = 0; i < num; i++)
+		libcras_node_info_destroy(nodes[i]);
+	free(nodes);
+}
diff --git a/cras/src/libcras/cras_client.h b/cras/src/libcras/cras_client.h
index 7012e2d..f26a081 100644
--- a/cras/src/libcras/cras_client.h
+++ b/cras/src/libcras/cras_client.h
@@ -477,6 +477,16 @@
 int cras_client_update_audio_debug_info(struct cras_client *client,
 					void (*cb)(struct cras_client *));
 
+/* Asks the server to dump current main thread information.
+ * Args:
+ *    client - The client from cras_client_create.
+ *    cb - A function to call when the data is received.
+ * Returns:
+ *    0 on success, -EINVAL if the client isn't valid or isn't running.
+ */
+int cras_client_update_main_thread_debug_info(struct cras_client *client,
+					      void (*cb)(struct cras_client *));
+
 /* Asks the server to dump bluetooth debug information.
  * Args:
  *    client - The client from cras_client_create.
@@ -525,6 +535,19 @@
 int cras_client_update_audio_thread_snapshots(struct cras_client *client,
 					      void (*cb)(struct cras_client *));
 
+/* Gets the max supported channel count of the output device from node_id.
+ * Args:
+ *    client - The client from cras_client_create.
+ *    node_id - ID of the node.
+ *    max_channels - Out parameter will be filled with the max supported channel
+ *        count.
+ * Returns:
+ *    0 on success, or negative error code on failure.
+ */
+int cras_client_get_max_supported_channels(const struct cras_client *client,
+					   cras_node_id_t node_id,
+					   uint32_t *max_channels);
+
 /*
  * Stream handling.
  */
@@ -538,7 +561,7 @@
  *        be called when buffer_frames have been captured).
  *    unused - No longer used.
  *    stream_type - media or talk (currently only support "default").
- *    flags - None currently used.
+ *    flags - Currently only used for CRAS_INPUT_STREAM_FLAG.
  *    user_data - Pointer that will be passed to the callback.
  *    aud_cb - Called when audio is needed(playback) or ready(capture). Allowed
  *        return EOF to indicate that the stream should terminate.
@@ -573,16 +596,6 @@
 void cras_client_stream_params_enable_vad(struct cras_stream_params *params);
 void cras_client_stream_params_disable_vad(struct cras_stream_params *params);
 
-/* Function to setup client-provided shm to be used as the backing shm for the
- * samples area in the cras_audio_shm shared with cras.
- * Args:
- *    client_shm_fd - shm fd to use for samples shm area.
- *    client_shm_size - size of shm area backed by 'client_shm_fd'.
- */
-void cras_client_stream_params_configure_client_shm(
-	struct cras_stream_params *params, int client_shm_fd,
-	size_t client_shm_size);
-
 /* Setup stream configuration parameters. DEPRECATED.
  * TODO(crbug.com/972928): remove this
  * Use cras_client_stream_params_create instead.
@@ -692,20 +705,6 @@
  */
 int cras_client_set_system_volume(struct cras_client *client, size_t volume);
 
-/* Sets the capture gain of the system.
- *
- * Gain is specified in dBFS * 100.  For example 5dB of gain would be specified
- * with an argument of 500, while -10 would be specified with -1000.
- *
- * Args:
- *    client - The client from cras_client_create.
- *    gain - The gain in dBFS * 100.
- * Returns:
- *    0 for success, -EPIPE if there is an I/O error talking to the server, or
- *    -EINVAL if 'client' is invalid.
- */
-int cras_client_set_system_capture_gain(struct cras_client *client, long gain);
-
 /* Sets the mute state of the system.
  *
  * Args:
@@ -781,17 +780,6 @@
  */
 size_t cras_client_get_system_volume(const struct cras_client *client);
 
-/* Gets the current system capture gain.
- *
- * Requires that the connection to the server has been established.
- *
- * Args:
- *    client - The client from cras_client_create.
- * Returns:
- *    The current system capture volume in dB * 100.
- */
-long cras_client_get_system_capture_gain(const struct cras_client *client);
-
 /* Gets the current system mute state.
  *
  * Requires that the connection to the server has been established.
@@ -843,27 +831,13 @@
  */
 long cras_client_get_system_max_volume(const struct cras_client *client);
 
-/* Gets the current minimum system capture gain.
- *
- * Requires that the connection to the server has been established.
- *
+/* Gets the default output buffer size.
  * Args:
  *    client - The client from cras_client_create.
  * Returns:
- *    The minimum capture gain for the current input device in dBFS * 100.
+ *    Default output buffer size in frames. A negative error on failure.
  */
-long cras_client_get_system_min_capture_gain(const struct cras_client *client);
-
-/* Gets the current maximum system capture gain.
- *
- * Requires that the connection to the server has been established.
- *
- * Args:
- *    client - The client from cras_client_create.
- * Returns:
- *    The maximum capture gain for the current input device in dBFS * 100.
- */
-long cras_client_get_system_max_capture_gain(const struct cras_client *client);
+int cras_client_get_default_output_buffer_size(struct cras_client *client);
 
 /* Gets audio debug info.
  *
@@ -892,6 +866,16 @@
 const struct cras_bt_debug_info *
 cras_client_get_bt_debug_info(const struct cras_client *client);
 
+/* Gets main thread debug info.
+ * Args:
+ *    client - The client from cras_client_create.
+ * Returns:
+ *    A pointer to the debug info. This info is updated and requested by
+ *    calling cras_client_update_main_thread_debug_info.
+ */
+const struct main_thread_debug_info *
+cras_client_get_main_thread_debug_info(const struct cras_client *client);
+
 /* Gets audio thread snapshot buffer.
  *
  * Requires that the connection to the server has been established.
@@ -987,7 +971,8 @@
  * Args:
  *    client - The client from cras_client_create.
  *    node_id - ID of the node.
- *    gain - New capture gain for the node.
+ *    gain - New capture gain for the node, in range (0, 100) which will
+ *        linearly maps to (-4000, 4000) 100*dBFS.
  */
 int cras_client_set_node_capture_gain(struct cras_client *client,
 				      cras_node_id_t node_id, long gain);
@@ -1323,6 +1308,699 @@
 int cras_client_set_num_active_streams_changed_callback(
 	struct cras_client *client,
 	cras_client_num_active_streams_changed_callback cb);
+
+/*
+ * The functions below prefixed with libcras wrap the original CRAS library
+ * They provide an interface that maps the pointers to the functions above.
+ * Please add a new function instead of modifying the existing function.
+ * Here are some rules about how to add a new function:
+ * 1. Increase the CRAS_API_VERSION by 1.
+ * 2. Write a new function in cras_client.c.
+ * 3. Append the corresponding pointer to the structure. Remeber DO NOT change
+ *    the order of functions in the structs.
+ * 4. Assign the pointer to the new function in cras_client.c.
+ * 5. Create the inline function in cras_client.h, which is used by clients.
+ *    Remember to add DISABLE_CFI_ICALL on the inline function.
+ * 6. Add CHECK_VERSION in the inline function. If the api_version is smaller
+ *    than the supported version, this inline function will return -ENOSYS.
+ */
+
+#define CRAS_API_VERSION 1
+#define CHECK_VERSION(object, version)                                         \
+	if (object->api_version < version) {                                   \
+		return -ENOSYS;                                                \
+	}
+
+/*
+ * The inline functions use the indirect function call. Therefore, they are
+ * incompatible with CFI-icall.
+ */
+#define DISABLE_CFI_ICALL __attribute__((no_sanitize("cfi-icall")))
+
+struct libcras_node_info {
+	int api_version;
+	struct cras_node_info *node_;
+	int (*get_id)(struct cras_node_info *node, uint64_t *id);
+	int (*get_dev_idx)(struct cras_node_info *node, uint32_t *dev_idx);
+	int (*get_node_idx)(struct cras_node_info *node, uint32_t *node_idx);
+	int (*get_max_supported_channels)(struct cras_node_info *node,
+					  uint32_t *max_supported_channels);
+	int (*is_plugged)(struct cras_node_info *node, bool *plugged);
+	int (*is_active)(struct cras_node_info *node, bool *active);
+	int (*get_type)(struct cras_node_info *node, char **name);
+	int (*get_node_name)(struct cras_node_info *node, char **name);
+	int (*get_dev_name)(struct cras_node_info *node, char **name);
+};
+
+struct libcras_client {
+	int api_version;
+	struct cras_client *client_;
+	int (*connect)(struct cras_client *client);
+	int (*connect_timeout)(struct cras_client *client,
+			       unsigned int timeout_ms);
+	int (*connected_wait)(struct cras_client *client);
+	int (*run_thread)(struct cras_client *client);
+	int (*stop)(struct cras_client *client);
+	int (*add_pinned_stream)(struct cras_client *client, uint32_t dev_idx,
+				 cras_stream_id_t *stream_id_out,
+				 struct cras_stream_params *config);
+	int (*rm_stream)(struct cras_client *client,
+			 cras_stream_id_t stream_id);
+	int (*set_stream_volume)(struct cras_client *client,
+				 cras_stream_id_t stream_id,
+				 float volume_scaler);
+	int (*get_nodes)(struct cras_client *client,
+			 enum CRAS_STREAM_DIRECTION direction,
+			 struct libcras_node_info ***nodes, size_t *num);
+	int (*get_default_output_buffer_size)(struct cras_client *client,
+					      int *size);
+	int (*get_aec_group_id)(struct cras_client *client, int *id);
+	int (*get_aec_supported)(struct cras_client *client, int *supported);
+	int (*get_system_muted)(struct cras_client *client, int *muted);
+	int (*set_system_mute)(struct cras_client *client, int mute);
+	int (*get_loopback_dev_idx)(struct cras_client *client, int *idx);
+};
+
+struct cras_stream_cb_data;
+struct libcras_stream_cb_data {
+	int api_version;
+	struct cras_stream_cb_data *data_;
+	int (*get_stream_id)(struct cras_stream_cb_data *data,
+			     cras_stream_id_t *id);
+	int (*get_buf)(struct cras_stream_cb_data *data, uint8_t **buf);
+	int (*get_frames)(struct cras_stream_cb_data *data,
+			  unsigned int *frames);
+	int (*get_latency)(struct cras_stream_cb_data *data,
+			   struct timespec *latency);
+	int (*get_user_arg)(struct cras_stream_cb_data *data, void **user_arg);
+};
+typedef int (*libcras_stream_cb_t)(struct libcras_stream_cb_data *data);
+
+struct libcras_stream_params {
+	int api_version;
+	struct cras_stream_params *params_;
+	int (*set)(struct cras_stream_params *params,
+		   enum CRAS_STREAM_DIRECTION direction, size_t buffer_frames,
+		   size_t cb_threshold, enum CRAS_STREAM_TYPE stream_type,
+		   enum CRAS_CLIENT_TYPE client_type, uint32_t flags,
+		   void *user_data, libcras_stream_cb_t stream_cb,
+		   cras_error_cb_t err_cb, size_t rate, snd_pcm_format_t format,
+		   size_t num_channels);
+	int (*set_channel_layout)(struct cras_stream_params *params, int length,
+				  const int8_t *layout);
+	void (*enable_aec)(struct cras_stream_params *params);
+};
+
+/*
+ * Creates a new client.
+ * Returns:
+ *    If success, return a valid libcras_client pointer. Otherwise, return
+ *    NULL.
+ */
+struct libcras_client *libcras_client_create();
+
+/*
+ * Destroys a client.
+ * Args:
+ *    client - pointer returned from "libcras_client_create".
+ */
+void libcras_client_destroy(struct libcras_client *client);
+
+/*
+ * Connects a client to the running server.
+ * Waits forever (until interrupted or connected).
+ * Args:
+ *    client - pointer returned from "libcras_client_create".
+ * Returns:
+ *    0 on success, or a negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_client_connect(struct libcras_client *client)
+{
+	return client->connect(client->client_);
+}
+
+/*
+ * Connects a client to the running server, retries until timeout.
+ * Args:
+ *    client - pointer returned from "libcras_client_create".
+ *    timeout_ms - timeout in milliseconds or negative to wait forever.
+ * Returns:
+ *    0 on success, or a negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_client_connect_timeout(struct libcras_client *client,
+					  unsigned int timeout_ms)
+{
+	return client->connect_timeout(client->client_, timeout_ms);
+}
+
+/*
+ * Wait up to 1 second for the client thread to complete the server connection.
+ *
+ * After libcras_client_run_thread() is executed, this function can be
+ * used to ensure that the connection has been established with the server and
+ * ensure that any information about the server is up to date. If
+ * libcras_client_run_thread() has not yet been executed, or
+ * libcras_client_stop() was executed and thread isn't running, then this
+ * function returns -EINVAL.
+ *
+ * Args:
+ *    client - pointer returned from "libcras_client_create".
+ * Returns:
+ *    0 on success, or a negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_client_connected_wait(struct libcras_client *client)
+{
+	return client->connected_wait(client->client_);
+}
+
+/*
+ * Begins running the client control thread.
+ *
+ * Required for stream operations and other operations noted below.
+ *
+ * Args:
+ *    client - pointer returned from "libcras_client_create".
+ * Returns:
+ *    0 on success, or a negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_client_run_thread(struct libcras_client *client)
+{
+	return client->run_thread(client->client_);
+}
+
+/*
+ * Stops running a client.
+ * This function is executed automatically by cras_client_destroy().
+ * Args:
+ *    client - pointer returned from "libcras_client_create".
+ * Returns:
+ *    0 on success or if the thread was already stopped, -EINVAL if the client
+ *    isn't valid.
+ */
+DISABLE_CFI_ICALL
+inline int libcras_client_stop(struct libcras_client *client)
+{
+	return client->stop(client->client_);
+}
+
+/*
+ * Creates a pinned stream and return the stream id or < 0 on error.
+ *
+ * Requires execution of libcras_client_run_thread(), and an active
+ * connection to the audio server.
+ *
+ * Args:
+ *    client - pointer returned from "libcras_client_create".
+ *    dev_idx - Index of the device to attach the newly created stream.
+ *    stream_id_out - On success will be filled with the new stream id.
+ *        Guaranteed to be set before any callbacks are made.
+ *    params - The pointer specifying the parameters for the stream.
+ *        (returned from libcras_stream_params_create)
+ * Returns:
+ *    0 on success, negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_client_add_pinned_stream(
+	struct libcras_client *client, uint32_t dev_idx,
+	cras_stream_id_t *stream_id_out, struct libcras_stream_params *params)
+{
+	return client->add_pinned_stream(client->client_, dev_idx,
+					 stream_id_out, params->params_);
+}
+
+/*
+ * Removes a currently playing/capturing stream.
+ *
+ * Requires execution of libcras_client_run_thread().
+ *
+ * Args:
+ *    client - pointer returned from "libcras_client_create".
+ *    stream_id - ID returned from libcras_client_add_stream to identify
+ *        the stream to remove.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_client_rm_stream(struct libcras_client *client,
+				    cras_stream_id_t stream_id)
+{
+	return client->rm_stream(client->client_, stream_id);
+}
+
+/*
+ * Sets the volume scaling factor for the given stream.
+ *
+ * Requires execution of cras_client_run_thread().
+ *
+ * Args:
+ *    client - pointer returned from "libcras_client_create".
+ *    stream_id - ID returned from libcras_client_add_stream.
+ *    volume_scaler - 0.0-1.0 the new value to scale this stream by.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_client_set_stream_volume(struct libcras_client *client,
+					    cras_stream_id_t stream_id,
+					    float volume_scaler)
+{
+	return client->set_stream_volume(client->client_, stream_id,
+					 volume_scaler);
+}
+
+/*
+ * Gets the current list of audio nodes.
+ *
+ * Args:
+ *    client - Pointer returned from "libcras_client_create".
+ *    direction - Input or output.
+ *    nodes - Array that will be filled with libcras_node_info pointers.
+ *    num - Pointer to store the size of the array.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ *    Remember to call libcras_node_info_array_destroy to free the array.
+ */
+DISABLE_CFI_ICALL
+inline int libcras_client_get_nodes(struct libcras_client *client,
+				    enum CRAS_STREAM_DIRECTION direction,
+				    struct libcras_node_info ***nodes,
+				    size_t *num)
+{
+	return client->get_nodes(client->client_, direction, nodes, num);
+}
+
+/*
+ * Gets the default output buffer size.
+ * Args:
+ *    client - Pointer returned from "libcras_client_create".
+ *    size - The pointer to save the result.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int
+libcras_client_get_default_output_buffer_size(struct libcras_client *client,
+					      int *size)
+{
+	return client->get_default_output_buffer_size(client->client_, size);
+}
+
+/*
+ * Gets the AEC group ID.
+ * Args:
+ *    client - Pointer returned from "libcras_client_create".
+ *    id - The pointer to save the result.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_client_get_aec_group_id(struct libcras_client *client,
+					   int *id)
+{
+	return client->get_aec_group_id(client->client_, id);
+}
+
+/*
+ * Gets whether AEC is supported.
+ * Args:
+ *    client - Pointer returned from "libcras_client_create".
+ *    supported - The pointer to save the result.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_client_get_aec_supported(struct libcras_client *client,
+					    int *supported)
+{
+	return client->get_aec_supported(client->client_, supported);
+}
+
+/*
+ * Gets whether the system is muted.
+ * Args:
+ *    client - Pointer returned from "libcras_client_create".
+ *    muted - The pointer to save the result.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_client_get_system_muted(struct libcras_client *client,
+					   int *muted)
+{
+	return client->get_aec_group_id(client->client_, muted);
+}
+
+/*
+ * Mutes or unmutes the system.
+ * Args:
+ *    client - Pointer returned from "libcras_client_create".
+ *    mute - 1 is to mute and 0 is to unmute.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_client_set_system_mute(struct libcras_client *client,
+					  int mute)
+{
+	return client->set_system_mute(client->client_, mute);
+}
+
+/*
+ * Gets the index of the loopback device.
+ * Args:
+ *    client - Pointer returned from "libcras_client_create".
+ *    idx - The pointer to save the result.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_client_get_loopback_dev_idx(struct libcras_client *client,
+					       int *idx)
+{
+	return client->get_loopback_dev_idx(client->client_, idx);
+}
+
+/*
+ * Creates a new struct to save stream params.
+ * Returns:
+ *    If success, return a valid libcras_stream_params pointer. Otherwise,
+ *    return NULL.
+ */
+struct libcras_stream_params *libcras_stream_params_create();
+
+/*
+ * Destroys a stream params instance.
+ * Args:
+ *    params - The pointer returned from libcras_stream_params_create.
+ */
+void libcras_stream_params_destroy(struct libcras_stream_params *params);
+
+/*
+ * Setup stream configuration parameters.
+ * Args:
+ *    params - The pointer returned from libcras_stream_params_create.
+ *    direction - Playback(CRAS_STREAM_OUTPUT) or capture(CRAS_STREAM_INPUT).
+ *    buffer_frames - total number of audio frames to buffer (dictates latency).
+ *    cb_threshold - For playback, call back for more data when the buffer
+ *        reaches this level. For capture, this is ignored (Audio callback will
+ *        be called when buffer_frames have been captured).
+ *    stream_type - Media or talk (currently only support "default").
+ *    client_type - The client type, like Chrome or CrOSVM.
+ *    flags - Currently only used for CRAS_INPUT_STREAM_FLAG.
+ *    user_data - Pointer that will be passed to the callback.
+ *    stream_cb - The audio callback. Called when audio is needed(playback) or
+ *        ready(capture).
+ *    err_cb - Called when there is an error with the stream.
+ *    rate - The sample rate of the audio stream.
+ *    format - The format of the audio stream.
+ *    num_channels - The number of channels of the audio stream.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_stream_params_set(
+	struct libcras_stream_params *params,
+	enum CRAS_STREAM_DIRECTION direction, size_t buffer_frames,
+	size_t cb_threshold, enum CRAS_STREAM_TYPE stream_type,
+	enum CRAS_CLIENT_TYPE client_type, uint32_t flags, void *user_data,
+	libcras_stream_cb_t stream_cb, cras_error_cb_t err_cb, size_t rate,
+	snd_pcm_format_t format, size_t num_channels)
+{
+	return params->set(params->params_, direction, buffer_frames,
+			   cb_threshold, stream_type, client_type, flags,
+			   user_data, stream_cb, err_cb, rate, format,
+			   num_channels);
+}
+
+/*
+ * Sets channel layout on given stream parameter.
+ * Args:
+ *    params - The pointer returned from libcras_stream_params_create.
+ *    length - The length of the array.
+ *    layout - An integer array representing the position of each channel in
+ *    enum CRAS_CHANNEL.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int
+libcras_stream_params_set_channel_layout(struct libcras_stream_params *params,
+					 int length, const int8_t *layout)
+{
+	return params->set_channel_layout(params->params_, length, layout);
+}
+
+/*
+ * Enables AEC on given stream parameter.
+ * Args:
+ *    params - The pointer returned from libcras_stream_params_create.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int
+libcras_stream_params_enable_aec(struct libcras_stream_params *params)
+{
+	params->enable_aec(params->params_);
+	return 0;
+}
+
+/*
+ * Gets stream id from the callback data.
+ * Args:
+ *    data - The pointer passed to the callback function.
+ *    id - The pointer to save the stream id.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int
+libcras_stream_cb_data_get_stream_id(struct libcras_stream_cb_data *data,
+				     cras_stream_id_t *id)
+{
+	return data->get_stream_id(data->data_, id);
+}
+
+/*
+ * Gets stream buf from the callback data.
+ * Args:
+ *    data - The pointer passed to the callback function.
+ *    buf - The pointer to save the stream buffer.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_stream_cb_data_get_buf(struct libcras_stream_cb_data *data,
+					  uint8_t **buf)
+{
+	return data->get_buf(data->data_, buf);
+}
+
+/*
+ * Gets how many frames to read or play from the callback data.
+ * Args:
+ *    data - The pointer passed to the callback function.
+ *    frames - The pointer to save the number of frames.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int
+libcras_stream_cb_data_get_frames(struct libcras_stream_cb_data *data,
+				  unsigned int *frames)
+{
+	return data->get_frames(data->data_, frames);
+}
+
+/*
+ * Gets the latency from the callback data.
+ * Args:
+ *    data - The pointer passed to the callback function.
+ *    frames - The timespec pointer to save the latency.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int
+libcras_stream_cb_data_get_latency(struct libcras_stream_cb_data *data,
+				   struct timespec *latency)
+{
+	return data->get_latency(data->data_, latency);
+}
+
+/*
+ * Gets the user data from the callback data.
+ * Args:
+ *    data - The pointer passed to the callback function.
+ *    frames - The pointer to save the user data.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int
+libcras_stream_cb_data_get_usr_arg(struct libcras_stream_cb_data *data,
+				   void **user_arg)
+{
+	return data->get_user_arg(data->data_, user_arg);
+}
+
+/*
+ * Destroys a node info instance.
+ * Args:
+ *    node - The libcras_node_info pointer to destroy.
+ */
+void libcras_node_info_destroy(struct libcras_node_info *node);
+
+/*
+ * Destroys a node info array.
+ * Args:
+ *    nodes - The libcras_node_info pointer array to destroy.
+ *    num - The size of the array.
+ */
+void libcras_node_info_array_destroy(struct libcras_node_info **nodes,
+				     size_t num);
+
+/*
+ * Gets ID from the node info pointer.
+ * Args:
+ *    node - The node info pointer. (Returned from libcras_client_get_nodes)
+ *    id - The pointer to save ID.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_node_info_get_id(struct libcras_node_info *node,
+				    uint64_t *id)
+{
+	return node->get_id(node->node_, id);
+}
+
+/*
+ * Gets device index from the node info pointer.
+ * Args:
+ *    node - The node info pointer. (Returned from libcras_client_get_nodes)
+ *    dev_idx - The pointer to the device index.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_node_info_get_dev_idx(struct libcras_node_info *node,
+					 uint32_t *dev_idx)
+{
+	return node->get_dev_idx(node->node_, dev_idx);
+}
+
+/*
+ * Gets node index from the node info pointer.
+ * Args:
+ *    node - The node info pointer. (Returned from libcras_client_get_nodes)
+ *    node_idx - The pointer to save the node index.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_node_info_get_node_idx(struct libcras_node_info *node,
+					  uint32_t *node_idx)
+{
+	return node->get_node_idx(node->node_, node_idx);
+}
+
+/*
+ * Gets the max supported channels from the node info pointer.
+ * Args:
+ *    node - The node info pointer. (Returned from libcras_client_get_nodes)
+ *    max_supported_channels - The pointer to save the result.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int
+libcras_node_info_get_max_supported_channels(struct libcras_node_info *node,
+					     uint32_t *max_supported_channels)
+{
+	return node->get_max_supported_channels(node->node_,
+						max_supported_channels);
+}
+
+/*
+ * Gets whether the node is plugged from the node info pointer.
+ * Args:
+ *    node - The node info pointer. (Returned from libcras_client_get_nodes)
+ *    plugged - The pointer to save the result.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_node_info_is_plugged(struct libcras_node_info *node,
+					bool *plugged)
+{
+	return node->is_plugged(node->node_, plugged);
+}
+
+/*
+ * Gets whether the node is active from the node info pointer.
+ * Args:
+ *    node - The node info pointer. (Returned from libcras_client_get_nodes)
+ *    active - The pointer to save the result.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_node_info_is_active(struct libcras_node_info *node,
+				       bool *active)
+{
+	return node->is_active(node->node_, active);
+}
+
+/*
+ * Gets device type from the node info pointer.
+ * Args:
+ *    node - The node info pointer. (Returned from libcras_client_get_nodes)
+ *    type - The pointer to save the device type.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_node_info_get_type(struct libcras_node_info *node,
+				      char **type)
+{
+	return node->get_type(node->node_, type);
+}
+
+/*
+ * Gets device name from the node info pointer.
+ * Args:
+ *    node - The node info pointer. (Returned from libcras_client_get_nodes)
+ *    name - The pointer to save the device name.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_node_info_get_node_name(struct libcras_node_info *node,
+					   char **name)
+{
+	return node->get_node_name(node->node_, name);
+}
+
+/*
+ * Gets node name from the node info pointer.
+ * Args:
+ *    node - The node info pointer. (Returned from libcras_client_get_nodes)
+ *    name - The pointer to save the node name.
+ * Returns:
+ *    0 on success negative error code on failure (from errno.h).
+ */
+DISABLE_CFI_ICALL
+inline int libcras_node_info_get_dev_name(struct libcras_node_info *node,
+					  char **name)
+{
+	return node->get_dev_name(node->node_, name);
+}
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/cras/src/plc/cras_plc.c b/cras/src/plc/cras_plc.c
index ed42ae9..74c3568 100644
--- a/cras/src/plc/cras_plc.c
+++ b/cras/src/plc/cras_plc.c
@@ -22,6 +22,9 @@
 #define PLC_SBCRL 36 /* SBC Reconvergence sample Length */
 #define PLC_OLAL 16 /* OverLap-Add Length */
 
+#define PLC_WINDOW_SIZE 5
+#define PLC_PL_THRESHOLD 2
+
 /* The pre-computed zero input bit stream of mSBC codec, per HFP 1.7 spec.
  * This mSBC frame will be decoded into all-zero input PCM. */
 static const uint8_t msbc_zero_frame[] = {
@@ -40,6 +43,18 @@
 				      0.13049554f, 0.07489143f, 0.03376389f,
 				      0.00851345f };
 
+/* This structure tracks the packet loss information for last PLC_WINDOW_SIZE
+ * of packets:
+ *    loss_hist - The packet loss history of receiving packets. 1 means lost.
+ *    ptr - The index of the to be updated packet loss status.
+ *    count - The count of lost packets in the window.
+ */
+struct packet_window {
+	uint8_t loss_hist[PLC_WINDOW_SIZE];
+	unsigned int ptr;
+	unsigned int count;
+};
+
 /* The PLC is specifically designed for mSBC. The algorithm searches the
  * history of receiving samples to find the best match samples and constructs
  * substitutions for the lost samples. The selection is based on pattern
@@ -57,23 +72,30 @@
  *                         frame.
  *    zero_frame - A buffer used for storing the samples from decoding the
  *                 mSBC zero frame packet.
+ *    pl_window - A window monitoring how many packets are bad within the recent
+ *                PLC_WINDOW_SIZE of packets. This is used to determine if we
+ *                want to disable the PLC temporarily.
  */
 struct cras_msbc_plc {
 	int16_t hist[PLC_HL + MSBC_FS + PLC_SBCRL + PLC_OLAL];
 	unsigned int best_lag;
 	int handled_bad_frames;
 	int16_t zero_frame[MSBC_FS];
+	struct packet_window *pl_window;
 };
 
 struct cras_msbc_plc *cras_msbc_plc_create()
 {
 	struct cras_msbc_plc *plc =
 		(struct cras_msbc_plc *)calloc(1, sizeof(*plc));
+	plc->pl_window =
+		(struct packet_window *)calloc(1, sizeof(*plc->pl_window));
 	return plc;
 }
 
 void cras_msbc_plc_destroy(struct cras_msbc_plc *plc)
 {
+	free(plc->pl_window);
 	free(plc);
 }
 
@@ -94,14 +116,33 @@
 	}
 }
 
+void update_plc_state(struct packet_window *w, uint8_t is_packet_loss)
+{
+	uint8_t *curr = &w->loss_hist[w->ptr];
+	if (is_packet_loss != *curr) {
+		w->count += (is_packet_loss - *curr);
+		*curr = is_packet_loss;
+	}
+	w->ptr = (w->ptr + 1) % PLC_WINDOW_SIZE;
+}
+
+int possibly_pause_plc(struct packet_window *w)
+{
+	/* The packet loss count comes from a time window and we use it as an
+	 * indicator of our confidence of the PLC algorithm. It is known to
+	 * generate poorer and robotic feeling sounds, when the majority of
+	 * samples in the PLC history buffer are from the concealment results.
+	 */
+	return w->count >= PLC_PL_THRESHOLD;
+}
+
 int cras_msbc_plc_handle_good_frames(struct cras_msbc_plc *state,
 				     const uint8_t *input, uint8_t *output)
 {
 	int16_t *frame_head, *input_samples, *output_samples;
 	if (state->handled_bad_frames == 0) {
-		/* If there was no packet loss before this good frame, there
-		 * is nothing we need to do to the frame so we'll just pass
-		 * the input to output.
+		/* If there was no packet concealment before this good frame,
+		 * we just simply copy the input to output without reconverge.
 		 */
 		memmove(output, input, MSBC_FS * MSBC_SAMPLE_SIZE);
 	} else {
@@ -129,6 +170,7 @@
 		(PLC_HL - MSBC_FS) * MSBC_SAMPLE_SIZE);
 	memcpy(&state->hist[PLC_HL - MSBC_FS], output,
 	       MSBC_FS * MSBC_SAMPLE_SIZE);
+	update_plc_state(state->pl_window, 0);
 	return MSBC_CODE_SIZE;
 }
 
@@ -141,7 +183,7 @@
 		x2 += ((float)x[i]) * x[i];
 		y2 += ((float)y[i]) * y[i];
 	}
-	return sum / sqrt(x2 * y2);
+	return sum / sqrtf(x2 * y2);
 }
 
 int pattern_match(int16_t *hist)
@@ -184,37 +226,60 @@
 	int16_t *frame_head = &state->hist[PLC_HL];
 	size_t pcm_decoded = 0;
 
+	/* mSBC codec is stateful, the history of signal would contribute to the
+	 * decode result state->zero_frame.
+	 */
 	codec->decode(codec, msbc_zero_frame, MSBC_PKT_LEN, state->zero_frame,
 		      MSBC_FS, &pcm_decoded);
 
-	if (state->handled_bad_frames == 0) {
-		/* Finds the best matching samples and amplitude */
-		state->best_lag = pattern_match(state->hist) + PLC_TL;
-		best_match_hist = &state->hist[state->best_lag];
-		scaler = amplitude_match(&state->hist[PLC_HL - MSBC_FS],
-					 best_match_hist);
+	/* The PLC algorithm is more likely to generate bad results that sound
+	 * robotic after severe packet losses happened. Only applying it when
+	 * we are confident.
+	 */
+	if (!possibly_pause_plc(state->pl_window)) {
+		if (state->handled_bad_frames == 0) {
+			/* Finds the best matching samples and amplitude */
+			state->best_lag = pattern_match(state->hist) + PLC_TL;
+			best_match_hist = &state->hist[state->best_lag];
+			scaler = amplitude_match(&state->hist[PLC_HL - MSBC_FS],
+						 best_match_hist);
 
-		/* Constructs the substitution samples */
-		overlap_add(frame_head, 1.0, state->zero_frame, scaler,
-			    best_match_hist);
-		for (int i = PLC_OLAL; i < MSBC_FS; i++)
-			state->hist[PLC_HL + i] =
-				f_to_s16(scaler * best_match_hist[i]);
-		overlap_add(&frame_head[MSBC_FS], scaler,
-			    &best_match_hist[MSBC_FS], 1.0,
-			    &best_match_hist[MSBC_FS]);
+			/* Constructs the substitution samples */
+			overlap_add(frame_head, 1.0, state->zero_frame, scaler,
+				    best_match_hist);
+			for (int i = PLC_OLAL; i < MSBC_FS; i++)
+				state->hist[PLC_HL + i] =
+					f_to_s16(scaler * best_match_hist[i]);
+			overlap_add(&frame_head[MSBC_FS], scaler,
+				    &best_match_hist[MSBC_FS], 1.0,
+				    &best_match_hist[MSBC_FS]);
 
-		memmove(&frame_head[MSBC_FS + PLC_OLAL],
-			&best_match_hist[MSBC_FS + PLC_OLAL],
-			PLC_SBCRL * MSBC_SAMPLE_SIZE);
+			memmove(&frame_head[MSBC_FS + PLC_OLAL],
+				&best_match_hist[MSBC_FS + PLC_OLAL],
+				PLC_SBCRL * MSBC_SAMPLE_SIZE);
+		} else {
+			memmove(frame_head, &state->hist[state->best_lag],
+				(MSBC_FS + PLC_SBCRL + PLC_OLAL) *
+					MSBC_SAMPLE_SIZE);
+		}
+		state->handled_bad_frames++;
 	} else {
-		memmove(frame_head, &state->hist[state->best_lag],
-			(MSBC_FS + PLC_SBCRL + PLC_OLAL) * MSBC_SAMPLE_SIZE);
+		/* This is a case similar to receiving a good frame with all
+		 * zeros, we set handled_bad_frames to zero to prevent the
+		 * following good frame from being concealed to reconverge with
+		 * the zero frames we fill in. The concealment result sounds
+		 * more artificial and weird than simply writing zeros and
+		 * following samples.
+		 */
+		memmove(frame_head, state->zero_frame, MSBC_CODE_SIZE);
+		memset(frame_head + MSBC_CODE_SIZE, 0,
+		       (PLC_SBCRL + PLC_OLAL) * MSBC_SAMPLE_SIZE);
+		state->handled_bad_frames = 0;
 	}
-	state->handled_bad_frames++;
 
 	memcpy(output, frame_head, MSBC_CODE_SIZE);
 	memmove(state->hist, &state->hist[MSBC_FS],
 		(PLC_HL + PLC_SBCRL + PLC_OLAL) * MSBC_SAMPLE_SIZE);
+	update_plc_state(state->pl_window, 1);
 	return MSBC_CODE_SIZE;
 }
diff --git a/cras/src/plc/cras_plc_test.c b/cras/src/plc/cras_plc_test.c
index 458f125..4b7a6a7 100644
--- a/cras/src/plc/cras_plc_test.c
+++ b/cras/src/plc/cras_plc_test.c
@@ -4,11 +4,13 @@
  */
 
 #include <errno.h>
+#include <getopt.h>
 #include <math.h>
 #include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/param.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
@@ -29,32 +31,65 @@
 	0x6d, 0xb6, 0xdd, 0xdb, 0x6d, 0xb7, 0x76, 0xdb, 0x6c
 };
 
-bool *generate_pl_seq(unsigned pk_count, unsigned loss_count)
+bool *generate_pl_seq(int input_file_size, float pl_percent)
 {
-	bool *seq = (bool *)calloc(pk_count, sizeof(*seq));
+	unsigned pk_count, pl_count;
+	bool *seq;
+
+	pk_count = input_file_size / MSBC_CODE_SIZE;
+	pl_count = pk_count * (pl_percent / 100.0);
+	seq = (bool *)calloc(pk_count, sizeof(*seq));
 	srand(RND_SEED);
-	while (loss_count > 0) {
+	while (pl_count > 0) {
 		bool *missed = &seq[rand() % pk_count];
 		if (!*missed) {
 			*missed = true;
-			loss_count--;
+			pl_count--;
 		}
 	}
 	return seq;
 }
 
-void plc_experiment(char *input_filename, float pl_percent, bool with_plc)
+/* pl_hex is expected to be consecutive bytes(two chars) in hex format.*/
+bool *parse_pl_hex(int input_file_size, const char *pl_hex)
+{
+	char tmp[3];
+	uint8_t val = 0;
+	int i, pl_hex_len, seq_len;
+	bool *seq;
+
+	pl_hex_len = strlen(pl_hex);
+	seq_len = MAX(1 + input_file_size / MSBC_CODE_SIZE, pl_hex_len * 4);
+	seq = (bool *)calloc(seq_len, sizeof(*seq));
+
+	for (i = 0; i < seq_len; i++) {
+		/* If sequence is longer then the provided pl_hex, leave the
+		 * rest to all zeros. */
+		if (i > pl_hex_len * 4)
+			break;
+		if (i % 8 == 0) {
+			memcpy(tmp, pl_hex + i / 4, 2);
+			tmp[2] = '\0';
+			val = strtol(tmp, NULL, 16);
+		}
+		seq[i] = val & 1U;
+		val >>= 1;
+	}
+	printf("pl_hex string maps to %ld ms, total sequence size %f ms\n",
+	       strlen(pl_hex) * 30, seq_len * 7.5f);
+	return seq;
+}
+
+void plc_experiment(const char *input_filename, bool *pl_seq, bool with_plc)
 {
 	char output_filename[255];
 	int input_fd, output_fd, rc;
-	struct stat st;
-	bool *pl_seq;
 	struct cras_audio_codec *msbc_input = cras_msbc_codec_create();
 	struct cras_audio_codec *msbc_output = cras_msbc_codec_create();
 	struct cras_msbc_plc *plc = cras_msbc_plc_create();
 	uint8_t buffer[MSBC_CODE_SIZE], packet_buffer[MSBC_PKT_FRAME_LEN];
 	size_t encoded, decoded;
-	unsigned pk_count, pl_count, count = 0;
+	unsigned count = 0;
 
 	input_fd = open(input_filename, O_RDONLY);
 	if (input_fd == -1) {
@@ -63,9 +98,9 @@
 	}
 
 	if (with_plc)
-		sprintf(output_filename, "output_%2.2f_plc.raw", pl_percent);
+		sprintf(output_filename, "output_with_plc.raw");
 	else
-		sprintf(output_filename, "output_%2.2f_zero.raw", pl_percent);
+		sprintf(output_filename, "output_with_zero.raw");
 
 	output_fd = open(output_filename, O_CREAT | O_RDWR | O_TRUNC, 0644);
 	if (output_fd == -1) {
@@ -74,11 +109,6 @@
 		return;
 	}
 
-	fstat(input_fd, &st);
-	pk_count = st.st_size / MSBC_CODE_SIZE;
-	pl_count = pk_count * (pl_percent / 100.0);
-	pl_seq = generate_pl_seq(pk_count, pl_count);
-
 	while (1) {
 		rc = read(input_fd, buffer, MSBC_CODE_SIZE);
 		if (rc < 0) {
@@ -117,19 +147,77 @@
 	}
 }
 
+static void show_usage()
+{
+	printf("This test only supports reading/writing raw audio with format:\n"
+	       "\t16000 sample rate, mono channel, S16_LE\n");
+	printf("--help - Print this usage.\n");
+	printf("--input_file - path to an audio file.\n");
+	printf("--pattern - Hex string representing consecutive packets'"
+	       "status.\n");
+	printf("--random - Percentage of packet loss.\n");
+}
+
 int main(int argc, char **argv)
 {
-	if (argc != 3) {
-		printf("Usage: cras_plc_test input.raw pl_percentage\n"
-		       "This test only supports reading/writing files with "
-		       "format:\n"
-		       "- raw pcm\n"
-		       "- 16000 sample rate\n"
-		       "- mono channel\n"
-		       "- S16_LE sample format\n");
+	int fd;
+	struct stat st;
+	float pl_percent;
+	int pl_percent_set = 0;
+	int option_character;
+	int option_index = 0;
+	const char *input_file = NULL;
+	const char *pl_hex = NULL;
+	bool *pl_seq = NULL;
+	static struct option long_options[] = {
+		{ "help", no_argument, NULL, 'h' },
+		{ "input", required_argument, NULL, 'i' },
+		{ "pattern", required_argument, NULL, 'p' },
+		{ "random", required_argument, NULL, 'r' },
+		{ NULL, 0, NULL, 0 },
+	};
+
+	while (true) {
+		option_character = getopt_long(argc, argv, "i:r:p:h",
+					       long_options, &option_index);
+		if (option_character == -1)
+			break;
+		switch (option_character) {
+		case 'h':
+			show_usage();
+			break;
+		case 'i':
+			input_file = optarg;
+			break;
+		case 'p':
+			pl_hex = optarg;
+			break;
+		case 'r':
+			pl_percent = atof(optarg);
+			pl_percent_set = 1;
+			break;
+		default:
+			break;
+		}
+	}
+
+	if ((!pl_percent_set && !pl_hex) || !input_file) {
+		show_usage();
 		return 1;
 	}
 
-	plc_experiment(argv[1], atof(argv[2]), true);
-	plc_experiment(argv[1], atof(argv[2]), false);
+	fd = open(input_file, O_RDONLY);
+	if (fd == -1) {
+		fprintf(stderr, "Cannout open input file %s\n", input_file);
+		return 1;
+	}
+	fstat(fd, &st);
+	close(fd);
+	if (pl_percent_set)
+		pl_seq = generate_pl_seq(st.st_size, pl_percent);
+	else if (pl_hex)
+		pl_seq = parse_pl_hex(st.st_size, pl_hex);
+
+	plc_experiment(input_file, pl_seq, true);
+	plc_experiment(input_file, pl_seq, false);
 }
diff --git a/cras/src/plc/parse_sco.py b/cras/src/plc/parse_sco.py
new file mode 100755
index 0000000..c50df15
--- /dev/null
+++ b/cras/src/plc/parse_sco.py
@@ -0,0 +1,107 @@
+# -*- coding: utf-8 -*-
+# Copyright 2020 The Chromium OS 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 script to extract raw SCO RX packets from btsnoop.
+Use 'btmon -S' to dump SCO traffic from btsnoop file.
+Trim the btsnoop output to just the SCO traffic period.
+Then execute 'python parse-sco.py <btsnoop-output>'
+"""
+
+import atexit
+import binascii
+import os
+import re
+import sys
+
+
+class SCOParser:
+  """
+  Parser for grepping SCO packets
+  """
+
+  def __init__(self):
+    # On old releases, +CIEV: 4,1 indicates the start point of call session
+    # c 31 0d 0a 9a     ..+CIEV: 4,1..
+    self.call_start_re = re.compile(r'.*?\+CIEV:\s4,(\d).*?')
+
+    # > SCO Data RX: Handle 257 flags 0x00 dlen 60           #13826 [hci0] 650.388305
+    #         00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
+    #         00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
+    #         00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
+    #         00 00 00 00 00 00 00 00 00 00 00 00
+    self.sco_rx_re = re.compile(r'.*?SCO\sData\sRX.*?flags\s0x(\d+).*?')
+    self.sco_f = None
+    self.output_idx = 0
+    self.pk_count = 0
+    self.pl_count = 0
+
+    atexit.register(self._cleanup)
+
+  def _cleanup(self):
+    if self.sco_f is not None:
+      print(
+          "Current file contains %d packets (%d with erroneous status flag)" %
+          (self.pk_count, self.pl_count))
+      self.pk_count = 0
+      self.pl_count = 0
+      self.sco_f.close()
+
+  def _new_session(self):
+    if self.sco_f is not None:
+      close(self.sco_f)
+
+    new_file = "sco_file_%d" % self.output_idx
+    print("Record to %s" % new_file)
+    self.sco_f = open(new_file, 'wb')
+    self.output_idx += 1
+
+    return self.sco_f
+
+  def parse(self, filename):
+    if not os.path.exists(filename):
+      print("%s doesn't exist" % filename)
+      return
+
+    print("Start parsing %s" % filename)
+    parse_rx_data = 0
+    with open(filename, "r") as f:
+      for line in f.readlines():
+        if parse_rx_data > 0:
+          self.sco_f.write(binascii.unhexlify(''.join(line[:56].split())))
+          parse_rx_data = (parse_rx_data + 1) % 5
+
+        # Start a new session and output following SCO data to a new file
+        match = self.call_start_re.search(line)
+        if match and (1 == int(match.group(1))):
+          self._new_session()
+          continue
+
+        match = self.sco_rx_re.search(line)
+        if match:
+          if self.sco_f is None:
+            self._new_session()
+
+          self.pk_count += 1
+
+          status_flag = int(match.group(1))
+          hdr = ['01', str(status_flag) + '1', '3c']
+          if status_flag != 0:
+            self.pl_count += 1
+
+          self.sco_f.write(binascii.unhexlify(''.join(hdr)))
+          parse_rx_data = 1
+
+
+def main(argv):
+  if len(argv) < 1:
+    print("parse_sco.py [btsnoop.txt]")
+    return
+
+  p = SCOParser()
+  p.parse(argv[0])
+
+
+if __name__ == "__main__":
+  main(sys.argv[1:])
diff --git a/cras/src/server/audio_thread.c b/cras/src/server/audio_thread.c
index d7ef8bd..48bb0dc 100644
--- a/cras/src/server/audio_thread.c
+++ b/cras/src/server/audio_thread.c
@@ -37,6 +37,13 @@
  */
 #define MAX_CONTINUOUS_ZERO_SLEEP_COUNT 2
 
+/*
+ * If the number of continuous zero sleep is equal to this limit, the value
+ * will be recorded immediately. It can ensure all busyloop will be recorded
+ * even if the busyloop does not stop.
+ */
+#define MAX_CONTINUOUS_ZERO_SLEEP_METRIC_LIMIT 1000
+
 /* Messages that can be sent from the main context to the audio thread. */
 enum AUDIO_THREAD_COMMAND {
 	AUDIO_THREAD_ADD_OPEN_DEV,
@@ -117,16 +124,16 @@
 
 struct iodev_callback_list {
 	int fd;
-	int is_write;
-	int enabled;
+	int events;
+	enum AUDIO_THREAD_EVENTS_CB_TRIGGER trigger;
 	thread_callback cb;
 	void *cb_data;
 	struct pollfd *pollfd;
 	struct iodev_callback_list *prev, *next;
 };
 
-static void _audio_thread_add_callback(int fd, thread_callback cb, void *data,
-				       int is_write)
+void audio_thread_add_events_callback(int fd, thread_callback cb, void *data,
+				      int events)
 {
 	struct iodev_callback_list *iodev_cb;
 
@@ -139,22 +146,12 @@
 	iodev_cb->fd = fd;
 	iodev_cb->cb = cb;
 	iodev_cb->cb_data = data;
-	iodev_cb->enabled = 1;
-	iodev_cb->is_write = is_write;
+	iodev_cb->trigger = TRIGGER_POLL;
+	iodev_cb->events = events;
 
 	DL_APPEND(iodev_callbacks, iodev_cb);
 }
 
-void audio_thread_add_callback(int fd, thread_callback cb, void *data)
-{
-	_audio_thread_add_callback(fd, cb, data, 0);
-}
-
-void audio_thread_add_write_callback(int fd, thread_callback cb, void *data)
-{
-	_audio_thread_add_callback(fd, cb, data, 1);
-}
-
 void audio_thread_rm_callback(int fd)
 {
 	struct iodev_callback_list *iodev_cb;
@@ -168,13 +165,14 @@
 	}
 }
 
-void audio_thread_enable_callback(int fd, int enabled)
+void audio_thread_config_events_callback(
+	int fd, enum AUDIO_THREAD_EVENTS_CB_TRIGGER trigger)
 {
 	struct iodev_callback_list *iodev_cb;
 
 	DL_FOREACH (iodev_callbacks, iodev_cb) {
 		if (iodev_cb->fd == fd) {
-			iodev_cb->enabled = !!enabled;
+			iodev_cb->trigger = trigger;
 			return;
 		}
 	}
@@ -445,7 +443,8 @@
 {
 	int rc;
 
-	rc = dev_io_append_stream(&thread->open_devs[stream->direction], stream,
+	rc = dev_io_append_stream(&thread->open_devs[CRAS_STREAM_OUTPUT],
+				  &thread->open_devs[CRAS_STREAM_INPUT], stream,
 				  iodevs, num_iodevs);
 	if (rc < 0)
 		return rc;
@@ -557,8 +556,11 @@
 	si->runtime_nsec = time_since.tv_nsec;
 }
 
-/* Handle a message sent to the playback thread */
-static int handle_playback_thread_message(struct audio_thread *thread)
+/* Handle a message sent from main thread to the audio thread.
+ * Returns:
+ *    Error code when reading or sending message fails.
+ */
+static int handle_audio_thread_message(struct audio_thread *thread)
 {
 	uint8_t buf[256];
 	struct audio_thread_msg *msg = (struct audio_thread_msg *)buf;
@@ -713,7 +715,7 @@
 	err = audio_thread_send_response(thread, ret);
 	if (err < 0)
 		return err;
-	return ret;
+	return 0;
 }
 
 /* Returns the number of active streams plus the number of active devices. */
@@ -732,7 +734,7 @@
 	clock_gettime(CLOCK_MONOTONIC_RAW, &now);
 	add_timespecs(&min_ts, &now);
 	ret = dev_io_next_output_wake(&thread->open_devs[CRAS_STREAM_OUTPUT],
-				      &min_ts, &now);
+				      &min_ts);
 	ret += dev_io_next_input_wake(&thread->open_devs[CRAS_STREAM_INPUT],
 				      &min_ts);
 	if (timespec_after(&min_ts, &now))
@@ -742,13 +744,10 @@
 }
 
 static struct pollfd *add_pollfd(struct audio_thread *thread, int fd,
-				 int is_write)
+				 int events)
 {
 	thread->pollfds[thread->num_pollfds].fd = fd;
-	if (is_write)
-		thread->pollfds[thread->num_pollfds].events = POLLOUT;
-	else
-		thread->pollfds[thread->num_pollfds].events = POLLIN;
+	thread->pollfds[thread->num_pollfds].events = events;
 	thread->num_pollfds++;
 	if (thread->num_pollfds >= thread->pollfds_size) {
 		thread->pollfds_size *= 2;
@@ -796,7 +795,18 @@
 			busyloop_count++;
 			cras_audio_thread_event_busyloop();
 		}
+		if (continuous_zero_sleep_count ==
+		    MAX_CONTINUOUS_ZERO_SLEEP_METRIC_LIMIT)
+			cras_server_metrics_busyloop_length(
+				continuous_zero_sleep_count);
+
 	} else {
+		if (continuous_zero_sleep_count >=
+			    MAX_CONTINUOUS_ZERO_SLEEP_COUNT &&
+		    continuous_zero_sleep_count <
+			    MAX_CONTINUOUS_ZERO_SLEEP_METRIC_LIMIT)
+			cras_server_metrics_busyloop_length(
+				continuous_zero_sleep_count);
 		continuous_zero_sleep_count = 0;
 	}
 }
@@ -829,6 +839,7 @@
 	while (1) {
 		struct timespec *wait_ts;
 		struct iodev_callback_list *iodev_cb;
+		int non_empty;
 
 		wait_ts = NULL;
 		thread->num_pollfds = 1;
@@ -838,6 +849,9 @@
 			   &thread->open_devs[CRAS_STREAM_INPUT],
 			   thread->remix_converter);
 
+		non_empty = dev_io_check_non_empty_state_transition(
+			thread->open_devs[CRAS_STREAM_OUTPUT]);
+
 		if (fill_next_sleep_interval(thread, &ts))
 			wait_ts = &ts;
 
@@ -845,10 +859,12 @@
 		thread->num_pollfds = 1;
 
 		DL_FOREACH (iodev_callbacks, iodev_cb) {
-			if (!iodev_cb->enabled)
+			if (iodev_cb->trigger != TRIGGER_POLL) {
+				iodev_cb->pollfd = NULL;
 				continue;
+			}
 			iodev_cb->pollfd = add_pollfd(thread, iodev_cb->fd,
-						      iodev_cb->is_write);
+						      iodev_cb->events);
 			if (!iodev_cb->pollfd)
 				goto restart_poll_loop;
 		}
@@ -859,7 +875,7 @@
 				int fd = dev_stream_poll_stream_fd(curr);
 				if (fd < 0)
 					continue;
-				if (!add_pollfd(thread, fd, 0))
+				if (!add_pollfd(thread, fd, POLLIN))
 					goto restart_poll_loop;
 			}
 		}
@@ -868,7 +884,7 @@
 				int fd = dev_stream_poll_stream_fd(curr);
 				if (fd < 0)
 					continue;
-				if (!add_pollfd(thread, fd, 0))
+				if (!add_pollfd(thread, fd, POLLIN))
 					goto restart_poll_loop;
 			}
 		}
@@ -876,7 +892,7 @@
 		log_busyloop(wait_ts);
 
 		ATLOG(atlog, AUDIO_THREAD_SLEEP, wait_ts ? wait_ts->tv_sec : 0,
-		      wait_ts ? wait_ts->tv_nsec : 0, 0);
+		      wait_ts ? wait_ts->tv_nsec : 0, non_empty);
 		if (wait_ts)
 			check_busyloop(wait_ts);
 
@@ -886,21 +902,33 @@
 
 		rc = ppoll(thread->pollfds, thread->num_pollfds, wait_ts, NULL);
 		ATLOG(atlog, AUDIO_THREAD_WAKE, rc, 0, 0);
+
+		/* Handle callbacks registered by TRIGGER_WAKEUP */
+		DL_FOREACH (iodev_callbacks, iodev_cb) {
+			if (iodev_cb->trigger == TRIGGER_WAKEUP) {
+				ATLOG(atlog, AUDIO_THREAD_IODEV_CB, 0, 0, 0);
+				iodev_cb->cb(iodev_cb->cb_data, 0);
+			}
+		}
+
+		/* If there's no pollfd ready to handle. */
 		if (rc <= 0)
 			continue;
 
 		if (thread->pollfds[0].revents & POLLIN) {
-			rc = handle_playback_thread_message(thread);
+			rc = handle_audio_thread_message(thread);
 			if (rc < 0)
 				syslog(LOG_ERR, "handle message %d", rc);
 		}
 
 		DL_FOREACH (iodev_callbacks, iodev_cb) {
 			if (iodev_cb->pollfd &&
-			    iodev_cb->pollfd->revents & (POLLIN | POLLOUT)) {
+			    iodev_cb->pollfd->revents & iodev_cb->events) {
 				ATLOG(atlog, AUDIO_THREAD_IODEV_CB,
-				      iodev_cb->is_write, 0, 0);
-				iodev_cb->cb(iodev_cb->cb_data);
+				      iodev_cb->pollfd->revents,
+				      iodev_cb->events, 0);
+				iodev_cb->cb(iodev_cb->cb_data,
+					     iodev_cb->pollfd->revents);
 			}
 		}
 	}
diff --git a/cras/src/server/audio_thread.h b/cras/src/server/audio_thread.h
index 5e5e995..34b4786 100644
--- a/cras/src/server/audio_thread.h
+++ b/cras/src/server/audio_thread.h
@@ -45,11 +45,24 @@
 	struct cras_fmt_conv *remix_converter;
 };
 
+/*
+ * Enum to specify how a registered event callback be triggered.
+ * TRIGGER_NONE - Callback will not be triggered.
+ * TRIGGER_POLL - Triggered by poll given fd and revent.
+ * TRIGGER_WAKEUP - Triggered everytime when audio thread wakes up.
+ */
+enum AUDIO_THREAD_EVENTS_CB_TRIGGER {
+	TRIGGER_NONE,
+	TRIGGER_POLL,
+	TRIGGER_WAKEUP,
+};
+
 /* Callback function to be handled in main loop in audio thread.
  * Args:
  *    data - The data for callback function.
+ *    revent - The returned event from ppoll().
  */
-typedef int (*thread_callback)(void *data);
+typedef int (*thread_callback)(void *data, int revent);
 
 /* Creates an audio thread.
  * Returns:
@@ -83,23 +96,17 @@
 int audio_thread_is_dev_open(struct audio_thread *thread,
 			     struct cras_iodev *dev);
 
-/* Adds an thread_callback to audio thread.
+/* Adds a thread_callback to audio thread for requested events. By default
+ * the callback trigger is set to TRIGGER_POLL.
  * Args:
  *    fd - The file descriptor to be polled for the callback.
- *      The callback will be called when fd is readable.
+ *      The callback will be called when any of requested events matched.
  *    cb - The callback function.
  *    data - The data for the callback function.
+ *    events - The requested events to ppoll().
  */
-void audio_thread_add_callback(int fd, thread_callback cb, void *data);
-
-/* Adds an thread_callback to audio thread.
- * Args:
- *    fd - The file descriptor to be polled for the callback.
- *      The callback will be called when fd is writeable.
- *    cb - The callback function.
- *    data - The data for the callback function.
- */
-void audio_thread_add_write_callback(int fd, thread_callback cb, void *data);
+void audio_thread_add_events_callback(int fd, thread_callback cb, void *data,
+				      int events);
 
 /* Removes an thread_callback from audio thread.
  * Args:
@@ -114,8 +121,13 @@
  */
 int audio_thread_rm_callback_sync(struct audio_thread *thread, int fd);
 
-/* Enables or Disabled the callback associated with fd. */
-void audio_thread_enable_callback(int fd, int enabled);
+/* Configures the callback associated with fd when it should be triggerred.
+ * Args:
+ *    fd - The file descriptor associate to the callback.
+ *    trigger - Specifies how the callback should be triggered.
+ */
+void audio_thread_config_events_callback(
+	int fd, enum AUDIO_THREAD_EVENTS_CB_TRIGGER trigger);
 
 /* Starts a thread created with audio_thread_create.
  * Args:
diff --git a/cras/src/server/config/cras_board_config.c b/cras/src/server/config/cras_board_config.c
index 9acdf82..e36ea3c 100644
--- a/cras/src/server/config/cras_board_config.c
+++ b/cras/src/server/config/cras_board_config.c
@@ -3,59 +3,95 @@
  * found in the LICENSE file.
  */
 
+#include <errno.h>
 #include <syslog.h>
 
 #include "cras_board_config.h"
 #include "iniparser_wrapper.h"
 
-/* Allocate 63 chars + 1 for null where declared. */
-static const unsigned int MAX_INI_NAME_LEN = 63;
-static const unsigned int MAX_KEY_LEN = 63;
 static const int32_t DEFAULT_OUTPUT_BUFFER_SIZE = 512;
 static const int32_t AEC_SUPPORTED_DEFAULT = 0;
 static const int32_t AEC_GROUP_ID_DEFAULT = -1;
+static const int32_t BLUETOOTH_WBS_ENABLED_INI_DEFAULT = 1;
+static const int32_t BLUETOOTH_DEPRIORITIZE_WBS_MIC_INI_DEFAULT = 0;
+static const int32_t HOTWORD_PAUSE_AT_SUSPEND_DEFAULT = 0;
 
 #define CONFIG_NAME "board.ini"
 #define DEFAULT_OUTPUT_BUF_SIZE_INI_KEY "output:default_output_buffer_size"
 #define AEC_SUPPORTED_INI_KEY "processing:aec_supported"
 #define AEC_GROUP_ID_INI_KEY "processing:group_id"
+#define BLUETOOTH_WBS_ENABLED_INI_KEY "bluetooth:wbs_enabled"
+#define BLUETOOTH_DEPRIORITIZE_WBS_MIC_INI_KEY "bluetooth:deprioritize_wbs_mic"
+#define UCM_IGNORE_SUFFIX_KEY "ucm:ignore_suffix"
+#define HOTWORD_PAUSE_AT_SUSPEND "hotword:pause_at_suspend"
 
 void cras_board_config_get(const char *config_path,
 			   struct cras_board_config *board_config)
 {
-	char ini_name[MAX_INI_NAME_LEN + 1];
-	char ini_key[MAX_KEY_LEN + 1];
+	char ini_name[MAX_INI_NAME_LENGTH + 1];
+	char ini_key[MAX_INI_KEY_LENGTH + 1];
+	const char *ptr;
 	dictionary *ini;
 
 	board_config->default_output_buffer_size = DEFAULT_OUTPUT_BUFFER_SIZE;
 	board_config->aec_supported = AEC_SUPPORTED_DEFAULT;
 	board_config->aec_group_id = AEC_GROUP_ID_DEFAULT;
+	board_config->ucm_ignore_suffix = NULL;
+	board_config->bt_wbs_enabled = BLUETOOTH_WBS_ENABLED_INI_DEFAULT;
+	board_config->deprioritize_bt_wbs_mic =
+		BLUETOOTH_DEPRIORITIZE_WBS_MIC_INI_DEFAULT;
 	if (config_path == NULL)
 		return;
 
-	snprintf(ini_name, MAX_INI_NAME_LEN, "%s/%s", config_path, CONFIG_NAME);
-	ini_name[MAX_INI_NAME_LEN] = '\0';
+	snprintf(ini_name, MAX_INI_NAME_LENGTH, "%s/%s", config_path,
+		 CONFIG_NAME);
+	ini_name[MAX_INI_NAME_LENGTH] = '\0';
 	ini = iniparser_load_wrapper(ini_name);
 	if (ini == NULL) {
 		syslog(LOG_DEBUG, "No ini file %s", ini_name);
 		return;
 	}
 
-	snprintf(ini_key, MAX_KEY_LEN, DEFAULT_OUTPUT_BUF_SIZE_INI_KEY);
-	ini_key[MAX_KEY_LEN] = 0;
+	snprintf(ini_key, MAX_INI_KEY_LENGTH, DEFAULT_OUTPUT_BUF_SIZE_INI_KEY);
+	ini_key[MAX_INI_KEY_LENGTH] = 0;
 	board_config->default_output_buffer_size =
 		iniparser_getint(ini, ini_key, DEFAULT_OUTPUT_BUFFER_SIZE);
 
-	snprintf(ini_key, MAX_KEY_LEN, AEC_SUPPORTED_INI_KEY);
-	ini_key[MAX_KEY_LEN] = 0;
+	snprintf(ini_key, MAX_INI_KEY_LENGTH, AEC_SUPPORTED_INI_KEY);
+	ini_key[MAX_INI_KEY_LENGTH] = 0;
 	board_config->aec_supported =
 		iniparser_getint(ini, ini_key, AEC_SUPPORTED_DEFAULT);
 
-	snprintf(ini_key, MAX_KEY_LEN, AEC_GROUP_ID_INI_KEY);
-	ini_key[MAX_KEY_LEN] = 0;
+	snprintf(ini_key, MAX_INI_KEY_LENGTH, AEC_GROUP_ID_INI_KEY);
+	ini_key[MAX_INI_KEY_LENGTH] = 0;
 	board_config->aec_group_id =
 		iniparser_getint(ini, ini_key, AEC_GROUP_ID_DEFAULT);
 
+	snprintf(ini_key, MAX_INI_KEY_LENGTH, BLUETOOTH_WBS_ENABLED_INI_KEY);
+	ini_key[MAX_INI_KEY_LENGTH] = 0;
+	board_config->bt_wbs_enabled = iniparser_getint(
+		ini, ini_key, BLUETOOTH_WBS_ENABLED_INI_DEFAULT);
+
+	snprintf(ini_key, MAX_INI_KEY_LENGTH,
+		 BLUETOOTH_DEPRIORITIZE_WBS_MIC_INI_KEY);
+	ini_key[MAX_INI_KEY_LENGTH] = 0;
+	board_config->deprioritize_bt_wbs_mic = iniparser_getint(
+		ini, ini_key, BLUETOOTH_DEPRIORITIZE_WBS_MIC_INI_DEFAULT);
+
+	snprintf(ini_key, MAX_INI_KEY_LENGTH, UCM_IGNORE_SUFFIX_KEY);
+	ini_key[MAX_INI_KEY_LENGTH] = 0;
+	ptr = iniparser_getstring(ini, ini_key, "");
+	if (ptr) {
+		board_config->ucm_ignore_suffix = strdup(ptr);
+		if (!board_config->ucm_ignore_suffix)
+			syslog(LOG_ERR, "Failed to call strdup: %d", errno);
+	}
+
+	snprintf(ini_key, MAX_INI_KEY_LENGTH, HOTWORD_PAUSE_AT_SUSPEND);
+	ini_key[MAX_INI_KEY_LENGTH] = 0;
+	board_config->hotword_pause_at_suspend = iniparser_getint(
+		ini, ini_key, HOTWORD_PAUSE_AT_SUSPEND_DEFAULT);
+
 	iniparser_freedict(ini);
 	syslog(LOG_DEBUG, "Loaded ini file %s", ini_name);
 }
diff --git a/cras/src/server/config/cras_board_config.h b/cras/src/server/config/cras_board_config.h
index 92ef971..d4bd849 100644
--- a/cras/src/server/config/cras_board_config.h
+++ b/cras/src/server/config/cras_board_config.h
@@ -12,6 +12,10 @@
 	int32_t default_output_buffer_size;
 	int32_t aec_supported;
 	int32_t aec_group_id;
+	int32_t bt_wbs_enabled;
+	int32_t deprioritize_bt_wbs_mic;
+	char *ucm_ignore_suffix;
+	int32_t hotword_pause_at_suspend;
 };
 
 /* Gets a configuration based on the config file specified.
diff --git a/cras/src/server/config/cras_card_config.c b/cras/src/server/config/cras_card_config.c
index f19d085..ae36565 100644
--- a/cras/src/server/config/cras_card_config.c
+++ b/cras/src/server/config/cras_card_config.c
@@ -10,10 +10,6 @@
 #include "iniparser_wrapper.h"
 #include "utlist.h"
 
-/* Allocate 63 chars + 1 for null where declared. */
-static const unsigned int MAX_INI_NAME_LEN = 63;
-static const unsigned int MAX_KEY_LEN = 63;
-
 struct cras_card_config {
 	dictionary *ini;
 };
@@ -22,15 +18,15 @@
 create_simple_step_curve(const struct cras_card_config *card_config,
 			 const char *control_name)
 {
-	char ini_key[MAX_KEY_LEN + 1];
+	char ini_key[MAX_INI_KEY_LENGTH + 1];
 	int max_volume;
 	int volume_step;
 
-	snprintf(ini_key, MAX_KEY_LEN, "%s:max_volume", control_name);
-	ini_key[MAX_KEY_LEN] = 0;
+	snprintf(ini_key, MAX_INI_KEY_LENGTH, "%s:max_volume", control_name);
+	ini_key[MAX_INI_KEY_LENGTH] = 0;
 	max_volume = iniparser_getint(card_config->ini, ini_key, 0);
-	snprintf(ini_key, MAX_KEY_LEN, "%s:volume_step", control_name);
-	ini_key[MAX_KEY_LEN] = 0;
+	snprintf(ini_key, MAX_INI_KEY_LENGTH, "%s:volume_step", control_name);
+	ini_key[MAX_INI_KEY_LENGTH] = 0;
 	volume_step = iniparser_getint(card_config->ini, ini_key, 300);
 	syslog(LOG_INFO, "Configure curve found for %s.", control_name);
 	return cras_volume_curve_create_simple_step(max_volume, volume_step);
@@ -41,12 +37,13 @@
 		      const char *control_name)
 {
 	unsigned int i;
-	char ini_key[MAX_KEY_LEN + 1];
+	char ini_key[MAX_INI_KEY_LENGTH + 1];
 	long dB_values[101];
 
 	for (i = 0; i < 101; i++) {
-		snprintf(ini_key, MAX_KEY_LEN, "%s:dB_at_%u", control_name, i);
-		ini_key[MAX_KEY_LEN] = 0;
+		snprintf(ini_key, MAX_INI_KEY_LENGTH, "%s:dB_at_%u",
+			 control_name, i);
+		ini_key[MAX_INI_KEY_LENGTH] = 0;
 		dB_values[i] = iniparser_getint(card_config->ini, ini_key, 0);
 	}
 	syslog(LOG_INFO, "Explicit volume curve found for %s.", control_name);
@@ -61,11 +58,12 @@
 						 const char *card_name)
 {
 	struct cras_card_config *card_config = NULL;
-	char ini_name[MAX_INI_NAME_LEN + 1];
+	char ini_name[MAX_INI_NAME_LENGTH + 1];
 	dictionary *ini;
 
-	snprintf(ini_name, MAX_INI_NAME_LEN, "%s/%s", config_path, card_name);
-	ini_name[MAX_INI_NAME_LEN] = '\0';
+	snprintf(ini_name, MAX_INI_NAME_LENGTH, "%s/%s", config_path,
+		 card_name);
+	ini_name[MAX_INI_NAME_LENGTH] = '\0';
 	ini = iniparser_load_wrapper(ini_name);
 	if (ini == NULL) {
 		syslog(LOG_DEBUG, "No ini file %s", ini_name);
@@ -93,14 +91,14 @@
 struct cras_volume_curve *cras_card_config_get_volume_curve_for_control(
 	const struct cras_card_config *card_config, const char *control_name)
 {
-	char ini_key[MAX_KEY_LEN + 1];
+	char ini_key[MAX_INI_KEY_LENGTH + 1];
 	const char *curve_type;
 
 	if (card_config == NULL || control_name == NULL)
 		return NULL;
 
-	snprintf(ini_key, MAX_KEY_LEN, "%s:volume_curve", control_name);
-	ini_key[MAX_KEY_LEN] = 0;
+	snprintf(ini_key, MAX_INI_KEY_LENGTH, "%s:volume_curve", control_name);
+	ini_key[MAX_INI_KEY_LENGTH] = 0;
 	curve_type = iniparser_getstring(card_config->ini, ini_key, NULL);
 
 	if (curve_type && strcmp(curve_type, "simple_step") == 0)
diff --git a/cras/src/server/config/cras_device_blacklist.c b/cras/src/server/config/cras_device_blacklist.c
deleted file mode 100644
index 1d18eb0..0000000
--- a/cras/src/server/config/cras_device_blacklist.c
+++ /dev/null
@@ -1,60 +0,0 @@
-/* Copyright (c) 2012 The Chromium OS 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 "cras_device_blacklist.h"
-#include "iniparser_wrapper.h"
-#include "utlist.h"
-
-/* Allocate 63 chars + 1 for null where declared. */
-static const unsigned int MAX_INI_NAME_LEN = 63;
-static const unsigned int MAX_KEY_LEN = 63;
-
-struct cras_device_blacklist {
-	dictionary *ini;
-};
-
-/*
- * Exported Interface
- */
-
-struct cras_device_blacklist *
-cras_device_blacklist_create(const char *config_path)
-{
-	struct cras_device_blacklist *blacklist;
-	char ini_name[MAX_INI_NAME_LEN + 1];
-
-	blacklist = calloc(1, sizeof(*blacklist));
-	if (!blacklist)
-		return NULL;
-
-	snprintf(ini_name, MAX_INI_NAME_LEN, "%s/%s", config_path,
-		 "device_blacklist");
-	ini_name[MAX_INI_NAME_LEN] = '\0';
-	blacklist->ini = iniparser_load_wrapper(ini_name);
-
-	return blacklist;
-}
-
-void cras_device_blacklist_destroy(struct cras_device_blacklist *blacklist)
-{
-	if (blacklist && blacklist->ini)
-		iniparser_freedict(blacklist->ini);
-	free(blacklist);
-}
-
-int cras_device_blacklist_check(struct cras_device_blacklist *blacklist,
-				unsigned vendor_id, unsigned product_id,
-				unsigned desc_checksum, unsigned device_index)
-{
-	char ini_key[MAX_KEY_LEN + 1];
-
-	if (!blacklist)
-		return 0;
-
-	snprintf(ini_key, MAX_KEY_LEN, "USB_Outputs:%04x_%04x_%08x_%u",
-		 vendor_id, product_id, desc_checksum, device_index);
-	ini_key[MAX_KEY_LEN] = 0;
-	return iniparser_getboolean(blacklist->ini, ini_key, 0);
-}
diff --git a/cras/src/server/config/cras_device_blacklist.h b/cras/src/server/config/cras_device_blacklist.h
deleted file mode 100644
index ac7cfe2..0000000
--- a/cras/src/server/config/cras_device_blacklist.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/*
- * Keeps a list of playback devices that should be ignored for a card.  This is
- * useful for devices that present non-functional alsa devices.  For instance
- * some mics show a phantom playback device.
- */
-#ifndef CRAS_DEVICE_BLACKLIST_H_
-#define CRAS_DEVICE_BLACKLIST_H_
-
-#include <stdint.h>
-
-#include "cras_types.h"
-
-struct cras_device_blacklist;
-
-/* Creates a blacklist of devices that should never be added to the system.
- * Args:
- *    config_path - Path containing the config files.
- * Returns:
- *    A pointer to the created blacklist on success, NULL on failure.
- */
-struct cras_device_blacklist *
-cras_device_blacklist_create(const char *config_path);
-
-/* Destroys a blacklist returned by cras_device_blacklist_create().
- * Args:
- *    blacklist - Blacklist returned by cras_device_blacklist_create()
- */
-void cras_device_blacklist_destroy(struct cras_device_blacklist *blacklist);
-
-/* Checks if a playback device on a USB card is blacklisted.
- * Args:
- *    blacklist - Blacklist returned by cras_device_blacklist_create()
- *    vendor_id - USB vendor ID.
- *    product_id - USB product ID.
- *    device_index - Index of the alsa device in the card.
- * Returns:
- *  1 if the device is blacklisted, 0 otherwise.
- */
-int cras_device_blacklist_check(struct cras_device_blacklist *blacklist,
-				unsigned vendor_id, unsigned product_id,
-				unsigned desc_checksum, unsigned device_index);
-
-#endif /* CRAS_CARD_DEVICE_BLACKLIST_H_ */
diff --git a/cras/src/server/config/cras_device_blocklist.c b/cras/src/server/config/cras_device_blocklist.c
new file mode 100644
index 0000000..d418fb8
--- /dev/null
+++ b/cras/src/server/config/cras_device_blocklist.c
@@ -0,0 +1,56 @@
+/* Copyright (c) 2012 The Chromium OS 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 "cras_device_blocklist.h"
+#include "iniparser_wrapper.h"
+#include "utlist.h"
+
+struct cras_device_blocklist {
+	dictionary *ini;
+};
+
+/*
+ * Exported Interface
+ */
+
+struct cras_device_blocklist *
+cras_device_blocklist_create(const char *config_path)
+{
+	struct cras_device_blocklist *blocklist;
+	char ini_name[MAX_INI_NAME_LENGTH + 1];
+
+	blocklist = calloc(1, sizeof(*blocklist));
+	if (!blocklist)
+		return NULL;
+
+	snprintf(ini_name, MAX_INI_NAME_LENGTH, "%s/%s", config_path,
+		 "device_blocklist");
+	ini_name[MAX_INI_NAME_LENGTH] = '\0';
+	blocklist->ini = iniparser_load_wrapper(ini_name);
+
+	return blocklist;
+}
+
+void cras_device_blocklist_destroy(struct cras_device_blocklist *blocklist)
+{
+	if (blocklist && blocklist->ini)
+		iniparser_freedict(blocklist->ini);
+	free(blocklist);
+}
+
+int cras_device_blocklist_check(struct cras_device_blocklist *blocklist,
+				unsigned vendor_id, unsigned product_id,
+				unsigned desc_checksum, unsigned device_index)
+{
+	char ini_key[MAX_INI_KEY_LENGTH + 1];
+
+	if (!blocklist)
+		return 0;
+
+	snprintf(ini_key, MAX_INI_KEY_LENGTH, "USB_Outputs:%04x_%04x_%08x_%u",
+		 vendor_id, product_id, desc_checksum, device_index);
+	ini_key[MAX_INI_KEY_LENGTH] = 0;
+	return iniparser_getboolean(blocklist->ini, ini_key, 0);
+}
diff --git a/cras/src/server/config/cras_device_blocklist.h b/cras/src/server/config/cras_device_blocklist.h
new file mode 100644
index 0000000..d0f750b
--- /dev/null
+++ b/cras/src/server/config/cras_device_blocklist.h
@@ -0,0 +1,48 @@
+/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/*
+ * Keeps a list of playback devices that should be ignored for a card.  This is
+ * useful for devices that present non-functional alsa devices.  For instance
+ * some mics show a phantom playback device.
+ */
+#ifndef CRAS_DEVICE_BLOCKLIST_H_
+#define CRAS_DEVICE_BLOCKLIST_H_
+
+#include <stdint.h>
+
+#include "cras_types.h"
+
+struct cras_device_blocklist;
+
+/* Creates a blocklist of devices that should never be added to the system.
+ * Args:
+ *    config_path - Path containing the config files.
+ * Returns:
+ *    A pointer to the created blocklist on success, NULL on failure.
+ */
+struct cras_device_blocklist *
+cras_device_blocklist_create(const char *config_path);
+
+/* Destroys a blocklist returned by cras_device_blocklist_create().
+ * Args:
+ *    blocklist - Blocklist returned by cras_device_blocklist_create()
+ */
+void cras_device_blocklist_destroy(struct cras_device_blocklist *blocklist);
+
+/* Checks if a playback device on a USB card is blocklisted.
+ * Args:
+ *    blocklist - Blocklist returned by cras_device_blocklist_create()
+ *    vendor_id - USB vendor ID.
+ *    product_id - USB product ID.
+ *    device_index - Index of the alsa device in the card.
+ * Returns:
+ *  1 if the device is blocklisted, 0 otherwise.
+ */
+int cras_device_blocklist_check(struct cras_device_blocklist *blocklist,
+				unsigned vendor_id, unsigned product_id,
+				unsigned desc_checksum, unsigned device_index);
+
+#endif /* CRAS_CARD_DEVICE_BLOCKLIST_H_ */
diff --git a/cras/src/server/cras.c b/cras/src/server/cras.c
index 32953ef..8d23907 100644
--- a/cras/src/server/cras.c
+++ b/cras/src/server/cras.c
@@ -9,6 +9,7 @@
 #include <stdio.h>
 #include <syslog.h>
 
+#include "cras_alsa_plugin_io.h"
 #include "cras_apm_list.h"
 #include "cras_config.h"
 #include "cras_iodev_list.h"
@@ -37,7 +38,7 @@
 int main(int argc, char **argv)
 {
 	int c, option_index;
-	int log_mask = LOG_ERR;
+	int log_mask = LOG_WARNING;
 	const char default_dsp_config[] = CRAS_CONFIG_FILE_DIR "/dsp.ini";
 	const char *dsp_config = default_dsp_config;
 	const char *device_config_dir = CRAS_CONFIG_FILE_DIR;
@@ -138,6 +139,7 @@
 	cras_dsp_init(dsp_config);
 	cras_apm_list_init(device_config_dir);
 	cras_iodev_list_init();
+	cras_alsa_plugin_io_init(device_config_dir);
 
 	/* Start the server. */
 	return cras_server_run(profile_disable_mask);
diff --git a/cras/src/server/cras_a2dp_info.c b/cras/src/server/cras_a2dp_info.c
index ffddb25..b2db384 100644
--- a/cras/src/server/cras_a2dp_info.c
+++ b/cras/src/server/cras_a2dp_info.c
@@ -104,7 +104,7 @@
 	return a2dp->samples;
 }
 
-void a2dp_drain(struct a2dp_info *a2dp)
+void a2dp_reset(struct a2dp_info *a2dp)
 {
 	a2dp->a2dp_buf_used =
 		sizeof(struct rtp_header) + sizeof(struct rtp_payload);
@@ -180,8 +180,7 @@
 int a2dp_write(struct a2dp_info *a2dp, int stream_fd, size_t link_mtu)
 {
 	/* Do avdtp write when the max number of SBC frames is reached. */
-	if (a2dp->a2dp_buf_used + a2dp->frame_length >
-	    link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
+	if (a2dp->a2dp_buf_used + a2dp->frame_length > link_mtu)
 		return avdtp_write(stream_fd, a2dp);
 
 	return 0;
diff --git a/cras/src/server/cras_a2dp_info.h b/cras/src/server/cras_a2dp_info.h
index eaf00d5..b33911e 100644
--- a/cras/src/server/cras_a2dp_info.h
+++ b/cras/src/server/cras_a2dp_info.h
@@ -8,7 +8,7 @@
 
 #include "a2dp-codecs.h"
 
-#define A2DP_BUF_SIZE_BYTES 1024
+#define A2DP_BUF_SIZE_BYTES 2048
 
 /* Represents the codec and encoded state of a2dp iodev.
  * Members:
@@ -60,9 +60,9 @@
 int a2dp_queued_frames(const struct a2dp_info *a2dp);
 
 /*
- * Drains queued samples in a2dp_info.
+ * Empty all queued samples in a2dp_info.
  */
-void a2dp_drain(struct a2dp_info *a2dp);
+void a2dp_reset(struct a2dp_info *a2dp);
 
 /*
  * Encodes samples using the codec for this a2dp instance, returns the number of
diff --git a/cras/src/server/cras_a2dp_iodev.c b/cras/src/server/cras_a2dp_iodev.c
index f6351ab..b8a606e 100644
--- a/cras/src/server/cras_a2dp_iodev.c
+++ b/cras/src/server/cras_a2dp_iodev.c
@@ -20,19 +20,24 @@
 #include "cras_a2dp_info.h"
 #include "cras_a2dp_iodev.h"
 #include "cras_audio_area.h"
+#include "cras_audio_thread_monitor.h"
 #include "cras_bt_device.h"
 #include "cras_iodev.h"
 #include "cras_util.h"
-#include "sfh.h"
 #include "rtp.h"
 #include "utlist.h"
 
 #define PCM_BUF_MAX_SIZE_FRAMES (4096 * 4)
 #define PCM_BUF_MAX_SIZE_BYTES (PCM_BUF_MAX_SIZE_FRAMES * 4)
 
-/* no_stream target_frames in timespec. */
-static const struct timespec no_stream_target_frames_ts = {
-	0, 10 * 1000 * 1000 /* 10 msec. */
+/* Threshold for reasonable a2dp throttle log in audio dump. */
+static const struct timespec throttle_log_threshold = {
+	0, 20000000 /* 20ms */
+};
+
+/* Threshold for severe a2dp throttle event. */
+static const struct timespec throttle_event_threshold = {
+	2, 0 /* 2s */
 };
 
 /* Child of cras_iodev to handle bluetooth A2DP streaming.
@@ -43,14 +48,10 @@
  *    sock_depth_frames - Socket depth in frames of the a2dp socket.
  *    pcm_buf - Buffer to hold pcm samples before encode.
  *    destroyed - Flag to note if this a2dp_io is about to destroy.
- *    bt_written_frames - Accumulated frames written to a2dp socket. Used
- *        together with the device open timestamp to estimate how many virtual
- *        buffer is queued there.
- *    dev_open_time - The last time a2dp_ios is opened.
- *    drain_complete - Flag to indicate if valid frames have all been drained
- *        in no stream state.
- *    filled_zeros_bytes - Number of zero data in bytes that have been filled
- *        in no stream state.
+ *    next_flush_time - The time when it is okay for next flush call.
+ *    flush_period - The time period between two a2dp packet writes.
+ *    write_block - How many frames of audio samples are transferred in one
+ *        a2dp packet write.
  */
 struct a2dp_io {
 	struct cras_iodev base;
@@ -59,13 +60,12 @@
 	unsigned sock_depth_frames;
 	struct byte_buffer *pcm_buf;
 	int destroyed;
-	uint64_t bt_written_frames;
-	struct timespec dev_open_time;
-	bool drain_complete;
-	int filled_zeros_bytes;
+	struct timespec next_flush_time;
+	struct timespec flush_period;
+	unsigned int write_block;
 };
 
-static int flush_data(void *arg);
+static int encode_and_flush(const struct cras_iodev *iodev);
 
 static int update_supported_formats(struct cras_iodev *iodev)
 {
@@ -76,7 +76,6 @@
 
 	cras_bt_transport_configuration(a2dpio->transport, &a2dp, sizeof(a2dp));
 
-	iodev->format->format = SND_PCM_FORMAT_S16_LE;
 	channel = (a2dp.channel_mode == SBC_CHANNEL_MODE_MONO) ? 1 : 2;
 
 	if (a2dp.frequency & SBC_SAMPLING_FREQ_48000)
@@ -102,97 +101,167 @@
 	iodev->supported_formats =
 		(snd_pcm_format_t *)malloc(2 * sizeof(snd_pcm_format_t));
 	iodev->supported_formats[0] = SND_PCM_FORMAT_S16_LE;
-	iodev->supported_formats[1] = 0;
+	iodev->supported_formats[1] = (snd_pcm_format_t)0;
 
 	return 0;
 }
 
-/* Calculates the number of virtual buffer in frames. Assuming all written
- * buffer is consumed in a constant frame rate at bluetooth device side.
- * Args:
- *    iodev: The a2dp iodev to estimate the queued frames for.
- *    fr: The amount of frames just transmitted.
- */
-static int bt_queued_frames(const struct cras_iodev *iodev, int fr)
+static unsigned int bt_local_queued_frames(const struct cras_iodev *iodev)
 {
-	uint64_t consumed;
 	struct a2dp_io *a2dpio = (struct a2dp_io *)iodev;
-
-	/* Calculate consumed frames since device has opened */
-	a2dpio->bt_written_frames += fr;
-	consumed = cras_frames_since_time(&a2dpio->dev_open_time,
-					  iodev->format->frame_rate);
-
-	if (a2dpio->bt_written_frames > consumed)
-		return a2dpio->bt_written_frames - consumed;
-	else
-		return 0;
+	return a2dp_queued_frames(&a2dpio->a2dp) +
+	       buf_queued(a2dpio->pcm_buf) /
+		       cras_get_format_bytes(iodev->format);
 }
 
 static int frames_queued(const struct cras_iodev *iodev,
 			 struct timespec *tstamp)
 {
-	struct a2dp_io *a2dpio = (struct a2dp_io *)iodev;
-	int estimate_queued_frames = bt_queued_frames(iodev, 0);
-	int local_queued_frames = a2dp_queued_frames(&a2dpio->a2dp) +
-				  buf_queued(a2dpio->pcm_buf) /
-					  cras_get_format_bytes(iodev->format);
+	int local_queued_frames = bt_local_queued_frames(iodev);
 	clock_gettime(CLOCK_MONOTONIC_RAW, tstamp);
-	return MIN(iodev->buffer_size,
-		   MAX(estimate_queued_frames, local_queued_frames));
+	return MIN(iodev->buffer_size, local_queued_frames);
 }
 
-static int no_stream(struct cras_iodev *iodev, int enable)
+/*
+ * Utility function to fill zero frames until buffer level reaches
+ * target_level. This is useful to allocate just enough data to write
+ * to controller, while not introducing extra latency.
+ */
+static int fill_zeros_to_target_level(struct cras_iodev *iodev,
+				      unsigned int target_level)
 {
-	struct a2dp_io *a2dpio = (struct a2dp_io *)iodev;
-	unsigned int buf_avail;
-	unsigned int format_bytes;
-	unsigned int target_bytes;
-	unsigned int target_total_bytes;
-	unsigned int bt_queued_bytes;
-	uint8_t *buf;
-	struct timespec tstamp;
-	int i;
+	unsigned int local_queued_frames = bt_local_queued_frames(iodev);
 
-	format_bytes = cras_get_format_bytes(iodev->format);
+	if (local_queued_frames < target_level)
+		return cras_iodev_fill_odev_zeros(
+			iodev, target_level - local_queued_frames);
+	return 0;
+}
 
-	if (enable) {
-		/* Target to have let hw_level = 2 * (frames in 10ms) */
-		bt_queued_bytes =
-			cras_iodev_frames_queued(iodev, &tstamp) * format_bytes;
-		target_total_bytes =
-			2 *
-			cras_time_to_frames(&no_stream_target_frames_ts,
-					    iodev->format->frame_rate) *
-			format_bytes;
-		if (target_total_bytes <= bt_queued_bytes)
-			return 0;
-		target_total_bytes -= bt_queued_bytes;
+/*
+ * dev_io_playback_write() has the logic to detect underrun scenario
+ * and calls into this underrun ops, by comparing buffer level with
+ * number of frames just written. Note that it's not correct 100% of
+ * the time in a2dp case, because we lose track of samples once they're
+ * flushed to socket.
+ */
+static int output_underrun(struct cras_iodev *iodev)
+{
+	unsigned int local_queued_frames = bt_local_queued_frames(iodev);
 
-		/* Loop twice to make sure target_total_bytes are filled. */
-		for (i = 0; i < 2; i++) {
-			buf = buf_write_pointer_size(a2dpio->pcm_buf,
-						     &buf_avail);
-			if (buf_avail == 0 || target_total_bytes == 0)
-				break;
-			target_bytes = MIN(buf_avail, target_total_bytes);
-			memset(buf, 0, target_bytes);
-			buf_increment_write(a2dpio->pcm_buf, target_bytes);
-			bt_queued_frames(iodev, target_bytes / format_bytes);
-			target_total_bytes -= target_bytes;
-		}
-		flush_data(iodev);
+	/*
+	 * Examples to help understand the check:
+	 *
+	 * [False-positive underrun]
+	 * Assume min_buffer_level = 1000, written 900, and flushes
+	 * 800 of data. Audio thread sees 1000 + 900 - 800 = 1100 of
+	 * data left. This is merely 100(< 900) above min_buffer_level
+	 * so audio_thread thinks it underruns, but actually not.
+	 *
+	 * [True underrun]
+	 * min_buffer_level = 1000, written 200, and flushes 800 of
+	 * data. Now that buffer runs lower than min_buffer_level so
+	 * it's indeed an underrun.
+	 */
+	if (local_queued_frames > iodev->min_buffer_level)
 		return 0;
+
+	return cras_iodev_fill_odev_zeros(iodev, iodev->min_cb_level);
+}
+
+/*
+ * This will be called multiple times when a2dpio is in no_stream state
+ * frames_to_play_in_sleep ops determins how regular this will be called.
+ */
+static int enter_no_stream(struct a2dp_io *a2dpio)
+{
+	struct cras_iodev *odev = &a2dpio->base;
+	int rc;
+	/*
+         * Setting target level to 3 times of min_buffer_level.
+         * We want hw_level to stay bewteen 1-2 times of min_buffer_level on
+	 * top of the underrun threshold(i.e one min_cb_level).
+         */
+	rc = fill_zeros_to_target_level(odev, 3 * odev->min_buffer_level);
+	if (rc)
+		syslog(LOG_ERR, "Error in A2DP enter_no_stream");
+	return encode_and_flush(odev);
+}
+
+/*
+ * This is called when stream data is available to write. Prepare audio
+ * data to one min_buffer_level. Don't flush it now because stream data is
+ * coming right up which will trigger next flush at appropriate time.
+ */
+static int leave_no_stream(struct a2dp_io *a2dpio)
+{
+	struct cras_iodev *odev = &a2dpio->base;
+
+	/*
+	 * Since stream data is ready, just make sure hw_level doesn't underrun
+	 * after one flush. Hence setting the target level to 2 times of
+	 * min_buffer_level.
+         */
+	return fill_zeros_to_target_level(odev, 2 * odev->min_buffer_level);
+}
+
+/*
+ * Makes sure there's enough data(zero frames) to flush when no stream presents.
+ * Note that the underrun condition is when real buffer level goes below
+ * min_buffer_level, so we want to keep data at a reasonable higher level on top
+ * of that.
+ */
+static int no_stream(struct cras_iodev *odev, int enable)
+{
+	struct a2dp_io *a2dpio = (struct a2dp_io *)odev;
+
+	if (enable)
+		return enter_no_stream(a2dpio);
+	else
+		return leave_no_stream(a2dpio);
+}
+
+/* Encode as much PCM data as we can until the buffer level of a2dp_info
+ * reaches MTU.
+ * Returns:
+ *    0 for success, otherwise negative error code.
+ */
+static int encode_a2dp_packet(struct a2dp_io *a2dpio)
+{
+	int processed;
+	size_t format_bytes = cras_get_format_bytes(a2dpio->base.format);
+
+	while (buf_queued(a2dpio->pcm_buf)) {
+		processed = a2dp_encode(
+			&a2dpio->a2dp, buf_read_pointer(a2dpio->pcm_buf),
+			buf_readable(a2dpio->pcm_buf), format_bytes,
+			cras_bt_transport_write_mtu(a2dpio->transport));
+		if (processed == -ENOSPC || processed == 0)
+			break;
+		if (processed < 0)
+			return processed;
+
+		buf_increment_read(a2dpio->pcm_buf, processed);
 	}
 	return 0;
 }
 
+/*
+ * To be called when a2dp socket becomes writable.
+ */
+static int a2dp_socket_write_cb(void *arg, int revent)
+{
+	struct cras_iodev *iodev = (struct cras_iodev *)arg;
+	return encode_and_flush(iodev);
+}
+
 static int configure_dev(struct cras_iodev *iodev)
 {
 	struct a2dp_io *a2dpio = (struct a2dp_io *)iodev;
 	int sock_depth;
 	int err;
 	socklen_t optlen;
+	int a2dp_payload_length;
 
 	err = cras_bt_transport_acquire(a2dpio->transport);
 	if (err < 0) {
@@ -215,34 +284,60 @@
 	if (!a2dpio->pcm_buf)
 		return -ENOMEM;
 
-	iodev->buffer_size = PCM_BUF_MAX_SIZE_FRAMES;
-
 	/* Set up the socket to hold two MTUs full of data before returning
 	 * EAGAIN.  This will allow the write to be throttled when a reasonable
 	 * amount of data is queued. */
 	sock_depth = 2 * cras_bt_transport_write_mtu(a2dpio->transport);
 	setsockopt(cras_bt_transport_fd(a2dpio->transport), SOL_SOCKET,
 		   SO_SNDBUF, &sock_depth, sizeof(sock_depth));
-
 	optlen = sizeof(sock_depth);
 	getsockopt(cras_bt_transport_fd(a2dpio->transport), SOL_SOCKET,
 		   SO_SNDBUF, &sock_depth, &optlen);
 	a2dpio->sock_depth_frames = a2dp_block_size(&a2dpio->a2dp, sock_depth) /
 				    cras_get_format_bytes(iodev->format);
+	/*
+	 * Per avdtp_write, subtract the room for packet header first.
+	 * Calculate how many frames are encapsulated in one a2dp packet, and
+	 * the corresponding time period between two packets.
+	 */
+	a2dp_payload_length = cras_bt_transport_write_mtu(a2dpio->transport) -
+			      sizeof(struct rtp_header) -
+			      sizeof(struct rtp_payload);
+	a2dpio->write_block =
+		a2dp_block_size(&a2dpio->a2dp, a2dp_payload_length) /
+		cras_get_format_bytes(iodev->format);
+	cras_frames_to_time(a2dpio->write_block, iodev->format->frame_rate,
+			    &a2dpio->flush_period);
 
-	iodev->min_buffer_level = a2dpio->sock_depth_frames;
+	/* PCM buffer size plus one encoded a2dp packet. */
+	iodev->buffer_size = PCM_BUF_MAX_SIZE_FRAMES + a2dpio->write_block;
 
-	a2dpio->drain_complete = 0;
-	a2dpio->filled_zeros_bytes = 0;
+	/*
+	 * Buffer level less than one write_block can't be send over a2dp
+	 * packet. Configure min_buffer_level to this value so when stream
+	 * underruns, audio thread can take action to fill some zeros.
+	 */
+	iodev->min_buffer_level = a2dpio->write_block;
 
-	/* Initialize variables for bt_queued_frames() */
-	a2dpio->bt_written_frames = 0;
-	clock_gettime(CLOCK_MONOTONIC_RAW, &a2dpio->dev_open_time);
+	audio_thread_add_events_callback(
+		cras_bt_transport_fd(a2dpio->transport), a2dp_socket_write_cb,
+		iodev, POLLOUT | POLLERR | POLLHUP);
+	audio_thread_config_events_callback(
+		cras_bt_transport_fd(a2dpio->transport), TRIGGER_NONE);
+	return 0;
+}
 
-	audio_thread_add_write_callback(cras_bt_transport_fd(a2dpio->transport),
-					flush_data, iodev);
-	audio_thread_enable_callback(cras_bt_transport_fd(a2dpio->transport),
-				     0);
+static int start(const struct cras_iodev *iodev)
+{
+	struct a2dp_io *a2dpio = (struct a2dp_io *)iodev;
+
+	/*
+	 * This is called when iodev in open state, at the moment when
+	 * output sample is ready. Initialize the next_flush_time for
+	 * following flush calls.
+	 */
+	clock_gettime(CLOCK_MONOTONIC_RAW, &a2dpio->next_flush_time);
+
 	return 0;
 }
 
@@ -267,26 +362,53 @@
 	device = cras_bt_transport_device(a2dpio->transport);
 	if (device)
 		cras_bt_device_cancel_suspend(device);
-	a2dp_drain(&a2dpio->a2dp);
+	a2dp_reset(&a2dpio->a2dp);
 	byte_buffer_destroy(&a2dpio->pcm_buf);
 	cras_iodev_free_format(iodev);
 	cras_iodev_free_audio_area(iodev);
 	return 0;
 }
 
-/* Flushes queued buffer, including pcm and a2dp buffer.
+static unsigned int frames_to_play_in_sleep(struct cras_iodev *iodev,
+					    unsigned int *hw_level,
+					    struct timespec *hw_tstamp)
+{
+	struct a2dp_io *a2dpio = (struct a2dp_io *)iodev;
+	int frames_until;
+
+	*hw_level = frames_queued(iodev, hw_tstamp);
+	if (*hw_level < a2dpio->write_block)
+		*hw_level = 0;
+	else
+		*hw_level -= a2dpio->write_block;
+
+	frames_until = cras_frames_until_time(&a2dpio->next_flush_time,
+					      iodev->format->frame_rate);
+	if (frames_until > 0)
+		return frames_until;
+
+	/* If time has passed next_flush_time, for example when socket write
+	 * throttles, sleep a moderate of time so that audio thread doesn't
+	 * busy wake up. */
+	return a2dpio->write_block;
+}
+
+/* Encodes PCM data to a2dp frames and try to flush it to the socket.
  * Returns:
  *    0 when the flush succeeded, -1 when error occurred.
  */
-static int flush_data(void *arg)
+static int encode_and_flush(const struct cras_iodev *iodev)
 {
-	struct cras_iodev *iodev = (struct cras_iodev *)arg;
-	int processed;
+	int err;
 	size_t format_bytes;
 	int written = 0;
-	int queued_frames;
+	unsigned int queued_frames;
 	struct a2dp_io *a2dpio;
 	struct cras_bt_device *device;
+	struct timespec now, ts;
+	static const struct timespec flush_wake_fuzz_ts = {
+		0, 1000000 /* 1ms */
+	};
 
 	a2dpio = (struct a2dp_io *)iodev;
 	format_bytes = cras_get_format_bytes(iodev->format);
@@ -297,23 +419,51 @@
 	if (device == NULL)
 		return -EINVAL;
 
-encode_more:
-	while (buf_queued(a2dpio->pcm_buf)) {
-		processed = a2dp_encode(
-			&a2dpio->a2dp, buf_read_pointer(a2dpio->pcm_buf),
-			buf_readable(a2dpio->pcm_buf), format_bytes,
-			cras_bt_transport_write_mtu(a2dpio->transport));
-		ATLOG(atlog, AUDIO_THREAD_A2DP_ENCODE, processed,
-		      buf_queued(a2dpio->pcm_buf),
-		      buf_readable(a2dpio->pcm_buf));
-		if (processed == -ENOSPC || processed == 0)
-			break;
-		if (processed < 0)
-			return 0;
+	ATLOG(atlog, AUDIO_THREAD_A2DP_FLUSH, iodev->state,
+	      a2dpio->next_flush_time.tv_sec, a2dpio->next_flush_time.tv_nsec);
 
-		buf_increment_read(a2dpio->pcm_buf, processed);
+	/* Only allow data to be flushed after start() ops is called. */
+	if ((iodev->state != CRAS_IODEV_STATE_NORMAL_RUN) &&
+	    (iodev->state != CRAS_IODEV_STATE_NO_STREAM_RUN))
+		return 0;
+
+	err = encode_a2dp_packet(a2dpio);
+	if (err < 0)
+		return err;
+
+do_flush:
+	/* If flush gets called before targeted next flush time, do nothing. */
+	clock_gettime(CLOCK_MONOTONIC_RAW, &now);
+	add_timespecs(&now, &flush_wake_fuzz_ts);
+	if (!timespec_after(&now, &a2dpio->next_flush_time)) {
+		if (iodev->buffer_size == bt_local_queued_frames(iodev)) {
+			/*
+			 * If buffer is full, audio thread will no longer call
+			 * into get/put buffer in subsequent wake-ups. In that
+			 * case set the registered callback to be triggered at
+			 * next audio thread wake up.
+			 */
+			audio_thread_config_events_callback(
+				cras_bt_transport_fd(a2dpio->transport),
+				TRIGGER_WAKEUP);
+			cras_audio_thread_event_a2dp_overrun();
+			syslog(LOG_WARNING, "Buffer overrun in A2DP iodev");
+		}
+		return 0;
 	}
 
+	/* If the A2DP write schedule miss exceeds a small threshold, log it for
+	 * debug purpose. */
+	subtract_timespecs(&now, &a2dpio->next_flush_time, &ts);
+	if (timespec_after(&ts, &throttle_log_threshold))
+		ATLOG(atlog, AUDIO_THREAD_A2DP_THROTTLE_TIME, ts.tv_sec,
+		      ts.tv_nsec, bt_local_queued_frames(iodev));
+
+	/* Log an event if the A2DP write schedule miss exceeds a large threshold
+	 * that we consider it as something severe. */
+	if (timespec_after(&ts, &throttle_event_threshold))
+		cras_audio_thread_event_a2dp_throttle();
+
 	written = a2dp_write(&a2dpio->a2dp,
 			     cras_bt_transport_fd(a2dpio->transport),
 			     cras_bt_transport_write_mtu(a2dpio->transport));
@@ -322,18 +472,32 @@
 	if (written == -EAGAIN) {
 		/* If EAGAIN error lasts longer than 5 seconds, suspend the
 		 * a2dp connection. */
-		cras_bt_device_schedule_suspend(device, 5000);
-		audio_thread_enable_callback(
-			cras_bt_transport_fd(a2dpio->transport), 1);
+		cras_bt_device_schedule_suspend(device, 5000,
+						A2DP_LONG_TX_FAILURE);
+		audio_thread_config_events_callback(
+			cras_bt_transport_fd(a2dpio->transport), TRIGGER_POLL);
 		return 0;
 	} else if (written < 0) {
 		/* Suspend a2dp immediately when receives error other than
 		 * EAGAIN. */
 		cras_bt_device_cancel_suspend(device);
-		cras_bt_device_schedule_suspend(device, 0);
+		cras_bt_device_schedule_suspend(device, 0, A2DP_TX_FATAL_ERROR);
+		/* Stop polling the socket in audio thread. Main thread will
+		 * close this iodev soon. */
+		audio_thread_config_events_callback(
+			cras_bt_transport_fd(a2dpio->transport), TRIGGER_NONE);
 		return written;
 	}
 
+	/* Update the next flush time if one block successfully been written. */
+	if (written)
+		add_timespecs(&a2dpio->next_flush_time, &a2dpio->flush_period);
+
+	/* a2dp_write no longer return -EAGAIN when reaches here, disable
+	 * the polling write callback. */
+	audio_thread_config_events_callback(
+		cras_bt_transport_fd(a2dpio->transport), TRIGGER_NONE);
+
 	/* Data succcessfully written to a2dp socket, cancel any scheduled
 	 * suspend timer. */
 	cras_bt_device_cancel_suspend(device);
@@ -343,12 +507,13 @@
 	 * to min_buffer_level so that another A2DP write could causes underrun.
 	 */
 	queued_frames = buf_queued(a2dpio->pcm_buf) / format_bytes;
-	if (written && (iodev->min_buffer_level + written < queued_frames))
-		goto encode_more;
-
-	/* everything written. */
-	audio_thread_enable_callback(cras_bt_transport_fd(a2dpio->transport),
-				     0);
+	if (written &&
+	    (iodev->min_buffer_level + a2dpio->write_block < queued_frames)) {
+		err = encode_a2dp_packet(a2dpio);
+		if (err < 0)
+			return err;
+		goto do_flush;
+	}
 
 	return 0;
 }
@@ -397,13 +562,7 @@
 
 	buf_increment_write(a2dpio->pcm_buf, written_bytes);
 
-	/* Set dev open time at when the first data arrives. */
-	if (nwritten && !a2dpio->bt_written_frames)
-		clock_gettime(CLOCK_MONOTONIC_RAW, &a2dpio->dev_open_time);
-
-	bt_queued_frames(iodev, nwritten);
-
-	return flush_data(iodev);
+	return encode_and_flush(iodev);
 }
 
 static int flush_buffer(struct cras_iodev *iodev)
@@ -484,10 +643,7 @@
 
 	snprintf(iodev->info.name, sizeof(iodev->info.name), "%s", name);
 	iodev->info.name[ARRAY_SIZE(iodev->info.name) - 1] = '\0';
-	iodev->info.stable_id =
-		SuperFastHash(cras_bt_device_object_path(device),
-			      strlen(cras_bt_device_object_path(device)),
-			      strlen(cras_bt_device_object_path(device)));
+	iodev->info.stable_id = cras_bt_device_get_stable_id(device);
 
 	iodev->configure_dev = configure_dev;
 	iodev->frames_queued = frames_queued;
@@ -496,12 +652,15 @@
 	iodev->put_buffer = put_buffer;
 	iodev->flush_buffer = flush_buffer;
 	iodev->no_stream = no_stream;
+	iodev->output_underrun = output_underrun;
 	iodev->close_dev = close_dev;
 	iodev->update_supported_formats = update_supported_formats;
 	iodev->update_active_node = update_active_node;
 	iodev->set_volume = set_volume;
+	iodev->start = start;
+	iodev->frames_to_play_in_sleep = frames_to_play_in_sleep;
 
-	/* Create a dummy ionode */
+	/* Create an empty ionode */
 	node = (struct cras_ionode *)calloc(1, sizeof(*node));
 	node->dev = iodev;
 	strcpy(node->name, iodev->info.name);
@@ -510,11 +669,18 @@
 	node->volume = 100;
 	gettimeofday(&node->plugged_time, NULL);
 
-	/* A2DP does output only */
-	cras_bt_device_append_iodev(
-		device, iodev, cras_bt_transport_profile(a2dpio->transport));
+	/* Prepare active node before append, so bt_io can extract correct
+	 * info from A2DP iodev and node. */
 	cras_iodev_add_node(iodev, node);
 	cras_iodev_set_active_node(iodev, node);
+	cras_bt_device_append_iodev(
+		device, iodev, cras_bt_transport_profile(a2dpio->transport));
+
+	/* Record max supported channels into cras_iodev_info. */
+	iodev->info.max_supported_channels =
+		(a2dp.channel_mode == SBC_CHANNEL_MODE_MONO) ? 1 : 2;
+
+	ewma_power_disable(&iodev->ewma);
 
 	return iodev;
 error:
diff --git a/cras/src/server/cras_alsa_card.c b/cras/src/server/cras_alsa_card.c
index 709cc1d..362e6a6 100644
--- a/cras/src/server/cras_alsa_card.c
+++ b/cras/src/server/cras_alsa_card.c
@@ -14,7 +14,7 @@
 #include "cras_alsa_io.h"
 #include "cras_alsa_mixer.h"
 #include "cras_alsa_ucm.h"
-#include "cras_device_blacklist.h"
+#include "cras_device_blocklist.h"
 #include "cras_card_config.h"
 #include "cras_config.h"
 #include "cras_iodev.h"
@@ -25,8 +25,8 @@
 #include "utlist.h"
 
 #define MAX_ALSA_CARDS 32 /* Alsa limit on number of cards. */
-#define MAX_ALSA_PCM_NAME_LENGTH 6 /* Alsa names "hw:XX" + 1 for null. */
-#define MAX_INI_NAME_LENGTH 63 /* 63 chars + 1 for null where declared. */
+#define MAX_ALSA_CARD_NAME_LENGTH 6 /* Alsa card name "hw:XX" + 1 for null. */
+#define MAX_ALSA_PCM_NAME_LENGTH 9 /* Alsa pcm name "hw:XX,YY" + 1 for null. */
 #define MAX_COUPLED_OUTPUT_SIZE 4
 
 struct iodev_list_node {
@@ -43,7 +43,7 @@
 };
 
 /* Holds information about each sound card on the system.
- * name - of the form hw:XX,YY.
+ * name - of the form hw:XX.
  * card_index - 0 based index, value of "XX" in the name.
  * iodevs - Input and output devices for this card.
  * mixer - Controls the mixer controls for this card.
@@ -53,7 +53,7 @@
  * config - Config info for this card, can be NULL if none found.
  */
 struct cras_alsa_card {
-	char name[MAX_ALSA_PCM_NAME_LENGTH];
+	char name[MAX_ALSA_CARD_NAME_LENGTH];
 	size_t card_index;
 	struct iodev_list_node *iodevs;
 	struct cras_alsa_mixer *mixer;
@@ -84,6 +84,7 @@
 	struct iodev_list_node *new_dev;
 	struct iodev_list_node *node;
 	int first = 1;
+	char pcm_name[MAX_ALSA_PCM_NAME_LENGTH];
 
 	/* Find whether this is the first device in this direction, and
 	 * avoid duplicate device indexes. */
@@ -103,22 +104,28 @@
 	if (new_dev == NULL)
 		return NULL;
 
+	/* Append device index to card namem, ex: 'hw:0', for the PCM name of
+	 * target iodev. */
+	snprintf(pcm_name, MAX_ALSA_PCM_NAME_LENGTH, "%s,%u", alsa_card->name,
+		 device_index);
+
 	new_dev->direction = direction;
-	new_dev->iodev = alsa_iodev_create(
-		info->card_index, card_name, device_index, dev_name, dev_id,
-		info->card_type, first, alsa_card->mixer, alsa_card->config,
-		alsa_card->ucm, alsa_card->hctl, direction, info->usb_vendor_id,
-		info->usb_product_id, info->usb_serial_number);
+	new_dev->iodev =
+		alsa_iodev_create(info->card_index, card_name, device_index,
+				  pcm_name, dev_name, dev_id, info->card_type,
+				  first, alsa_card->mixer, alsa_card->config,
+				  alsa_card->ucm, alsa_card->hctl, direction,
+				  info->usb_vendor_id, info->usb_product_id,
+				  info->usb_serial_number);
 	if (new_dev->iodev == NULL) {
-		syslog(LOG_ERR, "Couldn't create alsa_iodev for %u:%u\n",
-		       info->card_index, device_index);
+		syslog(LOG_ERR, "Couldn't create alsa_iodev for %s", pcm_name);
 		free(new_dev);
 		return NULL;
 	}
 
-	syslog(LOG_DEBUG, "New %s device %u:%d",
+	syslog(LOG_DEBUG, "New %s device %s",
 	       direction == CRAS_STREAM_OUTPUT ? "playback" : "capture",
-	       info->card_index, device_index);
+	       pcm_name);
 
 	DL_APPEND(alsa_card->iodevs, new_dev);
 	return new_dev->iodev;
@@ -139,15 +146,15 @@
 }
 
 /* Check if a device should be ignored for this card. Returns non-zero if the
- * device is in the blacklist and should be ignored.
+ * device is in the blocklist and should be ignored.
  */
 static int should_ignore_dev(struct cras_alsa_card_info *info,
-			     struct cras_device_blacklist *blacklist,
+			     struct cras_device_blocklist *blocklist,
 			     size_t device_index)
 {
 	if (info->card_type == ALSA_CARD_TYPE_USB)
-		return cras_device_blacklist_check(
-			blacklist, info->usb_vendor_id, info->usb_product_id,
+		return cras_device_blocklist_check(
+			blocklist, info->usb_vendor_id, info->usb_product_id,
 			info->usb_desc_checksum, device_index);
 	return 0;
 }
@@ -171,7 +178,7 @@
 
 /* Handles notifications from alsa controls.  Called by main thread when a poll
  * fd provided by alsa signals there is an event available. */
-static void alsa_control_event_pending(void *arg)
+static void alsa_control_event_pending(void *arg, int revent)
 {
 	struct cras_alsa_card *card;
 
@@ -188,7 +195,7 @@
 
 static int
 add_controls_and_iodevs_by_matching(struct cras_alsa_card_info *info,
-				    struct cras_device_blacklist *blacklist,
+				    struct cras_device_blocklist *blocklist,
 				    struct cras_alsa_card *alsa_card,
 				    const char *card_name, snd_ctl_t *handle)
 {
@@ -251,7 +258,7 @@
 		/* Check for playback devices. */
 		snd_pcm_info_set_stream(dev_info, SND_PCM_STREAM_PLAYBACK);
 		if (snd_ctl_pcm_info(handle, dev_info) == 0 &&
-		    !should_ignore_dev(info, blacklist, dev_idx)) {
+		    !should_ignore_dev(info, blocklist, dev_idx)) {
 			struct cras_iodev *iodev = create_iodev_for_device(
 				alsa_card, info, card_name,
 				snd_pcm_info_get_name(dev_info),
@@ -338,6 +345,11 @@
 
 	/* Create all of the devices. */
 	DL_FOREACH (ucm_sections, section) {
+		/* If a UCM section specifies certain device as dependency
+		 * then don't create an alsa iodev for it, just append it
+		 * as node later. */
+		if (section->dependent_dev_idx != -1)
+			continue;
 		snd_pcm_info_set_device(dev_info, section->dev_idx);
 		snd_pcm_info_set_subdevice(dev_info, 0);
 		if (section->dir == CRAS_STREAM_OUTPUT)
@@ -365,11 +377,17 @@
 					section->dev_idx, section->dir);
 	}
 
-	/* Setup jacks and controls for the devices. */
+	/* Setup jacks and controls for the devices. If a SectionDevice is
+	 * dependent on another SectionDevice, it'll be added as a node to
+	 * a existing ALSA iodev. */
 	DL_FOREACH (ucm_sections, section) {
 		DL_FOREACH (alsa_card->iodevs, node) {
-			if (node->direction == section->dir &&
-			    alsa_iodev_index(node->iodev) == section->dev_idx)
+			if (node->direction != section->dir)
+				continue;
+			if (alsa_iodev_index(node->iodev) == section->dev_idx)
+				break;
+			if (alsa_iodev_index(node->iodev) ==
+			    section->dependent_dev_idx)
 				break;
 		}
 		if (node) {
@@ -431,7 +449,7 @@
 
 struct cras_alsa_card *cras_alsa_card_create(
 	struct cras_alsa_card_info *info, const char *device_config_dir,
-	struct cras_device_blacklist *blacklist, const char *ucm_suffix)
+	struct cras_device_blocklist *blocklist, const char *ucm_suffix)
 {
 	snd_ctl_t *handle = NULL;
 	int rc, n;
@@ -451,7 +469,7 @@
 		return NULL;
 	alsa_card->card_index = info->card_index;
 
-	snprintf(alsa_card->name, MAX_ALSA_PCM_NAME_LENGTH, "hw:%u",
+	snprintf(alsa_card->name, MAX_ALSA_CARD_NAME_LENGTH, "hw:%u",
 		 info->card_index);
 
 	rc = snd_ctl_open(&handle, alsa_card->name, 0);
@@ -472,6 +490,10 @@
 		goto error_bail;
 	}
 
+	if (info->card_type != ALSA_CARD_TYPE_INTERNAL ||
+	    cras_system_check_ignore_ucm_suffix(card_name))
+		ucm_suffix = NULL;
+
 	/* Read config file for this card if it exists. */
 	alsa_card->config =
 		cras_card_config_create(device_config_dir, card_name);
@@ -495,6 +517,9 @@
 		       card_name, alsa_card->ucm ? "yes" : "no");
 	}
 
+	if (info->card_type == ALSA_CARD_TYPE_INTERNAL && !alsa_card->ucm)
+		syslog(LOG_ERR, "No ucm config on internal card %s", card_name);
+
 	rc = snd_hctl_open(&alsa_card->hctl, alsa_card->name, SND_CTL_NONBLOCK);
 	if (rc < 0) {
 		syslog(LOG_DEBUG, "failed to get hctl for %s", alsa_card->name);
@@ -528,7 +553,7 @@
 						      card_name, handle);
 	else
 		rc = add_controls_and_iodevs_by_matching(
-			info, blacklist, alsa_card, card_name, handle);
+			info, blocklist, alsa_card, card_name, handle);
 	if (rc)
 		goto error_bail;
 
@@ -559,7 +584,7 @@
 			DL_APPEND(alsa_card->hctl_poll_fds, registered_fd);
 			rc = cras_system_add_select_fd(
 				registered_fd->fd, alsa_control_event_pending,
-				alsa_card);
+				alsa_card, POLLIN);
 			if (rc < 0) {
 				DL_DELETE(alsa_card->hctl_poll_fds,
 					  registered_fd);
diff --git a/cras/src/server/cras_alsa_card.h b/cras/src/server/cras_alsa_card.h
index 749a17f..a63bf90 100644
--- a/cras/src/server/cras_alsa_card.h
+++ b/cras/src/server/cras_alsa_card.h
@@ -15,7 +15,7 @@
  */
 
 struct cras_alsa_card;
-struct cras_device_blacklist;
+struct cras_device_blocklist;
 
 /* Creates a cras_alsa_card instance for the given alsa device.  Enumerates the
  * devices for the card and adds them to the system as possible playback or
@@ -24,7 +24,7 @@
  *    card_info - Contains the card index, type, and priority.
  *    device_config_dir - The directory of device configs which contains the
  *                        volume curves.
- *    blacklist - List of devices that should be ignored.
+ *    blocklist - List of devices that should be ignored.
  *    ucm_suffix - The ucm config name is formed as <card-name>.<suffix>
  * Returns:
  *    A pointer to the newly created cras_alsa_card which must later be freed
@@ -32,7 +32,7 @@
  */
 struct cras_alsa_card *cras_alsa_card_create(
 	struct cras_alsa_card_info *info, const char *device_config_dir,
-	struct cras_device_blacklist *blacklist, const char *ucm_suffix);
+	struct cras_device_blocklist *blocklist, const char *ucm_suffix);
 
 /* Destroys a cras_alsa_card that was returned from cras_alsa_card_create.
  * Args:
diff --git a/cras/src/server/cras_alsa_helpers.c b/cras/src/server/cras_alsa_helpers.c
index b0729fd..6cdc165 100644
--- a/cras/src/server/cras_alsa_helpers.c
+++ b/cras/src/server/cras_alsa_helpers.c
@@ -25,6 +25,9 @@
 /* Time difference between two consecutive underrun logs. */
 #define UNDERRUN_LOG_TIME_SECS 30
 
+/* Limit the number of channels supported for devices: b/158509536 */
+#define TEMP_CHANNEL_LIMIT 20
+
 /* Chances to give mmap_begin to work. */
 static const size_t MAX_MMAP_BEGIN_ATTEMPTS = 3;
 /* Time to sleep between resume attempts. */
@@ -410,8 +413,19 @@
 	}
 	(*channel_counts)[num_found] = 0;
 	if (num_found == 0) {
-		syslog(LOG_WARNING, "No valid channel counts found.");
-		return -EINVAL;
+		// Pull the max channel count and use that.
+		unsigned int max_channels = 0;
+		rc = snd_pcm_hw_params_get_channels_max(params, &max_channels);
+		if (rc < 0) {
+			syslog(LOG_WARNING, "No valid channel counts found.");
+			return -EINVAL;
+		} else if (max_channels > TEMP_CHANNEL_LIMIT) {
+			syslog(LOG_WARNING, "Can't support so many channels.");
+			return -EINVAL;
+		} else {
+			(*channel_counts)[0] = (size_t)max_channels;
+			(*channel_counts)[1] = 0;
+		}
 	}
 
 	num_found = 0;
@@ -542,7 +556,7 @@
 	return 0;
 }
 
-int cras_alsa_set_swparams(snd_pcm_t *handle, int *enable_htimestamp)
+int cras_alsa_set_swparams(snd_pcm_t *handle)
 {
 	int err;
 	snd_pcm_sw_params_t *swparams;
@@ -579,50 +593,7 @@
 		return err;
 	}
 
-	if (*enable_htimestamp) {
-		/* Use MONOTONIC_RAW time-stamps. */
-		err = snd_pcm_sw_params_set_tstamp_type(
-			handle, swparams, SND_PCM_TSTAMP_TYPE_MONOTONIC_RAW);
-		if (err < 0) {
-			syslog(LOG_ERR, "set_tstamp_type: %s\n",
-			       snd_strerror(err));
-			return err;
-		}
-		err = snd_pcm_sw_params_set_tstamp_mode(handle, swparams,
-							SND_PCM_TSTAMP_ENABLE);
-		if (err < 0) {
-			syslog(LOG_ERR, "set_tstamp_mode: %s\n",
-			       snd_strerror(err));
-			return err;
-		}
-	}
-
-	/* This hack is required because ALSA-LIB does not provide any way to
-	 * detect whether MONOTONIC_RAW timestamps are supported by the kernel.
-	 * In ALSA-LIB, the code checks the hardware protocol version. */
 	err = snd_pcm_sw_params(handle, swparams);
-	if (err == -EINVAL && *enable_htimestamp) {
-		*enable_htimestamp = 0;
-		syslog(LOG_WARNING,
-		       "MONOTONIC_RAW timestamps are not supported.");
-
-		err = snd_pcm_sw_params_set_tstamp_type(
-			handle, swparams, SND_PCM_TSTAMP_TYPE_GETTIMEOFDAY);
-		if (err < 0) {
-			syslog(LOG_ERR, "set_tstamp_type: %s\n",
-			       snd_strerror(err));
-			return err;
-		}
-		err = snd_pcm_sw_params_set_tstamp_mode(handle, swparams,
-							SND_PCM_TSTAMP_NONE);
-		if (err < 0) {
-			syslog(LOG_ERR, "set_tstamp_mode: %s\n",
-			       snd_strerror(err));
-			return err;
-		}
-
-		err = snd_pcm_sw_params(handle, swparams);
-	}
 
 	if (err < 0) {
 		syslog(LOG_ERR, "sw_params: %s\n", snd_strerror(err));
diff --git a/cras/src/server/cras_alsa_helpers.h b/cras/src/server/cras_alsa_helpers.h
index 3897674..01a42ae 100644
--- a/cras/src/server/cras_alsa_helpers.h
+++ b/cras/src/server/cras_alsa_helpers.h
@@ -135,13 +135,10 @@
 /* Sets up the swparams to alsa.
  * Args:
  *    handle - The open PCM to configure.
- *    enable_htimestamp - If non-zero, enable and configure hardware timestamps,
- *                        updated to reflect whether MONOTONIC RAW htimestamps
- *                        are supported by the kernel implementation.
  * Returns:
  *    0 on success, negative error on failure.
  */
-int cras_alsa_set_swparams(snd_pcm_t *handle, int *enable_htimestamp);
+int cras_alsa_set_swparams(snd_pcm_t *handle);
 
 /* Get the number of used frames in the alsa buffer.
  *
diff --git a/cras/src/server/cras_alsa_io.c b/cras/src/server/cras_alsa_io.c
index 4aec9d6..275a681 100644
--- a/cras/src/server/cras_alsa_io.c
+++ b/cras/src/server/cras_alsa_io.c
@@ -38,7 +38,6 @@
 #include "softvol_curve.h"
 #include "utlist.h"
 
-#define MAX_ALSA_DEV_NAME_LENGTH 9 /* Alsa names "hw:XX,YY" + 1 for null. */
 #define HOTWORD_DEV "Wake on Voice"
 #define DEFAULT "(default)"
 #define HDMI "HDMI"
@@ -48,6 +47,8 @@
 #define HEADPHONE "Headphone"
 #define MIC "Mic"
 #define USB "USB"
+#define LOOPBACK_CAPTURE "Loopback Capture"
+#define LOOPBACK_PLAYBACK "Loopback Playback"
 
 /*
  * For USB, pad the output buffer.  This avoids a situation where there isn't a
@@ -79,12 +80,14 @@
  * This extends cras_ionode to include alsa-specific information.
  * Members:
  *    mixer_output - From cras_alsa_mixer.
+ *    pcm_name - PCM name for snd_pcm_open.
  *    volume_curve - Volume curve for this node.
  *    jack - The jack associated with the node.
  */
 struct alsa_output_node {
 	struct cras_ionode base;
 	struct mixer_control *mixer_output;
+	const char *pcm_name;
 	struct cras_volume_curve *volume_curve;
 	const struct cras_alsa_jack *jack;
 };
@@ -92,6 +95,7 @@
 struct alsa_input_node {
 	struct cras_ionode base;
 	struct mixer_control *mixer_input;
+	const char *pcm_name;
 	const struct cras_alsa_jack *jack;
 	int8_t *channel_layout;
 };
@@ -99,7 +103,7 @@
 /*
  * Child of cras_iodev, alsa_io handles ALSA interaction for sound devices.
  * base - The cras_iodev structure "base class".
- * dev - String that names this device (e.g. "hw:0,0").
+ * pcm_name - The PCM name passed to snd_pcm_open() (e.g. "hw:0,0").
  * dev_name - value from snd_pcm_info_get_name
  * dev_id - value from snd_pcm_info_get_id
  * device_index - ALSA index of device, Y in "hw:X:Y".
@@ -109,10 +113,7 @@
  * is_first - true if this is the first iodev on the card.
  * fully_specified - true if this device and it's nodes were fully specified.
  *     That is, don't automatically create nodes for it.
- * jack_always_plugged - true if this node is always plugged even without jack.
- * enable_htimestamp - True when the device's htimestamp is used.
  * handle - Handle to the opened ALSA device.
- * num_underruns - Number of times we have run out of data (playback only).
  * num_severe_underruns - Number of times we have run out of data badly.
                           Unlike num_underruns which records for the duration
                           where device is opened, num_severe_underruns records
@@ -124,8 +125,6 @@
  * jack_list - List of alsa jack controls for this device.
  * ucm - CRAS use case manager, if configuration is found.
  * mmap_offset - offset returned from mmap_begin.
- * dsp_name_default - the default dsp name for the device. It can be overridden
- *     by the jack specific dsp name.
  * poll_fd - Descriptor used to block until data is ready.
  * dma_period_set_microsecs - If non-zero, the value to apply to the dma_period.
  * free_running - true if device is playing zeros in the buffer without
@@ -136,10 +135,11 @@
  * severe_underrun_frames - The threshold for severe underrun.
  * default_volume_curve - Default volume curve that converts from an index
  *                        to dBFS.
+ * has_dependent_dev - true if this iodev has dependent device.
  */
 struct alsa_io {
 	struct cras_iodev base;
-	char *dev;
+	char *pcm_name;
 	char *dev_name;
 	char *dev_id;
 	uint32_t device_index;
@@ -147,10 +147,7 @@
 	enum CRAS_ALSA_CARD_TYPE card_type;
 	int is_first;
 	int fully_specified;
-	int jack_always_plugged;
-	int enable_htimestamp;
 	snd_pcm_t *handle;
-	unsigned int num_underruns;
 	unsigned int num_severe_underruns;
 	snd_pcm_stream_t alsa_stream;
 	struct cras_alsa_mixer *mixer;
@@ -158,7 +155,6 @@
 	struct cras_alsa_jack_list *jack_list;
 	struct cras_use_case_mgr *ucm;
 	snd_pcm_uframes_t mmap_offset;
-	const char *dsp_name_default;
 	int poll_fd;
 	unsigned int dma_period_set_microsecs;
 	int free_running;
@@ -166,6 +162,7 @@
 	snd_pcm_uframes_t severe_underrun_frames;
 	struct cras_volume_curve *default_volume_curve;
 	int hwparams_set;
+	int has_dependent_dev;
 };
 
 static void init_device_settings(struct alsa_io *aio);
@@ -174,6 +171,10 @@
 				      struct cras_ionode *ionode,
 				      unsigned dev_enabled);
 
+static int get_fixed_rate(struct alsa_io *aio);
+
+static int update_supported_formats(struct cras_iodev *iodev);
+
 /*
  * Defines the default values of nodes.
  */
@@ -267,6 +268,21 @@
 		.type = CRAS_NODE_TYPE_BLUETOOTH,
 		.position = NODE_POSITION_EXTERNAL,
 	},
+	{
+		.name = "Echo Reference",
+		.type = CRAS_NODE_TYPE_ECHO_REFERENCE,
+		.position = NODE_POSITION_INTERNAL,
+	},
+	{
+		.name = LOOPBACK_CAPTURE,
+		.type = CRAS_NODE_TYPE_ALSA_LOOPBACK,
+		.position = NODE_POSITION_INTERNAL,
+	},
+	{
+		.name = LOOPBACK_PLAYBACK,
+		.type = CRAS_NODE_TYPE_ALSA_LOOPBACK,
+		.position = NODE_POSITION_INTERNAL,
+	},
 };
 
 static int set_hwparams(struct cras_iodev *iodev)
@@ -313,8 +329,7 @@
 			aio->num_severe_underruns++;
 		return rc;
 	}
-	if (!aio->enable_htimestamp)
-		clock_gettime(CLOCK_MONOTONIC_RAW, tstamp);
+	clock_gettime(CLOCK_MONOTONIC_RAW, tstamp);
 	if (iodev->direction == CRAS_STREAM_INPUT)
 		return (int)frames;
 
@@ -356,7 +371,7 @@
 	return 0;
 }
 
-static int dummy_hotword_cb(void *arg)
+static int empty_hotword_cb(void *arg, int revents)
 {
 	/* Only need this once. */
 	struct alsa_io *aio = (struct alsa_io *)arg;
@@ -375,13 +390,42 @@
 	struct alsa_io *aio = (struct alsa_io *)iodev;
 	snd_pcm_t *handle;
 	int rc;
+	const char *pcm_name = NULL;
+	int enable_noise_cancellation;
 
-	rc = cras_alsa_pcm_open(&handle, aio->dev, aio->alsa_stream);
+	if (aio->base.direction == CRAS_STREAM_OUTPUT) {
+		struct alsa_output_node *aout =
+			(struct alsa_output_node *)aio->base.active_node;
+		pcm_name = aout->pcm_name;
+	} else {
+		struct alsa_input_node *ain =
+			(struct alsa_input_node *)aio->base.active_node;
+		pcm_name = ain->pcm_name;
+	}
+
+	/* For legacy UCM path which doesn't have PlaybackPCM or CapturePCM. */
+	if (pcm_name == NULL)
+		pcm_name = aio->pcm_name;
+
+	rc = cras_alsa_pcm_open(&handle, pcm_name, aio->alsa_stream);
 	if (rc < 0)
 		return rc;
 
 	aio->handle = handle;
 
+	/* Enable or disable noise cancellation if it supports. */
+	if (aio->ucm && iodev->direction == CRAS_STREAM_INPUT &&
+	    ucm_node_noise_cancellation_exists(aio->ucm,
+					       iodev->active_node->name)) {
+		enable_noise_cancellation =
+			cras_system_get_noise_cancellation_enabled();
+		rc = ucm_enable_node_noise_cancellation(
+			aio->ucm, iodev->active_node->name,
+			enable_noise_cancellation);
+		if (rc < 0)
+			return rc;
+	}
+
 	return 0;
 }
 
@@ -395,7 +439,6 @@
 	 */
 	if (iodev->format == NULL)
 		return -EINVAL;
-	aio->num_underruns = 0;
 	aio->free_running = 0;
 	aio->filled_zeros_for_draining = 0;
 	aio->severe_underrun_frames =
@@ -404,7 +447,7 @@
 	cras_iodev_init_audio_area(iodev, iodev->format->num_channels);
 
 	syslog(LOG_DEBUG, "Configure alsa device %s rate %zuHz, %zu channels",
-	       aio->dev, iodev->format->frame_rate,
+	       aio->pcm_name, iodev->format->frame_rate,
 	       iodev->format->num_channels);
 
 	rc = set_hwparams(iodev);
@@ -417,7 +460,7 @@
 		return rc;
 
 	/* Configure software params. */
-	rc = cras_alsa_set_swparams(aio->handle, &aio->enable_htimestamp);
+	rc = cras_alsa_set_swparams(aio->handle);
 	if (rc < 0)
 		return rc;
 
@@ -457,8 +500,8 @@
 		free(ufds);
 
 		if (aio->poll_fd >= 0)
-			audio_thread_add_callback(aio->poll_fd,
-						  dummy_hotword_cb, aio);
+			audio_thread_add_events_callback(
+				aio->poll_fd, empty_hotword_cb, aio, POLLIN);
 	}
 
 	/* Capture starts right away, playback will wait for samples. */
@@ -748,8 +791,7 @@
 {
 	const struct alsa_io *aio = (const struct alsa_io *)iodev;
 	struct alsa_input_node *ain;
-	long gain;
-
+	long min_capture_gain, max_capture_gain, gain;
 	assert(aio);
 	if (aio->mixer == NULL)
 		return;
@@ -757,20 +799,31 @@
 	/* Only set the volume if the dev is active. */
 	if (!has_handle(aio))
 		return;
-	gain = cras_iodev_adjust_active_node_gain(
-		iodev, cras_system_get_capture_gain());
+
+	ain = get_active_input(aio);
+
+	cras_alsa_mixer_set_capture_mute(aio->mixer,
+					 cras_system_get_capture_mute(),
+					 ain ? ain->mixer_input : NULL);
+
+	/* For USB device without UCM config, not change a gain control. */
+	if (ain && ain->base.type == CRAS_NODE_TYPE_USB && !aio->ucm)
+		return;
 
 	/* Set hardware gain to 0dB if software gain is needed. */
 	if (cras_iodev_software_volume_needed(iodev))
 		gain = 0;
-
-	ain = get_active_input(aio);
+	else {
+		min_capture_gain = cras_alsa_mixer_get_minimum_capture_gain(
+			aio->mixer, ain ? ain->mixer_input : NULL);
+		max_capture_gain = cras_alsa_mixer_get_maximum_capture_gain(
+			aio->mixer, ain ? ain->mixer_input : NULL);
+		gain = MAX(iodev->active_node->capture_gain, min_capture_gain);
+		gain = MIN(gain, max_capture_gain);
+	}
 
 	cras_alsa_mixer_set_capture_dBFS(aio->mixer, gain,
 					 ain ? ain->mixer_input : NULL);
-	cras_alsa_mixer_set_capture_mute(aio->mixer,
-					 cras_system_get_capture_mute(),
-					 ain ? ain->mixer_input : NULL);
 }
 
 /*
@@ -798,28 +851,6 @@
 		set_alsa_volume(&aio->base);
 		set_alsa_mute(&aio->base);
 	} else {
-		struct mixer_control *mixer_input = NULL;
-		struct alsa_input_node *ain = get_active_input(aio);
-		long min_capture_gain, max_capture_gain;
-
-		if (ain)
-			mixer_input = ain->mixer_input;
-
-		if (cras_iodev_software_volume_needed(&aio->base)) {
-			min_capture_gain =
-				cras_iodev_minimum_software_gain(&aio->base);
-			max_capture_gain =
-				cras_iodev_maximum_software_gain(&aio->base);
-		} else {
-			min_capture_gain =
-				cras_alsa_mixer_get_minimum_capture_gain(
-					aio->mixer, mixer_input);
-			max_capture_gain =
-				cras_alsa_mixer_get_maximum_capture_gain(
-					aio->mixer, mixer_input);
-		}
-		cras_system_set_capture_gain_limits(min_capture_gain,
-						    max_capture_gain);
 		set_alsa_capture_gain(&aio->base);
 	}
 }
@@ -837,6 +868,7 @@
 {
 	struct cras_ionode *node;
 	struct alsa_output_node *aout;
+	struct alsa_input_node *ain;
 
 	free(aio->base.supported_rates);
 	free(aio->base.supported_channel_counts);
@@ -846,6 +878,10 @@
 		if (aio->base.direction == CRAS_STREAM_OUTPUT) {
 			aout = (struct alsa_output_node *)node;
 			cras_volume_curve_destroy(aout->volume_curve);
+			free((void *)aout->pcm_name);
+		} else {
+			ain = (struct alsa_input_node *)node;
+			free((void *)ain->pcm_name);
 		}
 		cras_iodev_rm_node(&aio->base, node);
 		free(node->softvol_scalers);
@@ -853,9 +889,8 @@
 		free(node);
 	}
 
-	free((void *)aio->dsp_name_default);
 	cras_iodev_free_resources(&aio->base);
-	free(aio->dev);
+	free(aio->pcm_name);
 	if (aio->dev_id)
 		free(aio->dev_id);
 	if (aio->dev_name)
@@ -1067,71 +1102,50 @@
 		       output->base.name);
 }
 
-static void set_input_node_software_volume_needed(struct alsa_input_node *input,
-						  struct alsa_io *aio)
-{
-	long min_software_gain;
-	long max_software_gain;
-	int rc;
-
-	input->base.software_volume_needed = 0;
-	input->base.min_software_gain = DEFAULT_MIN_CAPTURE_GAIN;
-	input->base.max_software_gain = 0;
-
-	/* Enable software gain only if max software gain is specified in UCM. */
-	if (!aio->ucm)
-		return;
-
-	rc = ucm_get_max_software_gain(aio->ucm, input->base.name,
-				       &max_software_gain);
-
-	/* If max software gain doesn't exist, skip min software gain setting. */
-	if (rc)
-		return;
-
-	input->base.software_volume_needed = 1;
-	input->base.max_software_gain = max_software_gain;
-	syslog(LOG_INFO,
-	       "Use software gain for %s with max %ld because it is specified"
-	       " in UCM",
-	       input->base.name, max_software_gain);
-
-	/* Enable min software gain if it is specified in UCM. */
-	rc = ucm_get_min_software_gain(aio->ucm, input->base.name,
-				       &min_software_gain);
-	if (rc)
-		return;
-
-	if (min_software_gain > max_software_gain) {
-		syslog(LOG_ERR,
-		       "Ignore MinSoftwareGain %ld because it is larger than "
-		       "MaxSoftwareGain %ld",
-		       min_software_gain, max_software_gain);
-		return;
-	}
-
-	syslog(LOG_INFO,
-	       "Use software gain for %s with min %ld because it is specified"
-	       " in UCM",
-	       input->base.name, min_software_gain);
-	input->base.min_software_gain = min_software_gain;
-}
-
 static void set_input_default_node_gain(struct alsa_input_node *input,
 					struct alsa_io *aio)
 {
-	long default_node_gain;
-	int rc;
+	long gain;
+
+	input->base.capture_gain = DEFAULT_CAPTURE_GAIN;
+	input->base.ui_gain_scaler = 1.0f;
 
 	if (!aio->ucm)
 		return;
 
-	rc = ucm_get_default_node_gain(aio->ucm, input->base.name,
-				       &default_node_gain);
-	if (rc)
-		return;
+	if (ucm_get_default_node_gain(aio->ucm, input->base.name, &gain) == 0)
+		input->base.capture_gain = gain;
+}
 
-	input->base.capture_gain = default_node_gain;
+static void set_input_node_intrinsic_sensitivity(struct alsa_input_node *input,
+						 struct alsa_io *aio)
+{
+	long sensitivity;
+	int rc;
+
+	input->base.intrinsic_sensitivity = 0;
+
+	if (aio->ucm) {
+		rc = ucm_get_intrinsic_sensitivity(aio->ucm, input->base.name,
+						   &sensitivity);
+		if (rc)
+			return;
+	} else if (input->base.type == CRAS_NODE_TYPE_USB) {
+		/*
+		 * For USB devices without UCM config, trust the default capture gain.
+		 * Set sensitivity to the default dbfs so the capture gain is 0.
+		 */
+		sensitivity = DEFAULT_CAPTURE_VOLUME_DBFS;
+	} else {
+		return;
+	}
+
+	input->base.intrinsic_sensitivity = sensitivity;
+	input->base.capture_gain = DEFAULT_CAPTURE_VOLUME_DBFS - sensitivity;
+	syslog(LOG_INFO,
+	       "Use software gain %ld for %s because IntrinsicSensitivity %ld is"
+	       " specified in UCM",
+	       input->base.capture_gain, input->base.name, sensitivity);
 }
 
 static void check_auto_unplug_output_node(struct alsa_io *aio,
@@ -1249,7 +1263,6 @@
 {
 	struct cras_iodev *iodev = &aio->base;
 	struct alsa_input_node *input;
-	char *mic_positions;
 	int err;
 
 	input = (struct alsa_input_node *)calloc(1, sizeof(*input));
@@ -1266,22 +1279,10 @@
 	input->mixer_input = cras_input;
 	strncpy(input->base.name, name, sizeof(input->base.name) - 1);
 	set_node_initial_state(&input->base, aio->card_type);
-	set_input_node_software_volume_needed(input, aio);
 	set_input_default_node_gain(input, aio);
+	set_input_node_intrinsic_sensitivity(input, aio);
 
 	if (aio->ucm) {
-		/* Check mic positions only for internal mic. */
-		if ((input->base.type == CRAS_NODE_TYPE_MIC) &&
-		    (input->base.position == NODE_POSITION_INTERNAL)) {
-			mic_positions = ucm_get_mic_positions(aio->ucm);
-			if (mic_positions) {
-				strncpy(input->base.mic_positions,
-					mic_positions,
-					sizeof(input->base.mic_positions) - 1);
-				free(mic_positions);
-			}
-		}
-
 		/* Check if channel map is specified in UCM. */
 		input->channel_layout = (int8_t *)malloc(
 			CRAS_CH_MAX * sizeof(*input->channel_layout));
@@ -1387,8 +1388,7 @@
 
 /*
  * Returns the dsp name specified in the ucm config. If there is a dsp name
- * specified for the active node, use that. Otherwise use the default dsp name
- * for the alsa_io device.
+ * specified for the active node, use that. Otherwise NULL should be returned.
  */
 static const char *get_active_dsp_name(struct alsa_io *aio)
 {
@@ -1397,7 +1397,7 @@
 	if (node == NULL)
 		return NULL;
 
-	return node->dsp_name ?: aio->dsp_name_default;
+	return node->dsp_name;
 }
 
 /*
@@ -1426,6 +1426,73 @@
 }
 
 /*
+ * Updates max_supported_channels value into cras_iodev_info.
+ * Note that supported_rates, supported_channel_counts, and supported_formats of
+ * iodev will be updated to the latest values after calling.
+ */
+static void update_max_supported_channels(struct cras_iodev *iodev)
+{
+	struct alsa_io *aio = (struct alsa_io *)iodev;
+	unsigned int max_channels = 0;
+	size_t i;
+	bool active_node_predicted = false;
+	int rc;
+
+	/*
+	 * max_supported_channels might be wrong in dependent PCM cases. Always
+	 * return 2 for such cases.
+	 */
+	if (aio->has_dependent_dev) {
+		max_channels = 2;
+		goto update_info;
+	}
+
+	if (aio->handle) {
+		syslog(LOG_ERR,
+		       "update_max_supported_channels should not be called "
+		       "while device is opened.");
+		return;
+	}
+
+	/*
+	 * In the case of updating max_supported_channels on changing jack
+	 * plugging status of devices, the active node may not be determined
+	 * yet. Use the first node as the active node for obtaining the value of
+	 * max_supported_channels.
+	 */
+	if (!iodev->active_node) {
+		if (!iodev->nodes)
+			goto update_info;
+		iodev->active_node = iodev->nodes;
+		syslog(LOG_DEBUG,
+		       "Predict ionode %s as active node temporarily.",
+		       iodev->active_node->name);
+		active_node_predicted = true;
+	}
+
+	rc = open_dev(iodev);
+	if (active_node_predicted)
+		iodev->active_node = NULL; // Reset the predicted active_node.
+	if (rc)
+		goto update_info;
+
+	rc = update_supported_formats(iodev);
+	if (rc)
+		goto close_iodev;
+
+	for (i = 0; iodev->supported_channel_counts[i] != 0; i++) {
+		if (iodev->supported_channel_counts[i] > max_channels)
+			max_channels = iodev->supported_channel_counts[i];
+	}
+
+close_iodev:
+	close_dev(iodev);
+
+update_info:
+	iodev->info.max_supported_channels = max_channels;
+}
+
+/*
  * Callback that is called when an output jack is plugged or unplugged.
  */
 static void jack_output_plug_event(const struct cras_alsa_jack *jack,
@@ -1488,6 +1555,13 @@
 	cras_iodev_set_node_plugged(&node->base, plugged);
 
 	check_auto_unplug_output_node(aio, &node->base, plugged);
+
+	/*
+	 * For HDMI plug event cases, update max supported channels according
+	 * to the current active node.
+	 */
+	if (node->base.type == CRAS_NODE_TYPE_HDMI && plugged)
+		update_max_supported_channels(&aio->base);
 }
 
 /*
@@ -1601,6 +1675,29 @@
 	return ucm_get_sample_rate_for_dev(aio->ucm, name, aio->base.direction);
 }
 
+static size_t get_fixed_channels(struct alsa_io *aio)
+{
+	const char *name;
+	int rc;
+	size_t channels;
+
+	if (aio->base.direction == CRAS_STREAM_OUTPUT) {
+		struct alsa_output_node *active = get_active_output(aio);
+		if (!active)
+			return -ENOENT;
+		name = active->base.name;
+	} else {
+		struct alsa_input_node *active = get_active_input(aio);
+		if (!active)
+			return -ENOENT;
+		name = active->base.name;
+	}
+
+	rc = ucm_get_channels_for_dev(aio->ucm, name, aio->base.direction,
+				      &channels);
+	return (rc) ? 0 : channels;
+}
+
 /*
  * Updates the supported sample rates and channel counts.
  */
@@ -1609,6 +1706,7 @@
 	struct alsa_io *aio = (struct alsa_io *)iodev;
 	int err;
 	int fixed_rate;
+	size_t fixed_channels;
 
 	free(iodev->supported_rates);
 	iodev->supported_rates = NULL;
@@ -1633,6 +1731,16 @@
 			iodev->supported_rates[0] = fixed_rate;
 			iodev->supported_rates[1] = 0;
 		}
+
+		/* Allow UCM to override supported channel counts. */
+		fixed_channels = get_fixed_channels(aio);
+		if (fixed_channels > 0) {
+			free(iodev->supported_channel_counts);
+			iodev->supported_channel_counts = (size_t *)malloc(
+				2 * sizeof(iodev->supported_channel_counts[0]));
+			iodev->supported_channel_counts[0] = fixed_channels;
+			iodev->supported_channel_counts[1] = 0;
+		}
 	}
 	return 0;
 }
@@ -1753,8 +1861,8 @@
 	 * If underrun happened, handle it. Because alsa_output_underrun function
 	 * has already called adjust_appl_ptr, we don't need to call it again.
 	 */
-	if (real_hw_level < odev->min_buffer_level)
-		return odev->output_underrun(odev);
+	if (real_hw_level <= odev->min_buffer_level)
+		return cras_iodev_output_underrun(odev, real_hw_level, 0);
 
 	if (real_hw_level > aio->filled_zeros_for_draining)
 		valid_sample = real_hw_level - aio->filled_zeros_for_draining;
@@ -1772,12 +1880,8 @@
 
 static int alsa_output_underrun(struct cras_iodev *odev)
 {
-	struct alsa_io *aio = (struct alsa_io *)odev;
 	int rc;
 
-	/* Update number of underruns we got. */
-	aio->num_underruns++;
-
 	/* Fill whole buffer with zeros. This avoids samples left in buffer causing
 	 * noise when device plays them. */
 	rc = fill_whole_buffer_with_zeros(odev);
@@ -1806,8 +1910,8 @@
 	real_hw_level = rc;
 
 	/* If underrun happened, handle it and enter free run state. */
-	if (real_hw_level < odev->min_buffer_level) {
-		rc = odev->output_underrun(odev);
+	if (real_hw_level <= odev->min_buffer_level) {
+		rc = cras_iodev_output_underrun(odev, real_hw_level, 0);
 		if (rc < 0)
 			return rc;
 		aio->free_running = 1;
@@ -1840,6 +1944,10 @@
 	struct alsa_io *aio = (struct alsa_io *)odev;
 	int rc;
 
+	/* Restart rate estimation because free run internval should not
+	 * be included. */
+	cras_iodev_reset_rate_estimator(odev);
+
 	if (aio->free_running)
 		rc = adjust_appl_ptr_for_leaving_free_run(odev);
 	else
@@ -1876,12 +1984,6 @@
 	return aio->free_running;
 }
 
-static unsigned int get_num_underruns(const struct cras_iodev *iodev)
-{
-	const struct alsa_io *aio = (const struct alsa_io *)iodev;
-	return aio->num_underruns;
-}
-
 static unsigned int get_num_severe_underruns(const struct cras_iodev *iodev)
 {
 	const struct alsa_io *aio = (const struct alsa_io *)iodev;
@@ -1906,8 +2008,7 @@
 			return;
 }
 
-static int get_valid_frames(const struct cras_iodev *odev,
-			    struct timespec *tstamp)
+static int get_valid_frames(struct cras_iodev *odev, struct timespec *tstamp)
 {
 	struct alsa_io *aio = (struct alsa_io *)odev;
 	int rc;
@@ -1934,15 +2035,26 @@
 	return 0;
 }
 
+static int support_noise_cancellation(const struct cras_iodev *iodev)
+{
+	struct alsa_io *aio = (struct alsa_io *)iodev;
+
+	if (!aio->ucm || !iodev->active_node)
+		return 0;
+
+	return ucm_node_noise_cancellation_exists(aio->ucm,
+						  iodev->active_node->name);
+}
+
 /*
  * Exported Interface.
  */
 
 struct cras_iodev *
 alsa_iodev_create(size_t card_index, const char *card_name, size_t device_index,
-		  const char *dev_name, const char *dev_id,
-		  enum CRAS_ALSA_CARD_TYPE card_type, int is_first,
-		  struct cras_alsa_mixer *mixer,
+		  const char *pcm_name, const char *dev_name,
+		  const char *dev_id, enum CRAS_ALSA_CARD_TYPE card_type,
+		  int is_first, struct cras_alsa_mixer *mixer,
 		  const struct cras_card_config *config,
 		  struct cras_use_case_mgr *ucm, snd_hctl_t *hctl,
 		  enum CRAS_STREAM_DIRECTION direction, size_t usb_vid,
@@ -1965,7 +2077,6 @@
 	aio->is_first = is_first;
 	aio->handle = NULL;
 	aio->num_severe_underruns = 0;
-	aio->jack_always_plugged = 0;
 	if (dev_name) {
 		aio->dev_name = strdup(dev_name);
 		if (!aio->dev_name)
@@ -1978,11 +2089,10 @@
 	}
 	aio->free_running = 0;
 	aio->filled_zeros_for_draining = 0;
-	aio->dev = (char *)malloc(MAX_ALSA_DEV_NAME_LENGTH);
-	if (aio->dev == NULL)
+	aio->has_dependent_dev = 0;
+	aio->pcm_name = strdup(pcm_name);
+	if (aio->pcm_name == NULL)
 		goto cleanup_iodev;
-	snprintf(aio->dev, MAX_ALSA_DEV_NAME_LENGTH, "hw:%zu,%zu", card_index,
-		 device_index);
 
 	if (direction == CRAS_STREAM_INPUT) {
 		aio->alsa_stream = SND_PCM_STREAM_CAPTURE;
@@ -2010,10 +2120,10 @@
 	iodev->get_hotword_models = get_hotword_models;
 	iodev->no_stream = no_stream;
 	iodev->is_free_running = is_free_running;
-	iodev->get_num_underruns = get_num_underruns;
 	iodev->get_num_severe_underruns = get_num_severe_underruns;
 	iodev->get_valid_frames = get_valid_frames;
 	iodev->set_swap_mode_for_node = cras_iodev_dsp_set_swap_mode_for_node;
+	iodev->support_noise_cancellation = support_noise_cancellation;
 
 	if (card_type == ALSA_CARD_TYPE_USB)
 		iodev->min_buffer_level = USB_EXTRA_BUFFER_FRAMES;
@@ -2021,6 +2131,7 @@
 	iodev->ramp = cras_ramp_create();
 	if (iodev->ramp == NULL)
 		goto cleanup_iodev;
+	iodev->initial_ramp_request = CRAS_IODEV_RAMP_REQUEST_UP_START_PLAYBACK;
 
 	aio->mixer = mixer;
 	aio->config = config;
@@ -2037,8 +2148,6 @@
 		unsigned int level;
 		int rc;
 
-		aio->dsp_name_default =
-			ucm_get_dsp_name_default(ucm, direction);
 		/* Set callback for swap mode if it is supported
 		 * in ucm modifier. */
 		if (ucm_swap_mode_exists(ucm))
@@ -2048,8 +2157,6 @@
 		rc = ucm_get_min_buffer_level(ucm, &level);
 		if (!rc && direction == CRAS_STREAM_OUTPUT)
 			iodev->min_buffer_level = level;
-
-		aio->enable_htimestamp = ucm_get_enable_htimestamp_flag(ucm);
 	}
 
 	set_iodev_name(iodev, card_name, dev_name, card_index, device_index,
@@ -2163,6 +2270,9 @@
 
 	set_default_hotword_model(iodev);
 
+	/* Record max supported channels into cras_iodev_info. */
+	update_max_supported_channels(iodev);
+
 	return 0;
 }
 
@@ -2178,9 +2288,17 @@
 
 	if (!aio || !section)
 		return -EINVAL;
-	if ((uint32_t)section->dev_idx != aio->device_index)
+
+	/* Allow this section to add as a new node only if the device id
+	 * or dependent device id matches this iodev. */
+	if (((uint32_t)section->dev_idx != aio->device_index) &&
+	    ((uint32_t)section->dependent_dev_idx != aio->device_index))
 		return -EINVAL;
 
+	/* Set flag has_dependent_dev for the case of dependent device. */
+	if (section->dependent_dev_idx != -1)
+		aio->has_dependent_dev = 1;
+
 	/* This iodev is fully specified. Avoid automatic node creation. */
 	aio->fully_specified = 1;
 
@@ -2197,15 +2315,14 @@
 		output_node = new_output(aio, control, section->name);
 		if (!output_node)
 			return -ENOMEM;
+		output_node->pcm_name = strdup(section->pcm_name);
 	} else if (iodev->direction == CRAS_STREAM_INPUT) {
 		input_node = new_input(aio, control, section->name);
 		if (!input_node)
 			return -ENOMEM;
+		input_node->pcm_name = strdup(section->pcm_name);
 	}
 
-	if (section->jack_type && !strcmp(section->jack_type, "always"))
-		aio->jack_always_plugged = 1;
-
 	/* Find any jack controls for this device. */
 	rc = cras_alsa_jack_list_add_jack_for_section(aio->jack_list, section,
 						      &jack);
@@ -2230,6 +2347,7 @@
 void alsa_iodev_ucm_complete_init(struct cras_iodev *iodev)
 {
 	struct alsa_io *aio = (struct alsa_io *)iodev;
+	struct cras_ionode *node;
 
 	if (!iodev)
 		return;
@@ -2247,15 +2365,22 @@
 
 	/*
 	 * Set plugged for the USB device per card when it appears if
-	 * there is no jack reporting plug status and the jack is set
-	 * to be always plugged.
+	 * there is no jack reporting plug status
 	 */
-	if (aio->card_type == ALSA_CARD_TYPE_USB && aio->jack_always_plugged &&
-	    !get_jack_from_node(iodev->active_node)) {
-		cras_iodev_set_node_plugged(iodev->active_node, 1);
+	if (aio->card_type == ALSA_CARD_TYPE_USB) {
+		DL_FOREACH (iodev->nodes, node) {
+			if (!get_jack_from_node(node))
+				cras_iodev_set_node_plugged(node, 1);
+		}
 	}
 
 	set_default_hotword_model(iodev);
+
+	node = iodev->active_node;
+
+	/* Record max supported channels into cras_iodev_info. */
+	if (node && node->plugged)
+		update_max_supported_channels(iodev);
 }
 
 void alsa_iodev_destroy(struct cras_iodev *iodev)
@@ -2319,12 +2444,10 @@
 				      unsigned dev_enabled)
 {
 	struct alsa_io *aio = (struct alsa_io *)iodev;
+	int rc = 0;
 
-	if (iodev->active_node == ionode) {
-		enable_active_ucm(aio, dev_enabled);
-		init_device_settings(aio);
-		return 0;
-	}
+	if (iodev->active_node == ionode)
+		goto skip;
 
 	/* Disable jack ucm before switching node. */
 	enable_active_ucm(aio, 0);
@@ -2334,7 +2457,16 @@
 	cras_iodev_set_active_node(iodev, ionode);
 	aio->base.dsp_name = get_active_dsp_name(aio);
 	cras_iodev_update_dsp(iodev);
+skip:
 	enable_active_ucm(aio, dev_enabled);
+	if (ionode->type == CRAS_NODE_TYPE_HOTWORD) {
+		if (dev_enabled) {
+			rc = ucm_enable_hotword_model(aio->ucm);
+			if (rc < 0)
+				return rc;
+		} else
+			ucm_disable_all_hotword_models(aio->ucm);
+	}
 	/* Setting the volume will also unmute if the system isn't muted. */
 	init_device_settings(aio);
 	return 0;
diff --git a/cras/src/server/cras_alsa_io.h b/cras/src/server/cras_alsa_io.h
index 9bc0c1f..f8e613d 100644
--- a/cras/src/server/cras_alsa_io.h
+++ b/cras/src/server/cras_alsa_io.h
@@ -21,6 +21,7 @@
  *    card_index - 0 based index, value of "XX" in "hw:XX,YY".
  *    card_name - The name of the card.
  *    device_index - 0 based index, value of "YY" in "hw:XX,YY".
+ *    pcm_name - The pcm name passing to snd_pcm_open(), e.g hw:0,0
  *    dev_name - The name of the device.
  *    dev_id - The id string of the device.
  *    card_type - the type of the card this iodev belongs.
@@ -38,9 +39,9 @@
  */
 struct cras_iodev *
 alsa_iodev_create(size_t card_index, const char *card_name, size_t device_index,
-		  const char *dev_name, const char *dev_id,
-		  enum CRAS_ALSA_CARD_TYPE card_type, int is_first,
-		  struct cras_alsa_mixer *mixer,
+		  const char *pcm_name, const char *dev_name,
+		  const char *dev_id, enum CRAS_ALSA_CARD_TYPE card_type,
+		  int is_first, struct cras_alsa_mixer *mixer,
 		  const struct cras_card_config *config,
 		  struct cras_use_case_mgr *ucm, snd_hctl_t *hctl,
 		  enum CRAS_STREAM_DIRECTION direction, size_t usb_vid,
diff --git a/cras/src/server/cras_alsa_jack.c b/cras/src/server/cras_alsa_jack.c
index a16e94e..6d4d7bf 100644
--- a/cras/src/server/cras_alsa_jack.c
+++ b/cras/src/server/cras_alsa_jack.c
@@ -380,7 +380,7 @@
  *   file has data to read.  Perform autoswitching to / from the
  *   associated device when data is available.
  */
-static void gpio_switch_callback(void *arg)
+static void gpio_switch_callback(void *arg, int events)
 {
 	struct cras_alsa_jack *jack = arg;
 	int i;
@@ -408,38 +408,30 @@
 static unsigned int
 gpio_jack_match_device(const struct cras_alsa_jack *jack,
 		       struct cras_alsa_jack_list *jack_list,
-		       const char *card_name,
 		       enum CRAS_STREAM_DIRECTION direction)
 {
-	const char *target_device_name = NULL;
-	char current_device_name[CRAS_IODEV_NAME_BUFFER_SIZE];
-	unsigned int rc;
+	int target_dev_idx;
 
 	/* If the device name is not specified in UCM, assume it should be
 	 * associated with device 0. */
 	if (!jack_list->ucm || !jack->ucm_device)
 		return jack_list->is_first_device;
 
-	/* Look for device name specified in a device section of UCM. */
-	target_device_name = ucm_get_device_name_for_dev(
+	/* If jack has valid ucm_device, that means this jack has already been
+	 * associated to this card. Next step to match device index on this
+	 * card. */
+	target_dev_idx = ucm_get_alsa_dev_idx_for_dev(
 		jack_list->ucm, jack->ucm_device, direction);
 
-	if (!target_device_name)
+	if (target_dev_idx < 0)
 		return jack_list->is_first_device;
 
 	syslog(LOG_DEBUG,
-	       "Matching GPIO jack, target device name: %s, "
+	       "Matching GPIO jack, target device idx: %d, "
 	       "current card name: %s, device index: %zu\n",
-	       target_device_name, card_name, jack_list->device_index);
+	       target_dev_idx, jack_list->card_name, jack_list->device_index);
 
-	/* Device name of format "hw:<card_name>,<device_index>", should fit
-	 * in the string of size CRAS_IODEV_NAME_BUFFER_SIZE.*/
-	snprintf(current_device_name, sizeof(current_device_name), "hw:%s,%zu",
-		 card_name, jack_list->device_index);
-
-	rc = !strcmp(current_device_name, target_device_name);
-	free((void *)target_device_name);
-	return rc;
+	return (target_dev_idx == jack_list->device_index);
 }
 
 static int create_jack_for_gpio(struct cras_alsa_jack_list *jack_list,
@@ -511,8 +503,8 @@
 		cras_free_jack(jack, 0);
 		return -EIO;
 	}
-	r = cras_system_add_select_fd(jack->gpio.fd, gpio_switch_callback,
-				      jack);
+	r = cras_system_add_select_fd(jack->gpio.fd, gpio_switch_callback, jack,
+				      POLLIN);
 	if (r < 0) {
 		/* Not yet registered with system select. */
 		cras_free_jack(jack, 0);
@@ -540,7 +532,6 @@
 {
 	struct cras_alsa_jack *jack;
 	struct cras_alsa_jack_list *jack_list = data->jack_list;
-	const char *card_name = jack_list->card_name;
 	enum CRAS_STREAM_DIRECTION direction = jack_list->direction;
 	int r;
 
@@ -553,7 +544,7 @@
 		jack->ucm_device = ucm_get_dev_for_jack(
 			jack_list->ucm, jack->gpio.device_name, direction);
 
-	if (!gpio_jack_match_device(jack, jack_list, card_name, direction)) {
+	if (!gpio_jack_match_device(jack, jack_list, direction)) {
 		cras_free_jack(jack, 0);
 		return -EIO;
 	}
@@ -728,6 +719,31 @@
 	return data->rc;
 }
 
+/* Find ELD control for HDMI/DP gpio jack. */
+static snd_hctl_elem_t *find_eld_control_by_dev_index(snd_hctl_t *hctl,
+						      unsigned int dev_idx)
+{
+	static const char eld_control_name[] = "ELD";
+	snd_ctl_elem_id_t *elem_id;
+
+	snd_ctl_elem_id_alloca(&elem_id);
+	snd_ctl_elem_id_clear(elem_id);
+	snd_ctl_elem_id_set_interface(elem_id, SND_CTL_ELEM_IFACE_PCM);
+	snd_ctl_elem_id_set_device(elem_id, dev_idx);
+	snd_ctl_elem_id_set_name(elem_id, eld_control_name);
+	return snd_hctl_find_elem(hctl, elem_id);
+}
+
+/* For non-gpio jack, check if it's of type hdmi/dp by
+ * matching jack name. */
+static int is_jack_hdmi_dp(const char *jack_name)
+{
+	// TODO(hychao): Use the information provided in UCM instead of
+	// name matching.
+	static const char *hdmi_dp = "HDMI";
+	return !!strstr(jack_name, hdmi_dp);
+}
+
 /* Find GPIO jacks for this jack_list.
  * Args:
  *    jack_list - Jack list to add to.
@@ -763,8 +779,17 @@
 		gpio_switch_list_for_each(gpio_switch_list_with_section, &data);
 	else
 		gpio_switch_list_for_each(gpio_switch_list_by_matching, &data);
-	if (result_jack)
+	if (result_jack) {
 		*result_jack = data.result_jack;
+
+		/* Find ELD control only for HDMI/DP gpio jack. */
+		if (*result_jack &&
+		    is_jack_hdmi_dp((*result_jack)->gpio.device_name))
+			(*result_jack)->eld_control =
+				find_eld_control_by_dev_index(
+					jack_list->hctl,
+					jack_list->device_index);
+	}
 	return data.rc;
 }
 
@@ -819,14 +844,6 @@
 	return (unsigned int)device_index;
 }
 
-/* For non-gpio jack, check if it's of type hdmi/dp by
- * matching jack name. */
-static int is_jack_hdmi_dp(const char *jack_name)
-{
-	static const char *hdmi_dp = "HDMI/DP";
-	return strncmp(jack_name, hdmi_dp, strlen(hdmi_dp)) == 0;
-}
-
 /* Checks if the given control name is in the supplied list of possible jack
  * control base names. */
 static int is_jack_control_in_list(const char *const *list,
@@ -867,7 +884,6 @@
 	static const char *const input_jack_base_names[] = {
 		"Mic Jack",
 	};
-	static const char eld_control_name[] = "ELD";
 	const char *const *jack_names;
 	unsigned int num_jack_names;
 
@@ -941,17 +957,9 @@
 		name = snd_hctl_elem_get_name(jack->elem);
 		if (!is_jack_hdmi_dp(name))
 			continue;
-		for (elem = snd_hctl_first_elem(jack_list->hctl); elem != NULL;
-		     elem = snd_hctl_elem_next(elem)) {
-			if (strcmp(snd_hctl_elem_get_name(elem),
-				   eld_control_name))
-				continue;
-			if (snd_hctl_elem_get_device(elem) !=
-			    jack_list->device_index)
-				continue;
-			jack->eld_control = elem;
-			break;
-		}
+
+		jack->eld_control = find_eld_control_by_dev_index(
+			jack_list->hctl, jack_list->device_index);
 	}
 
 	return 0;
@@ -977,7 +985,6 @@
 				      struct ucm_section *section,
 				      struct cras_alsa_jack **result_jack)
 {
-	static const char eld_control_name[] = "ELD";
 	snd_hctl_elem_t *elem;
 	snd_ctl_elem_id_t *elem_id;
 	struct cras_alsa_jack *jack;
@@ -1028,10 +1035,8 @@
 		return 0;
 
 	/* Look up ELD control. */
-	snd_ctl_elem_id_set_name(elem_id, eld_control_name);
-	elem = snd_hctl_find_elem(jack_list->hctl, elem_id);
-	if (elem)
-		jack->eld_control = elem;
+	jack->eld_control = find_eld_control_by_dev_index(
+		jack_list->hctl, jack_list->device_index);
 	return 0;
 }
 
diff --git a/cras/src/server/cras_alsa_mixer.c b/cras/src/server/cras_alsa_mixer.c
index 99f4d61..3379d95 100644
--- a/cras/src/server/cras_alsa_mixer.c
+++ b/cras/src/server/cras_alsa_mixer.c
@@ -417,7 +417,7 @@
 				  int muted)
 {
 	const struct mixer_control_element *elem = NULL;
-	int rc;
+	int rc = -EINVAL;
 	if (!control)
 		return -EINVAL;
 	DL_FOREACH (control->elements, elem) {
@@ -943,7 +943,7 @@
 	assert(cras_mixer);
 
 	/* dBFS is normally < 0 to specify the attenuation from max. max is the
-	 * combined max of the master controls and the current output.
+	 * combined max of the main controls and the current output.
 	 */
 	to_set = dBFS + cras_mixer->max_volume_dB;
 	if (cras_alsa_mixer_has_volume(mixer_output))
@@ -959,9 +959,9 @@
 
 		if (!c->has_volume)
 			continue;
-		mixer_control_set_dBFS(c, to_set);
-		mixer_control_get_dBFS(c, &actual_dB);
-		to_set -= actual_dB;
+		if (mixer_control_set_dBFS(c, to_set) == 0 &&
+		    mixer_control_get_dBFS(c, &actual_dB) == 0)
+			to_set -= actual_dB;
 	}
 	/* Apply the rest to the output-specific control. */
 	if (cras_alsa_mixer_has_volume(mixer_output))
@@ -1002,9 +1002,9 @@
 
 		if (!c->has_volume)
 			continue;
-		mixer_control_set_dBFS(c, to_set);
-		mixer_control_get_dBFS(c, &actual_dB);
-		to_set -= actual_dB;
+		if (mixer_control_set_dBFS(c, to_set) == 0 &&
+		    mixer_control_get_dBFS(c, &actual_dB) == 0)
+			to_set -= actual_dB;
 	}
 
 	/* Apply the reset to input specific control */
diff --git a/cras/src/server/cras_alsa_mixer.h b/cras/src/server/cras_alsa_mixer.h
index 6b30603..878fbe5 100644
--- a/cras/src/server/cras_alsa_mixer.h
+++ b/cras/src/server/cras_alsa_mixer.h
@@ -6,9 +6,6 @@
 #ifndef _CRAS_ALSA_MIXER_H
 #define _CRAS_ALSA_MIXER_H
 
-#include <alsa/asoundlib.h>
-#include <iniparser.h>
-
 #include "cras_types.h"
 
 /* cras_alsa_mixer represents the alsa mixer interface for an alsa card.  It
@@ -150,7 +147,7 @@
  * Args:
  *    cras_mixer - Mixer to set the volume in.
  *    muted - 1 if muted, 0 if not.
- *    mixer_input - The mixer input to mute if no master mute.
+ *    mixer_input - The mixer input to mute if no card mute.
  */
 void cras_alsa_mixer_set_capture_mute(struct cras_alsa_mixer *cras_mixer,
 				      int muted,
diff --git a/cras/src/server/cras_alsa_plugin_io.c b/cras/src/server/cras_alsa_plugin_io.c
new file mode 100644
index 0000000..32c1ae1
--- /dev/null
+++ b/cras/src/server/cras_alsa_plugin_io.c
@@ -0,0 +1,265 @@
+/* Copyright 2020 The Chromium OS 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 <alsa/asoundlib.h>
+#include <alsa/use-case.h>
+#include <stdio.h>
+#include <sys/select.h>
+#include <syslog.h>
+
+#include "cras_alsa_io.h"
+#include "cras_alsa_jack.h"
+#include "cras_alsa_mixer.h"
+#include "cras_alsa_ucm.h"
+#include "cras_iodev.h"
+#include "cras_system_state.h"
+#include "iniparser_wrapper.h"
+#include "utlist.h"
+
+#define PLUGINS_INI "plugins.ini"
+#define PLUGIN_KEY_CTL "ctl"
+#define PLUGIN_KEY_DIR "dir"
+#define PLUGIN_KEY_PCM "pcm"
+#define PLUGIN_KEY_CARD "card"
+
+#define NULL_USB_VID 0x00
+#define NULL_USB_PID 0x00
+#define NULL_USB_SERIAL_NUMBER "serial-number-not-used"
+
+struct hctl_poll_fd {
+	int fd;
+	struct hctl_poll_fd *prev, *next;
+};
+
+struct alsa_plugin {
+	snd_hctl_t *hctl;
+	struct cras_alsa_mixer *mixer;
+	struct hctl_poll_fd *hctl_poll_fds;
+	struct cras_use_case_mgr *ucm;
+	struct cras_iodev *iodev;
+	struct alsa_plugin *next, *prev;
+};
+
+static struct alsa_plugin *plugins;
+
+static char ini_name[MAX_INI_NAME_LENGTH + 1];
+static char key_name[MAX_INI_NAME_LENGTH + 1];
+static dictionary *plugins_ini = NULL;
+
+static void hctl_event_pending(void *arg, int revents)
+{
+	struct alsa_plugin *plugin;
+
+	plugin = (struct alsa_plugin *)arg;
+	if (plugin->hctl == NULL)
+		return;
+
+	/* handle_events will trigger the callback registered with each control
+	 * that has changed. */
+	snd_hctl_handle_events(plugin->hctl);
+}
+
+/* hctl poll descritpor */
+static void collect_poll_descriptors(struct alsa_plugin *plugin)
+{
+	struct hctl_poll_fd *registered_fd;
+	struct pollfd *pollfds;
+	int i, n, rc;
+
+	n = snd_hctl_poll_descriptors_count(plugin->hctl);
+	if (n == 0) {
+		syslog(LOG_DEBUG, "No hctl descritpor to poll");
+		return;
+	}
+
+	pollfds = malloc(n * sizeof(*pollfds));
+	if (pollfds == NULL)
+		return;
+
+	n = snd_hctl_poll_descriptors(plugin->hctl, pollfds, n);
+	for (i = 0; i < n; i++) {
+		registered_fd = calloc(1, sizeof(*registered_fd));
+		if (registered_fd == NULL) {
+			free(pollfds);
+			return;
+		}
+		registered_fd->fd = pollfds[i].fd;
+		DL_APPEND(plugin->hctl_poll_fds, registered_fd);
+		rc = cras_system_add_select_fd(
+			registered_fd->fd, hctl_event_pending, plugin, POLLIN);
+		if (rc < 0) {
+			DL_DELETE(plugin->hctl_poll_fds, registered_fd);
+			free(pollfds);
+			return;
+		}
+	}
+	free(pollfds);
+}
+
+static void cleanup_poll_descriptors(struct alsa_plugin *plugin)
+{
+	struct hctl_poll_fd *poll_fd;
+	DL_FOREACH (plugin->hctl_poll_fds, poll_fd) {
+		cras_system_rm_select_fd(poll_fd->fd);
+		DL_DELETE(plugin->hctl_poll_fds, poll_fd);
+		free(poll_fd);
+	}
+}
+
+static void destroy_plugin(struct alsa_plugin *plugin);
+
+void alsa_plugin_io_create(enum CRAS_STREAM_DIRECTION direction,
+			   const char *pcm_name, const char *ctl_name,
+			   const char *card_name)
+{
+	struct alsa_plugin *plugin;
+	struct ucm_section *section;
+	struct ucm_section *ucm_sections;
+	int rc;
+
+	plugin = (struct alsa_plugin *)calloc(1, sizeof(*plugin));
+	if (!plugin) {
+		syslog(LOG_ERR, "No memory to create alsa plugin");
+		return;
+	}
+
+	rc = snd_hctl_open(&plugin->hctl, ctl_name, SND_CTL_NONBLOCK);
+	if (rc < 0) {
+		syslog(LOG_ERR, "open hctl fail for plugin %s", ctl_name);
+		goto cleanup;
+	}
+
+	rc = snd_hctl_nonblock(plugin->hctl, 1);
+	if (rc < 0) {
+		syslog(LOG_ERR, "Failed to nonblock hctl for %s", ctl_name);
+		goto cleanup;
+	}
+	rc = snd_hctl_load(plugin->hctl);
+	if (rc < 0) {
+		syslog(LOG_ERR, "Failed to load hctl for %s", ctl_name);
+		goto cleanup;
+	}
+	collect_poll_descriptors(plugin);
+
+	plugin->mixer = cras_alsa_mixer_create(ctl_name);
+
+	plugin->ucm = ucm_create(card_name);
+
+	DL_APPEND(plugins, plugin);
+
+	ucm_sections = ucm_get_sections(plugin->ucm);
+	DL_FOREACH (ucm_sections, section) {
+		rc = cras_alsa_mixer_add_controls_in_section(plugin->mixer,
+							     section);
+		if (rc)
+			syslog(LOG_ERR,
+			       "Failed adding control to plugin,"
+			       "section %s mixer_name %s",
+			       section->name, section->mixer_name);
+	}
+	plugin->iodev = alsa_iodev_create(0, card_name, 0, pcm_name, "", "",
+					  ALSA_CARD_TYPE_USB, 1, /* is first */
+					  plugin->mixer, NULL, plugin->ucm,
+					  plugin->hctl, direction, NULL_USB_VID,
+					  NULL_USB_PID, NULL_USB_SERIAL_NUMBER);
+
+	DL_FOREACH (ucm_sections, section) {
+		if (section->dir != plugin->iodev->direction)
+			continue;
+		section->dev_idx = 0;
+		alsa_iodev_ucm_add_nodes_and_jacks(plugin->iodev, section);
+	}
+
+	alsa_iodev_ucm_complete_init(plugin->iodev);
+
+	return;
+cleanup:
+	if (plugin)
+		destroy_plugin(plugin);
+}
+
+static void destroy_plugin(struct alsa_plugin *plugin)
+{
+	cleanup_poll_descriptors(plugin);
+	if (plugin->hctl)
+		snd_hctl_close(plugin->hctl);
+	if (plugin->iodev)
+		alsa_iodev_destroy(plugin->iodev);
+	if (plugin->mixer)
+		cras_alsa_mixer_destroy(plugin->mixer);
+
+	free(plugin);
+}
+
+void alsa_pluigin_io_destroy_all()
+{
+	struct alsa_plugin *plugin;
+
+	DL_FOREACH (plugins, plugin)
+		destroy_plugin(plugin);
+}
+
+void cras_alsa_plugin_io_init(const char *device_config_dir)
+{
+	int nsec, i;
+	enum CRAS_STREAM_DIRECTION direction;
+	const char *sec_name;
+	const char *tmp, *pcm_name, *ctl_name, *card_name;
+
+	snprintf(ini_name, MAX_INI_NAME_LENGTH, "%s/%s", device_config_dir,
+		 PLUGINS_INI);
+	ini_name[MAX_INI_NAME_LENGTH] = '\0';
+
+	plugins_ini = iniparser_load_wrapper(ini_name);
+	if (!plugins_ini)
+		return;
+
+	nsec = iniparser_getnsec(plugins_ini);
+	for (i = 0; i < nsec; i++) {
+		sec_name = iniparser_getsecname(plugins_ini, i);
+
+		/* Parse dir=output or dir=input */
+		snprintf(key_name, MAX_INI_NAME_LENGTH, "%s:%s", sec_name,
+			 PLUGIN_KEY_DIR);
+		tmp = iniparser_getstring(plugins_ini, key_name, NULL);
+		if (strcmp(tmp, "output") == 0)
+			direction = CRAS_STREAM_OUTPUT;
+		else if (strcmp(tmp, "input") == 0)
+			direction = CRAS_STREAM_INPUT;
+		else
+			continue;
+
+		/* pcm=<plugin-pcm-name> this name will be used with
+		 * snd_pcm_open. */
+		snprintf(key_name, MAX_INI_NAME_LENGTH, "%s:%s", sec_name,
+			 PLUGIN_KEY_PCM);
+		pcm_name = iniparser_getstring(plugins_ini, key_name, NULL);
+		if (!pcm_name)
+			continue;
+
+		/* ctl=<plugin-ctl-name> this name will be used with
+		 * snd_hctl_open. */
+		snprintf(key_name, MAX_INI_NAME_LENGTH, "%s:%s", sec_name,
+			 PLUGIN_KEY_CTL);
+		ctl_name = iniparser_getstring(plugins_ini, key_name, NULL);
+		if (!ctl_name)
+			continue;
+
+		/* card=<card-name> this name will be used with
+		 * snd_use_case_mgr_open. */
+		snprintf(key_name, MAX_INI_NAME_LENGTH, "%s:%s", sec_name,
+			 PLUGIN_KEY_CARD);
+		card_name = iniparser_getstring(plugins_ini, key_name, NULL);
+		if (!card_name)
+			continue;
+
+		syslog(LOG_DEBUG,
+		       "Creating plugin for direction %s, pcm %s, ctl %s, card %s",
+		       direction == CRAS_STREAM_OUTPUT ? "output" : "input",
+		       pcm_name, ctl_name, card_name);
+
+		alsa_plugin_io_create(direction, pcm_name, ctl_name, card_name);
+	}
+}
diff --git a/cras/src/server/cras_alsa_plugin_io.h b/cras/src/server/cras_alsa_plugin_io.h
new file mode 100644
index 0000000..995f463
--- /dev/null
+++ b/cras/src/server/cras_alsa_plugin_io.h
@@ -0,0 +1,20 @@
+/* Copyright 2019 The Chromium OS 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 CRAS_ALSA_PLUGIN_IO_H_
+#define CRAS_ALSA_PLUGIN_IO_H_
+
+/*
+ * Disclaimer:
+ * The ALSA plugin path in CRAS is intended to be used for development or
+ * testing. CrOS audio team is not responsible for nor provides hot-fix to
+ * any breakage if it’s used in production code.
+ */
+
+void alsa_pluigin_io_destroy_all();
+
+void cras_alsa_plugin_io_init(const char *device_config_dir);
+
+#endif /* CRAS_ALSA_PLUGIN_IO_H_ */
diff --git a/cras/src/server/cras_alsa_ucm.c b/cras/src/server/cras_alsa_ucm.c
index 12ee81e..3e46f6a 100644
--- a/cras/src/server/cras_alsa_ucm.c
+++ b/cras/src/server/cras_alsa_ucm.c
@@ -13,49 +13,53 @@
 #include "cras_util.h"
 #include "utlist.h"
 
-static const char jack_var[] = "JackName";
-static const char jack_type_var[] = "JackType";
+static const char jack_control_var[] = "JackControl";
+static const char jack_dev_var[] = "JackDev";
 static const char jack_switch_var[] = "JackSwitch";
 static const char edid_var[] = "EDIDFile";
 static const char cap_var[] = "CaptureControl";
-static const char mic_positions[] = "MicPositions";
 static const char override_type_name_var[] = "OverrideNodeType";
-static const char output_dsp_name_var[] = "OutputDspName";
-static const char input_dsp_name_var[] = "InputDspName";
 static const char dsp_name_var[] = "DspName";
-static const char mixer_var[] = "MixerName";
-static const char swap_mode_suffix[] = "Swap Mode";
+static const char playback_mixer_elem_var[] = "PlaybackMixerElem";
+static const char capture_mixer_elem_var[] = "CaptureMixerElem";
 static const char min_buffer_level_var[] = "MinBufferLevel";
 static const char dma_period_var[] = "DmaPeriodMicrosecs";
 static const char disable_software_volume[] = "DisableSoftwareVolume";
 static const char playback_device_name_var[] = "PlaybackPCM";
 static const char playback_device_rate_var[] = "PlaybackRate";
+static const char playback_channels_var[] = "PlaybackChannels";
 static const char capture_device_name_var[] = "CapturePCM";
 static const char capture_device_rate_var[] = "CaptureRate";
 static const char capture_channel_map_var[] = "CaptureChannelMap";
+static const char capture_channels_var[] = "CaptureChannels";
 static const char coupled_mixers[] = "CoupledMixers";
+static const char dependent_device_name_var[] = "DependentPCM";
 static const char preempt_hotword_var[] = "PreemptHotword";
 static const char echo_reference_dev_name_var[] = "EchoReferenceDev";
+
+/* SectionModifier prefixes and suffixes. */
+static const char hotword_model_prefix[] = "Hotword Model";
+static const char swap_mode_suffix[] = "Swap Mode";
+static const char noise_cancellation_suffix[] = "Noise Cancellation";
+
 /*
- * Set this value in a SectionDevice to specify the minimum software gain in
- * 0.01 dB and enable software gain on this node. It must be used with
- * MaxSoftwareGain. If not, the value will be ignored.
+ * Set this value in a SectionDevice to specify the intrinsic sensitivity in
+ * 0.01 dBFS/Pa. It currently only supports input devices. You should get the
+ * value by recording samples without either hardware or software gain. We are
+ * still working on building a standard process for measuring it. The value you
+ * see now in our UCM is just estimated value. If it is set, CRAS will enable
+ * software gain and use the value as a reference for calculating the
+ * appropriate software gain to apply to the device to meet our target volume.
  */
-static const char min_software_gain[] = "MinSoftwareGain";
-/*
- * Set this value in a SectionDevice to specify the maximum software gain in
- * 0.01 dB and enable software gain on this node.
- */
-static const char max_software_gain[] = "MaxSoftwareGain";
+static const char intrinsic_sensitivity_var[] = "IntrinsicSensitivity";
+
 /*
  * Set this value in a SectionDevice to specify the default node gain in
  * 0.01 dB.
  */
 static const char default_node_gain[] = "DefaultNodeGain";
-static const char hotword_model_prefix[] = "Hotword Model";
 static const char fully_specified_ucm_var[] = "FullySpecifiedUCM";
 static const char main_volume_names[] = "MainVolumeNames";
-static const char enable_htimestamp_var[] = "EnableHtimestamp";
 
 /* Use case verbs corresponding to CRAS_STREAM_TYPE. */
 static const char *use_case_verbs[] = {
@@ -63,6 +67,8 @@
 	"Speech", "Pro Audio",	"Accessibility",
 };
 
+static const size_t max_section_name_len = 100;
+
 /* Represents a list of section names found in UCM. */
 struct section_name {
 	const char *name;
@@ -71,9 +77,10 @@
 
 struct cras_use_case_mgr {
 	snd_use_case_mgr_t *mgr;
-	const char *name;
+	char *name;
 	unsigned int avail_use_cases;
 	enum CRAS_STREAM_TYPE use_case;
+	char *hotword_modifier;
 };
 
 static inline const char *uc_verb(struct cras_use_case_mgr *mgr)
@@ -308,32 +315,42 @@
 	return section_names;
 }
 
-static const char *
-ucm_get_playback_device_name_for_dev(struct cras_use_case_mgr *mgr,
-				     const char *dev)
+static const char *ucm_get_value_for_dev(struct cras_use_case_mgr *mgr,
+					 const char *value_var, const char *dev)
 {
 	const char *name = NULL;
 	int rc;
 
-	rc = get_var(mgr, playback_device_name_var, dev, uc_verb(mgr), &name);
+	rc = get_var(mgr, value_var, dev, uc_verb(mgr), &name);
 	if (rc)
 		return NULL;
 
 	return name;
 }
 
-static const char *
+static inline const char *
+ucm_get_playback_device_name_for_dev(struct cras_use_case_mgr *mgr,
+				     const char *dev)
+{
+	return ucm_get_value_for_dev(mgr, playback_device_name_var, dev);
+}
+
+static inline const char *
 ucm_get_capture_device_name_for_dev(struct cras_use_case_mgr *mgr,
 				    const char *dev)
 {
-	const char *name = NULL;
-	int rc;
+	return ucm_get_value_for_dev(mgr, capture_device_name_var, dev);
+}
 
-	rc = get_var(mgr, capture_device_name_var, dev, uc_verb(mgr), &name);
-	if (rc)
-		return NULL;
-
-	return name;
+/* Gets the value of DependentPCM property. This is used to structure two
+ * SectionDevices under one cras iodev to avoid two PCMs be open at the
+ * same time because of restriction in lower layer driver or hardware.
+ */
+static inline const char *
+ucm_get_dependent_device_name_for_dev(struct cras_use_case_mgr *mgr,
+				      const char *dev)
+{
+	return ucm_get_value_for_dev(mgr, dependent_device_name_var, dev);
 }
 
 /* Get a list of mixer names specified in a UCM variable separated by ",".
@@ -365,6 +382,21 @@
 	return names;
 }
 
+/* Gets the modifier name of Noise Cancellation for the given node_name. */
+static void ucm_get_node_noise_cancellation_name(const char *node_name,
+						 char *mod_name)
+{
+	size_t len =
+		strlen(node_name) + 1 + strlen(noise_cancellation_suffix) + 1;
+	if (len > max_section_name_len) {
+		syslog(LOG_ERR,
+		       "Length of the given section name is %zu > %zu(max)",
+		       len, max_section_name_len);
+		len = max_section_name_len;
+	}
+	snprintf(mod_name, len, "%s %s", node_name, noise_cancellation_suffix);
+}
+
 /* Exported Interface */
 
 struct cras_use_case_mgr *ucm_create(const char *name)
@@ -383,6 +415,10 @@
 	if (!mgr)
 		return NULL;
 
+	mgr->name = strdup(name);
+	if (!mgr->name)
+		goto cleanup;
+
 	rc = snd_use_case_mgr_open(&mgr->mgr, name);
 	if (rc) {
 		syslog(LOG_WARNING, "Can not open ucm for card %s, rc = %d",
@@ -390,8 +426,8 @@
 		goto cleanup;
 	}
 
-	mgr->name = name;
 	mgr->avail_use_cases = 0;
+	mgr->hotword_modifier = NULL;
 	num_verbs = snd_use_case_get_list(mgr->mgr, "_verbs", &list);
 	for (i = 0; i < num_verbs; i += 2) {
 		for (j = 0; j < CRAS_STREAM_NUM_TYPES; ++j) {
@@ -413,6 +449,7 @@
 cleanup_mgr:
 	snd_use_case_mgr_close(mgr->mgr);
 cleanup:
+	free(mgr->name);
 	free(mgr);
 	return NULL;
 }
@@ -420,6 +457,8 @@
 void ucm_destroy(struct cras_use_case_mgr *mgr)
 {
 	snd_use_case_mgr_close(mgr->mgr);
+	free(mgr->hotword_modifier);
+	free(mgr->name);
 	free(mgr);
 }
 
@@ -476,12 +515,63 @@
 	return rc;
 }
 
+int ucm_node_noise_cancellation_exists(struct cras_use_case_mgr *mgr,
+				       const char *node_name)
+{
+	char *node_modifier_name = NULL;
+	int exists;
+
+	node_modifier_name = (char *)malloc(max_section_name_len);
+	if (!node_modifier_name)
+		return 0;
+	ucm_get_node_noise_cancellation_name(node_name, node_modifier_name);
+	exists = ucm_mod_exists_with_name(mgr, node_modifier_name);
+	free((void *)node_modifier_name);
+	return exists;
+}
+
+int ucm_enable_node_noise_cancellation(struct cras_use_case_mgr *mgr,
+				       const char *node_name, int enable)
+{
+	char *node_modifier_name = NULL;
+	int rc;
+
+	node_modifier_name = (char *)malloc(max_section_name_len);
+	if (!node_modifier_name)
+		return -ENOMEM;
+	ucm_get_node_noise_cancellation_name(node_name, node_modifier_name);
+	if (!ucm_mod_exists_with_name(mgr, node_modifier_name)) {
+		syslog(LOG_ERR, "Can not find modifier %s.",
+		       node_modifier_name);
+		free((void *)node_modifier_name);
+		return -EPERM;
+	}
+	if (modifier_enabled(mgr, node_modifier_name) == !!enable) {
+		syslog(LOG_DEBUG, "Modifier %s is already %s.",
+		       node_modifier_name, enable ? "enabled" : "disabled");
+		free((void *)node_modifier_name);
+		return 0;
+	}
+
+	syslog(LOG_DEBUG, "UCM %s Modifier %s", enable ? "enable" : "disable",
+	       node_modifier_name);
+	rc = ucm_set_modifier_enabled(mgr, node_modifier_name, enable);
+	free((void *)node_modifier_name);
+	return rc;
+}
+
 int ucm_set_enabled(struct cras_use_case_mgr *mgr, const char *dev, int enable)
 {
+	int rc;
 	if (device_enabled(mgr, dev) == !!enable)
 		return 0;
 	syslog(LOG_DEBUG, "UCM %s %s", enable ? "enable" : "disable", dev);
-	return snd_use_case_set(mgr->mgr, enable ? "_enadev" : "_disdev", dev);
+	rc = snd_use_case_set(mgr->mgr, enable ? "_enadev" : "_disdev", dev);
+	if (rc && (rc != -ENOENT || ucm_has_fully_specified_ucm_flag(mgr))) {
+		syslog(LOG_ERR, "Can not %s UCM for device %s, rc = %d",
+		       enable ? "enable" : "disable", dev, rc);
+	}
+	return rc;
 }
 
 char *ucm_get_flag(struct cras_use_case_mgr *mgr, const char *flag_name)
@@ -515,33 +605,10 @@
 	return control_name;
 }
 
-char *ucm_get_mic_positions(struct cras_use_case_mgr *mgr)
+inline const char *ucm_get_override_type_name(struct cras_use_case_mgr *mgr,
+					      const char *dev)
 {
-	char *control_name = NULL;
-	const char *value;
-	int rc;
-
-	rc = get_var(mgr, mic_positions, "", uc_verb(mgr), &value);
-	if (!rc) {
-		control_name = strdup(value);
-		free((void *)value);
-	}
-
-	return control_name;
-}
-
-const char *ucm_get_override_type_name(struct cras_use_case_mgr *mgr,
-				       const char *dev)
-{
-	const char *override_type_name;
-	int rc;
-
-	rc = get_var(mgr, override_type_name_var, dev, uc_verb(mgr),
-		     &override_type_name);
-	if (rc)
-		return NULL;
-
-	return override_type_name;
+	return ucm_get_value_for_dev(mgr, override_type_name_var, dev);
 }
 
 char *ucm_get_dev_for_jack(struct cras_use_case_mgr *mgr, const char *jack,
@@ -550,7 +617,8 @@
 	struct section_name *section_names, *c;
 	char *ret = NULL;
 
-	section_names = ucm_get_devices_for_var(mgr, jack_var, jack, direction);
+	section_names =
+		ucm_get_devices_for_var(mgr, jack_dev_var, jack, direction);
 
 	DL_FOREACH (section_names, c) {
 		if (!strcmp(c->name, "Mic")) {
@@ -578,10 +646,16 @@
 char *ucm_get_dev_for_mixer(struct cras_use_case_mgr *mgr, const char *mixer,
 			    enum CRAS_STREAM_DIRECTION dir)
 {
-	struct section_name *section_names, *c;
+	struct section_name *section_names = NULL, *c;
 	char *ret = NULL;
 
-	section_names = ucm_get_devices_for_var(mgr, mixer_var, mixer, dir);
+	if (dir == CRAS_STREAM_OUTPUT) {
+		section_names = ucm_get_devices_for_var(
+			mgr, playback_mixer_elem_var, mixer, dir);
+	} else if (dir == CRAS_STREAM_INPUT) {
+		section_names = ucm_get_devices_for_var(
+			mgr, capture_mixer_elem_var, mixer, dir);
+	}
 
 	if (section_names)
 		ret = strdup(section_names->name);
@@ -595,46 +669,16 @@
 	return ret;
 }
 
-const char *ucm_get_edid_file_for_dev(struct cras_use_case_mgr *mgr,
-				      const char *dev)
+inline const char *ucm_get_edid_file_for_dev(struct cras_use_case_mgr *mgr,
+					     const char *dev)
 {
-	const char *file_name;
-	int rc;
-
-	rc = get_var(mgr, edid_var, dev, uc_verb(mgr), &file_name);
-	if (rc)
-		return NULL;
-
-	return file_name;
+	return ucm_get_value_for_dev(mgr, edid_var, dev);
 }
 
-const char *ucm_get_dsp_name_default(struct cras_use_case_mgr *mgr,
-				     int direction)
+inline const char *ucm_get_dsp_name_for_dev(struct cras_use_case_mgr *mgr,
+					    const char *dev)
 {
-	const char *var = (direction == CRAS_STREAM_OUTPUT) ?
-				  output_dsp_name_var :
-				  input_dsp_name_var;
-	const char *dsp_name = NULL;
-	int rc;
-
-	rc = get_var(mgr, var, "", uc_verb(mgr), &dsp_name);
-	if (rc)
-		return NULL;
-
-	return dsp_name;
-}
-
-const char *ucm_get_dsp_name_for_dev(struct cras_use_case_mgr *mgr,
-				     const char *dev)
-{
-	const char *dsp_name = NULL;
-	int rc;
-
-	rc = get_var(mgr, dsp_name_var, dev, uc_verb(mgr), &dsp_name);
-	if (rc)
-		return NULL;
-
-	return dsp_name;
+	return ucm_get_value_for_dev(mgr, dsp_name_var, dev);
 }
 
 int ucm_get_min_buffer_level(struct cras_use_case_mgr *mgr, unsigned int *level)
@@ -662,32 +706,6 @@
 	return value;
 }
 
-int ucm_get_min_software_gain(struct cras_use_case_mgr *mgr, const char *dev,
-			      long *gain)
-{
-	int value;
-	int rc;
-
-	rc = get_int(mgr, min_software_gain, dev, uc_verb(mgr), &value);
-	if (rc)
-		return rc;
-	*gain = value;
-	return 0;
-}
-
-int ucm_get_max_software_gain(struct cras_use_case_mgr *mgr, const char *dev,
-			      long *gain)
-{
-	int value;
-	int rc;
-
-	rc = get_int(mgr, max_software_gain, dev, uc_verb(mgr), &value);
-	if (rc)
-		return rc;
-	*gain = value;
-	return 0;
-}
-
 int ucm_get_default_node_gain(struct cras_use_case_mgr *mgr, const char *dev,
 			      long *gain)
 {
@@ -701,6 +719,19 @@
 	return 0;
 }
 
+int ucm_get_intrinsic_sensitivity(struct cras_use_case_mgr *mgr,
+				  const char *dev, long *sensitivity)
+{
+	int value;
+	int rc;
+
+	rc = get_int(mgr, intrinsic_sensitivity_var, dev, uc_verb(mgr), &value);
+	if (rc)
+		return rc;
+	*sensitivity = value;
+	return 0;
+}
+
 int ucm_get_preempt_hotword(struct cras_use_case_mgr *mgr, const char *dev)
 {
 	int value;
@@ -712,29 +743,31 @@
 	return value;
 }
 
-const char *ucm_get_device_name_for_dev(struct cras_use_case_mgr *mgr,
-					const char *dev,
-					enum CRAS_STREAM_DIRECTION direction)
+static int get_device_index_from_target(const char *target_device_name);
+
+int ucm_get_alsa_dev_idx_for_dev(struct cras_use_case_mgr *mgr, const char *dev,
+				 enum CRAS_STREAM_DIRECTION direction)
 {
+	const char *pcm_name = NULL;
+	int dev_idx = -1;
+
 	if (direction == CRAS_STREAM_OUTPUT)
-		return ucm_get_playback_device_name_for_dev(mgr, dev);
+		pcm_name = ucm_get_playback_device_name_for_dev(mgr, dev);
 	else if (direction == CRAS_STREAM_INPUT)
-		return ucm_get_capture_device_name_for_dev(mgr, dev);
-	return NULL;
+		pcm_name = ucm_get_capture_device_name_for_dev(mgr, dev);
+
+	if (pcm_name) {
+		dev_idx = get_device_index_from_target(pcm_name);
+		free((void *)pcm_name);
+	}
+	return dev_idx;
 }
 
-const char *
+inline const char *
 ucm_get_echo_reference_dev_name_for_dev(struct cras_use_case_mgr *mgr,
 					const char *dev)
 {
-	const char *name = NULL;
-	int rc;
-
-	rc = get_var(mgr, echo_reference_dev_name_var, dev, uc_verb(mgr),
-		     &name);
-	if (rc)
-		return NULL;
-	return name;
+	return ucm_get_value_for_dev(mgr, echo_reference_dev_name_var, dev);
 }
 
 int ucm_get_sample_rate_for_dev(struct cras_use_case_mgr *mgr, const char *dev,
@@ -758,6 +791,31 @@
 	return value;
 }
 
+int ucm_get_channels_for_dev(struct cras_use_case_mgr *mgr, const char *dev,
+			     enum CRAS_STREAM_DIRECTION direction,
+			     size_t *channels)
+{
+	int value;
+	int rc;
+	const char *var_name;
+
+	if (direction == CRAS_STREAM_OUTPUT)
+		var_name = playback_channels_var;
+	else if (direction == CRAS_STREAM_INPUT)
+		var_name = capture_channels_var;
+	else
+		return -EINVAL;
+
+	rc = get_int(mgr, var_name, dev, uc_verb(mgr), &value);
+	if (rc)
+		return rc;
+	if (value < 0)
+		return -1;
+
+	*channels = (size_t)value;
+	return 0;
+}
+
 int ucm_get_capture_chmap_for_dev(struct cras_use_case_mgr *mgr,
 				  const char *dev, int8_t *channel_layout)
 {
@@ -803,10 +861,125 @@
 	return -1;
 }
 
+static const char *ucm_get_dir_for_device(struct cras_use_case_mgr *mgr,
+					  const char *dev_name,
+					  enum CRAS_STREAM_DIRECTION *dir)
+{
+	const char *pcm_name;
+
+	pcm_name = ucm_get_playback_device_name_for_dev(mgr, dev_name);
+
+	if (pcm_name) {
+		*dir = CRAS_STREAM_OUTPUT;
+		return pcm_name;
+	}
+
+	pcm_name = ucm_get_capture_device_name_for_dev(mgr, dev_name);
+	if (pcm_name) {
+		*dir = CRAS_STREAM_INPUT;
+		return pcm_name;
+	}
+
+	*dir = CRAS_STREAM_UNDEFINED;
+	return NULL;
+}
+
+static int ucm_parse_device_section(struct cras_use_case_mgr *mgr,
+				    const char *dev,
+				    struct ucm_section **sections)
+{
+	enum CRAS_STREAM_DIRECTION dir;
+	int dev_idx = -1;
+	int dependent_dev_idx = -1;
+	const char *jack_name = NULL;
+	const char *jack_type = NULL;
+	const char *jack_dev = NULL;
+	const char *jack_control = NULL;
+	const char *mixer_name = NULL;
+	struct mixer_name *m_name;
+	int rc = 0;
+	const char *pcm_name;
+	const char *dependent_dev_name = NULL;
+	struct ucm_section *dev_sec;
+	const char *dev_name;
+
+	dev_name = strdup(dev);
+	if (!dev_name)
+		return 0;
+
+	pcm_name = ucm_get_dir_for_device(mgr, dev_name, &dir);
+
+	if (pcm_name)
+		dev_idx = get_device_index_from_target(pcm_name);
+
+	if (dir == CRAS_STREAM_UNDEFINED) {
+		syslog(LOG_ERR,
+		       "UCM configuration for device '%s' missing"
+		       " PlaybackPCM or CapturePCM definition.",
+		       dev_name);
+		rc = -EINVAL;
+		goto error_cleanup;
+	}
+
+	dependent_dev_name =
+		ucm_get_dependent_device_name_for_dev(mgr, dev_name);
+	if (dependent_dev_name) {
+		dependent_dev_idx =
+			get_device_index_from_target(dependent_dev_name);
+	}
+
+	jack_dev = ucm_get_jack_dev_for_dev(mgr, dev_name);
+	jack_control = ucm_get_jack_control_for_dev(mgr, dev_name);
+	if (dir == CRAS_STREAM_OUTPUT)
+		mixer_name = ucm_get_playback_mixer_elem_for_dev(mgr, dev_name);
+	else if (dir == CRAS_STREAM_INPUT)
+		mixer_name = ucm_get_capture_mixer_elem_for_dev(mgr, dev_name);
+
+	if (jack_dev) {
+		jack_name = jack_dev;
+		jack_type = "gpio";
+	} else if (jack_control) {
+		jack_name = jack_control;
+		jack_type = "hctl";
+	}
+
+	dev_sec = ucm_section_create(dev_name, pcm_name, dev_idx,
+				     dependent_dev_idx, dir, jack_name,
+				     jack_type);
+
+	if (!dev_sec) {
+		syslog(LOG_ERR, "Failed to allocate memory.");
+		rc = -ENOMEM;
+		goto error_cleanup;
+	}
+
+	dev_sec->jack_switch = ucm_get_jack_switch_for_dev(mgr, dev_name);
+
+	if (mixer_name) {
+		rc = ucm_section_set_mixer_name(dev_sec, mixer_name);
+		if (rc)
+			goto error_cleanup;
+	}
+
+	m_name = ucm_get_mixer_names(mgr, dev_name, coupled_mixers, dir,
+				     MIXER_NAME_VOLUME);
+	ucm_section_concat_coupled(dev_sec, m_name);
+
+	DL_APPEND(*sections, dev_sec);
+	ucm_section_dump(dev_sec);
+error_cleanup:
+	free((void *)dev_name);
+	free((void *)dependent_dev_name);
+	free((void *)jack_dev);
+	free((void *)jack_control);
+	free((void *)mixer_name);
+	free((void *)pcm_name);
+	return rc;
+}
+
 struct ucm_section *ucm_get_sections(struct cras_use_case_mgr *mgr)
 {
 	struct ucm_section *sections = NULL;
-	struct ucm_section *dev_sec;
 	const char **list;
 	int num_devs;
 	int i;
@@ -820,101 +993,17 @@
 
 	/* snd_use_case_get_list fills list with pairs of device name and
 	 * comment, so device names are in even-indexed elements. */
-	const char *dev_name;
 	for (i = 0; i < num_devs; i += 2) {
-		enum CRAS_STREAM_DIRECTION dir = CRAS_STREAM_UNDEFINED;
-		int dev_idx = -1;
-		const char *jack_name;
-		const char *jack_type;
-		const char *mixer_name;
-		struct mixer_name *m_name;
-		int rc;
-		const char *target_device_name;
-
-		dev_name = strdup(list[i]);
-		if (!dev_name)
-			continue;
-
-		target_device_name =
-			ucm_get_playback_device_name_for_dev(mgr, dev_name);
-		if (target_device_name)
-			dir = CRAS_STREAM_OUTPUT;
-		else {
-			target_device_name =
-				ucm_get_capture_device_name_for_dev(mgr,
-								    dev_name);
-			if (target_device_name)
-				dir = CRAS_STREAM_INPUT;
+		if (ucm_parse_device_section(mgr, list[i], &sections) < 0) {
+			ucm_section_free_list(sections);
+			sections = NULL;
+			break;
 		}
-		if (target_device_name) {
-			dev_idx = get_device_index_from_target(
-				target_device_name);
-			free((void *)target_device_name);
-		}
-
-		if (dir == CRAS_STREAM_UNDEFINED) {
-			syslog(LOG_ERR,
-			       "UCM configuration for device '%s' missing"
-			       " PlaybackPCM or CapturePCM definition.",
-			       dev_name);
-			goto error_cleanup;
-		}
-
-		if (dev_idx == -1) {
-			syslog(LOG_ERR,
-			       "PlaybackPCM or CapturePCM for '%s' must be in"
-			       " the form 'hw:<card>,<number>'",
-			       dev_name);
-			goto error_cleanup;
-		}
-
-		jack_name = ucm_get_jack_name_for_dev(mgr, dev_name);
-		jack_type = ucm_get_jack_type_for_dev(mgr, dev_name);
-		mixer_name = ucm_get_mixer_name_for_dev(mgr, dev_name);
-
-		dev_sec = ucm_section_create(dev_name, dev_idx, dir, jack_name,
-					     jack_type);
-		if (jack_name)
-			free((void *)jack_name);
-		if (jack_type)
-			free((void *)jack_type);
-
-		if (!dev_sec) {
-			syslog(LOG_ERR, "Failed to allocate memory.");
-			if (mixer_name)
-				free((void *)mixer_name);
-			goto error_cleanup;
-		}
-
-		dev_sec->jack_switch =
-			ucm_get_jack_switch_for_dev(mgr, dev_name);
-
-		if (mixer_name) {
-			rc = ucm_section_set_mixer_name(dev_sec, mixer_name);
-			free((void *)mixer_name);
-			if (rc)
-				goto error_cleanup;
-		}
-
-		m_name = ucm_get_mixer_names(mgr, dev_name, coupled_mixers, dir,
-					     MIXER_NAME_VOLUME);
-		ucm_section_concat_coupled(dev_sec, m_name);
-
-		DL_APPEND(sections, dev_sec);
-		ucm_section_dump(dev_sec);
-		free((void *)dev_name);
 	}
 
 	if (num_devs > 0)
 		snd_use_case_free_list(list, num_devs);
 	return sections;
-
-error_cleanup:
-	if (num_devs > 0)
-		snd_use_case_free_list(list, num_devs);
-	ucm_section_free_list(sections);
-	free((void *)dev_name);
-	return NULL;
 }
 
 char *ucm_get_hotword_models(struct cras_use_case_mgr *mgr)
@@ -968,14 +1057,61 @@
 	return models;
 }
 
-int ucm_set_hotword_model(struct cras_use_case_mgr *mgr, const char *model)
+void ucm_disable_all_hotword_models(struct cras_use_case_mgr *mgr)
 {
 	const char **list;
 	int num_enmods, mod_idx;
-	char *model_mod = NULL;
+
+	if (!mgr)
+		return;
+
+	/* Disable all currently enabled hotword model modifiers. */
+	num_enmods = snd_use_case_get_list(mgr->mgr, "_enamods", &list);
+	if (num_enmods <= 0)
+		return;
+
+	for (mod_idx = 0; mod_idx < num_enmods; mod_idx++) {
+		if (!strncmp(list[mod_idx], hotword_model_prefix,
+			     strlen(hotword_model_prefix)))
+			ucm_set_modifier_enabled(mgr, list[mod_idx], 0);
+	}
+	snd_use_case_free_list(list, num_enmods);
+}
+
+int ucm_enable_hotword_model(struct cras_use_case_mgr *mgr)
+{
+	if (mgr->hotword_modifier)
+		return ucm_set_modifier_enabled(mgr, mgr->hotword_modifier, 1);
+	return -EINVAL;
+}
+
+static int ucm_is_modifier_enabled(struct cras_use_case_mgr *mgr,
+				   char *modifier, long *value)
+{
+	int rc;
+	char *id;
+	size_t len = strlen(modifier) + 11 + 1;
+
+	id = (char *)malloc(len);
+
+	if (!id)
+		return -ENOMEM;
+
+	snprintf(id, len, "_modstatus/%s", modifier);
+	rc = snd_use_case_geti(mgr->mgr, id, value);
+	free(id);
+	return rc;
+}
+
+int ucm_set_hotword_model(struct cras_use_case_mgr *mgr, const char *model)
+{
+	char *model_mod;
+	long mod_status = 0;
 	size_t model_mod_size =
 		strlen(model) + 1 + strlen(hotword_model_prefix) + 1;
+
 	model_mod = (char *)malloc(model_mod_size);
+
 	if (!model_mod)
 		return -ENOMEM;
 	snprintf(model_mod, model_mod_size, "%s %s", hotword_model_prefix,
@@ -985,21 +1121,16 @@
 		return -EINVAL;
 	}
 
-	/* Disable all currently enabled horword model modifiers. */
-	num_enmods = snd_use_case_get_list(mgr->mgr, "_enamods", &list);
-	if (num_enmods <= 0)
-		goto enable_mod;
+	/* If check failed, just move on, dont fail incoming model */
+	if (mgr->hotword_modifier)
+		ucm_is_modifier_enabled(mgr, mgr->hotword_modifier,
+					&mod_status);
 
-	for (mod_idx = 0; mod_idx < num_enmods; mod_idx++) {
-		if (!strncmp(list[mod_idx], hotword_model_prefix,
-			     strlen(hotword_model_prefix)))
-			ucm_set_modifier_enabled(mgr, list[mod_idx], 0);
-	}
-	snd_use_case_free_list(list, num_enmods);
-
-enable_mod:
-	ucm_set_modifier_enabled(mgr, model_mod, 1);
-	free((void *)model_mod);
+	ucm_disable_all_hotword_models(mgr);
+	free(mgr->hotword_modifier);
+	mgr->hotword_modifier = model_mod;
+	if (mod_status)
+		return ucm_enable_hotword_model(mgr);
 	return 0;
 }
 
@@ -1015,17 +1146,18 @@
 	return ret;
 }
 
-const char *ucm_get_mixer_name_for_dev(struct cras_use_case_mgr *mgr,
-				       const char *dev)
+inline const char *
+ucm_get_playback_mixer_elem_for_dev(struct cras_use_case_mgr *mgr,
+				    const char *dev)
 {
-	const char *name = NULL;
-	int rc;
+	return ucm_get_value_for_dev(mgr, playback_mixer_elem_var, dev);
+}
 
-	rc = get_var(mgr, mixer_var, dev, uc_verb(mgr), &name);
-	if (rc)
-		return NULL;
-
-	return name;
+inline const char *
+ucm_get_capture_mixer_elem_for_dev(struct cras_use_case_mgr *mgr,
+				   const char *dev)
+{
+	return ucm_get_value_for_dev(mgr, capture_mixer_elem_var, dev);
 }
 
 struct mixer_name *ucm_get_main_volume_names(struct cras_use_case_mgr *mgr)
@@ -1071,37 +1203,16 @@
 	return listed;
 }
 
-const char *ucm_get_jack_name_for_dev(struct cras_use_case_mgr *mgr,
-				      const char *dev)
+inline const char *ucm_get_jack_control_for_dev(struct cras_use_case_mgr *mgr,
+						const char *dev)
 {
-	const char *name = NULL;
-	int rc;
-
-	rc = get_var(mgr, jack_var, dev, uc_verb(mgr), &name);
-	if (rc)
-		return NULL;
-
-	return name;
+	return ucm_get_value_for_dev(mgr, jack_control_var, dev);
 }
 
-const char *ucm_get_jack_type_for_dev(struct cras_use_case_mgr *mgr,
-				      const char *dev)
+inline const char *ucm_get_jack_dev_for_dev(struct cras_use_case_mgr *mgr,
+					    const char *dev)
 {
-	const char *name = NULL;
-	int rc;
-
-	rc = get_var(mgr, jack_type_var, dev, uc_verb(mgr), &name);
-	if (rc)
-		return NULL;
-
-	if (strcmp(name, "hctl") && strcmp(name, "gpio") &&
-	    strcmp(name, "always")) {
-		syslog(LOG_ERR, "Unknown jack type: %s", name);
-		if (name)
-			free((void *)name);
-		return NULL;
-	}
-	return name;
+	return ucm_get_value_for_dev(mgr, jack_dev_var, dev);
 }
 
 int ucm_get_jack_switch_for_dev(struct cras_use_case_mgr *mgr, const char *dev)
@@ -1124,15 +1235,3 @@
 		return 0;
 	return value;
 }
-
-unsigned int ucm_get_enable_htimestamp_flag(struct cras_use_case_mgr *mgr)
-{
-	char *flag;
-	int ret = 0;
-	flag = ucm_get_flag(mgr, enable_htimestamp_var);
-	if (!flag)
-		return 0;
-	ret = !strcmp(flag, "1");
-	free(flag);
-	return ret;
-}
diff --git a/cras/src/server/cras_alsa_ucm.h b/cras/src/server/cras_alsa_ucm.h
index 36f68a0..55c3cf6 100644
--- a/cras/src/server/cras_alsa_ucm.h
+++ b/cras/src/server/cras_alsa_ucm.h
@@ -67,6 +67,28 @@
 int ucm_enable_swap_mode(struct cras_use_case_mgr *mgr, const char *node_name,
 			 int enable);
 
+/* Checks if modifier of noise cancellation for given node_name exists in ucm.
+ * Args:
+ *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
+ *    node_name - The node name.
+ * Returns:
+ *    1 if it exists, 0 otherwise.
+ */
+int ucm_node_noise_cancellation_exists(struct cras_use_case_mgr *mgr,
+				       const char *node_name);
+
+/* Enables or disables noise cancellation for the given node_name. First checks
+ * if the modifier is already enabled or disabled.
+ * Args:
+ *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
+ *    node_name - The node name.
+ *    enable - Enable device if non-zero.
+ * Returns:
+ *    0 on success or negative error code on failure.
+ */
+int ucm_enable_node_noise_cancellation(struct cras_use_case_mgr *mgr,
+				       const char *node_name, int enable);
+
 /* Enables or disables a UCM device.  First checks if the device is already
  * enabled or disabled.
  * Args:
@@ -98,15 +120,6 @@
  */
 char *ucm_get_cap_control(struct cras_use_case_mgr *mgr, const char *ucm_dev);
 
-/* Gets the mic positions string for internal mic.
- * Args:
- *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
- * Returns:
- *    A pointer to the allocated string containing the mic positions
- *    information, or NULL if not specified.
- */
-char *ucm_get_mic_positions(struct cras_use_case_mgr *mgr);
-
 /* Gets the new node type name which user wants to override the old one for
  * given ucm device.
  * Args:
@@ -155,17 +168,6 @@
 const char *ucm_get_edid_file_for_dev(struct cras_use_case_mgr *mgr,
 				      const char *dev);
 
-/* Gets the default dsp name.
- * Args:
- *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
- *    direction - playback(CRAS_STREAM_OUTPUT) or capture(CRAS_STREAM_INPUT).
- * Returns:
- *    A pointer to the allocated string containing the default dsp name, or
- *    NULL if no default dsp name is found.
- */
-const char *ucm_get_dsp_name_default(struct cras_use_case_mgr *mgr,
-				     int direction);
-
 /* Gets the dsp name which is associated with the given ucm device.
  * Args:
  *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
@@ -196,39 +198,28 @@
  */
 unsigned int ucm_get_disable_software_volume(struct cras_use_case_mgr *mgr);
 
-/* Gets the value for minimum software gain.
- * Args:
- *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
- *    dev - The device to check for minimum software gain.
- *    gain - The pointer to the returned value;
- * Returns:
- *    0 on success, other error codes on failure.
- */
-int ucm_get_min_software_gain(struct cras_use_case_mgr *mgr, const char *dev,
-			      long *gain);
-
-/* Gets the value for maximum software gain.
- * Args:
- *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
- *    dev - The device to check for maximum software gain.
- *    gain - The pointer to the returned value;
- * Returns:
- *    0 on success, other error codes on failure.
- */
-int ucm_get_max_software_gain(struct cras_use_case_mgr *mgr, const char *dev,
-			      long *gain);
-
 /* Gets the value for default node gain.
  * Args:
  *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
  *    dev - The device to check for default node gain.
- *    gain - The pointer to the returned value;
+ *    gain - The pointer to the returned value.
  * Returns:
  *    0 on success, other error codes on failure.
  */
 int ucm_get_default_node_gain(struct cras_use_case_mgr *mgr, const char *dev,
 			      long *gain);
 
+/* Gets the value for intrinsic sensitivity.
+ * Args:
+ *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
+ *    dev - The device to query for intrinsic volume.
+ *    sensitivity - The pointer to the returned value.
+ * Returns:
+ *    0 on success, other error codes on failure.
+ */
+int ucm_get_intrinsic_sensitivity(struct cras_use_case_mgr *mgr,
+				  const char *dev, long *sensitivity);
+
 /* Gets the flag if an input device can preempt hotword recording.
  * Args:
  *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
@@ -239,20 +230,19 @@
  */
 int ucm_get_preempt_hotword(struct cras_use_case_mgr *mgr, const char *dev);
 
-/* Gets the device name of this device on the card..
+/* Gets the ALSA device index on the card for given UCM dev.
  *
  * Args:
  *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
- *    dev - The device to check for device name
+ *    dev - The UCM device to check for ALSA device index.
  *    direction - playback(CRAS_STREAM_OUTPUT) or capture(CRAS_STREAM_INPUT).
  * Returns:
- *    A pointer to the allocated string containing the device name, or NULL
- *    if no device name is found. The device name is of format
- *    "card_name:device_index".
+ *    Non-negative integer for the ALSA device index on the card, -1 if not
+ *    found. The ALSA device index is parsed from the PCM name which is
+ *    formatted as "hw:<some-name>,<idx>".
  */
-const char *ucm_get_device_name_for_dev(struct cras_use_case_mgr *mgr,
-					const char *dev,
-					enum CRAS_STREAM_DIRECTION direction);
+int ucm_get_alsa_dev_idx_for_dev(struct cras_use_case_mgr *mgr, const char *dev,
+				 enum CRAS_STREAM_DIRECTION direction);
 
 /* Gets the node name of the echo reference device on the card.
  * Args:
@@ -279,6 +269,20 @@
 int ucm_get_sample_rate_for_dev(struct cras_use_case_mgr *mgr, const char *dev,
 				enum CRAS_STREAM_DIRECTION direction);
 
+/* Gets the channel count at which to run this device.
+ *
+ * Args:
+ *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
+ *    dev - The device to check for channel count.
+ *    direction - playback(CRAS_STREAM_OUTPUT) or capture(CRAS_STREAM_INPUT).
+ *    channels - The pointer to the returned channel count.
+ * Returns:
+ *    0 on success, other error codes on failure.
+ */
+int ucm_get_channels_for_dev(struct cras_use_case_mgr *mgr, const char *dev,
+			     enum CRAS_STREAM_DIRECTION direction,
+			     size_t *channels);
+
 /* Gets the capture channel map for this device.
  * Args:
  *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
@@ -324,11 +328,26 @@
 /* Sets the desired hotword model.
  * Args:
  *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
+ *    model - locale for model
  * Returns:
  *    0 on success or negative error code on failure.
  */
 int ucm_set_hotword_model(struct cras_use_case_mgr *mgr, const char *model);
 
+/* Enable previously set hotword modifier
+ * Args:
+ *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
+ * Returns:
+ *    0 on success or negative error code on failure.
+ */
+int ucm_enable_hotword_model(struct cras_use_case_mgr *mgr);
+
+/* Disable all hotword model modifiers
+ * Args:
+ *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
+ */
+void ucm_disable_all_hotword_models(struct cras_use_case_mgr *mgr);
+
 /* Checks if this card has fully specified UCM config.
  *
  * Args:
@@ -338,7 +357,7 @@
  */
 int ucm_has_fully_specified_ucm_flag(struct cras_use_case_mgr *mgr);
 
-/* Gets the mixer name of this device on the card.
+/* Gets the playback mixer name of this device on the card.
  *
  * Args:
  *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
@@ -347,8 +366,20 @@
  *    A pointer to the allocated string containing the mixer name, or NULL
  *    if no device name is found.
  */
-const char *ucm_get_mixer_name_for_dev(struct cras_use_case_mgr *mgr,
-				       const char *dev);
+const char *ucm_get_playback_mixer_elem_for_dev(struct cras_use_case_mgr *mgr,
+						const char *dev);
+
+/* Gets the capture mixer name of this device on the card.
+ *
+ * Args:
+ *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
+ *    dev - The device to check for device name
+ * Returns:
+ *    A pointer to the allocated string containing the mixer name, or NULL
+ *    if no device name is found.
+ */
+const char *ucm_get_capture_mixer_elem_for_dev(struct cras_use_case_mgr *mgr,
+					       const char *dev);
 
 /* Gets the mixer names for the main volume controls on the card.
  *
@@ -419,6 +450,30 @@
 const char *ucm_get_jack_type_for_dev(struct cras_use_case_mgr *mgr,
 				      const char *dev);
 
+/* Gets the jack dev of this device on the card.
+ *
+ * Args:
+ *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
+ *    dev - The device to check for jack name.
+ * Returns:
+ *    A pointer to the allocated string containing the input jack name, or NULL
+ *    if no jack name is found.
+ */
+const char *ucm_get_jack_dev_for_dev(struct cras_use_case_mgr *mgr,
+				     const char *dev);
+
+/* Gets the jack control of this device on the card.
+ *
+ * Args:
+ *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
+ *    dev - The device to check for jack type.
+ * Returns:
+ *    A pointer to the allocated string containing the alsa jack name, or NULL
+ *    if no jack type is found or the found jack type is invalid.
+ */
+const char *ucm_get_jack_control_for_dev(struct cras_use_case_mgr *mgr,
+					 const char *dev);
+
 /* Gets the jack switch number for this device.
  * Some sound cards can detect multiple types of connections into the
  * audio jack - for example distinguish between line-out and headphones
@@ -454,12 +509,4 @@
  */
 unsigned int ucm_get_optimize_no_stream_flag(struct cras_use_case_mgr *mgr);
 
-/* Retrieve the flag that enables use of htimestamp.
- * Args:
- *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
- * Returns:
- *    1 if the flag is enabled. 0 otherwise.
- */
-unsigned int ucm_get_enable_htimestamp_flag(struct cras_use_case_mgr *mgr);
-
 #endif /* _CRAS_ALSA_UCM_H */
diff --git a/cras/src/server/cras_alsa_ucm_section.c b/cras/src/server/cras_alsa_ucm_section.c
index 36d4422..d4df8c7 100644
--- a/cras/src/server/cras_alsa_ucm_section.c
+++ b/cras/src/server/cras_alsa_ucm_section.c
@@ -13,14 +13,11 @@
 
 static void ucm_section_free(struct ucm_section *section)
 {
-	if (section->name)
-		free((void *)section->name);
-	if (section->jack_name)
-		free((void *)section->jack_name);
-	if (section->jack_type)
-		free((void *)section->jack_type);
-	if (section->mixer_name)
-		free((void *)section->mixer_name);
+	free((void *)section->name);
+	free((void *)section->pcm_name);
+	free((void *)section->jack_name);
+	free((void *)section->jack_type);
+	free((void *)section->mixer_name);
 	mixer_name_free(section->coupled);
 	free(section);
 }
@@ -34,7 +31,8 @@
 	}
 }
 
-struct ucm_section *ucm_section_create(const char *name, int dev_idx,
+struct ucm_section *ucm_section_create(const char *name, const char *pcm_name,
+				       int dev_idx, int dependent_dev_idx,
 				       enum CRAS_STREAM_DIRECTION dir,
 				       const char *jack_name,
 				       const char *jack_type)
@@ -50,11 +48,16 @@
 		return NULL;
 
 	section->dev_idx = dev_idx;
+	section->dependent_dev_idx = dependent_dev_idx;
 	section->dir = dir;
 	section->name = strdup(name);
 	if (!section->name)
 		goto error;
 
+	section->pcm_name = strdup(pcm_name);
+	if (!section->pcm_name)
+		goto error;
+
 	if (jack_name) {
 		section->jack_name = strdup(jack_name);
 		if (!section->jack_name)
diff --git a/cras/src/server/cras_alsa_ucm_section.h b/cras/src/server/cras_alsa_ucm_section.h
index 0ffa5ab..77c5ed8 100644
--- a/cras/src/server/cras_alsa_ucm_section.h
+++ b/cras/src/server/cras_alsa_ucm_section.h
@@ -17,8 +17,12 @@
 struct ucm_section {
 	/* Section name. */
 	const char *name;
+	/* Value of PlaybackPCM or CapturePCM. */
+	const char *pcm_name;
 	/* Device PCM index. */
 	int dev_idx;
+	/* Device PCM index to associate this section to. */
+	int dependent_dev_idx;
 	/* Output or Input. */
 	enum CRAS_STREAM_DIRECTION dir;
 	/* Associated jack's name. */
@@ -27,7 +31,7 @@
 	const char *jack_type;
 	/* Switch number for jack from linux/input.h, or -1. */
 	int jack_switch;
-	/* MixerName value. */
+	/* (Playback/Capture)MixerElem value. */
 	const char *mixer_name;
 	/* CoupledMixers value. */
 	struct mixer_name *coupled;
@@ -38,7 +42,10 @@
  *
  * Args:
  *    name - Section name (must not be NULL).
+ *    pcm_name - PCM name used for snd_pcm_open.
  *    dev_idx - Section's device index (PCM number).
+ *    dependent_dev_idx - Another ALSA device index (PCM number) under which
+ *        we want to make this section a node.
  *    dir - Device direction: INPUT or OUTPUT.
  *    jack_name - Name of an associated jack (or NULL).
  *    jack_type - Type of the associated jack (or NULL).
@@ -46,7 +53,8 @@
  * Returns:
  *    A valid pointer on success, NULL for memory allocation error.
  */
-struct ucm_section *ucm_section_create(const char *name, int dev_idx,
+struct ucm_section *ucm_section_create(const char *name, const char *pcm_name,
+				       int dev_idx, int dependent_dev_idx,
 				       enum CRAS_STREAM_DIRECTION dir,
 				       const char *jack_name,
 				       const char *jack_type);
diff --git a/cras/src/server/cras_apm_list.c b/cras/src/server/cras_apm_list.c
index d0ea9a9..ab89113 100644
--- a/cras/src/server/cras_apm_list.c
+++ b/cras/src/server/cras_apm_list.c
@@ -22,8 +22,6 @@
 #include "iniparser_wrapper.h"
 #include "utlist.h"
 
-static const unsigned int MAX_INI_NAME_LEN = 63;
-
 #define AEC_CONFIG_NAME "aec.ini"
 #define APM_CONFIG_NAME "apm.ini"
 
@@ -63,6 +61,10 @@
  *        stream.
  *    work_queue - A task queue instance created and destroyed by
  *        libwebrtc_apm.
+ *    is_aec_use_case - True if the input and output devices pair is in the
+ *        typical AEC use case. This flag decides whether to use settings
+ *        tuned specifically for this hardware if exists. Otherwise it uses
+ *        the generic settings like run inside browser.
  */
 struct cras_apm {
 	webrtc_apm apm_ptr;
@@ -73,6 +75,7 @@
 	struct cras_audio_format fmt;
 	struct cras_audio_area *area;
 	void *work_queue;
+	bool is_aec_use_case;
 	struct cras_apm *prev, *next;
 };
 
@@ -81,6 +84,10 @@
  * have more than one cras_apm when multiple input devices are
  * enabled. The most common scenario is the silent input iodev be
  * enabled when CRAS switches active input device.
+ *
+ * Note that cras_apm_list is owned and modified in main thread.
+ * Only in synchronized audio thread event this cras_apm_list is safe
+ * to access for passing single APM instance between threads.
  */
 struct cras_apm_list {
 	void *stream_ptr;
@@ -90,6 +97,22 @@
 };
 
 /*
+ * Wrappers of APM instances that are active, which means it is associated
+ * to a dev/stream pair in audio thread and ready for processing.
+ *
+ * Members:
+ *    apm - The APM for audio data processing.
+ *    stream_ptr - Stream pointer from the associated dev/stream pair.
+ *    effects - The effecets bit map of APM.
+ */
+struct active_apm {
+	struct cras_apm *apm;
+	void *stream_ptr;
+	int effects;
+	struct active_apm *prev, *next;
+} * active_apms;
+
+/*
  * Object used to analyze playback audio from output iodev. It is responsible
  * to get buffer containing latest output data and provide it to the APM
  * instances which want to analyze reverse stream.
@@ -112,9 +135,8 @@
 };
 
 static struct cras_apm_reverse_module *rmodule = NULL;
-static struct cras_apm_list *apm_list = NULL;
 static const char *aec_config_dir = NULL;
-static char ini_name[MAX_INI_NAME_LEN + 1];
+static char ini_name[MAX_INI_NAME_LENGTH + 1];
 static dictionary *aec_ini = NULL;
 static dictionary *apm_ini = NULL;
 
@@ -122,14 +144,14 @@
  * or removed. */
 static void update_process_reverse_flag()
 {
-	struct cras_apm_list *list;
+	struct active_apm *active;
 
 	if (!rmodule)
 		return;
 	rmodule->process_reverse = 0;
-	DL_FOREACH (apm_list, list) {
+	DL_FOREACH (active_apms, active) {
 		rmodule->process_reverse |=
-			!!(list->effects & APM_ECHO_CANCELLATION);
+			!!(active->effects & APM_ECHO_CANCELLATION);
 	}
 }
 
@@ -154,33 +176,36 @@
 	if (effects == 0)
 		return NULL;
 
-	DL_SEARCH_SCALAR(apm_list, list, stream_ptr, stream_ptr);
-	if (list)
-		return list;
-
 	list = (struct cras_apm_list *)calloc(1, sizeof(*list));
+	if (list == NULL) {
+		syslog(LOG_ERR, "No memory in creating apm list");
+		return NULL;
+	}
 	list->stream_ptr = stream_ptr;
 	list->effects = effects;
 	list->apms = NULL;
-	DL_APPEND(apm_list, list);
 
 	return list;
 }
 
-struct cras_apm *cras_apm_list_get(struct cras_apm_list *list, void *dev_ptr)
+static struct active_apm *get_active_apm(void *stream_ptr, void *dev_ptr)
 {
-	struct cras_apm *apm;
+	struct active_apm *active;
 
-	if (list == NULL)
-		return NULL;
-
-	DL_FOREACH (list->apms, apm) {
-		if (apm->dev_ptr == dev_ptr)
-			return apm;
+	DL_FOREACH (active_apms, active) {
+		if ((active->apm->dev_ptr == dev_ptr) &&
+		    (active->stream_ptr == stream_ptr))
+			return active;
 	}
 	return NULL;
 }
 
+struct cras_apm *cras_apm_list_get_active_apm(void *stream_ptr, void *dev_ptr)
+{
+	struct active_apm *active = get_active_apm(stream_ptr, dev_ptr);
+	return active ? active->apm : NULL;
+}
+
 uint64_t cras_apm_list_get_effects(struct cras_apm_list *list)
 {
 	if (list == NULL)
@@ -189,7 +214,7 @@
 		return list->effects;
 }
 
-void cras_apm_list_remove(struct cras_apm_list *list, void *dev_ptr)
+void cras_apm_list_remove_apm(struct cras_apm_list *list, void *dev_ptr)
 {
 	struct cras_apm *apm;
 
@@ -215,13 +240,12 @@
 	int ch;
 	int8_t layout[CRAS_CH_MAX];
 
-	/* Assume device format has correct channel layout populated. */
-	if (apm_fmt->num_channels <= 2)
-		return;
-
-	/* If the device provides recording from more channels than we care
-	 * about, construct a new channel layout containing subset of original
-	 * channels that matches either FL, FR, or FC.
+	/* Using the format from dev_fmt is dangerous because input device
+	 * could have wild configurations like unuse the 1st channel and
+	 * connects 2nd channel to the only mic. Data in the first channel
+	 * is what APM cares about so always construct a new channel layout
+	 * containing subset of original channels that matches either FL, FR,
+	 * or FC.
 	 * TODO(hychao): extend the logic when we have a stream that wants
 	 * to record channels like RR(rear right).
 	 */
@@ -240,8 +264,10 @@
 		apm_fmt->channel_layout[ch] = layout[ch];
 }
 
-struct cras_apm *cras_apm_list_add(struct cras_apm_list *list, void *dev_ptr,
-				   const struct cras_audio_format *dev_fmt)
+struct cras_apm *cras_apm_list_add_apm(struct cras_apm_list *list,
+				       void *dev_ptr,
+				       const struct cras_audio_format *dev_fmt,
+				       bool is_aec_use_case)
 {
 	struct cras_apm *apm;
 
@@ -262,8 +288,23 @@
 	apm->fmt = *dev_fmt;
 	get_best_channels(&apm->fmt);
 
-	apm->apm_ptr = webrtc_apm_create(apm->fmt.num_channels,
-					 apm->fmt.frame_rate, aec_ini, apm_ini);
+	/* Use tuned settings only when the forward dev(capture) and reverse
+	 * dev(playback) both are in typical AEC use case. */
+	apm->is_aec_use_case = is_aec_use_case;
+	if (rmodule->odev) {
+		apm->is_aec_use_case &=
+			cras_iodev_is_aec_use_case(rmodule->odev->active_node);
+	}
+
+	/* Use the configs tuned specifically for internal device. Otherwise
+	 * just pass NULL so every other settings will be default. */
+	apm->apm_ptr =
+		apm->is_aec_use_case ?
+			webrtc_apm_create(apm->fmt.num_channels,
+					  apm->fmt.frame_rate, aec_ini,
+					  apm_ini) :
+			webrtc_apm_create(apm->fmt.num_channels,
+					  apm->fmt.frame_rate, NULL, NULL);
 	if (apm->apm_ptr == NULL) {
 		syslog(LOG_ERR,
 		       "Fail to create webrtc apm for ch %zu"
@@ -286,25 +327,59 @@
 	cras_audio_area_config_channels(apm->area, &apm->fmt);
 
 	DL_APPEND(list->apms, apm);
-	update_process_reverse_flag();
 
 	return apm;
 }
 
-int cras_apm_list_destroy(struct cras_apm_list *list)
+void cras_apm_list_start_apm(struct cras_apm_list *list, void *dev_ptr)
 {
-	struct cras_apm_list *tmp;
+	struct active_apm *active;
 	struct cras_apm *apm;
 
-	DL_FOREACH (apm_list, tmp) {
-		if (tmp == list) {
-			DL_DELETE(apm_list, tmp);
-			break;
-		}
+	if (list == NULL)
+		return;
+
+	/* Check if this apm has already been started. */
+	apm = cras_apm_list_get_active_apm(list->stream_ptr, dev_ptr);
+	if (apm)
+		return;
+
+	DL_SEARCH_SCALAR(list->apms, apm, dev_ptr, dev_ptr);
+	if (apm == NULL)
+		return;
+
+	active = (struct active_apm *)calloc(1, sizeof(*active));
+	if (active == NULL) {
+		syslog(LOG_ERR, "No memory to start apm.");
+		return;
+	}
+	active->apm = apm;
+	active->stream_ptr = list->stream_ptr;
+	active->effects = list->effects;
+	DL_APPEND(active_apms, active);
+
+	update_process_reverse_flag();
+}
+
+void cras_apm_list_stop_apm(struct cras_apm_list *list, void *dev_ptr)
+{
+	struct active_apm *active;
+
+	if (list == NULL)
+		return;
+
+	active = get_active_apm(list->stream_ptr, dev_ptr);
+	if (active) {
+		DL_DELETE(active_apms, active);
+		free(active);
 	}
 
-	if (tmp == NULL)
-		return 0;
+	update_process_reverse_flag();
+}
+
+int cras_apm_list_destroy(struct cras_apm_list *list)
+{
+	struct cras_apm *apm;
 
 	DL_FOREACH (list->apms, apm) {
 		DL_DELETE(list->apms, apm);
@@ -312,8 +387,6 @@
 	}
 	free(list);
 
-	update_process_reverse_flag();
-
 	return 0;
 }
 
@@ -389,8 +462,7 @@
 
 static int process_reverse(struct float_buffer *fbuf, unsigned int frame_rate)
 {
-	struct cras_apm_list *list;
-	struct cras_apm *apm;
+	struct active_apm *active;
 	int ret;
 	float *const *wp;
 
@@ -399,18 +471,16 @@
 
 	wp = float_buffer_write_pointer(fbuf);
 
-	DL_FOREACH (apm_list, list) {
-		if (!(list->effects & APM_ECHO_CANCELLATION))
+	DL_FOREACH (active_apms, active) {
+		if (!(active->effects & APM_ECHO_CANCELLATION))
 			continue;
 
-		DL_FOREACH (list->apms, apm) {
-			ret = webrtc_apm_process_reverse_stream_f(
-				apm->apm_ptr, fbuf->num_channels, frame_rate,
-				wp);
-			if (ret) {
-				syslog(LOG_ERR, "APM process reverse err");
-				return ret;
-			}
+		ret = webrtc_apm_process_reverse_stream_f(active->apm->apm_ptr,
+							  fbuf->num_channels,
+							  frame_rate, wp);
+		if (ret) {
+			syslog(LOG_ERR, "APM process reverse err");
+			return ret;
 		}
 	}
 	float_buffer_reset(fbuf);
@@ -457,9 +527,9 @@
 
 static void get_aec_ini(const char *config_dir)
 {
-	snprintf(ini_name, MAX_INI_NAME_LEN, "%s/%s", config_dir,
+	snprintf(ini_name, MAX_INI_NAME_LENGTH, "%s/%s", config_dir,
 		 AEC_CONFIG_NAME);
-	ini_name[MAX_INI_NAME_LEN] = '\0';
+	ini_name[MAX_INI_NAME_LENGTH] = '\0';
 
 	if (aec_ini) {
 		iniparser_freedict(aec_ini);
@@ -472,9 +542,9 @@
 
 static void get_apm_ini(const char *config_dir)
 {
-	snprintf(ini_name, MAX_INI_NAME_LEN, "%s/%s", config_dir,
+	snprintf(ini_name, MAX_INI_NAME_LENGTH, "%s/%s", config_dir,
 		 APM_CONFIG_NAME);
-	ini_name[MAX_INI_NAME_LEN] = '\0';
+	ini_name[MAX_INI_NAME_LENGTH] = '\0';
 
 	if (apm_ini) {
 		iniparser_freedict(apm_ini);
@@ -619,6 +689,13 @@
 	return &apm->fmt;
 }
 
+bool cras_apm_list_get_use_tuned_settings(struct cras_apm *apm)
+{
+	/* If input and output devices in AEC use case, plus that a
+	 * tuned setting is provided. */
+	return apm->is_aec_use_case && (aec_ini || apm_ini);
+}
+
 void cras_apm_list_set_aec_dump(struct cras_apm_list *list, void *dev_ptr,
 				int start, int fd)
 {
diff --git a/cras/src/server/cras_apm_list.h b/cras/src/server/cras_apm_list.h
index 486b943..7a36cea 100644
--- a/cras/src/server/cras_apm_list.h
+++ b/cras/src/server/cras_apm_list.h
@@ -27,7 +27,25 @@
 
 /*
  * Creates an list to hold all APM instances created when a stream
- * attaches to an iodev.
+ * attaches to iodev(s). This should be called in main thread.
+ *
+ * Below diagram explains the life cycle of an APM instance, how are
+ * related APIs used, and in which thread should each API be called.
+ *
+ * Main thread                     Audio thread
+ * maintaining apm_list            maintaining active_apms
+ * -----------                     ------------
+ * cras_apm_list_create
+ * cras_apm_list_add_apm    ->     cras_apm_list_start_apm
+ *
+ *                                 cras_apm_list_get_active_apm
+ *                                 cras_apm_list_process
+ *                                 cras_apm_list_get_processed
+ *                                 cras_apm_list_put_processed
+ *
+ * cras_apm_list_remove_apm <-     cras_apm_list_stop_apm
+ * cras_apm_list_destroy
+ *
  * Args:
  *    stream_ptr - Pointer to the stream.
  *    effects - Bit map specifying the enabled effects on this stream.
@@ -37,22 +55,39 @@
 /*
  * Creates a cras_apm associated to given dev_ptr and adds it to the list.
  * If there already exists an APM instance linked to dev_ptr, we assume
- * the open format is unchanged so just return it.
+ * the open format is unchanged so just return it. This should be called
+ * in main thread.
  * Args:
  *    list - The list holding APM instances.
  *    dev_ptr - Pointer to the iodev to add new APM for.
  *    fmt - Format of the audio data used for this cras_apm.
+ *    is_aec_use_case - If the dev_ptr is for typical AEC use case.
  */
-struct cras_apm *cras_apm_list_add(struct cras_apm_list *list, void *dev_ptr,
-				   const struct cras_audio_format *fmt);
+struct cras_apm *cras_apm_list_add_apm(struct cras_apm_list *list,
+				       void *dev_ptr,
+				       const struct cras_audio_format *fmt,
+				       bool is_aec_use_case);
 
 /*
- * Gets the cras_apm instance in the list that associates with given dev.
+ * Gets the active APM instance that is associated to given stream and dev pair.
+ * This should be called in audio thread.
  * Args:
- *    list - The list holding APM instances.
+ *    stream_ptr - Pointer to the stream.
  *    dev_ptr - The iodev as key to look up associated APM.
  */
-struct cras_apm *cras_apm_list_get(struct cras_apm_list *list, void *dev_ptr);
+struct cras_apm *cras_apm_list_get_active_apm(void *stream_ptr, void *dev_ptr);
+
+/*
+ * Starts the APM instance in the list that is associated with dev_ptr by
+ * adding it to the active APM list in audio thread.
+ */
+void cras_apm_list_start_apm(struct cras_apm_list *list, void *dev_ptr);
+
+/*
+ * Stops the APM instance in the list that is associated with dev_ptr by
+ * removing it from the active APM list in audio thread.
+ */
+void cras_apm_list_stop_apm(struct cras_apm_list *list, void *dev_ptr);
 
 /*
  * Gets the effects bit map of the APM list.
@@ -66,12 +101,13 @@
 
 /*
  * Removes an APM from the list, expected to be used when an iodev is no
- * longer open for the client stream holding the APM list.
+ * longer open for the client stream holding the APM list. This should
+ * be called in main thread.
  * Args:
  *    list - The list holding APM instances.
  *    dev_ptr - Device pointer used to look up which apm to remove.
  */
-void cras_apm_list_remove(struct cras_apm_list *list, void *dev_ptr);
+void cras_apm_list_remove_apm(struct cras_apm_list *list, void *dev_ptr);
 
 /* Passes audio data from hardware for cras_apm to process.
  * Args:
@@ -107,6 +143,11 @@
  */
 struct cras_audio_format *cras_apm_list_get_format(struct cras_apm *apm);
 
+/*
+ * Gets if this apm instance is using tuned settings.
+ */
+bool cras_apm_list_get_use_tuned_settings(struct cras_apm *apm);
+
 /* Sets debug recording to start or stop.
  * Args:
  *    list - List contains the apm instance to start/stop debug recording.
@@ -121,7 +162,7 @@
 
 /*
  * If webrtc audio processing library is not available then define all
- * cras_apm_list functions as dummy. As long as cras_apm_list_add returns
+ * cras_apm_list functions as empty. As long as cras_apm_list_add returns
  * NULL, non of the other functions should be called.
  */
 static inline int cras_apm_list_init(const char *device_config_dir)
@@ -137,13 +178,13 @@
 	return NULL;
 }
 static inline struct cras_apm *
-cras_apm_list_add(struct cras_apm_list *list, void *dev_ptr,
-		  const struct cras_audio_format *fmt)
+cras_apm_list_add_apm(struct cras_apm_list *list, void *dev_ptr,
+		      const struct cras_audio_format *fmt, bool is_aec_use_case)
 {
 	return NULL;
 }
-static inline struct cras_apm *cras_apm_list_get(struct cras_apm_list *list,
-						 void *dev_ptr)
+static inline struct cras_apm *cras_apm_list_get_active_apm(void *stream_ptr,
+							    void *dev_ptr)
 {
 	return NULL;
 }
@@ -155,8 +196,8 @@
 {
 	return 0;
 }
-static inline void cras_apm_list_remove(struct cras_apm_list *list,
-					void *dev_ptr)
+static inline void cras_apm_list_remove_apm(struct cras_apm_list *list,
+					    void *dev_ptr)
 {
 }
 
@@ -177,6 +218,14 @@
 					       unsigned int frames)
 {
 }
+static inline void cras_apm_list_start_apm(struct cras_apm_list *list,
+					   void *dev_ptr)
+{
+}
+static inline void cras_apm_list_stop_apm(struct cras_apm_list *list,
+					  void *dev_ptr)
+{
+}
 
 static inline struct cras_audio_format *
 cras_apm_list_get_format(struct cras_apm *apm)
@@ -184,6 +233,11 @@
 	return NULL;
 }
 
+static inline bool cras_apm_list_get_use_tuned_settings(struct cras_apm *apm)
+{
+	return 0;
+}
+
 static inline void cras_apm_list_set_aec_dump(struct cras_apm_list *list,
 					      void *dev_ptr, int start, int fd)
 {
diff --git a/cras/src/server/cras_audio_thread_monitor.c b/cras/src/server/cras_audio_thread_monitor.c
index 5921245..ed3afba 100644
--- a/cras/src/server/cras_audio_thread_monitor.c
+++ b/cras/src/server/cras_audio_thread_monitor.c
@@ -50,6 +50,16 @@
 	return cras_main_message_send(&msg.header);
 }
 
+int cras_audio_thread_event_a2dp_overrun()
+{
+	return cras_audio_thread_event_send(AUDIO_THREAD_EVENT_A2DP_OVERRUN);
+}
+
+int cras_audio_thread_event_a2dp_throttle()
+{
+	return cras_audio_thread_event_send(AUDIO_THREAD_EVENT_A2DP_THROTTLE);
+}
+
 int cras_audio_thread_event_debug()
 {
 	return cras_audio_thread_event_send(AUDIO_THREAD_EVENT_DEBUG);
@@ -75,6 +85,11 @@
 	return cras_audio_thread_event_send(AUDIO_THREAD_EVENT_DROP_SAMPLES);
 }
 
+int cras_audio_thread_event_dev_overrun()
+{
+	return cras_audio_thread_event_send(AUDIO_THREAD_EVENT_DEV_OVERRUN);
+}
+
 static struct timespec last_event_snapshot_time[AUDIO_THREAD_EVENT_TYPE_COUNT];
 
 /*
diff --git a/cras/src/server/cras_audio_thread_monitor.h b/cras/src/server/cras_audio_thread_monitor.h
index b7355ca..39b2176 100644
--- a/cras/src/server/cras_audio_thread_monitor.h
+++ b/cras/src/server/cras_audio_thread_monitor.h
@@ -7,6 +7,16 @@
 #define CRAS_AUDIO_THREAD_MONITOR_H_
 
 /*
+ * Notifies the main thread when A2DP buffer overruns.
+ */
+int cras_audio_thread_event_a2dp_overrun();
+
+/*
+ * Notifies the main thread when A2DP packet transmittion throttles.
+ */
+int cras_audio_thread_event_a2dp_throttle();
+
+/*
  * Sends a debug event to the audio thread for debugging.
  */
 int cras_audio_thread_event_debug();
@@ -32,6 +42,11 @@
 int cras_audio_thread_event_drop_samples();
 
 /*
+ * Notifies the main thread when a device overrun event happens.
+ */
+int cras_audio_thread_event_dev_overrun();
+
+/*
  * Initializes audio thread monitor and sets main thread callback.
  */
 int cras_audio_thread_monitor_init();
diff --git a/cras/src/server/cras_bt_battery_provider.c b/cras/src/server/cras_bt_battery_provider.c
new file mode 100644
index 0000000..13e6590
--- /dev/null
+++ b/cras/src/server/cras_bt_battery_provider.c
@@ -0,0 +1,371 @@
+/* Copyright 2020 The Chromium OS 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 <dbus/dbus.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+
+#include "cras_bt_adapter.h"
+#include "cras_bt_battery_provider.h"
+#include "cras_bt_constants.h"
+#include "cras_dbus_util.h"
+#include "cras_observer.h"
+#include "utlist.h"
+
+/* CRAS registers one battery provider to BlueZ, so we use a singleton. */
+static struct cras_bt_battery_provider battery_provider = {
+	.object_path = CRAS_DEFAULT_BATTERY_PROVIDER,
+	.interface = BLUEZ_INTERFACE_BATTERY_PROVIDER,
+	.conn = NULL,
+	.is_registered = false,
+	.observer = NULL,
+	.batteries = NULL,
+};
+
+static int cmp_battery_address(const struct cras_bt_battery *battery,
+			       const char *address)
+{
+	return strcmp(battery->address, address);
+}
+
+static void replace_colon_with_underscore(char *str)
+{
+	for (int i = 0; str[i]; i++) {
+		if (str[i] == ':')
+			str[i] = '_';
+	}
+}
+
+/* Converts address XX:XX:XX:XX:XX:XX to Battery Provider object path:
+ * /org/chromium/Cras/Bluetooth/BatteryProvider/XX_XX_XX_XX_XX_XX
+ */
+static char *address_to_battery_path(const char *address)
+{
+	char *object_path = malloc(strlen(CRAS_DEFAULT_BATTERY_PROVIDER) +
+				   strlen(address) + 2);
+
+	sprintf(object_path, "%s/%s", CRAS_DEFAULT_BATTERY_PROVIDER, address);
+	replace_colon_with_underscore(object_path);
+
+	return object_path;
+}
+
+/* Converts address XX:XX:XX:XX:XX:XX to device object path:
+ * /org/bluez/hci0/dev_XX_XX_XX_XX_XX_XX
+ */
+static char *address_to_device_path(const char *address)
+{
+	char *object_path = malloc(strlen(CRAS_DEFAULT_BATTERY_PREFIX) +
+				   strlen(address) + 1);
+
+	sprintf(object_path, "%s%s", CRAS_DEFAULT_BATTERY_PREFIX, address);
+	replace_colon_with_underscore(object_path);
+
+	return object_path;
+}
+
+static struct cras_bt_battery *battery_new(const char *address, uint32_t level)
+{
+	struct cras_bt_battery *battery;
+
+	battery = calloc(1, sizeof(struct cras_bt_battery));
+	battery->address = strdup(address);
+	battery->object_path = address_to_battery_path(address);
+	battery->device_path = address_to_device_path(address);
+	battery->level = level;
+
+	return battery;
+}
+
+static void battery_free(struct cras_bt_battery *battery)
+{
+	if (battery->address)
+		free(battery->address);
+	if (battery->object_path)
+		free(battery->object_path);
+	if (battery->device_path)
+		free(battery->device_path);
+	free(battery);
+}
+
+static void populate_battery_properties(DBusMessageIter *iter,
+					const struct cras_bt_battery *battery)
+{
+	DBusMessageIter dict, entry, variant;
+	const char *property_percentage = "Percentage";
+	const char *property_device = "Device";
+	uint8_t level = battery->level;
+
+	dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{sv}", &dict);
+
+	dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL,
+					 &entry);
+	dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING,
+				       &property_percentage);
+	dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT,
+					 DBUS_TYPE_BYTE_AS_STRING, &variant);
+	dbus_message_iter_append_basic(&variant, DBUS_TYPE_BYTE, &level);
+	dbus_message_iter_close_container(&entry, &variant);
+	dbus_message_iter_close_container(&dict, &entry);
+
+	dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL,
+					 &entry);
+	dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING,
+				       &property_device);
+	dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT,
+					 DBUS_TYPE_OBJECT_PATH_AS_STRING,
+					 &variant);
+	dbus_message_iter_append_basic(&variant, DBUS_TYPE_OBJECT_PATH,
+				       &battery->device_path);
+	dbus_message_iter_close_container(&entry, &variant);
+	dbus_message_iter_close_container(&dict, &entry);
+
+	dbus_message_iter_close_container(iter, &dict);
+}
+
+/* Creates a new battery object and exposes it on D-Bus. */
+static struct cras_bt_battery *
+get_or_create_battery(struct cras_bt_battery_provider *provider,
+		      const char *address, uint32_t level)
+{
+	struct cras_bt_battery *battery;
+	DBusMessage *msg;
+	DBusMessageIter iter, dict, entry;
+
+	LL_SEARCH(provider->batteries, battery, address, cmp_battery_address);
+
+	if (battery)
+		return battery;
+
+	syslog(LOG_DEBUG, "Creating new battery for %s", address);
+
+	battery = battery_new(address, level);
+	LL_APPEND(provider->batteries, battery);
+
+	msg = dbus_message_new_signal(CRAS_DEFAULT_BATTERY_PROVIDER,
+				      DBUS_INTERFACE_OBJECT_MANAGER,
+				      DBUS_SIGNAL_INTERFACES_ADDED);
+
+	dbus_message_iter_init_append(msg, &iter);
+	dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH,
+				       &battery->object_path);
+	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sa{sv}}",
+					 &dict);
+	dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL,
+					 &entry);
+	dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING,
+				       &provider->interface);
+	populate_battery_properties(&entry, battery);
+	dbus_message_iter_close_container(&dict, &entry);
+	dbus_message_iter_close_container(&iter, &dict);
+
+	if (!dbus_connection_send(provider->conn, msg, NULL)) {
+		syslog(LOG_ERR,
+		       "Error sending " DBUS_SIGNAL_INTERFACES_ADDED " signal");
+	}
+
+	dbus_message_unref(msg);
+
+	return battery;
+}
+
+/* Updates the level of a battery object and signals it on D-Bus. */
+static void
+update_battery_level(const struct cras_bt_battery_provider *provider,
+		     struct cras_bt_battery *battery, uint32_t level)
+{
+	DBusMessage *msg;
+	DBusMessageIter iter;
+
+	if (battery->level == level)
+		return;
+
+	battery->level = level;
+
+	msg = dbus_message_new_signal(battery->object_path,
+				      DBUS_INTERFACE_PROPERTIES,
+				      DBUS_SIGNAL_PROPERTIES_CHANGED);
+
+	dbus_message_iter_init_append(msg, &iter);
+	dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING,
+				       &provider->interface);
+	populate_battery_properties(&iter, battery);
+
+	if (!dbus_connection_send(provider->conn, msg, NULL)) {
+		syslog(LOG_ERR, "Error sending " DBUS_SIGNAL_PROPERTIES_CHANGED
+				" signal");
+	}
+
+	dbus_message_unref(msg);
+}
+
+/* Invoked when HFP sends an alert about a battery value change. */
+static void on_bt_battery_changed(void *context, const char *address,
+				  uint32_t level)
+{
+	struct cras_bt_battery_provider *provider = context;
+
+	syslog(LOG_DEBUG, "Battery changed for address %s, level %d", address,
+	       level);
+
+	if (!provider->is_registered) {
+		syslog(LOG_WARNING, "Received battery level update while "
+				    "battery provider is not registered");
+		return;
+	}
+
+	struct cras_bt_battery *battery =
+		get_or_create_battery(provider, address, level);
+
+	update_battery_level(provider, battery, level);
+}
+
+/* Invoked when we receive a D-Bus return of RegisterBatteryProvider from
+ * BlueZ.
+ */
+static void
+cras_bt_on_battery_provider_registered(DBusPendingCall *pending_call,
+				       void *data)
+{
+	DBusMessage *reply;
+	struct cras_bt_battery_provider *provider = data;
+	struct cras_observer_ops observer_ops;
+
+	reply = dbus_pending_call_steal_reply(pending_call);
+	dbus_pending_call_unref(pending_call);
+
+	if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) {
+		syslog(LOG_ERR, "RegisterBatteryProvider returned error: %s",
+		       dbus_message_get_error_name(reply));
+		dbus_message_unref(reply);
+		return;
+	}
+
+	syslog(LOG_INFO, "RegisterBatteryProvider succeeded");
+
+	provider->is_registered = true;
+
+	memset(&observer_ops, 0, sizeof(observer_ops));
+	observer_ops.bt_battery_changed = on_bt_battery_changed;
+	provider->observer = cras_observer_add(&observer_ops, provider);
+
+	dbus_message_unref(reply);
+}
+
+int cras_bt_register_battery_provider(DBusConnection *conn,
+				      const struct cras_bt_adapter *adapter)
+{
+	const char *adapter_path;
+	DBusMessage *method_call;
+	DBusMessageIter message_iter;
+	DBusPendingCall *pending_call;
+
+	if (battery_provider.is_registered) {
+		syslog(LOG_ERR, "Battery Provider already registered");
+		return -EBUSY;
+	}
+
+	if (battery_provider.conn)
+		dbus_connection_unref(battery_provider.conn);
+
+	battery_provider.conn = conn;
+	dbus_connection_ref(battery_provider.conn);
+
+	adapter_path = cras_bt_adapter_object_path(adapter);
+	method_call = dbus_message_new_method_call(
+		BLUEZ_SERVICE, adapter_path,
+		BLUEZ_INTERFACE_BATTERY_PROVIDER_MANAGER,
+		"RegisterBatteryProvider");
+	if (!method_call)
+		return -ENOMEM;
+
+	dbus_message_iter_init_append(method_call, &message_iter);
+	dbus_message_iter_append_basic(&message_iter, DBUS_TYPE_OBJECT_PATH,
+				       &battery_provider.object_path);
+
+	if (!dbus_connection_send_with_reply(conn, method_call, &pending_call,
+					     DBUS_TIMEOUT_USE_DEFAULT)) {
+		dbus_message_unref(method_call);
+		return -ENOMEM;
+	}
+
+	dbus_message_unref(method_call);
+
+	if (!pending_call)
+		return -EIO;
+
+	if (!dbus_pending_call_set_notify(
+		    pending_call, cras_bt_on_battery_provider_registered,
+		    &battery_provider, NULL)) {
+		dbus_pending_call_cancel(pending_call);
+		dbus_pending_call_unref(pending_call);
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+/* Removes a battery object and signals the removal on D-Bus as well. */
+static void cleanup_battery(struct cras_bt_battery_provider *provider,
+			    struct cras_bt_battery *battery)
+{
+	DBusMessage *msg;
+	DBusMessageIter iter, entry;
+
+	if (!battery)
+		return;
+
+	LL_DELETE(provider->batteries, battery);
+
+	msg = dbus_message_new_signal(CRAS_DEFAULT_BATTERY_PROVIDER,
+				      DBUS_INTERFACE_OBJECT_MANAGER,
+				      DBUS_SIGNAL_INTERFACES_REMOVED);
+
+	dbus_message_iter_init_append(msg, &iter);
+	dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH,
+				       &battery->object_path);
+	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+					 DBUS_TYPE_STRING_AS_STRING, &entry);
+	dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING,
+				       &provider->interface);
+	dbus_message_iter_close_container(&iter, &entry);
+
+	if (!dbus_connection_send(provider->conn, msg, NULL)) {
+		syslog(LOG_ERR, "Error sending " DBUS_SIGNAL_INTERFACES_REMOVED
+				" signal");
+	}
+
+	dbus_message_unref(msg);
+
+	battery_free(battery);
+}
+
+void cras_bt_battery_provider_reset()
+{
+	struct cras_bt_battery *battery;
+
+	syslog(LOG_INFO, "Resetting battery provider");
+
+	if (!battery_provider.is_registered)
+		return;
+
+	battery_provider.is_registered = false;
+
+	LL_FOREACH (battery_provider.batteries, battery) {
+		cleanup_battery(&battery_provider, battery);
+	}
+
+	if (battery_provider.conn) {
+		dbus_connection_unref(battery_provider.conn);
+		battery_provider.conn = NULL;
+	}
+
+	if (battery_provider.observer) {
+		cras_observer_remove(battery_provider.observer);
+		battery_provider.observer = NULL;
+	}
+}
diff --git a/cras/src/server/cras_bt_battery_provider.h b/cras/src/server/cras_bt_battery_provider.h
new file mode 100644
index 0000000..1998cd7
--- /dev/null
+++ b/cras/src/server/cras_bt_battery_provider.h
@@ -0,0 +1,47 @@
+/* Copyright 2020 The Chromium OS 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 CRAS_BT_BATTERY_PROVIDER_H_
+#define CRAS_BT_BATTERY_PROVIDER_H_
+
+#include <dbus/dbus.h>
+#include <stdbool.h>
+
+#include "cras_bt_adapter.h"
+
+/* Object to represent a battery that is exposed to BlueZ. */
+struct cras_bt_battery {
+	char *address;
+	char *object_path;
+	char *device_path;
+	uint32_t level;
+	struct cras_bt_battery *next;
+};
+
+/* Object to register as battery provider so that bluetoothd will monitor
+ * battery objects that we expose.
+ */
+struct cras_bt_battery_provider {
+	const char *object_path;
+	const char *interface;
+	DBusConnection *conn;
+	bool is_registered;
+	struct cras_observer_client *observer;
+	struct cras_bt_battery *batteries;
+};
+
+/* Registers battery provider to bluetoothd. This is used when a Bluetooth
+ * adapter got enumerated.
+ * Args:
+ *    conn - The D-Bus connection.
+ *    adapter - The enumerated bluetooth adapter.
+ */
+int cras_bt_register_battery_provider(DBusConnection *conn,
+				      const struct cras_bt_adapter *adapter);
+
+/* Resets internal state of battery provider. */
+void cras_bt_battery_provider_reset();
+
+#endif /* CRAS_BT_BATTERY_PROVIDER_H_ */
diff --git a/cras/src/server/cras_bt_constants.h b/cras/src/server/cras_bt_constants.h
index 8d9ad5d..318aeca 100644
--- a/cras/src/server/cras_bt_constants.h
+++ b/cras/src/server/cras_bt_constants.h
@@ -6,12 +6,16 @@
 #ifndef CRAS_BT_CONSTANTS_H_
 #define CRAS_BT_CONSTANTS_H_
 
-#define BLUEZ_SERVICE "org.chromium.Bluetooth"
+#define BLUEZ_SERVICE "org.bluez"
 
 #define BLUEZ_INTERFACE_ADAPTER "org.bluez.Adapter1"
+#define BLUEZ_INTERFACE_BATTERY_PROVIDER "org.bluez.BatteryProvider1"
+#define BLUEZ_INTERFACE_BATTERY_PROVIDER_MANAGER                               \
+	"org.bluez.BatteryProviderManager1"
 #define BLUEZ_INTERFACE_DEVICE "org.bluez.Device1"
 #define BLUEZ_INTERFACE_MEDIA "org.bluez.Media1"
 #define BLUEZ_INTERFACE_MEDIA_ENDPOINT "org.bluez.MediaEndpoint1"
+#define BLUEZ_INTERFACE_MEDIA_PLAYER "org.mpris.MediaPlayer2.Player"
 #define BLUEZ_INTERFACE_MEDIA_TRANSPORT "org.bluez.MediaTransport1"
 #define BLUEZ_INTERFACE_PLAYER "org.bluez.MediaPlayer1"
 #define BLUEZ_INTERFACE_PROFILE "org.bluez.Profile1"
@@ -20,6 +24,9 @@
 #ifndef DBUS_INTERFACE_OBJECT_MANAGER
 #define DBUS_INTERFACE_OBJECT_MANAGER "org.freedesktop.DBus.ObjectManager"
 #endif
+#define DBUS_SIGNAL_INTERFACES_ADDED "InterfacesAdded"
+#define DBUS_SIGNAL_INTERFACES_REMOVED "InterfacesRemoved"
+#define DBUS_SIGNAL_PROPERTIES_CHANGED "PropertiesChanged"
 
 /* UUIDs taken from lib/uuid.h in the BlueZ source */
 #define HSP_HS_UUID "00001108-0000-1000-8000-00805f9b34fb"
@@ -36,4 +43,29 @@
 
 #define GENERIC_AUDIO_UUID "00001203-0000-1000-8000-00805f9b34fb"
 
+/* Constants for CRAS BT player */
+#define CRAS_DEFAULT_PLAYER "/org/chromium/Cras/Bluetooth/DefaultPlayer"
+/* The longest possible player playback status is "forward-seek" */
+#define CRAS_PLAYER_PLAYBACK_STATUS_SIZE_MAX 13 * sizeof(char)
+#define CRAS_PLAYER_PLAYBACK_STATUS_DEFAULT "playing"
+/* Neither BlueZ or the MRPIS specs limited the player identity max size, 128
+ * should be large enough for most.
+ */
+#define CRAS_PLAYER_IDENTITY_SIZE_MAX 128 * sizeof(char)
+#define CRAS_PLAYER_IDENTITY_DEFAULT "DefaultPlayer"
+#define CRAS_PLAYER_METADATA_SIZE_MAX 128 * sizeof(char)
+
+#define CRAS_DEFAULT_BATTERY_PROVIDER                                          \
+	"/org/chromium/Cras/Bluetooth/BatteryProvider"
+#define CRAS_DEFAULT_BATTERY_PREFIX "/org/bluez/hci0/dev_"
+
+/* Instead of letting CRAS obtain the A2DP streaming packet size (a.k.a. AVDTP
+ * MTU) from BlueZ Media Transport, force the packet size to the default L2CAP
+ * packet size. This prevent the audio peripheral device to negotiate a larger
+ * packet size and later failed to fulfill it and causing audio artifact. This
+ * defined constant is for experiment only and is put back behind a
+ * chrome://flag.
+ */
+#define A2DP_FIX_PACKET_SIZE 672
+
 #endif /* CRAS_BT_CONSTANTS_H_ */
diff --git a/cras/src/server/cras_bt_device.c b/cras/src/server/cras_bt_device.c
index bc6b43d..6b06dd1 100644
--- a/cras/src/server/cras_bt_device.c
+++ b/cras/src/server/cras_bt_device.c
@@ -31,8 +31,10 @@
 #include "cras_iodev.h"
 #include "cras_iodev_list.h"
 #include "cras_main_message.h"
+#include "cras_server_metrics.h"
 #include "cras_system_state.h"
 #include "cras_tm.h"
+#include "sfh.h"
 #include "utlist.h"
 
 /*
@@ -52,12 +54,15 @@
  */
 static const unsigned int CONN_WATCH_PERIOD_MS = 2000;
 static const unsigned int CONN_WATCH_MAX_RETRIES = 30;
-static const unsigned int PROFILE_CONN_RETRIES = 3;
+
+/* This is used when a critical SCO failure happens and is worth scheduling a
+ * suspend in case for some reason BT headset stays connected in baseband and
+ * confuses user.
+ */
+static const unsigned int SCO_SUSPEND_DELAY_MS = 5000;
 
 static const unsigned int CRAS_SUPPORTED_PROFILES =
-	CRAS_BT_DEVICE_PROFILE_A2DP_SINK |
-	CRAS_BT_DEVICE_PROFILE_HFP_HANDSFREE |
-	CRAS_BT_DEVICE_PROFILE_HSP_AUDIOGATEWAY;
+	CRAS_BT_DEVICE_PROFILE_A2DP_SINK | CRAS_BT_DEVICE_PROFILE_HFP_HANDSFREE;
 
 /* Object to represent a general bluetooth device, and used to
  * associate with some CRAS modules if it supports audio.
@@ -73,6 +78,8 @@
  *    connected - If this devices is connected.
  *    connected_profiles - OR'ed all connected audio profiles.
  *    profiles - OR'ed by all audio profiles this device supports.
+ *    hidden_profiles - OR'ed by all audio profiles this device actually
+ *        supports but is not scanned by BlueZ.
  *    bt_iodevs - The pointer to the cras_iodevs of this device.
  *    active_profile - The flag to indicate the active audio profile this
  *        device is currently using.
@@ -84,6 +91,8 @@
  *        profile switch.
  *    sco_fd - The file descriptor of the SCO connection.
  *    sco_ref_count - The reference counts of the SCO connection.
+ *    suspend_reason - The reason code for why suspend is scheduled.
+ *    stable_id - The unique and persistent id of this bt_device.
  */
 struct cras_bt_device {
 	DBusConnection *conn;
@@ -95,8 +104,9 @@
 	int paired;
 	int trusted;
 	int connected;
-	enum cras_bt_device_profile connected_profiles;
-	enum cras_bt_device_profile profiles;
+	unsigned int connected_profiles;
+	unsigned int profiles;
+	unsigned int hidden_profiles;
 	struct cras_iodev *bt_iodevs[CRAS_NUM_DIRECTIONS];
 	unsigned int active_profile;
 	int use_hardware_volume;
@@ -106,6 +116,8 @@
 	struct cras_timer *switch_profile_timer;
 	int sco_fd;
 	size_t sco_ref_count;
+	enum cras_bt_device_suspend_reason suspend_reason;
+	unsigned int stable_id;
 
 	struct cras_bt_device *prev, *next;
 };
@@ -122,7 +134,8 @@
 	enum BT_DEVICE_COMMAND cmd;
 	struct cras_bt_device *device;
 	struct cras_iodev *dev;
-	unsigned int arg;
+	unsigned int arg1;
+	unsigned int arg2;
 };
 
 static struct cras_bt_device *devices;
@@ -164,6 +177,9 @@
 		free(device);
 		return NULL;
 	}
+	device->stable_id =
+		SuperFastHash(device->object_path, strlen(device->object_path),
+			      strlen(device->object_path));
 
 	DL_APPEND(devices, device);
 
@@ -333,6 +349,11 @@
 	return device->object_path;
 }
 
+int cras_bt_device_get_stable_id(const struct cras_bt_device *device)
+{
+	return device->stable_id;
+}
+
 struct cras_bt_adapter *
 cras_bt_device_adapter(const struct cras_bt_device *device)
 {
@@ -386,6 +407,25 @@
 	}
 }
 
+/*
+ * Sets the audio nodes to 'plugged' means UI can select it and open it
+ * for streams. Sets to 'unplugged' to hide these nodes from UI, when device
+ * disconnects in progress.
+ */
+static void bt_device_set_nodes_plugged(struct cras_bt_device *device,
+					int plugged)
+{
+	struct cras_iodev *iodev;
+
+	iodev = device->bt_iodevs[CRAS_STREAM_INPUT];
+	if (iodev)
+		cras_iodev_set_node_plugged(iodev->active_node, plugged);
+
+	iodev = device->bt_iodevs[CRAS_STREAM_OUTPUT];
+	if (iodev)
+		cras_iodev_set_node_plugged(iodev->active_node, plugged);
+}
+
 static void bt_device_switch_profile(struct cras_bt_device *device,
 				     struct cras_iodev *bt_iodev,
 				     int enable_dev);
@@ -396,6 +436,8 @@
 	struct cras_iodev *bt_iodev;
 	int rc;
 
+	bt_device_set_nodes_plugged(device, 0);
+
 	bt_iodev = device->bt_iodevs[iodev->direction];
 	if (bt_iodev) {
 		unsigned try_profile;
@@ -467,6 +509,8 @@
 		cras_a2dp_suspend_connected_device(connected);
 }
 
+static void bt_device_conn_watch_cb(struct cras_timer *timer, void *arg);
+
 int cras_bt_device_audio_gateway_initialized(struct cras_bt_device *device)
 {
 	BTLOG(btlog, BT_AUDIO_GATEWAY_INIT, device->profiles, 0);
@@ -475,10 +519,23 @@
 	device->connected_profiles |= (CRAS_BT_DEVICE_PROFILE_HFP_HANDSFREE |
 				       CRAS_BT_DEVICE_PROFILE_HSP_HEADSET);
 
+	/* If device connects HFP but not reporting correct UUID, manually add
+	 * it to allow CRAS to enumerate audio node for it. We're seeing this
+	 * behavior on qualification test software. */
+	if (!cras_bt_device_supports_profile(
+		    device, CRAS_BT_DEVICE_PROFILE_HFP_HANDSFREE)) {
+		unsigned int profiles =
+			device->profiles | CRAS_BT_DEVICE_PROFILE_HFP_HANDSFREE;
+		cras_bt_device_set_supported_profiles(device, profiles);
+		device->hidden_profiles |= CRAS_BT_DEVICE_PROFILE_HFP_HANDSFREE;
+		bt_device_conn_watch_cb(NULL, (void *)device);
+	}
+
 	return 0;
 }
 
-int cras_bt_device_get_active_profile(const struct cras_bt_device *device)
+unsigned int
+cras_bt_device_get_active_profile(const struct cras_bt_device *device)
 {
 	return device->active_profile;
 }
@@ -528,6 +585,19 @@
 	}
 }
 
+static void cras_bt_device_log_profiles(const struct cras_bt_device *device,
+					unsigned int profiles)
+{
+	unsigned int profile;
+
+	while (profiles) {
+		/* Get the LSB of profiles */
+		profile = profiles & -profiles;
+		cras_bt_device_log_profile(device, profile);
+		profiles ^= profile;
+	}
+}
+
 static int
 cras_bt_device_is_profile_connected(const struct cras_bt_device *device,
 				    enum cras_bt_device_profile profile)
@@ -535,8 +605,9 @@
 	return !!(device->connected_profiles & profile);
 }
 
-static void bt_device_schedule_suspend(struct cras_bt_device *device,
-				       unsigned int msec);
+static void
+bt_device_schedule_suspend(struct cras_bt_device *device, unsigned int msec,
+			   enum cras_bt_device_suspend_reason suspend_reason);
 
 /* Callback used to periodically check if supported profiles are connected. */
 static void bt_device_conn_watch_cb(struct cras_timer *timer, void *arg)
@@ -544,6 +615,10 @@
 	struct cras_tm *tm;
 	struct cras_bt_device *device = (struct cras_bt_device *)arg;
 	int rc;
+	bool a2dp_supported;
+	bool a2dp_connected;
+	bool hfp_supported;
+	bool hfp_connected;
 
 	BTLOG(btlog, BT_DEV_CONN_WATCH_CB, device->conn_watch_retries,
 	      device->profiles);
@@ -553,28 +628,35 @@
 	if (!device->profiles)
 		return;
 
-	/* If A2DP is not ready, try connect it after a while. */
-	if (cras_bt_device_supports_profile(device,
-					    CRAS_BT_DEVICE_PROFILE_A2DP_SINK) &&
-	    !cras_bt_device_is_profile_connected(
-		    device, CRAS_BT_DEVICE_PROFILE_A2DP_SINK)) {
-		if (0 == device->conn_watch_retries % PROFILE_CONN_RETRIES)
+	a2dp_supported = cras_bt_device_supports_profile(
+		device, CRAS_BT_DEVICE_PROFILE_A2DP_SINK);
+	a2dp_connected = cras_bt_device_is_profile_connected(
+		device, CRAS_BT_DEVICE_PROFILE_A2DP_SINK);
+	hfp_supported = cras_bt_device_supports_profile(
+		device, CRAS_BT_DEVICE_PROFILE_HFP_HANDSFREE);
+	hfp_connected = cras_bt_device_is_profile_connected(
+		device, CRAS_BT_DEVICE_PROFILE_HFP_HANDSFREE);
+
+	/* If not both A2DP and HFP are supported, simply wait for BlueZ
+	 * to notify us about the new connection.
+	 * Otherwise, when seeing one but not the other profile is connected,
+	 * send message to ask BlueZ to connect the pending one.
+	 */
+	if (a2dp_supported && hfp_supported) {
+		/* If both a2dp and hfp are not connected, do nothing. BlueZ
+		 * should be responsible to notify connection of one profile.
+		 */
+		if (!a2dp_connected && hfp_connected)
 			cras_bt_device_connect_profile(device->conn, device,
 						       A2DP_SINK_UUID);
-		goto arm_retry_timer;
-	}
-
-	/* If HFP is not ready, try connect it after a while. */
-	if (cras_bt_device_supports_profile(
-		    device, CRAS_BT_DEVICE_PROFILE_HFP_HANDSFREE) &&
-	    !cras_bt_device_is_profile_connected(
-		    device, CRAS_BT_DEVICE_PROFILE_HFP_HANDSFREE)) {
-		if (0 == device->conn_watch_retries % PROFILE_CONN_RETRIES)
+		if (a2dp_connected && !hfp_connected)
 			cras_bt_device_connect_profile(device->conn, device,
 						       HFP_HF_UUID);
-		goto arm_retry_timer;
 	}
 
+	if (a2dp_supported != a2dp_connected || hfp_supported != hfp_connected)
+		goto arm_retry_timer;
+
 	/* Expected profiles are all connected, no more connection watch
 	 * callback will be scheduled.
 	 * Base on the decision that we expose only the latest connected
@@ -593,9 +675,11 @@
 		if (rc) {
 			syslog(LOG_ERR, "Start audio gateway failed, rc %d",
 			       rc);
-			bt_device_schedule_suspend(device, 0);
+			bt_device_schedule_suspend(device, 0,
+						   HFP_AG_START_FAILURE);
 		}
 	}
+	bt_device_set_nodes_plugged(device, 1);
 	return;
 
 arm_retry_timer:
@@ -609,7 +693,7 @@
 					     bt_device_conn_watch_cb, device);
 	} else {
 		syslog(LOG_ERR, "Connection watch timeout.");
-		bt_device_schedule_suspend(device, 0);
+		bt_device_schedule_suspend(device, 0, CONN_WATCH_TIME_OUT);
 	}
 }
 
@@ -626,16 +710,24 @@
 		tm, CONN_WATCH_PERIOD_MS, bt_device_conn_watch_cb, device);
 }
 
+static void bt_device_cancel_suspend(struct cras_bt_device *device);
+
 void cras_bt_device_set_connected(struct cras_bt_device *device, int value)
 {
 	struct cras_tm *tm = cras_system_state_get_tm();
-	if (device->connected || value)
-		BTLOG(btlog, BT_DEV_CONNECTED_CHANGE, device->profiles, value);
+	if (!device->connected && value) {
+		BTLOG(btlog, BT_DEV_CONNECTED, device->profiles,
+		      device->stable_id);
+	}
 
 	if (device->connected && !value) {
+		BTLOG(btlog, BT_DEV_DISCONNECTED, device->profiles,
+		      device->stable_id);
 		cras_bt_profile_on_device_disconnected(device);
-		/* Device is disconnected, resets connected profiles. */
+		/* Device is disconnected, resets connected profiles and the
+		 * suspend timer which scheduled earlier. */
 		device->connected_profiles = 0;
+		bt_device_cancel_suspend(device);
 	}
 
 	device->connected = value;
@@ -649,47 +741,48 @@
 void cras_bt_device_notify_profile_dropped(struct cras_bt_device *device,
 					   enum cras_bt_device_profile profile)
 {
-	device->connected_profiles &= !profile;
+	device->connected_profiles &= ~profile;
+
+	/* Do nothing if device already disconnected. */
+	if (!device->connected)
+		return;
 
 	/* If any profile, a2dp or hfp/hsp, has dropped for some reason,
 	 * we shall make sure this device is fully disconnected within
 	 * given time so that user does not see a headset stay connected
 	 * but works with partial function.
 	 */
-	bt_device_schedule_suspend(device, PROFILE_DROP_SUSPEND_DELAY_MS);
+	bt_device_schedule_suspend(device, PROFILE_DROP_SUSPEND_DELAY_MS,
+				   UNEXPECTED_PROFILE_DROP);
 }
 
-/*
- * Check if the uuid is of a new audio profile that isn't listed
- * as supported by device.
+/* Refresh the list of known supported profiles.
  * Args:
- *    device - The BT device holding supported profiles bitmap.
- *    uuid - UUID string from the device properties notified by BlueZ.
+ *    device - The BT device holding scanned profiles bitmap.
+ *    profiles - The OR'ed profiles the device claims to support as is notified
+ *               by BlueZ.
  * Returns:
- *    True if uuid is a new audio profiles not already supported by device.
+ *    The OR'ed profiles that are both supported by Cras and isn't previously
+ *    supported by the device.
  */
-int cras_bt_device_add_supported_profiles(struct cras_bt_device *device,
-					  const char *uuid)
+int cras_bt_device_set_supported_profiles(struct cras_bt_device *device,
+					  unsigned int profiles)
 {
-	enum cras_bt_device_profile profile =
-		cras_bt_device_profile_from_uuid(uuid);
-
-	if (profile == 0)
+	/* Do nothing if no new profiles. */
+	if ((device->profiles & profiles) == profiles)
 		return 0;
 
-	/* Do nothing if this profile is not new. */
-	if (device->profiles & profile)
-		return 0;
+	unsigned int new_profiles = profiles & ~device->profiles;
 
 	/* Log this event as we might need to re-intialize the BT audio nodes
 	 * if new audio profile is reported for already connected device. */
-	if (device->connected && (profile & CRAS_SUPPORTED_PROFILES))
+	if (device->connected && (new_profiles & CRAS_SUPPORTED_PROFILES))
 		BTLOG(btlog, BT_NEW_AUDIO_PROFILE_AFTER_CONNECT,
-		      device->profiles, profile);
-	device->profiles |= profile;
-	cras_bt_device_log_profile(device, profile);
+		      device->profiles, new_profiles);
+	cras_bt_device_log_profiles(device, new_profiles);
+	device->profiles = profiles | device->hidden_profiles;
 
-	return (profile & CRAS_SUPPORTED_PROFILES);
+	return (new_profiles & CRAS_SUPPORTED_PROFILES);
 }
 
 void cras_bt_device_update_properties(struct cras_bt_device *device,
@@ -757,6 +850,7 @@
 				  "as") == 0 &&
 			   strcmp(key, "UUIDs") == 0) {
 			DBusMessageIter uuid_array_iter;
+			unsigned int profiles = 0;
 
 			dbus_message_iter_recurse(&variant_iter,
 						  &uuid_array_iter);
@@ -766,22 +860,21 @@
 
 				dbus_message_iter_get_basic(&uuid_array_iter,
 							    &uuid);
-
-				/*
-				 * If updated properties includes new audio
-				 * profile, and device is connected, we need
-				 * to start connection watcher. This is needed
-				 * because on some bluetooth device, supported
-				 * profiles do not present when device
-				 * interface is added and they are updated
-				 * later.
-				 */
-				if (cras_bt_device_add_supported_profiles(
-					    device, uuid))
-					watch_needed = device->connected;
+				profiles |=
+					cras_bt_device_profile_from_uuid(uuid);
 
 				dbus_message_iter_next(&uuid_array_iter);
 			}
+
+			/* If updated properties includes new audio profile and
+			 * device is connected, we need to start connection
+			 * watcher. This is needed because on some bluetooth
+			 * devices, supported profiles do not present when
+			 * device interface is added and they are updated later.
+			 */
+			if (cras_bt_device_set_supported_profiles(device,
+								  profiles))
+				watch_needed = device->connected;
 		}
 
 		dbus_message_iter_next(properties_array_iter);
@@ -812,7 +905,7 @@
 		} else if (strcmp(key, "Connected") == 0) {
 			device->connected = 0;
 		} else if (strcmp(key, "UUIDs") == 0) {
-			device->profiles = 0;
+			device->profiles = device->hidden_profiles;
 		}
 
 		dbus_message_iter_next(invalidated_array_iter);
@@ -852,6 +945,7 @@
 static int apply_codec_settings(int fd, uint8_t codec)
 {
 	struct bt_voice voice;
+	uint32_t pkt_status;
 
 	memset(&voice, 0, sizeof(voice));
 	if (codec == HFP_CODEC_ID_CVSD)
@@ -869,6 +963,12 @@
 		syslog(LOG_ERR, "Failed to apply voice setting");
 		return -1;
 	}
+
+	pkt_status = 1;
+	if (setsockopt(fd, SOL_BLUETOOTH, BT_PKT_STATUS, &pkt_status,
+		       sizeof(pkt_status))) {
+		syslog(LOG_ERR, "Failed to enable BT_PKT_STATUS");
+	}
 	return 0;
 }
 
@@ -878,7 +978,7 @@
 	struct sockaddr addr;
 	struct cras_bt_adapter *adapter;
 	struct timespec timeout = { 1, 0 };
-	struct pollfd *pollfds;
+	struct pollfd pollfd;
 
 	adapter = cras_bt_device_adapter(device);
 	if (!adapter) {
@@ -892,6 +992,8 @@
 	if (sk < 0) {
 		syslog(LOG_ERR, "Failed to create socket: %s (%d)",
 		       strerror(errno), errno);
+		cras_server_metrics_hfp_sco_connection_error(
+			CRAS_METRICS_SCO_SKT_OPEN_ERROR);
 		return -errno;
 	}
 
@@ -906,9 +1008,6 @@
 
 	/* Connect to remote in nonblocking mode */
 	fcntl(sk, F_SETFL, O_NONBLOCK);
-	pollfds = (struct pollfd *)malloc(sizeof(*pollfds));
-	pollfds[0].fd = sk;
-	pollfds[0].events = POLLOUT;
 
 	if (bt_address(cras_bt_device_address(device), &addr))
 		goto error;
@@ -921,22 +1020,35 @@
 	if (err && errno != EINPROGRESS) {
 		syslog(LOG_ERR, "Failed to connect: %s (%d)", strerror(errno),
 		       errno);
+		cras_server_metrics_hfp_sco_connection_error(
+			CRAS_METRICS_SCO_SKT_CONNECT_ERROR);
 		goto error;
 	}
 
-	err = ppoll(pollfds, 1, &timeout, NULL);
+	pollfd.fd = sk;
+	pollfd.events = POLLOUT;
+
+	err = ppoll(&pollfd, 1, &timeout, NULL);
 	if (err <= 0) {
 		syslog(LOG_ERR, "Connect SCO: poll for writable timeout");
+		cras_server_metrics_hfp_sco_connection_error(
+			CRAS_METRICS_SCO_SKT_POLL_TIMEOUT);
 		goto error;
 	}
 
-	if (pollfds[0].revents & (POLLERR | POLLHUP)) {
-		syslog(LOG_ERR, "SCO socket error, revents: %u",
-		       pollfds[0].revents);
-		bt_device_schedule_suspend(device, 0);
+	if (pollfd.revents & (POLLERR | POLLHUP)) {
+		syslog(LOG_ERR,
+		       "SCO socket error, revents: %u. Suspend in %u seconds",
+		       pollfd.revents, SCO_SUSPEND_DELAY_MS);
+		cras_server_metrics_hfp_sco_connection_error(
+			CRAS_METRICS_SCO_SKT_POLL_ERR_HUP);
+		bt_device_schedule_suspend(device, SCO_SUSPEND_DELAY_MS,
+					   HFP_SCO_SOCKET_ERROR);
 		goto error;
 	}
 
+	cras_server_metrics_hfp_sco_connection_error(
+		CRAS_METRICS_SCO_SKT_SUCCESS);
 	BTLOG(btlog, BT_SCO_CONNECT, 1, sk);
 	return sk;
 
@@ -953,12 +1065,23 @@
 	struct sco_options so;
 	socklen_t len = sizeof(so);
 	struct cras_bt_adapter *adapter;
+	uint32_t wbs_pkt_len = 0;
+	socklen_t optlen = sizeof(wbs_pkt_len);
 
 	adapter = cras_bt_adapter_get(device->adapter_obj_path);
 
 	if (cras_bt_adapter_on_usb(adapter)) {
-		return (codec == HFP_CODEC_ID_MSBC) ? USB_MSBC_PKT_SIZE :
-						      USB_CVSD_PKT_SIZE;
+		if (codec == HFP_CODEC_ID_MSBC) {
+			/* BT_SNDMTU and BT_RCVMTU return the same value. */
+			if (getsockopt(sco_socket, SOL_BLUETOOTH, BT_SNDMTU,
+				       &wbs_pkt_len, &optlen))
+				syslog(LOG_ERR, "Failed to get BT_SNDMTU");
+
+			return (wbs_pkt_len > 0) ? wbs_pkt_len :
+						   USB_MSBC_PKT_SIZE;
+		} else {
+			return USB_CVSD_PKT_SIZE;
+		}
 	}
 
 	/* For non-USB cases, query the SCO MTU from driver. */
@@ -988,7 +1111,8 @@
 static void init_bt_device_msg(struct bt_device_msg *msg,
 			       enum BT_DEVICE_COMMAND cmd,
 			       struct cras_bt_device *device,
-			       struct cras_iodev *dev, unsigned int arg)
+			       struct cras_iodev *dev, unsigned int arg1,
+			       unsigned int arg2)
 {
 	memset(msg, 0, sizeof(*msg));
 	msg->header.type = CRAS_MAIN_BT;
@@ -996,7 +1120,8 @@
 	msg->cmd = cmd;
 	msg->device = device;
 	msg->dev = dev;
-	msg->arg = arg;
+	msg->arg1 = arg1;
+	msg->arg2 = arg2;
 }
 
 int cras_bt_device_cancel_suspend(struct cras_bt_device *device)
@@ -1004,19 +1129,20 @@
 	struct bt_device_msg msg;
 	int rc;
 
-	init_bt_device_msg(&msg, BT_DEVICE_CANCEL_SUSPEND, device, NULL, 0);
+	init_bt_device_msg(&msg, BT_DEVICE_CANCEL_SUSPEND, device, NULL, 0, 0);
 	rc = cras_main_message_send((struct cras_main_message *)&msg);
 	return rc;
 }
 
-int cras_bt_device_schedule_suspend(struct cras_bt_device *device,
-				    unsigned int msec)
+int cras_bt_device_schedule_suspend(
+	struct cras_bt_device *device, unsigned int msec,
+	enum cras_bt_device_suspend_reason suspend_reason)
 {
 	struct bt_device_msg msg;
 	int rc;
 
-	init_bt_device_msg(&msg, BT_DEVICE_SCHEDULE_SUSPEND, device, NULL,
-			   msec);
+	init_bt_device_msg(&msg, BT_DEVICE_SCHEDULE_SUSPEND, device, NULL, msec,
+			   suspend_reason);
 	rc = cras_main_message_send((struct cras_main_message *)&msg);
 	return rc;
 }
@@ -1050,7 +1176,7 @@
 	int rc;
 
 	init_bt_device_msg(&msg, BT_DEVICE_SWITCH_PROFILE_ENABLE_DEV, device,
-			   bt_iodev, 0);
+			   bt_iodev, 0, 0);
 	rc = cras_main_message_send((struct cras_main_message *)&msg);
 	return rc;
 }
@@ -1061,7 +1187,8 @@
 	struct bt_device_msg msg;
 	int rc;
 
-	init_bt_device_msg(&msg, BT_DEVICE_SWITCH_PROFILE, device, bt_iodev, 0);
+	init_bt_device_msg(&msg, BT_DEVICE_SWITCH_PROFILE, device, bt_iodev, 0,
+			   0);
 	rc = cras_main_message_send((struct cras_main_message *)&msg);
 	return rc;
 }
@@ -1150,21 +1277,45 @@
 	struct cras_bt_device *device = (struct cras_bt_device *)arg;
 
 	BTLOG(btlog, BT_DEV_SUSPEND_CB, device->profiles,
-	      device->connected_profiles);
+	      device->suspend_reason);
 	device->suspend_timer = NULL;
 
+	/* Error log the reason so we can track them in user reports. */
+	switch (device->suspend_reason) {
+	case A2DP_LONG_TX_FAILURE:
+		syslog(LOG_ERR, "Suspend dev: A2DP long Tx failure");
+		break;
+	case A2DP_TX_FATAL_ERROR:
+		syslog(LOG_ERR, "Suspend dev: A2DP Tx fatal error");
+		break;
+	case CONN_WATCH_TIME_OUT:
+		syslog(LOG_ERR, "Suspend dev: Conn watch times out");
+		break;
+	case HFP_SCO_SOCKET_ERROR:
+		syslog(LOG_ERR, "Suspend dev: SCO socket error");
+		break;
+	case HFP_AG_START_FAILURE:
+		syslog(LOG_ERR, "Suspend dev: HFP AG start failure");
+		break;
+	case UNEXPECTED_PROFILE_DROP:
+		syslog(LOG_ERR, "Suspend dev: Unexpected profile drop");
+		break;
+	}
+
 	cras_a2dp_suspend_connected_device(device);
 	cras_hfp_ag_suspend_connected_device(device);
 	cras_bt_device_disconnect(device->conn, device);
 }
 
-static void bt_device_schedule_suspend(struct cras_bt_device *device,
-				       unsigned int msec)
+static void
+bt_device_schedule_suspend(struct cras_bt_device *device, unsigned int msec,
+			   enum cras_bt_device_suspend_reason suspend_reason)
 {
 	struct cras_tm *tm = cras_system_state_get_tm();
 
 	if (device->suspend_timer)
 		return;
+	device->suspend_reason = suspend_reason;
 	device->suspend_timer =
 		cras_tm_create_timer(tm, msec, bt_device_suspend_cb, device);
 }
@@ -1200,7 +1351,8 @@
 		bt_device_switch_profile(bt_msg->device, bt_msg->dev, 1);
 		break;
 	case BT_DEVICE_SCHEDULE_SUSPEND:
-		bt_device_schedule_suspend(bt_msg->device, bt_msg->arg);
+		bt_device_schedule_suspend(bt_msg->device, bt_msg->arg1,
+					   bt_msg->arg2);
 		break;
 	case BT_DEVICE_CANCEL_SUSPEND:
 		bt_device_cancel_suspend(bt_msg->device);
diff --git a/cras/src/server/cras_bt_device.h b/cras/src/server/cras_bt_device.h
index 904a5f4..9d3a2b9 100644
--- a/cras/src/server/cras_bt_device.h
+++ b/cras/src/server/cras_bt_device.h
@@ -13,6 +13,16 @@
 struct cras_iodev;
 struct cras_timer;
 
+/* All the reasons for when CRAS schedule a suspend to BT device. */
+enum cras_bt_device_suspend_reason {
+	A2DP_LONG_TX_FAILURE,
+	A2DP_TX_FATAL_ERROR,
+	CONN_WATCH_TIME_OUT,
+	HFP_SCO_SOCKET_ERROR,
+	HFP_AG_START_FAILURE,
+	UNEXPECTED_PROFILE_DROP,
+};
+
 enum cras_bt_device_profile {
 	CRAS_BT_DEVICE_PROFILE_A2DP_SOURCE = (1 << 0),
 	CRAS_BT_DEVICE_PROFILE_A2DP_SINK = (1 << 1),
@@ -40,6 +50,10 @@
 struct cras_bt_device *cras_bt_device_get(const char *object_path);
 
 const char *cras_bt_device_object_path(const struct cras_bt_device *device);
+
+/* Gets the stable id of given cras_bt_device. */
+int cras_bt_device_get_stable_id(const struct cras_bt_device *device);
+
 struct cras_bt_adapter *
 cras_bt_device_adapter(const struct cras_bt_device *device);
 const char *cras_bt_device_address(const struct cras_bt_device *device);
@@ -53,8 +67,8 @@
 				      DBusMessageIter *invalidated_array_iter);
 
 /* Updates the supported profiles on dev. Expose for unit test. */
-int cras_bt_device_add_supported_profiles(struct cras_bt_device *device,
-					  const char *uuid);
+int cras_bt_device_set_supported_profiles(struct cras_bt_device *device,
+					  unsigned int profiles);
 
 /* Checks if profile is claimed supported by the device. */
 int cras_bt_device_supports_profile(const struct cras_bt_device *device,
@@ -123,7 +137,8 @@
 			     struct cras_iodev *iodev);
 
 /* Gets the active profile of the bt device. */
-int cras_bt_device_get_active_profile(const struct cras_bt_device *device);
+unsigned int
+cras_bt_device_get_active_profile(const struct cras_bt_device *device);
 
 /* Sets the active profile of the bt device. */
 void cras_bt_device_set_active_profile(struct cras_bt_device *device,
@@ -170,8 +185,9 @@
 int cras_bt_device_cancel_suspend(struct cras_bt_device *device);
 
 /* Schedules device to suspend after given delay. */
-int cras_bt_device_schedule_suspend(struct cras_bt_device *device,
-				    unsigned int msec);
+int cras_bt_device_schedule_suspend(
+	struct cras_bt_device *device, unsigned int msec,
+	enum cras_bt_device_suspend_reason suspend_reason);
 
 /* Notifies bt device that audio gateway is initialized.
  * Args:
diff --git a/cras/src/server/cras_bt_io.c b/cras/src/server/cras_bt_io.c
index 637f0a7..acdca80 100644
--- a/cras/src/server/cras_bt_io.c
+++ b/cras/src/server/cras_bt_io.c
@@ -8,6 +8,7 @@
 
 #include "cras_bt_io.h"
 #include "cras_bt_device.h"
+#include "cras_hfp_iodev.h"
 #include "cras_utf8.h"
 #include "cras_iodev.h"
 #include "cras_iodev_list.h"
@@ -69,7 +70,7 @@
 	n->base.type = CRAS_NODE_TYPE_BLUETOOTH;
 	n->base.volume = 100;
 	n->base.stable_id = dev->info.stable_id;
-	n->base.max_software_gain = 0;
+	n->base.capture_gain = 0;
 	gettimeofday(&n->base.plugged_time, NULL);
 
 	strcpy(n->base.name, dev->info.name);
@@ -105,6 +106,16 @@
 	}
 }
 
+/* Switches the active profile to A2DP if it can. */
+static void bt_possibly_switch_to_a2dp(struct bt_io *btio)
+{
+	if (!cras_bt_device_has_a2dp(btio->device))
+		return;
+	cras_bt_device_set_active_profile(btio->device,
+					  CRAS_BT_DEVICE_PROFILE_A2DP_SOURCE);
+	cras_bt_device_switch_profile(btio->device, &btio->base);
+}
+
 /* Checks if bt device is active for the given profile.
  */
 static int device_using_profile(struct cras_bt_device *device,
@@ -125,6 +136,7 @@
 {
 	struct bt_io *btio = (struct bt_io *)iodev;
 	struct cras_iodev *dev = active_profile_dev(iodev);
+	int rc;
 
 	/* Force to use HFP if opening input dev. */
 	if (device_using_profile(btio->device,
@@ -136,8 +148,16 @@
 		return -EAGAIN;
 	}
 
-	if (dev && dev->open_dev)
-		return dev->open_dev(dev);
+	if (dev && dev->open_dev) {
+		rc = dev->open_dev(dev);
+		if (rc == 0)
+			return 0;
+
+		/* If input iodev open fails, switch profile back to A2DP. */
+		if (iodev->direction == CRAS_STREAM_INPUT)
+			bt_possibly_switch_to_a2dp(btio);
+		return rc;
+	}
 
 	return 0;
 }
@@ -150,12 +170,6 @@
 	if (!dev)
 		return -EINVAL;
 
-	if (dev->format == NULL) {
-		dev->format = (struct cras_audio_format *)malloc(
-			sizeof(*dev->format));
-		*dev->format = *iodev->format;
-	}
-
 	if (dev->update_supported_formats) {
 		rc = dev->update_supported_formats(dev);
 		if (rc)
@@ -186,6 +200,9 @@
 		(length + 1) * sizeof(*iodev->supported_formats));
 	for (i = 0; i < length + 1; i++)
 		iodev->supported_formats[i] = dev->supported_formats[i];
+
+	/* Record max supported channels into cras_iodev_info. */
+	iodev->info.max_supported_channels = dev->info.max_supported_channels;
 	return 0;
 }
 
@@ -197,7 +214,13 @@
 		return -EINVAL;
 
 	/* Fill back the format iodev is using. */
-	*dev->format = *iodev->format;
+	if (dev->format == NULL) {
+		dev->format = (struct cras_audio_format *)malloc(
+			sizeof(*dev->format));
+		if (!dev->format)
+			return -ENOMEM;
+		*dev->format = *iodev->format;
+	}
 
 	rc = dev->configure_dev(dev);
 	if (rc)
@@ -205,6 +228,11 @@
 
 	iodev->buffer_size = dev->buffer_size;
 	iodev->min_buffer_level = dev->min_buffer_level;
+	if (dev->start)
+		dev->state = CRAS_IODEV_STATE_OPEN;
+	else
+		dev->state = CRAS_IODEV_STATE_NO_STREAM_RUN;
+
 	return 0;
 }
 
@@ -216,22 +244,21 @@
 	if (!dev)
 		return -EINVAL;
 
-	/* Force back to A2DP if closing HFP. */
-	if (device_using_profile(
+	/* If input iodev is in open state and being closed, switch profile
+	 * from HFP to A2DP. */
+	if (cras_iodev_is_open(iodev) &&
+	    device_using_profile(
 		    btio->device,
 		    CRAS_BT_DEVICE_PROFILE_HSP_AUDIOGATEWAY |
 			    CRAS_BT_DEVICE_PROFILE_HFP_AUDIOGATEWAY) &&
-	    iodev->direction == CRAS_STREAM_INPUT &&
-	    cras_bt_device_has_a2dp(btio->device)) {
-		cras_bt_device_set_active_profile(
-			btio->device, CRAS_BT_DEVICE_PROFILE_A2DP_SOURCE);
-		cras_bt_device_switch_profile(btio->device, iodev);
-	}
+	    (iodev->direction == CRAS_STREAM_INPUT))
+		bt_possibly_switch_to_a2dp(btio);
 
 	rc = dev->close_dev(dev);
 	if (rc < 0)
 		return rc;
 	cras_iodev_free_format(iodev);
+	dev->state = CRAS_IODEV_STATE_CLOSE;
 	return 0;
 }
 
@@ -304,6 +331,7 @@
 	struct cras_ionode *node;
 	struct bt_node *active = (struct bt_node *)iodev->active_node;
 	struct cras_iodev *dev;
+	int rc;
 
 	if (device_using_profile(btio->device, active->profile))
 		goto leave;
@@ -327,11 +355,38 @@
 	dev = active_profile_dev(iodev);
 	if (dev && dev->update_active_node)
 		dev->update_active_node(dev, node_idx, dev_enabled);
+
+	/* Update supported formats here to get the supported formats from the
+	 * new updated active profile dev.
+	 */
+	rc = update_supported_formats(iodev);
+	if (rc) {
+		syslog(LOG_ERR, "Failed to update supported formats, rc=%d",
+		       rc);
+	}
+}
+
+static int output_underrun(struct cras_iodev *iodev)
+{
+	struct cras_iodev *dev = active_profile_dev(iodev);
+	if (!dev)
+		return -EINVAL;
+
+	if (dev->output_underrun) {
+		dev->min_cb_level = iodev->min_cb_level;
+		dev->max_cb_level = iodev->max_cb_level;
+		dev->buffer_size = iodev->buffer_size;
+		return dev->output_underrun(dev);
+	}
+
+	return 0;
 }
 
 static int no_stream(struct cras_iodev *iodev, int enable)
 {
 	struct cras_iodev *dev = active_profile_dev(iodev);
+	int rc;
+
 	if (!dev)
 		return -EINVAL;
 
@@ -346,8 +401,15 @@
 		dev->min_cb_level = iodev->min_cb_level;
 		dev->max_cb_level = iodev->max_cb_level;
 		dev->buffer_size = iodev->buffer_size;
-		return dev->no_stream(dev, enable);
+		rc = dev->no_stream(dev, enable);
+		if (rc < 0)
+			return rc;
 	}
+	if (enable)
+		dev->state = CRAS_IODEV_STATE_NO_STREAM_RUN;
+	else
+		dev->state = CRAS_IODEV_STATE_NORMAL_RUN;
+
 	return 0;
 }
 
@@ -366,14 +428,45 @@
 static int start(const struct cras_iodev *iodev)
 {
 	struct cras_iodev *dev = active_profile_dev(iodev);
+	int rc;
+
 	if (!dev)
 		return -EINVAL;
 
-	if (dev->start)
-		return dev->start(dev);
+	if (dev->start) {
+		rc = dev->start(dev);
+		if (rc)
+			return rc;
+	}
+	dev->state = CRAS_IODEV_STATE_NORMAL_RUN;
 	return 0;
 }
 
+static unsigned int frames_to_play_in_sleep(struct cras_iodev *iodev,
+					    unsigned int *hw_level,
+					    struct timespec *hw_tstamp)
+{
+	struct cras_iodev *dev = active_profile_dev(iodev);
+	if (!dev || !dev->frames_to_play_in_sleep)
+		return cras_iodev_default_frames_to_play_in_sleep(
+			iodev, hw_level, hw_tstamp);
+
+	return dev->frames_to_play_in_sleep(dev, hw_level, hw_tstamp);
+}
+
+static int get_valid_frames(struct cras_iodev *iodev,
+			    struct timespec *hw_tstamp)
+{
+	struct cras_iodev *dev = active_profile_dev(iodev);
+	if (!dev)
+		return -EINVAL;
+
+	if (dev->get_valid_frames)
+		return dev->get_valid_frames(dev, hw_tstamp);
+
+	return cras_iodev_frames_queued(iodev, hw_tstamp);
+}
+
 struct cras_iodev *cras_bt_io_create(struct cras_bt_device *device,
 				     struct cras_iodev *dev,
 				     enum cras_bt_device_profile profile)
@@ -408,8 +501,11 @@
 	iodev->update_supported_formats = update_supported_formats;
 	iodev->update_active_node = update_active_node;
 	iodev->no_stream = no_stream;
+	iodev->output_underrun = output_underrun;
 	iodev->is_free_running = is_free_running;
+	iodev->get_valid_frames = get_valid_frames;
 	iodev->start = start;
+	iodev->frames_to_play_in_sleep = frames_to_play_in_sleep;
 
 	/* Input also checks |software_volume_needed| flag for using software
 	 * gain. Keep it as false for BT input.
@@ -422,23 +518,30 @@
 		iodev->set_volume = set_bt_volume;
 	}
 
-	/* Create the dummy node set to plugged so it's the only node exposed
-	 * to UI, and point it to the first profile dev. */
+	/* Create the fake node so it's the only node exposed to UI, and
+	 * point it to the first profile dev. */
 	active = (struct bt_node *)calloc(1, sizeof(*active));
 	if (!active)
-		return NULL;
+		goto error;
 	active->base.dev = iodev;
 	active->base.idx = btio->next_node_id++;
-	active->base.type = CRAS_NODE_TYPE_BLUETOOTH;
+	active->base.type = dev->active_node->type;
 	active->base.volume = 100;
-	active->base.plugged = 1;
-	active->base.stable_id =
-		SuperFastHash(cras_bt_device_object_path(device),
-			      strlen(cras_bt_device_object_path(device)),
-			      strlen(cras_bt_device_object_path(device)));
+	active->base.stable_id = cras_bt_device_get_stable_id(device);
+	active->base.ui_gain_scaler = 1.0f;
+	/*
+	 * If the same headset is connected in wideband mode, we shall assign
+	 * a separate stable_id so the node priority/preference mechanism in
+	 * Chrome UI doesn't break.
+	 */
+	if ((active->base.type == CRAS_NODE_TYPE_BLUETOOTH) &&
+	    (dev->direction == CRAS_STREAM_INPUT))
+		active->base.stable_id =
+			SuperFastHash((const char *)&active->base.type,
+				      sizeof(active->base.type),
+				      active->base.stable_id);
 	active->profile = profile;
 	active->profile_dev = dev;
-	gettimeofday(&active->base.plugged_time, NULL);
 	strcpy(active->base.name, dev->info.name);
 	/* The node name exposed to UI should be a valid UTF8 string. */
 	if (!is_utf8_string(active->base.name))
@@ -547,6 +650,20 @@
 	return !!(profile & btnode->profile);
 }
 
+enum cras_bt_device_profile
+cras_bt_io_profile_to_log(struct cras_iodev *bt_iodev)
+{
+	struct bt_node *btnode = (struct bt_node *)bt_iodev->active_node;
+
+	if (btnode->profile & CRAS_BT_DEVICE_PROFILE_A2DP_SOURCE)
+		return CRAS_BT_DEVICE_PROFILE_A2DP_SOURCE;
+
+	if (hfp_iodev_is_hsp(btnode->profile_dev))
+		return CRAS_BT_DEVICE_PROFILE_HSP_AUDIOGATEWAY;
+	else
+		return CRAS_BT_DEVICE_PROFILE_HFP_AUDIOGATEWAY;
+}
+
 unsigned int cras_bt_io_try_remove(struct cras_iodev *bt_iodev,
 				   struct cras_iodev *dev)
 {
diff --git a/cras/src/server/cras_bt_io.h b/cras/src/server/cras_bt_io.h
index b833cd9..a867afd 100644
--- a/cras/src/server/cras_bt_io.h
+++ b/cras/src/server/cras_bt_io.h
@@ -30,6 +30,12 @@
 int cras_bt_io_on_profile(struct cras_iodev *bt_iodev,
 			  enum cras_bt_device_profile profile);
 
+/* Returns A2DP, HFP or HSP that this bt_iodev is running for.
+ * Do NOT use this function except for logging.
+ */
+enum cras_bt_device_profile
+cras_bt_io_profile_to_log(struct cras_iodev *bt_iodev);
+
 /* Dry-run the profile device removal from bt_iodev.
  * Returns:
  *    0 if the bt_iodev will be empty and should to be destroied
diff --git a/cras/src/server/cras_bt_manager.c b/cras/src/server/cras_bt_manager.c
index 3a3ea4a..a710340 100644
--- a/cras/src/server/cras_bt_manager.c
+++ b/cras/src/server/cras_bt_manager.c
@@ -19,6 +19,7 @@
 #include "cras_bt_player.h"
 #include "cras_bt_profile.h"
 #include "cras_bt_transport.h"
+#include "cras_bt_battery_provider.h"
 #include "utlist.h"
 
 struct cras_bt_event_log *btlog;
@@ -41,9 +42,6 @@
 			if (adapter) {
 				cras_bt_adapter_update_properties(
 					adapter, properties_array_iter, NULL);
-				cras_bt_register_endpoints(conn, adapter);
-				cras_bt_register_player(conn, adapter);
-				cras_bt_register_profiles(conn);
 
 				syslog(LOG_INFO, "Bluetooth Adapter: %s added",
 				       cras_bt_adapter_address(adapter));
@@ -54,6 +52,28 @@
 			}
 		}
 
+	} else if (strcmp(interface_name, BLUEZ_INTERFACE_MEDIA) == 0) {
+		struct cras_bt_adapter *adapter;
+
+		adapter = cras_bt_adapter_get(object_path);
+		if (adapter) {
+			cras_bt_register_endpoints(conn, adapter);
+			cras_bt_register_player(conn, adapter);
+
+			syslog(LOG_INFO,
+			       "Bluetooth Endpoint and/or Player: %s added",
+			       cras_bt_adapter_address(adapter));
+		} else {
+			syslog(LOG_WARNING,
+			       "Failed to create Bluetooth Endpoint and/or Player: %s",
+			       object_path);
+		}
+
+	} else if (strcmp(interface_name, BLUEZ_PROFILE_MGMT_INTERFACE) == 0) {
+		cras_bt_register_profiles(conn);
+
+		syslog(LOG_INFO, "Bluetooth Profile Manager added");
+
 	} else if (strcmp(interface_name, BLUEZ_INTERFACE_DEVICE) == 0) {
 		struct cras_bt_device *device;
 
@@ -101,6 +121,32 @@
 				       object_path);
 			}
 		}
+	} else if (strcmp(interface_name,
+			  BLUEZ_INTERFACE_BATTERY_PROVIDER_MANAGER) == 0) {
+		struct cras_bt_adapter *adapter;
+		int ret;
+
+		syslog(LOG_INFO,
+		       "Bluetooth Battery Provider Manager available");
+
+		adapter = cras_bt_adapter_get(object_path);
+		if (adapter) {
+			syslog(LOG_INFO,
+			       "Registering Battery Provider for adapter %s",
+			       cras_bt_adapter_address(adapter));
+			ret = cras_bt_register_battery_provider(conn, adapter);
+			if (ret != 0) {
+				syslog(LOG_ERR,
+				       "Error registering Battery Provider "
+				       "for adapter %s: %s",
+				       cras_bt_adapter_address(adapter),
+				       strerror(-ret));
+			}
+		} else {
+			syslog(LOG_WARNING,
+			       "Adapter not available when trying to create "
+			       "Battery Provider");
+		}
 	}
 }
 
@@ -139,6 +185,10 @@
 			       cras_bt_transport_object_path(transport));
 			cras_bt_transport_remove(transport);
 		}
+	} else if (strcmp(interface_name,
+			  BLUEZ_INTERFACE_BATTERY_PROVIDER_MANAGER) == 0) {
+		syslog(LOG_INFO, "Bluetooth Battery Provider Manager removed");
+		cras_bt_battery_provider_reset();
 	}
 }
 
diff --git a/cras/src/server/cras_bt_player.c b/cras/src/server/cras_bt_player.c
index 13343a6..446cd91 100644
--- a/cras/src/server/cras_bt_player.c
+++ b/cras/src/server/cras_bt_player.c
@@ -4,17 +4,18 @@
  */
 #include <dbus/dbus.h>
 #include <errno.h>
+#include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
 #include <syslog.h>
 
-#include "cras_bt_constants.h"
 #include "cras_bt_adapter.h"
+#include "cras_bt_constants.h"
 #include "cras_bt_player.h"
 #include "cras_dbus_util.h"
+#include "cras_utf8.h"
 #include "utlist.h"
 
-#define CRAS_DEFAULT_PLAYER "/org/chromium/Cras/Bluetooth/DefaultPlayer"
-
 static void cras_bt_on_player_registered(DBusPendingCall *pending_call,
 					 void *data)
 {
@@ -109,10 +110,11 @@
  */
 static struct cras_bt_player player = {
 	.object_path = CRAS_DEFAULT_PLAYER,
-	.playback_status = "playing",
-	.identity = "DefaultPlayer",
+	.playback_status = NULL,
+	.identity = NULL,
 	.loop_status = "None",
 	.shuffle = 0,
+	.metadata = NULL,
 	.position = 0,
 	.can_go_next = 0,
 	.can_go_prev = 0,
@@ -134,6 +136,123 @@
 	return DBUS_HANDLER_RESULT_HANDLED;
 }
 
+static struct cras_bt_player_metadata *cras_bt_player_metadata_init()
+{
+	struct cras_bt_player_metadata *metadata =
+		malloc(sizeof(struct cras_bt_player_metadata));
+	metadata->title = calloc(1, CRAS_PLAYER_METADATA_SIZE_MAX);
+	metadata->album = calloc(1, CRAS_PLAYER_METADATA_SIZE_MAX);
+	metadata->artist = calloc(1, CRAS_PLAYER_METADATA_SIZE_MAX);
+	metadata->length = 0;
+
+	return metadata;
+}
+
+static void cras_bt_player_init()
+{
+	player.playback_status = malloc(CRAS_PLAYER_PLAYBACK_STATUS_SIZE_MAX);
+	player.identity = malloc(CRAS_PLAYER_IDENTITY_SIZE_MAX);
+
+	strcpy(player.playback_status, CRAS_PLAYER_PLAYBACK_STATUS_DEFAULT);
+	strcpy(player.identity, CRAS_PLAYER_IDENTITY_DEFAULT);
+	player.position = 0;
+
+	player.metadata = cras_bt_player_metadata_init();
+}
+
+static void cras_bt_player_append_metadata_artist(DBusMessageIter *iter,
+						  const char *artist)
+{
+	DBusMessageIter dict, varient, array;
+	const char *artist_key = "xesam:artist";
+
+	dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL,
+					 &dict);
+	dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &artist_key);
+	dbus_message_iter_open_container(
+		&dict, DBUS_TYPE_VARIANT,
+		DBUS_TYPE_ARRAY_AS_STRING DBUS_TYPE_STRING_AS_STRING, &varient);
+	dbus_message_iter_open_container(&varient, DBUS_TYPE_ARRAY,
+					 DBUS_TYPE_STRING_AS_STRING, &array);
+	dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &artist);
+	dbus_message_iter_close_container(&varient, &array);
+	dbus_message_iter_close_container(&dict, &varient);
+	dbus_message_iter_close_container(iter, &dict);
+}
+
+static void cras_bt_player_append_metadata(DBusMessageIter *iter,
+					   const char *title,
+					   const char *artist,
+					   const char *album,
+					   dbus_int64_t length)
+{
+	DBusMessageIter varient, array;
+	dbus_message_iter_open_container(
+		iter, DBUS_TYPE_VARIANT,
+		DBUS_TYPE_ARRAY_AS_STRING DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+			DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+				DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
+		&varient);
+	dbus_message_iter_open_container(
+		&varient, DBUS_TYPE_ARRAY,
+		DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING
+			DBUS_TYPE_VARIANT_AS_STRING
+				DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
+		&array);
+	if (!is_utf8_string(title)) {
+		syslog(LOG_INFO, "Non-utf8 title: %s", title);
+		title = "";
+	}
+	if (!is_utf8_string(album)) {
+		syslog(LOG_INFO, "Non-utf8 album: %s", album);
+		album = "";
+	}
+	if (!is_utf8_string(artist)) {
+		syslog(LOG_INFO, "Non-utf8 artist: %s", artist);
+		artist = "";
+	}
+
+	append_key_value(&array, "xesam:title", DBUS_TYPE_STRING,
+			 DBUS_TYPE_STRING_AS_STRING, &title);
+	append_key_value(&array, "xesam:album", DBUS_TYPE_STRING,
+			 DBUS_TYPE_STRING_AS_STRING, &album);
+	append_key_value(&array, "mpris:length", DBUS_TYPE_INT64,
+			 DBUS_TYPE_INT64_AS_STRING, &length);
+	cras_bt_player_append_metadata_artist(&array, artist);
+
+	dbus_message_iter_close_container(&varient, &array);
+	dbus_message_iter_close_container(iter, &varient);
+}
+
+static bool cras_bt_player_parse_metadata(const char *title, const char *album,
+					  const char *artist,
+					  const dbus_int64_t length)
+{
+	bool require_update = false;
+
+	if (title && strcmp(player.metadata->title, title)) {
+		snprintf(player.metadata->title, CRAS_PLAYER_METADATA_SIZE_MAX,
+			 "%s", title);
+		require_update = true;
+	}
+	if (artist && strcmp(player.metadata->artist, artist)) {
+		snprintf(player.metadata->artist, CRAS_PLAYER_METADATA_SIZE_MAX,
+			 "%s", artist);
+		require_update = true;
+	}
+	if (album && strcmp(player.metadata->album, album)) {
+		snprintf(player.metadata->album, CRAS_PLAYER_METADATA_SIZE_MAX,
+			 "%s", album);
+		require_update = true;
+	}
+	if (length && player.metadata->length != length) {
+		player.metadata->length = length;
+		require_update = true;
+	}
+
+	return require_update;
+}
+
 int cras_bt_player_create(DBusConnection *conn)
 {
 	static const DBusObjectPathVTable player_vtable = {
@@ -146,6 +265,7 @@
 
 	dbus_error_init(&dbus_error);
 
+	cras_bt_player_init();
 	if (!dbus_connection_register_object_path(
 		    conn, player.object_path, &player_vtable, &dbus_error)) {
 		syslog(LOG_ERR, "Cannot register player %s",
@@ -166,3 +286,201 @@
 {
 	return cras_bt_add_player(conn, adapter, &player);
 }
+
+int cras_bt_player_update_playback_status(DBusConnection *conn,
+					  const char *status)
+{
+	DBusMessage *msg;
+	DBusMessageIter iter, dict;
+	const char *playerInterface = BLUEZ_INTERFACE_MEDIA_PLAYER;
+
+	if (!player.playback_status)
+		return -ENXIO;
+
+	/* Verify the string value matches one of the possible status defined in
+	 * bluez/profiles/audio/avrcp.c
+	 */
+	if (strcasecmp(status, "stopped") != 0 &&
+	    strcasecmp(status, "playing") != 0 &&
+	    strcasecmp(status, "paused") != 0 &&
+	    strcasecmp(status, "forward-seek") != 0 &&
+	    strcasecmp(status, "reverse-seek") != 0 &&
+	    strcasecmp(status, "error") != 0)
+		return -EINVAL;
+
+	if (!strcasecmp(player.playback_status, status))
+		return 0;
+
+	strcpy(player.playback_status, status);
+
+	msg = dbus_message_new_signal(CRAS_DEFAULT_PLAYER,
+				      DBUS_INTERFACE_PROPERTIES,
+				      "PropertiesChanged");
+	if (!msg)
+		return -ENOMEM;
+
+	dbus_message_iter_init_append(msg, &iter);
+	dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING,
+				       &playerInterface);
+	dbus_message_iter_open_container(
+		&iter, DBUS_TYPE_ARRAY,
+		DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING
+			DBUS_TYPE_VARIANT_AS_STRING
+				DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
+		&dict);
+	append_key_value(&dict, "PlaybackStatus", DBUS_TYPE_STRING,
+			 DBUS_TYPE_STRING_AS_STRING, &status);
+	dbus_message_iter_close_container(&iter, &dict);
+
+	if (!dbus_connection_send(conn, msg, NULL)) {
+		dbus_message_unref(msg);
+		return -ENOMEM;
+	}
+
+	dbus_message_unref(msg);
+	return 0;
+}
+
+int cras_bt_player_update_identity(DBusConnection *conn, const char *identity)
+{
+	DBusMessage *msg;
+	DBusMessageIter iter, dict;
+	const char *playerInterface = BLUEZ_INTERFACE_MEDIA_PLAYER;
+
+	if (!player.identity)
+		return -ENXIO;
+
+	if (!identity)
+		return -EINVAL;
+
+	if (!is_utf8_string(identity)) {
+		syslog(LOG_INFO, "Non-utf8 identity: %s", identity);
+		identity = "";
+	}
+
+	if (!strcasecmp(player.identity, identity))
+		return 0;
+
+	strcpy(player.identity, identity);
+
+	msg = dbus_message_new_signal(CRAS_DEFAULT_PLAYER,
+				      DBUS_INTERFACE_PROPERTIES,
+				      "PropertiesChanged");
+	if (!msg)
+		return -ENOMEM;
+
+	dbus_message_iter_init_append(msg, &iter);
+	dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING,
+				       &playerInterface);
+	dbus_message_iter_open_container(
+		&iter, DBUS_TYPE_ARRAY,
+		DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING
+			DBUS_TYPE_VARIANT_AS_STRING
+				DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
+		&dict);
+	append_key_value(&dict, "Identity", DBUS_TYPE_STRING,
+			 DBUS_TYPE_STRING_AS_STRING, &identity);
+	dbus_message_iter_close_container(&iter, &dict);
+
+	if (!dbus_connection_send(conn, msg, NULL)) {
+		dbus_message_unref(msg);
+		return -ENOMEM;
+	}
+
+	dbus_message_unref(msg);
+	return 0;
+}
+
+int cras_bt_player_update_position(DBusConnection *conn,
+				   const dbus_int64_t position)
+{
+	DBusMessage *msg;
+	DBusMessageIter iter, dict;
+	const char *playerInterface = BLUEZ_INTERFACE_MEDIA_PLAYER;
+
+	if (position < 0)
+		return -EINVAL;
+
+	player.position = position;
+
+	msg = dbus_message_new_signal(CRAS_DEFAULT_PLAYER,
+				      DBUS_INTERFACE_PROPERTIES,
+				      "PropertiesChanged");
+	if (!msg)
+		return -ENOMEM;
+
+	dbus_message_iter_init_append(msg, &iter);
+	dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING,
+				       &playerInterface);
+	dbus_message_iter_open_container(
+		&iter, DBUS_TYPE_ARRAY,
+		DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING
+			DBUS_TYPE_VARIANT_AS_STRING
+				DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
+		&dict);
+	append_key_value(&dict, "Position", DBUS_TYPE_INT64,
+			 DBUS_TYPE_INT64_AS_STRING, &player.position);
+	dbus_message_iter_close_container(&iter, &dict);
+
+	if (!dbus_connection_send(conn, msg, NULL)) {
+		dbus_message_unref(msg);
+		return -ENOMEM;
+	}
+
+	dbus_message_unref(msg);
+	return 0;
+}
+
+int cras_bt_player_update_metadata(DBusConnection *conn, const char *title,
+				   const char *artist, const char *album,
+				   const dbus_int64_t length)
+{
+	DBusMessage *msg;
+	DBusMessageIter iter, array, dict;
+	const char *property = "Metadata";
+	const char *playerInterface = BLUEZ_INTERFACE_MEDIA_PLAYER;
+
+	if (!player.metadata)
+		return -ENXIO;
+
+	msg = dbus_message_new_signal(CRAS_DEFAULT_PLAYER,
+				      DBUS_INTERFACE_PROPERTIES,
+				      "PropertiesChanged");
+	if (!msg)
+		return -ENOMEM;
+
+	dbus_message_iter_init_append(msg, &iter);
+	dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING,
+				       &playerInterface);
+	dbus_message_iter_open_container(
+		&iter, DBUS_TYPE_ARRAY,
+		DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING
+			DBUS_TYPE_VARIANT_AS_STRING
+				DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
+		&array);
+	dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY, NULL,
+					 &dict);
+	dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &property);
+
+	if (!cras_bt_player_parse_metadata(title, album, artist, length)) {
+		/* Nothing to update. */
+		dbus_message_unref(msg);
+		return 0;
+	}
+
+	cras_bt_player_append_metadata(&dict, player.metadata->title,
+				       player.metadata->artist,
+				       player.metadata->album,
+				       player.metadata->length);
+
+	dbus_message_iter_close_container(&array, &dict);
+	dbus_message_iter_close_container(&iter, &array);
+
+	if (!dbus_connection_send(conn, msg, NULL)) {
+		dbus_message_unref(msg);
+		return -ENOMEM;
+	}
+
+	dbus_message_unref(msg);
+	return 0;
+}
diff --git a/cras/src/server/cras_bt_player.h b/cras/src/server/cras_bt_player.h
index 9b66d70..25a6c8c 100644
--- a/cras/src/server/cras_bt_player.h
+++ b/cras/src/server/cras_bt_player.h
@@ -11,16 +11,27 @@
 
 #include "cras_bt_adapter.h"
 
+/* Object to hold current metadata. This is not a full list of what BlueZ/MPRIS
+ * supports but a subset because Chromium only provides the following.
+ */
+struct cras_bt_player_metadata {
+	char *title;
+	char *artist;
+	char *album;
+	int64_t length;
+};
+
 /* Object to register as media player so that bluetoothd will report hardware
  * volume from device through bt_transport. Properties of the player are defined
  * in BlueZ's media API.
  */
 struct cras_bt_player {
 	const char *object_path;
-	const char *playback_status;
-	const char *identity;
+	char *playback_status;
+	char *identity;
 	const char *loop_status;
-	int position;
+	struct cras_bt_player_metadata *metadata;
+	int64_t position;
 	bool can_go_next;
 	bool can_go_prev;
 	bool can_play;
@@ -45,4 +56,40 @@
 int cras_bt_register_player(DBusConnection *conn,
 			    const struct cras_bt_adapter *adapter);
 
+/* Updates playback status for player and notifies bluetoothd
+ * Args:
+ *    conn - The dbus connection.
+ *    status - The player playback status.
+ */
+int cras_bt_player_update_playback_status(DBusConnection *conn,
+					  const char *status);
+
+/* Updates the player identity and notifies bluetoothd.
+ * Args:
+ *    conn - The dbus connection.
+ *    identity - The identity of the registered player. This could be the name
+ *               of the app or the name of the site playing media.
+ */
+int cras_bt_player_update_identity(DBusConnection *conn, const char *identity);
+
+/* Updates the player current track's position and notifies bluetoothd.
+ * Args:
+ *    conn - The dbus connection.
+ *    position - The current track position in microseconds.
+ */
+int cras_bt_player_update_position(DBusConnection *conn,
+				   const dbus_int64_t position);
+
+/* Updates the player current metadata and notifies bluetoothd.
+ * Args:
+ *    conn - The dbus connection.
+ *    title - The title associated to the current media session.
+ *    artist - The artist associated to the current media session.
+ *    album - The album associated to the current media session.
+ *    length - The duration in microseconds associated to the current media
+ *             session.
+ */
+int cras_bt_player_update_metadata(DBusConnection *conn, const char *title,
+				   const char *artist, const char *album,
+				   const dbus_int64_t length);
 #endif /* CRAS_BT_PLAYER_H_ */
diff --git a/cras/src/server/cras_bt_profile.c b/cras/src/server/cras_bt_profile.c
index d180a1b..9b4171f 100644
--- a/cras/src/server/cras_bt_profile.c
+++ b/cras/src/server/cras_bt_profile.c
@@ -362,6 +362,46 @@
 	return 0;
 }
 
+int cras_bt_unregister_profile(DBusConnection *conn,
+			       struct cras_bt_profile *profile)
+{
+	DBusMessage *method_call;
+	DBusMessageIter message_iter;
+	DBusError dbus_error;
+	DBusMessage *reply;
+
+	method_call = dbus_message_new_method_call(BLUEZ_SERVICE,
+						   PROFILE_MANAGER_OBJ_PATH,
+						   BLUEZ_PROFILE_MGMT_INTERFACE,
+						   "UnregisterProfile");
+
+	if (!method_call)
+		return -ENOMEM;
+	dbus_error_init(&dbus_error);
+	dbus_message_iter_init_append(method_call, &message_iter);
+	dbus_message_iter_append_basic(&message_iter, DBUS_TYPE_OBJECT_PATH,
+				       &profile->object_path);
+	reply = dbus_connection_send_with_reply_and_block(
+		conn, method_call, DBUS_TIMEOUT_USE_DEFAULT, &dbus_error);
+
+	if (!reply) {
+		dbus_error_free(&dbus_error);
+		dbus_message_unref(method_call);
+		return -EIO;
+	}
+
+	dbus_message_unref(method_call);
+
+	if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) {
+		syslog(LOG_ERR, "Unregister profile returned error: %s",
+		       dbus_message_get_error_name(reply));
+		dbus_message_unref(reply);
+		return -EIO;
+	}
+	dbus_message_unref(reply);
+	return 0;
+}
+
 int cras_bt_register_profiles(DBusConnection *conn)
 {
 	struct cras_bt_profile *profile;
@@ -399,6 +439,19 @@
 	return 0;
 }
 
+int cras_bt_rm_profile(DBusConnection *conn, struct cras_bt_profile *profile)
+{
+	DL_DELETE(profiles, profile);
+
+	if (!dbus_connection_unregister_object_path(conn,
+						    profile->object_path)) {
+		syslog(LOG_ERR, "Could not unregister BT profile %s",
+		       profile->object_path);
+		return -ENOMEM;
+	}
+	return 0;
+}
+
 void cras_bt_profile_reset()
 {
 	struct cras_bt_profile *profile;
diff --git a/cras/src/server/cras_bt_profile.h b/cras/src/server/cras_bt_profile.h
index ed42660..4f8ab44 100644
--- a/cras/src/server/cras_bt_profile.h
+++ b/cras/src/server/cras_bt_profile.h
@@ -41,6 +41,13 @@
  */
 int cras_bt_add_profile(DBusConnection *conn, struct cras_bt_profile *profile);
 
+/* Removes |profile| from the list of profiles registered with bluez.
+ * Args:
+ *    conn - The dbus connection.
+ *    profile - Pointer to the profile structure to be removed.
+ */
+int cras_bt_rm_profile(DBusConnection *conn, struct cras_bt_profile *profile);
+
 /* Gets the profile by object path.
  * Args:
  *    path - The object path of the desired profile.
@@ -57,6 +64,22 @@
 /* Notifies all profiles when a device is disconnected. */
 void cras_bt_profile_on_device_disconnected(struct cras_bt_device *device);
 
+/* Registeres |profile| with bluez.
+ * Args:
+ *    conn - The dbus connection.
+ *    profile - Pointer to the profile structure to be registered.
+ */
+int cras_bt_register_profile(DBusConnection *conn,
+			     struct cras_bt_profile *profile);
+
+/* Unregisteres |profile| with bluez.
+ * Args:
+ *    conn - The dbus connection.
+ *    profile - Pointer to the profile structure to be unregistered.
+ */
+int cras_bt_unregister_profile(DBusConnection *conn,
+			       struct cras_bt_profile *profile);
+
 /* Registers all added profiles.
  * Args:
  *    conn - The dbus connection.
diff --git a/cras/src/server/cras_bt_transport.c b/cras/src/server/cras_bt_transport.c
index 9e06dac..402cd75 100644
--- a/cras/src/server/cras_bt_transport.c
+++ b/cras/src/server/cras_bt_transport.c
@@ -17,6 +17,7 @@
 #include "cras_bt_log.h"
 #include "cras_bt_transport.h"
 #include "cras_bt_constants.h"
+#include "cras_system_state.h"
 #include "utlist.h"
 
 struct cras_bt_transport {
@@ -100,6 +101,8 @@
 	if (transport->fd >= 0)
 		close(transport->fd);
 
+	cras_bt_device_set_use_hardware_volume(transport->device, 0);
+
 	free(transport->object_path);
 	free(transport->configuration);
 	free(transport);
@@ -284,17 +287,24 @@
 		} else if (type == DBUS_TYPE_OBJECT_PATH) {
 			const char *obj_path;
 
-			/* Property: object Device [readonly] */
-			dbus_message_iter_get_basic(&variant_iter, &obj_path);
-			transport->device = cras_bt_device_get(obj_path);
-			if (!transport->device) {
-				syslog(LOG_ERR,
-				       "Device %s not found at update"
-				       "transport properties",
-				       obj_path);
-				transport->device = cras_bt_device_create(
-					transport->conn, obj_path);
-				cras_bt_transport_update_device(transport);
+			if (strcmp(key, "Device") == 0) {
+				/* Property: object Device [readonly] */
+				dbus_message_iter_get_basic(&variant_iter,
+							    &obj_path);
+				transport->device =
+					cras_bt_device_get(obj_path);
+				if (!transport->device) {
+					syslog(LOG_ERR,
+					       "Device %s not found at update "
+					       "transport properties",
+					       obj_path);
+					transport->device =
+						cras_bt_device_create(
+							transport->conn,
+							obj_path);
+					cras_bt_transport_update_device(
+						transport);
+				}
 			}
 		} else if (strcmp(dbus_message_iter_get_signature(&variant_iter),
 				  "ay") == 0 &&
@@ -321,6 +331,7 @@
 
 			dbus_message_iter_get_basic(&variant_iter, &volume);
 			transport->volume = volume;
+			BTLOG(btlog, BT_TRANSPORT_UPDATE_VOLUME, volume, 0);
 			cras_bt_transport_update_device(transport);
 		}
 
@@ -374,6 +385,7 @@
 	DBusMessageIter message_iter, variant;
 	DBusPendingCall *pending_call;
 
+	BTLOG(btlog, BT_TRANSPORT_SET_VOLUME, volume, 0);
 	method_call =
 		dbus_message_new_method_call(BLUEZ_SERVICE,
 					     transport->object_path,
@@ -464,6 +476,10 @@
 		goto acquire_fail;
 	}
 
+	if (cras_system_get_bt_fix_a2dp_packet_size_enabled() &&
+	    transport->write_mtu > A2DP_FIX_PACKET_SIZE)
+		transport->write_mtu = A2DP_FIX_PACKET_SIZE;
+
 	BTLOG(btlog, BT_TRANSPORT_ACQUIRE, 1, transport->fd);
 	dbus_message_unref(reply);
 	return 0;
diff --git a/cras/src/server/cras_capture_rclient.c b/cras/src/server/cras_capture_rclient.c
index c13792c..9b1f2b8 100644
--- a/cras/src/server/cras_capture_rclient.c
+++ b/cras/src/server/cras_capture_rclient.c
@@ -11,63 +11,12 @@
 #include "cras_rclient.h"
 #include "cras_rclient_util.h"
 #include "cras_rstream.h"
-#include "cras_system_state.h"
 #include "cras_types.h"
 #include "cras_util.h"
-#include "stream_list.h"
-
-/* Entry point for handling a message from the client.  Called from the main
- * server context. */
-static int ccr_handle_message_from_client(struct cras_rclient *client,
-					  const struct cras_server_message *msg,
-					  int *fds, unsigned int num_fds)
-{
-	int rc = 0;
-	assert(client && msg);
-
-	rc = rclient_validate_message_fds(msg, fds, num_fds);
-	if (rc < 0) {
-		for (int i = 0; i < (int)num_fds; i++)
-			if (fds[i] >= 0)
-				close(fds[i]);
-		return rc;
-	}
-	int fd = num_fds > 0 ? fds[0] : -1;
-
-	switch (msg->id) {
-	case CRAS_SERVER_CONNECT_STREAM: {
-		int client_shm_fd = num_fds > 1 ? fds[1] : -1;
-		struct cras_connect_message cmsg;
-		if (MSG_LEN_VALID(msg, struct cras_connect_message)) {
-			rc = rclient_handle_client_stream_connect(
-				client,
-				(const struct cras_connect_message *)msg, fd,
-				client_shm_fd);
-		} else if (!convert_connect_message_old(msg, &cmsg)) {
-			rc = rclient_handle_client_stream_connect(
-				client, &cmsg, fd, client_shm_fd);
-		} else {
-			return -EINVAL;
-		}
-		break;
-	}
-	case CRAS_SERVER_DISCONNECT_STREAM:
-		if (!MSG_LEN_VALID(msg, struct cras_disconnect_stream_message))
-			return -EINVAL;
-		rc = rclient_handle_client_stream_disconnect(
-			client,
-			(const struct cras_disconnect_stream_message *)msg);
-		break;
-	default:
-		break;
-	}
-
-	return rc;
-}
 
 /* Declarations of cras_rclient operators for cras_capture_rclient. */
 static const struct cras_rclient_ops cras_capture_rclient_ops = {
-	.handle_message_from_client = ccr_handle_message_from_client,
+	.handle_message_from_client = rclient_handle_message_from_client,
 	.send_message_to_client = rclient_send_message_to_client,
 	.destroy = rclient_destroy,
 };
@@ -80,24 +29,7 @@
  * the connection has succeeded. */
 struct cras_rclient *cras_capture_rclient_create(int fd, size_t id)
 {
-	struct cras_rclient *client;
-	struct cras_client_connected msg;
-	int state_fd;
-
-	client = (struct cras_rclient *)calloc(1, sizeof(struct cras_rclient));
-	if (!client)
-		return NULL;
-
-	client->fd = fd;
-	client->id = id;
-
-	client->ops = &cras_capture_rclient_ops;
-	client->supported_directions =
-		cras_stream_direction_mask(CRAS_STREAM_INPUT);
-
-	cras_fill_client_connected(&msg, client->id);
-	state_fd = cras_sys_state_shm_fd();
-	client->ops->send_message_to_client(client, &msg.header, &state_fd, 1);
-
-	return client;
+	return rclient_generic_create(
+		fd, id, &cras_capture_rclient_ops,
+		cras_stream_direction_mask(CRAS_STREAM_INPUT));
 }
diff --git a/cras/src/server/cras_control_rclient.c b/cras/src/server/cras_control_rclient.c
index c0eea5d..cd0c4d3 100644
--- a/cras/src/server/cras_control_rclient.c
+++ b/cras/src/server/cras_control_rclient.c
@@ -15,6 +15,8 @@
 #include "cras_dsp.h"
 #include "cras_iodev.h"
 #include "cras_iodev_list.h"
+#include "cras_hfp_ag_profile.h"
+#include "cras_main_thread_log.h"
 #include "cras_messages.h"
 #include "cras_observer.h"
 #include "cras_rclient.h"
@@ -23,7 +25,6 @@
 #include "cras_system_state.h"
 #include "cras_types.h"
 #include "cras_util.h"
-#include "stream_list.h"
 #include "utlist.h"
 
 /* Handles dumping audio thread debug info back to the client. */
@@ -271,7 +272,14 @@
 }
 
 /* Entry point for handling a message from the client.  Called from the main
- * server context. */
+ * server context.
+ *
+ * If the message from clients has incorrect length (truncated message), return
+ * an error up to CRAS server.
+ * If the message from clients has invalid content, should return the errors to
+ * clients by send_message_to_client and return 0 here.
+ *
+ */
 static int ccr_handle_message_from_client(struct cras_rclient *client,
 					  const struct cras_server_message *msg,
 					  int *fds, unsigned int num_fds)
@@ -291,18 +299,15 @@
 	switch (msg->id) {
 	case CRAS_SERVER_CONNECT_STREAM: {
 		int client_shm_fd = num_fds > 1 ? fds[1] : -1;
-		struct cras_connect_message cmsg;
 		if (MSG_LEN_VALID(msg, struct cras_connect_message)) {
-			return rclient_handle_client_stream_connect(
+			rclient_handle_client_stream_connect(
 				client,
 				(const struct cras_connect_message *)msg, fd,
 				client_shm_fd);
-		} else if (!convert_connect_message_old(msg, &cmsg)) {
-			return rclient_handle_client_stream_connect(
-				client, &cmsg, fd, client_shm_fd);
 		} else {
 			return -EINVAL;
 		}
+		break;
 	}
 	case CRAS_SERVER_DISCONNECT_STREAM:
 		if (!MSG_LEN_VALID(msg, struct cras_disconnect_stream_message))
@@ -335,14 +340,6 @@
 		cras_system_set_mute_locked(
 			((const struct cras_set_system_mute *)msg)->mute);
 		break;
-	case CRAS_SERVER_SET_SYSTEM_CAPTURE_GAIN: {
-		const struct cras_set_system_capture_gain *m =
-			(const struct cras_set_system_capture_gain *)msg;
-		if (!MSG_LEN_VALID(msg, struct cras_set_system_capture_gain))
-			return -EINVAL;
-		cras_system_set_capture_gain(m->gain);
-		break;
-	}
 	case CRAS_SERVER_SET_SYSTEM_CAPTURE_MUTE:
 		if (!MSG_LEN_VALID(msg, struct cras_set_system_mute))
 			return -EINVAL;
@@ -402,6 +399,19 @@
 	case CRAS_SERVER_GET_ATLOG_FD:
 		get_atlog_fd(client);
 		break;
+	case CRAS_SERVER_DUMP_MAIN: {
+		struct cras_client_audio_debug_info_ready msg;
+		struct cras_server_state *state;
+
+		state = cras_system_state_get_no_lock();
+		memcpy(&state->main_thread_debug_info.main_log, main_log,
+		       sizeof(struct main_thread_event_log));
+
+		cras_fill_client_audio_debug_info_ready(&msg);
+		client->ops->send_message_to_client(client, &msg.header, NULL,
+						    0);
+		break;
+	}
 	case CRAS_SERVER_DUMP_BT: {
 		struct cras_client_audio_debug_info_ready msg;
 		struct cras_server_state *state;
@@ -409,10 +419,15 @@
 		state = cras_system_state_get_no_lock();
 #ifdef CRAS_DBUS
 		memcpy(&state->bt_debug_info.bt_log, btlog,
-		       sizeof(struct cras_bt_debug_info));
+		       sizeof(struct cras_bt_event_log));
+		memcpy(&state->bt_debug_info.wbs_logger,
+		       cras_hfp_ag_get_wbs_logger(),
+		       sizeof(struct packet_status_logger));
 #else
 		memset(&state->bt_debug_info.bt_log, 0,
 		       sizeof(struct cras_bt_debug_info));
+		memset(&state->bt_debug_info.wbs_logger, 0,
+		       sizeof(struct packet_status_logger));
 #endif
 
 		cras_fill_client_audio_debug_info_ready(&msg);
@@ -458,17 +473,33 @@
 	case CRAS_CONFIG_GLOBAL_REMIX: {
 		const struct cras_config_global_remix *m =
 			(const struct cras_config_global_remix *)msg;
+		float *coefficient;
+
 		if (!MSG_LEN_VALID(msg, struct cras_config_global_remix) ||
 		    m->num_channels > CRAS_MAX_REMIX_CHANNELS)
 			return -EINVAL;
-		size_t size_with_coefficients =
-			sizeof(*m) + m->num_channels * m->num_channels *
-					     sizeof(m->coefficient[0]);
+		const size_t coefficient_len =
+			(size_t)m->num_channels * (size_t)m->num_channels;
+		const size_t size_with_coefficients =
+			sizeof(*m) +
+			coefficient_len * sizeof(m->coefficient[0]);
 		if (size_with_coefficients != msg->length)
 			return -EINVAL;
+
+		coefficient =
+			(float *)calloc(coefficient_len, sizeof(coefficient));
+		if (!coefficient) {
+			syslog(LOG_ERR,
+			       "Failed to create local coefficient array.");
+			break;
+		}
+		memcpy(coefficient, m->coefficient,
+		       coefficient_len * sizeof(coefficient));
+
 		audio_thread_config_global_remix(
 			cras_iodev_list_get_audio_thread(), m->num_channels,
-			m->coefficient);
+			coefficient);
+		free(coefficient);
 		break;
 	}
 	case CRAS_SERVER_GET_HOTWORD_MODELS: {
@@ -531,25 +562,11 @@
  * the conneciton has succeeded. */
 struct cras_rclient *cras_control_rclient_create(int fd, size_t id)
 {
-	struct cras_rclient *client;
-	struct cras_client_connected msg;
-	int state_fd;
-
-	client = (struct cras_rclient *)calloc(1, sizeof(struct cras_rclient));
-	if (!client)
-		return NULL;
-
-	client->fd = fd;
-	client->id = id;
-	client->ops = &cras_control_rclient_ops;
-	client->supported_directions = CRAS_STREAM_ALL_DIRECTION;
-	/* Filters CRAS_STREAM_UNDEFINED stream out. */
-	client->supported_directions ^=
+	/* Supports all directions but not CRAS_STREAM_UNDEFINED. */
+	int supported_directions =
+		CRAS_STREAM_ALL_DIRECTION ^
 		cras_stream_direction_mask(CRAS_STREAM_UNDEFINED);
 
-	cras_fill_client_connected(&msg, client->id);
-	state_fd = cras_sys_state_shm_fd();
-	client->ops->send_message_to_client(client, &msg.header, &state_fd, 1);
-
-	return client;
+	return rclient_generic_create(fd, id, &cras_control_rclient_ops,
+				      supported_directions);
 }
diff --git a/cras/src/server/cras_dbus.c b/cras/src/server/cras_dbus.c
index d127485..5975f1c 100644
--- a/cras/src/server/cras_dbus.c
+++ b/cras/src/server/cras_dbus.c
@@ -15,7 +15,7 @@
 #include "cras_system_state.h"
 #include "cras_tm.h"
 
-static void dbus_watch_callback(void *arg)
+static void dbus_watch_callback(void *arg, int revents)
 {
 	DBusWatch *watch = (DBusWatch *)arg;
 	int r, flags;
@@ -48,7 +48,8 @@
 	 */
 	if ((flags & DBUS_WATCH_READABLE) && dbus_watch_get_enabled(watch)) {
 		r = cras_system_add_select_fd(dbus_watch_get_unix_fd(watch),
-					      dbus_watch_callback, watch);
+					      dbus_watch_callback, watch,
+					      POLLIN);
 		if (r != 0)
 			return FALSE;
 	}
diff --git a/cras/src/server/cras_dbus_control.c b/cras/src/server/cras_dbus_control.c
index 978c64a..b66e127 100644
--- a/cras/src/server/cras_dbus_control.c
+++ b/cras/src/server/cras_dbus_control.c
@@ -11,12 +11,16 @@
 #include <syslog.h>
 
 #include "audio_thread.h"
+#include "cras_bt_player.h"
 #include "cras_dbus.h"
 #include "cras_dbus_control.h"
 #include "cras_dbus_util.h"
+#include "cras_hfp_ag_profile.h"
 #include "cras_iodev_list.h"
+#include "cras_main_thread_log.h"
 #include "cras_observer.h"
 #include "cras_system_state.h"
+#include "cras_utf8.h"
 #include "cras_util.h"
 #include "utlist.h"
 
@@ -46,9 +50,6 @@
 	"    <method name=\"SetSuspendAudio\">\n"                               \
 	"      <arg name=\"suspend\" type=\"b\" direction=\"in\"/>\n"           \
 	"    </method>\n"                                                       \
-	"    <method name=\"SetInputGain\">\n"                                  \
-	"      <arg name=\"gain\" type=\"i\" direction=\"in\"/>\n"              \
-	"    </method>\n"                                                       \
 	"    <method name=\"SetInputNodeGain\">\n"                              \
 	"      <arg name=\"node_id\" type=\"t\" direction=\"in\"/>\n"           \
 	"      <arg name=\"gain\" type=\"i\" direction=\"in\"/>\n"              \
@@ -59,7 +60,6 @@
 	"    <method name=\"GetVolumeState\">\n"                                \
 	"      <arg name=\"output_volume\" type=\"i\" direction=\"out\"/>\n"    \
 	"      <arg name=\"output_mute\" type=\"b\" direction=\"out\"/>\n"      \
-	"      <arg name=\"input_gain\" type=\"i\" direction=\"out\"/>\n"       \
 	"      <arg name=\"input_mute\" type=\"b\" direction=\"out\"/>\n"       \
 	"      <arg name=\"output_user_mute\" type=\"b\" direction=\"out\"/>\n" \
 	"    </method>\n"                                                       \
@@ -75,6 +75,9 @@
 	"    <method name=\"GetSystemAecGroupId\">\n"                           \
 	"      <arg name=\"group_id\" type=\"i\" direction=\"out\"/>\n"         \
 	"    </method>\n"                                                       \
+	"    <method name=\"GetDeprioritizeBtWbsMic\">\n"                       \
+	"      <arg name=\"deprioritized\" type=\"b\" direction=\"out\"/>\n"    \
+	"    </method>\n"                                                       \
 	"    <method name=\"SetActiveOutputNode\">\n"                           \
 	"      <arg name=\"node_id\" type=\"t\" direction=\"in\"/>\n"           \
 	"    </method>\n"                                                       \
@@ -93,6 +96,9 @@
 	"    <method name=\"RemoveActiveOutputNode\">\n"                        \
 	"      <arg name=\"node_id\" type=\"t\" direction=\"in\"/>\n"           \
 	"    </method>\n"                                                       \
+	"    <method name=\"SetFixA2dpPacketSize\">\n"                          \
+	"      <arg name=\"toggle\" type=\"b\" direction=\"in\"/>\n"            \
+	"    </method>\n"                                                       \
 	"    <method name=\"GetNumberOfActiveStreams\">\n"                      \
 	"      <arg name=\"num\" type=\"i\" direction=\"out\"/>\n"              \
 	"    </method>\n"                                                       \
@@ -102,6 +108,9 @@
 	"    <method name=\"GetNumberOfActiveInputStreams\">\n"                 \
 	"      <arg name=\"num\" type=\"i\" direction=\"out\"/>\n"              \
 	"    </method>\n"                                                       \
+	"    <method name=\"GetNumberOfInputStreamsWithPermission\">\n"         \
+	"      <arg name=\"num\" type=\"a{sv}\" direction=\"out\"/>\n"          \
+	"    </method>\n"                                                       \
 	"    <method name=\"SetGlobalOutputChannelRemix\">\n"                   \
 	"      <arg name=\"num_channels\" type=\"i\" direction=\"in\"/>\n"      \
 	"      <arg name=\"coefficient\" type=\"ad\" direction=\"in\"/>\n"      \
@@ -116,6 +125,21 @@
 	"    <method name=\"SetWbsEnabled\">\n"                                 \
 	"      <arg name=\"enabled\" type=\"b\" direction=\"in\"/>\n"           \
 	"    </method>\n"                                                       \
+	"    <method name=\"SetNoiseCancellationEnabled\">\n"                   \
+	"      <arg name=\"enabled\" type=\"b\" direction=\"in\"/>\n"           \
+	"    </method>\n"                                                       \
+	"    <method name=\"SetPlayerPlaybackStatus\">\n"                       \
+	"      <arg name=\"status\" type=\"s\" direction=\"in\"/>\n"            \
+	"    </method>\n"                                                       \
+	"    <method name=\"SetPlayerIdentity\">\n"                             \
+	"      <arg name=\"identity\" type=\"s\" direction=\"in\"/>\n"          \
+	"    </method>\n"                                                       \
+	"    <method name=\"SetPlayerPosition\">\n"                             \
+	"      <arg name=\"position\" type=\"x\" direction=\"in\"/>\n"          \
+	"    </method>\n"                                                       \
+	"    <method name=\"SetPlayerMetadata\">\n"                             \
+	"      <arg name=\"metadata\" type=\"a{sv}\" direction=\"in\"/>\n"      \
+	"    </method>\n"                                                       \
 	"  </interface>\n"                                                      \
 	"  <interface name=\"" DBUS_INTERFACE_INTROSPECTABLE "\">\n"            \
 	"    <method name=\"Introspect\">\n"                                    \
@@ -148,6 +172,79 @@
 	return 0;
 }
 
+static bool get_string_metadata(DBusMessageIter *iter, const char **dst)
+{
+	if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING)
+		return FALSE;
+
+	dbus_message_iter_get_basic(iter, dst);
+	return TRUE;
+}
+
+static bool get_int64_metadata(DBusMessageIter *iter, dbus_int64_t *dst)
+{
+	if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_INT64)
+		return FALSE;
+
+	dbus_message_iter_get_basic(iter, dst);
+	return TRUE;
+}
+
+static bool get_metadata(DBusMessage *message, const char **title,
+			 const char **artist, const char **album,
+			 dbus_int64_t *length)
+{
+	DBusError dbus_error;
+	DBusMessageIter iter, dict;
+
+	dbus_error_init(&dbus_error);
+	dbus_message_iter_init(message, &iter);
+
+	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY)
+		return FALSE;
+
+	dbus_message_iter_recurse(&iter, &dict);
+
+	while (dbus_message_iter_get_arg_type(&dict) != DBUS_TYPE_INVALID) {
+		DBusMessageIter entry, var;
+		const char *key;
+
+		if (dbus_message_iter_get_arg_type(&dict) !=
+		    DBUS_TYPE_DICT_ENTRY)
+			return FALSE;
+
+		dbus_message_iter_recurse(&dict, &entry);
+		if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING)
+			return FALSE;
+
+		dbus_message_iter_get_basic(&entry, &key);
+		dbus_message_iter_next(&entry);
+
+		if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_VARIANT)
+			return FALSE;
+
+		dbus_message_iter_recurse(&entry, &var);
+		if (strcasecmp(key, "title") == 0) {
+			if (!get_string_metadata(&var, title))
+				return FALSE;
+		} else if (strcasecmp(key, "artist") == 0) {
+			if (!get_string_metadata(&var, artist))
+				return FALSE;
+		} else if (strcasecmp(key, "album") == 0) {
+			if (!get_string_metadata(&var, album))
+				return FALSE;
+		} else if (strcasecmp(key, "length") == 0) {
+			if (!get_int64_metadata(&var, length))
+				return FALSE;
+		} else
+			syslog(LOG_WARNING, "%s not supported, ignoring", key);
+
+		dbus_message_iter_next(&dict);
+	}
+
+	return TRUE;
+}
+
 /* Helper to send an empty reply. */
 static void send_empty_reply(DBusConnection *conn, DBusMessage *message)
 {
@@ -279,6 +376,7 @@
 		return rc;
 
 	cras_system_set_user_mute(new_mute);
+	MAINLOG(main_log, MAIN_THREAD_SET_OUTPUT_USER_MUTE, new_mute, 0, 0);
 
 	send_empty_reply(conn, message);
 
@@ -301,23 +399,6 @@
 	return DBUS_HANDLER_RESULT_HANDLED;
 }
 
-static DBusHandlerResult handle_set_input_gain(DBusConnection *conn,
-					       DBusMessage *message, void *arg)
-{
-	int rc;
-	dbus_int32_t new_gain;
-
-	rc = get_single_arg(message, DBUS_TYPE_INT32, &new_gain);
-	if (rc)
-		return rc;
-
-	cras_system_set_capture_gain(new_gain);
-
-	send_empty_reply(conn, message);
-
-	return DBUS_HANDLER_RESULT_HANDLED;
-}
-
 static DBusHandlerResult handle_set_input_node_gain(DBusConnection *conn,
 						    DBusMessage *message,
 						    void *arg)
@@ -369,7 +450,6 @@
 	dbus_int32_t volume;
 	dbus_bool_t system_muted;
 	dbus_bool_t user_muted;
-	dbus_int32_t capture_gain;
 	dbus_bool_t capture_muted;
 
 	reply = dbus_message_new_method_return(message);
@@ -377,12 +457,10 @@
 	volume = cras_system_get_volume();
 	system_muted = cras_system_get_system_mute();
 	user_muted = cras_system_get_user_mute();
-	capture_gain = cras_system_get_capture_gain();
 	capture_muted = cras_system_get_capture_mute();
 
 	dbus_message_append_args(reply, DBUS_TYPE_INT32, &volume,
 				 DBUS_TYPE_BOOLEAN, &system_muted,
-				 DBUS_TYPE_INT32, &capture_gain,
 				 DBUS_TYPE_BOOLEAN, &capture_muted,
 				 DBUS_TYPE_BOOLEAN, &user_muted,
 				 DBUS_TYPE_INVALID);
@@ -429,7 +507,6 @@
 	dbus_uint64_t stable_dev_id = node->stable_id;
 	const char *node_type = node->type;
 	const char *node_name = node->name;
-	const char *mic_positions = node->mic_positions;
 	dbus_bool_t active;
 	dbus_uint64_t plugged_time = node->plugged_time.tv_sec * 1000000ULL +
 				     node->plugged_time.tv_usec;
@@ -442,6 +519,14 @@
 	id = (id << 32) | node->ionode_idx;
 	active = !!node->active;
 
+	// If dev_name is not utf8, libdbus may abort cras.
+	if (!is_utf8_string(dev_name)) {
+		syslog(LOG_ERR,
+		       "Non-utf8 device name '%s' cannot be sent via dbus",
+		       dev_name);
+		dev_name = "";
+	}
+
 	if (!dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{sv}",
 					      &dict))
 		return FALSE;
@@ -467,9 +552,6 @@
 	if (!append_key_value(&dict, "Name", DBUS_TYPE_STRING,
 			      DBUS_TYPE_STRING_AS_STRING, &node_name))
 		return FALSE;
-	if (!append_key_value(&dict, "MicPositions", DBUS_TYPE_STRING,
-			      DBUS_TYPE_STRING_AS_STRING, &mic_positions))
-		return FALSE;
 	if (!append_key_value(&dict, "Active", DBUS_TYPE_BOOLEAN,
 			      DBUS_TYPE_BOOLEAN_AS_STRING, &active))
 		return FALSE;
@@ -596,6 +678,27 @@
 }
 
 static DBusHandlerResult
+handle_get_deprioritize_bt_wbs_mic(DBusConnection *conn, DBusMessage *message,
+				   void *arg)
+{
+	DBusMessage *reply;
+	dbus_uint32_t serial = 0;
+	dbus_bool_t deprioritized;
+
+	reply = dbus_message_new_method_return(message);
+
+	deprioritized = cras_system_get_deprioritize_bt_wbs_mic();
+	dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &deprioritized,
+				 DBUS_TYPE_INVALID);
+
+	dbus_connection_send(conn, reply, &serial);
+
+	dbus_message_unref(reply);
+
+	return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static DBusHandlerResult
 handle_set_active_node(DBusConnection *conn, DBusMessage *message, void *arg,
 		       enum CRAS_STREAM_DIRECTION direction)
 {
@@ -649,6 +752,24 @@
 	return DBUS_HANDLER_RESULT_HANDLED;
 }
 
+static DBusHandlerResult handle_set_fix_a2dp_packet_size(DBusConnection *conn,
+							 DBusMessage *message,
+							 void *arg)
+{
+	int rc;
+	dbus_bool_t enabled = FALSE;
+
+	rc = get_single_arg(message, DBUS_TYPE_BOOLEAN, &enabled);
+	if (rc)
+		return rc;
+
+	cras_system_set_bt_fix_a2dp_packet_size_enabled(enabled);
+
+	send_empty_reply(conn, message);
+
+	return DBUS_HANDLER_RESULT_HANDLED;
+}
+
 static DBusHandlerResult handle_get_num_active_streams(DBusConnection *conn,
 						       DBusMessage *message,
 						       void *arg)
@@ -691,6 +812,65 @@
 	return DBUS_HANDLER_RESULT_HANDLED;
 }
 
+static bool append_num_input_streams_with_permission(
+	DBusMessage *message, uint32_t num_input_streams[CRAS_NUM_CLIENT_TYPE])
+{
+	DBusMessageIter array;
+	DBusMessageIter dict;
+	unsigned type;
+
+	dbus_message_iter_init_append(message, &array);
+	for (type = 0; type < CRAS_NUM_CLIENT_TYPE; ++type) {
+		const char *client_type_str = cras_client_type_str(type);
+		if (!is_utf8_string(client_type_str)) {
+			syslog(LOG_ERR,
+			       "Non-utf8 clinet_type_str '%s' cannot be sent "
+			       "via dbus",
+			       client_type_str);
+			client_type_str = "";
+		}
+
+		if (!dbus_message_iter_open_container(&array, DBUS_TYPE_ARRAY,
+						      "{sv}", &dict))
+			return false;
+		if (!append_key_value(&dict, "ClientType", DBUS_TYPE_STRING,
+				      DBUS_TYPE_STRING_AS_STRING,
+				      &client_type_str))
+			return false;
+		if (!append_key_value(&dict, "NumStreamsWithPermission",
+				      DBUS_TYPE_UINT32,
+				      DBUS_TYPE_UINT32_AS_STRING,
+				      &num_input_streams[type]))
+			return false;
+		if (!dbus_message_iter_close_container(&array, &dict))
+			return false;
+	}
+	return true;
+}
+
+static DBusHandlerResult
+handle_get_num_input_streams_with_permission(DBusConnection *conn,
+					     DBusMessage *message, void *arg)
+{
+	DBusMessage *reply;
+	dbus_uint32_t serial = 0;
+	uint32_t num_input_streams[CRAS_NUM_CLIENT_TYPE] = {};
+
+	reply = dbus_message_new_method_return(message);
+
+	cras_system_state_get_input_streams_with_permission(num_input_streams);
+	if (!append_num_input_streams_with_permission(reply, num_input_streams))
+		goto error;
+
+	dbus_connection_send(conn, reply, &serial);
+	dbus_message_unref(reply);
+	return DBUS_HANDLER_RESULT_HANDLED;
+
+error:
+	dbus_message_unref(reply);
+	return DBUS_HANDLER_RESULT_NEED_MEMORY;
+}
+
 static DBusHandlerResult
 handle_set_global_output_channel_remix(DBusConnection *conn,
 				       DBusMessage *message, void *arg)
@@ -781,6 +961,124 @@
 	return DBUS_HANDLER_RESULT_HANDLED;
 }
 
+static DBusHandlerResult
+handle_set_noise_cancellation_enabled(DBusConnection *conn,
+				      DBusMessage *message, void *arg)
+{
+	int rc;
+	dbus_bool_t enabled;
+
+	rc = get_single_arg(message, DBUS_TYPE_BOOLEAN, &enabled);
+	if (rc)
+		return rc;
+
+	cras_system_set_noise_cancellation_enabled(enabled);
+
+	send_empty_reply(conn, message);
+
+	return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static DBusHandlerResult handle_set_player_playback_status(DBusConnection *conn,
+							   DBusMessage *message,
+							   void *arg)
+{
+	char *status;
+	DBusError dbus_error;
+	int rc;
+
+	dbus_error_init(&dbus_error);
+
+	rc = get_single_arg(message, DBUS_TYPE_STRING, &status);
+	if (rc)
+		return rc;
+
+	rc = cras_bt_player_update_playback_status(conn, status);
+	if (rc) {
+		syslog(LOG_WARNING,
+		       "CRAS failed to update BT Player Status: %d", rc);
+	}
+
+	send_empty_reply(conn, message);
+
+	return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static DBusHandlerResult handle_set_player_identity(DBusConnection *conn,
+						    DBusMessage *message,
+						    void *arg)
+{
+	char *identity;
+	DBusError dbus_error;
+	int rc;
+
+	dbus_error_init(&dbus_error);
+
+	rc = get_single_arg(message, DBUS_TYPE_STRING, &identity);
+	if (rc)
+		return rc;
+
+	rc = cras_bt_player_update_identity(conn, identity);
+	if (rc) {
+		syslog(LOG_WARNING,
+		       "CRAS failed to update BT Player Identity: %d", rc);
+	}
+
+	send_empty_reply(conn, message);
+
+	return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static DBusHandlerResult handle_set_player_position(DBusConnection *conn,
+						    DBusMessage *message,
+						    void *arg)
+{
+	dbus_int64_t position;
+	DBusError dbus_error;
+	int rc;
+
+	dbus_error_init(&dbus_error);
+
+	rc = get_single_arg(message, DBUS_TYPE_INT64, &position);
+	if (rc)
+		return rc;
+
+	rc = cras_bt_player_update_position(conn, position);
+	if (rc) {
+		syslog(LOG_WARNING,
+		       "CRAS failed to update BT Player Position: %d", rc);
+	}
+
+	send_empty_reply(conn, message);
+
+	return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static DBusHandlerResult handle_set_player_metadata(DBusConnection *conn,
+						    DBusMessage *message,
+						    void *arg)
+{
+	DBusError dbus_error;
+	int rc;
+
+	dbus_error_init(&dbus_error);
+	const char *title = NULL, *artist = NULL, *album = NULL;
+	dbus_int64_t length = 0;
+
+	if (!get_metadata(message, &title, &artist, &album, &length))
+		return -EINVAL;
+
+	rc = cras_bt_player_update_metadata(conn, title, artist, album, length);
+	if (rc) {
+		syslog(LOG_WARNING, "CRAS failed to update BT Metadata: %d",
+		       rc);
+	}
+
+	send_empty_reply(conn, message);
+
+	return DBUS_HANDLER_RESULT_HANDLED;
+}
+
 /* Handle incoming messages. */
 static DBusHandlerResult handle_control_message(DBusConnection *conn,
 						DBusMessage *message, void *arg)
@@ -826,9 +1124,6 @@
 					       "SetSuspendAudio")) {
 		return handle_set_suspend_audio(conn, message, arg);
 	} else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE,
-					       "SetInputGain")) {
-		return handle_set_input_gain(conn, message, arg);
-	} else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE,
 					       "SetInputNodeGain")) {
 		return handle_set_input_node_gain(conn, message, arg);
 	} else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE,
@@ -851,6 +1146,9 @@
 					       "GetSystemAecGroupId")) {
 		return handle_get_system_aec_group_id(conn, message, arg);
 	} else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE,
+					       "GetDeprioritizeBtWbsMic")) {
+		return handle_get_deprioritize_bt_wbs_mic(conn, message, arg);
+	} else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE,
 					       "SetActiveOutputNode")) {
 		return handle_set_active_node(conn, message, arg,
 					      CRAS_STREAM_OUTPUT);
@@ -875,6 +1173,9 @@
 		return handle_rm_active_node(conn, message, arg,
 					     CRAS_STREAM_OUTPUT);
 	} else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE,
+					       "SetFixA2dpPacketSize")) {
+		return handle_set_fix_a2dp_packet_size(conn, message, arg);
+	} else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE,
 					       "GetNumberOfActiveStreams")) {
 		return handle_get_num_active_streams(conn, message, arg);
 	} else if (dbus_message_is_method_call(
@@ -884,6 +1185,11 @@
 								  arg);
 	} else if (dbus_message_is_method_call(
 			   message, CRAS_CONTROL_INTERFACE,
+			   "GetNumberOfInputStreamsWithPermission")) {
+		return handle_get_num_input_streams_with_permission(
+			conn, message, arg);
+	} else if (dbus_message_is_method_call(
+			   message, CRAS_CONTROL_INTERFACE,
 			   "GetNumberOfActiveOutputStreams")) {
 		return handle_get_num_active_streams_use_output_hw(
 			conn, message, arg);
@@ -900,6 +1206,22 @@
 	} else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE,
 					       "SetWbsEnabled")) {
 		return handle_set_wbs_enabled(conn, message, arg);
+	} else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE,
+					       "SetNoiseCancellationEnabled")) {
+		return handle_set_noise_cancellation_enabled(conn, message,
+							     arg);
+	} else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE,
+					       "SetPlayerPlaybackStatus")) {
+		return handle_set_player_playback_status(conn, message, arg);
+	} else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE,
+					       "SetPlayerIdentity")) {
+		return handle_set_player_identity(conn, message, arg);
+	} else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE,
+					       "SetPlayerPosition")) {
+		return handle_set_player_position(conn, message, arg);
+	} else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE,
+					       "SetPlayerMetadata")) {
+		return handle_set_player_metadata(conn, message, arg);
 	}
 
 	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
@@ -1011,8 +1333,8 @@
 	dbus_uint32_t serial = 0;
 
 	msg = create_dbus_message((dir == CRAS_STREAM_OUTPUT) ?
-					  "ActiveOutputNodeChanged" :
-					  "ActiveInputNodeChanged");
+						"ActiveOutputNodeChanged" :
+						"ActiveInputNodeChanged");
 	if (!msg)
 		return;
 	dbus_message_append_args(msg, DBUS_TYPE_UINT64, &node_id,
@@ -1096,6 +1418,25 @@
 	dbus_message_unref(msg);
 }
 
+static void signal_num_input_streams_with_permission_changed(
+	void *context, uint32_t num_input_streams[CRAS_NUM_CLIENT_TYPE])
+{
+	struct cras_dbus_control *control = (struct cras_dbus_control *)context;
+	dbus_uint32_t serial = 0;
+	DBusMessage *msg;
+
+	msg = create_dbus_message("NumberOfInputStreamsWithPermissionChanged");
+	if (!msg)
+		return;
+
+	if (!append_num_input_streams_with_permission(msg, num_input_streams))
+		goto error;
+
+	dbus_connection_send(control->conn, msg, &serial);
+error:
+	dbus_message_unref(msg);
+}
+
 static void signal_hotword_triggered(void *context, int64_t tv_sec,
 				     int64_t tv_nsec)
 {
@@ -1161,6 +1502,8 @@
 	observer_ops.capture_mute_changed = signal_capture_mute;
 	observer_ops.num_active_streams_changed =
 		signal_num_active_streams_changed;
+	observer_ops.num_input_streams_with_permission_changed =
+		signal_num_input_streams_with_permission_changed;
 	observer_ops.nodes_changed = signal_nodes_changed;
 	observer_ops.active_node_changed = signal_active_node_changed;
 	observer_ops.input_node_gain_changed = signal_node_capture_gain_changed;
diff --git a/cras/src/server/cras_device_monitor.c b/cras/src/server/cras_device_monitor.c
index 7dd0f5d..e9730a0 100644
--- a/cras/src/server/cras_device_monitor.c
+++ b/cras/src/server/cras_device_monitor.c
@@ -13,6 +13,7 @@
 enum CRAS_DEVICE_MONITOR_MSG_TYPE {
 	RESET_DEVICE,
 	SET_MUTE_STATE,
+	ERROR_CLOSE,
 };
 
 struct cras_device_monitor_message {
@@ -62,6 +63,21 @@
 	return 0;
 }
 
+int cras_device_monitor_error_close(unsigned int dev_idx)
+{
+	struct cras_device_monitor_message msg;
+	int err;
+
+	init_device_msg(&msg, ERROR_CLOSE, dev_idx);
+	err = cras_main_message_send((struct cras_main_message *)&msg);
+	if (err < 0) {
+		syslog(LOG_ERR, "Failed to send device message %d",
+		       ERROR_CLOSE);
+		return err;
+	}
+	return 0;
+}
+
 /* When device is in a bad state, e.g. severe underrun,
  * it might break how audio thread works and cause busy wake up loop.
  * Resetting the device can bring device back to normal state.
@@ -84,6 +100,10 @@
 	case SET_MUTE_STATE:
 		cras_iodev_list_set_dev_mute(device_msg->dev_idx);
 		break;
+	case ERROR_CLOSE:
+		syslog(LOG_ERR, "Close erroneous device in main thread");
+		cras_iodev_list_suspend_dev(device_msg->dev_idx);
+		break;
 	default:
 		syslog(LOG_ERR, "Unknown device message type %u",
 		       device_msg->message_type);
diff --git a/cras/src/server/cras_device_monitor.h b/cras/src/server/cras_device_monitor.h
index ac31adb..eca2372 100644
--- a/cras/src/server/cras_device_monitor.h
+++ b/cras/src/server/cras_device_monitor.h
@@ -15,4 +15,8 @@
 /* Initializes device monitor and sets main thread callback. */
 int cras_device_monitor_init();
 
+/* Asks main thread to close device because error has occured in audio
+ * thread. */
+int cras_device_monitor_error_close(unsigned int dev_idx);
+
 #endif /* CRAS_DEVICE_MONITOR_H_ */
diff --git a/cras/src/server/cras_dsp.c b/cras/src/server/cras_dsp.c
index 1a2707d..9c4cc7b 100644
--- a/cras/src/server/cras_dsp.c
+++ b/cras/src/server/cras_dsp.c
@@ -36,7 +36,7 @@
 
 static struct dumper *syslog_dumper;
 static const char *ini_filename;
-static struct ini *ini;
+static struct ini *global_ini;
 static struct cras_dsp_context *context_list;
 
 static void initialize_environment(struct cras_expr_env *env)
@@ -60,7 +60,7 @@
 	 * this ini so its life cycle is aligned with the associated dsp
 	 * pipeline.
 	 */
-	if (private_ini && (private_ini != ini))
+	if (private_ini && (private_ini != global_ini))
 		cras_dsp_ini_free(private_ini);
 }
 
@@ -123,19 +123,21 @@
 
 static void cmd_reload_ini()
 {
-	struct ini *old_ini = ini;
+	struct ini *old_ini = global_ini;
 	struct cras_dsp_context *ctx;
 
-	ini = cras_dsp_ini_create(ini_filename);
-	if (!ini) {
+	struct ini *new_ini = cras_dsp_ini_create(ini_filename);
+	if (!new_ini) {
 		syslog(LOG_DEBUG, "cannot create dsp ini");
 		return;
 	}
 
 	DL_FOREACH (context_list, ctx) {
-		cmd_load_pipeline(ctx, ini);
+		cmd_load_pipeline(ctx, new_ini);
 	}
 
+	global_ini = new_ini;
+
 	if (old_ini)
 		cras_dsp_ini_free(old_ini);
 }
@@ -153,10 +155,11 @@
 void cras_dsp_stop()
 {
 	syslog_dumper_free(syslog_dumper);
-	free((char *)ini_filename);
-	if (ini) {
-		cras_dsp_ini_free(ini);
-		ini = NULL;
+	if (ini_filename)
+		free((char *)ini_filename);
+	if (global_ini) {
+		cras_dsp_ini_free(global_ini);
+		global_ini = NULL;
 	}
 }
 
@@ -202,18 +205,18 @@
 
 void cras_dsp_load_pipeline(struct cras_dsp_context *ctx)
 {
-	cmd_load_pipeline(ctx, ini);
+	cmd_load_pipeline(ctx, global_ini);
 }
 
-void cras_dsp_load_dummy_pipeline(struct cras_dsp_context *ctx,
-				  unsigned int num_channels)
+void cras_dsp_load_mock_pipeline(struct cras_dsp_context *ctx,
+				 unsigned int num_channels)
 {
-	struct ini *dummy_ini;
-	dummy_ini = create_dummy_ini(ctx->purpose, num_channels);
-	if (dummy_ini == NULL)
-		syslog(LOG_ERR, "Failed to create dummy ini");
+	struct ini *mock_ini;
+	mock_ini = create_mock_ini(ctx->purpose, num_channels);
+	if (mock_ini == NULL)
+		syslog(LOG_ERR, "Failed to create mock ini");
 	else
-		cmd_load_pipeline(ctx, dummy_ini);
+		cmd_load_pipeline(ctx, mock_ini);
 }
 
 struct pipeline *cras_dsp_get_pipeline(struct cras_dsp_context *ctx)
@@ -241,8 +244,8 @@
 	struct pipeline *pipeline;
 	struct cras_dsp_context *ctx;
 
-	if (ini)
-		cras_dsp_ini_dump(syslog_dumper, ini);
+	if (global_ini)
+		cras_dsp_ini_dump(syslog_dumper, global_ini);
 	DL_FOREACH (context_list, ctx) {
 		cras_expr_env_dump(syslog_dumper, &ctx->env);
 		pipeline = ctx->pipeline;
diff --git a/cras/src/server/cras_dsp.h b/cras/src/server/cras_dsp.h
index 9a72f42..366e2e6 100644
--- a/cras/src/server/cras_dsp.h
+++ b/cras/src/server/cras_dsp.h
@@ -54,11 +54,11 @@
  * blocking the audio thread. */
 void cras_dsp_load_pipeline(struct cras_dsp_context *ctx);
 
-/* Loads a dummy pipeline of source directly connects to sink, of given
+/* Loads a mock pipeline of source directly connects to sink, of given
  * number of channels.
  */
-void cras_dsp_load_dummy_pipeline(struct cras_dsp_context *ctx,
-				  unsigned int num_channels);
+void cras_dsp_load_mock_pipeline(struct cras_dsp_context *ctx,
+				 unsigned int num_channels);
 
 /* Locks the pipeline in the context for access. Returns NULL if the
  * pipeline is still being loaded or cannot be loaded. */
diff --git a/cras/src/server/cras_dsp_ini.c b/cras/src/server/cras_dsp_ini.c
index 966c6b0..a331acf 100644
--- a/cras/src/server/cras_dsp_ini.c
+++ b/cras/src/server/cras_dsp_ini.c
@@ -9,10 +9,9 @@
 #include "cras_dsp_ini.h"
 #include "iniparser_wrapper.h"
 
-#define MAX_INI_KEY_LENGTH 64 /* names like "output_source:output_0" */
 #define MAX_NR_PORT 128 /* the max number of ports for a plugin */
 #define MAX_PORT_NAME_LENGTH 20 /* names like "output_32" */
-#define MAX_DUMMY_INI_CH 8 /* Max number of channels to create dummy ini */
+#define MAX_MOCK_INI_CH 20 /* Max number of channels to create mock ini */
 
 /* Format of the ini file (See dsp.ini.sample for an example).
 
@@ -63,7 +62,7 @@
 static const char *getstring(struct ini *ini, const char *sec_name,
 			     const char *key)
 {
-	char full_key[MAX_INI_KEY_LENGTH];
+	char full_key[MAX_INI_KEY_LENGTH + 1];
 	snprintf(full_key, sizeof(full_key), "%s:%s", sec_name, key);
 	return iniparser_getstring(ini->dict, full_key, NULL);
 }
@@ -306,19 +305,21 @@
 	return 0;
 }
 
-struct ini *create_dummy_ini(const char *purpose, unsigned int num_channels)
+struct ini *create_mock_ini(const char *purpose, unsigned int num_channels)
 {
-	static char dummy_flow_names[MAX_DUMMY_INI_CH][8] = {
-		"{tmp:0}", "{tmp:1}", "{tmp:2}", "{tmp:3}",
-		"{tmp:4}", "{tmp:5}", "{tmp:6}", "{tmp:7}",
+	static char mock_flow_names[MAX_MOCK_INI_CH][9] = {
+		"{tmp:0}",  "{tmp:1}",	"{tmp:2}",  "{tmp:3}",	"{tmp:4}",
+		"{tmp:5}",  "{tmp:6}",	"{tmp:7}",  "{tmp:8}",	"{tmp:9}",
+		"{tmp:10}", "{tmp:11}", "{tmp:12}", "{tmp:13}", "{tmp:14}",
+		"{tmp:15}", "{tmp:16}", "{tmp:17}", "{tmp:18}", "{tmp:19}",
 	};
 	struct ini *ini;
 	struct plugin *source, *sink;
-	int tmp_flow_ids[MAX_DUMMY_INI_CH];
+	int tmp_flow_ids[MAX_MOCK_INI_CH];
 	int i;
 
-	if (num_channels > MAX_DUMMY_INI_CH) {
-		syslog(LOG_ERR, "Unable to create %u channels of dummy ini",
+	if (num_channels > MAX_MOCK_INI_CH) {
+		syslog(LOG_ERR, "Unable to create %u channels of mock ini",
 		       num_channels);
 		return NULL;
 	}
@@ -330,7 +331,7 @@
 	}
 
 	for (i = 0; i < num_channels; i++)
-		tmp_flow_ids[i] = add_new_flow(ini, dummy_flow_names[i]);
+		tmp_flow_ids[i] = add_new_flow(ini, mock_flow_names[i]);
 
 	source = ARRAY_APPEND_ZERO(&ini->plugins);
 	source->title = "source";
diff --git a/cras/src/server/cras_dsp_ini.h b/cras/src/server/cras_dsp_ini.h
index 117cfd2..c839d4b 100644
--- a/cras/src/server/cras_dsp_ini.h
+++ b/cras/src/server/cras_dsp_ini.h
@@ -6,12 +6,12 @@
 #ifndef CRAS_DSP_INI_H_
 #define CRAS_DSP_INI_H_
 
+#include "iniparser_wrapper.h"
+
 #ifdef __cplusplus
 extern "C" {
 #endif
 
-#include <iniparser.h>
-
 #include "array.h"
 #include "dumper.h"
 #include "cras_expr.h"
@@ -71,7 +71,7 @@
 };
 
 /*
- * Creates a dummy ini structure equivalent to:
+ * Creates a mock ini structure equivalent to:
  *
  * [src]
  * out0={tmp:0}
@@ -86,7 +86,7 @@
  * The caller of this function is responsible to free the returned
  * ini by calling cras_dsp_ini_free().
  */
-struct ini *create_dummy_ini(const char *purpose, unsigned int num_channels);
+struct ini *create_mock_ini(const char *purpose, unsigned int num_channels);
 
 /* Reads the ini file into the ini structure */
 struct ini *cras_dsp_ini_create(const char *ini_filename);
diff --git a/cras/src/server/cras_dsp_pipeline.c b/cras/src/server/cras_dsp_pipeline.c
index 945e5c3..9e492ac 100644
--- a/cras/src/server/cras_dsp_pipeline.c
+++ b/cras/src/server/cras_dsp_pipeline.c
@@ -144,8 +144,8 @@
 	int64_t total_samples;
 };
 
-static struct instance *find_instance_by_plugin(instance_array *instances,
-						struct plugin *plugin)
+static struct instance *find_instance_by_plugin(const instance_array *instances,
+						const struct plugin *plugin)
 {
 	int i;
 	struct instance *instance;
@@ -161,9 +161,9 @@
 /* Finds out where the data sent to plugin:index come from. The issue
  * we need to handle here is the previous plugin may be disabled, so
  * we need to go upstream until we find the real origin */
-static int find_origin_port(struct ini *ini, instance_array *instances,
-			    struct plugin *plugin, int index,
-			    struct plugin **origin, int *origin_index)
+static int find_origin_port(struct ini *ini, const instance_array *instances,
+			    const struct plugin *plugin, int index,
+			    const struct plugin **origin, int *origin_index)
 {
 	enum port_type type;
 	struct port *port;
@@ -226,7 +226,7 @@
 }
 
 static struct audio_port *find_output_audio_port(instance_array *instances,
-						 struct plugin *plugin,
+						 const struct plugin *plugin,
 						 int index)
 {
 	int i;
@@ -246,7 +246,7 @@
 }
 
 static struct control_port *find_output_control_port(instance_array *instances,
-						     struct plugin *plugin,
+						     const struct plugin *plugin,
 						     int index)
 {
 	int i;
@@ -317,7 +317,7 @@
 	ARRAY_ELEMENT_FOREACH (&plugin->ports, i, port) {
 		int need_connect = (port->flow_id != INVALID_FLOW_ID &&
 				    port->direction == PORT_INPUT);
-		struct plugin *origin = NULL;
+		const struct plugin *origin = NULL;
 		int origin_index = 0;
 
 		if (need_connect) {
@@ -435,6 +435,7 @@
 
 	if (rc < 0) {
 		syslog(LOG_ERR, "failed to construct pipeline");
+		cras_dsp_pipeline_free(pipeline);
 		return NULL;
 	}
 
@@ -524,6 +525,17 @@
 			peak_buf = MAX(peak_buf, need_buf);
 		}
 	}
+	/*
+	 * cras_dsp_pipeline_create creates pipeline with source and sink and it
+	 * makes sure all ports could be accessed from some sources, which means
+	 * that there is at least one source with out > 0 and in == 0.
+	 * This will give us peak_buf > 0 in the previous calculation.
+	 */
+	if (peak_buf <= 0) {
+		syslog(LOG_ERR, "peak_buf = %d, which must be greater than 0.",
+		       peak_buf);
+		return -1;
+	}
 
 	/* then allocate the buffers */
 	pipeline->peak_buf = peak_buf;
@@ -970,9 +982,10 @@
 	      pipeline->min_time);
 	dumpf(d, " max processing time per block: %" PRId64 "ns\n",
 	      pipeline->max_time);
-	dumpf(d, " cpu load: %g%%\n",
-	      pipeline->total_time * 1e-9 / pipeline->total_samples *
-		      pipeline->sample_rate * 100);
+	if (pipeline->total_samples)
+		dumpf(d, " cpu load: %g%%\n",
+		      pipeline->total_time * 1e-9 / pipeline->total_samples *
+			      pipeline->sample_rate * 100);
 	dumpf(d, " instances (%d):\n", ARRAY_COUNT(&pipeline->instances));
 	ARRAY_ELEMENT_FOREACH (&pipeline->instances, i, instance) {
 		struct dsp_module *module = instance->module;
diff --git a/cras/src/server/cras_empty_iodev.c b/cras/src/server/cras_empty_iodev.c
index 574f536..3471c75 100644
--- a/cras/src/server/cras_empty_iodev.c
+++ b/cras/src/server/cras_empty_iodev.c
@@ -15,9 +15,9 @@
 #include "cras_types.h"
 #include "utlist.h"
 
-#define EMPTY_BUFFER_SIZE (16 * 1024)
-#define EMPTY_FRAME_SIZE 4
-#define EMPTY_FRAMES (EMPTY_BUFFER_SIZE / EMPTY_FRAME_SIZE)
+#define EMPTY_BUFFER_SIZE (32 * 1024)
+#define MAX_EMPTY_FRAME_SIZE 8
+#define EMPTY_FRAMES (EMPTY_BUFFER_SIZE / MAX_EMPTY_FRAME_SIZE)
 
 static size_t empty_supported_rates[] = { 44100, 48000, 0 };
 
@@ -199,11 +199,12 @@
 	iodev->update_active_node = update_active_node;
 	iodev->no_stream = cras_iodev_default_no_stream_playback;
 
-	/* Create a dummy ionode */
+	/* Create an empty ionode */
 	node = (struct cras_ionode *)calloc(1, sizeof(*node));
 	node->dev = iodev;
 	node->type = node_type;
 	node->volume = 100;
+	node->ui_gain_scaler = 1.0f;
 	strcpy(node->name, "(default)");
 	cras_iodev_add_node(iodev, node);
 	cras_iodev_set_active_node(iodev, node);
@@ -230,6 +231,12 @@
 		iodev->info.idx = SILENT_PLAYBACK_DEVICE;
 	}
 
+	/*
+	 * Record max supported channels into cras_iodev_info.
+	 * The value is the max of empty_supported_channel_counts.
+	 */
+	iodev->info.max_supported_channels = 2;
+
 	return iodev;
 }
 
diff --git a/cras/src/server/cras_fmt_conv.c b/cras/src/server/cras_fmt_conv.c
index 35ab8ec..842529b 100644
--- a/cras/src/server/cras_fmt_conv.c
+++ b/cras/src/server/cras_fmt_conv.c
@@ -9,6 +9,7 @@
 #include <syslog.h>
 #include <endian.h>
 #include <limits.h>
+#include <math.h>
 
 #include "cras_fmt_conv.h"
 #include "cras_fmt_conv_ops.h"
@@ -58,17 +59,35 @@
 	return 1;
 }
 
-static void normalize_buf(float *buf, size_t size)
+/*
+ * Calculates the normalize_factor abs_sum(ci) from given coefficients.
+ * Since sum(ci / abs_sum(ci)) <= 1, this could prevent sample overflow while
+ * upmixing or downmixing.
+ */
+static float normalize_factor(float *buf, size_t n)
 {
 	int i;
-	float squre_sum = 0.0;
-	for (i = 0; i < size; i++)
-		squre_sum += buf[i] * buf[i];
-	for (i = 0; i < size; i++)
-		buf[i] /= squre_sum;
+	float abs_sum = 0.0;
+	for (i = 0; i < n; i++)
+		abs_sum += fabs(buf[i]);
+
+	return 1.0 / abs_sum;
 }
 
-/* Populates the down mix matrix by rules:
+/*
+ * Normalize all channels with the same factor to maintain
+ * the energy ratio between original channels.
+ */
+static void normalize(float **mtx, size_t m, size_t n, float factor)
+{
+	int i, j;
+	for (i = 0; i < m; i++)
+		for (j = 0; j < n; j++)
+			mtx[i][j] *= factor;
+}
+
+/*
+ * Populates the down mix matrix by rules:
  * 1. Front/side left(right) channel will mix to left(right) of
  *    full scale.
  * 2. Center and LFE will be split equally to left and right.
@@ -102,9 +121,46 @@
 		mtx[STEREO_L][layout[CRAS_CH_LFE]] = 0.707;
 		mtx[STEREO_R][layout[CRAS_CH_LFE]] = 0.707;
 	}
+	normalize(mtx, 2, 6, normalize_factor(mtx[STEREO_L], 6));
+}
 
-	normalize_buf(mtx[STEREO_L], 6);
-	normalize_buf(mtx[STEREO_R], 6);
+/* Populates the down mix matrix by rules:
+ * 1. Front left(right) channel will mix to the front left(right) of
+ *    full scale.
+ * 2. Rear and side left(right) channel will mix to the rear left(right) of
+ *    full scale.
+ * 3. Center will be split equally to the front left and right.
+ * 4. LFE will be split equally to the other channels.
+ */
+static void surround51_to_quad_downmix_mtx(float **mtx,
+					   int8_t layout[CRAS_CH_MAX])
+{
+	if (layout[CRAS_CH_FL] != -1 && layout[CRAS_CH_FR] != -1) {
+		mtx[CRAS_CH_FL][layout[CRAS_CH_FL]] = 1.0;
+		mtx[CRAS_CH_FR][layout[CRAS_CH_FR]] = 1.0;
+	}
+	if (layout[CRAS_CH_RL] != -1 && layout[CRAS_CH_RR] != -1) {
+		mtx[CRAS_CH_RL][layout[CRAS_CH_RL]] = 1.0;
+		mtx[CRAS_CH_RR][layout[CRAS_CH_RR]] = 1.0;
+	}
+	if (layout[CRAS_CH_SL] != -1 && layout[CRAS_CH_SR] != -1) {
+		mtx[CRAS_CH_RL][layout[CRAS_CH_SL]] = 1.0;
+		mtx[CRAS_CH_RR][layout[CRAS_CH_SR]] = 1.0;
+	}
+	if (layout[CRAS_CH_FC] != -1) {
+		/* Split 1/2 power to the front L/R */
+		mtx[CRAS_CH_FL][layout[CRAS_CH_FC]] = 0.707;
+		mtx[CRAS_CH_FR][layout[CRAS_CH_FC]] = 0.707;
+	}
+	if (layout[CRAS_CH_LFE] != -1) {
+		/* Split 1/4 power to the other channel */
+		mtx[CRAS_CH_FL][layout[CRAS_CH_LFE]] = 0.5;
+		mtx[CRAS_CH_FR][layout[CRAS_CH_LFE]] = 0.5;
+		mtx[CRAS_CH_RL][layout[CRAS_CH_LFE]] = 0.5;
+		mtx[CRAS_CH_RR][layout[CRAS_CH_LFE]] = 0.5;
+	}
+
+	normalize(mtx, 4, 6, normalize_factor(mtx[CRAS_CH_FL], 6));
 }
 
 static int is_supported_format(const struct cras_audio_format *fmt)
@@ -160,12 +216,31 @@
 	return s16_stereo_to_51(left, right, center, in, in_frames, out);
 }
 
+static size_t quad_to_51(struct cras_fmt_conv *conv, const uint8_t *in,
+			 size_t in_frames, uint8_t *out)
+{
+	size_t fl, fr, rl, rr;
+
+	fl = conv->out_fmt.channel_layout[CRAS_CH_FL];
+	fr = conv->out_fmt.channel_layout[CRAS_CH_FR];
+	rl = conv->out_fmt.channel_layout[CRAS_CH_RL];
+	rr = conv->out_fmt.channel_layout[CRAS_CH_RR];
+
+	return s16_quad_to_51(fl, fr, rl, rr, in, in_frames, out);
+}
+
 static size_t _51_to_stereo(struct cras_fmt_conv *conv, const uint8_t *in,
 			    size_t in_frames, uint8_t *out)
 {
 	return s16_51_to_stereo(in, in_frames, out);
 }
 
+static size_t _51_to_quad(struct cras_fmt_conv *conv, const uint8_t *in,
+			  size_t in_frames, uint8_t *out)
+{
+	return s16_51_to_quad(in, in_frames, out);
+}
+
 static size_t stereo_to_quad(struct cras_fmt_conv *conv, const uint8_t *in,
 			     size_t in_frames, uint8_t *out)
 {
@@ -206,6 +281,21 @@
 				      in_frames, out);
 }
 
+// Fill min(in channels, out_channels), leave the rest 0s.
+static size_t default_some_to_some(struct cras_fmt_conv *conv,
+				   const uint8_t *in,
+				   size_t in_frames,
+				   uint8_t *out)
+{
+	size_t num_in_ch, num_out_ch;
+
+	num_in_ch = conv->in_fmt.num_channels;
+	num_out_ch = conv->out_fmt.num_channels;
+
+	return s16_some_to_some(&conv->out_fmt, num_in_ch, num_out_ch, in,
+				in_frames, out);
+}
+
 static size_t convert_channels(struct cras_fmt_conv *conv, const uint8_t *in,
 			       size_t in_frames, uint8_t *out)
 {
@@ -321,7 +411,10 @@
 			conv->channel_converter = quad_to_stereo;
 		} else if (in->num_channels == 2 && out->num_channels == 6) {
 			conv->channel_converter = stereo_to_51;
-		} else if (in->num_channels == 6 && out->num_channels == 2) {
+		} else if (in->num_channels == 4 && out->num_channels == 6) {
+			conv->channel_converter = quad_to_51;
+		} else if (in->num_channels == 6 &&
+			   (out->num_channels == 2 || out->num_channels == 4)) {
 			int in_channel_layout_set = 0;
 
 			/* Checks if channel_layout is set in the incoming format */
@@ -342,28 +435,44 @@
 					return NULL;
 				}
 				conv->channel_converter = convert_channels;
-				surround51_to_stereo_downmix_mtx(
-					conv->ch_conv_mtx,
-					conv->in_fmt.channel_layout);
+				if (out->num_channels == 4) {
+					surround51_to_quad_downmix_mtx(
+						conv->ch_conv_mtx,
+						conv->in_fmt.channel_layout);
+				} else {
+					surround51_to_stereo_downmix_mtx(
+						conv->ch_conv_mtx,
+						conv->in_fmt.channel_layout);
+				}
 			} else {
-				conv->channel_converter = _51_to_stereo;
+				if (out->num_channels == 4)
+					conv->channel_converter = _51_to_quad;
+				else
+					conv->channel_converter = _51_to_stereo;
 			}
-		} else {
+		} else if (in->num_channels <= 8 && out->num_channels <= 8) {
+			// For average channel counts mix from all to all.
 			syslog(LOG_WARNING,
-			       "Using default channel map for %zu to %zu",
+			       "Using all_to_all map for %zu to %zu",
 			       in->num_channels, out->num_channels);
 			conv->channel_converter = default_all_to_all;
+		} else {
+			syslog(LOG_WARNING,
+			       "Using some_to_some channel map for %zu to %zu",
+			       in->num_channels, out->num_channels);
+			conv->channel_converter = default_some_to_some;
 		}
 	} else if (in->num_channels > 2 && !is_channel_layout_equal(in, out)) {
 		conv->num_converters++;
 		conv->ch_conv_mtx = cras_channel_conv_matrix_create(in, out);
 		if (conv->ch_conv_mtx == NULL) {
 			syslog(LOG_ERR,
-			       "Failed to create channel conversion matrix");
-			cras_fmt_conv_destroy(&conv);
-			return NULL;
+			       "Failed to create channel conversion matrix."
+			       "Fallback to default_all_to_all.");
+			conv->channel_converter = default_all_to_all;
+		} else {
+			conv->channel_converter = convert_channels;
 		}
-		conv->channel_converter = convert_channels;
 	}
 	/* Set up sample rate conversion. */
 	if (in->frame_rate != out->frame_rate) {
diff --git a/cras/src/server/cras_fmt_conv_ops.c b/cras/src/server/cras_fmt_conv_ops.c
index bf479c9..adc5521 100644
--- a/cras/src/server/cras_fmt_conv_ops.c
+++ b/cras/src/server/cras_fmt_conv_ops.c
@@ -223,6 +223,44 @@
 }
 
 /*
+ * Channel converter: quad to 5.1 surround.
+ *
+ * Fit the front left/right of input to the front left/right of output
+ * and rear left/right of input to the rear left/right of output
+ * respectively and fill others with zero.
+ */
+size_t s16_quad_to_51(size_t font_left, size_t front_right, size_t rear_left,
+		      size_t rear_right, const uint8_t *_in, size_t in_frames,
+		      uint8_t *_out)
+{
+	size_t i;
+	const int16_t *in = (const int16_t *)_in;
+	int16_t *out = (int16_t *)_out;
+
+	memset(out, 0, sizeof(*out) * 6 * in_frames);
+
+	if (font_left != -1 && front_right != -1 && rear_left != -1 &&
+	    rear_right != -1)
+		for (i = 0; i < in_frames; i++) {
+			out[6 * i + font_left] = in[4 * i];
+			out[6 * i + front_right] = in[4 * i + 1];
+			out[6 * i + rear_left] = in[4 * i + 2];
+			out[6 * i + rear_right] = in[4 * i + 3];
+		}
+	else
+		/* Use default 5.1 channel mapping for the conversion.
+		 */
+		for (i = 0; i < in_frames; i++) {
+			out[6 * i] = in[4 * i];
+			out[6 * i + 1] = in[4 * i + 1];
+			out[6 * i + 4] = in[4 * i + 2];
+			out[6 * i + 5] = in[4 * i + 3];
+		}
+
+	return in_frames;
+}
+
+/*
  * Channel converter: 5.1 surround to stereo.
  *
  * The out buffer can have room for just stereo samples. This convert function
@@ -235,20 +273,69 @@
 	int16_t *out = (int16_t *)_out;
 	static const unsigned int left_idx = 0;
 	static const unsigned int right_idx = 1;
-	/* static const unsigned int left_surround_idx = 2; */
-	/* static const unsigned int right_surround_idx = 3; */
-	static const unsigned int center_idx = 4;
-	/* static const unsigned int lfe_idx = 5; */
+	static const unsigned int center_idx = 2;
+	/* static const unsigned int lfe_idx = 3; */
+	/* static const unsigned int left_surround_idx = 4; */
+	/* static const unsigned int right_surround_idx = 5; */
+
 	size_t i;
-
+	int16_t half_center;
+	/* Use the normalized_factor from the left channel = 1 / (|1| + |0.707|)
+	 * to prevent mixing overflow.
+	 */
+	const float normalized_factor = 0.585;
 	for (i = 0; i < in_frames; i++) {
-		unsigned int half_center;
-
-		half_center = in[6 * i + center_idx] / 2;
+		half_center =
+			in[6 * i + center_idx] * 0.707 * normalized_factor;
 		out[2 * i + left_idx] =
-			s16_add_and_clip(in[6 * i + left_idx], half_center);
+			in[6 * i + left_idx] * normalized_factor + half_center;
 		out[2 * i + right_idx] =
-			s16_add_and_clip(in[6 * i + right_idx], half_center);
+			in[6 * i + right_idx] * normalized_factor + half_center;
+	}
+	return in_frames;
+}
+
+/*
+ * Channel converter: 5.1 surround to quad (front L/R, rear L/R).
+ *
+ * The out buffer can have room for just quad samples. This convert function
+ * is used as the default behavior when channel layout is not set from the
+ * client side.
+ */
+size_t s16_51_to_quad(const uint8_t *_in, size_t in_frames, uint8_t *_out)
+{
+	const int16_t *in = (const int16_t *)_in;
+	int16_t *out = (int16_t *)_out;
+	static const unsigned int l_quad = 0;
+	static const unsigned int r_quad = 1;
+	static const unsigned int rl_quad = 2;
+	static const unsigned int rr_quad = 3;
+
+	static const unsigned int l_51 = 0;
+	static const unsigned int r_51 = 1;
+	static const unsigned int center_51 = 2;
+	static const unsigned int lfe_51 = 3;
+	static const unsigned int rl_51 = 4;
+	static const unsigned int rr_51 = 5;
+
+	/* Use normalized_factor from the left channel = 1 / (|1| + |0.707| + |0.5|)
+	 * to prevent overflow. */
+	const float normalized_factor = 0.453;
+	size_t i;
+	for (i = 0; i < in_frames; i++) {
+		int16_t half_center;
+		int16_t lfe;
+
+		half_center = in[6 * i + center_51] * 0.707 * normalized_factor;
+		lfe = in[6 * i + lfe_51] * 0.5 * normalized_factor;
+		out[4 * i + l_quad] = normalized_factor * in[6 * i + l_51] +
+				      half_center + lfe;
+		out[4 * i + r_quad] = normalized_factor * in[6 * i + r_51] +
+				      half_center + lfe;
+		out[4 * i + rl_quad] =
+			normalized_factor * in[6 * i + rl_51] + lfe;
+		out[4 * i + rr_quad] =
+			normalized_factor * in[6 * i + rr_51] + lfe;
 	}
 	return in_frames;
 }
@@ -331,20 +418,50 @@
 	unsigned int in_ch, out_ch, i;
 	const int16_t *in = (const int16_t *)_in;
 	int16_t *out = (int16_t *)_out;
+	int32_t sum;
 
-	memset(out, 0, in_frames * cras_get_format_bytes(out_fmt));
-	for (out_ch = 0; out_ch < num_out_ch; out_ch++) {
+	for (i = 0; i < in_frames; i++) {
+		sum = 0;
 		for (in_ch = 0; in_ch < num_in_ch; in_ch++) {
-			for (i = 0; i < in_frames; i++) {
-				out[out_ch + i * num_out_ch] +=
-					in[in_ch + i * num_in_ch] / num_in_ch;
-			}
+			sum += (int32_t)in[in_ch + i * num_in_ch];
+		}
+		/*
+		 * 1. Divide `int32_t` by `size_t` without an explicit
+		 *    conversion will generate corrupted results.
+		 * 2. After the division, `sum` should be in the range of
+		 *    int16_t. No clipping is needed.
+		 */
+		sum /= (int32_t)num_in_ch;
+		for (out_ch = 0; out_ch < num_out_ch; out_ch++) {
+			out[out_ch + i * num_out_ch] = (int16_t)sum;
 		}
 	}
 	return in_frames;
 }
 
 /*
+ * Copies the input channels across output channels. Drops input channels that
+ * don't fit. Ignores output channels greater than the number of input channels.
+ */
+size_t s16_some_to_some(const struct cras_audio_format *out_fmt,
+			const size_t num_in_ch, const size_t num_out_ch,
+			const uint8_t *_in, const size_t frame_count,
+			uint8_t *_out)
+{
+	unsigned int i;
+	const int16_t *in = (const int16_t *)_in;
+	int16_t *out = (int16_t *)_out;
+	const size_t num_copy_ch = MIN(num_in_ch, num_out_ch);
+
+	memset(out, 0, frame_count * cras_get_format_bytes(out_fmt));
+	for (i = 0; i < frame_count; i++, out += num_out_ch, in += num_in_ch) {
+		memcpy(out, in, num_copy_ch * sizeof(int16_t));
+	}
+
+	return frame_count;
+}
+
+/*
  * Multiplies buffer vector with coefficient vector.
  */
 int16_t s16_multiply_buf_with_coef(float *coef, const int16_t *buf, size_t size)
diff --git a/cras/src/server/cras_fmt_conv_ops.h b/cras/src/server/cras_fmt_conv_ops.h
index 6943f23..0af7564 100644
--- a/cras/src/server/cras_fmt_conv_ops.h
+++ b/cras/src/server/cras_fmt_conv_ops.h
@@ -46,11 +46,23 @@
 			const uint8_t *in, size_t in_frames, uint8_t *out);
 
 /*
+ * Channel converter: quad to 5.1 surround.
+ */
+size_t s16_quad_to_51(size_t font_left, size_t front_right, size_t rear_left,
+		      size_t rear_right, const uint8_t *in, size_t in_frames,
+		      uint8_t *out);
+
+/*
  * Channel converter: 5.1 surround to stereo.
  */
 size_t s16_51_to_stereo(const uint8_t *in, size_t in_frames, uint8_t *out);
 
 /*
+ * Channel converter: 5.1 surround to quad.
+ */
+size_t s16_51_to_quad(const uint8_t *in, size_t in_frames, uint8_t *out);
+
+/*
  * Channel converter: stereo to quad (front L/R, rear L/R).
  */
 size_t s16_stereo_to_quad(size_t front_left, size_t front_right,
@@ -73,6 +85,15 @@
 			      uint8_t *out);
 
 /*
+ * Channel converter: N channels to M channels filling min(N,M) channels by
+ * directly copying to the destination.
+ */
+size_t s16_some_to_some(const struct cras_audio_format *out_fmt,
+			const size_t num_in_ch, const size_t num_out_ch,
+			const uint8_t *_in, const size_t frame_count,
+			uint8_t *_out);
+
+/*
  * Multiplies buffer vector with coefficient vector.
  */
 int16_t s16_multiply_buf_with_coef(float *coef, const int16_t *buf,
diff --git a/cras/src/server/cras_hfp_ag_profile.c b/cras/src/server/cras_hfp_ag_profile.c
index 6320191..b5fcecc 100644
--- a/cras/src/server/cras_hfp_ag_profile.c
+++ b/cras/src/server/cras_hfp_ag_profile.c
@@ -21,10 +21,11 @@
 #include "cras_system_state.h"
 #include "cras_iodev_list.h"
 #include "utlist.h"
+#include "packet_status_logger.h"
 
 #define HFP_AG_PROFILE_NAME "Hands-Free Voice gateway"
 #define HFP_AG_PROFILE_PATH "/org/chromium/Cras/Bluetooth/HFPAG"
-#define HFP_VERSION_1_5 0x0105
+#define HFP_VERSION 0x0107
 #define HSP_AG_PROFILE_NAME "Headset Voice gateway"
 #define HSP_AG_PROFILE_PATH "/org/chromium/Cras/Bluetooth/HSPAG"
 #define HSP_VERSION_1_2 0x0102
@@ -71,6 +72,12 @@
 	"  </attribute>"                                                       \
 	"</record>"
 
+/* The supported features value in +BSRF command response of HFP AG in CRAS */
+#define BSRF_SUPPORTED_FEATURES (AG_ENHANCED_CALL_STATUS | AG_HF_INDICATORS)
+
+/* The "SupportedFeatures" attribute value of HFP AG service record in CRAS. */
+#define SDP_SUPPORTED_FEATURES FEATURES_AG_WIDE_BAND_SPEECH
+
 /* Object representing the audio gateway role for HFP/HSP.
  * Members:
  *    idev - The input iodev for HFP/HSP.
@@ -96,6 +103,7 @@
 };
 
 static struct audio_gateway *connected_ags;
+static struct packet_status_logger wbs_logger;
 
 static int need_go_sco_pcm(struct cras_bt_device *device)
 {
@@ -107,6 +115,9 @@
 {
 	DL_DELETE(connected_ags, ag);
 
+	cras_server_metrics_hfp_battery_indicator(
+		hfp_slc_get_hf_supports_battery_indicator(ag->slc_handle));
+
 	if (need_go_sco_pcm(ag->device)) {
 		if (ag->idev)
 			hfp_alsa_iodev_destroy(ag->idev);
@@ -164,6 +175,14 @@
 	cras_server_metrics_hfp_wideband_support(
 		hfp_slc_get_hf_codec_negotiation_supported(handle));
 
+	/* Log the final selected codec given that codec negotiation is
+	 * supported.
+	 */
+	if (hfp_slc_get_hf_codec_negotiation_supported(handle) &&
+	    hfp_slc_get_ag_codec_negotiation_supported(handle))
+		cras_server_metrics_hfp_wideband_selected_codec(
+			hfp_slc_get_selected_codec(handle));
+
 	/* Defer the starting of audio gateway to bt_device. */
 	return cras_bt_device_audio_gateway_initialized(ag->device);
 }
@@ -249,8 +268,8 @@
 	 * TODO(hychao): AND the two conditions to let bluetooth daemon
 	 * control whether to turn on WBS feature.
 	 */
-	ag_features = profile->features;
-	if (cras_system_get_bt_wbs_enabled() &&
+	ag_features = BSRF_SUPPORTED_FEATURES;
+	if (cras_system_get_bt_wbs_enabled() && adapter &&
 	    cras_bt_adapter_wbs_supported(adapter))
 		ag_features |= AG_CODEC_NEGOTIATION;
 
@@ -270,10 +289,10 @@
 
 	DL_FOREACH (connected_ags, ag) {
 		if (ag->slc_handle && ag->device == device) {
-			destroy_audio_gateway(ag);
 			cras_bt_device_notify_profile_dropped(
 				ag->device,
 				CRAS_BT_DEVICE_PROFILE_HFP_HANDSFREE);
+			destroy_audio_gateway(ag);
 		}
 	}
 }
@@ -286,9 +305,9 @@
 	.name = HFP_AG_PROFILE_NAME,
 	.object_path = HFP_AG_PROFILE_PATH,
 	.uuid = HFP_AG_UUID,
-	.version = HFP_VERSION_1_5,
+	.version = HFP_VERSION,
 	.role = NULL,
-	.features = CRAS_AG_SUPPORTED_FEATURES & 0x1F,
+	.features = SDP_SUPPORTED_FEATURES,
 	.record = NULL,
 	.release = cras_hfp_ag_release,
 	.new_connection = cras_hfp_ag_new_connection,
@@ -325,8 +344,9 @@
 	ag->device = device;
 	ag->conn = conn;
 	ag->profile = cras_bt_device_profile_from_uuid(profile->uuid);
-	ag->slc_handle = hfp_slc_create(rfcomm_fd, 1, profile->features, device,
-					NULL, cras_hfp_ag_slc_disconnected);
+	ag->slc_handle =
+		hfp_slc_create(rfcomm_fd, 1, BSRF_SUPPORTED_FEATURES, device,
+			       NULL, cras_hfp_ag_slc_disconnected);
 	DL_APPEND(connected_ags, ag);
 	cras_hfp_ag_slc_initialized(ag->slc_handle);
 	return 0;
@@ -341,9 +361,9 @@
 
 	DL_FOREACH (connected_ags, ag) {
 		if (ag->slc_handle && ag->device == device) {
-			destroy_audio_gateway(ag);
 			cras_bt_device_notify_profile_dropped(
 				ag->device, CRAS_BT_DEVICE_PROFILE_HSP_HEADSET);
+			destroy_audio_gateway(ag);
 		}
 	}
 }
@@ -391,8 +411,8 @@
 		ag->odev = hfp_alsa_iodev_create(out_aio, ag->device,
 						 ag->slc_handle, ag->profile);
 	} else {
-		ag->info = hfp_info_create(
-			hfp_slc_get_selected_codec(ag->slc_handle));
+		ag->info = hfp_info_create();
+		hfp_info_set_wbs_logger(ag->info, &wbs_logger);
 		ag->idev =
 			hfp_iodev_create(CRAS_STREAM_INPUT, ag->device,
 					 ag->slc_handle, ag->profile, ag->info);
@@ -435,6 +455,11 @@
 	return NULL;
 }
 
+struct packet_status_logger *cras_hfp_ag_get_wbs_logger()
+{
+	return &wbs_logger;
+}
+
 int cras_hsp_ag_profile_create(DBusConnection *conn)
 {
 	return cras_bt_add_profile(conn, &cras_hsp_ag_profile);
diff --git a/cras/src/server/cras_hfp_ag_profile.h b/cras/src/server/cras_hfp_ag_profile.h
index 4c1d49d..3de5618 100644
--- a/cras/src/server/cras_hfp_ag_profile.h
+++ b/cras/src/server/cras_hfp_ag_profile.h
@@ -7,12 +7,22 @@
 #define CRAS_HFP_AG_PROFILE_H_
 
 #include <dbus/dbus.h>
+#include <stdbool.h>
 
 #include "cras_bt_device.h"
 #include "cras_hfp_slc.h"
 
-/* The bitmap of HFP AG feature supported by CRAS */
-#define CRAS_AG_SUPPORTED_FEATURES (AG_ENHANCED_CALL_STATUS)
+/*
+ * For service record profile, 'SupportedFearues' attribute bit mapping
+ * for HFP AG. Bits 0 to 4 are identical to the unsolicited result code
+ * of +BRSF command.
+ */
+#define FEATURES_AG_THREE_WAY_CALLING 0x0001
+#define FEATURES_AG_EC_ANDOR_NR 0x0002
+#define FEATURES_AG_VOICE_RECOGNITION 0x0004
+#define FEATURES_AG_INBAND_RINGTONE 0x0008
+#define FEATURES_AG_ATTACH_NUMBER_TO_VOICETAG 0x0010
+#define FEATURES_AG_WIDE_BAND_SPEECH 0x0020
 
 struct hfp_slc_handle;
 
@@ -43,4 +53,7 @@
 /* Gets the SLC handle for given cras_bt_device. */
 struct hfp_slc_handle *cras_hfp_ag_get_slc(struct cras_bt_device *device);
 
+/* Gets the logger for WBS packet status. */
+struct packet_status_logger *cras_hfp_ag_get_wbs_logger();
+
 #endif /* CRAS_HFP_AG_PROFILE_H_ */
diff --git a/cras/src/server/cras_hfp_alsa_iodev.c b/cras/src/server/cras_hfp_alsa_iodev.c
index aecc47b..c1b60b3 100644
--- a/cras/src/server/cras_hfp_alsa_iodev.c
+++ b/cras/src/server/cras_hfp_alsa_iodev.c
@@ -12,7 +12,6 @@
 #include "cras_iodev.h"
 #include "cras_system_state.h"
 #include "cras_util.h"
-#include "sfh.h"
 #include "utlist.h"
 #include "cras_bt_device.h"
 
@@ -33,6 +32,15 @@
 	struct cras_iodev *aio;
 };
 
+static int hfp_alsa_get_valid_frames(struct cras_iodev *iodev,
+				     struct timespec *hw_tstamp)
+{
+	struct hfp_alsa_io *hfp_alsa_io = (struct hfp_alsa_io *)iodev;
+	struct cras_iodev *aio = hfp_alsa_io->aio;
+
+	return aio->get_valid_frames(aio, hw_tstamp);
+}
+
 static int hfp_alsa_open_dev(struct cras_iodev *iodev)
 {
 	struct hfp_alsa_io *hfp_alsa_io = (struct hfp_alsa_io *)iodev;
@@ -43,41 +51,7 @@
 
 static int hfp_alsa_update_supported_formats(struct cras_iodev *iodev)
 {
-	struct hfp_alsa_io *hfp_alsa_io = (struct hfp_alsa_io *)iodev;
-	struct cras_iodev *aio = hfp_alsa_io->aio;
-	int rc, i;
-
 	/* 16 bit, mono, 8kHz (narrow band speech); */
-	rc = aio->update_supported_formats(aio);
-	if (rc)
-		return rc;
-
-	for (i = 0; aio->supported_rates[i]; ++i)
-		if (aio->supported_rates[i] == 8000)
-			break;
-	if (aio->supported_rates[i] != 8000)
-		return -EINVAL;
-
-	for (i = 0; aio->supported_channel_counts[i]; ++i)
-		if (aio->supported_channel_counts[i] == 1)
-			break;
-	if (aio->supported_channel_counts[i] != 1)
-		return -EINVAL;
-
-	for (i = 0; aio->supported_formats[i]; ++i)
-		if (aio->supported_formats[i] == SND_PCM_FORMAT_S16_LE)
-			break;
-	if (aio->supported_formats[i] != SND_PCM_FORMAT_S16_LE)
-		return -EINVAL;
-
-	free(aio->format);
-	aio->format = malloc(sizeof(struct cras_audio_format));
-	if (!aio->format)
-		return -ENOMEM;
-	aio->format->format = SND_PCM_FORMAT_S16_LE;
-	aio->format->frame_rate = 8000;
-	aio->format->num_channels = 1;
-
 	free(iodev->supported_rates);
 	iodev->supported_rates = malloc(2 * sizeof(*iodev->supported_rates));
 	if (!iodev->supported_rates)
@@ -110,6 +84,15 @@
 	struct cras_iodev *aio = hfp_alsa_io->aio;
 	int rc;
 
+	/* Fill back the format iodev is using. */
+	if (aio->format == NULL) {
+		aio->format = (struct cras_audio_format *)malloc(
+			sizeof(*aio->format));
+		if (!aio->format)
+			return -ENOMEM;
+		*aio->format = *iodev->format;
+	}
+
 	rc = aio->configure_dev(aio);
 	if (rc) {
 		syslog(LOG_ERR, "Failed to configure aio: %d\n", rc);
@@ -135,6 +118,7 @@
 	struct hfp_alsa_io *hfp_alsa_io = (struct hfp_alsa_io *)iodev;
 	struct cras_iodev *aio = hfp_alsa_io->aio;
 
+	hfp_set_call_status(hfp_alsa_io->slc, 0);
 	cras_bt_device_put_sco(hfp_alsa_io->device);
 	cras_iodev_free_format(iodev);
 	return aio->close_dev(aio);
@@ -234,6 +218,21 @@
 	return aio->is_free_running(aio);
 }
 
+static int hfp_alsa_output_underrun(struct cras_iodev *iodev)
+{
+	struct hfp_alsa_io *hfp_alsa_io = (struct hfp_alsa_io *)iodev;
+	struct cras_iodev *aio = hfp_alsa_io->aio;
+
+	/*
+	 * Copy iodev->min_cb_level and iodev->max_cb_level from the parent
+	 * (i.e. hfp_alsa_iodev).  output_underrun() of alsa_io will use them.
+	 */
+	aio->min_cb_level = iodev->min_cb_level;
+	aio->max_cb_level = iodev->max_cb_level;
+
+	return aio->output_underrun(aio);
+}
+
 struct cras_iodev *hfp_alsa_iodev_create(struct cras_iodev *aio,
 					 struct cras_bt_device *device,
 					 struct hfp_slc_handle *slc,
@@ -259,13 +258,9 @@
 	name = cras_bt_device_name(device);
 	if (!name)
 		name = cras_bt_device_object_path(device);
-	snprintf(iodev->info.name, sizeof(iodev->info.name), "%s.HFP_PCM",
-		 name);
+	snprintf(iodev->info.name, sizeof(iodev->info.name), "%s", name);
 	iodev->info.name[ARRAY_SIZE(iodev->info.name) - 1] = 0;
-	iodev->info.stable_id =
-		SuperFastHash(cras_bt_device_object_path(device),
-			      strlen(cras_bt_device_object_path(device)),
-			      strlen(cras_bt_device_object_path(device)));
+	iodev->info.stable_id = cras_bt_device_get_stable_id(device);
 
 	iodev->open_dev = hfp_alsa_open_dev;
 	iodev->update_supported_formats = hfp_alsa_update_supported_formats;
@@ -281,8 +276,10 @@
 	iodev->update_active_node = hfp_alsa_update_active_node;
 	iodev->start = hfp_alsa_start;
 	iodev->set_volume = hfp_alsa_set_volume;
+	iodev->get_valid_frames = hfp_alsa_get_valid_frames;
 	iodev->no_stream = hfp_alsa_no_stream;
 	iodev->is_free_running = hfp_alsa_is_free_running;
+	iodev->output_underrun = hfp_alsa_output_underrun;
 
 	iodev->min_buffer_level = aio->min_buffer_level;
 
@@ -291,13 +288,28 @@
 	strcpy(node->name, iodev->info.name);
 
 	node->plugged = 1;
+	/* If headset mic uses legacy narrow band, i.e CVSD codec, report a
+	 * different node type so UI can set different plug priority. */
 	node->type = CRAS_NODE_TYPE_BLUETOOTH;
+	if ((hfp_slc_get_selected_codec(hfp_alsa_io->slc) ==
+	     HFP_CODEC_ID_CVSD) &&
+	    (iodev->direction == CRAS_STREAM_INPUT))
+		node->type = CRAS_NODE_TYPE_BLUETOOTH_NB_MIC;
 	node->volume = 100;
 	gettimeofday(&node->plugged_time, NULL);
 
-	cras_bt_device_append_iodev(device, iodev, profile);
+	/* Prepare active node before append, so bt_io can extract correct
+	 * info from hfp_alsa iodev and node. */
 	cras_iodev_add_node(iodev, node);
 	cras_iodev_set_active_node(iodev, node);
+	cras_bt_device_append_iodev(device, iodev, profile);
+
+	/* Record max supported channels into cras_iodev_info. */
+	iodev->info.max_supported_channels = 1;
+
+	/* Specifically disable EWMA calculation on this and the child iodev. */
+	ewma_power_disable(&iodev->ewma);
+	ewma_power_disable(&aio->ewma);
 
 	return iodev;
 }
diff --git a/cras/src/server/cras_hfp_info.c b/cras/src/server/cras_hfp_info.c
index 02bc8b4..fc407b2 100644
--- a/cras/src/server/cras_hfp_info.c
+++ b/cras/src/server/cras_hfp_info.c
@@ -10,6 +10,7 @@
 #include <syslog.h>
 
 #include "audio_thread.h"
+#include "bluetooth.h"
 #include "byte_buffer.h"
 #include "cras_hfp_info.h"
 #include "cras_hfp_slc.h"
@@ -18,6 +19,7 @@
 #include "cras_sbc_codec.h"
 #include "cras_server_metrics.h"
 #include "utlist.h"
+#include "packet_status_logger.h"
 
 /* The max buffer size. Note that the actual used size must set to multiple
  * of SCO packet size, and the packet size does not necessarily be equal to
@@ -39,12 +41,21 @@
 /* For one mSBC 1 compressed wideband audio channel the HCI packets will
  * be 3 octets of HCI header + 60 octets of data. */
 #define MSBC_PKT_SIZE 60
-#define WRITE_BUF_SIZE_BYTES MSBC_PKT_SIZE
-#define HCI_SCO_HDR_SIZE_BYTES 3
-#define HCI_SCO_PKT_SIZE (MSBC_PKT_SIZE + HCI_SCO_HDR_SIZE_BYTES)
 
 #define H2_HEADER_0 0x01
 
+/* Supported HCI SCO packet sizes. The wideband speech mSBC frame parsing
+ * code ties to limited packet size values. Specifically list them out
+ * to check against when setting packet size.
+ *
+ * Temp buffer size should be set to least common multiple of HCI SCO packet
+ * size and MSBC_PKT_SIZE for optimizing buffer copy.
+ * To add a new supported packet size value, add corresponding entry to the
+ * lists, test the read/write msbc code, and fix the code if needed.
+ */
+static const size_t wbs_supported_packet_size[] = { 60, 24, 0 };
+static const size_t wbs_hci_sco_buffer_size[] = { 60, 120, 0 };
+
 /* Second octet of H2 header is composed by 4 bits fixed 0x8 and 4 bits
  * sequence number 0000, 0011, 1100, 1111. */
 static const uint8_t h2_header_frames_count[] = { 0x08, 0x38, 0xc8, 0xf8 };
@@ -70,11 +81,20 @@
  *     read_cb - Callback to call when SCO socket can read. It returns the
  *         number of PCM bytes read.
  *     write_cb - Callback to call when SCO socket can write.
- *     hci_sco_buf - Buffer to read one HCI SCO packet.
+ *     write_buf - Temp buffer for writeing HCI SCO packet in wideband.
+ *     read_buf - Temp buffer for reading HCI SCO packet in wideband.
  *     input_format_bytes - The audio format bytes for input device. 0 means
  *         there is no input device for the hfp_info.
  *     output_format_bytes - The audio format bytes for output device. 0 means
  *         there is no output device for the hfp_info.
+ *     write_wp - Write pointer of write_buf.
+ *     write_rp - Read pointer of write_buf.
+ *     read_wp - Write pointer of read_buf.
+ *     read_rp - Read pointer of read_buf.
+ *     read_align_cb - Callback used to align mSBC frame reading with read buf.
+ *     msbc_read_current_corrupted - Flag to mark if the current mSBC frame
+ *         read is corrupted.
+ *     wbs_logger - The logger for packet status in WBS.
  */
 struct hfp_info {
 	int fd;
@@ -91,10 +111,17 @@
 	unsigned int msbc_num_lost_frames;
 	int (*read_cb)(struct hfp_info *info);
 	int (*write_cb)(struct hfp_info *info);
-	uint8_t write_buf[WRITE_BUF_SIZE_BYTES];
-	uint8_t hci_sco_buf[HCI_SCO_PKT_SIZE];
+	uint8_t *write_buf;
+	uint8_t *read_buf;
 	size_t input_format_bytes;
 	size_t output_format_bytes;
+	size_t write_wp;
+	size_t write_rp;
+	size_t read_wp;
+	size_t read_rp;
+	int (*read_align_cb)(uint8_t *buf);
+	bool msbc_read_current_corrupted;
+	struct packet_status_logger *wbs_logger;
 };
 
 int hfp_info_add_iodev(struct hfp_info *info,
@@ -239,43 +266,66 @@
 	size_t encoded;
 	int err;
 	int pcm_encoded;
-	unsigned int pcm_avail;
+	unsigned int pcm_avail, to_write;
 	uint8_t *samples;
 	uint8_t *wp;
 
+	if (info->write_rp + info->packet_size <= info->write_wp)
+		goto msbc_send_again;
+
+	/* Make sure there are MSBC_CODE_SIZE bytes to encode. */
 	samples = buf_read_pointer_size(info->playback_buf, &pcm_avail);
-	wp = info->write_buf;
-	if (pcm_avail >= MSBC_CODE_SIZE) {
-		/* Encode more */
-		wp[0] = H2_HEADER_0;
-		wp[1] = h2_header_frames_count[info->msbc_num_out_frames % 4];
-		pcm_encoded = info->msbc_write->encode(
-			info->msbc_write, samples, pcm_avail,
-			wp + MSBC_H2_HEADER_LEN,
-			WRITE_BUF_SIZE_BYTES - MSBC_H2_HEADER_LEN, &encoded);
-		if (pcm_encoded < 0) {
-			syslog(LOG_ERR, "msbc encoding err: %s",
-			       strerror(pcm_encoded));
-			return pcm_encoded;
-		}
-		buf_increment_read(info->playback_buf, pcm_encoded);
-		pcm_avail -= pcm_encoded;
-	} else {
-		memset(wp, 0, WRITE_BUF_SIZE_BYTES);
+	if (pcm_avail < MSBC_CODE_SIZE) {
+		to_write = MSBC_CODE_SIZE - pcm_avail;
+		/*
+		 * Size of playback_buf is multiple of MSBC_CODE_SIZE so we
+		 * are safe to prepare the buffer by appending some zero bytes.
+		 */
+		wp = buf_write_pointer_size(info->playback_buf, &pcm_avail);
+		memset(wp, 0, to_write);
+		buf_increment_write(info->playback_buf, to_write);
+
+		samples = buf_read_pointer_size(info->playback_buf, &pcm_avail);
+		if (pcm_avail < MSBC_CODE_SIZE)
+			return -EINVAL;
 	}
 
+	/* Encode the next MSBC_CODE_SIZE of bytes. */
+	wp = info->write_buf + info->write_wp;
+	wp[0] = H2_HEADER_0;
+	wp[1] = h2_header_frames_count[info->msbc_num_out_frames % 4];
+	pcm_encoded = info->msbc_write->encode(
+		info->msbc_write, samples, pcm_avail, wp + MSBC_H2_HEADER_LEN,
+		MSBC_PKT_SIZE - MSBC_H2_HEADER_LEN, &encoded);
+	if (pcm_encoded < 0) {
+		syslog(LOG_ERR, "msbc encoding err: %s", strerror(pcm_encoded));
+		return pcm_encoded;
+	}
+	buf_increment_read(info->playback_buf, pcm_encoded);
+	pcm_avail -= pcm_encoded;
+	info->write_wp += MSBC_PKT_SIZE;
+	info->msbc_num_out_frames++;
+
+	if (info->write_rp + info->packet_size > info->write_wp)
+		return 0;
+
 msbc_send_again:
-	err = send(info->fd, info->write_buf, MSBC_PKT_SIZE, 0);
+	err = send(info->fd, info->write_buf + info->write_rp,
+		   info->packet_size, 0);
 	if (err < 0) {
 		if (errno == EINTR)
 			goto msbc_send_again;
 		return err;
 	}
-	if (err != MSBC_PKT_SIZE) {
+	if (err != (int)info->packet_size) {
 		syslog(LOG_ERR, "Partially write %d bytes for mSBC", err);
 		return -1;
 	}
-	info->msbc_num_out_frames++;
+	info->write_rp += info->packet_size;
+	if (info->write_rp == info->write_wp) {
+		info->write_rp = 0;
+		info->write_wp = 0;
+	}
 
 	return err;
 }
@@ -356,6 +406,20 @@
 	return NULL;
 }
 
+/* Log value 0 when packet is received. */
+static void log_wbs_packet_received(struct hfp_info *info)
+{
+	if (info->wbs_logger)
+		packet_status_logger_update(info->wbs_logger, 0);
+}
+
+/* Log value 1 when packet is lost. */
+static void log_wbs_packet_lost(struct hfp_info *info)
+{
+	if (info->wbs_logger)
+		packet_status_logger_update(info->wbs_logger, 1);
+}
+
 /*
  * Handle the case when mSBC frame is considered lost.
  * Args:
@@ -372,6 +436,8 @@
 	info->msbc_num_in_frames++;
 	info->msbc_num_lost_frames++;
 
+	log_wbs_packet_lost(info);
+
 	in_bytes = buf_write_pointer_size(info->capture_buf, &pcm_avail);
 	if (pcm_avail < MSBC_CODE_SIZE)
 		return 0;
@@ -386,6 +452,16 @@
 	return decoded;
 }
 
+/* Checks if mSBC frame header aligns with the beginning of buffer. */
+static int msbc_frame_align(uint8_t *buf)
+{
+	if ((buf[0] != H2_HEADER_0) || (buf[2] != MSBC_SYNC_WORD)) {
+		syslog(LOG_DEBUG, "Waiting for valid mSBC frame head");
+		return 0;
+	}
+	return 1;
+}
+
 int hfp_read_msbc(struct hfp_info *info)
 {
 	int err = 0;
@@ -397,8 +473,23 @@
 	const uint8_t *frame_head = NULL;
 	unsigned int seq;
 
+	struct msghdr msg = { 0 };
+	struct iovec iov;
+	struct cmsghdr *cmsg;
+	const unsigned int control_size = CMSG_SPACE(sizeof(int));
+	char control[control_size];
+	uint8_t pkt_status;
+
+	memset(control, 0, sizeof(control));
 recv_msbc_bytes:
-	err = recv(info->fd, info->hci_sco_buf, HCI_SCO_PKT_SIZE, 0);
+	msg.msg_iov = &iov;
+	msg.msg_iovlen = 1;
+	iov.iov_base = info->read_buf + info->read_wp;
+	iov.iov_len = info->packet_size;
+	msg.msg_control = control;
+	msg.msg_controllen = control_size;
+
+	err = recvmsg(info->fd, &msg, 0);
 	if (err < 0) {
 		syslog(LOG_ERR, "HCI SCO packet read err %s", strerror(errno));
 		if (errno == EINTR)
@@ -409,40 +500,78 @@
 	 * Treat return code 0 (socket shutdown) as error here. BT stack
 	 * shall send signal to main thread for device disconnection.
 	 */
-	if (err != HCI_SCO_PKT_SIZE) {
+	if (err != (int)info->packet_size) {
 		syslog(LOG_ERR, "Partially read %d bytes for mSBC packet", err);
 		return -1;
 	}
 
+	/* Offset in input data breaks mSBC frame parsing. Discard this packet
+	 * until read alignment succeed. */
+	if (info->read_align_cb) {
+		if (!info->read_align_cb(info->read_buf))
+			return 0;
+		else
+			info->read_align_cb = NULL;
+	}
+	info->read_wp += err;
+
+	pkt_status = 0;
+	for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
+	     cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+		if (cmsg->cmsg_level == SOL_BLUETOOTH &&
+		    cmsg->cmsg_type == BT_SCM_PKT_STATUS) {
+			size_t len = cmsg->cmsg_len - sizeof(*cmsg);
+			memcpy(&pkt_status, CMSG_DATA(cmsg), len);
+		}
+	}
+
 	/*
 	 * HCI SCO packet status flag:
 	 * 0x00 - correctly received data.
 	 * 0x01 - possibly invalid data.
 	 * 0x10 - No data received.
 	 * 0x11 - Data partially lost.
+	 *
+	 * If the latest SCO packet read doesn't cross the boundary of a mSBC
+	 * frame, the packet status flag can be used to derive if the current
+	 * mSBC frame is corrupted.
 	 */
-	err = (info->hci_sco_buf[1] >> 4);
-	if (err) {
-		syslog(LOG_ERR, "HCI SCO status flag %u", err);
-		return handle_packet_loss(info);
+	if (info->read_rp + MSBC_PKT_SIZE >= info->read_wp)
+		info->msbc_read_current_corrupted |= (pkt_status > 0);
+
+	/* Read buffer not enough to parse another mSBC frame. */
+	if (info->read_rp + MSBC_PKT_SIZE > info->read_wp)
+		return 0;
+
+	if (info->msbc_read_current_corrupted) {
+		syslog(LOG_DEBUG, "mSBC frame corrputed from packet status");
+		info->msbc_read_current_corrupted = 0;
+		frame_head = NULL;
+	} else {
+		frame_head =
+			extract_msbc_frame(info->read_buf + info->read_rp,
+					   info->read_wp - info->read_rp, &seq);
+		if (!frame_head)
+			syslog(LOG_DEBUG, "Failed to extract msbc frame");
 	}
 
-	/* There is chance that erroneous data reporting gives us false positive.
-	 * If mSBC frame extraction fails, we shall handle it as packet loss.
+	/*
+	 * Done with parsing the raw bytes just read. If mSBC frame head not
+	 * found, we shall handle it as packet loss.
 	 */
-	frame_head =
-		extract_msbc_frame(info->hci_sco_buf + HCI_SCO_HDR_SIZE_BYTES,
-				   MSBC_PKT_SIZE, &seq);
-	if (!frame_head) {
-		syslog(LOG_ERR, "Failed to extract msbc frame");
-		return handle_packet_loss(info);
+	info->read_rp += MSBC_PKT_SIZE;
+	if (info->read_rp == info->read_wp) {
+		info->read_rp = 0;
+		info->read_wp = 0;
 	}
+	if (!frame_head)
+		return handle_packet_loss(info);
 
 	/*
 	 * Consider packet loss when found discontinuity in sequence number.
 	 */
 	while (seq != (info->msbc_num_in_frames % 4)) {
-		syslog(LOG_ERR, "SCO packet seq unmatch");
+		syslog(LOG_DEBUG, "SCO packet seq unmatch");
 		err = handle_packet_loss(info);
 		if (err < 0)
 			return err;
@@ -470,6 +599,7 @@
 		pcm_read += err;
 	} else {
 		/* Good mSBC frame decoded. */
+		log_wbs_packet_received(info);
 		buf_increment_write(info->capture_buf, pcm_decoded);
 		info->msbc_num_in_frames++;
 		cras_msbc_plc_handle_good_frames(info->msbc_plc, capture_buf,
@@ -531,24 +661,31 @@
  * 2. When input device not attached, ignore the data just read.
  * 3. When output device attached, write one chunk of MTU bytes of data.
  */
-static int hfp_info_callback(void *arg)
+static int hfp_info_callback(void *arg, int revents)
 {
 	struct hfp_info *info = (struct hfp_info *)arg;
-	int err;
+	int err = 0;
 
 	if (!info->started)
 		return 0;
 
-	err = info->read_cb(info);
-	if (err < 0) {
-		syslog(LOG_ERR, "Read error");
-		goto read_write_error;
+	/* Allow last read before handling error or hang-up events. */
+	if (revents & POLLIN) {
+		err = info->read_cb(info);
+		if (err < 0) {
+			syslog(LOG_ERR, "Read error");
+			goto read_write_error;
+		}
 	}
-
 	/* Ignore the bytes just read if input dev not in present */
 	if (!info->input_format_bytes)
 		buf_increment_read(info->capture_buf, err);
 
+	if (revents & (POLLERR | POLLHUP)) {
+		syslog(LOG_ERR, "Error polling SCO socket, revent %d", revents);
+		goto read_write_error;
+	}
+
 	/* Without output stream's presence, we shall still send zero packets
 	 * to HF. This is required for some HF devices to start sending non-zero
 	 * data to AG.
@@ -578,7 +715,7 @@
 	return 0;
 }
 
-struct hfp_info *hfp_info_create(int codec)
+struct hfp_info *hfp_info_create()
 {
 	struct hfp_info *info;
 	info = (struct hfp_info *)calloc(1, sizeof(*info));
@@ -593,17 +730,6 @@
 	if (!info->playback_buf)
 		goto error;
 
-	if (codec == HFP_CODEC_ID_MSBC) {
-		info->write_cb = hfp_write_msbc;
-		info->read_cb = hfp_read_msbc;
-		info->msbc_read = cras_msbc_codec_create();
-		info->msbc_write = cras_msbc_codec_create();
-		info->msbc_plc = cras_msbc_plc_create();
-	} else {
-		info->write_cb = hfp_write;
-		info->read_cb = hfp_read;
-	}
-
 	return info;
 
 error:
@@ -617,12 +743,18 @@
 	return NULL;
 }
 
+void hfp_info_set_wbs_logger(struct hfp_info *info,
+			     struct packet_status_logger *wbs_logger)
+{
+	info->wbs_logger = wbs_logger;
+}
+
 int hfp_info_running(struct hfp_info *info)
 {
 	return info->started;
 }
 
-int hfp_info_start(int fd, unsigned int mtu, struct hfp_info *info)
+int hfp_info_start(int fd, unsigned int mtu, int codec, struct hfp_info *info)
 {
 	info->fd = fd;
 	info->mtu = mtu;
@@ -632,12 +764,51 @@
 	buf_reset(info->playback_buf);
 	buf_reset(info->capture_buf);
 
-	audio_thread_add_callback(info->fd, hfp_info_callback, info);
+	if (codec == HFP_CODEC_ID_MSBC) {
+		int i;
+		for (i = 0; wbs_supported_packet_size[i] != 0; i++) {
+			if (info->packet_size == wbs_supported_packet_size[i])
+				break;
+		}
+		/* In case of unsupported value, error log and fallback to
+		 * MSBC_PKT_SIZE(60). */
+		if (wbs_supported_packet_size[i] == 0) {
+			syslog(LOG_ERR, "Unsupported packet size %u",
+			       info->packet_size);
+			i = 0;
+		}
+		info->packet_size = wbs_supported_packet_size[i];
+		info->write_buf = (uint8_t *)malloc(wbs_hci_sco_buffer_size[i]);
+		info->read_buf = (uint8_t *)malloc(wbs_hci_sco_buffer_size[i]);
+
+		info->write_cb = hfp_write_msbc;
+		info->read_cb = hfp_read_msbc;
+		info->msbc_read = cras_msbc_codec_create();
+		info->msbc_write = cras_msbc_codec_create();
+		info->msbc_plc = cras_msbc_plc_create();
+
+		packet_status_logger_init(info->wbs_logger);
+	} else {
+		info->write_cb = hfp_write;
+		info->read_cb = hfp_read;
+	}
+
+	audio_thread_add_events_callback(info->fd, hfp_info_callback, info,
+					 POLLIN | POLLERR | POLLHUP);
 
 	info->started = 1;
 	info->msbc_num_out_frames = 0;
 	info->msbc_num_in_frames = 0;
 	info->msbc_num_lost_frames = 0;
+	info->write_rp = 0;
+	info->write_wp = 0;
+	info->read_rp = 0;
+	info->read_wp = 0;
+
+	/* Mark as aligned if packet size equals to MSBC_PKT_SIZE. */
+	info->read_align_cb =
+		(info->packet_size == MSBC_PKT_SIZE) ? NULL : msbc_frame_align;
+	info->msbc_read_current_corrupted = 0;
 
 	return 0;
 }
@@ -654,6 +825,28 @@
 	info->fd = 0;
 	info->started = 0;
 
+	/* Unset the write/read callbacks. */
+	info->write_cb = NULL;
+	info->read_cb = NULL;
+
+	if (info->write_buf)
+		free(info->write_buf);
+	if (info->read_buf)
+		free(info->read_buf);
+
+	if (info->msbc_read) {
+		cras_sbc_codec_destroy(info->msbc_read);
+		info->msbc_read = NULL;
+	}
+	if (info->msbc_write) {
+		cras_sbc_codec_destroy(info->msbc_write);
+		info->msbc_write = NULL;
+	}
+	if (info->msbc_plc) {
+		cras_msbc_plc_destroy(info->msbc_plc);
+		info->msbc_plc = NULL;
+	}
+
 	if (info->msbc_num_in_frames) {
 		cras_server_metrics_hfp_packet_loss(
 			(float)info->msbc_num_lost_frames /
@@ -671,12 +864,5 @@
 	if (info->playback_buf)
 		byte_buffer_destroy(&info->playback_buf);
 
-	if (info->msbc_read)
-		cras_sbc_codec_destroy(info->msbc_read);
-	if (info->msbc_write)
-		cras_sbc_codec_destroy(info->msbc_write);
-	if (info->msbc_plc)
-		cras_msbc_plc_destroy(info->msbc_plc);
-
 	free(info);
 }
diff --git a/cras/src/server/cras_hfp_info.h b/cras/src/server/cras_hfp_info.h
index 334278e..3472aea 100644
--- a/cras/src/server/cras_hfp_info.h
+++ b/cras/src/server/cras_hfp_info.h
@@ -24,21 +24,25 @@
 struct hfp_info;
 
 /* Creates an hfp_info instance.
- * Args:
- *    codec - 1 for CVSD, 2 for mSBC per HFP 1.7 specification.
  */
-struct hfp_info *hfp_info_create(int codec);
+struct hfp_info *hfp_info_create();
 
 /* Destroys given hfp_info instance. */
 void hfp_info_destroy(struct hfp_info *info);
 
+/* Sets the wbs_logger to hfp_info instance. */
+void hfp_info_set_wbs_logger(struct hfp_info *info,
+			     struct packet_status_logger *wbs_logger);
+
 /* Checks if given hfp_info is running. */
 int hfp_info_running(struct hfp_info *info);
 
 /* Starts the hfp_info to transmit and reveice samples to and from the file
  * descriptor of a SCO socket. This should be called from main thread.
+ * Args:
+ *    codec - 1 for CVSD, 2 for mSBC per HFP 1.7 specification.
  */
-int hfp_info_start(int fd, unsigned int mtu, struct hfp_info *info);
+int hfp_info_start(int fd, unsigned int mtu, int codec, struct hfp_info *info);
 
 /* Stops given hfp_info. This implies sample transmission will
  * stop and socket be closed. This should be called from main thread.
diff --git a/cras/src/server/cras_hfp_iodev.c b/cras/src/server/cras_hfp_iodev.c
index 08e26d6..6a4ced0 100644
--- a/cras/src/server/cras_hfp_iodev.c
+++ b/cras/src/server/cras_hfp_iodev.c
@@ -17,7 +17,6 @@
 #include "cras_iodev.h"
 #include "cras_system_state.h"
 #include "cras_util.h"
-#include "sfh.h"
 #include "utlist.h"
 
 /* Implementation of bluetooth hands-free profile iodev.
@@ -44,12 +43,10 @@
 {
 	struct hfp_io *hfpio = (struct hfp_io *)iodev;
 
-	/* 16 bit, mono, 8kHz for narrowband and 16KHz for wideband */
-	iodev->format->format = SND_PCM_FORMAT_S16_LE;
-
 	free(iodev->supported_rates);
 	iodev->supported_rates = (size_t *)malloc(2 * sizeof(size_t));
 
+	/* 16 bit, mono, 8kHz for narrowband and 16KHz for wideband */
 	iodev->supported_rates[0] =
 		(hfp_slc_get_selected_codec(hfpio->slc) == HFP_CODEC_ID_MSBC) ?
 			16000 :
@@ -125,6 +122,12 @@
 	return hfp_buf_queued(hfpio->info, iodev->direction);
 }
 
+static int output_underrun(struct cras_iodev *iodev)
+{
+	/* Handle it the same way as cras_iodev_output_underrun(). */
+	return cras_iodev_fill_odev_zeros(iodev, iodev->min_cb_level);
+}
+
 static int configure_dev(struct cras_iodev *iodev)
 {
 	struct hfp_io *hfpio = (struct hfp_io *)iodev;
@@ -133,12 +136,18 @@
 	/* Assert format is set before opening device. */
 	if (iodev->format == NULL)
 		return -EINVAL;
+
 	iodev->format->format = SND_PCM_FORMAT_S16_LE;
 	cras_iodev_init_audio_area(iodev, iodev->format->num_channels);
 
 	if (hfp_info_running(hfpio->info))
 		goto add_dev;
 
+	/*
+	 * Might require a codec negotiation before building the sco connection.
+	 */
+	hfp_slc_codec_connection_setup(hfpio->slc);
+
 	sk = cras_bt_device_sco_connect(hfpio->device,
 					hfp_slc_get_selected_codec(hfpio->slc));
 	if (sk < 0)
@@ -148,7 +157,8 @@
 		hfpio->device, sk, hfp_slc_get_selected_codec(hfpio->slc));
 
 	/* Start hfp_info */
-	err = hfp_info_start(sk, mtu, hfpio->info);
+	err = hfp_info_start(sk, mtu, hfp_slc_get_selected_codec(hfpio->slc),
+			     hfpio->info);
 	if (err)
 		goto error;
 
@@ -250,6 +260,12 @@
 {
 }
 
+int hfp_iodev_is_hsp(struct cras_iodev *iodev)
+{
+	struct hfp_io *hfpio = (struct hfp_io *)iodev;
+	return hfp_slc_is_hsp(hfpio->slc);
+}
+
 void hfp_free_resources(struct hfp_io *hfpio)
 {
 	struct cras_ionode *node;
@@ -292,10 +308,7 @@
 
 	snprintf(iodev->info.name, sizeof(iodev->info.name), "%s", name);
 	iodev->info.name[ARRAY_SIZE(iodev->info.name) - 1] = 0;
-	iodev->info.stable_id =
-		SuperFastHash(cras_bt_device_object_path(device),
-			      strlen(cras_bt_device_object_path(device)),
-			      strlen(cras_bt_device_object_path(device)));
+	iodev->info.stable_id = cras_bt_device_get_stable_id(device);
 
 	iodev->configure_dev = configure_dev;
 	iodev->frames_queued = frames_queued;
@@ -308,22 +321,36 @@
 	iodev->update_supported_formats = update_supported_formats;
 	iodev->update_active_node = update_active_node;
 	iodev->set_volume = set_hfp_volume;
+	iodev->output_underrun = output_underrun;
 
 	node = (struct cras_ionode *)calloc(1, sizeof(*node));
 	node->dev = iodev;
 	strcpy(node->name, iodev->info.name);
 
 	node->plugged = 1;
+	/* If headset mic doesn't support the wideband speech, report a
+	 * different node type so UI can set different plug priority. */
 	node->type = CRAS_NODE_TYPE_BLUETOOTH;
+	if (!hfp_slc_get_wideband_speech_supported(hfpio->slc) &&
+	    (dir == CRAS_STREAM_INPUT))
+		node->type = CRAS_NODE_TYPE_BLUETOOTH_NB_MIC;
+
 	node->volume = 100;
 	gettimeofday(&node->plugged_time, NULL);
 
-	cras_bt_device_append_iodev(device, iodev, profile);
+	/* Prepare active node before append, so bt_io can extract correct
+	 * info from HFP iodev and node. */
 	cras_iodev_add_node(iodev, node);
 	cras_iodev_set_active_node(iodev, node);
+	cras_bt_device_append_iodev(device, iodev, profile);
 
 	hfpio->info = info;
 
+	/* Record max supported channels into cras_iodev_info. */
+	iodev->info.max_supported_channels = 1;
+
+	ewma_power_disable(&iodev->ewma);
+
 	return iodev;
 
 error:
diff --git a/cras/src/server/cras_hfp_iodev.h b/cras/src/server/cras_hfp_iodev.h
index b2762be..b50aa25 100644
--- a/cras/src/server/cras_hfp_iodev.h
+++ b/cras/src/server/cras_hfp_iodev.h
@@ -23,4 +23,11 @@
 
 void hfp_iodev_destroy(struct cras_iodev *iodev);
 
+/*
+ * Returns if the iodev is running for a HSP connection. Note that
+ * hfp_iodev is implemented for both HFP and HSP connection. And this
+ * function allows caller to test if it falls to the rare case - HSP.
+ */
+int hfp_iodev_is_hsp(struct cras_iodev *iodev);
+
 #endif /* CRAS_HFP_IODEV_H_ */
diff --git a/cras/src/server/cras_hfp_slc.c b/cras/src/server/cras_hfp_slc.c
index 6b30a0b..28f73ed 100644
--- a/cras/src/server/cras_hfp_slc.c
+++ b/cras/src/server/cras_hfp_slc.c
@@ -10,28 +10,38 @@
 
 #include "cras_bt_device.h"
 #include "cras_bt_log.h"
+#include "cras_observer.h"
 #include "cras_telephony.h"
 #include "cras_hfp_slc.h"
+#include "cras_server_metrics.h"
 #include "cras_system_state.h"
 #include "cras_tm.h"
+#include "cras_util.h"
 
 /* Message start and end with "\r\n". refer to spec 4.33. */
 #define AT_CMD(cmd) "\r\n" cmd "\r\n"
 
-/* The timeout between service level initialized and codec negotiation
- * completed. */
-#define CODEC_NEGOTIATION_TIMEOUT_MS 500
+/* The timeout between event reporting and HF indicator commands */
+#define HF_INDICATORS_TIMEOUT_MS 2000
+/* The sleep time before reading and processing the following AT commands during
+ * codec connection setup.
+ */
+#define CODEC_CONN_SLEEP_TIME_US 2000
 #define SLC_BUF_SIZE_BYTES 256
 
 /* Indicator update command response and indicator indices.
- * Note that indicator index starts from '1'.
+ * Note that indicator index starts from '1', index 0 is used for CRAS to record
+ * if the event report has been enabled or not.
  */
+#define CRAS_INDICATOR_ENABLE_INDEX 0
 #define BATTERY_IND_INDEX 1
 #define SIGNAL_IND_INDEX 2
 #define SERVICE_IND_INDEX 3
 #define CALL_IND_INDEX 4
 #define CALLSETUP_IND_INDEX 5
 #define CALLHELD_IND_INDEX 6
+#define ROAM_IND_INDEX 7
+#define INDICATOR_IND_MAX 8
 #define INDICATOR_UPDATE_RSP                                                   \
 	"+CIND: "                                                              \
 	"(\"battchg\",(0-5)),"                                                 \
@@ -59,19 +69,19 @@
  *    signal - Current signal strength of AG stored in SLC.
  *    service - Current service availability of AG stored in SLC.
  *    callheld - Current callheld status of AG stored in SLC.
- *    ind_event_report - Activate status of indicator events reporting.
+ *    ind_event_reports - Activate statuses of indicator events reporting.
  *    ag_supported_features - Supported AG features bitmap.
- *    hf_codec_supported - Flags to indicate if codec is supported in HF.
- *    hf_supports_codec_negotiation - If the connected HF supports codec
- *        negotiation.
+ *    hf_supported_features - Bit map of HF supported features.
+ *    hf_supports_battery_indicator - Bit map of battery indicator support of
+ *        connected HF.
+ *    hf_battery - Current battery level of HF reported by the HF. The data
+ *    range should be 0 ~ 100. Use -1 for no battery level reported.
  *    preferred_codec - CVSD or mSBC based on the situation and strategy. This
- *        need not to be equal to selected_codec because codec negotiation
+ *        needs not to be equal to selected_codec because codec negotiation
  *        process may fail.
  *    selected_codec - The codec id defaults to HFP_CODEC_UNUSED and changes
  *        only if codec negotiation is supported and the negotiation flow
  *        has completed.
- *    pending_codec_negotiation - True if codec negotiation process has started
- *        but haven't got reply from HF.
  *    telephony - A reference of current telephony handle.
  *    device - The associated bt device.
  */
@@ -88,13 +98,14 @@
 	int signal;
 	int service;
 	int callheld;
-	int ind_event_report;
+	int ind_event_reports[INDICATOR_IND_MAX];
 	int ag_supported_features;
 	bool hf_codec_supported[HFP_MAX_CODECS];
-	int hf_supports_codec_negotiation;
+	int hf_supported_features;
+	int hf_supports_battery_indicator;
+	int hf_battery;
 	int preferred_codec;
 	int selected_codec;
-	int pending_codec_negotiation;
 	struct cras_bt_device *device;
 	struct cras_timer *timer;
 
@@ -133,7 +144,9 @@
 {
 	char cmd[64];
 
-	if (handle->is_hsp || !handle->ind_event_report)
+	if (handle->is_hsp ||
+	    !handle->ind_event_reports[CRAS_INDICATOR_ENABLE_INDEX] ||
+	    !handle->ind_event_reports[ind_index])
 		return 0;
 
 	snprintf(cmd, 64, AT_CMD("+CIEV: %d,%d"), ind_index, value);
@@ -181,6 +194,10 @@
  */
 static int cli_notification(struct hfp_slc_handle *handle, const char *cmd)
 {
+	if (strlen(cmd) < 9) {
+		syslog(LOG_ERR, "%s: malformed command: '%s'", __func__, cmd);
+		return hfp_send(handle, AT_CMD("ERROR"));
+	}
 	handle->cli_active = (cmd[8] == '1');
 	return hfp_send(handle, AT_CMD("OK"));
 }
@@ -194,6 +211,8 @@
 	int rc, cmd_len;
 
 	cmd_len = strlen(cmd);
+	if (cmd_len < 4)
+		goto error_out;
 
 	if (cmd[3] == '>') {
 		/* Handle memory dial. Extract memory location from command
@@ -214,6 +233,10 @@
 
 	handle->telephony->callsetup = 2;
 	return hfp_send_ind_event_report(handle, CALLSETUP_IND_INDEX, 2);
+
+error_out:
+	syslog(LOG_ERR, "%s: malformed command: '%s'", __func__, cmd);
+	return hfp_send(handle, AT_CMD("ERROR"));
 }
 
 /* AT+VTS command to generate a DTMF code. Mandatory per spec 4.27. */
@@ -240,36 +263,23 @@
 	if (timer)
 		handle->timer = NULL;
 
-	/*
-	 * Catch the case if mSBC codec negotiation never complete or even
-	 * failed. AG side falls back to use codec CVSD and also tells
-	 * HF to select CVSD again.
-	 */
-	if ((handle->selected_codec == HFP_CODEC_UNUSED) &&
-	    handle->hf_codec_supported[HFP_CODEC_ID_MSBC]) {
-		handle->preferred_codec = HFP_CODEC_ID_CVSD;
-		select_preferred_codec(handle);
-	}
-
-	/*
-	 * Codec negotiation is considered to be ended at this point.
-	 * The owner of init_cb may use hfp_slc_get_selected_codec() to
-	 * query the final codec to use for this connection.
-	 */
 	if (handle->init_cb) {
 		handle->init_cb(handle);
 		handle->init_cb = NULL;
 	}
 }
 
-/* Tasks to execute after receiving an AT command. This is useful because
- * some HF replies to command X only after it sends command Y. We rely on
- * this function to achieve reliable codec negotiation.
+/* Handles the event that headset request to start a codec connection
+ * procedure.
  */
-static void post_at_command_tasks(struct hfp_slc_handle *handle)
+static int bluetooth_codec_connection(struct hfp_slc_handle *handle,
+				      const char *cmd)
 {
-	if (handle->pending_codec_negotiation)
-		select_preferred_codec(handle);
+	/* Reset current selected codec to force a new codec connection
+	 * procedure when the next hfp_slc_codec_connection_setup is called.
+	 */
+	handle->selected_codec = HFP_CODEC_UNUSED;
+	return hfp_send(handle, AT_CMD("OK"));
 }
 
 /* Handles the event that headset request to select specific codec. */
@@ -278,46 +288,136 @@
 {
 	char *tokens = strdup(cmd);
 	char *codec;
-	int err;
+	int id, err;
 
-	handle->pending_codec_negotiation = 0;
 	strtok(tokens, "=");
 	codec = strtok(NULL, ",");
-
-	if (codec) {
-		BTLOG(btlog, BT_CODEC_SELECTION, 1, atoi(codec));
-		handle->selected_codec = atoi(codec);
+	if (!codec)
+		goto bcs_cmd_cleanup;
+	id = atoi(codec);
+	if ((id <= HFP_CODEC_UNUSED) || (id >= HFP_MAX_CODECS)) {
+		syslog(LOG_ERR, "%s: invalid codec id: '%s'", __func__, cmd);
+		free(tokens);
+		return hfp_send(handle, AT_CMD("ERROR"));
 	}
 
-	err = hfp_send(handle, AT_CMD("OK"));
-	initialize_slc_handle(NULL, (void *)handle);
+	if (id != handle->preferred_codec)
+		syslog(LOG_WARNING, "%s: inconsistent codec id: '%s'", __func__,
+		       cmd);
+
+	BTLOG(btlog, BT_CODEC_SELECTION, 1, id);
+	handle->selected_codec = id;
+
+bcs_cmd_cleanup:
 	free(tokens);
+	err = hfp_send(handle, AT_CMD("OK"));
 	return err;
 }
 
 /*
- * Possibly choose mSBC code from the supported codecs. Otherwise just
- * initialize the SLC so the default CVSD codec is used.
+ * AT+IPHONEACCEV command from HF to report state change.You can find details
+ * of this command in the Accessory Design Guidelines for Apple Devices R11
+ * section 16.1.
  */
-static void choose_codec_and_init_slc(struct hfp_slc_handle *handle)
+static int apple_accessory_state_change(struct hfp_slc_handle *handle,
+					const char *cmd)
 {
-	if (handle->hf_supports_codec_negotiation &&
-	    handle->hf_codec_supported[HFP_CODEC_ID_MSBC]) {
-		/* Sets preferred codec to mSBC, and schedule callback to
-		 * select preferred codec until reply received or timeout.
-		 */
-		handle->preferred_codec = HFP_CODEC_ID_MSBC;
-		handle->pending_codec_negotiation = 1;
+	char *tokens, *num, *key, *val;
+	int i, level;
 
-		/* Delay init to give headset some time to confirm
-		 * codec selection. */
-		handle->timer =
-			cras_tm_create_timer(cras_system_state_get_tm(),
-					     CODEC_NEGOTIATION_TIMEOUT_MS,
-					     initialize_slc_handle, handle);
-	} else {
-		initialize_slc_handle(NULL, (void *)handle);
+	/* AT+IPHONEACCEV=Number of key/value pairs,key1,val1,key2,val2,...
+	 * Number of key/value pairs: The number of parameters coming next.
+	 * key: the type of change being reported:
+         *      1 = Battery Level
+         *      2 = Dock State
+         * val: the value of the change:
+         * Battery Level: string value between '0' and '9'
+         * Dock State: 0 = undocked, 1 = docked
+	 */
+	tokens = strdup(cmd);
+	strtok(tokens, "=");
+	num = strtok(NULL, ",");
+	if (!num) {
+		free(tokens);
+		return hfp_send(handle, AT_CMD("ERROR"));
 	}
+
+	for (i = 0; i < atoi(num); i++) {
+		key = strtok(NULL, ",");
+		val = strtok(NULL, ",");
+		if (!key || !val) {
+			syslog(LOG_WARNING,
+			       "IPHONEACCEV: Expected %d kv pairs but got %d",
+			       atoi(num), i);
+			break;
+		}
+
+		if (atoi(key) == 1) {
+			level = atoi(val);
+			if (level >= 0 && level < 10) {
+				cras_server_metrics_hfp_battery_report(
+					CRAS_HFP_BATTERY_INDICATOR_APPLE);
+				level = (level + 1) * 10;
+				if (handle->hf_battery != level) {
+					handle->hf_battery = level;
+					cras_observer_notify_bt_battery_changed(
+						cras_bt_device_address(
+							handle->device),
+						(uint32_t)(level));
+				}
+			} else {
+				syslog(LOG_ERR,
+				       "Get invalid battery status from cmd:%s",
+				       cmd);
+			}
+		}
+	}
+	free(tokens);
+	return hfp_send(handle, AT_CMD("OK"));
+}
+
+/*
+ * AT+XAPL command from HF to enable Apple custom features. You can find details
+ * of it in the Accessory Design Guidelines for Apple Devices R11 section 15.1.
+ */
+static int apple_supported_features(struct hfp_slc_handle *handle,
+				    const char *cmd)
+{
+	char *tokens, *features;
+	int apple_features, err;
+	char buf[64];
+
+	/* AT+XAPL=<vendorID>-<productID>-<version>,<features>
+	 * Parse <features>, the only token we care about.
+	 */
+	tokens = strdup(cmd);
+	strtok(tokens, "=");
+
+	strtok(NULL, ",");
+	features = strtok(NULL, ",");
+	if (!features)
+		goto error_out;
+
+	apple_features = atoi(features);
+
+	if (apple_features & APL_BATTERY)
+		handle->hf_supports_battery_indicator |=
+			CRAS_HFP_BATTERY_INDICATOR_APPLE;
+
+	snprintf(buf, 64, AT_CMD("+XAPL=iPhone,%d"),
+		 CRAS_APL_SUPPORTED_FEATURES);
+	err = hfp_send(handle, buf);
+	if (err)
+		goto error_out;
+
+	err = hfp_send(handle, AT_CMD("OK"));
+	free(tokens);
+	return err;
+
+error_out:
+	syslog(LOG_ERR, "%s: malformed command: '%s'", __func__, cmd);
+	free(tokens);
+	return hfp_send(handle, AT_CMD("ERROR"));
 }
 
 /* Handles the event when headset reports its available codecs list. */
@@ -341,6 +441,11 @@
 		id_str = strtok(NULL, ",");
 	}
 
+	if (hfp_slc_get_wideband_speech_supported(handle))
+		handle->preferred_codec = HFP_CODEC_ID_MSBC;
+	else
+		handle->preferred_codec = HFP_CODEC_ID_CVSD;
+
 	free(tokens);
 	return hfp_send(handle, AT_CMD("OK"));
 }
@@ -377,7 +482,8 @@
 		goto event_reporting_done;
 	}
 	if (atoi(mode) == FORWARD_UNSOLICIT_RESULT_CODE)
-		handle->ind_event_report = atoi(tmp);
+		handle->ind_event_reports[CRAS_INDICATOR_ENABLE_INDEX] =
+			atoi(tmp);
 
 	err = hfp_send(handle, AT_CMD("OK"));
 	if (err) {
@@ -386,13 +492,21 @@
 	}
 
 	/*
-	 * Consider the Service Level Connection to be fully initialized,
-	 * and thereby established, after successfully responded with OK.
-	 * However we should postpone the initialize call after codec selection,
-	 * otherwise iodev could be open immediately while the headset is still
-	 * communicating about which of CVSD or mSBC codec to use.
+	 * Wait for HF to retrieve information about HF indicators and consider
+	 * the Service Level Connection to be fully initialized, and thereby
+	 * established, if HF doesn't support HF indicators.
 	 */
-	choose_codec_and_init_slc(handle);
+	if (hfp_slc_get_hf_hf_indicators_supported(handle))
+		handle->timer =
+			cras_tm_create_timer(cras_system_state_get_tm(),
+					     HF_INDICATORS_TIMEOUT_MS,
+					     initialize_slc_handle, handle);
+	/*
+	 * Otherwise, regard the Service Level Connection to be fully
+	 * initialized and ready for the potential codec negotiation.
+	 */
+	else
+		initialize_slc_handle(NULL, (void *)handle);
 
 event_reporting_done:
 	free(tokens);
@@ -493,6 +607,26 @@
 	return hfp_send(handle, AT_CMD("OK"));
 }
 
+/* The AT+CHLD command is used to control call hold, release, and multiparty
+ * states.
+ */
+static int call_hold(struct hfp_slc_handle *handle, const char *buf)
+{
+	int rc;
+
+	// Chrome OS doesn't yet support CHLD features but we need to reply
+	// the query with an empty feature list rather than "ERROR" to increase
+	// interoperability with certain devices (b/172413440).
+	if (strlen(buf) > 8 && buf[7] == '=' && buf[8] == '?') {
+		rc = hfp_send(handle, AT_CMD("+CHLD:"));
+		if (rc)
+			return rc;
+		return hfp_send(handle, AT_CMD("OK"));
+	}
+
+	return hfp_send(handle, AT_CMD("ERROR"));
+}
+
 /* AT+CIND command retrieves the supported indicator and its corresponding
  * range and order index or read current status of indicators. Mandatory
  * support per spec 4.2.
@@ -502,6 +636,11 @@
 	int err;
 	char buf[64];
 
+	if (strlen(cmd) < 8) {
+		syslog(LOG_ERR, "%s: malformed command: '%s'", __func__, cmd);
+		return hfp_send(handle, AT_CMD("ERROR"));
+	}
+
 	if (cmd[7] == '=') {
 		/* Indicator update test command "AT+CIND=?" */
 		err = hfp_send(handle, AT_CMD(INDICATOR_UPDATE_RSP));
@@ -527,16 +666,187 @@
 }
 
 /* AT+BIA command to change the subset of indicators that shall be
- * sent by the AG. It is okay to ignore this command here since we
- * don't do event reporting(CMER).
+ * sent by the AG.
  */
 static int indicator_activation(struct hfp_slc_handle *handle, const char *cmd)
 {
-	/* AT+BIA=[[<indrep 1>][,[<indrep 2>][,...[,[<indrep n>]]]]] */
-	syslog(LOG_ERR, "Bluetooth indicator activation command %s", cmd);
+	char *ptr;
+	int idx = BATTERY_IND_INDEX;
+
+	/* AT+BIA=[[<indrep 1>][,[<indrep 2>][,...[,[<indrep n>]]]]]
+	 * According to the spec:
+	 * - The indicator state can be omitted and the current reporting
+	 *   states of the indicator shall not change.
+	 *     Ex: AT+BIA=,1,,0
+	 *         Only the 2nd and 4th indicators may be affected.
+	 * - HF can provide fewer indicators than AG and states not provided
+	 *   shall not change.
+	 *     Ex: CRAS supports 7 indicators and gets AT+BIA=1,0,1
+	 *         Only the first three indicators may be affected.
+	 * - Call, Call Setup and Held Call are mandatory and should be always
+	 *   on no matter what state HF set.
+	 */
+	ptr = strchr(cmd, '=');
+	while (ptr && idx < INDICATOR_IND_MAX) {
+		if (idx != CALL_IND_INDEX && idx != CALLSETUP_IND_INDEX &&
+		    idx != CALLHELD_IND_INDEX) {
+			if (*(ptr + 1) == '1')
+				handle->ind_event_reports[idx] = 1;
+			else if (*(ptr + 1) == '0')
+				handle->ind_event_reports[idx] = 0;
+		}
+		ptr = strchr(ptr + 1, ',');
+		idx++;
+	}
 	return hfp_send(handle, AT_CMD("OK"));
 }
 
+/* AT+BIND command to report, query and activate Generic Status Indicators.
+ * It is sent by the HF if both AG and HF support the HF indicator feature.
+ */
+static int indicator_support(struct hfp_slc_handle *handle, const char *cmd)
+{
+	char *tokens, *key;
+	int err, cmd_len;
+
+	cmd_len = strlen(cmd);
+	if (cmd_len < 8)
+		goto error_out;
+
+	if (cmd[7] == '=') {
+		/* AT+BIND=? (Read AG supported indicators) */
+		if (cmd_len > 8 && cmd[8] == '?') {
+			/* +BIND: (<a>,<b>,<c>,...,<n>) (Response to AT+BIND=?)
+			 * <a> ... <n>: 0-65535, entered as decimal unsigned
+			 * integer values without leading zeros, referencing an
+			 * HF indicator assigned number.
+			 * 1 is for Enhanced Driver Status.
+			 * 2 is for Battery Level.
+			 * For the list of HF indicator assigned number, you can
+			 * check the  Bluetooth SIG Assigned Numbers web page.
+			 */
+			BTLOG(btlog, BT_HFP_HF_INDICATOR, 1, 0);
+			/* "2" is for HF Battery Level that we support. We don't
+			 * support "1" but this is a workaround for Pixel Buds 2
+			 * which expects this exact combination for battery
+			 * reporting (HFP 1.7 standard) to work. This workaround
+			 * is fine since we don't enable Safety Drive with
+			 * +BIND: 1,1 (b/172680041).
+			 */
+			err = hfp_send(handle, AT_CMD("+BIND: (1,2)"));
+			if (err < 0)
+				return err;
+		}
+		/* AT+BIND=<a>,<b>,...,<n>(List HF supported indicators) */
+		else {
+			tokens = strdup(cmd);
+			strtok(tokens, "=");
+			key = strtok(NULL, ",");
+			while (key != NULL) {
+				if (atoi(key) == 2)
+					handle->hf_supports_battery_indicator |=
+						CRAS_HFP_BATTERY_INDICATOR_HFP;
+				key = strtok(NULL, ",");
+			}
+			free(tokens);
+		}
+	}
+	/* AT+BIND? (Read AG enabled/disabled status of indicators) */
+	else if (cmd[7] == '?') {
+		/* +BIND: <a>,<state> (Unsolicited or Response to AT+BIND?)
+		 * This response enables the AG to notify the HF which HF
+		 * indicators are supported and their state, enabled or
+		 * disabled.
+		 * <a>: 1 or 2, referencing an HF indicator assigned number.
+		 * <state>: 0-1, entered as integer values, where
+		 * 0 = disabled, no value changes shall be sent for this
+		 * indicator
+		 * 1 = enabled, value changes may be sent for this indicator
+		 */
+
+		/* We don't support Enhanced Driver Status, so explicitly
+		 * disable it (b/172680041).
+		 */
+		err = hfp_send(handle, AT_CMD("+BIND: 1,0"));
+		if (err < 0)
+			return err;
+
+		BTLOG(btlog, BT_HFP_HF_INDICATOR, 0, 0);
+
+		err = hfp_send(handle, AT_CMD("+BIND: 2,1"));
+		if (err < 0)
+			return err;
+
+		err = hfp_send(handle, AT_CMD("OK"));
+		if (err)
+			return err;
+		/*
+		 * Consider the Service Level Connection to be fully initialized
+		 * and thereby established, after successfully responded with OK
+		 */
+		initialize_slc_handle(NULL, (void *)handle);
+		return 0;
+	} else {
+		goto error_out;
+	}
+	/* This OK reply is required after both +BIND AT commands. It also
+	 * covers the AT+BIND= <a>,<b>,...,<n> case.
+	 */
+	return hfp_send(handle, AT_CMD("OK"));
+
+error_out:
+	syslog(LOG_ERR, "%s: malformed command: '%s'", __func__, cmd);
+	return hfp_send(handle, AT_CMD("ERROR"));
+}
+
+/* AT+BIEV command reports updated values of enabled HF indicators to the AG.
+ */
+static int indicator_state_change(struct hfp_slc_handle *handle,
+				  const char *cmd)
+{
+	char *tokens, *key, *val;
+	int level;
+	/* AT+BIEV= <assigned number>,<value> (Update value of indicator)
+	 * CRAS only supports battery level, which is with assigned number 2.
+	 * Battery level should range from 0 to 100 defined by the spec.
+	 */
+	tokens = strdup(cmd);
+	strtok(tokens, "=");
+	key = strtok(NULL, ",");
+	if (!key)
+		goto error_out;
+
+	if (atoi(key) == 2) {
+		val = strtok(NULL, ",");
+		if (!val)
+			goto error_out;
+		level = atoi(val);
+		if (level >= 0 && level <= 100) {
+			cras_server_metrics_hfp_battery_report(
+				CRAS_HFP_BATTERY_INDICATOR_HFP);
+			if (handle->hf_battery != level) {
+				handle->hf_battery = level;
+				cras_observer_notify_bt_battery_changed(
+					cras_bt_device_address(handle->device),
+					(uint32_t)(level));
+			}
+		} else {
+			syslog(LOG_ERR,
+			       "Get invalid battery status from cmd:%s", cmd);
+		}
+	} else {
+		goto error_out;
+	}
+
+	free(tokens);
+	return hfp_send(handle, AT_CMD("OK"));
+
+error_out:
+	syslog(LOG_WARNING, "%s: invalid command: '%s'", __func__, cmd);
+	free(tokens);
+	return hfp_send(handle, AT_CMD("ERROR"));
+}
+
 /* AT+VGM and AT+VGS command reports the current mic and speaker gain
  * level respectively. Optional support per spec 4.28.
  */
@@ -545,14 +855,21 @@
 	int gain;
 
 	if (strlen(cmd) < 8) {
-		syslog(LOG_ERR, "Invalid gain setting command %s", cmd);
-		return -EINVAL;
+		syslog(LOG_ERR, "%s: malformed command: '%s'", __func__, cmd);
+		return hfp_send(handle, AT_CMD("ERROR"));
 	}
 
 	/* Map 0 to the smallest non-zero scale 6/100, and 15 to
 	 * 100/100 full. */
 	if (cmd[5] == 'S') {
 		gain = atoi(&cmd[7]);
+		if (gain < 0 || gain > 15) {
+			syslog(LOG_ERR,
+			       "signal_gain_setting: gain %d is not between 0 and 15",
+			       gain);
+			return hfp_send(handle, AT_CMD("ERROR"));
+		}
+		BTLOG(btlog, BT_HFP_UPDATE_SPEAKER_GAIN, gain, 0);
 		cras_bt_device_update_hardware_volume(handle->device,
 						      (gain + 1) * 100 / 16);
 	}
@@ -574,23 +891,24 @@
  */
 static int supported_features(struct hfp_slc_handle *handle, const char *cmd)
 {
-	int err, hf_features;
+	int err;
 	char response[128];
 	char *tokens, *features;
 
-	if (strlen(cmd) < 9)
-		return -EINVAL;
-
-	handle->hf_supports_codec_negotiation = 0;
+	if (strlen(cmd) < 9) {
+		syslog(LOG_ERR, "%s: malformed command: '%s'", __func__, cmd);
+		return hfp_send(handle, AT_CMD("ERROR"));
+	}
 
 	tokens = strdup(cmd);
 	strtok(tokens, "=");
 	features = strtok(NULL, ",");
+	if (!features)
+		goto error_out;
 
-	hf_features = atoi(features);
-	BTLOG(btlog, BT_HFP_SUPPORTED_FEATURES, 0, hf_features);
-	if (hf_features & HF_CODEC_NEGOTIATION)
-		handle->hf_supports_codec_negotiation = 1;
+	handle->hf_supported_features = atoi(features);
+	BTLOG(btlog, BT_HFP_SUPPORTED_FEATURES, 0,
+	      handle->hf_supported_features);
 	free(tokens);
 
 	/* AT+BRSF=<feature> command received, ignore the HF supported feature
@@ -606,6 +924,11 @@
 		return err;
 
 	return hfp_send(handle, AT_CMD("OK"));
+
+error_out:
+	free(tokens);
+	syslog(LOG_ERR, "%s: malformed command: '%s'", __func__, cmd);
+	return hfp_send(handle, AT_CMD("ERROR"));
 }
 
 int hfp_event_speaker_gain(struct hfp_slc_handle *handle, int gain)
@@ -614,6 +937,7 @@
 
 	/* Normailize gain value to 0-15 */
 	gain = gain * 15 / 100;
+	BTLOG(btlog, BT_HFP_SET_SPEAKER_GAIN, gain, 0);
 	snprintf(command, 128, AT_CMD("+VGS=%d"), gain);
 
 	return hfp_send(handle, command);
@@ -632,11 +956,75 @@
 	return cras_telephony_event_terminate_call();
 }
 
+/* AT+XEVENT is defined by Android to support vendor specific features.
+ * Currently, the only known supported case for CrOS is the battery event sent
+ * by some Plantronics headsets.
+ */
+static int vendor_specific_features(struct hfp_slc_handle *handle,
+				    const char *cmd)
+{
+	char *tokens, *event, *level_str, *num_of_level_str;
+	int level, num_of_level;
+
+	tokens = strdup(cmd);
+	strtok(tokens, "=");
+	event = strtok(NULL, ",");
+	if (!event)
+		goto error_out;
+
+	/* AT+XEVENT=BATTERY,Level,NumberOfLevel,MinutesOfTalkTime,IsCharging
+	 * Level: The charge level with a zero-based integer.
+	 * NumberOfLevel: How many charging levels there are.
+	 * MinuteOfTalkTime: The estimated number of talk minutes remaining.
+	 * IsCharging: A 0 or 1 value.
+	 *
+	 * We only support the battery level and thus only care about the first
+	 * 3 arguments.
+	 */
+	if (!strncmp(event, "BATTERY", 7)) {
+		level_str = strtok(NULL, ",");
+		num_of_level_str = strtok(NULL, ",");
+		if (!level_str || !num_of_level_str)
+			goto error_out;
+
+		level = atoi(level_str);
+		num_of_level = atoi(num_of_level_str);
+		if (level < 0 || num_of_level <= 1 || level >= num_of_level)
+			goto error_out;
+
+		level = (int64_t)level * 100 / (num_of_level - 1);
+		if (handle->hf_battery != level) {
+			handle->hf_supports_battery_indicator |=
+				CRAS_HFP_BATTERY_INDICATOR_PLANTRONICS;
+			cras_server_metrics_hfp_battery_report(
+				CRAS_HFP_BATTERY_INDICATOR_PLANTRONICS);
+			handle->hf_battery = level;
+			cras_observer_notify_bt_battery_changed(
+				cras_bt_device_address(handle->device),
+				(uint32_t)(level));
+		}
+	}
+
+	free(tokens);
+	/* For Plantronic headsets, it is required to reply "OK" for the first
+	 * AT+XEVENT=USER-AGENT... command to tell the headset our support of
+	 * the xevent protocol. Otherwise, all following events including
+	 * BATTERY won't be sent.
+	 */
+	return hfp_send(handle, AT_CMD("OK"));
+
+error_out:
+	syslog(LOG_ERR, "%s: malformed vendor specific command: '%s'", __func__,
+	       cmd);
+	free(tokens);
+	return hfp_send(handle, AT_CMD("ERROR"));
+}
+
 /* AT commands to support in order to conform HFP specification.
  *
  * An initialized service level connection is the pre-condition for all
  * call related procedures. Note that for the call related commands,
- * we are good to just respond with a dummy "OK".
+ * we are good to just respond with a meaningless "OK".
  *
  * The procedure to establish a service level connection is described below:
  *
@@ -668,27 +1056,35 @@
  *                     AT+CMER= -->
  *                 <-- OK
  */
-static struct at_command at_commands[] = { { "ATA", answer_call },
-					   { "ATD", dial_number },
-					   { "AT+BAC", available_codecs },
-					   { "AT+BCS",
-					     bluetooth_codec_selection },
-					   { "AT+BIA", indicator_activation },
-					   { "AT+BLDN", last_dialed_number },
-					   { "AT+BRSF", supported_features },
-					   { "AT+CCWA", call_waiting_notify },
-					   { "AT+CHUP", terminate_call },
-					   { "AT+CIND", report_indicators },
-					   { "AT+CKPD", key_press },
-					   { "AT+CLCC", list_current_calls },
-					   { "AT+CLIP", cli_notification },
-					   { "AT+CMEE", extended_errors },
-					   { "AT+CMER", event_reporting },
-					   { "AT+CNUM", subscriber_number },
-					   { "AT+COPS", operator_selection },
-					   { "AT+VG", signal_gain_setting },
-					   { "AT+VTS", dtmf_tone },
-					   { 0 } };
+static struct at_command at_commands[] = {
+	{ "ATA", answer_call },
+	{ "ATD", dial_number },
+	{ "AT+BAC", available_codecs },
+	{ "AT+BCC", bluetooth_codec_connection },
+	{ "AT+BCS", bluetooth_codec_selection },
+	{ "AT+BIA", indicator_activation },
+	{ "AT+BIEV", indicator_state_change },
+	{ "AT+BIND", indicator_support },
+	{ "AT+BLDN", last_dialed_number },
+	{ "AT+BRSF", supported_features },
+	{ "AT+CCWA", call_waiting_notify },
+	{ "AT+CHUP", terminate_call },
+	{ "AT+CIND", report_indicators },
+	{ "AT+CKPD", key_press },
+	{ "AT+CLCC", list_current_calls },
+	{ "AT+CLIP", cli_notification },
+	{ "AT+CMEE", extended_errors },
+	{ "AT+CMER", event_reporting },
+	{ "AT+CNUM", subscriber_number },
+	{ "AT+COPS", operator_selection },
+	{ "AT+IPHONEACCEV", apple_accessory_state_change },
+	{ "AT+VG", signal_gain_setting },
+	{ "AT+VTS", dtmf_tone },
+	{ "AT+XAPL", apple_supported_features },
+	{ "AT+XEVENT", vendor_specific_features },
+	{ "AT+CHLD", call_hold },
+	{ 0 }
+};
 
 static int handle_at_command(struct hfp_slc_handle *slc_handle, const char *cmd)
 {
@@ -702,21 +1098,23 @@
 	return hfp_send(slc_handle, AT_CMD("ERROR"));
 }
 
-static void slc_watch_callback(void *arg)
+int handle_at_command_for_test(struct hfp_slc_handle *slc_handle,
+			       const char *cmd)
 {
-	struct hfp_slc_handle *handle = (struct hfp_slc_handle *)arg;
+	return handle_at_command(slc_handle, cmd);
+}
+
+static int process_at_commands(struct hfp_slc_handle *handle)
+{
 	ssize_t bytes_read;
 	int err;
 
 	bytes_read =
 		read(handle->rfcomm_fd, &handle->buf[handle->buf_write_idx],
 		     SLC_BUF_SIZE_BYTES - handle->buf_write_idx - 1);
-	if (bytes_read < 0) {
-		syslog(LOG_ERR, "Error reading slc command %s",
-		       strerror(errno));
-		handle->disconnect_cb(handle);
-		return;
-	}
+	if (bytes_read < 0)
+		return bytes_read;
+
 	handle->buf_write_idx += bytes_read;
 	handle->buf[handle->buf_write_idx] = '\0';
 
@@ -730,7 +1128,7 @@
 		err = handle_at_command(handle,
 					&handle->buf[handle->buf_read_idx]);
 		if (err < 0)
-			return;
+			return 0;
 
 		/* Shift the read index */
 		handle->buf_read_idx = 1 + end_char - handle->buf;
@@ -753,9 +1151,21 @@
 			handle->buf_write_idx = 0;
 		}
 	}
+	return bytes_read;
+}
 
-	post_at_command_tasks(handle);
+static void slc_watch_callback(void *arg, int revents)
+{
+	struct hfp_slc_handle *handle = (struct hfp_slc_handle *)arg;
+	int err;
 
+	err = process_at_commands(handle);
+	if (err < 0) {
+		syslog(LOG_ERR, "Error reading slc command %s",
+		       strerror(errno));
+		cras_system_rm_select_fd(handle->rfcomm_fd);
+		handle->disconnect_cb(handle);
+	}
 	return;
 }
 
@@ -768,6 +1178,10 @@
 				      hfp_slc_disconnect_cb disconnect_cb)
 {
 	struct hfp_slc_handle *handle;
+	int i;
+
+	if (!disconnect_cb)
+		return NULL;
 
 	handle = (struct hfp_slc_handle *)calloc(1, sizeof(*handle));
 	if (!handle)
@@ -776,6 +1190,7 @@
 	handle->rfcomm_fd = fd;
 	handle->is_hsp = is_hsp;
 	handle->ag_supported_features = ag_supported_features;
+	handle->hf_supported_features = 0;
 	handle->device = device;
 	handle->init_cb = init_cb;
 	handle->disconnect_cb = disconnect_cb;
@@ -783,13 +1198,16 @@
 	handle->battery = 5;
 	handle->signal = 5;
 	handle->service = 1;
-	handle->ind_event_report = 0;
+	handle->ind_event_reports[CRAS_INDICATOR_ENABLE_INDEX] = 0;
+	for (i = BATTERY_IND_INDEX; i < INDICATOR_IND_MAX; i++)
+		handle->ind_event_reports[i] = 1;
 	handle->telephony = cras_telephony_get();
 	handle->preferred_codec = HFP_CODEC_ID_CVSD;
 	handle->selected_codec = HFP_CODEC_UNUSED;
-
-	cras_system_add_select_fd(handle->rfcomm_fd, slc_watch_callback,
-				  handle);
+	handle->hf_supports_battery_indicator = CRAS_HFP_BATTERY_INDICATOR_NONE;
+	handle->hf_battery = -1;
+	cras_system_add_select_fd(handle->rfcomm_fd, slc_watch_callback, handle,
+				  POLLIN | POLLERR | POLLHUP);
 
 	return handle;
 }
@@ -804,6 +1222,11 @@
 	free(slc_handle);
 }
 
+int hfp_slc_is_hsp(struct hfp_slc_handle *handle)
+{
+	return handle->is_hsp;
+}
+
 int hfp_slc_get_selected_codec(struct hfp_slc_handle *handle)
 {
 	/* If codec negotiation is not supported on HF, or the negotiation
@@ -814,6 +1237,65 @@
 		return handle->selected_codec;
 }
 
+int hfp_slc_codec_connection_setup(struct hfp_slc_handle *handle)
+{
+	/* The time we wait for codec selection response. */
+	static struct timespec timeout = { 0, 100000000 };
+	struct pollfd poll_fd;
+	int rc = 0;
+	struct timespec ts = timeout;
+
+	/*
+	 * Codec negotiation is not required, if either AG or HF doesn't support
+	 * it or it has been done once.
+	 */
+	if (!hfp_slc_get_hf_codec_negotiation_supported(handle) ||
+	    !hfp_slc_get_ag_codec_negotiation_supported(handle) ||
+	    handle->selected_codec == handle->preferred_codec)
+		return 0;
+
+redo_codec_conn:
+	select_preferred_codec(handle);
+
+	poll_fd.fd = handle->rfcomm_fd;
+	poll_fd.events = POLLIN;
+
+	ts = timeout;
+	while (rc <= 0) {
+		rc = cras_poll(&poll_fd, 1, &ts, NULL);
+		if (rc == -ETIMEDOUT) {
+			/*
+	 		 * Catch the case that the first initial codec
+			 * negotiation timeout. At this point we're not sure
+			 * if HF is good with the preferred codec from AG.
+			 * Fallback to CVSD doesn't help because very likely
+			 * HF won't reply that either. The best thing we can
+			 * do is just leave a warning log.
+	 		 */
+			if (handle->selected_codec == HFP_CODEC_UNUSED) {
+				syslog(LOG_WARNING,
+				       "Proceed using codec %d without HF reply",
+				       handle->preferred_codec);
+			}
+			return rc;
+		}
+	}
+
+	if (rc > 0) {
+		do {
+			usleep(CODEC_CONN_SLEEP_TIME_US);
+			rc = process_at_commands(handle);
+		} while (rc == -EAGAIN);
+
+		if (rc <= 0)
+			return rc;
+		if (handle->selected_codec != handle->preferred_codec)
+			goto redo_codec_conn;
+	}
+
+	return 0;
+}
+
 int hfp_set_call_status(struct hfp_slc_handle *handle, int call)
 {
 	int old_call = handle->telephony->call;
@@ -890,7 +1372,29 @@
 	return hfp_send_ind_event_report(handle, SERVICE_IND_INDEX, avail);
 }
 
+int hfp_slc_get_ag_codec_negotiation_supported(struct hfp_slc_handle *handle)
+{
+	return handle->ag_supported_features & AG_CODEC_NEGOTIATION;
+}
+
 int hfp_slc_get_hf_codec_negotiation_supported(struct hfp_slc_handle *handle)
 {
-	return handle->hf_supports_codec_negotiation;
+	return handle->hf_supported_features & HF_CODEC_NEGOTIATION;
+}
+
+int hfp_slc_get_hf_hf_indicators_supported(struct hfp_slc_handle *handle)
+{
+	return handle->hf_supported_features & HF_HF_INDICATORS;
+}
+
+bool hfp_slc_get_wideband_speech_supported(struct hfp_slc_handle *handle)
+{
+	return hfp_slc_get_ag_codec_negotiation_supported(handle) &&
+	       hfp_slc_get_hf_codec_negotiation_supported(handle) &&
+	       handle->hf_codec_supported[HFP_CODEC_ID_MSBC];
+}
+
+int hfp_slc_get_hf_supports_battery_indicator(struct hfp_slc_handle *handle)
+{
+	return handle->hf_supports_battery_indicator;
 }
diff --git a/cras/src/server/cras_hfp_slc.h b/cras/src/server/cras_hfp_slc.h
index fd3ee55..99335ea 100644
--- a/cras/src/server/cras_hfp_slc.h
+++ b/cras/src/server/cras_hfp_slc.h
@@ -18,7 +18,7 @@
 #define HF_THREE_WAY_CALLING 0x0002
 #define HF_CLI_PRESENTATION_CAP 0x0004
 #define HF_VOICE_RECOGNITION 0x0008
-#define HF_REMOVE_VOLUME_CTONTROL 0x0010
+#define HF_REMOTE_VOLUME_CONTROL 0x0010
 #define HF_ENHANCED_CALL_STATUS 0x0020
 #define HF_ENHANCED_CALL_CONTROL 0x0040
 #define HF_CODEC_NEGOTIATION 0x0080
@@ -38,12 +38,32 @@
 #define AG_HF_INDICATORS 0x0400
 #define AG_ESCO_S4_T2_SETTINGS 0x0800
 
+/*
+ * Apple specific bluetooth commands that extend accessory capabilities.
+ * Per Accessory Design Guidelines for Apple devices, command AT+XAPL
+ */
+
+#define APL_RESERVED 0x01
+#define APL_BATTERY 0x02
+#define APL_DOCKED_OR_POWERED 0x04
+#define APL_SIRI 0x08
+#define APL_NOISE_REDUCTION 0x10
+
+#define CRAS_APL_SUPPORTED_FEATURES (APL_BATTERY)
+
 /* Codec ids for codec negotiation, per HFP 1.7.1 spec appendix B. */
 #define HFP_CODEC_UNUSED 0
 #define HFP_CODEC_ID_CVSD 1
 #define HFP_CODEC_ID_MSBC 2
 #define HFP_MAX_CODECS 3
 
+/* Hands-free HFP supported battery indicator bit definition.
+ * This is currently only used for logging purpose. */
+#define CRAS_HFP_BATTERY_INDICATOR_NONE 0x0
+#define CRAS_HFP_BATTERY_INDICATOR_HFP 0x1
+#define CRAS_HFP_BATTERY_INDICATOR_APPLE 0x2
+#define CRAS_HFP_BATTERY_INDICATOR_PLANTRONICS 0x4
+
 /* Callback to call when service level connection initialized. */
 typedef int (*hfp_slc_init_cb)(struct hfp_slc_handle *handle);
 
@@ -71,6 +91,11 @@
 /* Destroys an hfp_slc_handle. */
 void hfp_slc_destroy(struct hfp_slc_handle *handle);
 
+/* Returns true if this SLC is created for headset profile(HSP), false
+ * means it's created for hands-free profile(HFP).
+ */
+int hfp_slc_is_hsp(struct hfp_slc_handle *handle);
+
 /* Sets the call status to notify handsfree device. */
 int hfp_set_call_status(struct hfp_slc_handle *handle, int call);
 
@@ -108,4 +133,24 @@
 /* Gets if the remote HF supports codec negotiation. */
 int hfp_slc_get_hf_codec_negotiation_supported(struct hfp_slc_handle *handle);
 
+/* Gets if the remote HF supports HF indicator. */
+int hfp_slc_get_hf_hf_indicators_supported(struct hfp_slc_handle *handle);
+
+/* Gets if the HF side supports wideband speech. */
+bool hfp_slc_get_wideband_speech_supported(struct hfp_slc_handle *handle);
+
+/* Gets if the AG side supports codec negotiation. */
+int hfp_slc_get_ag_codec_negotiation_supported(struct hfp_slc_handle *handle);
+
+/* Gets an enum representing which spec the HF supports battery indicator.
+ * Apple, HFP, none, or both. */
+int hfp_slc_get_hf_supports_battery_indicator(struct hfp_slc_handle *handle);
+
+/* Init the codec negotiation process if needed. */
+int hfp_slc_codec_connection_setup(struct hfp_slc_handle *handle);
+
+// Expose internal AT command handling for fuzzing.
+int handle_at_command_for_test(struct hfp_slc_handle *slc_handle,
+			       const char *cmd);
+
 #endif /* CRAS_HFP_SLC_H_ */
diff --git a/cras/src/server/cras_iodev.c b/cras/src/server/cras_iodev.c
index cf6b71e..651cef7 100644
--- a/cras/src/server/cras_iodev.c
+++ b/cras/src/server/cras_iodev.c
@@ -21,6 +21,7 @@
 #include "cras_dsp_pipeline.h"
 #include "cras_fmt_conv.h"
 #include "cras_iodev.h"
+#include "cras_main_thread_log.h"
 #include "cras_iodev_list.h"
 #include "cras_mix.h"
 #include "cras_ramp.h"
@@ -37,6 +38,8 @@
 static const float RAMP_UNMUTE_DURATION_SECS = 0.5;
 static const float RAMP_NEW_STREAM_DURATION_SECS = 0.01;
 static const float RAMP_MUTE_DURATION_SECS = 0.1;
+static const float RAMP_RESUME_MUTE_DURATION_SECS = 1;
+static const float RAMP_SWITCH_MUTE_DURATION_SECS = 0.5;
 static const float RAMP_VOLUME_CHANGE_DURATION_SECS = 0.1;
 
 /*
@@ -81,7 +84,7 @@
 
 	/* If underrun happened, handle underrun and get hw_level again. */
 	if (hw_level == 0) {
-		rc = cras_iodev_output_underrun(odev);
+		rc = cras_iodev_output_underrun(odev, hw_level, 0);
 		if (rc < 0)
 			return rc;
 
@@ -236,7 +239,7 @@
  *  |                        ----------------              | device from
  *  |                        | S1  Open     |              | audio_thread and
  *  |                        ----------------              | closes device
- *  | Device with dummy start       |                      |
+ *  | Device with empty start       |                      |
  *  | ops transits into             | Sample is ready      |
  *  | no stream state right         V                      |
  *  | after open.            ----------------              |
@@ -261,10 +264,9 @@
 		/* Starts ramping up if device should not be muted.
 		 * Both mute and volume are taken into consideration.
 		 */
-		if (odev->ramp && !output_should_mute(odev))
-			cras_iodev_start_ramp(
-				odev,
-				CRAS_IODEV_RAMP_REQUEST_UP_START_PLAYBACK);
+		if (odev->ramp && !output_should_mute(odev)) {
+			cras_iodev_start_ramp(odev, odev->initial_ramp_request);
+		}
 	}
 
 	if (odev->state == CRAS_IODEV_STATE_OPEN) {
@@ -447,6 +449,17 @@
 	snd_pcm_format_t actual_format;
 	int rc;
 
+	/* Update supported formats on iodev before negotiating the final value
+	 * with what stream requested.
+	 */
+	if (iodev->update_supported_formats) {
+		rc = iodev->update_supported_formats(iodev);
+		if (rc) {
+			syslog(LOG_ERR, "Failed to update formats");
+			return rc;
+		}
+	}
+
 	/* If this device isn't already using a format, try to match the one
 	 * requested in "fmt". */
 	if (iodev->format == NULL) {
@@ -455,14 +468,6 @@
 			return -ENOMEM;
 		*iodev->format = *fmt;
 
-		if (iodev->update_supported_formats) {
-			rc = iodev->update_supported_formats(iodev);
-			if (rc) {
-				syslog(LOG_ERR, "Failed to update formats");
-				goto error;
-			}
-		}
-
 		/* Finds the actual rate of device before allocating DSP
 		 * because DSP needs to use the rate of device, not rate of
 		 * stream. */
@@ -522,8 +527,8 @@
 
 	if (!pipeline) {
 		cras_iodev_alloc_dsp(iodev);
-		cras_dsp_load_dummy_pipeline(iodev->dsp_context,
-					     iodev->format->num_channels);
+		cras_dsp_load_mock_pipeline(iodev->dsp_context,
+					    iodev->format->num_channels);
 		pipeline = cras_dsp_get_pipeline(iodev->dsp_context);
 	}
 	/* dsp_context mutex locked. Now it's safe to modify dsp
@@ -681,10 +686,16 @@
 	if (node->plugged == plugged)
 		return;
 	node->plugged = plugged;
+	MAINLOG(main_log, MAIN_THREAD_NODE_PLUGGED, node->dev->info.idx,
+		plugged, 0);
 	if (plugged) {
 		gettimeofday(&node->plugged_time, NULL);
 	} else if (node == node->dev->active_node) {
-		cras_iodev_list_disable_dev(node->dev, false);
+		/*
+		 * Remove normal and pinned streams, when node unplugged.
+		 * TODO(hychao): clean this up, per crbug.com/1006646
+		 */
+		cras_iodev_list_disable_dev(node->dev, true);
 	}
 	cras_iodev_list_notify_nodes_changed();
 }
@@ -708,6 +719,30 @@
 	cras_iodev_list_notify_active_node_changed(iodev->direction);
 }
 
+bool cras_iodev_is_aec_use_case(const struct cras_ionode *node)
+{
+	if ((node->type == CRAS_NODE_TYPE_INTERNAL_SPEAKER) ||
+	    (node->type == CRAS_NODE_TYPE_ECHO_REFERENCE))
+		return true;
+
+	if (node->type == CRAS_NODE_TYPE_MIC)
+		return (node->position == NODE_POSITION_INTERNAL) ||
+		       (node->position == NODE_POSITION_FRONT);
+
+	return false;
+}
+
+bool cras_iodev_is_on_internal_card(const struct cras_ionode *node)
+{
+	if (node->type == CRAS_NODE_TYPE_INTERNAL_SPEAKER)
+		return true;
+	if (node->type == CRAS_NODE_TYPE_HEADPHONE)
+		return true;
+	if (node->type == CRAS_NODE_TYPE_MIC)
+		return true;
+	return false;
+}
+
 float cras_iodev_get_software_volume_scaler(struct cras_iodev *iodev)
 {
 	unsigned int volume;
@@ -722,13 +757,10 @@
 
 float cras_iodev_get_software_gain_scaler(const struct cras_iodev *iodev)
 {
-	float scaler = 1.0f;
-	if (cras_iodev_software_volume_needed(iodev)) {
-		long gain = cras_iodev_adjust_active_node_gain(
-			iodev, cras_system_get_capture_gain());
-		scaler = convert_softvol_scaler_from_dB(gain);
-	}
-	return scaler;
+	if (cras_iodev_software_volume_needed(iodev))
+		return convert_softvol_scaler_from_dB(
+			iodev->active_node->capture_gain);
+	return 1.0f;
 }
 
 int cras_iodev_get_valid_frames(struct cras_iodev *odev,
@@ -925,19 +957,24 @@
 	iodev->min_cb_level = MIN(iodev->buffer_size / 2, cb_level);
 	iodev->max_cb_level = 0;
 	iodev->largest_cb_level = 0;
+	iodev->num_underruns = 0;
 
 	iodev->reset_request_pending = 0;
 	iodev->state = CRAS_IODEV_STATE_OPEN;
 	iodev->highest_hw_level = 0;
 	iodev->input_dsp_offset = 0;
 
+	ewma_power_init(&iodev->ewma, iodev->format->frame_rate);
+
 	if (iodev->direction == CRAS_STREAM_OUTPUT) {
 		/* If device supports start ops, device can be in open state.
 		 * Otherwise, device starts running right after opening. */
-		if (iodev->start)
+		if (iodev->start) {
 			iodev->state = CRAS_IODEV_STATE_OPEN;
-		else
+		} else {
 			iodev->state = CRAS_IODEV_STATE_NO_STREAM_RUN;
+			cras_iodev_fill_odev_zeros(iodev, iodev->min_cb_level);
+		}
 	} else {
 		iodev->input_data = input_data_create(iodev);
 		/* If this is the echo reference dev, its ext_dsp_module will
@@ -956,8 +993,8 @@
 		/*
 		 * The device specific gain scaler to be used in audio thread.
 		 * It's expected to stick to 1.0f if device has hardware gain
-		 * control. For alsa device, this gain value can be configured
-		 * through UCM labels DefaultNodeGain.
+		 * control. For alsa device, this gain value will be configured
+		 * based on UCM labels IntrinsicSensitivity.
 		 */
 		iodev->software_gain_scaler =
 			cras_iodev_get_software_gain_scaler(iodev);
@@ -982,7 +1019,11 @@
 	if (!cras_iodev_is_open(iodev))
 		return 0;
 
-	cras_server_metrics_device_runtime(iodev);
+	if (iodev->active_node) {
+		cras_server_metrics_device_runtime(iodev);
+		cras_server_metrics_device_gain(iodev);
+		cras_server_metrics_device_volume(iodev);
+	}
 
 	if (iodev->input_data) {
 		if (iodev->ext_dsp_module == &iodev->input_data->ext)
@@ -992,7 +1033,8 @@
 
 	rc = iodev->close_dev(iodev);
 	if (rc)
-		return rc;
+		syslog(LOG_ERR, "Error closing dev %s, rc %d", iodev->info.name,
+		       rc);
 	iodev->state = CRAS_IODEV_STATE_CLOSE;
 	if (iodev->ramp)
 		cras_ramp_reset(iodev->ramp);
@@ -1050,13 +1092,17 @@
 
 	/* Calculate whether the final output was non-empty, if requested. */
 	if (is_non_empty) {
-		unsigned int i;
-		for (i = 0; i < nframes * cras_get_format_bytes(fmt); i++) {
-			if (frames[i]) {
-				*is_non_empty = 1;
-				break;
-			}
-		}
+		const size_t bytes = nframes * cras_get_format_bytes(fmt);
+
+		/*
+		 * Speed up checking frames are all zeros using memcmp.
+		 * frames contains all zeros if both conditions are met:
+		 *  - frames[0] is 0.
+		 *  - frames[i] == frames[i+1] for i in [0, 1, ..., bytes - 2].
+		 */
+		*is_non_empty = bytes ? (*frames || memcmp(frames, frames + 1,
+							   bytes - 1)) :
+					0;
 	}
 
 	DL_FOREACH (iodev->loopbacks, loopback) {
@@ -1065,6 +1111,9 @@
 					    loopback->cb_data);
 	}
 
+	ewma_power_calculate(&iodev->ewma, (int16_t *)frames,
+			     iodev->format->num_channels, nframes);
+
 	rc = apply_dsp(iodev, frames, nframes);
 	if (rc)
 		return rc;
@@ -1168,6 +1217,11 @@
 			       *frames - iodev->input_dsp_offset);
 		if (rc)
 			return rc;
+		ewma_power_calculate_area(
+			&iodev->ewma,
+			(int16_t *)(hw_buffer +
+				    iodev->input_dsp_offset * frame_bytes),
+			data->area, *frames - iodev->input_dsp_offset);
 	}
 
 	if (cras_system_get_capture_mute())
@@ -1243,9 +1297,6 @@
 	int rc;
 
 	rc = iodev->frames_queued(iodev, hw_tstamp);
-	if (rc == -EPIPE)
-		cras_audio_thread_event_severe_underrun();
-
 	if (rc < 0)
 		return rc;
 
@@ -1295,7 +1346,7 @@
 
 		/* This assumes consecutive channel areas. */
 		buf = area->channels[0].buf;
-		memset(buf, 0, frames_written * frame_bytes);
+		memset(buf, 0, (size_t)frames_written * (size_t)frame_bytes);
 		cras_iodev_put_output_buffer(odev, buf, frames_written, NULL,
 					     NULL);
 		frames -= frames_written;
@@ -1304,8 +1355,12 @@
 	return 0;
 }
 
-int cras_iodev_output_underrun(struct cras_iodev *odev)
+int cras_iodev_output_underrun(struct cras_iodev *odev, unsigned int hw_level,
+			       unsigned int frames_written)
 {
+	ATLOG(atlog, AUDIO_THREAD_UNDERRUN, odev->info.idx, hw_level,
+	      frames_written);
+	odev->num_underruns++;
 	cras_audio_thread_event_underrun();
 	if (odev->output_underrun)
 		return odev->output_underrun(odev);
@@ -1326,9 +1381,10 @@
 		odev->state == CRAS_IODEV_STATE_NO_STREAM_RUN);
 }
 
-unsigned int cras_iodev_frames_to_play_in_sleep(struct cras_iodev *odev,
-						unsigned int *hw_level,
-						struct timespec *hw_tstamp)
+unsigned int
+cras_iodev_default_frames_to_play_in_sleep(struct cras_iodev *odev,
+					   unsigned int *hw_level,
+					   struct timespec *hw_tstamp)
 {
 	int rc = cras_iodev_frames_queued(odev, hw_tstamp);
 	unsigned int level = (rc < 0) ? 0 : rc;
@@ -1371,6 +1427,19 @@
 		return 0;
 }
 
+unsigned int cras_iodev_frames_to_play_in_sleep(struct cras_iodev *odev,
+						unsigned int *hw_level,
+						struct timespec *hw_tstamp)
+{
+	/* Use odev's own implementation, if not supported then fall back
+	 * to default behavior below. */
+	if (odev->frames_to_play_in_sleep)
+		return odev->frames_to_play_in_sleep(odev, hw_level, hw_tstamp);
+	else
+		return cras_iodev_default_frames_to_play_in_sleep(
+			odev, hw_level, hw_tstamp);
+}
+
 int cras_iodev_default_no_stream_playback(struct cras_iodev *odev, int enable)
 {
 	if (enable)
@@ -1403,9 +1472,7 @@
 
 unsigned int cras_iodev_get_num_underruns(const struct cras_iodev *iodev)
 {
-	if (iodev->get_num_underruns)
-		return iodev->get_num_underruns(iodev);
-	return 0;
+	return iodev->num_underruns;
 }
 
 unsigned int cras_iodev_get_num_severe_underruns(const struct cras_iodev *iodev)
@@ -1433,7 +1500,7 @@
 	return cras_device_monitor_reset_device(iodev->info.idx);
 }
 
-static void ramp_mute_callback(void *data)
+static void ramp_down_mute_callback(void *data)
 {
 	struct cras_iodev *odev = (struct cras_iodev *)data;
 	cras_device_monitor_set_device_mute_state(odev->info.idx);
@@ -1469,9 +1536,23 @@
 		from = 1.0;
 		to = 0.0;
 		duration_secs = RAMP_MUTE_DURATION_SECS;
-		cb = ramp_mute_callback;
+		cb = ramp_down_mute_callback;
 		cb_data = (void *)odev;
 		break;
+	case CRAS_IODEV_RAMP_REQUEST_RESUME_MUTE:
+		from = 0;
+		to = 0;
+		duration_secs = RAMP_RESUME_MUTE_DURATION_SECS;
+		odev->initial_ramp_request =
+			CRAS_IODEV_RAMP_REQUEST_UP_START_PLAYBACK;
+		break;
+	case CRAS_IODEV_RAMP_REQUEST_SWITCH_MUTE:
+		from = 0;
+		to = 0;
+		duration_secs = RAMP_SWITCH_MUTE_DURATION_SECS;
+		odev->initial_ramp_request =
+			CRAS_IODEV_RAMP_REQUEST_UP_START_PLAYBACK;
+		break;
 	default:
 		return -EINVAL;
 	}
@@ -1541,6 +1622,22 @@
 void cras_iodev_update_highest_hw_level(struct cras_iodev *iodev,
 					unsigned int hw_level)
 {
+	/*
+	 * If the hw_level is unreasonably high and reach to the device's
+	 * buffer size, regard it as a device overrun.
+	 * In the normal status, the hw_level for should be between 1 to 2
+	 * largest_cb_level for an output device and 0 to 1 largest_cb_level
+	 * for an input device. Therefore, larger than 3 can be considered
+	 * unreasonable.
+	 */
+	if (hw_level == iodev->buffer_size &&
+	    iodev->largest_cb_level * 3 < iodev->buffer_size) {
+		ATLOG(atlog, AUDIO_THREAD_DEV_OVERRUN, iodev->info.idx,
+		      hw_level, 0);
+		/* Only log the event when the first time it happens. */
+		if (iodev->highest_hw_level != hw_level)
+			cras_audio_thread_event_dev_overrun();
+	}
 	iodev->highest_hw_level = MAX(iodev->highest_hw_level, hw_level);
 }
 
@@ -1555,7 +1652,8 @@
 static int cras_iodev_drop_frames(struct cras_iodev *iodev, unsigned int frames)
 {
 	struct timespec hw_tstamp;
-	int rc;
+	int i, rc;
+	unsigned int target_frames, dropped_frames = 0;
 
 	if (iodev->direction != CRAS_STREAM_INPUT)
 		return -EINVAL;
@@ -1564,23 +1662,33 @@
 	if (rc < 0)
 		return rc;
 
-	frames = MIN(frames, rc);
-
-	rc = iodev->get_buffer(iodev, &iodev->input_data->area, &frames);
-	if (rc < 0)
-		return rc;
-
-	rc = iodev->put_buffer(iodev, frames);
-	if (rc < 0)
-		return rc;
+	target_frames = MIN(frames, rc);
 
 	/*
-	 * Tell rate estimator that some frames have been dropped to avoid calculating
-	 * the wrong rate.
+	 * Loop reading the buffer, at most twice. This is to cover when
+	 * circular buffer is at the end and returns partial of the target
+	 * frames.
 	 */
-	rate_estimator_add_frames(iodev->rate_est, -frames);
+	for (i = 0; (dropped_frames < target_frames) && (i < 2); i++) {
+		frames = target_frames - dropped_frames;
+		rc = iodev->get_buffer(iodev, &iodev->input_data->area,
+				       &frames);
+		if (rc < 0)
+			return rc;
 
-	ATLOG(atlog, AUDIO_THREAD_DEV_DROP_FRAMES, iodev->info.idx, frames, 0);
+		rc = iodev->put_buffer(iodev, frames);
+		if (rc < 0)
+			return rc;
+		dropped_frames += frames;
+		/*
+		 * Tell rate estimator that some frames have been dropped to
+		 * avoid calculating the wrong rate.
+		 */
+		rate_estimator_add_frames(iodev->rate_est, -frames);
+	}
+
+	ATLOG(atlog, AUDIO_THREAD_DEV_DROP_FRAMES, iodev->info.idx,
+	      dropped_frames, 0);
 
 	return frames;
 }
@@ -1599,3 +1707,13 @@
 
 	return rc;
 }
+
+bool cras_iodev_support_noise_cancellation(const struct cras_iodev *iodev)
+{
+	if (iodev->direction != CRAS_STREAM_INPUT)
+		return false;
+
+	if (iodev->support_noise_cancellation)
+		return !!iodev->support_noise_cancellation(iodev);
+	return false;
+}
diff --git a/cras/src/server/cras_iodev.h b/cras/src/server/cras_iodev.h
index f699933..18a0962 100644
--- a/cras/src/server/cras_iodev.h
+++ b/cras/src/server/cras_iodev.h
@@ -19,6 +19,7 @@
 #include "cras_dsp.h"
 #include "cras_iodev_info.h"
 #include "cras_messages.h"
+#include "ewma_power.h"
 
 struct buffer_share;
 struct cras_fmt_conv;
@@ -96,13 +97,13 @@
  *    plugged - true if the device is plugged.
  *    plugged_time - If plugged is true, this is the time it was attached.
  *    volume - per-node volume (0-100)
- *    capture_gain - per-node capture gain/attenuation (in 100*dBFS)
+ *    capture_gain - Internal per-node capture gain/attenuation (in 100*dBFS)
+ *        This is only used for CRAS internal tuning, no way to change by
+ *        client.
+ *    ui_gain_scaler - The adjustable gain scaler set by client.
  *    left_right_swapped - If left and right output channels are swapped.
  *    type - Type displayed to the user.
  *    position - Specify where on the system this node locates.
- *    mic_positions - Whitespace-separated microphone positions using Cartesian
- *      coordinates in meters with ordering x, y, z. The string is formatted as:
- *      "x1 y1 z1 ... xn yn zn" for an n-microphone array.
  *    name - Name displayed to the user.
  *    dsp_name - The "DspName" variable specified in the ucm config.
  *    active_hotword_model - name of the currently selected hotword model.
@@ -110,8 +111,8 @@
  *    software_volume_needed - For output: True if the volume range of the node
  *      is smaller than desired. For input: True if this node needs software
  *      gain.
- *    min_software_gain - The minimum software gain in 0.01 dB if needed.
- *    max_software_gain - The maximum software gain in 0.01 dB if needed.
+ *    intrinsic_sensitivity - The "IntrinsicSensitivity" in 0.01 dBFS/Pa
+ *    specified in the ucm config.
  *    stable_id - id for node that doesn't change after unplug/plug.
  *    is_sco_pcm - Bool to indicate whether the ionode is for SCO over PCM.
  */
@@ -122,17 +123,16 @@
 	struct timeval plugged_time;
 	unsigned int volume;
 	long capture_gain;
+	float ui_gain_scaler;
 	int left_right_swapped;
 	enum CRAS_NODE_TYPE type;
 	enum CRAS_NODE_POSITION position;
-	char mic_positions[CRAS_NODE_MIC_POS_BUFFER_SIZE];
 	char name[CRAS_NODE_NAME_BUFFER_SIZE];
 	const char *dsp_name;
 	char active_hotword_model[CRAS_NODE_HOTWORD_MODEL_BUFFER_SIZE];
 	float *softvol_scalers;
 	int software_volume_needed;
-	long min_software_gain;
-	long max_software_gain;
+	long intrinsic_sensitivity;
 	unsigned int stable_id;
 	int is_sco_pcm;
 	struct cras_ionode *prev, *next;
@@ -140,8 +140,8 @@
 
 /* An input or output device, that can have audio routed to/from it.
  * set_volume - Function to call if the system volume changes.
+ * set_capture_gain - Function to call if active node's capture_gain changes.
  * set_mute - Function to call if the system mute state changes.
- * set_capture_gain - Function to call if the system capture_gain changes.
  * set_capture_mute - Function to call if the system capture mute state changes.
  * set_swap_mode_for_node - Function to call to set swap mode for the node.
  * open_dev - Opens the device.
@@ -175,12 +175,17 @@
  * set_hotword_model - Sets the hotword model to this iodev.
  * get_hotword_models - Gets a comma separated string of the list of supported
  *     hotword models of this iodev.
- * get_num_underruns - Gets number of underrun recorded so far.
  * get_num_severe_underruns - Gets number of severe underrun recorded since
  *                            iodev was created.
  * get_valid_frames - Gets number of valid frames in device which have not
  *                    played yet. Valid frames does not include zero samples
  *                    we filled under no streams state.
+ * frames_to_play_in_sleep - Returns the non-negative number of frames that
+ *        audio thread can sleep before serving this playback dev the next time.
+ *        Not implementing this ops means fall back to default behavior in
+ *        cras_iodev_default_frames_to_play_in_sleep().
+ * support_noise_cancellation - (Optional) Checks if the device supports noise
+ *                              cancellation.
  * format - The audio format being rendered or captured to hardware.
  * rate_est - Rate estimator to estimate the actual device rate.
  * area - Information about how the samples are stored.
@@ -212,6 +217,7 @@
  * largest_cb_level - The largest callback level of streams attached to this
  *                    device. The difference with max_cb_level is it takes all
  *                    streams into account even if they have been removed.
+ * num_underruns - Number of times we have run out of data (playback only).
  * buf_state - If multiple streams are writing to this device, then this
  *     keeps track of how much each stream has written.
  * idle_timeout - The timestamp when to close the dev after being idle.
@@ -232,6 +238,9 @@
  *                    been processed by the input DSP.
  * input_data - Used to pass audio input data to streams with or without
  *              stream side processing.
+ * initial_ramp_request - The value indicates which type of ramp the device
+ * should perform when some samples are ready for playback.
+ * ewma - The ewma instance to calculate iodev volume.
  */
 struct cras_iodev {
 	void (*set_volume)(struct cras_iodev *iodev);
@@ -261,10 +270,13 @@
 	int (*set_hotword_model)(struct cras_iodev *iodev,
 				 const char *model_name);
 	char *(*get_hotword_models)(struct cras_iodev *iodev);
-	unsigned int (*get_num_underruns)(const struct cras_iodev *iodev);
 	unsigned int (*get_num_severe_underruns)(const struct cras_iodev *iodev);
-	int (*get_valid_frames)(const struct cras_iodev *odev,
+	int (*get_valid_frames)(struct cras_iodev *odev,
 				struct timespec *tstamp);
+	unsigned int (*frames_to_play_in_sleep)(struct cras_iodev *iodev,
+						unsigned int *hw_level,
+						struct timespec *hw_tstamp);
+	int (*support_noise_cancellation)(const struct cras_iodev *iodev);
 	struct cras_audio_format *format;
 	struct rate_estimator *rate_est;
 	struct cras_audio_area *area;
@@ -289,6 +301,7 @@
 	unsigned int max_cb_level;
 	unsigned int highest_hw_level;
 	unsigned int largest_cb_level;
+	unsigned int num_underruns;
 	struct buffer_share *buf_state;
 	struct timespec idle_timeout;
 	struct timespec open_ts;
@@ -301,7 +314,9 @@
 	int input_streaming;
 	unsigned int input_frames_read;
 	unsigned int input_dsp_offset;
+	unsigned int initial_ramp_request;
 	struct input_data *input_data;
+	struct ewma_power ewma;
 	struct cras_iodev *prev, *next;
 };
 
@@ -329,12 +344,24 @@
  * - CRAS_IODEV_RAMP_REQUEST_UP_START_PLAYBACK: Ramping is requested because
  *   first sample of new stream is ready, there is no need to change mute/unmute
  *   state.
+ *
+ * - CRAS_IODEV_RAMP_REQUEST_RESUME_MUTE: To prevent popped noise, mute the
+ *   device for RAMP_RESUME_MUTE_DURATION_SECS seconds on sample ready after
+ *   resume if there were playback stream before suspend.
+ *
+ * - CRAS_IODEV_RAMP_REQUEST_SWITCH_MUTE: To prevent popped noise, mute the
+ *   device for RAMP_SWITCH_MUTE_DURATION_SECS seconds on sample ready after
+ *   device switch if there were playback stream before switch.
+ *
  */
 
 enum CRAS_IODEV_RAMP_REQUEST {
-	CRAS_IODEV_RAMP_REQUEST_UP_UNMUTE = 0,
-	CRAS_IODEV_RAMP_REQUEST_DOWN_MUTE = 1,
-	CRAS_IODEV_RAMP_REQUEST_UP_START_PLAYBACK = 2,
+	CRAS_IODEV_RAMP_REQUEST_NONE = 0,
+	CRAS_IODEV_RAMP_REQUEST_UP_UNMUTE = 1,
+	CRAS_IODEV_RAMP_REQUEST_DOWN_MUTE = 2,
+	CRAS_IODEV_RAMP_REQUEST_UP_START_PLAYBACK = 3,
+	CRAS_IODEV_RAMP_REQUEST_RESUME_MUTE = 4,
+	CRAS_IODEV_RAMP_REQUEST_SWITCH_MUTE = 5,
 };
 
 /*
@@ -432,6 +459,12 @@
 void cras_iodev_set_active_node(struct cras_iodev *iodev,
 				struct cras_ionode *node);
 
+/* Checks if the node is the typical playback or capture option for AEC usage. */
+bool cras_iodev_is_aec_use_case(const struct cras_ionode *node);
+
+/* Checks if the node is a playback or capture node on internal card. */
+bool cras_iodev_is_on_internal_card(const struct cras_ionode *node);
+
 /* Adjust the system volume based on the volume of the given node. */
 static inline unsigned int
 cras_iodev_adjust_node_volume(const struct cras_ionode *node,
@@ -456,17 +489,6 @@
 	return cras_iodev_adjust_node_volume(iodev->active_node, system_volume);
 }
 
-/* Get the gain adjusted based on system for the active node. */
-static inline long
-cras_iodev_adjust_active_node_gain(const struct cras_iodev *iodev,
-				   long system_gain)
-{
-	if (!iodev->active_node)
-		return system_gain;
-
-	return iodev->active_node->capture_gain + system_gain;
-}
-
 /* Returns true if the active node of the iodev needs software volume. */
 static inline int
 cras_iodev_software_volume_needed(const struct cras_iodev *iodev)
@@ -477,39 +499,18 @@
 	if (!iodev->active_node)
 		return 0;
 
+	if (iodev->active_node->intrinsic_sensitivity)
+		return 1;
+
 	return iodev->active_node->software_volume_needed;
 }
 
-/* Returns minimum software gain for the iodev.
- * Args:
- *    iodev - The device.
- * Returs:
- *    0 if software gain is not needed, or if there is no active node.
- *    Returns min_software_gain on active node if there is one. */
-static inline long
-cras_iodev_minimum_software_gain(const struct cras_iodev *iodev)
+static inline float
+cras_iodev_get_ui_gain_scaler(const struct cras_iodev *iodev)
 {
-	if (!cras_iodev_software_volume_needed(iodev))
-		return 0;
 	if (!iodev->active_node)
-		return 0;
-	return iodev->active_node->min_software_gain;
-}
-
-/* Returns maximum software gain for the iodev.
- * Args:
- *    iodev - The device.
- * Returs:
- *    0 if software gain is not needed, or if there is no active node.
- *    Returns max_software_gain on active node if there is one. */
-static inline long
-cras_iodev_maximum_software_gain(const struct cras_iodev *iodev)
-{
-	if (!cras_iodev_software_volume_needed(iodev))
-		return 0;
-	if (!iodev->active_node)
-		return 0;
-	return iodev->active_node->max_software_gain;
+		return 1.0f;
+	return iodev->active_node->ui_gain_scaler;
 }
 
 /* Gets the software gain scaler should be applied on the deivce.
@@ -663,6 +664,17 @@
 /* Put 'frames' worth of zero samples into odev. */
 int cras_iodev_fill_odev_zeros(struct cras_iodev *odev, unsigned int frames);
 
+/*
+ * The default implementation of frames_to_play_in_sleep ops, used when an
+ * iodev doesn't have its own logic.
+ * The default behavior is to calculate how log it takes for buffer level to
+ * run to as low as min_buffer_level.
+ */
+unsigned int
+cras_iodev_default_frames_to_play_in_sleep(struct cras_iodev *odev,
+					   unsigned int *hw_level,
+					   struct timespec *hw_tstamp);
+
 /* Gets the number of frames to play when audio thread sleeps.
  * Args:
  *    iodev[in] - The device.
@@ -758,10 +770,13 @@
 /* Handle output underrun.
  * Args:
  *    odev[in] - The output device.
+ *    hw_level[in] - The current hw_level. Used in the debug log.
+ *    frames_written[in] - The number of written frames. Used in the debug log.
  * Returns:
  *    0 on success. Negative error code on failure.
  */
-int cras_iodev_output_underrun(struct cras_iodev *odev);
+int cras_iodev_output_underrun(struct cras_iodev *odev, unsigned int hw_level,
+			       unsigned int frames_written);
 
 /* Start ramping samples up/down on a device.
  * Args:
@@ -824,4 +839,12 @@
 int cras_iodev_drop_frames_by_time(struct cras_iodev *iodev,
 				   struct timespec ts);
 
+/* Checks if an input device supports noise cancellation.
+ * Args:
+ *    iodev - The device.
+ * Returns:
+ *    True if device supports noise cancellation. False otherwise.
+ */
+bool cras_iodev_support_noise_cancellation(const struct cras_iodev *iodev);
+
 #endif /* CRAS_IODEV_H_ */
diff --git a/cras/src/server/cras_iodev_list.c b/cras/src/server/cras_iodev_list.c
index c581acd..b818c97 100644
--- a/cras/src/server/cras_iodev_list.c
+++ b/cras/src/server/cras_iodev_list.c
@@ -11,6 +11,7 @@
 #include "cras_iodev_info.h"
 #include "cras_iodev_list.h"
 #include "cras_loopback_iodev.h"
+#include "cras_main_thread_log.h"
 #include "cras_observer.h"
 #include "cras_rstream.h"
 #include "cras_server.h"
@@ -18,6 +19,7 @@
 #include "cras_types.h"
 #include "cras_system_state.h"
 #include "server_stream.h"
+#include "softvol_curve.h"
 #include "stream_list.h"
 #include "test_iodev.h"
 #include "utlist.h"
@@ -52,6 +54,8 @@
 	struct device_enabled_cb *next, *prev;
 };
 
+struct main_thread_event_log *main_log;
+
 /* Lists for devs[CRAS_STREAM_INPUT] and devs[CRAS_STREAM_OUTPUT]. */
 static struct iodev_list devs[CRAS_NUM_DIRECTIONS];
 /* The observer client iodev_list used to listen on various events. */
@@ -87,6 +91,9 @@
 static const unsigned int INIT_DEV_DELAY_MS = 1000;
 /* Flag to indicate that hotword streams are suspended. */
 static int hotword_suspended = 0;
+/* Flag to indicate that suspended hotword streams should be auto-resumed at
+ * system resume. */
+static int hotword_auto_resume = 0;
 
 static void idle_dev_check(struct cras_timer *timer, void *data);
 
@@ -220,10 +227,16 @@
 		return "USB";
 	case CRAS_NODE_TYPE_BLUETOOTH:
 		return "BLUETOOTH";
+	case CRAS_NODE_TYPE_BLUETOOTH_NB_MIC:
+		return "BLUETOOTH_NB_MIC";
 	case CRAS_NODE_TYPE_FALLBACK_NORMAL:
 		return "FALLBACK_NORMAL";
 	case CRAS_NODE_TYPE_FALLBACK_ABNORMAL:
 		return "FALLBACK_ABNORMAL";
+	case CRAS_NODE_TYPE_ECHO_REFERENCE:
+		return "ECHO_REFERENCE";
+	case CRAS_NODE_TYPE_ALSA_LOOPBACK:
+		return "ALSA_LOOPBACK";
 	case CRAS_NODE_TYPE_UNKNOWN:
 	default:
 		return "UNKNOWN";
@@ -250,10 +263,10 @@
 				dev->is_enabled && (dev->active_node == node);
 			node_info->volume = node->volume;
 			node_info->capture_gain = node->capture_gain;
+			node_info->ui_gain_scaler = node->ui_gain_scaler;
 			node_info->left_right_swapped =
 				node->left_right_swapped;
 			node_info->stable_id = node->stable_id;
-			strcpy(node_info->mic_positions, node->mic_positions);
 			strcpy(node_info->name, node->name);
 			strcpy(node_info->active_hotword_model,
 			       node->active_hotword_model);
@@ -335,7 +348,7 @@
 	DL_FOREACH (stream_list_get(stream_list), rstream) {
 		if (rstream->apm_list == NULL)
 			continue;
-		cras_apm_list_remove(rstream->apm_list, dev);
+		cras_apm_list_remove_apm(rstream->apm_list, dev);
 	}
 }
 
@@ -351,7 +364,8 @@
 	if (dev->echo_reference_dev == NULL)
 		return;
 
-	server_stream_create(stream_list, dev->echo_reference_dev->info.idx);
+	server_stream_create(stream_list, dev->echo_reference_dev->info.idx,
+			     dev->format);
 }
 
 /*
@@ -367,33 +381,19 @@
 }
 
 /*
- * Close dev if it's opened, without the extra call to idle_dev_check.
- * This is useful for closing a dev inside idle_dev_check function to
- * avoid infinite recursive call.
- *
- * Returns:
- *    -EINVAL if device was not opened, otherwise return 0.
+ * Removes all attached streams and close dev if it's opened.
  */
-static int close_dev_without_idle_check(struct cras_iodev *dev)
-{
-	if (!cras_iodev_is_open(dev))
-		return -EINVAL;
-
-	remove_all_streams_from_dev(dev);
-	dev->idle_timeout.tv_sec = 0;
-	cras_iodev_close(dev);
-	possibly_disable_echo_reference(dev);
-	return 0;
-}
-
 static void close_dev(struct cras_iodev *dev)
 {
-	if (close_dev_without_idle_check(dev))
+	if (!cras_iodev_is_open(dev))
 		return;
 
-	if (idle_timer)
-		cras_tm_cancel_timer(cras_system_state_get_tm(), idle_timer);
-	idle_dev_check(NULL, NULL);
+	MAINLOG(main_log, MAIN_THREAD_DEV_CLOSE, dev->info.idx, 0, 0);
+	remove_all_streams_from_dev(dev);
+	dev->idle_timeout.tv_sec = 0;
+	/* close echo ref first to avoid underrun in hardware */
+	possibly_disable_echo_reference(dev);
+	cras_iodev_close(dev);
 }
 
 static void idle_dev_check(struct cras_timer *timer, void *data)
@@ -412,7 +412,7 @@
 		if (edev->dev->idle_timeout.tv_sec == 0)
 			continue;
 		if (timespec_after(&now, &edev->dev->idle_timeout)) {
-			close_dev_without_idle_check(edev->dev);
+			close_dev(edev->dev);
 			continue;
 		}
 		num_idle_devs++;
@@ -467,6 +467,8 @@
 	if (cras_iodev_is_open(dev))
 		return 0;
 	cancel_pending_init_retries(dev->info.idx);
+	MAINLOG(main_log, MAIN_THREAD_DEV_INIT, dev->info.idx,
+		rstream->format.num_channels, rstream->format.frame_rate);
 
 	rc = cras_iodev_open(dev, rstream->cb_threshold, &rstream->format);
 	if (rc)
@@ -486,10 +488,17 @@
 	struct enabled_dev *edev;
 	struct cras_rstream *rstream;
 
+	MAINLOG(main_log, MAIN_THREAD_SUSPEND_DEVS, 0, 0, 0);
+
 	DL_FOREACH (stream_list_get(stream_list), rstream) {
 		if (rstream->is_pinned) {
 			struct cras_iodev *dev;
 
+			/* Skip closing hotword stream in the first pass.
+			 * Closing an input device may resume hotword stream
+			 * with its post_close_iodev_hook so we should deal
+			 * with hotword stream in the second pass.
+			 */
 			if ((rstream->flags & HOTWORD_STREAM) == HOTWORD_STREAM)
 				continue;
 
@@ -513,15 +522,52 @@
 	DL_FOREACH (enabled_devs[CRAS_STREAM_INPUT], edev) {
 		close_dev(edev->dev);
 	}
+
+	/* Doing this check after all the other enabled iodevs are closed to
+	 * ensure preempted hotword streams obey the pause_at_suspend flag.
+	 */
+	if (cras_system_get_hotword_pause_at_suspend()) {
+		cras_iodev_list_suspend_hotword_streams();
+		hotword_auto_resume = 1;
+	}
 }
 
 static int stream_added_cb(struct cras_rstream *rstream);
 
 static void resume_devs()
 {
+	struct enabled_dev *edev;
 	struct cras_rstream *rstream;
 
+	int has_output_stream = 0;
 	stream_list_suspended = 0;
+
+	MAINLOG(main_log, MAIN_THREAD_RESUME_DEVS, 0, 0, 0);
+
+	/* Auto-resume based on the local flag in case the system state flag has
+	 * changed.
+	 */
+	if (hotword_auto_resume) {
+		cras_iodev_list_resume_hotword_stream();
+		hotword_auto_resume = 0;
+	}
+
+	/*
+	 * To remove the short popped noise caused by applications that can not
+	 * stop playback "right away" after resume, we mute all output devices
+	 * for a short time if there is any output stream.
+	 */
+	DL_FOREACH (stream_list_get(stream_list), rstream) {
+		if (rstream->direction == CRAS_STREAM_OUTPUT)
+			has_output_stream++;
+	}
+	if (has_output_stream) {
+		DL_FOREACH (enabled_devs[CRAS_STREAM_OUTPUT], edev) {
+			edev->dev->initial_ramp_request =
+				CRAS_IODEV_RAMP_REQUEST_RESUME_MUTE;
+		}
+	}
+
 	DL_FOREACH (stream_list_get(stream_list), rstream) {
 		if ((rstream->flags & HOTWORD_STREAM) == HOTWORD_STREAM)
 			continue;
@@ -538,18 +584,6 @@
 		resume_devs();
 }
 
-/* Called when the system capture gain changes.  Pass the current capture_gain
- * setting to the default input if it is active. */
-void sys_cap_gain_change(void *context, int32_t gain)
-{
-	struct cras_iodev *dev;
-
-	DL_FOREACH (devs[CRAS_STREAM_INPUT].iodevs, dev) {
-		if (dev->set_capture_gain && cras_iodev_is_open(dev))
-			dev->set_capture_gain(dev);
-	}
-}
-
 /* Called when the system capture mute state changes.  Pass the current capture
  * mute setting to the default input if it is active. */
 static void sys_cap_mute_change(void *context, int muted, int mute_locked)
@@ -616,8 +650,10 @@
 	int i;
 	if (stream->apm_list) {
 		for (i = 0; i < num_iodevs; i++)
-			cras_apm_list_add(stream->apm_list, iodevs[i],
-					  iodevs[i]->format);
+			cras_apm_list_add_apm(stream->apm_list, iodevs[i],
+					      iodevs[i]->format,
+					      cras_iodev_is_aec_use_case(
+						      iodevs[i]->active_node));
 	}
 	return audio_thread_add_stream(audio_thread, stream, iodevs,
 				       num_iodevs);
@@ -638,20 +674,39 @@
 	/* If there are active streams to attach to this device,
 	 * open it. */
 	DL_FOREACH (stream_list_get(stream_list), stream) {
+		bool can_attach = 0;
+
 		if (stream->direction != dir)
 			continue;
 		/*
-		 * Don't attach this stream if (1) this stream pins to a
-		 * different device, or (2) this is a normal stream, but
-		 * device is not enabled.
+		 * For normal stream, if device is enabled by UI then it can
+		 * attach to this dev.
 		 */
-		if (stream->is_pinned) {
-			if (stream->pinned_dev_idx != dev->info.idx)
-				continue;
-		} else if (!dev_enabled) {
-			continue;
+		if (!stream->is_pinned) {
+			can_attach = dev_enabled;
+		}
+		/*
+		 * If this is a pinned stream, attach it if its pinned dev id
+		 * matches this device or any fallback dev. Note that attaching
+		 * a pinned stream to fallback device is temporary. When the
+		 * fallback dev gets disabled in possibly_disable_fallback()
+		 * the check stream_list_has_pinned_stream() is key to allow
+		 * all streams to be removed from fallback and close it.
+		 */
+		else if ((stream->pinned_dev_idx == dev->info.idx) ||
+			 (SILENT_PLAYBACK_DEVICE == dev->info.idx) ||
+			 (SILENT_RECORD_DEVICE == dev->info.idx)) {
+			can_attach = 1;
 		}
 
+		if (!can_attach)
+			continue;
+
+		/*
+		 * Note that the stream list is descending ordered by channel
+		 * count, which guarantees the first attachable stream will have
+		 * the highest channel count.
+		 */
 		rc = init_device(dev, stream);
 		if (rc) {
 			syslog(LOG_ERR, "Enable %s failed, rc = %d",
@@ -723,6 +778,11 @@
 	return 0;
 }
 
+/*
+ * Close device enabled by pinned stream. Since it's NOT in the enabled
+ * dev list, make sure update_active_node() is called to correctly
+ * configure the ALSA UCM or BT profile state.
+ */
 static int close_pinned_device(struct cras_iodev *dev)
 {
 	close_dev(dev);
@@ -776,34 +836,64 @@
 	struct cras_iodev *iodevs[10];
 	unsigned int num_iodevs;
 	int rc;
+	bool iodev_reopened;
 
 	if (stream_list_suspended)
 		return 0;
 
+	MAINLOG(main_log, MAIN_THREAD_STREAM_ADDED, rstream->stream_id,
+		rstream->direction, rstream->buffer_frames);
+
 	if (rstream->is_pinned)
 		return pinned_stream_added(rstream);
 
 	/* Add the new stream to all enabled iodevs at once to avoid offset
 	 * in shm level between different ouput iodevs. */
 	num_iodevs = 0;
+	iodev_reopened = false;
 	DL_FOREACH (enabled_devs[rstream->direction], edev) {
 		if (num_iodevs >= ARRAY_SIZE(iodevs)) {
 			syslog(LOG_ERR, "too many enabled devices");
 			break;
 		}
 
-		rc = init_device(edev->dev, rstream);
-		if (rc) {
-			/* Error log but don't return error here, because
-			 * stopping audio could block video playback.
+		if (cras_iodev_is_open(edev->dev) &&
+		    (rstream->format.num_channels >
+		     edev->dev->format->num_channels) &&
+		    (rstream->format.num_channels <=
+		     edev->dev->info.max_supported_channels)) {
+			/* Re-open the device with the format of the attached
+			 * stream if it has higher channel count than the
+			 * current format of the device, and doesn't exceed the
+			 * max_supported_channels of the device.
+			 * Fallback device will be transciently enabled during
+			 * the device re-opening.
 			 */
-			syslog(LOG_ERR, "Init %s failed, rc = %d",
-			       edev->dev->info.name, rc);
-			schedule_init_device_retry(edev->dev);
-			continue;
-		}
+			MAINLOG(main_log, MAIN_THREAD_DEV_REOPEN,
+				rstream->format.num_channels,
+				edev->dev->format->num_channels,
+				edev->dev->format->frame_rate);
+			syslog(LOG_INFO, "re-open %s for higher channel count",
+			       edev->dev->info.name);
+			possibly_enable_fallback(rstream->direction, false);
+			cras_iodev_list_suspend_dev(edev->dev->info.idx);
+			cras_iodev_list_resume_dev(edev->dev->info.idx);
+			possibly_disable_fallback(rstream->direction);
+			iodev_reopened = true;
+		} else {
+			rc = init_device(edev->dev, rstream);
+			if (rc) {
+				/* Error log but don't return error here, because
+				 * stopping audio could block video playback.
+				 */
+				syslog(LOG_ERR, "Init %s failed, rc = %d",
+				       edev->dev->info.name, rc);
+				schedule_init_device_retry(edev->dev);
+				continue;
+			}
 
-		iodevs[num_iodevs++] = edev->dev;
+			iodevs[num_iodevs++] = edev->dev;
+		}
 	}
 	if (num_iodevs) {
 		rc = add_stream_to_open_devs(rstream, iodevs, num_iodevs);
@@ -811,9 +901,9 @@
 			syslog(LOG_ERR, "adding stream to thread fail");
 			return rc;
 		}
-	} else {
+	} else if (!iodev_reopened) {
 		/* Enable fallback device if no other iodevs can be initialized
-		 * successfully.
+		 * or re-opened successfully.
 		 * For error codes like EAGAIN and ENOENT, a new iodev will be
 		 * enabled soon so streams are going to route there. As for the
 		 * rest of the error cases, silence will be played or recorded
@@ -880,6 +970,8 @@
 	if (rc)
 		return rc;
 
+	MAINLOG(main_log, MAIN_THREAD_STREAM_REMOVED, rstream->stream_id, 0, 0);
+
 	if (rstream->is_pinned)
 		pinned_stream_removed(rstream);
 
@@ -926,6 +1018,7 @@
 	struct cras_rstream *stream;
 	struct device_enabled_cb *callback;
 
+	MAINLOG(main_log, MAIN_THREAD_DEV_DISABLE, dev->info.idx, force, 0);
 	/*
 	 * Remove from enabled dev list. However this dev could have a stream
 	 * pinned to it, only cancel pending init timers when force flag is set.
@@ -933,26 +1026,23 @@
 	DL_DELETE(enabled_devs[dir], edev);
 	free(edev);
 	dev->is_enabled = 0;
-	if (force)
+	if (force) {
 		cancel_pending_init_retries(dev->info.idx);
-
-	/*
-	 * Pull all default streams off this device.
-	 * Pull all pinned streams off as well if force is true.
-	 */
-	DL_FOREACH (stream_list_get(stream_list), stream) {
-		if (stream->direction != dev->direction)
-			continue;
-		if (stream->is_pinned && !force)
-			continue;
-		audio_thread_disconnect_stream(audio_thread, stream, dev);
 	}
-	/* If this is a force disable call, that guarantees pinned streams have
-	 * all been detached. Otherwise check with stream_list to see if
-	 * there's still a pinned stream using this device.
-	 */
-	if (!force && stream_list_has_pinned_stream(stream_list, dev->info.idx))
+	/* If there's a pinned stream exists, simply disconnect all the normal
+	 * streams off this device and return. */
+	else if (stream_list_has_pinned_stream(stream_list, dev->info.idx)) {
+		DL_FOREACH (stream_list_get(stream_list), stream) {
+			if (stream->direction != dev->direction)
+				continue;
+			if (stream->is_pinned)
+				continue;
+			audio_thread_disconnect_stream(audio_thread, stream,
+						       dev);
+		}
 		return 0;
+	}
+
 	DL_FOREACH (device_enable_cbs, callback)
 		callback->disabled_cb(dev, callback->cb_data);
 	close_dev(dev);
@@ -962,36 +1052,6 @@
 }
 
 /*
- * Assume the device is not in enabled_devs list.
- * Assume there is no default stream on the device.
- * An example is that this device is unplugged while it is playing
- * a pinned stream. The device and stream may have been removed in
- * audio thread due to I/O error handling.
- */
-static int force_close_pinned_only_device(struct cras_iodev *dev)
-{
-	struct cras_rstream *rstream;
-
-	/* Pull pinned streams off this device. Note that this is initiated
-	 * from server side, so the pin stream still exist in stream_list
-	 * pending client side to actually remove it.
-	 */
-	DL_FOREACH (stream_list_get(stream_list), rstream) {
-		if (rstream->direction != dev->direction)
-			continue;
-		if (!rstream->is_pinned)
-			continue;
-		if (dev->info.idx != rstream->pinned_dev_idx)
-			continue;
-		audio_thread_disconnect_stream(audio_thread, rstream, dev);
-	}
-
-	close_dev(dev);
-	dev->update_active_node(dev, dev->active_node->idx, 0);
-	return 0;
-}
-
-/*
  * Exported Interface.
  */
 
@@ -1002,12 +1062,13 @@
 	memset(&observer_ops, 0, sizeof(observer_ops));
 	observer_ops.output_volume_changed = sys_vol_change;
 	observer_ops.output_mute_changed = sys_mute_change;
-	observer_ops.capture_gain_changed = sys_cap_gain_change;
 	observer_ops.capture_mute_changed = sys_cap_mute_change;
 	observer_ops.suspend_changed = sys_suspend_change;
 	list_observer = cras_observer_add(&observer_ops, NULL);
 	idle_timer = NULL;
 
+	main_log = main_thread_event_log_init();
+
 	/* Create the audio stream list for the system. */
 	stream_list =
 		stream_list_create(stream_added_cb, stream_removed_cb,
@@ -1049,6 +1110,7 @@
 	empty_iodev_destroy(fallback_devs[CRAS_STREAM_INPUT]);
 	empty_iodev_destroy(fallback_devs[CRAS_STREAM_OUTPUT]);
 	stream_list_destroy(stream_list);
+	main_thread_event_log_deinit(main_log);
 	if (list_observer) {
 		cras_observer_remove(list_observer);
 		list_observer = NULL;
@@ -1084,6 +1146,8 @@
 	if (!new_dev || new_dev->direction != dir)
 		return;
 
+	MAINLOG(main_log, MAIN_THREAD_ADD_ACTIVE_NODE, new_dev->info.idx, 0, 0);
+
 	/* If the new dev is already enabled but its active node needs to be
 	 * changed. Disable new dev first, update active node, and then
 	 * re-enable it again.
@@ -1123,7 +1187,7 @@
 	 */
 	if (!edev_to_disable) {
 		if (force_close)
-			force_close_pinned_only_device(dev);
+			close_pinned_device(dev);
 		return;
 	}
 
@@ -1141,25 +1205,13 @@
 
 void cras_iodev_list_suspend_dev(unsigned int dev_idx)
 {
-	struct cras_rstream *rstream;
 	struct cras_iodev *dev = find_dev(dev_idx);
 
 	if (!dev)
 		return;
 
-	DL_FOREACH (stream_list_get(stream_list), rstream) {
-		if (rstream->direction != dev->direction)
-			continue;
-		/* Disconnect all streams that are either:
-		 * (1) normal stream while dev is enabled by UI, or
-		 * (2) stream specifically pins to this dev.
-		 */
-		if ((dev->is_enabled && !rstream->is_pinned) ||
-		    (rstream->is_pinned &&
-		     (dev->info.idx != rstream->pinned_dev_idx)))
-			audio_thread_disconnect_stream(audio_thread, rstream,
-						       dev);
-	}
+	/* Remove all streams including the pinned streams, and close
+	 * this iodev. */
 	close_dev(dev);
 	dev->update_active_node(dev, dev->active_node->idx, 0);
 }
@@ -1220,6 +1272,8 @@
 	if (rc)
 		return rc;
 
+	MAINLOG(main_log, MAIN_THREAD_ADD_TO_DEV_LIST, output->info.idx,
+		CRAS_STREAM_OUTPUT, 0);
 	return 0;
 }
 
@@ -1234,6 +1288,8 @@
 	if (rc)
 		return rc;
 
+	MAINLOG(main_log, MAIN_THREAD_ADD_TO_DEV_LIST, input->info.idx,
+		CRAS_STREAM_INPUT, 0);
 	return 0;
 }
 
@@ -1476,11 +1532,15 @@
 	struct cras_iodev *new_dev = NULL;
 	struct enabled_dev *edev;
 	int new_node_already_enabled = 0;
+	struct cras_rstream *rstream;
+	int has_output_stream = 0;
 	int rc;
 
 	/* find the devices for the id. */
 	new_dev = find_dev(dev_index_of(node_id));
 
+	MAINLOG(main_log, MAIN_THREAD_SELECT_NODE, dev_index_of(node_id), 0, 0);
+
 	/* Do nothing if the direction is mismatched. The new_dev == NULL case
 	   could happen if node_id is 0 (no selection), or the client tries
 	   to select a non-existing node (maybe it's unplugged just before
@@ -1508,17 +1568,44 @@
 	if (!new_node_already_enabled)
 		possibly_enable_fallback(direction, false);
 
-	/* Disable all devices except for fallback device, and the new device,
-	 * provided it is already enabled. */
 	DL_FOREACH (enabled_devs[direction], edev) {
-		if (edev->dev != fallback_devs[direction] &&
-		    !(new_node_already_enabled && edev->dev == new_dev)) {
+		/* Don't disable fallback devices. */
+		if (edev->dev == fallback_devs[direction])
+			continue;
+		/*
+		 * Disable enabled device if it's not the new one, use non-force
+		 * disable call so we don't interrupt existing pinned streams on
+		 * it.
+		 */
+		if (edev->dev != new_dev) {
 			disable_device(edev, false);
 		}
+		/*
+		 * Otherwise if this happens to be the new device but about to
+		 * select to a different node (on the same dev). Force disable
+		 * this device to avoid any pinned stream occupies it in audio
+		 * thread and cause problem in later update_active_node call.
+		 */
+		else if (!new_node_already_enabled) {
+			disable_device(edev, true);
+		}
 	}
 
 	if (new_dev && !new_node_already_enabled) {
 		new_dev->update_active_node(new_dev, node_index_of(node_id), 1);
+
+		/* To reduce the popped noise of active device change, mute
+		 * new_dev's for RAMP_SWITCH_MUTE_DURATION_SECS s.
+		 */
+		DL_FOREACH (stream_list_get(stream_list), rstream) {
+			if (rstream->direction == CRAS_STREAM_OUTPUT)
+				has_output_stream++;
+		}
+		if (direction == CRAS_STREAM_OUTPUT && has_output_stream) {
+			new_dev->initial_ramp_request =
+				CRAS_IODEV_RAMP_REQUEST_SWITCH_MUTE;
+		}
+
 		rc = enable_device(new_dev);
 		if (rc == 0) {
 			/* Disable fallback device after new device is enabled.
@@ -1560,22 +1647,38 @@
 	if (iodev->set_volume)
 		iodev->set_volume(iodev);
 	cras_iodev_list_notify_node_volume(node);
+	MAINLOG(main_log, MAIN_THREAD_OUTPUT_NODE_VOLUME, iodev->info.idx,
+		volume, 0);
 	return 0;
 }
 
 static int set_node_capture_gain(struct cras_iodev *iodev,
-				 unsigned int node_idx, int capture_gain)
+				 unsigned int node_idx, int value)
 {
 	struct cras_ionode *node;
+	int db_scale;
 
 	node = find_node(iodev, node_idx);
 	if (!node)
 		return -EINVAL;
 
-	node->capture_gain = capture_gain;
+	/* Assert value in range 0 - 100. */
+	if (value < 0)
+		value = 0;
+	if (value > 100)
+		value = 100;
+
+	/* Linear maps (0, 50) to (-4000, 0) and (50, 100) to (0, 2000) dBFS.
+	 * Calculate and store corresponding scaler in ui_gain_scaler. */
+	db_scale = (value > 50) ? 40 : 80;
+	node->ui_gain_scaler =
+		convert_softvol_scaler_from_dB((value - 50) * db_scale);
+
 	if (iodev->set_capture_gain)
 		iodev->set_capture_gain(iodev);
 	cras_iodev_list_notify_node_capture_gain(node);
+	MAINLOG(main_log, MAIN_THREAD_INPUT_NODE_GAIN, iodev->info.idx, value,
+		0);
 	return 0;
 }
 
@@ -1778,6 +1881,24 @@
 	}
 }
 
+void cras_iodev_list_reset_for_noise_cancellation()
+{
+	struct cras_iodev *dev;
+	bool enabled = cras_system_get_noise_cancellation_enabled();
+
+	DL_FOREACH (devs[CRAS_STREAM_INPUT].iodevs, dev) {
+		if (!cras_iodev_is_open(dev) ||
+		    !cras_iodev_support_noise_cancellation(dev))
+			continue;
+		syslog(LOG_INFO, "Re-open %s for %s noise cancellation",
+		       dev->info.name, enabled ? "enabling" : "disabling");
+		possibly_enable_fallback(CRAS_STREAM_INPUT, false);
+		cras_iodev_list_suspend_dev(dev->info.idx);
+		cras_iodev_list_resume_dev(dev->info.idx);
+		possibly_disable_fallback(CRAS_STREAM_INPUT);
+	}
+}
+
 void cras_iodev_list_reset()
 {
 	struct enabled_dev *edev;
diff --git a/cras/src/server/cras_iodev_list.h b/cras/src/server/cras_iodev_list.h
index 61c3a18..d6e9ba5 100644
--- a/cras/src/server/cras_iodev_list.h
+++ b/cras/src/server/cras_iodev_list.h
@@ -274,6 +274,11 @@
 /* Resumes all hotwording streams. */
 int cras_iodev_list_resume_hotword_stream();
 
+/* Sets the state of noise cancellation for input devices which supports noise
+ * cancellation by suspend, enable/disable, then resume.
+ */
+void cras_iodev_list_reset_for_noise_cancellation();
+
 /* For unit test only. */
 void cras_iodev_list_reset();
 
diff --git a/cras/src/server/cras_loopback_iodev.c b/cras/src/server/cras_loopback_iodev.c
index dea9616..cf3ba4a 100644
--- a/cras/src/server/cras_loopback_iodev.c
+++ b/cras/src/server/cras_loopback_iodev.c
@@ -7,6 +7,7 @@
 #include <sys/param.h>
 #include <syslog.h>
 
+#include "audio_thread_log.h"
 #include "byte_buffer.h"
 #include "cras_audio_area.h"
 #include "cras_config.h"
@@ -60,6 +61,9 @@
 
 /*
  * Called in the put buffer function of the sender that hooked to.
+ *
+ * Returns:
+ *   Number of frames copied to the sample buffer in the hook.
  */
 static int sample_hook(const uint8_t *frames, unsigned int nframes,
 		       const struct cras_audio_format *fmt, void *cb_data)
@@ -67,17 +71,26 @@
 	struct loopback_iodev *loopdev = (struct loopback_iodev *)cb_data;
 	struct byte_buffer *sbuf = loopdev->sample_buffer;
 	unsigned int frame_bytes = cras_get_format_bytes(fmt);
-	unsigned int frames_to_copy, bytes_to_copy;
+	unsigned int frames_to_copy, bytes_to_copy, frames_copied = 0;
+	int i;
 
-	frames_to_copy = MIN(buf_writable(sbuf) / frame_bytes, nframes);
-	if (!frames_to_copy)
-		return 0;
+	for (i = 0; i < 2; i++) {
+		frames_to_copy = MIN(buf_writable(sbuf) / frame_bytes, nframes);
+		if (!frames_to_copy)
+			break;
 
-	bytes_to_copy = frames_to_copy * frame_bytes;
-	memcpy(buf_write_pointer(sbuf), frames, bytes_to_copy);
-	buf_increment_write(sbuf, bytes_to_copy);
+		bytes_to_copy = frames_to_copy * frame_bytes;
+		memcpy(buf_write_pointer(sbuf), frames, bytes_to_copy);
+		buf_increment_write(sbuf, bytes_to_copy);
+		frames += bytes_to_copy;
+		nframes -= frames_to_copy;
+		frames_copied += frames_to_copy;
+	}
 
-	return frames_to_copy;
+	ATLOG(atlog, AUDIO_THREAD_LOOPBACK_SAMPLE_HOOK, nframes + frames_copied,
+	      frames_copied, 0);
+
+	return frames_copied;
 }
 
 static void update_first_output_to_loopback(struct loopback_iodev *loopdev)
@@ -206,6 +219,8 @@
 	unsigned int frame_bytes = cras_get_format_bytes(iodev->format);
 	unsigned int avail_frames = buf_readable(sbuf) / frame_bytes;
 
+	ATLOG(atlog, AUDIO_THREAD_LOOPBACK_GET, *frames, avail_frames, 0);
+
 	*frames = MIN(avail_frames, *frames);
 	iodev->area->frames = *frames;
 	cras_audio_area_config_buf_pointers(iodev->area, iodev->format,
@@ -221,8 +236,9 @@
 	struct byte_buffer *sbuf = loopdev->sample_buffer;
 	unsigned int frame_bytes = cras_get_format_bytes(iodev->format);
 
-	buf_increment_read(sbuf, nframes * frame_bytes);
+	buf_increment_read(sbuf, (size_t)nframes * (size_t)frame_bytes);
 	loopdev->read_frames += nframes;
+	ATLOG(atlog, AUDIO_THREAD_LOOPBACK_PUT, nframes, 0, 0);
 	return 0;
 }
 
@@ -281,6 +297,12 @@
 	iodev->put_buffer = put_record_buffer;
 	iodev->flush_buffer = flush_record_buffer;
 
+	/*
+	 * Record max supported channels into cras_iodev_info.
+	 * The value is the max of loopback_supported_channel_counts.
+	 */
+	iodev->info.max_supported_channels = 2;
+
 	return iodev;
 }
 
@@ -309,15 +331,15 @@
 	if (iodev == NULL)
 		return NULL;
 
-	/* Create a dummy ionode */
+	/* Create an empty ionode */
 	node = (struct cras_ionode *)calloc(1, sizeof(*node));
 	node->dev = iodev;
 	node->type = node_type;
 	node->plugged = 1;
 	node->volume = 100;
+	node->ui_gain_scaler = 1.0f;
 	node->stable_id = iodev->info.stable_id;
 	node->software_volume_needed = 0;
-	node->max_software_gain = 0;
 	strcpy(node->name, loopdev_names[type]);
 	cras_iodev_add_node(iodev, node);
 	cras_iodev_set_active_node(iodev, node);
diff --git a/cras/src/server/cras_main_message.c b/cras/src/server/cras_main_message.c
index 9fe62af..b88e400 100644
--- a/cras/src/server/cras_main_message.c
+++ b/cras/src/server/cras_main_message.c
@@ -77,7 +77,7 @@
 	return 0;
 }
 
-static void handle_main_messages(void *arg)
+static void handle_main_messages(void *arg, int revents)
 {
 	uint8_t buf[256];
 	int rc;
@@ -112,5 +112,6 @@
 	cras_make_fd_nonblocking(main_msg_fds[0]);
 	cras_make_fd_nonblocking(main_msg_fds[1]);
 
-	cras_system_add_select_fd(main_msg_fds[0], handle_main_messages, NULL);
+	cras_system_add_select_fd(main_msg_fds[0], handle_main_messages, NULL,
+				  POLLIN);
 }
diff --git a/cras/src/server/cras_main_thread_log.h b/cras/src/server/cras_main_thread_log.h
new file mode 100644
index 0000000..1d92585
--- /dev/null
+++ b/cras/src/server/cras_main_thread_log.h
@@ -0,0 +1,64 @@
+/* Copyright 2020 The Chromium OS 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 CRAS_MAIN_THREAD_LOG_H_
+#define CRAS_MAIN_THREAD_LOG_H_
+
+#include <stdint.h>
+
+#include "cras_types.h"
+
+#define CRAS_MAIN_THREAD_LOGGING 1
+
+#if (CRAS_MAIN_THREAD_LOGGING)
+#define MAINLOG(log, event, data1, data2, data3)                               \
+	main_thread_event_log_data(log, event, data1, data2, data3);
+#else
+#define MAINLOG(log, event, data1, data2, data3)
+#endif
+
+extern struct main_thread_event_log *main_log;
+
+static inline struct main_thread_event_log *main_thread_event_log_init()
+{
+	struct main_thread_event_log *log;
+	log = (struct main_thread_event_log *)calloc(
+		1, sizeof(struct main_thread_event_log));
+	if (!log)
+		return NULL;
+
+	log->len = MAIN_THREAD_EVENT_LOG_SIZE;
+	return log;
+}
+
+static inline void
+main_thread_event_log_deinit(struct main_thread_event_log *log)
+{
+	if (log)
+		free(log);
+}
+
+static inline void main_thread_event_log_data(struct main_thread_event_log *log,
+					      enum MAIN_THREAD_LOG_EVENTS event,
+					      uint32_t data1, uint32_t data2,
+					      uint32_t data3)
+{
+	struct timespec now;
+
+	if (!log)
+		return;
+
+	clock_gettime(CLOCK_MONOTONIC_RAW, &now);
+	log->log[log->write_pos].tag_sec =
+		(event << 24) | ((now.tv_sec % 86400) & 0x00ffffff);
+	log->log[log->write_pos].nsec = now.tv_nsec;
+	log->log[log->write_pos].data1 = data1;
+	log->log[log->write_pos].data2 = data2;
+	log->log[log->write_pos].data3 = data3;
+	log->write_pos++;
+	log->write_pos %= MAIN_THREAD_EVENT_LOG_SIZE;
+}
+
+#endif /* CRAS_MAIN_THREAD_LOG_H_ */
diff --git a/cras/src/server/cras_observer.c b/cras/src/server/cras_observer.c
index 0f6ab96..0f17dc9 100644
--- a/cras/src/server/cras_observer.c
+++ b/cras/src/server/cras_observer.c
@@ -7,6 +7,7 @@
 
 #include "cras_alert.h"
 #include "cras_iodev_list.h"
+#include "cras_types.h"
 #include "utlist.h"
 
 struct cras_observer_client {
@@ -34,6 +35,8 @@
          * per-direciton. */
 	struct cras_alert *num_active_streams[CRAS_NUM_DIRECTIONS];
 	struct cras_alert *non_empty_audio_state_changed;
+	struct cras_alert *bt_battery_changed;
+	struct cras_alert *num_input_streams_with_permission;
 };
 
 struct cras_observer_server {
@@ -75,6 +78,10 @@
 	uint32_t num_active_streams;
 };
 
+struct cras_observer_alert_data_input_streams {
+	uint32_t num_input_streams[CRAS_NUM_CLIENT_TYPE];
+};
+
 struct cras_observer_alert_data_hotword_triggered {
 	int64_t tv_sec;
 	int64_t tv_nsec;
@@ -84,6 +91,11 @@
 	int non_empty;
 };
 
+struct cras_observer_alert_data_bt_battery_changed {
+	const char *address;
+	uint32_t level;
+};
+
 /* Global observer instance. */
 static struct cras_observer_server *g_observer;
 
@@ -247,6 +259,20 @@
 	}
 }
 
+static void num_input_streams_with_permission_alert(void *arg, void *data)
+{
+	struct cras_observer_client *client;
+	struct cras_observer_alert_data_input_streams *input_streams_data =
+		(struct cras_observer_alert_data_input_streams *)data;
+
+	DL_FOREACH (g_observer->clients, client) {
+		if (client->ops.num_input_streams_with_permission_changed)
+			client->ops.num_input_streams_with_permission_changed(
+				client->context,
+				input_streams_data->num_input_streams);
+	}
+}
+
 static void hotword_triggered_alert(void *arg, void *data)
 {
 	struct cras_observer_client *client;
@@ -276,6 +302,20 @@
 	}
 }
 
+static void bt_battery_changed_alert(void *arg, void *data)
+{
+	struct cras_observer_client *client;
+	struct cras_observer_alert_data_bt_battery_changed *triggered_data =
+		(struct cras_observer_alert_data_bt_battery_changed *)data;
+
+	DL_FOREACH (g_observer->clients, client) {
+		if (client->ops.bt_battery_changed)
+			client->ops.bt_battery_changed(client->context,
+						       triggered_data->address,
+						       triggered_data->level);
+	}
+}
+
 static int cras_observer_server_set_alert(struct cras_alert **alert,
 					  cras_alert_cb cb,
 					  cras_alert_prepare prepare,
@@ -332,6 +372,8 @@
 	CRAS_OBSERVER_SET_ALERT(suspend_changed, NULL, 0);
 	CRAS_OBSERVER_SET_ALERT(hotword_triggered, NULL, 0);
 	CRAS_OBSERVER_SET_ALERT(non_empty_audio_state_changed, NULL, 0);
+	CRAS_OBSERVER_SET_ALERT(bt_battery_changed, NULL, 0);
+	CRAS_OBSERVER_SET_ALERT(num_input_streams_with_permission, NULL, 0);
 
 	CRAS_OBSERVER_SET_ALERT_WITH_DIRECTION(num_active_streams,
 					       CRAS_STREAM_OUTPUT);
@@ -363,6 +405,9 @@
 	cras_alert_destroy(g_observer->alerts.suspend_changed);
 	cras_alert_destroy(g_observer->alerts.hotword_triggered);
 	cras_alert_destroy(g_observer->alerts.non_empty_audio_state_changed);
+	cras_alert_destroy(g_observer->alerts.bt_battery_changed);
+	cras_alert_destroy(
+		g_observer->alerts.num_input_streams_with_permission);
 	cras_alert_destroy(
 		g_observer->alerts.num_active_streams[CRAS_STREAM_OUTPUT]);
 	cras_alert_destroy(
@@ -540,6 +585,21 @@
 	cras_alert_pending_data(alert, &data, sizeof(data));
 }
 
+void cras_observer_notify_input_streams_with_permission(
+	uint32_t num_input_streams[CRAS_NUM_CLIENT_TYPE])
+{
+	struct cras_observer_alert_data_input_streams data;
+	struct cras_alert *alert;
+
+	memcpy(&data.num_input_streams, num_input_streams,
+	       sizeof(*num_input_streams) * CRAS_NUM_CLIENT_TYPE);
+	alert = g_observer->alerts.num_input_streams_with_permission;
+	if (!alert)
+		return;
+
+	cras_alert_pending_data(alert, &data, sizeof(data));
+}
+
 void cras_observer_notify_hotword_triggered(int64_t tv_sec, int64_t tv_nsec)
 {
 	struct cras_observer_alert_data_hotword_triggered data;
@@ -559,4 +619,16 @@
 	cras_alert_pending_data(
 		g_observer->alerts.non_empty_audio_state_changed, &data,
 		sizeof(data));
-}
\ No newline at end of file
+}
+
+void cras_observer_notify_bt_battery_changed(const char *address,
+					     uint32_t level)
+{
+	struct cras_observer_alert_data_bt_battery_changed data;
+
+	data.address = address;
+	data.level = level;
+
+	cras_alert_pending_data(g_observer->alerts.bt_battery_changed, &data,
+				sizeof(data));
+}
diff --git a/cras/src/server/cras_observer.h b/cras/src/server/cras_observer.h
index 109bd60..2dd013b 100644
--- a/cras/src/server/cras_observer.h
+++ b/cras/src/server/cras_observer.h
@@ -92,10 +92,18 @@
 void cras_observer_notify_num_active_streams(enum CRAS_STREAM_DIRECTION dir,
 					     uint32_t num_active_streams);
 
+/* Notify observers of the number of input streams with permission. */
+void cras_observer_notify_input_streams_with_permission(
+	uint32_t num_input_streams[CRAS_NUM_CLIENT_TYPE]);
+
 /* Notify observers of the timestamp when hotword triggered. */
 void cras_observer_notify_hotword_triggered(int64_t tv_sec, int64_t tv_nsec);
 
 /* Notify observers the non-empty audio state changed. */
 void cras_observer_notify_non_empty_audio_state_changed(int active);
 
+/* Notify observers the bluetooth headset battery level changed. */
+void cras_observer_notify_bt_battery_changed(const char *address,
+					     uint32_t level);
+
 #endif /* CRAS_OBSERVER_H */
diff --git a/cras/src/server/cras_playback_rclient.c b/cras/src/server/cras_playback_rclient.c
index a282e8b..54a75db 100644
--- a/cras/src/server/cras_playback_rclient.c
+++ b/cras/src/server/cras_playback_rclient.c
@@ -11,63 +11,12 @@
 #include "cras_rclient.h"
 #include "cras_rclient_util.h"
 #include "cras_rstream.h"
-#include "cras_system_state.h"
 #include "cras_types.h"
 #include "cras_util.h"
-#include "stream_list.h"
-
-/* Entry point for handling a message from the client.  Called from the main
- * server context. */
-static int cpr_handle_message_from_client(struct cras_rclient *client,
-					  const struct cras_server_message *msg,
-					  int *fds, unsigned int num_fds)
-{
-	int rc = 0;
-	assert(client && msg);
-
-	rc = rclient_validate_message_fds(msg, fds, num_fds);
-	if (rc < 0) {
-		for (int i = 0; i < (int)num_fds; i++)
-			if (fds[i] >= 0)
-				close(fds[i]);
-		return rc;
-	}
-	int fd = num_fds > 0 ? fds[0] : -1;
-
-	switch (msg->id) {
-	case CRAS_SERVER_CONNECT_STREAM: {
-		int client_shm_fd = num_fds > 1 ? fds[1] : -1;
-		struct cras_connect_message cmsg;
-		if (MSG_LEN_VALID(msg, struct cras_connect_message)) {
-			rc = rclient_handle_client_stream_connect(
-				client,
-				(const struct cras_connect_message *)msg, fd,
-				client_shm_fd);
-		} else if (!convert_connect_message_old(msg, &cmsg)) {
-			rc = rclient_handle_client_stream_connect(
-				client, &cmsg, fd, client_shm_fd);
-		} else {
-			return -EINVAL;
-		}
-		break;
-	}
-	case CRAS_SERVER_DISCONNECT_STREAM:
-		if (!MSG_LEN_VALID(msg, struct cras_disconnect_stream_message))
-			return -EINVAL;
-		rc = rclient_handle_client_stream_disconnect(
-			client,
-			(const struct cras_disconnect_stream_message *)msg);
-		break;
-	default:
-		break;
-	}
-
-	return rc;
-}
 
 /* Declarations of cras_rclient operators for cras_playback_rclient. */
 static const struct cras_rclient_ops cras_playback_rclient_ops = {
-	.handle_message_from_client = cpr_handle_message_from_client,
+	.handle_message_from_client = rclient_handle_message_from_client,
 	.send_message_to_client = rclient_send_message_to_client,
 	.destroy = rclient_destroy,
 };
@@ -80,24 +29,7 @@
  * the connection has succeeded. */
 struct cras_rclient *cras_playback_rclient_create(int fd, size_t id)
 {
-	struct cras_rclient *client;
-	struct cras_client_connected msg;
-	int state_fd;
-
-	client = (struct cras_rclient *)calloc(1, sizeof(struct cras_rclient));
-	if (!client)
-		return NULL;
-
-	client->fd = fd;
-	client->id = id;
-
-	client->ops = &cras_playback_rclient_ops;
-	client->supported_directions =
-		cras_stream_direction_mask(CRAS_STREAM_OUTPUT);
-
-	cras_fill_client_connected(&msg, client->id);
-	state_fd = cras_sys_state_shm_fd();
-	client->ops->send_message_to_client(client, &msg.header, &state_fd, 1);
-
-	return client;
+	return rclient_generic_create(
+		fd, id, &cras_playback_rclient_ops,
+		cras_stream_direction_mask(CRAS_STREAM_OUTPUT));
 }
diff --git a/cras/src/server/cras_ramp.c b/cras/src/server/cras_ramp.c
index 5f40a77..f027204 100644
--- a/cras/src/server/cras_ramp.c
+++ b/cras/src/server/cras_ramp.c
@@ -62,7 +62,11 @@
 {
 	struct cras_ramp_action action;
 
-	if (from == to)
+	if (!ramp)
+		return -EINVAL;
+
+	/* if from == to == 0 means we want to mute for duration_frames */
+	if (from == to && from != 0)
 		return 0;
 
 	/* Get current scaler position so it can serve as new start scaler. */
diff --git a/cras/src/server/cras_rclient.c b/cras/src/server/cras_rclient.c
index 38ba5c2..d2646e7 100644
--- a/cras/src/server/cras_rclient.c
+++ b/cras/src/server/cras_rclient.c
@@ -24,6 +24,7 @@
 #include "cras_server_metrics.h"
 #include "cras_system_state.h"
 #include "cras_types.h"
+#include "cras_unified_rclient.h"
 #include "cras_util.h"
 #include "stream_list.h"
 #include "utlist.h"
@@ -58,9 +59,16 @@
 	return client->ops->send_message_to_client(client, msg, fds, num_fds);
 }
 
+static void cras_rclient_set_client_type(struct cras_rclient *client,
+					 enum CRAS_CLIENT_TYPE client_type)
+{
+	client->client_type = client_type;
+}
+
 struct cras_rclient *cras_rclient_create(int fd, size_t id,
 					 enum CRAS_CONNECTION_TYPE conn_type)
 {
+	struct cras_rclient *client;
 	if (!cras_validate_connection_type(conn_type))
 		goto error;
 
@@ -71,6 +79,18 @@
 		return cras_playback_rclient_create(fd, id);
 	case CRAS_CAPTURE:
 		return cras_capture_rclient_create(fd, id);
+	case CRAS_VMS_LEGACY:
+		return cras_playback_rclient_create(fd, id);
+	case CRAS_VMS_UNIFIED:
+		return cras_unified_rclient_create(fd, id);
+	case CRAS_PLUGIN_PLAYBACK:
+		client = cras_playback_rclient_create(fd, id);
+		cras_rclient_set_client_type(client, CRAS_CLIENT_TYPE_PLUGIN);
+		return client;
+	case CRAS_PLUGIN_UNIFIED:
+		client = cras_unified_rclient_create(fd, id);
+		cras_rclient_set_client_type(client, CRAS_CLIENT_TYPE_PLUGIN);
+		return client;
 	default:
 		goto error;
 	}
diff --git a/cras/src/server/cras_rclient.h b/cras/src/server/cras_rclient.h
index 6cffb7d..3a3988c 100644
--- a/cras/src/server/cras_rclient.h
+++ b/cras/src/server/cras_rclient.h
@@ -20,6 +20,9 @@
  *  fd - Connection for client communication.
  *  ops - cras_rclient_ops for the cras_rclient.
  *  supported_directions - Bit mask for supported stream directions.
+ *  client_type - Client type of this rclient. If this is set to value other
+ *                than CRAS_CLIENT_TYPE_UNKNOWN, rclient will overwrite incoming
+ *                messages' client type.
  */
 struct cras_rclient {
 	struct cras_observer_client *observer;
@@ -27,6 +30,7 @@
 	int fd;
 	const struct cras_rclient_ops *ops;
 	int supported_directions;
+	enum CRAS_CLIENT_TYPE client_type;
 };
 
 /* Operations for cras_rclient.
diff --git a/cras/src/server/cras_rclient_util.c b/cras/src/server/cras_rclient_util.c
index c88df82..0af9886 100644
--- a/cras/src/server/cras_rclient_util.c
+++ b/cras/src/server/cras_rclient_util.c
@@ -12,6 +12,7 @@
 #include "cras_rclient_util.h"
 #include "cras_rstream.h"
 #include "cras_server_metrics.h"
+#include "cras_system_state.h"
 #include "cras_tm.h"
 #include "cras_types.h"
 #include "cras_util.h"
@@ -42,9 +43,8 @@
 			goto error;
 		break;
 	case CRAS_SERVER_SET_AEC_DUMP:
-		if (num_fds != 1)
+		if (num_fds > 1)
 			goto error;
-		syslog(LOG_ERR, "client msg for APM debug, fd %d", fds[0]);
 		break;
 	default:
 		if (num_fds > 0)
@@ -80,6 +80,13 @@
 		       msg->direction, client->id);
 		return -EINVAL;
 	}
+
+	if (!cras_validate_client_type(msg->client_type)) {
+		syslog(LOG_ERR,
+		       "stream_connect: invalid stream client_type: %x for "
+		       "client: %zx.\n",
+		       msg->client_type, client->id);
+	}
 	return 0;
 }
 
@@ -133,10 +140,15 @@
 	struct cras_audio_format remote_fmt;
 	struct cras_rstream_config stream_config;
 	int rc, header_fd, samples_fd;
+	size_t samples_size;
 	int stream_fds[2];
 
 	rc = rclient_validate_stream_connect_params(client, msg, aud_fd,
 						    client_shm_fd);
+	remote_fmt = unpack_cras_audio_format(&msg->format);
+	if (rc == 0 && !cras_audio_format_valid(&remote_fmt)) {
+		rc = -EINVAL;
+	}
 	if (rc) {
 		if (client_shm_fd >= 0)
 			close(client_shm_fd);
@@ -145,26 +157,40 @@
 		goto reply_err;
 	}
 
-	unpack_cras_audio_format(&remote_fmt, &msg->format);
-
 	/* When full, getting an error is preferable to blocking. */
 	cras_make_fd_nonblocking(aud_fd);
 
-	cras_rstream_config_init_with_message(client, msg, &aud_fd,
-					      &client_shm_fd, &remote_fmt,
-					      &stream_config);
+	stream_config = cras_rstream_config_init_with_message(
+		client, msg, &aud_fd, &client_shm_fd, &remote_fmt);
+	/* Overwrite client_type if client->client_type is set. */
+	if (client->client_type != CRAS_CLIENT_TYPE_UNKNOWN)
+		stream_config.client_type = client->client_type;
 	rc = stream_list_add(cras_iodev_list_get_stream_list(), &stream_config,
 			     &stream);
 	if (rc)
 		goto cleanup_config;
 
+	detect_rtc_stream_pair(cras_iodev_list_get_stream_list(), stream);
+
 	/* Tell client about the stream setup. */
 	syslog(LOG_DEBUG, "Send connected for stream %x\n", msg->stream_id);
-	cras_fill_client_stream_connected(
-		&stream_connected, 0, /* No error. */
-		msg->stream_id, &remote_fmt,
-		cras_rstream_get_samples_shm_size(stream),
-		cras_rstream_get_effects(stream));
+
+	// Check that shm size is at most UINT32_MAX for non-shm streams.
+	samples_size = cras_rstream_get_samples_shm_size(stream);
+	if (samples_size > UINT32_MAX && stream_config.client_shm_fd < 0) {
+		syslog(LOG_ERR,
+		       "Non client-provided shm stream has samples shm larger "
+		       "than uint32_t: %zu",
+		       samples_size);
+		if (aud_fd >= 0)
+			close(aud_fd);
+		rc = -EINVAL;
+		goto cleanup_config;
+	}
+	cras_fill_client_stream_connected(&stream_connected, 0, /* No error. */
+					  msg->stream_id, &remote_fmt,
+					  samples_size,
+					  cras_rstream_get_effects(stream));
 	reply = &stream_connected.header;
 
 	rc = cras_rstream_get_shm_fds(stream, &header_fd, &samples_fd);
@@ -184,9 +210,6 @@
 		goto cleanup_config;
 	}
 
-	/* Metrics logs the stream configurations. */
-	cras_server_metrics_stream_config(&stream_config);
-
 	/* Cleanup local object explicitly. */
 	cras_rstream_config_cleanup(&stream_config);
 	return 0;
@@ -220,3 +243,74 @@
 	return stream_list_rm(cras_iodev_list_get_stream_list(),
 			      msg->stream_id);
 }
+
+/* Creates a client structure and sends a message back informing the client that
+ * the connection has succeeded. */
+struct cras_rclient *rclient_generic_create(int fd, size_t id,
+					    const struct cras_rclient_ops *ops,
+					    int supported_directions)
+{
+	struct cras_rclient *client;
+	struct cras_client_connected msg;
+	int state_fd;
+
+	client = (struct cras_rclient *)calloc(1, sizeof(struct cras_rclient));
+	if (!client)
+		return NULL;
+
+	client->fd = fd;
+	client->id = id;
+	client->ops = ops;
+	client->supported_directions = supported_directions;
+
+	cras_fill_client_connected(&msg, client->id);
+	state_fd = cras_sys_state_shm_fd();
+	client->ops->send_message_to_client(client, &msg.header, &state_fd, 1);
+
+	return client;
+}
+
+/* A generic entry point for handling a message from the client. Called from
+ * the main server context. */
+int rclient_handle_message_from_client(struct cras_rclient *client,
+				       const struct cras_server_message *msg,
+				       int *fds, unsigned int num_fds)
+{
+	int rc = 0;
+	assert(client && msg);
+
+	rc = rclient_validate_message_fds(msg, fds, num_fds);
+	if (rc < 0) {
+		for (int i = 0; i < (int)num_fds; i++)
+			if (fds[i] >= 0)
+				close(fds[i]);
+		return rc;
+	}
+	int fd = num_fds > 0 ? fds[0] : -1;
+
+	switch (msg->id) {
+	case CRAS_SERVER_CONNECT_STREAM: {
+		int client_shm_fd = num_fds > 1 ? fds[1] : -1;
+		if (MSG_LEN_VALID(msg, struct cras_connect_message)) {
+			rclient_handle_client_stream_connect(
+				client,
+				(const struct cras_connect_message *)msg, fd,
+				client_shm_fd);
+		} else {
+			return -EINVAL;
+		}
+		break;
+	}
+	case CRAS_SERVER_DISCONNECT_STREAM:
+		if (!MSG_LEN_VALID(msg, struct cras_disconnect_stream_message))
+			return -EINVAL;
+		rclient_handle_client_stream_disconnect(
+			client,
+			(const struct cras_disconnect_stream_message *)msg);
+		break;
+	default:
+		break;
+	}
+
+	return rc;
+}
diff --git a/cras/src/server/cras_rclient_util.h b/cras/src/server/cras_rclient_util.h
index 4768a47..089c2ec 100644
--- a/cras/src/server/cras_rclient_util.h
+++ b/cras/src/server/cras_rclient_util.h
@@ -86,30 +86,40 @@
 	struct cras_rclient *client,
 	const struct cras_disconnect_stream_message *msg);
 
-/*
- * Converts an old version of connect message to the correct
- * cras_connect_message. Returns zero on success, negative on failure.
- * Note that this is special check only for libcras transition in
- * clients, from CRAS_PROTO_VER = 3 to 5.
- * TODO(yuhsuan): clean up the function once clients transition is done.
+/* Generic rclient create function for different types of rclients.
+ * Creates a client structure and sends a message back informing the client
+ * that the connection has succeeded.
+ *
+ * Args:
+ *    fd - The file descriptor used for communication with the client.
+ *    id - Unique identifier for this client.
+ *    ops - cras_rclient_ops pointer for the client.
+ *    supported_directions - supported directions for the this rclient.
+ * Returns:
+ *    A pointer to the newly created rclient on success, NULL on failure.
  */
-static inline int
-convert_connect_message_old(const struct cras_server_message *msg,
-			    struct cras_connect_message *cmsg)
-{
-	struct cras_connect_message_old *old;
+struct cras_rclient *rclient_generic_create(int fd, size_t id,
+					    const struct cras_rclient_ops *ops,
+					    int supported_directions);
 
-	if (!MSG_LEN_VALID(msg, struct cras_connect_message_old))
-		return -EINVAL;
-
-	old = (struct cras_connect_message_old *)msg;
-	if (old->proto_version != 3 || CRAS_PROTO_VER != 5)
-		return -EINVAL;
-
-	memcpy(cmsg, old, sizeof(*old));
-	cmsg->client_type = CRAS_CLIENT_TYPE_LEGACY;
-	cmsg->client_shm_size = 0;
-	return 0;
-}
+/* Generic handle_message_from_client function for different types of rlicnets.
+ * Supports only stream connect and stream disconnect messages.
+ *
+ * If the message from clients has incorrect length (truncated message), return
+ * an error up to CRAS server.
+ * If the message from clients has invalid content, should return the errors to
+ * clients by send_message_to_client and return 0 here.
+ *
+ * Args:
+ *   client - The cras_rclient which gets the message.
+ *   msg - The cras_server_message from client.
+ *   fds - The array for incoming fds from client.
+ *   num_fds - The number of fds from client.
+ * Returns:
+ *   0 on success, negative error on failure.
+ */
+int rclient_handle_message_from_client(struct cras_rclient *client,
+				       const struct cras_server_message *msg,
+				       int *fds, unsigned int num_fds);
 
 #endif /* CRAS_RCLIENT_UTIL_H_ */
diff --git a/cras/src/server/cras_rstream.c b/cras/src/server/cras_rstream.c
index 55beed2..3c0a0ce 100644
--- a/cras/src/server/cras_rstream.c
+++ b/cras/src/server/cras_rstream.c
@@ -17,63 +17,20 @@
 #include "cras_server_metrics.h"
 #include "cras_shm.h"
 #include "cras_types.h"
-#include "buffer_share.h"
 #include "cras_system_state.h"
 
-void cras_rstream_config_init(
-	struct cras_rclient *client, cras_stream_id_t stream_id,
-	enum CRAS_STREAM_TYPE stream_type, enum CRAS_CLIENT_TYPE client_type,
-	enum CRAS_STREAM_DIRECTION direction, uint32_t dev_idx, uint32_t flags,
-	uint32_t effects, const struct cras_audio_format *format,
-	size_t buffer_frames, size_t cb_threshold, int *audio_fd,
-	int *client_shm_fd, size_t client_shm_size,
-	struct cras_rstream_config *stream_config)
+static bool cras_rstream_config_is_client_shm_stream(
+	const struct cras_rstream_config *config)
 {
-	stream_config->stream_id = stream_id;
-	stream_config->stream_type = stream_type;
-	stream_config->client_type = client_type;
-	stream_config->direction = direction;
-	stream_config->dev_idx = dev_idx;
-	stream_config->flags = flags;
-	stream_config->effects = effects;
-	stream_config->format = format;
-	stream_config->buffer_frames = buffer_frames;
-	stream_config->cb_threshold = cb_threshold;
-	stream_config->audio_fd = *audio_fd;
-	*audio_fd = -1;
-	stream_config->client_shm_fd = *client_shm_fd;
-	*client_shm_fd = -1;
-	stream_config->client_shm_size = client_shm_size;
-	stream_config->client = client;
+	return config && config->client_shm_fd >= 0 &&
+	       config->client_shm_size > 0;
 }
 
-void cras_rstream_config_init_with_message(
-	struct cras_rclient *client, const struct cras_connect_message *msg,
-	int *aud_fd, int *client_shm_fd,
-	const struct cras_audio_format *remote_fmt,
-	struct cras_rstream_config *stream_config)
-{
-	cras_rstream_config_init(client, msg->stream_id, msg->stream_type,
-				 msg->client_type, msg->direction, msg->dev_idx,
-				 msg->flags, msg->effects, remote_fmt,
-				 msg->buffer_frames, msg->cb_threshold, aud_fd,
-				 client_shm_fd, msg->client_shm_size,
-				 stream_config);
-}
-
-void cras_rstream_config_cleanup(struct cras_rstream_config *stream_config)
-{
-	if (stream_config->audio_fd >= 0)
-		close(stream_config->audio_fd);
-	if (stream_config->client_shm_fd >= 0)
-		close(stream_config->client_shm_fd);
-}
-
-/* Setup the shared memory area used for audio samples. client_shm_fd must be
- * closed after calling this function.
+/* Setup the shared memory area used for audio samples. config->client_shm_fd
+ * must be closed after calling this function.
  */
-static inline int setup_shm_area(struct cras_rstream *stream, int client_shm_fd,
-				 size_t client_shm_size)
+static inline int setup_shm_area(struct cras_rstream *stream,
+				 struct cras_rstream_config *config)
 {
 	const struct cras_audio_format *fmt = &stream->format;
 	char header_name[NAME_MAX];
@@ -81,6 +38,8 @@
 	struct cras_shm_info header_info, samples_info;
 	uint32_t frame_bytes, used_size;
 	int rc;
+	bool client_shm_stream =
+		cras_rstream_config_is_client_shm_stream(config);
 
 	if (stream->shm) {
 		/* already setup */
@@ -99,8 +58,9 @@
 		      fmt->num_channels;
 	used_size = stream->buffer_frames * frame_bytes;
 
-	if (client_shm_fd >= 0 && client_shm_size > 0) {
-		rc = cras_shm_info_init_with_fd(client_shm_fd, client_shm_size,
+	if (client_shm_stream) {
+		rc = cras_shm_info_init_with_fd(config->client_shm_fd,
+						config->client_shm_size,
 						&samples_info);
 	} else {
 		snprintf(samples_name, sizeof(samples_name),
@@ -129,6 +89,11 @@
 
 	cras_shm_set_frame_bytes(stream->shm, frame_bytes);
 	cras_shm_set_used_size(stream->shm, used_size);
+	if (client_shm_stream) {
+		for (int i = 0; i < 2; i++)
+			cras_shm_set_buffer_offset(stream->shm, i,
+						   config->buffer_offsets[i]);
+	}
 
 	stream->audio_area =
 		cras_audio_area_create(stream->format.num_channels);
@@ -139,23 +104,16 @@
 
 static inline int buffer_meets_size_limit(size_t buffer_size, size_t rate)
 {
-	return buffer_size > (CRAS_MIN_BUFFER_TIME_IN_US * rate) / 1000000;
+	return (buffer_size < (CRAS_MAX_BUFFER_TIME_IN_S * rate)) &&
+	       (buffer_size > (CRAS_MIN_BUFFER_TIME_IN_US * rate) / 1000000);
 }
 
 /* Verifies that the given stream parameters are valid. */
-static int verify_rstream_parameters(enum CRAS_STREAM_DIRECTION direction,
-				     const struct cras_audio_format *format,
-				     enum CRAS_STREAM_TYPE stream_type,
-				     size_t buffer_frames, size_t cb_threshold,
-				     int client_shm_fd, size_t client_shm_size,
-				     struct cras_rclient *client,
-				     struct cras_rstream **stream_out)
+static int verify_rstream_parameters(const struct cras_rstream_config *config,
+				     struct cras_rstream *const *stream_out)
 {
-	if (!buffer_meets_size_limit(buffer_frames, format->frame_rate)) {
-		syslog(LOG_ERR, "rstream: invalid buffer_frames %zu\n",
-		       buffer_frames);
-		return -EINVAL;
-	}
+	const struct cras_audio_format *format = config->format;
+
 	if (stream_out == NULL) {
 		syslog(LOG_ERR, "rstream: stream_out can't be NULL\n");
 		return -EINVAL;
@@ -164,6 +122,33 @@
 		syslog(LOG_ERR, "rstream: format can't be NULL\n");
 		return -EINVAL;
 	}
+	if (format->frame_rate < 4000 || format->frame_rate > 192000) {
+		syslog(LOG_ERR, "rstream: invalid frame_rate %zu\n",
+		       format->frame_rate);
+		return -EINVAL;
+	}
+	/*
+	 * Valid buffer settings:
+	 *   Frames in 1ms <= cb_threshold <= buffer_frames <= Frames in 10s.
+	 */
+	if (!buffer_meets_size_limit(config->buffer_frames,
+				     format->frame_rate)) {
+		syslog(LOG_ERR, "rstream: invalid buffer_frames %zu\n",
+		       config->buffer_frames);
+		return -EINVAL;
+	}
+	if (!buffer_meets_size_limit(config->cb_threshold,
+				     format->frame_rate) ||
+	    config->cb_threshold > config->buffer_frames) {
+		syslog(LOG_ERR, "rstream: invalid cb_threshold %zu\n",
+		       config->cb_threshold);
+		return -EINVAL;
+	}
+	if (format->num_channels < 0 || format->num_channels > CRAS_CH_MAX) {
+		syslog(LOG_ERR, "rstream: invalid num_channels %zu\n",
+		       format->num_channels);
+		return -EINVAL;
+	}
 	if ((format->format != SND_PCM_FORMAT_S16_LE) &&
 	    (format->format != SND_PCM_FORMAT_S32_LE) &&
 	    (format->format != SND_PCM_FORMAT_U8) &&
@@ -172,24 +157,34 @@
 		       format->format);
 		return -EINVAL;
 	}
-	if (direction != CRAS_STREAM_OUTPUT && direction != CRAS_STREAM_INPUT) {
+	if (config->direction != CRAS_STREAM_OUTPUT &&
+	    config->direction != CRAS_STREAM_INPUT) {
 		syslog(LOG_ERR, "rstream: Invalid direction.\n");
 		return -EINVAL;
 	}
-	if (stream_type < CRAS_STREAM_TYPE_DEFAULT ||
-	    stream_type >= CRAS_STREAM_NUM_TYPES) {
+	if (config->stream_type < CRAS_STREAM_TYPE_DEFAULT ||
+	    config->stream_type >= CRAS_STREAM_NUM_TYPES) {
 		syslog(LOG_ERR, "rstream: Invalid stream type.\n");
 		return -EINVAL;
 	}
-	if (!buffer_meets_size_limit(cb_threshold, format->frame_rate)) {
-		syslog(LOG_ERR, "rstream: cb_threshold too low\n");
+	if (config->client_type < CRAS_CLIENT_TYPE_UNKNOWN ||
+	    config->client_type >= CRAS_NUM_CLIENT_TYPE) {
+		syslog(LOG_ERR, "rstream: Invalid client type.\n");
 		return -EINVAL;
 	}
-	if ((client_shm_size > 0 && client_shm_fd < 0) ||
-	    (client_shm_size == 0 && client_shm_fd >= 0)) {
+	if ((config->client_shm_size > 0 && config->client_shm_fd < 0) ||
+	    (config->client_shm_size == 0 && config->client_shm_fd >= 0)) {
 		syslog(LOG_ERR, "rstream: invalid client-provided shm info\n");
 		return -EINVAL;
 	}
+	if (cras_rstream_config_is_client_shm_stream(config) &&
+	    (config->buffer_offsets[0] > config->client_shm_size ||
+	     config->buffer_offsets[1] > config->client_shm_size)) {
+		syslog(LOG_ERR,
+		       "rstream: initial buffer offsets are outside shm area\n");
+		return -EINVAL;
+	}
+
 	return 0;
 }
 
@@ -248,7 +243,6 @@
 
 	rc = get_audio_request_reply(stream, &msg);
 	if (rc <= 0) {
-		syslog(LOG_ERR, "Got error from client: rc: %d", rc);
 		clear_pending_reply(stream);
 		return rc;
 	}
@@ -280,11 +274,7 @@
 	struct cras_rstream *stream;
 	int rc;
 
-	rc = verify_rstream_parameters(
-		config->direction, config->format, config->stream_type,
-		config->buffer_frames, config->cb_threshold,
-		config->client_shm_fd, config->client_shm_size, config->client,
-		stream_out);
+	rc = verify_rstream_parameters(config, stream_out);
 	if (rc < 0)
 		return rc;
 
@@ -302,14 +292,14 @@
 	stream->cb_threshold = config->cb_threshold;
 	stream->client = config->client;
 	stream->shm = NULL;
-	stream->master_dev.dev_id = NO_DEVICE;
-	stream->master_dev.dev_ptr = NULL;
+	stream->main_dev.dev_id = NO_DEVICE;
+	stream->main_dev.dev_ptr = NULL;
 	stream->num_missed_cb = 0;
 	stream->is_pinned = (config->dev_idx != NO_DEVICE);
 	stream->pinned_dev_idx = config->dev_idx;
+	ewma_power_init(&stream->ewma, stream->format.frame_rate);
 
-	rc = setup_shm_area(stream, config->client_shm_fd,
-			    config->client_shm_size);
+	rc = setup_shm_area(stream, config);
 	if (rc < 0) {
 		syslog(LOG_ERR, "failed to setup shm %d\n", rc);
 		free(stream);
@@ -328,17 +318,20 @@
 	       config->stream_id, config->buffer_frames, config->cb_threshold);
 	*stream_out = stream;
 
-	cras_system_state_stream_added(stream->direction);
+	cras_system_state_stream_added(stream->direction, stream->client_type);
 
 	clock_gettime(CLOCK_MONOTONIC_RAW, &stream->start_ts);
 
+	cras_server_metrics_stream_create(config);
+
 	return 0;
 }
 
 void cras_rstream_destroy(struct cras_rstream *stream)
 {
-	cras_server_metrics_missed_cb_frequency(stream);
-	cras_system_state_stream_removed(stream->direction);
+	cras_server_metrics_stream_destroy(stream);
+	cras_system_state_stream_removed(stream->direction,
+					 stream->client_type);
 	close(stream->fd);
 	cras_audio_shm_destroy(stream->shm);
 	cras_audio_area_destroy(stream->audio_area);
@@ -360,10 +353,7 @@
 {
 	struct cras_apm *apm;
 
-	if (NULL == stream->apm_list)
-		return NULL;
-
-	apm = cras_apm_list_get(stream->apm_list, dev_ptr);
+	apm = cras_apm_list_get_active_apm((void *)stream, dev_ptr);
 	if (NULL == apm)
 		return NULL;
 	return cras_apm_list_get_format(apm);
@@ -441,12 +431,12 @@
 	if (buffer_share_add_id(rstream->buf_state, dev_id, dev_ptr) == 0)
 		rstream->num_attached_devs++;
 
-	/* TODO(hychao): Handle master device assignment for complicated
+	/* TODO(hychao): Handle main device assignment for complicated
 	 * routing case.
 	 */
-	if (rstream->master_dev.dev_id == NO_DEVICE) {
-		rstream->master_dev.dev_id = dev_id;
-		rstream->master_dev.dev_ptr = dev_ptr;
+	if (rstream->main_dev.dev_id == NO_DEVICE) {
+		rstream->main_dev.dev_id = dev_id;
+		rstream->main_dev.dev_ptr = dev_ptr;
 	}
 }
 
@@ -455,18 +445,18 @@
 	if (buffer_share_rm_id(rstream->buf_state, dev_id) == 0)
 		rstream->num_attached_devs--;
 
-	if (rstream->master_dev.dev_id == dev_id) {
+	if (rstream->main_dev.dev_id == dev_id) {
 		int i;
 		struct id_offset *o;
 
-		/* Choose the first device id as master. */
-		rstream->master_dev.dev_id = NO_DEVICE;
-		rstream->master_dev.dev_ptr = NULL;
+		/* Choose the first device id as a main device. */
+		rstream->main_dev.dev_id = NO_DEVICE;
+		rstream->main_dev.dev_ptr = NULL;
 		for (i = 0; i < rstream->buf_state->id_sz; i++) {
 			o = &rstream->buf_state->wr_idx[i];
 			if (o->used) {
-				rstream->master_dev.dev_id = o->id;
-				rstream->master_dev.dev_ptr = o->data;
+				rstream->main_dev.dev_id = o->id;
+				rstream->main_dev.dev_ptr = o->data;
 				break;
 			}
 		}
@@ -489,9 +479,16 @@
 
 void cras_rstream_update_output_read_pointer(struct cras_rstream *rstream)
 {
+	size_t nfr = 0;
+	uint8_t *src;
 	unsigned int nwritten =
 		buffer_share_get_new_write_point(rstream->buf_state);
 
+	/* Retrieve the read pointer |src| start from which to calculate
+	 * the EWMA power. */
+	src = cras_shm_get_readable_frames(rstream->shm, 0, &nfr);
+	ewma_power_calculate(&rstream->ewma, (int16_t *)src,
+			     rstream->format.num_channels, nwritten);
 	cras_shm_buffer_read(rstream->shm, nwritten);
 }
 
@@ -556,5 +553,8 @@
 		}
 	} while (err > 0);
 
+	if (err < 0)
+		syslog(LOG_ERR, "Error reading msg from client: rc: %d", err);
+
 	return 0;
 }
diff --git a/cras/src/server/cras_rstream.h b/cras/src/server/cras_rstream.h
index 3329fe0..d57c13b 100644
--- a/cras/src/server/cras_rstream.h
+++ b/cras/src/server/cras_rstream.h
@@ -9,20 +9,23 @@
 #ifndef CRAS_RSTREAM_H_
 #define CRAS_RSTREAM_H_
 
+#include "buffer_share.h"
 #include "cras_apm_list.h"
 #include "cras_shm.h"
 #include "cras_types.h"
+#include "cras_rstream_config.h"
+#include "ewma_power.h"
 
 struct cras_connect_message;
 struct cras_rclient;
 struct dev_mix;
 
-/* Holds informations about the master active device.
+/* Holds informations about the main active device.
  * Members:
- *    dev_id - id of the master device.
- *    dev_ptr - pointer to the master device.
+ *    dev_id - id of the main device.
+ *    dev_ptr - pointer to the main device.
  */
-struct master_dev_info {
+struct main_dev_info {
 	int dev_id;
 	void *dev_ptr;
 };
@@ -39,7 +42,7 @@
  *    fd - Socket for requesting and sending audio buffer events.
  *    buffer_frames - Buffer size in frames.
  *    cb_threshold - Callback client when this much is left.
- *    master_dev_info - The info of the master device this stream attaches to.
+ *    main_dev_info - The info of the main device this stream attaches to.
  *    is_draining - The stream is draining and waiting to be removed.
  *    client - The client who uses this stream.
  *    shm - shared memory
@@ -53,6 +56,7 @@
  *    first_missed_cb_ts - The time when the first missed callback happens.
  *    buf_state - State of the buffer from all devices for this stream.
  *    apm_list - List of audio processing module instances.
+ *    ewma - The ewma instance to calculate stream volume.
  *    num_attached_devs - Number of iodevs this stream has attached to.
  *    num_missed_cb - Number of callback schedules have been missed.
  *    queued_frames - Cached value of the number of queued frames in shm.
@@ -70,7 +74,7 @@
 	size_t buffer_frames;
 	size_t cb_threshold;
 	int is_draining;
-	struct master_dev_info master_dev;
+	struct main_dev_info main_dev;
 	struct cras_rclient *client;
 	struct cras_audio_shm *shm;
 	struct cras_audio_area *audio_area;
@@ -83,6 +87,7 @@
 	struct timespec first_missed_cb_ts;
 	struct buffer_share *buf_state;
 	struct cras_apm_list *apm_list;
+	struct ewma_power ewma;
 	int num_attached_devs;
 	int num_missed_cb;
 	int queued_frames;
@@ -92,85 +97,6 @@
 	struct cras_rstream *prev, *next;
 };
 
-/* Config for creating an rstream.
- *    stream_type - CRAS_STREAM_TYPE.
- *    client_type - CRAS_CLIENT_TYPE.
- *    direction - CRAS_STREAM_OUTPUT or CRAS_STREAM_INPUT.
- *    dev_idx - Pin to this device if != NO_DEVICE.
- *    flags - Any special handling for this stream.
- *    effects - Bit map of effects to be enabled on this stream.
- *    format - The audio format the stream wishes to use.
- *    buffer_frames - Total number of audio frames to buffer.
- *    cb_threshold - # of frames when to request more from the client.
- *    audio_fd - The fd to read/write audio signals to. May be -1 for server
- *               stream. Some functions may mutably borrow the config and move
- *               the fd ownership.
- *    client_shm_fd - The shm fd to use to back the samples area. May be -1.
- *                    Some functions may dup this fd while borrowing the config.
- *    client_shm_size - The size of shm area backed by client_shm_fd.
- *    client - The client that owns this stream.
- */
-struct cras_rstream_config {
-	cras_stream_id_t stream_id;
-	enum CRAS_STREAM_TYPE stream_type;
-	enum CRAS_CLIENT_TYPE client_type;
-	enum CRAS_STREAM_DIRECTION direction;
-	uint32_t dev_idx;
-	uint32_t flags;
-	uint32_t effects;
-	const struct cras_audio_format *format;
-	size_t buffer_frames;
-	size_t cb_threshold;
-	int audio_fd;
-	int client_shm_fd;
-	size_t client_shm_size;
-	struct cras_rclient *client;
-};
-
-/* Fills cras_rstream_config with given parameters.
- *
- * Args:
- *   audio_fd - The audio fd pointer from client. Its ownership will be moved to
- *              stream_config.
- *   client_shm_fd - The shared memory fd pointer for samples from client. Its
- *                   ownership will be moved to stream_config.
- *   Other args - See comments in struct cras_rstream_config.
- */
-void cras_rstream_config_init(
-	struct cras_rclient *client, cras_stream_id_t stream_id,
-	enum CRAS_STREAM_TYPE stream_type, enum CRAS_CLIENT_TYPE client_type,
-	enum CRAS_STREAM_DIRECTION direction, uint32_t dev_idx, uint32_t flags,
-	uint32_t effects, const struct cras_audio_format *format,
-	size_t buffer_frames, size_t cb_threshold, int *audio_fd,
-	int *client_shm_fd, size_t client_shm_size,
-	struct cras_rstream_config *stream_config);
-
-/* Fills cras_rstream_config with given parameters and a cras_connect_message.
- *
- * Args:
- *   client - The rclient which handles the connect message.
- *   msg - The cras_connect_message from client.
- *   aud_fd - The audio fd pointer from client. Its ownership will be moved to
- *            stream_config.
- *   client_shm_fd - The shared memory fd pointer for samples from client. Its
- *                   ownership will be moved to stream_config.
- *   remote_format - The remote_format for the config.
- *   stream_config - The cras_rstream_config to be filled.
- */
-void cras_rstream_config_init_with_message(
-	struct cras_rclient *client, const struct cras_connect_message *msg,
-	int *aud_fd, int *client_shm_fd,
-	const struct cras_audio_format *remote_format,
-	struct cras_rstream_config *stream_config);
-
-/* Cleans up given cras_rstream_config. All fds inside the config will be
- * closed.
- *
- * Args:
- *   stream_config - The config to be cleaned up.
- */
-void cras_rstream_config_cleanup(struct cras_rstream_config *stream_config);
-
 /* Creates an rstream.
  * Args:
  *    config - Params for configuration of the new rstream. It's a mutable
@@ -334,6 +260,12 @@
 			     void *dev_ptr);
 void cras_rstream_dev_detach(struct cras_rstream *rstream, unsigned int dev_id);
 
+static inline void *cras_rstream_dev_ptr(struct cras_rstream *rstream,
+					 unsigned int dev_id)
+{
+	return buffer_share_get_data(rstream->buf_state, dev_id);
+}
+
 /* A device using this stream has read or written samples. */
 void cras_rstream_dev_offset_update(struct cras_rstream *rstream,
 				    unsigned int frames, unsigned int dev_id);
diff --git a/cras/src/server/cras_rstream_config.c b/cras/src/server/cras_rstream_config.c
new file mode 100644
index 0000000..c5cd9c5
--- /dev/null
+++ b/cras/src/server/cras_rstream_config.c
@@ -0,0 +1,77 @@
+/* Copyright 2020 The Chromium OS 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 <fcntl.h>
+#include <stdint.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <syslog.h>
+
+#include "cras_audio_area.h"
+#include "cras_config.h"
+#include "cras_messages.h"
+#include "cras_rclient.h"
+#include "cras_rstream.h"
+#include "cras_rstream_config.h"
+#include "cras_server_metrics.h"
+#include "cras_shm.h"
+#include "cras_types.h"
+#include "cras_system_state.h"
+
+void cras_rstream_config_init(
+	struct cras_rclient *client, cras_stream_id_t stream_id,
+	enum CRAS_STREAM_TYPE stream_type, enum CRAS_CLIENT_TYPE client_type,
+	enum CRAS_STREAM_DIRECTION direction, uint32_t dev_idx, uint32_t flags,
+	uint32_t effects, const struct cras_audio_format *format,
+	size_t buffer_frames, size_t cb_threshold, int *audio_fd,
+	int *client_shm_fd, size_t client_shm_size,
+	const uint64_t buffer_offsets[2],
+	struct cras_rstream_config *stream_config)
+{
+	stream_config->stream_id = stream_id;
+	stream_config->stream_type = stream_type;
+	stream_config->client_type = client_type;
+	stream_config->direction = direction;
+	stream_config->dev_idx = dev_idx;
+	stream_config->flags = flags;
+	stream_config->effects = effects;
+	stream_config->format = format;
+	stream_config->buffer_frames = buffer_frames;
+	stream_config->cb_threshold = cb_threshold;
+	stream_config->audio_fd = *audio_fd;
+	*audio_fd = -1;
+	stream_config->client_shm_fd = *client_shm_fd;
+	*client_shm_fd = -1;
+	stream_config->client_shm_size = client_shm_size;
+	stream_config->buffer_offsets[0] = buffer_offsets[0];
+	stream_config->buffer_offsets[1] = buffer_offsets[1];
+	stream_config->client = client;
+}
+
+struct cras_rstream_config cras_rstream_config_init_with_message(
+	struct cras_rclient *client, const struct cras_connect_message *msg,
+	int *aud_fd, int *client_shm_fd,
+	const struct cras_audio_format *remote_fmt)
+{
+	struct cras_rstream_config stream_config;
+
+	const uint64_t buffer_offsets[2] = { msg->buffer_offsets[0],
+					     msg->buffer_offsets[1] };
+	cras_rstream_config_init(client, msg->stream_id, msg->stream_type,
+				 msg->client_type, msg->direction, msg->dev_idx,
+				 msg->flags, msg->effects, remote_fmt,
+				 msg->buffer_frames, msg->cb_threshold, aud_fd,
+				 client_shm_fd, msg->client_shm_size,
+				 buffer_offsets, &stream_config);
+	return stream_config;
+}
+
+void cras_rstream_config_cleanup(struct cras_rstream_config *stream_config)
+{
+	if (stream_config->audio_fd >= 0)
+		close(stream_config->audio_fd);
+	if (stream_config->client_shm_fd >= 0)
+		close(stream_config->client_shm_fd);
+}
diff --git a/cras/src/server/cras_rstream_config.h b/cras/src/server/cras_rstream_config.h
new file mode 100644
index 0000000..4d3713c
--- /dev/null
+++ b/cras/src/server/cras_rstream_config.h
@@ -0,0 +1,101 @@
+/* Copyright 2020 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/*
+ * Remote Stream Configuration
+ */
+#ifndef CRAS_RSTREAM_CONFIG_H_
+#define CRAS_RSTREAM_CONFIG_H_
+
+#include "buffer_share.h"
+#include "cras_shm.h"
+#include "cras_types.h"
+
+struct cras_connect_message;
+struct dev_mix;
+
+/* Config for creating an rstream.
+ *    stream_type - CRAS_STREAM_TYPE.
+ *    client_type - CRAS_CLIENT_TYPE.
+ *    direction - CRAS_STREAM_OUTPUT or CRAS_STREAM_INPUT.
+ *    dev_idx - Pin to this device if != NO_DEVICE.
+ *    flags - Any special handling for this stream.
+ *    effects - Bit map of effects to be enabled on this stream.
+ *    format - The audio format the stream wishes to use.
+ *    buffer_frames - Total number of audio frames to buffer.
+ *    cb_threshold - # of frames when to request more from the client.
+ *    audio_fd - The fd to read/write audio signals to. May be -1 for server
+ *               stream. Some functions may mutably borrow the config and move
+ *               the fd ownership.
+ *    client_shm_fd - The shm fd to use to back the samples area. May be -1.
+ *                    Some functions may dup this fd while borrowing the config.
+ *    client_shm_size - The size of shm area backed by client_shm_fd.
+ *    buffer_offsets - Initial values for buffer_offset for a client shm stream.
+ *    client - The client that owns this stream.
+ */
+struct cras_rstream_config {
+	cras_stream_id_t stream_id;
+	enum CRAS_STREAM_TYPE stream_type;
+	enum CRAS_CLIENT_TYPE client_type;
+	enum CRAS_STREAM_DIRECTION direction;
+	uint32_t dev_idx;
+	uint32_t flags;
+	uint32_t effects;
+	const struct cras_audio_format *format;
+	size_t buffer_frames;
+	size_t cb_threshold;
+	int audio_fd;
+	int client_shm_fd;
+	size_t client_shm_size;
+	uint32_t buffer_offsets[2];
+	struct cras_rclient *client;
+};
+
+/* Fills cras_rstream_config with given parameters.
+ *
+ * Args:
+ *   audio_fd - The audio fd pointer from client. Its ownership will be moved to
+ *              stream_config.
+ *   client_shm_fd - The shared memory fd pointer for samples from client. Its
+ *                   ownership will be moved to stream_config.
+ *   Other args - See comments in struct cras_rstream_config.
+ */
+void cras_rstream_config_init(
+	struct cras_rclient *client, cras_stream_id_t stream_id,
+	enum CRAS_STREAM_TYPE stream_type, enum CRAS_CLIENT_TYPE client_type,
+	enum CRAS_STREAM_DIRECTION direction, uint32_t dev_idx, uint32_t flags,
+	uint32_t effects, const struct cras_audio_format *format,
+	size_t buffer_frames, size_t cb_threshold, int *audio_fd,
+	int *client_shm_fd, size_t client_shm_size,
+	const uint64_t buffer_offsets[2],
+	struct cras_rstream_config *stream_config);
+
+/* Fills cras_rstream_config with given parameters and a cras_connect_message.
+ *
+ * Args:
+ *   client - The rclient which handles the connect message.
+ *   msg - The cras_connect_message from client.
+ *   aud_fd - The audio fd pointer from client. Its ownership will be moved to
+ *            stream_config.
+ *   client_shm_fd - The shared memory fd pointer for samples from client. Its
+ *                   ownership will be moved to stream_config.
+ *   remote_format - The remote_format for the config.
+ *
+ * Returns a cras_rstream_config struct filled in with params from the message.
+ */
+struct cras_rstream_config cras_rstream_config_init_with_message(
+	struct cras_rclient *client, const struct cras_connect_message *msg,
+	int *aud_fd, int *client_shm_fd,
+	const struct cras_audio_format *remote_format);
+
+/* Cleans up given cras_rstream_config. All fds inside the config will be
+ * closed.
+ *
+ * Args:
+ *   stream_config - The config to be cleaned up.
+ */
+void cras_rstream_config_cleanup(struct cras_rstream_config *stream_config);
+
+#endif /* CRAS_RSTREAM_CONFIG_H_ */
diff --git a/cras/src/server/cras_server.c b/cras/src/server/cras_server.c
index 97c83df..5f2ce63 100644
--- a/cras/src/server/cras_server.c
+++ b/cras/src/server/cras_server.c
@@ -79,16 +79,18 @@
  * to watch file descriptors.  The client can then read or write the fd.
  * Members:
  *    fd - The file descriptor passed to select.
- *    callack - The funciton to call when fd is ready.
+ *    callback - The funciton to call when fd is ready.
  *    callback_data - Pointer passed to the callback.
  *    pollfd - Pointer to struct pollfd for this callback.
+ *    events - The events to poll for.
  */
 struct client_callback {
 	int select_fd;
-	void (*callback)(void *);
+	void (*callback)(void *data, int revents);
 	void *callback_data;
 	struct pollfd *pollfd;
 	int deleted;
+	int events;
 	struct client_callback *prev, *next;
 };
 
@@ -277,8 +279,8 @@
 /* Add a file descriptor to be passed to select in the main loop. This is
  * registered with system state so that it is called when any client asks to
  * have a callback triggered based on an fd being readable. */
-static int add_select_fd(int fd, void (*cb)(void *data), void *callback_data,
-			 void *server_data)
+static int add_select_fd(int fd, void (*cb)(void *data, int events),
+			 void *callback_data, int events, void *server_data)
 {
 	struct client_callback *new_cb;
 	struct client_callback *client_cb;
@@ -301,6 +303,7 @@
 	new_cb->callback = cb;
 	new_cb->callback_data = callback_data;
 	new_cb->deleted = 0;
+	new_cb->events = events;
 	new_cb->pollfd = NULL;
 
 	DL_APPEND(serv->client_callbacks, new_cb);
@@ -474,7 +477,7 @@
  * Returns 0 on success and leaves the created fd and the address information
  * in server_socket.
  * When error occurs, the created fd will be closed and the file path will be
- * unlinked.
+ * unlinked and returns negative error code.
  */
 static int create_and_listen_server_socket(enum CRAS_CONNECTION_TYPE conn_type,
 					   struct server_socket *server_socket)
@@ -508,7 +511,7 @@
 		  sizeof(struct sockaddr_un));
 	if (rc < 0) {
 		syslog(LOG_ERR, "Bind to server socket failed.");
-		rc = errno;
+		rc = -errno;
 		goto error;
 	}
 
@@ -519,7 +522,7 @@
 
 	if (listen(socket_fd, 5) != 0) {
 		syslog(LOG_ERR, "Listen on server socket failed.");
-		rc = errno;
+		rc = -errno;
 		goto error;
 	}
 
@@ -641,7 +644,7 @@
 			if (client_cb->deleted)
 				continue;
 			pollfds[num_pollfds].fd = client_cb->select_fd;
-			pollfds[num_pollfds].events = POLLIN;
+			pollfds[num_pollfds].events = client_cb->events;
 			client_cb->pollfd = &pollfds[num_pollfds];
 			num_pollfds++;
 		}
@@ -687,8 +690,9 @@
 		/* Check any client-registered fd/callback pairs. */
 		DL_FOREACH (server_instance.client_callbacks, client_cb)
 			if (!client_cb->deleted && client_cb->pollfd &&
-			    (client_cb->pollfd->revents & POLLIN))
-				client_cb->callback(client_cb->callback_data);
+			    (client_cb->pollfd->revents & client_cb->events))
+				client_cb->callback(client_cb->callback_data,
+						    client_cb->pollfd->revents);
 
 		cleanup_select_fds(&server_instance);
 
@@ -713,4 +717,4 @@
 
 	DL_FOREACH (server_instance.clients_head, client)
 		cras_rclient_send_message(client->client, msg, NULL, 0);
-}
\ No newline at end of file
+}
diff --git a/cras/src/server/cras_server_metrics.c b/cras/src/server/cras_server_metrics.c
index 5f51e83..7e48710 100644
--- a/cras/src/server/cras_server_metrics.c
+++ b/cras/src/server/cras_server_metrics.c
@@ -16,13 +16,18 @@
 #include "cras_metrics.h"
 #include "cras_main_message.h"
 #include "cras_rstream.h"
+#include "cras_server_metrics.h"
 #include "cras_system_state.h"
 
-#define METRICS_NAME_BUFFER_SIZE 50
+#define METRICS_NAME_BUFFER_SIZE 100
 
 const char kBusyloop[] = "Cras.Busyloop";
+const char kBusyloopLength[] = "Cras.BusyloopLength";
 const char kDeviceTypeInput[] = "Cras.DeviceTypeInput";
 const char kDeviceTypeOutput[] = "Cras.DeviceTypeOutput";
+const char kDeviceGain[] = "Cras.DeviceGain";
+const char kDeviceVolume[] = "Cras.DeviceVolume";
+const char kFetchDelayMilliSeconds[] = "Cras.FetchDelayMilliSeconds";
 const char kHighestDeviceDelayInput[] = "Cras.HighestDeviceDelayInput";
 const char kHighestDeviceDelayOutput[] = "Cras.HighestDeviceDelayOutput";
 const char kHighestInputHardwareLevel[] = "Cras.HighestInputHardwareLevel";
@@ -44,16 +49,23 @@
 const char kMissedCallbackSecondTimeOutput[] =
 	"Cras.MissedCallbackSecondTimeOutput";
 const char kNoCodecsFoundMetric[] = "Cras.NoCodecsFoundAtBoot";
-const char kStreamTimeoutMilliSeconds[] = "Cras.StreamTimeoutMilliSeconds";
 const char kStreamCallbackThreshold[] = "Cras.StreamCallbackThreshold";
 const char kStreamClientTypeInput[] = "Cras.StreamClientTypeInput";
 const char kStreamClientTypeOutput[] = "Cras.StreamClientTypeOutput";
 const char kStreamFlags[] = "Cras.StreamFlags";
+const char kStreamEffects[] = "Cras.StreamEffects";
+const char kStreamRuntime[] = "Cras.StreamRuntime";
 const char kStreamSamplingFormat[] = "Cras.StreamSamplingFormat";
 const char kStreamSamplingRate[] = "Cras.StreamSamplingRate";
 const char kUnderrunsPerDevice[] = "Cras.UnderrunsPerDevice";
+const char kHfpScoConnectionError[] = "Cras.HfpScoConnectionError";
+const char kHfpBatteryIndicatorSupported[] =
+	"Cras.HfpBatteryIndicatorSupported";
+const char kHfpBatteryReport[] = "Cras.HfpBatteryReport";
 const char kHfpWidebandSpeechSupported[] = "Cras.HfpWidebandSpeechSupported";
 const char kHfpWidebandSpeechPacketLoss[] = "Cras.HfpWidebandSpeechPacketLoss";
+const char kHfpWidebandSpeechSelectedCodec[] =
+	"Cras.kHfpWidebandSpeechSelectedCodec";
 
 /*
  * Records missed callback frequency only when the runtime of stream is larger
@@ -75,10 +87,17 @@
 
 /* Type of metrics to log. */
 enum CRAS_SERVER_METRICS_TYPE {
+	BT_BATTERY_INDICATOR_SUPPORTED,
+	BT_BATTERY_REPORT,
+	BT_SCO_CONNECTION_ERROR,
 	BT_WIDEBAND_PACKET_LOSS,
 	BT_WIDEBAND_SUPPORTED,
+	BT_WIDEBAND_SELECTED_CODEC,
 	BUSYLOOP,
+	BUSYLOOP_LENGTH,
+	DEVICE_GAIN,
 	DEVICE_RUNTIME,
+	DEVICE_VOLUME,
 	HIGHEST_DEVICE_DELAY_INPUT,
 	HIGHEST_DEVICE_DELAY_OUTPUT,
 	HIGHEST_INPUT_HW_LEVEL,
@@ -93,7 +112,8 @@
 	MISSED_CB_SECOND_TIME_INPUT,
 	MISSED_CB_SECOND_TIME_OUTPUT,
 	NUM_UNDERRUNS,
-	STREAM_CONFIG
+	STREAM_CONFIG,
+	STREAM_RUNTIME
 };
 
 enum CRAS_METRICS_DEVICE_TYPE {
@@ -118,17 +138,21 @@
 	CRAS_METRICS_DEVICE_HFP,
 	CRAS_METRICS_DEVICE_HSP,
 	CRAS_METRICS_DEVICE_BLUETOOTH,
+	CRAS_METRICS_DEVICE_BLUETOOTH_NB_MIC,
 	CRAS_METRICS_DEVICE_NO_DEVICE,
 	CRAS_METRICS_DEVICE_NORMAL_FALLBACK,
 	CRAS_METRICS_DEVICE_ABNORMAL_FALLBACK,
 	CRAS_METRICS_DEVICE_SILENT_HOTWORD,
 	CRAS_METRICS_DEVICE_UNKNOWN,
+	CRAS_METRICS_DEVICE_BLUETOOTH_WB_MIC,
+	CRAS_METRICS_DEVICE_ALSA_LOOPBACK,
 };
 
 struct cras_server_metrics_stream_config {
 	enum CRAS_STREAM_DIRECTION direction;
 	unsigned cb_threshold;
 	unsigned flags;
+	unsigned effects;
 	int format;
 	unsigned rate;
 	enum CRAS_CLIENT_TYPE client_type;
@@ -138,6 +162,14 @@
 	enum CRAS_METRICS_DEVICE_TYPE type;
 	enum CRAS_STREAM_DIRECTION direction;
 	struct timespec runtime;
+	unsigned value;
+};
+
+struct cras_server_metrics_stream_data {
+	enum CRAS_CLIENT_TYPE client_type;
+	enum CRAS_STREAM_TYPE stream_type;
+	enum CRAS_STREAM_DIRECTION direction;
+	struct timespec runtime;
 };
 
 struct cras_server_metrics_timespec_data {
@@ -149,6 +181,7 @@
 	unsigned value;
 	struct cras_server_metrics_stream_config stream_config;
 	struct cras_server_metrics_device_data device_data;
+	struct cras_server_metrics_stream_data stream_data;
 	struct cras_server_metrics_timespec_data timespec_data;
 };
 
@@ -231,9 +264,15 @@
 		return "HSP";
 	case CRAS_METRICS_DEVICE_BLUETOOTH:
 		return "Bluetooth";
+	case CRAS_METRICS_DEVICE_BLUETOOTH_NB_MIC:
+		return "BluetoothNarrowBandMic";
+	case CRAS_METRICS_DEVICE_BLUETOOTH_WB_MIC:
+		return "BluetoothWideBandMic";
 	case CRAS_METRICS_DEVICE_NO_DEVICE:
 		return "NoDevice";
-	/* Other dummy devices. */
+	case CRAS_METRICS_DEVICE_ALSA_LOOPBACK:
+		return "AlsaLoopback";
+	/* Other fallback devices. */
 	case CRAS_METRICS_DEVICE_NORMAL_FALLBACK:
 		return "NormalFallback";
 	case CRAS_METRICS_DEVICE_ABNORMAL_FALLBACK:
@@ -247,6 +286,58 @@
 	}
 }
 
+static inline const char *
+metrics_client_type_str(enum CRAS_CLIENT_TYPE client_type)
+{
+	switch (client_type) {
+	case CRAS_CLIENT_TYPE_UNKNOWN:
+		return "Unknown";
+	case CRAS_CLIENT_TYPE_LEGACY:
+		return "Legacy";
+	case CRAS_CLIENT_TYPE_TEST:
+		return "Test";
+	case CRAS_CLIENT_TYPE_PCM:
+		return "PCM";
+	case CRAS_CLIENT_TYPE_CHROME:
+		return "Chrome";
+	case CRAS_CLIENT_TYPE_ARC:
+		return "ARC";
+	case CRAS_CLIENT_TYPE_CROSVM:
+		return "CrOSVM";
+	case CRAS_CLIENT_TYPE_SERVER_STREAM:
+		return "ServerStream";
+	case CRAS_CLIENT_TYPE_LACROS:
+		return "LaCrOS";
+	case CRAS_CLIENT_TYPE_PLUGIN:
+		return "PluginVM";
+	case CRAS_CLIENT_TYPE_ARCVM:
+		return "ARCVM";
+	default:
+		return "InvalidType";
+	}
+}
+
+static inline const char *
+metrics_stream_type_str(enum CRAS_STREAM_TYPE stream_type)
+{
+	switch (stream_type) {
+	case CRAS_STREAM_TYPE_DEFAULT:
+		return "Default";
+	case CRAS_STREAM_TYPE_MULTIMEDIA:
+		return "Multimedia";
+	case CRAS_STREAM_TYPE_VOICE_COMMUNICATION:
+		return "VoiceCommunication";
+	case CRAS_STREAM_TYPE_SPEECH_RECOGNITION:
+		return "SpeechRecognition";
+	case CRAS_STREAM_TYPE_PRO_AUDIO:
+		return "ProAudio";
+	case CRAS_STREAM_TYPE_ACCESSIBILITY:
+		return "Accessibility";
+	default:
+		return "InvalidType";
+	}
+}
+
 static enum CRAS_METRICS_DEVICE_TYPE
 get_metrics_device_type(struct cras_iodev *iodev)
 {
@@ -301,25 +392,158 @@
 		return CRAS_METRICS_DEVICE_POST_DSP_LOOPBACK;
 	case CRAS_NODE_TYPE_USB:
 		return CRAS_METRICS_DEVICE_USB;
-	case CRAS_NODE_TYPE_BLUETOOTH:
+	case CRAS_NODE_TYPE_BLUETOOTH: {
 #ifdef CRAS_DBUS
-		if (cras_bt_io_on_profile(iodev,
-					  CRAS_BT_DEVICE_PROFILE_A2DP_SOURCE))
+		enum cras_bt_device_profile profile =
+			cras_bt_io_profile_to_log(iodev);
+		switch (profile) {
+		case CRAS_BT_DEVICE_PROFILE_A2DP_SOURCE:
 			return CRAS_METRICS_DEVICE_A2DP;
-		if (cras_bt_io_on_profile(
-			    iodev, CRAS_BT_DEVICE_PROFILE_HFP_AUDIOGATEWAY))
-			return CRAS_METRICS_DEVICE_HFP;
-		if (cras_bt_io_on_profile(
-			    iodev, CRAS_BT_DEVICE_PROFILE_HSP_AUDIOGATEWAY))
+		case CRAS_BT_DEVICE_PROFILE_HFP_AUDIOGATEWAY:
+			/* HFP narrow band has its own node type so we know
+			 * this is wideband mic for sure. */
+			return (iodev->direction == CRAS_STREAM_INPUT) ?
+				       CRAS_METRICS_DEVICE_BLUETOOTH_WB_MIC :
+				       CRAS_METRICS_DEVICE_HFP;
+		case CRAS_BT_DEVICE_PROFILE_HSP_AUDIOGATEWAY:
 			return CRAS_METRICS_DEVICE_HSP;
+		default:
+			break;
+		}
 #endif
 		return CRAS_METRICS_DEVICE_BLUETOOTH;
+	}
+	case CRAS_NODE_TYPE_BLUETOOTH_NB_MIC:
+		return CRAS_METRICS_DEVICE_BLUETOOTH_NB_MIC;
+	case CRAS_NODE_TYPE_ALSA_LOOPBACK:
+		return CRAS_METRICS_DEVICE_ALSA_LOOPBACK;
 	case CRAS_NODE_TYPE_UNKNOWN:
 	default:
 		return CRAS_METRICS_DEVICE_UNKNOWN;
 	}
 }
 
+/*
+ * Logs metrics for each group it belongs to. The UMA does not merge subgroups
+ * automatically so we need to log them separately.
+ *
+ * For example, if we call this function with argument (3, 48000,
+ * Cras.StreamSamplingRate, Input, Chrome), it will send 48000 to below
+ * metrics:
+ * Cras.StreamSamplingRate.Input.Chrome
+ * Cras.StreamSamplingRate.Input
+ * Cras.StreamSamplingRate
+ */
+static void log_sparse_histogram_each_level(int num, int sample, ...)
+{
+	char metrics_name[METRICS_NAME_BUFFER_SIZE] = {};
+	va_list valist;
+	int i, len = 0;
+
+	va_start(valist, sample);
+
+	for (i = 0; i < num && len < METRICS_NAME_BUFFER_SIZE; i++) {
+		int metric_len =
+			snprintf(metrics_name + len,
+				 METRICS_NAME_BUFFER_SIZE - len, "%s%s",
+				 i ? "." : "", va_arg(valist, char *));
+		// Exit early on error or running out of bufferspace. Avoids
+		// logging partial or corrupted strings.
+		if (metric_len < 0 ||
+		    metric_len > METRICS_NAME_BUFFER_SIZE - len)
+			break;
+		len += metric_len;
+		cras_metrics_log_sparse_histogram(metrics_name, sample);
+	}
+
+	va_end(valist);
+}
+
+static void log_histogram_each_level(int num, int sample, int min, int max,
+				     int nbuckets, ...)
+{
+	char metrics_name[METRICS_NAME_BUFFER_SIZE] = {};
+	va_list valist;
+	int i, len = 0;
+
+	va_start(valist, nbuckets);
+
+	for (i = 0; i < num && len < METRICS_NAME_BUFFER_SIZE; i++) {
+		int metric_len =
+			snprintf(metrics_name + len,
+				 METRICS_NAME_BUFFER_SIZE - len, "%s%s",
+				 i ? "." : "", va_arg(valist, char *));
+		// Exit early on error or running out of bufferspace. Avoids
+		// logging partial or corrupted strings.
+		if (metric_len < 0 ||
+		    metric_len > METRICS_NAME_BUFFER_SIZE - len)
+			break;
+		len += metric_len;
+		cras_metrics_log_histogram(metrics_name, sample, min, max,
+					   nbuckets);
+	}
+
+	va_end(valist);
+}
+
+int cras_server_metrics_hfp_sco_connection_error(
+	enum CRAS_METRICS_BT_SCO_ERROR_TYPE type)
+{
+	struct cras_server_metrics_message msg;
+	union cras_server_metrics_data data;
+	int err;
+
+	data.value = type;
+	init_server_metrics_msg(&msg, BT_SCO_CONNECTION_ERROR, data);
+
+	err = cras_server_metrics_message_send(
+		(struct cras_main_message *)&msg);
+	if (err < 0) {
+		syslog(LOG_ERR, "Failed to send metrics message: "
+				"BT_SCO_CONNECTION_ERROR");
+		return err;
+	}
+	return 0;
+}
+
+int cras_server_metrics_hfp_battery_indicator(int battery_indicator_support)
+{
+	struct cras_server_metrics_message msg;
+	union cras_server_metrics_data data;
+	int err;
+
+	data.value = battery_indicator_support;
+	init_server_metrics_msg(&msg, BT_BATTERY_INDICATOR_SUPPORTED, data);
+
+	err = cras_server_metrics_message_send(
+		(struct cras_main_message *)&msg);
+	if (err < 0) {
+		syslog(LOG_ERR, "Failed to send metrics message: "
+				"BT_BATTERY_INDICATOR_SUPPORTED");
+		return err;
+	}
+	return 0;
+}
+
+int cras_server_metrics_hfp_battery_report(int battery_report)
+{
+	struct cras_server_metrics_message msg;
+	union cras_server_metrics_data data;
+	int err;
+
+	data.value = battery_report;
+	init_server_metrics_msg(&msg, BT_BATTERY_REPORT, data);
+
+	err = cras_server_metrics_message_send(
+		(struct cras_main_message *)&msg);
+	if (err < 0) {
+		syslog(LOG_ERR, "Failed to send metrics message: "
+				"BT_BATTERY_REPORT");
+		return err;
+	}
+	return 0;
+}
+
 int cras_server_metrics_hfp_packet_loss(float packet_loss_ratio)
 {
 	struct cras_server_metrics_message msg;
@@ -360,6 +584,25 @@
 	return 0;
 }
 
+int cras_server_metrics_hfp_wideband_selected_codec(int codec)
+{
+	struct cras_server_metrics_message msg;
+	union cras_server_metrics_data data;
+	int err;
+
+	data.value = codec;
+	init_server_metrics_msg(&msg, BT_WIDEBAND_SELECTED_CODEC, data);
+
+	err = cras_server_metrics_message_send(
+		(struct cras_main_message *)&msg);
+	if (err < 0) {
+		syslog(LOG_ERR, "Failed to send metrics message: "
+				"BT_WIDEBAND_SELECTED_CODEC");
+		return err;
+	}
+	return 0;
+}
+
 int cras_server_metrics_device_runtime(struct cras_iodev *iodev)
 {
 	struct cras_server_metrics_message msg;
@@ -385,6 +628,56 @@
 	return 0;
 }
 
+int cras_server_metrics_device_gain(struct cras_iodev *iodev)
+{
+	struct cras_server_metrics_message msg;
+	union cras_server_metrics_data data;
+	int err;
+
+	if (iodev->direction == CRAS_STREAM_OUTPUT)
+		return 0;
+
+	data.device_data.type = get_metrics_device_type(iodev);
+	data.device_data.value =
+		(unsigned)100 * iodev->active_node->ui_gain_scaler;
+
+	init_server_metrics_msg(&msg, DEVICE_GAIN, data);
+
+	err = cras_server_metrics_message_send(
+		(struct cras_main_message *)&msg);
+	if (err < 0) {
+		syslog(LOG_ERR, "Failed to send metrics message: DEVICE_GAIN");
+		return err;
+	}
+
+	return 0;
+}
+
+int cras_server_metrics_device_volume(struct cras_iodev *iodev)
+{
+	struct cras_server_metrics_message msg;
+	union cras_server_metrics_data data;
+	int err;
+
+	if (iodev->direction == CRAS_STREAM_INPUT)
+		return 0;
+
+	data.device_data.type = get_metrics_device_type(iodev);
+	data.device_data.value = iodev->active_node->volume;
+
+	init_server_metrics_msg(&msg, DEVICE_VOLUME, data);
+
+	err = cras_server_metrics_message_send(
+		(struct cras_main_message *)&msg);
+	if (err < 0) {
+		syslog(LOG_ERR,
+		       "Failed to send metrics message: DEVICE_VOLUME");
+		return err;
+	}
+
+	return 0;
+}
+
 int cras_server_metrics_highest_device_delay(
 	unsigned int hw_level, unsigned int largest_cb_level,
 	enum CRAS_STREAM_DIRECTION direction)
@@ -464,13 +757,31 @@
 	return 0;
 }
 
-int cras_server_metrics_longest_fetch_delay(unsigned delay_msec)
+/* Logs longest fetch delay of a stream. */
+int cras_server_metrics_longest_fetch_delay(const struct cras_rstream *stream)
 {
 	struct cras_server_metrics_message msg;
 	union cras_server_metrics_data data;
 	int err;
 
-	data.value = delay_msec;
+	data.stream_data.client_type = stream->client_type;
+	data.stream_data.stream_type = stream->stream_type;
+	data.stream_data.direction = stream->direction;
+
+	/*
+	 * There is no delay when the sleep_interval_ts larger than the
+	 * longest_fetch_interval.
+	 */
+	if (!timespec_after(&stream->longest_fetch_interval,
+			    &stream->sleep_interval_ts)) {
+		data.stream_data.runtime.tv_sec = 0;
+		data.stream_data.runtime.tv_nsec = 0;
+	} else {
+		subtract_timespecs(&stream->longest_fetch_interval,
+				   &stream->sleep_interval_ts,
+				   &data.stream_data.runtime);
+	}
+
 	init_server_metrics_msg(&msg, LONGEST_FETCH_DELAY, data);
 	err = cras_server_metrics_message_send(
 		(struct cras_main_message *)&msg);
@@ -502,7 +813,9 @@
 	return 0;
 }
 
-int cras_server_metrics_missed_cb_frequency(const struct cras_rstream *stream)
+/* Logs the frequency of missed callback. */
+static int
+cras_server_metrics_missed_cb_frequency(const struct cras_rstream *stream)
 {
 	struct cras_server_metrics_message msg;
 	union cras_server_metrics_data data;
@@ -655,7 +968,9 @@
 	return rc;
 }
 
-int cras_server_metrics_stream_config(struct cras_rstream_config *config)
+/* Logs the stream configurations from clients. */
+static int
+cras_server_metrics_stream_config(const struct cras_rstream_config *config)
 {
 	struct cras_server_metrics_message msg;
 	union cras_server_metrics_data data;
@@ -664,6 +979,7 @@
 	data.stream_config.direction = config->direction;
 	data.stream_config.cb_threshold = (unsigned)config->cb_threshold;
 	data.stream_config.flags = (unsigned)config->flags;
+	data.stream_config.effects = (unsigned)config->effects;
 	data.stream_config.format = (int)config->format->format;
 	data.stream_config.rate = (unsigned)config->format->frame_rate;
 	data.stream_config.client_type = config->client_type;
@@ -680,6 +996,50 @@
 	return 0;
 }
 
+/* Logs runtime of a stream. */
+int cras_server_metrics_stream_runtime(const struct cras_rstream *stream)
+{
+	struct cras_server_metrics_message msg;
+	union cras_server_metrics_data data;
+	struct timespec now;
+	int err;
+
+	data.stream_data.client_type = stream->client_type;
+	data.stream_data.stream_type = stream->stream_type;
+	data.stream_data.direction = stream->direction;
+	clock_gettime(CLOCK_MONOTONIC_RAW, &now);
+	subtract_timespecs(&now, &stream->start_ts, &data.stream_data.runtime);
+
+	init_server_metrics_msg(&msg, STREAM_RUNTIME, data);
+
+	err = cras_server_metrics_message_send(
+		(struct cras_main_message *)&msg);
+	if (err < 0) {
+		syslog(LOG_ERR,
+		       "Failed to send metrics message: STREAM_RUNTIME");
+		return err;
+	}
+
+	return 0;
+}
+
+int cras_server_metrics_stream_create(const struct cras_rstream_config *config)
+{
+	return cras_server_metrics_stream_config(config);
+}
+
+int cras_server_metrics_stream_destroy(const struct cras_rstream *stream)
+{
+	int rc;
+	rc = cras_server_metrics_missed_cb_frequency(stream);
+	if (rc < 0)
+		return rc;
+	rc = cras_server_metrics_stream_runtime(stream);
+	if (rc < 0)
+		return rc;
+	return cras_server_metrics_longest_fetch_delay(stream);
+}
+
 int cras_server_metrics_busyloop(struct timespec *ts, unsigned count)
 {
 	struct cras_server_metrics_message msg;
@@ -700,6 +1060,26 @@
 	return 0;
 }
 
+int cras_server_metrics_busyloop_length(unsigned length)
+{
+	struct cras_server_metrics_message msg;
+	union cras_server_metrics_data data;
+	int err;
+
+	data.value = length;
+
+	init_server_metrics_msg(&msg, BUSYLOOP_LENGTH, data);
+
+	err = cras_server_metrics_message_send(
+		(struct cras_main_message *)&msg);
+	if (err < 0) {
+		syslog(LOG_ERR,
+		       "Failed to send metrics message: BUSYLOOP_LENGTH");
+		return err;
+	}
+	return 0;
+}
+
 static void metrics_device_runtime(struct cras_server_metrics_device_data data)
 {
 	char metrics_name[METRICS_NAME_BUFFER_SIZE];
@@ -718,6 +1098,44 @@
 		cras_metrics_log_sparse_histogram(kDeviceTypeOutput, data.type);
 }
 
+static void metrics_device_gain(struct cras_server_metrics_device_data data)
+{
+	char metrics_name[METRICS_NAME_BUFFER_SIZE];
+
+	snprintf(metrics_name, METRICS_NAME_BUFFER_SIZE, "%s.%s", kDeviceGain,
+		 metrics_device_type_str(data.type));
+	cras_metrics_log_histogram(metrics_name, data.value, 0, 2000, 20);
+}
+
+static void metrics_device_volume(struct cras_server_metrics_device_data data)
+{
+	char metrics_name[METRICS_NAME_BUFFER_SIZE];
+
+	snprintf(metrics_name, METRICS_NAME_BUFFER_SIZE, "%s.%s", kDeviceVolume,
+		 metrics_device_type_str(data.type));
+	cras_metrics_log_histogram(metrics_name, data.value, 0, 100, 20);
+}
+
+static void
+metrics_longest_fetch_delay(struct cras_server_metrics_stream_data data)
+{
+	int fetch_delay_msec =
+		data.runtime.tv_sec * 1000 + data.runtime.tv_nsec / 1000000;
+	log_histogram_each_level(3, fetch_delay_msec, 0, 10000, 20,
+				 kFetchDelayMilliSeconds,
+				 metrics_client_type_str(data.client_type),
+				 metrics_stream_type_str(data.stream_type));
+}
+
+static void metrics_stream_runtime(struct cras_server_metrics_stream_data data)
+{
+	log_histogram_each_level(
+		4, (int)data.runtime.tv_sec, 0, 10000, 20, kStreamRuntime,
+		data.direction == CRAS_STREAM_INPUT ? "Input" : "Output",
+		metrics_client_type_str(data.client_type),
+		metrics_stream_type_str(data.stream_type));
+}
+
 static void metrics_busyloop(struct cras_server_metrics_timespec_data data)
 {
 	char metrics_name[METRICS_NAME_BUFFER_SIZE];
@@ -731,18 +1149,37 @@
 static void
 metrics_stream_config(struct cras_server_metrics_stream_config config)
 {
+	const char *direction;
+
+	if (config.direction == CRAS_STREAM_INPUT)
+		direction = "Input";
+	else
+		direction = "Output";
+
 	/* Logs stream callback threshold. */
-	cras_metrics_log_sparse_histogram(kStreamCallbackThreshold,
-					  config.cb_threshold);
+	log_sparse_histogram_each_level(
+		3, config.cb_threshold, kStreamCallbackThreshold, direction,
+		metrics_client_type_str(config.client_type));
 
 	/* Logs stream flags. */
-	cras_metrics_log_sparse_histogram(kStreamFlags, config.flags);
+	log_sparse_histogram_each_level(
+		3, config.flags, kStreamFlags, direction,
+		metrics_client_type_str(config.client_type));
+
+	/* Logs stream effects. */
+	log_sparse_histogram_each_level(
+		3, config.effects, kStreamEffects, direction,
+		metrics_client_type_str(config.client_type));
 
 	/* Logs stream sampling format. */
-	cras_metrics_log_sparse_histogram(kStreamSamplingFormat, config.format);
+	log_sparse_histogram_each_level(
+		3, config.format, kStreamSamplingFormat, direction,
+		metrics_client_type_str(config.client_type));
 
 	/* Logs stream sampling rate. */
-	cras_metrics_log_sparse_histogram(kStreamSamplingRate, config.rate);
+	log_sparse_histogram_each_level(
+		3, config.rate, kStreamSamplingRate, direction,
+		metrics_client_type_str(config.client_type));
 
 	/* Logs stream client type. */
 	if (config.direction == CRAS_STREAM_INPUT)
@@ -758,6 +1195,18 @@
 	struct cras_server_metrics_message *metrics_msg =
 		(struct cras_server_metrics_message *)msg;
 	switch (metrics_msg->metrics_type) {
+	case BT_SCO_CONNECTION_ERROR:
+		cras_metrics_log_sparse_histogram(kHfpScoConnectionError,
+						  metrics_msg->data.value);
+		break;
+	case BT_BATTERY_INDICATOR_SUPPORTED:
+		cras_metrics_log_sparse_histogram(kHfpBatteryIndicatorSupported,
+						  metrics_msg->data.value);
+		break;
+	case BT_BATTERY_REPORT:
+		cras_metrics_log_sparse_histogram(kHfpBatteryReport,
+						  metrics_msg->data.value);
+		break;
 	case BT_WIDEBAND_PACKET_LOSS:
 		cras_metrics_log_histogram(kHfpWidebandSpeechPacketLoss,
 					   metrics_msg->data.value, 0, 1000,
@@ -767,9 +1216,20 @@
 		cras_metrics_log_sparse_histogram(kHfpWidebandSpeechSupported,
 						  metrics_msg->data.value);
 		break;
+	case BT_WIDEBAND_SELECTED_CODEC:
+		cras_metrics_log_sparse_histogram(
+			kHfpWidebandSpeechSelectedCodec,
+			metrics_msg->data.value);
+		break;
+	case DEVICE_GAIN:
+		metrics_device_gain(metrics_msg->data.device_data);
+		break;
 	case DEVICE_RUNTIME:
 		metrics_device_runtime(metrics_msg->data.device_data);
 		break;
+	case DEVICE_VOLUME:
+		metrics_device_volume(metrics_msg->data.device_data);
+		break;
 	case HIGHEST_DEVICE_DELAY_INPUT:
 		cras_metrics_log_histogram(kHighestDeviceDelayInput,
 					   metrics_msg->data.value, 1, 10000,
@@ -791,9 +1251,7 @@
 					   20);
 		break;
 	case LONGEST_FETCH_DELAY:
-		cras_metrics_log_histogram(kStreamTimeoutMilliSeconds,
-					   metrics_msg->data.value, 1, 20000,
-					   10);
+		metrics_longest_fetch_delay(metrics_msg->data.stream_data);
 		break;
 	case MISSED_CB_FIRST_TIME_INPUT:
 		cras_metrics_log_histogram(kMissedCallbackFirstTimeInput,
@@ -843,9 +1301,16 @@
 	case STREAM_CONFIG:
 		metrics_stream_config(metrics_msg->data.stream_config);
 		break;
+	case STREAM_RUNTIME:
+		metrics_stream_runtime(metrics_msg->data.stream_data);
+		break;
 	case BUSYLOOP:
 		metrics_busyloop(metrics_msg->data.timespec_data);
 		break;
+	case BUSYLOOP_LENGTH:
+		cras_metrics_log_histogram(
+			kBusyloopLength, metrics_msg->data.value, 0, 1000, 50);
+		break;
 	default:
 		syslog(LOG_ERR, "Unknown metrics type %u",
 		       metrics_msg->metrics_type);
diff --git a/cras/src/server/cras_server_metrics.h b/cras/src/server/cras_server_metrics.h
index b9eb121..e845808 100644
--- a/cras/src/server/cras_server_metrics.h
+++ b/cras/src/server/cras_server_metrics.h
@@ -13,15 +13,48 @@
 
 extern const char kNoCodecsFoundMetric[];
 
+enum CRAS_METRICS_BT_SCO_ERROR_TYPE {
+	CRAS_METRICS_SCO_SKT_SUCCESS = 0,
+	CRAS_METRICS_SCO_SKT_CONNECT_ERROR = 1,
+	CRAS_METRICS_SCO_SKT_OPEN_ERROR = 2,
+	CRAS_METRICS_SCO_SKT_POLL_TIMEOUT = 3,
+	CRAS_METRICS_SCO_SKT_POLL_ERR_HUP = 4,
+};
+
+/* Logs the error type happens when setting up SCO connection. This is mainly
+ * used to track whether the setup of SCO connection succeeds and the frequency
+ * of different errors. This will also be used to track if our fixes for these
+ * errors address the issues we find.
+ */
+int cras_server_metrics_hfp_sco_connection_error(
+	enum CRAS_METRICS_BT_SCO_ERROR_TYPE type);
+
+/* Logs an enum representing which spec does HFP headset supports battery
+ * indicator. Apple, HFP, none or both. */
+int cras_server_metrics_hfp_battery_indicator(int battery_indicator_support);
+
+/* Logs an enum representing the spec through which the battery level change
+ * event reported. Apple or HFP.*/
+int cras_server_metrics_hfp_battery_report(int battery_report);
+
 /* Logs if connected HFP headset supports wideband speech. */
 int cras_server_metrics_hfp_wideband_support(bool supported);
 
+/* Logs the selected codec in HFP wideband connection. */
+int cras_server_metrics_hfp_wideband_selected_codec(int codec);
+
 /* Logs the number of packet loss per 1000 packets under HFP capture. */
 int cras_server_metrics_hfp_packet_loss(float packet_loss_ratio);
 
 /* Logs runtime of a device. */
 int cras_server_metrics_device_runtime(struct cras_iodev *iodev);
 
+/* Logs the gain of a device. */
+int cras_server_metrics_device_gain(struct cras_iodev *iodev);
+
+/* Logs the volume of a device. */
+int cras_server_metrics_device_volume(struct cras_iodev *iodev);
+
 /* Logs the highest delay time of a device. */
 int cras_server_metrics_highest_device_delay(
 	unsigned int hw_level, unsigned int largest_cb_level,
@@ -31,24 +64,24 @@
 int cras_server_metrics_highest_hw_level(unsigned hw_level,
 					 enum CRAS_STREAM_DIRECTION direction);
 
-/* Logs the longest fetch delay of a stream in millisecond. */
-int cras_server_metrics_longest_fetch_delay(unsigned delay_msec);
-
 /* Logs the number of underruns of a device. */
 int cras_server_metrics_num_underruns(unsigned num_underruns);
 
-/* Logs the frequency of missed callback. */
-int cras_server_metrics_missed_cb_frequency(const struct cras_rstream *stream);
-
 /* Logs the missed callback event. */
-int cras_server_metrics_missed_cb_event(const struct cras_rstream *stream);
+int cras_server_metrics_missed_cb_event(struct cras_rstream *stream);
 
-/* Logs the stream configurations from clients. */
-int cras_server_metrics_stream_config(struct cras_rstream_config *config);
+/* Logs information when a stream creates. */
+int cras_server_metrics_stream_create(const struct cras_rstream_config *config);
+
+/* Logs information when a stream destroys. */
+int cras_server_metrics_stream_destroy(const struct cras_rstream *stream);
 
 /* Logs the number of busyloops for different time periods. */
 int cras_server_metrics_busyloop(struct timespec *ts, unsigned count);
 
+/* Logs the length of busyloops. */
+int cras_server_metrics_busyloop_length(unsigned length);
+
 /* Initialize metrics logging stuff. */
 int cras_server_metrics_init();
 
diff --git a/cras/src/server/cras_system_state.c b/cras/src/server/cras_system_state.c
index 7aa3367..366afb5 100644
--- a/cras/src/server/cras_system_state.c
+++ b/cras/src/server/cras_system_state.c
@@ -3,6 +3,7 @@
  * found in the LICENSE file.
  */
 
+#include <errno.h>
 #include <fcntl.h>
 #include <pthread.h>
 #include <string.h>
@@ -13,9 +14,11 @@
 #include <syslog.h>
 
 #include "cras_alsa_card.h"
+#include "cras_alert.h"
 #include "cras_board_config.h"
 #include "cras_config.h"
-#include "cras_device_blacklist.h"
+#include "cras_device_blocklist.h"
+#include "cras_iodev_list.h"
 #include "cras_observer.h"
 #include "cras_shm.h"
 #include "cras_system_state.h"
@@ -29,6 +32,11 @@
 	struct card_list *prev, *next;
 };
 
+struct name_list {
+	char name[NAME_MAX];
+	struct name_list *prev, *next;
+};
+
 /* The system state.
  * Members:
  *    exp_state - The exported system state shared with clients.
@@ -40,7 +48,7 @@
  *    device_config_dir - Directory of device configs where volume curves live.
  *    internal_ucm_suffix - The suffix to append to internal card name to
  *        control which ucm config file to load.
- *    device_blacklist - Blacklist of device the server will ignore.
+ *    device_blocklist - Blocklist of device the server will ignore.
  *    cards - A list of active sound cards in the system.
  *    update_lock - Protects the update_count, as audio threads can update the
  *      stream count.
@@ -48,6 +56,8 @@
  *    add_task - Function to handle adding a task for main thread to execute.
  *    task_data - Data to be passed to add_task handler function.
  *    main_thread_tid - The thread id of the main thread.
+ *    bt_fix_a2dp_packet_size - The flag to override A2DP packet size set by
+ *      Blueetoh peer devices to a smaller default value.
  */
 static struct {
 	struct cras_server_state *exp_state;
@@ -57,13 +67,14 @@
 	size_t shm_size;
 	const char *device_config_dir;
 	const char *internal_ucm_suffix;
-	struct cras_device_blacklist *device_blacklist;
+	struct name_list *ignore_suffix_cards;
+	struct cras_device_blocklist *device_blocklist;
 	struct card_list *cards;
 	pthread_mutex_t update_lock;
 	struct cras_tm *tm;
 	/* Select loop callback registration. */
-	int (*fd_add)(int fd, void (*cb)(void *data), void *cb_data,
-		      void *select_data);
+	int (*fd_add)(int fd, void (*cb)(void *data, int events), void *cb_data,
+		      int events, void *select_data);
 	void (*fd_rm)(int fd, void *select_data);
 	void *select_data;
 	int (*add_task)(void (*callback)(void *data), void *callback_data,
@@ -71,8 +82,42 @@
 	void *task_data;
 	struct cras_audio_thread_snapshot_buffer snapshot_buffer;
 	pthread_t main_thread_tid;
+	bool bt_fix_a2dp_packet_size;
 } state;
 
+/* The string format is CARD1,CARD2,CARD3. Divide it into a list. */
+void init_ignore_suffix_cards(char *str)
+{
+	struct name_list *card;
+	char *ptr;
+
+	state.ignore_suffix_cards = NULL;
+
+	if (str == NULL)
+		return;
+
+	ptr = strtok(str, ",");
+	while (ptr != NULL) {
+		card = (struct name_list *)calloc(1, sizeof(*card));
+		if (!card) {
+			syslog(LOG_ERR, "Failed to call calloc: %d", errno);
+			return;
+		}
+		strncpy(card->name, ptr, NAME_MAX - 1);
+		DL_APPEND(state.ignore_suffix_cards, card);
+		ptr = strtok(NULL, ",");
+	}
+}
+
+void deinit_ignore_suffix_cards()
+{
+	struct name_list *card;
+	DL_FOREACH (state.ignore_suffix_cards, card) {
+		DL_DELETE(state.ignore_suffix_cards, card);
+		free(card);
+	}
+}
+
 /*
  * Exported Interface.
  */
@@ -103,20 +148,21 @@
 	exp_state->mute = 0;
 	exp_state->mute_locked = 0;
 	exp_state->suspended = 0;
-	exp_state->capture_gain = DEFAULT_CAPTURE_GAIN;
-	exp_state->capture_gain_target = DEFAULT_CAPTURE_GAIN;
 	exp_state->capture_mute = 0;
 	exp_state->capture_mute_locked = 0;
 	exp_state->min_volume_dBFS = DEFAULT_MIN_VOLUME_DBFS;
 	exp_state->max_volume_dBFS = DEFAULT_MAX_VOLUME_DBFS;
-	exp_state->min_capture_gain = DEFAULT_MIN_CAPTURE_GAIN;
-	exp_state->max_capture_gain = DEFAULT_MAX_CAPTURE_GAIN;
 	exp_state->num_streams_attached = 0;
 	exp_state->default_output_buffer_size =
 		board_config.default_output_buffer_size;
 	exp_state->aec_supported = board_config.aec_supported;
 	exp_state->aec_group_id = board_config.aec_group_id;
-	exp_state->bt_wbs_enabled = 0;
+	exp_state->bt_wbs_enabled = board_config.bt_wbs_enabled;
+	exp_state->deprioritize_bt_wbs_mic =
+		board_config.deprioritize_bt_wbs_mic;
+	exp_state->noise_cancellation_enabled = 0;
+	exp_state->hotword_pause_at_suspend =
+		board_config.hotword_pause_at_suspend;
 
 	if ((rc = pthread_mutex_init(&state.update_lock, 0) != 0)) {
 		syslog(LOG_ERR, "Fatal: system state mutex init");
@@ -126,11 +172,13 @@
 	state.exp_state = exp_state;
 
 	/* Directory for volume curve configs.
-	 * Note that device_config_dir does not affect device blacklist.
-	 * Device blacklist is common to all boards so we do not need
-	 * to change device blacklist at run time. */
+	 * Note that device_config_dir does not affect device blocklist.
+	 * Device blocklist is common to all boards so we do not need
+	 * to change device blocklist at run time. */
 	state.device_config_dir = device_config_dir;
 	state.internal_ucm_suffix = NULL;
+	init_ignore_suffix_cards(board_config.ucm_ignore_suffix);
+	free(board_config.ucm_ignore_suffix);
 
 	state.tm = cras_tm_init();
 	if (!state.tm) {
@@ -138,9 +186,9 @@
 		exit(-ENOMEM);
 	}
 
-	/* Read config file for blacklisted devices. */
-	state.device_blacklist =
-		cras_device_blacklist_create(CRAS_CONFIG_FILE_DIR);
+	/* Read config file for blocklisted devices. */
+	state.device_blocklist =
+		cras_device_blocklist_create(CRAS_CONFIG_FILE_DIR);
 
 	/* Initialize snapshot buffer memory */
 	memset(&state.snapshot_buffer, 0,
@@ -148,6 +196,8 @@
 
 	/* Save thread id of the main thread. */
 	state.main_thread_tid = pthread_self();
+
+	state.bt_fix_a2dp_packet_size = false;
 }
 
 void cras_system_state_set_internal_ucm_suffix(const char *internal_ucm_suffix)
@@ -159,7 +209,7 @@
 {
 	/* Free any resources used.  This prevents unit tests from leaking. */
 
-	cras_device_blacklist_destroy(state.device_blacklist);
+	cras_device_blocklist_destroy(state.device_blocklist);
 
 	cras_tm_deinit(state.tm);
 
@@ -170,6 +220,7 @@
 			close(state.shm_fd_ro);
 	}
 
+	deinit_ignore_suffix_cards();
 	pthread_mutex_destroy(&state.update_lock);
 }
 
@@ -187,21 +238,6 @@
 	return state.exp_state->volume;
 }
 
-void cras_system_set_capture_gain(long gain)
-{
-	/* Adjust targeted gain to be in supported range. */
-	state.exp_state->capture_gain_target = gain;
-	gain = MAX(gain, state.exp_state->min_capture_gain);
-	gain = MIN(gain, state.exp_state->max_capture_gain);
-	state.exp_state->capture_gain = gain;
-	cras_observer_notify_capture_gain(state.exp_state->capture_gain);
-}
-
-long cras_system_get_capture_gain()
-{
-	return state.exp_state->capture_gain;
-}
-
 void cras_system_notify_mute(void)
 {
 	cras_observer_notify_output_mute(state.exp_state->mute,
@@ -310,6 +346,7 @@
 {
 	state.exp_state->suspended = suspended;
 	cras_observer_notify_suspend_changed(suspended);
+	cras_alert_process_all_pending_alerts();
 }
 
 void cras_system_set_volume_limits(long min, long max)
@@ -328,24 +365,6 @@
 	return state.exp_state->max_volume_dBFS;
 }
 
-void cras_system_set_capture_gain_limits(long min, long max)
-{
-	state.exp_state->min_capture_gain = MAX(min, DEFAULT_MIN_CAPTURE_GAIN);
-	state.exp_state->max_capture_gain = max;
-	/* Re-apply target gain subjected to the new supported range. */
-	cras_system_set_capture_gain(state.exp_state->capture_gain_target);
-}
-
-long cras_system_get_min_capture_gain()
-{
-	return state.exp_state->min_capture_gain;
-}
-
-long cras_system_get_max_capture_gain()
-{
-	return state.exp_state->max_capture_gain;
-}
-
 int cras_system_get_default_output_buffer_size()
 {
 	return state.exp_state->default_output_buffer_size;
@@ -371,6 +390,60 @@
 	return !!state.exp_state->bt_wbs_enabled;
 }
 
+bool cras_system_get_deprioritize_bt_wbs_mic()
+{
+	return !!state.exp_state->deprioritize_bt_wbs_mic;
+}
+
+void cras_system_set_bt_fix_a2dp_packet_size_enabled(bool enabled)
+{
+	state.bt_fix_a2dp_packet_size = enabled;
+}
+
+bool cras_system_get_bt_fix_a2dp_packet_size_enabled()
+{
+	return state.bt_fix_a2dp_packet_size;
+}
+
+void cras_system_set_noise_cancellation_enabled(bool enabled)
+{
+	/* When the flag is toggled, propagate to all iodevs immediately. */
+	if (cras_system_get_noise_cancellation_enabled() != enabled) {
+		state.exp_state->noise_cancellation_enabled = enabled;
+		cras_iodev_list_reset_for_noise_cancellation();
+	}
+}
+
+bool cras_system_get_noise_cancellation_enabled()
+{
+	return !!state.exp_state->noise_cancellation_enabled;
+}
+
+bool cras_system_check_ignore_ucm_suffix(const char *card_name)
+{
+	/* Check the general case: ALSA Loopback card "Loopback". */
+	if (!strcmp("Loopback", card_name))
+		return true;
+
+	/* Check board-specific ignore ucm suffix cards. */
+	struct name_list *card;
+	DL_FOREACH (state.ignore_suffix_cards, card) {
+		if (!strcmp(card->name, card_name))
+			return true;
+	}
+	return false;
+}
+
+bool cras_system_get_hotword_pause_at_suspend()
+{
+	return !!state.exp_state->hotword_pause_at_suspend;
+}
+
+void cras_system_set_hotword_pause_at_suspend(bool pause)
+{
+	state.exp_state->hotword_pause_at_suspend = pause;
+}
+
 int cras_system_add_alsa_card(struct cras_alsa_card_info *alsa_card_info)
 {
 	struct card_list *card;
@@ -386,11 +459,10 @@
 		if (card_index == cras_alsa_card_get_index(card->card))
 			return -EEXIST;
 	}
-	alsa_card = cras_alsa_card_create(
-		alsa_card_info, state.device_config_dir, state.device_blacklist,
-		(alsa_card_info->card_type == ALSA_CARD_TYPE_INTERNAL) ?
-			state.internal_ucm_suffix :
-			NULL);
+	alsa_card =
+		cras_alsa_card_create(alsa_card_info, state.device_config_dir,
+				      state.device_blocklist,
+				      state.internal_ucm_suffix);
 	if (alsa_card == NULL)
 		return -ENOMEM;
 	card = calloc(1, sizeof(*card));
@@ -428,8 +500,8 @@
 }
 
 int cras_system_set_select_handler(
-	int (*add)(int fd, void (*callback)(void *data), void *callback_data,
-		   void *select_data),
+	int (*add)(int fd, void (*callback)(void *data, int events),
+		   void *callback_data, int events, void *select_data),
 	void (*rm)(int fd, void *select_data), void *select_data)
 {
 	if (state.fd_add != NULL || state.fd_rm != NULL)
@@ -440,12 +512,13 @@
 	return 0;
 }
 
-int cras_system_add_select_fd(int fd, void (*callback)(void *data),
-			      void *callback_data)
+int cras_system_add_select_fd(int fd, void (*callback)(void *data, int revents),
+			      void *callback_data, int events)
 {
 	if (state.fd_add == NULL)
 		return -EINVAL;
-	return state.fd_add(fd, callback, callback_data, state.select_data);
+	return state.fd_add(fd, callback, callback_data, events,
+			    state.select_data);
 }
 
 int cras_system_set_add_task_handler(int (*add_task)(void (*cb)(void *data),
@@ -475,7 +548,8 @@
 		state.fd_rm(fd, state.select_data);
 }
 
-void cras_system_state_stream_added(enum CRAS_STREAM_DIRECTION direction)
+void cras_system_state_stream_added(enum CRAS_STREAM_DIRECTION direction,
+				    enum CRAS_CLIENT_TYPE client_type)
 {
 	struct cras_server_state *s;
 
@@ -485,13 +559,19 @@
 
 	s->num_active_streams[direction]++;
 	s->num_streams_attached++;
+	if (direction == CRAS_STREAM_INPUT) {
+		s->num_input_streams_with_permission[client_type]++;
+		cras_observer_notify_input_streams_with_permission(
+			s->num_input_streams_with_permission);
+	}
 
 	cras_system_state_update_complete();
 	cras_observer_notify_num_active_streams(
 		direction, s->num_active_streams[direction]);
 }
 
-void cras_system_state_stream_removed(enum CRAS_STREAM_DIRECTION direction)
+void cras_system_state_stream_removed(enum CRAS_STREAM_DIRECTION direction,
+				      enum CRAS_CLIENT_TYPE client_type)
 {
 	struct cras_server_state *s;
 	unsigned i, sum;
@@ -509,6 +589,11 @@
 		cras_clock_gettime(CLOCK_MONOTONIC_RAW,
 				   &s->last_active_stream_time);
 	s->num_active_streams[direction]--;
+	if (direction == CRAS_STREAM_INPUT) {
+		s->num_input_streams_with_permission[client_type]--;
+		cras_observer_notify_input_streams_with_permission(
+			s->num_input_streams_with_permission);
+	}
 
 	cras_system_state_update_complete();
 	cras_observer_notify_num_active_streams(
@@ -530,6 +615,15 @@
 	return state.exp_state->num_active_streams[direction];
 }
 
+void cras_system_state_get_input_streams_with_permission(
+	uint32_t num_input_streams[CRAS_NUM_CLIENT_TYPE])
+{
+	unsigned type;
+	for (type = 0; type < CRAS_NUM_CLIENT_TYPE; ++type)
+		num_input_streams[type] =
+			state.exp_state->num_input_streams_with_permission[type];
+}
+
 void cras_system_state_get_last_stream_active_time(struct cras_timespec *ts)
 {
 	*ts = state.exp_state->last_active_stream_time;
diff --git a/cras/src/server/cras_system_state.h b/cras/src/server/cras_system_state.h
index 270e54c..bd09395 100644
--- a/cras/src/server/cras_system_state.h
+++ b/cras/src/server/cras_system_state.h
@@ -20,7 +20,12 @@
 
 #define CRAS_MAX_SYSTEM_VOLUME 100
 #define DEFAULT_CAPTURE_GAIN 2000 /* 20dB of gain. */
-/* Default to 1--dB of range for palyback and capture. */
+/* Default to -6 dBFS as 90% of CrOS boards use microphone with -26dBFS
+ * sensitivity under 94dB SPL @ 1kHz and we generally added 20dB gain to it.
+ * This is a temporary value that should be refined when the standard process
+ * measuring intrinsic sensitivity is built. */
+#define DEFAULT_CAPTURE_VOLUME_DBFS -600
+/* Default to 1--dB of range for playback and capture. */
 #define DEFAULT_MIN_VOLUME_DBFS -10000
 #define DEFAULT_MAX_VOLUME_DBFS 0
 #define DEFAULT_MIN_CAPTURE_GAIN -5000
@@ -52,9 +57,8 @@
 /* Gets the current system volume. */
 size_t cras_system_get_volume();
 
-/* Sets the system capture volume.  Will be applied by the active device. */
-void cras_system_set_capture_gain(long gain);
-/* Gets the current system capture volume. */
+/* Gets the current system capture volume. As we remove the support of setting
+ * system capture gain, it should always be DEFAULT_CAPTURE_GAIN now. */
 long cras_system_get_capture_gain();
 
 /* Sets if the system is muted by the user. */
@@ -103,19 +107,6 @@
 /* Returns the dB value when volume = CRAS_MAX_SYSTEM_VOLUME, in dB * 100. */
 long cras_system_get_max_volume();
 
-/* Sets the limits in dB * 100 of the MAX and MIN capture gain.  This will allow
- * clients to query what range of control is available.  Both arguments are
- * specified as dB * 100.
- * Args:
- *     min - minimum allowed capture gain.
- *     max - maximum allowed capture gaax.
- */
-void cras_system_set_capture_gain_limits(long min, long max);
-/* Returns the max value allowed for capture gain in dB * 100. */
-long cras_system_get_min_capture_gain();
-/* Returns the min value allowed for capture gain in dB * 100. */
-long cras_system_get_max_capture_gain();
-
 /* Returns the default value of output buffer size in frames. */
 int cras_system_get_default_output_buffer_size();
 
@@ -131,6 +122,33 @@
 /* Gets the elable flag of bluetooth wideband speech feature. */
 bool cras_system_get_bt_wbs_enabled();
 
+/*
+ * Returns if Bluetooth WBS mic should be deprioritized for selecting
+ * as default audio input option.
+ */
+bool cras_system_get_deprioritize_bt_wbs_mic();
+
+/* Sets the flag to enable or disable Bluetooth fixed A2DP packet size. */
+void cras_system_set_bt_fix_a2dp_packet_size_enabled(bool enabled);
+
+/* Gets the flag of Bluetooth fixed A2DP packet size. */
+bool cras_system_get_bt_fix_a2dp_packet_size_enabled();
+
+/* Sets the flag to enable or disable Noise Cancellation. */
+void cras_system_set_noise_cancellation_enabled(bool enabled);
+
+/* Gets the flag of Noise Cancellation. */
+bool cras_system_get_noise_cancellation_enabled();
+
+/* Checks if the card ignores the ucm suffix. */
+bool cras_system_check_ignore_ucm_suffix(const char *card_name);
+
+/* Returns true if hotword detection is paused at system suspend. */
+bool cras_system_get_hotword_pause_at_suspend();
+
+/* Sets whether to pause hotword detection at system suspend. */
+void cras_system_set_hotword_pause_at_suspend(bool pause);
+
 /* Adds a card at the given index to the system.  When a new card is found
  * (through a udev event notification) this will add the card to the system,
  * causing its devices to become available for playback/capture.
@@ -174,8 +192,8 @@
  *    0 on success, or -EBUSY if there is already a registered handler.
  */
 int cras_system_set_select_handler(
-	int (*add)(int fd, void (*callback)(void *data), void *callback_data,
-		   void *select_data),
+	int (*add)(int fd, void (*callback)(void *data, int revents),
+		   void *callback_data, int events, void *select_data),
 	void (*rm)(int fd, void *select_data), void *select_data);
 
 /* Adds the fd and callback pair.  When select indicates that fd is readable,
@@ -184,11 +202,12 @@
  *    fd - The file descriptor to pass to select(2).
  *    callback - The callback to call when fd is ready.
  *    callback_data - Value passed back to the callback.
+ *    events - The events to poll for.
  * Returns:
  *    0 on success or a negative error code on failure.
  */
-int cras_system_add_select_fd(int fd, void (*callback)(void *data),
-			      void *callback_data);
+int cras_system_add_select_fd(int fd, void (*callback)(void *data, int revents),
+			      void *callback_data, int events);
 
 /* Removes the fd from the list of fds that are passed to select.
  * Args:
@@ -225,16 +244,20 @@
  * subsystem is idle.
  * Args:
  *   direction - Directions of audio streams.
+ *   client_type - CRAS_CLIENT_TYPE of the audio stream.
  */
-void cras_system_state_stream_added(enum CRAS_STREAM_DIRECTION direction);
+void cras_system_state_stream_added(enum CRAS_STREAM_DIRECTION direction,
+				    enum CRAS_CLIENT_TYPE client_type);
 
 /* Signals that an audio input or output stream has been removed from the
  * system.  This allows the count of active streams can be used to notice when
  * the audio subsystem is idle.
  * Args:
  *   direction - Directions of audio stream.
+ *   client_type - CRAS_CLIENT_TYPE of the audio stream.
  */
-void cras_system_state_stream_removed(enum CRAS_STREAM_DIRECTION direction);
+void cras_system_state_stream_removed(enum CRAS_STREAM_DIRECTION direction,
+				      enum CRAS_CLIENT_TYPE client_type);
 
 /* Returns the number of active playback and capture streams. */
 unsigned cras_system_state_get_active_streams();
@@ -246,6 +269,16 @@
 unsigned cras_system_state_get_active_streams_by_direction(
 	enum CRAS_STREAM_DIRECTION direction);
 
+/* Returns the number of input streams with permission per CRAS_CLIENT_TYPE.
+ *
+ * Returns:
+ *   num_input_streams - An array with length = CRAS_NUM_CLIENT_TYPE and each
+ *                        element is the number of the current input
+ *                        streams with permission in each client type.
+ */
+void cras_system_state_get_input_streams_with_permission(
+	uint32_t num_input_streams[CRAS_NUM_CLIENT_TYPE]);
+
 /* Fills ts with the time the last stream was removed from the system, the time
  * the stream count went to zero.
  */
diff --git a/cras/src/server/cras_telephony.c b/cras/src/server/cras_telephony.c
index ac95f7e..805fb13 100644
--- a/cras/src/server/cras_telephony.c
+++ b/cras/src/server/cras_telephony.c
@@ -41,6 +41,12 @@
 	"    <method name=\"SetCallheld\">\n"                                  \
 	"      <arg name=\"value\" type=\"i\" direction=\"in\"/>\n"            \
 	"    </method>\n"                                                      \
+	"    <method name=\"SetCallsetup\">\n"                                 \
+	"      <arg name=\"value\" type=\"i\" direction=\"in\"/>\n"            \
+	"    </method>\n"                                                      \
+	"    <method name=\"SetCall\">\n"                                      \
+	"      <arg name=\"value\" type=\"i\" direction=\"in\"/>\n"            \
+	"    </method>\n"                                                      \
 	"  </interface>\n"                                                     \
 	"  <interface name=\"" DBUS_INTERFACE_INTROSPECTABLE "\">\n"           \
 	"    <method name=\"Introspect\">\n"                                   \
@@ -220,6 +226,46 @@
 	return DBUS_HANDLER_RESULT_HANDLED;
 }
 
+static DBusHandlerResult handle_set_callsetup(DBusConnection *conn,
+					      DBusMessage *message, void *arg)
+{
+	struct hfp_slc_handle *handle;
+	DBusHandlerResult rc;
+	int value;
+
+	rc = get_single_arg(message, DBUS_TYPE_INT32, &value);
+	if (rc != DBUS_HANDLER_RESULT_HANDLED)
+		return rc;
+
+	telephony_handle.callsetup = value;
+	handle = cras_hfp_ag_get_active_handle();
+	if (handle)
+		hfp_event_update_callsetup(handle);
+
+	send_empty_reply(conn, message);
+	return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static DBusHandlerResult handle_set_call(DBusConnection *conn,
+					 DBusMessage *message, void *arg)
+{
+	struct hfp_slc_handle *handle;
+	DBusHandlerResult rc;
+	int value;
+
+	rc = get_single_arg(message, DBUS_TYPE_INT32, &value);
+	if (rc != DBUS_HANDLER_RESULT_HANDLED)
+		return rc;
+
+	telephony_handle.call = value;
+	handle = cras_hfp_ag_get_active_handle();
+	if (handle)
+		hfp_event_update_call(handle);
+
+	send_empty_reply(conn, message);
+	return DBUS_HANDLER_RESULT_HANDLED;
+}
+
 /* Handle incoming messages. */
 static DBusHandlerResult
 handle_telephony_message(DBusConnection *conn, DBusMessage *message, void *arg)
@@ -274,6 +320,12 @@
 	} else if (dbus_message_is_method_call(
 			   message, CRAS_TELEPHONY_INTERFACE, "SetCallheld")) {
 		return handle_set_callheld(conn, message, arg);
+	} else if (dbus_message_is_method_call(
+			   message, CRAS_TELEPHONY_INTERFACE, "SetCallsetup")) {
+		return handle_set_callsetup(conn, message, arg);
+	} else if (dbus_message_is_method_call(
+			   message, CRAS_TELEPHONY_INTERFACE, "SetCall")) {
+		return handle_set_call(conn, message, arg);
 	}
 
 	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
diff --git a/cras/src/server/cras_udev.c b/cras/src/server/cras_udev.c
index dc4c2dd..16f0e3b 100644
--- a/cras/src/server/cras_udev.c
+++ b/cras/src/server/cras_udev.c
@@ -271,7 +271,7 @@
 
 	card_info->usb_desc_checksum = calculate_desc_checksum(parent_dev);
 
-	syslog(LOG_ERR,
+	syslog(LOG_INFO,
 	       "USB card: vendor:%04x, product:%04x, serial num:%s, "
 	       "checksum:%08x",
 	       card_info->usb_vendor_id, card_info->usb_product_id,
@@ -367,7 +367,7 @@
 	udev_enumerate_unref(enumerate);
 }
 
-static void udev_sound_subsystem_callback(void *arg)
+static void udev_sound_subsystem_callback(void *arg, int revents)
 {
 	struct udev_callback_data *data = (struct udev_callback_data *)arg;
 	struct udev_device *dev;
@@ -408,8 +408,9 @@
 	udev_monitor_enable_receiving(udev_data.mon);
 	udev_data.fd = udev_monitor_get_fd(udev_data.mon);
 
-	r = cras_system_add_select_fd(
-		udev_data.fd, udev_sound_subsystem_callback, &udev_data);
+	r = cras_system_add_select_fd(udev_data.fd,
+				      udev_sound_subsystem_callback, &udev_data,
+				      POLLIN);
 	assert(r == 0);
 	compile_regex(&pcm_regex, pcm_regex_string);
 	compile_regex(&card_regex, card_regex_string);
diff --git a/cras/src/server/cras_unified_rclient.c b/cras/src/server/cras_unified_rclient.c
new file mode 100644
index 0000000..cdb7b47
--- /dev/null
+++ b/cras/src/server/cras_unified_rclient.c
@@ -0,0 +1,37 @@
+/* Copyright 2020 The Chromium OS 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 <syslog.h>
+
+#include "cras_iodev_list.h"
+#include "cras_messages.h"
+#include "cras_observer.h"
+#include "cras_rclient.h"
+#include "cras_rclient_util.h"
+#include "cras_rstream.h"
+#include "cras_types.h"
+#include "cras_util.h"
+
+/* Declarations of cras_rclient operators for cras_unified_rclient. */
+static const struct cras_rclient_ops cras_unified_rclient_ops = {
+	.handle_message_from_client = rclient_handle_message_from_client,
+	.send_message_to_client = rclient_send_message_to_client,
+	.destroy = rclient_destroy,
+};
+
+/*
+ * Exported Functions.
+ */
+
+/* Creates a client structure and sends a message back informing the client that
+ * the connection has succeeded. */
+struct cras_rclient *cras_unified_rclient_create(int fd, size_t id)
+{
+	int supported_directions =
+		cras_stream_direction_mask(CRAS_STREAM_OUTPUT) |
+		cras_stream_direction_mask(CRAS_STREAM_INPUT);
+	return rclient_generic_create(fd, id, &cras_unified_rclient_ops,
+				      supported_directions);
+}
diff --git a/cras/src/server/cras_unified_rclient.h b/cras/src/server/cras_unified_rclient.h
new file mode 100644
index 0000000..19973f8
--- /dev/null
+++ b/cras/src/server/cras_unified_rclient.h
@@ -0,0 +1,21 @@
+/* Copyright 2020 The Chromium OS 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 CRAS_UNIFIED_RCLIENT_H_
+#define CRAS_UNIFIED_RCLIENT_H_
+
+struct cras_rclient;
+
+/* Creates a unified rclient structure. This client supports only playback
+ * and capture functions but not control features.
+ * Args:
+ *    fd - The file descriptor used for communication with the client.
+ *    id - Unique identifier for this client.
+ * Returns:
+ *    A pointer to the newly created rclient on success, NULL on failure.
+ */
+struct cras_rclient *cras_unified_rclient_create(int fd, size_t id);
+
+#endif /* CRAS_UNIFIED_RCLIENT_H_ */
diff --git a/cras/src/server/dev_io.c b/cras/src/server/dev_io.c
index 97c61f9..b311b22 100644
--- a/cras/src/server/dev_io.c
+++ b/cras/src/server/dev_io.c
@@ -10,6 +10,7 @@
 #include "audio_thread_log.h"
 #include "cras_audio_area.h"
 #include "cras_audio_thread_monitor.h"
+#include "cras_device_monitor.h"
 #include "cras_iodev.h"
 #include "cras_non_empty_audio_handler.h"
 #include "cras_rstream.h"
@@ -47,33 +48,78 @@
 /* The number of devices playing/capturing non-empty stream(s). */
 static int non_empty_device_count = 0;
 
-/* Gets the master device which the stream is attached to. */
-static inline struct cras_iodev *get_master_dev(const struct dev_stream *stream)
+/* The timestamp of last EIO error time. */
+static struct timespec last_io_err_time = { 0, 0 };
+
+/* The gap time to avoid repeated error close request to main thread. */
+static const int ERROR_CLOSE_GAP_TIME_SECS = 10;
+
+/* Gets the main device which the stream is attached to. */
+static inline struct cras_iodev *get_main_dev(const struct dev_stream *stream)
 {
-	return (struct cras_iodev *)stream->stream->master_dev.dev_ptr;
+	return (struct cras_iodev *)stream->stream->main_dev.dev_ptr;
 }
 
 /* Updates the estimated sample rate of open device to all attached
  * streams.
  */
-static void update_estimated_rate(struct open_dev *adev)
+static void update_estimated_rate(struct open_dev *adev,
+				  struct open_dev *odev_list,
+				  bool self_rate_need_update)
 {
-	struct cras_iodev *master_dev;
+	struct cras_iodev *main_dev;
 	struct cras_iodev *dev = adev->dev;
+	struct cras_iodev *tracked_dev = NULL;
 	struct dev_stream *dev_stream;
+	double dev_rate_ratio;
+	double main_dev_rate_ratio;
+
+	/*
+	 * If there is an output device on the same sound card running with the same
+	 * sampling rate, use the rate of that output device for this device.
+	 */
+	if (dev->direction == CRAS_STREAM_INPUT &&
+	    cras_iodev_is_on_internal_card(dev->active_node)) {
+		struct open_dev *odev;
+		DL_FOREACH (odev_list, odev) {
+			if (!cras_iodev_is_on_internal_card(
+				    odev->dev->active_node))
+				continue;
+			if (odev->dev->format->frame_rate !=
+			    dev->format->frame_rate)
+				continue;
+			tracked_dev = odev->dev;
+			break;
+		}
+	}
+
+	/*
+	 * Self-owned rate esimator does not need to udpate rate. There is no tracked
+	 * output device. So there is no need to update.
+	 */
+	if (!self_rate_need_update && !tracked_dev)
+		return;
 
 	DL_FOREACH (dev->streams, dev_stream) {
-		master_dev = get_master_dev(dev_stream);
-		if (master_dev == NULL) {
-			syslog(LOG_ERR, "Fail to find master open dev.");
+		main_dev = get_main_dev(dev_stream);
+		if (main_dev == NULL) {
+			syslog(LOG_ERR, "Fail to find main open dev.");
 			continue;
 		}
 
-		dev_stream_set_dev_rate(
-			dev_stream, dev->format->frame_rate,
-			cras_iodev_get_est_rate_ratio(dev),
-			cras_iodev_get_est_rate_ratio(master_dev),
-			adev->coarse_rate_adjust);
+		if (tracked_dev) {
+			dev_rate_ratio =
+				cras_iodev_get_est_rate_ratio(tracked_dev);
+			main_dev_rate_ratio = dev_rate_ratio;
+		} else {
+			dev_rate_ratio = cras_iodev_get_est_rate_ratio(dev);
+			main_dev_rate_ratio =
+				cras_iodev_get_est_rate_ratio(main_dev);
+		}
+
+		dev_stream_set_dev_rate(dev_stream, dev->format->frame_rate,
+					dev_rate_ratio, main_dev_rate_ratio,
+					adev->coarse_rate_adjust);
 	}
 }
 
@@ -92,7 +138,7 @@
 	return count;
 }
 
-static void check_non_empty_state_transition(struct open_dev *adevs)
+int dev_io_check_non_empty_state_transition(struct open_dev *adevs)
 {
 	int new_non_empty_dev_count = count_non_empty_dev(adevs);
 
@@ -103,6 +149,7 @@
 									    0);
 
 	non_empty_device_count = new_non_empty_dev_count;
+	return non_empty_device_count > 0;
 }
 
 /* Checks whether it is time to fetch. */
@@ -125,6 +172,19 @@
 	return 0;
 }
 
+/* The log only accepts uint32 arguments, so the float power
+ * must be written as bits and assumed to have a float when
+ * parsing the log.
+ */
+static uint32_t get_ewma_power_as_int(struct ewma_power *ewma)
+{
+	uint32_t pow_as_int = 0;
+
+	if (sizeof(uint32_t) == sizeof(float))
+		memcpy(&pow_as_int, &ewma->power, sizeof(uint32_t));
+	return pow_as_int;
+}
+
 /* Asks any stream with room for more data. Sets the time stamp for all streams.
  * Args:
  *    adev - The output device streams are attached to.
@@ -186,13 +246,15 @@
 			      shm->header->write_offset[0],
 			      shm->header->write_offset[1]);
 			dev_stream_update_next_wake_time(dev_stream);
+			cras_server_metrics_missed_cb_event(dev_stream->stream);
 			continue;
 		}
 
 		dev_stream_set_delay(dev_stream, delay);
 
 		ATLOG(atlog, AUDIO_THREAD_FETCH_STREAM, rstream->stream_id,
-		      cras_rstream_get_cb_threshold(rstream), delay);
+		      cras_rstream_get_cb_threshold(rstream),
+		      get_ewma_power_as_int(&rstream->ewma));
 
 		rc = dev_stream_request_playback_samples(dev_stream, &now);
 		if (rc < 0) {
@@ -342,7 +404,9 @@
 	if (!iodev->streams)
 		return false;
 	if (!iodev->active_node ||
-	    iodev->active_node->type == CRAS_NODE_TYPE_HOTWORD)
+	    iodev->active_node->type == CRAS_NODE_TYPE_HOTWORD ||
+	    iodev->active_node->type == CRAS_NODE_TYPE_POST_MIX_PRE_DSP ||
+	    iodev->active_node->type == CRAS_NODE_TYPE_POST_DSP)
 		return false;
 	return true;
 }
@@ -382,11 +446,14 @@
 		clock_gettime(CLOCK_MONOTONIC_RAW, &level_tstamp);
 
 	/*
-	 * If any input device has more than largest_cb_level * 1.5 frames, need to
-	 * drop frames from all devices.
+	 * Drop frames from all devices if any device meets these requirements:
+	 * 1. The hw_level is larger than largest_cb_level * 1.5 or larger than
+	 *    buffer_size * 0.5.
+	 * 2. The time of those frames is larger than DROP_FRAMES_THRESHOLD_MS.
 	 */
 	if (input_devices_can_drop_samples(adev->dev) &&
-	    rc >= adev->dev->largest_cb_level * 1.5 &&
+	    (rc >= adev->dev->largest_cb_level * 1.5 ||
+	     rc >= adev->dev->buffer_size * 0.5) &&
 	    cras_frames_to_ms(rc, adev->dev->format->frame_rate) >=
 		    DROP_FRAMES_THRESHOLD_MS)
 		*need_to_drop = true;
@@ -443,7 +510,7 @@
  *    adev - The device to capture samples from.
  * Returns 0 on success.
  */
-static int capture_to_streams(struct open_dev *adev)
+static int capture_to_streams(struct open_dev *adev, struct open_dev *odev_list)
 {
 	struct cras_iodev *idev = adev->dev;
 	snd_pcm_uframes_t remainder, hw_level, cap_limit;
@@ -465,14 +532,29 @@
 	ATLOG(atlog, AUDIO_THREAD_READ_AUDIO_TSTAMP, idev->info.idx,
 	      hw_tstamp.tv_sec, hw_tstamp.tv_nsec);
 	if (timespec_is_nonzero(&hw_tstamp)) {
+		bool self_rate_need_update;
+
 		if (hw_level < idev->min_cb_level / 2)
 			adev->coarse_rate_adjust = 1;
 		else if (hw_level > idev->max_cb_level * 2)
 			adev->coarse_rate_adjust = -1;
 		else
 			adev->coarse_rate_adjust = 0;
-		if (cras_iodev_update_rate(idev, hw_level, &hw_tstamp))
-			update_estimated_rate(adev);
+
+		/*
+		 * This values means whether the rate estimator in the device
+		 * wants to update estimated rate.
+		 */
+		self_rate_need_update =
+			!!cras_iodev_update_rate(idev, hw_level, &hw_tstamp);
+
+		/*
+		 * Always calls update_estimated_rate so that new output rate
+		 * has a chance to propagate to input. In update_estimated_rate,
+		 * it will decide whether the new rate is from self rate estimator
+		 * or from the tracked output device.
+		 */
+		update_estimated_rate(adev, odev_list, self_rate_need_update);
 	}
 
 	cap_limit = get_stream_limit(adev, hw_level, &cap_limit_stream);
@@ -509,24 +591,19 @@
 						  stream->stream,
 						  idev->buf_state, &area,
 						  &area_offset);
+
 			/*
-			 * The software gain scaler consist of two parts:
-			 * (1) The device gain scaler used when lack of hardware
-			 * gain control. Configured by the DefaultNodeGain label
-			 * in alsa UCM config.
-			 * (2) The gain scaler in cras_rstream set by app, for
-			 * example the AGC module in Chrome.
-			 *
-			 * APM has more advanced gain control mechanism, we shall
-			 * give APM total control of the captured samples without
-			 * additional gain scaler at all.
+			 * The UI gain scaler should always take effect.
+			 * input_data will decide if stream and iodev internal
+			 * software gains should be used or not, based on use
+			 * case.
 			 */
 			software_gain_scaler =
-				stream->stream->apm_list ?
-					1.0f :
-					idev->software_gain_scaler *
-						cras_rstream_get_volume_scaler(
-							stream->stream);
+				cras_iodev_get_ui_gain_scaler(idev) *
+				input_data_get_software_gain_scaler(
+					idev->input_data,
+					idev->software_gain_scaler,
+					stream->stream);
 
 			this_read =
 				dev_stream_capture(stream, area, area_offset,
@@ -548,7 +625,8 @@
 			break;
 	}
 
-	ATLOG(atlog, AUDIO_THREAD_READ_AUDIO_DONE, remainder, 0, 0);
+	ATLOG(atlog, AUDIO_THREAD_READ_AUDIO_DONE, remainder,
+	      get_ewma_power_as_int(&idev->ewma), 0);
 
 	return 0;
 }
@@ -562,12 +640,13 @@
  *    write_limit - The maximum number of frames to write to dst.
  *
  * Returns:
- *    The number of frames rendered on success, a negative error code otherwise.
+ *    The number of frames rendered on success.
  *    This number of frames is the minimum of the amount of frames each stream
  *    could provide which is the maximum that can currently be rendered.
  */
-static int write_streams(struct open_dev **odevs, struct open_dev *adev,
-			 uint8_t *dst, size_t write_limit)
+static unsigned int write_streams(struct open_dev **odevs,
+				  struct open_dev *adev, uint8_t *dst,
+				  size_t write_limit)
 {
 	struct cras_iodev *odev = adev->dev;
 	struct dev_stream *curr;
@@ -729,9 +808,10 @@
 			adev->coarse_rate_adjust = 0;
 
 		if (cras_iodev_update_rate(odev, hw_level, &hw_tstamp))
-			update_estimated_rate(adev);
+			update_estimated_rate(adev, NULL, true);
 	}
-	ATLOG(atlog, AUDIO_THREAD_FILL_AUDIO, adev->dev->info.idx, hw_level, 0);
+	ATLOG(atlog, AUDIO_THREAD_FILL_AUDIO, adev->dev->info.idx, hw_level,
+	      odev->min_cb_level);
 
 	/* Don't request more than hardware can hold. Note that min_buffer_level
 	 * has been subtracted from the actual hw_level so we need to take it
@@ -750,9 +830,6 @@
 		/* TODO(dgreid) - This assumes interleaved audio. */
 		dst = area->channels[0].buf;
 		written = write_streams(odevs, adev, dst, frames);
-		if (written < 0) /* pcm has been closed */
-			return (int)written;
-
 		if (written < (snd_pcm_sframes_t)frames)
 			/* Got all the samples from client that we can, but it
 			 * won't fill the request. */
@@ -795,7 +872,7 @@
 	}
 
 	ATLOG(atlog, AUDIO_THREAD_FILL_AUDIO_DONE, hw_level, total_written,
-	      odev->min_cb_level);
+	      get_ewma_power_as_int(&odev->ewma));
 
 	return total_written;
 }
@@ -851,7 +928,7 @@
 static void dev_io_drop_samples(struct open_dev *idev_list)
 {
 	struct open_dev *adev;
-	struct timespec drop_time;
+	struct timespec drop_time = {};
 	int rc;
 
 	get_input_devices_drop_time(idev_list, &drop_time);
@@ -916,26 +993,46 @@
 static void handle_dev_err(int err_rc, struct open_dev **odevs,
 			   struct open_dev *adev)
 {
+	struct timespec diff, now;
 	if (err_rc == -EPIPE) {
 		/* Handle severe underrun. */
 		ATLOG(atlog, AUDIO_THREAD_SEVERE_UNDERRUN, adev->dev->info.idx,
 		      0, 0);
 		cras_iodev_reset_request(adev->dev);
+		cras_audio_thread_event_severe_underrun();
+	} else if (err_rc == -EIO) {
+		syslog(LOG_WARNING, "I/O err, reseting %s dev %s",
+		       adev->dev->direction == CRAS_STREAM_OUTPUT ? "output" :
+								    "input",
+		       adev->dev->info.name);
+		clock_gettime(CLOCK_REALTIME, &now);
+		subtract_timespecs(&now, &last_io_err_time, &diff);
+		if ((last_io_err_time.tv_sec == 0 &&
+		     last_io_err_time.tv_nsec == 0) ||
+		    diff.tv_sec > ERROR_CLOSE_GAP_TIME_SECS)
+			cras_iodev_reset_request(adev->dev);
+		else
+			cras_device_monitor_error_close(adev->dev->info.idx);
+
+		last_io_err_time = now;
+	} else {
+		syslog(LOG_ERR, "Dev %s err %d", adev->dev->info.name, err_rc);
 	}
 	/* Device error, remove it. */
 	dev_io_rm_open_dev(odevs, adev);
 }
 
-int dev_io_capture(struct open_dev **list)
+int dev_io_capture(struct open_dev **list, struct open_dev **olist)
 {
 	struct open_dev *idev_list = *list;
+	struct open_dev *odev_list = *olist;
 	struct open_dev *adev;
 	int rc;
 
 	DL_FOREACH (idev_list, adev) {
 		if (!cras_iodev_is_open(adev->dev))
 			continue;
-		rc = capture_to_streams(adev);
+		rc = capture_to_streams(adev, odev_list);
 		if (rc < 0)
 			handle_dev_err(rc, list, adev);
 	}
@@ -1028,10 +1125,8 @@
 			 * we should handle it.
 			 */
 			if (hw_level <= total_written) {
-				ATLOG(atlog, AUDIO_THREAD_UNDERRUN,
-				      adev->dev->info.idx, hw_level,
-				      total_written);
-				rc = cras_iodev_output_underrun(adev->dev);
+				rc = cras_iodev_output_underrun(
+					adev->dev, hw_level, total_written);
 				if (rc < 0) {
 					handle_dev_err(rc, odevs, adev);
 				} else {
@@ -1088,11 +1183,9 @@
 	update_longest_wake(*idevs, &now);
 
 	dev_io_playback_fetch(*odevs);
-	dev_io_capture(idevs);
+	dev_io_capture(idevs, odevs);
 	dev_io_send_captured_samples(*idevs);
 	dev_io_playback_write(odevs, output_converter);
-
-	check_non_empty_state_transition(*odevs);
 }
 
 static int input_adev_ignore_wake(const struct open_dev *adev)
@@ -1159,8 +1252,7 @@
 	return ret;
 }
 
-int dev_io_next_output_wake(struct open_dev **odevs, struct timespec *min_ts,
-			    const struct timespec *now)
+int dev_io_next_output_wake(struct open_dev **odevs, struct timespec *min_ts)
 {
 	struct open_dev *adev;
 	int ret = 0;
@@ -1219,7 +1311,7 @@
 	cras_server_metrics_highest_hw_level(dev_to_rm->dev->highest_hw_level,
 					     dev_to_rm->dev->direction);
 
-	check_non_empty_state_transition(*odev_list);
+	dev_io_check_non_empty_state_transition(*odev_list);
 
 	ATLOG(atlog, AUDIO_THREAD_DEV_REMOVED, dev_to_rm->dev->info.idx, 0, 0);
 
@@ -1245,14 +1337,61 @@
 		dev_stream_destroy(out);
 }
 
-int dev_io_append_stream(struct open_dev **dev_list,
+/*
+ * Finds a matched input stream from open device list.
+ * The definition of the matched streams: Two streams having
+ * the same sampling rate and the same cb_threshold.
+ * This means their sleep time intervals should be very close
+ * if we neglect device estimated rate.
+ */
+static struct dev_stream *
+find_matched_input_stream(const struct cras_rstream *out_stream,
+			  struct open_dev *odev_list)
+{
+	struct open_dev *odev;
+	struct dev_stream *dev_stream;
+	size_t out_rate = out_stream->format.frame_rate;
+	size_t out_cb_threshold = cras_rstream_get_cb_threshold(out_stream);
+
+	DL_FOREACH (odev_list, odev) {
+		DL_FOREACH (odev->dev->streams, dev_stream) {
+			if (dev_stream->stream->format.frame_rate != out_rate)
+				continue;
+			if (cras_rstream_get_cb_threshold(dev_stream->stream) !=
+			    out_cb_threshold)
+				continue;
+			return dev_stream;
+		}
+	}
+	return NULL;
+}
+
+static bool
+find_matched_input_stream_next_cb_ts(const struct cras_rstream *stream,
+				     struct open_dev *odev_list,
+				     const struct timespec **next_cb_ts,
+				     const struct timespec **sleep_interval_ts)
+{
+	struct dev_stream *dev_stream =
+		find_matched_input_stream(stream, odev_list);
+	if (dev_stream) {
+		*next_cb_ts = dev_stream_next_cb_ts(dev_stream);
+		*sleep_interval_ts = dev_stream_sleep_interval_ts(dev_stream);
+		return *next_cb_ts != NULL;
+	}
+	return false;
+}
+
+int dev_io_append_stream(struct open_dev **odevs, struct open_dev **idevs,
 			 struct cras_rstream *stream,
 			 struct cras_iodev **iodevs, unsigned int num_iodevs)
 {
+	struct open_dev **dev_list;
 	struct open_dev *open_dev;
 	struct cras_iodev *dev;
 	struct dev_stream *out;
 	struct timespec init_cb_ts;
+	const struct timespec *init_sleep_interval_ts = NULL;
 	struct timespec extra_sleep;
 	const struct timespec *stream_ts;
 	unsigned int i;
@@ -1260,6 +1399,11 @@
 	int level;
 	int rc = 0;
 
+	if (stream->direction == CRAS_STREAM_OUTPUT)
+		dev_list = odevs;
+	else
+		dev_list = idevs;
+
 	for (i = 0; i < num_iodevs; i++) {
 		DL_SEARCH_SCALAR(*dev_list, open_dev, dev, iodevs[i]);
 		if (!open_dev)
@@ -1304,35 +1448,55 @@
 		 * may cause device buffer level stack up.
 		 */
 		if (stream->direction == CRAS_STREAM_OUTPUT) {
-			DL_FOREACH (dev->streams, out) {
-				stream_ts = dev_stream_next_cb_ts(out);
-				if (stream_ts &&
-				    (!cb_ts_set ||
-				     timespec_after(&init_cb_ts, stream_ts))) {
-					init_cb_ts = *stream_ts;
-					cb_ts_set = true;
+			/*
+			 * If there is a matched input stream, find its next cb time.
+			 * Use that as the initial cb time for this output stream.
+			 */
+			const struct timespec *in_stream_ts;
+			const struct timespec *in_stream_sleep_interval_ts;
+			bool found_matched_input;
+			found_matched_input =
+				find_matched_input_stream_next_cb_ts(
+					stream, *idevs, &in_stream_ts,
+					&in_stream_sleep_interval_ts);
+			if (found_matched_input) {
+				init_cb_ts = *in_stream_ts;
+				init_sleep_interval_ts =
+					in_stream_sleep_interval_ts;
+			} else {
+				DL_FOREACH (dev->streams, out) {
+					stream_ts = dev_stream_next_cb_ts(out);
+					if (stream_ts &&
+					    (!cb_ts_set ||
+					     timespec_after(&init_cb_ts,
+							    stream_ts))) {
+						init_cb_ts = *stream_ts;
+						cb_ts_set = true;
+					}
 				}
-			}
-			if (!cb_ts_set) {
-				level = cras_iodev_get_valid_frames(
-					dev, &init_cb_ts);
-				if (level < 0) {
-					syslog(LOG_ERR,
-					       "Failed to set output init_cb_ts, rc = %d",
-					       level);
-					rc = -EINVAL;
-					break;
+				if (!cb_ts_set) {
+					level = cras_iodev_get_valid_frames(
+						dev, &init_cb_ts);
+					if (level < 0) {
+						syslog(LOG_ERR,
+						       "Failed to set output init_cb_ts, rc = %d",
+						       level);
+						rc = -EINVAL;
+						break;
+					}
+					level -= cras_frames_at_rate(
+						stream->format.frame_rate,
+						cras_rstream_get_cb_threshold(
+							stream),
+						dev->format->frame_rate);
+					if (level < 0)
+						level = 0;
+					cras_frames_to_time(
+						level, dev->format->frame_rate,
+						&extra_sleep);
+					add_timespecs(&init_cb_ts,
+						      &extra_sleep);
 				}
-				level -= cras_frames_at_rate(
-					stream->format.frame_rate,
-					cras_rstream_get_cb_threshold(stream),
-					dev->format->frame_rate);
-				if (level < 0)
-					level = 0;
-				cras_frames_to_time(level,
-						    dev->format->frame_rate,
-						    &extra_sleep);
-				add_timespecs(&init_cb_ts, &extra_sleep);
 			}
 		} else {
 			/*
@@ -1351,7 +1515,7 @@
 		}
 
 		out = dev_stream_create(stream, dev->info.idx, dev->format, dev,
-					&init_cb_ts);
+					&init_cb_ts, init_sleep_interval_ts);
 		if (!out) {
 			rc = -EINVAL;
 			break;
@@ -1404,20 +1568,6 @@
 			 struct cras_rstream *stream, struct cras_iodev *dev)
 {
 	struct open_dev *open_dev;
-	struct timespec delay;
-	unsigned fetch_delay_msec;
-
-	/* Metrics log the longest fetch delay of this stream. */
-	if (timespec_after(&stream->longest_fetch_interval,
-			   &stream->sleep_interval_ts)) {
-		subtract_timespecs(&stream->longest_fetch_interval,
-				   &stream->sleep_interval_ts, &delay);
-		fetch_delay_msec =
-			delay.tv_sec * 1000 + delay.tv_nsec / 1000000;
-		if (fetch_delay_msec)
-			cras_server_metrics_longest_fetch_delay(
-				fetch_delay_msec);
-	}
 
 	ATLOG(atlog, AUDIO_THREAD_STREAM_REMOVED, stream->stream_id, 0, 0);
 
diff --git a/cras/src/server/dev_io.h b/cras/src/server/dev_io.h
index 3184e4c..ca71a80 100644
--- a/cras/src/server/dev_io.h
+++ b/cras/src/server/dev_io.h
@@ -58,8 +58,9 @@
  * Captures samples from each device in the list.
  *    list - Pointer to the list of input devices.  Devices that fail to read
  *           will be removed from the list.
+ *    olist - Pointer to the list of output devices.
  */
-int dev_io_capture(struct open_dev **list);
+int dev_io_capture(struct open_dev **list, struct open_dev **olist);
 
 /*
  * Send samples that have been captured to their streams.
@@ -71,6 +72,12 @@
 		struct cras_fmt_conv *output_converter);
 
 /*
+ * Checks the non-empty device state in active output lists and return
+ * if there's at least one non-empty device.
+ */
+int dev_io_check_non_empty_state_transition(struct open_dev *adevs);
+
+/*
  * Fills min_ts with the next time the system should wake to service input.
  * Returns the number of devices waiting.
  */
@@ -80,8 +87,7 @@
  * Fills min_ts with the next time the system should wake to service output.
  * Returns the number of devices waiting.
  */
-int dev_io_next_output_wake(struct open_dev **odevs, struct timespec *min_ts,
-			    const struct timespec *now);
+int dev_io_next_output_wake(struct open_dev **odevs, struct timespec *min_ts);
 
 /*
  * Removes a device from a list of devices.
@@ -96,7 +102,7 @@
 				      unsigned int dev_idx);
 
 /* Append a new stream to a specified set of iodevs. */
-int dev_io_append_stream(struct open_dev **dev_list,
+int dev_io_append_stream(struct open_dev **odevs, struct open_dev **idevs,
 			 struct cras_rstream *stream,
 			 struct cras_iodev **iodevs, unsigned int num_iodevs);
 
diff --git a/cras/src/server/dev_stream.c b/cras/src/server/dev_stream.c
index 878bb8a..be5a6da 100644
--- a/cras/src/server/dev_stream.c
+++ b/cras/src/server/dev_stream.c
@@ -63,7 +63,8 @@
 struct dev_stream *dev_stream_create(struct cras_rstream *stream,
 				     unsigned int dev_id,
 				     const struct cras_audio_format *dev_fmt,
-				     void *dev_ptr, struct timespec *cb_ts)
+				     void *dev_ptr, struct timespec *cb_ts,
+				     const struct timespec *sleep_interval_ts)
 {
 	struct dev_stream *out;
 	struct cras_audio_format *stream_fmt = &stream->format;
@@ -87,9 +88,12 @@
 	} else {
 		/*
 		 * For input, take into account the stream specific processing
-		 * like AEC. Use the post processing format to configure format
-		 * converter.
+		 * like AEC. APM exists only in input path, and has no dependency
+		 * to dev_stream. Starts APM in dev_stream's constructor just to
+		 * align with its life cycle, and then gets the post processing
+		 * format to configure format converter.
 		 */
+		cras_apm_list_start_apm(stream->apm_list, dev_ptr);
 		ofmt = cras_rstream_post_processing_format(stream, dev_ptr) ?:
 			       dev_fmt,
 		rc = config_format_converter(&out->conv, stream->direction,
@@ -119,10 +123,18 @@
 	out->conv_buffer = byte_buffer_create(buf_bytes);
 	out->conv_area = cras_audio_area_create(ofmt->num_channels);
 
-	cras_frames_to_time(cras_rstream_get_cb_threshold(stream),
-			    stream_fmt->frame_rate, &stream->sleep_interval_ts);
+	/* Use sleep interval hint from argument if it is provided */
+	if (sleep_interval_ts) {
+		stream->sleep_interval_ts = *sleep_interval_ts;
+	} else {
+		cras_frames_to_time(cras_rstream_get_cb_threshold(stream),
+				    stream_fmt->frame_rate,
+				    &stream->sleep_interval_ts);
+	}
+
 	stream->next_cb_ts = *cb_ts;
 
+	/* Sets up the stream & dev pair. */
 	cras_rstream_dev_attach(stream, dev_id, dev_ptr);
 
 	return out;
@@ -130,6 +142,10 @@
 
 void dev_stream_destroy(struct dev_stream *dev_stream)
 {
+	void *dev_ptr =
+		cras_rstream_dev_ptr(dev_stream->stream, dev_stream->dev_id);
+	/* Stops the APM and then unlink the dev stream pair. */
+	cras_apm_list_stop_apm(dev_stream->stream->apm_list, dev_ptr);
 	cras_rstream_dev_detach(dev_stream->stream, dev_stream->dev_id);
 	if (dev_stream->conv) {
 		cras_audio_area_destroy(dev_stream->conv_area);
@@ -141,9 +157,9 @@
 
 void dev_stream_set_dev_rate(struct dev_stream *dev_stream,
 			     unsigned int dev_rate, double dev_rate_ratio,
-			     double master_rate_ratio, int coarse_rate_adjust)
+			     double main_rate_ratio, int coarse_rate_adjust)
 {
-	if (dev_stream->dev_id == dev_stream->stream->master_dev.dev_id) {
+	if (dev_stream->dev_id == dev_stream->stream->main_dev.dev_id) {
 		cras_fmt_conv_set_linear_resample_rates(dev_stream->conv,
 							dev_rate, dev_rate);
 		cras_frames_to_time_precise(
@@ -151,9 +167,8 @@
 			dev_stream->stream->format.frame_rate * dev_rate_ratio,
 			&dev_stream->stream->sleep_interval_ts);
 	} else {
-		double new_rate =
-			dev_rate * dev_rate_ratio / master_rate_ratio +
-			coarse_rate_adjust_step * coarse_rate_adjust;
+		double new_rate = dev_rate * dev_rate_ratio / main_rate_ratio +
+				  coarse_rate_adjust_step * coarse_rate_adjust;
 		cras_fmt_conv_set_linear_resample_rates(dev_stream->conv,
 							dev_rate, new_rate);
 	}
@@ -253,7 +268,8 @@
 		total_read += read_frames;
 		source_samples += read_frames * source_frame_bytes;
 		buf_increment_write(dev_stream->conv_buffer,
-				    write_frames * dst_frame_bytes);
+				    (size_t)write_frames *
+					    (size_t)dst_frame_bytes);
 	}
 
 	return total_read;
@@ -311,7 +327,7 @@
 				     software_gain_scaler);
 
 		buf_increment_read(dev_stream->conv_buffer,
-				   write_frames * frame_bytes);
+				   (size_t)write_frames * (size_t)frame_bytes);
 		total_written += write_frames;
 		cras_rstream_dev_offset_update(rstream, write_frames,
 					       dev_stream->dev_id);
diff --git a/cras/src/server/dev_stream.h b/cras/src/server/dev_stream.h
index c39a801..6b34d5d 100644
--- a/cras/src/server/dev_stream.h
+++ b/cras/src/server/dev_stream.h
@@ -46,30 +46,47 @@
 	int is_running;
 };
 
+/*
+ * Creates a dev_stream.
+ *
+ * Args:
+ *    stream - The associated rstream.
+ *    dev_id - Index of the device.
+ *    dev_fmt - The format of the device.
+ *    dev_ptr - A pointer to the device
+ *    cb_ts - A pointer to the initial callback time.
+ *    sleep_interval_ts - A pointer to the initial sleep interval.
+ *        Set to null to calculate the value from device rate and block size.
+ *        Note that we need this argument so that output device sleep interval
+ *        can use input device sleep interval in the beginning to have perfect
+ *        alignment in WebRTC use case.
+ * Returns the pointer to the created dev_stream.
+ */
 struct dev_stream *dev_stream_create(struct cras_rstream *stream,
 				     unsigned int dev_id,
 				     const struct cras_audio_format *dev_fmt,
-				     void *dev_ptr, struct timespec *cb_ts);
+				     void *dev_ptr, struct timespec *cb_ts,
+				     const struct timespec *sleep_interval_ts);
 void dev_stream_destroy(struct dev_stream *dev_stream);
 
 /*
  * Update the estimated sample rate of the device. For multiple active
  * devices case, the linear resampler will be configured by the estimated
- * rate ration of the master device and the current active device the
+ * rate ration of the main device and the current active device the
  * rstream attaches to.
  *
  * Args:
  *    dev_stream - The structure holding the stream.
  *    dev_rate - The sample rate device is using.
  *    dev_rate_ratio - The ratio of estimated rate and used rate.
- *    master_rate_ratio - The ratio of estimated rate and used rate of
- *        master device.
+ *    main_rate_ratio - The ratio of estimated rate and used rate of
+ *        main device.
  *    coarse_rate_adjust - The flag to indicate the direction device
  *        sample rate should adjust to.
  */
 void dev_stream_set_dev_rate(struct dev_stream *dev_stream,
 			     unsigned int dev_rate, double dev_rate_ratio,
-			     double master_rate_ratio, int coarse_rate_adjust);
+			     double main_rate_ratio, int coarse_rate_adjust);
 
 /*
  * Renders count frames from shm into dst.  Updates count if anything is
diff --git a/cras/src/server/ewma_power.c b/cras/src/server/ewma_power.c
new file mode 100644
index 0000000..5270ef0
--- /dev/null
+++ b/cras/src/server/ewma_power.c
@@ -0,0 +1,81 @@
+/* Copyright 2020 The Chromium OS 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 "ewma_power.h"
+#include "math.h"
+
+/* One sample per 1ms. */
+#define EWMA_SAMPLE_RATE 1000
+
+/* Smooth factor for EWMA, 1 - expf(-1.0/(rate * 0.01))
+ * where the 0.01 corresponds to 10ms interval that is chosen and
+ * being used in Chrome for a long time.
+ * Here the |rate| is set to the down sampled EWMA_SAMPLE_RATE and
+ * whenever it changes the calculated |smooth_factor| should be updated
+ * accordingly.
+ */
+const static float smooth_factor = 0.095;
+
+void ewma_power_disable(struct ewma_power *ewma)
+{
+	ewma->enabled = 0;
+}
+
+void ewma_power_init(struct ewma_power *ewma, unsigned int rate)
+{
+	ewma->enabled = 1;
+	ewma->power_set = 0;
+	ewma->step_fr = rate / EWMA_SAMPLE_RATE;
+}
+
+void ewma_power_calculate(struct ewma_power *ewma, const int16_t *buf,
+			  unsigned int channels, unsigned int size)
+{
+	int i, ch;
+	float power, f;
+
+	if (!ewma->enabled)
+		return;
+	for (i = 0; i < size; i += ewma->step_fr * channels) {
+		power = 0.0f;
+		for (ch = 0; ch < channels; ch++) {
+			f = buf[i + ch] / 32768.0f;
+			power += f * f / channels;
+		}
+		if (!ewma->power_set) {
+			ewma->power = power;
+			ewma->power_set = 1;
+		} else {
+			ewma->power = smooth_factor * power +
+				      (1 - smooth_factor) * ewma->power;
+		}
+	}
+}
+
+void ewma_power_calculate_area(struct ewma_power *ewma, const int16_t *buf,
+			       struct cras_audio_area *area, unsigned int size)
+{
+	int i, ch;
+	float power, f;
+
+	if (!ewma->enabled)
+		return;
+	for (i = 0; i < size; i += ewma->step_fr * area->num_channels) {
+		power = 0.0f;
+		for (ch = 0; ch < area->num_channels; ch++) {
+			if (area->channels[ch].ch_set == 0)
+				continue;
+			f = buf[i + ch] / 32768.0f;
+			power += f * f / area->num_channels;
+		}
+		if (!ewma->power_set) {
+			ewma->power = power;
+			ewma->power_set = 1;
+		} else {
+			ewma->power = smooth_factor * power +
+				      (1 - smooth_factor) * ewma->power;
+		}
+	}
+}
diff --git a/cras/src/server/ewma_power.h b/cras/src/server/ewma_power.h
new file mode 100644
index 0000000..78d2e50
--- /dev/null
+++ b/cras/src/server/ewma_power.h
@@ -0,0 +1,65 @@
+/* Copyright 2020 The Chromium OS 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 EWMA_POWER_H_
+#define EWMA_POWER_H_
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "cras_audio_area.h"
+
+/*
+ * The exponentially weighted moving average power module used to
+ * calculate the energe level in audio stream.
+ * Members:
+ *    power_set - Flag to note if the first power value has set.
+ *    enabled - Flag to enable ewma calculation. Set to false to
+ *        make all calculations no-ops.
+ *    power - The power value.
+ *    step_fr - How many frames to sample one for EWMA calculation.
+ */
+struct ewma_power {
+	bool power_set;
+	bool enabled;
+	float power;
+	unsigned int step_fr;
+};
+
+/*
+ * Disables the ewma instance.
+ */
+void ewma_power_disable(struct ewma_power *ewma);
+
+/*
+ * Initializes the ewma_power object.
+ * Args:
+ *    ewma - The ewma_power object to initialize.
+ *    rate - The sample rate of the audio data that the ewma object
+ *        will calculate power from.
+ */
+void ewma_power_init(struct ewma_power *ewma, unsigned int rate);
+
+/*
+ * Feeds an audio buffer to ewma_power object to calculate the
+ * latest power value.
+ * Args:
+ *    ewma - The ewma_power object to calculate power.
+ *    buf - Pointer to the audio data.
+ *    channels - Number of channels of the audio data.
+ *    size - Length in frames of the audio data.
+ */
+void ewma_power_calculate(struct ewma_power *ewma, const int16_t *buf,
+			  unsigned int channels, unsigned int size);
+
+/*
+ * Feeds non-interleaved audio data to ewma_power to calculate the
+ * latest power value. This is similar to ewma_power_calculate but
+ * accepts cras_audio_area.
+ */
+void ewma_power_calculate_area(struct ewma_power *ewma, const int16_t *buf,
+			       struct cras_audio_area *area, unsigned int size);
+
+#endif /* EWMA_POWER_H_ */
diff --git a/cras/src/server/float_buffer.h b/cras/src/server/float_buffer.h
index 39a0187..ba3523d 100644
--- a/cras/src/server/float_buffer.h
+++ b/cras/src/server/float_buffer.h
@@ -37,7 +37,7 @@
 	b->fp = (float **)malloc(num_channels * sizeof(float *));
 	b->buf = (struct byte_buffer *)calloc(
 		1, sizeof(struct byte_buffer) +
-			   max_size * num_channels * sizeof(float));
+			   sizeof(float) * max_size * num_channels);
 	b->buf->max_size = max_size;
 	b->buf->used_size = max_size;
 	return b;
diff --git a/cras/src/server/iniparser_wrapper.h b/cras/src/server/iniparser_wrapper.h
index b6d32f9..89a213b 100644
--- a/cras/src/server/iniparser_wrapper.h
+++ b/cras/src/server/iniparser_wrapper.h
@@ -5,11 +5,19 @@
 #ifndef INIPARSER_WRAPPER_H_
 #define INIPARSER_WRAPPER_H_
 
+#ifdef HAVE_INIPARSER_INIPARSER_H
+#include <iniparser/iniparser.h>
+#else
 #include <iniparser.h>
+#endif
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
 
+/* Allocate 63 chars + 1 for null where declared. */
+#define MAX_INI_NAME_LENGTH 63
+#define MAX_INI_KEY_LENGTH 63 /* names like "output_source:output_0" */
+
 static inline dictionary *iniparser_load_wrapper(const char *ini_name)
 {
 	struct stat st;
diff --git a/cras/src/server/input_data.c b/cras/src/server/input_data.c
index b52c166..cc0f10b 100644
--- a/cras/src/server/input_data.c
+++ b/cras/src/server/input_data.c
@@ -151,7 +151,7 @@
 	struct cras_apm *apm;
 	int stream_offset = buffer_share_id_offset(offsets, stream->stream_id);
 
-	apm = cras_apm_list_get(stream->apm_list, data->dev_ptr);
+	apm = cras_apm_list_get_active_apm(stream, data->dev_ptr);
 	if (apm == NULL) {
 		/*
 		 * Case 1 and 2 from above example.
@@ -165,7 +165,7 @@
 		apm_processed = cras_apm_list_process(apm, data->fbuffer,
 						      stream_offset);
 		if (apm_processed < 0) {
-			cras_apm_list_remove(stream->apm_list, apm);
+			cras_apm_list_remove_apm(stream->apm_list, apm);
 			return 0;
 		}
 		buffer_share_offset_update(offsets, stream->stream_id,
@@ -182,7 +182,7 @@
 			      struct buffer_share *offsets, unsigned int frames)
 {
 	struct cras_apm *apm =
-		cras_apm_list_get(stream->apm_list, data->dev_ptr);
+		cras_apm_list_get_active_apm(stream, data->dev_ptr);
 
 	if (apm)
 		cras_apm_list_put_processed(apm, frames);
@@ -191,3 +191,20 @@
 
 	return 0;
 }
+
+float input_data_get_software_gain_scaler(struct input_data *data,
+					  float idev_sw_gain_scaler,
+					  struct cras_rstream *stream)
+{
+	struct cras_apm *apm;
+	/*
+	 * APM has more advanced gain control mechanism. If it is using tuned
+	 * settings, give APM total control of the captured samples without
+	 * additional gain scaler at all.
+	 */
+	apm = cras_apm_list_get_active_apm(stream, data->dev_ptr);
+	if (apm && cras_apm_list_get_use_tuned_settings(apm))
+		return 1.0f;
+
+	return idev_sw_gain_scaler * cras_rstream_get_volume_scaler(stream);
+}
diff --git a/cras/src/server/input_data.h b/cras/src/server/input_data.h
index bb120b4..7ac80be 100644
--- a/cras/src/server/input_data.h
+++ b/cras/src/server/input_data.h
@@ -75,4 +75,21 @@
 			      struct buffer_share *offsets,
 			      unsigned int frames);
 
+/*
+ * The software gain scaler of input path consist of two parts:
+ * (1) The device gain scaler used when lack of hardware gain control.
+ * Configured by the IntrinsicSensitivity label in alsa UCM config.
+ * (2) The gain scaler in cras_rstream set by app, for example the AGC
+ * module in Chrome.
+ * Args:
+ *    data - The input data that holds pointer to APM instance.
+ *    idev_sw_agin_scaler - The gain scaler configured on input iodev.
+ *    stream - To provide stream layer software gain.
+ * Returns:
+ *    1.0 if tuned APM in use, otherwise |iodev gain| * |cras_rstream gain|
+ */
+float input_data_get_software_gain_scaler(struct input_data *data,
+					  float idev_sw_gain_scaler,
+					  struct cras_rstream *stream);
+
 #endif /* INPUT_DATA_H_ */
diff --git a/cras/src/server/rate_estimator.c b/cras/src/server/rate_estimator.c
deleted file mode 100644
index 4b1277e..0000000
--- a/cras/src/server/rate_estimator.c
+++ /dev/null
@@ -1,109 +0,0 @@
-/* Copyright (c) 2014 The Chromium OS 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 "math.h"
-
-#include "cras_util.h"
-#include "rate_estimator.h"
-
-/* The max rate skew that considered reasonable */
-#define MAX_RATE_SKEW 100
-
-static void least_square_reset(struct least_square *lsq)
-{
-	memset(lsq, 0, sizeof(*lsq));
-}
-
-void least_square_add_sample(struct least_square *lsq, double x, double y)
-{
-	lsq->sum_x += x;
-	lsq->sum_y += y;
-	lsq->sum_xy += x * y;
-	lsq->sum_x2 += x * x;
-	lsq->num_samples++;
-}
-
-double least_square_best_fit_slope(struct least_square *lsq)
-{
-	double num, denom;
-	num = lsq->num_samples * lsq->sum_xy - lsq->sum_x * lsq->sum_y;
-	denom = lsq->num_samples * lsq->sum_x2 - lsq->sum_x * lsq->sum_x;
-	return num / denom;
-}
-
-void rate_estimator_destroy(struct rate_estimator *re)
-{
-	if (re)
-		free(re);
-}
-
-struct rate_estimator *rate_estimator_create(unsigned int rate,
-					     const struct timespec *window_size,
-					     double smooth_factor)
-{
-	struct rate_estimator *re;
-
-	re = (struct rate_estimator *)calloc(1, sizeof(*re));
-	if (re == NULL)
-		return NULL;
-
-	re->window_size = *window_size;
-	re->estimated_rate = rate;
-	re->smooth_factor = smooth_factor;
-
-	return re;
-}
-
-void rate_estimator_add_frames(struct rate_estimator *re, int fr)
-{
-	re->level_diff += fr;
-}
-
-double rate_estimator_get_rate(struct rate_estimator *re)
-{
-	return re->estimated_rate;
-}
-
-void rate_estimator_reset_rate(struct rate_estimator *re, unsigned int rate)
-{
-	re->estimated_rate = rate;
-	least_square_reset(&re->lsq);
-	re->window_start_ts.tv_sec = 0;
-	re->window_start_ts.tv_nsec = 0;
-	re->window_frames = 0;
-	re->level_diff = 0;
-	re->last_level = 0;
-}
-
-int rate_estimator_check(struct rate_estimator *re, int level,
-			 struct timespec *now)
-{
-	struct timespec td;
-
-	if (re->window_start_ts.tv_sec == 0) {
-		re->window_start_ts = *now;
-		return 0;
-	}
-
-	subtract_timespecs(now, &re->window_start_ts, &td);
-	re->window_frames += abs(re->last_level - level + re->level_diff);
-	re->level_diff = 0;
-	re->last_level = level;
-
-	least_square_add_sample(&re->lsq,
-				td.tv_sec + (double)td.tv_nsec / 1000000000L,
-				re->window_frames);
-	if (timespec_after(&td, &re->window_size) && re->lsq.num_samples > 1) {
-		double rate = least_square_best_fit_slope(&re->lsq);
-		if (fabs(re->estimated_rate - rate) < MAX_RATE_SKEW)
-			re->estimated_rate =
-				rate * (1 - re->smooth_factor) +
-				re->smooth_factor * re->estimated_rate;
-		least_square_reset(&re->lsq);
-		re->window_start_ts = *now;
-		re->window_frames = 0;
-		return 1;
-	}
-	return 0;
-}
diff --git a/cras/src/server/rate_estimator.h b/cras/src/server/rate_estimator.h
deleted file mode 100644
index c9a5aff..0000000
--- a/cras/src/server/rate_estimator.h
+++ /dev/null
@@ -1,86 +0,0 @@
-/* Copyright (c) 2014 The Chromium OS 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 RATE_ESTIMATOR_H_
-#define RATE_ESTIMATOR_H_
-
-#include <time.h>
-
-/* Hold information to calculate linear least square from
- * several (x, y) samples.
- */
-struct least_square {
-	double sum_x;
-	double sum_y;
-	double sum_xy;
-	double sum_x2;
-	int num_samples;
-};
-
-/* An estimator holding the required information to determine the actual frame
- * rate of an audio device.
- * Members:
- *    last_level - Buffer level of the audio device at last check time.
- *    level_diff - Number of frames written to or read from audio device since
- *        the last check time. Rate estimator will use this change plus the
- *        difference of buffer level to derive the number of frames audio
- *        device has actually processed.
- *    window_start_ts - The start time of the current window.
- *    window_size - The size of the window.
- *    window_frames - The number of frames accumulated in current window.
- *    lsq - The helper used to estimate sample rate.
- */
-struct rate_estimator {
-	int last_level;
-	int level_diff;
-	struct timespec window_start_ts;
-	struct timespec window_size;
-	int window_frames;
-	struct least_square lsq;
-	double smooth_factor;
-	double estimated_rate;
-};
-
-/* Creates a rate estimator.
- * Args:
- *    rate - The initial value to estimate rate from.
- *    window_size - The window size of the rate estimator.
- *    smooth_factor - The coefficient used to calculate moving average
- *        from old estimated rate values.
- */
-struct rate_estimator *rate_estimator_create(unsigned int rate,
-					     const struct timespec *window_size,
-					     double smooth_factor);
-/* Destroy a rate estimator. */
-void rate_estimator_destroy(struct rate_estimator *re);
-
-/* Adds additional frames transmitted to/from audio device.
- * Args:
- *    re - The rate estimator.
- *    fr - The number of frames written to the device.  For input, this should
- *      be negative to indicate how many samples were read.
- */
-void rate_estimator_add_frames(struct rate_estimator *re, int fr);
-
-/* Checks the timestamp and buffer level difference since last check time,
- * and use them as a new sample to update the estimated rate.
- * Args:
- *    re - The rate estimator.
- *    level - The current buffer level of audio device.
- *    now - The time at which this function is called.
- * Returns:
- *    True if the estimated rate is updated and window is reset,
- *    otherwise false.
- */
-int rate_estimator_check(struct rate_estimator *re, int level,
-			 struct timespec *now);
-
-/* Gets the estimated rate. */
-double rate_estimator_get_rate(struct rate_estimator *re);
-
-/* Resets the estimated rate. */
-void rate_estimator_reset_rate(struct rate_estimator *re, unsigned int rate);
-
-#endif /* RATE_ESTIMATOR_H_ */
diff --git a/cras/src/server/rust/.gitignore b/cras/src/server/rust/.gitignore
new file mode 100644
index 0000000..2f7896d
--- /dev/null
+++ b/cras/src/server/rust/.gitignore
@@ -0,0 +1 @@
+target/
diff --git a/cras/src/server/rust/Cargo.toml b/cras/src/server/rust/Cargo.toml
new file mode 100644
index 0000000..afebda8
--- /dev/null
+++ b/cras/src/server/rust/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "cras_rust"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+edition = "2018"
+
+[lib]
+path = "src/rate_estimator.rs"
+crate-type = ["staticlib"]
+
+[dependencies]
+libc = "0.2.44"
+
+[profile.release]
+lto = true
+panic = "abort"
+overflow-checks = true
diff --git a/cras/src/server/rust/binding_generator/Cargo.toml b/cras/src/server/rust/binding_generator/Cargo.toml
new file mode 100644
index 0000000..1ec7348
--- /dev/null
+++ b/cras/src/server/rust/binding_generator/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "binding_generator"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+edition = "2018"
+
+[dependencies]
+cbindgen = "*"
diff --git a/cras/src/server/rust/binding_generator/src/main.rs b/cras/src/server/rust/binding_generator/src/main.rs
new file mode 100644
index 0000000..f780396
--- /dev/null
+++ b/cras/src/server/rust/binding_generator/src/main.rs
@@ -0,0 +1,27 @@
+// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+extern crate cbindgen;
+
+use cbindgen::Builder;
+
+const HEADER: &str = "// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Generated from files in cras/src/server/rust/src in adhd.";
+
+fn main() {
+    Builder::new()
+        .with_src("../src/rate_estimator.rs")
+        .rename_item("RateEstimator", "rate_estimator")
+        .rename_item("timespec", "struct timespec")
+        .with_no_includes()
+        .with_sys_include("time.h")
+        .with_include_guard("RATE_ESTIMATOR_H_")
+        .with_language(cbindgen::Language::C)
+        .with_header(HEADER)
+        .generate()
+        .expect("Unable to generate bindings")
+        .write_to_file("../src/headers/rate_estimator.h");
+}
diff --git a/cras/src/server/rust/src/headers/rate_estimator.h b/cras/src/server/rust/src/headers/rate_estimator.h
new file mode 100644
index 0000000..3ac9cfa
--- /dev/null
+++ b/cras/src/server/rust/src/headers/rate_estimator.h
@@ -0,0 +1,85 @@
+// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Generated from files in cras/src/server/rust/src in adhd.
+
+#ifndef RATE_ESTIMATOR_H_
+#define RATE_ESTIMATOR_H_
+
+#include <time.h>
+
+/**
+ * An estimator holding the required information to determine the actual frame
+ * rate of an audio device.
+ *
+ * # Members
+ *    * `last_level` - Buffer level of the audio device at last check time.
+ *    * `level_diff` - Number of frames written to or read from audio device
+ *                     since the last check time. Rate estimator will use this
+ *                     change plus the difference of buffer level to derive the
+ *                     number of frames audio device has actually processed.
+ *    * `window_start` - The start time of the current window.
+ *    * `window_size` - The size of the window.
+ *    * `window_frames` - The number of frames accumulated in current window.
+ *    * `lsq` - The helper used to estimate sample rate.
+ *    * `smooth_factor` - A scaling factor used to average the previous and new
+ *                        rate estimates to ensure that estimates do not change
+ *                        too quickly.
+ *    * `estimated_rate` - The estimated rate at which samples are consumed.
+ */
+typedef struct rate_estimator rate_estimator;
+
+/**
+ * # Safety
+ *
+ * To use this function safely, `re` must be a pointer returned from
+ * rate_estimator_create, or null.
+ */
+void rate_estimator_add_frames(rate_estimator *re, int frames);
+
+/**
+ * # Safety
+ *
+ * To use this function safely, `re` must be a pointer returned from
+ * rate_estimator_create, or null, and `now` must be a valid pointer to a
+ * timespec.
+ */
+int32_t rate_estimator_check(rate_estimator *re, int level,
+			     const struct timespec *now);
+
+/**
+ * # Safety
+ *
+ * To use this function safely, `window_size` must be a valid pointer to a
+ * timespec.
+ */
+rate_estimator *rate_estimator_create(unsigned int rate,
+				      const struct timespec *window_size,
+				      double smooth_factor);
+
+/**
+ * # Safety
+ *
+ * To use this function safely, `re` must be a pointer returned from
+ * rate_estimator_create, or null.
+ */
+void rate_estimator_destroy(rate_estimator *re);
+
+/**
+ * # Safety
+ *
+ * To use this function safely, `re` must be a pointer returned from
+ * rate_estimator_create, or null.
+ */
+double rate_estimator_get_rate(const rate_estimator *re);
+
+/**
+ * # Safety
+ *
+ * To use this function safely, `re` must be a pointer returned from
+ * rate_estimator_create, or null.
+ */
+void rate_estimator_reset_rate(rate_estimator *re, unsigned int rate);
+
+#endif /* RATE_ESTIMATOR_H_ */
diff --git a/cras/src/server/rust/src/rate_estimator.rs b/cras/src/server/rust/src/rate_estimator.rs
new file mode 100644
index 0000000..585f346
--- /dev/null
+++ b/cras/src/server/rust/src/rate_estimator.rs
@@ -0,0 +1,188 @@
+// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+pub mod rate_estimator_bindings;
+
+use std::error;
+use std::fmt;
+use std::time::Duration;
+
+#[derive(Debug)]
+pub enum Error {
+    InvalidSmoothFactor(f64),
+}
+
+impl error::Error for Error {}
+
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        use Error::*;
+        match self {
+            InvalidSmoothFactor(sf) => write!(f, "Smooth factor {} is not between 0.0 and 1.0", sf),
+        }
+    }
+}
+
+type Result<T> = std::result::Result<T, Error>;
+
+const MAX_RATE_SKEW: f64 = 100.0;
+
+/// Hold information to calculate linear least square from
+/// several (x, y) samples.
+#[derive(Debug, Default)]
+struct LeastSquares {
+    sum_x: f64,
+    sum_y: f64,
+    sum_xy: f64,
+    sum_x2: f64,
+    num_samples: u32,
+}
+
+impl LeastSquares {
+    fn new() -> Self {
+        Self::default()
+    }
+
+    fn add_sample(&mut self, x: f64, y: f64) {
+        self.sum_x += x;
+        self.sum_y += y;
+        self.sum_xy += x * y;
+        self.sum_x2 += x * x;
+        self.num_samples += 1;
+    }
+
+    fn best_fit_slope(&self) -> f64 {
+        let num = self.num_samples as f64 * self.sum_xy - self.sum_x * self.sum_y;
+        let den = self.num_samples as f64 * self.sum_x2 - self.sum_x * self.sum_x;
+        num / den
+    }
+}
+
+/// An estimator holding the required information to determine the actual frame
+/// rate of an audio device.
+///
+/// # Members
+///    * `last_level` - Buffer level of the audio device at last check time.
+///    * `level_diff` - Number of frames written to or read from audio device
+///                     since the last check time. Rate estimator will use this
+///                     change plus the difference of buffer level to derive the
+///                     number of frames audio device has actually processed.
+///    * `window_start` - The start time of the current window.
+///    * `window_size` - The size of the window.
+///    * `window_frames` - The number of frames accumulated in current window.
+///    * `lsq` - The helper used to estimate sample rate.
+///    * `smooth_factor` - A scaling factor used to average the previous and new
+///                        rate estimates to ensure that estimates do not change
+///                        too quickly.
+///    * `estimated_rate` - The estimated rate at which samples are consumed.
+pub struct RateEstimator {
+    last_level: i32,
+    level_diff: i32,
+    window_start: Option<Duration>,
+    window_size: Duration,
+    window_frames: u32,
+    lsq: LeastSquares,
+    smooth_factor: f64,
+    estimated_rate: f64,
+}
+
+impl RateEstimator {
+    /// Creates a rate estimator.
+    ///
+    /// # Arguments
+    ///    * `rate` - The initial value to estimate rate from.
+    ///    * `window_size` - The window size of the rate estimator.
+    ///    * `smooth_factor` - The coefficient used to calculate moving average
+    ///                        from old estimated rate values. Must be between
+    ///                        0.0 and 1.0
+    ///
+    /// # Errors
+    ///    * If `smooth_factor` is not between 0.0 and 1.0
+    pub fn try_new(rate: u32, window_size: Duration, smooth_factor: f64) -> Result<Self> {
+        if smooth_factor < 0.0 || smooth_factor > 1.0 {
+            return Err(Error::InvalidSmoothFactor(smooth_factor));
+        }
+
+        Ok(RateEstimator {
+            last_level: 0,
+            level_diff: 0,
+            window_start: None,
+            window_size,
+            window_frames: 0,
+            lsq: LeastSquares::new(),
+            smooth_factor,
+            estimated_rate: rate as f64,
+        })
+    }
+
+    /// Resets the estimated rate
+    ///
+    /// Reset the estimated rate to `rate`, and erase all collected data.
+    pub fn reset_rate(&mut self, rate: u32) {
+        self.last_level = 0;
+        self.level_diff = 0;
+        self.window_start = None;
+        self.window_frames = 0;
+        self.lsq = LeastSquares::new();
+        self.estimated_rate = rate as f64;
+    }
+
+    /// Adds additional frames transmitted to/from audio device.
+    ///
+    /// # Arguments
+    ///    * `frames` - The number of frames written to the device.  For input,
+    ///                 this should be negative to indicate how many samples
+    ///                 were read.
+    pub fn add_frames(&mut self, frames: i32) {
+        self.level_diff += frames;
+    }
+
+    /// Gets the estimated rate.
+    pub fn get_estimated_rate(&self) -> f64 {
+        self.estimated_rate
+    }
+
+    /// Check the timestamp and buffer level difference since last check time,
+    /// and use them as a new sample to update the estimated rate.
+    ///
+    /// # Arguments
+    ///    * `level` - The current buffer level of audio device.
+    ///    * `now` - The time at which this function is called.
+    ///
+    /// # Returns
+    ///    True if the estimated rate is updated and window is reset,
+    ///    otherwise false.
+    pub fn update_estimated_rate(&mut self, level: i32, now: Duration) -> bool {
+        let start = match self.window_start {
+            None => {
+                self.window_start = Some(now);
+                return false;
+            }
+            Some(t) => t,
+        };
+
+        let delta = match now.checked_sub(start) {
+            Some(d) => d,
+            None => return false,
+        };
+        self.window_frames += (self.last_level - level + self.level_diff).abs() as u32;
+        self.level_diff = 0;
+        self.last_level = level;
+
+        let secs = (delta.as_secs() as f64) + delta.subsec_nanos() as f64 / 1_000_000_000.0;
+        self.lsq.add_sample(secs, self.window_frames as f64);
+        if delta > self.window_size && self.lsq.num_samples > 1 {
+            let rate = self.lsq.best_fit_slope();
+            if (self.estimated_rate - rate).abs() < MAX_RATE_SKEW {
+                self.estimated_rate =
+                    rate * (1.0 - self.smooth_factor) + self.estimated_rate * self.smooth_factor;
+            }
+            self.lsq = LeastSquares::new();
+            self.window_start = Some(now);
+            self.window_frames = 0;
+            return true;
+        }
+        false
+    }
+}
diff --git a/cras/src/server/rust/src/rate_estimator_bindings.rs b/cras/src/server/rust/src/rate_estimator_bindings.rs
new file mode 100644
index 0000000..3073ba7
--- /dev/null
+++ b/cras/src/server/rust/src/rate_estimator_bindings.rs
@@ -0,0 +1,110 @@
+// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::time::Duration;
+
+use libc;
+
+use crate::RateEstimator;
+
+/// # Safety
+///
+/// To use this function safely, `window_size` must be a valid pointer to a
+/// timespec.
+#[no_mangle]
+pub unsafe extern "C" fn rate_estimator_create(
+    rate: libc::c_uint,
+    window_size: *const libc::timespec,
+    smooth_factor: libc::c_double,
+) -> *mut RateEstimator {
+    if window_size.is_null() {
+        return std::ptr::null_mut::<RateEstimator>();
+    }
+
+    let ts = &*window_size;
+    let window = Duration::new(ts.tv_sec as u64, ts.tv_nsec as u32);
+
+    match RateEstimator::try_new(rate, window, smooth_factor) {
+        Ok(re) => Box::into_raw(Box::new(re)),
+        Err(_) => std::ptr::null_mut::<RateEstimator>(),
+    }
+}
+
+/// # Safety
+///
+/// To use this function safely, `re` must be a pointer returned from
+/// rate_estimator_create, or null.
+#[no_mangle]
+pub unsafe extern "C" fn rate_estimator_destroy(re: *mut RateEstimator) {
+    if re.is_null() {
+        return;
+    }
+
+    drop(Box::from_raw(re));
+}
+
+/// # Safety
+///
+/// To use this function safely, `re` must be a pointer returned from
+/// rate_estimator_create, or null.
+#[no_mangle]
+pub unsafe extern "C" fn rate_estimator_add_frames(re: *mut RateEstimator, frames: libc::c_int) {
+    if re.is_null() {
+        return;
+    }
+
+    (*re).add_frames(frames)
+}
+
+/// # Safety
+///
+/// To use this function safely, `re` must be a pointer returned from
+/// rate_estimator_create, or null, and `now` must be a valid pointer to a
+/// timespec.
+#[no_mangle]
+pub unsafe extern "C" fn rate_estimator_check(
+    re: *mut RateEstimator,
+    level: libc::c_int,
+    now: *const libc::timespec,
+) -> i32 {
+    if re.is_null() || now.is_null() {
+        return 0;
+    }
+
+    let ts = &*now;
+    if ts.tv_sec < 0 || ts.tv_nsec < 0 {
+        return 0;
+    }
+    let secs = ts.tv_sec as u64 + (ts.tv_nsec / 1_000_000_000) as u64;
+    let nsecs = (ts.tv_nsec % 1_000_000_000) as u32;
+    let now = Duration::new(secs, nsecs);
+
+    (*re).update_estimated_rate(level, now) as i32
+}
+
+/// # Safety
+///
+/// To use this function safely, `re` must be a pointer returned from
+/// rate_estimator_create, or null.
+#[no_mangle]
+pub unsafe extern "C" fn rate_estimator_get_rate(re: *const RateEstimator) -> libc::c_double {
+    if re.is_null() {
+        return 0.0;
+    }
+
+    (*re).get_estimated_rate()
+}
+
+/// # Safety
+///
+/// To use this function safely, `re` must be a pointer returned from
+/// rate_estimator_create, or null.
+#[no_mangle]
+pub unsafe extern "C" fn rate_estimator_reset_rate(re: *mut RateEstimator, rate: libc::c_uint) {
+    if re.is_null() {
+        return;
+    }
+
+    (*re).reset_rate(rate)
+}
diff --git a/cras/src/server/server_stream.c b/cras/src/server/server_stream.c
index cc16cd8..36d5496 100644
--- a/cras/src/server/server_stream.c
+++ b/cras/src/server/server_stream.c
@@ -16,18 +16,6 @@
 static unsigned int server_stream_block_size = 480;
 
 /*
- * Server stream doesn't care what format is used, because no
- * client is reading data from stream. The main point is to
- * make pinned device open and let data flow through its dsp
- * pipeline.
- */
-static struct cras_audio_format format = {
-	SND_PCM_FORMAT_S16_LE,
-	48000,
-	2,
-};
-
-/*
  * Information of a stream created by server. Currently only
  * one server stream is allowed, for echo reference use.
  */
@@ -45,10 +33,12 @@
 	stream_list_add(stream_list, stream_config, &stream);
 }
 
-void server_stream_create(struct stream_list *stream_list, unsigned int dev_idx)
+void server_stream_create(struct stream_list *stream_list, unsigned int dev_idx,
+			  struct cras_audio_format *format)
 {
 	int audio_fd = -1;
 	int client_shm_fd = -1;
+	uint64_t buffer_offsets[2] = { 0, 0 };
 
 	if (stream_config) {
 		syslog(LOG_ERR, "server stream already exists, dev %u",
@@ -63,9 +53,9 @@
 		CRAS_STREAM_TYPE_DEFAULT, CRAS_CLIENT_TYPE_SERVER_STREAM,
 		CRAS_STREAM_INPUT, dev_idx,
 		/*flags=*/SERVER_ONLY,
-		/*effects=*/0, &format, server_stream_block_size,
+		/*effects=*/0, format, server_stream_block_size,
 		server_stream_block_size, &audio_fd, &client_shm_fd,
-		/*client_shm_size=*/0, stream_config);
+		/*client_shm_size=*/0, buffer_offsets, stream_config);
 
 	/* Schedule add stream in next main thread loop. */
 	cras_system_add_task(server_stream_add_cb, stream_list);
@@ -93,6 +83,5 @@
 		syslog(LOG_ERR, "No server stream to destroy");
 		return;
 	}
-	/* Schedule remove stream in next main thread loop. */
-	cras_system_add_task(server_stream_rm_cb, stream_list);
+	server_stream_rm_cb(stream_list);
 }
diff --git a/cras/src/server/server_stream.h b/cras/src/server/server_stream.h
index 595987c..e1eb8e1 100644
--- a/cras/src/server/server_stream.h
+++ b/cras/src/server/server_stream.h
@@ -14,8 +14,8 @@
  *    stream_list - List of stream to add new server stream to.
  *    dev_idx - The id of the device that new server stream will pin to.
  */
-void server_stream_create(struct stream_list *stream_list,
-			  unsigned int dev_idx);
+void server_stream_create(struct stream_list *stream_list, unsigned int dev_idx,
+			  struct cras_audio_format *format);
 
 /*
  * Asynchronously destroys existing server stream pinned to device of given idx.
diff --git a/cras/src/server/stream_list.c b/cras/src/server/stream_list.c
index d247ec8..04ef9fe 100644
--- a/cras/src/server/stream_list.c
+++ b/cras/src/server/stream_list.c
@@ -3,12 +3,19 @@
  * found in the LICENSE file.
  */
 
+#include <syslog.h>
 #include "cras_rstream.h"
 #include "cras_tm.h"
 #include "cras_types.h"
 #include "stream_list.h"
 #include "utlist.h"
 
+/*
+ * If the time difference of two streams is short than 10s, they may be the RTC
+ * streams.
+ */
+static const struct timespec RTC_STREAM_THRESHOLD = { 10, 0 };
+
 struct stream_list {
 	struct cras_rstream *streams;
 	struct cras_rstream *streams_to_delete;
@@ -81,12 +88,19 @@
 		    struct cras_rstream **stream)
 {
 	int rc;
+	struct cras_rstream *next_stream;
 
 	rc = list->stream_create_cb(stream_config, stream);
 	if (rc)
 		return rc;
 
-	DL_APPEND(list->streams, *stream);
+	/* Keep stream list in descending order by channel count. */
+	DL_FOREACH (list->streams, next_stream) {
+		if ((*stream)->format.num_channels >=
+		    next_stream->format.num_channels)
+			break;
+	}
+	DL_INSERT(list->streams, next_stream, *stream);
 	rc = list->stream_added_cb(*stream);
 	if (rc) {
 		DL_DELETE(list->streams, *stream);
@@ -147,3 +161,28 @@
 	}
 	return false;
 }
+
+void detect_rtc_stream_pair(struct stream_list *list,
+			    struct cras_rstream *stream)
+{
+	struct cras_rstream *next_stream;
+	if (stream->cb_threshold != 480)
+		return;
+	if (stream->client_type != CRAS_CLIENT_TYPE_CHROME &&
+	    stream->client_type != CRAS_CLIENT_TYPE_LACROS)
+		return;
+	DL_FOREACH (list->streams, next_stream) {
+		if (next_stream->cb_threshold == 480 &&
+		    next_stream->direction != stream->direction &&
+		    next_stream->client_type == stream->client_type &&
+		    timespec_diff_shorter_than(&stream->start_ts,
+					       &next_stream->start_ts,
+					       &RTC_STREAM_THRESHOLD)) {
+			stream->stream_type =
+				CRAS_STREAM_TYPE_VOICE_COMMUNICATION;
+			next_stream->stream_type =
+				CRAS_STREAM_TYPE_VOICE_COMMUNICATION;
+			return;
+		}
+	}
+}
diff --git a/cras/src/server/stream_list.h b/cras/src/server/stream_list.h
index ae77a33..a527bc9 100644
--- a/cras/src/server/stream_list.h
+++ b/cras/src/server/stream_list.h
@@ -30,8 +30,8 @@
 
 struct cras_rstream *stream_list_get(struct stream_list *list);
 
-/* Creates a cras_rstream from cras_rstreaem_config and adds the cras_rstream
- * to stream_list.
+/* Creates a cras_rstream from cras_rstream_config and inserts the cras_rstream
+ * to stream_list in descending order by channel count.
  *
  * Args:
  *   list - stream_list to add streams.
@@ -55,3 +55,14 @@
  */
 bool stream_list_has_pinned_stream(struct stream_list *list,
 				   unsigned int dev_idx);
+
+/*
+ * Detects whether there is a RTC stream pair based on these rules:
+ * 1. The cb_threshold is 480.
+ * 2. The direction of two streams are opposite.
+ * 3. Two streams are from the same client. (Chrome or LaCrOS)
+ * 4. The start time of two streams are close enough. (shorter than 1s)
+ * If all rules are passed, set the stream type to the voice communication.
+ */
+void detect_rtc_stream_pair(struct stream_list *list,
+			    struct cras_rstream *stream);
diff --git a/cras/src/server/test_iodev.c b/cras/src/server/test_iodev.c
index 47ff336..cb7d5f3 100644
--- a/cras/src/server/test_iodev.c
+++ b/cras/src/server/test_iodev.c
@@ -102,7 +102,8 @@
 	struct test_iodev *testio = (struct test_iodev *)iodev;
 
 	/* Input */
-	buf_increment_read(testio->audbuff, frames * testio->fmt_bytes);
+	buf_increment_read(testio->audbuff,
+			   (size_t)frames * (size_t)testio->fmt_bytes);
 
 	return 0;
 }
@@ -194,7 +195,13 @@
 	iodev->put_buffer = put_buffer;
 	iodev->update_active_node = update_active_node;
 
-	/* Create a dummy ionode */
+	/*
+	 * Record max supported channels into cras_iodev_info.
+	 * The value is the max of test_supported_channel_counts.
+	 */
+	iodev->info.max_supported_channels = 1;
+
+	/* Create an empty ionode */
 	node = (struct cras_ionode *)calloc(1, sizeof(*node));
 	node->dev = iodev;
 	node->plugged = 1;
@@ -204,7 +211,7 @@
 		node->type = CRAS_NODE_TYPE_UNKNOWN;
 	node->volume = 100;
 	node->software_volume_needed = 0;
-	node->max_software_gain = 0;
+	node->ui_gain_scaler = 1.0f;
 	strcpy(node->name, "(default)");
 	cras_iodev_add_node(iodev, node);
 	cras_iodev_set_active_node(iodev, node);
@@ -235,8 +242,9 @@
 
 	write_ptr = buf_write_pointer_size(testio->audbuff, &avail);
 	count = MIN(count, avail);
-	memcpy(write_ptr, samples, count * testio->fmt_bytes);
-	buf_increment_write(testio->audbuff, count * testio->fmt_bytes);
+	memcpy(write_ptr, samples, (size_t)count * (size_t)testio->fmt_bytes);
+	buf_increment_write(testio->audbuff,
+			    (size_t)count * (size_t)testio->fmt_bytes);
 	return count;
 }
 
diff --git a/cras/src/tests/a2dp_info_unittest.cc b/cras/src/tests/a2dp_info_unittest.cc
index 4858c46..eb082b0 100644
--- a/cras/src/tests/a2dp_info_unittest.cc
+++ b/cras/src/tests/a2dp_info_unittest.cc
@@ -80,7 +80,7 @@
   ASSERT_EQ(1, get_sbc_codec_destroy_called());
 }
 
-TEST(A2dpInfoInit, DrainA2dp) {
+TEST(A2dpInfoInit, ResetA2dp) {
   ResetStubData();
   init_a2dp(&a2dp, &sbc);
   a2dp.a2dp_buf_used = 99;
@@ -88,7 +88,7 @@
   a2dp.seq_num = 11;
   a2dp.frame_count = 12;
 
-  a2dp_drain(&a2dp);
+  a2dp_reset(&a2dp);
 
   ASSERT_EQ(a2dp.a2dp_buf_used, 13);
   ASSERT_EQ(a2dp.frame_count, 0);
diff --git a/cras/src/tests/a2dp_iodev_unittest.cc b/cras/src/tests/a2dp_iodev_unittest.cc
index c85b9d6..06c1cd3 100644
--- a/cras/src/tests/a2dp_iodev_unittest.cc
+++ b/cras/src/tests/a2dp_iodev_unittest.cc
@@ -7,11 +7,10 @@
 #include <stdio.h>
 
 extern "C" {
+#include "cras_a2dp_iodev.c"
 
-#include "a2dp-codecs.h"
 #include "audio_thread.h"
 #include "audio_thread_log.h"
-#include "cras_a2dp_iodev.h"
 #include "cras_audio_area.h"
 #include "cras_bt_transport.h"
 #include "cras_iodev.h"
@@ -22,12 +21,15 @@
 #define MAX_A2DP_ENCODE_CALLS 8
 #define MAX_A2DP_WRITE_CALLS 4
 
+/* Fake the codec to encode (512/4) frames into 128 bytes. */
+#define FAKE_A2DP_CODE_SIZE 512
+#define FAKE_A2DP_FRAME_LENGTH 128
+
 static struct cras_bt_transport* fake_transport;
 static cras_audio_format format;
 static size_t cras_bt_device_append_iodev_called;
 static size_t cras_bt_device_rm_iodev_called;
 static size_t cras_iodev_add_node_called;
-static int cras_iodev_frames_queued_called;
 static size_t cras_iodev_rm_node_called;
 static size_t cras_iodev_set_active_node_called;
 static size_t cras_bt_transport_acquire_called;
@@ -36,28 +38,28 @@
 static size_t init_a2dp_called;
 static int init_a2dp_return_val;
 static size_t destroy_a2dp_called;
-static size_t drain_a2dp_called;
-static size_t a2dp_block_size_called;
-static size_t a2dp_queued_frames_val;
+static size_t a2dp_reset_called;
 static size_t cras_iodev_free_format_called;
 static size_t cras_iodev_free_resources_called;
-static int pcm_buf_size_val[MAX_A2DP_ENCODE_CALLS];
-static unsigned int a2dp_encode_processed_bytes_val[MAX_A2DP_ENCODE_CALLS];
-static unsigned int a2dp_encode_index;
 static int a2dp_write_return_val[MAX_A2DP_WRITE_CALLS];
 static unsigned int a2dp_write_index;
-static cras_audio_area* dummy_audio_area;
+static int a2dp_encode_called;
+static cras_audio_area* mock_audio_area;
 static thread_callback write_callback;
 static void* write_callback_data;
 static const char* fake_device_name = "fake device name";
 static const char* cras_bt_device_name_ret;
 static unsigned int cras_bt_transport_write_mtu_ret;
+static int cras_iodev_fill_odev_zeros_called;
+static unsigned int cras_iodev_fill_odev_zeros_frames;
+static int audio_thread_config_events_callback_called;
+static enum AUDIO_THREAD_EVENTS_CB_TRIGGER
+    audio_thread_config_events_callback_trigger;
 
 void ResetStubData() {
   cras_bt_device_append_iodev_called = 0;
   cras_bt_device_rm_iodev_called = 0;
   cras_iodev_add_node_called = 0;
-  cras_iodev_frames_queued_called = 0;
   cras_iodev_rm_node_called = 0;
   cras_iodev_set_active_node_called = 0;
   cras_bt_transport_acquire_called = 0;
@@ -66,22 +68,20 @@
   init_a2dp_called = 0;
   init_a2dp_return_val = 0;
   destroy_a2dp_called = 0;
-  drain_a2dp_called = 0;
-  a2dp_block_size_called = 0;
-  a2dp_queued_frames_val = 0;
+  a2dp_reset_called = 0;
   cras_iodev_free_format_called = 0;
   cras_iodev_free_resources_called = 0;
-  memset(a2dp_encode_processed_bytes_val, 0,
-         sizeof(a2dp_encode_processed_bytes_val));
-  a2dp_encode_index = 0;
   a2dp_write_index = 0;
-  cras_bt_transport_write_mtu_ret = 800;
+  a2dp_encode_called = 0;
+  /* Fake the MTU value. min_buffer_level will be derived from this value. */
+  cras_bt_transport_write_mtu_ret = 950;
+  cras_iodev_fill_odev_zeros_called = 0;
 
   fake_transport = reinterpret_cast<struct cras_bt_transport*>(0x123);
 
-  if (!dummy_audio_area) {
-    dummy_audio_area = (cras_audio_area*)calloc(
-        1, sizeof(*dummy_audio_area) + sizeof(cras_channel_area) * 2);
+  if (!mock_audio_area) {
+    mock_audio_area = (cras_audio_area*)calloc(
+        1, sizeof(*mock_audio_area) + sizeof(cras_channel_area) * 2);
   }
 
   write_callback = NULL;
@@ -102,12 +102,14 @@
  protected:
   virtual void SetUp() {
     ResetStubData();
+    time_now.tv_sec = 0;
+    time_now.tv_nsec = 0;
     atlog = (audio_thread_event_log*)calloc(1, sizeof(audio_thread_event_log));
   }
 
   virtual void TearDown() {
-    free(dummy_audio_area);
-    dummy_audio_area = NULL;
+    free(mock_audio_area);
+    mock_audio_area = NULL;
     free(atlog);
   }
 };
@@ -167,12 +169,14 @@
 
   iodev_set_format(iodev, &format);
   iodev->configure_dev(iodev);
+  iodev->start(iodev);
+  iodev->state = CRAS_IODEV_STATE_NORMAL_RUN;
 
   ASSERT_EQ(1, cras_bt_transport_acquire_called);
 
   iodev->close_dev(iodev);
   ASSERT_EQ(1, cras_bt_transport_release_called);
-  ASSERT_EQ(1, drain_a2dp_called);
+  ASSERT_EQ(1, a2dp_reset_called);
   ASSERT_EQ(1, cras_iodev_free_format_called);
 
   a2dp_iodev_destroy(iodev);
@@ -181,58 +185,74 @@
 TEST_F(A2dpIodev, GetPutBuffer) {
   struct cras_iodev* iodev;
   struct cras_audio_area *area1, *area2, *area3;
-  uint8_t* area1_buf;
+  uint8_t* last_buf_head;
   unsigned frames;
+  struct timespec tstamp;
+  struct a2dp_io* a2dpio;
 
   iodev = a2dp_iodev_create(fake_transport);
+  a2dpio = (struct a2dp_io*)iodev;
 
   iodev_set_format(iodev, &format);
   iodev->configure_dev(iodev);
   ASSERT_NE(write_callback, (void*)NULL);
 
-  frames = 256;
+  iodev->start(iodev);
+  iodev->state = CRAS_IODEV_STATE_NORMAL_RUN;
+
+  /* (950 - 13) / 128 * 512 / 4 */
+  ASSERT_EQ(iodev->min_buffer_level, 896);
+
+  frames = 1500;
   iodev->get_buffer(iodev, &area1, &frames);
-  ASSERT_EQ(256, frames);
-  ASSERT_EQ(256, area1->frames);
-  area1_buf = area1->channels[0].buf;
+  ASSERT_EQ(1500, frames);
+  ASSERT_EQ(1500, area1->frames);
+  last_buf_head = area1->channels[0].buf;
+  iodev->put_buffer(iodev, 1000);
+  /* 1000 frames takes 8 encode call, FAKE_A2DP_CODE_SIZE / 4 = 128
+   * and 7 * 128 < 1000 < 8 * 128
+   */
+  EXPECT_EQ(8, a2dp_encode_called);
+  /* Expect flushed one block, leaving 1000 - 896 = 104 queued and
+   * next_flush_time has shifted. */
+  EXPECT_EQ(1, a2dp_write_index);
+  EXPECT_EQ(104, iodev->frames_queued(iodev, &tstamp));
+  EXPECT_GT(a2dpio->next_flush_time.tv_nsec, 0);
 
-  /* Test 100 frames(400 bytes) put and all processed. */
-  a2dp_encode_processed_bytes_val[0] = 400;
-  a2dp_write_index = 0;
-  a2dp_write_return_val[0] = 400;
-  iodev->put_buffer(iodev, 100);
-  write_callback(write_callback_data);
-  // Start with 4k frames.
-  EXPECT_EQ(400, pcm_buf_size_val[0]);
-
+  /* Assert buffer possition shifted 1000 * 4 bytes */
+  frames = 1000;
   iodev->get_buffer(iodev, &area2, &frames);
-  ASSERT_EQ(256, frames);
-  ASSERT_EQ(256, area2->frames);
+  ASSERT_EQ(1000, frames);
+  ASSERT_EQ(1000, area2->frames);
+  ASSERT_EQ(4000, area2->channels[0].buf - last_buf_head);
+  last_buf_head = area2->channels[0].buf;
 
-  /* Assert buf2 points to the same position as buf1 */
-  ASSERT_EQ(400, area2->channels[0].buf - area1_buf);
-
-  /* Test 100 frames(400 bytes) put, only 360 bytes processed,
-   * 40 bytes left in pcm buffer.
+  iodev->put_buffer(iodev, 700);
+  EXPECT_EQ(804, iodev->frames_queued(iodev, &tstamp));
+  /* Assert that even next_flush_time is not met, pcm data still processed.
+   * Expect to takes 7 more encode calls to process the 804 frames of data.
+   * and 6 * 128 < 804 < 7 * 128
    */
-  a2dp_encode_index = 0;
-  a2dp_encode_processed_bytes_val[0] = 360;
-  a2dp_encode_processed_bytes_val[1] = 0;
-  a2dp_write_index = 0;
-  a2dp_write_return_val[0] = 360;
-  a2dp_write_return_val[1] = 0;
-  iodev->put_buffer(iodev, 100);
-  write_callback(write_callback_data);
-  EXPECT_EQ(400, pcm_buf_size_val[0]);
-  ASSERT_EQ(40, pcm_buf_size_val[1]);
+  EXPECT_EQ(15, a2dp_encode_called);
+  EXPECT_EQ(768, a2dpio->a2dp.samples);
 
+  time_now.tv_nsec = 25000000;
+  frames = 50;
   iodev->get_buffer(iodev, &area3, &frames);
+  ASSERT_EQ(50, frames);
+  /* Assert buffer possition shifted 700 * 4 bytes */
+  EXPECT_EQ(2800, area3->channels[0].buf - last_buf_head);
 
-  /* Existing buffer not completed processed, assert new buffer starts from
-   * current write pointer.
-   */
-  ASSERT_EQ(256, frames);
-  EXPECT_EQ(800, area3->channels[0].buf - area1_buf);
+  iodev->put_buffer(iodev, 50);
+  /* 804 + 50 = 854 queued, 768 of them are encoded. */
+  EXPECT_EQ(854, iodev->frames_queued(iodev, &tstamp));
+  EXPECT_EQ(768, a2dpio->a2dp.samples);
+  /* Expect one a2dp encode call was executed for the left un-encoded frames.
+   * 854 - 768 = 86 < 128 */
+  EXPECT_EQ(16, a2dp_encode_called);
+  /* Even time now has passed next_flush_time, no a2dp write gets called
+   * because the number of encoded samples is not sufficient for a flush. */
+  EXPECT_EQ(1, a2dp_write_index);
 
   iodev->close_dev(iodev);
   a2dp_iodev_destroy(iodev);
@@ -243,8 +263,176 @@
   struct cras_audio_area* area;
   struct timespec tstamp;
   unsigned frames;
+  struct a2dp_io* a2dpio;
 
   iodev = a2dp_iodev_create(fake_transport);
+  a2dpio = (struct a2dp_io*)iodev;
+
+  iodev_set_format(iodev, &format);
+  time_now.tv_sec = 0;
+  time_now.tv_nsec = 0;
+  iodev->configure_dev(iodev);
+  ASSERT_NE(write_callback, (void*)NULL);
+  /* a2dp_block_size(mtu) / format_bytes
+   * (950 - 13) / 128 * 512 / 4 = 896 */
+  EXPECT_EQ(896, a2dpio->write_block);
+
+  iodev->start(iodev);
+  iodev->state = CRAS_IODEV_STATE_NORMAL_RUN;
+
+  frames = 256;
+  iodev->get_buffer(iodev, &area, &frames);
+  ASSERT_EQ(256, frames);
+  ASSERT_EQ(256, area->frames);
+
+  /* Data less than write_block hence not written. */
+  iodev->put_buffer(iodev, 200);
+  EXPECT_EQ(200, iodev->frames_queued(iodev, &tstamp));
+  EXPECT_EQ(tstamp.tv_sec, time_now.tv_sec);
+  EXPECT_EQ(tstamp.tv_nsec, time_now.tv_nsec);
+
+  /* 200 + 800 - 896 = 104 */
+  a2dp_write_return_val[0] = 0;
+  frames = 800;
+  iodev->get_buffer(iodev, &area, &frames);
+  iodev->put_buffer(iodev, 800);
+  EXPECT_EQ(104, iodev->frames_queued(iodev, &tstamp));
+
+  /* Some time has passed, same amount of frames are queued. */
+  time_now.tv_nsec = 15000000;
+  write_callback(write_callback_data, POLLOUT);
+  EXPECT_EQ(104, iodev->frames_queued(iodev, &tstamp));
+
+  /* Put 900 more frames. next_flush_time not yet passed so expect
+   * total 900 + 104 = 1004 are queued. */
+  frames = 900;
+  iodev->get_buffer(iodev, &area, &frames);
+  iodev->put_buffer(iodev, 900);
+  EXPECT_EQ(1004, iodev->frames_queued(iodev, &tstamp));
+
+  /* Time passes next_flush_time, 1004 + 300 - 896 = 408 */
+  time_now.tv_nsec = 25000000;
+  frames = 300;
+  iodev->get_buffer(iodev, &area, &frames);
+  iodev->put_buffer(iodev, 300);
+  EXPECT_EQ(408, iodev->frames_queued(iodev, &tstamp));
+
+  iodev->close_dev(iodev);
+  a2dp_iodev_destroy(iodev);
+}
+
+TEST_F(A2dpIodev, SleepTimeWithWriteThrottle) {
+  struct cras_iodev* iodev;
+  struct cras_audio_area* area;
+  unsigned frames;
+  unsigned int level;
+  unsigned long target;
+  struct timespec tstamp;
+  struct a2dp_io* a2dpio;
+
+  iodev = a2dp_iodev_create(fake_transport);
+  a2dpio = (struct a2dp_io*)iodev;
+
+  iodev_set_format(iodev, &format);
+  iodev->configure_dev(iodev);
+  ASSERT_NE(write_callback, (void*)NULL);
+  /* a2dp_block_size(mtu) / format_bytes
+   * 900 / 128 * 512 / 4 = 896 */
+  EXPECT_EQ(896, a2dpio->write_block);
+
+  iodev->start(iodev);
+  iodev->state = CRAS_IODEV_STATE_NORMAL_RUN;
+
+  /* Both time now and next_flush_time are at 0. Expect write_block of
+   * time to sleep */
+  EXPECT_EQ(a2dpio->write_block,
+            iodev->frames_to_play_in_sleep(iodev, &level, &tstamp));
+
+  /* Fake that 1000 frames are put and one block got flushed.
+   * Expect next_wake_time be fast forward by one flush_period. */
+  frames = 1000;
+  iodev->get_buffer(iodev, &area, &frames);
+  ASSERT_EQ(1000, frames);
+  ASSERT_EQ(1000, area->frames);
+
+  /* Expect the first block be flushed at time 0. */
+  time_now.tv_nsec = 0;
+  a2dp_write_return_val[0] = 0;
+  EXPECT_EQ(0, iodev->put_buffer(iodev, 1000));
+  EXPECT_EQ(104, iodev->frames_queued(iodev, &tstamp)); /* 1000 - 896 */
+
+  /* Same amount of frames are queued after some time has passed. */
+  time_now.tv_nsec = 10000000;
+  EXPECT_EQ(104, iodev->frames_queued(iodev, &tstamp));
+
+  /* Expect to sleep the time between now(10ms) and next_flush_time(~20.3ms). */
+  frames = iodev->frames_to_play_in_sleep(iodev, &level, &tstamp);
+  target =
+      a2dpio->write_block - time_now.tv_nsec * format.frame_rate / 1000000000;
+  EXPECT_GE(frames + 1, target);
+  EXPECT_GE(target + 1, frames);
+
+  /* Time now has passed the next flush time(~20.3ms), expect to return
+   * write_block of time to sleep. */
+  time_now.tv_nsec = 25000000;
+  EXPECT_EQ(a2dpio->write_block,
+            iodev->frames_to_play_in_sleep(iodev, &level, &tstamp));
+
+  a2dp_write_return_val[1] = 0;
+  frames = 1000;
+  iodev->get_buffer(iodev, &area, &frames);
+  EXPECT_EQ(0, iodev->put_buffer(iodev, 1000));
+  EXPECT_EQ(208, iodev->frames_queued(iodev, &tstamp)); /* 104 + 1000 - 896 */
+
+  /* Flush another write_block of data, next_wake_time fast forward by
+   * another flush_period. Expect to sleep the time between now(25ms)
+   * and next_flush_time(~40.6ms). */
+  frames = iodev->frames_to_play_in_sleep(iodev, &level, &tstamp);
+  target = a2dpio->write_block * 2 -
+           time_now.tv_nsec * format.frame_rate / 1000000000;
+  EXPECT_GE(frames + 1, target);
+  EXPECT_GE(target + 1, frames);
+
+  /* Put 1000 more frames, and make a fake failure to this flush. */
+  time_now.tv_nsec = 45000000;
+  a2dp_write_return_val[2] = -EAGAIN;
+  frames = 1000;
+  iodev->get_buffer(iodev, &area, &frames);
+  EXPECT_EQ(0, iodev->put_buffer(iodev, 1000));
+
+  /* Last a2dp write call failed with -EAGAIN, time now(45ms) is after
+   * next_flush_time. Expect to return exact |write_block| equivalant
+   * of time to sleep. */
+  EXPECT_EQ(1208, iodev->frames_queued(iodev, &tstamp)); /* 208 + 1000 */
+  EXPECT_EQ(a2dpio->write_block,
+            iodev->frames_to_play_in_sleep(iodev, &level, &tstamp));
+
+  /* Fake the event that socket becomes writable so data continues to flush.
+   * next_flush_time fast forwards by another flush_period. */
+  a2dp_write_return_val[3] = 0;
+  write_callback(write_callback_data, POLLOUT);
+  EXPECT_EQ(312, iodev->frames_queued(iodev, &tstamp)); /* 1208 - 896 */
+
+  /* Expect to sleep the time between now and next_flush_time(~60.9ms). */
+  frames = iodev->frames_to_play_in_sleep(iodev, &level, &tstamp);
+  target = a2dpio->write_block * 3 -
+           time_now.tv_nsec * format.frame_rate / 1000000000;
+  EXPECT_GE(frames + 1, target);
+  EXPECT_GE(target + 1, frames);
+
+  iodev->close_dev(iodev);
+  a2dp_iodev_destroy(iodev);
+}
+
+TEST_F(A2dpIodev, EnableThreadCallbackAtBufferFull) {
+  struct cras_iodev* iodev;
+  struct cras_audio_area* area;
+  struct timespec tstamp;
+  unsigned frames;
+  struct a2dp_io* a2dpio;
+
+  iodev = a2dp_iodev_create(fake_transport);
+  a2dpio = (struct a2dp_io*)iodev;
 
   iodev_set_format(iodev, &format);
   time_now.tv_sec = 0;
@@ -252,56 +440,38 @@
   iodev->configure_dev(iodev);
   ASSERT_NE(write_callback, (void*)NULL);
 
-  frames = 256;
+  iodev->start(iodev);
+  iodev->state = CRAS_IODEV_STATE_NORMAL_RUN;
+
+  audio_thread_config_events_callback_called = 0;
+  a2dp_write_return_val[0] = 0;
+  frames = iodev->buffer_size;
   iodev->get_buffer(iodev, &area, &frames);
-  ASSERT_EQ(256, frames);
-  ASSERT_EQ(256, area->frames);
+  EXPECT_LE(frames, iodev->buffer_size);
+  EXPECT_EQ(0, iodev->put_buffer(iodev, frames));
+  EXPECT_EQ(1, a2dp_write_index);
+  EXPECT_EQ(a2dpio->flush_period.tv_nsec, a2dpio->next_flush_time.tv_nsec);
+  EXPECT_EQ(1, audio_thread_config_events_callback_called);
+  EXPECT_EQ(TRIGGER_NONE, audio_thread_config_events_callback_trigger);
 
-  /* Put 100 frames, proccessed 400 bytes to a2dp buffer.
-   * Assume 200 bytes written out, queued 50 frames in a2dp buffer.
-   */
-  a2dp_encode_processed_bytes_val[0] = 400;
-  a2dp_write_return_val[0] = 50;
-  a2dp_queued_frames_val = 50;
-  time_now.tv_sec = 0;
+  /* Fastfoward time 1ms, not yet reaches the next flush time. */
   time_now.tv_nsec = 1000000;
-  iodev->put_buffer(iodev, 200);
-  write_callback(write_callback_data);
-  EXPECT_EQ(200, iodev->frames_queued(iodev, &tstamp));
-  EXPECT_EQ(tstamp.tv_sec, time_now.tv_sec);
-  EXPECT_EQ(tstamp.tv_nsec, time_now.tv_nsec);
 
-  /* After writing another 200 frames, check for correct buffer level. */
-  time_now.tv_sec = 0;
-  time_now.tv_nsec = 2000000;
-  a2dp_encode_index = 0;
-  a2dp_write_index = 0;
-  a2dp_encode_processed_bytes_val[0] = 400;
-  write_callback(write_callback_data);
-  /* 1000000 nsec has passed, estimated queued frames adjusted by 44 */
-  EXPECT_EQ(156, iodev->frames_queued(iodev, &tstamp));
-  EXPECT_EQ(400, pcm_buf_size_val[0]);
-  EXPECT_EQ(tstamp.tv_sec, time_now.tv_sec);
-  EXPECT_EQ(tstamp.tv_nsec, time_now.tv_nsec);
+  /* Cram into iodev as much data as possible. Expect its buffer to
+   * be full because flush time does not yet met. */
+  frames = iodev->buffer_size;
+  iodev->get_buffer(iodev, &area, &frames);
+  EXPECT_LE(frames, iodev->buffer_size);
+  EXPECT_EQ(0, iodev->put_buffer(iodev, frames));
+  frames = iodev->frames_queued(iodev, &tstamp);
+  EXPECT_EQ(frames, iodev->buffer_size);
 
-  /* Queued frames and new put buffer are all written */
-  a2dp_encode_processed_bytes_val[0] = 400;
-  a2dp_encode_processed_bytes_val[1] = 0;
-  a2dp_encode_index = 0;
-  a2dp_write_return_val[0] = 400;
-  a2dp_write_return_val[1] = -EAGAIN;
-  a2dp_write_index = 0;
+  /* Expect a2dp_write didn't get called in last get/put buffer. And
+   * audio thread callback has been enabled. */
+  EXPECT_EQ(1, a2dp_write_index);
+  EXPECT_EQ(2, audio_thread_config_events_callback_called);
+  EXPECT_EQ(TRIGGER_WAKEUP, audio_thread_config_events_callback_trigger);
 
-  /* Add wnother 200 samples, get back to the original level. */
-  time_now.tv_sec = 0;
-  time_now.tv_nsec = 50000000;
-  a2dp_encode_processed_bytes_val[0] = 600;
-  a2dp_queued_frames_val = 50;
-  iodev->put_buffer(iodev, 200);
-  EXPECT_EQ(800, pcm_buf_size_val[0]);
-  EXPECT_EQ(100, iodev->frames_queued(iodev, &tstamp));
-  EXPECT_EQ(tstamp.tv_sec, time_now.tv_sec);
-  EXPECT_EQ(tstamp.tv_nsec, time_now.tv_nsec);
   iodev->close_dev(iodev);
   a2dp_iodev_destroy(iodev);
 }
@@ -315,77 +485,231 @@
   iodev = a2dp_iodev_create(fake_transport);
 
   iodev_set_format(iodev, &format);
-  time_now.tv_sec = 0;
-  time_now.tv_nsec = 0;
   iodev->configure_dev(iodev);
   ASSERT_NE(write_callback, (void*)NULL);
 
-  ASSERT_EQ(iodev->min_buffer_level, 400);
+  /* (950 - 13)/ 128 * 512 / 4 */
+  ASSERT_EQ(iodev->min_buffer_level, 896);
 
-  frames = 700;
+  iodev->start(iodev);
+  iodev->state = CRAS_IODEV_STATE_NORMAL_RUN;
+
+  frames = 1500;
   iodev->get_buffer(iodev, &area, &frames);
-  ASSERT_EQ(700, frames);
-  ASSERT_EQ(700, area->frames);
+  ASSERT_EQ(1500, frames);
+  ASSERT_EQ(1500, area->frames);
 
-  /* First call to a2dp_encode() processed 800 bytes. */
-  a2dp_encode_processed_bytes_val[0] = 800;
-  a2dp_encode_processed_bytes_val[1] = 0;
-  a2dp_write_return_val[0] = 200;
-
-  /* put_buffer shouldn't trigger the 2nd call to a2dp_encode() because
-   * buffer is low. Fake some data to make sure this test case will fail
-   * when a2dp_encode() called twice.
+  /*
+   * Assert put_buffer shouldn't trigger the 2nd call to a2dp_encode()
+   * because buffer is low: 896 < 1500 < 896 * 2
    */
-  a2dp_encode_processed_bytes_val[2] = 800;
-  a2dp_encode_processed_bytes_val[3] = 0;
-  a2dp_write_return_val[1] = -EAGAIN;
+  a2dp_write_return_val[0] = 0;
+  EXPECT_EQ(0, iodev->put_buffer(iodev, 1500));
+  EXPECT_EQ(1, a2dp_write_index);
 
-  time_now.tv_nsec = 10000000;
-  iodev->put_buffer(iodev, 700);
-
-  time_now.tv_nsec = 20000000;
-  EXPECT_EQ(500, iodev->frames_queued(iodev, &tstamp));
+  /* 1500 - 896 */
+  time_now.tv_nsec = 25000000;
+  EXPECT_EQ(604, iodev->frames_queued(iodev, &tstamp));
   EXPECT_EQ(tstamp.tv_sec, time_now.tv_sec);
   EXPECT_EQ(tstamp.tv_nsec, time_now.tv_nsec);
   iodev->close_dev(iodev);
   a2dp_iodev_destroy(iodev);
 }
 
-TEST_F(A2dpIodev, NoStreamState) {
+TEST_F(A2dpIodev, HandleUnderrun) {
   struct cras_iodev* iodev;
   struct cras_audio_area* area;
   struct timespec tstamp;
   unsigned frames;
 
   iodev = a2dp_iodev_create(fake_transport);
+
+  iodev_set_format(iodev, &format);
+  time_now.tv_sec = 0;
+  time_now.tv_nsec = 0;
+  iodev->configure_dev(iodev);
+  /* (950 - 13) / 128 * 512 / 4 */
+  EXPECT_EQ(896, iodev->min_buffer_level);
+
+  iodev->start(iodev);
+  iodev->state = CRAS_IODEV_STATE_NORMAL_RUN;
+
+  frames = 300;
+  iodev->get_buffer(iodev, &area, &frames);
+  ASSERT_EQ(300, frames);
+  ASSERT_EQ(300, area->frames);
+  a2dp_write_return_val[0] = -EAGAIN;
+
+  time_now.tv_nsec = 10000000;
+  iodev->put_buffer(iodev, 300);
+
+  time_now.tv_nsec = 20000000;
+  EXPECT_EQ(300, iodev->frames_queued(iodev, &tstamp));
+
+  /* Frames queued below min_buffer_level, which is derived from transport MTU.
+   * Assert min_cb_level of zero frames are filled. */
+  iodev->min_cb_level = 150;
+  iodev->output_underrun(iodev);
+  ASSERT_EQ(1, cras_iodev_fill_odev_zeros_called);
+  EXPECT_EQ(150, cras_iodev_fill_odev_zeros_frames);
+
+  iodev->close_dev(iodev);
+  a2dp_iodev_destroy(iodev);
+}
+
+TEST_F(A2dpIodev, LeavingNoStreamStateWithSmallStreamDoesntUnderrun) {
+  struct cras_iodev* iodev;
+  struct cras_audio_area* area;
+  struct timespec tstamp;
+  unsigned frames;
+  struct a2dp_io* a2dpio;
+
+  iodev = a2dp_iodev_create(fake_transport);
+  a2dpio = (struct a2dp_io*)iodev;
+
   iodev_set_format(iodev, &format);
   time_now.tv_sec = 0;
   time_now.tv_nsec = 0;
   iodev->configure_dev(iodev);
   ASSERT_NE(write_callback, (void*)NULL);
-  ASSERT_EQ(400, iodev->min_buffer_level);
+  /* (950 - 13)/ 128 * 512 / 4 */
+  ASSERT_EQ(896, iodev->min_buffer_level);
+
+  iodev->start(iodev);
+  iodev->state = CRAS_IODEV_STATE_NORMAL_RUN;
+
+  /* Put iodev in no_stream state. Verify it doesn't underrun after each
+   * call of no_stream ops. */
+  a2dp_write_return_val[0] = 0;
+  iodev->no_stream(iodev, 1);
+  EXPECT_EQ(1, a2dp_write_index);
+  EXPECT_EQ(a2dpio->flush_period.tv_nsec, a2dpio->next_flush_time.tv_nsec);
+  frames = iodev->frames_queued(iodev, &tstamp);
+  EXPECT_LE(iodev->min_buffer_level, frames);
+
+  /* Some time has passed and a small stream of 200 frames block is added.
+   * Verify leaving no_stream state doesn't underrun immediately. */
+  time_now.tv_nsec = 20000000;
+  iodev->no_stream(iodev, 1);
+  frames = 200;
+  iodev->get_buffer(iodev, &area, &frames);
+  iodev->put_buffer(iodev, 200);
+  frames = iodev->frames_queued(iodev, &tstamp);
+  EXPECT_LE(iodev->min_buffer_level, frames);
+
+  iodev->close_dev(iodev);
+  a2dp_iodev_destroy(iodev);
+}
+
+TEST_F(A2dpIodev, NoStreamStateFillZerosToTargetLevel) {
+  struct cras_iodev* iodev;
+  struct cras_audio_area* area;
+  struct timespec tstamp;
+  unsigned frames;
+  struct a2dp_io* a2dpio;
+
+  iodev = a2dp_iodev_create(fake_transport);
+  a2dpio = (struct a2dp_io*)iodev;
+
+  iodev_set_format(iodev, &format);
+  time_now.tv_sec = 0;
+  time_now.tv_nsec = 0;
+  iodev->configure_dev(iodev);
+  ASSERT_NE(write_callback, (void*)NULL);
+  /* (950 - 13)/ 128 * 512 / 4 */
+  ASSERT_EQ(896, iodev->min_buffer_level);
+
+  iodev->start(iodev);
+  iodev->state = CRAS_IODEV_STATE_NORMAL_RUN;
 
   iodev->min_cb_level = 480;
   frames = 200;
   iodev->get_buffer(iodev, &area, &frames);
   iodev->put_buffer(iodev, 200);
 
+  a2dp_write_return_val[0] = 0;
   iodev->no_stream(iodev, 1);
-  EXPECT_EQ(1, cras_iodev_frames_queued_called);
+  EXPECT_EQ(1, a2dp_write_index);
+  EXPECT_EQ(a2dpio->flush_period.tv_nsec, a2dpio->next_flush_time.tv_nsec);
 
-  /* no_stream will fill the buffer to hw_level = (441 (44100 * 0.01)) * 2
-   * frames, but 200 < min_buffer_level so cras_iodev_frames_queued will return
-   * 0 in no_stream and no_stream will fill 882 frames to device buffer.
-   */
+  /* Some time has passed but not yet reach next flush. Entering no_stream
+   * fills buffer to 3 times of min_buffer_level. */
+  time_now.tv_nsec = 10000000;
+  iodev->no_stream(iodev, 1);
   frames = iodev->frames_queued(iodev, &tstamp);
-  ASSERT_EQ(1082, frames);
+  EXPECT_EQ(3 * iodev->min_buffer_level, frames);
 
-  /* After leaving no stream state, output buffer won't be adjusted */
+  /* Time has passed next flush time, expect one block is flushed.  */
+  a2dp_write_return_val[1] = 0;
+  time_now.tv_nsec = 25000000;
+  iodev->no_stream(iodev, 1);
+  frames = iodev->frames_queued(iodev, &tstamp);
+  ASSERT_EQ(2 * iodev->min_buffer_level, frames);
+  EXPECT_EQ(2, a2dp_write_index);
+
+  /* Leaving no_stream state fills buffer level back to  2 * min_buffer_level.
+   */
+  a2dp_write_return_val[2] = 0;
+  time_now.tv_nsec = 30000000;
   iodev->no_stream(iodev, 0);
   frames = iodev->frames_queued(iodev, &tstamp);
-  ASSERT_EQ(1082, frames);
+  ASSERT_EQ(2 * iodev->min_buffer_level, frames);
+  EXPECT_EQ(2, a2dp_write_index);
+
+  iodev->close_dev(iodev);
+  a2dp_iodev_destroy(iodev);
 }
 
+TEST_F(A2dpIodev, EnterNoStreamStateAtHighBufferLevelDoesntFillMore) {
+  struct cras_iodev* iodev;
+  struct cras_audio_area* area;
+  struct timespec tstamp;
+  unsigned frames, start_level;
+  struct a2dp_io* a2dpio;
+
+  iodev = a2dp_iodev_create(fake_transport);
+  a2dpio = (struct a2dp_io*)iodev;
+
+  iodev_set_format(iodev, &format);
+  time_now.tv_sec = 0;
+  time_now.tv_nsec = 0;
+  iodev->configure_dev(iodev);
+  ASSERT_NE(write_callback, (void*)NULL);
+  /* (950 - 13)/ 128 * 512 / 4 */
+  ASSERT_EQ(896, iodev->min_buffer_level);
+
+  iodev->start(iodev);
+  iodev->state = CRAS_IODEV_STATE_NORMAL_RUN;
+
+  a2dp_write_return_val[0] = 0;
+  start_level = 6000;
+  frames = start_level;
+  iodev->get_buffer(iodev, &area, &frames);
+  iodev->put_buffer(iodev, frames);
+  frames = iodev->frames_queued(iodev, &tstamp);
+  /* Assert one block has fluxhed */
+  EXPECT_EQ(start_level - iodev->min_buffer_level, frames);
+  EXPECT_EQ(1, a2dp_write_index);
+  EXPECT_EQ(a2dpio->flush_period.tv_nsec, a2dpio->next_flush_time.tv_nsec);
+
+  a2dp_write_return_val[1] = 0;
+  time_now.tv_nsec = 25000000;
+  iodev->no_stream(iodev, 1);
+  frames = iodev->frames_queued(iodev, &tstamp);
+  /* Next flush time meets requirement so another block is flushed. */
+  ASSERT_EQ(start_level - 2 * iodev->min_buffer_level, frames);
+
+  a2dp_write_return_val[2] = 0;
+  time_now.tv_nsec = 50000000;
+  iodev->no_stream(iodev, 1);
+  frames = iodev->frames_queued(iodev, &tstamp);
+  /* Another block flushed at leaving no stream state. No more data
+   * filled because level is high. */
+  ASSERT_EQ(start_level - 3 * iodev->min_buffer_level, frames);
+
+  iodev->close_dev(iodev);
+  a2dp_iodev_destroy(iodev);
+}
 }  // namespace
 
 int main(int argc, char** argv) {
@@ -398,6 +722,7 @@
 int cras_bt_transport_configuration(const struct cras_bt_transport* transport,
                                     void* configuration,
                                     int len) {
+  memset(configuration, 0, len);
   cras_bt_transport_configuration_called++;
   return 0;
 }
@@ -478,6 +803,10 @@
   return "/org/bluez/hci0/dev_1A_2B_3C_4D_5E_6F";
 }
 
+int cras_bt_device_get_stable_id(const struct cras_bt_device* device) {
+  return 123;
+}
+
 void cras_bt_device_append_iodev(struct cras_bt_device* device,
                                  struct cras_iodev* iodev,
                                  enum cras_bt_device_profile profile) {
@@ -497,13 +826,18 @@
   return 0;
 }
 
-int cras_bt_device_schedule_suspend(struct cras_bt_device* device,
-                                    unsigned int msec) {
+int cras_bt_device_schedule_suspend(
+    struct cras_bt_device* device,
+    unsigned int msec,
+    enum cras_bt_device_suspend_reason suspend_reason) {
   return 0;
 }
 
 int init_a2dp(struct a2dp_info* a2dp, a2dp_sbc_t* sbc) {
   init_a2dp_called++;
+  memset(a2dp, 0, sizeof(*a2dp));
+  a2dp->frame_length = FAKE_A2DP_FRAME_LENGTH;
+  a2dp->codesize = FAKE_A2DP_CODE_SIZE;
   return init_a2dp_return_val;
 }
 
@@ -512,22 +846,20 @@
 }
 
 int a2dp_codesize(struct a2dp_info* a2dp) {
-  return 512;
+  return a2dp->codesize;
 }
 
 int a2dp_block_size(struct a2dp_info* a2dp, int encoded_bytes) {
-  a2dp_block_size_called++;
-
-  // Assumes a2dp block size is 1:1 before/after encode.
-  return encoded_bytes;
+  return encoded_bytes / a2dp->frame_length * a2dp->codesize;
 }
 
-int a2dp_queued_frames(struct a2dp_info* a2dp) {
-  return a2dp_queued_frames_val;
+int a2dp_queued_frames(const struct a2dp_info* a2dp) {
+  return a2dp->samples;
 }
 
-void a2dp_drain(struct a2dp_info* a2dp) {
-  drain_a2dp_called++;
+void a2dp_reset(struct a2dp_info* a2dp) {
+  a2dp_reset_called++;
+  a2dp->samples = 0;
 }
 
 int a2dp_encode(struct a2dp_info* a2dp,
@@ -535,19 +867,34 @@
                 int pcm_buf_size,
                 int format_bytes,
                 size_t link_mtu) {
-  unsigned int processed;
+  int processed = 0;
+  a2dp_encode_called++;
 
-  if (a2dp_encode_index == MAX_A2DP_ENCODE_CALLS)
+  if (a2dp->a2dp_buf_used + a2dp->frame_length > link_mtu)
     return 0;
-  processed = a2dp_encode_processed_bytes_val[a2dp_encode_index];
-  pcm_buf_size_val[a2dp_encode_index] = pcm_buf_size;
-  a2dp_encode_index++;
+  if (pcm_buf_size < a2dp->codesize)
+    return 0;
+
+  processed += a2dp->codesize;
+  a2dp->a2dp_buf_used += a2dp->frame_length;
+  a2dp->samples += processed / format_bytes;
+
   return processed;
 }
 
 int a2dp_write(struct a2dp_info* a2dp, int stream_fd, size_t link_mtu) {
-  return a2dp_write_return_val[a2dp_write_index++];
-  ;
+  int ret, samples;
+  if (a2dp->frame_length + a2dp->a2dp_buf_used < link_mtu)
+    return 0;
+
+  ret = a2dp_write_return_val[a2dp_write_index++];
+  if (ret < 0)
+    return ret;
+
+  samples = a2dp->samples;
+  a2dp->samples = 0;
+  a2dp->a2dp_buf_used = 0;
+  return samples;
 }
 
 int clock_gettime(clockid_t clk_id, struct timespec* tp) {
@@ -556,38 +903,40 @@
 }
 
 void cras_iodev_init_audio_area(struct cras_iodev* iodev, int num_channels) {
-  iodev->area = dummy_audio_area;
+  iodev->area = mock_audio_area;
 }
 
 void cras_iodev_free_audio_area(struct cras_iodev* iodev) {}
 
-int cras_iodev_frames_queued(struct cras_iodev* iodev,
-                             struct timespec* hw_tstamp) {
-  int rc;
-  cras_iodev_frames_queued_called++;
-  rc = iodev->frames_queued(iodev, hw_tstamp);
-  if (rc < 0)
-    return 0;
-  unsigned int num_queued = (unsigned int)rc;
-  if (num_queued < iodev->min_buffer_level)
-    return 0;
+int cras_iodev_fill_odev_zeros(struct cras_iodev* odev, unsigned int frames) {
+  struct cras_audio_area* area;
+  cras_iodev_fill_odev_zeros_called++;
+  cras_iodev_fill_odev_zeros_frames = frames;
 
-  return num_queued - iodev->min_buffer_level;
+  odev->get_buffer(odev, &area, &frames);
+  odev->put_buffer(odev, frames);
+  return 0;
 }
 
 void cras_audio_area_config_buf_pointers(struct cras_audio_area* area,
                                          const struct cras_audio_format* fmt,
                                          uint8_t* base_buffer) {
-  dummy_audio_area->channels[0].buf = base_buffer;
+  mock_audio_area->channels[0].buf = base_buffer;
 }
 
 struct audio_thread* cras_iodev_list_get_audio_thread() {
   return NULL;
 }
+// From ewma_power
+void ewma_power_disable(struct ewma_power* ewma) {}
+
 // From audio_thread
 struct audio_thread_event_log* atlog;
 
-void audio_thread_add_write_callback(int fd, thread_callback cb, void* data) {
+void audio_thread_add_events_callback(int fd,
+                                      thread_callback cb,
+                                      void* data,
+                                      int events) {
   write_callback = cb;
   write_callback_data = data;
 }
@@ -596,5 +945,18 @@
   return 0;
 }
 
-void audio_thread_enable_callback(int fd, int enabled) {}
+void audio_thread_config_events_callback(
+    int fd,
+    enum AUDIO_THREAD_EVENTS_CB_TRIGGER trigger) {
+  audio_thread_config_events_callback_called++;
+  audio_thread_config_events_callback_trigger = trigger;
+}
+}
+
+int cras_audio_thread_event_a2dp_overrun() {
+  return 0;
+}
+
+int cras_audio_thread_event_a2dp_throttle() {
+  return 0;
 }
diff --git a/cras/src/tests/alsa_card_unittest.cc b/cras/src/tests/alsa_card_unittest.cc
index dd75b1d..cfb6756 100644
--- a/cras/src/tests/alsa_card_unittest.cc
+++ b/cras/src/tests/alsa_card_unittest.cc
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 #include <gtest/gtest.h>
-#include <iniparser.h>
 #include <stdio.h>
 #include <sys/param.h>
 #include <syslog.h>
@@ -76,9 +75,10 @@
 static size_t snd_hctl_handle_events_called;
 static size_t iniparser_freedict_called;
 static size_t iniparser_load_called;
-static struct cras_device_blacklist* fake_blacklist;
-static int cras_device_blacklist_check_retval;
+static struct cras_device_blocklist* fake_blocklist;
+static int cras_device_blocklist_check_retval;
 static unsigned ucm_create_called;
+static char ucm_create_name[100];
 static unsigned ucm_destroy_called;
 static size_t ucm_get_dev_for_mixer_called;
 static size_t ucm_get_flag_called;
@@ -96,6 +96,8 @@
 static int cras_alsa_mixer_add_main_volume_control_by_name_called;
 static int cras_alsa_mixer_add_main_volume_control_by_name_return_value;
 static int ucm_get_echo_reference_dev_name_for_dev_called;
+static size_t cras_system_check_ignore_ucm_suffix_called;
+static bool cras_system_check_ignore_ucm_suffix_value;
 static const char* ucm_get_echo_reference_dev_name_for_dev_return_value[4];
 
 static void ResetStubData() {
@@ -145,9 +147,10 @@
   cras_system_rm_select_fd_values.clear();
   iniparser_freedict_called = 0;
   iniparser_load_called = 0;
-  fake_blacklist = reinterpret_cast<struct cras_device_blacklist*>(3);
-  cras_device_blacklist_check_retval = 0;
+  fake_blocklist = reinterpret_cast<struct cras_device_blocklist*>(3);
+  cras_device_blocklist_check_retval = 0;
   ucm_create_called = 0;
+  memset(ucm_create_name, 0, sizeof(ucm_get_flag_name));
   ucm_destroy_called = 0;
   ucm_get_dev_for_mixer_called = 0;
   ucm_get_flag_called = 0;
@@ -166,6 +169,8 @@
   cras_alsa_mixer_add_main_volume_control_by_name_called = 0;
   cras_alsa_mixer_add_main_volume_control_by_name_return_value = 0;
   ucm_get_echo_reference_dev_name_for_dev_called = 0;
+  cras_system_check_ignore_ucm_suffix_called = 0;
+  cras_system_check_ignore_ucm_suffix_value = 0;
   fake_dev1.nodes = NULL;
   fake_dev2.nodes = NULL;
   fake_dev3.nodes = NULL;
@@ -179,7 +184,7 @@
   ResetStubData();
   card_info.card_type = ALSA_CARD_TYPE_INTERNAL;
   card_info.card_index = 55;
-  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blacklist,
+  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blocklist,
                             NULL);
   EXPECT_EQ(static_cast<struct cras_alsa_card*>(NULL), c);
   EXPECT_EQ(snd_ctl_close_called, snd_ctl_open_called);
@@ -194,7 +199,7 @@
   card_info.card_type = ALSA_CARD_TYPE_INTERNAL;
   card_info.card_index = 0;
   cras_alsa_mixer_create_return = static_cast<struct cras_alsa_mixer*>(NULL);
-  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blacklist,
+  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blocklist,
                             NULL);
   EXPECT_EQ(static_cast<struct cras_alsa_card*>(NULL), c);
   EXPECT_EQ(snd_ctl_close_called, snd_ctl_open_called);
@@ -210,7 +215,7 @@
   card_info.card_type = ALSA_CARD_TYPE_INTERNAL;
   card_info.card_index = 0;
   snd_ctl_open_return = -1;
-  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blacklist,
+  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blocklist,
                             NULL);
   EXPECT_EQ(static_cast<struct cras_alsa_card*>(NULL), c);
   EXPECT_EQ(1, snd_ctl_open_called);
@@ -229,7 +234,7 @@
   snd_hctl_open_pointer_val = NULL;
   snd_hctl_open_return_value = -1;
 
-  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blacklist,
+  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blocklist,
                             NULL);
   EXPECT_NE(static_cast<struct cras_alsa_card*>(NULL), c);
   EXPECT_EQ(1, snd_ctl_open_called);
@@ -251,7 +256,7 @@
   card_info.card_index = 0;
   snd_hctl_load_return_value = -1;
 
-  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blacklist,
+  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blocklist,
                             NULL);
   EXPECT_EQ(static_cast<struct cras_alsa_card*>(NULL), c);
   EXPECT_EQ(1, snd_ctl_open_called);
@@ -277,7 +282,7 @@
   snd_hctl_poll_descriptors_fds = poll_fds;
   snd_hctl_poll_descriptors_num_fds = ARRAY_SIZE(poll_fds);
 
-  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blacklist,
+  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blocklist,
                             NULL);
   EXPECT_NE(static_cast<struct cras_alsa_card*>(NULL), c);
   EXPECT_EQ(1, snd_ctl_open_called);
@@ -312,7 +317,7 @@
   snd_hctl_poll_descriptors_fds = poll_fds;
   snd_hctl_poll_descriptors_num_fds = ARRAY_SIZE(poll_fds);
 
-  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blacklist,
+  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blocklist,
                             NULL);
   EXPECT_NE(static_cast<struct cras_alsa_card*>(NULL), c);
   EXPECT_EQ(snd_ctl_close_called, snd_ctl_open_called);
@@ -344,7 +349,7 @@
   card_info.card_type = ALSA_CARD_TYPE_INTERNAL;
   card_info.card_index = 0;
   snd_ctl_card_info_ret = -1;
-  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blacklist,
+  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blocklist,
                             NULL);
   EXPECT_EQ(static_cast<struct cras_alsa_card*>(NULL), c);
   EXPECT_EQ(1, snd_ctl_open_called);
@@ -360,7 +365,7 @@
   ResetStubData();
   card_info.card_type = ALSA_CARD_TYPE_INTERNAL;
   card_info.card_index = 1;
-  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blacklist,
+  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blocklist,
                             NULL);
   EXPECT_NE(static_cast<struct cras_alsa_card*>(NULL), c);
   EXPECT_EQ(snd_ctl_close_called, snd_ctl_open_called);
@@ -385,7 +390,7 @@
   snd_ctl_pcm_next_device_return_error = true;
   card_info.card_type = ALSA_CARD_TYPE_USB;
   card_info.card_index = 0;
-  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blacklist,
+  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blocklist,
                             NULL);
   EXPECT_EQ(static_cast<struct cras_alsa_card*>(NULL), c);
   EXPECT_EQ(cras_alsa_mixer_create_called, cras_alsa_mixer_destroy_called);
@@ -406,7 +411,7 @@
   snd_ctl_pcm_info_rets = info_rets;
   card_info.card_type = ALSA_CARD_TYPE_USB;
   card_info.card_index = 0;
-  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blacklist,
+  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blocklist,
                             NULL);
   EXPECT_NE(static_cast<struct cras_alsa_card*>(NULL), c);
   EXPECT_EQ(snd_ctl_close_called, snd_ctl_open_called);
@@ -431,7 +436,7 @@
   EXPECT_EQ(iniparser_load_called, iniparser_freedict_called);
 }
 
-TEST(AlsaCard, CreateOneOutputBlacklisted) {
+TEST(AlsaCard, CreateOneOutputBlocklisted) {
   struct cras_alsa_card* c;
   int dev_nums[] = {0};
   int info_rets[] = {0, -1};
@@ -443,10 +448,10 @@
   snd_ctl_pcm_info_rets_size = ARRAY_SIZE(info_rets);
   snd_ctl_pcm_info_rets = info_rets;
   alsa_iodev_has_hctl_jacks_return = 0;
-  cras_device_blacklist_check_retval = 1;
+  cras_device_blocklist_check_retval = 1;
   card_info.card_type = ALSA_CARD_TYPE_USB;
   card_info.card_index = 0;
-  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blacklist,
+  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blocklist,
                             NULL);
   EXPECT_NE(static_cast<struct cras_alsa_card*>(NULL), c);
   EXPECT_EQ(snd_ctl_close_called, snd_ctl_open_called);
@@ -476,7 +481,7 @@
   snd_ctl_pcm_info_rets = info_rets;
   card_info.card_type = ALSA_CARD_TYPE_INTERNAL;
   card_info.card_index = 0;
-  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blacklist,
+  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blocklist,
                             NULL);
   EXPECT_NE(static_cast<struct cras_alsa_card*>(NULL), c);
   EXPECT_EQ(snd_ctl_close_called, snd_ctl_open_called);
@@ -509,7 +514,7 @@
   snd_ctl_pcm_info_rets = info_rets;
   card_info.card_type = ALSA_CARD_TYPE_INTERNAL;
   card_info.card_index = 0;
-  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blacklist,
+  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blocklist,
                             NULL);
   EXPECT_NE(static_cast<struct cras_alsa_card*>(NULL), c);
   EXPECT_EQ(snd_ctl_close_called, snd_ctl_open_called);
@@ -542,7 +547,7 @@
   snd_ctl_pcm_info_rets = info_rets;
   card_info.card_type = ALSA_CARD_TYPE_INTERNAL;
   card_info.card_index = 0;
-  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blacklist,
+  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blocklist,
                             NULL);
   EXPECT_NE(static_cast<struct cras_alsa_card*>(NULL), c);
   EXPECT_EQ(snd_ctl_close_called, snd_ctl_open_called);
@@ -574,7 +579,7 @@
   snd_ctl_pcm_info_rets = info_rets;
   card_info.card_type = ALSA_CARD_TYPE_INTERNAL;
   card_info.card_index = 0;
-  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blacklist,
+  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blocklist,
                             NULL);
   EXPECT_NE(static_cast<struct cras_alsa_card*>(NULL), c);
   EXPECT_EQ(snd_ctl_close_called, snd_ctl_open_called);
@@ -606,7 +611,7 @@
   snd_ctl_pcm_info_rets = info_rets;
   card_info.card_type = ALSA_CARD_TYPE_INTERNAL;
   card_info.card_index = 0;
-  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blacklist,
+  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blocklist,
                             NULL);
   EXPECT_NE(static_cast<struct cras_alsa_card*>(NULL), c);
   EXPECT_EQ(snd_ctl_close_called, snd_ctl_open_called);
@@ -657,7 +662,7 @@
   DL_APPEND(ucm_get_coupled_mixer_names_return_value, mixer_name_1);
   DL_APPEND(ucm_get_coupled_mixer_names_return_value, mixer_name_2);
 
-  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blacklist,
+  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blocklist,
                             NULL);
 
   EXPECT_NE(static_cast<struct cras_alsa_card*>(NULL), c);
@@ -703,7 +708,7 @@
   card_info.card_index = 0;
   ucm_has_fully_specified_ucm_flag_return_value = 1;
   ucm_get_sections_return_value = NULL;
-  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blacklist,
+  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blocklist,
                             NULL);
   EXPECT_EQ(static_cast<struct cras_alsa_card*>(NULL), c);
   EXPECT_EQ(snd_ctl_close_called, snd_ctl_open_called);
@@ -746,7 +751,7 @@
   DL_APPEND(ucm_get_main_volume_names_return_value, mixer_name_1);
   DL_APPEND(ucm_get_main_volume_names_return_value, mixer_name_2);
 
-  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blacklist,
+  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blocklist,
                             NULL);
 
   EXPECT_EQ(static_cast<struct cras_alsa_card*>(NULL), c);
@@ -766,34 +771,81 @@
   EXPECT_EQ(iniparser_load_called, iniparser_freedict_called);
 }
 
+TEST(AlsaCard, TwoUCMSecionsDependentPCM) {
+  struct cras_alsa_card* c;
+  cras_alsa_card_info card_info;
+  struct ucm_section* sections = NULL;
+  struct ucm_section* section;
+
+  /* Create UCM so that MIC1 and MIC2 will be two nodes on the same iodev. */
+  section = ucm_section_create("MIC1", "hw:0,3", 0, -1, CRAS_STREAM_INPUT,
+                               "my-sound-card Headset Jack", "gpio");
+  DL_APPEND(sections, section);
+  section = ucm_section_create("MIC2", "hw:0,5", 0, 3, CRAS_STREAM_INPUT,
+                               "my-sound-card Headset Jack", "gpio");
+  DL_APPEND(sections, section);
+
+  ResetStubData();
+  int info_rets[] = {0, 0};
+  card_info.card_type = ALSA_CARD_TYPE_INTERNAL;
+  card_info.card_index = 0;
+  snd_ctl_pcm_info_rets_size = ARRAY_SIZE(info_rets);
+  snd_ctl_pcm_info_rets = info_rets;
+  ucm_has_fully_specified_ucm_flag_return_value = 1;
+  ucm_get_sections_return_value = sections;
+  ASSERT_NE(ucm_get_sections_return_value, (struct ucm_section*)NULL);
+
+  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blocklist,
+                            NULL);
+
+  EXPECT_NE(static_cast<struct cras_alsa_card*>(NULL), c);
+  EXPECT_EQ(snd_ctl_close_called, snd_ctl_open_called);
+  EXPECT_EQ(1, snd_ctl_card_info_called);
+  EXPECT_EQ(1, ucm_get_sections_called);
+  EXPECT_EQ(1, snd_ctl_pcm_info_called);
+  EXPECT_EQ(2, cras_alsa_mixer_add_controls_in_section_called);
+  EXPECT_EQ(1, cras_alsa_iodev_create_called);
+  EXPECT_EQ(2, cras_alsa_iodev_ucm_add_nodes_and_jacks_called);
+  EXPECT_EQ(1, cras_alsa_iodev_ucm_complete_init_called);
+
+  cras_alsa_card_destroy(c);
+  EXPECT_EQ(1, ucm_destroy_called);
+  EXPECT_EQ(1, cras_alsa_iodev_destroy_called);
+  EXPECT_EQ(cras_alsa_iodev_create_return[0], cras_alsa_iodev_destroy_arg);
+  EXPECT_EQ(cras_alsa_mixer_create_called, cras_alsa_mixer_destroy_called);
+  EXPECT_EQ(iniparser_load_called, iniparser_freedict_called);
+}
+
 struct ucm_section* GenerateUcmSections(void) {
   struct ucm_section* sections = NULL;
   struct ucm_section* section;
 
-  section = ucm_section_create("Headphone", 0, CRAS_STREAM_OUTPUT,
+  section = ucm_section_create("Headphone", "hw:0,1", 0, -1, CRAS_STREAM_OUTPUT,
                                "my-sound-card Headset Jack", "gpio");
   ucm_section_add_coupled(section, "HP-L", MIXER_NAME_VOLUME);
   ucm_section_add_coupled(section, "HP-R", MIXER_NAME_VOLUME);
   DL_APPEND(sections, section);
 
-  section = ucm_section_create("Speaker", 0, CRAS_STREAM_OUTPUT, NULL, NULL);
+  section = ucm_section_create("Speaker", "hw:0,1", 0, -1, CRAS_STREAM_OUTPUT,
+                               NULL, NULL);
   ucm_section_add_coupled(section, "SPK-L", MIXER_NAME_VOLUME);
   ucm_section_add_coupled(section, "SPK-R", MIXER_NAME_VOLUME);
   DL_APPEND(sections, section);
 
-  section =
-      ucm_section_create("Internal Mic", 0, CRAS_STREAM_INPUT, NULL, NULL);
+  section = ucm_section_create("Internal Mic", "hw:0,1", 0, -1,
+                               CRAS_STREAM_INPUT, NULL, NULL);
   ucm_section_add_coupled(section, "INT-MIC-L", MIXER_NAME_VOLUME);
   ucm_section_add_coupled(section, "INT-MIC-R", MIXER_NAME_VOLUME);
   DL_APPEND(sections, section);
 
-  section = ucm_section_create("Mic", 1, CRAS_STREAM_INPUT,
+  section = ucm_section_create("Mic", "hw:0,1", 1, -1, CRAS_STREAM_INPUT,
                                "my-sound-card Headset Jack", "gpio");
   ucm_section_add_coupled(section, "MIC-L", MIXER_NAME_VOLUME);
   ucm_section_add_coupled(section, "MIC-R", MIXER_NAME_VOLUME);
   DL_APPEND(sections, section);
 
-  section = ucm_section_create("HDMI", 2, CRAS_STREAM_OUTPUT, NULL, NULL);
+  section = ucm_section_create("HDMI", "hw:0,1", 2, -1, CRAS_STREAM_OUTPUT,
+                               NULL, NULL);
   ucm_section_set_mixer_name(section, "HDMI");
   DL_APPEND(sections, section);
 
@@ -813,7 +865,7 @@
 
   cras_alsa_mixer_add_controls_in_section_return_value = -EINVAL;
 
-  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blacklist,
+  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blocklist,
                             NULL);
 
   EXPECT_EQ(static_cast<struct cras_alsa_card*>(NULL), c);
@@ -850,7 +902,7 @@
   cras_alsa_iodev_index_return[cras_alsa_iodev_create_return[3]] = 2;
   ASSERT_NE(ucm_get_sections_return_value, (struct ucm_section*)NULL);
 
-  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blacklist,
+  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blocklist,
                             NULL);
 
   EXPECT_NE(static_cast<struct cras_alsa_card*>(NULL), c);
@@ -897,7 +949,7 @@
 
   ucm_get_echo_reference_dev_name_for_dev_return_value[0] = strdup(echo_ref);
 
-  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blacklist,
+  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blocklist,
                             NULL);
 
   EXPECT_NE(static_cast<struct cras_alsa_card*>(NULL), c);
@@ -905,6 +957,45 @@
   cras_alsa_card_destroy(c);
 }
 
+TEST(AlsaCard, UCMSuffix) {
+  struct cras_alsa_card* c;
+  cras_alsa_card_info card_info;
+  int info_rets[] = {0, 0, 0, 0, 0, -1};
+
+  ResetStubData();
+  card_info.card_type = ALSA_CARD_TYPE_INTERNAL;
+  card_info.card_index = 0;
+  snd_ctl_pcm_info_rets_size = ARRAY_SIZE(info_rets);
+  snd_ctl_pcm_info_rets = info_rets;
+  ucm_has_fully_specified_ucm_flag_return_value = 1;
+  ucm_get_sections_return_value = GenerateUcmSections();
+  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blocklist,
+                            "1mic");
+  EXPECT_EQ(0, strcmp(ucm_create_name, "TestName.1mic"));
+  EXPECT_EQ(1, cras_system_check_ignore_ucm_suffix_called);
+  cras_alsa_card_destroy(c);
+}
+
+TEST(AlsaCard, UCMIgnoreSuffix) {
+  struct cras_alsa_card* c;
+  cras_alsa_card_info card_info;
+  int info_rets[] = {0, 0, 0, 0, 0, -1};
+
+  ResetStubData();
+  card_info.card_type = ALSA_CARD_TYPE_INTERNAL;
+  card_info.card_index = 0;
+  snd_ctl_pcm_info_rets_size = ARRAY_SIZE(info_rets);
+  snd_ctl_pcm_info_rets = info_rets;
+  ucm_has_fully_specified_ucm_flag_return_value = 1;
+  ucm_get_sections_return_value = GenerateUcmSections();
+  cras_system_check_ignore_ucm_suffix_value = true;
+  c = cras_alsa_card_create(&card_info, device_config_dir, fake_blocklist,
+                            "1mic");
+  EXPECT_EQ(0, strcmp(ucm_create_name, "TestName"));
+  EXPECT_EQ(1, cras_system_check_ignore_ucm_suffix_called);
+  cras_alsa_card_destroy(c);
+}
+
 /* Stubs */
 
 extern "C" {
@@ -935,6 +1026,7 @@
 struct cras_iodev* alsa_iodev_create(size_t card_index,
                                      const char* card_name,
                                      size_t device_index,
+                                     const char* pcm_name,
                                      const char* dev_name,
                                      const char* dev_id,
                                      enum CRAS_ALSA_CARD_TYPE card_type,
@@ -1103,17 +1195,18 @@
   return NULL;
 }
 
-int cras_device_blacklist_check(struct cras_device_blacklist* blacklist,
+int cras_device_blocklist_check(struct cras_device_blocklist* blocklist,
                                 unsigned vendor_id,
                                 unsigned product_id,
                                 unsigned device_index) {
-  EXPECT_EQ(fake_blacklist, blacklist);
+  EXPECT_EQ(fake_blocklist, blocklist);
 
-  return cras_device_blacklist_check_retval;
+  return cras_device_blocklist_check_retval;
 }
 
 struct cras_use_case_mgr* ucm_create(const char* name) {
   ucm_create_called++;
+  strncpy(ucm_create_name, name, sizeof(ucm_create_name) - 1);
   return reinterpret_cast<struct cras_use_case_mgr*>(0x44);
 }
 
@@ -1171,6 +1264,11 @@
   return cras_alsa_mixer_add_controls_in_section_return_value;
 }
 
+bool cras_system_check_ignore_ucm_suffix(const char* card_name) {
+  cras_system_check_ignore_ucm_suffix_called++;
+  return cras_system_check_ignore_ucm_suffix_value;
+}
+
 void ucm_free_mixer_names(struct mixer_name* names) {
   struct mixer_name* m;
   DL_FOREACH (names, m) {
diff --git a/cras/src/tests/alsa_helpers_unittest.cc b/cras/src/tests/alsa_helpers_unittest.cc
index 0e8112c..32df30a 100644
--- a/cras/src/tests/alsa_helpers_unittest.cc
+++ b/cras/src/tests/alsa_helpers_unittest.cc
@@ -163,34 +163,12 @@
 }
 
 TEST(AlsaHelper, Htimestamp) {
-  snd_pcm_t* dummy_handle = reinterpret_cast<snd_pcm_t*>(0x1);
+  snd_pcm_t* mock_handle = reinterpret_cast<snd_pcm_t*>(0x1);
   snd_pcm_uframes_t used;
   snd_pcm_uframes_t severe_underrun_frames = 480;
   struct timespec tstamp;
-  int htimestamp_enabled = 1;
   const char* dev_name = "dev_name";
 
-  // Enable htimestamp use.
-  ResetStubData();
-  EXPECT_EQ(0, cras_alsa_set_swparams(dummy_handle, &htimestamp_enabled));
-  EXPECT_EQ(snd_pcm_sw_params_set_tstamp_mode_called, 1);
-  EXPECT_EQ(snd_pcm_sw_params_set_tstamp_type_called, 1);
-  EXPECT_EQ(1, htimestamp_enabled);
-
-  // Try to enable htimestamp use: not supported.
-  ResetStubData();
-  snd_pcm_sw_params_ret_vals.push_back(-EINVAL);
-  EXPECT_EQ(0, cras_alsa_set_swparams(dummy_handle, &htimestamp_enabled));
-  EXPECT_EQ(snd_pcm_sw_params_set_tstamp_mode_called, 2);
-  EXPECT_EQ(snd_pcm_sw_params_set_tstamp_type_called, 2);
-  EXPECT_EQ(0, htimestamp_enabled);
-
-  // Disable htimestamp use.
-  ResetStubData();
-  EXPECT_EQ(0, cras_alsa_set_swparams(dummy_handle, &htimestamp_enabled));
-  EXPECT_EQ(snd_pcm_sw_params_set_tstamp_mode_called, 0);
-  EXPECT_EQ(snd_pcm_sw_params_set_tstamp_type_called, 0);
-
   ResetStubData();
   tstamp.tv_sec = 0;
   tstamp.tv_nsec = 0;
@@ -198,7 +176,7 @@
   snd_pcm_htimestamp_tstamp_ret_val.tv_sec = 10;
   snd_pcm_htimestamp_tstamp_ret_val.tv_nsec = 10000;
 
-  cras_alsa_get_avail_frames(dummy_handle, 48000, severe_underrun_frames,
+  cras_alsa_get_avail_frames(mock_handle, 48000, severe_underrun_frames,
                              dev_name, &used, &tstamp);
   EXPECT_EQ(used, snd_pcm_htimestamp_avail_ret_val);
   EXPECT_EQ(tstamp.tv_sec, snd_pcm_htimestamp_tstamp_ret_val.tv_sec);
@@ -206,7 +184,7 @@
 }
 
 TEST(AlsaHelper, GetAvailFramesSevereUnderrun) {
-  snd_pcm_t* dummy_handle = reinterpret_cast<snd_pcm_t*>(0x1);
+  snd_pcm_t* mock_handle = reinterpret_cast<snd_pcm_t*>(0x1);
   snd_pcm_uframes_t avail;
   snd_pcm_uframes_t severe_underrun_frames = 480;
   snd_pcm_uframes_t buffer_size = 48000;
@@ -216,7 +194,7 @@
 
   ResetStubData();
   snd_pcm_htimestamp_avail_ret_val = buffer_size + severe_underrun_frames + 1;
-  rc = cras_alsa_get_avail_frames(dummy_handle, buffer_size,
+  rc = cras_alsa_get_avail_frames(mock_handle, buffer_size,
                                   severe_underrun_frames, dev_name, &avail,
                                   &tstamp);
   // Returns -EPIPE when severe underrun happens.
@@ -224,7 +202,7 @@
 
   ResetStubData();
   snd_pcm_htimestamp_avail_ret_val = buffer_size + severe_underrun_frames;
-  rc = cras_alsa_get_avail_frames(dummy_handle, buffer_size,
+  rc = cras_alsa_get_avail_frames(mock_handle, buffer_size,
                                   severe_underrun_frames, dev_name, &avail,
                                   &tstamp);
   // Underrun which is not severe enough will be masked.
@@ -234,7 +212,7 @@
 
   ResetStubData();
   snd_pcm_htimestamp_avail_ret_val = buffer_size - 1;
-  rc = cras_alsa_get_avail_frames(dummy_handle, buffer_size,
+  rc = cras_alsa_get_avail_frames(mock_handle, buffer_size,
                                   severe_underrun_frames, dev_name, &avail,
                                   &tstamp);
   // When avail < buffer_size, there is no underrun.
diff --git a/cras/src/tests/alsa_io_unittest.cc b/cras/src/tests/alsa_io_unittest.cc
index 2211f34..021b478 100644
--- a/cras/src/tests/alsa_io_unittest.cc
+++ b/cras/src/tests/alsa_io_unittest.cc
@@ -32,6 +32,7 @@
 static uint8_t* cras_alsa_mmap_begin_buffer;
 static size_t cras_alsa_mmap_begin_frames;
 static size_t cras_alsa_fill_properties_called;
+static bool cras_alsa_support_8_channels;
 static size_t alsa_mixer_set_dBFS_called;
 static int alsa_mixer_set_dBFS_value;
 static const struct mixer_control* alsa_mixer_set_dBFS_output;
@@ -49,8 +50,6 @@
     cras_alsa_mixer_get_control_for_section_return_value;
 static size_t sys_get_volume_called;
 static size_t sys_get_volume_return_value;
-static size_t sys_get_capture_gain_called;
-static long sys_get_capture_gain_return_value;
 static size_t alsa_mixer_set_mute_called;
 static int alsa_mixer_set_mute_value;
 static size_t alsa_mixer_get_dB_range_called;
@@ -77,7 +76,6 @@
 static std::vector<int> cras_alsa_mixer_set_output_active_state_values;
 static cras_audio_format* fake_format;
 static size_t sys_set_volume_limits_called;
-static size_t sys_set_capture_gain_limits_called;
 static size_t cras_alsa_mixer_get_minimum_capture_gain_called;
 static size_t cras_alsa_mixer_get_maximum_capture_gain_called;
 static struct mixer_control* cras_alsa_jack_get_mixer_output_ret;
@@ -96,6 +94,7 @@
 static jack_state_change_callback* cras_alsa_jack_list_create_cb;
 static void* cras_alsa_jack_list_create_cb_data;
 static char test_card_name[] = "TestCard";
+static char test_pcm_name[] = "TestPCM";
 static char test_dev_name[] = "TestDev";
 static char test_dev_id[] = "TestDevId";
 static size_t cras_iodev_add_node_called;
@@ -106,8 +105,6 @@
 static unsigned ucm_set_enabled_called;
 static size_t cras_iodev_update_dsp_called;
 static const char* cras_iodev_update_dsp_name;
-static size_t ucm_get_dsp_name_default_called;
-static const char* ucm_get_dsp_name_default_value;
 typedef std::map<const char*, std::string> DspNameMap;
 static size_t ucm_get_dsp_name_for_dev_called;
 static DspNameMap ucm_get_dsp_name_for_dev_values;
@@ -123,13 +120,6 @@
 static char default_jack_name[] = "Something Jack";
 static int auto_unplug_input_node_ret = 0;
 static int auto_unplug_output_node_ret = 0;
-static int ucm_get_min_software_gain_called;
-static int ucm_get_min_software_gain_ret_value;
-static long ucm_get_min_software_gain_value;
-static int ucm_get_max_software_gain_called;
-static int ucm_get_max_software_gain_ret_value;
-static long ucm_get_max_software_gain_value;
-static long cras_system_set_capture_gain_limits_set_value[2];
 static long cras_alsa_mixer_get_minimum_capture_gain_ret_value;
 static long cras_alsa_mixer_get_maximum_capture_gain_ret_value;
 static snd_pcm_state_t snd_pcm_state_ret;
@@ -147,14 +137,15 @@
 static int cras_iodev_buffer_avail_ret;
 static int cras_alsa_resume_appl_ptr_called;
 static int cras_alsa_resume_appl_ptr_ahead;
-static int ucm_get_enable_htimestamp_flag_ret;
 static const struct cras_volume_curve* fake_get_dBFS_volume_curve_val;
 static int cras_iodev_dsp_set_swap_mode_for_node_called;
 static std::map<std::string, long> ucm_get_default_node_gain_values;
+static std::map<std::string, long> ucm_get_intrinsic_sensitivity_values;
 static thread_callback audio_thread_cb;
 static void* audio_thread_cb_data;
 static int hotword_send_triggered_msg_called;
 static struct timespec clock_gettime_retspec;
+static unsigned cras_iodev_reset_rate_estimator_called;
 
 void ResetStubData() {
   cras_alsa_open_called = 0;
@@ -163,8 +154,8 @@
   cras_alsa_get_avail_frames_avail = 0;
   cras_alsa_start_called = 0;
   cras_alsa_fill_properties_called = 0;
+  cras_alsa_support_8_channels = false;
   sys_get_volume_called = 0;
-  sys_get_capture_gain_called = 0;
   alsa_mixer_set_dBFS_called = 0;
   alsa_mixer_set_capture_dBFS_called = 0;
   sys_get_mute_called = 0;
@@ -183,8 +174,6 @@
   cras_alsa_mixer_set_output_active_state_outputs.clear();
   cras_alsa_mixer_set_output_active_state_values.clear();
   sys_set_volume_limits_called = 0;
-  sys_set_capture_gain_limits_called = 0;
-  sys_get_capture_gain_return_value = 0;
   cras_alsa_mixer_get_minimum_capture_gain_called = 0;
   cras_alsa_mixer_get_maximum_capture_gain_called = 0;
   cras_alsa_mixer_get_output_volume_curve_called = 0;
@@ -204,8 +193,6 @@
   ucm_set_enabled_called = 0;
   cras_iodev_update_dsp_called = 0;
   cras_iodev_update_dsp_name = 0;
-  ucm_get_dsp_name_default_called = 0;
-  ucm_get_dsp_name_default_value = NULL;
   ucm_get_dsp_name_for_dev_called = 0;
   ucm_get_dsp_name_for_dev_values.clear();
   cras_iodev_free_resources_called = 0;
@@ -217,16 +204,8 @@
   cras_alsa_jack_get_name_called = 0;
   cras_alsa_jack_get_name_ret_value = default_jack_name;
   cras_alsa_jack_update_monitor_fake_name = 0;
-  ucm_get_min_software_gain_called = 0;
-  ucm_get_min_software_gain_ret_value = -1;
-  ucm_get_min_software_gain_value = 0;
-  ucm_get_max_software_gain_called = 0;
-  ucm_get_max_software_gain_ret_value = -1;
-  ucm_get_max_software_gain_value = 0;
   cras_card_config_get_volume_curve_for_control_called = 0;
   cras_card_config_get_volume_curve_vals.clear();
-  cras_system_set_capture_gain_limits_set_value[0] = -1;
-  cras_system_set_capture_gain_limits_set_value[1] = -1;
   cras_alsa_mixer_get_minimum_capture_gain_ret_value = 0;
   cras_alsa_mixer_get_maximum_capture_gain_ret_value = 0;
   snd_pcm_state_ret = SND_PCM_STATE_RUNNING;
@@ -240,10 +219,11 @@
   cras_iodev_buffer_avail_ret = 0;
   cras_alsa_resume_appl_ptr_called = 0;
   cras_alsa_resume_appl_ptr_ahead = 0;
-  ucm_get_enable_htimestamp_flag_ret = 0;
   fake_get_dBFS_volume_curve_val = NULL;
   cras_iodev_dsp_set_swap_mode_for_node_called = 0;
   ucm_get_default_node_gain_values.clear();
+  ucm_get_intrinsic_sensitivity_values.clear();
+  cras_iodev_reset_rate_estimator_called = 0;
 }
 
 static long fake_get_dBFS(const struct cras_volume_curve* curve,
@@ -264,13 +244,13 @@
     struct cras_card_config* config,
     struct cras_use_case_mgr* ucm,
     enum CRAS_STREAM_DIRECTION direction) {
-  return alsa_iodev_create(card_index, test_card_name, 0, test_dev_name, dev_id,
-                           card_type, is_first, mixer, config, ucm, fake_hctl,
-                           direction, 0, 0, (char*)"123");
+  return alsa_iodev_create(card_index, test_card_name, 0, test_pcm_name,
+                           test_dev_name, dev_id, card_type, is_first, mixer,
+                           config, ucm, fake_hctl, direction, 0, 0,
+                           (char*)"123");
 }
 
 namespace {
-
 TEST(AlsaIoInit, InitializeInvalidDirection) {
   struct alsa_io* aio;
 
@@ -292,12 +272,13 @@
   /* Get volume curve twice for iodev, and default node. */
   EXPECT_EQ(2, cras_card_config_get_volume_curve_for_control_called);
   EXPECT_EQ(SND_PCM_STREAM_PLAYBACK, aio->alsa_stream);
-  EXPECT_EQ(0, cras_alsa_fill_properties_called);
+  /* Call cras_alsa_fill_properties once on update_max_supported_channels. */
+  EXPECT_EQ(1, cras_alsa_fill_properties_called);
   EXPECT_EQ(1, cras_alsa_mixer_list_outputs_called);
   EXPECT_EQ(
       0, strncmp(test_card_name, aio->base.info.name, strlen(test_card_name)));
-  EXPECT_EQ(0, ucm_get_dsp_name_default_called);
-  EXPECT_EQ(NULL, cras_iodev_update_dsp_name);
+  EXPECT_EQ(1, cras_iodev_update_dsp_called);
+  EXPECT_EQ("", cras_iodev_update_dsp_name);
   ASSERT_NE(reinterpret_cast<const char*>(NULL), aio->dev_name);
   EXPECT_EQ(0, strcmp(test_dev_name, aio->dev_name));
   ASSERT_NE(reinterpret_cast<const char*>(NULL), aio->dev_id);
@@ -381,6 +362,11 @@
   ASSERT_STREQ(DEFAULT, aio->base.active_node->name);
   ASSERT_EQ(1, aio->base.active_node->plugged);
   EXPECT_EQ(2, cras_iodev_set_node_plugged_called);
+
+  /* No extra gain applied. */
+  ASSERT_EQ(DEFAULT_CAPTURE_VOLUME_DBFS,
+            aio->base.active_node->intrinsic_sensitivity);
+  ASSERT_EQ(0, aio->base.active_node->capture_gain);
   alsa_iodev_destroy((struct cras_iodev*)aio);
 }
 
@@ -394,6 +380,8 @@
       0, NULL, ALSA_CARD_TYPE_INTERNAL, 0, fake_mixer, fake_config, NULL,
       CRAS_STREAM_OUTPUT);
   ASSERT_EQ(0, alsa_iodev_legacy_complete_init(iodev));
+  /* Call open_dev once on update_max_supported_channels. */
+  EXPECT_EQ(1, cras_alsa_open_called);
   EXPECT_EQ(2, cras_card_config_get_volume_curve_for_control_called);
   aio = (struct alsa_io*)iodev;
   format.frame_rate = 48000;
@@ -404,9 +392,9 @@
   aio->free_running = 1;
   aio->filled_zeros_for_draining = 512;
   iodev->open_dev(iodev);
-  EXPECT_EQ(1, cras_alsa_open_called);
+  EXPECT_EQ(2, cras_alsa_open_called);
   iodev->configure_dev(iodev);
-  EXPECT_EQ(1, cras_alsa_open_called);
+  EXPECT_EQ(2, cras_alsa_open_called);
   EXPECT_EQ(1, sys_set_volume_limits_called);
   EXPECT_EQ(1, alsa_mixer_set_dBFS_called);
   EXPECT_EQ(0, cras_alsa_start_called);
@@ -483,120 +471,25 @@
   alsa_iodev_destroy(iodev);
 }
 
-TEST(AlsaIoInit, UseSoftwareGain) {
+TEST(AlsaIoInit, SoftwareGainIntrinsicSensitivity) {
   struct cras_iodev* iodev;
   struct cras_use_case_mgr* const fake_ucm = (struct cras_use_case_mgr*)3;
-
-  /* MaxSoftwareGain is specified in UCM */
-  ResetStubData();
-  ucm_get_min_software_gain_ret_value = 1;
-  ucm_get_min_software_gain_value = 1;
-  ucm_get_max_software_gain_ret_value = 0;
-  ucm_get_max_software_gain_value = 2000;
-  iodev = alsa_iodev_create_with_default_parameters(
-      0, NULL, ALSA_CARD_TYPE_INTERNAL, 1, fake_mixer, fake_config, fake_ucm,
-      CRAS_STREAM_INPUT);
-  ASSERT_EQ(0, alsa_iodev_legacy_complete_init(iodev));
-  EXPECT_EQ(1, iodev->active_node->software_volume_needed);
-  EXPECT_EQ(DEFAULT_MIN_CAPTURE_GAIN, iodev->active_node->min_software_gain);
-  EXPECT_EQ(2000, iodev->active_node->max_software_gain);
-  ASSERT_EQ(1, sys_set_capture_gain_limits_called);
-  /* The gain range is [DEFAULT_MIN_CAPTURE_GAIN, maximum software gain]. */
-  ASSERT_EQ(cras_system_set_capture_gain_limits_set_value[0],
-            DEFAULT_MIN_CAPTURE_GAIN);
-  ASSERT_EQ(cras_system_set_capture_gain_limits_set_value[1], 2000);
-
-  alsa_iodev_destroy(iodev);
-
-  /* MaxSoftwareGain and MinSoftwareGain are specified in UCM. */
-  ResetStubData();
-  ucm_get_min_software_gain_ret_value = 0;
-  ucm_get_min_software_gain_value = 1000;
-  ucm_get_max_software_gain_ret_value = 0;
-  ucm_get_max_software_gain_value = 2000;
-  iodev = alsa_iodev_create_with_default_parameters(
-      0, NULL, ALSA_CARD_TYPE_INTERNAL, 1, fake_mixer, fake_config, fake_ucm,
-      CRAS_STREAM_INPUT);
-  ASSERT_EQ(0, alsa_iodev_legacy_complete_init(iodev));
-  EXPECT_EQ(1, iodev->active_node->software_volume_needed);
-  EXPECT_EQ(1000, iodev->active_node->min_software_gain);
-  EXPECT_EQ(2000, iodev->active_node->max_software_gain);
-  ASSERT_EQ(1, sys_set_capture_gain_limits_called);
-  /* The gain range is [minimum software gain, maximum software gain]. */
-  ASSERT_EQ(cras_system_set_capture_gain_limits_set_value[0], 1000);
-  ASSERT_EQ(cras_system_set_capture_gain_limits_set_value[1], 2000);
-
-  alsa_iodev_destroy(iodev);
-
-  /* MinSoftwareGain is larger than MaxSoftwareGain in UCM. */
-  ResetStubData();
-  ucm_get_min_software_gain_ret_value = 0;
-  ucm_get_min_software_gain_value = 3000;
-  ucm_get_max_software_gain_ret_value = 0;
-  ucm_get_max_software_gain_value = 2000;
-  iodev = alsa_iodev_create_with_default_parameters(
-      0, NULL, ALSA_CARD_TYPE_INTERNAL, 1, fake_mixer, fake_config, fake_ucm,
-      CRAS_STREAM_INPUT);
-  ASSERT_EQ(0, alsa_iodev_legacy_complete_init(iodev));
-  EXPECT_EQ(1, iodev->active_node->software_volume_needed);
-  EXPECT_EQ(DEFAULT_MIN_CAPTURE_GAIN, iodev->active_node->min_software_gain);
-  EXPECT_EQ(2000, iodev->active_node->max_software_gain);
-  ASSERT_EQ(1, sys_set_capture_gain_limits_called);
-  /* The gain range is [DEFAULT_MIN_CAPTURE_GAIN, maximum software gain]. */
-  ASSERT_EQ(cras_system_set_capture_gain_limits_set_value[0],
-            DEFAULT_MIN_CAPTURE_GAIN);
-  ASSERT_EQ(cras_system_set_capture_gain_limits_set_value[1], 2000);
-
-  alsa_iodev_destroy(iodev);
-
-  /* MaxSoftwareGain is not specified in UCM. */
-  ResetStubData();
-  ucm_get_max_software_gain_ret_value = 1;
-  ucm_get_max_software_gain_value = 1;
-  cras_alsa_mixer_get_minimum_capture_gain_ret_value = -500;
-  cras_alsa_mixer_get_maximum_capture_gain_ret_value = 500;
-  iodev = alsa_iodev_create_with_default_parameters(
-      0, NULL, ALSA_CARD_TYPE_INTERNAL, 1, fake_mixer, fake_config, fake_ucm,
-      CRAS_STREAM_INPUT);
-  ASSERT_EQ(0, alsa_iodev_legacy_complete_init(iodev));
-  EXPECT_EQ(0, iodev->active_node->software_volume_needed);
-  EXPECT_EQ(0, iodev->active_node->max_software_gain);
-  ASSERT_EQ(1, sys_set_capture_gain_limits_called);
-  /* The gain range is reported by controls. */
-  ASSERT_EQ(cras_system_set_capture_gain_limits_set_value[0], -500);
-  ASSERT_EQ(cras_system_set_capture_gain_limits_set_value[1], 500);
-
-  alsa_iodev_destroy(iodev);
-}
-
-TEST(AlsaIoInit, SoftwareGainWithDefaultNodeGain) {
-  struct cras_iodev* iodev;
-  struct cras_use_case_mgr* const fake_ucm = (struct cras_use_case_mgr*)3;
-  long system_gain = 500;
-  long default_node_gain = -1000;
+  long intrinsic_sensitivity = -2700;
 
   ResetStubData();
 
-  // Use software gain.
-  ucm_get_max_software_gain_ret_value = 0;
-  ucm_get_max_software_gain_value = 2000;
-
-  // Set default node gain to -1000 * 0.01 dB.
-  ucm_get_default_node_gain_values[INTERNAL_MICROPHONE] = default_node_gain;
+  // Set intrinsic sensitivity to -2700 * 0.01 dBFS/Pa.
+  ucm_get_intrinsic_sensitivity_values[INTERNAL_MICROPHONE] =
+      intrinsic_sensitivity;
 
   // Assume this is the first device so it gets internal mic node name.
   iodev = alsa_iodev_create_with_default_parameters(
       0, NULL, ALSA_CARD_TYPE_INTERNAL, 1, fake_mixer, fake_config, fake_ucm,
       CRAS_STREAM_INPUT);
   ASSERT_EQ(0, alsa_iodev_legacy_complete_init(iodev));
-
-  // Gain on node is 300 * 0.01 dB.
-  iodev->active_node->capture_gain = default_node_gain;
-
-  // cras_iodev will call cras_iodev_adjust_active_node_gain to get gain for
-  // software gain.
-  ASSERT_EQ(system_gain + default_node_gain,
-            cras_iodev_adjust_active_node_gain(iodev, system_gain));
+  ASSERT_EQ(intrinsic_sensitivity, iodev->active_node->intrinsic_sensitivity);
+  ASSERT_EQ(DEFAULT_CAPTURE_VOLUME_DBFS - intrinsic_sensitivity,
+            iodev->active_node->capture_gain);
 
   alsa_iodev_destroy(iodev);
 }
@@ -613,7 +506,8 @@
   ASSERT_EQ(0, alsa_iodev_legacy_complete_init((struct cras_iodev*)aio));
   EXPECT_EQ(2, cras_card_config_get_volume_curve_for_control_called);
   EXPECT_EQ(SND_PCM_STREAM_PLAYBACK, aio->alsa_stream);
-  EXPECT_EQ(0, cras_alsa_fill_properties_called);
+  /* Call cras_alsa_fill_properties once on update_max_supported_channels. */
+  EXPECT_EQ(1, cras_alsa_fill_properties_called);
   EXPECT_EQ(1, cras_alsa_mixer_list_outputs_called);
   EXPECT_EQ(1, cras_alsa_jack_list_create_called);
   EXPECT_EQ(1, cras_alsa_jack_list_find_jacks_by_name_matching_called);
@@ -642,7 +536,8 @@
   ASSERT_EQ(0, alsa_iodev_legacy_complete_init((struct cras_iodev*)aio));
 
   EXPECT_EQ(SND_PCM_STREAM_CAPTURE, aio->alsa_stream);
-  EXPECT_EQ(0, cras_alsa_fill_properties_called);
+  /* Call cras_alsa_fill_properties once on update_max_supported_channels. */
+  EXPECT_EQ(1, cras_alsa_fill_properties_called);
   EXPECT_EQ(1, cras_alsa_jack_list_create_called);
   EXPECT_EQ(1, cras_alsa_jack_list_find_jacks_by_name_matching_called);
   EXPECT_EQ(0, cras_alsa_jack_list_add_jack_for_section_called);
@@ -669,7 +564,8 @@
   ASSERT_EQ(0, alsa_iodev_legacy_complete_init((struct cras_iodev*)aio));
 
   EXPECT_EQ(SND_PCM_STREAM_CAPTURE, aio->alsa_stream);
-  EXPECT_EQ(0, cras_alsa_fill_properties_called);
+  /* Call cras_alsa_fill_properties once on update_max_supported_channels. */
+  EXPECT_EQ(1, cras_alsa_fill_properties_called);
   EXPECT_EQ(1, cras_alsa_mixer_list_inputs_called);
 
   alsa_iodev_destroy((struct cras_iodev*)aio);
@@ -697,8 +593,6 @@
   EXPECT_EQ(1, cras_alsa_open_called);
   EXPECT_EQ(1, cras_alsa_mixer_get_minimum_capture_gain_called);
   EXPECT_EQ(1, cras_alsa_mixer_get_maximum_capture_gain_called);
-  EXPECT_EQ(1, sys_set_capture_gain_limits_called);
-  EXPECT_EQ(1, sys_get_capture_gain_called);
   EXPECT_EQ(1, alsa_mixer_set_capture_dBFS_called);
   EXPECT_EQ(1, sys_get_capture_mute_called);
   EXPECT_EQ(1, alsa_mixer_set_capture_mute_called);
@@ -714,8 +608,7 @@
   struct cras_iodev* iodev;
   struct cras_audio_format format;
   struct cras_use_case_mgr* const fake_ucm = (struct cras_use_case_mgr*)3;
-  long system_gain = 2000;
-  long default_node_gain = -1000;
+  long default_node_gain = 1000;
 
   ResetStubData();
   // Set default node gain to -1000 * 0.01 dB.
@@ -731,15 +624,24 @@
 
   // Check the default node gain is the same as what specified in UCM.
   EXPECT_EQ(default_node_gain, iodev->active_node->capture_gain);
-  // System gain is set to 2000 * 0.01 dB.
-  sys_get_capture_gain_return_value = system_gain;
+  cras_alsa_mixer_get_minimum_capture_gain_ret_value = 0;
+  cras_alsa_mixer_get_maximum_capture_gain_ret_value = 2000;
 
   iodev->open_dev(iodev);
   iodev->configure_dev(iodev);
   iodev->close_dev(iodev);
 
-  // Hardware gain is set to (2000 - 1000) * 0.01 dB.
-  EXPECT_EQ(system_gain + default_node_gain, alsa_mixer_set_capture_dBFS_value);
+  // Hardware gain is in the hardware gain range and set to 1000 * 0.01 dB.
+  EXPECT_EQ(default_node_gain, alsa_mixer_set_capture_dBFS_value);
+
+  // Check we do respect the hardware maximum capture gain.
+  cras_alsa_mixer_get_maximum_capture_gain_ret_value = 500;
+
+  iodev->open_dev(iodev);
+  iodev->configure_dev(iodev);
+  iodev->close_dev(iodev);
+
+  EXPECT_EQ(500, alsa_mixer_set_capture_dBFS_value);
 
   alsa_iodev_destroy(iodev);
   free(fake_format);
@@ -752,8 +654,6 @@
 
   /* Meet the requirements of using software gain. */
   ResetStubData();
-  ucm_get_max_software_gain_ret_value = 0;
-  ucm_get_max_software_gain_value = 2000;
 
   iodev = alsa_iodev_create_with_default_parameters(
       0, NULL, ALSA_CARD_TYPE_INTERNAL, 0, fake_mixer, fake_config, fake_ucm,
@@ -764,9 +664,6 @@
   format.num_channels = 1;
   cras_iodev_set_format(iodev, &format);
 
-  /* System gain is set to 1000 * 0.01 dB */
-  sys_get_capture_gain_return_value = 1000;
-
   iodev->open_dev(iodev);
   iodev->configure_dev(iodev);
   iodev->close_dev(iodev);
@@ -776,12 +673,43 @@
 
   /* Test the case where software gain is not needed. */
   iodev->active_node->software_volume_needed = 0;
+  iodev->active_node->capture_gain = 1000;
   iodev->open_dev(iodev);
   iodev->configure_dev(iodev);
   iodev->close_dev(iodev);
 
-  /* Hardware gain is set to 1000 * 0.01 dB as got from system capture gain.*/
-  EXPECT_EQ(1000, alsa_mixer_set_capture_dBFS_value);
+  /* Hardware gain is set to 1000 * 0.01 dB as got from catpure_gain.*/
+  EXPECT_EQ(0, alsa_mixer_set_capture_dBFS_value);
+
+  alsa_iodev_destroy(iodev);
+  free(fake_format);
+}
+
+TEST(AlsaIoInit, OpenCaptureSetCaptureGainWithDefaultUsbDevice) {
+  struct cras_iodev* iodev;
+  struct cras_audio_format format;
+
+  iodev = alsa_iodev_create_with_default_parameters(0, NULL, ALSA_CARD_TYPE_USB,
+                                                    0, fake_mixer, fake_config,
+                                                    NULL, CRAS_STREAM_INPUT);
+  ASSERT_EQ(0, alsa_iodev_legacy_complete_init(iodev));
+
+  format.frame_rate = 48000;
+  format.num_channels = 1;
+  cras_iodev_set_format(iodev, &format);
+
+  iodev->active_node->intrinsic_sensitivity = DEFAULT_CAPTURE_VOLUME_DBFS;
+  iodev->active_node->capture_gain = 0;
+
+  ResetStubData();
+  iodev->open_dev(iodev);
+  iodev->configure_dev(iodev);
+
+  EXPECT_EQ(1, sys_get_capture_mute_called);
+  EXPECT_EQ(1, alsa_mixer_set_capture_mute_called);
+
+  /* Not change mixer controls for USB devices without UCM config. */
+  EXPECT_EQ(0, alsa_mixer_set_capture_dBFS_called);
 
   alsa_iodev_destroy(iodev);
   free(fake_format);
@@ -855,7 +783,6 @@
   struct cras_use_case_mgr* const fake_ucm = (struct cras_use_case_mgr*)3;
 
   ResetStubData();
-  ucm_get_dsp_name_default_value = "hello";
   aio = (struct alsa_io*)alsa_iodev_create_with_default_parameters(
       0, NULL, ALSA_CARD_TYPE_INTERNAL, 0, fake_mixer, fake_config, fake_ucm,
       CRAS_STREAM_OUTPUT);
@@ -863,19 +790,17 @@
   EXPECT_EQ(2, cras_card_config_get_volume_curve_for_control_called);
   EXPECT_EQ(SND_PCM_STREAM_PLAYBACK, aio->alsa_stream);
   EXPECT_EQ(1, ucm_get_dsp_name_for_dev_called);
-  EXPECT_EQ(1, ucm_get_dsp_name_default_called);
-  EXPECT_STREQ("hello", cras_iodev_update_dsp_name);
+  EXPECT_STREQ("", cras_iodev_update_dsp_name);
 
   alsa_iodev_destroy((struct cras_iodev*)aio);
 }
 
-TEST(AlsaIoInit, DspNameWithoutDefault) {
+TEST(AlsaIoInit, DspName) {
   struct alsa_io* aio;
   struct cras_alsa_mixer* const fake_mixer = (struct cras_alsa_mixer*)2;
   struct cras_use_case_mgr* const fake_ucm = (struct cras_use_case_mgr*)3;
 
   ResetStubData();
-  ucm_get_dsp_name_default_value = NULL;
   ucm_get_dsp_name_for_dev_values[DEFAULT] = "hello";
   aio = (struct alsa_io*)alsa_iodev_create_with_default_parameters(
       0, NULL, ALSA_CARD_TYPE_INTERNAL, 0, fake_mixer, fake_config, fake_ucm,
@@ -884,7 +809,6 @@
   EXPECT_EQ(2, cras_card_config_get_volume_curve_for_control_called);
   EXPECT_EQ(SND_PCM_STREAM_PLAYBACK, aio->alsa_stream);
   EXPECT_EQ(1, ucm_get_dsp_name_for_dev_called);
-  EXPECT_EQ(1, ucm_get_dsp_name_default_called);
   EXPECT_STREQ("hello", cras_iodev_update_dsp_name);
 
   alsa_iodev_destroy((struct cras_iodev*)aio);
@@ -898,16 +822,14 @@
   static const char* jack_name = "jack";
 
   ResetStubData();
-  ucm_get_dsp_name_default_value = "default_dsp";
   aio = (struct alsa_io*)alsa_iodev_create_with_default_parameters(
       0, NULL, ALSA_CARD_TYPE_INTERNAL, 0, fake_mixer, fake_config, fake_ucm,
       CRAS_STREAM_OUTPUT);
   ASSERT_EQ(0, alsa_iodev_legacy_complete_init((struct cras_iodev*)aio));
   EXPECT_EQ(SND_PCM_STREAM_PLAYBACK, aio->alsa_stream);
   EXPECT_EQ(1, ucm_get_dsp_name_for_dev_called);
-  EXPECT_EQ(1, ucm_get_dsp_name_default_called);
   EXPECT_EQ(1, cras_iodev_update_dsp_called);
-  EXPECT_STREQ("default_dsp", cras_iodev_update_dsp_name);
+  EXPECT_STREQ("", cras_iodev_update_dsp_name);
 
   cras_alsa_jack_get_name_ret_value = jack_name;
   ucm_get_dsp_name_for_dev_values[jack_name] = "override_dsp";
@@ -915,7 +837,6 @@
   cras_alsa_jack_list_create_cb(jack, 1, cras_alsa_jack_list_create_cb_data);
   EXPECT_EQ(2, cras_alsa_jack_get_name_called);
   EXPECT_EQ(2, ucm_get_dsp_name_for_dev_called);
-  EXPECT_EQ(1, ucm_get_dsp_name_default_called);
 
   // Mark the jack node as active.
   alsa_iodev_set_active_node(&aio->base, aio->base.nodes->next, 1);
@@ -925,10 +846,9 @@
 
   // Mark the default node as active.
   alsa_iodev_set_active_node(&aio->base, aio->base.nodes, 1);
-  EXPECT_EQ(1, ucm_get_dsp_name_default_called);
   EXPECT_EQ(2, ucm_get_dsp_name_for_dev_called);
   EXPECT_EQ(3, cras_iodev_update_dsp_called);
-  EXPECT_STREQ("default_dsp", cras_iodev_update_dsp_name);
+  EXPECT_STREQ("", cras_iodev_update_dsp_name);
 
   alsa_iodev_destroy((struct cras_iodev*)aio);
 }
@@ -991,6 +911,30 @@
   free(fake_node);
 }
 
+TEST(AlsaIoInit, MaxSupportedChannels) {
+  struct alsa_io* aio;
+  struct cras_alsa_mixer* const fake_mixer = (struct cras_alsa_mixer*)2;
+  int i;
+
+  // i = 0: cras_alsa_support_8_channels is false, support 2 channels only.
+  // i = 1: cras_alsa_support_8_channels is true, support up to 8 channels.
+  for (i = 0; i < 2; i++) {
+    ResetStubData();
+    cras_alsa_support_8_channels = (bool)i;
+
+    aio = (struct alsa_io*)alsa_iodev_create_with_default_parameters(
+        0, test_dev_id, ALSA_CARD_TYPE_INTERNAL, 1, fake_mixer, fake_config,
+        NULL, CRAS_STREAM_OUTPUT);
+    ASSERT_EQ(0, alsa_iodev_legacy_complete_init((struct cras_iodev*)aio));
+    /* Call cras_alsa_fill_properties once on update_max_supported_channels. */
+    EXPECT_EQ(1, cras_alsa_fill_properties_called);
+    uint32_t max_channels = (cras_alsa_support_8_channels) ? 8 : 2;
+    EXPECT_EQ(max_channels, aio->base.info.max_supported_channels);
+    alsa_iodev_destroy((struct cras_iodev*)aio);
+    EXPECT_EQ(1, cras_iodev_free_resources_called);
+  }
+}
+
 // Test that system settins aren't touched if no streams active.
 TEST(AlsaOutputNode, SystemSettingsWhenInactive) {
   int rc;
@@ -1095,8 +1039,8 @@
   EXPECT_EQ(1, cras_card_config_get_volume_curve_for_control_called);
 
   // First node 'Headphone'
-  section =
-      ucm_section_create(HEADPHONE, 0, CRAS_STREAM_OUTPUT, "fake-jack", "gpio");
+  section = ucm_section_create(HEADPHONE, "hw:0,1", 0, -1, CRAS_STREAM_OUTPUT,
+                               "fake-jack", "gpio");
   ucm_section_set_mixer_name(section, HEADPHONE);
   cras_alsa_jack_list_add_jack_for_section_result_jack =
       reinterpret_cast<struct cras_alsa_jack*>(10);
@@ -1106,8 +1050,8 @@
   ucm_section_free_list(section);
 
   // Second node 'Line Out'
-  section = ucm_section_create("Line Out", 0, CRAS_STREAM_OUTPUT, "fake-jack",
-                               "gpio");
+  section = ucm_section_create("Line Out", "hw:0.1", 0, -1, CRAS_STREAM_OUTPUT,
+                               "fake-jack", "gpio");
   ucm_section_set_mixer_name(section, HEADPHONE);
   cras_alsa_jack_list_add_jack_for_section_result_jack =
       reinterpret_cast<struct cras_alsa_jack*>(20);
@@ -1128,6 +1072,43 @@
   alsa_iodev_destroy(iodev);
 }
 
+TEST(AlsaOutputNode, MaxSupportedChannels) {
+  struct cras_use_case_mgr* const fake_ucm = (struct cras_use_case_mgr*)3;
+  struct cras_iodev* iodev;
+  struct ucm_section* section;
+  int i;
+
+  // i = 0: cras_alsa_support_8_channels is false, support 2 channels only.
+  // i = 1: cras_alsa_support_8_channels is true, support up to 8 channels.
+  for (i = 0; i < 2; i++) {
+    ResetStubData();
+    cras_alsa_support_8_channels = (bool)i;
+
+    // Create the IO device.
+    iodev = alsa_iodev_create_with_default_parameters(
+        1, NULL, ALSA_CARD_TYPE_INTERNAL, 1, fake_mixer, fake_config, fake_ucm,
+        CRAS_STREAM_OUTPUT);
+    ASSERT_NE(iodev, (void*)NULL);
+
+    // Node without controls or jacks.
+    section = ucm_section_create(INTERNAL_SPEAKER, "hw:0,1", 1, -1,
+                                 CRAS_STREAM_OUTPUT, NULL, NULL);
+    // Device index doesn't match.
+    EXPECT_EQ(-22, alsa_iodev_ucm_add_nodes_and_jacks(iodev, section));
+    section->dev_idx = 0;
+    ASSERT_EQ(0, alsa_iodev_ucm_add_nodes_and_jacks(iodev, section));
+    ucm_section_free_list(section);
+
+    // Complete initialization, and make first node active.
+    alsa_iodev_ucm_complete_init(iodev);
+    /* Call cras_alsa_fill_properties once on update_max_supported_channels. */
+    EXPECT_EQ(1, cras_alsa_fill_properties_called);
+    uint32_t max_channels = (cras_alsa_support_8_channels) ? 8 : 2;
+    EXPECT_EQ(max_channels, iodev->info.max_supported_channels);
+    alsa_iodev_destroy(iodev);
+  }
+}
+
 TEST(AlsaOutputNode, OutputsFromUCM) {
   struct alsa_io* aio;
   struct cras_alsa_mixer* const fake_mixer = (struct cras_alsa_mixer*)2;
@@ -1156,8 +1137,8 @@
   EXPECT_EQ(1, cras_card_config_get_volume_curve_for_control_called);
 
   // First node.
-  section =
-      ucm_section_create(INTERNAL_SPEAKER, 0, CRAS_STREAM_OUTPUT, NULL, NULL);
+  section = ucm_section_create(INTERNAL_SPEAKER, "hw:0,1", 0, -1,
+                               CRAS_STREAM_OUTPUT, NULL, NULL);
   ucm_section_set_mixer_name(section, INTERNAL_SPEAKER);
   cras_alsa_jack_list_add_jack_for_section_result_jack =
       reinterpret_cast<struct cras_alsa_jack*>(1);
@@ -1167,8 +1148,8 @@
   EXPECT_EQ(4, cras_card_config_get_volume_curve_for_control_called);
 
   // Add a second node (will use the same iodev).
-  section =
-      ucm_section_create(HEADPHONE, 0, CRAS_STREAM_OUTPUT, jack_name, "hctl");
+  section = ucm_section_create(HEADPHONE, "hw:0,2", 0, -1, CRAS_STREAM_OUTPUT,
+                               jack_name, "hctl");
   ucm_section_add_coupled(section, "HP-L", MIXER_NAME_VOLUME);
   ucm_section_add_coupled(section, "HP-R", MIXER_NAME_VOLUME);
   cras_alsa_jack_list_add_jack_for_section_result_jack = NULL;
@@ -1186,12 +1167,16 @@
   EXPECT_EQ(0, cras_iodev_set_node_plugged_called);
 
   // Complete initialization, and make first node active.
+  cras_alsa_support_8_channels = false;  // Support 2 channels only.
   alsa_iodev_ucm_complete_init(iodev);
   EXPECT_EQ(SND_PCM_STREAM_PLAYBACK, aio->alsa_stream);
   EXPECT_EQ(2, cras_alsa_jack_list_add_jack_for_section_called);
   EXPECT_EQ(2, cras_alsa_mixer_get_control_for_section_called);
   EXPECT_EQ(1, ucm_get_dma_period_for_dev_called);
   EXPECT_EQ(ucm_get_dma_period_for_dev_ret, aio->dma_period_set_microsecs);
+  /* Call cras_alsa_fill_properties once on update_max_supported_channels. */
+  EXPECT_EQ(1, cras_alsa_fill_properties_called);
+  EXPECT_EQ(2, iodev->info.max_supported_channels);
 
   aio->handle = (snd_pcm_t*)0x24;
 
@@ -1212,10 +1197,14 @@
   EXPECT_EQ(1, ucm_set_enabled_called);
 
   // Simulate jack plug event.
+  cras_alsa_support_8_channels = true;  // Support up to 8 channels.
   cras_alsa_jack_get_mixer_output_ret = outputs[1];
   cras_alsa_jack_get_name_ret_value = jack_name;
   jack_output_plug_event(reinterpret_cast<struct cras_alsa_jack*>(4), 0, aio);
   EXPECT_EQ(1, cras_iodev_set_node_plugged_called);
+  /* Headphone plug event shouldn't trigger update_max_supported_channels. */
+  EXPECT_EQ(0, cras_alsa_fill_properties_called);
+  EXPECT_EQ(2, iodev->info.max_supported_channels);
 
   alsa_iodev_destroy(iodev);
 }
@@ -1237,8 +1226,8 @@
   EXPECT_EQ(1, cras_card_config_get_volume_curve_for_control_called);
 
   // Node without controls or jacks.
-  section =
-      ucm_section_create(INTERNAL_SPEAKER, 1, CRAS_STREAM_OUTPUT, NULL, NULL);
+  section = ucm_section_create(INTERNAL_SPEAKER, "hw:0,1", 1, -1,
+                               CRAS_STREAM_OUTPUT, NULL, NULL);
   // Device index doesn't match.
   EXPECT_EQ(-22, alsa_iodev_ucm_add_nodes_and_jacks(iodev, section));
   section->dev_idx = 0;
@@ -1279,8 +1268,8 @@
   // Node without controls or jacks.
   cras_alsa_jack_list_add_jack_for_section_result_jack =
       reinterpret_cast<struct cras_alsa_jack*>(1);
-  section =
-      ucm_section_create(HEADPHONE, 0, CRAS_STREAM_OUTPUT, jack_name, "hctl");
+  section = ucm_section_create(HEADPHONE, "hw:0,1", 0, -1, CRAS_STREAM_OUTPUT,
+                               jack_name, "hctl");
   ASSERT_EQ(0, alsa_iodev_ucm_add_nodes_and_jacks(iodev, section));
   EXPECT_EQ(4, cras_card_config_get_volume_curve_for_control_called);
   EXPECT_EQ(1, cras_alsa_mixer_get_control_for_section_called);
@@ -1308,6 +1297,7 @@
   static const char* jack_name = "TestCard - Headset Jack";
   int rc;
   struct ucm_section* section;
+  long intrinsic_sensitivity = -2700;
 
   ResetStubData();
   inputs[0] = reinterpret_cast<struct mixer_control*>(3);
@@ -1326,9 +1316,8 @@
 
   // First node.
   cras_alsa_mixer_get_control_for_section_return_value = inputs[0];
-  ucm_get_max_software_gain_ret_value = -1;
-  section =
-      ucm_section_create(INTERNAL_MICROPHONE, 0, CRAS_STREAM_INPUT, NULL, NULL);
+  section = ucm_section_create(INTERNAL_MICROPHONE, "hw:0,1", 0, -1,
+                               CRAS_STREAM_INPUT, NULL, NULL);
   ucm_section_add_coupled(section, "MIC-L", MIXER_NAME_VOLUME);
   ucm_section_add_coupled(section, "MIC-R", MIXER_NAME_VOLUME);
   ASSERT_EQ(0, alsa_iodev_ucm_add_nodes_and_jacks(iodev, section));
@@ -1336,17 +1325,18 @@
 
   // Add a second node (will use the same iodev).
   cras_alsa_mixer_get_control_name_called = 0;
-  ucm_get_max_software_gain_ret_value = 0;
-  ucm_get_max_software_gain_value = 2000;
+  // Set intrinsic sensitivity to enable software gain.
+  ucm_get_intrinsic_sensitivity_values[MIC] = intrinsic_sensitivity;
   cras_alsa_jack_list_add_jack_for_section_result_jack =
       reinterpret_cast<struct cras_alsa_jack*>(1);
   cras_alsa_mixer_get_control_for_section_return_value = inputs[1];
-  section = ucm_section_create(MIC, 0, CRAS_STREAM_INPUT, jack_name, "hctl");
+  section = ucm_section_create(MIC, "hw:0,2", 0, -1, CRAS_STREAM_INPUT,
+                               jack_name, "hctl");
   ucm_section_set_mixer_name(section, MIC);
   ASSERT_EQ(0, alsa_iodev_ucm_add_nodes_and_jacks(iodev, section));
   ucm_section_free_list(section);
 
-  // Jack plug of an unkonwn device should do nothing.
+  // Jack plug of an unknown device should do nothing.
   cras_alsa_jack_get_mixer_input_ret = NULL;
   cras_alsa_jack_get_name_ret_value = "Some other jack";
   jack_input_plug_event(reinterpret_cast<struct cras_alsa_jack*>(4), 0, aio);
@@ -1364,7 +1354,6 @@
   EXPECT_EQ(2, cras_alsa_jack_list_add_jack_for_section_called);
   EXPECT_EQ(2, cras_alsa_mixer_get_control_for_section_called);
   EXPECT_EQ(1, cras_alsa_mixer_get_control_name_called);
-  EXPECT_EQ(1, sys_set_capture_gain_limits_called);
   EXPECT_EQ(2, cras_iodev_add_node_called);
   EXPECT_EQ(2, ucm_get_dma_period_for_dev_called);
   EXPECT_EQ(0, aio->dma_period_set_microsecs);
@@ -1380,10 +1369,9 @@
   EXPECT_EQ(1, cras_iodev_update_dsp_called);
   EXPECT_EQ(1, cras_alsa_jack_enable_ucm_called);
   EXPECT_EQ(1, ucm_set_enabled_called);
-  EXPECT_EQ(1, sys_set_capture_gain_limits_called);
   EXPECT_EQ(1, alsa_mixer_set_capture_mute_called);
-  EXPECT_EQ(1, iodev->active_node->software_volume_needed);
-  EXPECT_EQ(2000, iodev->active_node->max_software_gain);
+  ASSERT_EQ(DEFAULT_CAPTURE_VOLUME_DBFS - intrinsic_sensitivity,
+            iodev->active_node->capture_gain);
 
   alsa_iodev_destroy(iodev);
 }
@@ -1404,8 +1392,8 @@
   aio = reinterpret_cast<struct alsa_io*>(iodev);
 
   // Node without controls or jacks.
-  section =
-      ucm_section_create(INTERNAL_MICROPHONE, 1, CRAS_STREAM_INPUT, NULL, NULL);
+  section = ucm_section_create(INTERNAL_MICROPHONE, "hw:0,1", 1, -1,
+                               CRAS_STREAM_INPUT, NULL, NULL);
   // Device index doesn't match.
   EXPECT_EQ(-22, alsa_iodev_ucm_add_nodes_and_jacks(iodev, section));
   section->dev_idx = 0;
@@ -1446,7 +1434,8 @@
   // Node without controls or jacks.
   cras_alsa_jack_list_add_jack_for_section_result_jack =
       reinterpret_cast<struct cras_alsa_jack*>(1);
-  section = ucm_section_create(MIC, 0, CRAS_STREAM_INPUT, jack_name, "hctl");
+  section = ucm_section_create(MIC, "hw:0,1", 0, -1, CRAS_STREAM_INPUT,
+                               jack_name, "hctl");
   ASSERT_EQ(0, alsa_iodev_ucm_add_nodes_and_jacks(iodev, section));
   EXPECT_EQ(1, cras_alsa_mixer_get_control_for_section_called);
   EXPECT_EQ(1, cras_iodev_add_node_called);
@@ -1554,6 +1543,83 @@
   alsa_iodev_destroy((struct cras_iodev*)aio);
 }
 
+TEST(AlsaLoopback, InitializePlayback) {
+  struct alsa_io* aio;
+  struct cras_alsa_mixer* const fake_mixer = (struct cras_alsa_mixer*)2;
+  struct cras_use_case_mgr* const fake_ucm = (struct cras_use_case_mgr*)3;
+  struct cras_iodev* iodev;
+  static const char* jack_name = "TestCard - Alsa Loopback";
+  struct mixer_control* outputs[1];
+  struct ucm_section* section;
+
+  ResetStubData();
+  outputs[0] = reinterpret_cast<struct mixer_control*>(3);
+  cras_alsa_mixer_list_outputs_outputs = outputs;
+  cras_alsa_mixer_list_outputs_outputs_length = ARRAY_SIZE(outputs);
+  cras_alsa_mixer_get_control_name_values[outputs[0]] = LOOPBACK_PLAYBACK;
+
+  // Create the IO device.
+  iodev = alsa_iodev_create_with_default_parameters(
+      0, NULL, ALSA_CARD_TYPE_INTERNAL, 1, fake_mixer, fake_config, fake_ucm,
+      CRAS_STREAM_OUTPUT);
+  ASSERT_NE(iodev, (void*)NULL);
+  aio = reinterpret_cast<struct alsa_io*>(iodev);
+
+  // Add node.
+  section = ucm_section_create(LOOPBACK_PLAYBACK, "hw:0,1", 0, -1,
+                               CRAS_STREAM_OUTPUT, jack_name, NULL);
+  ucm_section_set_mixer_name(section, LOOPBACK_PLAYBACK);
+  cras_alsa_jack_list_add_jack_for_section_result_jack = NULL;
+  cras_alsa_mixer_get_control_for_section_return_value = outputs[0];
+  ASSERT_EQ(0, alsa_iodev_ucm_add_nodes_and_jacks(iodev, section));
+  ucm_section_free_list(section);
+
+  // Complete initialization, and check the loopback playback node is plugged as
+  // the active node.
+  alsa_iodev_ucm_complete_init(iodev);
+  EXPECT_EQ(SND_PCM_STREAM_PLAYBACK, aio->alsa_stream);
+  ASSERT_NE(aio->base.active_node, (void*)NULL);
+  EXPECT_STREQ(LOOPBACK_PLAYBACK, aio->base.active_node->name);
+  EXPECT_EQ(1, aio->base.active_node->plugged);
+
+  alsa_iodev_destroy(iodev);
+}
+
+TEST(AlsaLoopback, InitializeCapture) {
+  struct alsa_io* aio;
+  struct cras_use_case_mgr* const fake_ucm = (struct cras_use_case_mgr*)3;
+  struct cras_iodev* iodev;
+  static const char* jack_name = "TestCard - Alsa Loopback";
+  struct ucm_section* section;
+
+  ResetStubData();
+
+  // Create the IO device.
+  iodev = alsa_iodev_create_with_default_parameters(
+      1, NULL, ALSA_CARD_TYPE_INTERNAL, 1, fake_mixer, fake_config, fake_ucm,
+      CRAS_STREAM_INPUT);
+  ASSERT_NE(iodev, (void*)NULL);
+  aio = reinterpret_cast<struct alsa_io*>(iodev);
+
+  // Node without controls or jacks.
+  cras_alsa_jack_list_add_jack_for_section_result_jack =
+      reinterpret_cast<struct cras_alsa_jack*>(1);
+  section = ucm_section_create(LOOPBACK_CAPTURE, "hw:0,1", 0, -1,
+                               CRAS_STREAM_INPUT, jack_name, NULL);
+  ASSERT_EQ(0, alsa_iodev_ucm_add_nodes_and_jacks(iodev, section));
+  ucm_section_free_list(section);
+
+  // Complete initialization, and check the loopback capture node is plugged as
+  // the active node.
+  alsa_iodev_ucm_complete_init(iodev);
+  EXPECT_EQ(SND_PCM_STREAM_CAPTURE, aio->alsa_stream);
+  ASSERT_NE(aio->base.active_node, (void*)NULL);
+  EXPECT_STREQ(LOOPBACK_CAPTURE, aio->base.active_node->name);
+  EXPECT_EQ(1, aio->base.active_node->plugged);
+
+  alsa_iodev_destroy(iodev);
+}
+
 TEST(AlsaInitNode, SetNodeInitialState) {
   struct cras_ionode node;
   struct cras_iodev dev;
@@ -1867,11 +1933,6 @@
       .get_dBFS = fake_get_dBFS,
   };
 
-  fmt = (struct cras_audio_format*)malloc(sizeof(*fmt));
-  memcpy(fmt, &fmt_, sizeof(fmt_));
-  aio_output_->base.format = fmt;
-  aio_output_->handle = (snd_pcm_t*)0x24;
-
   // Headphone jack plugged and has its own volume curve.
   cras_alsa_jack_get_mixer_output_ret = NULL;
   cras_alsa_jack_get_name_ret_value = HEADPHONE;
@@ -1880,6 +1941,16 @@
   EXPECT_EQ(1, cras_alsa_jack_update_node_type_called);
   EXPECT_EQ(3, cras_card_config_get_volume_curve_for_control_called);
 
+  // These settings should be placed after plugging jacks to make it safer.
+  // If is HDMI jack, plug event will trigger update_max_supported_channels()
+  // and do open_dev() and close_dev() once. close_dev() will perform alsa_io
+  // cleanup.
+  // Headphone jack won't trigger, but we still place here due to coherence.
+  fmt = (struct cras_audio_format*)malloc(sizeof(*fmt));
+  memcpy(fmt, &fmt_, sizeof(fmt_));
+  aio_output_->base.format = fmt;
+  aio_output_->handle = (snd_pcm_t*)0x24;
+
   // Switch to node 'Headphone'.
   node = aio_output_->base.nodes->next;
   aio_output_->base.active_node = node;
@@ -1904,7 +1975,6 @@
   aio_output_->base.format = fmt;
   aio_output_->handle = (snd_pcm_t*)0x24;
 
-  aio_output_->num_underruns = 3;  //  Something non-zero.
   sys_get_volume_return_value = fake_system_volume;
   rc = aio_output_->base.configure_dev(&aio_output_->base);
   ASSERT_EQ(0, rc);
@@ -1985,6 +2055,7 @@
     fmt_.frame_rate = 48000;
     fmt_.num_channels = 2;
     aio.base.frames_queued = frames_queued;
+    aio.base.output_underrun = alsa_output_underrun;
     aio.base.direction = CRAS_STREAM_OUTPUT;
     aio.base.format = &fmt_;
     aio.base.buffer_size = BUFFER_SIZE;
@@ -2136,6 +2207,7 @@
   EXPECT_EQ(0, cras_iodev_fill_odev_zeros_frames);
   EXPECT_EQ(0, aio.free_running);
   EXPECT_EQ(0, aio.filled_zeros_for_draining);
+  EXPECT_EQ(1, cras_iodev_reset_rate_estimator_called);
 }
 
 TEST_F(AlsaFreeRunTestSuite, LeaveFreeRunNotInFreeRunLessRemain) {
@@ -2160,6 +2232,7 @@
   EXPECT_EQ(96, cras_iodev_fill_odev_zeros_frames);
   EXPECT_EQ(0, aio.free_running);
   EXPECT_EQ(0, aio.filled_zeros_for_draining);
+  EXPECT_EQ(1, cras_iodev_reset_rate_estimator_called);
 }
 
 TEST_F(AlsaFreeRunTestSuite, LeaveFreeRunInFreeRun) {
@@ -2177,6 +2250,7 @@
             cras_alsa_resume_appl_ptr_ahead);
   EXPECT_EQ(0, aio.free_running);
   EXPECT_EQ(0, aio.filled_zeros_for_draining);
+  EXPECT_EQ(1, cras_iodev_reset_rate_estimator_called);
 }
 
 // Reuse AlsaFreeRunTestSuite for output underrun handling because they are
@@ -2186,12 +2260,9 @@
   int16_t* zeros;
   snd_pcm_uframes_t offset;
 
-  aio.num_underruns = 0;
-
   // Ask alsa_io to handle output underrun.
   rc = alsa_output_underrun(&aio.base);
   EXPECT_EQ(0, rc);
-  EXPECT_EQ(1, aio.num_underruns);
 
   // mmap buffer should be filled with zeros.
   zeros = (int16_t*)calloc(BUFFER_SIZE * 2, sizeof(*zeros));
@@ -2235,7 +2306,7 @@
   ASSERT_EQ(0, rc);
 
   ASSERT_NE(reinterpret_cast<thread_callback>(NULL), audio_thread_cb);
-  audio_thread_cb(audio_thread_cb_data);
+  audio_thread_cb(audio_thread_cb_data, POLLIN);
   EXPECT_EQ(1, hotword_send_triggered_msg_called);
   alsa_iodev_destroy(iodev);
 }
@@ -2371,9 +2442,21 @@
   (*rates)[0] = 44100;
   (*rates)[1] = 48000;
   (*rates)[2] = 0;
-  *channel_counts = (size_t*)malloc(sizeof(**channel_counts) * 2);
-  (*channel_counts)[0] = 2;
-  (*channel_counts)[1] = 0;
+
+  if (cras_alsa_support_8_channels) {  // Support up to 8 channels.
+    *channel_counts = (size_t*)malloc(sizeof(**channel_counts) * 6);
+    (*channel_counts)[0] = 6;
+    (*channel_counts)[1] = 4;
+    (*channel_counts)[2] = 2;
+    (*channel_counts)[3] = 1;
+    (*channel_counts)[4] = 8;
+    (*channel_counts)[5] = 0;
+  } else {  // Support 2 channels only.
+    *channel_counts = (size_t*)malloc(sizeof(**channel_counts) * 2);
+    (*channel_counts)[0] = 2;
+    (*channel_counts)[1] = 0;
+  }
+
   *formats = (snd_pcm_format_t*)malloc(sizeof(**formats) * 2);
   (*formats)[0] = SND_PCM_FORMAT_S16_LE;
   (*formats)[1] = (snd_pcm_format_t)0;
@@ -2388,7 +2471,7 @@
                            unsigned int dma_period_time) {
   return 0;
 }
-int cras_alsa_set_swparams(snd_pcm_t* handle, int* enable_htimestamp) {
+int cras_alsa_set_swparams(snd_pcm_t* handle) {
   return 0;
 }
 int cras_alsa_get_avail_frames(snd_pcm_t* handle,
@@ -2462,11 +2545,6 @@
   return sys_get_volume_return_value;
 }
 
-long cras_system_get_capture_gain() {
-  sys_get_capture_gain_called++;
-  return sys_get_capture_gain_return_value;
-}
-
 int cras_system_get_mute() {
   sys_get_mute_called++;
   return sys_get_mute_return_value;
@@ -2481,10 +2559,8 @@
   sys_set_volume_limits_called++;
 }
 
-void cras_system_set_capture_gain_limits(long min, long max) {
-  cras_system_set_capture_gain_limits_set_value[0] = min;
-  cras_system_set_capture_gain_limits_set_value[1] = max;
-  sys_set_capture_gain_limits_called++;
+bool cras_system_get_noise_cancellation_enabled() {
+  return false;
 }
 
 //  From cras_alsa_mixer.
@@ -2635,15 +2711,6 @@
   return cras_alsa_jack_get_name_ret_value;
 }
 
-const char* ucm_get_dsp_name_default(struct cras_use_case_mgr* mgr,
-                                     int direction) {
-  ucm_get_dsp_name_default_called++;
-  if (ucm_get_dsp_name_default_value)
-    return strdup(ucm_get_dsp_name_default_value);
-  else
-    return NULL;
-}
-
 const char* ucm_get_dsp_name_for_dev(struct cras_use_case_mgr* mgr,
                                      const char* dev) {
   DspNameMap::iterator it;
@@ -2686,10 +2753,6 @@
   return NULL;
 }
 
-char* ucm_get_mic_positions(struct cras_use_case_mgr* mgr) {
-  return NULL;
-}
-
 int ucm_swap_mode_exists(struct cras_use_case_mgr* mgr) {
   return ucm_swap_mode_exists_ret_value;
 }
@@ -2707,30 +2770,10 @@
   return 0;
 }
 
-unsigned int ucm_get_enable_htimestamp_flag(struct cras_use_case_mgr* mgr) {
-  return ucm_get_enable_htimestamp_flag_ret;
-}
-
 unsigned int ucm_get_disable_software_volume(struct cras_use_case_mgr* mgr) {
   return 0;
 }
 
-int ucm_get_min_software_gain(struct cras_use_case_mgr* mgr,
-                              const char* dev,
-                              long* gain) {
-  ucm_get_min_software_gain_called++;
-  *gain = ucm_get_min_software_gain_value;
-  return ucm_get_min_software_gain_ret_value;
-}
-
-int ucm_get_max_software_gain(struct cras_use_case_mgr* mgr,
-                              const char* dev,
-                              long* gain) {
-  ucm_get_max_software_gain_called++;
-  *gain = ucm_get_max_software_gain_value;
-  return ucm_get_max_software_gain_ret_value;
-}
-
 char* ucm_get_hotword_models(struct cras_use_case_mgr* mgr) {
   return NULL;
 }
@@ -2761,6 +2804,24 @@
   return 0;
 }
 
+int ucm_get_channels_for_dev(struct cras_use_case_mgr* mgr,
+                             const char* dev,
+                             enum CRAS_STREAM_DIRECTION direction,
+                             size_t* channels) {
+  return -EINVAL;
+}
+
+int ucm_node_noise_cancellation_exists(struct cras_use_case_mgr* mgr,
+                                       const char* node_name) {
+  return 0;
+}
+
+int ucm_enable_node_noise_cancellation(struct cras_use_case_mgr* mgr,
+                                       const char* node_name,
+                                       int enable) {
+  return 0;
+}
+
 struct cras_volume_curve* cras_volume_curve_create_default() {
   return &default_curve;
 }
@@ -2797,7 +2858,7 @@
 
 void cras_iodev_update_dsp(struct cras_iodev* iodev) {
   cras_iodev_update_dsp_called++;
-  cras_iodev_update_dsp_name = iodev->dsp_name;
+  cras_iodev_update_dsp_name = iodev->dsp_name ?: "";
 }
 
 void cras_iodev_set_node_plugged(struct cras_ionode* ionode, int plugged) {
@@ -2842,6 +2903,12 @@
   return NULL;
 }
 
+void ucm_disable_all_hotword_models(struct cras_use_case_mgr* mgr) {}
+
+int ucm_enable_hotword_model(struct cras_use_case_mgr* mgr) {
+  return 0;
+}
+
 int ucm_get_default_node_gain(struct cras_use_case_mgr* mgr,
                               const char* dev,
                               long* gain) {
@@ -2853,11 +2920,23 @@
   return 0;
 }
 
+int ucm_get_intrinsic_sensitivity(struct cras_use_case_mgr* mgr,
+                                  const char* dev,
+                                  long* vol) {
+  if (ucm_get_intrinsic_sensitivity_values.find(dev) ==
+      ucm_get_intrinsic_sensitivity_values.end())
+    return 1;
+
+  *vol = ucm_get_intrinsic_sensitivity_values[dev];
+  return 0;
+}
+
 void cras_iodev_init_audio_area(struct cras_iodev* iodev, int num_channels) {}
 
 void cras_iodev_free_audio_area(struct cras_iodev* iodev) {}
 
 int cras_iodev_reset_rate_estimator(const struct cras_iodev* iodev) {
+  cras_iodev_reset_rate_estimator_called++;
   return 0;
 }
 
@@ -2881,7 +2960,10 @@
                                          const struct cras_audio_format* fmt,
                                          uint8_t* base_buffer) {}
 
-void audio_thread_add_callback(int fd, thread_callback cb, void* data) {
+void audio_thread_add_events_callback(int fd,
+                                      thread_callback cb,
+                                      void* data,
+                                      int events) {
   audio_thread_cb = cb;
   audio_thread_cb_data = data;
 }
@@ -2932,6 +3014,12 @@
   return 0;
 }
 
+int cras_iodev_output_underrun(struct cras_iodev* odev,
+                               unsigned int hw_level,
+                               unsigned int frames_written) {
+  return odev->output_underrun(odev);
+}
+
 enum CRAS_IODEV_STATE cras_iodev_state(const struct cras_iodev* iodev) {
   return iodev->state;
 }
diff --git a/cras/src/tests/alsa_jack_unittest.cc b/cras/src/tests/alsa_jack_unittest.cc
index 24b43a3..1aa9549 100644
--- a/cras/src/tests/alsa_jack_unittest.cc
+++ b/cras/src/tests/alsa_jack_unittest.cc
@@ -86,7 +86,7 @@
 static int gpio_switch_eviocgbit_fd;
 static const char* edid_file_ret;
 static unsigned ucm_get_override_type_name_called;
-static char* ucm_get_device_name_for_dev_value;
+static int ucm_get_alsa_dev_idx_for_dev_value;
 static snd_hctl_t* fake_hctl = (snd_hctl_t*)2;
 
 static void ResetStubData() {
@@ -137,7 +137,7 @@
   ucm_get_dev_for_jack_return = false;
   edid_file_ret = NULL;
   ucm_get_override_type_name_called = 0;
-  ucm_get_device_name_for_dev_value = NULL;
+  ucm_get_alsa_dev_idx_for_dev_value = -1;
 
   memset(eviocbit_ret, 0, sizeof(eviocbit_ret));
 }
@@ -216,14 +216,15 @@
   EXPECT_EQ(0, cras_alsa_jack_list_find_jacks_by_name_matching(jack_list));
   EXPECT_EQ(ucm ? njacks : 0, ucm_get_dev_for_jack_called);
   EXPECT_EQ(ucm ? njacks : 0, ucm_get_override_type_name_called);
-  EXPECT_EQ(1 + nhdmi_jacks, snd_hctl_first_elem_called);
+  EXPECT_EQ(1, snd_hctl_first_elem_called);
   EXPECT_EQ(njacks, snd_hctl_elem_set_callback_called);
+  EXPECT_EQ(nhdmi_jacks, snd_hctl_find_elem_called);
 
   /* For some functions, the number of calls to them could
    * be larger then expected count if there is ELD control
    * in given elements. */
-  EXPECT_GE(snd_hctl_elem_next_called, nelems + nhdmi_jacks);
-  EXPECT_GE(snd_hctl_elem_get_name_called, nelems + njacks);
+  EXPECT_GE(snd_hctl_elem_next_called, nelems);
+  EXPECT_GE(snd_hctl_elem_get_name_called, nelems);
 
   if (direction == CRAS_STREAM_OUTPUT) {
     EXPECT_EQ(njacks, cras_alsa_mixer_get_output_matching_name_called);
@@ -417,7 +418,7 @@
 
   /* PlaybackPCM matched, so create jack even if this is not the first device.*/
   ucm_get_dev_for_jack_return = true;
-  ucm_get_device_name_for_dev_value = strdup("hw:c1,1");
+  ucm_get_alsa_dev_idx_for_dev_value = 1;
 
   run_gpio_jack_test(device_index, is_first_device, direction,
                      should_create_jack, "c1 Headset Jack");
@@ -433,7 +434,7 @@
 
   /* CapturePCM matched, so create jack even if this is not the first device.*/
   ucm_get_dev_for_jack_return = true;
-  ucm_get_device_name_for_dev_value = strdup("hw:c1,1");
+  ucm_get_alsa_dev_idx_for_dev_value = 1;
 
   run_gpio_jack_test(device_index, is_first_device, direction,
                      should_create_jack, "c1 Mic Jack");
@@ -449,7 +450,7 @@
 
   /* PlaybackPCM not matched, do not create jack. */
   ucm_get_dev_for_jack_return = true;
-  ucm_get_device_name_for_dev_value = strdup("hw:c1,2");
+  ucm_get_alsa_dev_idx_for_dev_value = 2;
 
   run_gpio_jack_test(device_index, is_first_device, direction,
                      should_create_jack, "c1 Headset Jack");
@@ -465,7 +466,7 @@
 
   /* PlaybackPCM not specified, create jack for the first device. */
   ucm_get_dev_for_jack_return = true;
-  ucm_get_device_name_for_dev_value = NULL;
+  ucm_get_alsa_dev_idx_for_dev_value = -1;
 
   run_gpio_jack_test(device_index, is_first_device, direction,
                      should_create_jack, "c1 Headset Jack");
@@ -481,7 +482,7 @@
 
   /* PlaybackPCM not specified, do not create jack for the second device. */
   ucm_get_dev_for_jack_return = true;
-  ucm_get_device_name_for_dev_value = NULL;
+  ucm_get_alsa_dev_idx_for_dev_value = -1;
 
   run_gpio_jack_test(device_index, is_first_device, direction,
                      should_create_jack, "c1 Headset Jack");
@@ -497,7 +498,7 @@
 
   /* No UCM for this jack, create jack for the first device. */
   ucm_get_dev_for_jack_return = false;
-  ucm_get_device_name_for_dev_value = NULL;
+  ucm_get_alsa_dev_idx_for_dev_value = -1;
 
   run_gpio_jack_test(device_index, is_first_device, direction,
                      should_create_jack, "c1 Headset Jack");
@@ -513,7 +514,7 @@
 
   /* No UCM for this jack, dot not create jack for the second device. */
   ucm_get_dev_for_jack_return = false;
-  ucm_get_device_name_for_dev_value = NULL;
+  ucm_get_alsa_dev_idx_for_dev_value = -1;
 
   run_gpio_jack_test(device_index, is_first_device, direction,
                      should_create_jack, "c1 Headset Jack");
@@ -529,7 +530,7 @@
 
   // No UCM for this jack, create jack for the first device.
   ucm_get_dev_for_jack_return = false;
-  ucm_get_device_name_for_dev_value = NULL;
+  ucm_get_alsa_dev_idx_for_dev_value = -1;
 
   // Mic Jack is a valid name for microphone jack.
   run_gpio_jack_test(device_index, is_first_device, direction,
@@ -546,7 +547,7 @@
 
   // No UCM for this jack, create jack for the first device.
   ucm_get_dev_for_jack_return = false;
-  ucm_get_device_name_for_dev_value = NULL;
+  ucm_get_alsa_dev_idx_for_dev_value = -1;
 
   // Headset Jack is a valid name for microphone jack.
   run_gpio_jack_test(device_index, is_first_device, direction,
@@ -678,7 +679,7 @@
   ASSERT_NE(static_cast<struct cras_alsa_jack_list*>(NULL), jack_list);
 
   /* Assert get device is called for the ELD control */
-  EXPECT_EQ(1, snd_hctl_elem_get_device_called);
+  EXPECT_EQ(1, snd_hctl_find_elem_called);
   cras_alsa_jack_list_destroy(jack_list);
 }
 
@@ -728,7 +729,7 @@
   struct cras_alsa_jack_list* jack_list;
   struct ucm_section* section;
 
-  section = ucm_section_create("Headphone", 0, CRAS_STREAM_OUTPUT,
+  section = ucm_section_create("Headphone", "hw:0,1", 0, -1, CRAS_STREAM_OUTPUT,
                                "Headphone Jack", "hctl");
 
   ResetStubData();
@@ -764,7 +765,7 @@
   struct cras_alsa_jack* jack;
   struct ucm_section* section;
 
-  section = ucm_section_create("Headphone", 0, CRAS_STREAM_OUTPUT,
+  section = ucm_section_create("Headphone", "hw:0,1", 0, -1, CRAS_STREAM_OUTPUT,
                                "c1 Headphone Jack", "gpio");
 
   ResetStubData();
@@ -806,7 +807,7 @@
   struct cras_alsa_jack_list* jack_list;
   struct ucm_section* section;
 
-  section = ucm_section_create("Headphone", 0, CRAS_STREAM_OUTPUT,
+  section = ucm_section_create("Headphone", "hw:0,1", 0, -1, CRAS_STREAM_OUTPUT,
                                "Headphone Jack", "badtype");
 
   ResetStubData();
@@ -828,7 +829,7 @@
   struct cras_alsa_jack_list* jack_list;
   struct ucm_section* section;
 
-  section = ucm_section_create("Headphone", 0, CRAS_STREAM_OUTPUT,
+  section = ucm_section_create("Headphone", "hw:0,1", 0, -1, CRAS_STREAM_OUTPUT,
                                "Headphone Jack", NULL);
 
   ResetStubData();
@@ -1053,10 +1054,10 @@
   return NULL;
 }
 
-const char* ucm_get_device_name_for_dev(struct cras_use_case_mgr* mgr,
-                                        const char* dev,
-                                        enum CRAS_STREAM_DIRECTION direction) {
-  return ucm_get_device_name_for_dev_value;
+int ucm_get_alsa_dev_idx_for_dev(struct cras_use_case_mgr* mgr,
+                                 const char* dev,
+                                 enum CRAS_STREAM_DIRECTION direction) {
+  return ucm_get_alsa_dev_idx_for_dev_value;
 }
 
 cras_timer* cras_tm_create_timer(cras_tm* tm,
diff --git a/cras/src/tests/alsa_mixer_unittest.cc b/cras/src/tests/alsa_mixer_unittest.cc
index d61ba3d..b3db9de 100644
--- a/cras/src/tests/alsa_mixer_unittest.cc
+++ b/cras/src/tests/alsa_mixer_unittest.cc
@@ -381,7 +381,7 @@
   mixer_control_destroy(mixer_output);
 }
 
-TEST(AlsaMixer, CreateOneMasterElement) {
+TEST(AlsaMixer, CreateOneMainElement) {
   struct cras_alsa_mixer* c;
   int element_playback_volume[] = {
       1,
@@ -419,10 +419,10 @@
   EXPECT_EQ(3, snd_mixer_selem_get_name_called);
   EXPECT_EQ(1, snd_mixer_elem_next_called);
 
-  /* set mute should be called for Master. */
+  /* set mute should be called for Main. */
   cras_alsa_mixer_set_mute(c, 0, NULL);
   EXPECT_EQ(1, snd_mixer_selem_set_playback_switch_all_called);
-  /* set volume should be called for Master. */
+  /* set volume should be called for Main. */
   cras_alsa_mixer_set_dBFS(c, 0, NULL);
   EXPECT_EQ(1, snd_mixer_selem_set_playback_dB_all_called);
 
@@ -515,15 +515,15 @@
   EXPECT_EQ(5, snd_mixer_selem_get_name_called);
   EXPECT_EQ(3, snd_mixer_selem_has_playback_switch_called);
 
-  /* Set mute should be called for Master only. */
+  /* Set mute should be called for Main only. */
   cras_alsa_mixer_set_mute(c, 0, NULL);
   EXPECT_EQ(1, snd_mixer_selem_set_playback_switch_all_called);
 
-  /* Set volume should be called for Master and PCM. If Master doesn't set to
+  /* Set volume should be called for Main and PCM. If Main doesn't set to
    * anything but zero then the entire volume should be passed to the PCM
    * control.*/
 
-  /* Set volume should be called for Master and PCM. (without mixer_output) */
+  /* Set volume should be called for Main and PCM. (without mixer_output) */
   snd_mixer_selem_get_playback_dB_return_values = get_dB_returns;
   snd_mixer_selem_get_playback_dB_return_values_length =
       ARRAY_SIZE(get_dB_returns);
@@ -557,8 +557,8 @@
   EXPECT_EQ(1, snd_mixer_selem_has_playback_switch_called);
   EXPECT_EQ(1, snd_mixer_selem_get_playback_dB_range_called);
 
-  /* Set volume should be called for Master, PCM, and the mixer_output passed
-   * in. If Master doesn't set to anything but zero then the entire volume
+  /* Set volume should be called for Main, PCM, and the mixer_output passed
+   * in. If Main doesn't set to anything but zero then the entire volume
    * should be passed to the PCM control.*/
   cras_alsa_mixer_set_dBFS(c, -50, mixer_output);
   EXPECT_EQ(3, snd_mixer_selem_set_playback_dB_all_called);
@@ -566,8 +566,8 @@
   EXPECT_EQ(30, set_dB_values[0]);
   EXPECT_EQ(30, set_dB_values[1]);
   EXPECT_EQ(30, set_dB_values[2]);
-  /* Set volume should be called for Master and PCM. Since the controls were
-   * sorted, Master should get the volume remaining after PCM is set, in this
+  /* Set volume should be called for Main and PCM. Since the controls were
+   * sorted, Main should get the volume remaining after PCM is set, in this
    * case -50 - -24 = -26. */
   long get_dB_returns2[] = {
       -25,
@@ -584,7 +584,7 @@
   cras_alsa_mixer_set_dBFS(c, -50, mixer_output);
   EXPECT_EQ(2, snd_mixer_selem_set_playback_dB_all_called);
   EXPECT_EQ(2, snd_mixer_selem_get_playback_dB_called);
-  EXPECT_EQ(54, set_dB_values[0]);  // Master
+  EXPECT_EQ(54, set_dB_values[0]);  // Main
   EXPECT_EQ(30, set_dB_values[1]);  // PCM
 
   cras_alsa_mixer_destroy(c);
@@ -639,7 +639,7 @@
   EXPECT_EQ(5, snd_mixer_selem_get_name_called);
   EXPECT_EQ(3, snd_mixer_selem_has_capture_switch_called);
 
-  /* Set mute should be called for Master only. */
+  /* Set mute should be called for Main only. */
   cras_alsa_mixer_set_capture_mute(c, 0, NULL);
   EXPECT_EQ(1, snd_mixer_selem_set_capture_switch_all_called);
   /* Set volume should be called for Capture and Digital Capture. If Capture
@@ -773,7 +773,7 @@
 
     ResetStubData();
     snd_mixer_first_elem_return_value =
-        reinterpret_cast<snd_mixer_elem_t*>(1);  // Master
+        reinterpret_cast<snd_mixer_elem_t*>(1);  // Main
     snd_mixer_elem_next_return_values = elements;
     snd_mixer_elem_next_return_values_length = ARRAY_SIZE(elements);
     snd_mixer_selem_has_playback_volume_return_values = element_playback_volume;
@@ -1275,29 +1275,32 @@
     for (i = 0; i < ARRAY_SIZE(elements); i++)
       snd_mixer_find_elem_map[element_names[i]] = elements[i];
 
-    section =
-        ucm_section_create("NullElement", 0, CRAS_STREAM_OUTPUT, NULL, NULL);
+    section = ucm_section_create("NullElement", "hw:0,1", 0, -1,
+                                 CRAS_STREAM_OUTPUT, NULL, NULL);
     ucm_section_set_mixer_name(section, "Unknown");
     DL_APPEND(sections, section);
-    section = ucm_section_create("Headphone", 0, CRAS_STREAM_OUTPUT,
-                                 "my-sound-card Headset Jack", "gpio");
+    section =
+        ucm_section_create("Headphone", "hw:0,1", 0, -1, CRAS_STREAM_OUTPUT,
+                           "my-sound-card Headset Jack", "gpio");
     ucm_section_add_coupled(section, "HP-L", MIXER_NAME_VOLUME);
     ucm_section_add_coupled(section, "HP-R", MIXER_NAME_VOLUME);
     DL_APPEND(sections, section);
-    section = ucm_section_create("Speaker", 0, CRAS_STREAM_OUTPUT, NULL, NULL);
+    section = ucm_section_create("Speaker", "hw:0,1", 0, -1, CRAS_STREAM_OUTPUT,
+                                 NULL, NULL);
     ucm_section_add_coupled(section, "SPK-L", MIXER_NAME_VOLUME);
     ucm_section_add_coupled(section, "SPK-R", MIXER_NAME_VOLUME);
     DL_APPEND(sections, section);
-    section = ucm_section_create("Mic", 0, CRAS_STREAM_INPUT,
+    section = ucm_section_create("Mic", "hw:0,1", 0, -1, CRAS_STREAM_INPUT,
                                  "my-sound-card Headset Jack", "gpio");
     ucm_section_set_mixer_name(section, "CAPTURE");
     DL_APPEND(sections, section);
-    section =
-        ucm_section_create("Internal Mic", 0, CRAS_STREAM_INPUT, NULL, NULL);
+    section = ucm_section_create("Internal Mic", "hw:0,1", 0, -1,
+                                 CRAS_STREAM_INPUT, NULL, NULL);
     ucm_section_add_coupled(section, "MIC-L", MIXER_NAME_VOLUME);
     ucm_section_add_coupled(section, "MIC-R", MIXER_NAME_VOLUME);
     DL_APPEND(sections, section);
-    section = ucm_section_create("HDMI", 0, CRAS_STREAM_OUTPUT, NULL, NULL);
+    section = ucm_section_create("HDMI", "hw:0,1", 0, -1, CRAS_STREAM_OUTPUT,
+                                 NULL, NULL);
     ucm_section_set_mixer_name(section, "HDMI");
     DL_APPEND(sections, section);
     ASSERT_NE(sections, (struct ucm_section*)NULL);
diff --git a/cras/src/tests/alsa_ucm_unittest.cc b/cras/src/tests/alsa_ucm_unittest.cc
index 0773079..1b351dd 100644
--- a/cras/src/tests/alsa_ucm_unittest.cc
+++ b/cras/src/tests/alsa_ucm_unittest.cc
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 #include <gtest/gtest.h>
-#include <iniparser.h>
 #include <stdio.h>
 #include <syslog.h>
 
@@ -29,11 +28,13 @@
 static std::vector<std::string> snd_use_case_get_id;
 static int snd_use_case_set_return;
 static std::map<std::string, std::string> snd_use_case_get_value;
+static std::map<std::string, unsigned> snd_use_case_geti_value;
 static unsigned snd_use_case_set_called;
 static std::vector<std::pair<std::string, std::string> > snd_use_case_set_param;
 static std::map<std::string, const char**> fake_list;
 static std::map<std::string, unsigned> fake_list_size;
 static unsigned snd_use_case_free_list_called;
+static unsigned snd_use_case_geti_called;
 static std::vector<std::string> list_devices_callback_names;
 static std::vector<void*> list_devices_callback_args;
 static struct cras_use_case_mgr cras_ucm_mgr;
@@ -46,10 +47,12 @@
   snd_use_case_set_return = 0;
   snd_use_case_get_called = 0;
   snd_use_case_set_called = 0;
+  snd_use_case_geti_called = 0;
   snd_use_case_set_param.clear();
   snd_use_case_free_list_called = 0;
   snd_use_case_get_id.clear();
   snd_use_case_get_value.clear();
+  snd_use_case_geti_value.clear();
   fake_list.clear();
   fake_list_size.clear();
   fake_list["_verbs"] = avail_verbs;
@@ -58,6 +61,7 @@
   list_devices_callback_args.clear();
   snd_use_case_mgr_open_mgr_ptr = reinterpret_cast<snd_use_case_mgr_t*>(0x55);
   cras_ucm_mgr.use_case = CRAS_STREAM_TYPE_DEFAULT;
+  cras_ucm_mgr.hotword_modifier = NULL;
 }
 
 static void list_devices_callback(const char* section_name, void* arg) {
@@ -269,8 +273,8 @@
 
   fake_list["_devices/HiFi"] = devices;
   fake_list_size["_devices/HiFi"] = 4;
-  std::string id_1 = "=JackName/Dev1/HiFi";
-  std::string id_2 = "=JackName/Dev2/HiFi";
+  std::string id_1 = "=JackDev/Dev1/HiFi";
+  std::string id_2 = "=JackDev/Dev2/HiFi";
   std::string value_1 = "Value1";
   std::string value_2 = "Value2";
 
@@ -296,8 +300,8 @@
 
   fake_list["_devices/HiFi"] = devices;
   fake_list_size["_devices/HiFi"] = 4;
-  std::string id_1 = "=JackName/Mic/HiFi";
-  std::string id_2 = "=JackName/Headphone/HiFi";
+  std::string id_1 = "=JackDev/Mic/HiFi";
+  std::string id_2 = "=JackDev/Headphone/HiFi";
   std::string value = "JackValue";
 
   snd_use_case_get_value[id_1] = value;
@@ -322,8 +326,8 @@
 
   fake_list["_devices/HiFi"] = devices;
   fake_list_size["_devices/HiFi"] = 4;
-  std::string id_1 = "=JackName/Headphone/HiFi";
-  std::string id_2 = "=JackName/Mic/HiFi";
+  std::string id_1 = "=JackDev/Headphone/HiFi";
+  std::string id_2 = "=JackDev/Mic/HiFi";
   std::string value = "JackValue";
 
   snd_use_case_get_value[id_1] = value;
@@ -348,8 +352,8 @@
 
   fake_list["_devices/HiFi"] = devices;
   fake_list_size["_devices/HiFi"] = 4;
-  std::string id_1 = "=MixerName/Dev1/HiFi";
-  std::string id_2 = "=MixerName/Dev2/HiFi";
+  std::string id_1 = "=PlaybackMixerElem/Dev1/HiFi";
+  std::string id_2 = "=CaptureMixerElem/Dev2/HiFi";
   std::string value_1 = "Value1";
   std::string value_2 = "Value2";
 
@@ -368,9 +372,8 @@
   free((void*)dev_name_in);
 }
 
-TEST(AlsaUcm, GetDeviceNameForDevice) {
+TEST(AlsaUcm, GetAlsaDeviceIndexForDevice) {
   struct cras_use_case_mgr* mgr = &cras_ucm_mgr;
-  const char *input_dev_name, *output_dev_name;
   const char* devices[] = {"Dev1", "Comment for Dev1", "Dev2",
                            "Comment for Dev2"};
 
@@ -380,24 +383,17 @@
   fake_list_size["_devices/HiFi"] = 4;
   std::string id_1 = "=CapturePCM/Dev1/HiFi";
   std::string id_2 = "=PlaybackPCM/Dev2/HiFi";
-  std::string value_1 = "DeviceName1";
-  std::string value_2 = "DeviceName2";
+  std::string value_1 = "PCMName,1";
+  std::string value_2 = "PCMName,2";
 
   snd_use_case_get_value[id_1] = value_1;
   snd_use_case_get_value[id_2] = value_2;
-  input_dev_name = ucm_get_device_name_for_dev(mgr, "Dev1", CRAS_STREAM_INPUT);
-  output_dev_name =
-      ucm_get_device_name_for_dev(mgr, "Dev2", CRAS_STREAM_OUTPUT);
-  ASSERT_TRUE(input_dev_name);
-  ASSERT_TRUE(output_dev_name);
-  EXPECT_EQ(0, strcmp(input_dev_name, value_1.c_str()));
-  EXPECT_EQ(0, strcmp(output_dev_name, value_2.c_str()));
+  EXPECT_EQ(1, ucm_get_alsa_dev_idx_for_dev(mgr, "Dev1", CRAS_STREAM_INPUT));
+  EXPECT_EQ(2, ucm_get_alsa_dev_idx_for_dev(mgr, "Dev2", CRAS_STREAM_OUTPUT));
 
   ASSERT_EQ(2, snd_use_case_get_called);
   EXPECT_EQ(snd_use_case_get_id[0], id_1);
   EXPECT_EQ(snd_use_case_get_id[1], id_2);
-  free((void*)input_dev_name);
-  free((void*)output_dev_name);
 }
 
 TEST(AlsaUcm, GetDeviceRateForDevice) {
@@ -428,6 +424,38 @@
   EXPECT_EQ(snd_use_case_get_id[1], id_2);
 }
 
+TEST(AlsaUcm, GetDeviceChannelsForDevice) {
+  struct cras_use_case_mgr* mgr = &cras_ucm_mgr;
+  int rc;
+  size_t input_dev_channels, output_dev_channels;
+  const char* devices[] = {"Dev1", "Comment for Dev1", "Dev2",
+                           "Comment for Dev2"};
+
+  ResetStubData();
+
+  fake_list["_devices/HiFi"] = devices;
+  fake_list_size["_devices/HiFi"] = 4;
+  std::string id_1 = "=CaptureChannels/Dev1/HiFi";
+  std::string id_2 = "=PlaybackChannels/Dev2/HiFi";
+  std::string value_1 = "4";
+  std::string value_2 = "8";
+
+  snd_use_case_get_value[id_1] = value_1;
+  snd_use_case_get_value[id_2] = value_2;
+  rc = ucm_get_channels_for_dev(mgr, "Dev1", CRAS_STREAM_INPUT,
+                                &input_dev_channels);
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(4, input_dev_channels);
+  rc = ucm_get_channels_for_dev(mgr, "Dev2", CRAS_STREAM_OUTPUT,
+                                &output_dev_channels);
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(8, output_dev_channels);
+
+  ASSERT_EQ(2, snd_use_case_get_called);
+  EXPECT_EQ(snd_use_case_get_id[0], id_1);
+  EXPECT_EQ(snd_use_case_get_id[1], id_2);
+}
+
 TEST(AlsaUcm, GetCaptureChannelMapForDevice) {
   struct cras_use_case_mgr* mgr = &cras_ucm_mgr;
   int8_t channel_layout[CRAS_CH_MAX];
@@ -499,26 +527,85 @@
   const char* modifiers[] = {"Hotword Model en", "Comment1",
                              "Hotword Model jp", "Comment2",
                              "Hotword Model de", "Comment3"};
-  const char* enabled_mods[] = {"Hotword Model en"};
+  const char* enabled_mods[] = {"Hotword Model jp"};
+  int ret;
+  std::string id = "_modstatus/Hotword Model jp";
   ResetStubData();
 
+  snd_use_case_geti_value[id] = 1;
   fake_list["_modifiers/HiFi"] = modifiers;
   fake_list_size["_modifiers/HiFi"] = 6;
 
   EXPECT_EQ(-EINVAL, ucm_set_hotword_model(mgr, "zh"));
   EXPECT_EQ(0, snd_use_case_set_called);
 
+  ret = ucm_set_hotword_model(mgr, "jp");
+
+  EXPECT_EQ(0, ret);
+  EXPECT_EQ(0, snd_use_case_set_called);
+  EXPECT_EQ(0, strcmp(mgr->hotword_modifier, "Hotword Model jp"));
+
   fake_list["_enamods"] = enabled_mods;
   fake_list_size["_enamods"] = 1;
-  ucm_set_hotword_model(mgr, "jp");
-
+  ret = ucm_set_hotword_model(mgr, "de");
+  EXPECT_EQ(0, ret);
   EXPECT_EQ(2, snd_use_case_set_called);
+  EXPECT_EQ(1, snd_use_case_geti_called);
+  EXPECT_EQ(
+      snd_use_case_set_param[0],
+      std::make_pair(std::string("_dismod"), std::string("Hotword Model jp")));
+  EXPECT_EQ(
+      snd_use_case_set_param[1],
+      std::make_pair(std::string("_enamod"), std::string("Hotword Model de")));
+  free(mgr->hotword_modifier);
+}
+
+TEST(AlsaUcm, DisableAllHotwordModels) {
+  struct cras_use_case_mgr* mgr = &cras_ucm_mgr;
+  const char* modifiers[] = {"Hotword Model en", "Comment1",
+                             "Hotword Model jp", "Comment2",
+                             "Hotword Model de", "Comment3"};
+  const char* enabled_mods[] = {"Hotword Model en"};
+  ResetStubData();
+
+  fake_list["_modifiers/HiFi"] = modifiers;
+  fake_list_size["_modifiers/HiFi"] = 6;
+  fake_list["_enamods"] = enabled_mods;
+  fake_list_size["_enamods"] = 1;
+
+  ucm_disable_all_hotword_models(mgr);
+
+  EXPECT_EQ(1, snd_use_case_set_called);
   EXPECT_EQ(
       snd_use_case_set_param[0],
       std::make_pair(std::string("_dismod"), std::string("Hotword Model en")));
+}
+
+TEST(AlsaUcm, EnableHotwordModel) {
+  struct cras_use_case_mgr* mgr = &cras_ucm_mgr;
+  const char* modifiers[] = {"Hotword Model en", "Comment1",
+                             "Hotword Model jp", "Comment2",
+                             "Hotword Model de", "Comment3"};
+  const char* enabled_mods[] = {""};
+  int ret;
+  ResetStubData();
+
+  fake_list["_modifiers/HiFi"] = modifiers;
+  fake_list_size["_modifiers/HiFi"] = 6;
+  fake_list["_enamods"] = enabled_mods;
+  fake_list_size["_enamods"] = 0;
+
+  EXPECT_EQ(-EINVAL, ucm_enable_hotword_model(mgr));
+
+  mgr->hotword_modifier = strdup("Hotword Model de");
+  ret = ucm_enable_hotword_model(mgr);
+
+  EXPECT_EQ(0, ret);
+  EXPECT_EQ(1, snd_use_case_set_called);
   EXPECT_EQ(
-      snd_use_case_set_param[1],
-      std::make_pair(std::string("_enamod"), std::string("Hotword Model jp")));
+      snd_use_case_set_param[0],
+      std::make_pair(std::string("_enamod"), std::string("Hotword Model de")));
+  free(mgr->hotword_modifier);
 }
 
 TEST(AlsaUcm, SwapModeExists) {
@@ -606,6 +693,76 @@
   EXPECT_EQ(1, snd_use_case_set_called);
 }
 
+TEST(AlsaUcm, NoiseCancellationExists) {
+  struct cras_use_case_mgr* mgr = &cras_ucm_mgr;
+  int rc;
+  const char* node = "Internal Mic";
+  const char* modifiers_1[] = {"Internal Mic Noise Cancellation", "Comment"};
+  const char* modifiers_2[] = {"Internal Mic Noise Augmentation", "Comment"};
+  const char* modifiers_3[] = {"Microphone Noise Cancellation", "Comment"};
+
+  ResetStubData();
+
+  fake_list["_modifiers/HiFi"] = modifiers_1;
+  fake_list_size["_modifiers/HiFi"] = 2;
+  rc = ucm_node_noise_cancellation_exists(mgr, node);
+  EXPECT_EQ(1, rc);
+
+  fake_list["_modifiers/HiFi"] = modifiers_2;
+  fake_list_size["_modifiers/HiFi"] = 2;
+  rc = ucm_node_noise_cancellation_exists(mgr, node);
+  EXPECT_EQ(0, rc);
+
+  fake_list["_modifiers/HiFi"] = modifiers_3;
+  fake_list_size["_modifiers/HiFi"] = 2;
+  rc = ucm_node_noise_cancellation_exists(mgr, node);
+  EXPECT_EQ(0, rc);
+}
+
+TEST(AlsaUcm, EnableDisableNoiseCancellation) {
+  struct cras_use_case_mgr* mgr = &cras_ucm_mgr;
+  int rc;
+  const char* modifiers[] = {"Internal Mic Noise Cancellation", "Comment1",
+                             "Microphone Noise Cancellation", "Comment2"};
+  const char* modifiers_enabled[] = {"Internal Mic Noise Cancellation"};
+
+  ResetStubData();
+
+  fake_list["_modifiers/HiFi"] = modifiers;
+  fake_list_size["_modifiers/HiFi"] = 4;
+
+  fake_list["_enamods"] = modifiers_enabled;
+  fake_list_size["_enamods"] = 1;
+
+  snd_use_case_set_return = 0;
+
+  rc = ucm_enable_node_noise_cancellation(mgr, "Line In", 1);
+  EXPECT_EQ(-EPERM, rc);  // Modifier is not existed
+  EXPECT_EQ(0, snd_use_case_set_called);
+
+  rc = ucm_enable_node_noise_cancellation(mgr, "Line In", 0);
+  EXPECT_EQ(-EPERM, rc);  // Modifier is not existed
+  EXPECT_EQ(0, snd_use_case_set_called);
+
+  rc = ucm_enable_node_noise_cancellation(mgr, "Microphone", 0);
+  EXPECT_EQ(0, rc);  // Modifier is already disabled
+  EXPECT_EQ(0, snd_use_case_set_called);
+
+  rc = ucm_enable_node_noise_cancellation(mgr, "Microphone", 1);
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(1, snd_use_case_set_called);
+
+  snd_use_case_set_called = 0;
+
+  rc = ucm_enable_node_noise_cancellation(mgr, "Internal Mic", 1);
+  EXPECT_EQ(0, rc);  // Modifier is already enabled
+  EXPECT_EQ(0, snd_use_case_set_called);
+
+  rc = ucm_enable_node_noise_cancellation(mgr, "Internal Mic", 0);
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(1, snd_use_case_set_called);
+}
+
 TEST(AlsaFlag, GetFlag) {
   struct cras_use_case_mgr* mgr = &cras_ucm_mgr;
   char* flag_value;
@@ -767,56 +924,6 @@
   mixer_name_free(mixer_names_1);
 }
 
-TEST(AlsaUcm, MaxSoftwareGain) {
-  struct cras_use_case_mgr* mgr = &cras_ucm_mgr;
-  long max_software_gain;
-  int ret;
-  std::string id = "=MaxSoftwareGain/Internal Mic/HiFi";
-  std::string value = "2000";
-
-  ResetStubData();
-
-  /* Value can be found in UCM. */
-  snd_use_case_get_value[id] = value;
-
-  ret = ucm_get_max_software_gain(mgr, "Internal Mic", &max_software_gain);
-
-  EXPECT_EQ(0, ret);
-  EXPECT_EQ(2000, max_software_gain);
-
-  ResetStubData();
-
-  /* Value can not be found in UCM. */
-  ret = ucm_get_max_software_gain(mgr, "Internal Mic", &max_software_gain);
-
-  ASSERT_TRUE(ret);
-}
-
-TEST(AlsaUcm, MinSoftwareGain) {
-  struct cras_use_case_mgr* mgr = &cras_ucm_mgr;
-  long min_software_gain;
-  int ret;
-  std::string id = "=MinSoftwareGain/Internal Mic/HiFi";
-  std::string value = "2000";
-
-  ResetStubData();
-
-  /* Value can be found in UCM. */
-  snd_use_case_get_value[id] = value;
-
-  ret = ucm_get_min_software_gain(mgr, "Internal Mic", &min_software_gain);
-
-  EXPECT_EQ(0, ret);
-  EXPECT_EQ(2000, min_software_gain);
-
-  ResetStubData();
-
-  /* Value can not be found in UCM. */
-  ret = ucm_get_min_software_gain(mgr, "Internal Mic", &min_software_gain);
-
-  ASSERT_TRUE(ret);
-}
-
 TEST(AlsaUcm, DefaultNodeGain) {
   struct cras_use_case_mgr* mgr = &cras_ucm_mgr;
   long default_node_gain;
@@ -842,6 +949,31 @@
   ASSERT_TRUE(ret);
 }
 
+TEST(AlsaUcm, IntrinsicSensitivity) {
+  struct cras_use_case_mgr* mgr = &cras_ucm_mgr;
+  long intrinsic_vol;
+  int ret;
+  std::string id = "=IntrinsicSensitivity/Internal Mic/HiFi";
+  std::string value = "-2000";
+
+  ResetStubData();
+
+  /* Value can be found in UCM. */
+  snd_use_case_get_value[id] = value;
+
+  ret = ucm_get_intrinsic_sensitivity(mgr, "Internal Mic", &intrinsic_vol);
+
+  EXPECT_EQ(0, ret);
+  EXPECT_EQ(-2000, intrinsic_vol);
+
+  ResetStubData();
+
+  /* Value can not be found in UCM. */
+  ret = ucm_get_intrinsic_sensitivity(mgr, "Internal Mic", &intrinsic_vol);
+
+  ASSERT_TRUE(ret);
+}
+
 TEST(AlsaUcm, UseFullySpecifiedUCMConfig) {
   struct cras_use_case_mgr* mgr = &cras_ucm_mgr;
   int fully_specified_flag;
@@ -864,28 +996,6 @@
   ASSERT_FALSE(fully_specified_flag);
 }
 
-TEST(AlsaUcm, EnableHtimestampFlag) {
-  struct cras_use_case_mgr* mgr = &cras_ucm_mgr;
-  unsigned int enable_htimestamp_flag;
-
-  std::string id = "=EnableHtimestamp//HiFi";
-  ResetStubData();
-
-  /* Flag is not set */
-  enable_htimestamp_flag = ucm_get_enable_htimestamp_flag(mgr);
-  ASSERT_FALSE(enable_htimestamp_flag);
-
-  /* Flag is set to "1". */
-  snd_use_case_get_value[id] = std::string("1");
-  enable_htimestamp_flag = ucm_get_enable_htimestamp_flag(mgr);
-  ASSERT_TRUE(enable_htimestamp_flag);
-
-  /* Flag is set to "0". */
-  snd_use_case_get_value[id] = std::string("0");
-  enable_htimestamp_flag = ucm_get_enable_htimestamp_flag(mgr);
-  ASSERT_FALSE(enable_htimestamp_flag);
-}
-
 TEST(AlsaUcm, GetMixerNameForDevice) {
   struct cras_use_case_mgr* mgr = &cras_ucm_mgr;
   const char *mixer_name_1, *mixer_name_2;
@@ -896,15 +1006,15 @@
 
   fake_list["_devices/HiFi"] = devices;
   fake_list_size["_devices/HiFi"] = 4;
-  std::string id_1 = "=MixerName/Dev1/HiFi";
-  std::string id_2 = "=MixerName/Dev2/HiFi";
+  std::string id_1 = "=PlaybackMixerElem/Dev1/HiFi";
+  std::string id_2 = "=CaptureMixerElem/Dev2/HiFi";
   std::string value_1 = "MixerName1";
   std::string value_2 = "MixerName2";
 
   snd_use_case_get_value[id_1] = value_1;
   snd_use_case_get_value[id_2] = value_2;
-  mixer_name_1 = ucm_get_mixer_name_for_dev(mgr, "Dev1");
-  mixer_name_2 = ucm_get_mixer_name_for_dev(mgr, "Dev2");
+  mixer_name_1 = ucm_get_playback_mixer_elem_for_dev(mgr, "Dev1");
+  mixer_name_2 = ucm_get_capture_mixer_elem_for_dev(mgr, "Dev2");
 
   EXPECT_EQ(0, strcmp(mixer_name_1, value_1.c_str()));
   EXPECT_EQ(0, strcmp(mixer_name_2, value_2.c_str()));
@@ -990,7 +1100,7 @@
   EXPECT_EQ(callback_arg, list_devices_callback_args[1]);
 }
 
-TEST(AlsaUcm, GetJackNameForDevice) {
+TEST(AlsaUcm, GetJackDevForDevice) {
   struct cras_use_case_mgr* mgr = &cras_ucm_mgr;
   const char *jack_name_1, *jack_name_2;
   const char* devices[] = {"Dev1", "Comment for Dev1", "Dev2",
@@ -1000,12 +1110,12 @@
 
   fake_list["_devices/HiFi"] = devices;
   fake_list_size["_devices/HiFi"] = 4;
-  std::string id_1 = "=JackName/Dev1/HiFi";
-  std::string value_1 = "JackName1";
+  std::string id_1 = "=JackDev/Dev1/HiFi";
+  std::string value_1 = "JackDev1";
 
   snd_use_case_get_value[id_1] = value_1;
-  jack_name_1 = ucm_get_jack_name_for_dev(mgr, "Dev1");
-  jack_name_2 = ucm_get_jack_name_for_dev(mgr, "Dev2");
+  jack_name_1 = ucm_get_jack_dev_for_dev(mgr, "Dev1");
+  jack_name_2 = ucm_get_jack_dev_for_dev(mgr, "Dev2");
 
   EXPECT_EQ(0, strcmp(jack_name_1, value_1.c_str()));
   EXPECT_EQ(NULL, jack_name_2);
@@ -1014,43 +1124,28 @@
   free((void*)jack_name_2);
 }
 
-TEST(AlsaUcm, GetJackTypeForDevice) {
+TEST(AlsaUcm, GetJackControlForDevice) {
   struct cras_use_case_mgr* mgr = &cras_ucm_mgr;
-  const char *jack_type_1, *jack_type_2, *jack_type_3, *jack_type_4;
-  const char* devices[] = {
-      "Dev1", "Comment for Dev1", "Dev2", "Comment for Dev2",
-      "Dev3", "Comment for Dev3", "Dev4", "Comment for Dev4"};
+  const char *jack_name_1, *jack_name_2;
+  const char* devices[] = {"Dev1", "Comment for Dev1", "Dev2",
+                           "Comment for Dev2"};
 
   ResetStubData();
 
   fake_list["_devices/HiFi"] = devices;
-  fake_list_size["_devices/HiFi"] = 8;
-  std::string id_1 = "=JackType/Dev1/HiFi";
-  std::string value_1 = "hctl";
-  std::string id_2 = "=JackType/Dev2/HiFi";
-  std::string value_2 = "gpio";
-  std::string id_3 = "=JackType/Dev3/HiFi";
-  std::string value_3 = "something";
+  fake_list_size["_devices/HiFi"] = 4;
+  std::string id_1 = "=JackControl/Dev1/HiFi";
+  std::string value_1 = "JackControl1";
 
   snd_use_case_get_value[id_1] = value_1;
-  snd_use_case_get_value[id_2] = value_2;
-  snd_use_case_get_value[id_3] = value_3;
+  jack_name_1 = ucm_get_jack_control_for_dev(mgr, "Dev1");
+  jack_name_2 = ucm_get_jack_control_for_dev(mgr, "Dev2");
 
-  jack_type_1 = ucm_get_jack_type_for_dev(mgr, "Dev1");
-  jack_type_2 = ucm_get_jack_type_for_dev(mgr, "Dev2");
-  jack_type_3 = ucm_get_jack_type_for_dev(mgr, "Dev3");
-  jack_type_4 = ucm_get_jack_type_for_dev(mgr, "Dev4");
+  EXPECT_EQ(0, strcmp(jack_name_1, value_1.c_str()));
+  EXPECT_EQ(NULL, jack_name_2);
 
-  /* Only "hctl" and "gpio" are valid types. */
-  EXPECT_EQ(0, strcmp(jack_type_1, value_1.c_str()));
-  EXPECT_EQ(0, strcmp(jack_type_2, value_2.c_str()));
-  EXPECT_EQ(NULL, jack_type_3);
-  EXPECT_EQ(NULL, jack_type_4);
-
-  free((void*)jack_type_1);
-  free((void*)jack_type_2);
-  free((void*)jack_type_3);
-  free((void*)jack_type_4);
+  free((void*)jack_name_1);
+  free((void*)jack_name_2);
 }
 
 TEST(AlsaUcm, GetPeriodFramesForDevice) {
@@ -1076,7 +1171,6 @@
   dma_period_2 = ucm_get_dma_period_for_dev(mgr, "Dev2");
   dma_period_3 = ucm_get_dma_period_for_dev(mgr, "Dev3");
 
-  /* Only "hctl" and "gpio" are valid types. */
   EXPECT_EQ(1000, dma_period_1);
   EXPECT_EQ(0, dma_period_2);
   EXPECT_EQ(0, dma_period_3);
@@ -1088,18 +1182,22 @@
   struct mixer_name* controls = NULL;
   struct mixer_name* m_name;
   int dev_idx = 0;
+  int dependent_dev_idx = -1;
   size_t i;
   enum CRAS_STREAM_DIRECTION dir = CRAS_STREAM_OUTPUT;
   static const char* name = "Headphone";
+  static const char* pcm_name = "hw:0,1";
   static const char* jack_name = "my-card-name Headset Jack";
   static const char* jack_type = "gpio";
   static const char* mixer_name = "Control1";
   static const char* coupled_names[] = {"Coupled1", "Coupled2"};
 
-  section = ucm_section_create(NULL, 0, CRAS_STREAM_OUTPUT, NULL, NULL);
+  section =
+      ucm_section_create(NULL, NULL, 0, -1, CRAS_STREAM_OUTPUT, NULL, NULL);
   EXPECT_EQ(reinterpret_cast<struct ucm_section*>(NULL), section);
 
-  section = ucm_section_create(name, dev_idx, dir, jack_name, jack_type);
+  section = ucm_section_create(name, pcm_name, dev_idx, dependent_dev_idx, dir,
+                               jack_name, jack_type);
   EXPECT_NE(reinterpret_cast<struct ucm_section*>(NULL), section);
   EXPECT_NE(name, section->name);
   EXPECT_EQ(0, strcmp(name, section->name));
@@ -1154,6 +1252,7 @@
   struct ucm_section* section;
   struct mixer_name* m_name;
   int section_count = 0;
+  int dev_idx;
   int i = 0;
   const char* devices[] = {"Headphone",    "The headphones jack.",
                            "Speaker",      "The speakers.",
@@ -1161,49 +1260,47 @@
                            "Internal Mic", "Internal Microphones",
                            "HDMI",         "HDMI output"};
   const char* ids[] = {"=PlaybackPCM/Headphone/HiFi",
-                       "=JackName/Headphone/HiFi",
-                       "=JackType/Headphone/HiFi",
+                       "=JackDev/Headphone/HiFi",
                        "=JackSwitch/Headphone/HiFi",
                        "=CoupledMixers/Headphone/HiFi",
 
                        "=PlaybackPCM/Speaker/HiFi",
                        "=CoupledMixers/Speaker/HiFi",
+                       "=DependentPCM/Speaker/HiFi",
 
                        "=CapturePCM/Mic/HiFi",
-                       "=JackName/Mic/HiFi",
-                       "=JackType/Mic/HiFi",
+                       "=JackDev/Mic/HiFi",
                        "=JackSwitch/Mic/HiFi",
-                       "=MixerName/Mic/HiFi",
+                       "=CaptureMixerElem/Mic/HiFi",
 
                        "=CapturePCM/Internal Mic/HiFi",
                        "=CoupledMixers/Internal Mic/HiFi",
                        "=JackSwitch/Internal Mic/HiFi",
 
                        "=PlaybackPCM/HDMI/HiFi",
-                       "=MixerName/HDMI/HiFi",
+                       "=PlaybackMixerElem/HDMI/HiFi",
 
                        NULL};
   const char* values[] = {
       "hw:my-sound-card,0",
       "my-sound-card Headset Jack",
-      "gpio",
       "2",
       "HP-L,HP-R",
 
-      "hw:my-sound-card,0",
+      "hw:my-sound-card,1",
       "SPK-L,SPK-R",
-
       "hw:my-sound-card,0",
+
+      "hw:my-sound-card,2",
       "my-sound-card Headset Jack",
-      "gpio",
       "0",
       "CAPTURE",
 
-      "hw:my-sound-card,0",
+      "hw:my-sound-card,3",
       "MIC-L,MIC-R",
       "-10",
 
-      "hw:my-sound-card,2",
+      "hw:my-sound-card,4",
       "HDMI",
   };
 
@@ -1224,11 +1321,12 @@
 
   // Headphone
   section = sections;
+  EXPECT_EQ(0, strcmp(section->pcm_name, "hw:my-sound-card,0"));
   EXPECT_EQ(0, strcmp(section->name, "Headphone"));
   EXPECT_EQ(0, section->dev_idx);
   EXPECT_EQ(CRAS_STREAM_OUTPUT, section->dir);
   EXPECT_EQ(0, strcmp(section->jack_name, values[1]));
-  EXPECT_EQ(0, strcmp(section->jack_type, values[2]));
+  EXPECT_EQ(0, strcmp(section->jack_type, "gpio"));
   EXPECT_EQ(NULL, section->mixer_name);
   ASSERT_NE((struct mixer_name*)NULL, section->coupled);
   m_name = section->coupled;
@@ -1237,16 +1335,19 @@
   EXPECT_EQ(0, strcmp(m_name->name, "HP-R"));
   EXPECT_EQ(NULL, m_name->next);
   EXPECT_EQ(2, section->jack_switch);
+  dev_idx = section->dev_idx;
 
   // Speaker
   section = section->next;
+  EXPECT_EQ(0, strcmp(section->pcm_name, "hw:my-sound-card,1"));
   EXPECT_EQ(0, strcmp(section->name, "Speaker"));
-  EXPECT_EQ(0, section->dev_idx);
+  EXPECT_EQ(1, section->dev_idx);
   EXPECT_EQ(CRAS_STREAM_OUTPUT, section->dir);
   EXPECT_EQ(NULL, section->jack_name);
   EXPECT_EQ(NULL, section->jack_type);
   EXPECT_EQ(-1, section->jack_switch);
   EXPECT_EQ(NULL, section->mixer_name);
+  EXPECT_EQ(dev_idx, section->dependent_dev_idx);
   ASSERT_NE((struct mixer_name*)NULL, section->coupled);
   m_name = section->coupled;
   EXPECT_EQ(0, strcmp(m_name->name, "SPK-L"));
@@ -1256,11 +1357,12 @@
 
   // Mic
   section = section->next;
+  EXPECT_EQ(0, strcmp(section->pcm_name, "hw:my-sound-card,2"));
   EXPECT_EQ(0, strcmp(section->name, "Mic"));
-  EXPECT_EQ(0, section->dev_idx);
+  EXPECT_EQ(2, section->dev_idx);
   EXPECT_EQ(CRAS_STREAM_INPUT, section->dir);
   EXPECT_EQ(0, strcmp(section->jack_name, values[1]));
-  EXPECT_EQ(0, strcmp(section->jack_type, values[2]));
+  EXPECT_EQ(0, strcmp(section->jack_type, "gpio"));
   EXPECT_EQ(0, section->jack_switch);
   ASSERT_NE((const char*)NULL, section->mixer_name);
   EXPECT_EQ(0, strcmp(section->mixer_name, "CAPTURE"));
@@ -1268,8 +1370,9 @@
 
   // Internal Mic
   section = section->next;
+  EXPECT_EQ(0, strcmp(section->pcm_name, "hw:my-sound-card,3"));
   EXPECT_EQ(0, strcmp(section->name, "Internal Mic"));
-  EXPECT_EQ(0, section->dev_idx);
+  EXPECT_EQ(3, section->dev_idx);
   EXPECT_EQ(CRAS_STREAM_INPUT, section->dir);
   EXPECT_EQ(NULL, section->jack_name);
   EXPECT_EQ(NULL, section->jack_type);
@@ -1283,8 +1386,9 @@
 
   // HDMI
   section = section->next;
+  EXPECT_EQ(0, strcmp(section->pcm_name, "hw:my-sound-card,4"));
   EXPECT_EQ(0, strcmp(section->name, "HDMI"));
-  EXPECT_EQ(2, section->dev_idx);
+  EXPECT_EQ(4, section->dev_idx);
   EXPECT_EQ(CRAS_STREAM_OUTPUT, section->dir);
   EXPECT_EQ(NULL, section->jack_name);
   EXPECT_EQ(NULL, section->jack_type);
@@ -1301,7 +1405,7 @@
   struct ucm_section* sections;
   int i = 0;
   const char* devices[] = {"Headphone", "The headphones jack."};
-  const char* ids[] = {"=JackName/Headphone/HiFi",
+  const char* ids[] = {"=JackDev/Headphone/HiFi",
                        "=CoupledMixers/Headphone/HiFi", NULL};
   const char* values[] = {
       "my-sound-card Headset Jack",
@@ -1322,34 +1426,6 @@
   EXPECT_EQ(NULL, sections);
 }
 
-TEST(AlsaUcm, GetSectionsBadPCM) {
-  struct cras_use_case_mgr* mgr = &cras_ucm_mgr;
-  struct ucm_section* sections;
-  int i = 0;
-  const char* devices[] = {"Headphone", "The headphones jack."};
-  const char* ids[] = {"=PlaybackPCM/Headphone/HiFi",
-                       "=JackName/Headphone/HiFi",
-                       "=CoupledMixers/Headphone/HiFi", NULL};
-  const char* values[] = {
-      "hw:my-sound-card:0",
-      "my-sound-card Headset Jack",
-      "HP-L,HP-R",
-  };
-
-  ResetStubData();
-
-  fake_list["_devices/HiFi"] = devices;
-  fake_list_size["_devices/HiFi"] = ARRAY_SIZE(devices);
-
-  while (ids[i]) {
-    snd_use_case_get_value[ids[i]] = values[i];
-    i++;
-  }
-
-  sections = ucm_get_sections(mgr);
-  EXPECT_EQ(NULL, sections);
-}
-
 TEST(AlsaUcm, CheckUseCaseVerbs) {
   struct cras_use_case_mgr* mgr = &cras_ucm_mgr;
 
@@ -1464,6 +1540,19 @@
   return 0;
 }
 
+int snd_use_case_geti(snd_use_case_mgr_t* uc_mgr,
+                      const char* identifier,
+                      long* value) {
+  snd_use_case_geti_called++;
+  if (snd_use_case_geti_value.find(identifier) ==
+      snd_use_case_geti_value.end()) {
+    *value = 0;
+    return -1;
+  }
+  *value = snd_use_case_geti_value[identifier];
+  return 0;
+}
+
 } /* extern "C" */
 
 }  //  namespace
diff --git a/cras/src/tests/apm_list_unittest.cc b/cras/src/tests/apm_list_unittest.cc
index 2c91cf9..65e712f 100644
--- a/cras/src/tests/apm_list_unittest.cc
+++ b/cras/src/tests/apm_list_unittest.cc
@@ -16,6 +16,8 @@
 #include "webrtc_apm.h"
 }
 
+#define FILENAME_TEMPLATE "ApmTest.XXXXXX"
+
 namespace {
 
 static void* stream_ptr = reinterpret_cast<void*>(0x123);
@@ -28,8 +30,12 @@
 static unsigned int webrtc_apm_process_reverse_stream_f_called;
 static device_enabled_callback_t device_enabled_callback_val;
 static struct ext_dsp_module* ext_dsp_module_value;
+static struct cras_ionode fake_node;
 static struct cras_iodev fake_iodev;
 static int webrtc_apm_create_called;
+static bool cras_iodev_is_aec_use_case_ret;
+static dictionary* webrtc_apm_create_aec_ini_val = NULL;
+static dictionary* webrtc_apm_create_apm_ini_val = NULL;
 
 TEST(ApmList, ApmListCreate) {
   list = cras_apm_list_create(stream_ptr, 0);
@@ -42,30 +48,175 @@
   cras_apm_list_destroy(list);
 }
 
+static char* prepare_tempdir() {
+  char dirname[sizeof(FILENAME_TEMPLATE) + 1];
+  char filename[64];
+  char* tempdir;
+  FILE* fp;
+
+  strcpy(dirname, FILENAME_TEMPLATE);
+  tempdir = mkdtemp(dirname);
+  snprintf(filename, 64, "%s/apm.ini", tempdir);
+  fp = fopen(filename, "w");
+  fprintf(fp, "%s", "[foo]\n");
+  fclose(fp);
+  fp = NULL;
+  snprintf(filename, 64, "%s/aec.ini", tempdir);
+  fp = fopen(filename, "w");
+  fprintf(fp, "%s", "[bar]\n");
+  fclose(fp);
+  fp = NULL;
+  return strdup(tempdir);
+}
+
+static void delete_tempdir(char* dir) {
+  char filename[64];
+
+  snprintf(filename, 64, "%s/apm.ini", dir);
+  unlink(filename);
+  snprintf(filename, 64, "%s/aec.ini", dir);
+  unlink(filename);
+  rmdir(dir);
+}
+
+static void init_channel_layout(struct cras_audio_format* fmt) {
+  int i;
+  for (i = 0; i < CRAS_CH_MAX; i++)
+    fmt->channel_layout[i] = -1;
+}
+
+TEST(ApmList, AddApmInputDevUnuseFirstChannel) {
+  struct cras_audio_format fmt;
+  struct cras_audio_format* val;
+  struct cras_apm* apm;
+  int ch;
+  const int num_test_casts = 9;
+  int test_layouts[num_test_casts][CRAS_CH_MAX] = {
+      {0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+      {0, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+      {0, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+      {1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+      {1, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+      {2, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+      {2, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+      {3, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1},
+      {3, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1}};
+  int test_num_channels[num_test_casts] = {1, 2, 2, 2, 2, 3, 4, 4, 4};
+
+  fmt.frame_rate = 48000;
+  fmt.format = SND_PCM_FORMAT_S16_LE;
+
+  cras_apm_list_init("");
+  list = cras_apm_list_create(stream_ptr, APM_ECHO_CANCELLATION);
+  EXPECT_NE((void*)NULL, list);
+
+  for (int i = 0; i < num_test_casts; i++) {
+    fmt.num_channels = test_num_channels[i];
+    init_channel_layout(&fmt);
+    for (ch = 0; ch < CRAS_CH_MAX; ch++)
+      fmt.channel_layout[ch] = test_layouts[i][ch];
+
+    /* Input dev is of aec use case. */
+    apm = cras_apm_list_add_apm(list, dev_ptr, &fmt, 1);
+    EXPECT_NE((void*)NULL, apm);
+
+    /* Assert that the post-processing format never has an unset
+     * first channel in the layout. */
+    bool first_channel_found_in_layout = 0;
+    val = cras_apm_list_get_format(apm);
+    for (ch = 0; ch < CRAS_CH_MAX; ch++)
+      if (0 == val->channel_layout[ch])
+        first_channel_found_in_layout = 1;
+
+    EXPECT_EQ(1, first_channel_found_in_layout);
+
+    cras_apm_list_remove_apm(list, dev_ptr);
+  }
+
+  cras_apm_list_destroy(list);
+  cras_apm_list_deinit();
+}
+
 TEST(ApmList, AddRemoveApm) {
   struct cras_audio_format fmt;
+  char* dir;
 
   fmt.num_channels = 2;
   fmt.frame_rate = 48000;
   fmt.format = SND_PCM_FORMAT_S16_LE;
+  fake_iodev.active_node = &fake_node;
+  fake_node.type = CRAS_NODE_TYPE_INTERNAL_SPEAKER;
+
+  dir = prepare_tempdir();
+  cras_apm_list_init(dir);
+  cras_iodev_is_aec_use_case_ret = 1;
 
   list = cras_apm_list_create(stream_ptr, APM_ECHO_CANCELLATION);
   EXPECT_NE((void*)NULL, list);
 
-  EXPECT_NE((void*)NULL, cras_apm_list_add(list, dev_ptr, &fmt));
-  EXPECT_EQ((void*)NULL, cras_apm_list_get(list, dev_ptr2));
+  /* Input dev is of aec use case. */
+  EXPECT_NE((void*)NULL, cras_apm_list_add_apm(list, dev_ptr, &fmt, 1));
+  EXPECT_NE((void*)NULL, webrtc_apm_create_aec_ini_val);
+  EXPECT_NE((void*)NULL, webrtc_apm_create_apm_ini_val);
+  EXPECT_EQ((void*)NULL, cras_apm_list_get_active_apm(stream_ptr, dev_ptr));
 
-  EXPECT_NE((void*)NULL, cras_apm_list_add(list, dev_ptr2, &fmt));
-  EXPECT_NE((void*)NULL, cras_apm_list_get(list, dev_ptr));
+  cras_apm_list_start_apm(list, dev_ptr);
+  EXPECT_NE((void*)NULL, cras_apm_list_get_active_apm(stream_ptr, dev_ptr));
+  EXPECT_EQ((void*)NULL, cras_apm_list_get_active_apm(stream_ptr, dev_ptr2));
 
-  cras_apm_list_remove(list, dev_ptr);
-  EXPECT_EQ((void*)NULL, cras_apm_list_get(list, dev_ptr));
-  EXPECT_NE((void*)NULL, cras_apm_list_get(list, dev_ptr2));
+  /* Input dev is not of aec use case. */
+  EXPECT_NE((void*)NULL, cras_apm_list_add_apm(list, dev_ptr2, &fmt, 0));
+  EXPECT_EQ((void*)NULL, webrtc_apm_create_aec_ini_val);
+  EXPECT_EQ((void*)NULL, webrtc_apm_create_apm_ini_val);
+  cras_apm_list_start_apm(list, dev_ptr2);
+  cras_apm_list_stop_apm(list, dev_ptr);
 
-  cras_apm_list_remove(list, dev_ptr2);
-  EXPECT_EQ((void*)NULL, cras_apm_list_get(list, dev_ptr2));
+  EXPECT_EQ((void*)NULL, cras_apm_list_get_active_apm(stream_ptr, dev_ptr));
+  EXPECT_NE((void*)NULL, cras_apm_list_get_active_apm(stream_ptr, dev_ptr2));
+
+  cras_apm_list_stop_apm(list, dev_ptr2);
+  cras_apm_list_remove_apm(list, dev_ptr);
+  cras_apm_list_remove_apm(list, dev_ptr2);
 
   cras_apm_list_destroy(list);
+  cras_apm_list_deinit();
+  delete_tempdir(dir);
+  free(dir);
+}
+
+TEST(ApmList, OutputTypeNotAecUseCase) {
+  struct cras_audio_format fmt;
+  char* dir;
+
+  fmt.num_channels = 2;
+  fmt.frame_rate = 48000;
+  fmt.format = SND_PCM_FORMAT_S16_LE;
+  fake_iodev.active_node = &fake_node;
+
+  dir = prepare_tempdir();
+  cras_apm_list_init(dir);
+
+  list = cras_apm_list_create(stream_ptr, APM_ECHO_CANCELLATION);
+  EXPECT_NE((void*)NULL, list);
+
+  /* Output device is of aec use case. */
+  cras_iodev_is_aec_use_case_ret = 1;
+  EXPECT_NE((void*)NULL, cras_apm_list_add_apm(list, dev_ptr, &fmt, 1));
+  EXPECT_NE((void*)NULL, webrtc_apm_create_aec_ini_val);
+  EXPECT_NE((void*)NULL, webrtc_apm_create_apm_ini_val);
+  cras_apm_list_remove_apm(list, dev_ptr);
+
+  /* Output device is not of aec use case. */
+  cras_iodev_is_aec_use_case_ret = 0;
+  EXPECT_NE((void*)NULL, cras_apm_list_add_apm(list, dev_ptr, &fmt, 1));
+  EXPECT_EQ((void*)NULL, webrtc_apm_create_aec_ini_val);
+  EXPECT_EQ((void*)NULL, webrtc_apm_create_apm_ini_val);
+  cras_apm_list_remove_apm(list, dev_ptr);
+
+  cras_apm_list_destroy(list);
+  cras_apm_list_deinit();
+  delete_tempdir(dir);
+  free(dir);
 }
 
 TEST(ApmList, ApmProcessForwardBuffer) {
@@ -77,11 +228,16 @@
   fmt.num_channels = 2;
   fmt.frame_rate = 48000;
   fmt.format = SND_PCM_FORMAT_S16_LE;
+  init_channel_layout(&fmt);
+  fmt.channel_layout[CRAS_CH_FL] = 0;
+  fmt.channel_layout[CRAS_CH_FR] = 1;
+
+  cras_apm_list_init("");
 
   list = cras_apm_list_create(stream_ptr, APM_ECHO_CANCELLATION);
   EXPECT_NE((void*)NULL, list);
 
-  apm = cras_apm_list_add(list, dev_ptr, &fmt);
+  apm = cras_apm_list_add_apm(list, dev_ptr, &fmt, 1);
 
   buf = float_buffer_create(500, 2);
   float_buffer_written(buf, 300);
@@ -118,6 +274,7 @@
 
   float_buffer_destroy(&buf);
   cras_apm_list_destroy(list);
+  cras_apm_list_deinit();
 }
 
 TEST(ApmList, ApmProcessReverseData) {
@@ -159,7 +316,8 @@
   list = cras_apm_list_create(stream_ptr, APM_ECHO_CANCELLATION);
   EXPECT_NE((void*)NULL, list);
 
-  apm = cras_apm_list_add(list, dev_ptr, &fmt);
+  apm = cras_apm_list_add_apm(list, dev_ptr, &fmt, 1);
+  cras_apm_list_start_apm(list, dev_ptr);
 
   ext_dsp_module_value->run(ext_dsp_module_value, 250);
   EXPECT_EQ(0, webrtc_apm_process_reverse_stream_f_called);
@@ -180,19 +338,22 @@
   fmt.frame_rate = 48000;
   fmt.format = SND_PCM_FORMAT_S16_LE;
 
+  cras_apm_list_init("");
+
   webrtc_apm_create_called = 0;
   list = cras_apm_list_create(stream_ptr, APM_ECHO_CANCELLATION);
   EXPECT_NE((void*)NULL, list);
 
-  apm1 = cras_apm_list_add(list, dev_ptr, &fmt);
+  apm1 = cras_apm_list_add_apm(list, dev_ptr, &fmt, 1);
   EXPECT_EQ(1, webrtc_apm_create_called);
   EXPECT_NE((void*)NULL, apm1);
 
-  apm2 = cras_apm_list_add(list, dev_ptr, &fmt);
+  apm2 = cras_apm_list_add_apm(list, dev_ptr, &fmt, 1);
   EXPECT_EQ(1, webrtc_apm_create_called);
   EXPECT_EQ(apm1, apm2);
 
   cras_apm_list_destroy(list);
+  cras_apm_list_deinit();
 }
 
 extern "C" {
@@ -211,6 +372,9 @@
                                    struct ext_dsp_module* ext) {
   ext_dsp_module_value = ext;
 }
+bool cras_iodev_is_aec_use_case(const struct cras_ionode* node) {
+  return cras_iodev_is_aec_use_case_ret;
+}
 struct cras_audio_area* cras_audio_area_create(int num_channels) {
   return &fake_audio_area;
 }
@@ -241,6 +405,8 @@
                              dictionary* aec_ini,
                              dictionary* apm_ini) {
   webrtc_apm_create_called++;
+  webrtc_apm_create_aec_ini_val = aec_ini;
+  webrtc_apm_create_apm_ini_val = apm_ini;
   return reinterpret_cast<webrtc_apm>(0x11);
 }
 void webrtc_apm_dump_configs(dictionary* aec_ini, dictionary* apm_ini) {}
diff --git a/cras/src/tests/audio_thread_unittest.cc b/cras/src/tests/audio_thread_unittest.cc
index e06514e..93045e0 100644
--- a/cras/src/tests/audio_thread_unittest.cc
+++ b/cras/src/tests/audio_thread_unittest.cc
@@ -18,6 +18,7 @@
 #define FIRST_CB_LEVEL 480
 
 static int cras_audio_thread_event_busyloop_called;
+static int cras_audio_thread_event_severe_underrun_called;
 static unsigned int cras_rstream_dev_offset_called;
 static unsigned int cras_rstream_dev_offset_ret[MAX_CALLS];
 static const struct cras_rstream*
@@ -55,6 +56,7 @@
 static enum CRAS_IODEV_RAMP_REQUEST cras_iodev_start_ramp_request;
 static struct timespec clock_gettime_retspec;
 static struct timespec init_cb_ts_;
+static struct timespec sleep_interval_ts_;
 static std::map<const struct dev_stream*, struct timespec>
     dev_stream_wake_time_val;
 static int cras_device_monitor_set_device_mute_state_called;
@@ -963,6 +965,7 @@
   thread_add_stream(thread_, &rstream, &piodev, 1);
 
   // Assume device is running and there is a severe underrun.
+  cras_audio_thread_event_severe_underrun_called = 0;
   iodev.state = CRAS_IODEV_STATE_NORMAL_RUN;
   frames_queued_ = -EPIPE;
 
@@ -975,6 +978,7 @@
   // Audio thread should ask main thread to reset device.
   EXPECT_EQ(1, cras_iodev_reset_request_called);
   EXPECT_EQ(&iodev, cras_iodev_reset_request_iodev);
+  EXPECT_EQ(1, cras_audio_thread_event_severe_underrun_called);
 
   thread_rm_open_dev(thread_, CRAS_STREAM_OUTPUT, iodev.info.idx);
   TearDownRstream(&rstream);
@@ -1222,10 +1226,12 @@
                                      unsigned int dev_id,
                                      const struct cras_audio_format* dev_fmt,
                                      void* dev_ptr,
-                                     struct timespec* cb_ts) {
+                                     struct timespec* cb_ts,
+                                     const struct timespec* sleep_interval_ts) {
   struct dev_stream* out = static_cast<dev_stream*>(calloc(1, sizeof(*out)));
   out->stream = stream;
   init_cb_ts_ = *cb_ts;
+  sleep_interval_ts_ = *sleep_interval_ts;
   return out;
 }
 
@@ -1265,7 +1271,7 @@
 void dev_stream_set_dev_rate(struct dev_stream* dev_stream,
                              unsigned int dev_rate,
                              double dev_rate_ratio,
-                             double master_rate_ratio,
+                             double main_rate_ratio,
                              int coarse_rate_adjust) {}
 
 void dev_stream_update_frames(const struct dev_stream* dev_stream) {}
@@ -1311,7 +1317,9 @@
   return 0;
 }
 
-int cras_iodev_output_underrun(struct cras_iodev* odev) {
+int cras_iodev_output_underrun(struct cras_iodev* odev,
+                               unsigned int hw_level,
+                               unsigned int frames_written) {
   cras_iodev_output_underrun_called++;
   return 0;
 }
@@ -1404,12 +1412,19 @@
   cras_device_monitor_set_device_mute_state_called++;
   return 0;
 }
+int cras_device_monitor_error_close(unsigned int dev_idx) {
+  return 0;
+}
 
 int cras_iodev_drop_frames_by_time(struct cras_iodev* iodev,
                                    struct timespec ts) {
   return 0;
 }
 
+bool cras_iodev_is_on_internal_card(const struct cras_ionode* node) {
+  return 0;
+}
+
 //  From librt.
 int clock_gettime(clockid_t clk_id, struct timespec* tp) {
   *tp = clock_gettime_retspec;
@@ -1442,6 +1457,16 @@
   return 0;
 }
 
+int cras_audio_thread_event_severe_underrun() {
+  cras_audio_thread_event_severe_underrun_called++;
+  return 0;
+}
+
+float input_data_get_software_gain_scaler(struct input_data* data,
+                                          float idev_sw_gain_scaler,
+                                          struct cras_rstream* stream) {
+  return 1.0;
+}
 }  // extern "C"
 
 int main(int argc, char** argv) {
diff --git a/cras/src/tests/audio_thread_unittest_obsolete.cc b/cras/src/tests/audio_thread_unittest_obsolete.cc
index 0baeb38..ae9f5ef 100644
--- a/cras/src/tests/audio_thread_unittest_obsolete.cc
+++ b/cras/src/tests/audio_thread_unittest_obsolete.cc
@@ -52,8 +52,8 @@
 
 static struct timespec time_now;
 static int cras_fmt_conversion_needed_return_val;
-static struct cras_audio_area* dummy_audio_area1;
-static struct cras_audio_area* dummy_audio_area2;
+static struct cras_audio_area* mock_audio_area1;
+static struct cras_audio_area* mock_audio_area2;
 static struct cras_audio_format cras_iodev_set_format_val;
 
 static struct dev_stream_capture_call dev_stream_capture_call;
@@ -95,18 +95,18 @@
     SetupRstream(&rstream2_, 2);
     shm2_ = cras_rstream_input_shm(rstream2_);
 
-    dummy_audio_area1 = (cras_audio_area*)calloc(
+    mock_audio_area1 = (cras_audio_area*)calloc(
         1, sizeof(cras_audio_area) + 2 * sizeof(cras_channel_area));
-    dummy_audio_area1->num_channels = 2;
-    channel_area_set_channel(&dummy_audio_area1->channels[0], CRAS_CH_FL);
-    channel_area_set_channel(&dummy_audio_area1->channels[1], CRAS_CH_FR);
-    rstream_->input_audio_area = dummy_audio_area1;
-    dummy_audio_area2 = (cras_audio_area*)calloc(
+    mock_audio_area1->num_channels = 2;
+    channel_area_set_channel(&mock_audio_area1->channels[0], CRAS_CH_FL);
+    channel_area_set_channel(&mock_audio_area1->channels[1], CRAS_CH_FR);
+    rstream_->input_audio_area = mock_audio_area1;
+    mock_audio_area2 = (cras_audio_area*)calloc(
         1, sizeof(cras_audio_area) + 2 * sizeof(cras_channel_area));
-    dummy_audio_area2->num_channels = 2;
-    channel_area_set_channel(&dummy_audio_area2->channels[0], CRAS_CH_FL);
-    channel_area_set_channel(&dummy_audio_area2->channels[1], CRAS_CH_FR);
-    rstream2_->input_audio_area = dummy_audio_area2;
+    mock_audio_area2->num_channels = 2;
+    channel_area_set_channel(&mock_audio_area2->channels[0], CRAS_CH_FL);
+    channel_area_set_channel(&mock_audio_area2->channels[1], CRAS_CH_FR);
+    rstream2_->input_audio_area = mock_audio_area2;
 
     dev_stream_mix_dont_fill_next = 0;
     dev_stream_mix_count = 0;
@@ -123,8 +123,8 @@
     free(rstream_);
     free(shm2_->area);
     free(rstream2_);
-    free(dummy_audio_area1);
-    free(dummy_audio_area2);
+    free(mock_audio_area1);
+    free(mock_audio_area2);
   }
 
   void SetupRstream(struct cras_rstream** rstream, int fd) {
@@ -1444,18 +1444,18 @@
     rstream2_->buffer_frames -= 50;
     rstream2_->cb_threshold -= 50;
 
-    dummy_audio_area1 = (cras_audio_area*)calloc(
+    mock_audio_area1 = (cras_audio_area*)calloc(
         1, sizeof(cras_audio_area) + 2 * sizeof(cras_channel_area));
-    dummy_audio_area1->num_channels = 2;
-    channel_area_set_channel(&dummy_audio_area1->channels[0], CRAS_CH_FL);
-    channel_area_set_channel(&dummy_audio_area1->channels[1], CRAS_CH_FR);
-    rstream_->input_audio_area = dummy_audio_area1;
-    dummy_audio_area2 = (cras_audio_area*)calloc(
+    mock_audio_area1->num_channels = 2;
+    channel_area_set_channel(&mock_audio_area1->channels[0], CRAS_CH_FL);
+    channel_area_set_channel(&mock_audio_area1->channels[1], CRAS_CH_FR);
+    rstream_->input_audio_area = mock_audio_area1;
+    mock_audio_area2 = (cras_audio_area*)calloc(
         1, sizeof(cras_audio_area) + 2 * sizeof(cras_channel_area));
-    dummy_audio_area2->num_channels = 2;
-    channel_area_set_channel(&dummy_audio_area2->channels[0], CRAS_CH_FL);
-    channel_area_set_channel(&dummy_audio_area2->channels[1], CRAS_CH_FR);
-    rstream2_->input_audio_area = dummy_audio_area2;
+    mock_audio_area2->num_channels = 2;
+    channel_area_set_channel(&mock_audio_area2->channels[0], CRAS_CH_FL);
+    channel_area_set_channel(&mock_audio_area2->channels[1], CRAS_CH_FR);
+    rstream2_->input_audio_area = mock_audio_area2;
 
     cras_iodev_set_format_called = 0;
     close_dev_called_ = 0;
@@ -1483,8 +1483,8 @@
     shm = cras_rstream_output_shm(rstream_);
     free(shm->header);
     free(rstream_);
-    free(dummy_audio_area1);
-    free(dummy_audio_area2);
+    free(mock_audio_area1);
+    free(mock_audio_area2);
   }
 
   void SetupRstream(struct cras_rstream** rstream) {
diff --git a/cras/src/tests/bt_device_unittest.cc b/cras/src/tests/bt_device_unittest.cc
index d2e9a13..ccb581c 100644
--- a/cras/src/tests/bt_device_unittest.cc
+++ b/cras/src/tests/bt_device_unittest.cc
@@ -15,6 +15,8 @@
 #define FAKE_OBJ_PATH "/obj/path"
 }
 
+static const unsigned int CONN_WATCH_MAX_RETRIES = 30;
+
 static struct cras_iodev* cras_bt_io_create_profile_ret;
 static struct cras_iodev* cras_bt_io_append_btio_val;
 static struct cras_ionode* cras_bt_io_get_profile_ret;
@@ -30,6 +32,7 @@
 static cras_message_callback cras_main_message_add_handler_callback;
 static void* cras_main_message_add_handler_callback_data;
 static int cras_tm_create_timer_called;
+static int cras_tm_cancel_timer_called;
 static int cras_a2dp_start_called;
 static int cras_a2dp_suspend_connected_device_called;
 static int cras_hfp_ag_remove_conflict_called;
@@ -41,6 +44,10 @@
 static const char* dbus_message_new_method_call_method;
 static struct cras_bt_device* cras_a2dp_connected_device_ret;
 static struct cras_bt_device* cras_a2dp_suspend_connected_device_dev;
+static struct cras_timer* cras_tm_cancel_timer_arg;
+static struct cras_timer* cras_tm_create_timer_ret;
+static size_t cras_iodev_set_node_plugged_called;
+static int cras_iodev_set_node_plugged_value;
 
 struct MockDBusMessage {
   int type;
@@ -58,6 +65,7 @@
   cras_bt_io_try_remove_ret = 0;
   cras_main_message_send_msg = NULL;
   cras_tm_create_timer_called = 0;
+  cras_tm_cancel_timer_called = 0;
   cras_a2dp_start_called = 0;
   cras_a2dp_suspend_connected_device_called = 0;
   cras_hfp_ag_remove_conflict_called = 0;
@@ -66,6 +74,7 @@
   dbus_message_new_method_call_method = NULL;
   dbus_message_new_method_call_called = 0;
   cras_a2dp_connected_device_ret = NULL;
+  cras_iodev_set_node_plugged_called = 0;
 }
 
 static void FreeMockDBusMessage(MockDBusMessage* head) {
@@ -78,11 +87,12 @@
   delete head;
 }
 
-static struct MockDBusMessage* NewMockDBusConnectedMessage() {
+static struct MockDBusMessage* NewMockDBusConnectedMessage(long connected) {
   MockDBusMessage* msg = new MockDBusMessage{DBUS_TYPE_ARRAY, NULL};
   MockDBusMessage* dict =
       new MockDBusMessage{DBUS_TYPE_STRING, (void*)strdup("Connected")};
-  MockDBusMessage* variant = new MockDBusMessage{DBUS_TYPE_BOOLEAN, (void*)1};
+  MockDBusMessage* variant =
+      new MockDBusMessage{DBUS_TYPE_BOOLEAN, (void*)connected};
 
   msg->recurse = dict;
   dict->next = new MockDBusMessage{DBUS_TYPE_INVALID, NULL};
@@ -166,6 +176,8 @@
                                     CRAS_BT_DEVICE_PROFILE_HFP_AUDIOGATEWAY);
   cras_bt_device_rm_iodev(device, &d2_);
   EXPECT_EQ(1, cras_bt_io_remove_called);
+  EXPECT_EQ(1, cras_iodev_set_node_plugged_called);
+  EXPECT_EQ(0, cras_iodev_set_node_plugged_value);
 
   /* Test A2DP disconnection will cause bt_io destroy. */
   cras_bt_io_try_remove_ret = 0;
@@ -173,6 +185,8 @@
   EXPECT_EQ(1, cras_bt_io_remove_called);
   EXPECT_EQ(1, cras_bt_io_destroy_called);
   EXPECT_EQ(0, cras_bt_device_get_active_profile(device));
+  EXPECT_EQ(2, cras_iodev_set_node_plugged_called);
+  EXPECT_EQ(0, cras_iodev_set_node_plugged_value);
   cras_bt_device_remove(device);
 }
 
@@ -214,9 +228,10 @@
   device = cras_bt_device_create(NULL, FAKE_OBJ_PATH);
   EXPECT_NE((void*)NULL, device);
 
-  cras_bt_device_add_supported_profiles(device, A2DP_SINK_UUID);
+  cras_bt_device_set_supported_profiles(device,
+                                        CRAS_BT_DEVICE_PROFILE_A2DP_SINK);
 
-  cur = msg_root = NewMockDBusConnectedMessage();
+  cur = msg_root = NewMockDBusConnectedMessage(1);
   cras_bt_device_update_properties(device, (DBusMessageIter*)&cur, NULL);
   EXPECT_EQ(1, cras_tm_create_timer_called);
   EXPECT_NE((void*)NULL, cras_tm_create_timer_cb);
@@ -224,14 +239,22 @@
   /* Schedule another timer, if A2DP not yet configured. */
   cras_tm_create_timer_cb(NULL, cras_tm_create_timer_cb_data);
   EXPECT_EQ(2, cras_tm_create_timer_called);
-  EXPECT_EQ(1, dbus_message_new_method_call_called);
-  EXPECT_STREQ("ConnectProfile", dbus_message_new_method_call_method);
+
+  /* ConnectProfile must not be called, since this is A2DP only case. */
+  EXPECT_EQ(0, dbus_message_new_method_call_called);
 
   cras_bt_device_a2dp_configured(device);
+
+  /* Prepate the iodev created by cras_a2dp_start. */
+  cras_bt_io_create_profile_ret = &bt_iodev1;
+  cras_bt_device_append_iodev(device, &d1_, CRAS_BT_DEVICE_PROFILE_A2DP_SOURCE);
+
   cras_tm_create_timer_cb(NULL, cras_tm_create_timer_cb_data);
   EXPECT_EQ(2, cras_tm_create_timer_called);
   EXPECT_EQ(1, cras_hfp_ag_remove_conflict_called);
   EXPECT_EQ(1, cras_a2dp_start_called);
+  EXPECT_EQ(1, cras_iodev_set_node_plugged_called);
+  EXPECT_EQ(1, cras_iodev_set_node_plugged_value);
 
   cras_bt_device_remove(device);
   FreeMockDBusMessage(msg_root);
@@ -246,10 +269,11 @@
   device = cras_bt_device_create(NULL, FAKE_OBJ_PATH);
   EXPECT_NE((void*)NULL, device);
 
-  cras_bt_device_add_supported_profiles(device, HSP_HS_UUID);
-  cras_bt_device_add_supported_profiles(device, HFP_HF_UUID);
+  cras_bt_device_set_supported_profiles(
+      device, CRAS_BT_DEVICE_PROFILE_HSP_HEADSET |
+                  CRAS_BT_DEVICE_PROFILE_HFP_HANDSFREE);
 
-  cur = msg_root = NewMockDBusConnectedMessage();
+  cur = msg_root = NewMockDBusConnectedMessage(1);
   cras_bt_device_update_properties(device, (DBusMessageIter*)&cur, NULL);
   EXPECT_EQ(1, cras_tm_create_timer_called);
   EXPECT_NE((void*)NULL, cras_tm_create_timer_cb);
@@ -257,15 +281,23 @@
   /* Schedule another timer, if HFP AG not yet intialized. */
   cras_tm_create_timer_cb(NULL, cras_tm_create_timer_cb_data);
   EXPECT_EQ(2, cras_tm_create_timer_called);
-  EXPECT_EQ(1, dbus_message_new_method_call_called);
-  EXPECT_STREQ("ConnectProfile", dbus_message_new_method_call_method);
+
+  /* ConnectProfile must not be called, since this is HFP only case. */
+  EXPECT_EQ(0, dbus_message_new_method_call_called);
 
   cras_bt_device_audio_gateway_initialized(device);
 
+  /* Prepate the iodev created by ag initialization. */
+  cras_bt_io_create_profile_ret = &bt_iodev2;
+  cras_bt_device_append_iodev(device, &d3_,
+                              CRAS_BT_DEVICE_PROFILE_HFP_AUDIOGATEWAY);
+
   cras_tm_create_timer_cb(NULL, cras_tm_create_timer_cb_data);
   EXPECT_EQ(2, cras_tm_create_timer_called);
   EXPECT_EQ(1, cras_hfp_ag_remove_conflict_called);
   EXPECT_EQ(1, cras_hfp_ag_start_called);
+  EXPECT_EQ(1, cras_iodev_set_node_plugged_called);
+  EXPECT_EQ(1, cras_iodev_set_node_plugged_value);
 
   cras_bt_device_remove(device);
   FreeMockDBusMessage(msg_root);
@@ -280,11 +312,12 @@
   device = cras_bt_device_create(NULL, FAKE_OBJ_PATH);
   EXPECT_NE((void*)NULL, device);
 
-  cras_bt_device_add_supported_profiles(device, A2DP_SINK_UUID);
-  cras_bt_device_add_supported_profiles(device, HSP_HS_UUID);
-  cras_bt_device_add_supported_profiles(device, HFP_HF_UUID);
+  cras_bt_device_set_supported_profiles(
+      device, CRAS_BT_DEVICE_PROFILE_A2DP_SINK |
+                  CRAS_BT_DEVICE_PROFILE_HSP_HEADSET |
+                  CRAS_BT_DEVICE_PROFILE_HFP_HANDSFREE);
 
-  cur = msg_root = NewMockDBusConnectedMessage();
+  cur = msg_root = NewMockDBusConnectedMessage(1);
   cras_bt_device_update_properties(device, (DBusMessageIter*)&cur, NULL);
   EXPECT_EQ(1, cras_tm_create_timer_called);
   EXPECT_NE((void*)NULL, cras_tm_create_timer_cb);
@@ -293,12 +326,19 @@
   cras_tm_create_timer_cb(NULL, cras_tm_create_timer_cb_data);
   EXPECT_EQ(2, cras_tm_create_timer_called);
 
+  /* ConnectProfile must not be called, since the first profile connection
+   * should be initiated by Bluez.
+   */
+  EXPECT_EQ(0, dbus_message_new_method_call_called);
+
   cras_bt_device_audio_gateway_initialized(device);
 
   /* Schedule another timer, because A2DP is not ready. */
   cras_tm_create_timer_cb(NULL, cras_tm_create_timer_cb_data);
   EXPECT_EQ(3, cras_tm_create_timer_called);
   EXPECT_EQ(0, cras_hfp_ag_start_called);
+
+  /* ConnectProfile should be called to connect A2DP, since HFP is connected */
   EXPECT_EQ(1, dbus_message_new_method_call_called);
   EXPECT_STREQ("ConnectProfile", dbus_message_new_method_call_method);
 
@@ -323,11 +363,12 @@
   device = cras_bt_device_create(NULL, FAKE_OBJ_PATH);
   EXPECT_NE((void*)NULL, device);
 
-  cras_bt_device_add_supported_profiles(device, A2DP_SINK_UUID);
-  cras_bt_device_add_supported_profiles(device, HSP_HS_UUID);
-  cras_bt_device_add_supported_profiles(device, HFP_HF_UUID);
+  cras_bt_device_set_supported_profiles(
+      device, CRAS_BT_DEVICE_PROFILE_A2DP_SINK |
+                  CRAS_BT_DEVICE_PROFILE_HSP_HEADSET |
+                  CRAS_BT_DEVICE_PROFILE_HFP_HANDSFREE);
 
-  cur = msg_root = NewMockDBusConnectedMessage();
+  cur = msg_root = NewMockDBusConnectedMessage(1);
   cras_bt_device_update_properties(device, (DBusMessageIter*)&cur, NULL);
   cras_bt_device_audio_gateway_initialized(device);
   cras_bt_device_a2dp_configured(device);
@@ -361,41 +402,101 @@
   device = cras_bt_device_create(NULL, FAKE_OBJ_PATH);
   EXPECT_NE((void*)NULL, device);
 
-  cras_bt_device_add_supported_profiles(device, A2DP_SINK_UUID);
-  cras_bt_device_add_supported_profiles(device, HSP_HS_UUID);
-  cras_bt_device_add_supported_profiles(device, HFP_HF_UUID);
+  cras_bt_device_set_supported_profiles(
+      device, CRAS_BT_DEVICE_PROFILE_A2DP_SINK |
+                  CRAS_BT_DEVICE_PROFILE_HSP_HEADSET |
+                  CRAS_BT_DEVICE_PROFILE_HFP_HANDSFREE);
 
-  cur = msg_root = NewMockDBusConnectedMessage();
+  cur = msg_root = NewMockDBusConnectedMessage(1);
   cras_bt_device_update_properties(device, (DBusMessageIter*)&cur, NULL);
+  cras_bt_device_audio_gateway_initialized(device);
+  cras_bt_device_a2dp_configured(device);
   EXPECT_EQ(1, cras_tm_create_timer_called);
   EXPECT_NE((void*)NULL, cras_tm_create_timer_cb);
 
-  /* Schedule another timer, if HFP AG not yet intialized. */
-  cras_tm_create_timer_cb(NULL, cras_tm_create_timer_cb_data);
-  EXPECT_EQ(2, cras_tm_create_timer_called);
-  EXPECT_EQ(1, dbus_message_new_method_call_called);
-  EXPECT_STREQ("ConnectProfile", dbus_message_new_method_call_method);
-
-  cras_bt_device_a2dp_configured(device);
-
-  cras_tm_create_timer_cb(NULL, cras_tm_create_timer_cb_data);
-  EXPECT_EQ(3, cras_tm_create_timer_called);
-
   cras_bt_device_notify_profile_dropped(device,
                                         CRAS_BT_DEVICE_PROFILE_A2DP_SINK);
-  EXPECT_EQ(4, cras_tm_create_timer_called);
+  EXPECT_EQ(2, cras_tm_create_timer_called);
 
   /* Expect suspend timer is scheduled. */
   cras_tm_create_timer_cb(NULL, cras_tm_create_timer_cb_data);
   EXPECT_EQ(1, cras_a2dp_suspend_connected_device_called);
   EXPECT_EQ(1, cras_hfp_ag_suspend_connected_device_called);
-  EXPECT_EQ(2, dbus_message_new_method_call_called);
+  EXPECT_EQ(1, dbus_message_new_method_call_called);
   EXPECT_STREQ("Disconnect", dbus_message_new_method_call_method);
 
   cras_bt_device_remove(device);
   FreeMockDBusMessage(msg_root);
 }
 
+TEST_F(BtDeviceTestSuite, DevConnectDisconnectBackToBack) {
+  struct cras_bt_device* device;
+  struct MockDBusMessage *msg_root, *cur;
+
+  ResetStubData();
+
+  device = cras_bt_device_create(NULL, FAKE_OBJ_PATH);
+  EXPECT_NE((void*)NULL, device);
+
+  cras_bt_device_set_supported_profiles(
+      device, CRAS_BT_DEVICE_PROFILE_A2DP_SINK |
+                  CRAS_BT_DEVICE_PROFILE_HSP_HEADSET |
+                  CRAS_BT_DEVICE_PROFILE_HFP_HANDSFREE);
+
+  cur = msg_root = NewMockDBusConnectedMessage(1);
+  cras_bt_device_update_properties(device, (DBusMessageIter*)&cur, NULL);
+  EXPECT_EQ(1, cras_tm_create_timer_called);
+  EXPECT_NE((void*)NULL, cras_tm_create_timer_cb);
+  FreeMockDBusMessage(msg_root);
+
+  cras_bt_device_a2dp_configured(device);
+  cras_bt_device_audio_gateway_initialized(device);
+
+  /* Expect suspend timer is scheduled. */
+  cras_tm_create_timer_ret = reinterpret_cast<struct cras_timer*>(0x101);
+  cras_bt_device_notify_profile_dropped(device,
+                                        CRAS_BT_DEVICE_PROFILE_A2DP_SINK);
+  EXPECT_EQ(2, cras_tm_create_timer_called);
+  /* Another profile drop won't schedule another timer because one is
+   * already armed. */
+  EXPECT_NE((void*)NULL, cras_tm_create_timer_cb);
+  cras_bt_device_notify_profile_dropped(device,
+                                        CRAS_BT_DEVICE_PROFILE_HFP_HANDSFREE);
+  EXPECT_EQ(2, cras_tm_create_timer_called);
+
+  cur = msg_root = NewMockDBusConnectedMessage(0);
+  cras_bt_device_update_properties(device, (DBusMessageIter*)&cur, NULL);
+
+  /* When BlueZ reports headset disconnection, cancel the pending timer.  */
+  EXPECT_EQ(cras_tm_cancel_timer_called, 1);
+  EXPECT_EQ(cras_tm_cancel_timer_arg, (void*)0x101);
+  FreeMockDBusMessage(msg_root);
+
+  /* Headset connects again. */
+  cur = msg_root = NewMockDBusConnectedMessage(1);
+  cras_bt_device_update_properties(device, (DBusMessageIter*)&cur, NULL);
+  EXPECT_EQ(3, cras_tm_create_timer_called);
+  EXPECT_NE((void*)NULL, cras_tm_create_timer_cb);
+  FreeMockDBusMessage(msg_root);
+
+  /* Headset disconnects, later profile drop events shouldn't trigger
+   * suspend timer because headset is already in disconnected stats.
+   */
+  cur = msg_root = NewMockDBusConnectedMessage(0);
+  cras_bt_device_update_properties(device, (DBusMessageIter*)&cur, NULL);
+  FreeMockDBusMessage(msg_root);
+
+  cras_tm_create_timer_called = 0;
+  cras_bt_device_notify_profile_dropped(device,
+                                        CRAS_BT_DEVICE_PROFILE_A2DP_SINK);
+  EXPECT_EQ(0, cras_tm_create_timer_called);
+  cras_bt_device_notify_profile_dropped(device,
+                                        CRAS_BT_DEVICE_PROFILE_HFP_HANDSFREE);
+  EXPECT_EQ(0, cras_tm_create_timer_called);
+
+  cras_bt_device_remove(device);
+}
+
 TEST_F(BtDeviceTestSuite, ConnectionWatchTimeout) {
   struct cras_bt_device* device;
   struct MockDBusMessage *msg_root, *cur;
@@ -405,26 +506,21 @@
   device = cras_bt_device_create(NULL, FAKE_OBJ_PATH);
   EXPECT_NE((void*)NULL, device);
 
-  cras_bt_device_add_supported_profiles(device, A2DP_SINK_UUID);
-  cras_bt_device_add_supported_profiles(device, HSP_HS_UUID);
-  cras_bt_device_add_supported_profiles(device, HFP_HF_UUID);
+  cras_bt_device_set_supported_profiles(
+      device, CRAS_BT_DEVICE_PROFILE_A2DP_SINK |
+                  CRAS_BT_DEVICE_PROFILE_HSP_HEADSET |
+                  CRAS_BT_DEVICE_PROFILE_HFP_HANDSFREE);
 
-  cur = msg_root = NewMockDBusConnectedMessage();
+  cur = msg_root = NewMockDBusConnectedMessage(1);
   cras_bt_device_update_properties(device, (DBusMessageIter*)&cur, NULL);
   EXPECT_EQ(1, cras_tm_create_timer_called);
   EXPECT_NE((void*)NULL, cras_tm_create_timer_cb);
 
-  /* Schedule another timer, if HFP AG not yet intialized. */
-  cras_tm_create_timer_cb(NULL, cras_tm_create_timer_cb_data);
-  EXPECT_EQ(2, cras_tm_create_timer_called);
-  EXPECT_EQ(1, dbus_message_new_method_call_called);
-  EXPECT_STREQ("ConnectProfile", dbus_message_new_method_call_method);
-
   cras_bt_device_a2dp_configured(device);
 
-  for (int i = 0; i < 29; i++) {
+  for (unsigned int i = 0; i < CONN_WATCH_MAX_RETRIES; i++) {
     cras_tm_create_timer_cb(NULL, cras_tm_create_timer_cb_data);
-    EXPECT_EQ(i + 3, cras_tm_create_timer_called);
+    EXPECT_EQ(i + 2, cras_tm_create_timer_called);
     EXPECT_EQ(0, cras_a2dp_start_called);
     EXPECT_EQ(0, cras_hfp_ag_start_called);
     EXPECT_EQ(0, cras_hfp_ag_remove_conflict_called);
@@ -551,6 +647,11 @@
   return 0;
 }
 
+void cras_iodev_set_node_plugged(struct cras_ionode* ionode, int plugged) {
+  cras_iodev_set_node_plugged_called++;
+  cras_iodev_set_node_plugged_value = plugged;
+}
+
 int cras_iodev_list_dev_is_enabled(const struct cras_iodev* dev) {
   return 0;
 }
@@ -594,10 +695,13 @@
   cras_tm_create_timer_called++;
   cras_tm_create_timer_cb = cb;
   cras_tm_create_timer_cb_data = cb_data;
-  return NULL;
+  return cras_tm_create_timer_ret;
 }
 
-void cras_tm_cancel_timer(struct cras_tm* tm, struct cras_timer* t) {}
+void cras_tm_cancel_timer(struct cras_tm* tm, struct cras_timer* t) {
+  cras_tm_cancel_timer_called++;
+  cras_tm_cancel_timer_arg = t;
+}
 
 DBusMessage* dbus_message_new_method_call(const char* destination,
                                           const char* path,
diff --git a/cras/src/tests/bt_io_unittest.cc b/cras/src/tests/bt_io_unittest.cc
index 97f4dae..dd02652 100644
--- a/cras/src/tests/bt_io_unittest.cc
+++ b/cras/src/tests/bt_io_unittest.cc
@@ -59,6 +59,8 @@
     ResetStubData();
     SetUpIodev(&iodev_, CRAS_STREAM_OUTPUT);
     SetUpIodev(&iodev2_, CRAS_STREAM_OUTPUT);
+    iodev_.active_node = &node_;
+    iodev2_.active_node = &node2_;
 
     update_supported_formats_called_ = 0;
     frames_queued_called_ = 0;
@@ -137,6 +139,8 @@
   static struct cras_iodev* bt_iodev;
   static struct cras_iodev iodev_;
   static struct cras_iodev iodev2_;
+  static struct cras_ionode node_;
+  static struct cras_ionode node2_;
   static unsigned int update_supported_formats_called_;
   static unsigned int frames_queued_called_;
   static unsigned int delay_frames_called_;
@@ -149,6 +153,8 @@
 struct cras_iodev* BtIoBasicSuite::bt_iodev;
 struct cras_iodev BtIoBasicSuite::iodev_;
 struct cras_iodev BtIoBasicSuite::iodev2_;
+struct cras_ionode BtIoBasicSuite::node_;
+struct cras_ionode BtIoBasicSuite::node2_;
 unsigned int BtIoBasicSuite::update_supported_formats_called_;
 unsigned int BtIoBasicSuite::frames_queued_called_;
 unsigned int BtIoBasicSuite::delay_frames_called_;
@@ -172,6 +178,7 @@
   bt_iodev->update_supported_formats(bt_iodev);
   EXPECT_EQ(1, update_supported_formats_called_);
 
+  bt_iodev->state = CRAS_IODEV_STATE_OPEN;
   bt_iodev->configure_dev(bt_iodev);
   EXPECT_EQ(1, configure_dev_called_);
   bt_iodev->frames_queued(bt_iodev, &tstamp);
@@ -228,6 +235,7 @@
   iodev_.direction = CRAS_STREAM_INPUT;
   bt_iodev = cras_bt_io_create(fake_device, &iodev_,
                                CRAS_BT_DEVICE_PROFILE_HFP_AUDIOGATEWAY);
+  bt_iodev->state = CRAS_IODEV_STATE_OPEN;
 
   cras_bt_device_get_active_profile_ret =
       CRAS_BT_DEVICE_PROFILE_HFP_AUDIOGATEWAY |
@@ -246,6 +254,7 @@
   iodev_.direction = CRAS_STREAM_INPUT;
   bt_iodev = cras_bt_io_create(fake_device, &iodev_,
                                CRAS_BT_DEVICE_PROFILE_HFP_AUDIOGATEWAY);
+  bt_iodev->state = CRAS_IODEV_STATE_OPEN;
 
   cras_bt_device_get_active_profile_ret =
       CRAS_BT_DEVICE_PROFILE_HFP_AUDIOGATEWAY |
@@ -257,6 +266,23 @@
   cras_bt_io_destroy(bt_iodev);
 }
 
+TEST_F(BtIoBasicSuite, NoSwitchProfileOnCloseInputDevInCloseState) {
+  ResetStubData();
+  iodev_.direction = CRAS_STREAM_INPUT;
+  bt_iodev = cras_bt_io_create(fake_device, &iodev_,
+                               CRAS_BT_DEVICE_PROFILE_HFP_AUDIOGATEWAY);
+  bt_iodev->state = CRAS_IODEV_STATE_CLOSE;
+
+  cras_bt_device_get_active_profile_ret =
+      CRAS_BT_DEVICE_PROFILE_HFP_AUDIOGATEWAY |
+      CRAS_BT_DEVICE_PROFILE_HSP_AUDIOGATEWAY;
+  cras_bt_device_has_a2dp_ret = 1;
+  bt_iodev->close_dev(bt_iodev);
+
+  EXPECT_EQ(0, cras_bt_device_switch_profile_called);
+  cras_bt_io_destroy(bt_iodev);
+}
+
 TEST_F(BtIoBasicSuite, SwitchProfileOnAppendA2dpDev) {
   ResetStubData();
   bt_iodev = cras_bt_io_create(fake_device, &iodev_,
@@ -397,7 +423,8 @@
 }
 
 // From bt device
-int cras_bt_device_get_active_profile(const struct cras_bt_device* device) {
+unsigned int cras_bt_device_get_active_profile(
+    const struct cras_bt_device* device) {
   return cras_bt_device_get_active_profile_ret;
 }
 
@@ -431,6 +458,10 @@
   return "/fake/object/path";
 }
 
+int cras_bt_device_get_stable_id(const struct cras_bt_device* device) {
+  return 123;
+}
+
 int cras_bt_device_get_use_hardware_volume(struct cras_bt_device* device) {
   return 1;
 }
@@ -443,4 +474,20 @@
   return 0;
 }
 
+int cras_iodev_frames_queued(struct cras_iodev* iodev,
+                             struct timespec* hw_tstamp) {
+  return 0;
+}
+
+unsigned int cras_iodev_default_frames_to_play_in_sleep(
+    struct cras_iodev* odev,
+    unsigned int* hw_level,
+    struct timespec* hw_tstamp) {
+  return 0;
+}
+
+int hfp_iodev_is_hsp(struct cras_iodev* iodev) {
+  return 0;
+}
+
 }  // extern "C"
diff --git a/cras/src/tests/capture_rclient_unittest.cc b/cras/src/tests/capture_rclient_unittest.cc
index 5e0bc58..446fddf 100644
--- a/cras/src/tests/capture_rclient_unittest.cc
+++ b/cras/src/tests/capture_rclient_unittest.cc
@@ -20,22 +20,18 @@
 }
 static unsigned int cras_make_fd_nonblocking_called;
 static unsigned int cras_observer_remove_called;
-static unsigned int cras_server_metrics_stream_config_called;
 static int stream_list_add_called;
 static int stream_list_add_return;
 static unsigned int stream_list_rm_called;
-static struct cras_audio_shm dummy_shm;
-static struct cras_rstream dummy_rstream;
-static unsigned int cras_rstream_config_init_with_message_called;
+static struct cras_audio_shm mock_shm;
+static struct cras_rstream mock_rstream;
 
 void ResetStubData() {
   cras_make_fd_nonblocking_called = 0;
   cras_observer_remove_called = 0;
-  cras_server_metrics_stream_config_called = 0;
   stream_list_add_called = 0;
   stream_list_add_return = 0;
   stream_list_rm_called = 0;
-  cras_rstream_config_init_with_message_called = 0;
 }
 
 namespace {
@@ -110,13 +106,12 @@
   cras_fill_connect_message(&msg, CRAS_STREAM_INPUT, stream_id,
                             CRAS_STREAM_TYPE_DEFAULT, CRAS_CLIENT_TYPE_UNKNOWN,
                             480, 240, /*flags=*/0, /*effects=*/0, fmt,
-                            NO_DEVICE, /*client_shm_size=*/0);
+                            NO_DEVICE);
   ASSERT_EQ(stream_id, msg.stream_id);
 
   fd_ = 100;
   rclient_->ops->handle_message_from_client(rclient_, &msg.header, &fd_, 1);
   EXPECT_EQ(1, cras_make_fd_nonblocking_called);
-  EXPECT_EQ(1, cras_rstream_config_init_with_message_called);
   EXPECT_EQ(1, stream_list_add_called);
   EXPECT_EQ(0, stream_list_rm_called);
 
@@ -138,16 +133,14 @@
       continue;
     cras_fill_connect_message(&msg, dir, stream_id, CRAS_STREAM_TYPE_DEFAULT,
                               CRAS_CLIENT_TYPE_UNKNOWN, 480, 240, /*flags=*/0,
-                              /*effects=*/0, fmt, NO_DEVICE,
-                              /*client_shm_size=*/0);
+                              /*effects=*/0, fmt, NO_DEVICE);
     ASSERT_EQ(stream_id, msg.stream_id);
 
     fd_ = 100;
     rc = rclient_->ops->handle_message_from_client(rclient_, &msg.header, &fd_,
                                                    1);
-    EXPECT_EQ(-EINVAL, rc);
+    EXPECT_EQ(0, rc);
     EXPECT_EQ(0, cras_make_fd_nonblocking_called);
-    EXPECT_EQ(0, cras_rstream_config_init_with_message_called);
     EXPECT_EQ(0, stream_list_add_called);
     EXPECT_EQ(0, stream_list_rm_called);
 
@@ -167,15 +160,14 @@
   cras_fill_connect_message(&msg, CRAS_STREAM_INPUT, stream_id,
                             CRAS_STREAM_TYPE_DEFAULT, CRAS_CLIENT_TYPE_UNKNOWN,
                             480, 240, /*flags=*/0, /*effects=*/0, fmt,
-                            NO_DEVICE, /*client_shm_size=*/0);
+                            NO_DEVICE);
   ASSERT_EQ(stream_id, msg.stream_id);
 
   fd_ = 100;
   rc =
       rclient_->ops->handle_message_from_client(rclient_, &msg.header, &fd_, 1);
-  EXPECT_EQ(-EINVAL, rc);
+  EXPECT_EQ(0, rc);
   EXPECT_EQ(0, cras_make_fd_nonblocking_called);
-  EXPECT_EQ(0, cras_rstream_config_init_with_message_called);
   EXPECT_EQ(0, stream_list_add_called);
   EXPECT_EQ(0, stream_list_rm_called);
 
@@ -185,43 +177,6 @@
   EXPECT_EQ(stream_id, out_msg.stream_id);
 }
 
-/*
- * TODO(yuhsaun): Remove this test when there are no client uses the old
- * craslib. (CRAS_PROTO_VER = 3)
- */
-TEST_F(CCRMessageSuite, StreamConnectMessageOldProtocal) {
-  struct cras_client_stream_connected out_msg;
-  int rc;
-
-  struct cras_connect_message_old msg;
-  cras_stream_id_t stream_id = 0x10002;
-
-  msg.proto_version = 3;
-  msg.direction = CRAS_STREAM_INPUT;
-  msg.stream_id = stream_id;
-  msg.stream_type = CRAS_STREAM_TYPE_DEFAULT;
-  msg.buffer_frames = 480;
-  msg.cb_threshold = 240;
-  msg.flags = 0;
-  msg.effects = 0;
-  pack_cras_audio_format(&msg.format, &fmt);
-  msg.dev_idx = NO_DEVICE;
-  msg.header.id = CRAS_SERVER_CONNECT_STREAM;
-  msg.header.length = sizeof(struct cras_connect_message_old);
-
-  fd_ = 100;
-  rc =
-      rclient_->ops->handle_message_from_client(rclient_, &msg.header, &fd_, 1);
-  EXPECT_EQ(1, cras_make_fd_nonblocking_called);
-  EXPECT_EQ(1, cras_rstream_config_init_with_message_called);
-  EXPECT_EQ(1, stream_list_add_called);
-  EXPECT_EQ(0, stream_list_rm_called);
-
-  rc = read(pipe_fds_[0], &out_msg, sizeof(out_msg));
-  EXPECT_EQ(sizeof(out_msg), rc);
-  EXPECT_EQ(stream_id, out_msg.stream_id);
-}
-
 TEST_F(CCRMessageSuite, StreamDisconnectMessage) {
   struct cras_disconnect_stream_message msg;
   cras_stream_id_t stream_id = 0x10002;
@@ -268,11 +223,6 @@
   return 0;
 }
 
-int cras_server_metrics_stream_config(struct cras_rstream_config* config) {
-  cras_server_metrics_stream_config_called++;
-  return 0;
-}
-
 int cras_send_with_fds(int sockfd,
                        const void* buf,
                        size_t len,
@@ -302,30 +252,27 @@
                     struct cras_rstream** stream) {
   int ret;
 
-  *stream = &dummy_rstream;
+  *stream = &mock_rstream;
 
   stream_list_add_called++;
   ret = stream_list_add_return;
   if (ret)
     stream_list_add_return = -EINVAL;
 
-  dummy_rstream.shm = &dummy_shm;
-  dummy_rstream.direction = config->direction;
-  dummy_rstream.stream_id = config->stream_id;
+  mock_rstream.shm = &mock_shm;
+  mock_rstream.direction = config->direction;
+  mock_rstream.stream_id = config->stream_id;
 
   return ret;
 }
 
-void cras_rstream_config_init_with_message(
-    struct cras_rclient* client,
-    const struct cras_connect_message* msg,
-    int* aud_fd,
-    int* client_shm_fd,
-    const struct cras_audio_format* remote_fmt,
-    struct cras_rstream_config* stream_config) {
-  cras_rstream_config_init_with_message_called++;
+bool cras_audio_format_valid(const struct cras_audio_format* fmt) {
+  return true;
 }
 
-void cras_rstream_config_cleanup(struct cras_rstream_config* stream_config) {}
+void detect_rtc_stream_pair(struct stream_list* list,
+                            struct cras_rstream* stream) {
+  return;
+}
 
 }  // extern "C"
diff --git a/cras/src/tests/control_rclient_unittest.cc b/cras/src/tests/control_rclient_unittest.cc
index 634bfe1..63e3c8f 100644
--- a/cras/src/tests/control_rclient_unittest.cc
+++ b/cras/src/tests/control_rclient_unittest.cc
@@ -9,6 +9,7 @@
 extern "C" {
 #include "audio_thread.h"
 #include "cras_bt_log.h"
+#include "cras_main_thread_log.h"
 #include "cras_messages.h"
 #include "cras_rclient.h"
 #include "cras_rstream.h"
@@ -20,13 +21,14 @@
 }
 
 //  Stub data.
+static int audio_thread_config_global_remix_called;
+static float audio_thread_config_global_remix_copy[CRAS_MAX_REMIX_CHANNELS *
+                                                   CRAS_MAX_REMIX_CHANNELS];
 static int cras_rstream_create_return;
 static struct cras_rstream* cras_rstream_create_stream_out;
 static int cras_iodev_attach_stream_retval;
 static size_t cras_system_set_volume_value;
 static int cras_system_set_volume_called;
-static size_t cras_system_set_capture_gain_value;
-static int cras_system_set_capture_gain_called;
 static size_t cras_system_set_mute_value;
 static int cras_system_set_mute_called;
 static size_t cras_system_set_user_mute_value;
@@ -45,9 +47,8 @@
 static unsigned int stream_list_disconnect_stream_called;
 static unsigned int cras_iodev_list_rm_input_called;
 static unsigned int cras_iodev_list_rm_output_called;
-static unsigned int cras_server_metrics_stream_config_called;
-static struct cras_audio_shm dummy_shm;
-static struct cras_rstream dummy_rstream;
+static struct cras_audio_shm mock_shm;
+static struct cras_rstream mock_rstream;
 static size_t cras_observer_num_ops_registered;
 static size_t cras_observer_register_notify_called;
 static size_t cras_observer_add_called;
@@ -59,17 +60,16 @@
 static size_t cras_observer_ops_are_empty_called;
 static struct cras_observer_ops cras_observer_ops_are_empty_empty_ops;
 static size_t cras_observer_remove_called;
-static unsigned int cras_rstream_config_init_with_message_called;
 
 void ResetStubData() {
+  audio_thread_config_global_remix_called = 0;
+  memset(audio_thread_config_global_remix_copy, 0,
+         sizeof(audio_thread_config_global_remix_copy));
   cras_rstream_create_return = 0;
   cras_rstream_create_stream_out = (struct cras_rstream*)NULL;
   cras_iodev_attach_stream_retval = 0;
-  cras_server_metrics_stream_config_called = 0;
   cras_system_set_volume_value = 0;
   cras_system_set_volume_called = 0;
-  cras_system_set_capture_gain_value = 0;
-  cras_system_set_capture_gain_called = 0;
   cras_system_set_mute_value = 0;
   cras_system_set_mute_called = 0;
   cras_system_set_user_mute_value = 0;
@@ -101,7 +101,6 @@
   memset(&cras_observer_ops_are_empty_empty_ops, 0,
          sizeof(cras_observer_ops_are_empty_empty_ops));
   cras_observer_remove_called = 0;
-  cras_rstream_config_init_with_message_called = 0;
 }
 
 namespace {
@@ -161,6 +160,7 @@
     connect_msg_.dev_idx = NO_DEVICE;
     connect_msg_.client_shm_size = 0;
     btlog = cras_bt_event_log_init();
+    main_log = main_thread_event_log_init();
     ResetStubData();
   }
 
@@ -170,6 +170,7 @@
     close(pipe_fds_[0]);
     close(pipe_fds_[1]);
     cras_bt_event_log_deinit(btlog);
+    main_thread_event_log_deinit(main_log);
   }
 
   void RegisterNotification(enum CRAS_CLIENT_MESSAGE_ID msg_id,
@@ -194,17 +195,15 @@
   fd_ = 100;
   rc = rclient_->ops->handle_message_from_client(rclient_, &connect_msg_.header,
                                                  &fd_, 1);
-  EXPECT_EQ(-EINVAL, rc);
+  EXPECT_EQ(0, rc);
 
   rc = read(pipe_fds_[0], &out_msg, sizeof(out_msg));
   EXPECT_EQ(sizeof(out_msg), rc);
   EXPECT_EQ(stream_id_, out_msg.stream_id);
   EXPECT_NE(0, out_msg.err);
   EXPECT_EQ(0, cras_iodev_list_rm_output_called);
-  EXPECT_EQ(1, cras_rstream_config_init_with_message_called);
   EXPECT_EQ(1, stream_list_add_stream_called);
   EXPECT_EQ(0, stream_list_disconnect_stream_called);
-  EXPECT_EQ(0, cras_server_metrics_stream_config_called);
 }
 
 TEST_F(RClientMessagesSuite, ConnectMsgWithBadFd) {
@@ -213,7 +212,7 @@
 
   rc = rclient_->ops->handle_message_from_client(rclient_, &connect_msg_.header,
                                                  NULL, 0);
-  EXPECT_EQ(-EBADF, rc);
+  EXPECT_EQ(0, rc);
 
   rc = read(pipe_fds_[0], &out_msg, sizeof(out_msg));
   EXPECT_EQ(sizeof(out_msg), rc);
@@ -221,33 +220,6 @@
   EXPECT_NE(0, out_msg.err);
   EXPECT_EQ(stream_list_add_stream_called,
             stream_list_disconnect_stream_called);
-  EXPECT_EQ(0, cras_server_metrics_stream_config_called);
-}
-
-TEST_F(RClientMessagesSuite, ConnectMsgFromOldClient) {
-  struct cras_client_stream_connected out_msg;
-  int rc;
-
-  cras_rstream_create_stream_out = rstream_;
-  cras_iodev_attach_stream_retval = 0;
-
-  connect_msg_.header.length = sizeof(struct cras_connect_message_old);
-  connect_msg_.proto_version = 3;
-
-  fd_ = 100;
-  rc = rclient_->ops->handle_message_from_client(rclient_, &connect_msg_.header,
-                                                 &fd_, 1);
-  EXPECT_EQ(0, rc);
-  EXPECT_EQ(1, cras_make_fd_nonblocking_called);
-
-  rc = read(pipe_fds_[0], &out_msg, sizeof(out_msg));
-  EXPECT_EQ(sizeof(out_msg), rc);
-  EXPECT_EQ(stream_id_, out_msg.stream_id);
-  EXPECT_EQ(0, out_msg.err);
-  EXPECT_EQ(1, cras_rstream_config_init_with_message_called);
-  EXPECT_EQ(1, stream_list_add_stream_called);
-  EXPECT_EQ(0, stream_list_disconnect_stream_called);
-  EXPECT_EQ(1, cras_server_metrics_stream_config_called);
 }
 
 TEST_F(RClientMessagesSuite, StreamConnectMessageValidDirection) {
@@ -273,10 +245,8 @@
     EXPECT_EQ(sizeof(out_msg), rc);
     EXPECT_EQ(stream_id_, out_msg.stream_id);
     EXPECT_EQ(0, out_msg.err);
-    EXPECT_EQ(called, cras_rstream_config_init_with_message_called);
     EXPECT_EQ(called, stream_list_add_stream_called);
     EXPECT_EQ(0, stream_list_disconnect_stream_called);
-    EXPECT_EQ(called, cras_server_metrics_stream_config_called);
   }
 }
 
@@ -291,7 +261,7 @@
   fd_ = 100;
   rc = rclient_->ops->handle_message_from_client(rclient_, &connect_msg_.header,
                                                  &fd_, 1);
-  EXPECT_EQ(-EINVAL, rc);
+  EXPECT_EQ(0, rc);
   EXPECT_EQ(0, cras_make_fd_nonblocking_called);
 
   rc = read(pipe_fds_[0], &out_msg, sizeof(out_msg));
@@ -300,7 +270,6 @@
   EXPECT_EQ(-EINVAL, out_msg.err);
   EXPECT_EQ(0, stream_list_add_stream_called);
   EXPECT_EQ(0, stream_list_disconnect_stream_called);
-  EXPECT_EQ(0, cras_server_metrics_stream_config_called);
 }
 
 TEST_F(RClientMessagesSuite, StreamConnectMessageInvalidClientId) {
@@ -312,9 +281,8 @@
   fd_ = 100;
   rc = rclient_->ops->handle_message_from_client(rclient_, &connect_msg_.header,
                                                  &fd_, 1);
-  EXPECT_EQ(-EINVAL, rc);
+  EXPECT_EQ(0, rc);
   EXPECT_EQ(0, cras_make_fd_nonblocking_called);
-  EXPECT_EQ(0, cras_rstream_config_init_with_message_called);
   EXPECT_EQ(0, stream_list_add_stream_called);
   EXPECT_EQ(0, stream_list_disconnect_stream_called);
 
@@ -341,10 +309,8 @@
   EXPECT_EQ(sizeof(out_msg), rc);
   EXPECT_EQ(stream_id_, out_msg.stream_id);
   EXPECT_EQ(0, out_msg.err);
-  EXPECT_EQ(1, cras_rstream_config_init_with_message_called);
   EXPECT_EQ(1, stream_list_add_stream_called);
   EXPECT_EQ(0, stream_list_disconnect_stream_called);
-  EXPECT_EQ(1, cras_server_metrics_stream_config_called);
 }
 
 TEST_F(RClientMessagesSuite, SuccessCreateThreadReply) {
@@ -364,10 +330,8 @@
   EXPECT_EQ(sizeof(out_msg), rc);
   EXPECT_EQ(stream_id_, out_msg.stream_id);
   EXPECT_EQ(0, out_msg.err);
-  EXPECT_EQ(1, cras_rstream_config_init_with_message_called);
   EXPECT_EQ(1, stream_list_add_stream_called);
   EXPECT_EQ(0, stream_list_disconnect_stream_called);
-  EXPECT_EQ(1, cras_server_metrics_stream_config_called);
 }
 
 TEST_F(RClientMessagesSuite, SetVolume) {
@@ -385,21 +349,6 @@
   EXPECT_EQ(66, cras_system_set_volume_value);
 }
 
-TEST_F(RClientMessagesSuite, SetCaptureVolume) {
-  struct cras_set_system_volume msg;
-  int rc;
-
-  msg.header.id = CRAS_SERVER_SET_SYSTEM_CAPTURE_GAIN;
-  msg.header.length = sizeof(msg);
-  msg.volume = 66;
-
-  rc =
-      rclient_->ops->handle_message_from_client(rclient_, &msg.header, NULL, 0);
-  EXPECT_EQ(0, rc);
-  EXPECT_EQ(1, cras_system_set_capture_gain_called);
-  EXPECT_EQ(66, cras_system_set_capture_gain_value);
-}
-
 TEST_F(RClientMessagesSuite, SetMute) {
   struct cras_set_system_mute msg;
   int rc;
@@ -469,6 +418,23 @@
   EXPECT_EQ(1, cras_system_state_dump_snapshots_called);
 }
 
+TEST_F(RClientMessagesSuite, ConfigGlobalRemix) {
+  int rc;
+  struct cras_config_global_remix msg;
+  const int num_channels = 2;
+  float coefficient[4] = {0.1, 0.2, 0.3, 0.4};
+  cras_fill_config_global_remix_command(&msg, num_channels, coefficient,
+                                        num_channels * num_channels);
+
+  rc =
+      rclient_->ops->handle_message_from_client(rclient_, &msg.header, NULL, 0);
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(1, audio_thread_config_global_remix_called);
+  for (unsigned i = 0; i < (unsigned)num_channels * num_channels; i++) {
+    EXPECT_EQ(audio_thread_config_global_remix_copy[i], coefficient[i]);
+  }
+}
+
 void RClientMessagesSuite::RegisterNotification(
     enum CRAS_CLIENT_MESSAGE_ID msg_id,
     void* callback,
@@ -622,21 +588,6 @@
   EXPECT_EQ(msg->mute_locked, mute_locked);
 }
 
-TEST_F(RClientMessagesSuite, SendCaptureGainChanged) {
-  void* void_client = reinterpret_cast<void*>(rclient_);
-  char buf[1024];
-  ssize_t rc;
-  struct cras_client_volume_changed* msg =
-      (struct cras_client_volume_changed*)buf;
-  const int32_t gain = 90;
-
-  send_capture_gain_changed(void_client, gain);
-  rc = read(pipe_fds_[0], buf, sizeof(buf));
-  ASSERT_EQ(rc, (ssize_t)sizeof(*msg));
-  EXPECT_EQ(msg->header.id, CRAS_CLIENT_CAPTURE_GAIN_CHANGED);
-  EXPECT_EQ(msg->volume, gain);
-}
-
 TEST_F(RClientMessagesSuite, SendCaptureMuteChanged) {
   void* void_client = reinterpret_cast<void*>(rclient_);
   char buf[1024];
@@ -762,6 +713,7 @@
 extern "C" {
 
 struct cras_bt_event_log* btlog;
+struct main_thread_event_log* main_log;
 
 struct audio_thread* cras_iodev_list_get_audio_thread() {
   return iodev_get_thread_return;
@@ -796,6 +748,9 @@
 int audio_thread_config_global_remix(struct audio_thread* thread,
                                      unsigned int num_channels,
                                      const float* coefficient) {
+  audio_thread_config_global_remix_called++;
+  memcpy(audio_thread_config_global_remix_copy, coefficient,
+         num_channels * num_channels * sizeof(coefficient));
   return 0;
 }
 
@@ -862,11 +817,6 @@
   cras_system_set_volume_called++;
 }
 
-void cras_system_set_capture_gain(long gain) {
-  cras_system_set_capture_gain_value = gain;
-  cras_system_set_capture_gain_called++;
-}
-
 //  From system_state.
 void cras_system_set_mute(int mute) {
   cras_system_set_mute_value = mute;
@@ -937,16 +887,16 @@
                     struct cras_rstream** stream) {
   int ret;
 
-  *stream = &dummy_rstream;
+  *stream = &mock_rstream;
 
   stream_list_add_stream_called++;
   ret = stream_list_add_stream_return;
   if (ret)
     stream_list_add_stream_return = -EINVAL;
 
-  dummy_rstream.shm = &dummy_shm;
-  dummy_rstream.direction = config->direction;
-  dummy_rstream.stream_id = config->stream_id;
+  mock_rstream.shm = &mock_shm;
+  mock_rstream.direction = config->direction;
+  mock_rstream.stream_id = config->stream_id;
 
   return ret;
 }
@@ -1009,21 +959,19 @@
   cras_observer_remove_called++;
 }
 
-int cras_server_metrics_stream_config(struct cras_rstream_config* config) {
-  cras_server_metrics_stream_config_called++;
-  return 0;
+bool cras_audio_format_valid(const struct cras_audio_format* fmt) {
+  return true;
 }
 
-void cras_rstream_config_init_with_message(
-    struct cras_rclient* client,
-    const struct cras_connect_message* msg,
-    int* aud_fd,
-    int* client_shm_fd,
-    const struct cras_audio_format* remote_fmt,
-    struct cras_rstream_config* stream_config) {
-  cras_rstream_config_init_with_message_called++;
+struct packet_status_logger* cras_hfp_ag_get_wbs_logger() {
+  return NULL;
 }
 
-void cras_rstream_config_cleanup(struct cras_rstream_config* stream_config) {}
+void detect_rtc_stream_pair(struct stream_list* list,
+                            struct cras_rstream* stream) {
+  return;
+}
+
+void cras_system_set_hotword_pause_at_suspend(bool pause) {}
 
 }  // extern "C"
diff --git a/cras/src/tests/cras_abi_unittest.cc b/cras/src/tests/cras_abi_unittest.cc
new file mode 100644
index 0000000..d566a9b
--- /dev/null
+++ b/cras/src/tests/cras_abi_unittest.cc
@@ -0,0 +1,139 @@
+/* Copyright 2021 The Chromium OS 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 <gtest/gtest.h>
+
+extern "C" {
+#include "cras_client.c"
+#include "cras_client.h"
+
+inline int libcras_unsupported_func(struct libcras_client* client) {
+  CHECK_VERSION(client, INT_MAX);
+  return 0;
+}
+
+cras_stream_id_t cb_stream_id;
+uint8_t* cb_buf;
+unsigned int cb_frames;
+struct timespec cb_latency;
+void* cb_usr_arg;
+int get_stream_cb_called;
+struct timespec now;
+
+int get_stream_cb(struct libcras_stream_cb_data* data) {
+  get_stream_cb_called++;
+  EXPECT_NE((void*)NULL, data);
+  EXPECT_EQ(0, libcras_stream_cb_data_get_stream_id(data, &cb_stream_id));
+  EXPECT_EQ(0, libcras_stream_cb_data_get_buf(data, &cb_buf));
+  EXPECT_EQ(0, libcras_stream_cb_data_get_frames(data, &cb_frames));
+  EXPECT_EQ(0, libcras_stream_cb_data_get_latency(data, &cb_latency));
+  EXPECT_EQ(0, libcras_stream_cb_data_get_usr_arg(data, &cb_usr_arg));
+  return 0;
+}
+}
+
+namespace {
+class CrasAbiTestSuite : public testing::Test {
+ protected:
+  struct cras_audio_shm* InitShm(int frames) {
+    struct cras_audio_shm* shm =
+        static_cast<struct cras_audio_shm*>(calloc(1, sizeof(*shm)));
+    shm->header =
+        static_cast<cras_audio_shm_header*>(calloc(1, sizeof(*shm->header)));
+    cras_shm_set_frame_bytes(shm, 4);
+    uint32_t used_size = frames * 4;
+    cras_shm_set_used_size(shm, used_size);
+    shm->samples_info.length = used_size * 2;
+    memcpy(&shm->header->config, &shm->config, sizeof(shm->config));
+    return shm;
+  }
+
+  void DestroyShm(struct cras_audio_shm* shm) {
+    if (shm)
+      free(shm->header);
+    free(shm);
+  }
+
+  virtual void SetUp() { get_stream_cb_called = 0; }
+};
+
+TEST_F(CrasAbiTestSuite, CheckUnsupportedFunction) {
+  auto* client = libcras_client_create();
+  EXPECT_NE((void*)NULL, client);
+  EXPECT_EQ(-ENOSYS, libcras_unsupported_func(client));
+  libcras_client_destroy(client);
+}
+
+TEST_F(CrasAbiTestSuite, BasicStream) {
+  auto* client = libcras_client_create();
+  EXPECT_NE((void*)NULL, client);
+  auto* stream = libcras_stream_params_create();
+  EXPECT_NE((void*)NULL, stream);
+  /* Returns timeout because there is no real CRAS server in unittest. */
+  EXPECT_EQ(-ETIMEDOUT, libcras_client_connect_timeout(client, 0));
+  EXPECT_EQ(0, libcras_client_run_thread(client));
+  EXPECT_EQ(0, libcras_stream_params_set(stream, CRAS_STREAM_INPUT, 480, 480,
+                                         CRAS_STREAM_TYPE_DEFAULT,
+                                         CRAS_CLIENT_TYPE_TEST, 0, NULL, NULL,
+                                         NULL, 48000, SND_PCM_FORMAT_S16, 2));
+  cras_stream_id_t id;
+  /* Fails to add a stream because the stream callback is not set. */
+  EXPECT_EQ(-EINVAL, libcras_client_add_pinned_stream(client, 0, &id, stream));
+  /* Fails to set a stream volume because the stream is not added. */
+  EXPECT_EQ(-EINVAL, libcras_client_set_stream_volume(client, id, 1.0));
+  EXPECT_EQ(0, libcras_client_rm_stream(client, id));
+  EXPECT_EQ(0, libcras_client_stop(client));
+  libcras_stream_params_destroy(stream);
+  libcras_client_destroy(client);
+}
+
+TEST_F(CrasAbiTestSuite, StreamCallback) {
+  struct client_stream stream;
+  struct cras_stream_params params;
+  stream.id = 0x123;
+  stream.direction = CRAS_STREAM_INPUT;
+  stream.flags = 0;
+  stream.config = &params;
+  params.stream_cb = get_stream_cb;
+  params.cb_threshold = 480;
+  params.user_data = (void*)0x321;
+  stream.shm = InitShm(960);
+  stream.shm->header->write_offset[0] = 960 * 4;
+  stream.shm->header->write_buf_idx = 0;
+  stream.shm->header->read_offset[0] = 0;
+  stream.shm->header->read_buf_idx = 0;
+  now.tv_sec = 100;
+  now.tv_nsec = 0;
+  stream.shm->header->ts.tv_sec = 90;
+  stream.shm->header->ts.tv_nsec = 0;
+
+  handle_capture_data_ready(&stream, 480);
+
+  EXPECT_EQ(1, get_stream_cb_called);
+  EXPECT_EQ(stream.id, cb_stream_id);
+  EXPECT_EQ(cras_shm_get_write_buffer_base(stream.shm), cb_buf);
+  EXPECT_EQ(480, cb_frames);
+  EXPECT_EQ(10, cb_latency.tv_sec);
+  EXPECT_EQ(0, cb_latency.tv_nsec);
+  EXPECT_EQ((void*)0x321, cb_usr_arg);
+
+  DestroyShm(stream.shm);
+}
+
+}  // namespace
+
+extern "C" {
+
+int clock_gettime(clockid_t clk_id, struct timespec* tp) {
+  *tp = now;
+  return 0;
+}
+}
+
+int main(int argc, char** argv) {
+  ::testing::InitGoogleTest(&argc, argv);
+  openlog(NULL, LOG_PERROR, LOG_USER);
+  return RUN_ALL_TESTS();
+}
diff --git a/cras/src/tests/dev_io_stubs.cc b/cras/src/tests/dev_io_stubs.cc
index b74162b..d97dde5 100644
--- a/cras/src/tests/dev_io_stubs.cc
+++ b/cras/src/tests/dev_io_stubs.cc
@@ -151,6 +151,11 @@
                                static_cast<size_t>(dev->max_cb_level));
   dev->largest_cb_level = std::max(stream->rstream->cb_threshold,
                                    static_cast<size_t>(dev->max_cb_level));
+
+  if (stream->rstream->main_dev.dev_id == NO_DEVICE) {
+    stream->rstream->main_dev.dev_id = dev->info.idx;
+    stream->rstream->main_dev.dev_ptr = dev.get();
+  }
 }
 
 void fill_audio_format(cras_audio_format* format, unsigned int rate) {
diff --git a/cras/src/tests/dev_io_unittest.cc b/cras/src/tests/dev_io_unittest.cc
index a18dd48..2dbf344 100644
--- a/cras/src/tests/dev_io_unittest.cc
+++ b/cras/src/tests/dev_io_unittest.cc
@@ -8,6 +8,7 @@
 #include <time.h>
 
 #include <memory>
+#include <unordered_map>
 
 extern "C" {
 #include "cras_iodev.h"    // stubbed
@@ -27,7 +28,15 @@
 #include "rstream_stub.h"
 
 static float dev_stream_capture_software_gain_scaler_val;
+static float input_data_get_software_gain_scaler_val;
 static unsigned int dev_stream_capture_avail_ret = 480;
+struct set_dev_rate_data {
+  unsigned int dev_rate;
+  double dev_rate_ratio;
+  double main_rate_ratio;
+  int coarse_rate_adjust;
+};
+std::unordered_map<struct dev_stream*, set_dev_rate_data> set_dev_rate_map;
 
 namespace {
 
@@ -38,6 +47,7 @@
     iodev_stub_reset();
     rstream_stub_reset();
     fill_audio_format(&format, 48000);
+    set_dev_rate_map.clear();
     stream = create_stream(1, 1, CRAS_STREAM_INPUT, cb_threshold, &format);
   }
 
@@ -69,25 +79,90 @@
 
 TEST_F(DevIoSuite, CaptureGain) {
   struct open_dev* dev_list = NULL;
+  struct open_dev* odev_list = NULL;
   struct timespec ts;
   DevicePtr dev = create_device(CRAS_STREAM_INPUT, cb_threshold, &format,
                                 CRAS_NODE_TYPE_MIC);
 
   dev->dev->state = CRAS_IODEV_STATE_NORMAL_RUN;
-  dev->dev->software_gain_scaler = 0.99f;
   iodev_stub_frames_queued(dev->dev.get(), 20, ts);
   DL_APPEND(dev_list, dev->odev.get());
   add_stream_to_dev(dev->dev, stream);
 
-  /* For stream that uses APM, always apply gain scaler 1.0f regardless of
-   * what node/stream gains are. */
-  stream->rstream->apm_list = reinterpret_cast<struct cras_apm_list*>(0xf0f);
-  dev_io_capture(&dev_list);
+  /* The applied scaler gain should match what is reported by input_data. */
+  dev->dev->active_node->ui_gain_scaler = 1.0f;
+  input_data_get_software_gain_scaler_val = 1.0f;
+  dev_io_capture(&dev_list, &odev_list);
   EXPECT_EQ(1.0f, dev_stream_capture_software_gain_scaler_val);
 
-  stream->rstream->apm_list = 0x0;
-  dev_io_capture(&dev_list);
+  input_data_get_software_gain_scaler_val = 0.99f;
+  dev_io_capture(&dev_list, &odev_list);
   EXPECT_EQ(0.99f, dev_stream_capture_software_gain_scaler_val);
+
+  dev->dev->active_node->ui_gain_scaler = 0.6f;
+  input_data_get_software_gain_scaler_val = 0.7f;
+  dev_io_capture(&dev_list, &odev_list);
+  EXPECT_FLOAT_EQ(0.42f, dev_stream_capture_software_gain_scaler_val);
+}
+
+/*
+ * When input and output devices are on the internal sound card,
+ * and their device rates are the same, use the estimated rate
+ * on the output device as the estimated rate of input device.
+ */
+TEST_F(DevIoSuite, CopyOutputEstimatedRate) {
+  struct open_dev* idev_list = NULL;
+  struct open_dev* odev_list = NULL;
+  struct timespec ts;
+  DevicePtr out_dev = create_device(CRAS_STREAM_OUTPUT, cb_threshold, &format,
+                                    CRAS_NODE_TYPE_INTERNAL_SPEAKER);
+  DevicePtr in_dev = create_device(CRAS_STREAM_INPUT, cb_threshold, &format,
+                                   CRAS_NODE_TYPE_MIC);
+
+  in_dev->dev->state = CRAS_IODEV_STATE_NORMAL_RUN;
+  iodev_stub_frames_queued(in_dev->dev.get(), 20, ts);
+  DL_APPEND(idev_list, in_dev->odev.get());
+  add_stream_to_dev(in_dev->dev, stream);
+  DL_APPEND(odev_list, out_dev->odev.get());
+  iodev_stub_on_internal_card(out_dev->dev->active_node, 1);
+  iodev_stub_on_internal_card(in_dev->dev->active_node, 1);
+
+  iodev_stub_est_rate_ratio(in_dev->dev.get(), 0.8f);
+  iodev_stub_est_rate_ratio(out_dev->dev.get(), 1.2f);
+
+  dev_io_capture(&idev_list, &odev_list);
+
+  EXPECT_FLOAT_EQ(1.2f, set_dev_rate_map[stream->dstream.get()].dev_rate_ratio);
+}
+
+/*
+ * When input and output devices are not both on the internal sound card,
+ * estimated rates are independent.
+ */
+TEST_F(DevIoSuite, InputOutputIndependentEstimatedRate) {
+  struct open_dev* idev_list = NULL;
+  struct open_dev* odev_list = NULL;
+  struct timespec ts;
+  DevicePtr out_dev = create_device(CRAS_STREAM_OUTPUT, cb_threshold, &format,
+                                    CRAS_NODE_TYPE_INTERNAL_SPEAKER);
+  DevicePtr in_dev = create_device(CRAS_STREAM_INPUT, cb_threshold, &format,
+                                   CRAS_NODE_TYPE_USB);
+
+  in_dev->dev->state = CRAS_IODEV_STATE_NORMAL_RUN;
+  iodev_stub_frames_queued(in_dev->dev.get(), 20, ts);
+  DL_APPEND(idev_list, in_dev->odev.get());
+  add_stream_to_dev(in_dev->dev, stream);
+  DL_APPEND(odev_list, out_dev->odev.get());
+  iodev_stub_on_internal_card(out_dev->dev->active_node, 1);
+  iodev_stub_on_internal_card(in_dev->dev->active_node, 0);
+
+  iodev_stub_est_rate_ratio(in_dev->dev.get(), 0.8f);
+  iodev_stub_est_rate_ratio(out_dev->dev.get(), 1.2f);
+  iodev_stub_update_rate(in_dev->dev.get(), 1);
+
+  dev_io_capture(&idev_list, &odev_list);
+
+  EXPECT_FLOAT_EQ(0.8f, set_dev_rate_map[stream->dstream.get()].dev_rate_ratio);
 }
 
 /*
@@ -132,6 +207,49 @@
 }
 
 /*
+ * If any hw_level is larger than 0.5 * buffer_size and
+ * DROP_FRAMES_THRESHOLD_MS, reset all input devices.
+ */
+
+TEST_F(DevIoSuite, SendCapturedNeedToResetDevices2) {
+  struct timespec start;
+  struct timespec drop_time;
+  struct open_dev* dev_list = NULL;
+  bool rc;
+
+  stream = create_stream(1, 1, CRAS_STREAM_INPUT, 2000, &format);
+
+  clock_gettime(CLOCK_MONOTONIC_RAW, &start);
+  AddFakeDataToStream(stream.get(), 0);
+
+  DevicePtr dev1 =
+      create_device(CRAS_STREAM_INPUT, 2048, &format, CRAS_NODE_TYPE_MIC);
+  DevicePtr dev2 =
+      create_device(CRAS_STREAM_INPUT, 10000, &format, CRAS_NODE_TYPE_MIC);
+  DL_APPEND(dev_list, dev1->odev.get());
+  DL_APPEND(dev_list, dev2->odev.get());
+  add_stream_to_dev(dev1->dev, stream);
+  add_stream_to_dev(dev2->dev, stream);
+
+  iodev_stub_frames_queued(dev1->dev.get(), 2480, start);
+  iodev_stub_frames_queued(dev2->dev.get(), 2480, start);
+  EXPECT_EQ(0, dev_io_send_captured_samples(dev_list));
+
+  /*
+   * Should drop frames to one min_cb_level, which is 2480 - 2000 = 480 (10ms).
+   */
+  rc = iodev_stub_get_drop_time(dev1->dev.get(), &drop_time);
+  EXPECT_EQ(true, rc);
+  EXPECT_EQ(0, drop_time.tv_sec);
+  EXPECT_EQ(10000000, drop_time.tv_nsec);
+
+  rc = iodev_stub_get_drop_time(dev2->dev.get(), &drop_time);
+  EXPECT_EQ(true, rc);
+  EXPECT_EQ(0, drop_time.tv_sec);
+  EXPECT_EQ(10000000, drop_time.tv_nsec);
+}
+
+/*
  * If the hw_level is larger than 1.5 * largest_cb_level but less than
  * DROP_FRAMES_THRESHOLD_MS, do nothing.
  */
@@ -156,7 +274,10 @@
   EXPECT_EQ(false, rc);
 }
 
-/* If all hw_level is less than 1.5 * largest_cb_level, do nothing. */
+/*
+ * If all hw_level is less than 1.5 * largest_cb_level and 0.5 * buffer_size,
+ * do nothing.
+ */
 TEST_F(DevIoSuite, SendCapturedNoNeedToResetDevices) {
   struct timespec start;
   struct timespec drop_time;
@@ -186,6 +307,50 @@
   EXPECT_EQ(false, rc);
 }
 
+/*
+ * On loopback and hotword devices, if any hw_level is larger than
+ *  1.5 * largest_cb_level and DROP_FRAMES_THRESHOLD_MS, do nothing.
+ */
+TEST_F(DevIoSuite, SendCapturedNoNeedToDrop) {
+  struct timespec start;
+  struct timespec drop_time;
+  struct open_dev* dev_list = NULL;
+  bool rc;
+
+  clock_gettime(CLOCK_MONOTONIC_RAW, &start);
+  AddFakeDataToStream(stream.get(), 0);
+
+  DevicePtr dev1 =
+      create_device(CRAS_STREAM_INPUT, 480, &format, CRAS_NODE_TYPE_HOTWORD);
+  DevicePtr dev2 = create_device(CRAS_STREAM_INPUT, 480, &format,
+                                 CRAS_NODE_TYPE_POST_MIX_PRE_DSP);
+  DevicePtr dev3 =
+      create_device(CRAS_STREAM_INPUT, 480, &format, CRAS_NODE_TYPE_POST_DSP);
+
+  DL_APPEND(dev_list, dev1->odev.get());
+  DL_APPEND(dev_list, dev2->odev.get());
+  DL_APPEND(dev_list, dev3->odev.get());
+
+  add_stream_to_dev(dev1->dev, stream);
+  add_stream_to_dev(dev2->dev, stream);
+  add_stream_to_dev(dev3->dev, stream);
+
+  iodev_stub_frames_queued(dev1->dev.get(), 4800, start);
+  iodev_stub_frames_queued(dev2->dev.get(), 4800, start);
+  iodev_stub_frames_queued(dev2->dev.get(), 4800, start);
+
+  EXPECT_EQ(0, dev_io_send_captured_samples(dev_list));
+
+  rc = iodev_stub_get_drop_time(dev1->dev.get(), &drop_time);
+  EXPECT_EQ(false, rc);
+
+  rc = iodev_stub_get_drop_time(dev2->dev.get(), &drop_time);
+  EXPECT_EQ(false, rc);
+
+  rc = iodev_stub_get_drop_time(dev3->dev.get(), &drop_time);
+  EXPECT_EQ(false, rc);
+}
+
 /* Stubs */
 extern "C" {
 
@@ -204,10 +369,20 @@
   return 0;
 }
 
+float input_data_get_software_gain_scaler(struct input_data* data,
+                                          float idev_sw_gain_scaler,
+                                          struct cras_rstream* stream) {
+  return input_data_get_software_gain_scaler_val;
+}
+
 int cras_audio_thread_event_drop_samples() {
   return 0;
 }
 
+int cras_audio_thread_event_severe_underrun() {
+  return 0;
+}
+
 int dev_stream_attached_devs(const struct dev_stream* dev_stream) {
   return 0;
 }
@@ -227,8 +402,16 @@
 void dev_stream_set_dev_rate(struct dev_stream* dev_stream,
                              unsigned int dev_rate,
                              double dev_rate_ratio,
-                             double master_rate_ratio,
-                             int coarse_rate_adjust) {}
+                             double main_rate_ratio,
+                             int coarse_rate_adjust) {
+  set_dev_rate_data new_data;
+  new_data.dev_rate = dev_rate;
+  new_data.dev_rate_ratio = dev_rate_ratio;
+  new_data.main_rate_ratio = main_rate_ratio;
+  new_data.coarse_rate_adjust = coarse_rate_adjust;
+
+  set_dev_rate_map[dev_stream] = new_data;
+}
 int dev_stream_capture_update_rstream(struct dev_stream* dev_stream) {
   return 0;
 }
@@ -268,7 +451,11 @@
                                      unsigned int dev_id,
                                      const struct cras_audio_format* dev_fmt,
                                      void* dev_ptr,
-                                     struct timespec* cb_ts) {
+                                     struct timespec* cb_ts,
+                                     const struct timespec* sleep_interval_ts) {
+  return 0;
+}
+int cras_device_monitor_error_close(unsigned int dev_idx) {
   return 0;
 }
 }  // extern "C"
diff --git a/cras/src/tests/dev_stream_unittest.cc b/cras/src/tests/dev_stream_unittest.cc
index 39c16e4..700376f 100644
--- a/cras/src/tests/dev_stream_unittest.cc
+++ b/cras/src/tests/dev_stream_unittest.cc
@@ -334,18 +334,36 @@
   out_fmt.frame_rate = 48000;  // Output from converter is device rate.
   config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33);
   dev_stream =
-      dev_stream_create(&rstream_, 0, &fmt_s16le_48, (void*)0x55, &cb_ts);
+      dev_stream_create(&rstream_, 0, &fmt_s16le_48, (void*)0x55, &cb_ts, NULL);
   EXPECT_EQ(1, config_format_converter_called);
   EXPECT_NE(static_cast<byte_buffer*>(NULL), dev_stream->conv_buffer);
   // Converter tmp and output buffers are large enough for device output.
   unsigned int device_frames =
       cras_frames_at_rate(in_fmt.frame_rate, kBufferFrames, out_fmt.frame_rate);
-  EXPECT_LE(kBufferFrames, device_frames);  // Sanity check.
+  EXPECT_LE(kBufferFrames, device_frames);  // Soundness check.
   EXPECT_LE(device_frames, config_format_converter_frames);
   EXPECT_LE(device_frames, dev_stream->conv_buffer_size_frames);
   dev_stream_destroy(dev_stream);
 }
 
+TEST_F(CreateSuite, CreateOutputWithSchedule) {
+  struct dev_stream* dev_stream;
+  unsigned int dev_id = 9;
+  // init_cb_ts and non-null init_sleep_ts will be used.
+  struct timespec init_cb_ts = {1, 2};
+  struct timespec init_sleep_ts = {3, 4};
+
+  rstream_.direction = CRAS_STREAM_OUTPUT;
+  dev_stream = dev_stream_create(&rstream_, dev_id, &fmt_s16le_48, (void*)0x55,
+                                 &init_cb_ts, &init_sleep_ts);
+
+  EXPECT_EQ(init_cb_ts.tv_sec, rstream_.next_cb_ts.tv_sec);
+  EXPECT_EQ(init_cb_ts.tv_nsec, rstream_.next_cb_ts.tv_nsec);
+  EXPECT_EQ(init_sleep_ts.tv_sec, rstream_.sleep_interval_ts.tv_sec);
+  EXPECT_EQ(init_sleep_ts.tv_nsec, rstream_.sleep_interval_ts.tv_nsec);
+  dev_stream_destroy(dev_stream);
+}
+
 TEST_F(CreateSuite, CreateSRC44from48Input) {
   struct dev_stream* dev_stream;
   struct cras_audio_format processed_fmt = fmt_s16le_48;
@@ -358,13 +376,13 @@
   config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33);
   cras_rstream_post_processing_format_val = &processed_fmt;
   dev_stream =
-      dev_stream_create(&rstream_, 0, &fmt_s16le_48, (void*)0x55, &cb_ts);
+      dev_stream_create(&rstream_, 0, &fmt_s16le_48, (void*)0x55, &cb_ts, NULL);
   EXPECT_EQ(1, config_format_converter_called);
   EXPECT_NE(static_cast<byte_buffer*>(NULL), dev_stream->conv_buffer);
   // Converter tmp and output buffers are large enough for device input.
   unsigned int device_frames =
       cras_frames_at_rate(out_fmt.frame_rate, kBufferFrames, in_fmt.frame_rate);
-  EXPECT_LE(kBufferFrames, device_frames);  // Sanity check.
+  EXPECT_LE(kBufferFrames, device_frames);  // Soundness check.
   EXPECT_LE(device_frames, config_format_converter_frames);
   EXPECT_EQ(&processed_fmt, config_format_converter_from_fmt);
   EXPECT_LE(device_frames, dev_stream->conv_buffer_size_frames);
@@ -378,8 +396,8 @@
   in_fmt.frame_rate = 48000;   // Stream rate.
   out_fmt.frame_rate = 44100;  // Device rate.
   config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33);
-  dev_stream =
-      dev_stream_create(&rstream_, 0, &fmt_s16le_44_1, (void*)0x55, &cb_ts);
+  dev_stream = dev_stream_create(&rstream_, 0, &fmt_s16le_44_1, (void*)0x55,
+                                 &cb_ts, NULL);
   EXPECT_EQ(1, config_format_converter_called);
   EXPECT_NE(static_cast<byte_buffer*>(NULL), dev_stream->conv_buffer);
   // Converter tmp and output buffers are large enough for stream input.
@@ -396,8 +414,8 @@
   in_fmt.frame_rate = 44100;   // Device rate.
   out_fmt.frame_rate = 48000;  // Stream rate.
   config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33);
-  dev_stream =
-      dev_stream_create(&rstream_, 0, &fmt_s16le_44_1, (void*)0x55, &cb_ts);
+  dev_stream = dev_stream_create(&rstream_, 0, &fmt_s16le_44_1, (void*)0x55,
+                                 &cb_ts, NULL);
   EXPECT_EQ(1, config_format_converter_called);
   EXPECT_NE(static_cast<byte_buffer*>(NULL), dev_stream->conv_buffer);
   // Converter tmp and output buffers are large enough for stream output.
@@ -414,13 +432,13 @@
   out_fmt.frame_rate = 48000;  // Device rate.
   config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33);
   dev_stream =
-      dev_stream_create(&rstream_, 0, &fmt_s16le_48, (void*)0x55, &cb_ts);
+      dev_stream_create(&rstream_, 0, &fmt_s16le_48, (void*)0x55, &cb_ts, NULL);
   EXPECT_EQ(1, config_format_converter_called);
   EXPECT_NE(static_cast<byte_buffer*>(NULL), dev_stream->conv_buffer);
   // Converter tmp and output buffers are large enough for device output.
   unsigned int device_frames =
       cras_frames_at_rate(in_fmt.frame_rate, kBufferFrames, out_fmt.frame_rate);
-  EXPECT_LE(kBufferFrames, device_frames);  // Sanity check.
+  EXPECT_LE(kBufferFrames, device_frames);  // Soundness check.
   EXPECT_LE(device_frames, config_format_converter_frames);
   EXPECT_LE(device_frames, dev_stream->conv_buffer_size_frames);
   dev_stream_destroy(dev_stream);
@@ -435,13 +453,13 @@
   out_fmt.frame_rate = 8000;  // Stream rate.
   config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33);
   dev_stream =
-      dev_stream_create(&rstream_, 0, &fmt_s16le_48, (void*)0x55, &cb_ts);
+      dev_stream_create(&rstream_, 0, &fmt_s16le_48, (void*)0x55, &cb_ts, NULL);
   EXPECT_EQ(1, config_format_converter_called);
   EXPECT_NE(static_cast<byte_buffer*>(NULL), dev_stream->conv_buffer);
   // Converter tmp and output buffers are large enough for device input.
   unsigned int device_frames =
       cras_frames_at_rate(out_fmt.frame_rate, kBufferFrames, in_fmt.frame_rate);
-  EXPECT_LE(kBufferFrames, device_frames);  // Sanity check.
+  EXPECT_LE(kBufferFrames, device_frames);  // Soundness check.
   EXPECT_LE(device_frames, config_format_converter_frames);
   EXPECT_LE(device_frames, dev_stream->conv_buffer_size_frames);
   dev_stream_destroy(dev_stream);
@@ -455,7 +473,7 @@
   out_fmt.frame_rate = 8000;  // Device rate.
   config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33);
   dev_stream =
-      dev_stream_create(&rstream_, 0, &fmt_s16le_8, (void*)0x55, &cb_ts);
+      dev_stream_create(&rstream_, 0, &fmt_s16le_8, (void*)0x55, &cb_ts, NULL);
   EXPECT_EQ(1, config_format_converter_called);
   EXPECT_NE(static_cast<byte_buffer*>(NULL), dev_stream->conv_buffer);
   // Converter tmp and output buffers are large enough for stream input.
@@ -473,7 +491,7 @@
   out_fmt.frame_rate = 48000;  // Stream rate.
   config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33);
   dev_stream =
-      dev_stream_create(&rstream_, 0, &fmt_s16le_8, (void*)0x55, &cb_ts);
+      dev_stream_create(&rstream_, 0, &fmt_s16le_8, (void*)0x55, &cb_ts, NULL);
   EXPECT_EQ(1, config_format_converter_called);
   EXPECT_NE(static_cast<byte_buffer*>(NULL), dev_stream->conv_buffer);
   // Converter tmp and output buffers are large enough for stream output.
@@ -490,8 +508,8 @@
   in_fmt.frame_rate = 44100;   // Device rate.
   out_fmt.frame_rate = 48000;  // Stream rate.
   config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33);
-  dev_stream =
-      dev_stream_create(&rstream_, 0, &fmt_s16le_44_1, (void*)0x55, &cb_ts);
+  dev_stream = dev_stream_create(&rstream_, 0, &fmt_s16le_44_1, (void*)0x55,
+                                 &cb_ts, NULL);
   EXPECT_EQ(1, config_format_converter_called);
   EXPECT_NE(static_cast<byte_buffer*>(NULL), dev_stream->conv_buffer);
   // Converter tmp and output buffers are large enough for stream output.
@@ -510,8 +528,8 @@
   rstream_.format = fmt_s16le_48;
   rstream_.direction = CRAS_STREAM_INPUT;
   config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33);
-  dev_stream =
-      dev_stream_create(&rstream_, 0, &fmt_s16le_44_1, (void*)0x55, &cb_ts);
+  dev_stream = dev_stream_create(&rstream_, 0, &fmt_s16le_44_1, (void*)0x55,
+                                 &cb_ts, NULL);
   EXPECT_EQ(1, config_format_converter_called);
   EXPECT_NE(static_cast<byte_buffer*>(NULL), dev_stream->conv_buffer);
   EXPECT_LE(
@@ -529,16 +547,16 @@
   dev_stream_destroy(dev_stream);
 }
 
-TEST_F(CreateSuite, SetDevRateNotMasterDev) {
+TEST_F(CreateSuite, SetDevRateNotMainDev) {
   struct dev_stream* dev_stream;
   unsigned int dev_id = 9;
 
   rstream_.format = fmt_s16le_48;
   rstream_.direction = CRAS_STREAM_INPUT;
-  rstream_.master_dev.dev_id = 4;
+  rstream_.main_dev.dev_id = 4;
   config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33);
   dev_stream = dev_stream_create(&rstream_, dev_id, &fmt_s16le_44_1,
-                                 (void*)0x55, &cb_ts);
+                                 (void*)0x55, &cb_ts, NULL);
 
   dev_stream_set_dev_rate(dev_stream, 44100, 1.01, 1.0, 0);
   EXPECT_EQ(1, cras_fmt_conv_set_linear_resample_rates_called);
@@ -557,17 +575,17 @@
   dev_stream_destroy(dev_stream);
 }
 
-TEST_F(CreateSuite, SetDevRateMasterDev) {
+TEST_F(CreateSuite, SetDevRateMainDev) {
   struct dev_stream* dev_stream;
   unsigned int dev_id = 9;
   unsigned int expected_ts_nsec;
 
   rstream_.format = fmt_s16le_48;
   rstream_.direction = CRAS_STREAM_INPUT;
-  rstream_.master_dev.dev_id = dev_id;
+  rstream_.main_dev.dev_id = dev_id;
   config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33);
   dev_stream = dev_stream_create(&rstream_, dev_id, &fmt_s16le_44_1,
-                                 (void*)0x55, &cb_ts);
+                                 (void*)0x55, &cb_ts, NULL);
 
   dev_stream_set_dev_rate(dev_stream, 44100, 1.01, 1.0, 0);
   EXPECT_EQ(1, cras_fmt_conv_set_linear_resample_rates_called);
@@ -661,7 +679,7 @@
   unsigned int dev_id = 9;
 
   dev_stream = dev_stream_create(&rstream_, dev_id, &fmt_s16le_44_1,
-                                 (void*)0x55, &cb_ts);
+                                 (void*)0x55, &cb_ts, NULL);
 
   dev_stream_flush_old_audio_messages(dev_stream);
   EXPECT_EQ(1, cras_rstream_flush_old_audio_messages_called);
@@ -673,7 +691,7 @@
   unsigned int dev_id = 9;
 
   dev_stream = dev_stream_create(&rstream_, dev_id, &fmt_s16le_44_1,
-                                 (void*)0x55, &cb_ts);
+                                 (void*)0x55, &cb_ts, NULL);
 
   // dev_stream_is_pending_reply is only a wrapper.
   cras_rstream_is_pending_reply_ret = 0;
@@ -694,7 +712,7 @@
 
   rstream_.direction = CRAS_STREAM_INPUT;
   dev_stream = dev_stream_create(&rstream_, dev_id, &fmt_s16le_44_1,
-                                 (void*)0x55, &cb_ts);
+                                 (void*)0x55, &cb_ts, NULL);
 
   // Assume there is a next_cb_ts on rstream.
   rstream_.next_cb_ts.tv_sec = 1;
@@ -791,7 +809,7 @@
   rstream_.direction = CRAS_STREAM_INPUT;
   rstream_.flags |= BULK_AUDIO_OK;
   dev_stream = dev_stream_create(&rstream_, dev_id, &fmt_s16le_44_1,
-                                 (void*)0x55, &cb_ts);
+                                 (void*)0x55, &cb_ts, NULL);
 
   // Assume there is a next_cb_ts on rstream.
   rstream_.next_cb_ts.tv_sec = 1;
@@ -864,7 +882,7 @@
 
   rstream_.direction = CRAS_STREAM_INPUT;
   dev_stream = dev_stream_create(&rstream_, dev_id, &fmt_s16le_44_1,
-                                 (void*)0x55, &cb_ts);
+                                 (void*)0x55, &cb_ts, NULL);
   dev_stream->stream->flags = TRIGGER_ONLY;
   dev_stream->stream->triggered = 0;
 
@@ -896,7 +914,7 @@
 
   rstream_.direction = CRAS_STREAM_INPUT;
   dev_stream = dev_stream_create(&rstream_, dev_id, &fmt_s16le_44_1,
-                                 (void*)0x55, &cb_ts);
+                                 (void*)0x55, &cb_ts, NULL);
 
   // Assume there is a next_cb_ts on rstream.
   rstream_.next_cb_ts.tv_sec = 1;
@@ -929,8 +947,8 @@
   int needed_frames_from_device = 0;
 
   rstream_.direction = CRAS_STREAM_INPUT;
-  dev_stream =
-      dev_stream_create(&rstream_, dev_id, &fmt_s16le_48, (void*)0x55, &cb_ts);
+  dev_stream = dev_stream_create(&rstream_, dev_id, &fmt_s16le_48, (void*)0x55,
+                                 &cb_ts, NULL);
 
   // Assume there is a next_cb_ts on rstream, that is, 1.005 seconds.
   rstream_.next_cb_ts.tv_sec = 1;
@@ -994,7 +1012,7 @@
 
   rstream_.direction = CRAS_STREAM_OUTPUT;
   dev_stream = dev_stream_create(&rstream_, dev_id, &fmt_s16le_44_1,
-                                 (void*)0x55, &cb_ts);
+                                 (void*)0x55, &cb_ts, NULL);
 
   // Case 1: The new next_cb_ts is greater than now. Do not need to reschedule.
   rstream_.next_cb_ts.tv_sec = 2;
@@ -1170,6 +1188,11 @@
     void* dev_ptr) {
   return cras_rstream_post_processing_format_val;
 }
+void* buffer_share_get_data(const struct buffer_share* mix, unsigned int id) {
+  return NULL;
+};
+void cras_apm_list_start_apm(struct cras_apm_list* list, void* dev_ptr){};
+void cras_apm_list_stop_apm(struct cras_apm_list* list, void* dev_ptr){};
 
 int config_format_converter(struct cras_fmt_conv** conv,
                             enum CRAS_STREAM_DIRECTION dir,
@@ -1291,7 +1314,7 @@
   return 0;
 }
 
-int cras_server_metrics_missed_cb_event(const struct cras_rstream* stream) {
+int cras_server_metrics_missed_cb_event(struct cras_rstream* stream) {
   cras_server_metrics_missed_cb_event_called++;
   return 0;
 }
diff --git a/cras/src/tests/device_blacklist_unittest.cc b/cras/src/tests/device_blacklist_unittest.cc
deleted file mode 100644
index 2c1edf0..0000000
--- a/cras/src/tests/device_blacklist_unittest.cc
+++ /dev/null
@@ -1,88 +0,0 @@
-// Copyright (c) 2012 The Chromium OS 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 <gtest/gtest.h>
-#include <stdio.h>
-
-extern "C" {
-#include "cras_device_blacklist.h"
-}
-
-namespace {
-
-static const char CONFIG_PATH[] = CRAS_UT_TMPDIR;
-static const char CONFIG_FILENAME[] = "device_blacklist";
-
-void CreateConfigFile(const char* config_text) {
-  FILE* f;
-  char card_path[128];
-
-  snprintf(card_path, sizeof(card_path), "%s/%s", CONFIG_PATH, CONFIG_FILENAME);
-  f = fopen(card_path, "w");
-  if (f == NULL)
-    return;
-
-  fprintf(f, "%s", config_text);
-
-  fclose(f);
-}
-
-TEST(Blacklist, EmptyBlacklist) {
-  static const char empty_config_text[] = "";
-  struct cras_device_blacklist* blacklist;
-
-  CreateConfigFile(empty_config_text);
-
-  blacklist = cras_device_blacklist_create(CONFIG_PATH);
-  ASSERT_NE(static_cast<cras_device_blacklist*>(NULL), blacklist);
-  EXPECT_EQ(0, cras_device_blacklist_check(blacklist, 0x0d8c, 0x0008, 0, 0));
-
-  cras_device_blacklist_destroy(blacklist);
-}
-
-TEST(Blacklist, BlackListOneUsbOutput) {
-  static const char usb_output_config_text[] =
-      "[USB_Outputs]\n"
-      "0d8c_0008_00000012_0 = 1\n";
-  struct cras_device_blacklist* blacklist;
-
-  CreateConfigFile(usb_output_config_text);
-
-  blacklist = cras_device_blacklist_create(CONFIG_PATH);
-  ASSERT_NE(static_cast<cras_device_blacklist*>(NULL), blacklist);
-
-  EXPECT_EQ(0, cras_device_blacklist_check(blacklist, 0x0d8d, 0x0008, 0x12, 0));
-  EXPECT_EQ(0, cras_device_blacklist_check(blacklist, 0x0d8c, 0x0009, 0x12, 0));
-  EXPECT_EQ(0, cras_device_blacklist_check(blacklist, 0x0d8c, 0x0008, 0x13, 0));
-  EXPECT_EQ(0, cras_device_blacklist_check(blacklist, 0x0d8c, 0x0008, 0x12, 1));
-  EXPECT_EQ(1, cras_device_blacklist_check(blacklist, 0x0d8c, 0x0008, 0x12, 0));
-
-  cras_device_blacklist_destroy(blacklist);
-}
-
-TEST(Blacklist, BlackListTwoUsbOutput) {
-  static const char usb_output_config_text[] =
-      "[USB_Outputs]\n"
-      "0d8c_0008_00000000_0 = 1\n"
-      "0d8c_0009_00000000_0 = 1\n";
-  struct cras_device_blacklist* blacklist;
-
-  CreateConfigFile(usb_output_config_text);
-
-  blacklist = cras_device_blacklist_create(CONFIG_PATH);
-  ASSERT_NE(static_cast<cras_device_blacklist*>(NULL), blacklist);
-
-  EXPECT_EQ(1, cras_device_blacklist_check(blacklist, 0x0d8c, 0x0009, 0, 0));
-  EXPECT_EQ(1, cras_device_blacklist_check(blacklist, 0x0d8c, 0x0008, 0, 0));
-  EXPECT_EQ(0, cras_device_blacklist_check(blacklist, 0x0d8c, 0x0008, 0, 1));
-
-  cras_device_blacklist_destroy(blacklist);
-}
-
-}  //  namespace
-
-int main(int argc, char** argv) {
-  ::testing::InitGoogleTest(&argc, argv);
-  return RUN_ALL_TESTS();
-}
diff --git a/cras/src/tests/device_blocklist_unittest.cc b/cras/src/tests/device_blocklist_unittest.cc
new file mode 100644
index 0000000..44a976e
--- /dev/null
+++ b/cras/src/tests/device_blocklist_unittest.cc
@@ -0,0 +1,88 @@
+// Copyright (c) 2012 The Chromium OS 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 <gtest/gtest.h>
+#include <stdio.h>
+
+extern "C" {
+#include "cras_device_blocklist.h"
+}
+
+namespace {
+
+static const char CONFIG_PATH[] = CRAS_UT_TMPDIR;
+static const char CONFIG_FILENAME[] = "device_blocklist";
+
+void CreateConfigFile(const char* config_text) {
+  FILE* f;
+  char card_path[128];
+
+  snprintf(card_path, sizeof(card_path), "%s/%s", CONFIG_PATH, CONFIG_FILENAME);
+  f = fopen(card_path, "w");
+  if (f == NULL)
+    return;
+
+  fprintf(f, "%s", config_text);
+
+  fclose(f);
+}
+
+TEST(Blocklist, EmptyBlocklist) {
+  static const char empty_config_text[] = "";
+  struct cras_device_blocklist* blocklist;
+
+  CreateConfigFile(empty_config_text);
+
+  blocklist = cras_device_blocklist_create(CONFIG_PATH);
+  ASSERT_NE(static_cast<cras_device_blocklist*>(NULL), blocklist);
+  EXPECT_EQ(0, cras_device_blocklist_check(blocklist, 0x0d8c, 0x0008, 0, 0));
+
+  cras_device_blocklist_destroy(blocklist);
+}
+
+TEST(Blocklist, BlockListOneUsbOutput) {
+  static const char usb_output_config_text[] =
+      "[USB_Outputs]\n"
+      "0d8c_0008_00000012_0 = 1\n";
+  struct cras_device_blocklist* blocklist;
+
+  CreateConfigFile(usb_output_config_text);
+
+  blocklist = cras_device_blocklist_create(CONFIG_PATH);
+  ASSERT_NE(static_cast<cras_device_blocklist*>(NULL), blocklist);
+
+  EXPECT_EQ(0, cras_device_blocklist_check(blocklist, 0x0d8d, 0x0008, 0x12, 0));
+  EXPECT_EQ(0, cras_device_blocklist_check(blocklist, 0x0d8c, 0x0009, 0x12, 0));
+  EXPECT_EQ(0, cras_device_blocklist_check(blocklist, 0x0d8c, 0x0008, 0x13, 0));
+  EXPECT_EQ(0, cras_device_blocklist_check(blocklist, 0x0d8c, 0x0008, 0x12, 1));
+  EXPECT_EQ(1, cras_device_blocklist_check(blocklist, 0x0d8c, 0x0008, 0x12, 0));
+
+  cras_device_blocklist_destroy(blocklist);
+}
+
+TEST(Blocklist, BlockListTwoUsbOutput) {
+  static const char usb_output_config_text[] =
+      "[USB_Outputs]\n"
+      "0d8c_0008_00000000_0 = 1\n"
+      "0d8c_0009_00000000_0 = 1\n";
+  struct cras_device_blocklist* blocklist;
+
+  CreateConfigFile(usb_output_config_text);
+
+  blocklist = cras_device_blocklist_create(CONFIG_PATH);
+  ASSERT_NE(static_cast<cras_device_blocklist*>(NULL), blocklist);
+
+  EXPECT_EQ(1, cras_device_blocklist_check(blocklist, 0x0d8c, 0x0009, 0, 0));
+  EXPECT_EQ(1, cras_device_blocklist_check(blocklist, 0x0d8c, 0x0008, 0, 0));
+  EXPECT_EQ(0, cras_device_blocklist_check(blocklist, 0x0d8c, 0x0008, 0, 1));
+
+  cras_device_blocklist_destroy(blocklist);
+}
+
+}  //  namespace
+
+int main(int argc, char** argv) {
+  ::testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
diff --git a/cras/src/tests/empty_iodev_unittest.cc b/cras/src/tests/empty_iodev_unittest.cc
index 585fba3..148d530 100644
--- a/cras/src/tests/empty_iodev_unittest.cc
+++ b/cras/src/tests/empty_iodev_unittest.cc
@@ -13,7 +13,7 @@
 
 static struct timespec clock_gettime_retspec;
 static struct cras_audio_format fake_format;
-static cras_audio_area dummy_audio_area;
+static cras_audio_area mock_audio_area;
 
 namespace {
 
@@ -57,7 +57,7 @@
 }
 
 void cras_iodev_init_audio_area(struct cras_iodev* iodev, int num_channels) {
-  iodev->area = &dummy_audio_area;
+  iodev->area = &mock_audio_area;
 }
 
 void cras_iodev_free_audio_area(struct cras_iodev* iodev) {}
diff --git a/cras/src/tests/ewma_power_unittest.cc b/cras/src/tests/ewma_power_unittest.cc
new file mode 100644
index 0000000..10f0318
--- /dev/null
+++ b/cras/src/tests/ewma_power_unittest.cc
@@ -0,0 +1,129 @@
+// Copyright (c) 2020 The Chromium OS 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 <gtest/gtest.h>
+
+extern "C" {
+#include "ewma_power.h"
+}
+
+namespace {
+
+TEST(EWMAPower, RelativePowerValue) {
+  struct ewma_power ewma;
+  int16_t buf[480];
+  float f;
+  int i;
+
+  for (i = 0; i < 480; i++)
+    buf[i] = 0x00fe;
+
+  ewma_power_init(&ewma, 48000);
+  EXPECT_EQ(48, ewma.step_fr);
+
+  ewma_power_calculate(&ewma, buf, 1, 480);
+  EXPECT_LT(0.0f, ewma.power);
+
+  // After 10ms of silence the power value decreases.
+  f = ewma.power;
+  for (i = 0; i < 480; i++)
+    buf[i] = 0x00;
+  ewma_power_calculate(&ewma, buf, 1, 480);
+  EXPECT_LT(ewma.power, f);
+
+  // After 300ms of silence the power value decreases to insignificant low.
+  for (i = 0; i < 30; i++)
+    ewma_power_calculate(&ewma, buf, 1, 480);
+  EXPECT_LT(ewma.power, 1.0e-10);
+}
+
+TEST(EWMAPower, PowerInStereoData) {
+  struct ewma_power ewma;
+  int16_t buf[960];
+  int i;
+  float f;
+
+  ewma_power_init(&ewma, 48000);
+
+  for (i = 0; i < 960; i += 2) {
+    buf[i] = 0x0;
+    buf[i + 1] = 0x00fe;
+  }
+  ewma_power_calculate(&ewma, buf, 2, 480);
+  EXPECT_LT(0.0f, ewma.power);
+
+  // After 10ms of silence the power value decreases.
+  f = ewma.power;
+  for (i = 0; i < 960; i++)
+    buf[i] = 0x0;
+  ewma_power_calculate(&ewma, buf, 2, 480);
+  EXPECT_LT(ewma.power, f);
+
+  // After 300ms of silence the power value decreases to insignificant low.
+  for (i = 0; i < 30; i++)
+    ewma_power_calculate(&ewma, buf, 2, 480);
+  EXPECT_LT(ewma.power, 1.0e-10);
+
+  // Assume the data is silent in the other channel.
+  ewma_power_init(&ewma, 48000);
+
+  for (i = 0; i < 960; i += 2) {
+    buf[i] = 0x0ffe;
+    buf[i + 1] = 0x0;
+  }
+  ewma_power_calculate(&ewma, buf, 2, 480);
+  EXPECT_LT(0.0f, ewma.power);
+}
+
+TEST(EWMAPower, PowerInAudioArea) {
+  struct ewma_power ewma;
+  struct cras_audio_area* area = cras_audio_area_create(4);
+  struct cras_audio_format* fmt =
+      cras_audio_format_create(SND_PCM_FORMAT_S16_LE, 48000, 4);
+  int8_t layout[CRAS_CH_MAX] = {0, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1};
+  int16_t buf[1920];
+  int i;
+  float f;
+
+  cras_audio_format_set_channel_layout(fmt, layout);
+  cras_audio_area_config_channels(area, fmt);
+
+  for (i = 0; i < 1920; i += 4) {
+    buf[i] = 0x0ffe;
+    buf[i + 1] = 0x0;
+    buf[i + 2] = 0x0;
+    buf[i + 3] = 0x0ffe;
+  }
+  ewma_power_init(&ewma, 48000);
+  ewma_power_calculate_area(&ewma, buf, area, 480);
+  f = ewma.power;
+  EXPECT_LT(0.0f, f);
+
+  /* Change the layout in the same audio area. Expect the power be lower because
+   * one of the channel is now silent. */
+  layout[CRAS_CH_FR] = 2;
+  cras_audio_format_set_channel_layout(fmt, layout);
+  cras_audio_area_config_channels(area, fmt);
+  ewma_power_init(&ewma, 48000);
+  ewma_power_calculate_area(&ewma, buf, area, 480);
+  EXPECT_GT(f, ewma.power);
+
+  /* Change layout to the two silent channels. Expect power is 0.0f. */
+  layout[CRAS_CH_FL] = 1;
+  cras_audio_format_set_channel_layout(fmt, layout);
+  cras_audio_area_config_channels(area, fmt);
+  ewma_power_init(&ewma, 48000);
+  ewma_power_calculate_area(&ewma, buf, area, 480);
+  EXPECT_EQ(0.0f, ewma.power);
+
+  cras_audio_format_destroy(fmt);
+  cras_audio_area_destroy(area);
+}
+
+}  // namespace
+
+int main(int argc, char** argv) {
+  ::testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
diff --git a/cras/src/tests/fmt_conv_ops_unittest.cc b/cras/src/tests/fmt_conv_ops_unittest.cc
index d58d618..0baf37b 100644
--- a/cras/src/tests/fmt_conv_ops_unittest.cc
+++ b/cras/src/tests/fmt_conv_ops_unittest.cc
@@ -4,6 +4,8 @@
 
 #include <gtest/gtest.h>
 #include <limits.h>
+#include <math.h>
+#include <stdint.h>
 #include <sys/param.h>
 
 #include <memory>
@@ -416,6 +418,39 @@
   }
 }
 
+// Test Quad to 5.1 conversion. S16_LE.
+TEST(FormatConverterOpsTest, QuadTo51S16LE) {
+  const size_t frames = 4096;
+  const size_t in_ch = 4;
+  const size_t out_ch = 6;
+  const unsigned int fl_quad = 0;
+  const unsigned int fr_quad = 1;
+  const unsigned int rl_quad = 2;
+  const unsigned int rr_quad = 3;
+
+  const unsigned int fl_51 = 0;
+  const unsigned int fr_51 = 1;
+  const unsigned int center_51 = 2;
+  const unsigned int lfe_51 = 3;
+  const unsigned int rl_51 = 4;
+  const unsigned int rr_51 = 5;
+
+  S16LEPtr src = CreateS16LE(frames * in_ch);
+  S16LEPtr dst = CreateS16LE(frames * out_ch);
+
+  size_t ret = s16_quad_to_51(fl_51, fr_51, rl_51, rr_51, (uint8_t*)src.get(),
+                              frames, (uint8_t*)dst.get());
+  EXPECT_EQ(ret, frames);
+  for (size_t i = 0; i < frames; ++i) {
+    EXPECT_EQ(0, dst[i * 6 + center_51]);
+    EXPECT_EQ(0, dst[i * 6 + lfe_51]);
+    EXPECT_EQ(src[i * 4 + fl_quad], dst[i * 6 + fl_51]);
+    EXPECT_EQ(src[i * 4 + fr_quad], dst[i * 6 + fr_51]);
+    EXPECT_EQ(src[i * 4 + rl_quad], dst[i * 6 + rl_51]);
+    EXPECT_EQ(src[i * 4 + rr_quad], dst[i * 6 + rr_51]);
+  }
+}
+
 // Test Stereo to 5.1 conversion.  S16_LE, LeftRight.
 TEST(FormatConverterOpsTest, StereoTo51S16LELeftRight) {
   const size_t frames = 4096;
@@ -477,7 +512,7 @@
   const size_t out_ch = 2;
   const size_t left = 0;
   const size_t right = 1;
-  const size_t center = 4;
+  const size_t center = 2;
 
   S16LEPtr src = CreateS16LE(frames * in_ch);
   S16LEPtr dst = CreateS16LE(frames * out_ch);
@@ -486,11 +521,58 @@
       s16_51_to_stereo((uint8_t*)src.get(), frames, (uint8_t*)dst.get());
   EXPECT_EQ(ret, frames);
 
+  /* Use the normalized_factor from the left channel = 1 / (|1| + |0.707|)
+   * to prevent mixing overflow.
+   */
+  const float normalized_factor = 0.585;
+
   for (size_t i = 0; i < frames; ++i) {
-    int16_t half_center = src[i * 6 + center] / 2;
-    EXPECT_EQ(S16AddAndClip(src[i * 6 + left], half_center), dst[i * 2 + left]);
-    EXPECT_EQ(S16AddAndClip(src[i * 6 + right], half_center),
-              dst[i * 2 + right]);
+    int16_t half_center = src[i * 6 + center] * 0.707 * normalized_factor;
+    int16_t l = normalized_factor * src[i * 6 + left] + half_center;
+    int16_t r = normalized_factor * src[i * 6 + right] + half_center;
+
+    EXPECT_EQ(l, dst[i * 2 + left]);
+    EXPECT_EQ(r, dst[i * 2 + right]);
+  }
+}
+
+// Test 5.1 to Quad conversion.  S16_LE.
+TEST(FormatConverterOpsTest, _51ToQuadS16LE) {
+  const size_t frames = 4096;
+  const size_t in_ch = 6;
+  const size_t out_ch = 4;
+  const unsigned int fl_quad = 0;
+  const unsigned int fr_quad = 1;
+  const unsigned int rl_quad = 2;
+  const unsigned int rr_quad = 3;
+
+  const unsigned int fl_51 = 0;
+  const unsigned int fr_51 = 1;
+  const unsigned int center_51 = 2;
+  const unsigned int lfe_51 = 3;
+  const unsigned int rl_51 = 4;
+  const unsigned int rr_51 = 5;
+
+  S16LEPtr src = CreateS16LE(frames * in_ch);
+  S16LEPtr dst = CreateS16LE(frames * out_ch);
+
+  size_t ret = s16_51_to_quad((uint8_t*)src.get(), frames, (uint8_t*)dst.get());
+  EXPECT_EQ(ret, frames);
+
+  /* Use normalized_factor from the left channel = 1 / (|1| + |0.707| + |0.5|)
+   * to prevent overflow. */
+  const float normalized_factor = 0.453;
+  for (size_t i = 0; i < frames; ++i) {
+    int16_t half_center = src[i * 6 + center_51] * 0.707 * normalized_factor;
+    int16_t lfe = src[6 * i + lfe_51] * 0.5 * normalized_factor;
+    int16_t fl = normalized_factor * src[6 * i + fl_51] + half_center + lfe;
+    int16_t fr = normalized_factor * src[6 * i + fr_51] + half_center + lfe;
+    int16_t rl = normalized_factor * src[6 * i + rl_51] + lfe;
+    int16_t rr = normalized_factor * src[6 * i + rr_51] + lfe;
+    EXPECT_EQ(fl, dst[4 * i + fl_quad]);
+    EXPECT_EQ(fr, dst[4 * i + fr_quad]);
+    EXPECT_EQ(rl, dst[4 * i + rl_quad]);
+    EXPECT_EQ(rr, dst[4 * i + rr_quad]);
   }
 }
 
@@ -619,10 +701,10 @@
   EXPECT_EQ(ret, frames);
 
   for (size_t i = 0; i < frames; ++i) {
+    int32_t sum = 0;
     for (size_t k = 0; k < in_ch; ++k)
-      src[i * in_ch + k] /= in_ch;
-    for (size_t k = 1; k < in_ch; ++k)
-      src[i * in_ch + 0] += src[i * in_ch + k];
+      sum += (int32_t)src[i * in_ch + k];
+    src[i * in_ch + 0] = (int16_t)(sum / (int32_t)in_ch);
   }
   for (size_t i = 0; i < frames; ++i) {
     for (size_t k = 0; k < out_ch; ++k)
@@ -630,6 +712,36 @@
   }
 }
 
+// Test 6ch to 8ch conversion.  S16_LE.
+TEST(FormatConverterOpsTest, 6chTo8chS16LE) {
+  const size_t frames = 65536;
+  const size_t in_ch = 6;
+  const size_t out_ch = 8;
+  struct cras_audio_format fmt = {
+      .format = SND_PCM_FORMAT_S16_LE,
+      .frame_rate = 48000,
+      .num_channels = 8,
+  };
+
+  S16LEPtr src = CreateS16LE(frames * in_ch);
+  S16LEPtr dst = CreateS16LE(frames * out_ch);
+  for (size_t i = 0; i < frames; ++i) {
+    for (size_t k = 0; k < in_ch; k++) {
+      src[i * in_ch + k] = (k == 0) ? (INT16_MIN + (int16_t)i) : 0;
+    }
+  }
+
+  size_t ret = s16_default_all_to_all(&fmt, in_ch, out_ch, (uint8_t*)src.get(),
+                                      frames, (uint8_t*)dst.get());
+  EXPECT_EQ(ret, frames);
+
+  for (size_t i = 0; i < frames; ++i) {
+    src[i * in_ch + 0] /= (int16_t)in_ch;
+    for (size_t k = 0; k < out_ch; ++k)
+      EXPECT_EQ(src[i * in_ch + 0], dst[i * out_ch + k]);
+  }
+}
+
 // Test Multiply with Coef.  S16_LE.
 TEST(FormatConverterOpsTest, MultiplyWithCoefS16LE) {
   const size_t buf_size = 4096;
@@ -676,6 +788,65 @@
   }
 }
 
+// Test Stereo to 20ch conversion.  S16_LE.
+TEST(FormatConverterOpsTest, TwoToTwentyS16LE) {
+  const size_t frames = 4096;
+  const size_t in_ch = 2;
+  const size_t out_ch = 20;
+  struct cras_audio_format fmt = {
+      .format = SND_PCM_FORMAT_S16_LE,
+      .frame_rate = 48000,
+      .num_channels = 20,
+  };
+
+  S16LEPtr src = CreateS16LE(frames * in_ch);
+  S16LEPtr dst = CreateS16LE(frames * out_ch);
+
+  size_t ret = s16_some_to_some(&fmt, in_ch, out_ch, (uint8_t*)src.get(),
+                                frames, (uint8_t*)dst.get());
+  EXPECT_EQ(ret, frames);
+
+  for (size_t i = 0; i < frames; ++i) {
+    size_t k;
+    // Input channles should be directly copied over.
+    for (k = 0; k < in_ch; ++k) {
+        EXPECT_EQ(src[i * in_ch + k], dst[i * out_ch + k]);
+    }
+    // The rest should be zeroed.
+    for (; k < out_ch; ++k) {
+        EXPECT_EQ(0, dst[i * out_ch + k]);
+    }
+
+  }
+}
+
+// Test 20ch to Stereo.  S16_LE.
+TEST(FormatConverterOpsTest, TwentyToTwoS16LE) {
+  const size_t frames = 4096;
+  const size_t in_ch = 20;
+  const size_t out_ch = 2;
+  struct cras_audio_format fmt = {
+      .format = SND_PCM_FORMAT_S16_LE,
+      .frame_rate = 48000,
+      .num_channels = 2,
+  };
+
+  S16LEPtr src = CreateS16LE(frames * in_ch);
+  S16LEPtr dst = CreateS16LE(frames * out_ch);
+
+  size_t ret = s16_some_to_some(&fmt, in_ch, out_ch, (uint8_t*)src.get(),
+                                frames, (uint8_t*)dst.get());
+  EXPECT_EQ(ret, frames);
+
+  for (size_t i = 0; i < frames; ++i) {
+    size_t k;
+    // Input channles should be directly copied over.
+    for (k = 0; k < out_ch; ++k) {
+        EXPECT_EQ(src[i * in_ch + k], dst[i * out_ch + k]);
+    }
+  }
+}
+
 extern "C" {}  // extern "C"
 
 int main(int argc, char** argv) {
diff --git a/cras/src/tests/fmt_conv_unittest.cc b/cras/src/tests/fmt_conv_unittest.cc
index 5474f17..c66984e 100644
--- a/cras/src/tests/fmt_conv_unittest.cc
+++ b/cras/src/tests/fmt_conv_unittest.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include <gtest/gtest.h>
+#include <math.h>
 #include <sys/param.h>
 
 extern "C" {
@@ -459,6 +460,81 @@
   free(out_buff);
 }
 
+// Test 5.1 to Quad mix.
+TEST(FormatConverterTest, SurroundToQuad) {
+  struct cras_fmt_conv* c;
+  struct cras_audio_format in_fmt;
+  struct cras_audio_format out_fmt;
+
+  size_t out_frames;
+  int16_t* in_buff;
+  int16_t* out_buff;
+  unsigned int i;
+  const size_t buf_size = 4096;
+  unsigned int in_buf_size = 4096;
+
+  ResetStub();
+  in_fmt.format = SND_PCM_FORMAT_S16_LE;
+  out_fmt.format = SND_PCM_FORMAT_S16_LE;
+  in_fmt.num_channels = 6;
+  out_fmt.num_channels = 4;
+  in_fmt.frame_rate = 48000;
+  out_fmt.frame_rate = 48000;
+  for (i = 0; i < CRAS_CH_MAX; i++)
+    in_fmt.channel_layout[i] = surround_channel_center_layout[i];
+
+  c = cras_fmt_conv_create(&in_fmt, &out_fmt, buf_size, 0);
+  ASSERT_NE(c, (void*)NULL);
+
+  out_frames = cras_fmt_conv_out_frames_to_in(c, buf_size);
+  EXPECT_EQ(buf_size, out_frames);
+
+  out_frames = cras_fmt_conv_in_frames_to_out(c, buf_size);
+  EXPECT_EQ(buf_size, out_frames);
+
+  in_buff = (int16_t*)malloc(buf_size * 2 * cras_get_format_bytes(&in_fmt));
+
+  const int16_t in_fl = 100;
+  const int16_t in_fr = 200;
+  const int16_t in_rl = 200;
+  const int16_t in_rr = 300;
+  const int16_t in_fc = 60;
+  const int16_t in_lfe = 90;
+
+  for (i = 0; i < buf_size; i++) {
+    in_buff[i * 6 + CRAS_CH_FL] = in_fl;
+    in_buff[i * 6 + CRAS_CH_FR] = in_fr;
+    in_buff[i * 6 + CRAS_CH_RL] = in_rl;
+    in_buff[i * 6 + CRAS_CH_RR] = in_rr;
+    in_buff[i * 6 + CRAS_CH_FC] = in_fc;
+    in_buff[i * 6 + CRAS_CH_LFE] = in_lfe;
+  }
+  out_buff = (int16_t*)malloc(buf_size * 2 * cras_get_format_bytes(&out_fmt));
+  out_frames = cras_fmt_conv_convert_frames(
+      c, (uint8_t*)in_buff, (uint8_t*)out_buff, &in_buf_size, buf_size);
+  EXPECT_EQ(buf_size, out_frames);
+
+  // This is the sum of mtx[CRAS_CH_FL] coefficients.
+  const float normalize_factor = 1.0 / (1 + 0.707 + 0.5);
+
+  for (i = 0; i < buf_size; i++) {
+    int16_t lfe = 0.5 * normalize_factor * in_lfe;
+    int16_t center = 0.707 * normalize_factor * in_fc;
+    int16_t fl = normalize_factor * in_fl + center + lfe;
+    int16_t fr = normalize_factor * in_fr + center + lfe;
+    int16_t rl = normalize_factor * in_rl + lfe;
+    int16_t rr = normalize_factor * in_rr + lfe;
+
+    EXPECT_EQ(fl, out_buff[i * 4 + CRAS_CH_FL]);
+    EXPECT_EQ(fr, out_buff[i * 4 + CRAS_CH_FR]);
+    EXPECT_EQ(rl, out_buff[i * 4 + CRAS_CH_RL]);
+    EXPECT_EQ(rr, out_buff[i * 4 + CRAS_CH_RR]);
+  }
+  cras_fmt_conv_destroy(&c);
+  free(in_buff);
+  free(out_buff);
+}
+
 // Test Quad to Stereo mix.
 TEST(FormatConverterTest, QuadToStereo) {
   struct cras_fmt_conv* c;
diff --git a/cras/src/tests/hfp_ag_profile_unittest.cc b/cras/src/tests/hfp_ag_profile_unittest.cc
index 56c9908..3ecd240 100644
--- a/cras/src/tests/hfp_ag_profile_unittest.cc
+++ b/cras/src/tests/hfp_ag_profile_unittest.cc
@@ -167,6 +167,25 @@
   return 0;
 }
 
+int cras_bt_rm_profile(DBusConnection* conn, struct cras_bt_profile* profile) {
+  internal_bt_profile = NULL;
+  return 0;
+}
+
+int cras_bt_register_profile(DBusConnection* conn,
+                             struct cras_bt_profile* profile) {
+  return 0;
+}
+
+int cras_bt_register_profiles(DBusConnection* conn) {
+  return 0;
+}
+
+int cras_bt_unregister_profile(DBusConnection* conn,
+                               struct cras_bt_profile* profile) {
+  return 0;
+}
+
 struct hfp_info* hfp_info_create() {
   return NULL;
 }
@@ -224,11 +243,22 @@
 int hfp_slc_get_selected_codec(struct hfp_slc_handle* handle) {
   return HFP_CODEC_ID_CVSD;
 }
+int hfp_slc_get_ag_codec_negotiation_supported(struct hfp_slc_handle* handle) {
+  return 1;
+}
 
 int hfp_slc_get_hf_codec_negotiation_supported(struct hfp_slc_handle* handle) {
   return 1;
 }
 
+int hfp_slc_get_hf_supports_battery_indicator(struct hfp_slc_handle* handle) {
+  return 0;
+}
+
+int hfp_slc_get_hf_battery_level(struct hfp_slc_handle* handle) {
+  return -1;
+}
+
 struct cras_bt_device* cras_a2dp_connected_device() {
   return NULL;
 }
@@ -240,6 +270,10 @@
 
 void cras_a2dp_suspend_connected_device(struct cras_bt_device* device) {}
 
+const char* cras_bt_device_address(const struct cras_bt_device* device) {
+  return "";
+}
+
 int cras_bt_device_audio_gateway_initialized(struct cras_bt_device* device) {
   return 0;
 }
@@ -252,10 +286,22 @@
   cras_bt_device_notify_profile_dropped_profile = profile;
 }
 
+void hfp_info_set_wbs_logger(struct hfp_info* info,
+                             struct packet_status_logger* wbs_logger) {}
+
+void cras_observer_notify_bt_battery_changed(const char* address,
+                                             uint32_t level) {
+  return;
+}
+
 bool cras_system_get_bt_wbs_enabled() {
   return true;
 }
 
+int cras_server_metrics_hfp_wideband_selected_codec(int codec) {
+  return HFP_CODEC_ID_MSBC;
+}
+
 }  // extern "C"
 
 int main(int argc, char** argv) {
diff --git a/cras/src/tests/hfp_alsa_iodev_unittest.cc b/cras/src/tests/hfp_alsa_iodev_unittest.cc
index a4d5ba9..8756c20 100644
--- a/cras/src/tests/hfp_alsa_iodev_unittest.cc
+++ b/cras/src/tests/hfp_alsa_iodev_unittest.cc
@@ -22,6 +22,7 @@
 static struct cras_iodev fake_sco_out, fake_sco_in;
 static struct cras_bt_device* fake_device;
 static struct hfp_slc_handle* fake_slc;
+static struct cras_audio_format fake_format;
 
 static size_t cras_bt_device_append_iodev_called;
 static size_t cras_bt_device_rm_iodev_called;
@@ -57,6 +58,7 @@
 _FAKE_CALL1(update_supported_formats);
 _FAKE_CALL1(configure_dev);
 _FAKE_CALL1(close_dev);
+_FAKE_CALL1(output_underrun);
 _FAKE_CALL2(frames_queued);
 _FAKE_CALL1(delay_frames);
 _FAKE_CALL3(get_buffer);
@@ -66,6 +68,7 @@
 _FAKE_CALL1(start);
 _FAKE_CALL2(no_stream);
 _FAKE_CALL1(is_free_running);
+_FAKE_CALL2(get_valid_frames);
 
 static void ResetStubData() {
   cras_bt_device_append_iodev_called = 0;
@@ -130,6 +133,14 @@
   fake_sco_out.is_free_running = fake_sco_in.is_free_running =
       (int (*)(const struct cras_iodev*))fake_is_free_running;
   fake_is_free_running_called = 0;
+
+  fake_sco_out.output_underrun =
+      (int (*)(struct cras_iodev*))fake_output_underrun;
+  fake_output_underrun_called = 0;
+
+  fake_sco_out.get_valid_frames =
+      (int (*)(struct cras_iodev*, struct timespec*))fake_get_valid_frames;
+  fake_get_valid_frames_called = 0;
 }
 
 namespace {
@@ -216,7 +227,8 @@
                                 CRAS_BT_DEVICE_PROFILE_HFP_AUDIOGATEWAY);
   iodev->update_supported_formats(iodev);
 
-  EXPECT_EQ(1, fake_update_supported_formats_called);
+  // update_supported_format on alsa_io is not called.
+  EXPECT_EQ(0, fake_update_supported_formats_called);
   for (size_t i = 0; i < 2; ++i) {
     EXPECT_EQ(supported_rates[i], iodev->supported_rates[i]);
     EXPECT_EQ(supported_channel_counts[i], iodev->supported_channel_counts[i]);
@@ -229,13 +241,23 @@
 TEST_F(HfpAlsaIodev, ConfigureDev) {
   struct cras_iodev* iodev;
   size_t buf_size = 8192;
+  struct hfp_alsa_io* hfp_alsa_io;
 
   fake_sco_out.direction = CRAS_STREAM_OUTPUT;
   fake_sco_out.buffer_size = buf_size;
   iodev = hfp_alsa_iodev_create(&fake_sco_out, fake_device, fake_slc,
                                 CRAS_BT_DEVICE_PROFILE_HFP_AUDIOGATEWAY);
+  hfp_alsa_io = (struct hfp_alsa_io*)iodev;
+  iodev->format = &fake_format;
   iodev->configure_dev(iodev);
 
+  EXPECT_EQ(fake_format.num_channels, hfp_alsa_io->aio->format->num_channels);
+  EXPECT_EQ(fake_format.frame_rate, hfp_alsa_io->aio->format->frame_rate);
+  EXPECT_EQ(fake_format.format, hfp_alsa_io->aio->format->format);
+  for (int i = 0; i < CRAS_CH_MAX; i++)
+    EXPECT_EQ(fake_format.channel_layout[i],
+              hfp_alsa_io->aio->format->channel_layout[i]);
+
   EXPECT_EQ(1, fake_configure_dev_called);
   EXPECT_EQ(1, hfp_set_call_status_called);
   EXPECT_EQ(buf_size, iodev->buffer_size);
@@ -251,6 +273,7 @@
                                 CRAS_BT_DEVICE_PROFILE_HFP_AUDIOGATEWAY);
   iodev->close_dev(iodev);
 
+  EXPECT_EQ(1, hfp_set_call_status_called);
   EXPECT_EQ(1, cras_iodev_free_format_called);
   EXPECT_EQ(1, fake_close_dev_called);
 
@@ -392,6 +415,38 @@
   hfp_alsa_iodev_destroy(iodev);
 }
 
+TEST_F(HfpAlsaIodev, OutputUnderrun) {
+  struct cras_iodev* iodev;
+
+  fake_sco_out.direction = CRAS_STREAM_OUTPUT;
+  iodev = hfp_alsa_iodev_create(&fake_sco_out, fake_device, fake_slc,
+                                CRAS_BT_DEVICE_PROFILE_HFP_AUDIOGATEWAY);
+  iodev->min_cb_level = 0xab;
+  iodev->max_cb_level = 0xcd;
+
+  iodev->output_underrun(iodev);
+
+  EXPECT_EQ(0xab, fake_sco_out.min_cb_level);
+  EXPECT_EQ(0xcd, fake_sco_out.max_cb_level);
+  EXPECT_EQ(1, fake_output_underrun_called);
+
+  hfp_alsa_iodev_destroy(iodev);
+}
+
+TEST_F(HfpAlsaIodev, GetValidFrames) {
+  struct cras_iodev* iodev;
+  struct timespec ts;
+
+  fake_sco_out.direction = CRAS_STREAM_OUTPUT;
+  iodev = hfp_alsa_iodev_create(&fake_sco_out, fake_device, fake_slc,
+                                CRAS_BT_DEVICE_PROFILE_HFP_AUDIOGATEWAY);
+
+  iodev->get_valid_frames(iodev, &ts);
+
+  EXPECT_EQ(1, fake_get_valid_frames_called);
+
+  hfp_alsa_iodev_destroy(iodev);
+}
 }  // namespace
 
 extern "C" {
@@ -422,6 +477,9 @@
   iodev->active_node = node;
 }
 
+// From ewma_power
+void ewma_power_disable(struct ewma_power* ewma) {}
+
 size_t cras_system_get_volume() {
   return 0;
 }
@@ -449,6 +507,10 @@
   return "/fake/object/path";
 }
 
+int cras_bt_device_get_stable_id(const struct cras_bt_device* device) {
+  return 123;
+}
+
 void cras_iodev_free_resources(struct cras_iodev* iodev) {
   cras_iodev_free_resources_called++;
 }
diff --git a/cras/src/tests/hfp_info_unittest.cc b/cras/src/tests/hfp_info_unittest.cc
index 482c3a9..24f536a 100644
--- a/cras/src/tests/hfp_info_unittest.cc
+++ b/cras/src/tests/hfp_info_unittest.cc
@@ -3,10 +3,15 @@
  * found in the LICENSE file.
  */
 
+#include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <stdint.h>
 #include <time.h>
 
+using testing::MatchesRegex;
+using testing::internal::CaptureStdout;
+using testing::internal::GetCapturedStdout;
+
 extern "C" {
 #include "cras_hfp_info.c"
 #include "sbc_codec_stub.h"
@@ -38,7 +43,7 @@
 TEST(HfpInfo, AddRmDev) {
   ResetStubData();
 
-  info = hfp_info_create(HFP_CODEC_ID_CVSD);
+  info = hfp_info_create();
   ASSERT_NE(info, (void*)NULL);
   dev.direction = CRAS_STREAM_OUTPUT;
 
@@ -56,7 +61,7 @@
 TEST(HfpInfo, AddRmDevInvalid) {
   ResetStubData();
 
-  info = hfp_info_create(HFP_CODEC_ID_CVSD);
+  info = hfp_info_create();
   ASSERT_NE(info, (void*)NULL);
 
   dev.direction = CRAS_STREAM_OUTPUT;
@@ -77,10 +82,10 @@
 
   ResetStubData();
 
-  info = hfp_info_create(HFP_CODEC_ID_CVSD);
+  info = hfp_info_create();
   ASSERT_NE(info, (void*)NULL);
 
-  hfp_info_start(1, 48, info);
+  hfp_info_start(1, 48, HFP_CODEC_ID_CVSD, info);
   dev.direction = CRAS_STREAM_OUTPUT;
   ASSERT_EQ(0, hfp_info_add_iodev(info, dev.direction, dev.format));
 
@@ -125,10 +130,10 @@
 
   ResetStubData();
 
-  info = hfp_info_create(HFP_CODEC_ID_CVSD);
+  info = hfp_info_create();
   ASSERT_NE(info, (void*)NULL);
 
-  hfp_info_start(1, 48, info);
+  hfp_info_start(1, 48, HFP_CODEC_ID_CVSD, info);
   dev.direction = CRAS_STREAM_INPUT;
   ASSERT_EQ(0, hfp_info_add_iodev(info, dev.direction, dev.format));
 
@@ -173,11 +178,11 @@
 
   ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sock));
 
-  info = hfp_info_create(HFP_CODEC_ID_CVSD);
+  info = hfp_info_create();
   ASSERT_NE(info, (void*)NULL);
 
   dev.direction = CRAS_STREAM_INPUT;
-  hfp_info_start(sock[1], 48, info);
+  hfp_info_start(sock[1], 48, HFP_CODEC_ID_CVSD, info);
   ASSERT_EQ(0, hfp_info_add_iodev(info, dev.direction, dev.format));
 
   /* Mock the sco fd and send some fake data */
@@ -224,10 +229,10 @@
 
   ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sock));
 
-  info = hfp_info_create(HFP_CODEC_ID_CVSD);
+  info = hfp_info_create();
   ASSERT_NE(info, (void*)NULL);
 
-  hfp_info_start(sock[0], 48, info);
+  hfp_info_start(sock[0], 48, HFP_CODEC_ID_CVSD, info);
   ASSERT_EQ(1, hfp_info_running(info));
   ASSERT_EQ(cb_data, (void*)info);
 
@@ -247,16 +252,16 @@
 
   ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sock));
 
-  info = hfp_info_create(HFP_CODEC_ID_CVSD);
+  info = hfp_info_create();
   ASSERT_NE(info, (void*)NULL);
 
   /* Start and send two chunk of fake data */
-  hfp_info_start(sock[1], 48, info);
+  hfp_info_start(sock[1], 48, HFP_CODEC_ID_CVSD, info);
   send(sock[0], sample, 48, 0);
   send(sock[0], sample, 48, 0);
 
   /* Trigger thread callback */
-  thread_cb((struct hfp_info*)cb_data);
+  thread_cb((struct hfp_info*)cb_data, POLLIN);
 
   dev.direction = CRAS_STREAM_INPUT;
   ASSERT_EQ(0, hfp_info_add_iodev(info, dev.direction, dev.format));
@@ -268,7 +273,7 @@
   /* Trigger thread callback after idev added. */
   ts.tv_sec = 0;
   ts.tv_nsec = 5000000;
-  thread_cb((struct hfp_info*)cb_data);
+  thread_cb((struct hfp_info*)cb_data, POLLIN);
 
   rc = hfp_buf_queued(info, dev.direction);
   ASSERT_EQ(48 / 2, rc);
@@ -292,15 +297,15 @@
 
   ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sock));
 
-  info = hfp_info_create(HFP_CODEC_ID_CVSD);
+  info = hfp_info_create();
   ASSERT_NE(info, (void*)NULL);
 
-  hfp_info_start(sock[1], 48, info);
+  hfp_info_start(sock[1], 48, HFP_CODEC_ID_CVSD, info);
   send(sock[0], sample, 48, 0);
   send(sock[0], sample, 48, 0);
 
   /* Trigger thread callback */
-  thread_cb((struct hfp_info*)cb_data);
+  thread_cb((struct hfp_info*)cb_data, POLLIN);
 
   /* Without odev in presence, zero packet should be sent. */
   rc = recv(sock[0], sample, 48, 0);
@@ -314,7 +319,7 @@
 
   /* Put some fake data and trigger thread callback again */
   buf_increment_write(info->playback_buf, 1008);
-  thread_cb((struct hfp_info*)cb_data);
+  thread_cb((struct hfp_info*)cb_data, POLLIN);
 
   /* Assert some samples written */
   rc = recv(sock[0], sample, 48, 0);
@@ -326,30 +331,44 @@
 }
 
 void send_mSBC_packet(int fd, unsigned seq, int broken_pkt) {
-  /* These three bytes are h2 header, frame count and mSBC sync word.
-   * The second octet of H2 header is composed by 4 bits fixed 0x8 and 4 bits
-   * sequence number 0000, 0011, 1100, 1111.
+  /* The first three bytes of hci_sco_buf are h2 header, frame count and mSBC
+   * sync word. The second octet of H2 header is composed by 4 bits fixed 0x8
+   * and 4 bits sequence number 0000, 0011, 1100, 1111.
    */
-  uint8_t headers[4][3] = {{0x01, 0x08, 0xAD},
-                           {0x01, 0x38, 0xAD},
-                           {0x01, 0xc8, 0xAD},
-                           {0x01, 0xf8, 0xAD}};
-  /* These three bytes are HCI SCO Data packet header, we only care the
-   * Packet_Status_Flag bits, which are the bit 4 to 5 in the second octet.
-   */
-  uint8_t sco_header[] = {0x01, 0x01, 0x3c};
-  uint8_t zero_frame[] = {
-      0xad, 0x00, 0x00, 0xc5, 0x00, 0x00, 0x00, 0x00, 0x77, 0x6d, 0xb6, 0xdd,
-      0xdb, 0x6d, 0xb7, 0x76, 0xdb, 0x6d, 0xdd, 0xb6, 0xdb, 0x77, 0x6d, 0xb6,
-      0xdd, 0xdb, 0x6d, 0xb7, 0x76, 0xdb, 0x6d, 0xdd, 0xb6, 0xdb, 0x77, 0x6d,
-      0xb6, 0xdd, 0xdb, 0x6d, 0xb7, 0x76, 0xdb, 0x6d, 0xdd, 0xb6, 0xdb, 0x77,
-      0x6d, 0xb6, 0xdd, 0xdb, 0x6d, 0xb7, 0x76, 0xdb, 0x6c};
-  if (broken_pkt)
-    sco_header[1] = 0x11;
+  uint8_t headers[4] = {0x08, 0x38, 0xc8, 0xf8};
+  uint8_t hci_sco_buf[] = {
+      0x01, 0x00, 0xAD, 0xad, 0x00, 0x00, 0xc5, 0x00, 0x00, 0x00, 0x00, 0x77,
+      0x6d, 0xb6, 0xdd, 0xdb, 0x6d, 0xb7, 0x76, 0xdb, 0x6d, 0xdd, 0xb6, 0xdb,
+      0x77, 0x6d, 0xb6, 0xdd, 0xdb, 0x6d, 0xb7, 0x76, 0xdb, 0x6d, 0xdd, 0xb6,
+      0xdb, 0x77, 0x6d, 0xb6, 0xdd, 0xdb, 0x6d, 0xb7, 0x76, 0xdb, 0x6d, 0xdd,
+      0xb6, 0xdb, 0x77, 0x6d, 0xb6, 0xdd, 0xdb, 0x6d, 0xb7, 0x76, 0xdb, 0x6c};
+  struct msghdr msg = {0};
+  struct iovec iov;
+  struct cmsghdr* cmsg;
+  const unsigned int control_size = CMSG_SPACE(sizeof(int));
+  char control[control_size] = {0};
+  uint8_t pkt_status = 0;
 
-  send(fd, sco_header, 3, 0);
-  send(fd, headers[seq % 4], 3, 0);
-  send(fd, zero_frame, 57, 0);
+  hci_sco_buf[1] = headers[seq % 4];
+
+  /* Assume typical 60 bytes case. */
+  msg.msg_iov = &iov;
+  msg.msg_iovlen = 1;
+  iov.iov_base = hci_sco_buf;
+  iov.iov_len = 60;
+  msg.msg_control = control;
+  msg.msg_controllen = control_size;
+
+  if (broken_pkt)
+    pkt_status = 0x11;
+
+  cmsg = CMSG_FIRSTHDR(&msg);
+  cmsg->cmsg_level = SOL_BLUETOOTH;
+  cmsg->cmsg_type = BT_SCM_PKT_STATUS;
+  cmsg->cmsg_len = CMSG_LEN(sizeof(pkt_status));
+  memcpy(CMSG_DATA(cmsg), &pkt_status, sizeof(pkt_status));
+
+  sendmsg(fd, &msg, 0);
 }
 
 TEST(HfpInfo, StartHfpInfoAndReadMsbc) {
@@ -363,17 +382,19 @@
 
   set_sbc_codec_decoded_out(MSBC_CODE_SIZE);
 
-  info = hfp_info_create(HFP_CODEC_ID_MSBC);
+  info = hfp_info_create();
   ASSERT_NE(info, (void*)NULL);
-  ASSERT_EQ(2, get_msbc_codec_create_called());
-  ASSERT_EQ(1, cras_msbc_plc_create_called);
+  ASSERT_EQ(0, get_msbc_codec_create_called());
+  ASSERT_EQ(0, cras_msbc_plc_create_called);
 
   /* Start and send an mSBC packets with all zero samples */
-  hfp_info_start(sock[1], 63, info);
+  hfp_info_start(sock[1], 63, HFP_CODEC_ID_MSBC, info);
+  ASSERT_EQ(2, get_msbc_codec_create_called());
+  ASSERT_EQ(1, cras_msbc_plc_create_called);
   send_mSBC_packet(sock[0], pkt_count++, 0);
 
   /* Trigger thread callback */
-  thread_cb((struct hfp_info*)cb_data);
+  thread_cb((struct hfp_info*)cb_data, POLLIN);
 
   /* Expect one empty mSBC packet is send, because no odev in presence. */
   rc = recv(sock[0], sample, MSBC_PKT_SIZE, 0);
@@ -388,7 +409,7 @@
   send_mSBC_packet(sock[0], pkt_count, 0);
 
   /* Trigger thread callback after idev added. */
-  thread_cb((struct hfp_info*)cb_data);
+  thread_cb((struct hfp_info*)cb_data, POLLIN);
   rc = recv(sock[0], sample, MSBC_PKT_SIZE, 0);
   ASSERT_EQ(MSBC_PKT_SIZE, rc);
 
@@ -401,7 +422,7 @@
    */
   pkt_count++;
   send_mSBC_packet(sock[0], pkt_count, 0);
-  thread_cb((struct hfp_info*)cb_data);
+  thread_cb((struct hfp_info*)cb_data, POLLIN);
   rc = recv(sock[0], sample, MSBC_PKT_SIZE, 0);
   ASSERT_EQ(MSBC_PKT_SIZE, rc);
 
@@ -418,7 +439,7 @@
 
   set_sbc_codec_decoded_fail(1);
 
-  thread_cb((struct hfp_info*)cb_data);
+  thread_cb((struct hfp_info*)cb_data, POLLIN);
   rc = recv(sock[0], sample, MSBC_PKT_SIZE, 0);
   ASSERT_EQ(MSBC_PKT_SIZE, rc);
 
@@ -434,7 +455,7 @@
 
   set_sbc_codec_decoded_fail(1);
 
-  thread_cb((struct hfp_info*)cb_data);
+  thread_cb((struct hfp_info*)cb_data, POLLIN);
   rc = recv(sock[0], sample, MSBC_PKT_SIZE, 0);
   ASSERT_EQ(MSBC_PKT_SIZE, rc);
 
@@ -459,14 +480,14 @@
   set_sbc_codec_encoded_out(57);
   ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sock));
 
-  info = hfp_info_create(HFP_CODEC_ID_MSBC);
+  info = hfp_info_create();
   ASSERT_NE(info, (void*)NULL);
 
-  hfp_info_start(sock[1], 63, info);
+  hfp_info_start(sock[1], 63, HFP_CODEC_ID_MSBC, info);
   send(sock[0], sample, 63, 0);
 
   /* Trigger thread callback */
-  thread_cb((struct hfp_info*)cb_data);
+  thread_cb((struct hfp_info*)cb_data, POLLIN);
 
   dev.direction = CRAS_STREAM_OUTPUT;
   ASSERT_EQ(0, hfp_info_add_iodev(info, dev.direction, dev.format));
@@ -477,7 +498,7 @@
   /* Put some fake data and trigger thread callback again */
   send(sock[0], sample, 63, 0);
   buf_increment_write(info->playback_buf, 240);
-  thread_cb((struct hfp_info*)cb_data);
+  thread_cb((struct hfp_info*)cb_data, POLLIN);
 
   /* Assert some samples written */
   rc = recv(sock[0], sample, 60, 0);
@@ -488,6 +509,26 @@
   hfp_info_destroy(info);
 }
 
+TEST(HfpInfo, WBSLoggerPacketStatusDumpBinary) {
+  struct packet_status_logger logger;
+  char log_regex[64];
+  int num_wraps[5] = {0, 0, 0, 1, 1};
+  int wp[5] = {40, 150, 162, 100, 32};
+
+  /* Expect the log line wraps at correct length to avoid feedback redact. */
+  snprintf(log_regex, 64, "([01D]{%d}\n)*", PACKET_STATUS_LOG_LINE_WRAP);
+
+  packet_status_logger_init(&logger);
+  logger.size = PACKET_STATUS_LEN_BYTES * 8;
+  for (int i = 0; i < 5; i++) {
+    CaptureStdout();
+    logger.num_wraps = num_wraps[i];
+    logger.wp = wp[i];
+    packet_status_logger_dump_binary(&logger);
+    EXPECT_THAT(GetCapturedStdout(), MatchesRegex(log_regex));
+  }
+}
+
 }  // namespace
 
 extern "C" {
@@ -496,7 +537,10 @@
   return NULL;
 }
 
-void audio_thread_add_callback(int fd, thread_callback cb, void* data) {
+void audio_thread_add_events_callback(int fd,
+                                      thread_callback cb,
+                                      void* data,
+                                      int events) {
   thread_cb = cb;
   cb_data = data;
   return;
@@ -530,6 +574,10 @@
   cras_msbc_plc_handle_good_frames_called++;
   return MSBC_CODE_SIZE;
 }
+void packet_status_logger_init(struct packet_status_logger* logger) {}
+
+void packet_status_logger_update(struct packet_status_logger* logger,
+                                 bool val) {}
 }
 
 int main(int argc, char** argv) {
diff --git a/cras/src/tests/hfp_iodev_unittest.cc b/cras/src/tests/hfp_iodev_unittest.cc
index 9bfb2dd..1275ef2 100644
--- a/cras/src/tests/hfp_iodev_unittest.cc
+++ b/cras/src/tests/hfp_iodev_unittest.cc
@@ -43,7 +43,7 @@
 static size_t hfp_force_output_level_called;
 static size_t hfp_force_output_level_target;
 static size_t fake_buffer_size = 500;
-static cras_audio_area* dummy_audio_area;
+static cras_audio_area* mock_audio_area;
 
 void ResetStubData() {
   cras_bt_device_append_iodev_called = 0;
@@ -73,9 +73,9 @@
 
   fake_info = reinterpret_cast<struct hfp_info*>(0x123);
 
-  if (!dummy_audio_area) {
-    dummy_audio_area = (cras_audio_area*)calloc(
-        1, sizeof(*dummy_audio_area) + sizeof(cras_channel_area) * 2);
+  if (!mock_audio_area) {
+    mock_audio_area = (cras_audio_area*)calloc(
+        1, sizeof(*mock_audio_area) + sizeof(cras_channel_area) * 2);
   }
 }
 
@@ -86,8 +86,8 @@
   virtual void SetUp() { ResetStubData(); }
 
   virtual void TearDown() {
-    free(dummy_audio_area);
-    dummy_audio_area = NULL;
+    free(mock_audio_area);
+    mock_audio_area = NULL;
   }
 };
 
@@ -243,6 +243,9 @@
   iodev->active_node = node;
 }
 
+// From ewma_power
+void ewma_power_disable(struct ewma_power* ewma) {}
+
 //  From system_state.
 size_t cras_system_get_volume() {
   return 0;
@@ -282,6 +285,10 @@
   return "/fake/object/path";
 }
 
+int cras_bt_device_get_stable_id(const struct cras_bt_device* device) {
+  return 123;
+}
+
 // From cras_hfp_info
 int hfp_info_add_iodev(struct hfp_info* info,
                        enum CRAS_STREAM_DIRECTION direction,
@@ -306,7 +313,7 @@
   return hfp_info_running_return_val;
 }
 
-int hfp_info_start(int fd, unsigned int mtu, struct hfp_info* info) {
+int hfp_info_start(int fd, unsigned int mtu, int codec, struct hfp_info* info) {
   hfp_info_start_called++;
   return 0;
 }
@@ -351,7 +358,7 @@
 }
 
 void cras_iodev_init_audio_area(struct cras_iodev* iodev, int num_channels) {
-  iodev->area = dummy_audio_area;
+  iodev->area = mock_audio_area;
 }
 
 void cras_iodev_free_audio_area(struct cras_iodev* iodev) {}
@@ -360,10 +367,14 @@
   cras_iodev_free_resources_called++;
 }
 
+int cras_iodev_fill_odev_zeros(struct cras_iodev* odev, unsigned int frames) {
+  return 0;
+}
+
 void cras_audio_area_config_buf_pointers(struct cras_audio_area* area,
                                          const struct cras_audio_format* fmt,
                                          uint8_t* base_buffer) {
-  dummy_audio_area->channels[0].buf = base_buffer;
+  mock_audio_area->channels[0].buf = base_buffer;
 }
 
 int hfp_set_call_status(struct hfp_slc_handle* handle, int call) {
@@ -378,6 +389,18 @@
   return HFP_CODEC_ID_CVSD;
 }
 
+bool hfp_slc_get_wideband_speech_supported(struct hfp_slc_handle* handle) {
+  return false;
+}
+
+int hfp_slc_codec_connection_setup(struct hfp_slc_handle* handle) {
+  return 0;
+}
+
+int hfp_slc_is_hsp(struct hfp_slc_handle* handle) {
+  return 0;
+}
+
 }  // extern "C"
 
 int main(int argc, char** argv) {
diff --git a/cras/src/tests/hfp_slc_unittest.cc b/cras/src/tests/hfp_slc_unittest.cc
index f98a1f3..966278f 100644
--- a/cras/src/tests/hfp_slc_unittest.cc
+++ b/cras/src/tests/hfp_slc_unittest.cc
@@ -17,6 +17,7 @@
 static struct hfp_slc_handle* handle;
 static struct cras_telephony_handle fake_telephony;
 static int cras_bt_device_update_hardware_volume_called;
+static int cras_observer_notify_bt_batter_changed_called;
 static int slc_initialized_cb_called;
 static int slc_disconnected_cb_called;
 static int cras_system_add_select_fd_called;
@@ -35,6 +36,7 @@
   slc_initialized_cb_called = 0;
   cras_system_add_select_fd_called = 0;
   cras_bt_device_update_hardware_volume_called = 0;
+  cras_observer_notify_bt_batter_changed_called = 0;
   slc_cb = NULL;
   slc_cb_data = NULL;
 }
@@ -59,6 +61,8 @@
   char* chp;
   ResetStubData();
 
+  btlog = cras_bt_event_log_init();
+
   ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sock));
   handle = hfp_slc_create(sock[0], 0, AG_ENHANCED_CALL_STATUS, device,
                           slc_initialized_cb, slc_disconnected_cb);
@@ -108,6 +112,7 @@
   ASSERT_EQ(1, cras_bt_device_update_hardware_volume_called);
 
   hfp_slc_destroy(handle);
+  cras_bt_event_log_deinit(btlog);
 }
 
 TEST(HfpSlc, DisconnectSlc) {
@@ -129,6 +134,108 @@
   hfp_slc_destroy(handle);
 }
 
+TEST(HfpSlc, InitializeSlcSupportsHfIndicator) {
+  int err;
+  int sock[2];
+  char buf[256];
+  char* chp;
+  ResetStubData();
+
+  btlog = cras_bt_event_log_init();
+
+  ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sock));
+  handle = hfp_slc_create(sock[0], 0, AG_ENHANCED_CALL_STATUS, device,
+                          slc_initialized_cb, slc_disconnected_cb);
+
+  /* Fake that HF supports HF indicator. */
+  err = write(sock[1], "AT+BRSF=256\r", 12);
+  ASSERT_EQ(err, 12);
+  slc_cb(slc_cb_data);
+  err = read(sock[1], buf, 256);
+
+  err = write(sock[1], "AT+CIND=?\r", 10);
+  ASSERT_EQ(10, err);
+  slc_cb(slc_cb_data);
+  err = read(sock[1], buf, 256);
+
+  /* Assert "\r\n+CIND: ... \r\n" response is received */
+  chp = strstr(buf, "\r\n");
+  ASSERT_NE((void*)NULL, (void*)chp);
+  ASSERT_EQ(0, strncmp("\r\n+CIND:", chp, 8));
+  chp += 2;
+  chp = strstr(chp, "\r\n");
+  ASSERT_NE((void*)NULL, (void*)chp);
+
+  /* Assert "\r\nOK\r\n" response is received */
+  chp += 2;
+  chp = strstr(chp, "\r\n");
+  ASSERT_NE((void*)NULL, (void*)chp);
+  ASSERT_EQ(0, strncmp("\r\nOK", chp, 4));
+
+  err = write(sock[1], "AT+CMER=3,0,0,1\r", 16);
+  ASSERT_EQ(16, err);
+  slc_cb(slc_cb_data);
+
+  ASSERT_NE((void*)NULL, cras_tm_timer_cb);
+  ASSERT_EQ(0, slc_initialized_cb_called);
+
+  /* Assert "\r\nOK\r\n" response is received */
+  err = read(sock[1], buf, 256);
+
+  chp = strstr(buf, "\r\nOK\r\n");
+  ASSERT_NE((void*)NULL, (void*)chp);
+
+  err = write(sock[1], "AT+BIND=2\r", 10);
+  ASSERT_EQ(err, 10);
+  slc_cb(slc_cb_data);
+
+  /* Assert "\r\nOK\r\n" response is received */
+  err = read(sock[1], buf, 256);
+
+  chp = strstr(buf, "\r\nOK\r\n");
+  ASSERT_NE((void*)NULL, (void*)chp);
+
+  err = write(sock[1], "AT+BIND=?\r", 10);
+  ASSERT_EQ(err, 10);
+  slc_cb(slc_cb_data);
+
+  /* Assert "\r\n+BIND: (2)\r\n" response is received */
+  err = read(sock[1], buf, 256);
+
+  chp = strstr(buf, "\r\n+BIND: (1,2)\r\n");
+  ASSERT_NE((void*)NULL, (void*)chp);
+  chp = strstr(buf, "\r\nOK\r\n");
+  ASSERT_NE((void*)NULL, (void*)chp);
+
+  err = write(sock[1], "AT+BIND?\r", 9);
+  ASSERT_EQ(err, 9);
+  slc_cb(slc_cb_data);
+
+  /* Assert "\r\n+BIND: 2,1\r\n" response is received */
+  err = read(sock[1], buf, 256);
+
+  chp = strstr(buf, "\r\n+BIND: 2,1\r\n");
+  ASSERT_NE((void*)NULL, (void*)chp);
+  chp = strstr(buf, "\r\nOK\r\n");
+  ASSERT_NE((void*)NULL, (void*)chp);
+
+  ASSERT_EQ(1, slc_initialized_cb_called);
+
+  err = write(sock[1], "AT+VGS=13\r", 10);
+  ASSERT_EQ(err, 10);
+  slc_cb(slc_cb_data);
+
+  err = read(sock[1], buf, 256);
+
+  chp = strstr(buf, "\r\nOK\r\n");
+  ASSERT_NE((void*)NULL, (void*)chp);
+
+  ASSERT_EQ(1, cras_bt_device_update_hardware_volume_called);
+
+  hfp_slc_destroy(handle);
+  cras_bt_event_log_deinit(btlog);
+}
+
 TEST(HfpSlc, CodecNegotiation) {
   int codec;
   int err;
@@ -167,6 +274,11 @@
   codec = hfp_slc_get_selected_codec(handle);
   EXPECT_EQ(HFP_CODEC_ID_MSBC, codec);
 
+  /* Fake HF selects mSBC codec. */
+  err = write(sock[1], "AT+BCS=2\r", 9);
+  ASSERT_EQ(err, 9);
+
+  err = hfp_slc_codec_connection_setup(handle);
   /* Assert CRAS initiates codec selection to mSBC. */
   memset(buf, 0, 256);
   err = read(sock[1], buf, 256);
@@ -177,27 +289,11 @@
   ASSERT_EQ(err, 9);
   slc_cb(slc_cb_data);
 
-  /* Assert CRAS initiates codec selection to mSBC. */
-  memset(buf, 0, 256);
-  err = read(sock[1], buf, 256);
-  pos = strstr(buf, "\r\n+BCS:2\r\n");
-  ASSERT_NE((void*)NULL, pos);
-
-  /* Fake that receiving codec selection from HF. */
-  err = write(sock[1], "AT+BCS=2\r", 9);
-  ASSERT_EQ(err, 9);
-  slc_cb(slc_cb_data);
-
-  memset(buf, 0, 256);
-  err = read(sock[1], buf, 256);
-  pos = strstr(buf, "\r\n+BCS:2\r\n");
-  ASSERT_EQ((void*)NULL, pos);
-
   hfp_slc_destroy(handle);
   cras_bt_event_log_deinit(btlog);
 }
 
-TEST(HfpSlc, CodecNegotiationTimeout) {
+TEST(HfpSlc, CodecNegotiationCapabilityChanged) {
   int codec;
   int err;
   int sock[2];
@@ -231,30 +327,41 @@
   ASSERT_EQ(err, 16);
   slc_cb(slc_cb_data);
 
-  ASSERT_NE((void*)NULL, cras_tm_timer_cb);
-
   /* Assert that AG side prefers mSBC codec. */
   codec = hfp_slc_get_selected_codec(handle);
   EXPECT_EQ(HFP_CODEC_ID_MSBC, codec);
 
+  /* Fake HF selects mSBC codec. */
+  err = write(sock[1], "AT+BCS=2\r", 9);
+  ASSERT_EQ(err, 9);
+
+  err = hfp_slc_codec_connection_setup(handle);
   /* Assert CRAS initiates codec selection to mSBC. */
   memset(buf, 0, 256);
   err = read(sock[1], buf, 256);
   pos = strstr(buf, "\r\n+BCS:2\r\n");
   ASSERT_NE((void*)NULL, pos);
 
-  /* Assume codec negotiation failed. so timeout is reached. */
-  cras_tm_timer_cb(NULL, cras_tm_timer_cb_data);
+  /* Fake that HF changes supported codecs. */
+  err = write(sock[1], "AT+BAC=1\r", 9);
+  ASSERT_EQ(err, 9);
+  slc_cb(slc_cb_data);
+  err = read(sock[1], buf, 256);
 
-  codec = hfp_slc_get_selected_codec(handle);
-  EXPECT_EQ(HFP_CODEC_ID_CVSD, codec);
+  /* Fake HF selects CVSD codec. */
+  err = write(sock[1], "AT+BCS=1\r", 9);
+  ASSERT_EQ(err, 9);
 
-  /* Expects CRAS fallback and selects to CVSD codec. */
+  err = hfp_slc_codec_connection_setup(handle);
+  /* Assert CRAS initiates codec selection to CVSD. */
   memset(buf, 0, 256);
   err = read(sock[1], buf, 256);
   pos = strstr(buf, "\r\n+BCS:1\r\n");
   ASSERT_NE((void*)NULL, pos);
 
+  codec = hfp_slc_get_selected_codec(handle);
+  EXPECT_EQ(HFP_CODEC_ID_CVSD, codec);
+
   hfp_slc_destroy(handle);
   cras_bt_event_log_deinit(btlog);
 }
@@ -286,11 +393,20 @@
 
 void cras_system_rm_select_fd(int fd) {}
 
+const char* cras_bt_device_address(struct cras_bt_device* device) {
+  return "";
+}
+
 void cras_bt_device_update_hardware_volume(struct cras_bt_device* device,
                                            int volume) {
   cras_bt_device_update_hardware_volume_called++;
 }
 
+void cras_observer_notify_bt_battery_changed(const char* address,
+                                             uint32_t level) {
+  cras_observer_notify_bt_batter_changed_called++;
+}
+
 /* To return fake errno */
 int* __errno_location() {
   return &fake_errno;
@@ -310,6 +426,13 @@
   return reinterpret_cast<struct cras_timer*>(0x404);
 }
 
+int cras_poll(struct pollfd* fds,
+              nfds_t nfds,
+              struct timespec* timeout,
+              const sigset_t* sigmask) {
+  return 1;
+}
+
 void cras_tm_cancel_timer(struct cras_tm* tm, struct cras_timer* t) {}
 }
 
diff --git a/cras/src/tests/input_data_unittest.cc b/cras/src/tests/input_data_unittest.cc
index f1c3fd7..3c6ae9f 100644
--- a/cras/src/tests/input_data_unittest.cc
+++ b/cras/src/tests/input_data_unittest.cc
@@ -14,12 +14,16 @@
 
 namespace {
 
+#define FAKE_CRAS_APM_PTR reinterpret_cast<struct cras_apm*>(0x99)
+
 #ifdef HAVE_WEBRTC_APM
 static struct cras_audio_area apm_area;
 static unsigned int cras_apm_list_process_offset_val;
 static unsigned int cras_apm_list_process_called;
-static struct cras_apm* cras_apm_list_get_ret = NULL;
+static struct cras_apm* cras_apm_list_get_active_ret = NULL;
+static bool cras_apm_list_get_use_tuned_settings_val;
 #endif  // HAVE_WEBRTC_APM
+static float cras_rstream_get_volume_scaler_val;
 
 TEST(InputData, GetForInputStream) {
   void* dev_ptr = reinterpret_cast<void*>(0x123);
@@ -55,7 +59,7 @@
   EXPECT_EQ(600, offset);
 #ifdef HAVE_WEBRTC_APM
   EXPECT_EQ(0, cras_apm_list_process_called);
-  cras_apm_list_get_ret = reinterpret_cast<struct cras_apm*>(0x99);
+  cras_apm_list_get_active_ret = FAKE_CRAS_APM_PTR;
 #endif  // HAVE_WEBRTC_APM
 
   input_data_get_for_stream(data, &stream, offsets, &area, &offset);
@@ -75,10 +79,44 @@
   buffer_share_destroy(offsets);
 }
 
+TEST(InputData, GetSWCaptureGain) {
+  void* dev_ptr = reinterpret_cast<void*>(0x123);
+  struct input_data* data = NULL;
+  struct cras_rstream stream;
+  float gain;
+
+  cras_rstream_get_volume_scaler_val = 0.8f;
+  stream.stream_id = 123;
+
+#ifdef HAVE_WEBRTC_APM
+  data = input_data_create(dev_ptr);
+
+  cras_apm_list_get_active_ret = FAKE_CRAS_APM_PTR;
+  cras_apm_list_get_use_tuned_settings_val = 1;
+  gain = input_data_get_software_gain_scaler(data, 0.7f, &stream);
+  EXPECT_FLOAT_EQ(1.0f, gain);
+
+  cras_apm_list_get_active_ret = NULL;
+  gain = input_data_get_software_gain_scaler(data, 0.7f, &stream);
+  EXPECT_FLOAT_EQ(0.56f, gain);
+
+  cras_apm_list_get_active_ret = FAKE_CRAS_APM_PTR;
+  cras_apm_list_get_use_tuned_settings_val = 0;
+  gain = input_data_get_software_gain_scaler(data, 0.6f, &stream);
+  EXPECT_FLOAT_EQ(0.48f, gain);
+  input_data_destroy(&data);
+#endif  // HAVE_WEBRTC_APM
+
+  data = input_data_create(dev_ptr);
+  gain = input_data_get_software_gain_scaler(data, 0.6f, &stream);
+  EXPECT_FLOAT_EQ(0.48f, gain);
+  input_data_destroy(&data);
+}
+
 extern "C" {
 #ifdef HAVE_WEBRTC_APM
-struct cras_apm* cras_apm_list_get(struct cras_apm_list* list, void* dev_ptr) {
-  return cras_apm_list_get_ret;
+struct cras_apm* cras_apm_list_get_active_apm(void* stream_ptr, void* dev_ptr) {
+  return cras_apm_list_get_active_ret;
 }
 int cras_apm_list_process(struct cras_apm* apm,
                           struct float_buffer* input,
@@ -91,10 +129,16 @@
 struct cras_audio_area* cras_apm_list_get_processed(struct cras_apm* apm) {
   return &apm_area;
 }
-void cras_apm_list_remove(struct cras_apm_list* list, void* dev_ptr) {}
+void cras_apm_list_remove_apm(struct cras_apm_list* list, void* dev_ptr) {}
 void cras_apm_list_put_processed(struct cras_apm* apm, unsigned int frames) {}
+bool cras_apm_list_get_use_tuned_settings(struct cras_apm* apm) {
+  return cras_apm_list_get_use_tuned_settings_val;
+}
 #endif  // HAVE_WEBRTC_APM
 
+float cras_rstream_get_volume_scaler(struct cras_rstream* rstream) {
+  return cras_rstream_get_volume_scaler_val;
+}
 }  // extern "C"
 }  // namespace
 
diff --git a/cras/src/tests/iodev_list_unittest.cc b/cras/src/tests/iodev_list_unittest.cc
index 798f295..8c71214 100644
--- a/cras/src/tests/iodev_list_unittest.cc
+++ b/cras/src/tests/iodev_list_unittest.cc
@@ -12,6 +12,7 @@
 #include "audio_thread.h"
 #include "cras_iodev.h"
 #include "cras_iodev_list.h"
+#include "cras_main_thread_log.h"
 #include "cras_observer_ops.h"
 #include "cras_ramp.h"
 #include "cras_rstream.h"
@@ -43,8 +44,8 @@
 static struct cras_iodev loopback_input;
 static int cras_iodev_close_called;
 static struct cras_iodev* cras_iodev_close_dev;
-static struct cras_iodev dummy_hotword_iodev;
-static struct cras_iodev dummy_empty_iodev[2];
+static struct cras_iodev mock_hotword_iodev;
+static struct cras_iodev mock_empty_iodev[2];
 static stream_callback* stream_add_cb;
 static stream_callback* stream_rm_cb;
 static struct cras_rstream* stream_list_get_ret;
@@ -83,6 +84,7 @@
 static size_t cras_observer_notify_input_node_gain_called;
 static int cras_iodev_open_called;
 static int cras_iodev_open_ret[8];
+static struct cras_audio_format cras_iodev_open_fmt;
 static int set_mute_called;
 static std::vector<struct cras_iodev*> set_mute_dev_vector;
 static std::vector<unsigned int> audio_thread_dev_start_ramp_dev_vector;
@@ -93,6 +95,7 @@
 static int audio_thread_disconnect_stream_called;
 static struct cras_iodev fake_sco_in_dev, fake_sco_out_dev;
 static struct cras_ionode fake_sco_in_node, fake_sco_out_node;
+static int server_state_hotword_pause_at_suspend;
 
 int dev_idx_in_vector(std::vector<unsigned int> v, unsigned int idx) {
   return std::find(v.begin(), v.end(), idx) != v.end();
@@ -129,6 +132,10 @@
     channel_counts_[0] = 2;
     channel_counts_[1] = 0;
 
+    fmt_.format = SND_PCM_FORMAT_S16_LE;
+    fmt_.frame_rate = 48000;
+    fmt_.num_channels = 2;
+
     memset(&d1_, 0, sizeof(d1_));
     memset(&d2_, 0, sizeof(d2_));
     memset(&d3_, 0, sizeof(d3_));
@@ -227,14 +234,17 @@
     DL_APPEND(fake_sco_out_dev.nodes, &fake_sco_out_node);
     fake_sco_in_node.is_sco_pcm = 0;
     fake_sco_out_node.is_sco_pcm = 0;
-    dummy_empty_iodev[0].state = CRAS_IODEV_STATE_CLOSE;
-    dummy_empty_iodev[0].update_active_node = update_active_node;
-    dummy_empty_iodev[1].state = CRAS_IODEV_STATE_CLOSE;
-    dummy_empty_iodev[1].update_active_node = update_active_node;
-    dummy_hotword_iodev.update_active_node = update_active_node;
+    mock_empty_iodev[0].state = CRAS_IODEV_STATE_CLOSE;
+    mock_empty_iodev[0].update_active_node = update_active_node;
+    mock_empty_iodev[1].state = CRAS_IODEV_STATE_CLOSE;
+    mock_empty_iodev[1].update_active_node = update_active_node;
+    mock_hotword_iodev.update_active_node = update_active_node;
+    server_state_hotword_pause_at_suspend = 0;
   }
 
-  virtual void TearDown() { cras_iodev_list_reset(); }
+  virtual void TearDown() {
+    cras_iodev_list_reset();
+  }
 
   static void set_volume_1(struct cras_iodev* iodev) { set_volume_1_called_++; }
 
@@ -266,6 +276,7 @@
   struct cras_iodev d1_;
   struct cras_iodev d2_;
   struct cras_iodev d3_;
+  struct cras_audio_format fmt_;
   size_t sample_rates_[3];
   size_t channel_counts_[2];
   static int set_volume_1_called_;
@@ -303,6 +314,8 @@
   rc = cras_iodev_list_add_output(&d1_);
   ASSERT_EQ(0, rc);
 
+  d1_.format = &fmt_;
+
   audio_thread_add_open_dev_called = 0;
   cras_iodev_list_add_active_node(CRAS_STREAM_OUTPUT,
                                   cras_make_node_id(d1_.info.idx, 1));
@@ -348,6 +361,138 @@
   EXPECT_EQ(3, cras_observer_notify_active_node_called);
 }
 
+/* Check that the suspend/resume call of active iodev will be triggered and
+ * fallback device will be transciently enabled while adding a new stream whose
+ * channel count is higher than the active iodev. */
+TEST_F(IoDevTestSuite, ReopenDevForHigherChannels) {
+  struct cras_rstream rstream, rstream2;
+  struct cras_rstream* stream_list = NULL;
+  int rc;
+
+  memset(&rstream, 0, sizeof(rstream));
+  memset(&rstream2, 0, sizeof(rstream2));
+  rstream.format = fmt_;
+  rstream2.format = fmt_;
+  rstream2.format.num_channels = 6;
+
+  cras_iodev_list_init();
+
+  d1_.direction = CRAS_STREAM_OUTPUT;
+  rc = cras_iodev_list_add_output(&d1_);
+  ASSERT_EQ(0, rc);
+
+  d1_.format = &fmt_;
+  d1_.info.max_supported_channels = 2;
+
+  audio_thread_add_open_dev_called = 0;
+  cras_iodev_list_add_active_node(CRAS_STREAM_OUTPUT,
+                                  cras_make_node_id(d1_.info.idx, 1));
+  DL_APPEND(stream_list, &rstream);
+  stream_list_get_ret = stream_list;
+  stream_add_cb(&rstream);
+  EXPECT_EQ(1, audio_thread_add_stream_called);
+  EXPECT_EQ(1, audio_thread_add_open_dev_called);
+  EXPECT_EQ(1, cras_iodev_open_called);
+  EXPECT_EQ(2, cras_iodev_open_fmt.num_channels);
+
+  audio_thread_add_stream_called = 0;
+  audio_thread_add_open_dev_called = 0;
+  cras_iodev_open_called = 0;
+
+  /* stream_list should be descending ordered by channel count. */
+  DL_PREPEND(stream_list, &rstream2);
+  stream_list_get_ret = stream_list;
+  stream_add_cb(&rstream2);
+  /* The channel count(=6) of rstream2 exceeds d1's max_supported_channels(=2),
+   * rstream2 will be added directly to d1, which will not be re-opened. */
+  EXPECT_EQ(1, audio_thread_add_stream_called);
+  EXPECT_EQ(0, audio_thread_add_open_dev_called);
+  EXPECT_EQ(0, cras_iodev_open_called);
+
+  d1_.info.max_supported_channels = 6;
+  stream_rm_cb(&rstream2);
+
+  audio_thread_add_stream_called = 0;
+  audio_thread_add_open_dev_called = 0;
+  cras_iodev_open_called = 0;
+
+  stream_add_cb(&rstream2);
+  /* Added both rstreams to fallback device, then re-opened d1. */
+  EXPECT_EQ(4, audio_thread_add_stream_called);
+  EXPECT_EQ(2, audio_thread_add_open_dev_called);
+  EXPECT_EQ(2, cras_iodev_open_called);
+  EXPECT_EQ(6, cras_iodev_open_fmt.num_channels);
+
+  cras_iodev_list_deinit();
+}
+
+/* Check that after resume, all output devices enter ramp mute state if there is
+ * any output stream. */
+TEST_F(IoDevTestSuite, RampMuteAfterResume) {
+  struct cras_rstream rstream, rstream2;
+  struct cras_rstream* stream_list = NULL;
+  int rc;
+
+  memset(&rstream, 0, sizeof(rstream));
+
+  cras_iodev_list_init();
+
+  d1_.direction = CRAS_STREAM_OUTPUT;
+  d1_.initial_ramp_request = CRAS_IODEV_RAMP_REQUEST_UP_START_PLAYBACK;
+  rc = cras_iodev_list_add_output(&d1_);
+  ASSERT_EQ(0, rc);
+
+  d2_.direction = CRAS_STREAM_INPUT;
+  d2_.initial_ramp_request = CRAS_IODEV_RAMP_REQUEST_UP_START_PLAYBACK;
+  rc = cras_iodev_list_add_input(&d2_);
+  ASSERT_EQ(0, rc);
+
+  d1_.format = &fmt_;
+  d2_.format = &fmt_;
+
+  audio_thread_add_open_dev_called = 0;
+  cras_iodev_list_add_active_node(CRAS_STREAM_OUTPUT,
+                                  cras_make_node_id(d1_.info.idx, 1));
+
+  rstream.direction = CRAS_STREAM_OUTPUT;
+  DL_APPEND(stream_list, &rstream);
+  stream_add_cb(&rstream);
+  EXPECT_EQ(1, audio_thread_add_stream_called);
+  EXPECT_EQ(1, audio_thread_add_open_dev_called);
+
+  rstream2.direction = CRAS_STREAM_INPUT;
+  DL_APPEND(stream_list, &rstream2);
+  stream_add_cb(&rstream2);
+
+  /* Suspend and resume */
+  observer_ops->suspend_changed(NULL, 1);
+  stream_list_get_ret = stream_list;
+  observer_ops->suspend_changed(NULL, 0);
+
+  /* Test only output device that has stream will be muted after resume */
+  EXPECT_EQ(d1_.initial_ramp_request, CRAS_IODEV_RAMP_REQUEST_RESUME_MUTE);
+  EXPECT_EQ(CRAS_IODEV_RAMP_REQUEST_UP_START_PLAYBACK,
+            d2_.initial_ramp_request);
+
+  /* Reset d1 ramp_mute and remove output stream to test again */
+  d1_.initial_ramp_request = CRAS_IODEV_RAMP_REQUEST_UP_START_PLAYBACK;
+  DL_DELETE(stream_list, &rstream);
+  stream_list_get_ret = stream_list;
+  stream_rm_cb(&rstream);
+
+  /* Suspend and resume */
+  observer_ops->suspend_changed(NULL, 1);
+  stream_list_get_ret = stream_list;
+  observer_ops->suspend_changed(NULL, 0);
+
+  EXPECT_EQ(CRAS_IODEV_RAMP_REQUEST_UP_START_PLAYBACK,
+            d1_.initial_ramp_request);
+  EXPECT_EQ(CRAS_IODEV_RAMP_REQUEST_UP_START_PLAYBACK,
+            d2_.initial_ramp_request);
+
+  cras_iodev_list_deinit();
+}
+
 TEST_F(IoDevTestSuite, InitDevFailShouldEnableFallback) {
   int rc;
   struct cras_rstream rstream;
@@ -360,6 +505,8 @@
   rc = cras_iodev_list_add_output(&d1_);
   ASSERT_EQ(0, rc);
 
+  d1_.format = &fmt_;
+
   cras_iodev_list_select_node(CRAS_STREAM_OUTPUT,
                               cras_make_node_id(d1_.info.idx, 0));
 
@@ -393,6 +540,9 @@
   rc = cras_iodev_list_add_input(&d2_);
   ASSERT_EQ(0, rc);
 
+  d1_.format = &fmt_;
+  d2_.format = &fmt_;
+
   cras_iodev_list_select_node(CRAS_STREAM_OUTPUT,
                               cras_make_node_id(d1_.info.idx, 0));
   /* No close call happened, because no stream exists. */
@@ -438,6 +588,9 @@
   rc = cras_iodev_list_add_output(&d2_);
   ASSERT_EQ(0, rc);
 
+  d1_.format = &fmt_;
+  d2_.format = &fmt_;
+
   cras_iodev_list_select_node(CRAS_STREAM_OUTPUT,
                               cras_make_node_id(d1_.info.idx, 1));
   DL_APPEND(stream_list, &rstream);
@@ -507,12 +660,15 @@
   struct cras_rstream* stream_list = NULL;
 
   memset(&rstream, 0, sizeof(rstream));
+  rstream.format = fmt_;
   cras_iodev_list_init();
 
   d1_.direction = CRAS_STREAM_OUTPUT;
   rc = cras_iodev_list_add_output(&d1_);
   ASSERT_EQ(0, rc);
 
+  d1_.format = &fmt_;
+
   cras_iodev_list_select_node(CRAS_STREAM_OUTPUT,
                               cras_make_node_id(d1_.info.idx, 0));
 
@@ -527,8 +683,7 @@
   EXPECT_EQ(2, cras_iodev_open_called);
   EXPECT_EQ(1, audio_thread_add_stream_called);
   EXPECT_EQ(0, update_active_node_called);
-  EXPECT_EQ(&dummy_empty_iodev[CRAS_STREAM_OUTPUT],
-            audio_thread_add_stream_dev);
+  EXPECT_EQ(&mock_empty_iodev[CRAS_STREAM_OUTPUT], audio_thread_add_stream_dev);
 
   EXPECT_NE((void*)NULL, cras_tm_timer_cb);
   EXPECT_EQ(1, cras_tm_create_timer_called);
@@ -539,6 +694,7 @@
   EXPECT_EQ(1, cras_tm_create_timer_called);
   EXPECT_EQ(1, audio_thread_add_stream_called);
 
+  mock_empty_iodev[CRAS_STREAM_OUTPUT].format = &fmt_;
   cras_tm_timer_cb = NULL;
   cras_iodev_open_ret[3] = -5;
   stream_add_cb(&rstream);
@@ -562,6 +718,8 @@
   rc = cras_iodev_list_add_output(&d1_);
   ASSERT_EQ(0, rc);
 
+  d1_.format = &fmt_;
+
   rstream.is_pinned = 1;
   rstream.pinned_dev_idx = d1_.info.idx;
 
@@ -617,6 +775,9 @@
   rc = cras_iodev_list_add_output(&d2_);
   ASSERT_EQ(0, rc);
 
+  d1_.format = &fmt_;
+  d2_.format = &fmt_;
+
   audio_thread_add_open_dev_called = 0;
   audio_thread_rm_open_dev_called = 0;
 
@@ -687,6 +848,9 @@
   rc = cras_iodev_list_add_output(&d2_);
   ASSERT_EQ(0, rc);
 
+  d1_.format = &fmt_;
+  d2_.format = &fmt_;
+
   audio_thread_add_open_dev_called = 0;
   audio_thread_rm_open_dev_called = 0;
   device_enabled_count = 0;
@@ -819,6 +983,7 @@
 TEST_F(IoDevTestSuite, AddRemoveOutput) {
   struct cras_iodev_info* dev_info;
   int rc;
+  cras_iodev_list_init();
 
   rc = cras_iodev_list_add_output(&d1_);
   EXPECT_EQ(0, rc);
@@ -850,6 +1015,7 @@
   EXPECT_EQ(0, rc);
   free(dev_info);
   EXPECT_EQ(0, cras_observer_notify_active_node_called);
+  cras_iodev_list_deinit();
 }
 
 // Test output_mute_changed callback.
@@ -928,39 +1094,56 @@
 
 // Test enable/disable an iodev.
 TEST_F(IoDevTestSuite, EnableDisableDevice) {
+  struct cras_rstream rstream;
+  cras_iodev_list_init();
   device_enabled_count = 0;
   device_disabled_count = 0;
+  memset(&rstream, 0, sizeof(rstream));
 
   EXPECT_EQ(0, cras_iodev_list_add_output(&d1_));
 
   EXPECT_EQ(0, cras_iodev_list_set_device_enabled_callback(
                    device_enabled_cb, device_disabled_cb, (void*)0xABCD));
 
-  // Enable a device.
+  // Enable a device, fallback should be diabled accordingly.
   cras_iodev_list_enable_dev(&d1_);
   EXPECT_EQ(&d1_, device_enabled_dev);
   EXPECT_EQ((void*)0xABCD, device_enabled_cb_data);
   EXPECT_EQ(1, device_enabled_count);
+  EXPECT_EQ(1, device_disabled_count);
   EXPECT_EQ(&d1_, cras_iodev_list_get_first_enabled_iodev(CRAS_STREAM_OUTPUT));
 
-  // Disable a device.
+  // Connect a normal stream.
+  cras_iodev_open_called = 0;
+  stream_add_cb(&rstream);
+  EXPECT_EQ(1, cras_iodev_open_called);
+
+  stream_list_has_pinned_stream_ret[d1_.info.idx] = 0;
+  // Disable a device. Expect dev is closed because there's no pinned stream.
+  update_active_node_called = 0;
   cras_iodev_list_disable_dev(&d1_, false);
   EXPECT_EQ(&d1_, device_disabled_dev);
-  EXPECT_EQ(1, device_disabled_count);
+  EXPECT_EQ(2, device_disabled_count);
   EXPECT_EQ((void*)0xABCD, device_disabled_cb_data);
 
+  EXPECT_EQ(1, audio_thread_rm_open_dev_called);
+  EXPECT_EQ(1, cras_iodev_close_called);
+  EXPECT_EQ(&d1_, cras_iodev_close_dev);
+  EXPECT_EQ(1, update_active_node_called);
+
   EXPECT_EQ(0, cras_iodev_list_set_device_enabled_callback(
                    device_enabled_cb, device_disabled_cb, (void*)0xCDEF));
   EXPECT_EQ(2, cras_observer_notify_active_node_called);
 
   EXPECT_EQ(0, cras_iodev_list_set_device_enabled_callback(NULL, NULL, NULL));
+  cras_iodev_list_deinit();
 }
 
 // Test adding/removing an input dev to the list.
 TEST_F(IoDevTestSuite, AddRemoveInput) {
   struct cras_iodev_info* dev_info;
   int rc, i;
-  uint32_t found_mask;
+  uint64_t found_mask;
 
   d1_.direction = CRAS_STREAM_INPUT;
   d2_.direction = CRAS_STREAM_INPUT;
@@ -993,8 +1176,8 @@
     found_mask = 0;
     for (i = 0; i < rc; i++) {
       uint32_t idx = dev_info[i].idx;
-      EXPECT_EQ(0, (found_mask & (1 << idx)));
-      found_mask |= (1 << idx);
+      EXPECT_EQ(0, (found_mask & (static_cast<uint64_t>(1) << idx)));
+      found_mask |= (static_cast<uint64_t>(1) << idx);
     }
   }
   if (rc > 0)
@@ -1030,6 +1213,7 @@
   d2_.direction = CRAS_STREAM_INPUT;
 
   server_state_update_begin_return = NULL;
+  cras_iodev_list_init();
 
   rc = cras_iodev_list_add_input(&d1_);
   EXPECT_EQ(0, rc);
@@ -1040,6 +1224,7 @@
 
   EXPECT_EQ(0, cras_iodev_list_rm_input(&d1_));
   EXPECT_EQ(0, cras_iodev_list_rm_input(&d2_));
+  cras_iodev_list_deinit();
 }
 
 // Test removing the last input.
@@ -1253,6 +1438,10 @@
   rc = cras_iodev_list_add_output(&d3_);
   ASSERT_EQ(0, rc);
 
+  d1_.format = &fmt_;
+  d2_.format = &fmt_;
+  d3_.format = &fmt_;
+
   audio_thread_add_open_dev_called = 0;
   cras_iodev_list_add_active_node(CRAS_STREAM_OUTPUT,
                                   cras_make_node_id(d3_.info.idx, 1));
@@ -1289,6 +1478,57 @@
   cras_iodev_list_deinit();
 }
 
+TEST_F(IoDevTestSuite, OutputDevIdleClose) {
+  int rc;
+  struct cras_rstream rstream;
+
+  memset(&rstream, 0, sizeof(rstream));
+  cras_iodev_list_init();
+
+  d1_.direction = CRAS_STREAM_OUTPUT;
+  rc = cras_iodev_list_add_output(&d1_);
+  EXPECT_EQ(0, rc);
+
+  d1_.format = &fmt_;
+
+  audio_thread_add_open_dev_called = 0;
+  cras_iodev_list_add_active_node(CRAS_STREAM_OUTPUT,
+                                  cras_make_node_id(d1_.info.idx, 1));
+  EXPECT_EQ(0, audio_thread_add_open_dev_called);
+  EXPECT_EQ(0, audio_thread_rm_open_dev_called);
+
+  // If a stream is added, the device should be opened.
+  stream_add_cb(&rstream);
+  EXPECT_EQ(1, audio_thread_add_open_dev_called);
+
+  audio_thread_rm_open_dev_called = 0;
+  audio_thread_drain_stream_return = 0;
+  clock_gettime_retspec.tv_sec = 15;
+  stream_rm_cb(&rstream);
+  EXPECT_EQ(1, audio_thread_drain_stream_called);
+  EXPECT_EQ(0, audio_thread_rm_open_dev_called);
+  EXPECT_EQ(1, cras_tm_create_timer_called);
+
+  // Expect no rm dev happen because idle time not yet expire, and
+  // new timer should be scheduled for the rest of the idle time.
+  clock_gettime_retspec.tv_sec += 7;
+  cras_tm_timer_cb(NULL, NULL);
+  EXPECT_EQ(0, audio_thread_rm_open_dev_called);
+  EXPECT_EQ(2, cras_tm_create_timer_called);
+
+  // Expect d1_ be closed upon unplug, and the timer stay armed.
+  cras_iodev_list_rm_output(&d1_);
+  EXPECT_EQ(1, audio_thread_rm_open_dev_called);
+  EXPECT_EQ(0, cras_tm_cancel_timer_called);
+
+  // When timer eventually fired expect there's no more new
+  // timer scheduled because d1_ has closed already.
+  clock_gettime_retspec.tv_sec += 4;
+  cras_tm_timer_cb(NULL, NULL);
+  EXPECT_EQ(2, cras_tm_create_timer_called);
+  cras_iodev_list_deinit();
+}
+
 TEST_F(IoDevTestSuite, DrainTimerCancel) {
   int rc;
   struct cras_rstream rstream;
@@ -1301,6 +1541,8 @@
   rc = cras_iodev_list_add_output(&d1_);
   EXPECT_EQ(0, rc);
 
+  d1_.format = &fmt_;
+
   audio_thread_add_open_dev_called = 0;
   cras_iodev_list_add_active_node(CRAS_STREAM_OUTPUT,
                                   cras_make_node_id(d1_.info.idx, 1));
@@ -1365,6 +1607,107 @@
   cras_iodev_list_deinit();
 }
 
+TEST_F(IoDevTestSuite, CloseDevWithPinnedStream) {
+  int rc;
+  struct cras_rstream rstream1, rstream2;
+  cras_iodev_list_init();
+
+  d1_.direction = CRAS_STREAM_OUTPUT;
+  d1_.info.idx = 1;
+  rc = cras_iodev_list_add_output(&d1_);
+  EXPECT_EQ(0, rc);
+
+  memset(&rstream1, 0, sizeof(rstream1));
+  memset(&rstream2, 0, sizeof(rstream2));
+  rstream2.is_pinned = 1;
+  rstream2.pinned_dev_idx = d1_.info.idx;
+
+  d1_.format = &fmt_;
+  audio_thread_add_open_dev_called = 0;
+  EXPECT_EQ(0, audio_thread_add_open_dev_called);
+  EXPECT_EQ(0, audio_thread_rm_open_dev_called);
+
+  // Add a normal stream
+  stream_add_cb(&rstream1);
+  EXPECT_EQ(1, audio_thread_add_open_dev_called);
+
+  // Add a pinned stream, expect another dev open call triggered.
+  cras_iodev_open_called = 0;
+  stream_add_cb(&rstream2);
+  EXPECT_EQ(1, cras_iodev_open_called);
+
+  // Force disable d1_ and make sure d1_ gets closed.
+  audio_thread_rm_open_dev_called = 0;
+  update_active_node_called = 0;
+  cras_iodev_close_called = 0;
+  cras_iodev_list_disable_dev(&d1_, 1);
+  EXPECT_EQ(1, audio_thread_rm_open_dev_called);
+  EXPECT_EQ(1, cras_iodev_close_called);
+  EXPECT_EQ(&d1_, cras_iodev_close_dev);
+  EXPECT_EQ(1, update_active_node_called);
+
+  // Add back the two streams, one normal one pinned.
+  audio_thread_add_open_dev_called = 0;
+  audio_thread_rm_open_dev_called = 0;
+  cras_iodev_open_called = 0;
+  stream_add_cb(&rstream2);
+  EXPECT_EQ(1, audio_thread_add_open_dev_called);
+  EXPECT_EQ(1, cras_iodev_open_called);
+  stream_add_cb(&rstream1);
+
+  // Suspend d1_ and make sure d1_ gets closed.
+  update_active_node_called = 0;
+  cras_iodev_close_called = 0;
+  cras_iodev_list_suspend_dev(d1_.info.idx);
+  EXPECT_EQ(1, audio_thread_rm_open_dev_called);
+  EXPECT_EQ(1, cras_iodev_close_called);
+  EXPECT_EQ(&d1_, cras_iodev_close_dev);
+  EXPECT_EQ(1, update_active_node_called);
+
+  cras_iodev_list_resume_dev(d1_.info.idx);
+
+  cras_iodev_list_deinit();
+}
+
+TEST_F(IoDevTestSuite, DisableDevWithPinnedStream) {
+  int rc;
+  struct cras_rstream rstream1;
+  cras_iodev_list_init();
+
+  d1_.direction = CRAS_STREAM_OUTPUT;
+  rc = cras_iodev_list_add_output(&d1_);
+  EXPECT_EQ(0, rc);
+
+  memset(&rstream1, 0, sizeof(rstream1));
+  rstream1.is_pinned = 1;
+  rstream1.pinned_dev_idx = d1_.info.idx;
+
+  d1_.format = &fmt_;
+  audio_thread_add_open_dev_called = 0;
+  cras_iodev_list_add_active_node(CRAS_STREAM_OUTPUT,
+                                  cras_make_node_id(d1_.info.idx, 1));
+  EXPECT_EQ(0, audio_thread_add_open_dev_called);
+  EXPECT_EQ(0, audio_thread_rm_open_dev_called);
+
+  // Add a pinned stream.
+  cras_iodev_open_called = 0;
+  stream_add_cb(&rstream1);
+  EXPECT_EQ(1, audio_thread_add_open_dev_called);
+  EXPECT_EQ(1, cras_iodev_open_called);
+
+  // Disable d1_ expect no close dev triggered because pinned stream.
+  stream_list_has_pinned_stream_ret[d1_.info.idx] = 1;
+  audio_thread_rm_open_dev_called = 0;
+  update_active_node_called = 0;
+  cras_iodev_close_called = 0;
+  cras_iodev_list_disable_dev(&d1_, 0);
+  EXPECT_EQ(0, audio_thread_rm_open_dev_called);
+  EXPECT_EQ(0, cras_iodev_close_called);
+  EXPECT_EQ(0, update_active_node_called);
+
+  cras_iodev_list_deinit();
+}
+
 TEST_F(IoDevTestSuite, AddRemovePinnedStream) {
   struct cras_rstream rstream;
 
@@ -1383,6 +1726,9 @@
   d2_.info.idx = 2;
   EXPECT_EQ(0, cras_iodev_list_add_output(&d2_));
 
+  d1_.format = &fmt_;
+  d2_.format = &fmt_;
+
   // Setup pinned stream.
   memset(&rstream, 0, sizeof(rstream));
   rstream.is_pinned = 1;
@@ -1407,7 +1753,7 @@
   EXPECT_EQ(2, update_active_node_called);
   // Unselect d1_ and select to d2_
   EXPECT_EQ(&d2_, update_active_node_iodev_val[0]);
-  EXPECT_EQ(&dummy_empty_iodev[CRAS_STREAM_OUTPUT],
+  EXPECT_EQ(&mock_empty_iodev[CRAS_STREAM_OUTPUT],
             update_active_node_iodev_val[1]);
 
   // Remove pinned stream from d1, check d1 is closed after stream removed.
@@ -1442,6 +1788,9 @@
   d2_.direction = CRAS_STREAM_OUTPUT;
   EXPECT_EQ(0, cras_iodev_list_add_output(&d2_));
 
+  d1_.format = &fmt_;
+  d2_.format = &fmt_;
+
   // Setup pinned stream.
   memset(&rstream, 0, sizeof(rstream));
   rstream.is_pinned = 1;
@@ -1497,6 +1846,8 @@
   d1_.direction = CRAS_STREAM_INPUT;
   EXPECT_EQ(0, cras_iodev_list_add_input(&d1_));
 
+  d1_.format = &fmt_;
+
   memset(&rstream, 0, sizeof(rstream));
   rstream.is_pinned = 1;
   rstream.pinned_dev_idx = d1_.info.idx;
@@ -1519,14 +1870,14 @@
   EXPECT_EQ(&d1_, audio_thread_disconnect_stream_dev);
   EXPECT_EQ(2, audio_thread_add_stream_called);
   EXPECT_EQ(&rstream, audio_thread_add_stream_stream);
-  EXPECT_EQ(&dummy_hotword_iodev, audio_thread_add_stream_dev);
+  EXPECT_EQ(&mock_hotword_iodev, audio_thread_add_stream_dev);
 
   /* Resume hotword streams, verify the stream disconnects from
    * the empty iodev and connects back to the real hotword iodev. */
   EXPECT_EQ(0, cras_iodev_list_resume_hotword_stream());
   EXPECT_EQ(2, audio_thread_disconnect_stream_called);
   EXPECT_EQ(&rstream, audio_thread_disconnect_stream_stream);
-  EXPECT_EQ(&dummy_hotword_iodev, audio_thread_disconnect_stream_dev);
+  EXPECT_EQ(&mock_hotword_iodev, audio_thread_disconnect_stream_dev);
   EXPECT_EQ(3, audio_thread_add_stream_called);
   EXPECT_EQ(&rstream, audio_thread_add_stream_stream);
   EXPECT_EQ(&d1_, audio_thread_add_stream_dev);
@@ -1542,6 +1893,8 @@
   d1_.direction = CRAS_STREAM_INPUT;
   EXPECT_EQ(0, cras_iodev_list_add_input(&d1_));
 
+  d1_.format = &fmt_;
+
   memset(&rstream, 0, sizeof(rstream));
   rstream.is_pinned = 1;
   rstream.pinned_dev_idx = d1_.info.idx;
@@ -1558,7 +1911,7 @@
   /* Hotword stream connected, verify it is added to the empty iodev. */
   EXPECT_EQ(0, stream_add_cb(&rstream));
   EXPECT_EQ(1, audio_thread_add_stream_called);
-  EXPECT_EQ(&dummy_hotword_iodev, audio_thread_add_stream_dev);
+  EXPECT_EQ(&mock_hotword_iodev, audio_thread_add_stream_dev);
   EXPECT_EQ(&rstream, audio_thread_add_stream_stream);
 
   /* Resume hotword streams, now the existing hotword stream should disconnect
@@ -1566,7 +1919,7 @@
   EXPECT_EQ(0, cras_iodev_list_resume_hotword_stream());
   EXPECT_EQ(1, audio_thread_disconnect_stream_called);
   EXPECT_EQ(&rstream, audio_thread_disconnect_stream_stream);
-  EXPECT_EQ(&dummy_hotword_iodev, audio_thread_disconnect_stream_dev);
+  EXPECT_EQ(&mock_hotword_iodev, audio_thread_disconnect_stream_dev);
   EXPECT_EQ(2, audio_thread_add_stream_called);
   EXPECT_EQ(&rstream, audio_thread_add_stream_stream);
   EXPECT_EQ(&d1_, audio_thread_add_stream_dev);
@@ -1591,6 +1944,105 @@
   cras_iodev_list_deinit();
 }
 
+TEST_F(IoDevTestSuite, HotwordStreamsPausedAtSystemSuspend) {
+  struct cras_rstream rstream;
+  struct cras_rstream* stream_list = NULL;
+  cras_iodev_list_init();
+
+  node1.type = CRAS_NODE_TYPE_HOTWORD;
+  d1_.direction = CRAS_STREAM_INPUT;
+  EXPECT_EQ(0, cras_iodev_list_add_input(&d1_));
+
+  d1_.format = &fmt_;
+
+  memset(&rstream, 0, sizeof(rstream));
+  rstream.is_pinned = 1;
+  rstream.pinned_dev_idx = d1_.info.idx;
+  rstream.flags = HOTWORD_STREAM;
+
+  /* Add a hotword stream. */
+  EXPECT_EQ(0, stream_add_cb(&rstream));
+  EXPECT_EQ(1, audio_thread_add_stream_called);
+  EXPECT_EQ(&d1_, audio_thread_add_stream_dev);
+  EXPECT_EQ(&rstream, audio_thread_add_stream_stream);
+
+  DL_APPEND(stream_list, &rstream);
+  stream_list_get_ret = stream_list;
+
+  server_state_hotword_pause_at_suspend = 1;
+
+  /* Trigger system suspend. Verify hotword stream is moved to empty dev. */
+  observer_ops->suspend_changed(NULL, 1);
+  EXPECT_EQ(1, audio_thread_disconnect_stream_called);
+  EXPECT_EQ(&rstream, audio_thread_disconnect_stream_stream);
+  EXPECT_EQ(&d1_, audio_thread_disconnect_stream_dev);
+  EXPECT_EQ(2, audio_thread_add_stream_called);
+  EXPECT_EQ(&rstream, audio_thread_add_stream_stream);
+  EXPECT_EQ(&mock_hotword_iodev, audio_thread_add_stream_dev);
+
+  /* Trigger system resume. Verify hotword stream is moved to real dev.*/
+  observer_ops->suspend_changed(NULL, 0);
+  EXPECT_EQ(2, audio_thread_disconnect_stream_called);
+  EXPECT_EQ(&rstream, audio_thread_disconnect_stream_stream);
+  EXPECT_EQ(&mock_hotword_iodev, audio_thread_disconnect_stream_dev);
+  EXPECT_EQ(3, audio_thread_add_stream_called);
+  EXPECT_EQ(&rstream, audio_thread_add_stream_stream);
+  EXPECT_EQ(&d1_, audio_thread_add_stream_dev);
+
+  server_state_hotword_pause_at_suspend = 0;
+  audio_thread_disconnect_stream_called = 0;
+  audio_thread_add_stream_called = 0;
+
+  /* Trigger system suspend. Verify hotword stream is not touched. */
+  observer_ops->suspend_changed(NULL, 1);
+  EXPECT_EQ(0, audio_thread_disconnect_stream_called);
+  EXPECT_EQ(0, audio_thread_add_stream_called);
+
+  /* Trigger system resume. Verify hotword stream is not touched.*/
+  observer_ops->suspend_changed(NULL, 0);
+  EXPECT_EQ(0, audio_thread_disconnect_stream_called);
+  EXPECT_EQ(0, audio_thread_add_stream_called);
+
+  cras_iodev_list_deinit();
+}
+
+TEST_F(IoDevTestSuite, SetNoiseCancellation) {
+  struct cras_rstream rstream;
+  struct cras_rstream* stream_list = NULL;
+  int rc;
+
+  memset(&rstream, 0, sizeof(rstream));
+
+  cras_iodev_list_init();
+
+  d1_.direction = CRAS_STREAM_INPUT;
+  rc = cras_iodev_list_add_input(&d1_);
+  ASSERT_EQ(0, rc);
+
+  d1_.format = &fmt_;
+
+  rstream.direction = CRAS_STREAM_INPUT;
+
+  audio_thread_add_open_dev_called = 0;
+  audio_thread_rm_open_dev_called = 0;
+  cras_iodev_list_add_active_node(CRAS_STREAM_INPUT,
+                                  cras_make_node_id(d1_.info.idx, 1));
+  DL_APPEND(stream_list, &rstream);
+  stream_add_cb(&rstream);
+  stream_list_get_ret = stream_list;
+  EXPECT_EQ(1, audio_thread_add_stream_called);
+  EXPECT_EQ(1, audio_thread_add_open_dev_called);
+
+  // reset_for_noise_cancellation causes device suspend & resume
+  // While suspending d1_: rm d1_, open fallback
+  // While resuming d1_: rm fallback, open d1_
+  cras_iodev_list_reset_for_noise_cancellation();
+  EXPECT_EQ(3, audio_thread_add_open_dev_called);
+  EXPECT_EQ(2, audio_thread_rm_open_dev_called);
+
+  cras_iodev_list_deinit();
+}
+
 }  //  namespace
 
 int main(int argc, char** argv) {
@@ -1601,6 +2053,7 @@
 extern "C" {
 
 // Stubs
+struct main_thread_event_log* main_log;
 
 struct cras_server_state* cras_system_state_update_begin() {
   return server_state_update_begin_return;
@@ -1612,6 +2065,10 @@
   return system_get_mute_return;
 }
 
+bool cras_system_get_noise_cancellation_enabled() {
+  return false;
+}
+
 struct audio_thread* audio_thread_create() {
   return &thread;
 }
@@ -1682,9 +2139,9 @@
                                       enum CRAS_NODE_TYPE node_type) {
   struct cras_iodev* dev;
   if (node_type == CRAS_NODE_TYPE_HOTWORD) {
-    dev = &dummy_hotword_iodev;
+    dev = &mock_hotword_iodev;
   } else {
-    dev = &dummy_empty_iodev[direction];
+    dev = &mock_empty_iodev[direction];
   }
   dev->direction = direction;
   if (dev->active_node == NULL) {
@@ -1723,6 +2180,8 @@
                     const struct cras_audio_format* fmt) {
   if (cras_iodev_open_ret[cras_iodev_open_called] == 0)
     iodev->state = CRAS_IODEV_STATE_OPEN;
+  cras_iodev_open_fmt = *fmt;
+  iodev->format = &cras_iodev_open_fmt;
   return cras_iodev_open_ret[cras_iodev_open_called++];
 }
 
@@ -1730,6 +2189,7 @@
   iodev->state = CRAS_IODEV_STATE_CLOSE;
   cras_iodev_close_called++;
   cras_iodev_close_dev = iodev;
+  iodev->format = NULL;
   return 0;
 }
 
@@ -1748,13 +2208,19 @@
   set_node_plugged_called++;
 }
 
+bool cras_iodev_support_noise_cancellation(const struct cras_iodev* iodev) {
+  return true;
+}
+
 int cras_iodev_start_volume_ramp(struct cras_iodev* odev,
                                  unsigned int old_volume,
                                  unsigned int new_volume) {
   cras_iodev_start_volume_ramp_called++;
   return 0;
 }
-
+bool cras_iodev_is_aec_use_case(const struct cras_ionode* node) {
+  return 1;
+}
 bool stream_list_has_pinned_stream(struct stream_list* list,
                                    unsigned int dev_idx) {
   return stream_list_has_pinned_stream_ret[dev_idx];
@@ -1870,12 +2336,13 @@
 }
 
 #ifdef HAVE_WEBRTC_APM
-struct cras_apm* cras_apm_list_add(struct cras_apm_list* list,
-                                   void* dev_ptr,
-                                   const struct cras_audio_format* fmt) {
+struct cras_apm* cras_apm_list_add_apm(struct cras_apm_list* list,
+                                       void* dev_ptr,
+                                       const struct cras_audio_format* fmt,
+                                       bool is_internal_dev) {
   return NULL;
 }
-void cras_apm_list_remove(struct cras_apm_list* list, void* dev_ptr) {}
+void cras_apm_list_remove_apm(struct cras_apm_list* list, void* dev_ptr) {}
 int cras_apm_list_init(const char* device_config_dir) {
   return 0;
 }
@@ -1888,4 +2355,8 @@
   return 0;
 }
 
+bool cras_system_get_hotword_pause_at_suspend() {
+  return !!server_state_hotword_pause_at_suspend;
+}
+
 }  // extern "C"
diff --git a/cras/src/tests/iodev_stub.cc b/cras/src/tests/iodev_stub.cc
index 25d59da..2e84faa 100644
--- a/cras/src/tests/iodev_stub.cc
+++ b/cras/src/tests/iodev_stub.cc
@@ -21,12 +21,30 @@
 std::unordered_map<cras_iodev*, cb_data> frames_queued_map;
 std::unordered_map<cras_iodev*, cb_data> valid_frames_map;
 std::unordered_map<cras_iodev*, timespec> drop_time_map;
+std::unordered_map<const cras_iodev*, double> est_rate_ratio_map;
+std::unordered_map<const cras_iodev*, int> update_rate_map;
+std::unordered_map<const cras_ionode*, int> on_internal_card_map;
 }  // namespace
 
 void iodev_stub_reset() {
   frames_queued_map.clear();
   valid_frames_map.clear();
   drop_time_map.clear();
+  est_rate_ratio_map.clear();
+  update_rate_map.clear();
+  on_internal_card_map.clear();
+}
+
+void iodev_stub_est_rate_ratio(cras_iodev* iodev, double ratio) {
+  est_rate_ratio_map.insert({iodev, ratio});
+}
+
+void iodev_stub_update_rate(cras_iodev* iodev, int data) {
+  update_rate_map.insert({iodev, data});
+}
+
+void iodev_stub_on_internal_card(cras_ionode* node, int data) {
+  on_internal_card_map.insert({node, data});
 }
 
 void iodev_stub_frames_queued(cras_iodev* iodev, int ret, timespec ts) {
@@ -67,7 +85,11 @@
 }
 
 double cras_iodev_get_est_rate_ratio(const struct cras_iodev* iodev) {
-  return 1.0;
+  auto elem = est_rate_ratio_map.find(iodev);
+  if (elem != est_rate_ratio_map.end()) {
+    return elem->second;
+  }
+  return 1.0f;
 }
 
 int cras_iodev_get_dsp_delay(const struct cras_iodev* iodev) {
@@ -93,6 +115,10 @@
 int cras_iodev_update_rate(struct cras_iodev* iodev,
                            unsigned int level,
                            struct timespec* level_tstamp) {
+  auto elem = update_rate_map.find(iodev);
+  if (elem != update_rate_map.end()) {
+    return elem->second;
+  }
   return 0;
 }
 
@@ -150,7 +176,9 @@
   return 1;
 }
 
-int cras_iodev_output_underrun(struct cras_iodev* odev) {
+int cras_iodev_output_underrun(struct cras_iodev* odev,
+                               unsigned int hw_level,
+                               unsigned int frames_written) {
   return 0;
 }
 
@@ -186,4 +214,12 @@
   drop_time_map.insert({iodev, ts});
   return 0;
 }
+
+bool cras_iodev_is_on_internal_card(const struct cras_ionode* node) {
+  auto elem = on_internal_card_map.find(node);
+  if (elem != on_internal_card_map.end()) {
+    return elem->second;
+  }
+  return 1;
+}
 }  // extern "C"
diff --git a/cras/src/tests/iodev_stub.h b/cras/src/tests/iodev_stub.h
index dde1b9f..e8016dd 100644
--- a/cras/src/tests/iodev_stub.h
+++ b/cras/src/tests/iodev_stub.h
@@ -10,6 +10,12 @@
 
 void iodev_stub_reset();
 
+void iodev_stub_est_rate_ratio(cras_iodev* iodev, double ratio);
+
+void iodev_stub_update_rate(cras_iodev* iodev, int data);
+
+void iodev_stub_on_internal_card(cras_ionode* node, int data);
+
 void iodev_stub_frames_queued(cras_iodev* iodev, int ret, timespec ts);
 
 void iodev_stub_valid_frames(cras_iodev* iodev, int ret, timespec ts);
diff --git a/cras/src/tests/iodev_unittest.cc b/cras/src/tests/iodev_unittest.cc
index 61f8f69..24b2b38 100644
--- a/cras/src/tests/iodev_unittest.cc
+++ b/cras/src/tests/iodev_unittest.cc
@@ -9,6 +9,7 @@
 #include "audio_thread_log.h"
 #include "cras_audio_area.h"
 #include "cras_iodev.h"
+#include "cras_main_thread_log.h"
 #include "cras_ramp.h"
 #include "cras_rstream.h"
 #include "dev_stream.h"
@@ -59,7 +60,7 @@
 static unsigned int cras_dsp_num_input_channels_return;
 static unsigned int cras_dsp_num_output_channels_return;
 struct cras_dsp_context* cras_dsp_context_new_return;
-static unsigned int cras_dsp_load_dummy_pipeline_called;
+static unsigned int cras_dsp_load_mock_pipeline_called;
 static unsigned int rate_estimator_add_frames_num_frames;
 static unsigned int rate_estimator_add_frames_called;
 static int cras_system_get_mute_return;
@@ -73,7 +74,6 @@
 static const uint8_t* post_dsp_hook_frames;
 static void* post_dsp_hook_cb_data;
 static int iodev_buffer_size;
-static long cras_system_get_capture_gain_ret_value;
 static uint8_t audio_buffer[BUFFER_SIZE];
 static struct cras_audio_area* audio_area;
 static unsigned int put_buffer_nframes;
@@ -85,7 +85,6 @@
 static unsigned int simple_no_stream_called;
 static int simple_no_stream_enable;
 static int dev_stream_playback_frames_ret;
-static int get_num_underruns_ret;
 static int device_monitor_reset_device_called;
 static int output_underrun_called;
 static int set_mute_called;
@@ -114,6 +113,7 @@
 static int ext_mod_configure_called;
 static struct input_data* input_data_create_ret;
 static double rate_estimator_get_rate_ret;
+static int cras_audio_thread_event_dev_overrun_called;
 
 static char* atlog_name;
 
@@ -148,7 +148,7 @@
   cras_dsp_num_input_channels_return = 2;
   cras_dsp_num_output_channels_return = 2;
   cras_dsp_context_new_return = NULL;
-  cras_dsp_load_dummy_pipeline_called = 0;
+  cras_dsp_load_mock_pipeline_called = 0;
   rate_estimator_add_frames_num_frames = 0;
   rate_estimator_add_frames_called = 0;
   cras_system_get_mute_return = 0;
@@ -159,7 +159,6 @@
   post_dsp_hook_called = 0;
   post_dsp_hook_frames = NULL;
   iodev_buffer_size = 0;
-  cras_system_get_capture_gain_ret_value = 0;
   // Assume there is some data in audio buffer.
   memset(audio_buffer, 0xff, sizeof(audio_buffer));
   if (audio_area) {
@@ -181,7 +180,6 @@
     atlog_rw_shm_fd = atlog_ro_shm_fd = -1;
     atlog = audio_thread_event_log_init(atlog_name);
   }
-  get_num_underruns_ret = 0;
   device_monitor_reset_device_called = 0;
   output_underrun_called = 0;
   set_mute_called = 0;
@@ -211,6 +209,7 @@
   buffer_share_add_id_called = 0;
   ext_mod_configure_called = 0;
   rate_estimator_get_rate_ret = 0;
+  cras_audio_thread_event_dev_overrun_called = 0;
 }
 
 namespace {
@@ -269,9 +268,14 @@
     iodev_.dsp_context = NULL;
 
     cras_audio_format_set_channel_layout_called = 0;
+
+    main_log = main_thread_event_log_init();
   }
 
-  virtual void TearDown() { cras_iodev_free_format(&iodev_); }
+  virtual void TearDown() {
+    cras_iodev_free_format(&iodev_);
+    main_thread_event_log_deinit(main_log);
+  }
 
   struct cras_iodev iodev_;
   size_t sample_rates_[3];
@@ -1058,6 +1062,8 @@
   struct cras_iodev iodev;
   struct cras_ionode ionode, ionode2;
 
+  main_log = main_thread_event_log_init();
+
   memset(&iodev, 0, sizeof(iodev));
   memset(&ionode, 0, sizeof(ionode));
   memset(&ionode2, 0, sizeof(ionode2));
@@ -1079,6 +1085,7 @@
   EXPECT_EQ(1, cras_iodev_list_disable_dev_called);
   cras_iodev_set_node_plugged(&ionode2, 0);
   EXPECT_EQ(1, cras_iodev_list_disable_dev_called);
+  main_thread_event_log_deinit(main_log);
 }
 
 TEST(IoDev, AddRemoveNode) {
@@ -1169,21 +1176,15 @@
   iodev.active_node = &ionode;
   iodev.active_node->dev = &iodev;
 
-  ionode.capture_gain = 400;
+  ionode.capture_gain = 2400;
   ionode.software_volume_needed = 1;
-  ionode.max_software_gain = 3000;
 
-  // Check that system volume changes software volume if needed.
-  cras_system_get_capture_gain_ret_value = 2000;
-  // system_gain + node_gain = 2000 + 400  = 2400
   // 2400 * 0.01 dB is 15.848931
   EXPECT_FLOAT_EQ(15.848931, cras_iodev_get_software_gain_scaler(&iodev));
-  EXPECT_FLOAT_EQ(3000, cras_iodev_maximum_software_gain(&iodev));
 
   // Software gain scaler should be 1.0 if software gain is not needed.
   ionode.software_volume_needed = 0;
   EXPECT_FLOAT_EQ(1.0, cras_iodev_get_software_gain_scaler(&iodev));
-  EXPECT_FLOAT_EQ(0, cras_iodev_maximum_software_gain(&iodev));
 }
 
 // This get_buffer implementation set returned frames larger than requested
@@ -1232,6 +1233,8 @@
   iodev.configure_dev = configure_dev;
   iodev.direction = CRAS_STREAM_OUTPUT;
   iodev.format = &audio_fmt;
+  iodev.get_buffer = get_buffer;
+  iodev.put_buffer = put_buffer;
   ResetStubData();
 
   iodev.state = CRAS_IODEV_STATE_CLOSE;
@@ -1252,6 +1255,8 @@
   iodev.configure_dev = configure_dev;
   iodev.direction = CRAS_STREAM_OUTPUT;
   iodev.format = &audio_fmt;
+  iodev.get_buffer = get_buffer;
+  iodev.put_buffer = put_buffer;
   ResetStubData();
 
   cras_audio_format low_rate_fmt = audio_fmt;
@@ -1373,6 +1378,8 @@
   iodev.no_stream = simple_no_stream;
   iodev.format = &audio_fmt;
   iodev.state = CRAS_IODEV_STATE_NORMAL_RUN;
+  iodev.get_buffer = get_buffer;
+  iodev.put_buffer = put_buffer;
   rstream1.cb_threshold = 800;
   stream1.stream = &rstream1;
   stream1.is_running = 0;
@@ -1426,6 +1433,8 @@
   iodev.no_stream = simple_no_stream;
   iodev.format = &audio_fmt;
   iodev.state = CRAS_IODEV_STATE_NORMAL_RUN;
+  iodev.get_buffer = get_buffer;
+  iodev.put_buffer = put_buffer;
   rstream1.direction = CRAS_STREAM_OUTPUT;
   rstream2.direction = CRAS_STREAM_OUTPUT;
   rstream3.direction = CRAS_STREAM_OUTPUT;
@@ -1473,6 +1482,8 @@
   iodev1.configure_dev = configure_dev;
   iodev1.format = &audio_fmt;
   iodev1.state = CRAS_IODEV_STATE_NORMAL_RUN;
+  iodev1.get_buffer = get_buffer;
+  iodev1.put_buffer = put_buffer;
   iodev2.configure_dev = configure_dev;
   iodev2.format = &audio_fmt;
   iodev2.state = CRAS_IODEV_STATE_NORMAL_RUN;
@@ -1722,7 +1733,7 @@
 
   // Assume device has ramp member.
   iodev.ramp = reinterpret_cast<struct cras_ramp*>(0x1);
-
+  iodev.initial_ramp_request = CRAS_IODEV_RAMP_REQUEST_UP_START_PLAYBACK;
   // Case 4.1: Assume device with ramp is started and is in no stream state.
   iodev.state = CRAS_IODEV_STATE_NO_STREAM_RUN;
   // Assume sample is ready.
@@ -2101,18 +2112,13 @@
   EXPECT_EQ(got_frames, hw_level - fmt.frame_rate / 1000 * 5);
 }
 
-static unsigned int get_num_underruns(const struct cras_iodev* iodev) {
-  return get_num_underruns_ret;
-}
-
 TEST(IoDev, GetNumUnderruns) {
   struct cras_iodev iodev;
   memset(&iodev, 0, sizeof(iodev));
 
   EXPECT_EQ(0, cras_iodev_get_num_underruns(&iodev));
 
-  iodev.get_num_underruns = get_num_underruns;
-  get_num_underruns_ret = 10;
+  iodev.num_underruns = 10;
   EXPECT_EQ(10, cras_iodev_get_num_underruns(&iodev));
 }
 
@@ -2125,6 +2131,8 @@
   iodev.configure_dev = configure_dev;
   iodev.direction = CRAS_STREAM_OUTPUT;
   iodev.format = &audio_fmt;
+  iodev.get_buffer = get_buffer;
+  iodev.put_buffer = put_buffer;
 
   iodev.state = CRAS_IODEV_STATE_CLOSE;
   iodev_buffer_size = 1024;
@@ -2173,7 +2181,7 @@
   iodev.min_cb_level = frames;
 
   // Default case, fill one block of zeros.
-  EXPECT_EQ(0, cras_iodev_output_underrun(&iodev));
+  EXPECT_EQ(0, cras_iodev_output_underrun(&iodev, 0, 0));
 
   EXPECT_EQ(frames, put_buffer_nframes);
   zeros = (int16_t*)calloc(frames * 2, sizeof(*zeros));
@@ -2183,7 +2191,7 @@
 
   // Test iodev has output_underrun ops.
   iodev.output_underrun = output_underrun;
-  EXPECT_EQ(0, cras_iodev_output_underrun(&iodev));
+  EXPECT_EQ(0, cras_iodev_output_underrun(&iodev, 0, 0));
   EXPECT_EQ(1, output_underrun_called);
 }
 
@@ -2232,12 +2240,12 @@
   EXPECT_EQ(3, cras_dsp_get_pipeline_called);
   EXPECT_EQ(3, cras_dsp_pipeline_set_sink_ext_module_called);
 
-  /* If pipeline doesn't exist, dummy pipeline should be loaded. */
+  /* If pipeline doesn't exist, mock pipeline should be loaded. */
   cras_dsp_get_pipeline_ret = 0x0;
   cras_iodev_set_ext_dsp_module(&iodev, &ext);
   EXPECT_EQ(3, ext_mod_configure_called);
   EXPECT_EQ(5, cras_dsp_get_pipeline_called);
-  EXPECT_EQ(1, cras_dsp_load_dummy_pipeline_called);
+  EXPECT_EQ(1, cras_dsp_load_mock_pipeline_called);
   EXPECT_EQ(4, cras_dsp_pipeline_set_sink_ext_module_called);
 }
 
@@ -2349,8 +2357,71 @@
   EXPECT_EQ(-360, rate_estimator_add_frames_num_frames);
 }
 
+TEST(IoDev, AecUseCaseCheck) {
+  struct cras_ionode node;
+
+  /* test output types */
+  node.type = CRAS_NODE_TYPE_INTERNAL_SPEAKER;
+  EXPECT_EQ(1, cras_iodev_is_aec_use_case(&node));
+  node.type = CRAS_NODE_TYPE_HEADPHONE;
+  EXPECT_EQ(0, cras_iodev_is_aec_use_case(&node));
+  node.type = CRAS_NODE_TYPE_HDMI;
+  EXPECT_EQ(0, cras_iodev_is_aec_use_case(&node));
+  node.type = CRAS_NODE_TYPE_USB;
+  EXPECT_EQ(0, cras_iodev_is_aec_use_case(&node));
+  node.type = CRAS_NODE_TYPE_BLUETOOTH;
+  EXPECT_EQ(0, cras_iodev_is_aec_use_case(&node));
+
+  /* test mic positions */
+  node.type = CRAS_NODE_TYPE_MIC;
+  node.position = NODE_POSITION_INTERNAL;
+  EXPECT_EQ(1, cras_iodev_is_aec_use_case(&node));
+  node.position = NODE_POSITION_FRONT;
+  EXPECT_EQ(1, cras_iodev_is_aec_use_case(&node));
+  node.position = NODE_POSITION_EXTERNAL;
+  EXPECT_EQ(0, cras_iodev_is_aec_use_case(&node));
+  node.position = NODE_POSITION_REAR;
+  EXPECT_EQ(0, cras_iodev_is_aec_use_case(&node));
+}
+
+TEST(IoDev, DeviceOverrun) {
+  struct cras_iodev iodev;
+
+  iodev.buffer_size = 4096;
+  iodev.largest_cb_level = 2048;
+  cras_iodev_update_highest_hw_level(&iodev, 4096);
+  EXPECT_EQ(0, cras_audio_thread_event_dev_overrun_called);
+
+  iodev.largest_cb_level = 1024;
+  iodev.highest_hw_level = 1024;
+  cras_iodev_update_highest_hw_level(&iodev, 2048);
+  EXPECT_EQ(0, cras_audio_thread_event_dev_overrun_called);
+
+  cras_iodev_update_highest_hw_level(&iodev, 4096);
+  EXPECT_EQ(1, cras_audio_thread_event_dev_overrun_called);
+
+  cras_iodev_update_highest_hw_level(&iodev, 4096);
+  EXPECT_EQ(1, cras_audio_thread_event_dev_overrun_called);
+}
+
+TEST(IoDev, OnInternalCard) {
+  static struct cras_ionode node;
+  node.type = CRAS_NODE_TYPE_INTERNAL_SPEAKER;
+  EXPECT_EQ(1, cras_iodev_is_on_internal_card(&node));
+  node.type = CRAS_NODE_TYPE_HEADPHONE;
+  EXPECT_EQ(1, cras_iodev_is_on_internal_card(&node));
+  node.type = CRAS_NODE_TYPE_MIC;
+  EXPECT_EQ(1, cras_iodev_is_on_internal_card(&node));
+  node.type = CRAS_NODE_TYPE_USB;
+  EXPECT_EQ(0, cras_iodev_is_on_internal_card(&node));
+  node.type = CRAS_NODE_TYPE_BLUETOOTH;
+  EXPECT_EQ(0, cras_iodev_is_on_internal_card(&node));
+}
+
 extern "C" {
 
+struct main_thread_event_log* main_log;
+
 //  From libpthread.
 int pthread_create(pthread_t* thread,
                    const pthread_attr_t* attr,
@@ -2390,7 +2461,7 @@
   return buffer_share_get_new_write_point_ret;
 }
 
-int buffer_share_add_id(struct buffer_share* mix, unsigned int id) {
+int buffer_share_add_id(struct buffer_share* mix, unsigned int id, void* data) {
   buffer_share_add_id_called++;
   return 0;
 }
@@ -2405,9 +2476,11 @@
 }
 
 // From cras_system_state.
-void cras_system_state_stream_added(enum CRAS_STREAM_DIRECTION direction) {}
+void cras_system_state_stream_added(enum CRAS_STREAM_DIRECTION direction,
+                                    enum CRAS_CLIENT_TYPE client_type) {}
 
-void cras_system_state_stream_removed(enum CRAS_STREAM_DIRECTION direction) {}
+void cras_system_state_stream_removed(enum CRAS_STREAM_DIRECTION direction,
+                                      enum CRAS_CLIENT_TYPE client_type) {}
 
 // From cras_dsp
 struct cras_dsp_context* cras_dsp_context_new(int sample_rate,
@@ -2422,9 +2495,9 @@
 }
 
 void cras_dsp_load_pipeline(struct cras_dsp_context* ctx) {}
-void cras_dsp_load_dummy_pipeline(struct cras_dsp_context* ctx,
-                                  unsigned int num_channels) {
-  cras_dsp_load_dummy_pipeline_called++;
+void cras_dsp_load_mock_pipeline(struct cras_dsp_context* ctx,
+                                 unsigned int num_channels) {
+  cras_dsp_load_mock_pipeline_called++;
 }
 
 void cras_dsp_set_variable_string(struct cras_dsp_context* ctx,
@@ -2541,10 +2614,6 @@
   return cras_system_get_volume_return;
 }
 
-long cras_system_get_capture_gain() {
-  return cras_system_get_capture_gain_ret_value;
-}
-
 int cras_system_get_mute() {
   return cras_system_get_mute_return;
 }
@@ -2690,11 +2759,12 @@
 void input_data_set_all_streams_read(struct input_data* data,
                                      unsigned int nframes) {}
 
-int cras_audio_thread_event_severe_underrun() {
+int cras_audio_thread_event_underrun() {
   return 0;
 }
 
-int cras_audio_thread_event_underrun() {
+int cras_audio_thread_event_dev_overrun() {
+  cras_audio_thread_event_dev_overrun_called++;
   return 0;
 }
 
@@ -2702,6 +2772,22 @@
   return 0;
 }
 
+int cras_server_metrics_device_volume(struct cras_iodev* iodev) {
+  return 0;
+}
+
+void ewma_power_init(struct ewma_power* ewma, unsigned int rate){};
+
+void ewma_power_calculate(struct ewma_power* ewma,
+                          const int16_t* buf,
+                          unsigned int channels,
+                          unsigned int size){};
+
+void ewma_power_calculate_area(struct ewma_power* ewma,
+                               const int16_t* buf,
+                               struct cras_audio_area* area,
+                               unsigned int size){};
+
 }  // extern "C"
 }  //  namespace
 
diff --git a/cras/src/tests/loopback_iodev_unittest.cc b/cras/src/tests/loopback_iodev_unittest.cc
index 3cdaffb..fde5037 100644
--- a/cras/src/tests/loopback_iodev_unittest.cc
+++ b/cras/src/tests/loopback_iodev_unittest.cc
@@ -7,6 +7,11 @@
 #include <stdlib.h>
 
 extern "C" {
+// For audio_thread_log.h use.
+struct audio_thread_event_log* atlog;
+int atlog_rw_shm_fd;
+int atlog_ro_shm_fd;
+#include "audio_thread_log.h"
 #include "cras_audio_area.h"
 #include "cras_iodev.h"
 #include "cras_iodev_list.h"
@@ -24,7 +29,7 @@
 static const unsigned int kBufferSize = kBufferFrames * kFrameBytes;
 
 static struct timespec time_now;
-static cras_audio_area* dummy_audio_area;
+static cras_audio_area* mock_audio_area;
 static loopback_hook_data_t loop_hook;
 static struct cras_iodev* enabled_dev;
 static unsigned int cras_iodev_list_add_input_called;
@@ -36,11 +41,13 @@
 static int cras_iodev_list_register_loopback_called;
 static int cras_iodev_list_unregister_loopback_called;
 
+static char* atlog_name;
+
 class LoopBackTestSuite : public testing::Test {
  protected:
   virtual void SetUp() {
-    dummy_audio_area = (cras_audio_area*)calloc(
-        1, sizeof(*dummy_audio_area) + sizeof(cras_channel_area) * 2);
+    mock_audio_area = (cras_audio_area*)calloc(
+        1, sizeof(*mock_audio_area) + sizeof(cras_channel_area) * 2);
     for (unsigned int i = 0; i < kBufferSize; i++) {
       buf_[i] = rand();
     }
@@ -58,6 +65,11 @@
     cras_iodev_list_set_device_enabled_callback_called = 0;
     cras_iodev_list_register_loopback_called = 0;
     cras_iodev_list_unregister_loopback_called = 0;
+
+    ASSERT_FALSE(asprintf(&atlog_name, "/ATlog-%d", getpid()) < 0);
+    /* To avoid un-used variable warning. */
+    atlog_rw_shm_fd = atlog_ro_shm_fd = -1;
+    atlog = audio_thread_event_log_init(atlog_name);
   }
 
   virtual void TearDown() {
@@ -65,7 +77,9 @@
     EXPECT_EQ(1, cras_iodev_list_rm_input_called);
     EXPECT_EQ(NULL, device_enabled_callback_cb);
     EXPECT_EQ(NULL, device_disabled_callback_cb);
-    free(dummy_audio_area);
+    free(mock_audio_area);
+    audio_thread_event_log_deinit(atlog, atlog_name);
+    free(atlog_name);
   }
 
   uint8_t buf_[kBufferSize];
@@ -210,7 +224,7 @@
 void cras_audio_area_config_buf_pointers(struct cras_audio_area* area,
                                          const struct cras_audio_format* fmt,
                                          uint8_t* base_buffer) {
-  dummy_audio_area->channels[0].buf = base_buffer;
+  mock_audio_area->channels[0].buf = base_buffer;
 }
 
 void cras_iodev_free_audio_area(struct cras_iodev* iodev) {}
@@ -218,7 +232,7 @@
 void cras_iodev_free_format(struct cras_iodev* iodev) {}
 
 void cras_iodev_init_audio_area(struct cras_iodev* iodev, int num_channels) {
-  iodev->area = dummy_audio_area;
+  iodev->area = mock_audio_area;
 }
 
 void cras_iodev_add_node(struct cras_iodev* iodev, struct cras_ionode* node) {
diff --git a/cras/src/tests/metrics_stub.cc b/cras/src/tests/metrics_stub.cc
index d0f19c3..96e8918 100644
--- a/cras/src/tests/metrics_stub.cc
+++ b/cras/src/tests/metrics_stub.cc
@@ -26,7 +26,7 @@
   return 0;
 }
 
-int cras_server_metrics_missed_cb_event(const struct cras_rstream* stream) {
+int cras_server_metrics_missed_cb_event(struct cras_rstream* stream) {
   return 0;
 }
 
@@ -34,6 +34,14 @@
   return 0;
 }
 
+int cras_server_metrics_hfp_battery_indicator(int battery_indicator_support) {
+  return 0;
+}
+
+int cras_server_metrics_hfp_battery_report(int battery_report) {
+  return 0;
+}
+
 int cras_server_metrics_hfp_wideband_support(bool supported) {
   return 0;
 }
@@ -42,8 +50,17 @@
   return 0;
 }
 
+int cras_server_metrics_hfp_sco_connection_error(
+    enum CRAS_METRICS_BT_SCO_ERROR_TYPE type) {
+  return 0;
+}
+
 int cras_server_metrics_busyloop(struct timespec* ts, unsigned count) {
   return 0;
 }
 
+int cras_server_metrics_busyloop_length(unsigned count) {
+  return 0;
+}
+
 }  // extern "C"
diff --git a/cras/src/tests/observer_unittest.cc b/cras/src/tests/observer_unittest.cc
index 5c9ca14..2a8fae2 100644
--- a/cras/src/tests/observer_unittest.cc
+++ b/cras/src/tests/observer_unittest.cc
@@ -11,6 +11,8 @@
 
 extern "C" {
 #include "cras_observer.c"
+#include "cras_observer.h"
+#include "cras_types.h"
 }
 
 namespace {
@@ -56,6 +58,9 @@
 static std::vector<enum CRAS_STREAM_DIRECTION>
     cb_num_active_streams_changed_dir;
 static std::vector<uint32_t> cb_num_active_streams_changed_num;
+static size_t cb_num_input_streams_with_permission_called;
+static std::vector<std::vector<uint32_t>>
+    cb_num_input_streams_with_permission_array;
 
 static void ResetStubData() {
   cras_alert_destroy_called = 0;
@@ -99,6 +104,8 @@
   cb_num_active_streams_changed_called = 0;
   cb_num_active_streams_changed_dir.clear();
   cb_num_active_streams_changed_num.clear();
+  cb_num_input_streams_with_permission_called = 0;
+  cb_num_input_streams_with_permission_array.clear();
 }
 
 /* System output volume changed. */
@@ -190,6 +197,15 @@
   cb_num_active_streams_changed_num.push_back(num_active_streams);
 }
 
+void cb_num_input_streams_with_permission_changed(
+    void* context,
+    uint32_t num_input_streams[CRAS_NUM_CLIENT_TYPE]) {
+  cb_num_input_streams_with_permission_called++;
+  cb_context.push_back(context);
+  cb_num_input_streams_with_permission_array.push_back(std::vector<uint32_t>(
+      num_input_streams, num_input_streams + CRAS_NUM_CLIENT_TYPE));
+}
+
 class ObserverTest : public testing::Test {
  protected:
   virtual void SetUp() {
@@ -198,7 +214,7 @@
     ResetStubData();
     rc = cras_observer_server_init();
     ASSERT_EQ(0, rc);
-    EXPECT_EQ(15, cras_alert_create_called);
+    EXPECT_EQ(17, cras_alert_create_called);
     EXPECT_EQ(reinterpret_cast<void*>(output_volume_alert),
               cras_alert_add_callback_map[g_observer->alerts.output_volume]);
     EXPECT_EQ(reinterpret_cast<void*>(output_mute_alert),
@@ -240,6 +256,9 @@
     EXPECT_EQ(reinterpret_cast<void*>(non_empty_audio_state_changed_alert),
               cras_alert_add_callback_map[g_observer->alerts
                                               .non_empty_audio_state_changed]);
+    EXPECT_EQ(
+        reinterpret_cast<void*>(bt_battery_changed_alert),
+        cras_alert_add_callback_map[g_observer->alerts.bt_battery_changed]);
 
     cras_observer_get_ops(NULL, &ops1_);
     EXPECT_NE(0, cras_observer_ops_are_empty(&ops1_));
@@ -253,7 +272,7 @@
 
   virtual void TearDown() {
     cras_observer_server_free();
-    EXPECT_EQ(15, cras_alert_destroy_called);
+    EXPECT_EQ(17, cras_alert_destroy_called);
     ResetStubData();
   }
 
@@ -575,6 +594,37 @@
   DoObserverRemoveClear(num_active_streams_alert, data);
 };
 
+TEST_F(ObserverTest, NotifyNumInputStreamsWithPermission) {
+  struct cras_observer_alert_data_input_streams* data;
+  uint32_t num_input_streams[CRAS_NUM_CLIENT_TYPE] = {};
+  for (unsigned type = 0; type < CRAS_NUM_CLIENT_TYPE; ++type) {
+    num_input_streams[type] = (uint32_t)type;
+  }
+
+  cras_observer_notify_input_streams_with_permission(num_input_streams);
+  ASSERT_EQ(cras_alert_pending_data_size_value, sizeof(*data));
+  ASSERT_NE(cras_alert_pending_data_value, reinterpret_cast<void*>(NULL));
+  data = reinterpret_cast<struct cras_observer_alert_data_input_streams*>(
+      cras_alert_pending_data_value);
+  for (unsigned type = 0; type < CRAS_NUM_CLIENT_TYPE; ++type) {
+    EXPECT_EQ(data->num_input_streams[type], num_input_streams[type]);
+  }
+
+  ops1_.num_input_streams_with_permission_changed =
+      cb_num_input_streams_with_permission_changed;
+  ops2_.num_input_streams_with_permission_changed =
+      cb_num_input_streams_with_permission_changed;
+  DoObserverAlert(num_input_streams_with_permission_alert, data);
+  ASSERT_EQ(2, cb_num_input_streams_with_permission_called);
+  for (auto cb_num_input_streams : cb_num_input_streams_with_permission_array) {
+    ASSERT_EQ(cb_num_input_streams.size(), (size_t)CRAS_NUM_CLIENT_TYPE);
+    for (unsigned type = 0; type < CRAS_NUM_CLIENT_TYPE; ++type) {
+      EXPECT_EQ(cb_num_input_streams[type], num_input_streams[type]);
+    }
+  }
+  DoObserverRemoveClear(num_input_streams_with_permission_alert, data);
+}
+
 TEST_F(ObserverTest, NotifyHotwordTriggered) {
   struct cras_observer_alert_data_hotword_triggered* data;
 
@@ -602,6 +652,21 @@
   EXPECT_EQ(data->non_empty, 1);
 }
 
+TEST_F(ObserverTest, BluetoothBatteryChanged) {
+  struct cras_observer_alert_data_bt_battery_changed* data;
+  const char* address = "test";
+
+  cras_observer_notify_bt_battery_changed(address, 30);
+  EXPECT_EQ(cras_alert_pending_alert_value,
+            g_observer->alerts.bt_battery_changed);
+  ASSERT_EQ(cras_alert_pending_data_size_value, sizeof(*data));
+  ASSERT_NE(cras_alert_pending_data_value, reinterpret_cast<void*>(NULL));
+  data = reinterpret_cast<struct cras_observer_alert_data_bt_battery_changed*>(
+      cras_alert_pending_data_value);
+  EXPECT_EQ(data->address, address);
+  EXPECT_EQ(data->level, 30);
+}
+
 // Stubs
 extern "C" {
 
diff --git a/cras/src/tests/playback_rclient_unittest.cc b/cras/src/tests/playback_rclient_unittest.cc
index aa24bc2..31ceda7 100644
--- a/cras/src/tests/playback_rclient_unittest.cc
+++ b/cras/src/tests/playback_rclient_unittest.cc
@@ -18,24 +18,22 @@
 #include "cras_playback_rclient.c"
 #include "cras_rclient_util.c"
 }
+static bool audio_format_valid;
 static unsigned int cras_make_fd_nonblocking_called;
 static unsigned int cras_observer_remove_called;
-static unsigned int cras_server_metrics_stream_config_called;
 static int stream_list_add_called;
 static int stream_list_add_return;
 static unsigned int stream_list_rm_called;
-static struct cras_audio_shm dummy_shm;
-static struct cras_rstream dummy_rstream;
-static unsigned int cras_rstream_config_init_with_message_called;
+static struct cras_audio_shm mock_shm;
+static struct cras_rstream mock_rstream;
 
 void ResetStubData() {
+  audio_format_valid = true;
   cras_make_fd_nonblocking_called = 0;
   cras_observer_remove_called = 0;
-  cras_server_metrics_stream_config_called = 0;
   stream_list_add_called = 0;
   stream_list_add_return = 0;
   stream_list_rm_called = 0;
-  cras_rstream_config_init_with_message_called = 0;
 }
 
 namespace {
@@ -110,13 +108,12 @@
   cras_fill_connect_message(&msg, CRAS_STREAM_OUTPUT, stream_id,
                             CRAS_STREAM_TYPE_DEFAULT, CRAS_CLIENT_TYPE_UNKNOWN,
                             480, 240, /*flags=*/0, /*effects=*/0, fmt,
-                            NO_DEVICE, /*client_shm_size=*/0);
+                            NO_DEVICE);
   ASSERT_EQ(stream_id, msg.stream_id);
 
   fd_ = 100;
   rclient_->ops->handle_message_from_client(rclient_, &msg.header, &fd_, 1);
   EXPECT_EQ(1, cras_make_fd_nonblocking_called);
-  EXPECT_EQ(1, cras_rstream_config_init_with_message_called);
   EXPECT_EQ(1, stream_list_add_called);
   EXPECT_EQ(0, stream_list_rm_called);
 
@@ -138,16 +135,14 @@
       continue;
     cras_fill_connect_message(&msg, dir, stream_id, CRAS_STREAM_TYPE_DEFAULT,
                               CRAS_CLIENT_TYPE_UNKNOWN, 480, 240, /*flags=*/0,
-                              /*effects=*/0, fmt, NO_DEVICE,
-                              /*client_shm_size=*/0);
+                              /*effects=*/0, fmt, NO_DEVICE);
     ASSERT_EQ(stream_id, msg.stream_id);
 
     fd_ = 100;
     rc = rclient_->ops->handle_message_from_client(rclient_, &msg.header, &fd_,
                                                    1);
-    EXPECT_EQ(-EINVAL, rc);
+    EXPECT_EQ(0, rc);
     EXPECT_EQ(0, cras_make_fd_nonblocking_called);
-    EXPECT_EQ(0, cras_rstream_config_init_with_message_called);
     EXPECT_EQ(0, stream_list_add_called);
     EXPECT_EQ(0, stream_list_rm_called);
 
@@ -167,15 +162,14 @@
   cras_fill_connect_message(&msg, CRAS_STREAM_OUTPUT, stream_id,
                             CRAS_STREAM_TYPE_DEFAULT, CRAS_CLIENT_TYPE_UNKNOWN,
                             480, 240, /*flags=*/0, /*effects=*/0, fmt,
-                            NO_DEVICE, /*client_shm_size=*/0);
+                            NO_DEVICE);
   ASSERT_EQ(stream_id, msg.stream_id);
 
   fd_ = 100;
   rc =
       rclient_->ops->handle_message_from_client(rclient_, &msg.header, &fd_, 1);
-  EXPECT_EQ(-EINVAL, rc);
+  EXPECT_EQ(0, rc);
   EXPECT_EQ(0, cras_make_fd_nonblocking_called);
-  EXPECT_EQ(0, cras_rstream_config_init_with_message_called);
   EXPECT_EQ(0, stream_list_add_called);
   EXPECT_EQ(0, stream_list_rm_called);
 
@@ -185,40 +179,31 @@
   EXPECT_EQ(stream_id, out_msg.stream_id);
 }
 
-/*
- * TODO(yuhsaun): Remove this test when there are no client uses the old
- * craslib. (CRAS_PROTO_VER = 3)
- */
-TEST_F(CPRMessageSuite, StreamConnectMessageOldProtocal) {
+TEST_F(CPRMessageSuite, StreamConnectMessageInvalidAudioFormat) {
   struct cras_client_stream_connected out_msg;
   int rc;
 
-  struct cras_connect_message_old msg;
+  struct cras_connect_message msg;
   cras_stream_id_t stream_id = 0x10002;
+  cras_fill_connect_message(&msg, CRAS_STREAM_OUTPUT, stream_id,
+                            CRAS_STREAM_TYPE_DEFAULT, CRAS_CLIENT_TYPE_UNKNOWN,
+                            480, 240, /*flags=*/0, /*effects=*/0, fmt,
+                            NO_DEVICE);
+  ASSERT_EQ(stream_id, msg.stream_id);
 
-  msg.proto_version = 3;
-  msg.direction = CRAS_STREAM_OUTPUT;
-  msg.stream_id = stream_id;
-  msg.stream_type = CRAS_STREAM_TYPE_DEFAULT;
-  msg.buffer_frames = 480;
-  msg.cb_threshold = 240;
-  msg.flags = 0;
-  msg.effects = 0;
-  pack_cras_audio_format(&msg.format, &fmt);
-  msg.dev_idx = NO_DEVICE;
-  msg.header.id = CRAS_SERVER_CONNECT_STREAM;
-  msg.header.length = sizeof(struct cras_connect_message_old);
+  audio_format_valid = false;  // stubs out verification failure.
 
   fd_ = 100;
   rc =
       rclient_->ops->handle_message_from_client(rclient_, &msg.header, &fd_, 1);
-  EXPECT_EQ(1, cras_make_fd_nonblocking_called);
-  EXPECT_EQ(1, cras_rstream_config_init_with_message_called);
-  EXPECT_EQ(1, stream_list_add_called);
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(0, cras_make_fd_nonblocking_called);
+  EXPECT_EQ(0, stream_list_add_called);
   EXPECT_EQ(0, stream_list_rm_called);
 
   rc = read(pipe_fds_[0], &out_msg, sizeof(out_msg));
   EXPECT_EQ(sizeof(out_msg), rc);
+  EXPECT_EQ(-EINVAL, out_msg.err);
   EXPECT_EQ(stream_id, out_msg.stream_id);
 }
 
@@ -268,11 +253,6 @@
   return 0;
 }
 
-int cras_server_metrics_stream_config(struct cras_rstream_config* config) {
-  cras_server_metrics_stream_config_called++;
-  return 0;
-}
-
 int cras_send_with_fds(int sockfd,
                        const void* buf,
                        size_t len,
@@ -302,30 +282,27 @@
                     struct cras_rstream** stream) {
   int ret;
 
-  *stream = &dummy_rstream;
+  *stream = &mock_rstream;
 
   stream_list_add_called++;
   ret = stream_list_add_return;
   if (ret)
     stream_list_add_return = -EINVAL;
 
-  dummy_rstream.shm = &dummy_shm;
-  dummy_rstream.direction = config->direction;
-  dummy_rstream.stream_id = config->stream_id;
+  mock_rstream.shm = &mock_shm;
+  mock_rstream.direction = config->direction;
+  mock_rstream.stream_id = config->stream_id;
 
   return ret;
 }
 
-void cras_rstream_config_init_with_message(
-    struct cras_rclient* client,
-    const struct cras_connect_message* msg,
-    int* aud_fd,
-    int* client_shm_fd,
-    const struct cras_audio_format* remote_fmt,
-    struct cras_rstream_config* stream_config) {
-  cras_rstream_config_init_with_message_called++;
+bool cras_audio_format_valid(const struct cras_audio_format* fmt) {
+  return audio_format_valid;
 }
 
-void cras_rstream_config_cleanup(struct cras_rstream_config* stream_config) {}
+void detect_rtc_stream_pair(struct stream_list* list,
+                            struct cras_rstream* stream) {
+  return;
+}
 
 }  // extern "C"
diff --git a/cras/src/tests/polled_interval_checker_unittest.cc b/cras/src/tests/polled_interval_checker_unittest.cc
index c18fdf7..a4aff09 100644
--- a/cras/src/tests/polled_interval_checker_unittest.cc
+++ b/cras/src/tests/polled_interval_checker_unittest.cc
@@ -70,7 +70,7 @@
 
   struct polled_interval* interval = create_interval();
 
-  // Sanity check.
+  // Initial check.
   EXPECT_FALSE(pic_interval_elapsed(interval));
 
   // Increment time so the interval elapses.
@@ -100,7 +100,7 @@
 
   struct polled_interval* interval = create_interval();
 
-  // Sanity check.
+  // Initial check.
   EXPECT_FALSE(pic_interval_elapsed(interval));
 
   // Increment time so the interval elapses.
diff --git a/cras/src/tests/ramp_unittest.cc b/cras/src/tests/ramp_unittest.cc
index 8d67479..a661dff 100644
--- a/cras/src/tests/ramp_unittest.cc
+++ b/cras/src/tests/ramp_unittest.cc
@@ -315,6 +315,22 @@
   cras_ramp_destroy(ramp);
 }
 
+TEST(RampTestSuite, NullWontCrash) {
+  float from;
+  float to;
+  int duration_frames = 48000;
+  int rc = 0;
+  struct cras_ramp* ramp = NULL;
+
+  ResetStubData();
+
+  from = 0.0;
+  to = 1.0;
+  rc = cras_mute_ramp_start(ramp, from, to, duration_frames, NULL, NULL);
+
+  EXPECT_EQ(-EINVAL, rc);
+}
+
 TEST(RampTestSuite, RampDownWhileHalfWayRampDown) {
   float from = 1.0;
   float to = 0.0;
@@ -354,6 +370,28 @@
   cras_ramp_destroy(ramp);
 }
 
+TEST(RampTestSuite, MuteRamp) {
+  float from = 0.0;
+  float to = 0.0;
+  int duration_frames = 48000;
+  struct cras_ramp* ramp;
+  struct cras_ramp_action action;
+
+  ResetStubData();
+
+  ramp = cras_ramp_create();
+  cras_mute_ramp_start(ramp, from, to, duration_frames, NULL, NULL);
+
+  action = cras_ramp_get_current_action(ramp);
+
+  EXPECT_EQ(CRAS_RAMP_ACTION_PARTIAL, action.type);
+  EXPECT_FLOAT_EQ(0.0, action.scaler);
+  EXPECT_FLOAT_EQ(0.0, action.increment);
+  EXPECT_FLOAT_EQ(0.0, action.target);
+
+  cras_ramp_destroy(ramp);
+}
+
 TEST(RampTestSuite, PartialRamp) {
   float from_one = 0.75;
   float to_one = 0.4;
diff --git a/cras/src/tests/rate_estimator_unittest.cc b/cras/src/tests/rate_estimator_unittest.cc
index 66f7c58..778a3cd 100644
--- a/cras/src/tests/rate_estimator_unittest.cc
+++ b/cras/src/tests/rate_estimator_unittest.cc
@@ -95,11 +95,10 @@
 
 TEST(RateEstimatorTest, EstimateOutputSmooth) {
   struct rate_estimator* re;
-  struct timespec t;
+  struct timespec t = {.tv_sec = 1, .tv_nsec = 0};
   int rc;
 
   re = rate_estimator_create(10010, &window, 0.9f);
-  t.tv_sec = 1;
   rc = rate_estimator_check(re, 240, &t);
   EXPECT_EQ(0, rc);
 
@@ -124,11 +123,10 @@
 
 TEST(RateEstimatorTest, EstimateInputLinear) {
   struct rate_estimator* re;
-  struct timespec t;
+  struct timespec t = {.tv_sec = 1, .tv_nsec = 0};
   int i, rc, level, tmp;
 
   re = rate_estimator_create(10000, &window, 0.0f);
-  t.tv_sec = 1;
   level = 1200;
   for (i = 0; i < 20; i++) {
     rc = rate_estimator_check(re, level, &t);
diff --git a/cras/src/tests/rstream_unittest.cc b/cras/src/tests/rstream_unittest.cc
index 69523f5..d8dae24 100644
--- a/cras/src/tests/rstream_unittest.cc
+++ b/cras/src/tests/rstream_unittest.cc
@@ -16,6 +16,8 @@
 #include "cras_shm.h"
 }
 
+#include "metrics_stub.h"
+
 namespace {
 
 class RstreamTestSuite : public testing::Test {
@@ -30,6 +32,7 @@
 
     config_.stream_id = 555;
     config_.stream_type = CRAS_STREAM_TYPE_DEFAULT;
+    config_.client_type = CRAS_CLIENT_TYPE_UNKNOWN;
     config_.direction = CRAS_STREAM_OUTPUT;
     config_.dev_idx = NO_DEVICE;
     config_.flags = 0;
@@ -376,7 +379,7 @@
   return 0;
 }
 
-int buffer_share_add_id(struct buffer_share* mix, unsigned int id) {
+int buffer_share_add_id(struct buffer_share* mix, unsigned int id, void* data) {
   return 0;
 }
 
@@ -388,14 +391,36 @@
                                     unsigned int id) {
   return 0;
 }
+void ewma_power_init(struct ewma_power* ewma, unsigned int rate) {}
 
-void cras_system_state_stream_added(enum CRAS_STREAM_DIRECTION direction) {}
+void ewma_power_calculate(struct ewma_power* ewma,
+                          const int16_t* buf,
+                          unsigned int channels,
+                          unsigned int size) {}
 
-void cras_system_state_stream_removed(enum CRAS_STREAM_DIRECTION direction) {}
+void cras_system_state_stream_added(enum CRAS_STREAM_DIRECTION direction,
+                                    enum CRAS_CLIENT_TYPE client_type) {}
+
+void cras_system_state_stream_removed(enum CRAS_STREAM_DIRECTION direction,
+                                      enum CRAS_CLIENT_TYPE client_type) {}
+
+int cras_server_metrics_stream_create(
+    const struct cras_rstream_config* config) {
+  return 0;
+}
+
+int cras_server_metrics_stream_destroy(const struct cras_rstream* stream) {
+  return 0;
+}
+
 #ifdef HAVE_WEBRTC_APM
+#define FAKE_CRAS_APM_PTR reinterpret_cast<struct cras_apm*>(0x99)
 struct cras_apm_list* cras_apm_list_create(void* stream_ptr, uint64_t effects) {
   return NULL;
 }
+struct cras_apm* cras_apm_list_get_active_apm(void* stream_ptr, void* dev_ptr) {
+  return FAKE_CRAS_APM_PTR;
+}
 int cras_apm_list_destroy(struct cras_apm_list* list) {
   return 0;
 }
@@ -409,8 +434,4 @@
   return NULL;
 }
 #endif
-
-int cras_server_metrics_missed_cb_frequency(const struct cras_rstream* stream) {
-  return 0;
-}
 }
diff --git a/cras/src/tests/server_metrics_unittest.cc b/cras/src/tests/server_metrics_unittest.cc
index 94a689c..e23906e 100644
--- a/cras/src/tests/server_metrics_unittest.cc
+++ b/cras/src/tests/server_metrics_unittest.cc
@@ -132,20 +132,6 @@
   EXPECT_EQ(sent_msgs[0].data.value, hw_level);
 }
 
-TEST(ServerMetricsTestSuite, SetMetricsLongestFetchDelay) {
-  ResetStubData();
-  unsigned int delay = 100;
-
-  cras_server_metrics_longest_fetch_delay(delay);
-
-  EXPECT_EQ(sent_msgs.size(), 1);
-  EXPECT_EQ(sent_msgs[0].header.type, CRAS_MAIN_METRICS);
-  EXPECT_EQ(sent_msgs[0].header.length,
-            sizeof(struct cras_server_metrics_message));
-  EXPECT_EQ(sent_msgs[0].metrics_type, LONGEST_FETCH_DELAY);
-  EXPECT_EQ(sent_msgs[0].data.value, delay);
-}
-
 TEST(ServerMetricsTestSuite, SetMetricsNumUnderruns) {
   ResetStubData();
   unsigned int underrun = 10;
@@ -160,79 +146,6 @@
   EXPECT_EQ(sent_msgs[0].data.value, underrun);
 }
 
-TEST(ServerMetricsTestSuite, SetMetricsMissedCallbackFrequencyInputStream) {
-  ResetStubData();
-  struct cras_rstream stream;
-  struct timespec diff_ts;
-
-  stream.flags = 0;
-  stream.start_ts.tv_sec = 0;
-  stream.start_ts.tv_nsec = 0;
-  clock_gettime_retspec.tv_sec = 1000;
-  clock_gettime_retspec.tv_nsec = 0;
-  stream.num_missed_cb = 5;
-  stream.first_missed_cb_ts.tv_sec = 100;
-  stream.first_missed_cb_ts.tv_nsec = 0;
-
-  stream.direction = CRAS_STREAM_INPUT;
-  cras_server_metrics_missed_cb_frequency(&stream);
-
-  subtract_timespecs(&clock_gettime_retspec, &stream.start_ts, &diff_ts);
-  EXPECT_EQ(sent_msgs.size(), 2);
-  EXPECT_EQ(sent_msgs[0].header.type, CRAS_MAIN_METRICS);
-  EXPECT_EQ(sent_msgs[0].header.length,
-            sizeof(struct cras_server_metrics_message));
-  EXPECT_EQ(sent_msgs[0].metrics_type, MISSED_CB_FREQUENCY_INPUT);
-  EXPECT_EQ(sent_msgs[0].data.value,
-            stream.num_missed_cb * 86400 / diff_ts.tv_sec);
-
-  subtract_timespecs(&clock_gettime_retspec, &stream.first_missed_cb_ts,
-                     &diff_ts);
-  EXPECT_EQ(sent_msgs[1].header.type, CRAS_MAIN_METRICS);
-  EXPECT_EQ(sent_msgs[1].header.length,
-            sizeof(struct cras_server_metrics_message));
-  EXPECT_EQ(sent_msgs[1].metrics_type,
-            MISSED_CB_FREQUENCY_AFTER_RESCHEDULING_INPUT);
-  EXPECT_EQ(sent_msgs[1].data.value,
-            (stream.num_missed_cb - 1) * 86400 / diff_ts.tv_sec);
-}
-
-TEST(ServerMetricsTestSuite, SetMetricsMissedCallbackFrequencyOutputStream) {
-  ResetStubData();
-  struct cras_rstream stream;
-  struct timespec diff_ts;
-
-  stream.flags = 0;
-  stream.start_ts.tv_sec = 0;
-  stream.start_ts.tv_nsec = 0;
-  clock_gettime_retspec.tv_sec = 1000;
-  clock_gettime_retspec.tv_nsec = 0;
-  stream.num_missed_cb = 5;
-  stream.first_missed_cb_ts.tv_sec = 100;
-  stream.first_missed_cb_ts.tv_nsec = 0;
-  stream.direction = CRAS_STREAM_OUTPUT;
-  cras_server_metrics_missed_cb_frequency(&stream);
-
-  subtract_timespecs(&clock_gettime_retspec, &stream.start_ts, &diff_ts);
-  EXPECT_EQ(sent_msgs.size(), 2);
-  EXPECT_EQ(sent_msgs[0].header.type, CRAS_MAIN_METRICS);
-  EXPECT_EQ(sent_msgs[0].header.length,
-            sizeof(struct cras_server_metrics_message));
-  EXPECT_EQ(sent_msgs[0].metrics_type, MISSED_CB_FREQUENCY_OUTPUT);
-  EXPECT_EQ(sent_msgs[0].data.value,
-            stream.num_missed_cb * 86400 / diff_ts.tv_sec);
-
-  subtract_timespecs(&clock_gettime_retspec, &stream.first_missed_cb_ts,
-                     &diff_ts);
-  EXPECT_EQ(sent_msgs[1].header.type, CRAS_MAIN_METRICS);
-  EXPECT_EQ(sent_msgs[1].header.length,
-            sizeof(struct cras_server_metrics_message));
-  EXPECT_EQ(sent_msgs[1].metrics_type,
-            MISSED_CB_FREQUENCY_AFTER_RESCHEDULING_OUTPUT);
-  EXPECT_EQ(sent_msgs[1].data.value,
-            (stream.num_missed_cb - 1) * 86400 / diff_ts.tv_sec);
-}
-
 TEST(ServerMetricsTestSuite, SetMetricsMissedCallbackEventInputStream) {
   ResetStubData();
   struct cras_rstream stream;
@@ -315,7 +228,7 @@
   EXPECT_EQ(stream.num_missed_cb, 2);
 }
 
-TEST(ServerMetricsTestSuite, SetMetricsStreamConfig) {
+TEST(ServerMetricsTestSuite, SetMetricsStreamCreate) {
   ResetStubData();
   struct cras_rstream_config config;
   struct cras_audio_format format;
@@ -326,10 +239,10 @@
   format.format = SND_PCM_FORMAT_S16_LE;
   format.frame_rate = 48000;
   config.client_type = CRAS_CLIENT_TYPE_TEST;
-
   config.format = &format;
-  cras_server_metrics_stream_config(&config);
+  cras_server_metrics_stream_create(&config);
 
+  // Log stream config.
   EXPECT_EQ(sent_msgs.size(), 1);
   EXPECT_EQ(sent_msgs[0].header.type, CRAS_MAIN_METRICS);
   EXPECT_EQ(sent_msgs[0].header.length,
@@ -343,6 +256,75 @@
   EXPECT_EQ(sent_msgs[0].data.stream_config.client_type, CRAS_CLIENT_TYPE_TEST);
 }
 
+TEST(ServerMetricsTestSuite, SetMetricsStreamDestroy) {
+  ResetStubData();
+  struct cras_rstream stream;
+  struct timespec diff_ts;
+
+  stream.flags = 0;
+  stream.start_ts.tv_sec = 0;
+  stream.start_ts.tv_nsec = 0;
+  clock_gettime_retspec.tv_sec = 1000;
+  clock_gettime_retspec.tv_nsec = 0;
+  stream.num_missed_cb = 5;
+  stream.first_missed_cb_ts.tv_sec = 100;
+  stream.first_missed_cb_ts.tv_nsec = 0;
+  stream.longest_fetch_interval.tv_sec = 1;
+  stream.longest_fetch_interval.tv_nsec = 0;
+  stream.sleep_interval_ts.tv_sec = 0;
+  stream.sleep_interval_ts.tv_nsec = 5000000;
+
+  stream.direction = CRAS_STREAM_INPUT;
+  stream.client_type = CRAS_CLIENT_TYPE_TEST;
+  stream.stream_type = CRAS_STREAM_TYPE_DEFAULT;
+  cras_server_metrics_stream_destroy(&stream);
+
+  subtract_timespecs(&clock_gettime_retspec, &stream.start_ts, &diff_ts);
+  EXPECT_EQ(sent_msgs.size(), 4);
+
+  // Log missed cb frequency.
+  EXPECT_EQ(sent_msgs[0].header.type, CRAS_MAIN_METRICS);
+  EXPECT_EQ(sent_msgs[0].header.length,
+            sizeof(struct cras_server_metrics_message));
+  EXPECT_EQ(sent_msgs[0].metrics_type, MISSED_CB_FREQUENCY_INPUT);
+  EXPECT_EQ(sent_msgs[0].data.value,
+            stream.num_missed_cb * 86400 / diff_ts.tv_sec);
+
+  // Log missed cb frequency after rescheduling.
+  subtract_timespecs(&clock_gettime_retspec, &stream.first_missed_cb_ts,
+                     &diff_ts);
+  EXPECT_EQ(sent_msgs[1].header.type, CRAS_MAIN_METRICS);
+  EXPECT_EQ(sent_msgs[1].header.length,
+            sizeof(struct cras_server_metrics_message));
+  EXPECT_EQ(sent_msgs[1].metrics_type,
+            MISSED_CB_FREQUENCY_AFTER_RESCHEDULING_INPUT);
+  EXPECT_EQ(sent_msgs[1].data.value,
+            (stream.num_missed_cb - 1) * 86400 / diff_ts.tv_sec);
+
+  // Log stream runtime.
+  EXPECT_EQ(sent_msgs[2].header.type, CRAS_MAIN_METRICS);
+  EXPECT_EQ(sent_msgs[2].header.length,
+            sizeof(struct cras_server_metrics_message));
+  EXPECT_EQ(sent_msgs[2].metrics_type, STREAM_RUNTIME);
+  EXPECT_EQ(sent_msgs[2].data.stream_data.client_type, CRAS_CLIENT_TYPE_TEST);
+  EXPECT_EQ(sent_msgs[2].data.stream_data.stream_type,
+            CRAS_STREAM_TYPE_DEFAULT);
+  EXPECT_EQ(sent_msgs[2].data.stream_data.direction, CRAS_STREAM_INPUT);
+  EXPECT_EQ(sent_msgs[2].data.stream_data.runtime.tv_sec, 1000);
+
+  // Log longest fetch delay.
+  EXPECT_EQ(sent_msgs[3].header.type, CRAS_MAIN_METRICS);
+  EXPECT_EQ(sent_msgs[3].header.length,
+            sizeof(struct cras_server_metrics_message));
+  EXPECT_EQ(sent_msgs[3].metrics_type, LONGEST_FETCH_DELAY);
+  EXPECT_EQ(sent_msgs[3].data.stream_data.client_type, CRAS_CLIENT_TYPE_TEST);
+  EXPECT_EQ(sent_msgs[3].data.stream_data.stream_type,
+            CRAS_STREAM_TYPE_DEFAULT);
+  EXPECT_EQ(sent_msgs[3].data.stream_data.direction, CRAS_STREAM_INPUT);
+  EXPECT_EQ(sent_msgs[3].data.stream_data.runtime.tv_sec, 0);
+  EXPECT_EQ(sent_msgs[3].data.stream_data.runtime.tv_nsec, 995000000);
+}
+
 TEST(ServerMetricsTestSuite, SetMetricsBusyloop) {
   ResetStubData();
   struct timespec time = {40, 0};
@@ -360,6 +342,20 @@
   EXPECT_EQ(sent_msgs[0].data.timespec_data.count, 3);
 }
 
+TEST(ServerMetricsTestSuite, SetMetricsBusyloopLength) {
+  ResetStubData();
+  unsigned length = 5;
+
+  cras_server_metrics_busyloop_length(length);
+
+  EXPECT_EQ(sent_msgs.size(), 1);
+  EXPECT_EQ(sent_msgs[0].header.type, CRAS_MAIN_METRICS);
+  EXPECT_EQ(sent_msgs[0].header.length,
+            sizeof(struct cras_server_metrics_message));
+  EXPECT_EQ(sent_msgs[0].metrics_type, BUSYLOOP_LENGTH);
+  EXPECT_EQ(sent_msgs[0].data.value, 5);
+}
+
 extern "C" {
 
 int cras_main_message_add_handler(enum CRAS_MAIN_MESSAGE_TYPE type,
diff --git a/cras/src/tests/stream_list_unittest.cc b/cras/src/tests/stream_list_unittest.cc
index 8f3c2e3..500774f 100644
--- a/cras/src/tests/stream_list_unittest.cc
+++ b/cras/src/tests/stream_list_unittest.cc
@@ -28,13 +28,20 @@
 
 static unsigned int create_called;
 static struct cras_rstream_config* create_config;
-static struct cras_rstream dummy_rstream;
 static int create_rstream_cb(struct cras_rstream_config* stream_config,
                              struct cras_rstream** stream) {
   create_called++;
   create_config = stream_config;
-  *stream = &dummy_rstream;
-  dummy_rstream.stream_id = 0x3003;
+  *stream = (struct cras_rstream*)malloc(sizeof(struct cras_rstream));
+  (*stream)->stream_id = stream_config->stream_id;
+  (*stream)->direction = stream_config->direction;
+  if (stream_config->format)
+    (*stream)->format = *(stream_config->format);
+  (*stream)->cb_threshold = stream_config->cb_threshold;
+  (*stream)->client_type = stream_config->client_type;
+  (*stream)->stream_type = stream_config->stream_type;
+  clock_gettime(CLOCK_MONOTONIC_RAW, &(*stream)->start_ts);
+
   return 0;
 }
 
@@ -43,6 +50,7 @@
 static void destroy_rstream_cb(struct cras_rstream* rstream) {
   destroy_called++;
   destroyed_stream = rstream;
+  free(rstream);
 }
 
 static void reset_test_data() {
@@ -57,6 +65,10 @@
   struct cras_rstream* s1;
   struct cras_rstream_config s1_config;
 
+  s1_config.stream_id = 0x3003;
+  s1_config.direction = CRAS_STREAM_OUTPUT;
+  s1_config.format = NULL;
+
   reset_test_data();
   l = stream_list_create(added_cb, removed_cb, create_rstream_cb,
                          destroy_rstream_cb, NULL);
@@ -72,6 +84,117 @@
   stream_list_destroy(l);
 }
 
+TEST(StreamList, AddInDescendingOrderByChannels) {
+  struct stream_list* l;
+  struct cras_rstream* s1;
+  struct cras_rstream* s2;
+  struct cras_rstream* s3;
+  struct cras_audio_format s1_format, s2_format, s3_format;
+  struct cras_rstream_config s1_config, s2_config, s3_config;
+
+  s1_config.stream_id = 0x4001;
+  s1_config.direction = CRAS_STREAM_INPUT;
+  s1_format.num_channels = 6;
+  s1_config.format = &s1_format;
+
+  s2_config.stream_id = 0x4002;
+  s2_config.direction = CRAS_STREAM_OUTPUT;
+  s2_format.num_channels = 8;
+  s2_config.format = &s2_format;
+
+  s3_config.stream_id = 0x4003;
+  s3_config.direction = CRAS_STREAM_OUTPUT;
+  s3_format.num_channels = 2;
+  s3_config.format = &s3_format;
+
+  reset_test_data();
+  l = stream_list_create(added_cb, removed_cb, create_rstream_cb,
+                         destroy_rstream_cb, NULL);
+  stream_list_add(l, &s1_config, &s1);
+  EXPECT_EQ(1, add_called);
+  EXPECT_EQ(1, create_called);
+  EXPECT_EQ(6, stream_list_get(l)->format.num_channels);
+
+  stream_list_add(l, &s2_config, &s2);
+  EXPECT_EQ(2, add_called);
+  EXPECT_EQ(2, create_called);
+  EXPECT_EQ(8, stream_list_get(l)->format.num_channels);
+  EXPECT_EQ(6, stream_list_get(l)->next->format.num_channels);
+
+  stream_list_add(l, &s3_config, &s3);
+  EXPECT_EQ(3, add_called);
+  EXPECT_EQ(3, create_called);
+  EXPECT_EQ(8, stream_list_get(l)->format.num_channels);
+  EXPECT_EQ(6, stream_list_get(l)->next->format.num_channels);
+  EXPECT_EQ(2, stream_list_get(l)->next->next->format.num_channels);
+  EXPECT_EQ(0, stream_list_rm(l, 0x4001));
+  EXPECT_EQ(0, stream_list_rm(l, 0x4002));
+  EXPECT_EQ(0, stream_list_rm(l, 0x4003));
+  stream_list_destroy(l);
+}
+
+TEST(StreamList, DetectRtcStreamPair) {
+  struct stream_list* l;
+  struct cras_rstream *s1, *s2, *s3, *s4;
+  struct cras_rstream_config s1_config, s2_config, s3_config, s4_config;
+
+  s1_config.stream_id = 0x5001;
+  s1_config.direction = CRAS_STREAM_OUTPUT;
+  s1_config.cb_threshold = 480;
+  s1_config.client_type = CRAS_CLIENT_TYPE_CHROME;
+  s1_config.stream_type = CRAS_STREAM_TYPE_DEFAULT;
+  s1_config.format = NULL;
+
+  s2_config.stream_id = 0x5002;
+  s2_config.direction = CRAS_STREAM_INPUT;
+  s2_config.cb_threshold = 480;
+  s2_config.client_type = CRAS_CLIENT_TYPE_CHROME;
+  s2_config.stream_type = CRAS_STREAM_TYPE_DEFAULT;
+  s2_config.format = NULL;
+
+  // s3 is not a RTC stream because the cb threshold is not 480.
+  s3_config.stream_id = 0x5003;
+  s3_config.direction = CRAS_STREAM_INPUT;
+  s3_config.cb_threshold = 500;
+  s3_config.client_type = CRAS_CLIENT_TYPE_CHROME;
+  s3_config.stream_type = CRAS_STREAM_TYPE_DEFAULT;
+  s3_config.format = NULL;
+
+  // s4 is not a RTC stream because it is not from the same client with s1.
+  s4_config.stream_id = 0x5004;
+  s4_config.direction = CRAS_STREAM_INPUT;
+  s4_config.cb_threshold = 480;
+  s4_config.client_type = CRAS_CLIENT_TYPE_LACROS;
+  s4_config.stream_type = CRAS_STREAM_TYPE_DEFAULT;
+  s4_config.format = NULL;
+
+  reset_test_data();
+  l = stream_list_create(added_cb, removed_cb, create_rstream_cb,
+                         destroy_rstream_cb, NULL);
+  stream_list_add(l, &s1_config, &s1);
+  EXPECT_EQ(1, add_called);
+  EXPECT_EQ(1, create_called);
+  EXPECT_EQ(&s1_config, create_config);
+
+  stream_list_add(l, &s2_config, &s2);
+  detect_rtc_stream_pair(l, s2);
+  stream_list_add(l, &s3_config, &s3);
+  detect_rtc_stream_pair(l, s3);
+  stream_list_add(l, &s4_config, &s4);
+  detect_rtc_stream_pair(l, s4);
+
+  EXPECT_EQ(CRAS_STREAM_TYPE_VOICE_COMMUNICATION, s1->stream_type);
+  EXPECT_EQ(CRAS_STREAM_TYPE_VOICE_COMMUNICATION, s2->stream_type);
+  EXPECT_EQ(CRAS_STREAM_TYPE_DEFAULT, s3->stream_type);
+  EXPECT_EQ(CRAS_STREAM_TYPE_DEFAULT, s4->stream_type);
+
+  EXPECT_EQ(0, stream_list_rm(l, 0x5001));
+  EXPECT_EQ(0, stream_list_rm(l, 0x5002));
+  EXPECT_EQ(0, stream_list_rm(l, 0x5003));
+  EXPECT_EQ(0, stream_list_rm(l, 0x5004));
+  stream_list_destroy(l);
+}
+
 extern "C" {
 
 struct cras_timer* cras_tm_create_timer(struct cras_tm* tm,
diff --git a/cras/src/tests/system_state_unittest.cc b/cras/src/tests/system_state_unittest.cc
index 9e7ecc8..45224bc 100644
--- a/cras/src/tests/system_state_unittest.cc
+++ b/cras/src/tests/system_state_unittest.cc
@@ -4,9 +4,11 @@
 
 #include <gtest/gtest.h>
 #include <stdio.h>
+#include <string.h>
 
 extern "C" {
 #include "cras_alert.h"
+#include "cras_board_config.h"
 #include "cras_shm.h"
 #include "cras_system_state.h"
 #include "cras_types.h"
@@ -33,10 +35,13 @@
 static const char* cras_alsa_card_config_dir;
 static size_t cras_observer_notify_output_volume_called;
 static size_t cras_observer_notify_output_mute_called;
-static size_t cras_observer_notify_capture_gain_called;
 static size_t cras_observer_notify_capture_mute_called;
 static size_t cras_observer_notify_suspend_changed_called;
 static size_t cras_observer_notify_num_active_streams_called;
+static size_t cras_observer_notify_input_streams_with_permission_called;
+static size_t cras_iodev_list_reset_for_noise_cancellation_called;
+static struct cras_board_config fake_board_config;
+static size_t cras_alert_process_all_pending_alerts_called;
 
 static void ResetStubData() {
   cras_alsa_card_create_called = 0;
@@ -53,15 +58,19 @@
   cras_alsa_card_config_dir = NULL;
   cras_observer_notify_output_volume_called = 0;
   cras_observer_notify_output_mute_called = 0;
-  cras_observer_notify_capture_gain_called = 0;
   cras_observer_notify_capture_mute_called = 0;
   cras_observer_notify_suspend_changed_called = 0;
   cras_observer_notify_num_active_streams_called = 0;
+  cras_observer_notify_input_streams_with_permission_called = 0;
+  cras_alert_process_all_pending_alerts_called = 0;
+  cras_iodev_list_reset_for_noise_cancellation_called = 0;
+  memset(&fake_board_config, 0, sizeof(fake_board_config));
 }
 
 static int add_stub(int fd,
-                    void (*cb)(void* data),
+                    void (*cb)(void* data, int revents),
                     void* callback_data,
+                    int events,
                     void* select_data) {
   add_stub_called++;
   select_data_value = select_data;
@@ -81,7 +90,11 @@
   return 0;
 }
 
-static void callback_stub(void* data) {
+static void callback_stub(void* data, int revents) {
+  callback_stub_called++;
+}
+
+static void task_stub(void* data) {
   callback_stub_called++;
 }
 
@@ -103,7 +116,6 @@
 TEST(SystemStateSuite, DefaultVolume) {
   do_sys_init();
   EXPECT_EQ(100, cras_system_get_volume());
-  EXPECT_EQ(2000, cras_system_get_capture_gain());
   EXPECT_EQ(0, cras_system_get_mute());
   EXPECT_EQ(0, cras_system_get_capture_mute());
   cras_system_state_deinit();
@@ -131,46 +143,6 @@
   cras_system_state_deinit();
 }
 
-TEST(SystemStateSuite, SetCaptureVolume) {
-  do_sys_init();
-  cras_system_set_capture_gain(0);
-  EXPECT_EQ(0, cras_system_get_capture_gain());
-  cras_system_set_capture_gain(3000);
-  EXPECT_EQ(3000, cras_system_get_capture_gain());
-  // Check that it is limited to the minimum allowed gain.
-  cras_system_set_capture_gain(-10000);
-  EXPECT_EQ(-5000, cras_system_get_capture_gain());
-  cras_system_state_deinit();
-  EXPECT_EQ(3, cras_observer_notify_capture_gain_called);
-}
-
-TEST(SystemStateSuite, SetCaptureVolumeStoreTarget) {
-  do_sys_init();
-  cras_system_set_capture_gain_limits(-2000, 2000);
-  cras_system_set_capture_gain(3000);
-  // Gain is within the limit.
-  EXPECT_EQ(2000, cras_system_get_capture_gain());
-
-  // Assume the range is changed.
-  cras_system_set_capture_gain_limits(-4000, 4000);
-
-  // Gain is also changed because target gain is re-applied.
-  EXPECT_EQ(3000, cras_system_get_capture_gain());
-
-  cras_system_state_deinit();
-}
-
-TEST(SystemStateSuite, SetMinMaxCaptureGain) {
-  do_sys_init();
-  cras_system_set_capture_gain(3000);
-  cras_system_set_capture_gain_limits(-2000, 2000);
-  EXPECT_EQ(-2000, cras_system_get_min_capture_gain());
-  EXPECT_EQ(2000, cras_system_get_max_capture_gain());
-  // Current gain is adjusted for range.
-  EXPECT_EQ(2000, cras_system_get_capture_gain());
-  cras_system_state_deinit();
-}
-
 TEST(SystemStateSuite, SetUserMute) {
   ResetStubData();
   do_sys_init();
@@ -307,6 +279,7 @@
 
   cras_system_set_suspended(1);
   EXPECT_EQ(1, cras_observer_notify_suspend_changed_called);
+  EXPECT_EQ(1, cras_alert_process_all_pending_alerts_called);
   EXPECT_EQ(1, cras_system_get_suspended());
 
   cras_system_set_suspended(0);
@@ -357,7 +330,7 @@
 
   ResetStubData();
   do_sys_init();
-  rc = cras_system_add_select_fd(7, callback_stub, stub_data);
+  rc = cras_system_add_select_fd(7, callback_stub, stub_data, POLLIN);
   EXPECT_NE(0, rc);
   EXPECT_EQ(0, add_stub_called);
   EXPECT_EQ(0, rm_stub_called);
@@ -369,7 +342,7 @@
   EXPECT_EQ(-EEXIST, rc);
   EXPECT_EQ(0, add_stub_called);
   EXPECT_EQ(0, rm_stub_called);
-  rc = cras_system_add_select_fd(7, callback_stub, stub_data);
+  rc = cras_system_add_select_fd(7, callback_stub, stub_data, POLLIN);
   EXPECT_EQ(0, rc);
   EXPECT_EQ(1, add_stub_called);
   EXPECT_EQ(select_data, select_data_value);
@@ -386,13 +359,13 @@
   int rc;
 
   do_sys_init();
-  rc = cras_system_add_task(callback_stub, stub_data);
+  rc = cras_system_add_task(task_stub, stub_data);
   EXPECT_NE(0, rc);
   EXPECT_EQ(0, add_task_stub_called);
   rc = cras_system_set_add_task_handler(add_task_stub, task_data);
   EXPECT_EQ(0, rc);
   EXPECT_EQ(0, add_task_stub_called);
-  rc = cras_system_add_task(callback_stub, stub_data);
+  rc = cras_system_add_task(task_stub, stub_data);
   EXPECT_EQ(0, rc);
   EXPECT_EQ(1, add_task_stub_called);
   EXPECT_EQ(task_data, task_data_value);
@@ -405,11 +378,11 @@
   do_sys_init();
 
   EXPECT_EQ(0, cras_system_state_get_active_streams());
-  cras_system_state_stream_added(CRAS_STREAM_OUTPUT);
+  cras_system_state_stream_added(CRAS_STREAM_OUTPUT, CRAS_CLIENT_TYPE_CHROME);
   EXPECT_EQ(1, cras_system_state_get_active_streams());
   struct cras_timespec ts1;
   cras_system_state_get_last_stream_active_time(&ts1);
-  cras_system_state_stream_removed(CRAS_STREAM_OUTPUT);
+  cras_system_state_stream_removed(CRAS_STREAM_OUTPUT, CRAS_CLIENT_TYPE_CHROME);
   EXPECT_EQ(0, cras_system_state_get_active_streams());
   struct cras_timespec ts2;
   cras_system_state_get_last_stream_active_time(&ts2);
@@ -422,9 +395,11 @@
   do_sys_init();
 
   EXPECT_EQ(0, cras_system_state_get_active_streams());
-  cras_system_state_stream_added(CRAS_STREAM_OUTPUT);
-  cras_system_state_stream_added(CRAS_STREAM_INPUT);
-  cras_system_state_stream_added(CRAS_STREAM_POST_MIX_PRE_DSP);
+  cras_system_state_stream_added(CRAS_STREAM_OUTPUT, CRAS_CLIENT_TYPE_CHROME);
+  cras_system_state_stream_added(CRAS_STREAM_INPUT, CRAS_CLIENT_TYPE_CHROME);
+  cras_system_state_stream_added(CRAS_STREAM_POST_MIX_PRE_DSP,
+                                 CRAS_CLIENT_TYPE_CHROME);
+  EXPECT_EQ(1, cras_observer_notify_input_streams_with_permission_called);
   EXPECT_EQ(
       1, cras_system_state_get_active_streams_by_direction(CRAS_STREAM_OUTPUT));
   EXPECT_EQ(
@@ -433,9 +408,11 @@
                    CRAS_STREAM_POST_MIX_PRE_DSP));
   EXPECT_EQ(3, cras_system_state_get_active_streams());
   EXPECT_EQ(3, cras_observer_notify_num_active_streams_called);
-  cras_system_state_stream_removed(CRAS_STREAM_OUTPUT);
-  cras_system_state_stream_removed(CRAS_STREAM_INPUT);
-  cras_system_state_stream_removed(CRAS_STREAM_POST_MIX_PRE_DSP);
+  cras_system_state_stream_removed(CRAS_STREAM_OUTPUT, CRAS_CLIENT_TYPE_CHROME);
+  cras_system_state_stream_removed(CRAS_STREAM_INPUT, CRAS_CLIENT_TYPE_CHROME);
+  cras_system_state_stream_removed(CRAS_STREAM_POST_MIX_PRE_DSP,
+                                   CRAS_CLIENT_TYPE_CHROME);
+  EXPECT_EQ(2, cras_observer_notify_input_streams_with_permission_called);
   EXPECT_EQ(
       0, cras_system_state_get_active_streams_by_direction(CRAS_STREAM_OUTPUT));
   EXPECT_EQ(
@@ -448,12 +425,50 @@
   cras_system_state_deinit();
 }
 
+TEST(SystemStateSuite, IgnoreUCMSuffix) {
+  fake_board_config.ucm_ignore_suffix = strdup("TEST1,TEST2,TEST3");
+  do_sys_init();
+
+  EXPECT_EQ(1, cras_system_check_ignore_ucm_suffix("TEST1"));
+  EXPECT_EQ(1, cras_system_check_ignore_ucm_suffix("TEST2"));
+  EXPECT_EQ(1, cras_system_check_ignore_ucm_suffix("TEST3"));
+  EXPECT_EQ(0, cras_system_check_ignore_ucm_suffix("TEST4"));
+  cras_system_state_deinit();
+}
+
+TEST(SystemStateSuite, SetNoiseCancellationEnabled) {
+  ResetStubData();
+  do_sys_init();
+
+  EXPECT_EQ(0, cras_system_get_noise_cancellation_enabled());
+
+  cras_system_set_noise_cancellation_enabled(0);
+  EXPECT_EQ(0, cras_system_get_noise_cancellation_enabled());
+  EXPECT_EQ(0, cras_iodev_list_reset_for_noise_cancellation_called);
+
+  cras_system_set_noise_cancellation_enabled(1);
+  EXPECT_EQ(1, cras_system_get_noise_cancellation_enabled());
+  EXPECT_EQ(1, cras_iodev_list_reset_for_noise_cancellation_called);
+
+  cras_system_set_noise_cancellation_enabled(1);
+  EXPECT_EQ(1, cras_system_get_noise_cancellation_enabled());
+  // cras_iodev_list_reset_for_noise_cancellation shouldn't be called if state
+  // is already enabled/disabled.
+  EXPECT_EQ(1, cras_iodev_list_reset_for_noise_cancellation_called);
+
+  cras_system_set_noise_cancellation_enabled(0);
+  EXPECT_EQ(0, cras_system_get_noise_cancellation_enabled());
+  EXPECT_EQ(2, cras_iodev_list_reset_for_noise_cancellation_called);
+
+  cras_system_state_deinit();
+}
+
 extern "C" {
 
 struct cras_alsa_card* cras_alsa_card_create(
     struct cras_alsa_card_info* info,
     const char* device_config_dir,
-    struct cras_device_blacklist* blacklist) {
+    struct cras_device_blocklist* blocklist) {
   cras_alsa_card_create_called++;
   cras_alsa_card_config_dir = device_config_dir;
   return kFakeAlsaCard;
@@ -467,12 +482,12 @@
   return 0;
 }
 
-struct cras_device_blacklist* cras_device_blacklist_create(
+struct cras_device_blocklist* cras_device_blocklist_create(
     const char* config_path) {
   return NULL;
 }
 
-void cras_device_blacklist_destroy(struct cras_device_blacklist* blacklist) {}
+void cras_device_blocklist_destroy(struct cras_device_blocklist* blocklist) {}
 
 struct cras_alert* cras_alert_create(cras_alert_prepare prepare,
                                      unsigned int flags) {
@@ -521,10 +536,6 @@
   cras_observer_notify_output_mute_called++;
 }
 
-void cras_observer_notify_capture_gain(int32_t gain) {
-  cras_observer_notify_capture_gain_called++;
-}
-
 void cras_observer_notify_capture_mute(int muted, int mute_locked) {
   cras_observer_notify_capture_mute_called++;
 }
@@ -538,6 +549,24 @@
   cras_observer_notify_num_active_streams_called++;
 }
 
+void cras_observer_notify_input_streams_with_permission(
+    uint32_t num_input_streams[CRAS_NUM_CLIENT_TYPE]) {
+  cras_observer_notify_input_streams_with_permission_called++;
+}
+
+void cras_board_config_get(const char* config_path,
+                           struct cras_board_config* board_config) {
+  *board_config = fake_board_config;
+}
+
+void cras_alert_process_all_pending_alerts() {
+  cras_alert_process_all_pending_alerts_called++;
+}
+
+void cras_iodev_list_reset_for_noise_cancellation() {
+  cras_iodev_list_reset_for_noise_cancellation_called++;
+}
+
 }  // extern "C"
 }  // namespace
 
diff --git a/cras/src/tests/timing_unittest.cc b/cras/src/tests/timing_unittest.cc
index beba2f7..964f30c 100644
--- a/cras/src/tests/timing_unittest.cc
+++ b/cras/src/tests/timing_unittest.cc
@@ -93,10 +93,9 @@
     // Set response for frames_queued.
     iodev_stub_frames_queued(dev->dev.get(), dev_level, *level_timestamp);
 
-    struct timespec dev_time, now;
+    struct timespec dev_time;
     dev_time.tv_sec = level_timestamp->tv_sec + 500;  // Far in the future.
-    clock_gettime(CLOCK_MONOTONIC_RAW, &now);
-    dev_io_next_output_wake(&dev_list_, &dev_time, &now);
+    dev_io_next_output_wake(&dev_list_, &dev_time);
     return dev_time;
   }
 };
@@ -112,20 +111,21 @@
 
 // Add a new input stream, make sure the initial next_cb_ts is 0.
 TEST_F(TimingSuite, NewInputStreamInit) {
-  struct open_dev* dev_list_ = NULL;
+  struct open_dev* odev_list_ = NULL;
+  struct open_dev* idev_list_ = NULL;
 
   cras_audio_format format;
   fill_audio_format(&format, 48000);
   DevicePtr dev =
       create_device(CRAS_STREAM_INPUT, 1024, &format, CRAS_NODE_TYPE_MIC);
-  DL_APPEND(dev_list_, dev->odev.get());
+  DL_APPEND(idev_list_, dev->odev.get());
   struct cras_iodev* iodev = dev->odev->dev;
 
   ShmPtr shm = create_shm(480);
   RstreamPtr rstream =
       create_rstream(1, CRAS_STREAM_INPUT, 480, &format, shm.get());
 
-  dev_io_append_stream(&dev_list_, rstream.get(), &iodev, 1);
+  dev_io_append_stream(&odev_list_, &idev_list_, rstream.get(), &iodev, 1);
 
   EXPECT_EQ(0, rstream->next_cb_ts.tv_sec);
   EXPECT_EQ(0, rstream->next_cb_ts.tv_nsec);
@@ -807,23 +807,68 @@
 
 // When a new output stream is added, there are two rules to determine the
 // initial next_cb_ts.
-// 1. If the device already has streams, the next_cb_ts will be the earliest
+// 1. If there is a matched input stream, use the next_cb_ts and
+//    sleep_interval_ts from that input stream as the initial values.
+// 2. If the device already has streams, the next_cb_ts will be the earliest
 // next callback time from these streams.
-// 2. If there are no other streams, the next_cb_ts will be set to the time
+// 3. If there are no other streams, the next_cb_ts will be set to the time
 // when the valid frames in device is lower than cb_threshold. (If it is
 // already lower than cb_threshold, set next_cb_ts to now.)
 
 // Test rule 1.
+// There is a matched input stream. The next_cb_ts of the newly added output
+// stream will use the next_cb_ts from the input stream.
+TEST_F(TimingSuite, NewOutputStreamInitExistMatchedStream) {
+  struct open_dev* odev_list_ = NULL;
+  struct open_dev* idev_list_ = NULL;
+
+  cras_audio_format format;
+  fill_audio_format(&format, 48000);
+  DevicePtr out_dev = create_device(CRAS_STREAM_OUTPUT, 1024, &format,
+                                    CRAS_NODE_TYPE_HEADPHONE);
+  DL_APPEND(odev_list_, out_dev->odev.get());
+  struct cras_iodev* out_iodev = out_dev->odev->dev;
+
+  DevicePtr in_dev =
+      create_device(CRAS_STREAM_INPUT, 1024, &format, CRAS_NODE_TYPE_MIC);
+  DL_APPEND(idev_list_, in_dev->odev.get());
+
+  StreamPtr in_stream = create_stream(1, 1, CRAS_STREAM_INPUT, 480, &format);
+  add_stream_to_dev(in_dev->dev, in_stream);
+  in_stream->rstream->next_cb_ts.tv_sec = 54321;
+  in_stream->rstream->next_cb_ts.tv_nsec = 12345;
+  in_stream->rstream->sleep_interval_ts.tv_sec = 321;
+  in_stream->rstream->sleep_interval_ts.tv_nsec = 123;
+
+  ShmPtr shm = create_shm(480);
+  RstreamPtr rstream =
+      create_rstream(1, CRAS_STREAM_OUTPUT, 480, &format, shm.get());
+
+  dev_io_append_stream(&odev_list_, &idev_list_, rstream.get(), &out_iodev, 1);
+
+  EXPECT_EQ(in_stream->rstream->next_cb_ts.tv_sec, rstream->next_cb_ts.tv_sec);
+  EXPECT_EQ(in_stream->rstream->next_cb_ts.tv_nsec,
+            rstream->next_cb_ts.tv_nsec);
+  EXPECT_EQ(in_stream->rstream->sleep_interval_ts.tv_sec,
+            rstream->sleep_interval_ts.tv_sec);
+  EXPECT_EQ(in_stream->rstream->sleep_interval_ts.tv_nsec,
+            rstream->sleep_interval_ts.tv_nsec);
+
+  dev_stream_destroy(out_iodev->streams);
+}
+
+// Test rule 2.
 // The device already has streams, the next_cb_ts will be the earliest
 // next_cb_ts from these streams.
 TEST_F(TimingSuite, NewOutputStreamInitStreamInDevice) {
-  struct open_dev* dev_list_ = NULL;
+  struct open_dev* odev_list_ = NULL;
+  struct open_dev* idev_list_ = NULL;
 
   cras_audio_format format;
   fill_audio_format(&format, 48000);
   DevicePtr dev = create_device(CRAS_STREAM_OUTPUT, 1024, &format,
                                 CRAS_NODE_TYPE_HEADPHONE);
-  DL_APPEND(dev_list_, dev->odev.get());
+  DL_APPEND(odev_list_, dev->odev.get());
   struct cras_iodev* iodev = dev->odev->dev;
 
   StreamPtr stream = create_stream(1, 1, CRAS_STREAM_OUTPUT, 480, &format);
@@ -835,7 +880,7 @@
   RstreamPtr rstream =
       create_rstream(1, CRAS_STREAM_OUTPUT, 480, &format, shm.get());
 
-  dev_io_append_stream(&dev_list_, rstream.get(), &iodev, 1);
+  dev_io_append_stream(&odev_list_, &idev_list_, rstream.get(), &iodev, 1);
 
   EXPECT_EQ(stream->rstream->next_cb_ts.tv_sec, rstream->next_cb_ts.tv_sec);
   EXPECT_EQ(stream->rstream->next_cb_ts.tv_nsec, rstream->next_cb_ts.tv_nsec);
@@ -843,17 +888,18 @@
   dev_stream_destroy(iodev->streams->next);
 }
 
-// Test rule 2.
+// Test rule 3.
 // The there are no streams and no frames in device buffer. The next_cb_ts
 // will be set to now.
 TEST_F(TimingSuite, NewOutputStreamInitNoStreamNoFramesInDevice) {
-  struct open_dev* dev_list_ = NULL;
+  struct open_dev* odev_list_ = NULL;
+  struct open_dev* idev_list_ = NULL;
 
   cras_audio_format format;
   fill_audio_format(&format, 48000);
   DevicePtr dev = create_device(CRAS_STREAM_OUTPUT, 1024, &format,
                                 CRAS_NODE_TYPE_HEADPHONE);
-  DL_APPEND(dev_list_, dev->odev.get());
+  DL_APPEND(odev_list_, dev->odev.get());
   struct cras_iodev* iodev = dev->odev->dev;
 
   struct timespec start;
@@ -863,7 +909,7 @@
   RstreamPtr rstream =
       create_rstream(1, CRAS_STREAM_OUTPUT, 480, &format, shm.get());
 
-  dev_io_append_stream(&dev_list_, rstream.get(), &iodev, 1);
+  dev_io_append_stream(&odev_list_, &idev_list_, rstream.get(), &iodev, 1);
 
   EXPECT_EQ(start.tv_sec, rstream->next_cb_ts.tv_sec);
   EXPECT_EQ(start.tv_nsec, rstream->next_cb_ts.tv_nsec);
@@ -876,13 +922,14 @@
 // next_cb_ts will be set to the time that valid frames in device is lower
 // than cb_threshold.
 TEST_F(TimingSuite, NewOutputStreamInitNoStreamSomeFramesInDevice) {
-  struct open_dev* dev_list_ = NULL;
+  struct open_dev* odev_list_ = NULL;
+  struct open_dev* idev_list_ = NULL;
 
   cras_audio_format format;
   fill_audio_format(&format, 48000);
   DevicePtr dev = create_device(CRAS_STREAM_OUTPUT, 1024, &format,
                                 CRAS_NODE_TYPE_HEADPHONE);
-  DL_APPEND(dev_list_, dev->odev.get());
+  DL_APPEND(odev_list_, dev->odev.get());
   struct cras_iodev* iodev = dev->odev->dev;
 
   struct timespec start;
@@ -894,7 +941,7 @@
   RstreamPtr rstream =
       create_rstream(1, CRAS_STREAM_OUTPUT, 480, &format, shm.get());
 
-  dev_io_append_stream(&dev_list_, rstream.get(), &iodev, 1);
+  dev_io_append_stream(&odev_list_, &idev_list_, rstream.get(), &iodev, 1);
 
   // The next_cb_ts should be 10ms from now. At that time there are
   // only 480 valid frames in the device.
@@ -1163,6 +1210,12 @@
   return 0;
 }
 
+float input_data_get_software_gain_scaler(struct input_data* data,
+                                          float idev_sw_gain_scaler,
+                                          struct cras_rstream* stream) {
+  return 1.0;
+}
+
 struct cras_audio_format* cras_rstream_post_processing_format(
     const struct cras_rstream* stream,
     void* dev_ptr) {
@@ -1173,6 +1226,15 @@
   return 0;
 }
 
+int cras_audio_thread_event_severe_underrun() {
+  return 0;
+}
+
+void* buffer_share_get_data(const struct buffer_share* mix, unsigned int id) {
+  return NULL;
+};
+void cras_apm_list_start_apm(struct cras_apm_list* list, void* dev_ptr){};
+void cras_apm_list_stop_apm(struct cras_apm_list* list, void* dev_ptr){};
 }  // extern "C"
 
 }  //  namespace
diff --git a/cras/src/tools/cras_monitor/cras_monitor.c b/cras/src/tools/cras_monitor/cras_monitor.c
index d021669..1925277 100644
--- a/cras/src/tools/cras_monitor/cras_monitor.c
+++ b/cras/src/tools/cras_monitor/cras_monitor.c
@@ -31,11 +31,6 @@
 	       muted, user_muted, mute_locked);
 }
 
-static void capture_gain_changed(void *context, int32_t gain)
-{
-	printf("capture gain: %d\n", gain);
-}
-
 static void capture_mute_changed(void *context, int muted, int mute_locked)
 {
 	printf("capture mute: muted: %d, mute_locked: %d\n", muted,
@@ -271,8 +266,6 @@
 						       output_volume_changed);
 	cras_client_set_output_mute_changed_callback(client,
 						     output_mute_changed);
-	cras_client_set_capture_gain_changed_callback(client,
-						      capture_gain_changed);
 	cras_client_set_capture_mute_changed_callback(client,
 						      capture_mute_changed);
 	cras_client_set_nodes_changed_callback(client, nodes_changed);
diff --git a/cras/src/tools/cras_test_client/cras_test_client.c b/cras/src/tools/cras_test_client/cras_test_client.c
index a784506..7a85185 100644
--- a/cras/src/tools/cras_test_client/cras_test_client.c
+++ b/cras/src/tools/cras_test_client/cras_test_client.c
@@ -310,7 +310,7 @@
 	int rc = 0;
 	uint32_t frame_bytes = cras_client_format_bytes_per_frame(aud_format);
 
-	rc = read(0, playback_samples, frames * frame_bytes);
+	rc = read(0, playback_samples, (size_t)frames * (size_t)frame_bytes);
 	if (rc <= 0) {
 		terminate_stream_loop();
 		return -1;
@@ -354,30 +354,50 @@
 {
 	unsigned i;
 
-	printf("\tID\tName\n");
+	printf("\tID\tMaxCha\tName\n");
 	for (i = 0; i < num_devs; i++)
-		printf("\t%u\t%s\n", devs[i].idx, devs[i].name);
+		printf("\t%u\t%u\t%s\n", devs[i].idx,
+		       devs[i].max_supported_channels, devs[i].name);
 }
 
-static void print_node_info(const struct cras_ionode_info *nodes, int num_nodes,
+static void print_node_info(struct cras_client *client,
+			    const struct cras_ionode_info *nodes, int num_nodes,
 			    int is_input)
 {
 	unsigned i;
 
-	printf("\tStable Id\t ID\t%4s   Plugged\tL/R swapped\t      "
-	       "Time Hotword\tType\t\t Name\n",
+	printf("\tStable Id\t ID\t%4s  UI       Plugged\tL/R swapped\t      "
+	       "Time Hotword\tType\t\tMaxCha Name\n",
 	       is_input ? "Gain" : " Vol");
-	for (i = 0; i < num_nodes; i++)
-		printf("\t(%08x)\t%u:%u\t%5g  %7s\t%14s\t%10ld %-7s\t%-16s%c%s\n",
+	for (i = 0; i < num_nodes; i++) {
+		char max_channels_str[7];
+		if (is_input) {
+			// Print "X" as don't-care for input nodes because
+			// cras_client_get_max_supported_channels() is only valid for outputs.
+			strcpy(max_channels_str, "     X");
+		} else {
+			uint32_t max_channels;
+			int rc = cras_client_get_max_supported_channels(
+				client,
+				cras_make_node_id(nodes[i].iodev_idx,
+						  nodes[i].ionode_idx),
+				&max_channels);
+			if (rc)
+				max_channels = 0;
+			sprintf(max_channels_str, "%6u", max_channels);
+		}
+		printf("\t(%08x)\t%u:%u\t%5g %f %7s\t%14s\t%10ld %-7s\t%-16s%-6s%c%s\n",
 		       nodes[i].stable_id, nodes[i].iodev_idx,
 		       nodes[i].ionode_idx,
 		       is_input ? nodes[i].capture_gain / 100.0 :
 				  (double)nodes[i].volume,
-		       nodes[i].plugged ? "yes" : "no",
+		       nodes[i].ui_gain_scaler, nodes[i].plugged ? "yes" : "no",
 		       nodes[i].left_right_swapped ? "yes" : "no",
 		       (long)nodes[i].plugged_time.tv_sec,
 		       nodes[i].active_hotword_model, nodes[i].type,
-		       nodes[i].active ? '*' : ' ', nodes[i].name);
+		       max_channels_str, nodes[i].active ? '*' : ' ',
+		       nodes[i].name);
+	}
 }
 
 static void print_device_lists(struct cras_client *client)
@@ -396,7 +416,7 @@
 	printf("Output Devices:\n");
 	print_dev_info(devs, num_devs);
 	printf("Output Nodes:\n");
-	print_node_info(nodes, num_nodes, 0);
+	print_node_info(client, nodes, num_nodes, 0);
 
 	num_devs = MAX_IODEVS;
 	num_nodes = MAX_IONODES;
@@ -405,7 +425,7 @@
 	printf("Input Devices:\n");
 	print_dev_info(devs, num_devs);
 	printf("Input Nodes:\n");
-	print_node_info(nodes, num_nodes, 1);
+	print_node_info(client, nodes, num_nodes, 1);
 }
 
 static void print_attached_client_list(struct cras_client *client)
@@ -440,13 +460,11 @@
 static void print_system_volumes(struct cras_client *client)
 {
 	printf("System Volume (0-100): %zu %s\n"
-	       "Capture Gain (%.2f - %.2f): %.2fdB %s\n",
+	       "Capture Muted : %s\n",
 	       cras_client_get_system_volume(client),
 	       cras_client_get_system_muted(client) ? "(Muted)" : "",
-	       cras_client_get_system_min_capture_gain(client) / 100.0,
-	       cras_client_get_system_max_capture_gain(client) / 100.0,
-	       cras_client_get_system_capture_gain(client) / 100.0,
-	       cras_client_get_system_capture_muted(client) ? "(Muted)" : "");
+	       cras_client_get_system_capture_muted(client) ? "Muted" :
+							      "Not muted");
 }
 
 static void print_user_muted(struct cras_client *client)
@@ -475,6 +493,26 @@
 	*nsec = nsec_offset;
 }
 
+static float get_ewma_power_as_float(uint32_t data)
+{
+	float f = 0.0f;
+
+	/* Convert from the uint32_t log type back to float.
+	 * If data cannot be assigned to float, default value will
+	 * be printed as -inf to hint the problem.
+	 */
+	if (sizeof(uint32_t) == sizeof(float))
+		memcpy(&f, &data, sizeof(float));
+	else
+		printf("%-30s float to uint32_t\n", "MEMORY_NOT_ALIGNED");
+
+	/* Convert to dBFS and set to zero if it's
+	 * insignificantly low.  Picking the same threshold
+	 * 1.0e-10f as in Chrome.
+	 */
+	return (f < 1.0e-10f) ? -INFINITY : 10.0f * log10f(f);
+}
+
 static void show_alog_tag(const struct audio_thread_event_log *log,
 			  unsigned int tag_idx, int32_t sec_offset,
 			  int32_t nsec_offset)
@@ -486,22 +524,23 @@
 	unsigned int data2 = log->log[tag_idx].data2;
 	unsigned int data3 = log->log[tag_idx].data3;
 	time_t lt;
-	struct tm *t;
+	struct tm t;
 
 	/* Skip unused log entries. */
 	if (log->log[tag_idx].tag_sec == 0 && log->log[tag_idx].nsec == 0)
 		return;
 
-	/* Convert from monotomic raw clock to realtime clock. */
+	/* Convert from monotonic raw clock to realtime clock. */
 	convert_time(&sec, &nsec, sec_offset, nsec_offset);
 	lt = sec;
-	t = localtime(&lt);
-	strftime(time_str, 128, "%Y-%m-%dT%H:%M:%S", t);
+	localtime_r(&lt, &t);
+	strftime(time_str, 128, "%Y-%m-%dT%H:%M:%S", &t);
 
 	printf("%s.%09u cras atlog  ", time_str, nsec);
 
 	/* Prepare realtime string for arguments. */
 	switch (tag) {
+	case AUDIO_THREAD_A2DP_FLUSH:
 	case AUDIO_THREAD_READ_AUDIO_TSTAMP:
 	case AUDIO_THREAD_FILL_AUDIO_TSTAMP:
 	case AUDIO_THREAD_STREAM_RESCHEDULE:
@@ -514,16 +553,16 @@
 	}
 	convert_time(&sec, &nsec, sec_offset, nsec_offset);
 	lt = sec;
-	t = localtime(&lt);
-	strftime(time_str, 128, " %H:%M:%S", t);
+	localtime_r(&lt, &t);
+	strftime(time_str, 128, " %H:%M:%S", &t);
 
 	switch (tag) {
 	case AUDIO_THREAD_WAKE:
 		printf("%-30s num_fds:%d\n", "WAKE", (int)data1);
 		break;
 	case AUDIO_THREAD_SLEEP:
-		printf("%-30s sleep:%09d.%09d\n", "SLEEP", (int)data1,
-		       (int)data2);
+		printf("%-30s sleep:%09d.%09d non_empty %u\n", "SLEEP",
+		       (int)data1, (int)data2, (int)data3);
 		break;
 	case AUDIO_THREAD_READ_AUDIO:
 		printf("%-30s dev:%u hw_level:%u read:%u\n", "READ_AUDIO",
@@ -533,25 +572,30 @@
 		printf("%-30s dev:%u tstamp:%s.%09u\n", "READ_AUDIO_TSTAMP",
 		       data1, time_str, nsec);
 		break;
-	case AUDIO_THREAD_READ_AUDIO_DONE:
-		printf("%-30s read_remainder:%u\n", "READ_AUDIO_DONE", data1);
+	case AUDIO_THREAD_READ_AUDIO_DONE: {
+		float f = get_ewma_power_as_float(data2);
+		printf("%-30s read_remainder:%u power:%f dBFS\n",
+		       "READ_AUDIO_DONE", data1, f);
 		break;
+	}
 	case AUDIO_THREAD_READ_OVERRUN:
 		printf("%-30s dev:%u stream:%x num_overruns:%u\n",
 		       "READ_AUDIO_OVERRUN", data1, data2, data3);
 		break;
 	case AUDIO_THREAD_FILL_AUDIO:
-		printf("%-30s dev:%u hw_level:%u\n", "FILL_AUDIO", data1,
-		       data2);
+		printf("%-30s dev:%u hw_level:%u min_cb_level:%u\n",
+		       "FILL_AUDIO", data1, data2, data3);
 		break;
 	case AUDIO_THREAD_FILL_AUDIO_TSTAMP:
 		printf("%-30s dev:%u tstamp:%s.%09u\n", "FILL_AUDIO_TSTAMP",
 		       data1, time_str, nsec);
 		break;
-	case AUDIO_THREAD_FILL_AUDIO_DONE:
-		printf("%-30s hw_level:%u total_written:%u min_cb_level:%u\n",
-		       "FILL_AUDIO_DONE", data1, data2, data3);
+	case AUDIO_THREAD_FILL_AUDIO_DONE: {
+		float f = get_ewma_power_as_float(data3);
+		printf("%-30s hw_level:%u total_written:%u power:%f dBFS\n",
+		       "FILL_AUDIO_DONE", data1, data2, f);
 		break;
+	}
 	case AUDIO_THREAD_WRITE_STREAMS_WAIT:
 		printf("%-30s stream:%x\n", "WRITE_STREAMS_WAIT", data1);
 		break;
@@ -569,19 +613,26 @@
 		printf("%-30s id:%x shm_frames:%u cb_pending:%u\n",
 		       "WRITE_STREAMS_STREAM", data1, data2, data3);
 		break;
-	case AUDIO_THREAD_FETCH_STREAM:
-		printf("%-30s id:%x cbth:%u delay:%u\n",
-		       "WRITE_STREAMS_FETCH_STREAM", data1, data2, data3);
+	case AUDIO_THREAD_FETCH_STREAM: {
+		float f = get_ewma_power_as_float(data3);
+		printf("%-30s id:%x cbth:%u power:%f dBFS\n",
+		       "WRITE_STREAMS_FETCH_STREAM", data1, data2, f);
 		break;
+	}
 	case AUDIO_THREAD_STREAM_ADDED:
 		printf("%-30s id:%x dev:%u\n", "STREAM_ADDED", data1, data2);
 		break;
 	case AUDIO_THREAD_STREAM_REMOVED:
 		printf("%-30s id:%x\n", "STREAM_REMOVED", data1);
 		break;
-	case AUDIO_THREAD_A2DP_ENCODE:
-		printf("%-30s proc:%d queued:%u readable:%u\n", "A2DP_ENCODE",
-		       data1, data2, data3);
+		break;
+	case AUDIO_THREAD_A2DP_FLUSH:
+		printf("%-30s state %u next flush time:%s.%09u\n", "A2DP_FLUSH",
+		       data1, time_str, nsec);
+		break;
+	case AUDIO_THREAD_A2DP_THROTTLE_TIME:
+		printf("%-30s %u ms, queued:%u\n", "A2DP_THROTTLE_TIME",
+		       data1 * 1000 + data2 / 1000000, data3);
 		break;
 	case AUDIO_THREAD_A2DP_WRITE:
 		printf("%-30s written:%d queued:%u\n", "A2DP_WRITE", data1,
@@ -637,7 +688,8 @@
 		printf("%-30s dev:%u\n", "DEV_REMOVED", data1);
 		break;
 	case AUDIO_THREAD_IODEV_CB:
-		printf("%-30s is_write:%u\n", "IODEV_CB", data1);
+		printf("%-30s revents:%u events:%u\n", "IODEV_CB", data1,
+		       data2);
 		break;
 	case AUDIO_THREAD_PB_MSG:
 		printf("%-30s msg_id:%u\n", "PB_MSG", data1);
@@ -675,6 +727,21 @@
 		printf("%-30s dev:%u frames:%u\n", "DEV_DROP_FRAMES", data1,
 		       data2);
 		break;
+	case AUDIO_THREAD_LOOPBACK_PUT:
+		printf("%-30s nframes_committed:%u\n", "LOOPBACK_PUT", data1);
+		break;
+	case AUDIO_THREAD_LOOPBACK_GET:
+		printf("%-30s nframes_requested:%u avail:%u\n", "LOOPBACK_GET",
+		       data1, data2);
+		break;
+	case AUDIO_THREAD_LOOPBACK_SAMPLE_HOOK:
+		printf("%-30s frames_to_copy:%u frames_copied:%u\n",
+		       "LOOPBACK_SAMPLE", data1, data2);
+		break;
+	case AUDIO_THREAD_DEV_OVERRUN:
+		printf("%-30s dev:%u hw_level:%u\n", "DEV_OVERRUN", data1,
+		       data2);
+		break;
 	default:
 		printf("%-30s tag:%u\n", "UNKNOWN", tag);
 		break;
@@ -735,8 +802,8 @@
 
 	for (i = 0; i < info->num_streams; i++) {
 		int channel;
-		printf("stream: %llu dev: %u\n",
-		       (unsigned long long)info->streams[i].stream_id,
+		printf("stream: 0x%" PRIx64 " dev: %u\n",
+		       info->streams[i].stream_id,
 		       (unsigned int)info->streams[i].dev_idx);
 		printf("direction: %s\n",
 		       (info->streams[i].direction == CRAS_STREAM_INPUT) ?
@@ -808,6 +875,92 @@
 	pthread_mutex_unlock(&done_mutex);
 }
 
+static void show_mainlog_tag(const struct main_thread_event_log *log,
+			     unsigned int tag_idx, int32_t sec_offset,
+			     int32_t nsec_offset)
+{
+	unsigned int tag = (log->log[tag_idx].tag_sec >> 24) & 0xff;
+	unsigned int sec = log->log[tag_idx].tag_sec & 0x00ffffff;
+	unsigned int nsec = log->log[tag_idx].nsec;
+	unsigned int data1 = log->log[tag_idx].data1;
+	unsigned int data2 = log->log[tag_idx].data2;
+	unsigned int data3 = log->log[tag_idx].data3;
+	time_t lt;
+	struct tm t;
+
+	/* Skip unused log entries. */
+	if (log->log[tag_idx].tag_sec == 0 && log->log[tag_idx].nsec == 0)
+		return;
+
+	/* Convert from monotomic raw clock to realtime clock. */
+	convert_time(&sec, &nsec, sec_offset, nsec_offset);
+	lt = sec;
+	localtime_r(&lt, &t);
+	strftime(time_str, 128, "%Y-%m-%dT%H:%M:%S", &t);
+
+	printf("%s.%09u cras mainlog  ", time_str, nsec);
+
+	switch (tag) {
+	case MAIN_THREAD_DEV_CLOSE:
+		printf("%-30s dev %u\n", "DEV_CLOSE", data1);
+		break;
+	case MAIN_THREAD_DEV_DISABLE:
+		printf("%-30s dev %u force %u\n", "DEV_DISABLE", data1, data2);
+		break;
+	case MAIN_THREAD_DEV_INIT:
+		printf("%-30s dev %u ch %u rate %u\n", "DEV_INIT", data1, data2,
+		       data3);
+		break;
+	case MAIN_THREAD_DEV_REOPEN:
+		printf("%-30s new ch %u old ch %u rate %u\n", "DEV_REOPEN",
+		       data1, data2, data3);
+		break;
+	case MAIN_THREAD_ADD_ACTIVE_NODE:
+		printf("%-30s dev %u\n", "ADD_ACTIVE_NODE", data1);
+		break;
+	case MAIN_THREAD_SELECT_NODE:
+		printf("%-30s dev %u\n", "SELECT_NODE", data1);
+		break;
+	case MAIN_THREAD_ADD_TO_DEV_LIST:
+		printf("%-30s dev %u %s\n", "ADD_TO_DEV_LIST", data1,
+		       (data2 == CRAS_STREAM_OUTPUT) ? "output" : "input");
+		break;
+	case MAIN_THREAD_NODE_PLUGGED:
+		printf("%-30s dev %u %s\n", "NODE_PLUGGED", data1,
+		       data2 ? "plugged" : "unplugged");
+		break;
+	case MAIN_THREAD_INPUT_NODE_GAIN:
+		printf("%-30s dev %u gain %u\n", "INPUT_NODE_GAIN", data1,
+		       data2);
+		break;
+	case MAIN_THREAD_OUTPUT_NODE_VOLUME:
+		printf("%-30s dev %u volume %u\n", "OUTPUT_NODE_VOLUME", data1,
+		       data2);
+		break;
+	case MAIN_THREAD_SET_OUTPUT_USER_MUTE:
+		printf("%-30s mute %u\n", "SET_OUTPUT_USER_MUTE", data1);
+		break;
+	case MAIN_THREAD_RESUME_DEVS:
+		printf("RESUME_DEVS\n");
+		break;
+	case MAIN_THREAD_SUSPEND_DEVS:
+		printf("SUSPEND_DEVS\n");
+		break;
+	case MAIN_THREAD_STREAM_ADDED:
+		printf("%-30s %s stream 0x%x buffer frames %u\n",
+		       "STREAM_ADDED",
+		       (data2 == CRAS_STREAM_OUTPUT ? "output" : "input"),
+		       data1, data3);
+		break;
+	case MAIN_THREAD_STREAM_REMOVED:
+		printf("%-30s stream 0x%x\n", "STREAM_REMOVED", data1);
+		break;
+	default:
+		printf("%-30s\n", "UNKNOWN");
+		break;
+	}
+}
+
 static void show_btlog_tag(const struct cras_bt_event_log *log,
 			   unsigned int tag_idx, int32_t sec_offset,
 			   int32_t nsec_offset)
@@ -818,17 +971,17 @@
 	unsigned int data1 = log->log[tag_idx].data1;
 	unsigned int data2 = log->log[tag_idx].data2;
 	time_t lt;
-	struct tm *t;
+	struct tm t;
 
 	/* Skip unused log entries. */
 	if (log->log[tag_idx].tag_sec == 0 && log->log[tag_idx].nsec == 0)
 		return;
 
-	/* Convert from monotomic raw clock to realtime clock. */
+	/* Convert from monotonic raw clock to realtime clock. */
 	convert_time(&sec, &nsec, sec_offset, nsec_offset);
 	lt = sec;
-	t = localtime(&lt);
-	strftime(time_str, 128, "%Y-%m-%dT%H:%M:%S", t);
+	localtime_r(&lt, &t);
+	strftime(time_str, 128, "%Y-%m-%dT%H:%M:%S", &t);
 
 	printf("%s.%09u cras btlog  ", time_str, nsec);
 
@@ -840,7 +993,7 @@
 		printf("%-30s\n", "ADAPTER_REMOVED");
 		break;
 	case BT_A2DP_CONFIGURED:
-		printf("%-30s connected profiles %u\n", "A2DP_CONFIGURED",
+		printf("%-30s connected profiles 0x%.2x\n", "A2DP_CONFIGURED",
 		       data1);
 		break;
 	case BT_A2DP_START:
@@ -850,8 +1003,8 @@
 		printf("%-30s\n", "A2DP_SUSPENDED");
 		break;
 	case BT_AUDIO_GATEWAY_INIT:
-		printf("%-30s supported profiles %u\n", "AUDIO_GATEWAY_INIT",
-		       data1);
+		printf("%-30s supported profiles 0x%.2x\n",
+		       "AUDIO_GATEWAY_INIT", data1);
 		break;
 	case BT_AUDIO_GATEWAY_START:
 		printf("%-30s \n", "AUDIO_GATEWAY_START");
@@ -864,18 +1017,34 @@
 		printf("%-30s dir %u codec id %u\n", "CODEC_SELECTION", data1,
 		       data2);
 		break;
-	case BT_DEV_CONNECTED_CHANGE:
-		printf("%-30s profiles %u now %u\n", "DEV_CONENCTED_CHANGE",
-		       data1, data2);
+	case BT_DEV_CONNECTED:
+		printf("%-30s supported profiles 0x%.2x stable_id 0x%08x\n",
+		       "DEV_CONNECTED", data1, data2);
+		break;
+	case BT_DEV_DISCONNECTED:
+		printf("%-30s supported profiles 0x%.2x stable_id 0x%08x\n",
+		       "DEV_DISCONNECTED", data1, data2);
 		break;
 	case BT_DEV_CONN_WATCH_CB:
-		printf("%-30s %u retries left, supported profiles %u\n",
+		printf("%-30s %u retries left, supported profiles 0x%.2x\n",
 		       "DEV_CONN_WATCH_CB", data1, data2);
 		break;
 	case BT_DEV_SUSPEND_CB:
-		printf("%-30s profiles supported %u, connected %u\n",
+		printf("%-30s profiles supported %u, reason %u\n",
 		       "DEV_SUSPEND_CB", data1, data2);
 		break;
+	case BT_HFP_HF_INDICATOR:
+		printf("%-30s HF read AG %s indicator\n", "HFP_HF_INDICATOR",
+		       data1 ? "enabled" : "supported");
+		break;
+	case BT_HFP_SET_SPEAKER_GAIN:
+		printf("%-30s HF set speaker gain %u\n", "HFP_SET_SPEAKER_GAIN",
+		       data1);
+		break;
+	case BT_HFP_UPDATE_SPEAKER_GAIN:
+		printf("%-30s HF update speaker gain %u\n",
+		       "HFP_UPDATE_SPEAKER_GAIN", data1);
+		break;
 	case BT_HFP_NEW_CONNECTION:
 		printf("%-30s\n", "HFP_NEW_CONNECTION");
 		break;
@@ -883,8 +1052,8 @@
 		printf("%-30s\n", "HFP_REQUEST_DISCONNECT");
 		break;
 	case BT_HFP_SUPPORTED_FEATURES:
-		printf("%-30s role %s features %u\n", "HFP_SUPPORTED_FEATURES",
-		       data1 ? "AG" : "HF", data2);
+		printf("%-30s role %s features 0x%.4x\n",
+		       "HFP_SUPPORTED_FEATURES", data1 ? "AG" : "HF", data2);
 		break;
 	case BT_HSP_NEW_CONNECTION:
 		printf("%-30s\n", "HSP_NEW_CONNECTION");
@@ -893,7 +1062,7 @@
 		printf("%-30s\n", "HSP_REQUEST_DISCONNECT");
 		break;
 	case BT_NEW_AUDIO_PROFILE_AFTER_CONNECT:
-		printf("%-30s old %u, new %u\n",
+		printf("%-30s old 0x%.2x, new 0x%.2x\n",
 		       "NEW_AUDIO_PROFILE_AFTER_CONNECT", data1, data2);
 		break;
 	case BT_RESET:
@@ -910,18 +1079,42 @@
 	case BT_TRANSPORT_RELEASE:
 		printf("%-30s\n", "TRANSPORT_RELEASE");
 		break;
+	case BT_TRANSPORT_SET_VOLUME:
+		printf("%-30s %d\n", "TRANSPORT_SET_VOLUME", data1);
+		break;
+	case BT_TRANSPORT_UPDATE_VOLUME:
+		printf("%-30s %d\n", "TRANSPORT_UPDATE_VOLUME", data1);
+		break;
 	default:
 		printf("%-30s\n", "UNKNOWN");
 		break;
 	}
 }
 
+static void convert_to_time_str(const struct timespec *ts, time_t sec_offset,
+				int32_t nsec_offset)
+{
+	time_t lt = ts->tv_sec;
+	struct tm t;
+	unsigned int time_nsec;
+
+	/* Assuming tv_nsec doesn't exceed 10^9 */
+	time_nsec = ts->tv_nsec;
+	convert_time((unsigned int *)&lt, &time_nsec, sec_offset, nsec_offset);
+	localtime_r(&lt, &t);
+	strftime(time_str, 128, "%Y-%m-%dT%H:%M:%S", &t);
+	snprintf(time_str + strlen(time_str), 128 - strlen(time_str), ".%09u",
+		 time_nsec);
+}
+
 static void cras_bt_debug_info(struct cras_client *client)
 {
 	const struct cras_bt_debug_info *info;
 	time_t sec_offset;
 	int32_t nsec_offset;
 	int i, j;
+	struct timespec ts;
+	struct packet_status_logger wbs_logger;
 
 	info = cras_client_get_bt_debug_info(client);
 	fill_time_offset(&sec_offset, &nsec_offset);
@@ -934,6 +1127,47 @@
 		j %= info->bt_log.len;
 	}
 
+	printf("-------------WBS packet loss------------\n");
+	wbs_logger = info->wbs_logger;
+
+	packet_status_logger_begin_ts(&wbs_logger, &ts);
+	convert_to_time_str(&ts, sec_offset, nsec_offset);
+	printf("%s [begin]\n", time_str);
+
+	packet_status_logger_end_ts(&wbs_logger, &ts);
+	convert_to_time_str(&ts, sec_offset, nsec_offset);
+	printf("%s [end]\n", time_str);
+
+	printf("In hex format:\n");
+	packet_status_logger_dump_hex(&wbs_logger);
+
+	printf("In binary format:\n");
+	packet_status_logger_dump_binary(&wbs_logger);
+
+	/* Signal main thread we are done after the last chunk. */
+	pthread_mutex_lock(&done_mutex);
+	pthread_cond_signal(&done_cond);
+	pthread_mutex_unlock(&done_mutex);
+}
+
+static void main_thread_debug_info(struct cras_client *client)
+{
+	const struct main_thread_debug_info *info;
+	time_t sec_offset;
+	int32_t nsec_offset;
+	int i, j;
+
+	info = cras_client_get_main_thread_debug_info(client);
+	fill_time_offset(&sec_offset, &nsec_offset);
+	j = info->main_log.write_pos;
+	i = 0;
+	printf("Main debug log:\n");
+	for (; i < info->main_log.len; i++) {
+		show_mainlog_tag(&info->main_log, j, sec_offset, nsec_offset);
+		j++;
+		j %= info->main_log.len;
+	}
+
 	/* Signal main thread we are done after the last chunk. */
 	pthread_mutex_lock(&done_mutex);
 	pthread_cond_signal(&done_cond);
@@ -950,6 +1184,9 @@
 
 	printf("Event type: ");
 	switch (snapshot->event_type) {
+	case AUDIO_THREAD_EVENT_A2DP_THROTTLE:
+		printf("a2dp throttle\n");
+		break;
 	case AUDIO_THREAD_EVENT_BUSYLOOP:
 		printf("busyloop\n");
 		break;
@@ -962,6 +1199,9 @@
 	case AUDIO_THREAD_EVENT_DROP_SAMPLES:
 		printf("drop samples\n");
 		break;
+	case AUDIO_THREAD_EVENT_DEV_OVERRUN:
+		printf("device overrun\n");
+		break;
 	case AUDIO_THREAD_EVENT_DEBUG:
 		printf("debug\n");
 		break;
@@ -1069,7 +1309,6 @@
 	struct timespec sleep_ts;
 	float volume_scaler = 1.0;
 	size_t sys_volume = 100;
-	long cap_gain = 0;
 	int mute = 0;
 	int8_t layout[CRAS_CH_MAX];
 
@@ -1218,14 +1457,6 @@
 			sys_volume = sys_volume == 0 ? 0 : sys_volume - 1;
 			cras_client_set_system_volume(client, sys_volume);
 			break;
-		case 'K':
-			cap_gain = MIN(cap_gain + 100, 5000);
-			cras_client_set_system_capture_gain(client, cap_gain);
-			break;
-		case 'J':
-			cap_gain = cap_gain == -5000 ? -5000 : cap_gain - 100;
-			cras_client_set_system_capture_gain(client, cap_gain);
-			break;
 		case 'm':
 			mute = !mute;
 			cras_client_set_system_mute(client, mute);
@@ -1238,19 +1469,16 @@
 			break;
 		case 'v':
 			printf("Volume: %zu%s Min dB: %ld Max dB: %ld\n"
-			       "Capture: %ld%s Min dB: %ld Max dB: %ld\n",
+			       "Capture: %s\n",
 			       cras_client_get_system_volume(client),
 			       cras_client_get_system_muted(client) ?
 				       "(Muted)" :
 				       "",
 			       cras_client_get_system_min_volume(client),
 			       cras_client_get_system_max_volume(client),
-			       cras_client_get_system_capture_gain(client),
 			       cras_client_get_system_capture_muted(client) ?
-				       "(Muted)" :
-				       "",
-			       cras_client_get_system_min_capture_gain(client),
-			       cras_client_get_system_max_capture_gain(client));
+				       "Muted" :
+				       "Not muted");
 			break;
 		case '\'':
 			play_short_sound_periods_left =
@@ -1379,6 +1607,22 @@
 	pthread_mutex_unlock(&done_mutex);
 }
 
+static void show_main_thread_debug_info(struct cras_client *client)
+{
+	struct timespec wait_time;
+	cras_client_run_thread(client);
+	cras_client_connected_wait(client); /* To synchronize data. */
+	cras_client_update_main_thread_debug_info(client,
+						  main_thread_debug_info);
+
+	clock_gettime(CLOCK_REALTIME, &wait_time);
+	wait_time.tv_sec += 2;
+
+	pthread_mutex_lock(&done_mutex);
+	pthread_cond_timedwait(&done_cond, &done_mutex, &wait_time);
+	pthread_mutex_unlock(&done_mutex);
+}
+
 static void hotword_models_cb(struct cras_client *client,
 			      const char *hotword_models)
 {
@@ -1409,7 +1653,7 @@
 	       cras_client_output_dev_plugged(client, name) ? "Yes" : "No");
 }
 
-/* Repeatedly mute and unmute the output until there is an error. */
+/* Repeatedly mute and un-mute the output until there is an error. */
 static void mute_loop_test(struct cras_client *client, int auto_reconnect)
 {
 	int mute = 0;
@@ -1474,6 +1718,9 @@
 
 	fill_time_offset(&sec_offset, &nsec_offset);
 
+	/* Set stdout buffer to line buffered mode. */
+	setlinebuf(stdout);
+
 	while (1) {
 		len = cras_client_read_atlog(client, &atlog_read_idx, &missing,
 					     &log);
@@ -1547,6 +1794,7 @@
 	{"connection_type",     required_argument,      0, 'K'},
 	{"loopback_file",       required_argument,      0, 'L'},
 	{"mute_loop_test",      required_argument,      0, 'M'},
+	{"dump_main",		no_argument,		0, 'N'},
 	{"playback_file",       required_argument,      0, 'P'},
 	{"stream_type",         required_argument,      0, 'T'},
 	{0, 0, 0, 0}
@@ -1570,7 +1818,7 @@
 	printf("--capture_file <name> - "
 	       "Name of file to record to.\n");
 	printf("--capture_gain <dB> - "
-	       "Set system caputre gain in dB*100 (100 = 1dB).\n");
+	       "Set system capture gain in dB*100 (100 = 1dB).\n");
 	printf("--capture_mute <0|1> - "
 	       "Set capture mute state.\n");
 	printf("--channel_layout <layout_str> - "
@@ -1584,11 +1832,17 @@
 	       "                                      "
 	       "          1 - For playback client.\n"
 	       "                                      "
-	       "          2 - For capture client.\n");
+	       "          2 - For capture client.\n"
+	       "                                      "
+	       "          3 - For legacy client in vms.\n"
+	       "                                      "
+	       "          4 - For unified client in vms.\n");
 	printf("--dump_audio_thread - "
 	       "Dumps audio thread info.\n");
 	printf("--dump_bt - "
 	       "Dumps debug info for bt audio\n");
+	printf("--dump_main - "
+	       "Dumps debug info from main thread\n");
 	printf("--dump_dsp - "
 	       "Print status of dsp to syslog.\n");
 	printf("--dump_server_info - "
@@ -1613,7 +1867,7 @@
 	printf("--mute <0|1> - "
 	       "Set system mute state.\n");
 	printf("--mute_loop_test <0|1> - "
-	       "Continuously loop mute/umute.\n"
+	       "Continuously loop mute/un-mute.\n"
 	       "                         "
 	       "Argument: 0 - stop on error.\n"
 	       "                         "
@@ -1667,7 +1921,7 @@
 	printf("--suspend <0|1> - "
 	       "Set audio suspend state.\n");
 	printf("--swap_left_right <N>:<M>:<0|1> - "
-	       "Swap or unswap (1 or 0) the left and right channel for the "
+	       "Swap or un-swap (1 or 0) the left and right channel for the "
 	       "ionode with the given index M on the device with index N\n");
 	printf("--stream_type <N> - "
 	       "Specify the type of the stream.\n");
@@ -1790,15 +2044,6 @@
 			}
 			break;
 		}
-		case 'g': {
-			long gain = atol(optarg);
-			rc = cras_client_set_system_capture_gain(client, gain);
-			if (rc < 0) {
-				fprintf(stderr, "problem setting capture\n");
-				goto destroy_exit;
-			}
-			break;
-		}
 		case 'h':
 			show_usage();
 			break;
@@ -1995,7 +2240,8 @@
 
 			s = strtok(optarg, ":");
 			nch = atoi(s);
-			coeff = (float *)calloc(nch * nch, sizeof(*coeff));
+			coeff = (float *)calloc((size_t)nch * (size_t)nch,
+						sizeof(*coeff));
 			for (size = 0; size < nch * nch; size++) {
 				s = strtok(NULL, ",");
 				if (NULL == s)
@@ -2116,6 +2362,9 @@
 		case 'M':
 			mute_loop_test(client, atoi(optarg));
 			break;
+		case 'N':
+			show_main_thread_debug_info(client);
+			break;
 		case 'P':
 			playback_file = optarg;
 			break;
@@ -2128,6 +2377,15 @@
 		}
 	}
 
+	if (optind < argc) {
+		printf("Warning: un-welcome arguments: ");
+		while (optind < argc)
+			printf("%s ", argv[optind++]);
+		printf("\n");
+		rc = 1;
+		goto destroy_exit;
+	}
+
 	duration_frames = duration_seconds * rate;
 	if (block_size == NOT_ASSIGNED)
 		block_size = get_block_size(PLAYBACK_BUFFERED_TIME_IN_US, rate);
diff --git a/cros_alsa/.gitignore b/cros_alsa/.gitignore
new file mode 100644
index 0000000..b659239
--- /dev/null
+++ b/cros_alsa/.gitignore
@@ -0,0 +1,3 @@
+target/
+.*.rustfmt
+Cargo.lock
diff --git a/cros_alsa/.rustfmt.toml b/cros_alsa/.rustfmt.toml
new file mode 100644
index 0000000..a2db301
--- /dev/null
+++ b/cros_alsa/.rustfmt.toml
@@ -0,0 +1,2 @@
+use_field_init_shorthand = true
+use_try_shorthand = true
diff --git a/cros_alsa/Cargo.toml b/cros_alsa/Cargo.toml
new file mode 100644
index 0000000..2e006e4
--- /dev/null
+++ b/cros_alsa/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "cros_alsa"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+edition = "2018"
+description = "The Chromium OS alsa-lib wrapper"
+
+[dependencies]
+alsa-sys = "0.2.0"
+cros_alsa_derive = "*"
+libc = "0.2.65"
+remain = "0.2.1"
+
+[patch.crates-io]
+cros_alsa_derive = { path = "cros_alsa_derive" }
diff --git a/cros_alsa/cros_alsa_derive/Cargo.toml b/cros_alsa/cros_alsa_derive/Cargo.toml
new file mode 100644
index 0000000..6a4da28
--- /dev/null
+++ b/cros_alsa/cros_alsa_derive/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "cros_alsa_derive"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+edition = "2018"
+description = "Derive macros of cors_alsa."
+
+[lib]
+proc-macro = true
+
+[dependencies]
+proc-macro2 = "^1"
+quote = "^1"
+syn = "^1"
diff --git a/cros_alsa/cros_alsa_derive/src/common.rs b/cros_alsa/cros_alsa_derive/src/common.rs
new file mode 100644
index 0000000..2daaa3b
--- /dev/null
+++ b/cros_alsa/cros_alsa_derive/src/common.rs
@@ -0,0 +1,77 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! This mod provides common constants, structs and functions used across cros_alsa_derive.
+use std::convert::TryFrom;
+
+use syn::{Lit, Meta, NestedMeta, Result};
+
+/// Attribute name of cros_alsa.
+const CROS_ALSA: &str = "cros_alsa";
+
+/// const for `#[cros_alsa(path = "...")]`
+const PATH: &str = "path";
+
+/// Possible `#[cros_alsa("...")]` derive macro helper attributes.
+pub enum CrosAlsaAttr {
+    /// Use `#[cros_alsa(path = "crate")]` to replace the crate path of cros_alsa in derive macros.
+    Path(syn::Path),
+}
+
+impl CrosAlsaAttr {
+    /// Return true if the value is a `Path` variant.
+    pub fn is_path(&self) -> bool {
+        use CrosAlsaAttr::*;
+        match self {
+            Path(_) => true,
+            // suppress unreachable_patterns warning because there is only one CrosAlsaAttr variant.
+            #[allow(unreachable_patterns)]
+            _ => false,
+        }
+    }
+}
+
+impl TryFrom<syn::NestedMeta> for CrosAlsaAttr {
+    type Error = syn::Error;
+    fn try_from(meta_item: NestedMeta) -> Result<CrosAlsaAttr> {
+        match meta_item {
+            // Parse `#[cros_alsa(path = "crate")]`
+            NestedMeta::Meta(Meta::NameValue(m)) if m.path.is_ident(PATH) => {
+                if let Lit::Str(lit_str) = &m.lit {
+                    return Ok(CrosAlsaAttr::Path(lit_str.parse()?));
+                }
+                Err(syn::Error::new_spanned(
+                    &m.lit,
+                    "expected a valid path for cros_alsa_derive::CrosAlsaAttr::Path",
+                ))
+            }
+            _ => Err(syn::Error::new_spanned(
+                meta_item,
+                "unrecognized cros_alsa_derive::CrosAlsaAttr",
+            )),
+        }
+    }
+}
+
+/// Parses `#[cros_alsa(path = "...")]` into a list of `CrosAlsaAttr`.
+/// It's used to replace the crate path of cros_alsa in derive macros.
+pub fn parse_cros_alsa_attr(attrs: &[syn::Attribute]) -> Result<Vec<CrosAlsaAttr>> {
+    attrs
+        .iter()
+        .flat_map(|attr| get_cros_alsa_meta_items(attr))
+        .flatten()
+        .map(CrosAlsaAttr::try_from)
+        .collect()
+}
+
+/// Parses and collects `NestedMeta` under `cros_alsa` attribute.
+fn get_cros_alsa_meta_items(attr: &syn::Attribute) -> Result<Vec<syn::NestedMeta>> {
+    if !attr.path.is_ident(CROS_ALSA) {
+        return Ok(Vec::new());
+    }
+    match attr.parse_meta() {
+        Ok(Meta::List(meta)) => Ok(meta.nested.into_iter().collect()),
+        _ => Err(syn::Error::new_spanned(attr, "expected #[cros_alsa(...)]")),
+    }
+}
diff --git a/cros_alsa/cros_alsa_derive/src/control.rs b/cros_alsa/cros_alsa_derive/src/control.rs
new file mode 100644
index 0000000..584a541
--- /dev/null
+++ b/cros_alsa/cros_alsa_derive/src/control.rs
@@ -0,0 +1,40 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! This mod provides derive macros for cros_alsa::control.
+
+use proc_macro::TokenStream;
+use proc_macro2::Span;
+use quote::quote;
+
+use crate::common::{parse_cros_alsa_attr, CrosAlsaAttr};
+
+/// The provide default implementation for `ControlOps`.
+/// Users could hold `Ctl` and `ElemID` as `handle` and `id` in their control structure and use
+/// `#[derive(ControlOps)]` macro to generate default load / save implementations.
+pub fn impl_control_ops(ast: &syn::DeriveInput) -> TokenStream {
+    let name = &ast.ident;
+    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
+    let attrs = match parse_cros_alsa_attr(&ast.attrs) {
+        Ok(attrs) => attrs,
+        Err(e) => return e.to_compile_error().into(),
+    };
+    let path = match attrs.iter().find(|x| x.is_path()) {
+        Some(CrosAlsaAttr::Path(path)) => path.clone(),
+        None => syn::LitStr::new("cros_alsa", Span::call_site())
+            .parse()
+            .expect("failed to create a default path for derive macro: ControlOps"),
+    };
+    let gen = quote! {
+        impl #impl_generics #path::ControlOps #impl_generics for #name #ty_generics #where_clause {
+            fn load(&mut self) -> ::std::result::Result<<Self as #path::Control #impl_generics>::Item, #path::ControlError> {
+                Ok(<Self as #path::Control>::Item::load(self.handle, &self.id)?)
+            }
+            fn save(&mut self, val: <Self as #path::Control #impl_generics>::Item) -> ::std::result::Result<bool, #path::ControlError> {
+                Ok(<Self as #path::Control>::Item::save(self.handle, &self.id, val)?)
+            }
+        }
+    };
+    gen.into()
+}
diff --git a/cros_alsa/cros_alsa_derive/src/lib.rs b/cros_alsa/cros_alsa_derive/src/lib.rs
new file mode 100644
index 0000000..9970de7
--- /dev/null
+++ b/cros_alsa/cros_alsa_derive/src/lib.rs
@@ -0,0 +1,26 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! `cros_alsa_derive` crate provides derive macros for cros_alsa.
+//!
+
+#![deny(missing_docs)]
+extern crate proc_macro;
+
+use proc_macro::TokenStream;
+
+mod common;
+mod control;
+use self::control::impl_control_ops;
+
+#[proc_macro_derive(ControlOps, attributes(cros_alsa))]
+/// Derive macro generating an impl of the trait ControlOps.
+/// To use this derive macro, users should hold `Ctl` and `ElemID` as `handle`
+/// and `id` in their control structure.
+pub fn control_ops_derive(input: TokenStream) -> TokenStream {
+    match syn::parse(input) {
+        Ok(ast) => impl_control_ops(&ast),
+        Err(e) => e.to_compile_error().into(),
+    }
+}
diff --git a/cros_alsa/src/card.rs b/cros_alsa/src/card.rs
new file mode 100644
index 0000000..42beef2
--- /dev/null
+++ b/cros_alsa/src/card.rs
@@ -0,0 +1,117 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::error;
+use std::fmt;
+
+use remain::sorted;
+
+use crate::control::{self, Control};
+use crate::control_primitive;
+use crate::control_primitive::{Ctl, ElemId, ElemIface};
+use crate::control_tlv::{self, ControlTLV};
+
+pub type Result<T> = std::result::Result<T, Error>;
+
+#[sorted]
+#[derive(Debug)]
+/// Possible errors that can occur in cros-alsa::card.
+pub enum Error {
+    /// Failed to call AlsaControlAPI.
+    AlsaControlAPI(control_primitive::Error),
+    /// Error occurs in Control.
+    Control(control::Error),
+    /// Error occurs in ControlTLV.
+    ControlTLV(control_tlv::Error),
+}
+
+impl error::Error for Error {}
+
+impl From<control::Error> for Error {
+    fn from(err: control::Error) -> Error {
+        Error::Control(err)
+    }
+}
+
+impl From<control_tlv::Error> for Error {
+    fn from(err: control_tlv::Error) -> Error {
+        Error::ControlTLV(err)
+    }
+}
+
+impl From<control_primitive::Error> for Error {
+    fn from(err: control_primitive::Error) -> Error {
+        Error::AlsaControlAPI(err)
+    }
+}
+
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        use Error::*;
+        match self {
+            AlsaControlAPI(e) => write!(f, "{}", e),
+            Control(e) => write!(f, "{}", e),
+            ControlTLV(e) => write!(f, "{}", e),
+        }
+    }
+}
+
+/// `Card` represents a sound card.
+#[derive(Debug)]
+pub struct Card {
+    handle: Ctl,
+    name: String,
+}
+
+impl Card {
+    /// Creates a `Card`.
+    ///
+    /// # Arguments
+    ///
+    /// * `card_name` - The sound card name, ex: sofcmlmax98390d.
+    ///
+    /// # Errors
+    ///
+    /// * If card_name is an invalid CString.
+    /// * If snd_ctl_open() fails.
+    pub fn new(card_name: &str) -> Result<Self> {
+        let handle = Ctl::new(&format!("hw:{}", card_name))?;
+        Ok(Card {
+            name: card_name.to_owned(),
+            handle,
+        })
+    }
+
+    /// Gets sound card name.
+    pub fn name(&self) -> &str {
+        &self.name
+    }
+
+    /// Creates a `Control` from control name.
+    ///
+    /// # Errors
+    ///
+    /// * If control name is an invalid CString.
+    /// * If control does not exist.
+    /// * If `Control` elem_type() mismatches the type of underlying mixer control.
+    /// * If `Control` size() mismatches the number of value entries of underlying mixer control.
+    pub fn control_by_name<'a, T: 'a>(&'a mut self, control_name: &str) -> Result<T>
+    where
+        T: Control<'a>,
+    {
+        let id = ElemId::new(ElemIface::Mixer, control_name)?;
+        Ok(T::from(&mut self.handle, id)?)
+    }
+
+    /// Creates a `ControlTLV` from control name.
+    ///
+    /// # Errors
+    ///
+    /// * If control name is an invalid CString.
+    /// * If control does not exist.
+    pub fn control_tlv_by_name<'a>(&'a mut self, control_name: &str) -> Result<ControlTLV<'a>> {
+        let id = ElemId::new(ElemIface::Mixer, control_name)?;
+        Ok(ControlTLV::new(&mut self.handle, id)?)
+    }
+}
diff --git a/cros_alsa/src/control.rs b/cros_alsa/src/control.rs
new file mode 100644
index 0000000..2bc9025
--- /dev/null
+++ b/cros_alsa/src/control.rs
@@ -0,0 +1,297 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! `control` module is meant to provide easier to use and more type safe abstractions
+//! for various alsa mixer controls.
+//!
+//! Each mixer control should implement the `Control` trait to allow itself to be created by `Card`.
+//! Each mixer control could hold `Ctl` as handle and `ElemID` as id and use `#[derive(ControlOps)]` macro
+//! to generate default load / save implementations of the `ControlOps` trait which allows itself to read and
+//! write the underlying hardware.
+//!
+//! # Examples
+//! This is an example of how to define a `SwitchControl`.
+//!
+//! ```
+//! use std::error::Error;
+//!
+//! use cros_alsa::{Ctl, ElemId, Control, ControlError, ControlOps};
+//! use cros_alsa::elem::Elem;
+//!
+//! type Result<T> = std::result::Result<T, ControlError>;
+//!
+//! #[derive(ControlOps)]
+//! pub struct SwitchControl<'a> {
+//!     // Must hold `Ctl` as handle and `ElemID` as id to use `#[derive(ControlOps)]`.
+//!     handle: &'a mut Ctl,
+//!     id: ElemId,
+//! }
+//!
+//! impl<'a> Control<'a> for SwitchControl <'a> {
+//!     type Item = [bool; 1];
+//!
+//!     fn new(handle: &'a mut Ctl, id: ElemId) -> Self {
+//!         Self {
+//!             handle,
+//!             id,
+//!         }
+//!     }
+//! }
+//!
+//! impl<'a> SwitchControl<'a> {
+//!     /// Reads the state of a switch type mix control.
+//!     pub fn state(&mut self) -> Result<bool> {
+//!         // Uses ControlOps::load() to read the mixer control.
+//!         let v = self.load()?;
+//!         Ok(v[0])
+//!     }
+//!
+//!     /// Updates the control state to true.
+//!     pub fn on(&mut self) -> Result<()> {
+//!         // Uses ControlOps::save() to write the mixer control.
+//!         self.save([true])?;
+//!         Ok(())
+//!     }
+//! }
+//!
+//! ```
+
+use std::error;
+use std::fmt;
+
+use cros_alsa_derive::ControlOps;
+use remain::sorted;
+
+use crate::control_primitive::{self, Ctl, ElemId, ElemInfo, ElemType};
+use crate::elem::{self, Elem};
+
+/// The Result type of cros-alsa::control.
+pub type Result<T> = std::result::Result<T, Error>;
+
+#[sorted]
+#[derive(Debug)]
+/// Possible errors that can occur in cros-alsa::control.
+pub enum Error {
+    /// Failed to call AlsaControlAPI.
+    AlsaControlAPI(control_primitive::Error),
+    /// Error occurs in Elem.
+    Elem(elem::Error),
+    /// Elem::size() does not match the element count of the mixer control.
+    MismatchElemCount(String, usize, usize),
+    /// Elem::elem_type() does not match the data type of the mixer control.
+    MismatchElemType(String, ElemType, ElemType),
+}
+
+impl error::Error for Error {}
+
+impl From<control_primitive::Error> for Error {
+    fn from(err: control_primitive::Error) -> Error {
+        Error::AlsaControlAPI(err)
+    }
+}
+
+impl From<elem::Error> for Error {
+    fn from(err: elem::Error) -> Error {
+        Error::Elem(err)
+    }
+}
+
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        use Error::*;
+        match self {
+            AlsaControlAPI(e) => write!(f, "{}", e),
+            Elem(e) => write!(f, "{}", e),
+            MismatchElemCount(name, count, elem_count) => write!(
+                f,
+                "invalid `Control::size()` of {}: expect: {}, get: {}",
+                name, count, elem_count
+            ),
+            MismatchElemType(name, t, elem_type) => write!(
+                f,
+                "invalid `Control::elem_type()` of {}: expect: {}, get: {}",
+                name, t, elem_type
+            ),
+        }
+    }
+}
+
+/// Each mixer control should implement the `Control` trait to allow itself to be created by `Card`.
+pub trait Control<'a>: Sized + 'a {
+    /// The data type of the mixer control.
+    /// Use `ElemType::load()` and `ElemType::save()` to read or write the mixer control.
+    type Item: Elem;
+
+    /// Called by `Self::from(handle: &'a mut Ctl, id: ElemId)` to create a `Control`.
+    fn new(handle: &'a mut Ctl, id: ElemId) -> Self;
+    /// Called by `Card` to create a `Control`.
+    fn from(handle: &'a mut Ctl, id: ElemId) -> Result<Self> {
+        let info = ElemInfo::new(handle, &id)?;
+        if info.elem_type()? != Self::elem_type() {
+            return Err(Error::MismatchElemType(
+                id.name()?.to_owned(),
+                info.elem_type()?,
+                Self::elem_type(),
+            ));
+        }
+
+        if info.count() != Self::size() {
+            return Err(Error::MismatchElemCount(
+                id.name()?.to_owned(),
+                info.count(),
+                Self::size(),
+            ));
+        }
+
+        Ok(Self::new(handle, id))
+    }
+    /// Called by `Self::from(handle: &'a mut Ctl, id: ElemId)` to validate the data type of a
+    /// `Control`.
+    fn elem_type() -> ElemType {
+        Self::Item::elem_type()
+    }
+    /// Called by `Self::from(handle: &'a mut Ctl, id: ElemId)` to validate the number of value
+    /// entries of a `Control`.
+    fn size() -> usize {
+        Self::Item::size()
+    }
+}
+
+/// Each mixer control could implement the `ControlOps` trait to allow itself to read and
+/// write the underlying hardware`. Users could hold `Ctl` and `ElemID` as `handle` and `id`
+/// in their control structure and use `#[derive(ControlOps)]` macro to generate default
+/// load / save implementations.
+pub trait ControlOps<'a>: Control<'a> {
+    /// Reads the values of the mixer control.
+    fn load(&mut self) -> Result<<Self as Control<'a>>::Item>;
+    /// Saves the values to the mixer control.
+    fn save(&mut self, val: <Self as Control<'a>>::Item) -> Result<bool>;
+}
+
+/// `Control` that reads and writes a single integer value entry.
+/// Since this crate is the `cros_alsa` crate, we replace the `cros_alsa`
+/// path to `crate` in derive macros by `cros_alsa` attribute.
+#[derive(ControlOps)]
+#[cros_alsa(path = "crate")]
+pub struct IntControl<'a> {
+    handle: &'a mut Ctl,
+    id: ElemId,
+}
+
+impl<'a> IntControl<'a> {
+    /// Gets an i32 value from the mixer control.
+    ///
+    /// # Errors
+    ///
+    /// * If it fails to read from the control.
+    pub fn get(&mut self) -> Result<i32> {
+        let val = self.load()?;
+        Ok(val[0])
+    }
+
+    /// Updates an i32 value to the mixer control.
+    ///
+    /// # Errors
+    ///
+    /// * If it fails to write to the control.
+    pub fn set(&mut self, val: i32) -> Result<()> {
+        self.save([val])?;
+        Ok(())
+    }
+}
+
+impl<'a> Control<'a> for IntControl<'a> {
+    type Item = [i32; 1];
+    fn new(handle: &'a mut Ctl, id: ElemId) -> Self {
+        Self { handle, id }
+    }
+}
+
+/// Stereo Volume Mixer Control
+/// Since this crate is the `cros_alsa` crate, we replace the `cros_alsa`
+/// path to `crate` in derive macros by `cros_alsa` attribute.
+#[derive(ControlOps)]
+#[cros_alsa(path = "crate")]
+pub struct StereoVolumeControl<'a> {
+    handle: &'a mut Ctl,
+    id: ElemId,
+}
+
+impl<'a> StereoVolumeControl<'a> {
+    /// Reads the left and right volume.
+    ///
+    /// # Errors
+    ///
+    /// * If it fails to read from the control.
+    pub fn volume(&mut self) -> Result<(i32, i32)> {
+        let val = self.load()?;
+        Ok((val[0], val[1]))
+    }
+
+    /// Updates the left and right volume.
+    ///
+    /// # Errors
+    ///
+    /// * If it fails to write to the control.
+    pub fn set_volume(&mut self, left: i32, right: i32) -> Result<()> {
+        self.save([left, right])?;
+        Ok(())
+    }
+}
+
+impl<'a> Control<'a> for StereoVolumeControl<'a> {
+    type Item = [i32; 2];
+    fn new(handle: &'a mut Ctl, id: ElemId) -> Self {
+        Self { handle, id }
+    }
+}
+
+/// `Control` that reads and writes a single boolean value entry.
+/// Since this crate is the `cros_alsa` crate, we replace the `cros_alsa`
+/// path to `crate` in derive macros by `cros_alsa` attribute.
+#[derive(ControlOps)]
+#[cros_alsa(path = "crate")]
+pub struct SwitchControl<'a> {
+    handle: &'a mut Ctl,
+    id: ElemId,
+}
+
+impl<'a> SwitchControl<'a> {
+    /// Reads the state of a switch type mix control.
+    ///
+    /// # Errors
+    ///
+    /// * If it fails to read from the control.
+    pub fn state(&mut self) -> Result<bool> {
+        let v = self.load()?;
+        Ok(v[0])
+    }
+
+    /// Updates the control state to true.
+    ///
+    /// # Errors
+    ///
+    /// * If it fails to write to the control.
+    pub fn on(&mut self) -> Result<()> {
+        self.save([true])?;
+        Ok(())
+    }
+
+    /// Updates the control state to false.
+    ///
+    /// # Errors
+    ///
+    /// * If it fails to write to the control.
+    pub fn off(&mut self) -> Result<()> {
+        self.save([false])?;
+        Ok(())
+    }
+}
+
+impl<'a> Control<'a> for SwitchControl<'a> {
+    type Item = [bool; 1];
+    fn new(handle: &'a mut Ctl, id: ElemId) -> Self {
+        Self { handle, id }
+    }
+}
diff --git a/cros_alsa/src/control_primitive.rs b/cros_alsa/src/control_primitive.rs
new file mode 100644
index 0000000..227aef2
--- /dev/null
+++ b/cros_alsa/src/control_primitive.rs
@@ -0,0 +1,406 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::convert::TryFrom;
+use std::error;
+use std::ffi::{CStr, CString, FromBytesWithNulError, NulError};
+use std::fmt;
+use std::marker::PhantomData;
+use std::ptr;
+use std::slice;
+use std::str;
+
+use alsa_sys::*;
+use libc::strlen;
+use remain::sorted;
+
+pub type Result<T> = std::result::Result<T, Error>;
+
+#[derive(Debug, PartialEq)]
+/// Possible errors that can occur in FFI functions.
+pub enum FFIError {
+    Rc(i32),
+    NullPtr,
+}
+
+impl fmt::Display for FFIError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        use FFIError::*;
+        match self {
+            Rc(rc) => write!(f, "{}", snd_strerror(*rc)?),
+            NullPtr => write!(f, "the return value is a null pointer"),
+        }
+    }
+}
+
+#[sorted]
+#[derive(Debug, PartialEq)]
+/// Possible errors that can occur in cros-alsa::control_primitive.
+pub enum Error {
+    /// Control with the given name does not exist.
+    ControlNotFound(String),
+    /// Failed to call snd_ctl_open().
+    CtlOpenFailed(FFIError, String),
+    /// snd_ctl_elem_id_get_name() returns null.
+    ElemIdGetNameFailed,
+    /// Failed to call snd_ctl_elem_id_malloc().
+    ElemIdMallocFailed(FFIError),
+    /// Failed to call snd_ctl_elem_info_malloc().
+    ElemInfoMallocFailed(FFIError),
+    /// Failed to call snd_ctl_elem_value_malloc().
+    ElemValueMallocFailed(FFIError),
+    /// The slice used to create a CStr does not have one and only one null
+    /// byte positioned at the end.
+    FromBytesWithNulError(FromBytesWithNulError),
+    /// Failed to convert to a valid ElemType.
+    InvalidElemType(u32),
+    /// An error indicating that an interior nul byte was found.
+    NulError(NulError),
+    /// Failed to call snd_strerror().
+    SndStrErrorFailed(i32),
+    /// UTF-8 validation failed
+    Utf8Error(str::Utf8Error),
+}
+
+impl error::Error for Error {}
+
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        use Error::*;
+        match self {
+            ControlNotFound(name) => write!(f, "control: {} does not exist", name),
+            CtlOpenFailed(e, name) => write!(f, "{} snd_ctl_open failed: {}", name, e,),
+            ElemIdGetNameFailed => write!(f, "snd_ctl_elem_id_get_name failed"),
+            ElemIdMallocFailed(e) => write!(f, "snd_ctl_elem_id_malloc failed: {}", e),
+            ElemInfoMallocFailed(e) => write!(f, "snd_ctl_elem_info_malloc failed: {}", e),
+            ElemValueMallocFailed(e) => write!(f, "snd_ctl_elem_value_malloc failed: {}", e),
+            FromBytesWithNulError(e) => write!(f, "invalid CString: {}", e),
+            InvalidElemType(v) => write!(f, "invalid ElemType: {}", v),
+            NulError(e) => write!(f, "invalid CString: {}", e),
+            SndStrErrorFailed(e) => write!(f, "snd_strerror() failed: {}", e),
+            Utf8Error(e) => write!(f, "{}", e),
+        }
+    }
+}
+
+impl From<Error> for fmt::Error {
+    fn from(_err: Error) -> fmt::Error {
+        fmt::Error
+    }
+}
+
+impl From<str::Utf8Error> for Error {
+    fn from(err: str::Utf8Error) -> Error {
+        Error::Utf8Error(err)
+    }
+}
+
+impl From<FromBytesWithNulError> for Error {
+    fn from(err: FromBytesWithNulError) -> Error {
+        Error::FromBytesWithNulError(err)
+    }
+}
+
+impl From<NulError> for Error {
+    fn from(err: NulError) -> Error {
+        Error::NulError(err)
+    }
+}
+
+/// [snd_ctl_elem_iface_t](https://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html#ga14baa0febb91cc4c5d72dcc825acf518) wrapper.
+#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum ElemIface {
+    Card = SND_CTL_ELEM_IFACE_CARD as isize,
+    Hwdep = SND_CTL_ELEM_IFACE_HWDEP as isize,
+    Mixer = SND_CTL_ELEM_IFACE_MIXER as isize,
+    PCM = SND_CTL_ELEM_IFACE_PCM as isize,
+    Rawmidi = SND_CTL_ELEM_IFACE_RAWMIDI as isize,
+    Timer = SND_CTL_ELEM_IFACE_TIMER as isize,
+    Sequencer = SND_CTL_ELEM_IFACE_SEQUENCER as isize,
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+/// [snd_ctl_elem_type_t](https://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html#gac42e0ed6713b62711af5e80b4b3bcfec) wrapper.
+pub enum ElemType {
+    None = SND_CTL_ELEM_TYPE_NONE as isize,
+    Boolean = SND_CTL_ELEM_TYPE_BOOLEAN as isize,
+    Integer = SND_CTL_ELEM_TYPE_INTEGER as isize,
+    Enumerated = SND_CTL_ELEM_TYPE_ENUMERATED as isize,
+    Bytes = SND_CTL_ELEM_TYPE_BYTES as isize,
+    IEC958 = SND_CTL_ELEM_TYPE_IEC958 as isize,
+    Integer64 = SND_CTL_ELEM_TYPE_INTEGER64 as isize,
+}
+
+impl fmt::Display for ElemType {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            ElemType::None => write!(f, "SND_CTL_ELEM_TYPE_NONE"),
+            ElemType::Boolean => write!(f, "SND_CTL_ELEM_TYPE_BOOLEAN"),
+            ElemType::Integer => write!(f, "SND_CTL_ELEM_TYPE_INTEGER"),
+            ElemType::Enumerated => write!(f, "SND_CTL_ELEM_TYPE_ENUMERATED"),
+            ElemType::Bytes => write!(f, "SND_CTL_ELEM_TYPE_BYTES"),
+            ElemType::IEC958 => write!(f, "SND_CTL_ELEM_TYPE_IEC958"),
+            ElemType::Integer64 => write!(f, "SND_CTL_ELEM_TYPE_INTEGER64"),
+        }
+    }
+}
+
+impl TryFrom<u32> for ElemType {
+    type Error = Error;
+    fn try_from(elem_type: u32) -> Result<ElemType> {
+        match elem_type {
+            SND_CTL_ELEM_TYPE_NONE => Ok(ElemType::None),
+            SND_CTL_ELEM_TYPE_BOOLEAN => Ok(ElemType::Boolean),
+            SND_CTL_ELEM_TYPE_INTEGER => Ok(ElemType::Integer),
+            SND_CTL_ELEM_TYPE_ENUMERATED => Ok(ElemType::Enumerated),
+            SND_CTL_ELEM_TYPE_BYTES => Ok(ElemType::Bytes),
+            SND_CTL_ELEM_TYPE_IEC958 => Ok(ElemType::IEC958),
+            SND_CTL_ELEM_TYPE_INTEGER64 => Ok(ElemType::Integer64),
+            _ => Err(Error::InvalidElemType(elem_type)),
+        }
+    }
+}
+
+/// [snd_ctl_elem_id_t](https://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html#gad6c3746f1925bfec6a4fd0e913430e55) wrapper.
+pub struct ElemId(
+    ptr::NonNull<snd_ctl_elem_id_t>,
+    PhantomData<snd_ctl_elem_id_t>,
+);
+
+impl Drop for ElemId {
+    fn drop(&mut self) {
+        // Safe because self.0.as_ptr() is a valid snd_ctl_elem_id_t*.
+        unsafe { snd_ctl_elem_id_free(self.0.as_ptr()) };
+    }
+}
+
+impl ElemId {
+    /// Creates an `ElemId` object by `ElemIface` and name.
+    ///
+    /// # Errors
+    ///
+    /// * If memory allocation fails.
+    /// * If ctl_name is not a valid CString.
+    pub fn new(iface: ElemIface, ctl_name: &str) -> Result<ElemId> {
+        let mut id_ptr = ptr::null_mut();
+        // Safe because we provide a valid id_ptr to be filled,
+        // and we validate the return code before using id_ptr.
+        let rc = unsafe { snd_ctl_elem_id_malloc(&mut id_ptr) };
+        if rc < 0 {
+            return Err(Error::ElemIdMallocFailed(FFIError::Rc(rc)));
+        }
+        let id = ptr::NonNull::new(id_ptr).ok_or(Error::ElemIdMallocFailed(FFIError::NullPtr))?;
+
+        // Safe because id.as_ptr() is a valid snd_ctl_elem_id_t*.
+        unsafe { snd_ctl_elem_id_set_interface(id.as_ptr(), iface as u32) };
+        let name = CString::new(ctl_name)?;
+        // Safe because id.as_ptr() is a valid snd_ctl_elem_id_t* and name is a safe CString.
+        unsafe { snd_ctl_elem_id_set_name(id.as_ptr(), name.as_ptr()) };
+        Ok(ElemId(id, PhantomData))
+    }
+
+    /// Borrows the const inner pointer.
+    pub fn as_ptr(&self) -> *const snd_ctl_elem_id_t {
+        self.0.as_ptr()
+    }
+
+    /// Safe [snd_ctl_elem_id_get_name()] (https://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html#gaa6cfea3ac963bfdaeb8189e03e927a76) wrapper.
+    ///
+    /// # Errors
+    ///
+    /// * If snd_ctl_elem_id_get_name() fails.
+    /// * If control element name is not a valid CString.
+    /// * If control element name is not valid UTF-8 data.
+    pub fn name(&self) -> Result<&str> {
+        // Safe because self.as_ptr() is a valid snd_ctl_elem_id_t*.
+        let name = unsafe { snd_ctl_elem_id_get_name(self.as_ptr()) };
+        if name.is_null() {
+            return Err(Error::ElemIdGetNameFailed);
+        }
+        // Safe because name is a valid *const i8, and its life time
+        // is the same as the passed reference of self.
+        let s = CStr::from_bytes_with_nul(unsafe {
+            slice::from_raw_parts(name as *const u8, strlen(name) + 1)
+        })?;
+        Ok(s.to_str()?)
+    }
+}
+
+/// [snd_ctl_elem_value_t](https://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html#ga266b478eb64f1cdd75e337df4b4b995e) wrapper.
+pub struct ElemValue(
+    ptr::NonNull<snd_ctl_elem_value_t>,
+    PhantomData<snd_ctl_elem_value_t>,
+);
+
+impl Drop for ElemValue {
+    // Safe because self.0.as_ptr() is valid.
+    fn drop(&mut self) {
+        unsafe { snd_ctl_elem_value_free(self.0.as_ptr()) };
+    }
+}
+
+impl ElemValue {
+    /// Creates an `ElemValue`.
+    ///
+    /// # Errors
+    ///
+    /// * If memory allocation fails.
+    pub fn new(id: &ElemId) -> Result<ElemValue> {
+        let mut v_ptr = ptr::null_mut();
+        // Safe because we provide a valid v_ptr to be filled,
+        // and we validate the return code before using v_ptr.
+        let rc = unsafe { snd_ctl_elem_value_malloc(&mut v_ptr) };
+        if rc < 0 {
+            return Err(Error::ElemValueMallocFailed(FFIError::Rc(rc)));
+        }
+        let value =
+            ptr::NonNull::new(v_ptr).ok_or(Error::ElemValueMallocFailed(FFIError::NullPtr))?;
+        // Safe because value.as_ptr() is a valid snd_ctl_elem_value_t* and id.as_ptr() is also valid.
+        unsafe { snd_ctl_elem_value_set_id(value.as_ptr(), id.as_ptr()) };
+        Ok(ElemValue(value, PhantomData))
+    }
+
+    /// Borrows the mutable inner pointer.
+    pub fn as_mut_ptr(&mut self) -> *mut snd_ctl_elem_value_t {
+        self.0.as_ptr()
+    }
+
+    /// Borrows the const inner pointer.
+    pub fn as_ptr(&self) -> *const snd_ctl_elem_value_t {
+        self.0.as_ptr()
+    }
+}
+
+/// [snd_ctl_elem_info_t](https://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html#ga2cae0bb76df919368e4ff9a7021dd3ab) wrapper.
+pub struct ElemInfo(
+    ptr::NonNull<snd_ctl_elem_info_t>,
+    PhantomData<snd_ctl_elem_info_t>,
+);
+
+impl Drop for ElemInfo {
+    fn drop(&mut self) {
+        // Safe because self.0.as_ptr() is a valid snd_ctl_elem_info_t*.
+        unsafe { snd_ctl_elem_info_free(self.0.as_ptr()) };
+    }
+}
+
+impl ElemInfo {
+    /// Creates an `ElemInfo`.
+    ///
+    /// # Errors
+    ///
+    /// * If memory allocation fails.
+    /// * If control does not exist.
+    pub fn new(handle: &mut Ctl, id: &ElemId) -> Result<ElemInfo> {
+        let mut info_ptr = ptr::null_mut();
+
+        // Safe because we provide a valid info_ptr to be filled,
+        // and we validate the return code before using info_ptr.
+        let rc = unsafe { snd_ctl_elem_info_malloc(&mut info_ptr) };
+        if rc < 0 {
+            return Err(Error::ElemInfoMallocFailed(FFIError::Rc(rc)));
+        }
+        let info =
+            ptr::NonNull::new(info_ptr).ok_or(Error::ElemInfoMallocFailed(FFIError::NullPtr))?;
+
+        // Safe because info.as_ptr() is a valid snd_ctl_elem_info_t* and id.as_ptr() is also valid.
+        unsafe { snd_ctl_elem_info_set_id(info.as_ptr(), id.as_ptr()) };
+
+        // Safe because handle.as_mut_ptr() is a valid snd_ctl_t* and info.as_ptr() is a valid
+        // snd_ctl_elem_info_t*.
+        let rc = unsafe { snd_ctl_elem_info(handle.as_mut_ptr(), info.as_ptr()) };
+        if rc < 0 {
+            return Err(Error::ControlNotFound(id.name()?.to_owned()));
+        }
+        Ok(ElemInfo(info, PhantomData))
+    }
+
+    /// Safe [snd_ctl_elem_info_get_type](https://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html#ga0fec5d22ee58d04f14b59f405adc595e) wrapper.
+    pub fn elem_type(&self) -> Result<ElemType> {
+        // Safe because self.0.as_ptr() is a valid snd_ctl_elem_info_t*.
+        unsafe { ElemType::try_from(snd_ctl_elem_info_get_type(self.0.as_ptr())) }
+    }
+
+    /// Safe [snd_ctl_elem_info_get_count](https://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html#gaa75a20d4190d324bcda5fd6659a4b377) wrapper.
+    pub fn count(&self) -> usize {
+        // Safe because self.0.as_ptr() is a valid snd_ctl_elem_info_t*.
+        unsafe { snd_ctl_elem_info_get_count(self.0.as_ptr()) as usize }
+    }
+
+    /// Safe [snd_ctl_elem_info_is_tlv_readable](https://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html#gaac6bb412e5a9fffb5509e98a10de45b5) wrapper.
+    pub fn tlv_readable(&self) -> bool {
+        // Safe because self.0.as_ptr() is a valid snd_ctl_elem_info_t*.
+        unsafe { snd_ctl_elem_info_is_tlv_readable(self.0.as_ptr()) as usize == 1 }
+    }
+
+    /// Safe [snd_ctl_elem_info_is_tlv_writable](https://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html#gacfbaae80d710b6feac682f8ba10a0341) wrapper.
+    pub fn tlv_writable(&self) -> bool {
+        // Safe because self.0.as_ptr() is a valid snd_ctl_elem_info_t*.
+        unsafe { snd_ctl_elem_info_is_tlv_writable(self.0.as_ptr()) as usize == 1 }
+    }
+}
+
+/// [snd_ctl_t](https://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html#ga06628f38def84a0fe3da74041db9d51f) wrapper.
+#[derive(Debug)]
+pub struct Ctl(ptr::NonNull<snd_ctl_t>, PhantomData<snd_ctl_t>);
+
+impl Drop for Ctl {
+    fn drop(&mut self) {
+        // Safe as we provide a valid snd_ctl_t*.
+        unsafe { snd_ctl_close(self.0.as_ptr()) };
+    }
+}
+
+impl Ctl {
+    /// Creates a `Ctl`.
+    /// Safe [snd_ctl_open](https://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html#ga58537f5b74c9c1f51699f9908a0d7f56).
+    /// Does not support async mode.
+    ///
+    /// # Errors
+    ///
+    /// * If `card` is an invalid CString.
+    /// * If `snd_ctl_open()` fails.
+    pub fn new(card: &str) -> Result<Ctl> {
+        let name = CString::new(card)?;
+        let mut ctl_ptr = ptr::null_mut();
+        // Safe because we provide a valid ctl_ptr to be filled, name is a safe CString
+        // and we validate the return code before using ctl_ptr.
+        let rc = unsafe { snd_ctl_open(&mut ctl_ptr, name.as_ptr(), 0) };
+        if rc < 0 {
+            return Err(Error::CtlOpenFailed(
+                FFIError::Rc(rc),
+                name.to_str()?.to_owned(),
+            ));
+        }
+        let ctl = ptr::NonNull::new(ctl_ptr).ok_or(Error::CtlOpenFailed(
+            FFIError::NullPtr,
+            name.to_str()?.to_owned(),
+        ))?;
+        Ok(Ctl(ctl, PhantomData))
+    }
+
+    /// Borrows the mutable inner pointer
+    pub fn as_mut_ptr(&mut self) -> *mut snd_ctl_t {
+        self.0.as_ptr()
+    }
+}
+
+/// Safe [snd_strerror](https://www.alsa-project.org/alsa-doc/alsa-lib/group___error.html#ga182bbadf2349e11602bc531e8cf22f7e) wrapper.
+///
+/// # Errors
+///
+/// * If `snd_strerror` returns invalid UTF-8 data.
+pub fn snd_strerror(err_num: i32) -> Result<&'static str> {
+    // Safe because we validate the return pointer of snd_strerror()
+    // before using it.
+    let s_ptr = unsafe { alsa_sys::snd_strerror(err_num) };
+    if s_ptr.is_null() {
+        return Err(Error::SndStrErrorFailed(err_num));
+    }
+    // Safe because s_ptr is a non-null *const u8 and its lifetime is static.
+    let s = CStr::from_bytes_with_nul(unsafe {
+        slice::from_raw_parts(s_ptr as *const u8, strlen(s_ptr) + 1)
+    })?;
+    Ok(s.to_str()?)
+}
diff --git a/cros_alsa/src/control_tlv.rs b/cros_alsa/src/control_tlv.rs
new file mode 100644
index 0000000..e1f3551
--- /dev/null
+++ b/cros_alsa/src/control_tlv.rs
@@ -0,0 +1,313 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! `ControlTLV` supports read and write of the alsa TLV byte controls
+//!  Users can obtain a ControlTLV by Card::control_tlv_by_name().
+//! # Examples
+//! This is an example of how to use a `TLV`.
+//!
+//! ```
+//! use std::assert_eq;
+//! use std::convert::TryFrom;
+//! use std::error::Error;
+//!
+//! use cros_alsa::{TLV, ControlTLVError};
+//! use cros_alsa::elem::Elem;
+//!
+//! type Result<T> = std::result::Result<T, ControlTLVError>;
+//!
+//!     let mut tlv = TLV::new(0, vec![1,2,3,4]);
+//!     assert_eq!(4, tlv.len());
+//!     assert_eq!(0, tlv.tlv_type());
+//!     assert_eq!(2, tlv[1]);
+//!     tlv[1] = 8;
+//!     assert_eq!(vec![1,8,3,4], tlv.value().to_vec());
+//!     assert_eq!(vec![0,16,1,8,3,4], Into::<Vec<u32>>::into(tlv));
+//!
+//! ```
+
+use std::{
+    convert::TryFrom,
+    fmt,
+    ops::{Index, IndexMut},
+    slice::SliceIndex,
+};
+use std::{error, mem::size_of};
+
+use remain::sorted;
+
+use crate::control_primitive::{self, Ctl, ElemId, ElemInfo, ElemType};
+
+/// The Result type of cros-alsa::control.
+pub type Result<T> = std::result::Result<T, Error>;
+
+#[sorted]
+#[derive(Debug, PartialEq)]
+/// Possible errors that can occur in cros-alsa::control.
+pub enum Error {
+    /// Failed to call AlsaControlAPI.
+    AlsaControlAPI(control_primitive::Error),
+    /// Failed to convert buffer to TLV struct.
+    InvalidTLV,
+    /// ElemInfo::count() is not multiple of size_of::<u32>.
+    InvalidTLVSize(String, usize),
+    /// ElemInfo::elem_type() is not ElemType::Bytes.
+    InvalidTLVType(String, ElemType),
+    /// The control is not readable.
+    TLVNotReadable,
+    /// The control is not writeable.
+    TLVNotWritable,
+    /// Failed to call snd_ctl_elem_tlv_read.
+    TLVReadFailed(i32),
+    /// Failed to call snd_ctl_elem_tlv_write.
+    TVLWriteFailed(i32),
+}
+
+impl error::Error for Error {}
+
+impl From<control_primitive::Error> for Error {
+    fn from(err: control_primitive::Error) -> Error {
+        Error::AlsaControlAPI(err)
+    }
+}
+
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        use Error::*;
+        match self {
+            AlsaControlAPI(e) => write!(f, "{}", e),
+            InvalidTLV => write!(f, "failed to convert to TLV"),
+            InvalidTLVSize(name, elem_size) => write!(
+                f,
+                "ElemInfo::size() of {} should be multiple of size_of::<u32>, get: {}",
+                name, elem_size
+            ),
+            InvalidTLVType(name, elem_type) => write!(
+                f,
+                "invalid ElemInfo::elem_type() of {}: expect: {}, get: {}",
+                name,
+                ElemType::Bytes,
+                elem_type
+            ),
+            TLVNotReadable => write!(f, "the control is not readable."),
+            TLVNotWritable => write!(f, "the control is not writable."),
+            TLVReadFailed(rc) => write!(f, "snd_ctl_elem_tlv_read failed: {}", rc),
+            TVLWriteFailed(rc) => write!(f, "snd_ctl_elem_tlv_write failed: {}", rc),
+        }
+    }
+}
+
+/// TLV struct represents the TLV data to be read from
+/// or write to an alsa TLV byte control.
+#[derive(Debug)]
+pub struct TLV {
+    /// data[Self::TYPE_OFFSET] contains the tlv type.
+    /// data[Self::LEN_OFFSET] contains the length of the value in bytes.
+    /// data[Self::VALUE_OFFSET..] contains the data.
+    data: Vec<u32>,
+}
+
+impl TLV {
+    const TYPE_OFFSET: usize = 0;
+    const LEN_OFFSET: usize = 1;
+    const VALUE_OFFSET: usize = 2;
+    const TLV_HEADER_SIZE_BYTES: usize = Self::VALUE_OFFSET * size_of::<u32>();
+
+    /// Initializes a `TLV` by giving the tlv type and tlv value.
+    pub fn new(tlv_type: u32, tlv_value: Vec<u32>) -> Self {
+        let mut data = vec![0; 2];
+        data[Self::TYPE_OFFSET] = tlv_type;
+        data[Self::LEN_OFFSET] = (tlv_value.len() * size_of::<u32>()) as u32;
+        data.extend(tlv_value.iter());
+        Self { data }
+    }
+
+    /// Returns the type of the tlv.
+    pub fn tlv_type(&self) -> u32 {
+        self.data[Self::TYPE_OFFSET]
+    }
+
+    /// Returns the length of the tlv value in dword.
+    pub fn len(&self) -> usize {
+        self.data[Self::LEN_OFFSET] as usize / size_of::<u32>()
+    }
+
+    /// Returns whether the tlv value is empty.
+    pub fn is_empty(&self) -> bool {
+        self.data[Self::LEN_OFFSET] == 0
+    }
+
+    /// Returns the tlv value in slice.
+    pub fn value(&self) -> &[u32] {
+        &self.data[Self::VALUE_OFFSET..]
+    }
+}
+
+impl<I: SliceIndex<[u32]>> Index<I> for TLV {
+    type Output = I::Output;
+    #[inline]
+    fn index(&self, index: I) -> &Self::Output {
+        &self.data[Self::VALUE_OFFSET..][index]
+    }
+}
+
+impl<I: SliceIndex<[u32]>> IndexMut<I> for TLV {
+    #[inline]
+    fn index_mut(&mut self, index: I) -> &mut Self::Output {
+        &mut self.data[Self::VALUE_OFFSET..][index]
+    }
+}
+
+impl TryFrom<Vec<u32>> for TLV {
+    type Error = Error;
+
+    /// Constructs a TLV from a vector with the following alsa tlv header validation:
+    ///  1 . tlv_buf[Self::LEN_OFFSET] should be multiple of size_of::<u32>
+    ///  2 . tlv_buf[Self::LEN_OFFSET] is the length of tlv value in byte and
+    ///      should be less than the buffer length * size_of::<u32>.
+    fn try_from(data: Vec<u32>) -> Result<Self> {
+        if data.len() < 2 {
+            return Err(Error::InvalidTLV);
+        }
+
+        if data[Self::LEN_OFFSET] % size_of::<u32>() as u32 != 0 {
+            return Err(Error::InvalidTLV);
+        }
+
+        if data[Self::LEN_OFFSET] / size_of::<u32>() as u32
+            > data[Self::VALUE_OFFSET..].len() as u32
+        {
+            return Err(Error::InvalidTLV);
+        }
+
+        Ok(Self { data })
+    }
+}
+
+impl Into<Vec<u32>> for TLV {
+    /// Returns the raw tlv data buffer (including the tlv header).
+    fn into(self) -> Vec<u32> {
+        self.data
+    }
+}
+
+/// `ControlTLV` supports read and write of the alsa TLV byte controls.
+pub struct ControlTLV<'a> {
+    handle: &'a mut Ctl,
+    id: ElemId,
+}
+
+impl<'a> ControlTLV<'a> {
+    /// Called by `Card` to create a `ControlTLV`.
+    pub fn new(handle: &'a mut Ctl, id: ElemId) -> Result<Self> {
+        let info = ElemInfo::new(handle, &id)?;
+        if info.count() % size_of::<u32>() != 0 {
+            return Err(Error::InvalidTLVSize(id.name()?.to_owned(), info.count()));
+        }
+        match info.elem_type()? {
+            ElemType::Bytes => Ok(Self { handle, id }),
+            _ => Err(Error::InvalidTLVType(
+                id.name()?.to_owned(),
+                info.elem_type()?,
+            )),
+        }
+    }
+
+    /// Reads data from the byte control by `snd_ctl_elem_tlv_read`
+    ///
+    /// #
+    /// # Errors
+    ///
+    /// * If it fails to read from the control.
+    pub fn load(&mut self) -> Result<TLV> {
+        if !ElemInfo::new(self.handle, &self.id)?.tlv_readable() {
+            return Err(Error::TLVNotReadable);
+        }
+
+        let tlv_size = ElemInfo::new(self.handle, &self.id)?.count() + TLV::TLV_HEADER_SIZE_BYTES;
+
+        let mut tlv_buf = vec![0; tlv_size / size_of::<u32>()];
+        // Safe because handle.as_mut_ptr() is a valid *mut snd_ctl_t, id_as_ptr is valid and
+        // tlv_buf.as_mut_ptr() is also valid.
+        let rc = unsafe {
+            alsa_sys::snd_ctl_elem_tlv_read(
+                self.handle.as_mut_ptr(),
+                self.id.as_ptr(),
+                tlv_buf.as_mut_ptr(),
+                tlv_size as u32,
+            )
+        };
+        if rc < 0 {
+            return Err(Error::TLVReadFailed(rc));
+        }
+        Ok(TLV::try_from(tlv_buf)?)
+    }
+
+    /// Writes to the byte control by `snd_ctl_elem_tlv_write`
+    ///
+    /// # Results
+    ///
+    /// * `changed` - false on success.
+    ///             - true on success when value was changed.
+    /// #
+    /// # Errors
+    ///
+    /// * If it fails to write to the control.
+    pub fn save(&mut self, tlv: TLV) -> Result<bool> {
+        if !ElemInfo::new(self.handle, &self.id)?.tlv_writable() {
+            return Err(Error::TLVNotReadable);
+        }
+        // Safe because handle.as_mut_ptr() is a valid *mut snd_ctl_t, id_as_ptr is valid and
+        // tlv.as_mut_ptr() is also valid.
+        let rc = unsafe {
+            alsa_sys::snd_ctl_elem_tlv_write(
+                self.handle.as_mut_ptr(),
+                self.id.as_ptr(),
+                Into::<Vec<u32>>::into(tlv).as_mut_ptr(),
+            )
+        };
+        if rc < 0 {
+            return Err(Error::TVLWriteFailed(rc));
+        }
+        Ok(rc > 0)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    #[test]
+    fn test_tlv_try_from_raw_vec() {
+        let tlv_buf = vec![0, 12, 2, 3, 4];
+        assert!(TLV::try_from(tlv_buf).is_ok());
+    }
+
+    #[test]
+    fn test_tlv_length_is_not_multiple_of_sizeof_int() {
+        // Invalid tlv length in data[Self::LEN_OFFSET].
+        let tlv_buf = vec![0, 1, 2, 3, 4];
+        assert_eq!(TLV::try_from(tlv_buf).unwrap_err(), Error::InvalidTLV);
+    }
+
+    #[test]
+    fn test_tlv_length_larger_than_buff_size() {
+        // Invalid tlv length in data[Self::LEN_OFFSET].
+        let tlv_buf = vec![0, 16, 2, 3, 4];
+        assert_eq!(TLV::try_from(tlv_buf).unwrap_err(), Error::InvalidTLV);
+    }
+
+    #[test]
+    fn test_tlv_length_less_than_two() {
+        // tlv buffer length < 2
+        let tlv_buf = vec![0];
+        assert_eq!(TLV::try_from(tlv_buf).unwrap_err(), Error::InvalidTLV);
+    }
+
+    #[test]
+    fn test_tlv_length_equal_two() {
+        // tlv buffer size = 2.
+        let tlv_buf = vec![0, 0];
+        assert!(TLV::try_from(tlv_buf).is_ok());
+    }
+}
diff --git a/cros_alsa/src/elem.rs b/cros_alsa/src/elem.rs
new file mode 100644
index 0000000..08fe8b6
--- /dev/null
+++ b/cros_alsa/src/elem.rs
@@ -0,0 +1,204 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! This module provides different implementations of `Elem` that use the alsa-lib control interface
+//! API to read and write alsa control elements.
+//!
+//! The `Elem::type()` returns the type of value that a control element can interact with,
+//! and it is one of integer, integer64, boolean, enumerators, bytes or IEC958 structure.
+//! The `Elem::size()` returns the number of values it reads from or writes to the hardware
+//! at a time.
+//! The `Elem::load(..)` and `Elem::save(..)` are used by `ControlOps` trait to read and write
+//! the underlying mixer control.
+//!
+//! Users should use the provided implementations of `Elem` to define the associated type in
+//! their owner encapsulation of `Control`.
+
+use std::default::Default;
+use std::error;
+use std::fmt;
+
+use libc::{c_long, c_uint};
+use remain::sorted;
+
+use crate::control_primitive::{self, snd_strerror, Ctl, ElemId, ElemType, ElemValue};
+
+/// The Result type of cros-alsa::elem.
+pub type Result<T> = std::result::Result<T, Error>;
+
+#[sorted]
+#[derive(Debug)]
+/// Possible errors that can occur in cros-alsa::elem.
+pub enum Error {
+    /// Failed to call AlsaControlAPI.
+    AlsaControlAPI(control_primitive::Error),
+    /// Failed to call `snd_ctl_elem_read()`.
+    ElemReadFailed(i32),
+    /// Failed to call `snd_ctl_elem_write()`.
+    ElemWriteFailed(i32),
+}
+
+impl error::Error for Error {}
+
+impl From<control_primitive::Error> for Error {
+    fn from(err: control_primitive::Error) -> Error {
+        Error::AlsaControlAPI(err)
+    }
+}
+
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        use Error::*;
+        match self {
+            AlsaControlAPI(e) => write!(f, "{}", e),
+            ElemReadFailed(e) => write!(f, "snd_ctl_elem_read failed: {}", snd_strerror(*e)?),
+            ElemWriteFailed(e) => write!(f, "snd_ctl_elem_write failed: {}", snd_strerror(*e)?),
+        }
+    }
+}
+
+// Uses a recursive macro to generate implementation for [bool; n] and [i32; n], n = 1 to 128.
+// The `$t:ident $($ts:ident)*` part matches and removes one token at a time. It's used for
+// counting recursive steps.
+macro_rules! impl_for_array {
+    {$n:expr, $type:ty, $t:ident $($ts:ident)*} => {
+        impl Elem for [$type; $n] {
+            type T = Self;
+            /// Reads [$type; $n] data from the mixer control.
+            ///
+            /// # Errors
+            ///
+            /// * If it fails to call `snd_ctl_elem_read()`.
+            fn load(handle: &mut Ctl, id: &ElemId) -> Result<Self::T>
+            {
+                let mut elem = ElemValue::new(id)?;
+                // Safe because self.handle.as_mut_ptr() is a valid *mut snd_ctl_t and
+                // elem.as_mut_ptr() is also a valid *mut snd_ctl_elem_value_t.
+                let rc = unsafe { alsa_sys::snd_ctl_elem_read(handle.as_mut_ptr(), elem.as_mut_ptr()) };
+                if rc < 0 {
+                    return Err(Error::ElemReadFailed(rc));
+                }
+                let mut ret = [Default::default(); $n];
+                for i in 0..$n {
+                    // Safe because elem.as_ptr() is a valid snd_ctl_elem_value_t* and i is guaranteed to be
+                    // within a valid range.
+                    ret[i] = unsafe { <$type>::elem_value_get(&elem, i) };
+                }
+                Ok(ret)
+            }
+
+            /// Updates [$type; $n] data to the mixer control.
+            ///
+            /// # Results
+            ///
+            /// * `changed` - false on success.
+            ///             - true on success when value was changed.
+            ///
+            /// # Errors
+            ///
+            /// * If it fails to call `snd_ctl_elem_write()`.
+            fn save(handle: &mut Ctl, id: &ElemId, val: Self::T) -> Result<bool> {
+                let mut elem = ElemValue::new(id)?;
+                for i in 0..$n {
+                    // Safe because elem.as_mut_ptr() is a valid snd_ctl_elem_value_t* and i is guaranteed to be
+                    // within a valid range.
+                    unsafe { <$type>::elem_value_set(&mut elem, i, val[i]) };
+                }
+                // Safe because self.handle.as_mut_ptr() is a valid *mut snd_ctl_t and
+                // elem.as_mut_ptr() is also a valid *mut snd_ctl_elem_value_t.
+                let rc = unsafe { alsa_sys::snd_ctl_elem_write(handle.as_mut_ptr(), elem.as_mut_ptr()) };
+                if rc < 0 {
+                    return Err(Error::ElemWriteFailed(rc));
+                }
+                Ok(rc > 0)
+            }
+
+            /// Gets the data type itself can read and write.
+            fn elem_type() -> ElemType {
+                <$type>::elem_type()
+            }
+
+            /// Gets the number of value entries itself can read and write.
+            fn size() -> usize {
+                $n
+            }
+        }
+        impl_for_array!{($n - 1), $type, $($ts)*}
+    };
+    {$n:expr, $type:ty,} => {};
+}
+
+// Implements `Elem` for [i32; n] where n = 1 to 128.
+impl_for_array! {128, i32,
+T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T
+T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T
+T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T
+T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T
+}
+
+// Implements `Elem` for [bool; n] where n = 1 to 128.
+impl_for_array! {128, bool,
+T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T
+T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T
+T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T
+T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T
+}
+
+impl CtlElemValue for bool {
+    type T = bool;
+    /// Gets a bool from the ElemValue.
+    unsafe fn elem_value_get(elem: &ElemValue, idx: usize) -> bool {
+        alsa_sys::snd_ctl_elem_value_get_boolean(elem.as_ptr(), idx as c_uint) != 0
+    }
+    /// Sets a bool to the ElemValue.
+    unsafe fn elem_value_set(elem: &mut ElemValue, idx: usize, val: bool) {
+        alsa_sys::snd_ctl_elem_value_set_boolean(elem.as_mut_ptr(), idx as c_uint, val as c_long);
+    }
+    /// Returns ElemType::Boolean.
+    fn elem_type() -> ElemType {
+        ElemType::Boolean
+    }
+}
+
+impl CtlElemValue for i32 {
+    type T = i32;
+    /// Gets an i32 from the ElemValue.
+    unsafe fn elem_value_get(elem: &ElemValue, idx: usize) -> i32 {
+        alsa_sys::snd_ctl_elem_value_get_integer(elem.as_ptr(), idx as c_uint) as i32
+    }
+    /// Sets an i32 to the ElemValue.
+    unsafe fn elem_value_set(elem: &mut ElemValue, idx: usize, val: i32) {
+        alsa_sys::snd_ctl_elem_value_set_integer(elem.as_mut_ptr(), idx as c_uint, val as c_long);
+    }
+    /// Returns ElemType::Integer.
+    fn elem_type() -> ElemType {
+        ElemType::Integer
+    }
+}
+
+/// All primitive types of a control element should implement `CtlElemValue` trait.
+trait CtlElemValue {
+    /// The primitive type of a control element.
+    type T;
+    /// Gets the value from the ElemValue.
+    unsafe fn elem_value_get(value: &ElemValue, idx: usize) -> Self::T;
+    /// Sets the value to the ElemValue.
+    unsafe fn elem_value_set(value: &mut ElemValue, id: usize, val: Self::T);
+    /// Gets the data type itself can read and write.
+    fn elem_type() -> ElemType;
+}
+
+/// Use `Elem` trait to access the underlying control element through the given `Ctl` and `ElemId`.
+pub trait Elem: Sized {
+    /// The data type of a control element.
+    type T;
+    /// Reads the value from the mixer control.
+    fn load(handle: &mut Ctl, id: &ElemId) -> Result<Self::T>;
+    /// Saves the value to the mixer control.
+    fn save(handle: &mut Ctl, id: &ElemId, val: Self::T) -> Result<bool>;
+    /// Gets the data type itself can read and write.
+    fn elem_type() -> ElemType;
+    /// Gets the number of value entries itself can read and write.
+    fn size() -> usize;
+}
diff --git a/cros_alsa/src/lib.rs b/cros_alsa/src/lib.rs
new file mode 100644
index 0000000..070d221
--- /dev/null
+++ b/cros_alsa/src/lib.rs
@@ -0,0 +1,61 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! `cros_alsa` crate currently supports interacting with alsa
+//! controls by using the control interface API of alsa-lib.
+//!
+//! # Examples
+//! This is an example of how to use the provided `Control` objects.
+//!
+//! ``` no_run
+//! use std::error::Error;
+//! use std::result::Result;
+//!
+//! use cros_alsa::{Card, SwitchControl, IntControl, StereoVolumeControl};
+//!
+//! fn main() -> Result<(), Box<dyn Error>> {
+//!
+//!   let mut card = Card::new("sofmax98390d")?;
+//!
+//!   // Uses a SwitchControl to turn on and off a mixer control that has a single boolean state.
+//!   let mut calib_ctrl:SwitchControl = card.control_by_name("Left DSM Calibration")?;
+//!   calib_ctrl.on()?;
+//!   assert_eq!(calib_ctrl.state()?, true);
+//!   calib_ctrl.off()?;
+//!
+//!   // Uses an IntControl to read and write a mixer control that has a single integer value.
+//!   let mut rdc_ctrl:IntControl = card.control_by_name("Left Rdc")?;
+//!   let _rdc = rdc_ctrl.get()?;
+//!   rdc_ctrl.set(13000)?;
+//!
+//!   // Uses a StereoVolumeControl to manipulate stereo volume related functionality.
+//!   let mut volume_ctrl:StereoVolumeControl = card.control_by_name("Master Playback Volume")?;
+//!   volume_ctrl.set_volume(184, 184)?;
+//!
+//!   Ok(())
+//! }
+//! ```
+
+// Allow the maximum recursive depth = 256 for macro expansion.
+#![recursion_limit = "256"]
+#![deny(missing_docs)]
+
+mod card;
+mod control;
+mod control_primitive;
+pub mod control_tlv;
+pub mod elem;
+
+pub use self::card::Card;
+pub use self::control::{Control, ControlOps, IntControl, StereoVolumeControl, SwitchControl};
+pub use self::control_primitive::{Ctl, ElemId};
+pub use self::control_tlv::{ControlTLV, TLV};
+
+pub use self::card::Error as CardError;
+pub use self::control::Error as ControlError;
+pub use self::control_tlv::Error as ControlTLVError;
+pub use self::elem::Error as ElemError;
+
+#[allow(unused_imports)]
+pub use cros_alsa_derive::*;
diff --git a/init/cras.conf b/init/cras.conf
index e7df799..2084749 100644
--- a/init/cras.conf
+++ b/init/cras.conf
@@ -9,6 +9,9 @@
 author          "chromium-os-dev@chromium.org"
 
 env CRAS_SOCKET_DIR=/run/cras
+env CRAS_VMS_SOCKET_DIR=/run/cras/vms
+env CRAS_PLUGIN_DIR=/run/cras/vms/plugin
+env CRAS_ARGS=
 
 start on starting system-services
 stop on stopping system-services
@@ -20,6 +23,20 @@
 pre-start script
   mkdir -p -m 1770 "${CRAS_SOCKET_DIR}"
   chown -R cras:cras "${CRAS_SOCKET_DIR}"
+  mkdir -p -m 1770 "${CRAS_VMS_SOCKET_DIR}"
+  chown -R cras:cras "${CRAS_VMS_SOCKET_DIR}"
+  for socket_dir in playback unified; do
+    mkdir -p -m 1770 "${CRAS_PLUGIN_DIR}/${socket_dir}"
+    chown -R cras:cras "${CRAS_PLUGIN_DIR}/${socket_dir}"
+  done
+  mkdir -m 0755 -p /var/lib/cras
+  chown -R cras:cras /var/lib/cras
 end script
 
 exec /bin/sh /usr/share/cros/init/cras.sh
+
+# sound_card_init uses CRAS stop timestamp as a criterion to skip boot time
+# calibration for DSM.
+post-stop script
+  echo "$(date +---%\nsecs:\ %s%\nnanos:\ %N)" > /var/lib/cras/stop
+end script
diff --git a/init/cras.sh b/init/cras.sh
index a1a21db..91114c0 100644
--- a/init/cras.sh
+++ b/init/cras.sh
@@ -6,6 +6,11 @@
 device_config_dir="$(cros_config /audio/main cras-config-dir)"
 internal_ucm_suffix="$(cros_config /audio/main ucm-suffix)"
 
+# Deprecate HSP since it's just too old.
+# TODO(hychao): Clean up all CRAS codes that are related to HSP once we're
+# sure no headset breaks because of that.
+DISABLE_PROFILE="--disable_profile=hsp"
+
 # Handle legacy config.
 if [ -z "${device_config_dir}" ]; then
   # Disable HSP/HFP on Google WiFi (Gale) with UART-HCI Bluetooth
@@ -43,7 +48,6 @@
         -k 'tmpfs,/run,tmpfs,MS_NODEV|MS_NOEXEC|MS_NOSUID,mode=755,size=10M' \
         -b /run/cras,/run/cras,1 \
         -b /run/dbus,/run/dbus,1 \
-        -b /run/systemd/journal \
         -b /run/udev,/run/udev \
         -b /dev,/dev \
         -b /dev/shm,/dev/shm,1 \
@@ -57,4 +61,4 @@
         -- \
         /usr/bin/cras \
         ${DSP_CONFIG} ${DEVICE_CONFIG_DIR} ${DISABLE_PROFILE} \
-        ${INTERNAL_UCM_SUFFIX}
+        ${INTERNAL_UCM_SUFFIX} ${CRAS_ARGS}
diff --git a/scripts/asoc_dapm_graph b/scripts/asoc_dapm_graph
index f02c60c..fd43736 100755
--- a/scripts/asoc_dapm_graph
+++ b/scripts/asoc_dapm_graph
@@ -42,7 +42,7 @@
     for l in lines[1:]:
       l = l.rstrip()
       # The string format is (in/out) "switch" "widget".
-      edge = filter(None, re.split(r' (in|out)  "(.+)" "(.+)"', l))
+      edge = list(filter(None, re.split(r' (in|out)  "(.+)" "(.+)"', l)))
 
       if len(edge) != 3:
         continue
diff --git a/scripts/audio_diagnostics b/scripts/audio_diagnostics
index c3fe4f8..b754c07 100755
--- a/scripts/audio_diagnostics
+++ b/scripts/audio_diagnostics
@@ -7,21 +7,37 @@
 # Collect information about the audio system from top to bottom.
 
 dump_cards() {
+    # shellcheck disable=SC2068
     for card in ${@}
     do
-        echo '=== amixer -c' $card scontents '==='
-        amixer -c $card scontents
-        echo '=== amixer -c' $card contents '==='
-        amixer -c $card contents
+        echo "=== amixer -c ${card} scontents ==="
+        amixer -c "${card}" scontents
+        echo "=== amixer -c ${card} contents ==="
+        amixer -c "${card}" contents
     done
 }
 
+# Helper function: in_the_list $1 $2
+# Returns 0 if str $1 is included in delimited str $2; otherwise 1
+in_the_list() {
+    for item in $2
+    do
+        if [ "$1" = "${item}" ]; then
+            return 0
+        fi
+    done
+    return 1
+}
+
 echo '=== cras_test_client --dump_server_info ==='
 cras_test_client --dump_server_info
 
 echo '=== cras_test_client --dump_audio_thread ==='
 cras_test_client --dump_audio_thread
 
+echo '=== cras_test_client --dump_main ==='
+cras_test_client --dump_main
+
 echo '=== cras_test_client --dump_bt ==='
 cras_test_client --dump_bt
 
@@ -33,28 +49,81 @@
 echo '=== arecord -l ==='
 arecord -l
 
-output_cards=$(aplay -l | egrep ^card | sed 's/card \([0-9]\+\).*/\1/' | sort -u)
-dump_cards $output_cards
+output_cards=$(
+    aplay -l | grep -E ^card | sed 's/card \([0-9]\+\).*/\1/' | sort -u)
+dump_cards "${output_cards}"
 
-input_cards=$(arecord -l | egrep ^card | sed 's/card \([0-9]\+\).*/\1/' | sort -u)
-dump_cards $input_cards
+input_cards=$(
+    arecord -l | grep -E ^card | sed 's/card \([0-9]\+\).*/\1/' | sort -u)
+dump_cards "${input_cards}"
 
 # HDA codec for codecs on x86.
 codecs=$(find /proc/asound -mindepth 2 -maxdepth 2 -path '*card*/codec#*')
-for codec in $codecs
+for codec in ${codecs}
 do
-    echo '=== codec:' $codec '==='
-    cat $codec
+    echo "=== codec: ${codec} ==="
+    cat "${codec}"
 done
 
 # I2C dump for codecs on arm.
 # Find lines like "max98088.7-0010" and extract "7 0x0010" from it.
 if [ -e /sys/kernel/debug/asoc/codecs ]; then
     sed_expr='s/^\([^.-]\+\)\.\([0-9]\+\)-\([0-9]\+\)$/\2 0x\3/p'
-    sed -n "$sed_expr" /sys/kernel/debug/asoc/codecs |
-    while read i2c_addr
+    sed -n "${sed_expr}" /sys/kernel/debug/asoc/codecs |
+    while read -r i2c_addr
     do
-        echo '===' i2cdump -f -y $i2c_addr '==='
-        i2cdump -f -y $i2c_addr
+        echo "=== i2cdump -f -y ${i2c_addr} ==="
+        i2cdump -f -y "${i2c_addr}"
     done
 fi
+
+# Dump registers from regmaps
+
+# List of audio components
+# For kernel>=4.14, it is in /sys/kernel/debug/asoc/components
+# For kernel<4.14, it is in /sys/kernel/debug/asoc/codecs
+if [ -f /sys/kernel/debug/asoc/components ]; then
+    audio_comps=$(cat /sys/kernel/debug/asoc/components)
+else
+    audio_comps=$(cat /sys/kernel/debug/asoc/codecs)
+fi
+
+# Blocklist regmap name of dumping registers (tracked by b/154177454)
+# Use the blank space as delimiter, e.g. 'name_a name_b name_c'
+name_blocklist='snd_hda_codec_hdmi'
+
+for file_path in /sys/kernel/debug/regmap/*
+do
+    [ -e "${file_path}" ] || break  # handle the case of no files
+    component=$(basename "${file_path}")
+
+    # Skip dumping registers if component is not listed in audio_comps
+    if ! in_the_list "${component}" "${audio_comps}"; then
+        continue
+    fi
+
+    if [ ! -f "${file_path}/name" ]; then
+        echo "Failed at dump registers: ${file_path}"
+        continue
+    fi
+
+    name=$(cat "${file_path}/name")
+    echo "=== dump registers component: ${component} name: ${name} ==="
+
+    # Skip dumping registers if regmap's name is in name_blocklist
+    if in_the_list "${name}" "${name_blocklist}"; then
+        echo 'skipped dumping due to b/154177454'
+        continue
+    fi
+
+    # Store back the original value
+    # Note: $(cat cache_bypass) returns 'Y' if flag is on; otherwise 'N'
+    cache_bypass=$(cat "${file_path}/cache_bypass")
+    if [ "${cache_bypass}" = "N" ]; then
+        echo 1 > "${file_path}/cache_bypass"
+    fi
+    cat "${file_path}/registers"
+    if [ "${cache_bypass}" = "N" ]; then
+        echo 0 > "${file_path}/cache_bypass"
+    fi
+done
diff --git a/scripts/audio_tuning/frontend/audio.js b/scripts/audio_tuning/frontend/audio.js
index 2476f1a..98870cd 100644
--- a/scripts/audio_tuning/frontend/audio.js
+++ b/scripts/audio_tuning/frontend/audio.js
@@ -1105,7 +1105,7 @@
   this.update = update;
 }
 
-function dummy() {
+function empty() {
 }
 
 /* Changes the opacity of a div. */
@@ -1185,7 +1185,7 @@
   var f_slider;
   if (lower_freq == 0) {  /* Special case for the lowest band */
     f_slider = new slider_input_log(table, freq_label, lower_freq, 0, 1,
-                                    'Hz', 0, dummy);
+                                    'Hz', 0, empty);
     f_slider.hide(true);
   } else {
     f_slider = new slider_input_log(table, freq_label, lower_freq, 1,
@@ -1570,7 +1570,7 @@
   var kneeThresholdDb;
   var kneeThreshold;
   var ykneeThresholdDb;
-  var masterLinearGain;
+  var mainLinearGain;
 
   var maxOutputDb = 6;
   var minOutputDb = -36;
@@ -1662,10 +1662,10 @@
     kneeThreshold = dBToLinear(kneeThresholdDb);
     ykneeThresholdDb = linearToDb(kneeCurve(kneeThreshold, curve_k));
 
-    /* Calculate masterLinearGain */
+    /* Calculate mainLinearGain */
     var fullRangeGain = saturate(1, curve_k);
     var fullRangeMakeupGain = Math.pow(1 / fullRangeGain, 0.6);
-    masterLinearGain = dBToLinear(boost) * fullRangeMakeupGain;
+    mainLinearGain = dBToLinear(boost) * fullRangeMakeupGain;
 
     /* Clear canvas */
     var width = canvas.width;
@@ -1725,7 +1725,7 @@
       var inputDb = xpixelToDb(x);
       var inputLinear = dBToLinear(inputDb);
       var outputLinear = saturate(inputLinear, curve_k);
-      outputLinear *= masterLinearGain;
+      outputLinear *= mainLinearGain;
       var outputDb = linearToDb(outputLinear);
       var y = dBToYPixel(outputDb);
 
diff --git a/scripts/volume_tuning/volume.js b/scripts/volume_tuning/volume.js
index c5f0526..88f3998 100644
--- a/scripts/volume_tuning/volume.js
+++ b/scripts/volume_tuning/volume.js
@@ -116,7 +116,7 @@
   var max = parseFloat(minmax_boxes[1].value);
   var step = parseFloat(minmax_boxes[2].value);
 
-  // Sanity checks
+  // Soundness checks
   if (isNaN(min) || isNaN(max) || isNaN(step)) return;
   if (min >= max || step <= 0 || (max - min) / step > 10000) return;
 
diff --git a/seccomp/cras-seccomp-amd64.policy b/seccomp/cras-seccomp-amd64.policy
index 06dc224..afa6ace 100644
--- a/seccomp/cras-seccomp-amd64.policy
+++ b/seccomp/cras-seccomp-amd64.policy
@@ -11,6 +11,7 @@
 ppoll: 1
 fstat: 1
 write: 1
+writev: 1
 open: 1
 openat: 1
 close: 1
@@ -19,6 +20,7 @@
 access: 1
 fcntl: 1
 getdents: 1
+getdents64: 1
 sendmsg: 1
 stat: 1
 statfs: 1
@@ -34,10 +36,13 @@
 lseek: 1
 rt_sigaction: 1
 socket: arg0 == AF_UNIX || arg0 == AF_BLUETOOTH || arg0 == AF_NETLINK
+socketpair: 1
 unlink: 1
 nanosleep: 1
+clock_nanosleep: 1
 pipe: 1
 ftruncate: 1
+fallocate: 1
 mprotect: 1
 connect: 1
 getsockname: 1
@@ -59,6 +64,7 @@
 listen: 1
 clone: 1
 set_robust_list: 1
+setpriority: 1
 getresuid: 1
 getresgid: 1
 sched_setscheduler: 1
@@ -86,3 +92,4 @@
 tgkill: 1
 mremap: 1
 dup: 1
+gettimeofday: 1
diff --git a/seccomp/cras-seccomp-arm.policy b/seccomp/cras-seccomp-arm.policy
index f917036..cbaa622 100644
--- a/seccomp/cras-seccomp-arm.policy
+++ b/seccomp/cras-seccomp-arm.policy
@@ -3,10 +3,13 @@
 # found in the LICENSE file.
 
 clock_gettime: 1
+clock_gettime64: 1
 poll: 1
 read: 1
 ppoll: 1
+ppoll_time64: 1
 write: 1
+writev: 1
 recv: 1
 send: 1
 recvmsg: 1
@@ -38,10 +41,13 @@
 lsetxattr: 1
 rt_sigprocmask: 1
 ftruncate: 1
+fallocate: 1
 futex: 1
+futex_time64: 1
 execve: 1
 set_robust_list: 1
 socket: arg0 == AF_UNIX || arg0 == AF_BLUETOOTH || arg0 == AF_NETLINK
+socketpair: 1
 clone: 1
 setsockopt: 1
 geteuid32: 1
@@ -61,6 +67,8 @@
 getsockname: 1
 getdents: 1
 nanosleep: 1
+clock_nanosleep: 1
+clock_nanosleep_time64: 1
 epoll_ctl: 1
 sched_setscheduler: 1
 restart_syscall: 1
@@ -69,8 +77,10 @@
 exit: 1
 prctl: arg0 == PR_SET_NAME
 clock_getres: 1
+clock_getres_time64: 1
 epoll_create1: 1
 fchmod: 1
+setpriority: 1
 setrlimit: 1
 listen: 1
 gettid: 1
@@ -81,7 +91,6 @@
 pipe2: 1
 sched_get_priority_max: 1
 sysinfo: 1
-flock: 1
 
 # Allow ioctl command of type 'A' and 'U' for SNDRV_PCM_IOCTL_* and
 # SNDRV_CTL_IOCTL_*, and EVIOCGSW(8), EVIOCGNAME(256), EVIOCGBIT(0x05, 8),
@@ -92,3 +101,4 @@
 tgkill: 1
 mremap: 1
 dup: 1
+_newselect: 1
diff --git a/seccomp/cras-seccomp-arm64.policy b/seccomp/cras-seccomp-arm64.policy
index 64f3244..4b52355 100644
--- a/seccomp/cras-seccomp-arm64.policy
+++ b/seccomp/cras-seccomp-arm64.policy
@@ -10,6 +10,7 @@
 ppoll: 1
 read: 1
 write: 1
+writev: 1
 newfstatat: 1
 fstat: 1
 openat: 1
@@ -30,6 +31,7 @@
 brk: 1
 munmap: 1
 socket: arg0 == AF_UNIX || arg0 == AF_BLUETOOTH || arg0 == AF_NETLINK
+socketpair: 1
 statfs: 1
 getsockopt: 1
 accept: 1
@@ -37,6 +39,7 @@
 prctl: arg0 == PR_SET_NAME
 futex: 1
 ftruncate: 1
+fallocate: 1
 connect: 1
 bind: 1
 clock_getres: 1
@@ -50,7 +53,6 @@
 fchmod: 1
 fchmodat: 1
 flock: 1
-flock: 1
 getegid: 1
 geteuid: 1
 getgid: 1
@@ -66,6 +68,7 @@
 lsetxattr: 1
 madvise: 1
 nanosleep: 1
+clock_nanosleep: 1
 restart_syscall: 1
 rt_sigprocmask: 1
 rt_sigreturn: 1
@@ -74,6 +77,7 @@
 sched_setscheduler: 1
 setrlimit: 1
 set_robust_list: 1
+setpriority: 1
 setsockopt: 1
 set_tid_address: 1
 sysinfo: 1
diff --git a/sof_sys/.gitignore b/sof_sys/.gitignore
new file mode 100644
index 0000000..fa8d85a
--- /dev/null
+++ b/sof_sys/.gitignore
@@ -0,0 +1,2 @@
+Cargo.lock
+target
diff --git a/sof_sys/.rustfmt.toml b/sof_sys/.rustfmt.toml
new file mode 100644
index 0000000..a2db301
--- /dev/null
+++ b/sof_sys/.rustfmt.toml
@@ -0,0 +1,2 @@
+use_field_init_shorthand = true
+use_try_shorthand = true
diff --git a/sof_sys/Cargo.toml b/sof_sys/Cargo.toml
new file mode 100644
index 0000000..21934ee
--- /dev/null
+++ b/sof_sys/Cargo.toml
@@ -0,0 +1,4 @@
+[package]
+name = "sof_sys"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
diff --git a/sof_sys/generator/.gitignore b/sof_sys/generator/.gitignore
new file mode 100644
index 0000000..03314f7
--- /dev/null
+++ b/sof_sys/generator/.gitignore
@@ -0,0 +1 @@
+Cargo.lock
diff --git a/sof_sys/generator/Cargo.toml b/sof_sys/generator/Cargo.toml
new file mode 100644
index 0000000..672d41d
--- /dev/null
+++ b/sof_sys/generator/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "generator"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+[dependencies]
+bindgen = "0.43.0"
diff --git a/sof_sys/generator/README.md b/sof_sys/generator/README.md
new file mode 100644
index 0000000..3e2ca77
--- /dev/null
+++ b/sof_sys/generator/README.md
@@ -0,0 +1 @@
+Use `cargo run` to generate rust bindings at sof_sys/src/bindings.rs
diff --git a/sof_sys/generator/src/main.rs b/sof_sys/generator/src/main.rs
new file mode 100644
index 0000000..60f0102
--- /dev/null
+++ b/sof_sys/generator/src/main.rs
@@ -0,0 +1,42 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+extern crate bindgen;
+
+use std::fs::File;
+use std::io::Write;
+use std::path::PathBuf;
+
+fn main() {
+    let bindings = bindgen::Builder::default()
+        .header("wrapper.h")
+        .derive_debug(false)
+        .clang_arg("-I../../../sound-open-firmware-private/src/include")
+        .whitelist_type("sof_abi_hdr")
+        .whitelist_type("sof_ipc_ctrl_cmd")
+        .generate()
+        .expect("Unable to generate bindings");
+
+    let header = b"// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+/*
+ * generated from files in sound-open-firmware-private/src/include:
+ * kernel/header.h
+ * ipc/control.h
+ */
+
+";
+
+    // Write the bindings to the $OUT_DIR/bindings.rs file.
+    let out_path = PathBuf::from("../src").join("bindings.rs");
+
+    let mut output_file =
+        File::create(&out_path).expect(&format!("Couldn't create {:?}", out_path));
+    output_file
+        .write_all(header)
+        .expect("Couldn't write header");
+    output_file
+        .write_all(bindings.to_string().as_bytes())
+        .expect("Couldn't write bindings");
+}
diff --git a/sof_sys/generator/wrapper.h b/sof_sys/generator/wrapper.h
new file mode 100644
index 0000000..5bac0f5
--- /dev/null
+++ b/sof_sys/generator/wrapper.h
@@ -0,0 +1,5 @@
+// Copyright 2021 The Chromium OS 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 <kernel/header.h>
+#include <ipc/control.h>
diff --git a/sof_sys/src/bindings.rs b/sof_sys/src/bindings.rs
new file mode 100644
index 0000000..7bed0dc
--- /dev/null
+++ b/sof_sys/src/bindings.rs
@@ -0,0 +1,154 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+/*
+ * generated from files in sound-open-firmware-private/src/include:
+ * kernel/header.h
+ * ipc/control.h
+ */
+
+/* automatically generated by rust-bindgen */
+
+#[repr(C)]
+#[derive(Default)]
+pub struct __IncompleteArrayField<T>(::std::marker::PhantomData<T>);
+impl<T> __IncompleteArrayField<T> {
+    #[inline]
+    pub fn new() -> Self {
+        __IncompleteArrayField(::std::marker::PhantomData)
+    }
+    #[inline]
+    pub unsafe fn as_ptr(&self) -> *const T {
+        ::std::mem::transmute(self)
+    }
+    #[inline]
+    pub unsafe fn as_mut_ptr(&mut self) -> *mut T {
+        ::std::mem::transmute(self)
+    }
+    #[inline]
+    pub unsafe fn as_slice(&self, len: usize) -> &[T] {
+        ::std::slice::from_raw_parts(self.as_ptr(), len)
+    }
+    #[inline]
+    pub unsafe fn as_mut_slice(&mut self, len: usize) -> &mut [T] {
+        ::std::slice::from_raw_parts_mut(self.as_mut_ptr(), len)
+    }
+}
+impl<T> ::std::fmt::Debug for __IncompleteArrayField<T> {
+    fn fmt(&self, fmt: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        fmt.write_str("__IncompleteArrayField")
+    }
+}
+impl<T> ::std::clone::Clone for __IncompleteArrayField<T> {
+    #[inline]
+    fn clone(&self) -> Self {
+        Self::new()
+    }
+}
+impl<T> ::std::marker::Copy for __IncompleteArrayField<T> {}
+pub type __uint32_t = ::std::os::raw::c_uint;
+/// \brief Header for all non IPC ABI data.
+///
+/// Identifies data type, size and ABI.
+/// Data header used for all component data structures and binary blobs sent to
+/// firmware as runtime data. This data is typically sent by userspace
+/// applications and tunnelled through any OS kernel (via binary kcontrol on
+/// Linux) to the firmware.
+#[repr(C, packed)]
+pub struct sof_abi_hdr {
+    ///< 'S', 'O', 'F', '\0'
+    pub magic: u32,
+    ///< component specific type
+    pub type_: u32,
+    ///< size in bytes of data excl. this struct
+    pub size: u32,
+    ///< SOF ABI version
+    pub abi: u32,
+    ///< reserved for future use
+    pub reserved: [u32; 4usize],
+    ///< Component data - opaque to core
+    pub data: __IncompleteArrayField<u32>,
+}
+#[test]
+fn bindgen_test_layout_sof_abi_hdr() {
+    assert_eq!(
+        ::std::mem::size_of::<sof_abi_hdr>(),
+        32usize,
+        concat!("Size of: ", stringify!(sof_abi_hdr))
+    );
+    assert_eq!(
+        ::std::mem::align_of::<sof_abi_hdr>(),
+        1usize,
+        concat!("Alignment of ", stringify!(sof_abi_hdr))
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<sof_abi_hdr>())).magic as *const _ as usize },
+        0usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(sof_abi_hdr),
+            "::",
+            stringify!(magic)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<sof_abi_hdr>())).type_ as *const _ as usize },
+        4usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(sof_abi_hdr),
+            "::",
+            stringify!(type_)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<sof_abi_hdr>())).size as *const _ as usize },
+        8usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(sof_abi_hdr),
+            "::",
+            stringify!(size)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<sof_abi_hdr>())).abi as *const _ as usize },
+        12usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(sof_abi_hdr),
+            "::",
+            stringify!(abi)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<sof_abi_hdr>())).reserved as *const _ as usize },
+        16usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(sof_abi_hdr),
+            "::",
+            stringify!(reserved)
+        )
+    );
+    assert_eq!(
+        unsafe { &(*(::std::ptr::null::<sof_abi_hdr>())).data as *const _ as usize },
+        32usize,
+        concat!(
+            "Offset of field: ",
+            stringify!(sof_abi_hdr),
+            "::",
+            stringify!(data)
+        )
+    );
+}
+///< maps to ALSA volume style controls
+pub const sof_ipc_ctrl_cmd_SOF_CTRL_CMD_VOLUME: sof_ipc_ctrl_cmd = 0;
+///< maps to ALSA enum style controls
+pub const sof_ipc_ctrl_cmd_SOF_CTRL_CMD_ENUM: sof_ipc_ctrl_cmd = 1;
+///< maps to ALSA switch style controls
+pub const sof_ipc_ctrl_cmd_SOF_CTRL_CMD_SWITCH: sof_ipc_ctrl_cmd = 2;
+///< maps to ALSA binary style controls
+pub const sof_ipc_ctrl_cmd_SOF_CTRL_CMD_BINARY: sof_ipc_ctrl_cmd = 3;
+/// Control command type.
+pub type sof_ipc_ctrl_cmd = u32;
diff --git a/sof_sys/src/lib.rs b/sof_sys/src/lib.rs
new file mode 100644
index 0000000..57119cf
--- /dev/null
+++ b/sof_sys/src/lib.rs
@@ -0,0 +1,12 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#![allow(clippy::unreadable_literal)]
+#![allow(clippy::cognitive_complexity)]
+#![allow(non_upper_case_globals)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+
+pub mod bindings;
+#[allow(unused_imports)]
+pub use bindings::sof_abi_hdr;
diff --git a/sound_card_init/.gitignore b/sound_card_init/.gitignore
new file mode 100644
index 0000000..b6e8700
--- /dev/null
+++ b/sound_card_init/.gitignore
@@ -0,0 +1,6 @@
+# Generated by Cargo
+# will have compiled files and executables
+**/target/
+
+# These are backup files generated by rustfmt
+**/*.rs.bk
diff --git a/sound_card_init/99-sound_card_init.rules b/sound_card_init/99-sound_card_init.rules
new file mode 100644
index 0000000..82a3aec
--- /dev/null
+++ b/sound_card_init/99-sound_card_init.rules
@@ -0,0 +1,13 @@
+# Copyright 2020 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+SUBSYSTEM!="sound", GOTO="sci_end"
+ACTION!="change", GOTO="sci_end"
+KERNEL!="card*", GOTO="sci_end"
+
+GOTO="sci_action"
+
+LABEL="sci_action"
+RUN+="/sbin/initctl start sound_card_init SOUND_CARD_ID=$attr{id}"
+LABEL="sci_end"
diff --git a/sound_card_init/Cargo.lock b/sound_card_init/Cargo.lock
new file mode 100644
index 0000000..c89ad1e
--- /dev/null
+++ b/sound_card_init/Cargo.lock
@@ -0,0 +1,286 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "alsa-sys"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "644d308f5822c2b39fba5a6d850f74c208bf73c61d1d2dfad62505d6960e4977"
+dependencies = [
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
+name = "amp"
+version = "0.1.0"
+dependencies = [
+ "cros_alsa",
+ "dsm",
+ "libcras",
+ "remain",
+ "serde",
+ "serde_yaml",
+ "sof_sys",
+ "sys_util",
+]
+
+[[package]]
+name = "android_log-sys"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85965b6739a430150bdd138e2374a98af0c3ee0d030b3bb7fc3bddff58d0102e"
+
+[[package]]
+name = "assertions"
+version = "0.1.0"
+
+[[package]]
+name = "audio_streams"
+version = "0.1.0"
+dependencies = [
+ "sync",
+ "sys_util",
+]
+
+[[package]]
+name = "cras-sys"
+version = "0.1.0"
+dependencies = [
+ "audio_streams",
+ "data_model",
+]
+
+[[package]]
+name = "cros_alsa"
+version = "0.1.0"
+dependencies = [
+ "alsa-sys",
+ "cros_alsa_derive",
+ "libc",
+ "remain",
+]
+
+[[package]]
+name = "cros_alsa_derive"
+version = "0.1.0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "data_model"
+version = "0.1.0"
+dependencies = [
+ "assertions",
+ "libc",
+]
+
+[[package]]
+name = "dsm"
+version = "0.1.0"
+dependencies = [
+ "audio_streams",
+ "cros_alsa",
+ "libcras",
+ "remain",
+ "serde",
+ "serde_yaml",
+ "sys_util",
+]
+
+[[package]]
+name = "dtoa"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88d7ed2934d741c6b37e33e3832298e8850b53fd2d2bea03873375596c7cea4e"
+
+[[package]]
+name = "getopts"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5"
+dependencies = [
+ "unicode-width",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.82"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89203f3fba0a3795506acaad8ebce3c80c0af93f994d5a1d7a0b1eeb23271929"
+
+[[package]]
+name = "libcras"
+version = "0.1.0"
+dependencies = [
+ "audio_streams",
+ "cras-sys",
+ "data_model",
+ "libc",
+ "sys_util",
+]
+
+[[package]]
+name = "linked-hash-map"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
+
+[[package]]
+name = "poll_token_derive"
+version = "0.1.0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "remain"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ba1e78fa68412cb93ef642fd4d20b9a941be49ee9333875ebaf13112673ea7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.119"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9bdd36f49e35b61d49efd8aa7fc068fd295961fd2286d0b2ee9a4c7a14e99cc3"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.119"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "552954ce79a059ddd5fd68c271592374bd15cab2274970380c000118aeffe1cd"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_yaml"
+version = "0.8.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "971be8f6e4d4a47163b405a3df70d14359186f9ab0f3a3ec37df144ca1ce089f"
+dependencies = [
+ "dtoa",
+ "linked-hash-map",
+ "serde",
+ "yaml-rust",
+]
+
+[[package]]
+name = "sof_sys"
+version = "0.1.0"
+
+[[package]]
+name = "sound_card_init"
+version = "0.1.0"
+dependencies = [
+ "amp",
+ "audio_streams",
+ "cros_alsa",
+ "dsm",
+ "getopts",
+ "libcras",
+ "remain",
+ "serde",
+ "serde_yaml",
+ "sof_sys",
+ "sys_util",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.58"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc60a3d73ea6594cd712d830cc1f0390fd71542d8c8cd24e70cc54cdfd5e05d5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "sync"
+version = "0.1.0"
+
+[[package]]
+name = "sys_util"
+version = "0.1.0"
+dependencies = [
+ "android_log-sys",
+ "data_model",
+ "libc",
+ "poll_token_derive",
+ "sync",
+ "syscall_defines",
+ "tempfile",
+]
+
+[[package]]
+name = "syscall_defines"
+version = "0.1.0"
+
+[[package]]
+name = "tempfile"
+version = "3.0.7"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "unicode-width"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
+
+[[package]]
+name = "yaml-rust"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
+dependencies = [
+ "linked-hash-map",
+]
diff --git a/sound_card_init/Cargo.toml b/sound_card_init/Cargo.toml
new file mode 100644
index 0000000..9d5a107
--- /dev/null
+++ b/sound_card_init/Cargo.toml
@@ -0,0 +1,33 @@
+[package]
+name = "sound_card_init"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+edition = "2018"
+description = "Sound Card Initializer"
+
+[workspace]
+members = [
+    "amp",
+    "dsm"
+]
+
+[dependencies]
+amp = { path = "amp" }
+audio_streams = "*"
+cros_alsa = "*"
+dsm = { path = "dsm" }
+getopts = "0.2"
+libcras = "*"
+remain = "0.2.1"
+serde = { version = "1.0", features = ["derive"] }
+serde_yaml = "0.8.11"
+sof_sys = "*"
+sys_util = "*"
+
+[patch.crates-io]
+audio_streams = { path = "../audio_streams" }  # ignored by ebuild
+cros_alsa = { path = "../cros_alsa" } # ignored by ebuild
+cros_alsa_derive = { path = "../cros_alsa/cros_alsa_derive" } # ignored by ebuild
+libcras = { path = "../cras/client/libcras" }  # ignored by ebuild
+sof_sys = { path = "../sof_sys" }  # ignored by ebuild
+sys_util = { path = "../../../platform/crosvm/sys_util" } # ignored by ebuild
diff --git a/sound_card_init/amp/Cargo.lock b/sound_card_init/amp/Cargo.lock
new file mode 100644
index 0000000..679e60c
--- /dev/null
+++ b/sound_card_init/amp/Cargo.lock
@@ -0,0 +1,254 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "alsa-sys"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "644d308f5822c2b39fba5a6d850f74c208bf73c61d1d2dfad62505d6960e4977"
+dependencies = [
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
+name = "amp"
+version = "0.1.0"
+dependencies = [
+ "cros_alsa",
+ "dsm",
+ "libcras",
+ "remain",
+ "serde",
+ "serde_yaml",
+ "sof_sys",
+ "sys_util",
+]
+
+[[package]]
+name = "android_log-sys"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85965b6739a430150bdd138e2374a98af0c3ee0d030b3bb7fc3bddff58d0102e"
+
+[[package]]
+name = "assertions"
+version = "0.1.0"
+
+[[package]]
+name = "audio_streams"
+version = "0.1.0"
+dependencies = [
+ "sync",
+ "sys_util",
+]
+
+[[package]]
+name = "cras-sys"
+version = "0.1.0"
+dependencies = [
+ "audio_streams",
+ "data_model",
+]
+
+[[package]]
+name = "cros_alsa"
+version = "0.1.0"
+dependencies = [
+ "alsa-sys",
+ "cros_alsa_derive",
+ "libc",
+ "remain",
+]
+
+[[package]]
+name = "cros_alsa_derive"
+version = "0.1.0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "data_model"
+version = "0.1.0"
+dependencies = [
+ "assertions",
+ "libc",
+]
+
+[[package]]
+name = "dsm"
+version = "0.1.0"
+dependencies = [
+ "audio_streams",
+ "cros_alsa",
+ "libcras",
+ "remain",
+ "serde",
+ "serde_yaml",
+ "sys_util",
+]
+
+[[package]]
+name = "dtoa"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88d7ed2934d741c6b37e33e3832298e8850b53fd2d2bea03873375596c7cea4e"
+
+[[package]]
+name = "libc"
+version = "0.2.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cca32fa0182e8c0989459524dc356b8f2b5c10f1b9eb521b7d182c03cf8c5ff"
+
+[[package]]
+name = "libcras"
+version = "0.1.0"
+dependencies = [
+ "audio_streams",
+ "cras-sys",
+ "data_model",
+ "libc",
+ "sys_util",
+]
+
+[[package]]
+name = "linked-hash-map"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
+
+[[package]]
+name = "poll_token_derive"
+version = "0.1.0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "remain"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ba1e78fa68412cb93ef642fd4d20b9a941be49ee9333875ebaf13112673ea7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.123"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.123"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_yaml"
+version = "0.8.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "971be8f6e4d4a47163b405a3df70d14359186f9ab0f3a3ec37df144ca1ce089f"
+dependencies = [
+ "dtoa",
+ "linked-hash-map",
+ "serde",
+ "yaml-rust",
+]
+
+[[package]]
+name = "sof_sys"
+version = "0.1.0"
+
+[[package]]
+name = "syn"
+version = "1.0.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "sync"
+version = "0.1.0"
+
+[[package]]
+name = "sys_util"
+version = "0.1.0"
+dependencies = [
+ "android_log-sys",
+ "data_model",
+ "libc",
+ "poll_token_derive",
+ "sync",
+ "syscall_defines",
+ "tempfile",
+]
+
+[[package]]
+name = "syscall_defines"
+version = "0.1.0"
+
+[[package]]
+name = "tempfile"
+version = "3.0.7"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
+
+[[package]]
+name = "yaml-rust"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
+dependencies = [
+ "linked-hash-map",
+]
diff --git a/sound_card_init/amp/Cargo.toml b/sound_card_init/amp/Cargo.toml
new file mode 100644
index 0000000..62e63db
--- /dev/null
+++ b/sound_card_init/amp/Cargo.toml
@@ -0,0 +1,16 @@
+[package]
+name = "amp"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+edition = "2018"
+description = "The boot time calibration logic for smart amp"
+
+[dependencies]
+cros_alsa = "*"
+libcras = "*"
+dsm = { path = "../dsm" }
+remain = "0.2.1"
+serde = { version = "1.0", features = ["derive"]}
+serde_yaml = "0.8.11"
+sof_sys = "*"
+sys_util = "*"
diff --git a/sound_card_init/amp/src/lib.rs b/sound_card_init/amp/src/lib.rs
new file mode 100644
index 0000000..7114233
--- /dev/null
+++ b/sound_card_init/amp/src/lib.rs
@@ -0,0 +1,58 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//! `amp` crate provides `Amp` trait for amplifier initializations and `AmpBuilder`
+//! to create `Amp` objects.
+#![deny(missing_docs)]
+
+mod max98373d;
+mod max98390d;
+use std::path::PathBuf;
+
+use dsm::Error;
+
+use max98373d::Max98373;
+use max98390d::Max98390;
+
+type Result<T> = std::result::Result<T, Error>;
+const CONF_DIR: &str = "/etc/sound_card_init";
+
+/// It creates `Amp` object based on the sound card name.
+pub struct AmpBuilder<'a> {
+    sound_card_id: &'a str,
+    config_path: PathBuf,
+}
+
+impl<'a> AmpBuilder<'a> {
+    /// Creates an `AmpBuilder`.
+    /// # Arguments
+    ///
+    /// * `card_name` - card name.
+    /// * `conf_file` - config file name.
+    pub fn new(sound_card_id: &'a str, conf_file: &'a str) -> Self {
+        let config_path = PathBuf::from(CONF_DIR).join(conf_file);
+        AmpBuilder {
+            sound_card_id,
+            config_path,
+        }
+    }
+
+    /// Creates an `Amp` based on the sound card name.
+    pub fn build(&self) -> Result<Box<dyn Amp>> {
+        match self.sound_card_id {
+            "sofcmlmax98390d" => {
+                Ok(Box::new(Max98390::new(self.sound_card_id, &self.config_path)?) as Box<dyn Amp>)
+            }
+            "sofrt5682" => {
+                Ok(Box::new(Max98373::new(self.sound_card_id, &self.config_path)?) as Box<dyn Amp>)
+            }
+            _ => Err(Error::UnsupportedSoundCard(self.sound_card_id.to_owned())),
+        }
+    }
+}
+
+/// It defines the required functions of amplifier objects.
+pub trait Amp {
+    /// The amplifier boot time calibration flow.
+    fn boot_time_calibration(&mut self) -> Result<()>;
+}
diff --git a/sound_card_init/amp/src/max98373d/dsm_param.rs b/sound_card_init/amp/src/max98373d/dsm_param.rs
new file mode 100644
index 0000000..d925752
--- /dev/null
+++ b/sound_card_init/amp/src/max98373d/dsm_param.rs
@@ -0,0 +1,211 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+use std::mem;
+
+use cros_alsa::{Card, TLV};
+use sof_sys::sof_abi_hdr;
+
+use dsm::{self, Error, Result};
+
+/// Amp volume mode enumeration used by set_volume().
+#[derive(Copy, Clone, PartialEq)]
+pub enum VolumeMode {
+    /// Low mode protects the speaker by limiting its output volume if the
+    /// calibration has not been completed successfully.
+    Low = 0x1009B9CF,
+    /// High mode removes the speaker output volume limitation after
+    /// having successfully completed the calibration.
+    High = 0x20000000,
+}
+
+#[derive(Copy, Clone)]
+/// Calibration mode enumeration.
+pub enum CalibMode {
+    ON = 0x4,
+    OFF = 0x1,
+}
+
+#[derive(Copy, Clone)]
+/// Smart pilot signal mode mode enumeration.
+pub enum SPTMode {
+    ON = 0x1,
+    OFF = 0x0,
+}
+
+#[derive(Copy, Clone)]
+/// DSM Parem field enumeration.
+enum DsmAPI {
+    ParamCount = 0x0,
+    CalibMode = 0x1,
+    MakeupGain = 0x5,
+    DsmRdc = 0x6,
+    DsmAmbientTemp = 0x8,
+    AdaptiveRdc = 0x12,
+    SPTMode = 0x68,
+}
+
+#[derive(Debug)]
+/// It implements functions to access the `DSMParam` fields.
+pub struct DSMParam {
+    param_count: usize,
+    num_channels: usize,
+    tlv: TLV,
+}
+
+impl DSMParam {
+    const DWORD_PER_PARAM: usize = 2;
+    const VALUE_OFFSET: usize = 1;
+    const SOF_HEADER_SIZE: usize = mem::size_of::<sof_abi_hdr>() / mem::size_of::<i32>();
+
+    /// Creates an `DSMParam`.
+    /// # Arguments
+    ///
+    /// * `card` - `&Card`.
+    /// * `num_channels` - number of channels.
+    /// * `ctl_name` - the mixer control name to access the DSM param.
+    ///
+    /// # Results
+    ///
+    /// * `DSMParam` - It is initialized by the content of the given byte control .
+    ///
+    /// # Errors
+    ///
+    /// * If `Card` creation from sound card name fails.
+    pub fn new(card: &mut Card, num_channels: usize, ctl_name: &str) -> Result<Self> {
+        let tlv = card.control_tlv_by_name(ctl_name)?.load()?;
+        Self::try_from_tlv(tlv, num_channels)
+    }
+
+    /// Sets DSMParam to the given calibration mode.
+    pub fn set_calibration_mode(&mut self, mode: CalibMode) {
+        for channel in 0..self.num_channels {
+            self.set(channel, DsmAPI::CalibMode, mode as i32);
+        }
+    }
+
+    /// Sets DSMParam to the given smart pilot signal mode.
+    pub fn set_spt_mode(&mut self, mode: SPTMode) {
+        for channel in 0..self.num_channels {
+            self.set(channel, DsmAPI::SPTMode, mode as i32);
+        }
+    }
+
+    /// Sets DSMParam to the given VolumeMode.
+    pub fn set_volume_mode(&mut self, mode: VolumeMode) {
+        for channel in 0..self.num_channels {
+            self.set(channel, DsmAPI::MakeupGain, mode as i32);
+        }
+    }
+
+    /// Reads the calibrated rdc from DSMParam.
+    pub fn get_adaptive_rdc(&self) -> Vec<i32> {
+        self.get(DsmAPI::AdaptiveRdc)
+    }
+
+    /// Sets DSMParam to the given the calibrated rdc.
+    pub fn set_rdc(&mut self, ch: usize, rdc: i32) {
+        self.set(ch, DsmAPI::DsmRdc, rdc);
+    }
+
+    /// Sets DSMParam to the given calibrated temp.
+    pub fn set_ambient_temp(&mut self, ch: usize, temp: i32) {
+        self.set(ch, DsmAPI::DsmAmbientTemp, temp);
+    }
+
+    /// Sets the `id` field to the given `val`.
+    fn set(&mut self, channel: usize, id: DsmAPI, val: i32) {
+        let pos = Self::value_pos(self.param_count, channel, id);
+        self.tlv[pos] = val as u32;
+    }
+
+    /// Gets the val from the `id` field from all the channels.
+    fn get(&self, id: DsmAPI) -> Vec<i32> {
+        (0..self.num_channels)
+            .map(|channel| {
+                let pos = Self::value_pos(self.param_count, channel, id);
+                self.tlv[pos] as i32
+            })
+            .collect()
+    }
+
+    fn try_from_tlv(tlv: TLV, num_channels: usize) -> Result<Self> {
+        let param_count_pos = Self::value_pos(0, 0, DsmAPI::ParamCount);
+
+        if tlv.len() < param_count_pos {
+            return Err(Error::InvalidDSMParam);
+        }
+
+        let param_count = tlv[param_count_pos] as usize;
+
+        if tlv.len() != Self::SOF_HEADER_SIZE + param_count * num_channels * Self::DWORD_PER_PARAM {
+            return Err(Error::InvalidDSMParam);
+        }
+
+        Ok(Self {
+            param_count,
+            num_channels,
+            tlv,
+        })
+    }
+
+    #[inline]
+    fn value_pos(param_count: usize, channel: usize, id: DsmAPI) -> usize {
+        Self::SOF_HEADER_SIZE
+            + (channel * param_count + id as usize) * Self::DWORD_PER_PARAM
+            + Self::VALUE_OFFSET
+    }
+}
+
+impl Into<TLV> for DSMParam {
+    fn into(self) -> TLV {
+        self.tlv
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    const PARAM_COUNT: usize = 138;
+    const CHANNEL_COUNT: usize = 2;
+
+    #[test]
+    fn test_dsmparam_try_from_tlv_ok() {
+        let mut data = vec![
+            0u32;
+            DSMParam::SOF_HEADER_SIZE
+                + CHANNEL_COUNT * PARAM_COUNT * DSMParam::DWORD_PER_PARAM
+        ];
+        data[DSMParam::value_pos(PARAM_COUNT, 0, DsmAPI::ParamCount)] = PARAM_COUNT as u32;
+        data[DSMParam::value_pos(PARAM_COUNT, 1, DsmAPI::ParamCount)] = PARAM_COUNT as u32;
+
+        let tlv = TLV::new(0, data);
+        assert!(DSMParam::try_from_tlv(tlv, CHANNEL_COUNT).is_ok());
+    }
+
+    #[test]
+    fn test_dsmparam_try_from_invalid_len() {
+        let data = vec![0u32; DSMParam::SOF_HEADER_SIZE];
+
+        let tlv = TLV::new(0, data);
+        assert_eq!(
+            DSMParam::try_from_tlv(tlv, CHANNEL_COUNT).unwrap_err(),
+            Error::InvalidDSMParam
+        );
+    }
+
+    #[test]
+    fn test_dsmparam_try_from_param_count() {
+        let data = vec![
+            0u32;
+            DSMParam::SOF_HEADER_SIZE
+                + CHANNEL_COUNT * PARAM_COUNT * DSMParam::DWORD_PER_PARAM
+        ];
+
+        let tlv = TLV::new(0, data);
+        assert_eq!(
+            DSMParam::try_from_tlv(tlv, CHANNEL_COUNT).unwrap_err(),
+            Error::InvalidDSMParam
+        );
+    }
+}
diff --git a/sound_card_init/amp/src/max98373d/mod.rs b/sound_card_init/amp/src/max98373d/mod.rs
new file mode 100644
index 0000000..1ee29ce
--- /dev/null
+++ b/sound_card_init/amp/src/max98373d/mod.rs
@@ -0,0 +1,274 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//! `max98373d` module implements the required initialization workflows for sound
+//! cards that use max98373d smart amp.
+//! It currently supports boot time calibration for max98373d.
+#![deny(missing_docs)]
+mod dsm_param;
+mod settings;
+
+use std::path::Path;
+use std::time::Duration;
+use std::{fs, thread};
+
+use cros_alsa::{Card, IntControl};
+use dsm::{CalibData, Error, Result, SpeakerStatus, ZeroPlayer, DSM};
+use sys_util::info;
+
+use crate::Amp;
+use dsm_param::*;
+use settings::{AmpCalibSettings, DeviceSettings};
+
+/// It implements the amplifier boot time calibration flow.
+pub struct Max98373 {
+    card: Card,
+    setting: AmpCalibSettings,
+}
+
+impl Amp for Max98373 {
+    /// Performs max98373d boot time calibration.
+    ///
+    /// # Errors
+    ///
+    /// If any amplifiers fail to complete the calibration.
+    fn boot_time_calibration(&mut self) -> Result<()> {
+        if !Path::new(&self.setting.dsm_param).exists() {
+            return Err(Error::MissingDSMParam);
+        }
+
+        let num_channels = self.setting.num_channels();
+        let dsm = DSM::new(
+            &self.card.name(),
+            num_channels,
+            Self::rdc_to_ohm,
+            Self::TEMP_UPPER_LIMIT_CELSIUS,
+            Self::TEMP_LOWER_LIMIT_CELSIUS,
+        );
+        self.set_volume(VolumeMode::Low)?;
+
+        let calib = if !self.setting.boot_time_calibration_enabled {
+            info!("skip boot time calibration and use vpd values");
+            // Needs Rdc updates to be done after internal speaker is ready otherwise
+            // it would be overwritten by the DSM blob update.
+            dsm.wait_for_speakers_ready()?;
+            dsm.get_all_vpd_calibration_value()?
+        } else {
+            match dsm.check_speaker_over_heated_workflow()? {
+                SpeakerStatus::Hot(previous_calib) => previous_calib,
+                SpeakerStatus::Cold => {
+                    let all_temp = self.get_ambient_temp()?;
+                    let all_rdc = self.do_rdc_calibration()?;
+                    all_rdc
+                        .iter()
+                        .zip(all_temp)
+                        .enumerate()
+                        .map(|(ch, (&rdc, temp))| {
+                            dsm.decide_calibration_value_workflow(ch, CalibData { rdc, temp })
+                        })
+                        .collect::<Result<Vec<_>>>()?
+                }
+            }
+        };
+        self.apply_calibration_value(&calib)?;
+        self.set_volume(VolumeMode::High)?;
+        Ok(())
+    }
+}
+
+impl Max98373 {
+    const TEMP_CALIB_WARM_UP_TIME: Duration = Duration::from_millis(10);
+    const RDC_CALIB_WARM_UP_TIME: Duration = Duration::from_millis(500);
+    const RDC_CALIB_INTERVAL: Duration = Duration::from_millis(200);
+    const CALIB_REPEAT_TIMES: usize = 5;
+
+    const TEMP_UPPER_LIMIT_CELSIUS: f32 = 40.0;
+    const TEMP_LOWER_LIMIT_CELSIUS: f32 = 0.0;
+
+    /// Creates an `Max98373`.
+    /// # Arguments
+    ///
+    /// * `card_name` - card_name.
+    /// * `config_path` - config file path.
+    ///
+    /// # Results
+    ///
+    /// * `Max98373` - It implements the Max98373 functions of boot time calibration.
+    ///
+    /// # Errors
+    ///
+    /// * If `Card` creation from sound card name fails.
+    pub fn new(card_name: &str, config_path: &Path) -> Result<Self> {
+        let conf = fs::read_to_string(config_path)
+            .map_err(|e| Error::FileIOFailed(config_path.to_path_buf(), e))?;
+        let settings = DeviceSettings::from_yaml_str(&conf)?;
+        Ok(Self {
+            card: Card::new(card_name)?,
+            setting: settings.amp_calibrations,
+        })
+    }
+
+    /// Triggers the amplifier calibration and reads the calibrated rdc.
+    /// To get accurate calibration results, the main thread calibrates the amplifier while
+    /// the `zero_player` starts another thread to play zeros to the speakers.
+    fn do_rdc_calibration(&mut self) -> Result<Vec<i32>> {
+        let mut zero_player: ZeroPlayer = Default::default();
+        zero_player.start(Self::RDC_CALIB_WARM_UP_TIME)?;
+        // Playback of zeros is started for Self::RDC_CALIB_WARM_UP_TIME, and the main thread
+        // can start the calibration.
+        self.set_spt_mode(SPTMode::OFF)?;
+        self.set_calibration_mode(CalibMode::ON)?;
+        // Playback of zeros is started, and the main thread can start the calibration.
+        let mut avg_rdc = vec![0; self.setting.num_channels()];
+        for _ in 0..Self::CALIB_REPEAT_TIMES {
+            let rdc = self.get_adaptive_rdc()?;
+            for i in 0..self.setting.num_channels() {
+                avg_rdc[i] += rdc[i];
+            }
+            thread::sleep(Self::RDC_CALIB_INTERVAL);
+        }
+        self.set_spt_mode(SPTMode::ON)?;
+        self.set_calibration_mode(CalibMode::OFF)?;
+        zero_player.stop()?;
+
+        avg_rdc = avg_rdc
+            .iter()
+            .map(|val| val / Self::CALIB_REPEAT_TIMES as i32)
+            .collect();
+        Ok(avg_rdc)
+    }
+
+    /// Sets the card volume control to the given VolumeMode.
+    fn set_volume(&mut self, mode: VolumeMode) -> Result<()> {
+        let mut dsm_param = DSMParam::new(
+            &mut self.card,
+            self.setting.num_channels(),
+            &self.setting.dsm_param_read_ctrl,
+        )?;
+
+        dsm_param.set_volume_mode(mode);
+
+        self.card
+            .control_tlv_by_name(&self.setting.dsm_param_write_ctrl)?
+            .save(dsm_param.into())
+            .map_err(Error::DSMParamUpdateFailed)?;
+        Ok(())
+    }
+
+    /// Applies the calibration value to the amp.
+    fn apply_calibration_value(&mut self, calib: &[CalibData]) -> Result<()> {
+        let mut dsm_param = DSMParam::new(
+            &mut self.card,
+            self.setting.num_channels(),
+            &self.setting.dsm_param_read_ctrl,
+        )?;
+        for ch in 0..self.setting.num_channels() {
+            dsm_param.set_rdc(ch, calib[ch].rdc);
+            dsm_param.set_ambient_temp(ch, Self::celsius_to_dsm_unit(calib[ch].temp));
+        }
+        self.card
+            .control_tlv_by_name(&self.setting.dsm_param_write_ctrl)?
+            .save(dsm_param.into())
+            .map_err(Error::DSMParamUpdateFailed)?;
+        Ok(())
+    }
+
+    /// Rdc (ohm) = [ID:0x12] * 3.66 / 2^27
+    #[inline]
+    fn rdc_to_ohm(x: i32) -> f32 {
+        (3.66 * x as f32) / (1 << 27) as f32
+    }
+
+    /// Returns the ambient temperature in celsius degree.
+    fn get_ambient_temp(&mut self) -> Result<Vec<f32>> {
+        let mut zero_player: ZeroPlayer = Default::default();
+        zero_player.start(Self::TEMP_CALIB_WARM_UP_TIME)?;
+        let mut temps = Vec::new();
+        for x in 0..self.setting.num_channels() as usize {
+            let temp = self
+                .card
+                .control_by_name::<IntControl>(&self.setting.temp_ctrl[x])?
+                .get()?;
+            let celsius = Self::measured_temp_to_celsius(temp);
+            temps.push(celsius);
+        }
+        zero_player.stop()?;
+
+        Ok(temps)
+    }
+
+    /// Converts the measured ambient temperature to celsius unit.
+    #[inline]
+    fn measured_temp_to_celsius(temp: i32) -> f32 {
+        // Measured Temperature (°C) = ([Mixer Val] * 1.28) - 29
+        (temp as f32 * 1.28) - 29.0
+    }
+
+    /// Converts the ambient temperature from celsius to the DsmSetAPI::DsmAmbientTemp unit.
+    #[inline]
+    fn celsius_to_dsm_unit(celsius: f32) -> i32 {
+        // Temperature (℃) = [ID:0x12] / 2^19
+        (celsius * (1 << 19) as f32) as i32
+    }
+
+    /// Sets the amp to the given smart pilot signal mode.
+    fn set_spt_mode(&mut self, mode: SPTMode) -> Result<()> {
+        let mut dsm_param = DSMParam::new(
+            &mut self.card,
+            self.setting.num_channels(),
+            &self.setting.dsm_param_read_ctrl,
+        )?;
+        dsm_param.set_spt_mode(mode);
+        self.card
+            .control_tlv_by_name(&self.setting.dsm_param_write_ctrl)?
+            .save(dsm_param.into())
+            .map_err(Error::DSMParamUpdateFailed)?;
+        Ok(())
+    }
+
+    /// Sets the amp to the given the calibration mode.
+    fn set_calibration_mode(&mut self, mode: CalibMode) -> Result<()> {
+        let mut dsm_param = DSMParam::new(
+            &mut self.card,
+            self.setting.num_channels(),
+            &self.setting.dsm_param_read_ctrl,
+        )?;
+        dsm_param.set_calibration_mode(mode);
+        self.card
+            .control_tlv_by_name(&self.setting.dsm_param_write_ctrl)?
+            .save(dsm_param.into())
+            .map_err(Error::DSMParamUpdateFailed)?;
+        Ok(())
+    }
+
+    /// Reads the calibrated rdc.
+    /// Must be called when the calibration mode in on.
+    fn get_adaptive_rdc(&mut self) -> Result<Vec<i32>> {
+        let dsm_param = DSMParam::new(
+            &mut self.card,
+            self.setting.num_channels(),
+            &self.setting.dsm_param_read_ctrl,
+        )?;
+        Ok(dsm_param.get_adaptive_rdc())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    #[test]
+    fn celsius_to_dsm_unit() {
+        assert_eq!(Max98373::celsius_to_dsm_unit(37.0), 0x01280000);
+        assert_eq!(Max98373::celsius_to_dsm_unit(50.0), 0x01900000);
+    }
+
+    #[test]
+    fn rdc_to_ohm() {
+        assert_eq!(Max98373::rdc_to_ohm(0x05cea0c7), 2.656767);
+    }
+
+    #[test]
+    fn measured_temp_to_celsius() {
+        assert_eq!(Max98373::measured_temp_to_celsius(56), 42.68);
+    }
+}
diff --git a/sound_card_init/amp/src/max98373d/settings.rs b/sound_card_init/amp/src/max98373d/settings.rs
new file mode 100644
index 0000000..1d6e64e
--- /dev/null
+++ b/sound_card_init/amp/src/max98373d/settings.rs
@@ -0,0 +1,41 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+use std::string::String;
+
+use dsm::{self, Error, Result};
+use serde::Deserialize;
+/// `DeviceSettings` includes the settings of max98373. It currently includes:
+/// * the settings of amplifier calibration.
+/// * the path of dsm_param.
+#[derive(Debug, Default, PartialEq, Deserialize, Clone)]
+pub struct DeviceSettings {
+    pub amp_calibrations: AmpCalibSettings,
+}
+
+/// `AmpCalibSettings` includes the settings needed for amplifier calibration.
+#[derive(Debug, Default, PartialEq, Deserialize, Clone)]
+pub struct AmpCalibSettings {
+    pub dsm_param_read_ctrl: String,
+    pub dsm_param_write_ctrl: String,
+    pub temp_ctrl: Vec<String>,
+    // Path of the dsm_param.bin file.
+    pub dsm_param: String,
+    pub boot_time_calibration_enabled: bool,
+}
+
+impl AmpCalibSettings {
+    /// Returns the number of channels.
+    pub fn num_channels(&self) -> usize {
+        self.temp_ctrl.len()
+    }
+}
+
+impl DeviceSettings {
+    /// Creates a `DeviceSettings` from a yaml str.
+    pub fn from_yaml_str(conf: &str) -> Result<DeviceSettings> {
+        let settings: DeviceSettings = serde_yaml::from_str(conf)
+            .map_err(|e| Error::DeserializationFailed("DeviceSettings".to_owned(), e))?;
+        Ok(settings)
+    }
+}
diff --git a/sound_card_init/amp/src/max98390d/mod.rs b/sound_card_init/amp/src/max98390d/mod.rs
new file mode 100644
index 0000000..601165e
--- /dev/null
+++ b/sound_card_init/amp/src/max98390d/mod.rs
@@ -0,0 +1,213 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//! `max98390d` module implements the required initialization workflows for sound
+//! cards that use max98390d smart amp.
+//! It currently supports boot time calibration for max98390d.
+#![deny(missing_docs)]
+mod settings;
+
+use std::time::Duration;
+use std::{fs, path::Path};
+
+use cros_alsa::{Card, IntControl, SwitchControl};
+use dsm::{CalibData, Error, Result, SpeakerStatus, TempConverter, ZeroPlayer, DSM};
+
+use crate::Amp;
+use settings::{AmpCalibSettings, DeviceSettings};
+
+/// Amp volume mode emulation used by set_volume().
+#[derive(PartialEq, Clone, Copy)]
+enum VolumeMode {
+    /// Low mode protects the speaker by limiting its output volume if the
+    /// calibration has not been completed successfully.
+    Low = 138,
+    /// High mode removes the speaker output volume limitation after
+    /// having successfully completed the calibration.
+    High = 148,
+}
+
+/// It implements the Max98390 functions of boot time calibration.
+#[derive(Debug)]
+pub struct Max98390 {
+    card: Card,
+    setting: AmpCalibSettings,
+}
+
+impl Amp for Max98390 {
+    /// Performs max98390d boot time calibration.
+    ///
+    /// # Errors
+    ///
+    /// If the amplifier fails to complete the calibration.
+    fn boot_time_calibration(&mut self) -> Result<()> {
+        if !Path::new(&self.setting.dsm_param).exists() {
+            return Err(Error::MissingDSMParam);
+        }
+
+        let mut dsm = DSM::new(
+            &self.card.name(),
+            self.setting.num_channels(),
+            Self::rdc_to_ohm,
+            Self::TEMP_UPPER_LIMIT_CELSIUS,
+            Self::TEMP_LOWER_LIMIT_CELSIUS,
+        );
+        dsm.set_temp_converter(TempConverter::new(
+            Self::dsm_unit_to_celsius,
+            Self::celsius_to_dsm_unit,
+        ));
+
+        self.set_volume(VolumeMode::Low)?;
+        let calib = match dsm.check_speaker_over_heated_workflow()? {
+            SpeakerStatus::Hot(previous_calib) => previous_calib,
+            SpeakerStatus::Cold => self
+                .do_calibration()?
+                .iter()
+                .enumerate()
+                .map(|(ch, calib_data)| dsm.decide_calibration_value_workflow(ch, *calib_data))
+                .collect::<Result<Vec<_>>>()?,
+        };
+        self.apply_calibration_value(calib)?;
+        self.set_volume(VolumeMode::High)?;
+        Ok(())
+    }
+}
+
+impl Max98390 {
+    const TEMP_UPPER_LIMIT_CELSIUS: f32 = 40.0;
+    const TEMP_LOWER_LIMIT_CELSIUS: f32 = 0.0;
+    const RDC_CALIB_WARM_UP_TIME: Duration = Duration::from_millis(300);
+
+    /// Creates an `Max98390`.
+    /// # Arguments
+    ///
+    /// * `card_name` - card name.
+    /// * `config_path` - config file path.
+    ///
+    /// # Results
+    ///
+    /// * `Max98390` - It implements the Max98390 functions of boot time calibration.
+    ///
+    /// # Errors
+    ///
+    /// * If `Card` creation from sound card name fails.
+    pub fn new(card_name: &str, config_path: &Path) -> Result<Self> {
+        let conf = fs::read_to_string(config_path)
+            .map_err(|e| Error::FileIOFailed(config_path.to_path_buf(), e))?;
+        let settings = DeviceSettings::from_yaml_str(&conf)?;
+        Ok(Self {
+            card: Card::new(card_name)?,
+            setting: settings.amp_calibrations,
+        })
+    }
+
+    /// Sets the card volume control to given VolumeMode.
+    fn set_volume(&mut self, mode: VolumeMode) -> Result<()> {
+        for control in &self.setting.controls {
+            self.card
+                .control_by_name::<IntControl>(&control.volume_ctrl)?
+                .set(mode as i32)?;
+        }
+        Ok(())
+    }
+
+    /// Applies the calibration value to the amp.
+    fn apply_calibration_value(&mut self, calib: Vec<CalibData>) -> Result<()> {
+        for (ch, &CalibData { rdc, temp }) in calib.iter().enumerate() {
+            self.card
+                .control_by_name::<IntControl>(&self.setting.controls[ch].rdc_ctrl)?
+                .set(rdc)?;
+            self.card
+                .control_by_name::<IntControl>(&self.setting.controls[ch].temp_ctrl)?
+                .set(Self::celsius_to_dsm_unit(temp))?;
+        }
+        Ok(())
+    }
+
+    /// Triggers the amplifier calibration and reads the calibrated rdc and ambient_temp value
+    /// from the mixer control.
+    /// To get accurate calibration results, the main thread calibrates the amplifier while
+    /// the `zero_player` starts another thread to play zeros to the speakers.
+    fn do_calibration(&mut self) -> Result<Vec<CalibData>> {
+        let mut zero_player: ZeroPlayer = Default::default();
+        zero_player.start(Self::RDC_CALIB_WARM_UP_TIME)?;
+        // Playback of zeros is started for Self::RDC_CALIB_WARM_UP_TIME, and the main thread
+        // can start the calibration.
+        let setting = &self.setting;
+        let card = &mut self.card;
+        let calib = setting
+            .controls
+            .iter()
+            .map(|control| {
+                card.control_by_name::<SwitchControl>(&control.calib_ctrl)?
+                    .on()?;
+                let rdc = card
+                    .control_by_name::<IntControl>(&control.rdc_ctrl)?
+                    .get()?;
+                let temp = card
+                    .control_by_name::<IntControl>(&control.temp_ctrl)?
+                    .get()?;
+                card.control_by_name::<SwitchControl>(&control.calib_ctrl)?
+                    .off()?;
+                Ok(CalibData {
+                    rdc,
+                    temp: Self::dsm_unit_to_celsius(temp),
+                })
+            })
+            .collect::<Result<Vec<CalibData>>>()?;
+        zero_player.stop()?;
+        Ok(calib)
+    }
+
+    /// Converts the ambient temperature from celsius to the DSM unit.
+    #[inline]
+    fn celsius_to_dsm_unit(celsius: f32) -> i32 {
+        (celsius * ((1 << 12) as f32) / 100.0) as i32
+    }
+
+    /// Converts the ambient temperature from  DSM unit to celsius.
+    #[inline]
+    fn dsm_unit_to_celsius(temp: i32) -> f32 {
+        temp as f32 * 100.0 / (1 << 12) as f32
+    }
+
+    /// Converts the calibrated value to real DC resistance in ohm unit.
+    #[inline]
+    fn rdc_to_ohm(x: i32) -> f32 {
+        3.66 * (1 << 20) as f32 / x as f32
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    #[test]
+    fn celsius_to_dsm_unit() {
+        assert_eq!(
+            Max98390::celsius_to_dsm_unit(Max98390::TEMP_UPPER_LIMIT_CELSIUS),
+            1638
+        );
+        assert_eq!(
+            Max98390::celsius_to_dsm_unit(Max98390::TEMP_LOWER_LIMIT_CELSIUS),
+            0
+        );
+    }
+
+    #[test]
+    fn dsm_unit_to_celsius() {
+        assert_eq!(
+            Max98390::dsm_unit_to_celsius(1638).round(),
+            Max98390::TEMP_UPPER_LIMIT_CELSIUS
+        );
+        assert_eq!(
+            Max98390::dsm_unit_to_celsius(0),
+            Max98390::TEMP_LOWER_LIMIT_CELSIUS
+        );
+    }
+
+    #[test]
+    fn rdc_to_ohm() {
+        assert_eq!(Max98390::rdc_to_ohm(1123160), 3.416956);
+        assert_eq!(Max98390::rdc_to_ohm(1157049), 3.3168762);
+    }
+}
diff --git a/sound_card_init/amp/src/max98390d/settings.rs b/sound_card_init/amp/src/max98390d/settings.rs
new file mode 100644
index 0000000..316f25b
--- /dev/null
+++ b/sound_card_init/amp/src/max98390d/settings.rs
@@ -0,0 +1,51 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+use std::string::String;
+
+use dsm::{self, Error, Result};
+use serde::Deserialize;
+
+/// `DeviceSettings` includes the settings of max98390. It currently includes:
+/// * the settings of amplifier calibration.
+/// * the path of dsm_param.
+#[derive(Debug, Default, PartialEq, Deserialize, Clone)]
+pub struct DeviceSettings {
+    pub amp_calibrations: AmpCalibSettings,
+}
+#[derive(Debug, Default, PartialEq, Deserialize, Clone)]
+pub struct AmpCalibCtrl {
+    // Mixer control to get/set rdc value.
+    pub rdc_ctrl: String,
+    // Mixer control to get/set ambient temperature value.
+    pub temp_ctrl: String,
+    // Mixer control to trigger calibration.
+    pub calib_ctrl: String,
+    // Mixer control to adjust volume.
+    pub volume_ctrl: String,
+}
+
+/// `AmpCalibSettings` includes the settings needed for amplifier calibration.
+#[derive(Debug, Default, PartialEq, Deserialize, Clone)]
+pub struct AmpCalibSettings {
+    // Mixer control to get/set rdc value.
+    pub controls: Vec<AmpCalibCtrl>,
+    // Path of the dsm_param.bin file.
+    pub dsm_param: String,
+}
+
+impl AmpCalibSettings {
+    /// Returns the number of channels.
+    pub fn num_channels(&self) -> usize {
+        self.controls.len()
+    }
+}
+
+impl DeviceSettings {
+    /// Creates a `DeviceSettings` from a yaml str.
+    pub fn from_yaml_str(conf: &str) -> Result<DeviceSettings> {
+        let settings: DeviceSettings = serde_yaml::from_str(conf)
+            .map_err(|e| Error::DeserializationFailed("DeviceSettings".to_owned(), e))?;
+        Ok(settings)
+    }
+}
diff --git a/sound_card_init/dsm/Cargo.lock b/sound_card_init/dsm/Cargo.lock
new file mode 100644
index 0000000..411c752
--- /dev/null
+++ b/sound_card_init/dsm/Cargo.lock
@@ -0,0 +1,229 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "alsa-sys"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "644d308f5822c2b39fba5a6d850f74c208bf73c61d1d2dfad62505d6960e4977"
+dependencies = [
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
+name = "assertions"
+version = "0.1.0"
+
+[[package]]
+name = "audio_streams"
+version = "0.1.0"
+dependencies = [
+ "sync",
+ "sys_util",
+]
+
+[[package]]
+name = "cras-sys"
+version = "0.1.0"
+dependencies = [
+ "audio_streams",
+ "data_model",
+]
+
+[[package]]
+name = "cros_alsa"
+version = "0.1.0"
+dependencies = [
+ "alsa-sys",
+ "libc",
+ "remain",
+ "sys_util",
+]
+
+[[package]]
+name = "data_model"
+version = "0.1.0"
+dependencies = [
+ "assertions",
+]
+
+[[package]]
+name = "dtoa"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3"
+
+[[package]]
+name = "libc"
+version = "0.2.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005"
+
+[[package]]
+name = "libcras"
+version = "0.1.0"
+dependencies = [
+ "audio_streams",
+ "cras-sys",
+ "data_model",
+ "libc",
+ "sys_util",
+]
+
+[[package]]
+name = "linked-hash-map"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83"
+
+[[package]]
+name = "max98390d"
+version = "0.1.0"
+dependencies = [
+ "audio_streams",
+ "cros_alsa",
+ "libcras",
+ "remain",
+ "serde",
+ "serde_yaml",
+ "sound_card_util",
+ "sys_util",
+]
+
+[[package]]
+name = "pkg-config"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677"
+
+[[package]]
+name = "poll_token_derive"
+version = "0.1.0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df246d292ff63439fea9bc8c0a270bed0e390d5ebd4db4ba15aba81111b5abe3"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "remain"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99c861227fc40c8da6fdaa3d58144ac84c0537080a43eb1d7d45c28f88dcb888"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36df6ac6412072f67cf767ebbde4133a5b2e88e76dc6187fa7104cd16f783399"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e549e3abf4fb8621bd1609f11dfc9f5e50320802273b12f3811a67e6716ea6c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_yaml"
+version = "0.8.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "691b17f19fc1ec9d94ec0b5864859290dff279dbd7b03f017afda54eb36c3c35"
+dependencies = [
+ "dtoa",
+ "linked-hash-map",
+ "serde",
+ "yaml-rust",
+]
+
+[[package]]
+name = "sound_card_util"
+version = "0.1.0"
+dependencies = [
+ "cros_alsa",
+ "remain",
+ "sys_util",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0df0eb663f387145cab623dea85b09c2c5b4b0aef44e945d928e682fce71bb03"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "sync"
+version = "0.1.0"
+
+[[package]]
+name = "sys_util"
+version = "0.1.0"
+dependencies = [
+ "data_model",
+ "libc",
+ "poll_token_derive",
+ "sync",
+ "syscall_defines",
+ "tempfile",
+]
+
+[[package]]
+name = "syscall_defines"
+version = "0.1.0"
+
+[[package]]
+name = "tempfile"
+version = "3.0.7"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
+
+[[package]]
+name = "yaml-rust"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "65923dd1784f44da1d2c3dbbc5e822045628c590ba72123e1c73d3c230c4434d"
+dependencies = [
+ "linked-hash-map",
+]
diff --git a/sound_card_init/dsm/Cargo.toml b/sound_card_init/dsm/Cargo.toml
new file mode 100644
index 0000000..280896d
--- /dev/null
+++ b/sound_card_init/dsm/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "dsm"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+edition = "2018"
+description = "The boot time calibration logic for smart amp"
+
+[dependencies]
+cros_alsa = "*"
+audio_streams = "*"
+libcras = "*"
+remain = "0.2.1"
+serde = { version = "1.0", features = ["derive"]}
+serde_yaml = "0.8.11"
+sys_util = "*"
\ No newline at end of file
diff --git a/sound_card_init/dsm/src/datastore.rs b/sound_card_init/dsm/src/datastore.rs
new file mode 100644
index 0000000..f0180cc
--- /dev/null
+++ b/sound_card_init/dsm/src/datastore.rs
@@ -0,0 +1,72 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+use std::fs::{remove_file, File};
+use std::io::{prelude::*, BufReader, BufWriter};
+use std::path::PathBuf;
+
+use serde::{Deserialize, Serialize};
+use sys_util::info;
+
+use crate::error::{Error, Result};
+
+/// `Datastore`, which stores and reads calibration values in yaml format.
+#[derive(Debug, Deserialize, Serialize, Copy, Clone)]
+pub enum Datastore {
+    /// Indicates using values in VPD.
+    UseVPD,
+    DSM {
+        rdc: i32,
+        temp: i32,
+    },
+}
+
+impl Datastore {
+    /// The dir of datastore.
+    pub const DATASTORE_DIR: &'static str = "/var/lib/sound_card_init";
+
+    /// Creates a `Datastore` and initializes its fields from the datastore file.
+    pub fn from_file(snd_card: &str, channel: usize) -> Result<Datastore> {
+        let path = Self::path(snd_card, channel);
+        let reader =
+            BufReader::new(File::open(&path).map_err(|e| Error::FileIOFailed(path.to_owned(), e))?);
+        let datastore: Datastore =
+            serde_yaml::from_reader(reader).map_err(|e| Error::SerdeError(path.to_owned(), e))?;
+        Ok(datastore)
+    }
+
+    /// Saves a `Datastore` to file.
+    pub fn save(&self, snd_card: &str, channel: usize) -> Result<()> {
+        let path = Self::path(snd_card, channel);
+
+        let mut writer = BufWriter::new(
+            File::create(&path).map_err(|e| Error::FileIOFailed(path.to_owned(), e))?,
+        );
+        writer
+            .write(
+                serde_yaml::to_string(self)
+                    .map_err(|e| Error::SerdeError(path.to_owned(), e))?
+                    .as_bytes(),
+            )
+            .map_err(|e| Error::FileIOFailed(path.to_owned(), e))?;
+        writer
+            .flush()
+            .map_err(|e| Error::FileIOFailed(path.to_owned(), e))?;
+        info!("update Datastore {}: {:?}", path.to_string_lossy(), self);
+        Ok(())
+    }
+
+    /// Deletes the datastore file.
+    pub fn delete(snd_card: &str, channel: usize) -> Result<()> {
+        let path = Self::path(snd_card, channel);
+        remove_file(&path).map_err(|e| Error::FileIOFailed(path.to_owned(), e))?;
+        info!("datastore: {:#?} is deleted.", path);
+        Ok(())
+    }
+
+    fn path(snd_card: &str, channel: usize) -> PathBuf {
+        PathBuf::from(Self::DATASTORE_DIR)
+            .join(snd_card)
+            .join(format!("calib_{}", channel))
+    }
+}
diff --git a/sound_card_init/dsm/src/error.rs b/sound_card_init/dsm/src/error.rs
new file mode 100644
index 0000000..4b6e8dc
--- /dev/null
+++ b/sound_card_init/dsm/src/error.rs
@@ -0,0 +1,128 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+use std::any::Any;
+use std::error;
+use std::fmt;
+use std::io;
+use std::num::ParseIntError;
+use std::path::PathBuf;
+use std::sync::PoisonError;
+use std::time;
+
+use remain::sorted;
+
+use crate::CalibData;
+
+pub type Result<T> = std::result::Result<T, Error>;
+
+#[sorted]
+#[derive(Debug)]
+pub enum Error {
+    AlsaCardError(cros_alsa::CardError),
+    AlsaControlError(cros_alsa::ControlError),
+    AlsaControlTLVError(cros_alsa::ControlTLVError),
+    CalibrationTimeout,
+    CrasClientFailed(libcras::Error),
+    DeserializationFailed(String, serde_yaml::Error),
+    DSMParamUpdateFailed(cros_alsa::ControlTLVError),
+    FileIOFailed(PathBuf, io::Error),
+    InternalSpeakerNotFound,
+    InvalidDatastore,
+    InvalidDSMParam,
+    InvalidShutDownTime,
+    InvalidTemperature(f32),
+    LargeCalibrationDiff(CalibData),
+    MissingDSMParam,
+    MutexPoisonError,
+    NewPlayStreamFailed(libcras::BoxError),
+    NextPlaybackBufferFailed(libcras::BoxError),
+    PlaybackFailed(io::Error),
+    SerdeError(PathBuf, serde_yaml::Error),
+    StartPlaybackTimeout,
+    SystemTimeError(time::SystemTimeError),
+    UnsupportedSoundCard(String),
+    VPDParseFailed(String, ParseIntError),
+    WorkerPanics(Box<dyn Any + Send + 'static>),
+    ZeroPlayerIsNotRunning,
+    ZeroPlayerIsRunning,
+}
+
+impl PartialEq for Error {
+    // Implement eq for more Error when needed.
+    fn eq(&self, other: &Error) -> bool {
+        match (self, other) {
+            (Error::InvalidDSMParam, Error::InvalidDSMParam) => true,
+            _ => false,
+        }
+    }
+}
+
+impl From<cros_alsa::CardError> for Error {
+    fn from(err: cros_alsa::CardError) -> Error {
+        Error::AlsaCardError(err)
+    }
+}
+
+impl From<cros_alsa::ControlError> for Error {
+    fn from(err: cros_alsa::ControlError) -> Error {
+        Error::AlsaControlError(err)
+    }
+}
+
+impl From<cros_alsa::ControlTLVError> for Error {
+    fn from(err: cros_alsa::ControlTLVError) -> Error {
+        Error::AlsaControlTLVError(err)
+    }
+}
+
+impl<T> From<PoisonError<T>> for Error {
+    fn from(_: PoisonError<T>) -> Error {
+        Error::MutexPoisonError
+    }
+}
+
+impl error::Error for Error {}
+
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        use Error::*;
+        match self {
+            AlsaCardError(e) => write!(f, "AlsaCardError: {}", e),
+            AlsaControlError(e) => write!(f, "AlsaControlError: {}", e),
+            AlsaControlTLVError(e) => write!(f, "AlsaControlTLVError: {}", e),
+            CalibrationTimeout => write!(f, "calibration is not finished in time"),
+            DSMParamUpdateFailed(e) => write!(f, "failed to update DsmParam, err: {}", e),
+            CrasClientFailed(e) => write!(f, "failed to create cras client: {}", e),
+            DeserializationFailed(file_path, e) => {
+                write!(f, "failed to parse {}: {}", file_path, e)
+            }
+            FileIOFailed(file_path, e) => write!(f, "{:#?}: {}", file_path, e),
+            InvalidShutDownTime => write!(f, "invalid shutdown time"),
+            InternalSpeakerNotFound => write!(f, "internal speaker is not found in cras"),
+            InvalidTemperature(temp) => write!(
+                f,
+                "invalid calibration temperature: {}, and there is no datastore",
+                temp
+            ),
+            InvalidDatastore => write!(f, "invalid datastore format"),
+            InvalidDSMParam => write!(f, "invalid dsm param from kcontrol"),
+            LargeCalibrationDiff(calib) => {
+                write!(f, "calibration difference is too large, calib: {:?}", calib)
+            }
+            MissingDSMParam => write!(f, "missing dsm_param.bin"),
+            MutexPoisonError => write!(f, "mutex is poisoned"),
+            NewPlayStreamFailed(e) => write!(f, "{}", e),
+            NextPlaybackBufferFailed(e) => write!(f, "{}", e),
+            PlaybackFailed(e) => write!(f, "{}", e),
+            SerdeError(file_path, e) => write!(f, "{:?}: {}", file_path, e),
+            StartPlaybackTimeout => write!(f, "playback is not started in time"),
+            SystemTimeError(e) => write!(f, "{}", e),
+            UnsupportedSoundCard(name) => write!(f, "unsupported sound card: {}", name),
+            VPDParseFailed(file_path, e) => write!(f, "failed to parse vpd {}: {}", file_path, e),
+            WorkerPanics(e) => write!(f, "run_play_zero_worker panics: {:#?}", e),
+            ZeroPlayerIsNotRunning => write!(f, "zero player is not running"),
+            ZeroPlayerIsRunning => write!(f, "zero player is running"),
+        }
+    }
+}
diff --git a/sound_card_init/dsm/src/lib.rs b/sound_card_init/dsm/src/lib.rs
new file mode 100644
index 0000000..0b3ec64
--- /dev/null
+++ b/sound_card_init/dsm/src/lib.rs
@@ -0,0 +1,335 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//! `dsm` crate implements the required initialization workflows for smart amps.
+
+mod datastore;
+mod error;
+pub mod utils;
+mod vpd;
+mod zero_player;
+
+use std::{
+    thread,
+    time::{Duration, SystemTime, UNIX_EPOCH},
+};
+
+use libcras::{CrasClient, CrasNodeType};
+use sys_util::{error, info};
+
+use crate::datastore::Datastore;
+pub use crate::error::{Error, Result};
+use crate::utils::{run_time, shutdown_time};
+use crate::vpd::VPD;
+pub use crate::zero_player::ZeroPlayer;
+
+#[derive(Debug, Clone, Copy)]
+/// `CalibData` represents the calibration data.
+pub struct CalibData {
+    /// The DC resistance of the speaker is DSM unit.
+    pub rdc: i32,
+    /// The ambient temperature in celsius unit at which the rdc is measured.
+    pub temp: f32,
+}
+
+/// `TempConverter` converts the temperature value between celsius and unit in VPD::dsm_calib_temp.
+pub struct TempConverter {
+    vpd_to_celsius: fn(i32) -> f32,
+    celsius_to_vpd: fn(f32) -> i32,
+}
+
+impl Default for TempConverter {
+    fn default() -> Self {
+        let vpd_to_celsius = |x: i32| x as f32;
+        let celsius_to_vpd = |x: f32| x.round() as i32;
+        Self {
+            vpd_to_celsius,
+            celsius_to_vpd,
+        }
+    }
+}
+
+impl TempConverter {
+    /// Creates a `TempConverter`
+    ///
+    /// # Arguments
+    ///
+    /// * `vpd_to_celsius` - function to convert VPD::dsm_calib_temp to celsius unit`
+    /// * `celsius_to_vpd` - function to convert celsius unit to VPD::dsm_calib_temp`
+    /// # Results
+    ///
+    /// * `TempConverter` - it converts the temperature value between celsius and unit in VPD::dsm_calib_temp.
+    pub fn new(vpd_to_celsius: fn(i32) -> f32, celsius_to_vpd: fn(f32) -> i32) -> Self {
+        Self {
+            vpd_to_celsius,
+            celsius_to_vpd,
+        }
+    }
+}
+
+/// `SpeakerStatus` are the possible return results of
+/// DSM::check_speaker_over_heated_workflow.
+pub enum SpeakerStatus {
+    ///`SpeakerStatus::Cold` means the speakers are not overheated and the Amp can
+    /// trigger the boot time calibration.
+    Cold,
+    /// `SpeakerStatus::Hot(Vec<CalibData>)` means the speakers may be too hot for calibration.
+    /// The boot time calibration should be skipped and the Amp should use the previous
+    /// calibration values returned by the enum.
+    Hot(Vec<CalibData>),
+}
+
+/// `DSM`, which implements the required initialization workflows for smart amps.
+pub struct DSM {
+    snd_card: String,
+    num_channels: usize,
+    temp_converter: TempConverter,
+    rdc_to_ohm: fn(i32) -> f32,
+    temp_upper_limit: f32,
+    temp_lower_limit: f32,
+}
+
+impl DSM {
+    const SPEAKER_COOL_DOWN_TIME: Duration = Duration::from_secs(180);
+    const CALI_ERROR_UPPER_LIMIT: f32 = 0.3;
+    const CALI_ERROR_LOWER_LIMIT: f32 = 0.03;
+
+    /// Creates a `DSM`
+    ///
+    /// # Arguments
+    ///
+    /// * `snd_card` - `sound card name`.
+    /// * `num_channels` - `number of channels`.
+    /// * `rdc_to_ohm` - `fn(rdc: i32) -> f32 to convert the CalibData::rdc to ohm unit`.
+    /// * `temp_upper_limit` - the high limit of the valid ambient temperature in dsm unit.
+    /// * `temp_lower_limit` - the low limit of the valid ambient temperature in dsm unit.
+    ///
+    /// # Results
+    ///
+    /// * `DSM` - It implements the required initialization workflows for smart amps.
+    pub fn new(
+        snd_card: &str,
+        num_channels: usize,
+        rdc_to_ohm: fn(i32) -> f32,
+        temp_upper_limit: f32,
+        temp_lower_limit: f32,
+    ) -> Self {
+        Self {
+            snd_card: snd_card.to_owned(),
+            num_channels,
+            rdc_to_ohm,
+            temp_converter: TempConverter::default(),
+            temp_upper_limit,
+            temp_lower_limit,
+        }
+    }
+
+    /// Sets self.temp_converter to the given temp_converter.
+    ///
+    /// # Arguments
+    ///
+    /// * `temp_converter` - the convert function to use.
+    pub fn set_temp_converter(&mut self, temp_converter: TempConverter) {
+        self.temp_converter = temp_converter;
+    }
+
+    /// Checks whether the speakers are overheated or not according to the previous shutdown time.
+    /// The boot time calibration should be skipped when the speakers may be too hot
+    /// and the Amp should use the previous calibration value returned by the
+    /// SpeakerStatus::Hot(Vec<CalibData>).
+    ///
+    /// # Results
+    ///
+    /// * `SpeakerStatus::Cold` - which means the speakers are not overheated and the Amp can
+    ///    trigger the boot time calibration.
+    /// * `SpeakerStatus::Hot(Vec<CalibData>)` - when the speakers may be too hot. The boot
+    ///   time calibration should be skipped and the Amp should use the previous calibration values
+    ///   returned by the enum.
+    ///
+    /// # Errors
+    ///
+    /// * The speakers are overheated and there are no previous calibration values stored.
+    /// * Cannot determine whether the speakers are overheated as previous shutdown time record is
+    ///   invalid.
+    pub fn check_speaker_over_heated_workflow(&self) -> Result<SpeakerStatus> {
+        if self.is_first_boot() {
+            return Ok(SpeakerStatus::Cold);
+        }
+        match self.is_speaker_over_heated() {
+            Ok(overheated) => {
+                if overheated {
+                    let calib: Vec<CalibData> = (0..self.num_channels)
+                        .map(|ch| -> Result<CalibData> { self.get_previous_calibration_value(ch) })
+                        .collect::<Result<Vec<CalibData>>>()?;
+                    info!("the speakers are hot, the boot time calibration should be skipped");
+                    return Ok(SpeakerStatus::Hot(calib));
+                }
+                Ok(SpeakerStatus::Cold)
+            }
+            Err(err) => {
+                // We cannot assume the speakers are not replaced or not overheated
+                // when the shutdown time file is invalid; therefore we can not use the datastore
+                // value anymore and we can not trigger boot time calibration.
+                for ch in 0..self.num_channels {
+                    if let Err(e) = Datastore::delete(&self.snd_card, ch) {
+                        error!("error delete datastore: {}", e);
+                    }
+                }
+                Err(err)
+            }
+        }
+    }
+
+    /// Decides a good calibration value and updates the stored value according to the following
+    /// logic:
+    /// * Returns the previous value if the ambient temperature is not within a valid range.
+    /// * Returns Error::LargeCalibrationDiff if rdc difference is larger than
+    ///   `CALI_ERROR_UPPER_LIMIT`.
+    /// * Returns the previous value if the rdc difference is smaller than `CALI_ERROR_LOWER_LIMIT`.
+    /// * Returns the boot time calibration value and updates the datastore value if the rdc.
+    ///   difference is between `CALI_ERROR_UPPER_LIMIT` and `CALI_ERROR_LOWER_LIMIT`.
+    ///
+    /// # Arguments
+    ///
+    /// * `card` - `&Card`.
+    /// * `channel` - `channel number`.
+    /// * `calib_data` - `boot time calibrated data`.
+    ///
+    /// # Results
+    ///
+    /// * `CalibData` - the calibration data to be applied according to the deciding logic.
+    ///
+    /// # Errors
+    ///
+    /// * VPD does not exist.
+    /// * rdc difference is larger than `CALI_ERROR_UPPER_LIMIT`.
+    /// * Failed to update Datastore.
+    pub fn decide_calibration_value_workflow(
+        &self,
+        channel: usize,
+        calib_data: CalibData,
+    ) -> Result<CalibData> {
+        if calib_data.temp < self.temp_lower_limit || calib_data.temp > self.temp_upper_limit {
+            info!("invalid temperature: {}.", calib_data.temp);
+            return self
+                .get_previous_calibration_value(channel)
+                .map_err(|_| Error::InvalidTemperature(calib_data.temp));
+        }
+        let (datastore_exist, previous_calib) = match self.get_previous_calibration_value(channel) {
+            Ok(previous_calib) => (true, previous_calib),
+            Err(e) => {
+                info!("{}, use vpd as previous calibration value", e);
+                (false, self.get_vpd_calibration_value(channel)?)
+            }
+        };
+
+        let diff = {
+            let calib_rdc_ohm = (self.rdc_to_ohm)(calib_data.rdc);
+            let previous_rdc_ohm = (self.rdc_to_ohm)(previous_calib.rdc);
+            (calib_rdc_ohm - previous_rdc_ohm) / previous_rdc_ohm
+        };
+        if diff > Self::CALI_ERROR_UPPER_LIMIT {
+            Err(Error::LargeCalibrationDiff(calib_data))
+        } else if diff < Self::CALI_ERROR_LOWER_LIMIT {
+            if !datastore_exist {
+                Datastore::UseVPD.save(&self.snd_card, channel)?;
+            }
+            Ok(previous_calib)
+        } else {
+            Datastore::DSM {
+                rdc: calib_data.rdc,
+                temp: (self.temp_converter.celsius_to_vpd)(calib_data.temp),
+            }
+            .save(&self.snd_card, channel)?;
+            Ok(calib_data)
+        }
+    }
+
+    /// Gets the calibration values from vpd.
+    ///
+    /// # Results
+    ///
+    /// * `Vec<CalibData>` - the calibration values in vpd.
+    ///
+    /// # Errors
+    ///
+    /// * Failed to read vpd.
+    pub fn get_all_vpd_calibration_value(&self) -> Result<Vec<CalibData>> {
+        (0..self.num_channels)
+            .map(|ch| self.get_vpd_calibration_value(ch))
+            .collect::<Result<Vec<_>>>()
+    }
+
+    /// Blocks until the internal speakers are ready.
+    ///
+    /// # Errors
+    ///
+    /// * Failed to wait the internal speakers to be ready.
+    pub fn wait_for_speakers_ready(&self) -> Result<()> {
+        let find_speaker = || -> Result<()> {
+            let cras_client = CrasClient::new().map_err(Error::CrasClientFailed)?;
+            let _node = cras_client
+                .output_nodes()
+                .find(|node| node.node_type == CrasNodeType::CRAS_NODE_TYPE_INTERNAL_SPEAKER)
+                .ok_or(Error::InternalSpeakerNotFound)?;
+            Ok(())
+        };
+        // TODO(b/155007305): Implement cras_client.wait_node_change and use it here.
+        const RETRY: usize = 3;
+        const RETRY_INTERVAL: Duration = Duration::from_millis(500);
+        for _ in 0..RETRY {
+            match find_speaker() {
+                Ok(_) => return Ok(()),
+                Err(e) => error!("retry on finding speaker: {}", e),
+            };
+            thread::sleep(RETRY_INTERVAL);
+        }
+        Err(Error::InternalSpeakerNotFound)
+    }
+
+    fn is_first_boot(&self) -> bool {
+        !run_time::exists(&self.snd_card)
+    }
+
+    // If (Current time - the latest CRAS shutdown time) < cool_down_time, we assume that
+    // the speakers may be overheated.
+    fn is_speaker_over_heated(&self) -> Result<bool> {
+        let last_run = run_time::from_file(&self.snd_card)?;
+        let last_shutdown = shutdown_time::from_file()?;
+        if last_shutdown < last_run {
+            return Err(Error::InvalidShutDownTime);
+        }
+
+        let now = SystemTime::now()
+            .duration_since(UNIX_EPOCH)
+            .map_err(Error::SystemTimeError)?;
+
+        let elapsed = now
+            .checked_sub(last_shutdown)
+            .ok_or(Error::InvalidShutDownTime)?;
+
+        if elapsed < Self::SPEAKER_COOL_DOWN_TIME {
+            return Ok(true);
+        }
+        Ok(false)
+    }
+
+    fn get_previous_calibration_value(&self, ch: usize) -> Result<CalibData> {
+        let sci_calib = Datastore::from_file(&self.snd_card, ch)?;
+        match sci_calib {
+            Datastore::UseVPD => self.get_vpd_calibration_value(ch),
+            Datastore::DSM { rdc, temp } => Ok(CalibData {
+                rdc,
+                temp: (self.temp_converter.vpd_to_celsius)(temp),
+            }),
+        }
+    }
+
+    fn get_vpd_calibration_value(&self, channel: usize) -> Result<CalibData> {
+        let vpd = VPD::new(channel)?;
+        Ok(CalibData {
+            rdc: vpd.dsm_calib_r0,
+            temp: (self.temp_converter.vpd_to_celsius)(vpd.dsm_calib_temp),
+        })
+    }
+}
diff --git a/sound_card_init/dsm/src/utils.rs b/sound_card_init/dsm/src/utils.rs
new file mode 100644
index 0000000..64f6c97
--- /dev/null
+++ b/sound_card_init/dsm/src/utils.rs
@@ -0,0 +1,82 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//! It contains common utils shared within sound_card_init.
+#![deny(missing_docs)]
+
+use std::fs::File;
+use std::io::{prelude::*, BufReader, BufWriter};
+use std::path::PathBuf;
+use std::time::Duration;
+
+use crate::datastore::Datastore;
+use crate::error::{Error, Result};
+
+fn duration_from_file(path: &PathBuf) -> Result<Duration> {
+    let reader =
+        BufReader::new(File::open(&path).map_err(|e| Error::FileIOFailed(path.clone(), e))?);
+    serde_yaml::from_reader(reader).map_err(|e| Error::SerdeError(path.clone(), e))
+}
+
+/// The utils to parse CRAS shutdown time file.
+pub mod shutdown_time {
+    use super::*;
+    // The path of CRAS shutdown time file.
+    const SHUTDOWN_TIME_FILE: &str = "/var/lib/cras/stop";
+
+    /// Reads the unix time from CRAS shutdown time file.
+    pub fn from_file() -> Result<Duration> {
+        duration_from_file(&PathBuf::from(SHUTDOWN_TIME_FILE))
+    }
+}
+
+/// The utils to create and parse sound_card_init run time file.
+pub mod run_time {
+    use std::time::SystemTime;
+
+    use super::*;
+    // The filename of sound_card_init run time file.
+    const RUN_TIME_FILE: &str = "run";
+
+    /// Returns the sound_card_init run time file existence.
+    pub fn exists(snd_card: &str) -> bool {
+        run_time_file(snd_card).exists()
+    }
+
+    /// Reads the unix time from sound_card_init run time file.
+    pub fn from_file(snd_card: &str) -> Result<Duration> {
+        duration_from_file(&run_time_file(snd_card))
+    }
+
+    /// Saves the current unix time to sound_card_init run time file.
+    pub fn now_to_file(snd_card: &str) -> Result<()> {
+        match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
+            Ok(t) => to_file(snd_card, t),
+            Err(e) => Err(Error::SystemTimeError(e)),
+        }
+    }
+
+    /// Saves the unix time to sound_card_init run time file.
+    pub fn to_file(snd_card: &str, duration: Duration) -> Result<()> {
+        let path = run_time_file(snd_card);
+        let mut writer =
+            BufWriter::new(File::create(&path).map_err(|e| Error::FileIOFailed(path.clone(), e))?);
+        writer
+            .write_all(
+                serde_yaml::to_string(&duration)
+                    .map_err(|e| Error::SerdeError(path.clone(), e))?
+                    .as_bytes(),
+            )
+            .map_err(|e| Error::FileIOFailed(path.clone(), e))?;
+        writer
+            .flush()
+            .map_err(|e| Error::FileIOFailed(path.clone(), e))?;
+        Ok(())
+    }
+
+    fn run_time_file(snd_card: &str) -> PathBuf {
+        PathBuf::from(Datastore::DATASTORE_DIR)
+            .join(snd_card)
+            .join(RUN_TIME_FILE)
+    }
+}
diff --git a/sound_card_init/dsm/src/vpd.rs b/sound_card_init/dsm/src/vpd.rs
new file mode 100644
index 0000000..b00864c
--- /dev/null
+++ b/sound_card_init/dsm/src/vpd.rs
@@ -0,0 +1,41 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+use std::fs::File;
+use std::io::prelude::*;
+use std::io::BufReader;
+use std::path::PathBuf;
+
+use crate::error::{Error, Result};
+
+const VPD_DIR: &str = "/sys/firmware/vpd/ro/vpdfile";
+
+/// `VPD`, which represents the amplifier factory calibration values.
+#[derive(Default, Debug)]
+pub struct VPD {
+    pub dsm_calib_r0: i32,
+    pub dsm_calib_temp: i32,
+}
+
+impl VPD {
+    /// Creates a `VPD` and initializes its fields from VPD_DIR/dsm_calib_r0_{channel}.
+    /// # Arguments
+    ///
+    /// * `channel` - channel number.
+    pub fn new(channel: usize) -> Result<VPD> {
+        let mut vpd: VPD = Default::default();
+        vpd.dsm_calib_r0 = read_vpd_files(&format!("dsm_calib_r0_{}", channel))?;
+        vpd.dsm_calib_temp = read_vpd_files(&format!("dsm_calib_temp_{}", channel))?;
+        Ok(vpd)
+    }
+}
+
+fn read_vpd_files(file: &str) -> Result<i32> {
+    let path = PathBuf::from(VPD_DIR).with_file_name(file);
+    let io_err = |e| Error::FileIOFailed(path.to_owned(), e);
+    let mut reader = BufReader::new(File::open(&path).map_err(io_err)?);
+    let mut line = String::new();
+    reader.read_line(&mut line).map_err(io_err)?;
+    line.parse::<i32>()
+        .map_err(|e| Error::VPDParseFailed(path.to_string_lossy().to_string(), e))
+}
diff --git a/sound_card_init/dsm/src/zero_player.rs b/sound_card_init/dsm/src/zero_player.rs
new file mode 100644
index 0000000..441f7ff
--- /dev/null
+++ b/sound_card_init/dsm/src/zero_player.rs
@@ -0,0 +1,209 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+use std::io::Write;
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::sync::{Arc, Condvar, Mutex};
+use std::thread;
+use std::thread::JoinHandle;
+use std::time::Duration;
+
+use audio_streams::SampleFormat;
+use libcras::{CrasClient, CrasNodeType};
+use sys_util::error;
+
+use crate::error::{Error, Result};
+
+/// `ZeroPlayer` provides the functionality to play zeros sample in the background thread.
+#[derive(Default)]
+pub struct ZeroPlayer {
+    thread_info: Option<PlayZeroWorkerInfo>,
+}
+
+impl Drop for ZeroPlayer {
+    fn drop(&mut self) {
+        if self.thread_info.is_some() {
+            if let Err(e) = self.stop() {
+                error!("{}", e);
+            }
+        }
+    }
+}
+
+impl ZeroPlayer {
+    /// It takes about 400 ms to get CRAS_NODE_TYPE_INTERNAL_SPEAKER during the boot time.
+    const TIMEOUT: Duration = Duration::from_millis(1000);
+
+    /// Returns whether the ZeroPlayer is running.
+    pub fn running(&self) -> bool {
+        self.thread_info.is_some()
+    }
+
+    /// Starts to play zeros for at most `max_playback_time`.
+    /// This function blocks and returns until playback has started for `min_playback_time`.
+    /// This function must be called when self.running() returns false.
+    ///
+    /// # Arguments
+    ///
+    /// * `min_playback_time` - It blocks and returns until playback has started for
+    ///                         `min_playback_time`.
+    ///
+    /// # Errors
+    ///
+    /// * If it's called when the `ZeroPlayer` is already running.
+    /// * Failed to find internal speakers.
+    /// * Failed to start the background thread.
+    pub fn start(&mut self, min_playback_time: Duration) -> Result<()> {
+        if self.running() {
+            return Err(Error::ZeroPlayerIsRunning);
+        }
+        self.thread_info = Some(PlayZeroWorkerInfo::new(min_playback_time));
+        if let Some(thread_info) = &mut self.thread_info {
+            // Block until playback of zeros has started for min_playback_time or timeout.
+            let (lock, cvar) = &*(thread_info.ready);
+            let result = cvar.wait_timeout_while(
+                lock.lock()?,
+                min_playback_time + ZeroPlayer::TIMEOUT,
+                |&mut is_ready| !is_ready,
+            )?;
+            if result.1.timed_out() {
+                return Err(Error::StartPlaybackTimeout);
+            }
+        }
+        Ok(())
+    }
+
+    /// Stops playing zeros in the background thread.
+    /// This function must be called when self.running() returns true.
+    ///
+    /// # Errors
+    ///
+    /// * If it's called again when the `ZeroPlayer` is not running.
+    /// * Failed to play zeros to internal speakers via CRAS client.
+    /// * Failed to join the background thread.
+    pub fn stop(&mut self) -> Result<()> {
+        match self.thread_info.take() {
+            Some(mut thread_info) => Ok(thread_info.destroy()?),
+            None => Err(Error::ZeroPlayerIsNotRunning),
+        }
+    }
+}
+
+// Audio thread book-keeping data
+struct PlayZeroWorkerInfo {
+    thread: Option<JoinHandle<Result<()>>>,
+    // Uses `thread_run` to notify the background thread to stop.
+    thread_run: Arc<AtomicBool>,
+    // The background thread uses `ready` to notify the main thread that playback
+    // of zeros has started for min_playback_time.
+    ready: Arc<(Mutex<bool>, Condvar)>,
+}
+
+impl Drop for PlayZeroWorkerInfo {
+    fn drop(&mut self) {
+        if let Err(e) = self.destroy() {
+            error!("{}", e);
+        }
+    }
+}
+
+impl PlayZeroWorkerInfo {
+    // Spawns the PlayZeroWorker.
+    fn new(min_playback_time: Duration) -> Self {
+        let thread_run = Arc::new(AtomicBool::new(false));
+        let ready = Arc::new((Mutex::new(false), Condvar::new()));
+        let mut worker = PlayZeroWorker::new(min_playback_time, thread_run.clone(), ready.clone());
+        Self {
+            thread: Some(thread::spawn(move || -> Result<()> {
+                worker.run()?;
+                Ok(())
+            })),
+            thread_run,
+            ready,
+        }
+    }
+
+    // Joins the PlayZeroWorker.
+    fn destroy(&mut self) -> Result<()> {
+        self.thread_run.store(false, Ordering::Relaxed);
+        if let Some(handle) = self.thread.take() {
+            let res = handle.join().map_err(Error::WorkerPanics)?;
+            return match res {
+                Err(e) => Err(e),
+                Ok(_) => Ok(()),
+            };
+        }
+        Ok(())
+    }
+}
+
+struct PlayZeroWorker {
+    min_playback_time: Duration,
+    // Uses `thread_run` to notify the background thread to stop.
+    thread_run: Arc<AtomicBool>,
+    // The background thread uses `ready` to notify the main thread that playback
+    // of zeros has started for min_playback_time.
+    ready: Arc<(Mutex<bool>, Condvar)>,
+}
+
+impl PlayZeroWorker {
+    const FRAMES_PER_BUFFER: usize = 256;
+    const FRAME_RATE: u32 = 48000;
+    const NUM_CHANNELS: usize = 2;
+    const FORMAT: SampleFormat = SampleFormat::S16LE;
+
+    fn new(
+        min_playback_time: Duration,
+        thread_run: Arc<AtomicBool>,
+        ready: Arc<(Mutex<bool>, Condvar)>,
+    ) -> Self {
+        Self {
+            min_playback_time,
+            thread_run,
+            ready,
+        }
+    }
+
+    fn run(&mut self) -> Result<()> {
+        let mut cras_client = CrasClient::new().map_err(Error::CrasClientFailed)?;
+        // TODO(b/155007305): Implement cras_client.wait_node_change and use it here.
+        let node = cras_client
+            .output_nodes()
+            .find(|node| node.node_type == CrasNodeType::CRAS_NODE_TYPE_INTERNAL_SPEAKER)
+            .ok_or(Error::InternalSpeakerNotFound)?;
+        let local_buffer =
+            vec![0u8; Self::FRAMES_PER_BUFFER * Self::NUM_CHANNELS * Self::FORMAT.sample_bytes()];
+        let min_playback_iterations = (Self::FRAME_RATE
+            * self.min_playback_time.as_millis() as u32)
+            / Self::FRAMES_PER_BUFFER as u32
+            / 1000;
+        let (_control, mut stream) = cras_client
+            .new_pinned_playback_stream(
+                node.iodev_index,
+                Self::NUM_CHANNELS,
+                Self::FORMAT,
+                Self::FRAME_RATE,
+                Self::FRAMES_PER_BUFFER,
+            )
+            .map_err(|e| Error::NewPlayStreamFailed(e))?;
+
+        let mut iter = 0;
+        self.thread_run.store(true, Ordering::Relaxed);
+        while self.thread_run.load(Ordering::Relaxed) {
+            let mut buffer = stream
+                .next_playback_buffer()
+                .map_err(|e| Error::NextPlaybackBufferFailed(e))?;
+            let _write_frames = buffer.write(&local_buffer).map_err(Error::PlaybackFailed)?;
+
+            // Notifies the main thread that playback of zeros has started for min_playback_time.
+            if iter == min_playback_iterations {
+                let (lock, cvar) = &*self.ready;
+                let mut is_ready = lock.lock()?;
+                *is_ready = true;
+                cvar.notify_one();
+            }
+            iter += 1;
+        }
+        Ok(())
+    }
+}
diff --git a/sound_card_init/seccomp/sound_card_init-seccomp-amd64.policy b/sound_card_init/seccomp/sound_card_init-seccomp-amd64.policy
new file mode 100644
index 0000000..d06f225
--- /dev/null
+++ b/sound_card_init/seccomp/sound_card_init-seccomp-amd64.policy
@@ -0,0 +1,82 @@
+# Copyright 2020 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+access: 1
+arch_prctl: 1
+bind: 1
+brk: 1
+clone: 1
+close: 1
+connect: 1
+dup2: 1
+dup: 1
+epoll_create1: 1
+epoll_ctl: 1
+epoll_wait: 1
+execve: 1
+exit: 1
+exit_group: 1
+fcntl: 1
+fstat: 1
+futex: 1
+getcwd: 1
+getdents: 1
+getdents64: 1
+getegid: 1
+geteuid: 1
+getgid: 1
+getgroups: 1
+getpgid: 1
+getpgrp: 1
+getpid: 1
+getppid: 1
+getpriority: 1
+getrandom: 1
+getresgid: 1
+getresuid: 1
+getsid: 1
+getsockname: 1
+getuid: 1
+ioctl: arg1 == 0x5401 || arg1 == 0xc4c85512 || arg1 == 0x540f || arg1 == 0x80045500 || arg1 == 0xc4c85513 || arg1 == 0x81785501 || arg1 == 0x5413 || arg1 == 0xc1105511 || arg1 == 0x81785501 || arg1 == 0x80045500 || arg1 == 0xc008551a || arg1 == 0xc4c85512 || arg1 == 0xc008551b || arg1 == 0xc1105511
+lseek: 1
+madvise: 1
+mmap: arg2 in ~PROT_EXEC || arg2 in ~PROT_WRITE
+mprotect: arg2 in ~PROT_EXEC || arg2 in ~PROT_WRITE
+munmap: 1
+nanosleep: 1
+clock_nanosleep: 1
+openat: 1
+pipe2: 1
+ppoll: 1
+prctl: arg0 == 0x3 || arg0 == 0x4
+prlimit64: 1
+read: 1
+recvfrom: 1
+recvmsg: 1
+restart_syscall: 1
+rt_sigaction: 1
+rt_sigprocmask: 1
+rt_sigreturn: 1
+sched_getaffinity: 1
+sched_yield: 1
+sendmsg: 1
+sendto: 1
+set_robust_list: 1
+set_tid_address: 1
+setgid: 1
+setgroups: 1
+setpriority: 1
+setresgid: 1
+setresuid: 1
+setuid: 1
+sigaltstack: 1
+socket: arg0 == 0x10 || arg0 == 0x1
+socketpair: 1
+stat: 1
+statx: 1
+umask: 1
+uname: 1
+unlink: 1
+wait4: 1
+write: 1
diff --git a/sound_card_init/sound_card_init.conf b/sound_card_init/sound_card_init.conf
new file mode 100644
index 0000000..40bc88f
--- /dev/null
+++ b/sound_card_init/sound_card_init.conf
@@ -0,0 +1,80 @@
+# Copyright 2020 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Installed by sound_card_init package.
+# sound_card_init upstart job.
+# sound_card_init is started by /lib/udev/rules.d/99-sound_card_init.rules
+
+description     "Chrome OS sound card initializer"
+author          "chromium-os-dev@chromium.org"
+
+# sound_card_init is a short-running process, but we don't start it as
+# a task job, because sound_card_init needs the sound card to be ready in
+# CRAS therefore we do not want to block the udev rule processing.
+
+# Make the task killable, because if it has a leak it's better to
+# restart it than to OOM-panic.
+oom score 0
+
+# SOUND_CARD_ID is provided by /lib/udev/rules.d/99-sound_card_init.rules.
+import SOUND_CARD_ID
+
+pre-start script
+  if ! echo "${SOUND_CARD_ID}" | grep -Eq "^[a-zA-Z0-9]+$"; then
+    logger -t "${UPSTART_JOB}" \
+      "Invalid SOUND_CARD_ID supplied"
+    exit 1
+  else
+    mkdir -m 0755 -p /var/lib/sound_card_init/"${SOUND_CARD_ID}"
+    chown -R sound_card_init:sound_card_init /var/lib/sound_card_init
+  fi
+end script
+
+
+script
+  CONFIG="$(cros_config /audio/main sound-card-init-conf)"
+  if [ -f /etc/sound_card_init/"${CONFIG}" ]; then
+    # Here (in order) are a list of the args added:
+    # --uts: Create and enter new UTS namespace (hostname/NIS domain name).
+    # -e: doesn't need network access.
+    # -l: process doesn't use SysV shared memory or IPC.
+    # -N: doesn't need to modify control groups settings.
+    # -v: run inside a new VFS namespace.
+    # -p -r: process doesn't need to access other processes in the system.
+    # -n: process doesn't need new privileges.
+    # -P: set /mnt/empty as the root fs.
+    # -b: bind /
+    # -k: Get a writeable and empty /run tmpfs path.
+    # -b: need /run/cras to connect cras.
+    # -b: need /dev to send ioctls to the system's block devices.
+    # -k: empty /sys tmpfs path.
+    # -b: need /sys/firmware/vpd/ro/ access to read the default calibration
+    #     value in vpd.
+    # -k: get a writeable and empty /var tmpfs path.
+    # -b: need /var/lib/sound_card_init/$SOUND_CARD_ID writable access for
+    #     datastore update.
+    # -b: need /var/lib/cras readable
+    exec minijail0 \
+        --uts \
+        -e \
+        -l \
+        -N \
+        -v \
+        -p -r \
+        -n \
+        -P /mnt/empty \
+        -b / \
+        -k 'tmpfs,/run,tmpfs,MS_NODEV|MS_NOEXEC|MS_NOSUID,mode=755,size=10M' \
+        -b /run/cras \
+        -b /dev \
+        -k 'tmpfs,/sys,tmpfs,MS_NODEV|MS_NOEXEC|MS_NOSUID,mode=755,size=10M' \
+        -b /sys/firmware/vpd/ro/ \
+        -k 'tmpfs,/var,tmpfs,MS_NODEV|MS_NOEXEC|MS_NOSUID,mode=755,size=10M' \
+        -b /var/lib/sound_card_init/"${SOUND_CARD_ID}"/,,1 \
+        -b /var/lib/cras/ \
+        -u sound_card_init -g sound_card_init -G \
+        -S /usr/share/policy/sound_card_init-seccomp.policy \
+        /usr/bin/sound_card_init "--id=${SOUND_CARD_ID}" "--conf=${CONFIG}"
+  fi
+end script
\ No newline at end of file
diff --git a/sound_card_init/src/main.rs b/sound_card_init/src/main.rs
new file mode 100644
index 0000000..806b7d5
--- /dev/null
+++ b/sound_card_init/src/main.rs
@@ -0,0 +1,131 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//!  `sound_card_init` is an user space binary to perform sound card initialization during boot time.
+//!
+//!
+//!  # Arguments
+//!
+//!  * `sound_card_id` - The sound card name, ex: sofcmlmax98390d.
+//!
+//!  Given the `sound_card_id`, this binary parses the CONF_DIR/<sound_card_id>.yaml to perform per sound card initialization.
+//!  The upstart job of `sound_card_init` is started by the udev event specified in /lib/udev/rules.d/99-sound_card_init.rules.
+#![deny(missing_docs)]
+use std::env;
+use std::error;
+use std::fmt;
+use std::process;
+use std::string::String;
+
+use getopts::Options;
+use remain::sorted;
+use sys_util::{error, info, syslog};
+
+use amp::AmpBuilder;
+use dsm::utils::run_time;
+
+type Result<T> = std::result::Result<T, Error>;
+
+#[derive(Default)]
+struct Args {
+    pub sound_card_id: String,
+    pub conf: String,
+}
+
+#[sorted]
+#[derive(Debug)]
+enum Error {
+    MissingOption(String),
+    ParseArgsFailed(getopts::Fail),
+}
+
+impl error::Error for Error {}
+
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        use Error::*;
+        match self {
+            MissingOption(option) => write!(f, "missing required option: {}", option),
+            ParseArgsFailed(e) => write!(f, "parse_args failed: {}", e),
+        }
+    }
+}
+
+fn print_usage(opts: &Options) {
+    let brief = "Usage: sound_card_init [options]".to_owned();
+    print!("{}", opts.usage(&brief));
+}
+
+fn parse_args() -> Result<Args> {
+    let mut opts = Options::new();
+    opts.optopt("", "id", "sound card id", "ID");
+    opts.optopt(
+        "",
+        "conf",
+        "the config file name. It should be $(cros_config /audio/main sound-card-init-conf)",
+        "CONFIG_NAME",
+    );
+    opts.optflag("h", "help", "print help menu");
+    let matches = opts
+        .parse(&env::args().collect::<Vec<_>>()[1..])
+        .map_err(|e| {
+            print_usage(&opts);
+            Error::ParseArgsFailed(e)
+        })?;
+
+    if matches.opt_present("h") {
+        print_usage(&opts);
+        process::exit(0);
+    }
+
+    let sound_card_id = matches
+        .opt_str("id")
+        .ok_or_else(|| Error::MissingOption("id".to_owned()))
+        .map_err(|e| {
+            print_usage(&opts);
+            e
+        })?;
+
+    let conf = matches
+        .opt_str("conf")
+        .ok_or_else(|| Error::MissingOption("conf".to_owned()))
+        .map_err(|e| {
+            print_usage(&opts);
+            e
+        })?;
+
+    Ok(Args {
+        sound_card_id,
+        conf,
+    })
+}
+
+/// Parses the CONF_DIR/${args.conf}.yaml and starts the boot time calibration.
+fn sound_card_init(args: &Args) -> std::result::Result<(), Box<dyn error::Error>> {
+    info!("sound_card_id: {}, conf:{}", args.sound_card_id, args.conf);
+    AmpBuilder::new(&args.sound_card_id, &args.conf)
+        .build()?
+        .boot_time_calibration()?;
+
+    Ok(())
+}
+
+fn main() {
+    syslog::init().expect("failed to initialize syslog");
+    let args = match parse_args() {
+        Ok(args) => args,
+        Err(e) => {
+            error!("failed to parse arguments: {}", e);
+            return;
+        }
+    };
+
+    match sound_card_init(&args) {
+        Ok(_) => info!("sound_card_init finished successfully."),
+        Err(e) => error!("sound_card_init: {}", e),
+    }
+
+    if let Err(e) = run_time::now_to_file(&args.sound_card_id) {
+        error!("failed to create sound_card_init run time file: {}", e);
+    }
+}
diff --git a/ucm-config/bolt/HDA Intel PCH/HDA Intel PCH.conf b/ucm-config/bolt/HDA Intel PCH/HDA Intel PCH.conf
deleted file mode 100644
index 1cd5b42..0000000
--- a/ucm-config/bolt/HDA Intel PCH/HDA Intel PCH.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Bolt internal card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/bolt/HDA Intel PCH/HiFi.conf b/ucm-config/bolt/HDA Intel PCH/HiFi.conf
deleted file mode 100644
index 2ab99c5..0000000
--- a/ucm-config/bolt/HDA Intel PCH/HiFi.conf
+++ /dev/null
@@ -1,67 +0,0 @@
-SectionVerb {
-	EnableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Master Playback Switch' on"
-		cset "name='HP/Speaker Playback Switch' off"
-		cset "name='HP/Speaker Auto Detect Playback Switch' off"
-		cset "name='PlayEnhancement Playback Switch' on"
-		cset "name='Surround Playback Switch' on"
-		cset "name='Crystalizer Playback Switch' off"
-		cset "name='Dialog Plus Playback Switch' off"
-		cset "name='Smart Volume Playback Switch' on"
-		cset "name='X-Bass Playback Switch' on"
-		cset "name='Equalizer Playback Switch' off"
-		cset "name='Echo Cancellation Capture Switch' on"
-
-		cset "name='Capture Switch' on"
-		cset "name='Capture Volume' 99"
-		cset "name='CrystalVoice Capture Switch' off"
-		cset "name='Analog-Mic2 Capture Volume' 90"
-		cset "name='Analog-Mic2 Capture Switch' on"
-		cset "name='Mic1-Boost (30dB) Capture Switch' on"
-		cset "name='AMic1/DMic Capture Switch' off"
-		cset "name='AMic1/DMic Auto Detect Capture Switch' off"
-	]
-	DisableSequence [
-	]
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-		JackName "Front Headphone Jack"
-	}
-
-	EnableSequence [
-		cdev "hw:PCH"
-
-		cset "name='PlayEnhancement Playback Switch' off"
-		cset "name='HP/Speaker Playback Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:PCH"
-
-		cset "name='PlayEnhancement Playback Switch' on"
-		cset "name='HP/Speaker Playback Switch' off"
-	]
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		JackName "Mic Jack"
-	}
-
-	EnableSequence [
-		cdev "hw:PCH"
-
-		cset "name='CrystalVoice Capture Switch' off"
-		cset "name='AMic1/DMic Capture Switch' on"
-	]
-
-	DisableSequence [
-		cdev "hw:PCH"
-
-		cset "name='CrystalVoice Capture Switch' off"
-		cset "name='AMic1/DMic Capture Switch' off"
-	]
-}
diff --git a/ucm-config/chell b/ucm-config/chell
deleted file mode 120000
index 1ec1f47..0000000
--- a/ucm-config/chell
+++ /dev/null
@@ -1 +0,0 @@
-glados/
\ No newline at end of file
diff --git a/ucm-config/cid/HDA Intel PCH/HDA Intel PCH.conf b/ucm-config/cid/HDA Intel PCH/HDA Intel PCH.conf
deleted file mode 100644
index 63485af..0000000
--- a/ucm-config/cid/HDA Intel PCH/HDA Intel PCH.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Cid internal card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/cid/HDA Intel PCH/HiFi.conf b/ucm-config/cid/HDA Intel PCH/HiFi.conf
deleted file mode 100644
index 6174c0b..0000000
--- a/ucm-config/cid/HDA Intel PCH/HiFi.conf
+++ /dev/null
@@ -1,55 +0,0 @@
-SectionVerb {
-	Value {
-		OutputDspName "speaker_eq"
-	}
-	EnableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Master Playback Switch' on"
-		cset "name='Headphone Playback Switch' off"
-		cset "name='Speaker Playback Switch' on"
-
-		cset "name='Capture Switch' on"
-		cset "name='Capture Volume' 39"
-		cset "name='Mic Boost Volume' 2"
-		cset "name='Internal Mic Boost Volume' 1"
-		cset "name='Capture Source' 0"
-	]
-	DisableSequence [
-	]
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-		JackName "Headphone Jack"
-		DspName ""
-	}
-	EnableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Speaker Playback Switch' off"
-		cset "name='Headphone Playback Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Headphone Playback Switch' off"
-		cset "name='Speaker Playback Switch' on"
-	]
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		JackName "Mic Jack"
-	}
-	EnableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Capture Source' 1"
-	]
-	DisableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Capture Source' 0"
-	]
-}
diff --git a/ucm-config/daisy/DAISY-I2S-98090/DAISY-I2S-98090.conf b/ucm-config/daisy/DAISY-I2S-98090/DAISY-I2S-98090.conf
deleted file mode 100644
index 3ca0ff0..0000000
--- a/ucm-config/daisy/DAISY-I2S-98090/DAISY-I2S-98090.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Daisy internal card (Maxim 98090)"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/daisy/DAISY-I2S-98090/HiFi.conf b/ucm-config/daisy/DAISY-I2S-98090/HiFi.conf
deleted file mode 100644
index 54e92b3..0000000
--- a/ucm-config/daisy/DAISY-I2S-98090/HiFi.conf
+++ /dev/null
@@ -1,107 +0,0 @@
-SectionVerb {
-	Value {
-		OutputDspName "speaker_eq"
-	}
-	EnableSequence [
-		cdev "hw:DAISYI2S98090"
-		cset "name='Left Speaker Mixer Left DAC Switch' on"
-		cset "name='Right Speaker Mixer Right DAC Switch' on"
-		cset "name='Headphone Left Switch' on"
-		cset "name='Headphone Right Switch' on"
-		cset "name='Digital EQ 3 Band Switch' off"
-		cset "name='Digital EQ 5 Band Switch' off"
-		cset "name='Digital EQ 7 Band Switch' off"
-		cset "name='Biquad Switch' off"
-		cset "name='Filter Mode' Music"
-		cset "name='ADC Oversampling Rate' 0"
-
-		cset "name='MIC2 Mux' IN34"
-		cset "name='DMIC Mux' DMIC"
-		cset "name='Right ADC Mixer MIC2 Switch' off"
-		cset "name='Left ADC Mixer MIC2 Switch' off"
-
-		cset "name='ADCR Boost Volume' 4"
-		cset "name='ADCL Boost Volume' 4"
-		cset "name='ADCR Volume' 11"
-		cset "name='ADCL Volume' 11"
-
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-
-		cset "name='Speaker Left Mixer Volume' 2"
-		cset "name='Speaker Right Mixer Volume' 2"
-		cset "name='Record Path DC Blocking' on"
-		cset "name='Playback Path DC Blocking' on"
-
-	]
-	DisableSequence [
-	]
-}
-
-SectionDevice."HDMI".0 {
-	Value {
-		JackName "DAISY-I2S-98090 HDMI Jack"
-		DspName ""
-		EDIDFile "/sys/devices/platform/exynos-drm/drm/card1/card1-HDMI-A-1/edid"
-	}
-	EnableSequence [
-		cdev "hw:DAISYI2S98090"
-		cset "name='Left Speaker Mixer Left DAC Switch' off"
-		cset "name='Right Speaker Mixer Right DAC Switch' off"
-	]
-	DisableSequence [
-		cdev "hw:DAISYI2S98090"
-		cset "name='Left Speaker Mixer Left DAC Switch' on"
-		cset "name='Right Speaker Mixer Right DAC Switch' on"
-	]
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-		JackName "DAISY-I2S-98090 Headphone Jack"
-		DspName ""
-	}
-
-	EnableSequence [
-		cdev "hw:DAISYI2S98090"
-		cset "name='Left Speaker Mixer Left DAC Switch' off"
-		cset "name='Right Speaker Mixer Right DAC Switch' off"
-		cset "name='HP Left Out Switch' on"
-		cset "name='HP Right Out Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:DAISYI2S98090"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-		cset "name='Left Speaker Mixer Left DAC Switch' on"
-		cset "name='Right Speaker Mixer Right DAC Switch' on"
-	]
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		JackName "DAISY-I2S-98090 Mic Jack"
-		CaptureControl "MIC2"
-		DefaultNodeGain "-500"
-	}
-
-	EnableSequence [
-		cdev "hw:DAISYI2S98090"
-
-		cset "name='Right ADC Mixer MIC2 Switch' on"
-		cset "name='Left ADC Mixer MIC2 Switch' on"
-		cset "name='DMIC Mux' ADC"
-		cset "name='MIC2 Volume' 20"
-		cset "name='Record Path DC Blocking' on"
-	]
-
-	DisableSequence [
-		cdev "hw:DAISYI2S98090"
-
-		cset "name='DMIC Mux' DMIC"
-		cset "name='Right ADC Mixer MIC2 Switch' off"
-		cset "name='Left ADC Mixer MIC2 Switch' off"
-		cset "name='MIC2 Volume' 0"
-		cset "name='Record Path DC Blocking' off"
-	]
-}
diff --git a/ucm-config/daisy/DAISY-I2S/DAISY-I2S.conf b/ucm-config/daisy/DAISY-I2S/DAISY-I2S.conf
deleted file mode 100644
index 0f4b5fc..0000000
--- a/ucm-config/daisy/DAISY-I2S/DAISY-I2S.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Daisy internal card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/daisy/DAISY-I2S/HiFi.conf b/ucm-config/daisy/DAISY-I2S/HiFi.conf
deleted file mode 100644
index 9031816..0000000
--- a/ucm-config/daisy/DAISY-I2S/HiFi.conf
+++ /dev/null
@@ -1,142 +0,0 @@
-SectionVerb {
-	EnableSequence [
-		cdev "hw:DAISYI2S"
-
-		cset "name='Headphone Volume' 13"
-		cset "name='Speaker Volume' 16"
-		cset "name='Headphone Switch' off"
-		cset "name='Speaker Switch' on"
-		cset "name='Receiver Switch' on"
-		cset "name='Lineout Switch' on"
-		cset "name='MIC1 Volume' 20"
-		cset "name='MIC2 Volume' 20"
-		cset "name='MIC1 Boost Volume' 0"
-		cset "name='MIC2 Boost Volume' 1"
-		cset "name='Linein Volume' 5"
-		cset "name='ADCL Volume' 15"
-		cset "name='ADCR Volume' 15"
-		cset "name='ADCL Boost Volume' 0"
-		cset "name='ADCR Boost Volume' 0"
-		cset "name='EQ1 Mode' Default"
-		cset "name='EQ1 Switch' on"
-		cset "name='EQ2 Switch' off"
-		cset "name='Biquad1 Switch' off"
-		cset "name='Biquad2 Switch' off"
-		cset "name='DMIC1 Left Capture Switch' on"
-		cset "name='DMIC1 Right Capture Switch' on"
-		cset "name='MIC1 External Mic Switch' off"
-		cset "name='MIC2 External Mic Switch' on"
-		cset "name='DAI2 Filter Mode' Voice"
-		cset "name='DAI1 DAC Filter' Off"
-		cset "name='DAI1 Filter Mode' Music"
-		cset "name='DAI2 DAC Filter' Off"
-		cset "name='DAI3 DAC Filter' Off"
-		cset "name='Linein Mode' Stereo"
-		cset "name='Lineout Mode' Stereo"
-		cset "name='Right ADC Mixer MIC1 Switch' off"
-		cset "name='Right ADC Mixer MIC2 Switch' on"
-		cset "name='Right ADC Mixer IN1 Switch' off"
-		cset "name='Right ADC Mixer IN2 Switch' off"
-		cset "name='Left ADC Mixer MIC1 Switch' off"
-		cset "name='Left ADC Mixer MIC2 Switch' on"
-		cset "name='Left ADC Mixer IN1 Switch' off"
-		cset "name='Left ADC Mixer IN2 Switch' off"
-		cset "name='Right Lineout Mixer Left DAC1 Switch' off"
-		cset "name='Right Lineout Mixer Right DAC1 Switch' off"
-		cset "name='Right Lineout Mixer MIC1 Switch' off"
-		cset "name='Right Lineout Mixer MIC2 Switch' off"
-		cset "name='Right Lineout Mixer IN1 Switch' off"
-		cset "name='Right Lineout Mixer IN2 Switch' off"
-		cset "name='Left Lineout Mixer Left DAC1 Switch' off"
-		cset "name='Left Lineout Mixer Right DAC1 Switch' off"
-		cset "name='Left Lineout Mixer MIC1 Switch' off"
-		cset "name='Left Lineout Mixer MIC2 Switch' off"
-		cset "name='Left Lineout Mixer IN1 Switch' off"
-		cset "name='Left Lineout Mixer IN2 Switch' off"
-		cset "name='Receiver Mixer Left DAC1 Switch' off"
-		cset "name='Receiver Mixer Right DAC1 Switch' off"
-		cset "name='Receiver Mixer MIC1 Switch' off"
-		cset "name='Receiver Mixer MIC2 Switch' off"
-		cset "name='Receiver Mixer IN1 Switch' off"
-		cset "name='Receiver Mixer IN2 Switch' off"
-		cset "name='Right Speaker Mixer Left DAC1 Switch' off"
-		cset "name='Right Speaker Mixer Right DAC1 Switch' on"
-		cset "name='Right Speaker Mixer Mono DAC2 Switch' off"
-		cset "name='Right Speaker Mixer Mono DAC3 Switch' off"
-		cset "name='Left Speaker Mixer Left DAC1 Switch' on"
-		cset "name='Left Speaker Mixer Right DAC1 Switch' off"
-		cset "name='Left Speaker Mixer Mono DAC2 Switch' off"
-		cset "name='Left Speaker Mixer Mono DAC3 Switch' off"
-		cset "name='Right Headphone Mixer Left DAC1 Switch' off"
-		cset "name='Right Headphone Mixer Right DAC1 Switch' on"
-		cset "name='Right Headphone Mixer MIC1 Switch' off"
-		cset "name='Right Headphone Mixer MIC2 Switch' off"
-		cset "name='Right Headphone Mixer IN1 Switch' off"
-		cset "name='Right Headphone Mixer IN2 Switch' off"
-		cset "name='Left Headphone Mixer Left DAC1 Switch' on"
-		cset "name='Left Headphone Mixer Right DAC1 Switch' off"
-		cset "name='Left Headphone Mixer MIC1 Switch' off"
-		cset "name='Left Headphone Mixer MIC2 Switch' off"
-		cset "name='Left Headphone Mixer IN1 Switch' off"
-		cset "name='Left Headphone Mixer IN2 Switch' off"
-		cset "name='Linein Mux' INA"
-		cset "name='External MIC' MIC2"
-		cset "name='HDMI Playback Switch' off"
-	]
-	DisableSequence [
-	]
-}
-
-SectionDevice."HDMI".0 {
-	Value {
-		JackName "DAISY-I2S HDMI Jack"
-		EDIDFile "/sys/devices/platform/exynos-drm/drm/card1/card1-HDMI-A-1/edid"
-	}
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-		JackName "DAISY-I2S Headphone Jack"
-	}
-
-	EnableSequence [
-		cdev "hw:DAISYI2S"
-		cset "name='EQ1 Switch' off"
-		cset "name='Speaker Switch' off"
-		cset "name='Left Headphone Mixer Left DAC1 Switch' on"
-		cset "name='Right Headphone Mixer Right DAC1 Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:DAISYI2S"
-		cset "name='EQ1 Mode' Default"
-		cset "name='EQ1 Switch' on"
-		cset "name='Left Speaker Mixer Left DAC1 Switch' on"
-		cset "name='Right Speaker Mixer Right DAC1 Switch' on"
-	]
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		JackName "DAISY-I2S Mic Jack"
-		CaptureControl "MIC2"
-		DefaultNodeGain "-500"
-	}
-
-	EnableSequence [
-		cdev "hw:DAISYI2S"
-
-		cset "name='DMIC1 Left Capture Switch' off"
-		cset "name='DMIC1 Right Capture Switch' off"
-		cset "name='Left ADC Mixer MIC2 Switch' on"
-		cset "name='Right ADC Mixer MIC2 Switch' on"
-	]
-
-	DisableSequence [
-		cdev "hw:DAISYI2S"
-
-		cset "name='Left ADC Mixer MIC2 Switch' off"
-		cset "name='Right ADC Mixer MIC2 Switch' off"
-		cset "name='DMIC1 Left Capture Switch' on"
-		cset "name='DMIC1 Right Capture Switch' on"
-	]
-}
diff --git a/ucm-config/daisy_skate/DAISY-I2S/DAISY-I2S.conf b/ucm-config/daisy_skate/DAISY-I2S/DAISY-I2S.conf
deleted file mode 100644
index bca7d63..0000000
--- a/ucm-config/daisy_skate/DAISY-I2S/DAISY-I2S.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Skate internal card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/daisy_skate/DAISY-I2S/HiFi.conf b/ucm-config/daisy_skate/DAISY-I2S/HiFi.conf
deleted file mode 100644
index d2a913b..0000000
--- a/ucm-config/daisy_skate/DAISY-I2S/HiFi.conf
+++ /dev/null
@@ -1,70 +0,0 @@
-SectionVerb {
-	Value {
-		OutputDspName "speaker_eq"
-	}
-	EnableSequence [
-		cdev "hw:DAISYI2S"
-		cset "name='Left SPK Mixer Left DAC1 Switch' on"
-		cset "name='Right SPK Mixer Right DAC1 Switch' on"
-		cset "name='Left HP Mixer Left DAC1 Switch' on"
-		cset "name='Right HP Mixer Right DAC1 Switch' on"
-		cset "name='External MIC' MIC2"
-		cset "name='Left ADC Mixer MIC2 Switch' on"
-		cset "name='Right ADC Mixer MIC2 Switch' on"
-		cset "name='DAI1 Filter Mode' Music"
-		cset "name='DIGMICL Switch' on"
-		cset "name='DIGMICR Switch' on"
-	]
-	DisableSequence [
-	]
-}
-
-SectionDevice."HDMI".0 {
-	Value {
-		JackName "DAISY-I2S HDMI Jack"
-		DspName ""
-		EDIDFile "/sys/devices/platform/exynos-drm/drm/card1/card1-HDMI-A-1/edid"
-	}
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-		JackName "DAISY-I2S Headphone Jack"
-		DspName ""
-	}
-
-	EnableSequence [
-		cdev "hw:DAISYI2S"
-		cset "name='EQ1 Switch' off"
-	]
-	DisableSequence [
-		cdev "hw:DAISYI2S"
-		cset "name='EQ1 Mode' Default"
-		cset "name='EQ1 Switch' on"
-	]
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		JackName "DAISY-I2S Mic Jack"
-		CaptureControl "MIC2"
-	}
-
-	EnableSequence [
-		cdev "hw:DAISYI2S"
-
-		cset "name='Left ADC Mixer MIC2 Switch' on"
-		cset "name='Right ADC Mixer MIC2 Switch' on"
-		cset "name='DIGMICL Switch' off"
-		cset "name='DIGMICR Switch' off"
-	]
-
-	DisableSequence [
-		cdev "hw:DAISYI2S"
-
-		cset "name='Left ADC Mixer MIC2 Switch' off"
-		cset "name='Right ADC Mixer MIC2 Switch' off"
-		cset "name='DIGMICL Switch' on"
-		cset "name='DIGMICR Switch' on"
-	]
-}
diff --git a/ucm-config/daisy_spring/DAISY-I2S/DAISY-I2S.conf b/ucm-config/daisy_spring/DAISY-I2S/DAISY-I2S.conf
deleted file mode 100644
index 05389ee..0000000
--- a/ucm-config/daisy_spring/DAISY-I2S/DAISY-I2S.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Spring internal card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/daisy_spring/DAISY-I2S/HiFi.conf b/ucm-config/daisy_spring/DAISY-I2S/HiFi.conf
deleted file mode 100644
index af49a05..0000000
--- a/ucm-config/daisy_spring/DAISY-I2S/HiFi.conf
+++ /dev/null
@@ -1,90 +0,0 @@
-SectionVerb {
-	Value {
-		OutputDspName "speaker_eq"
-	}
-	EnableSequence [
-		cdev "hw:DAISYI2S"
-
-		cset "name='Headphone Volume' 0"
-		cset "name='Speaker Volume' 0"
-		cset "name='Receiver Volume' 0"
-		cset "name='Headphone Switch' off"
-		cset "name='Speaker Switch' on"
-		cset "name='Receiver Switch' on"
-		cset "name='MIC1 Volume' 31"
-		cset "name='MIC2 Volume' 32"
-		cset "name='MIC1 Boost Volume' 0"
-		cset "name='MIC2 Boost Volume' 0"
-		cset "name='INA Volume' 7"
-		cset "name='INB Volume' 7"
-		cset "name='ADCL Volume' 0"
-		cset "name='ADCR Volume' 0"
-		cset "name='ADCL Boost Volume' 0"
-		cset "name='ADCR Boost Volume' 0"
-		cset "name='DIGMICR Switch' on"
-		cset "name='DIGMICL Switch' on"
-		cset "name='EQ1 Switch' off"
-		cset "name='EQ2 Switch' off"
-		cset "name='EX Limiter Mode' off"
-		cset "name='EX Limiter Threshold' '0.6'"
-		cset "name='DAI1 Filter Mode' Music"
-		cset "name='DAI1 DAC Filter' off"
-		cset "name='DAI1 ADC Filter' 1"
-		cset "name='DAI2 DC Block Switch' off"
-		cset "name='Right ADC Mixer MIC2 Switch' on"
-		cset "name='Left ADC Mixer MIC2 Switch' on"
-		cset "name='Right SPK Mixer Right DAC1 Switch' on"
-		cset "name='Left SPK Mixer Left DAC1 Switch' on"
-		cset "name='Right HP Mixer Right DAC1 Switch' on"
-		cset "name='Left HP Mixer Left DAC1 Switch' on"
-		cset "name='External MIC' MIC2"
-		cset "name='HDMI Playback Switch' off"
-	]
-	DisableSequence [
-	]
-}
-
-SectionDevice."HDMI".0 {
-	Value {
-		JackName "DAISY-I2S HDMI Jack"
-		DspName ""
-		EDIDFile "/sys/devices/platform/exynos-drm/drm/card1/card1-HDMI-A-1/edid"
-	}
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-		JackName "DAISY-I2S Headphone Jack"
-		DspName ""
-	}
-
-	EnableSequence [
-	]
-	DisableSequence [
-	]
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		JackName "DAISY-I2S Mic Jack"
-		CaptureControl "MIC2"
-	}
-
-	EnableSequence [
-		cdev "hw:DAISYI2S"
-
-		cset "name='Left ADC Mixer MIC2 Switch' on"
-		cset "name='Right ADC Mixer MIC2 Switch' on"
-		cset "name='DIGMICL Switch' off"
-		cset "name='DIGMICR Switch' off"
-	]
-
-	DisableSequence [
-		cdev "hw:DAISYI2S"
-
-		cset "name='Left ADC Mixer MIC2 Switch' off"
-		cset "name='Right ADC Mixer MIC2 Switch' off"
-		cset "name='DIGMICL Switch' on"
-		cset "name='DIGMICR Switch' on"
-	]
-}
diff --git a/ucm-config/falco/HDA Intel PCH/HDA Intel PCH.conf b/ucm-config/falco/HDA Intel PCH/HDA Intel PCH.conf
deleted file mode 100644
index 45c2983..0000000
--- a/ucm-config/falco/HDA Intel PCH/HDA Intel PCH.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Falco internal card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/falco/HDA Intel PCH/HiFi.conf b/ucm-config/falco/HDA Intel PCH/HiFi.conf
deleted file mode 100644
index 03f431e..0000000
--- a/ucm-config/falco/HDA Intel PCH/HiFi.conf
+++ /dev/null
@@ -1,55 +0,0 @@
-SectionVerb {
-	Value {
-		OutputDspName "speaker_eq"
-	}
-	EnableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Master Playback Switch' on"
-		cset "name='Headphone Playback Switch' off"
-		cset "name='Speaker Playback Switch' on"
-
-		cset "name='Capture Switch' on"
-		cset "name='Capture Volume' 39"
-		cset "name='Mic Boost Volume' 2"
-		cset "name='Internal Mic Boost Volume' 0"
-		cset "name='Capture Source' 0"
-	]
-	DisableSequence [
-	]
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-		JackName "Headphone Jack"
-		DspName ""
-	}
-	EnableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Speaker Playback Switch' off"
-		cset "name='Headphone Playback Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Headphone Playback Switch' off"
-		cset "name='Speaker Playback Switch' on"
-	]
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		JackName "Mic Jack"
-	}
-	EnableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Capture Source' 1"
-	]
-	DisableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Capture Source' 0"
-	]
-}
diff --git a/ucm-config/stout/HDA Intel PCH/HDA Intel PCH.conf b/ucm-config/for_all_boards/C505 HD Webcam/C505 HD Webcam.conf
similarity index 66%
copy from ucm-config/stout/HDA Intel PCH/HDA Intel PCH.conf
copy to ucm-config/for_all_boards/C505 HD Webcam/C505 HD Webcam.conf
index 2751b94..126ac33 100644
--- a/ucm-config/stout/HDA Intel PCH/HDA Intel PCH.conf
+++ b/ucm-config/for_all_boards/C505 HD Webcam/C505 HD Webcam.conf
@@ -1,4 +1,4 @@
-Comment "Stout internal card"
+Comment "Logitech Webcam C505"
 
 SectionUseCase."HiFi" {
 	File "HiFi.conf"
diff --git a/ucm-config/for_all_boards/C505 HD Webcam/HiFi.conf b/ucm-config/for_all_boards/C505 HD Webcam/HiFi.conf
new file mode 100644
index 0000000..c7e1342
--- /dev/null
+++ b/ucm-config/for_all_boards/C505 HD Webcam/HiFi.conf
@@ -0,0 +1,25 @@
+SectionVerb {
+	Value {
+		FullySpecifiedUCM "1"
+	}
+
+	EnableSequence [
+		cdev "hw:Webcam"
+		cset "name='Mic Capture Volume' 0"
+	]
+
+	DisableSequence [
+	]
+}
+
+SectionDevice."Webcam" {
+	Value {
+		CapturePCM "hw:Webcam,0"
+	}
+
+	EnableSequence [
+	]
+
+	DisableSequence [
+	]
+}
diff --git a/ucm-config/for_all_boards/Chat 150 C/HiFi.conf b/ucm-config/for_all_boards/Chat 150 C/HiFi.conf
index 5a73702..368796d 100644
--- a/ucm-config/for_all_boards/Chat 150 C/HiFi.conf
+++ b/ucm-config/for_all_boards/Chat 150 C/HiFi.conf
@@ -10,7 +10,7 @@
 	]
 }
 
-SectionDevice."Dummy".0 {
+SectionDevice."Chat 150 C".0 {
 	EnableSequence [
 	]
 
diff --git a/ucm-config/for_all_boards/DELL PROFESSIONAL SOUND BAR AE5/DELL PROFESSIONAL SOUND BAR AE5.conf b/ucm-config/for_all_boards/DELL PROFESSIONAL SOUND BAR AE5/DELL PROFESSIONAL SOUND BAR AE5.conf
new file mode 100644
index 0000000..e34c813
--- /dev/null
+++ b/ucm-config/for_all_boards/DELL PROFESSIONAL SOUND BAR AE5/DELL PROFESSIONAL SOUND BAR AE5.conf
@@ -0,0 +1,6 @@
+Comment "DELL PROFESSIONAL SOUND BAR AE5"
+
+SectionUseCase."HiFi" {
+	File "HiFi.conf"
+	Comment "Default"
+}
diff --git a/ucm-config/for_all_boards/DELL PROFESSIONAL SOUND BAR AE5/HiFi.conf b/ucm-config/for_all_boards/DELL PROFESSIONAL SOUND BAR AE5/HiFi.conf
new file mode 100644
index 0000000..67bd2d5
--- /dev/null
+++ b/ucm-config/for_all_boards/DELL PROFESSIONAL SOUND BAR AE5/HiFi.conf
@@ -0,0 +1,27 @@
+SectionVerb {
+	Value {
+		FullySpecifiedUCM "1"
+	}
+
+	EnableSequence [
+	]
+
+	DisableSequence [
+	]
+}
+
+SectionDevice."Dell AE515 USB SoundBar Output".0 {
+	Comment "SoundBar Output"
+
+	Value {
+		PlaybackPCM "hw:AE5,0"
+	}
+}
+
+SectionDevice."Dell AE515 USB SoundBar Input".0 {
+	Comment "SoundBar Input"
+
+	Value {
+		CapturePCM "hw:AE5,0"
+	}
+}
diff --git a/ucm-config/stout/HDA Intel PCH/HDA Intel PCH.conf b/ucm-config/for_all_boards/Dell AC511 USB SoundBar/Dell AC511 USB SoundBar.conf
similarity index 64%
copy from ucm-config/stout/HDA Intel PCH/HDA Intel PCH.conf
copy to ucm-config/for_all_boards/Dell AC511 USB SoundBar/Dell AC511 USB SoundBar.conf
index 2751b94..5491378 100644
--- a/ucm-config/stout/HDA Intel PCH/HDA Intel PCH.conf
+++ b/ucm-config/for_all_boards/Dell AC511 USB SoundBar/Dell AC511 USB SoundBar.conf
@@ -1,4 +1,4 @@
-Comment "Stout internal card"
+Comment "Dell AC511 USB SoundBar"
 
 SectionUseCase."HiFi" {
 	File "HiFi.conf"
diff --git a/ucm-config/for_all_boards/Dell AC511 USB SoundBar/HiFi.conf b/ucm-config/for_all_boards/Dell AC511 USB SoundBar/HiFi.conf
new file mode 100644
index 0000000..e920e3c
--- /dev/null
+++ b/ucm-config/for_all_boards/Dell AC511 USB SoundBar/HiFi.conf
@@ -0,0 +1,30 @@
+SectionVerb {
+	Value {
+		FullySpecifiedUCM "1"
+	}
+
+	EnableSequence [
+		cdev "hw:SoundBar"
+
+		cset "name='PCM Playback Volume' 51"
+	]
+
+	DisableSequence [
+	]
+}
+
+SectionDevice."Dell AC511 USB SoundBar Output".0 {
+	Comment "SoundBar Output"
+
+	Value {
+		PlaybackPCM "hw:SoundBar,0"
+	}
+}
+
+SectionDevice."Dell AC511 USB SoundBar Input".0 {
+	Comment "SoundBar Input"
+
+	Value {
+		CapturePCM "hw:SoundBar,0"
+	}
+}
diff --git a/ucm-config/for_all_boards/Dell-WD15-Dock/Dell-WD15-Dock.conf b/ucm-config/for_all_boards/Dell-WD15-Dock/Dell-WD15-Dock.conf
new file mode 100644
index 0000000..290758a
--- /dev/null
+++ b/ucm-config/for_all_boards/Dell-WD15-Dock/Dell-WD15-Dock.conf
@@ -0,0 +1,5 @@
+Comment "USB-audio on Dell docking station"
+SectionUseCase."HiFi" {
+	File "HiFi.conf"
+	Comment "Default"
+}
diff --git a/ucm-config/for_all_boards/Dell-WD15-Dock/HiFi.conf b/ucm-config/for_all_boards/Dell-WD15-Dock/HiFi.conf
new file mode 100644
index 0000000..c522540
--- /dev/null
+++ b/ucm-config/for_all_boards/Dell-WD15-Dock/HiFi.conf
@@ -0,0 +1,40 @@
+SectionVerb {
+	Value {
+		FullySpecifiedUCM "1"
+	}
+	EnableSequence [
+		cdev "hw:Dock"
+	]
+	DisableSequence [
+	]
+}
+
+SectionDevice."Dock Headphone".0 {
+	Comment "Headphone"
+
+	Value {
+		PlaybackPCM "hw:Dock,0"
+	}
+}
+
+SectionDevice."Dock Line Out".0 {
+	Value {
+		PlaybackPCM "hw:Dock,1"
+	}
+	EnableSequence [
+		cdev "hw:Dock"
+		cset "name='Line Playback Switch' on"
+	]
+	DisableSequence [
+		cdev "hw:Dock"
+		cset "name='Line Playback Switch' off"
+	]
+}
+
+SectionDevice."Dock Microphone".0 {
+	Comment "Microphone"
+
+	Value {
+		CapturePCM "hw:Dock,0"
+	}
+}
diff --git a/ucm-config/for_all_boards/HD Pro Webcam C920/HD Pro Webcam C920.conf b/ucm-config/for_all_boards/HD Pro Webcam C920/HD Pro Webcam C920.conf
deleted file mode 100644
index e7394ba..0000000
--- a/ucm-config/for_all_boards/HD Pro Webcam C920/HD Pro Webcam C920.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "HD Pro Webcam C920"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/for_all_boards/HD Pro Webcam C920/HiFi.conf b/ucm-config/for_all_boards/HD Pro Webcam C920/HiFi.conf
deleted file mode 100644
index e7d3723..0000000
--- a/ucm-config/for_all_boards/HD Pro Webcam C920/HiFi.conf
+++ /dev/null
@@ -1,28 +0,0 @@
-SectionVerb {
-	Value {
-		FullySpecifiedUCM "1"
-	}
-
-	EnableSequence [
-		cdev "hw:C920"
-
-		cset "name='Mic Capture Volume' 5"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionDevice."Mic" {
-	Value {
-		CapturePCM "hw:HD Pro Webcam C920,0"
-		MaxSoftwareGain "2000"
-		JackType "always"
-	}
-
-	EnableSequence [
-	]
-
-	DisableSequence [
-	]
-}
diff --git a/ucm-config/stout/HDA Intel PCH/HDA Intel PCH.conf b/ucm-config/for_all_boards/HUAWEI USB-C HEADSET/HUAWEI USB-C HEADSET.conf
similarity index 66%
copy from ucm-config/stout/HDA Intel PCH/HDA Intel PCH.conf
copy to ucm-config/for_all_boards/HUAWEI USB-C HEADSET/HUAWEI USB-C HEADSET.conf
index 2751b94..c1db519 100644
--- a/ucm-config/stout/HDA Intel PCH/HDA Intel PCH.conf
+++ b/ucm-config/for_all_boards/HUAWEI USB-C HEADSET/HUAWEI USB-C HEADSET.conf
@@ -1,4 +1,4 @@
-Comment "Stout internal card"
+Comment "HUAWEI USB-C HEADSET"
 
 SectionUseCase."HiFi" {
 	File "HiFi.conf"
diff --git a/ucm-config/for_all_boards/HUAWEI USB-C HEADSET/HiFi.conf b/ucm-config/for_all_boards/HUAWEI USB-C HEADSET/HiFi.conf
new file mode 100644
index 0000000..d48942b
--- /dev/null
+++ b/ucm-config/for_all_boards/HUAWEI USB-C HEADSET/HiFi.conf
@@ -0,0 +1,25 @@
+SectionVerb {
+	Value {
+		FullySpecifiedUCM "1"
+	}
+
+	EnableSequence [
+		cdev "hw:HEADSET"
+		cset "name='PCM Playback Volume' 45"
+	]
+
+	DisableSequence [
+	]
+}
+
+SectionDevice."HUAWEI USB-C HEADSET Output".0 {
+	Value {
+		PlaybackPCM "hw:HEADSET,0"
+	}
+}
+
+SectionDevice."HUAWEI USB-C HEADSET Input".0 {
+	Value {
+		CapturePCM "hw:HEADSET,0"
+	}
+}
diff --git a/ucm-config/for_all_boards/ICUSBAUDIO7D/HiFi.conf b/ucm-config/for_all_boards/ICUSBAUDIO7D/HiFi.conf
new file mode 100644
index 0000000..c44de8d
--- /dev/null
+++ b/ucm-config/for_all_boards/ICUSBAUDIO7D/HiFi.conf
@@ -0,0 +1,132 @@
+SectionVerb {
+	Value {
+		FullySpecifiedUCM "1"
+	}
+
+	EnableSequence [
+		cdev "hw:ICUSBAUDIO7D"
+
+		cset "name='Line Capture Switch', off"
+		cset "name='Mic Capture Switch', off"
+		cset "name='IEC958 In Capture Switch', off"
+		cset "name='PCM Capture Switch', off"
+		cset "name='Speaker Playback Switch', off"
+		cset "name='Mic Playback Switch', off"
+	]
+
+	DisableSequence [
+	]
+}
+
+SectionDevice."Speaker-Headset".0 {
+	Comment "Speaker out"
+
+	Value {
+		PlaybackPCM "hw:ICUSBAUDIO7D,0"
+		PlaybackMixerElem "Speaker"
+	}
+
+	EnableSequence [
+		cset "name='Speaker Playback Switch', on"
+	]
+
+	DisableSequence [
+		cset "name='Speaker Playback Switch', off"
+	]
+}
+
+SectionDevice."Line In".0 {
+	Comment "Line In"
+
+	Value {
+		CapturePCM "hw:ICUSBAUDIO7D,0"
+		CaptureMixerElem "Line"
+	}
+
+	ConflictingDevice [
+		"Mic"
+		"SPDIF In"
+		"PCM"
+	]
+
+	EnableSequence [
+		cset "name='Line Capture Switch', on"
+		cset "name='PCM Capture Source', Line"
+	]
+
+	DisableSequence [
+		cset "name='Line Capture Switch', off"
+	]
+}
+
+SectionDevice."Mic".0 {
+	Comment "Mic Input"
+
+	Value {
+		CapturePCM "hw:ICUSBAUDIO7D,0"
+		CaptureMixerElem "Mic"
+	}
+
+	ConflictingDevice [
+		"Line In"
+		"SPDIF In"
+		"PCM"
+	]
+
+	EnableSequence [
+		cset "name='Mic Capture Switch', on"
+		cset "name='PCM Capture Source', Mic"
+	]
+
+	DisableSequence [
+		cset "name='Mic Capture Switch', off"
+	]
+}
+
+SectionDevice."SPDIF In".0 {
+	Comment "S/PDIF In"
+
+	Value {
+		CapturePCM "hw:ICUSBAUDIO7D,0"
+		CaptureMixerElem "IEC958 In"
+	}
+
+	ConflictingDevice [
+		"Line In"
+		"Mic"
+		"PCM"
+	]
+
+	EnableSequence [
+		cset "name='IEC958 In Capture Switch', on"
+		cset "name='PCM Capture Source', IEC958 In"
+	]
+
+	DisableSequence [
+		cset "name='IEC958 In Capture Switch', off"
+	]
+}
+
+SectionDevice."PCM".0 {
+	Comment "PCM Capture"
+
+	Value {
+		CapturePCM "hw:ICUSBAUDIO7D,0"
+		CaptureMixerElem "PCM"
+	}
+
+	ConflictingDevice [
+		"Line In"
+		"Mic"
+		"SPDIF In"
+	]
+
+	EnableSequence [
+		cset "name='PCM Capture Switch', on"
+		cset "name='PCM Capture Source', Mixer"
+	]
+
+	DisableSequence [
+		cset "name='PCM Capture Switch', off"
+	]
+}
diff --git a/ucm-config/stout/HDA Intel PCH/HDA Intel PCH.conf b/ucm-config/for_all_boards/ICUSBAUDIO7D/ICUSBAUDIO7D.conf
similarity index 66%
copy from ucm-config/stout/HDA Intel PCH/HDA Intel PCH.conf
copy to ucm-config/for_all_boards/ICUSBAUDIO7D/ICUSBAUDIO7D.conf
index 2751b94..3dc10fa 100644
--- a/ucm-config/stout/HDA Intel PCH/HDA Intel PCH.conf
+++ b/ucm-config/for_all_boards/ICUSBAUDIO7D/ICUSBAUDIO7D.conf
@@ -1,4 +1,4 @@
-Comment "Stout internal card"
+Comment "Startech USB 7D Audio"
 
 SectionUseCase."HiFi" {
 	File "HiFi.conf"
diff --git a/ucm-config/for_all_boards/Jabra SPEAK 810/HiFi.conf b/ucm-config/for_all_boards/Jabra SPEAK 810/HiFi.conf
index 5a73702..313bffe 100644
--- a/ucm-config/for_all_boards/Jabra SPEAK 810/HiFi.conf
+++ b/ucm-config/for_all_boards/Jabra SPEAK 810/HiFi.conf
@@ -10,7 +10,7 @@
 	]
 }
 
-SectionDevice."Dummy".0 {
+SectionDevice."Jabra Speak 810".0 {
 	EnableSequence [
 	]
 
diff --git a/ucm-config/for_all_boards/Logitech BRIO/HiFi.conf b/ucm-config/for_all_boards/Logitech BRIO/HiFi.conf
new file mode 100644
index 0000000..eb20ee2
--- /dev/null
+++ b/ucm-config/for_all_boards/Logitech BRIO/HiFi.conf
@@ -0,0 +1,25 @@
+SectionVerb {
+	Value {
+		FullySpecifiedUCM "1"
+	}
+
+	EnableSequence [
+		cdev "hw:BRIO"
+		cset "name='Mic Capture Volume' 24"
+	]
+
+	DisableSequence [
+	]
+}
+
+SectionDevice."BRIO" {
+	Value {
+		CapturePCM "hw:BRIO,0"
+	}
+
+	EnableSequence [
+	]
+
+	DisableSequence [
+	]
+}
diff --git a/ucm-config/veyron/ROCKCHIP-I2S/ROCKCHIP-I2S.conf b/ucm-config/for_all_boards/Logitech BRIO/Logitech BRIO.conf
similarity index 72%
copy from ucm-config/veyron/ROCKCHIP-I2S/ROCKCHIP-I2S.conf
copy to ucm-config/for_all_boards/Logitech BRIO/Logitech BRIO.conf
index 0c399b0..7059eda 100644
--- a/ucm-config/veyron/ROCKCHIP-I2S/ROCKCHIP-I2S.conf
+++ b/ucm-config/for_all_boards/Logitech BRIO/Logitech BRIO.conf
@@ -1,4 +1,4 @@
-Comment "Rockchip card"
+Comment "Logitech BRIO"
 
 SectionUseCase."HiFi" {
 	File "HiFi.conf"
diff --git a/ucm-config/for_all_boards/Logitech Webcam C930e/HiFi.conf b/ucm-config/for_all_boards/Logitech Webcam C930e/HiFi.conf
new file mode 100644
index 0000000..95f2fcf
--- /dev/null
+++ b/ucm-config/for_all_boards/Logitech Webcam C930e/HiFi.conf
@@ -0,0 +1,25 @@
+SectionVerb {
+	Value {
+		FullySpecifiedUCM "1"
+	}
+
+	EnableSequence [
+		cdev "hw:C930e"
+		cset "name='Mic Capture Volume' 30"
+	]
+
+	DisableSequence [
+	]
+}
+
+SectionDevice."C930e" {
+	Value {
+		CapturePCM "hw:C930e,0"
+	}
+
+	EnableSequence [
+	]
+
+	DisableSequence [
+	]
+}
diff --git a/ucm-config/stout/HDA Intel PCH/HDA Intel PCH.conf b/ucm-config/for_all_boards/Logitech Webcam C930e/Logitech Webcam C930e.conf
similarity index 66%
copy from ucm-config/stout/HDA Intel PCH/HDA Intel PCH.conf
copy to ucm-config/for_all_boards/Logitech Webcam C930e/Logitech Webcam C930e.conf
index 2751b94..f6760e2 100644
--- a/ucm-config/stout/HDA Intel PCH/HDA Intel PCH.conf
+++ b/ucm-config/for_all_boards/Logitech Webcam C930e/Logitech Webcam C930e.conf
@@ -1,4 +1,4 @@
-Comment "Stout internal card"
+Comment "Logitech Webcam C930e"
 
 SectionUseCase."HiFi" {
 	File "HiFi.conf"
diff --git a/ucm-config/for_all_boards/Loopback/HiFi.conf b/ucm-config/for_all_boards/Loopback/HiFi.conf
new file mode 100644
index 0000000..254c995
--- /dev/null
+++ b/ucm-config/for_all_boards/Loopback/HiFi.conf
@@ -0,0 +1,25 @@
+SectionVerb {
+	Value {
+		FullySpecifiedUCM "1"
+	}
+
+	EnableSequence [
+	]
+
+	DisableSequence [
+	]
+}
+
+SectionDevice."Loopback Playback".0 {
+	Value {
+		PlaybackPCM "hw:Loopback,0"
+		PlaybackChannels "8"
+	}
+}
+
+SectionDevice."Loopback Capture".0 {
+	Value {
+		CapturePCM "hw:Loopback,1"
+		CaptureChannels "8"
+	}
+}
diff --git a/ucm-config/veyron/ROCKCHIP-I2S/ROCKCHIP-I2S.conf b/ucm-config/for_all_boards/Loopback/Loopback.conf
similarity index 72%
rename from ucm-config/veyron/ROCKCHIP-I2S/ROCKCHIP-I2S.conf
rename to ucm-config/for_all_boards/Loopback/Loopback.conf
index 0c399b0..44abedd 100644
--- a/ucm-config/veyron/ROCKCHIP-I2S/ROCKCHIP-I2S.conf
+++ b/ucm-config/for_all_boards/Loopback/Loopback.conf
@@ -1,4 +1,4 @@
-Comment "Rockchip card"
+Comment "Loopback"
 
 SectionUseCase."HiFi" {
 	File "HiFi.conf"
diff --git a/ucm-config/for_all_boards/Mi Dual Driver Earphones Type-C/HiFi.conf b/ucm-config/for_all_boards/Mi Dual Driver Earphones Type-C/HiFi.conf
new file mode 100644
index 0000000..9d63e16
--- /dev/null
+++ b/ucm-config/for_all_boards/Mi Dual Driver Earphones Type-C/HiFi.conf
@@ -0,0 +1,25 @@
+SectionVerb {
+	Value {
+		FullySpecifiedUCM "1"
+	}
+
+	EnableSequence [
+		cdev "hw:TypeC"
+		cset "name='PCM Playback Volume' 45"
+	]
+
+	DisableSequence [
+	]
+}
+
+SectionDevice."Mi Earphones Output".0 {
+	Value {
+		PlaybackPCM "hw:TypeC,0"
+	}
+}
+
+SectionDevice."Mi Earphones Input".0 {
+	Value {
+		CapturePCM "hw:TypeC,0"
+	}
+}
diff --git a/ucm-config/for_all_boards/Mi Dual Driver Earphones Type-C/Mi Dual Driver Earphones Type-C.conf b/ucm-config/for_all_boards/Mi Dual Driver Earphones Type-C/Mi Dual Driver Earphones Type-C.conf
new file mode 100644
index 0000000..df19b47
--- /dev/null
+++ b/ucm-config/for_all_boards/Mi Dual Driver Earphones Type-C/Mi Dual Driver Earphones Type-C.conf
@@ -0,0 +1,6 @@
+Comment "Mi Dual Driver Earphones Type-C"
+
+SectionUseCase."HiFi" {
+	File "HiFi.conf"
+	Comment "Default"
+}
diff --git a/ucm-config/for_all_boards/PCP-USB/HiFi.conf b/ucm-config/for_all_boards/PCP-USB/HiFi.conf
new file mode 100644
index 0000000..ac5c7ed
--- /dev/null
+++ b/ucm-config/for_all_boards/PCP-USB/HiFi.conf
@@ -0,0 +1,20 @@
+SectionVerb {
+	Value {
+		FullySpecifiedUCM "1"
+	}
+
+	EnableSequence [
+		cdev "hw:PCPUSB"
+
+		cset "name='Mic Capture Volume' 19"
+	]
+
+	DisableSequence [
+	]
+}
+
+SectionDevice."PCP USB Input".0 {
+	Value {
+		CapturePCM "hw:PCPUSB,0"
+	}
+}
diff --git a/ucm-config/stout/HDA Intel PCH/HDA Intel PCH.conf b/ucm-config/for_all_boards/PCP-USB/PCP-USB.conf
similarity index 67%
rename from ucm-config/stout/HDA Intel PCH/HDA Intel PCH.conf
rename to ucm-config/for_all_boards/PCP-USB/PCP-USB.conf
index 2751b94..b468927 100644
--- a/ucm-config/stout/HDA Intel PCH/HDA Intel PCH.conf
+++ b/ucm-config/for_all_boards/PCP-USB/PCP-USB.conf
@@ -1,4 +1,4 @@
-Comment "Stout internal card"
+Comment "PCP USB Stethoscope"
 
 SectionUseCase."HiFi" {
 	File "HiFi.conf"
diff --git a/ucm-config/for_all_boards/Pixel USB-C earbuds/HiFi.conf b/ucm-config/for_all_boards/Pixel USB-C earbuds/HiFi.conf
new file mode 100644
index 0000000..7a60745
--- /dev/null
+++ b/ucm-config/for_all_boards/Pixel USB-C earbuds/HiFi.conf
@@ -0,0 +1,24 @@
+SectionVerb {
+	Value {
+		FullySpecifiedUCM "1"
+	}
+
+	EnableSequence [
+	]
+
+	DisableSequence [
+	]
+}
+
+SectionDevice."Google Pixel earbuds Output".0 {
+	Value {
+		PlaybackPCM "hw:earbuds,0"
+	}
+}
+
+SectionDevice."Google Pixel earbuds Input".0 {
+	Value {
+		CapturePCM "hw:earbuds,0"
+		IntrinsicSensitivity "-3100"
+	}
+}
diff --git a/ucm-config/stout/HDA Intel PCH/HDA Intel PCH.conf b/ucm-config/for_all_boards/Pixel USB-C earbuds/Pixel USB-C earbuds.conf
similarity index 67%
copy from ucm-config/stout/HDA Intel PCH/HDA Intel PCH.conf
copy to ucm-config/for_all_boards/Pixel USB-C earbuds/Pixel USB-C earbuds.conf
index 2751b94..4166632 100644
--- a/ucm-config/stout/HDA Intel PCH/HDA Intel PCH.conf
+++ b/ucm-config/for_all_boards/Pixel USB-C earbuds/Pixel USB-C earbuds.conf
@@ -1,4 +1,4 @@
-Comment "Stout internal card"
+Comment "Pixel USB-C earbuds"
 
 SectionUseCase."HiFi" {
 	File "HiFi.conf"
diff --git a/ucm-config/for_all_boards/Plankton Captured HDMI Audio/HiFi.conf b/ucm-config/for_all_boards/Plankton Captured HDMI Audio/HiFi.conf
deleted file mode 100644
index 1a1f4a2..0000000
--- a/ucm-config/for_all_boards/Plankton Captured HDMI Audio/HiFi.conf
+++ /dev/null
@@ -1,24 +0,0 @@
-SectionVerb {
-        Value {
-                FullySpecifiedUCM "1"
-        }
-
-        EnableSequence [
-        ]
-
-        DisableSequence [
-        ]
-}
-
-SectionDevice."Plankton Captured HDMI Audio".0 {
-        Value {
-                CapturePCM "hw:Plankton Captured HDMI Audio,0"
-		JackType "always"
-        }
-
-        EnableSequence [
-        ]
-
-        DisableSequence [
-        ]
-}
diff --git a/ucm-config/for_all_boards/Plankton Captured HDMI Audio/Plankton Captured HDMI Audio.conf b/ucm-config/for_all_boards/Plankton Captured HDMI Audio/Plankton Captured HDMI Audio.conf
deleted file mode 100644
index 6514aa1..0000000
--- a/ucm-config/for_all_boards/Plankton Captured HDMI Audio/Plankton Captured HDMI Audio.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Plankton Captured HDMI Audio"
-
-SectionUseCase."HiFi" {
-        File "HiFi.conf"
-        Comment "Default"
-}
diff --git a/ucm-config/for_all_boards/Plantronics DA70/HiFi.conf b/ucm-config/for_all_boards/Plantronics DA70/HiFi.conf
new file mode 100644
index 0000000..0840b5d
--- /dev/null
+++ b/ucm-config/for_all_boards/Plantronics DA70/HiFi.conf
@@ -0,0 +1,28 @@
+SectionVerb {
+	Value {
+		FullySpecifiedUCM "1"
+	}
+
+	EnableSequence [
+		cdev "hw:SoundBar"
+	]
+
+	DisableSequence [
+	]
+}
+
+SectionDevice."Plantronics DA70 Output".0 {
+	Comment "DA70 Output"
+
+	Value {
+		PlaybackPCM "hw:DA70,0"
+	}
+}
+
+SectionDevice."Plantronics DA70 Input".0 {
+	Comment "DA70 Input"
+
+	Value {
+		CapturePCM "hw:DA70,0"
+	}
+}
diff --git a/ucm-config/veyron/ROCKCHIP-I2S/ROCKCHIP-I2S.conf b/ucm-config/for_all_boards/Plantronics DA70/Plantronics DA70.conf
similarity index 69%
copy from ucm-config/veyron/ROCKCHIP-I2S/ROCKCHIP-I2S.conf
copy to ucm-config/for_all_boards/Plantronics DA70/Plantronics DA70.conf
index 0c399b0..7857d47 100644
--- a/ucm-config/veyron/ROCKCHIP-I2S/ROCKCHIP-I2S.conf
+++ b/ucm-config/for_all_boards/Plantronics DA70/Plantronics DA70.conf
@@ -1,4 +1,4 @@
-Comment "Rockchip card"
+Comment "Plantronics DA70"
 
 SectionUseCase."HiFi" {
 	File "HiFi.conf"
diff --git a/ucm-config/for_all_boards/README b/ucm-config/for_all_boards/README
deleted file mode 100644
index b538435..0000000
--- a/ucm-config/for_all_boards/README
+++ /dev/null
@@ -1,8 +0,0 @@
-Regarding the two dead sym links WD15 Dock and WD19 Dock
-
-ALSA has added a ucm for the ucm the sym links they point to and the ucm
-has been patched in the chromiumos overlay. Unfortunately the alsa-lib
-installs via Makefile and make HATES files with spaces, so we are keeping
-the sym links here (even though they appear dead) they are NOT. Once
-deployed with alsa-lib 1.1.8 they will have a proper target on a dut.
-
diff --git a/ucm-config/for_all_boards/Scarlett 2i2 USB/HiFi.conf b/ucm-config/for_all_boards/Scarlett 2i2 USB/HiFi.conf
new file mode 100644
index 0000000..f49d681
--- /dev/null
+++ b/ucm-config/for_all_boards/Scarlett 2i2 USB/HiFi.conf
@@ -0,0 +1,27 @@
+SectionVerb {
+	Value {
+		FullySpecifiedUCM "1"
+	}
+	EnableSequence [
+		cdev "hw:USB,0"
+	]
+	DisableSequence [
+	]
+}
+
+SectionDevice."Scarlett 2i2 USB Output".0 {
+	Comment "Scarlett 2i2 Output"
+
+	Value {
+		PlaybackPCM "hw:USB,0"
+		PlaybackRate "48000"
+	}
+}
+
+SectionDevice."Scarlett 2i2 USB Input".0 {
+	Comment "Scarlett 2i2 Input"
+	Value {
+		CapturePCM "hw:USB,0"
+		CaptureRate "48000"
+	}
+}
diff --git a/ucm-config/veyron/ROCKCHIP-I2S/ROCKCHIP-I2S.conf b/ucm-config/for_all_boards/Scarlett 2i2 USB/Scarlett 2i2 USB.conf
similarity index 69%
copy from ucm-config/veyron/ROCKCHIP-I2S/ROCKCHIP-I2S.conf
copy to ucm-config/for_all_boards/Scarlett 2i2 USB/Scarlett 2i2 USB.conf
index 0c399b0..88bac6b 100644
--- a/ucm-config/veyron/ROCKCHIP-I2S/ROCKCHIP-I2S.conf
+++ b/ucm-config/for_all_boards/Scarlett 2i2 USB/Scarlett 2i2 USB.conf
@@ -1,5 +1,4 @@
-Comment "Rockchip card"
-
+Comment "Scarlett 2i2 USB"
 SectionUseCase."HiFi" {
 	File "HiFi.conf"
 	Comment "Default"
diff --git a/ucm-config/for_all_boards/Scarlett 2i4 USB/HiFi.conf b/ucm-config/for_all_boards/Scarlett 2i4 USB/HiFi.conf
new file mode 100644
index 0000000..894683c
--- /dev/null
+++ b/ucm-config/for_all_boards/Scarlett 2i4 USB/HiFi.conf
@@ -0,0 +1,27 @@
+SectionVerb {
+	Value {
+		FullySpecifiedUCM "1"
+	}
+	EnableSequence [
+		cdev "hw:USB,0"
+	]
+	DisableSequence [
+	]
+}
+
+SectionDevice."Scarlett 2i4 USB Output".0 {
+	Comment "Scarlett 2i4 Output"
+
+	Value {
+		PlaybackPCM "hw:USB,0"
+		PlaybackRate "48000"
+	}
+}
+
+SectionDevice."Scarlett 2i4 USB Input".0 {
+	Comment "Scarlett 2i4 Input"
+	Value {
+		CapturePCM "hw:USB,0"
+		CaptureRate "48000"
+	}
+}
diff --git a/ucm-config/veyron/ROCKCHIP-I2S/ROCKCHIP-I2S.conf b/ucm-config/for_all_boards/Scarlett 2i4 USB/Scarlett 2i4 USB.conf
similarity index 69%
copy from ucm-config/veyron/ROCKCHIP-I2S/ROCKCHIP-I2S.conf
copy to ucm-config/for_all_boards/Scarlett 2i4 USB/Scarlett 2i4 USB.conf
index 0c399b0..71ea169 100644
--- a/ucm-config/veyron/ROCKCHIP-I2S/ROCKCHIP-I2S.conf
+++ b/ucm-config/for_all_boards/Scarlett 2i4 USB/Scarlett 2i4 USB.conf
@@ -1,5 +1,4 @@
-Comment "Rockchip card"
-
+Comment "Scarlett 2i4 USB"
 SectionUseCase."HiFi" {
 	File "HiFi.conf"
 	Comment "Default"
diff --git a/ucm-config/for_all_boards/USB 2.0 Camera/HiFi.conf b/ucm-config/for_all_boards/USB 2.0 Camera/HiFi.conf
new file mode 100644
index 0000000..e4121da
--- /dev/null
+++ b/ucm-config/for_all_boards/USB 2.0 Camera/HiFi.conf
@@ -0,0 +1,25 @@
+SectionVerb {
+	Value {
+		FullySpecifiedUCM "1"
+	}
+
+	EnableSequence [
+		cdev "hw:Camera"
+		cset "name='Mic Capture Volume' 200"
+	]
+
+	DisableSequence [
+	]
+}
+
+SectionDevice."Camera" {
+	Value {
+		CapturePCM "hw:Camera,0"
+	}
+
+	EnableSequence [
+	]
+
+	DisableSequence [
+	]
+}
diff --git a/ucm-config/stout/HDA Intel PCH/HDA Intel PCH.conf b/ucm-config/for_all_boards/USB 2.0 Camera/USB 2.0 Camera
similarity index 64%
copy from ucm-config/stout/HDA Intel PCH/HDA Intel PCH.conf
copy to ucm-config/for_all_boards/USB 2.0 Camera/USB 2.0 Camera
index 2751b94..52bd294 100644
--- a/ucm-config/stout/HDA Intel PCH/HDA Intel PCH.conf
+++ b/ucm-config/for_all_boards/USB 2.0 Camera/USB 2.0 Camera
@@ -1,4 +1,4 @@
-Comment "Stout internal card"
+Comment "ZIQIAN N21 1080P Webcam"
 
 SectionUseCase."HiFi" {
 	File "HiFi.conf"
diff --git a/ucm-config/glados/sklnau8825adi/HiFi.conf b/ucm-config/glados/sklnau8825adi/HiFi.conf
deleted file mode 100644
index c10104d..0000000
--- a/ucm-config/glados/sklnau8825adi/HiFi.conf
+++ /dev/null
@@ -1,512 +0,0 @@
-SectionVerb {
-	Value {
-		OutputDspName "speaker_eq"
-		InputDspName "extmic_eq"
-	}
-
-	EnableSequence [
-		cdev "hw:sklnau8825adi"
-		cset "name='codec1_out mo media0_in mi Switch' off"
-		cset "name='codec0_out mo media0_in mi Switch' on"
-		cset "name='DAC Oversampling Rate' 64"
-		cset "name='Headset Mic Switch' off"
-		cset "name='BIQ Path Select' ADC"
-		cset "name='BIQ Coefficients' 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
-		cset "name='codec0_iv_in Switch' 0"
-		cset "name='media0_out mo codec0_in mi Switch' off"
-		cset "name='media0_out mo dmic01_hifi_in mi Switch' on"
-		cset "name='Pin 5 Mux' cvt 2"
-		cset "name='Pin 6 Mux' cvt 3"
-		cset "name='Pin 7 Mux' cvt 4"
-		cset "name='Mic Volume' 255"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionDevice."Internal Mic".0 {
-	Value {
-		MaxSoftwareGain "2400"
-		InputDspName ""
-	}
-
-	EnableSequence [
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionDevice."Speaker".0 {
-	Value {
-		CoupledMixers "Left Master,Right Master"
-	}
-
-	EnableSequence [
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionDevice."HDMI1".0 {
-	Value {
-		JackName "HDMI/DP, pcm=4 Jack"
-		DspName ""
-	}
-
-	EnableSequence [
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionDevice."HDMI2".0 {
-	Value {
-		JackName "HDMI/DP, pcm=5 Jack"
-		DspName ""
-	}
-
-	EnableSequence [
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-		JackName "sklnau8825adi Headset Jack"
-		DspName ""
-	}
-
-	EnableSequence [
-		cdev "hw:sklnau8825adi"
-		cset "name='codec0_out mo media0_in mi Switch' off"
-		cset "name='codec1_out mo media0_in mi Switch' on"
-		cset "name='Headphone Jack Switch' on"
-	]
-
-	DisableSequence [
-		cdev "hw:sklnau8825adi"
-		cset "name='codec0_out mo media0_in mi Switch' on"
-		cset "name='codec1_out mo media0_in mi Switch' off"
-		cset "name='Headphone Jack Switch' off"
-	]
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		JackName "sklnau8825adi Headset Jack"
-		CaptureControl "Mic"
-		DefaultNodeGain "800"
-	}
-
-	EnableSequence [
-		cdev "hw:sklnau8825adi"
-		cset "name='Headset Mic Switch' on"
-		cset "name='BIQ Coefficients' 0,155,0,6,255,102,0,0,255,179,0,0,0,154,0,6,255,179,128,0"
-		cset "name='media0_out mo codec0_in mi Switch' on"
-		cset "name='media0_out mo dmic01_hifi_in mi Switch' off"
-	]
-
-	DisableSequence [
-		cdev "hw:sklnau8825adi"
-		cset "name='Headset Mic Switch' off"
-		cset "name='BIQ Coefficients' 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
-		cset "name='media0_out mo codec0_in mi Switch' off"
-		cset "name='media0_out mo dmic01_hifi_in mi Switch' on"
-	]
-}
-
-SectionModifier."Hotword Model ar_eg".0 {
-	EnableSequence [
-		cdev "hw:sklnau8825adi"
-		cset-tlv "name='hwd_in hwd 0 mdl params' /opt/google/skl-hotword-support/ar_eg.hwd-blob"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionModifier."Hotword Model cmn_cn".0 {
-	EnableSequence [
-		cdev "hw:sklnau8825adi"
-		cset-tlv "name='hwd_in hwd 0 mdl params' /opt/google/skl-hotword-support/cmn_hans_cn.hwd-blob"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionModifier."Hotword Model cmn_tw".0 {
-	EnableSequence [
-		cdev "hw:sklnau8825adi"
-		cset-tlv "name='hwd_in hwd 0 mdl params' /opt/google/skl-hotword-support/cmn_hant_tw.hwd-blob"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionModifier."Hotword Model cs_cz".0 {
-	EnableSequence [
-		cdev "hw:sklnau8825adi"
-		cset-tlv "name='hwd_in hwd 0 mdl params' /opt/google/skl-hotword-support/cs_cz.hwd-blob"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionModifier."Hotword Model da_dk".0 {
-	EnableSequence [
-		cdev "hw:sklnau8825adi"
-		cset-tlv "name='hwd_in hwd 0 mdl params' /opt/google/skl-hotword-support/da_dk.hwd-blob"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionModifier."Hotword Model de_de".0 {
-	EnableSequence [
-		cdev "hw:sklnau8825adi"
-		cset-tlv "name='hwd_in hwd 0 mdl params' /opt/google/skl-hotword-support/de_de.hwd-blob"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionModifier."Hotword Model en_au".0 {
-	EnableSequence [
-		cdev "hw:sklnau8825adi"
-		cset-tlv "name='hwd_in hwd 0 mdl params' /opt/google/skl-hotword-support/en_au.hwd-blob"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionModifier."Hotword Model en_gb".0 {
-	EnableSequence [
-		cdev "hw:sklnau8825adi"
-		cset-tlv "name='hwd_in hwd 0 mdl params' /opt/google/skl-hotword-support/en_gb.hwd-blob"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionModifier."Hotword Model en_ie".0 {
-	EnableSequence [
-		cdev "hw:sklnau8825adi"
-		cset-tlv "name='hwd_in hwd 0 mdl params' /opt/google/skl-hotword-support/en_ie.hwd-blob"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionModifier."Hotword Model en_in".0 {
-	EnableSequence [
-		cdev "hw:sklnau8825adi"
-		cset-tlv "name='hwd_in hwd 0 mdl params' /opt/google/skl-hotword-support/en_in.hwd-blob"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionModifier."Hotword Model en_ph".0 {
-	EnableSequence [
-		cdev "hw:sklnau8825adi"
-		cset-tlv "name='hwd_in hwd 0 mdl params' /opt/google/skl-hotword-support/en_ph.hwd-blob"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionModifier."Hotword Model en_us".0 {
-	EnableSequence [
-		cdev "hw:sklnau8825adi"
-		cset-tlv "name='hwd_in hwd 0 mdl params' /opt/google/skl-hotword-support/en_us.hwd-blob"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionModifier."Hotword Model es_419".0 {
-	EnableSequence [
-		cdev "hw:sklnau8825adi"
-		cset-tlv "name='hwd_in hwd 0 mdl params' /opt/google/skl-hotword-support/es_419.hwd-blob"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionModifier."Hotword Model es_ar".0 {
-	EnableSequence [
-		cdev "hw:sklnau8825adi"
-		cset-tlv "name='hwd_in hwd 0 mdl params' /opt/google/skl-hotword-support/es_ar.hwd-blob"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionModifier."Hotword Model es_es".0 {
-	EnableSequence [
-		cdev "hw:sklnau8825adi"
-		cset-tlv "name='hwd_in hwd 0 mdl params' /opt/google/skl-hotword-support/es_es.hwd-blob"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionModifier."Hotword Model es_mx".0 {
-	EnableSequence [
-		cdev "hw:sklnau8825adi"
-		cset-tlv "name='hwd_in hwd 0 mdl params' /opt/google/skl-hotword-support/es_mx.hwd-blob"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionModifier."Hotword Model es_us".0 {
-	EnableSequence [
-		cdev "hw:sklnau8825adi"
-		cset-tlv "name='hwd_in hwd 0 mdl params' /opt/google/skl-hotword-support/es_us.hwd-blob"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionModifier."Hotword Model fa_ir".0 {
-	EnableSequence [
-		cdev "hw:sklnau8825adi"
-		cset-tlv "name='hwd_in hwd 0 mdl params' /opt/google/skl-hotword-support/fa_ir.hwd-blob"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionModifier."Hotword Model fi_fi".0 {
-	EnableSequence [
-		cdev "hw:sklnau8825adi"
-		cset-tlv "name='hwd_in hwd 0 mdl params' /opt/google/skl-hotword-support/fi_fi.hwd-blob"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionModifier."Hotword Model fil_ph".0 {
-	EnableSequence [
-		cdev "hw:sklnau8825adi"
-		cset-tlv "name='hwd_in hwd 0 mdl params' /opt/google/skl-hotword-support/fil_ph.hwd-blob"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionModifier."Hotword Model fr_fr".0 {
-	EnableSequence [
-		cdev "hw:sklnau8825adi"
-		cset-tlv "name='hwd_in hwd 0 mdl params' /opt/google/skl-hotword-support/fr_fr.hwd-blob"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionModifier."Hotword Model hi_in".0 {
-	EnableSequence [
-		cdev "hw:sklnau8825adi"
-		cset-tlv "name='hwd_in hwd 0 mdl params' /opt/google/skl-hotword-support/hi_in.hwd-blob"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionModifier."Hotword Model hr_hr".0 {
-	EnableSequence [
-		cdev "hw:sklnau8825adi"
-		cset-tlv "name='hwd_in hwd 0 mdl params' /opt/google/skl-hotword-support/hr_hr.hwd-blob"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionModifier."Hotword Model id_id".0 {
-	EnableSequence [
-		cdev "hw:sklnau8825adi"
-		cset-tlv "name='hwd_in hwd 0 mdl params' /opt/google/skl-hotword-support/id_id.hwd-blob"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionModifier."Hotword Model it_it".0 {
-	EnableSequence [
-		cdev "hw:sklnau8825adi"
-		cset-tlv "name='hwd_in hwd 0 mdl params' /opt/google/skl-hotword-support/it_it.hwd-blob"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionModifier."Hotword Model ja_jp".0 {
-	EnableSequence [
-		cdev "hw:sklnau8825adi"
-		cset-tlv "name='hwd_in hwd 0 mdl params' /opt/google/skl-hotword-support/ja_jp.hwd-blob"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionModifier."Hotword Model ko_kr".0 {
-	EnableSequence [
-		cdev "hw:sklnau8825adi"
-		cset-tlv "name='hwd_in hwd 0 mdl params' /opt/google/skl-hotword-support/ko_kr.hwd-blob"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionModifier."Hotword Model ms_my".0 {
-	EnableSequence [
-		cdev "hw:sklnau8825adi"
-		cset-tlv "name='hwd_in hwd 0 mdl params' /opt/google/skl-hotword-support/ms_my.hwd-blob"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionModifier."Hotword Model nb_no".0 {
-	EnableSequence [
-		cdev "hw:sklnau8825adi"
-		cset-tlv "name='hwd_in hwd 0 mdl params' /opt/google/skl-hotword-support/nb_no.hwd-blob"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionModifier."Hotword Model nl_nl".0 {
-	EnableSequence [
-		cdev "hw:sklnau8825adi"
-		cset-tlv "name='hwd_in hwd 0 mdl params' /opt/google/skl-hotword-support/nl_nl.hwd-blob"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionModifier."Hotword Model pl_pl".0 {
-	EnableSequence [
-		cdev "hw:sklnau8825adi"
-		cset-tlv "name='hwd_in hwd 0 mdl params' /opt/google/skl-hotword-support/pl_pl.hwd-blob"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionModifier."Hotword Model pt_br".0 {
-	EnableSequence [
-		cdev "hw:sklnau8825adi"
-		cset-tlv "name='hwd_in hwd 0 mdl params' /opt/google/skl-hotword-support/pt_br.hwd-blob"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionModifier."Hotword Model ro_ro".0 {
-	EnableSequence [
-		cdev "hw:sklnau8825adi"
-		cset-tlv "name='hwd_in hwd 0 mdl params' /opt/google/skl-hotword-support/ro_ro.hwd-blob"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionModifier."Hotword Model ru_ru".0 {
-	EnableSequence [
-		cdev "hw:sklnau8825adi"
-		cset-tlv "name='hwd_in hwd 0 mdl params' /opt/google/skl-hotword-support/ru_ru.hwd-blob"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionModifier."Hotword Model sv_se".0 {
-	EnableSequence [
-		cdev "hw:sklnau8825adi"
-		cset-tlv "name='hwd_in hwd 0 mdl params' /opt/google/skl-hotword-support/sv_se.hwd-blob"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionModifier."Hotword Model th_th".0 {
-	EnableSequence [
-		cdev "hw:sklnau8825adi"
-		cset-tlv "name='hwd_in hwd 0 mdl params' /opt/google/skl-hotword-support/th_th.hwd-blob"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionModifier."Hotword Model tr_tr".0 {
-	EnableSequence [
-		cdev "hw:sklnau8825adi"
-		cset-tlv "name='hwd_in hwd 0 mdl params' /opt/google/skl-hotword-support/tr_tr.hwd-blob"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionModifier."Hotword Model vi_vn".0 {
-	EnableSequence [
-		cdev "hw:sklnau8825adi"
-		cset-tlv "name='hwd_in hwd 0 mdl params' /opt/google/skl-hotword-support/vi_vn.hwd-blob"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionModifier."Hotword Model yue_hk".0 {
-	EnableSequence [
-		cdev "hw:sklnau8825adi"
-		cset-tlv "name='hwd_in hwd 0 mdl params' /opt/google/skl-hotword-support/yue_hant_hk.hwd-blob"
-	]
-
-	DisableSequence [
-	]
-}
diff --git a/ucm-config/glados/sklnau8825adi/sklnau8825adi.conf b/ucm-config/glados/sklnau8825adi/sklnau8825adi.conf
deleted file mode 100644
index e1b2a27..0000000
--- a/ucm-config/glados/sklnau8825adi/sklnau8825adi.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Glados internal card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/jecht/HDA Intel PCH/HDA Intel PCH.conf b/ucm-config/jecht/HDA Intel PCH/HDA Intel PCH.conf
deleted file mode 100644
index 17e4cb1..0000000
--- a/ucm-config/jecht/HDA Intel PCH/HDA Intel PCH.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Jecht internal card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/jecht/HDA Intel PCH/HiFi.conf b/ucm-config/jecht/HDA Intel PCH/HiFi.conf
deleted file mode 100644
index 5e280c2..0000000
--- a/ucm-config/jecht/HDA Intel PCH/HiFi.conf
+++ /dev/null
@@ -1,32 +0,0 @@
-SectionVerb {
-	Value {
-		NoCreateDefaultOutputNode "1"
-		NoCreateDefaultInputNode "1"
-	}
-	EnableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Capture Switch' off"
-		cset "name='Capture Volume' 23"
-		cset "name='Master Playback Switch' on"
-		cset "name='Master Playback Volume' 87"
-	]
-	DisableSequence [
-	]
-}
-
-SectionDevice."Mic".0 {
-        Value {
-		JackName "Mic Jack"
-        }
-	EnableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Capture Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Capture Switch' off"
-	]
-}
diff --git a/ucm-config/leon/HDA Intel PCH/HDA Intel PCH.conf b/ucm-config/leon/HDA Intel PCH/HDA Intel PCH.conf
deleted file mode 100644
index f6608a0..0000000
--- a/ucm-config/leon/HDA Intel PCH/HDA Intel PCH.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Leon internal card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/leon/HDA Intel PCH/HiFi.conf b/ucm-config/leon/HDA Intel PCH/HiFi.conf
deleted file mode 100644
index 03f431e..0000000
--- a/ucm-config/leon/HDA Intel PCH/HiFi.conf
+++ /dev/null
@@ -1,55 +0,0 @@
-SectionVerb {
-	Value {
-		OutputDspName "speaker_eq"
-	}
-	EnableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Master Playback Switch' on"
-		cset "name='Headphone Playback Switch' off"
-		cset "name='Speaker Playback Switch' on"
-
-		cset "name='Capture Switch' on"
-		cset "name='Capture Volume' 39"
-		cset "name='Mic Boost Volume' 2"
-		cset "name='Internal Mic Boost Volume' 0"
-		cset "name='Capture Source' 0"
-	]
-	DisableSequence [
-	]
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-		JackName "Headphone Jack"
-		DspName ""
-	}
-	EnableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Speaker Playback Switch' off"
-		cset "name='Headphone Playback Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Headphone Playback Switch' off"
-		cset "name='Speaker Playback Switch' on"
-	]
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		JackName "Mic Jack"
-	}
-	EnableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Capture Source' 1"
-	]
-	DisableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Capture Source' 0"
-	]
-}
diff --git a/ucm-config/link/HDA Intel PCH/HDA Intel PCH.conf b/ucm-config/link/HDA Intel PCH/HDA Intel PCH.conf
deleted file mode 100644
index 44e2932..0000000
--- a/ucm-config/link/HDA Intel PCH/HDA Intel PCH.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Link internal card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/link/HDA Intel PCH/HiFi.conf b/ucm-config/link/HDA Intel PCH/HiFi.conf
deleted file mode 100644
index 6c78a27..0000000
--- a/ucm-config/link/HDA Intel PCH/HiFi.conf
+++ /dev/null
@@ -1,70 +0,0 @@
-SectionVerb {
-	EnableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Master Playback Switch' on"
-		cset "name='HP/Speaker Playback Switch' off"
-		cset "name='HP/Speaker Auto Detect Playback Switch' off"
-		cset "name='PlayEnhancement Playback Switch' on"
-		cset "name='Surround Playback Switch' on"
-		cset "name='Crystalizer Playback Switch' off"
-		cset "name='Dialog Plus Playback Switch' off"
-		cset "name='Smart Volume Playback Switch' on"
-		cset "name='X-Bass Playback Switch' on"
-		cset "name='Equalizer Playback Switch' off"
-		cset "name='Echo Cancellation Capture Switch' on"
-		cset "name='IEC958 Playback Switch' on"
-		cset "name='IEC958 Playback Switch',index=1 on"
-		cset "name='IEC958 Playback Switch',index=2 on"
-
-		cset "name='Capture Switch' on"
-		cset "name='Capture Volume' 99"
-		cset "name='CrystalVoice Capture Switch' off"
-		cset "name='Analog-Mic2 Capture Volume' 90"
-		cset "name='Analog-Mic2 Capture Switch' on"
-		cset "name='Mic1-Boost (30dB) Capture Switch' on"
-		cset "name='AMic1/DMic Capture Switch' off"
-		cset "name='AMic1/DMic Auto Detect Capture Switch' off"
-	]
-	DisableSequence [
-	]
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-		JackName "Headphone Jack"
-	}
-
-	EnableSequence [
-		cdev "hw:PCH"
-
-		cset "name='PlayEnhancement Playback Switch' off"
-		cset "name='HP/Speaker Playback Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:PCH"
-
-		cset "name='PlayEnhancement Playback Switch' on"
-		cset "name='HP/Speaker Playback Switch' off"
-	]
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		JackName "Mic Jack"
-	}
-
-	EnableSequence [
-		cdev "hw:PCH"
-
-		cset "name='CrystalVoice Capture Switch' off"
-		cset "name='AMic1/DMic Capture Switch' on"
-	]
-
-	DisableSequence [
-		cdev "hw:PCH"
-
-		cset "name='CrystalVoice Capture Switch' off"
-		cset "name='AMic1/DMic Capture Switch' off"
-	]
-}
diff --git a/ucm-config/mccloud/HDA Intel PCH/HDA Intel PCH.conf b/ucm-config/mccloud/HDA Intel PCH/HDA Intel PCH.conf
deleted file mode 100644
index a10a4cd..0000000
--- a/ucm-config/mccloud/HDA Intel PCH/HDA Intel PCH.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Mccloud internal card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/mccloud/HDA Intel PCH/HiFi.conf b/ucm-config/mccloud/HDA Intel PCH/HiFi.conf
deleted file mode 100644
index 5e280c2..0000000
--- a/ucm-config/mccloud/HDA Intel PCH/HiFi.conf
+++ /dev/null
@@ -1,32 +0,0 @@
-SectionVerb {
-	Value {
-		NoCreateDefaultOutputNode "1"
-		NoCreateDefaultInputNode "1"
-	}
-	EnableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Capture Switch' off"
-		cset "name='Capture Volume' 23"
-		cset "name='Master Playback Switch' on"
-		cset "name='Master Playback Volume' 87"
-	]
-	DisableSequence [
-	]
-}
-
-SectionDevice."Mic".0 {
-        Value {
-		JackName "Mic Jack"
-        }
-	EnableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Capture Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Capture Switch' off"
-	]
-}
diff --git a/ucm-config/monroe/HDA Intel MID/HDA Intel MID.conf b/ucm-config/monroe/HDA Intel MID/HDA Intel MID.conf
deleted file mode 100644
index d3bf843..0000000
--- a/ucm-config/monroe/HDA Intel MID/HDA Intel MID.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Monroe HDMI card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/monroe/HDA Intel MID/HiFi.conf b/ucm-config/monroe/HDA Intel MID/HiFi.conf
deleted file mode 100644
index 10cc852..0000000
--- a/ucm-config/monroe/HDA Intel MID/HiFi.conf
+++ /dev/null
@@ -1,15 +0,0 @@
-SectionVerb {
-	Value {
-	}
-	EnableSequence [
-	]
-	DisableSequence [
-	]
-}
-
-SectionDevice."OUTPUT".0 {
-	Value {
-		JackName "HDMI/DP,pcm=3 Jack"
-		OverrideNodeType "Internal Speaker"
-	}
-}
diff --git a/ucm-config/monroe/HDA Intel PCH/HDA Intel PCH.conf b/ucm-config/monroe/HDA Intel PCH/HDA Intel PCH.conf
deleted file mode 100644
index cbd74d4..0000000
--- a/ucm-config/monroe/HDA Intel PCH/HDA Intel PCH.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Monroe internal card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/monroe/HDA Intel PCH/HiFi.conf b/ucm-config/monroe/HDA Intel PCH/HiFi.conf
deleted file mode 100644
index 54dd5a9..0000000
--- a/ucm-config/monroe/HDA Intel PCH/HiFi.conf
+++ /dev/null
@@ -1,37 +0,0 @@
-SectionVerb {
-	Value {
-		OutputDspName "speaker_eq"
-	}
-	EnableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Capture Switch' on"
-		cset "name='Capture Volume' 39"
-		cset "name='Mic Boost Volume' 2"
-		cset "name='Internal Mic Boost Volume' 1"
-		cset "name='Capture Source' 0"
-	]
-	DisableSequence [
-	]
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-	}
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		JackName "Mic Jack"
-	}
-	EnableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Capture Source' 1"
-	]
-	DisableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Capture Source' 0"
-	]
-}
diff --git a/ucm-config/panther/HDA Intel PCH/HDA Intel PCH.conf b/ucm-config/panther/HDA Intel PCH/HDA Intel PCH.conf
deleted file mode 100644
index 813d96e..0000000
--- a/ucm-config/panther/HDA Intel PCH/HDA Intel PCH.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Panther internal card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/panther/HDA Intel PCH/HiFi.conf b/ucm-config/panther/HDA Intel PCH/HiFi.conf
deleted file mode 100644
index 5e280c2..0000000
--- a/ucm-config/panther/HDA Intel PCH/HiFi.conf
+++ /dev/null
@@ -1,32 +0,0 @@
-SectionVerb {
-	Value {
-		NoCreateDefaultOutputNode "1"
-		NoCreateDefaultInputNode "1"
-	}
-	EnableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Capture Switch' off"
-		cset "name='Capture Volume' 23"
-		cset "name='Master Playback Switch' on"
-		cset "name='Master Playback Volume' 87"
-	]
-	DisableSequence [
-	]
-}
-
-SectionDevice."Mic".0 {
-        Value {
-		JackName "Mic Jack"
-        }
-	EnableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Capture Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Capture Switch' off"
-	]
-}
diff --git a/ucm-config/peppy/HDA Intel PCH/HDA Intel PCH.conf b/ucm-config/peppy/HDA Intel PCH/HDA Intel PCH.conf
deleted file mode 100644
index 819f826..0000000
--- a/ucm-config/peppy/HDA Intel PCH/HDA Intel PCH.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Peppy internal card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/peppy/HDA Intel PCH/HiFi.conf b/ucm-config/peppy/HDA Intel PCH/HiFi.conf
deleted file mode 100644
index c33144d..0000000
--- a/ucm-config/peppy/HDA Intel PCH/HiFi.conf
+++ /dev/null
@@ -1,55 +0,0 @@
-SectionVerb {
-	Value {
-		OutputDspName "speaker_eq"
-	}
-	EnableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Master Playback Switch' on"
-		cset "name='Headphone Playback Switch' off"
-		cset "name='Speaker Playback Switch' on"
-
-		cset "name='Capture Switch' on"
-		cset "name='Capture Volume' 39"
-		cset "name='Mic Boost Volume' 2"
-		cset "name='Internal Mic Boost Volume' 1"
-		cset "name='Input Source' 1"
-	]
-	DisableSequence [
-	]
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-		JackName "Headphone Jack"
-		DspName ""
-	}
-	EnableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Speaker Playback Switch' off"
-		cset "name='Headphone Playback Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Headphone Playback Switch' off"
-		cset "name='Speaker Playback Switch' on"
-	]
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		JackName "Mic Jack"
-	}
-	EnableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Input Source' 0"
-	]
-	DisableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Input Source' 1"
-	]
-}
diff --git a/ucm-config/rikku/HDA Intel PCH/HDA Intel PCH.conf b/ucm-config/rikku/HDA Intel PCH/HDA Intel PCH.conf
deleted file mode 100644
index d9365cc..0000000
--- a/ucm-config/rikku/HDA Intel PCH/HDA Intel PCH.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Rikku internal card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/rikku/HDA Intel PCH/HiFi.conf b/ucm-config/rikku/HDA Intel PCH/HiFi.conf
deleted file mode 100644
index 5e280c2..0000000
--- a/ucm-config/rikku/HDA Intel PCH/HiFi.conf
+++ /dev/null
@@ -1,32 +0,0 @@
-SectionVerb {
-	Value {
-		NoCreateDefaultOutputNode "1"
-		NoCreateDefaultInputNode "1"
-	}
-	EnableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Capture Switch' off"
-		cset "name='Capture Volume' 23"
-		cset "name='Master Playback Switch' on"
-		cset "name='Master Playback Volume' 87"
-	]
-	DisableSequence [
-	]
-}
-
-SectionDevice."Mic".0 {
-        Value {
-		JackName "Mic Jack"
-        }
-	EnableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Capture Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Capture Switch' off"
-	]
-}
diff --git a/ucm-config/stout/HDA Intel PCH/HiFi.conf b/ucm-config/stout/HDA Intel PCH/HiFi.conf
deleted file mode 100644
index 20c809a..0000000
--- a/ucm-config/stout/HDA Intel PCH/HiFi.conf
+++ /dev/null
@@ -1,62 +0,0 @@
-SectionVerb {
-	Value {
-	}
-	EnableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Headphone Playback Volume' 87"
-		cset "name='Headphone Playback Switch' off"
-		cset "name='Speaker Playback Volume' 87"
-		cset "name='Speaker Playback Switch' on"
-		cset "name='Mic Playback Volume' 0"
-		cset "name='Mic Playback Switch' off"
-		cset "name='Internal Mic Boost Volume' 0"
-		cset "name='Mic Boost Volume' 0"
-		cset "name='Capture Switch' on"
-		cset "name='Capture Volume' 39"
-		cset "name='Master Playback Volume' 79"
-		cset "name='Master Playback Switch' on"
-		cset "name='IEC958 Playback Switch' on"
-	]
-	DisableSequence [
-	]
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-		JackName "Headphone Jack"
-	}
-	EnableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Speaker Pin Playback Switch' off"
-		cset "name='HP Pin Playback Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:PCH"
-
-		cset "name='HP Pin Playback Switch' off"
-		cset "name='Speaker Pin Playback Switch' on"
-	]
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		JackName "Mic Jack"
-	}
-	EnableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Capture Source' 1"
-	]
-	DisableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Capture Source' 0"
-	]
-}
-
-SectionDevice."HDMI".0 {
-	Value {
-	}
-}
diff --git a/ucm-config/strago/chtrt5645/HiFi.conf b/ucm-config/strago/chtrt5645/HiFi.conf
deleted file mode 100644
index e51c947..0000000
--- a/ucm-config/strago/chtrt5645/HiFi.conf
+++ /dev/null
@@ -1,121 +0,0 @@
-SectionVerb {
-	Value {
-		OutputDspName "speaker_eq"
-	}
-
-	EnableSequence [
-		cdev "hw:chtrt5645"
-
-		cset "name='codec_out1 mix 0 pcm0_in Switch' on"
-		cset "name='media0_out mix 0 media1_in Switch' on"
-
-		cset "name='media1_in Gain 0 Ramp Delay' 50"
-		cset "name='media1_in Gain 0 Switch' off"
-		cset "name='media1_in Gain 0 Volume' 80% 80%"
-
-		cset "name='pcm0_in Gain 0 Ramp Delay' 50"
-		cset "name='pcm0_in Gain 0 Switch' off"
-		cset "name='pcm0_in Gain 0 Volume' 80% 80%"
-
-		cset "name='codec_out1 Gain 0 Ramp Delay' 50"
-		cset "name='codec_out1 Gain 0 Switch' off"
-		cset "name='codec_out1 Gain 0 Volume' 70% 70%"
-
-		cset "name='Ext Spk Switch' on"
-		cset "name='Speaker Channel Switch' on"
-		cset "name='Ext HP Switch' off"
-
-		cset "name='DAC R2 Mux' 'IF1 DAC'"
-		cset "name='DAC L2 Mux' 'IF1 DAC'"
-		cset "name='Mono DAC MIXL DAC L2 Switch' on"
-		cset "name='Mono DAC MIXR DAC R2 Switch' on"
-		cset "name='DAC2 Playback Switch' on"
-
-		cset "name='HPOVOL MIXL DAC2 Switch' on"
-		cset "name='HPOVOL MIXR DAC2 Switch' on"
-		cset "name='HPO MIX HPVOL Switch' on"
-		cset "name='HPOVOL L Switch' on"
-		cset "name='HPOVOL R Switch' on"
-
-		cset "name='SPK MIXL DAC L2 Switch' on"
-		cset "name='SPK MIXR DAC R2 Switch' on"
-		cset "name='SPOL MIX SPKVOL L Switch' on"
-		cset "name='SPOR MIX SPKVOL R Switch' on"
-		cset "name='SPKVOL L Switch' on"
-		cset "name='SPKVOL R Switch' on"
-
-		cset "name='Headset Mic Switch' off"
-		cset "name='Int Mic Switch' on"
-		cset "name='ADC Capture Switch' on"
-		cset "name='Stereo1 DMIC Mux' 0"
-		cset "name='Stereo1 ADC2 Mux' 1"
-		cset "name='I2S2 Func Switch' 0"
-		cset "name='pcm1_out mix 0 media_loop2_in Switch' 1"
-		cset "name='media_loop2_out mix 0 codec_in0 Switch' 1"
-		cset "name='codec_in0 Gain 0 Ramp Delay' 50"
-		cset "name='codec_in0 Gain 0 Switch' off"
-		cset "name='codec_in0 Gain 0 Volume' 80% 80%"
-		cset "name='media_loop2_out Gain 0 Ramp Delay' 50"
-		cset "name='media_loop2_out Gain 0 Switch' off"
-		cset "name='media_loop2_out Gain 0 Volume' 80% 80%"
-		cset "name='pcm1_out Gain 0 Ramp Delay' 50"
-		cset "name='pcm1_out Gain 0 Switch' off"
-		cset "name='pcm1_out Gain 0 Volume' 80% 80%"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-		JackName "chtrt5645 Headphone Jack"
-		DspName ""
-	}
-
-	EnableSequence [
-		cdev "hw:chtrt5645"
-		cset "name='Ext Spk Switch' off"
-		cset "name='Speaker Channel Switch' off"
-		cset "name='Ext HP Switch' on"
-		cset "name='HP Channel Switch' on"
-	]
-
-	DisableSequence [
-		cdev "hw:chtrt5645"
-		cset "name='Ext Spk Switch' on"
-		cset "name='Speaker Channel Switch' on"
-		cset "name='Ext HP Switch' off"
-		cset "name='HP Channel Switch' off"
-	]
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		JackName "chtrt5645 Mic Jack"
-	}
-
-	EnableSequence [
-		cdev "hw:chtrt5645"
-		cset "name='Headset Mic Switch' on"
-		cset "name='Int Mic Switch' off"
-		cset "name='Sto1 ADC MIXL ADC2 Switch' 0"
-		cset "name='Sto1 ADC MIXR ADC2 Switch' 0"
-		cset "name='RECMIXL BST1 Switch' 1"
-		cset "name='RECMIXR BST1 Switch' 1"
-		cset "name='Sto1 ADC MIXL ADC1 Switch' 1"
-		cset "name='Sto1 ADC MIXR ADC1 Switch' 1"
-	]
-
-	DisableSequence [
-		cdev "hw:chtrt5645"
-		cset "name='Headset Mic Switch' off"
-		cset "name='Int Mic Switch' on"
-		cset "name='RECMIXL BST1 Switch' 0"
-		cset "name='RECMIXR BST1 Switch' 0"
-		cset "name='Sto1 ADC MIXL ADC1 Switch' 0"
-		cset "name='Sto1 ADC MIXR ADC1 Switch' 0"
-		cset "name='Sto1 ADC MIXL ADC2 Switch' 1"
-		cset "name='Sto1 ADC MIXR ADC2 Switch' 1"
-	]
-}
diff --git a/ucm-config/strago/chtrt5645/chtrt5645.conf b/ucm-config/strago/chtrt5645/chtrt5645.conf
deleted file mode 100644
index 14b8450..0000000
--- a/ucm-config/strago/chtrt5645/chtrt5645.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Strago internal card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/strago/chtrt5650/HiFi.conf b/ucm-config/strago/chtrt5650/HiFi.conf
deleted file mode 100644
index 4cdc900..0000000
--- a/ucm-config/strago/chtrt5650/HiFi.conf
+++ /dev/null
@@ -1,129 +0,0 @@
-SectionVerb {
-	Value {
-		OutputDspName "speaker_eq"
-	}
-
-	EnableSequence [
-		cdev "hw:chtrt5650"
-
-		cset "name='codec_out0 mix 0 pcm0_in Switch' on"
-		cset "name='media0_out mix 0 media1_in Switch' on"
-
-		cset "name='media1_in Gain 0 Ramp Delay' 50"
-		cset "name='media1_in Gain 0 Switch' off"
-		cset "name='media1_in Gain 0 Volume' 80% 80%"
-
-		cset "name='pcm0_in Gain 0 Ramp Delay' 50"
-		cset "name='pcm0_in Gain 0 Switch' off"
-		cset "name='pcm0_in Gain 0 Volume' 80% 80%"
-
-		cset "name='codec_out0 Gain 0 Ramp Delay' 50"
-		cset "name='codec_out0 Gain 0 Switch' off"
-		cset "name='codec_out0 Gain 0 Volume' 80% 80%"
-
-		cset "name='Ext Spk Switch' on"
-		cset "name='Speaker Channel Switch' on"
-		cset "name='Ext HP Switch' off"
-
-		cset "name='Stereo DAC MIXL DAC L1 Switch' on"
-		cset "name='Stereo DAC MIXR DAC R1 Switch' on"
-		cset "name='DAC1 MIXL DAC1 Switch' on"
-		cset "name='DAC1 MIXR DAC1 Switch' on"
-
-		cset "name='SPK MIXL DAC L1 Switch' on"
-		cset "name='SPK MIXR DAC R1 Switch' on"
-		cset "name='SPOL MIX SPKVOL L Switch' on"
-		cset "name='SPOR MIX SPKVOL R Switch' on"
-		cset "name='SPKVOL L Switch' on"
-		cset "name='SPKVOL R Switch' on"
-		cset "name='Speaker Channel Switch' on"
-
-		cset "name='HPOVOL MIXL DAC1 Switch' on"
-		cset "name='HPOVOL MIXR DAC1 Switch' on"
-		cset "name='HPO MIX HPVOL Switch' on"
-		cset "name='HPOVOL L Switch' on"
-		cset "name='HPOVOL R Switch' on"
-		cset "name='Headphone Channel Switch' on"
-
-		cset "name='Headset Mic Switch' off"
-		cset "name='Int Mic Switch' on"
-		cset "name='ADC Capture Switch' on"
-		cset "name='Stereo1 DMIC Mux' 1"
-		cset "name='Stereo1 ADC2 Mux' 1"
-		cset "name='I2S2 Func Switch' 0"
-		cset "name='RT5650 IF1 ADC Mux' 0"
-		cset "name='ADC Capture Volume' 74"
-
-		cset "name='Sto1 ADC MIXL ADC1 Switch' 0"
-		cset "name='Sto1 ADC MIXR ADC1 Switch' 0"
-		cset "name='Sto1 ADC MIXL ADC2 Switch' 1"
-		cset "name='Sto1 ADC MIXR ADC2 Switch' 1"
-
-		cset "name='pcm1_out mix 0 media_loop2_in Switch' 1"
-		cset "name='media_loop2_out mix 0 codec_in0 Switch' 1"
-		cset "name='codec_in0 Gain 0 Ramp Delay' 50"
-		cset "name='codec_in0 Gain 0 Switch' off"
-		cset "name='codec_in0 Gain 0 Volume' 80% 80%"
-		cset "name='media_loop2_out Gain 0 Ramp Delay' 50"
-		cset "name='media_loop2_out Gain 0 Switch' off"
-		cset "name='media_loop2_out Gain 0 Volume' 80% 80%"
-		cset "name='pcm1_out Gain 0 Ramp Delay' 50"
-		cset "name='pcm1_out Gain 0 Switch' off"
-		cset "name='pcm1_out Gain 0 Volume' 80% 80%"
-
-
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-		JackName "chtrt5650 Headset Jack"
-		DspName ""
-	}
-
-	EnableSequence [
-		cdev "hw:chtrt5650"
-		cset "name='Ext Spk Switch' off"
-		cset "name='Ext HP Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:chtrt5650"
-		cset "name='Ext Spk Switch' on"
-		cset "name='Ext HP Switch' off"
-	]
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		JackName "chtrt5650 Headset Jack"
-	}
-
-	EnableSequence [
-		cdev "hw:chtrt5650"
-		cset "name='Headset Mic Switch' on"
-		cset "name='Int Mic Switch' off"
-		cset "name='Sto1 ADC MIXL ADC2 Switch' 0"
-		cset "name='Sto1 ADC MIXR ADC2 Switch' 0"
-		cset "name='RECMIXL BST1 Switch' 1"
-		cset "name='RECMIXR BST1 Switch' 1"
-		cset "name='Sto1 ADC MIXL ADC1 Switch' 1"
-		cset "name='Sto1 ADC MIXR ADC1 Switch' 1"
-		cset "name='IN1 Boost' 1"
-	]
-
-	DisableSequence [
-		cdev "hw:chtrt5650"
-		cset "name='Headset Mic Switch' off"
-		cset "name='Int Mic Switch' on"
-		cset "name='RECMIXL BST1 Switch' 0"
-		cset "name='RECMIXR BST1 Switch' 0"
-		cset "name='Sto1 ADC MIXL ADC1 Switch' 0"
-		cset "name='Sto1 ADC MIXR ADC1 Switch' 0"
-		cset "name='Sto1 ADC MIXL ADC2 Switch' 1"
-		cset "name='Sto1 ADC MIXR ADC2 Switch' 1"
-		cset "name='IN1 Boost' 0"
-	]
-}
diff --git a/ucm-config/strago/chtrt5650/chtrt5650.conf b/ucm-config/strago/chtrt5650/chtrt5650.conf
deleted file mode 100644
index 14b8450..0000000
--- a/ucm-config/strago/chtrt5650/chtrt5650.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Strago internal card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/strago/chtrt5676/HiFi.conf b/ucm-config/strago/chtrt5676/HiFi.conf
deleted file mode 100644
index dd2354f..0000000
--- a/ucm-config/strago/chtrt5676/HiFi.conf
+++ /dev/null
@@ -1,106 +0,0 @@
-SectionVerb {
-	Value {
-		OutputDspName "speaker_eq"
-	}
-
-	EnableSequence [
-		cdev "hw:chtrt5676"
-
-		cset "name='codec_out0 mix 0 pcm0_in Switch' on"
-		cset "name='media0_out mix 0 media1_in Switch' on"
-
-		cset "name='media1_in Gain 0 Ramp Delay' 50"
-		cset "name='media1_in Gain 0 Switch' off"
-		cset "name='media1_in Gain 0 Volume' 80% 80%"
-
-		cset "name='pcm0_in Gain 0 Ramp Delay' 50"
-		cset "name='pcm0_in Gain 0 Switch' off"
-		cset "name='pcm0_in Gain 0 Volume' 80% 80%"
-
-		cset "name='codec_out0 Gain 0 Ramp Delay' 50"
-		cset "name='codec_out0 Gain 0 Switch' off"
-		cset "name='codec_out0 Gain 0 Volume' 70% 70%"
-
-		cset "name='Ext Spk Switch' on"
-		cset "name='Ext HP Switch' off"
-
-		cset "name='DAC1 MIXL DAC1 Switch' on"
-		cset "name='DAC1 MIXR DAC1 Switch' on"
-		cset "name='Stereo DAC MIXL DAC1 L Switch' on"
-		cset "name='Stereo DAC MIXR DAC1 R Switch' on"
-		cset "name='PDM1 L Mux' 0"
-		cset "name='PDM1 R Mux' 0"
-
-		cset "name='DAC1 Mux' 0"
-		cset "name='DAC12 SRC Mux' 0"
-		cset "name='OUT1 Playback Switch' on"
-		cset "name='OUT2 Playback Switch' on"
-
-		cset "name='Headset Mic Switch' off"
-		cset "name='Int Mic Switch' on"
-
-		cset "name='Stereo1 DMIC Mux' 0"
-		cset "name='Stereo1 ADC2 Mux' 1"
-		cset "name='Sto1 ADC MIXL ADC2 Switch' on"
-		cset "name='Sto1 ADC MIXR ADC2 Switch' on"
-		cset "name='IF1 ADC1 Mux' 0"
-
-		cset "name='Stereo1 ADC1 Mux' 1"
-		cset "name='Sto1 ADC MIXL ADC1 Switch' on"
-		cset "name='Sto1 ADC MIXR ADC1 Switch' on"
-		cset "name='IN1 Boost' 1"
-		cset "name='IF1 ADC1 Swap Mux' 2"
-
-		cset "name='pcm1_out mix 0 media_loop2_in Switch' 1"
-		cset "name='media_loop2_out mix 0 codec_in0 Switch' 1"
-		cset "name='codec_in0 Gain 0 Ramp Delay' 50"
-		cset "name='codec_in0 Gain 0 Switch' off"
-		cset "name='codec_in0 Gain 0 Volume' 80% 80%"
-		cset "name='media_loop2_out Gain 0 Ramp Delay' 50"
-		cset "name='media_loop2_out Gain 0 Switch' off"
-		cset "name='media_loop2_out Gain 0 Volume' 80% 80%"
-		cset "name='pcm1_out Gain 0 Ramp Delay' 50"
-		cset "name='pcm1_out Gain 0 Switch' off"
-		cset "name='pcm1_out Gain 0 Volume' 80% 80%"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-		JackName "chtrt5676 Headset Jack"
-		DspName ""
-	}
-
-	EnableSequence [
-		cdev "hw:chtrt5676"
-		cset "name='Ext Spk Switch' off"
-		cset "name='Ext HP Switch' on"
-	]
-
-	DisableSequence [
-		cdev "hw:chtrt5676"
-		cset "name='Ext Spk Switch' on"
-		cset "name='Ext HP Switch' off"
-	]
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		JackName "chtrt5676 Headset Jack"
-	}
-
-	EnableSequence [
-		cdev "hw:chtrt5676"
-		cset "name='Headset Mic Switch' on"
-		cset "name='Int Mic Switch' off"
-	]
-
-	DisableSequence [
-		cdev "hw:chtrt5676"
-		cset "name='Headset Mic Switch' off"
-		cset "name='Int Mic Switch' on"
-	]
-}
diff --git a/ucm-config/strago/chtrt5676/chtrt5676.conf b/ucm-config/strago/chtrt5676/chtrt5676.conf
deleted file mode 100644
index 14b8450..0000000
--- a/ucm-config/strago/chtrt5676/chtrt5676.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Strago internal card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/tidus/HDA Intel PCH/HDA Intel PCH.conf b/ucm-config/tidus/HDA Intel PCH/HDA Intel PCH.conf
deleted file mode 100644
index ad8ba1a..0000000
--- a/ucm-config/tidus/HDA Intel PCH/HDA Intel PCH.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Tidus internal card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/tidus/HDA Intel PCH/HiFi.conf b/ucm-config/tidus/HDA Intel PCH/HiFi.conf
deleted file mode 100644
index 5e280c2..0000000
--- a/ucm-config/tidus/HDA Intel PCH/HiFi.conf
+++ /dev/null
@@ -1,32 +0,0 @@
-SectionVerb {
-	Value {
-		NoCreateDefaultOutputNode "1"
-		NoCreateDefaultInputNode "1"
-	}
-	EnableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Capture Switch' off"
-		cset "name='Capture Volume' 23"
-		cset "name='Master Playback Switch' on"
-		cset "name='Master Playback Volume' 87"
-	]
-	DisableSequence [
-	]
-}
-
-SectionDevice."Mic".0 {
-        Value {
-		JackName "Mic Jack"
-        }
-	EnableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Capture Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Capture Switch' off"
-	]
-}
diff --git a/ucm-config/tricky/HDA Intel PCH/HDA Intel PCH.conf b/ucm-config/tricky/HDA Intel PCH/HDA Intel PCH.conf
deleted file mode 100644
index f9b1aec..0000000
--- a/ucm-config/tricky/HDA Intel PCH/HDA Intel PCH.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Tricky internal card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/tricky/HDA Intel PCH/HiFi.conf b/ucm-config/tricky/HDA Intel PCH/HiFi.conf
deleted file mode 100644
index 5e280c2..0000000
--- a/ucm-config/tricky/HDA Intel PCH/HiFi.conf
+++ /dev/null
@@ -1,32 +0,0 @@
-SectionVerb {
-	Value {
-		NoCreateDefaultOutputNode "1"
-		NoCreateDefaultInputNode "1"
-	}
-	EnableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Capture Switch' off"
-		cset "name='Capture Volume' 23"
-		cset "name='Master Playback Switch' on"
-		cset "name='Master Playback Volume' 87"
-	]
-	DisableSequence [
-	]
-}
-
-SectionDevice."Mic".0 {
-        Value {
-		JackName "Mic Jack"
-        }
-	EnableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Capture Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Capture Switch' off"
-	]
-}
diff --git a/ucm-config/veyron/ROCKCHIP-I2S/HiFi.conf b/ucm-config/veyron/ROCKCHIP-I2S/HiFi.conf
deleted file mode 100644
index a7d8020..0000000
--- a/ucm-config/veyron/ROCKCHIP-I2S/HiFi.conf
+++ /dev/null
@@ -1,137 +0,0 @@
-SectionVerb {
-	Value {
-		OutputDspName "speaker_eq"
-		MinBufferLevel "512"
-		FullySpecifiedUCM "1"
-	}
-
-	EnableSequence [
-		cdev "hw:ROCKCHIPI2S"
-
-		cset "name='Left Speaker Mixer Left DAC Switch' on"
-		cset "name='Right Speaker Mixer Right DAC Switch' on"
-		cset "name='Headphone Left Switch' off"
-		cset "name='Headphone Right Switch' off"
-		cset "name='Digital EQ 3 Band Switch' off"
-		cset "name='Digital EQ 5 Band Switch' off"
-		cset "name='Digital EQ 7 Band Switch' off"
-		cset "name='Biquad Switch' off"
-		cset "name='Filter Mode' Music"
-		cset "name='ADC Oversampling Rate' 0"
-
-		cset "name='DMIC Mux' DMIC"
-		cset "name='MIC2 Mux' IN34"
-		cset "name='Right ADC Mixer MIC2 Switch' on"
-		cset "name='Left ADC Mixer MIC2 Switch' on"
-		cset "name='MIC2 Volume' 20"
-		cset "name='Headset Mic Switch' off"
-		cset "name='Int Mic Switch' on"
-
-		cset "name='ADCR Boost Volume' 4"
-		cset "name='ADCL Boost Volume' 4"
-		cset "name='ADCR Volume' 11"
-		cset "name='ADCL Volume' 11"
-
-		cset "name='Left Speaker Mixer Left DAC Switch' on"
-		cset "name='Right Speaker Mixer Right DAC Switch' on"
-		cset "name='Speaker Left Mixer Volume' 2"
-		cset "name='Speaker Right Mixer Volume' 2"
-		cset "name='Record Path DC Blocking' on"
-		cset "name='Playback Path DC Blocking' on"
-
-		cset "name='Speaker Left Switch' on"
-		cset "name='Speaker Right Switch' on"
-		cset "name='Speaker Switch' on"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionDevice."Speaker".0 {
-	Value {
-		PlaybackPCM "hw:ROCKCHIPI2S,0"
-		MixerName "Speaker"
-	}
-	EnableSequence [
-		cdev "hw:ROCKCHIPI2S"
-
-		cset "name='Speaker Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:ROCKCHIPI2S"
-
-		cset "name='Speaker Switch' off"
-	]
-}
-
-SectionDevice."Internal Mic".0 {
-	Value {
-		CapturePCM "hw:ROCKCHIPI2S,0"
-		MixerName "Int Mic"
-	}
-	EnableSequence [
-		cdev "hw:ROCKCHIPI2S"
-
-		cset "name='Int Mic Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:ROCKCHIPI2S"
-
-		cset "name='Int Mic Switch' off"
-	]
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-		PlaybackPCM "hw:ROCKCHIPI2S,0"
-		MixerName "Headphone"
-		JackType "gpio"
-		JackName "ROCKCHIP-I2S Headset Jack"
-		DspName ""
-	}
-
-	EnableSequence [
-		cdev "hw:ROCKCHIPI2S"
-
-		cset "name='Speaker Switch' off"
-		cset "name='Headphone Switch' on"
-		cset "name='Headphone Left Switch' on"
-		cset "name='Headphone Right Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:ROCKCHIPI2S"
-
-		cset "name='Headphone Left Switch' off"
-		cset "name='Headphone Right Switch' off"
-		cset "name='Headphone Switch' off"
-		cset "name='Speaker Switch' on"
-	]
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		CapturePCM "hw:ROCKCHIPI2S,0"
-		MixerName "Headset Mic"
-		JackType "gpio"
-		JackName "ROCKCHIP-I2S Headset Jack"
-	}
-
-	EnableSequence [
-		cdev "hw:ROCKCHIPI2S"
-
-		cset "name='Int Mic Switch' off"
-		cset "name='DMIC Mux' ADC"
-		cset "name='Headset Mic Switch' on"
-		cset "name='Record Path DC Blocking' on"
-	]
-
-	DisableSequence [
-		cdev "hw:ROCKCHIPI2S"
-
-		cset "name='Headset Mic Switch' off"
-		cset "name='DMIC Mux' DMIC"
-		cset "name='Int Mic Switch' on"
-		cset "name='Record Path DC Blocking' off"
-	]
-}
diff --git a/ucm-config/veyron/RockchipHDMI/HiFi.conf b/ucm-config/veyron/RockchipHDMI/HiFi.conf
deleted file mode 100644
index bf76d6a..0000000
--- a/ucm-config/veyron/RockchipHDMI/HiFi.conf
+++ /dev/null
@@ -1,12 +0,0 @@
-SectionVerb {
-	Value {
-		MinBufferLevel "512"
-	}
-}
-
-SectionDevice."HDMI".0 {
-	Value {
-		JackName "RockchipHDMI HDMI Jack"
-		EDIDFile "/sys/class/drm/card1-HDMI-A-1/edid"
-	}
-}
diff --git a/ucm-config/veyron/RockchipHDMI/RockchipHDMI.conf b/ucm-config/veyron/RockchipHDMI/RockchipHDMI.conf
deleted file mode 100644
index a167d74..0000000
--- a/ucm-config/veyron/RockchipHDMI/RockchipHDMI.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Rockchip HDMI card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/veyron/VEYRON-I2S/HiFi.conf b/ucm-config/veyron/VEYRON-I2S/HiFi.conf
deleted file mode 100644
index 2698529..0000000
--- a/ucm-config/veyron/VEYRON-I2S/HiFi.conf
+++ /dev/null
@@ -1,158 +0,0 @@
-SectionVerb {
-	Value {
-		OutputDspName "speaker_eq"
-		MinBufferLevel "512"
-		FullySpecifiedUCM "1"
-	}
-
-	EnableSequence [
-		cdev "hw:VEYRONI2S"
-
-		cset "name='Left Speaker Mixer Left DAC Switch' on"
-		cset "name='Right Speaker Mixer Right DAC Switch' on"
-		cset "name='Digital EQ 3 Band Switch' off"
-		cset "name='Digital EQ 5 Band Switch' off"
-		cset "name='Digital EQ 7 Band Switch' off"
-		cset "name='Biquad Switch' off"
-		cset "name='Filter Mode' Music"
-		cset "name='ADC Oversampling Rate' 0"
-
-		cset "name='DMIC Mux' DMIC"
-		cset "name='MIC2 Mux' IN34"
-		cset "name='Right ADC Mixer MIC2 Switch' on"
-		cset "name='Left ADC Mixer MIC2 Switch' on"
-		cset "name='MIC2 Volume' 20"
-		cset "name='Headset Mic Switch' off"
-		cset "name='Int Mic Switch' on"
-
-		cset "name='ADCR Boost Volume' 4"
-		cset "name='ADCL Boost Volume' 4"
-		cset "name='ADCR Volume' 11"
-		cset "name='ADCL Volume' 11"
-
-		cset "name='Left Speaker Mixer Left DAC Switch' on"
-		cset "name='Right Speaker Mixer Right DAC Switch' on"
-		cset "name='Speaker Left Mixer Volume' 2"
-		cset "name='Speaker Right Mixer Volume' 2"
-		cset "name='Record Path DC Blocking' on"
-		cset "name='Playback Path DC Blocking' on"
-
-		cset "name='Speaker Left Switch' off"
-		cset "name='Speaker Right Switch' off"
-		cset "name='Speaker Switch' off"
-
-		cset "name='Headphone Switch' off"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-		cset "name='Headphone Left Switch' on"
-		cset "name='Headphone Right Switch' on"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionDevice."Speaker".0 {
-	Value {
-		PlaybackPCM "hw:VEYRONI2S,0"
-		MixerName "Speaker"
-	}
-	EnableSequence [
-		cdev "hw:VEYRONI2S"
-
-		cset "name='Speaker Left Switch' on"
-		cset "name='Speaker Right Switch' on"
-		cset "name='Speaker Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:VEYRONI2S"
-
-		cset "name='Speaker Switch' off"
-		cset "name='Speaker Left Switch' off"
-		cset "name='Speaker Right Switch' off"
-	]
-}
-
-SectionDevice."Internal Mic".0 {
-	Value {
-		CapturePCM "hw:VEYRONI2S,0"
-		MixerName "Int Mic"
-	}
-	EnableSequence [
-		cdev "hw:VEYRONI2S"
-
-		cset "name='Int Mic Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:VEYRONI2S"
-
-		cset "name='Int Mic Switch' off"
-	]
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-		PlaybackPCM "hw:VEYRONI2S,0"
-		MixerName "Headphone"
-		JackType "gpio"
-		JackName "VEYRON-I2S Headset Jack"
-		OutputDspName ""
-	}
-
-	EnableSequence [
-		cdev "hw:VEYRONI2S"
-
-		cset "name='HP Left Out Switch' on"
-		cset "name='HP Right Out Switch' on"
-		cset "name='Headphone Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:VEYRONI2S"
-
-		cset "name='Headphone Switch' off"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-	]
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		CapturePCM "hw:VEYRONI2S,0"
-		MixerName "Headset Mic"
-		JackType "gpio"
-		JackName "VEYRON-I2S Headset Jack"
-	}
-
-	EnableSequence [
-		cdev "hw:VEYRONI2S"
-
-		cset "name='Int Mic Switch' off"
-		cset "name='DMIC Mux' ADC"
-		cset "name='Headset Mic Switch' on"
-		cset "name='Record Path DC Blocking' on"
-	]
-
-	DisableSequence [
-		cdev "hw:VEYRONI2S"
-
-		cset "name='Headset Mic Switch' off"
-		cset "name='DMIC Mux' DMIC"
-		cset "name='Int Mic Switch' on"
-		cset "name='Record Path DC Blocking' off"
-	]
-}
-
-SectionDevice."HDMI".0 {
-	Value {
-		PlaybackPCM "hw:VEYRONI2S,1"
-		JackName "VEYRON-I2S HDMI Jack"
-		JackType "gpio"
-		EDIDFile "/sys/class/drm/card1-HDMI-A-1/edid"
-	}
-	EnableSequence [
-		cdev "hw:VEYRONI2S"
-	]
-	DisableSequence [
-		cdev "hw:VEYRONI2S"
-	]
-}
diff --git a/ucm-config/veyron/VEYRON-I2S/VEYRON-I2S.conf b/ucm-config/veyron/VEYRON-I2S/VEYRON-I2S.conf
deleted file mode 100644
index 0c399b0..0000000
--- a/ucm-config/veyron/VEYRON-I2S/VEYRON-I2S.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Rockchip card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/veyron_fievel/ROCKCHIP-I2S/HiFi.conf b/ucm-config/veyron_fievel/ROCKCHIP-I2S/HiFi.conf
deleted file mode 100644
index c9e570a..0000000
--- a/ucm-config/veyron_fievel/ROCKCHIP-I2S/HiFi.conf
+++ /dev/null
@@ -1,103 +0,0 @@
-SectionVerb {
-	Value {
-		OutputDspName "speaker_eq"
-		MinBufferLevel "512"
-		FullySpecifiedUCM "1"
-	}
-
-	EnableSequence [
-		cdev "hw:ROCKCHIPI2S"
-
-		cset "name='Left Speaker Mixer Left DAC Switch' on"
-		cset "name='Right Speaker Mixer Right DAC Switch' on"
-		cset "name='Headphone Left Switch' off"
-		cset "name='Headphone Right Switch' off"
-		cset "name='Digital EQ 3 Band Switch' off"
-		cset "name='Digital EQ 5 Band Switch' off"
-		cset "name='Digital EQ 7 Band Switch' off"
-		cset "name='Biquad Switch' off"
-		cset "name='Filter Mode' Music"
-		cset "name='ADC Oversampling Rate' 0"
-
-		cset "name='DMIC Mux' DMIC"
-		cset "name='MIC2 Mux' IN34"
-		cset "name='Right ADC Mixer MIC2 Switch' on"
-		cset "name='Left ADC Mixer MIC2 Switch' on"
-		cset "name='MIC2 Volume' 20"
-		cset "name='Headset Mic Switch' off"
-		cset "name='Int Mic Switch' on"
-
-		cset "name='ADCR Boost Volume' 4"
-		cset "name='ADCL Boost Volume' 4"
-		cset "name='ADCR Volume' 11"
-		cset "name='ADCL Volume' 11"
-
-		cset "name='Left Speaker Mixer Left DAC Switch' on"
-		cset "name='Right Speaker Mixer Right DAC Switch' on"
-		cset "name='Speaker Left Mixer Volume' 2"
-		cset "name='Speaker Right Mixer Volume' 2"
-		cset "name='Record Path DC Blocking' on"
-		cset "name='Playback Path DC Blocking' on"
-
-		cset "name='Speaker Left Switch' on"
-		cset "name='Speaker Right Switch' on"
-		cset "name='Speaker Switch' on"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-		PlaybackPCM "hw:ROCKCHIPI2S,0"
-		MixerName "Headphone"
-		JackType "gpio"
-		JackName "ROCKCHIP-I2S Headset Jack"
-		DspName ""
-	}
-
-	EnableSequence [
-		cdev "hw:ROCKCHIPI2S"
-
-		cset "name='Speaker Switch' off"
-		cset "name='Headphone Switch' on"
-		cset "name='Headphone Left Switch' on"
-		cset "name='Headphone Right Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:ROCKCHIPI2S"
-
-		cset "name='Headphone Left Switch' off"
-		cset "name='Headphone Right Switch' off"
-		cset "name='Headphone Switch' off"
-		cset "name='Speaker Switch' on"
-	]
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		CapturePCM "hw:ROCKCHIPI2S,0"
-		MixerName "Headset Mic"
-		JackType "gpio"
-		JackName "ROCKCHIP-I2S Headset Jack"
-	}
-
-	EnableSequence [
-		cdev "hw:ROCKCHIPI2S"
-
-		cset "name='Int Mic Switch' off"
-		cset "name='DMIC Mux' ADC"
-		cset "name='Headset Mic Switch' on"
-		cset "name='Record Path DC Blocking' on"
-	]
-
-	DisableSequence [
-		cdev "hw:ROCKCHIPI2S"
-
-		cset "name='Headset Mic Switch' off"
-		cset "name='DMIC Mux' DMIC"
-		cset "name='Int Mic Switch' on"
-		cset "name='Record Path DC Blocking' off"
-	]
-}
diff --git a/ucm-config/veyron_fievel/ROCKCHIP-I2S/ROCKCHIP-I2S.conf b/ucm-config/veyron_fievel/ROCKCHIP-I2S/ROCKCHIP-I2S.conf
deleted file mode 100644
index 0c399b0..0000000
--- a/ucm-config/veyron_fievel/ROCKCHIP-I2S/ROCKCHIP-I2S.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Rockchip card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/veyron_fievel/RockchipHDMI/HiFi.conf b/ucm-config/veyron_fievel/RockchipHDMI/HiFi.conf
deleted file mode 100644
index bf76d6a..0000000
--- a/ucm-config/veyron_fievel/RockchipHDMI/HiFi.conf
+++ /dev/null
@@ -1,12 +0,0 @@
-SectionVerb {
-	Value {
-		MinBufferLevel "512"
-	}
-}
-
-SectionDevice."HDMI".0 {
-	Value {
-		JackName "RockchipHDMI HDMI Jack"
-		EDIDFile "/sys/class/drm/card1-HDMI-A-1/edid"
-	}
-}
diff --git a/ucm-config/veyron_fievel/RockchipHDMI/RockchipHDMI.conf b/ucm-config/veyron_fievel/RockchipHDMI/RockchipHDMI.conf
deleted file mode 100644
index a167d74..0000000
--- a/ucm-config/veyron_fievel/RockchipHDMI/RockchipHDMI.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Rockchip HDMI card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/veyron_fievel/VEYRON-I2S/HiFi.conf b/ucm-config/veyron_fievel/VEYRON-I2S/HiFi.conf
deleted file mode 100644
index f23f3bc..0000000
--- a/ucm-config/veyron_fievel/VEYRON-I2S/HiFi.conf
+++ /dev/null
@@ -1,120 +0,0 @@
-SectionVerb {
-	Value {
-		OutputDspName "speaker_eq"
-		MinBufferLevel "512"
-		FullySpecifiedUCM "1"
-	}
-
-	EnableSequence [
-		cdev "hw:VEYRONI2S"
-
-		cset "name='Left Speaker Mixer Left DAC Switch' on"
-		cset "name='Right Speaker Mixer Right DAC Switch' on"
-		cset "name='Digital EQ 3 Band Switch' off"
-		cset "name='Digital EQ 5 Band Switch' off"
-		cset "name='Digital EQ 7 Band Switch' off"
-		cset "name='Biquad Switch' off"
-		cset "name='Filter Mode' Music"
-		cset "name='ADC Oversampling Rate' 0"
-
-		cset "name='DMIC Mux' DMIC"
-		cset "name='MIC2 Mux' IN34"
-		cset "name='Right ADC Mixer MIC2 Switch' on"
-		cset "name='Left ADC Mixer MIC2 Switch' on"
-		cset "name='MIC2 Volume' 20"
-		cset "name='Headset Mic Switch' off"
-		cset "name='Int Mic Switch' on"
-
-		cset "name='ADCR Boost Volume' 4"
-		cset "name='ADCL Boost Volume' 4"
-		cset "name='ADCR Volume' 11"
-		cset "name='ADCL Volume' 11"
-
-		cset "name='Left Speaker Mixer Left DAC Switch' on"
-		cset "name='Right Speaker Mixer Right DAC Switch' on"
-		cset "name='Speaker Left Mixer Volume' 2"
-		cset "name='Speaker Right Mixer Volume' 2"
-		cset "name='Record Path DC Blocking' on"
-		cset "name='Playback Path DC Blocking' on"
-
-		cset "name='Speaker Left Switch' off"
-		cset "name='Speaker Right Switch' off"
-		cset "name='Speaker Switch' off"
-
-		cset "name='Headphone Switch' off"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-		cset "name='Headphone Left Switch' on"
-		cset "name='Headphone Right Switch' on"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-		PlaybackPCM "hw:VEYRONI2S,0"
-		MixerName "Headphone"
-		JackType "gpio"
-		JackName "VEYRON-I2S Headset Jack"
-		OutputDspName ""
-	}
-
-	EnableSequence [
-		cdev "hw:VEYRONI2S"
-
-		cset "name='HP Left Out Switch' on"
-		cset "name='HP Right Out Switch' on"
-		cset "name='Headphone Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:VEYRONI2S"
-
-		cset "name='Headphone Switch' off"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-	]
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		CapturePCM "hw:VEYRONI2S,0"
-		MixerName "Headset Mic"
-		JackType "gpio"
-		JackName "VEYRON-I2S Headset Jack"
-	}
-
-	EnableSequence [
-		cdev "hw:VEYRONI2S"
-
-		cset "name='Int Mic Switch' off"
-		cset "name='DMIC Mux' ADC"
-		cset "name='Headset Mic Switch' on"
-		cset "name='Record Path DC Blocking' on"
-	]
-
-	DisableSequence [
-		cdev "hw:VEYRONI2S"
-
-		cset "name='Headset Mic Switch' off"
-		cset "name='DMIC Mux' DMIC"
-		cset "name='Int Mic Switch' on"
-		cset "name='Record Path DC Blocking' off"
-	]
-}
-
-SectionDevice."HDMI".0 {
-	Value {
-		PlaybackPCM "hw:VEYRONI2S,1"
-		JackName "VEYRON-I2S HDMI Jack"
-		JackType "gpio"
-		EDIDFile "/sys/class/drm/card1-HDMI-A-1/edid"
-	}
-	EnableSequence [
-		cdev "hw:VEYRONI2S"
-	]
-	DisableSequence [
-		cdev "hw:VEYRONI2S"
-	]
-}
diff --git a/ucm-config/veyron_fievel/VEYRON-I2S/VEYRON-I2S.conf b/ucm-config/veyron_fievel/VEYRON-I2S/VEYRON-I2S.conf
deleted file mode 100644
index 0c399b0..0000000
--- a/ucm-config/veyron_fievel/VEYRON-I2S/VEYRON-I2S.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Rockchip card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/veyron_jaq/ROCKCHIP-I2S/HiFi.conf b/ucm-config/veyron_jaq/ROCKCHIP-I2S/HiFi.conf
deleted file mode 100644
index 8523a81..0000000
--- a/ucm-config/veyron_jaq/ROCKCHIP-I2S/HiFi.conf
+++ /dev/null
@@ -1,142 +0,0 @@
-SectionVerb {
-	Value {
-		OutputDspName "speaker_eq"
-		MinBufferLevel "512"
-		FullySpecifiedUCM "1"
-	}
-
-	EnableSequence [
-		cdev "hw:ROCKCHIPI2S"
-
-		cset "name='Left Speaker Mixer Left DAC Switch' on"
-		cset "name='Right Speaker Mixer Right DAC Switch' on"
-		cset "name='Headphone Left Switch' off"
-		cset "name='Headphone Right Switch' off"
-		cset "name='Digital EQ 3 Band Switch' off"
-		cset "name='Digital EQ 5 Band Switch' off"
-		cset "name='Digital EQ 7 Band Switch' off"
-		cset "name='Biquad Switch' off"
-		cset "name='Filter Mode' Music"
-		cset "name='ADC Oversampling Rate' 0"
-
-		cset "name='DMIC Mux' DMIC"
-		cset "name='MIC2 Mux' IN34"
-		cset "name='Right ADC Mixer MIC2 Switch' on"
-		cset "name='Left ADC Mixer MIC2 Switch' on"
-		cset "name='MIC2 Volume' 20"
-		cset "name='Headset Mic Switch' off"
-		cset "name='Int Mic Switch' on"
-
-		cset "name='ADCR Boost Volume' 4"
-		cset "name='ADCL Boost Volume' 4"
-		cset "name='ADCR Volume' 11"
-		cset "name='ADCL Volume' 11"
-
-		cset "name='Left Speaker Mixer Left DAC Switch' on"
-		cset "name='Right Speaker Mixer Right DAC Switch' on"
-		cset "name='Speaker Left Mixer Volume' 2"
-		cset "name='Speaker Right Mixer Volume' 2"
-		cset "name='Record Path DC Blocking' on"
-		cset "name='Playback Path DC Blocking' on"
-
-		cset "name='Speaker Left Switch' on"
-		cset "name='Speaker Right Switch' on"
-		cset "name='Speaker Switch' on"
-
-		cset "name='Left Speaker Mixer Left DAC Switch' off"
-		cset "name='Right Speaker Mixer Right DAC Switch' off"
-		cset "name='Left Speaker Mixer Right DAC Switch' on"
-		cset "name='Right Speaker Mixer Left DAC Switch' on"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionDevice."Speaker".0 {
-	Value {
-		PlaybackPCM "hw:ROCKCHIPI2S,0"
-		MixerName "Speaker"
-	}
-	EnableSequence [
-		cdev "hw:ROCKCHIPI2S"
-
-		cset "name='Speaker Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:ROCKCHIPI2S"
-
-		cset "name='Speaker Switch' off"
-	]
-}
-
-SectionDevice."Internal Mic".0 {
-	Value {
-		CapturePCM "hw:ROCKCHIPI2S,0"
-		MixerName "Int Mic"
-	}
-	EnableSequence [
-		cdev "hw:ROCKCHIPI2S"
-
-		cset "name='Int Mic Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:ROCKCHIPI2S"
-
-		cset "name='Int Mic Switch' off"
-	]
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-		PlaybackPCM "hw:ROCKCHIPI2S,0"
-		MixerName "Headphone"
-		JackType "gpio"
-		JackName "ROCKCHIP-I2S Headset Jack"
-		DspName ""
-	}
-
-	EnableSequence [
-		cdev "hw:ROCKCHIPI2S"
-
-		cset "name='Speaker Switch' off"
-		cset "name='Headphone Switch' on"
-		cset "name='Headphone Left Switch' on"
-		cset "name='Headphone Right Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:ROCKCHIPI2S"
-
-		cset "name='Headphone Left Switch' off"
-		cset "name='Headphone Right Switch' off"
-		cset "name='Headphone Switch' off"
-		cset "name='Speaker Switch' on"
-	]
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		CapturePCM "hw:ROCKCHIPI2S,0"
-		MixerName "Headset Mic"
-		JackType "gpio"
-		JackName "ROCKCHIP-I2S Headset Jack"
-	}
-
-	EnableSequence [
-		cdev "hw:ROCKCHIPI2S"
-
-		cset "name='Int Mic Switch' off"
-		cset "name='DMIC Mux' ADC"
-		cset "name='Headset Mic Switch' on"
-		cset "name='Record Path DC Blocking' on"
-	]
-
-	DisableSequence [
-		cdev "hw:ROCKCHIPI2S"
-
-		cset "name='Headset Mic Switch' off"
-		cset "name='DMIC Mux' DMIC"
-		cset "name='Int Mic Switch' on"
-		cset "name='Record Path DC Blocking' off"
-	]
-}
diff --git a/ucm-config/veyron_jaq/ROCKCHIP-I2S/ROCKCHIP-I2S.conf b/ucm-config/veyron_jaq/ROCKCHIP-I2S/ROCKCHIP-I2S.conf
deleted file mode 100644
index 0c399b0..0000000
--- a/ucm-config/veyron_jaq/ROCKCHIP-I2S/ROCKCHIP-I2S.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Rockchip card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/veyron_jaq/RockchipHDMI/HiFi.conf b/ucm-config/veyron_jaq/RockchipHDMI/HiFi.conf
deleted file mode 100644
index bf76d6a..0000000
--- a/ucm-config/veyron_jaq/RockchipHDMI/HiFi.conf
+++ /dev/null
@@ -1,12 +0,0 @@
-SectionVerb {
-	Value {
-		MinBufferLevel "512"
-	}
-}
-
-SectionDevice."HDMI".0 {
-	Value {
-		JackName "RockchipHDMI HDMI Jack"
-		EDIDFile "/sys/class/drm/card1-HDMI-A-1/edid"
-	}
-}
diff --git a/ucm-config/veyron_jaq/RockchipHDMI/RockchipHDMI.conf b/ucm-config/veyron_jaq/RockchipHDMI/RockchipHDMI.conf
deleted file mode 100644
index a167d74..0000000
--- a/ucm-config/veyron_jaq/RockchipHDMI/RockchipHDMI.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Rockchip HDMI card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/veyron_jaq/VEYRON-I2S/HiFi.conf b/ucm-config/veyron_jaq/VEYRON-I2S/HiFi.conf
deleted file mode 100644
index 2698529..0000000
--- a/ucm-config/veyron_jaq/VEYRON-I2S/HiFi.conf
+++ /dev/null
@@ -1,158 +0,0 @@
-SectionVerb {
-	Value {
-		OutputDspName "speaker_eq"
-		MinBufferLevel "512"
-		FullySpecifiedUCM "1"
-	}
-
-	EnableSequence [
-		cdev "hw:VEYRONI2S"
-
-		cset "name='Left Speaker Mixer Left DAC Switch' on"
-		cset "name='Right Speaker Mixer Right DAC Switch' on"
-		cset "name='Digital EQ 3 Band Switch' off"
-		cset "name='Digital EQ 5 Band Switch' off"
-		cset "name='Digital EQ 7 Band Switch' off"
-		cset "name='Biquad Switch' off"
-		cset "name='Filter Mode' Music"
-		cset "name='ADC Oversampling Rate' 0"
-
-		cset "name='DMIC Mux' DMIC"
-		cset "name='MIC2 Mux' IN34"
-		cset "name='Right ADC Mixer MIC2 Switch' on"
-		cset "name='Left ADC Mixer MIC2 Switch' on"
-		cset "name='MIC2 Volume' 20"
-		cset "name='Headset Mic Switch' off"
-		cset "name='Int Mic Switch' on"
-
-		cset "name='ADCR Boost Volume' 4"
-		cset "name='ADCL Boost Volume' 4"
-		cset "name='ADCR Volume' 11"
-		cset "name='ADCL Volume' 11"
-
-		cset "name='Left Speaker Mixer Left DAC Switch' on"
-		cset "name='Right Speaker Mixer Right DAC Switch' on"
-		cset "name='Speaker Left Mixer Volume' 2"
-		cset "name='Speaker Right Mixer Volume' 2"
-		cset "name='Record Path DC Blocking' on"
-		cset "name='Playback Path DC Blocking' on"
-
-		cset "name='Speaker Left Switch' off"
-		cset "name='Speaker Right Switch' off"
-		cset "name='Speaker Switch' off"
-
-		cset "name='Headphone Switch' off"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-		cset "name='Headphone Left Switch' on"
-		cset "name='Headphone Right Switch' on"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionDevice."Speaker".0 {
-	Value {
-		PlaybackPCM "hw:VEYRONI2S,0"
-		MixerName "Speaker"
-	}
-	EnableSequence [
-		cdev "hw:VEYRONI2S"
-
-		cset "name='Speaker Left Switch' on"
-		cset "name='Speaker Right Switch' on"
-		cset "name='Speaker Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:VEYRONI2S"
-
-		cset "name='Speaker Switch' off"
-		cset "name='Speaker Left Switch' off"
-		cset "name='Speaker Right Switch' off"
-	]
-}
-
-SectionDevice."Internal Mic".0 {
-	Value {
-		CapturePCM "hw:VEYRONI2S,0"
-		MixerName "Int Mic"
-	}
-	EnableSequence [
-		cdev "hw:VEYRONI2S"
-
-		cset "name='Int Mic Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:VEYRONI2S"
-
-		cset "name='Int Mic Switch' off"
-	]
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-		PlaybackPCM "hw:VEYRONI2S,0"
-		MixerName "Headphone"
-		JackType "gpio"
-		JackName "VEYRON-I2S Headset Jack"
-		OutputDspName ""
-	}
-
-	EnableSequence [
-		cdev "hw:VEYRONI2S"
-
-		cset "name='HP Left Out Switch' on"
-		cset "name='HP Right Out Switch' on"
-		cset "name='Headphone Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:VEYRONI2S"
-
-		cset "name='Headphone Switch' off"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-	]
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		CapturePCM "hw:VEYRONI2S,0"
-		MixerName "Headset Mic"
-		JackType "gpio"
-		JackName "VEYRON-I2S Headset Jack"
-	}
-
-	EnableSequence [
-		cdev "hw:VEYRONI2S"
-
-		cset "name='Int Mic Switch' off"
-		cset "name='DMIC Mux' ADC"
-		cset "name='Headset Mic Switch' on"
-		cset "name='Record Path DC Blocking' on"
-	]
-
-	DisableSequence [
-		cdev "hw:VEYRONI2S"
-
-		cset "name='Headset Mic Switch' off"
-		cset "name='DMIC Mux' DMIC"
-		cset "name='Int Mic Switch' on"
-		cset "name='Record Path DC Blocking' off"
-	]
-}
-
-SectionDevice."HDMI".0 {
-	Value {
-		PlaybackPCM "hw:VEYRONI2S,1"
-		JackName "VEYRON-I2S HDMI Jack"
-		JackType "gpio"
-		EDIDFile "/sys/class/drm/card1-HDMI-A-1/edid"
-	}
-	EnableSequence [
-		cdev "hw:VEYRONI2S"
-	]
-	DisableSequence [
-		cdev "hw:VEYRONI2S"
-	]
-}
diff --git a/ucm-config/veyron_jaq/VEYRON-I2S/VEYRON-I2S.conf b/ucm-config/veyron_jaq/VEYRON-I2S/VEYRON-I2S.conf
deleted file mode 100644
index 0c399b0..0000000
--- a/ucm-config/veyron_jaq/VEYRON-I2S/VEYRON-I2S.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Rockchip card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/veyron_minnie-kernelnext b/ucm-config/veyron_minnie-kernelnext
deleted file mode 120000
index 5fea6bb..0000000
--- a/ucm-config/veyron_minnie-kernelnext
+++ /dev/null
@@ -1 +0,0 @@
-veyron_minnie
\ No newline at end of file
diff --git a/ucm-config/veyron_minnie/ROCKCHIP-I2S/HiFi.conf b/ucm-config/veyron_minnie/ROCKCHIP-I2S/HiFi.conf
deleted file mode 100644
index bcfe915..0000000
--- a/ucm-config/veyron_minnie/ROCKCHIP-I2S/HiFi.conf
+++ /dev/null
@@ -1,137 +0,0 @@
-SectionVerb {
-	Value {
-		OutputDspName "speaker_eq"
-		MinBufferLevel "512"
-		FullySpecifiedUCM "1"
-	}
-
-	EnableSequence [
-		cdev "hw:ROCKCHIPI2S"
-
-		cset "name='Left Speaker Mixer Left DAC Switch' on"
-		cset "name='Right Speaker Mixer Right DAC Switch' on"
-		cset "name='Headphone Left Switch' off"
-		cset "name='Headphone Right Switch' off"
-		cset "name='Digital EQ 3 Band Switch' off"
-		cset "name='Digital EQ 5 Band Switch' off"
-		cset "name='Digital EQ 7 Band Switch' off"
-		cset "name='Biquad Switch' off"
-		cset "name='Filter Mode' Music"
-		cset "name='ADC Oversampling Rate' 0"
-
-		cset "name='DMIC Mux' DMIC"
-		cset "name='MIC2 Mux' IN34"
-		cset "name='Right ADC Mixer MIC2 Switch' on"
-		cset "name='Left ADC Mixer MIC2 Switch' on"
-		cset "name='MIC2 Volume' 20"
-		cset "name='Headset Mic Switch' off"
-		cset "name='Int Mic Switch' on"
-
-		cset "name='ADCR Boost Volume' 4"
-		cset "name='ADCL Boost Volume' 4"
-		cset "name='ADCR Volume' 8"
-		cset "name='ADCL Volume' 8"
-
-		cset "name='Left Speaker Mixer Left DAC Switch' on"
-		cset "name='Right Speaker Mixer Right DAC Switch' on"
-		cset "name='Speaker Left Mixer Volume' 2"
-		cset "name='Speaker Right Mixer Volume' 2"
-		cset "name='Record Path DC Blocking' on"
-		cset "name='Playback Path DC Blocking' on"
-
-		cset "name='Speaker Left Switch' on"
-		cset "name='Speaker Right Switch' on"
-		cset "name='Speaker Switch' on"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionDevice."Speaker".0 {
-	Value {
-		PlaybackPCM "hw:ROCKCHIPI2S,0"
-		MixerName "Speaker"
-	}
-	EnableSequence [
-		cdev "hw:ROCKCHIPI2S"
-
-		cset "name='Speaker Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:ROCKCHIPI2S"
-
-		cset "name='Speaker Switch' off"
-	]
-}
-
-SectionDevice."Internal Mic".0 {
-	Value {
-		CapturePCM "hw:ROCKCHIPI2S,0"
-		MixerName "Int Mic"
-	}
-	EnableSequence [
-		cdev "hw:ROCKCHIPI2S"
-
-		cset "name='Int Mic Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:ROCKCHIPI2S"
-
-		cset "name='Int Mic Switch' off"
-	]
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-		PlaybackPCM "hw:ROCKCHIPI2S,0"
-		MixerName "Headphone"
-		JackType "gpio"
-		JackName "ROCKCHIP-I2S Headset Jack"
-		DspName ""
-	}
-
-	EnableSequence [
-		cdev "hw:ROCKCHIPI2S"
-
-		cset "name='Speaker Switch' off"
-		cset "name='Headphone Switch' on"
-		cset "name='Headphone Left Switch' on"
-		cset "name='Headphone Right Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:ROCKCHIPI2S"
-
-		cset "name='Headphone Left Switch' off"
-		cset "name='Headphone Right Switch' off"
-		cset "name='Headphone Switch' off"
-		cset "name='Speaker Switch' on"
-	]
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		CapturePCM "hw:ROCKCHIPI2S,0"
-		MixerName "Headset Mic"
-		JackType "gpio"
-		JackName "ROCKCHIP-I2S Headset Jack"
-	}
-
-	EnableSequence [
-		cdev "hw:ROCKCHIPI2S"
-
-		cset "name='Int Mic Switch' off"
-		cset "name='DMIC Mux' ADC"
-		cset "name='Headset Mic Switch' on"
-		cset "name='Record Path DC Blocking' on"
-	]
-
-	DisableSequence [
-		cdev "hw:ROCKCHIPI2S"
-
-		cset "name='Headset Mic Switch' off"
-		cset "name='DMIC Mux' DMIC"
-		cset "name='Int Mic Switch' on"
-		cset "name='Record Path DC Blocking' off"
-	]
-}
diff --git a/ucm-config/veyron_minnie/ROCKCHIP-I2S/ROCKCHIP-I2S.conf b/ucm-config/veyron_minnie/ROCKCHIP-I2S/ROCKCHIP-I2S.conf
deleted file mode 100644
index 0c399b0..0000000
--- a/ucm-config/veyron_minnie/ROCKCHIP-I2S/ROCKCHIP-I2S.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Rockchip card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/veyron_minnie/RockchipHDMI/HiFi.conf b/ucm-config/veyron_minnie/RockchipHDMI/HiFi.conf
deleted file mode 100644
index bf76d6a..0000000
--- a/ucm-config/veyron_minnie/RockchipHDMI/HiFi.conf
+++ /dev/null
@@ -1,12 +0,0 @@
-SectionVerb {
-	Value {
-		MinBufferLevel "512"
-	}
-}
-
-SectionDevice."HDMI".0 {
-	Value {
-		JackName "RockchipHDMI HDMI Jack"
-		EDIDFile "/sys/class/drm/card1-HDMI-A-1/edid"
-	}
-}
diff --git a/ucm-config/veyron_minnie/RockchipHDMI/RockchipHDMI.conf b/ucm-config/veyron_minnie/RockchipHDMI/RockchipHDMI.conf
deleted file mode 100644
index a167d74..0000000
--- a/ucm-config/veyron_minnie/RockchipHDMI/RockchipHDMI.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Rockchip HDMI card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/veyron_minnie/VEYRON-I2S/HiFi.conf b/ucm-config/veyron_minnie/VEYRON-I2S/HiFi.conf
deleted file mode 100644
index fd4e6a8..0000000
--- a/ucm-config/veyron_minnie/VEYRON-I2S/HiFi.conf
+++ /dev/null
@@ -1,158 +0,0 @@
-SectionVerb {
-	Value {
-		OutputDspName "speaker_eq"
-		MinBufferLevel "512"
-		FullySpecifiedUCM "1"
-	}
-
-	EnableSequence [
-		cdev "hw:VEYRONI2S"
-
-		cset "name='Left Speaker Mixer Left DAC Switch' on"
-		cset "name='Right Speaker Mixer Right DAC Switch' on"
-		cset "name='Digital EQ 3 Band Switch' off"
-		cset "name='Digital EQ 5 Band Switch' off"
-		cset "name='Digital EQ 7 Band Switch' off"
-		cset "name='Biquad Switch' off"
-		cset "name='Filter Mode' Music"
-		cset "name='ADC Oversampling Rate' 0"
-
-		cset "name='DMIC Mux' DMIC"
-		cset "name='MIC2 Mux' IN34"
-		cset "name='Right ADC Mixer MIC2 Switch' on"
-		cset "name='Left ADC Mixer MIC2 Switch' on"
-		cset "name='MIC2 Volume' 20"
-		cset "name='Headset Mic Switch' off"
-		cset "name='Int Mic Switch' on"
-
-		cset "name='ADCR Boost Volume' 4"
-		cset "name='ADCL Boost Volume' 4"
-		cset "name='ADCR Volume' 8"
-		cset "name='ADCL Volume' 8"
-
-		cset "name='Left Speaker Mixer Left DAC Switch' on"
-		cset "name='Right Speaker Mixer Right DAC Switch' on"
-		cset "name='Speaker Left Mixer Volume' 2"
-		cset "name='Speaker Right Mixer Volume' 2"
-		cset "name='Record Path DC Blocking' on"
-		cset "name='Playback Path DC Blocking' on"
-
-		cset "name='Speaker Left Switch' off"
-		cset "name='Speaker Right Switch' off"
-		cset "name='Speaker Switch' off"
-
-		cset "name='Headphone Switch' off"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-		cset "name='Headphone Left Switch' on"
-		cset "name='Headphone Right Switch' on"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionDevice."Speaker".0 {
-	Value {
-		PlaybackPCM "hw:VEYRONI2S,0"
-		MixerName "Speaker"
-	}
-	EnableSequence [
-		cdev "hw:VEYRONI2S"
-
-		cset "name='Speaker Left Switch' on"
-		cset "name='Speaker Right Switch' on"
-		cset "name='Speaker Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:VEYRONI2S"
-
-		cset "name='Speaker Switch' off"
-		cset "name='Speaker Left Switch' off"
-		cset "name='Speaker Right Switch' off"
-	]
-}
-
-SectionDevice."Internal Mic".0 {
-	Value {
-		CapturePCM "hw:VEYRONI2S,0"
-		MixerName "Int Mic"
-	}
-	EnableSequence [
-		cdev "hw:VEYRONI2S"
-
-		cset "name='Int Mic Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:VEYRONI2S"
-
-		cset "name='Int Mic Switch' off"
-	]
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-		PlaybackPCM "hw:VEYRONI2S,0"
-		MixerName "Headphone"
-		JackType "gpio"
-		JackName "VEYRON-I2S Headset Jack"
-		OutputDspName ""
-	}
-
-	EnableSequence [
-		cdev "hw:VEYRONI2S"
-
-		cset "name='HP Left Out Switch' on"
-		cset "name='HP Right Out Switch' on"
-		cset "name='Headphone Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:VEYRONI2S"
-
-		cset "name='Headphone Switch' off"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-	]
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		CapturePCM "hw:VEYRONI2S,0"
-		MixerName "Headset Mic"
-		JackType "gpio"
-		JackName "VEYRON-I2S Headset Jack"
-	}
-
-	EnableSequence [
-		cdev "hw:VEYRONI2S"
-
-		cset "name='Int Mic Switch' off"
-		cset "name='DMIC Mux' ADC"
-		cset "name='Headset Mic Switch' on"
-		cset "name='Record Path DC Blocking' on"
-	]
-
-	DisableSequence [
-		cdev "hw:VEYRONI2S"
-
-		cset "name='Headset Mic Switch' off"
-		cset "name='DMIC Mux' DMIC"
-		cset "name='Int Mic Switch' on"
-		cset "name='Record Path DC Blocking' off"
-	]
-}
-
-SectionDevice."HDMI".0 {
-	Value {
-		PlaybackPCM "hw:VEYRONI2S,1"
-		JackName "VEYRON-I2S HDMI Jack"
-		JackType "gpio"
-		EDIDFile "/sys/class/drm/card1-HDMI-A-1/edid"
-	}
-	EnableSequence [
-		cdev "hw:VEYRONI2S"
-	]
-	DisableSequence [
-		cdev "hw:VEYRONI2S"
-	]
-}
diff --git a/ucm-config/veyron_minnie/VEYRON-I2S/VEYRON-I2S.conf b/ucm-config/veyron_minnie/VEYRON-I2S/VEYRON-I2S.conf
deleted file mode 100644
index 0c399b0..0000000
--- a/ucm-config/veyron_minnie/VEYRON-I2S/VEYRON-I2S.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Rockchip card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/veyron_speedy/ROCKCHIP-I2S/HiFi.conf b/ucm-config/veyron_speedy/ROCKCHIP-I2S/HiFi.conf
deleted file mode 100644
index bcfe915..0000000
--- a/ucm-config/veyron_speedy/ROCKCHIP-I2S/HiFi.conf
+++ /dev/null
@@ -1,137 +0,0 @@
-SectionVerb {
-	Value {
-		OutputDspName "speaker_eq"
-		MinBufferLevel "512"
-		FullySpecifiedUCM "1"
-	}
-
-	EnableSequence [
-		cdev "hw:ROCKCHIPI2S"
-
-		cset "name='Left Speaker Mixer Left DAC Switch' on"
-		cset "name='Right Speaker Mixer Right DAC Switch' on"
-		cset "name='Headphone Left Switch' off"
-		cset "name='Headphone Right Switch' off"
-		cset "name='Digital EQ 3 Band Switch' off"
-		cset "name='Digital EQ 5 Band Switch' off"
-		cset "name='Digital EQ 7 Band Switch' off"
-		cset "name='Biquad Switch' off"
-		cset "name='Filter Mode' Music"
-		cset "name='ADC Oversampling Rate' 0"
-
-		cset "name='DMIC Mux' DMIC"
-		cset "name='MIC2 Mux' IN34"
-		cset "name='Right ADC Mixer MIC2 Switch' on"
-		cset "name='Left ADC Mixer MIC2 Switch' on"
-		cset "name='MIC2 Volume' 20"
-		cset "name='Headset Mic Switch' off"
-		cset "name='Int Mic Switch' on"
-
-		cset "name='ADCR Boost Volume' 4"
-		cset "name='ADCL Boost Volume' 4"
-		cset "name='ADCR Volume' 8"
-		cset "name='ADCL Volume' 8"
-
-		cset "name='Left Speaker Mixer Left DAC Switch' on"
-		cset "name='Right Speaker Mixer Right DAC Switch' on"
-		cset "name='Speaker Left Mixer Volume' 2"
-		cset "name='Speaker Right Mixer Volume' 2"
-		cset "name='Record Path DC Blocking' on"
-		cset "name='Playback Path DC Blocking' on"
-
-		cset "name='Speaker Left Switch' on"
-		cset "name='Speaker Right Switch' on"
-		cset "name='Speaker Switch' on"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionDevice."Speaker".0 {
-	Value {
-		PlaybackPCM "hw:ROCKCHIPI2S,0"
-		MixerName "Speaker"
-	}
-	EnableSequence [
-		cdev "hw:ROCKCHIPI2S"
-
-		cset "name='Speaker Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:ROCKCHIPI2S"
-
-		cset "name='Speaker Switch' off"
-	]
-}
-
-SectionDevice."Internal Mic".0 {
-	Value {
-		CapturePCM "hw:ROCKCHIPI2S,0"
-		MixerName "Int Mic"
-	}
-	EnableSequence [
-		cdev "hw:ROCKCHIPI2S"
-
-		cset "name='Int Mic Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:ROCKCHIPI2S"
-
-		cset "name='Int Mic Switch' off"
-	]
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-		PlaybackPCM "hw:ROCKCHIPI2S,0"
-		MixerName "Headphone"
-		JackType "gpio"
-		JackName "ROCKCHIP-I2S Headset Jack"
-		DspName ""
-	}
-
-	EnableSequence [
-		cdev "hw:ROCKCHIPI2S"
-
-		cset "name='Speaker Switch' off"
-		cset "name='Headphone Switch' on"
-		cset "name='Headphone Left Switch' on"
-		cset "name='Headphone Right Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:ROCKCHIPI2S"
-
-		cset "name='Headphone Left Switch' off"
-		cset "name='Headphone Right Switch' off"
-		cset "name='Headphone Switch' off"
-		cset "name='Speaker Switch' on"
-	]
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		CapturePCM "hw:ROCKCHIPI2S,0"
-		MixerName "Headset Mic"
-		JackType "gpio"
-		JackName "ROCKCHIP-I2S Headset Jack"
-	}
-
-	EnableSequence [
-		cdev "hw:ROCKCHIPI2S"
-
-		cset "name='Int Mic Switch' off"
-		cset "name='DMIC Mux' ADC"
-		cset "name='Headset Mic Switch' on"
-		cset "name='Record Path DC Blocking' on"
-	]
-
-	DisableSequence [
-		cdev "hw:ROCKCHIPI2S"
-
-		cset "name='Headset Mic Switch' off"
-		cset "name='DMIC Mux' DMIC"
-		cset "name='Int Mic Switch' on"
-		cset "name='Record Path DC Blocking' off"
-	]
-}
diff --git a/ucm-config/veyron_speedy/ROCKCHIP-I2S/ROCKCHIP-I2S.conf b/ucm-config/veyron_speedy/ROCKCHIP-I2S/ROCKCHIP-I2S.conf
deleted file mode 100644
index 0c399b0..0000000
--- a/ucm-config/veyron_speedy/ROCKCHIP-I2S/ROCKCHIP-I2S.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Rockchip card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/veyron_speedy/RockchipHDMI/HiFi.conf b/ucm-config/veyron_speedy/RockchipHDMI/HiFi.conf
deleted file mode 100644
index bf76d6a..0000000
--- a/ucm-config/veyron_speedy/RockchipHDMI/HiFi.conf
+++ /dev/null
@@ -1,12 +0,0 @@
-SectionVerb {
-	Value {
-		MinBufferLevel "512"
-	}
-}
-
-SectionDevice."HDMI".0 {
-	Value {
-		JackName "RockchipHDMI HDMI Jack"
-		EDIDFile "/sys/class/drm/card1-HDMI-A-1/edid"
-	}
-}
diff --git a/ucm-config/veyron_speedy/RockchipHDMI/RockchipHDMI.conf b/ucm-config/veyron_speedy/RockchipHDMI/RockchipHDMI.conf
deleted file mode 100644
index a167d74..0000000
--- a/ucm-config/veyron_speedy/RockchipHDMI/RockchipHDMI.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Rockchip HDMI card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/veyron_speedy/VEYRON-I2S/HiFi.conf b/ucm-config/veyron_speedy/VEYRON-I2S/HiFi.conf
deleted file mode 100644
index fd4e6a8..0000000
--- a/ucm-config/veyron_speedy/VEYRON-I2S/HiFi.conf
+++ /dev/null
@@ -1,158 +0,0 @@
-SectionVerb {
-	Value {
-		OutputDspName "speaker_eq"
-		MinBufferLevel "512"
-		FullySpecifiedUCM "1"
-	}
-
-	EnableSequence [
-		cdev "hw:VEYRONI2S"
-
-		cset "name='Left Speaker Mixer Left DAC Switch' on"
-		cset "name='Right Speaker Mixer Right DAC Switch' on"
-		cset "name='Digital EQ 3 Band Switch' off"
-		cset "name='Digital EQ 5 Band Switch' off"
-		cset "name='Digital EQ 7 Band Switch' off"
-		cset "name='Biquad Switch' off"
-		cset "name='Filter Mode' Music"
-		cset "name='ADC Oversampling Rate' 0"
-
-		cset "name='DMIC Mux' DMIC"
-		cset "name='MIC2 Mux' IN34"
-		cset "name='Right ADC Mixer MIC2 Switch' on"
-		cset "name='Left ADC Mixer MIC2 Switch' on"
-		cset "name='MIC2 Volume' 20"
-		cset "name='Headset Mic Switch' off"
-		cset "name='Int Mic Switch' on"
-
-		cset "name='ADCR Boost Volume' 4"
-		cset "name='ADCL Boost Volume' 4"
-		cset "name='ADCR Volume' 8"
-		cset "name='ADCL Volume' 8"
-
-		cset "name='Left Speaker Mixer Left DAC Switch' on"
-		cset "name='Right Speaker Mixer Right DAC Switch' on"
-		cset "name='Speaker Left Mixer Volume' 2"
-		cset "name='Speaker Right Mixer Volume' 2"
-		cset "name='Record Path DC Blocking' on"
-		cset "name='Playback Path DC Blocking' on"
-
-		cset "name='Speaker Left Switch' off"
-		cset "name='Speaker Right Switch' off"
-		cset "name='Speaker Switch' off"
-
-		cset "name='Headphone Switch' off"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-		cset "name='Headphone Left Switch' on"
-		cset "name='Headphone Right Switch' on"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionDevice."Speaker".0 {
-	Value {
-		PlaybackPCM "hw:VEYRONI2S,0"
-		MixerName "Speaker"
-	}
-	EnableSequence [
-		cdev "hw:VEYRONI2S"
-
-		cset "name='Speaker Left Switch' on"
-		cset "name='Speaker Right Switch' on"
-		cset "name='Speaker Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:VEYRONI2S"
-
-		cset "name='Speaker Switch' off"
-		cset "name='Speaker Left Switch' off"
-		cset "name='Speaker Right Switch' off"
-	]
-}
-
-SectionDevice."Internal Mic".0 {
-	Value {
-		CapturePCM "hw:VEYRONI2S,0"
-		MixerName "Int Mic"
-	}
-	EnableSequence [
-		cdev "hw:VEYRONI2S"
-
-		cset "name='Int Mic Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:VEYRONI2S"
-
-		cset "name='Int Mic Switch' off"
-	]
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-		PlaybackPCM "hw:VEYRONI2S,0"
-		MixerName "Headphone"
-		JackType "gpio"
-		JackName "VEYRON-I2S Headset Jack"
-		OutputDspName ""
-	}
-
-	EnableSequence [
-		cdev "hw:VEYRONI2S"
-
-		cset "name='HP Left Out Switch' on"
-		cset "name='HP Right Out Switch' on"
-		cset "name='Headphone Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:VEYRONI2S"
-
-		cset "name='Headphone Switch' off"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-	]
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		CapturePCM "hw:VEYRONI2S,0"
-		MixerName "Headset Mic"
-		JackType "gpio"
-		JackName "VEYRON-I2S Headset Jack"
-	}
-
-	EnableSequence [
-		cdev "hw:VEYRONI2S"
-
-		cset "name='Int Mic Switch' off"
-		cset "name='DMIC Mux' ADC"
-		cset "name='Headset Mic Switch' on"
-		cset "name='Record Path DC Blocking' on"
-	]
-
-	DisableSequence [
-		cdev "hw:VEYRONI2S"
-
-		cset "name='Headset Mic Switch' off"
-		cset "name='DMIC Mux' DMIC"
-		cset "name='Int Mic Switch' on"
-		cset "name='Record Path DC Blocking' off"
-	]
-}
-
-SectionDevice."HDMI".0 {
-	Value {
-		PlaybackPCM "hw:VEYRONI2S,1"
-		JackName "VEYRON-I2S HDMI Jack"
-		JackType "gpio"
-		EDIDFile "/sys/class/drm/card1-HDMI-A-1/edid"
-	}
-	EnableSequence [
-		cdev "hw:VEYRONI2S"
-	]
-	DisableSequence [
-		cdev "hw:VEYRONI2S"
-	]
-}
diff --git a/ucm-config/veyron_speedy/VEYRON-I2S/VEYRON-I2S.conf b/ucm-config/veyron_speedy/VEYRON-I2S/VEYRON-I2S.conf
deleted file mode 100644
index 0c399b0..0000000
--- a/ucm-config/veyron_speedy/VEYRON-I2S/VEYRON-I2S.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Rockchip card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/wolf/HDA Intel PCH/HDA Intel PCH.conf b/ucm-config/wolf/HDA Intel PCH/HDA Intel PCH.conf
deleted file mode 100644
index 8c8d57e..0000000
--- a/ucm-config/wolf/HDA Intel PCH/HDA Intel PCH.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Wolf internal card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/wolf/HDA Intel PCH/HiFi.conf b/ucm-config/wolf/HDA Intel PCH/HiFi.conf
deleted file mode 100644
index 03f431e..0000000
--- a/ucm-config/wolf/HDA Intel PCH/HiFi.conf
+++ /dev/null
@@ -1,55 +0,0 @@
-SectionVerb {
-	Value {
-		OutputDspName "speaker_eq"
-	}
-	EnableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Master Playback Switch' on"
-		cset "name='Headphone Playback Switch' off"
-		cset "name='Speaker Playback Switch' on"
-
-		cset "name='Capture Switch' on"
-		cset "name='Capture Volume' 39"
-		cset "name='Mic Boost Volume' 2"
-		cset "name='Internal Mic Boost Volume' 0"
-		cset "name='Capture Source' 0"
-	]
-	DisableSequence [
-	]
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-		JackName "Headphone Jack"
-		DspName ""
-	}
-	EnableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Speaker Playback Switch' off"
-		cset "name='Headphone Playback Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Headphone Playback Switch' off"
-		cset "name='Speaker Playback Switch' on"
-	]
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		JackName "Mic Jack"
-	}
-	EnableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Capture Source' 1"
-	]
-	DisableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Capture Source' 0"
-	]
-}
diff --git a/ucm-config/zako/HDA Intel PCH/HDA Intel PCH.conf b/ucm-config/zako/HDA Intel PCH/HDA Intel PCH.conf
deleted file mode 100644
index 81234e1..0000000
--- a/ucm-config/zako/HDA Intel PCH/HDA Intel PCH.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Zako internal card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/zako/HDA Intel PCH/HiFi.conf b/ucm-config/zako/HDA Intel PCH/HiFi.conf
deleted file mode 100644
index 5e280c2..0000000
--- a/ucm-config/zako/HDA Intel PCH/HiFi.conf
+++ /dev/null
@@ -1,32 +0,0 @@
-SectionVerb {
-	Value {
-		NoCreateDefaultOutputNode "1"
-		NoCreateDefaultInputNode "1"
-	}
-	EnableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Capture Switch' off"
-		cset "name='Capture Volume' 23"
-		cset "name='Master Playback Switch' on"
-		cset "name='Master Playback Volume' 87"
-	]
-	DisableSequence [
-	]
-}
-
-SectionDevice."Mic".0 {
-        Value {
-		JackName "Mic Jack"
-        }
-	EnableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Capture Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:PCH"
-
-		cset "name='Capture Switch' off"
-	]
-}
diff --git a/unblocked_terms.txt b/unblocked_terms.txt
new file mode 100644
index 0000000..cba7545
--- /dev/null
+++ b/unblocked_terms.txt
@@ -0,0 +1,10 @@
+# KEEP THIS COMMENT IN YOUR COPY.
+#
+# Don't delete this file if you want to keep keyword_check enabled even if it's
+# empty.
+#
+# See repohooks/README.md for more details.
+
+master
+\bnative
+white.?list