Merge remote-tracking branch 'cros/master' into HEAD

Nothing currently builds the master branch here.  Update it for use on
Bertha.

Change-Id: I9e3e2c4e2a50383e810b21608b61f72c3027c72c
Signed-off-by: Dylan Reid <dgreid@chromium.org>
diff --git a/.gitignore b/.gitignore
index 98c5c08..2854f05 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
 build
 cscope.*
+PRESUBMIT.cfg
diff --git a/Makefile b/Makefile
index 33b631b..2be9ec9 100644
--- a/Makefile
+++ b/Makefile
@@ -19,9 +19,36 @@
 	$(INSTALL) --mode 755 -D $(ADHD_DIR)/scripts/audio_diagnostics \
 		$(DESTDIR)usr/bin/
 
-$(DESTDIR)/etc/init/cras.conf:	$(ADHD_DIR)/upstart/cras.conf
-	$(ECHO) "Installing '$<' to '$@'"
-	$(INSTALL) --mode 644 -D $< $@
+cras_init_upstart:	$(ADHD_DIR)/init/cras.conf
+	$(ECHO) "Installing upstart file"
+	$(INSTALL) --mode 644 -D $< $(DESTDIR)/etc/init/cras.conf
+
+cras_init_scripts:	$(ADHD_DIR)/init/cras.sh
+	$(INSTALL) --mode 644 -D $< $(DESTDIR)/usr/share/cros/init/cras.sh
+
+SYSTEMD_UNIT_DIR := /usr/lib/systemd/system/
+SYSTEMD_TMPFILESD_DIR := /usr/lib/tmpfiles.d/
+
+cras_init_systemd:	$(ADHD_DIR)/init/cras.service \
+	$(ADHD_DIR)/init/cras-directories.conf
+	$(ECHO) "Installing systemd files"
+	$(INSTALL) --mode 644 -D $(ADHD_DIR)/init/cras.service \
+		$(DESTDIR)/$(SYSTEMD_UNIT_DIR)/cras.service
+	$(INSTALL) --mode 755 -d $(DESTDIR)/$(SYSTEMD_UNIT_DIR)/system-services.target.wants
+	$(LINK) -s ../cras.service \
+		$(DESTDIR)/$(SYSTEMD_UNIT_DIR)/system-services.target.wants/cras.service
+	$(INSTALL) --mode 644 -D $(ADHD_DIR)/init/cras-directories.conf \
+		$(DESTDIR)/$(SYSTEMD_TMPFILESD_DIR)/cras-directories.conf
+
+ifeq ($(strip $(SYSTEMD)), yes)
+
+cras_init: cras_init_systemd cras_init_scripts
+
+else
+
+cras_init: cras_init_upstart cras_init_scripts
+
+endif
 
 $(DESTDIR)/etc/cras/device_blacklist:	$(ADHD_DIR)/cras-config/device_blacklist
 	$(ECHO) "Installing '$<' to '$@'"
@@ -51,10 +78,11 @@
 
 endif
 
-install:	$(DESTDIR)/etc/init/cras.conf				\
-		$(DESTDIR)/etc/cras/device_blacklist			\
+install:	$(DESTDIR)/etc/cras/device_blacklist \
 		cras-scripts \
-		cras_install
+		cras_install \
+		cras_init
+
 clean:
 	@rm -rf $(ADHD_BUILD_DIR)
 
diff --git a/OWNERS b/OWNERS
index b86edff..85070ac 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,2 +1,6 @@
 dgreid@chromium.org
-thutt@chromium.org
+chinyue@chromium.org
+cychiang@chromium.org
+
+# For bluetooth support.
+hychao@chromium.org
diff --git a/cras-config/banjo/byt-max98090 b/cras-config/banjo/byt-max98090
deleted file mode 100644
index fba7d1b..0000000
--- a/cras-config/banjo/byt-max98090
+++ /dev/null
@@ -1,107 +0,0 @@
-[Speaker]
-  volume_curve = explicit
-  db_at_100 = -200
-  db_at_99 = -200
-  db_at_98 = -250
-  db_at_97 = -250
-  db_at_96 = -300
-  db_at_95 = -300
-  db_at_94 = -350
-  db_at_93 = -350
-  db_at_92 = -400
-  db_at_91 = -400
-  db_at_90 = -400
-  db_at_89 = -450
-  db_at_88 = -450
-  db_at_87 = -450
-  db_at_86 = -450
-  db_at_85 = -500
-  db_at_84 = -500
-  db_at_83 = -500
-  db_at_82 = -550
-  db_at_81 = -550
-  db_at_80 = -600
-  db_at_79 = -600
-  db_at_78 = -650
-  db_at_77 = -650
-  db_at_76 = -700
-  db_at_75 = -700
-  db_at_74 = -750
-  db_at_73 = -750
-  db_at_72 = -800
-  db_at_71 = -800
-  db_at_70 = -850
-  db_at_69 = -850
-  db_at_68 = -900
-  db_at_67 = -900
-  db_at_66 = -950
-  db_at_65 = -950
-  db_at_64 = -1000
-  db_at_63 = -1000
-  db_at_62 = -1050
-  db_at_61 = -1050
-  db_at_60 = -1100
-  db_at_59 = -1100
-  db_at_58 = -1150
-  db_at_57 = -1150
-  db_at_56 = -1200
-  db_at_55 = -1200
-  db_at_54 = -1250
-  db_at_53 = -1250
-  db_at_52 = -1300
-  db_at_51 = -1300
-  db_at_50 = -1350
-  db_at_49 = -1350
-  db_at_48 = -1400
-  db_at_47 = -1400
-  db_at_46 = -1450
-  db_at_45 = -1450
-  db_at_44 = -1500
-  db_at_43 = -1550
-  db_at_42 = -1600
-  db_at_41 = -1650
-  db_at_40 = -1700
-  db_at_39 = -1750
-  db_at_38 = -1850
-  db_at_37 = -1900
-  db_at_36 = -2000
-  db_at_35 = -2100
-  db_at_34 = -2200
-  db_at_33 = -2300
-  db_at_32 = -2400
-  db_at_31 = -2450
-  db_at_30 = -2500
-  db_at_29 = -2550
-  db_at_28 = -2600
-  db_at_27 = -2650
-  db_at_26 = -2700
-  db_at_25 = -2750
-  db_at_24 = -2800
-  db_at_23 = -2850
-  db_at_22 = -2950
-  db_at_21 = -3000
-  db_at_20 = -3100
-  db_at_19 = -3150
-  db_at_18 = -3250
-  db_at_17 = -3300
-  db_at_16 = -3400
-  db_at_15 = -3450
-  db_at_14 = -3550
-  db_at_13 = -3600
-  db_at_12 = -3700
-  db_at_11 = -3750
-  db_at_10 = -3850
-  db_at_9 = -3900
-  db_at_8 = -4000
-  db_at_7 = -4050
-  db_at_6 = -4150
-  db_at_5 = -4200
-  db_at_4 = -4300
-  db_at_3 = -4350
-  db_at_2 = -4450
-  db_at_1 = -4500
-  db_at_0 = -4600
-[Headphone]
-  volume_curve = simple_step
-  volume_step = 70
-  max_volume = 0
diff --git a/cras-config/banjo/dsp.ini b/cras-config/banjo/dsp.ini
deleted file mode 100644
index d226159..0000000
--- a/cras-config/banjo/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=20        ; knee
-input_9=7.8       ; ratio
-input_10=0.003     ; attack
-input_11=0.915     ; release
-input_12=2         ; boost
-input_13=450       ; f
-input_14=1         ; enable
-input_15=-23       ; threshold
-input_16=24        ; knee
-input_17=11        ; ratio
-input_18=0.003     ; attack
-input_19=0.25      ; release
-input_20=1.5       ; boost
-input_21=2000      ; f
-input_22=1         ; enable
-input_23=-21       ; threshold
-input_24=25        ; knee
-input_25=12        ; ratio
-input_26=0.003     ; attack
-input_27=0.25      ; release
-input_28=0.7       ; 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=180     ; 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=200     ; freq
-input_14=2.8     ; Q
-input_15=-3.5    ; gain
-input_16=6       ; peaking
-input_17=200     ; freq
-input_18=2.8     ; Q
-input_19=-3.5    ; gain
-input_20=6       ; peaking
-input_21=500     ; freq
-input_22=2.5     ; Q
-input_23=-6      ; gain
-input_24=6       ; peaking
-input_25=500     ; freq
-input_26=2.5     ; Q
-input_27=-6      ; gain
-input_28=6       ; peaking
-input_29=3400    ; freq
-input_30=3.5     ; Q
-input_31=-5      ; gain
-input_32=6       ; peaking
-input_33=3400    ; freq
-input_34=3.5     ; Q
-input_35=-5      ; gain
-input_36=6       ; peaking
-input_37=800     ; freq
-input_38=3.5     ; Q
-input_39=-5      ; gain
-input_40=6       ; peaking
-input_41=800     ; freq
-input_42=3.5     ; Q
-input_43=-5      ; gain
-input_44=6       ; peaking
-input_45=5000    ; freq
-input_46=2       ; Q
-input_47=-7.8    ; gain
-input_48=5       ; highshelf
-input_49=5000    ; freq
-input_50=2       ; Q
-input_51=-7.8    ; gain
-input_52=6       ; peaking
-input_53=2000    ; freq
-input_54=4       ; Q
-input_55=-5      ; gain
-input_56=6       ; peaking
-input_57=2000    ; freq
-input_58=4       ; Q
-input_59=-5      ; gain
diff --git a/cras-config/candy/byt-max98090 b/cras-config/candy/byt-max98090
deleted file mode 100644
index 7e8b38b..0000000
--- a/cras-config/candy/byt-max98090
+++ /dev/null
@@ -1,107 +0,0 @@
-[Speaker]
-  volume_curve = explicit
-  db_at_100 = 0
-  db_at_99 = 0
-  db_at_98 = -50
-  db_at_97 = -50
-  db_at_96 = -50
-  db_at_95 = -100
-  db_at_94 = -100
-  db_at_93 = -150
-  db_at_92 = -150
-  db_at_91 = -150
-  db_at_90 = -200
-  db_at_89 = -200
-  db_at_88 = -200
-  db_at_87 = -250
-  db_at_86 = -250
-  db_at_85 = -300
-  db_at_84 = -300
-  db_at_83 = -350
-  db_at_82 = -350
-  db_at_81 = -400
-  db_at_80 = -450
-  db_at_79 = -500
-  db_at_78 = -500
-  db_at_77 = -550
-  db_at_76 = -600
-  db_at_75 = -650
-  db_at_74 = -650
-  db_at_73 = -700
-  db_at_72 = -750
-  db_at_71 = -800
-  db_at_70 = -800
-  db_at_69 = -850
-  db_at_68 = -900
-  db_at_67 = -950
-  db_at_66 = -950
-  db_at_65 = -1000
-  db_at_64 = -1050
-  db_at_63 = -1100
-  db_at_62 = -1100
-  db_at_61 = -1150
-  db_at_60 = -1200
-  db_at_59 = -1300
-  db_at_58 = -1350
-  db_at_57 = -1450
-  db_at_56 = -1500
-  db_at_55 = -1600
-  db_at_54 = -1650
-  db_at_53 = -1750
-  db_at_52 = -1800
-  db_at_51 = -1900
-  db_at_50 = -1950
-  db_at_49 = -2050
-  db_at_48 = -2100
-  db_at_47 = -2200
-  db_at_46 = -2250
-  db_at_45 = -2350
-  db_at_44 = -2450
-  db_at_43 = -2500
-  db_at_42 = -2600
-  db_at_41 = -2650
-  db_at_40 = -2750
-  db_at_39 = -2800
-  db_at_38 = -2900
-  db_at_37 = -2950
-  db_at_36 = -3050
-  db_at_35 = -3100
-  db_at_34 = -3200
-  db_at_33 = -3250
-  db_at_32 = -3350
-  db_at_31 = -3400
-  db_at_30 = -3500
-  db_at_29 = -3600
-  db_at_28 = -3650
-  db_at_27 = -3750
-  db_at_26 = -3800
-  db_at_25 = -3900
-  db_at_24 = -3950
-  db_at_23 = -4050
-  db_at_22 = -4100
-  db_at_21 = -4200
-  db_at_20 = -4250
-  db_at_19 = -4350
-  db_at_18 = -4400
-  db_at_17 = -4500
-  db_at_16 = -4550
-  db_at_15 = -4650
-  db_at_14 = -4750
-  db_at_13 = -4800
-  db_at_12 = -4900
-  db_at_11 = -4950
-  db_at_10 = -5050
-  db_at_9 = -5100
-  db_at_8 = -5200
-  db_at_7 = -5250
-  db_at_6 = -5350
-  db_at_5 = -5400
-  db_at_4 = -5500
-  db_at_3 = -5550
-  db_at_2 = -5650
-  db_at_1 = -5700
-  db_at_0 = -5800
-[Headphone]
-  volume_curve = simple_step
-  volume_step = 70
-  max_volume = 0
diff --git a/cras-config/candy/dsp.ini b/cras-config/candy/dsp.ini
deleted file mode 100644
index dc80534..0000000
--- a/cras-config/candy/dsp.ini
+++ /dev/null
@@ -1,87 +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=28        ; knee
-input_9=11.774    ; ratio
-input_10=0.003     ; attack
-input_11=0.25      ; release
-input_12=2         ; boost
-input_13=400       ; f
-input_14=1         ; enable
-input_15=-24       ; threshold
-input_16=29        ; knee
-input_17=16        ; ratio
-input_18=0.003     ; attack
-input_19=0.25      ; release
-input_20=0         ; 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=5       ; highshelf
-input_13=10000   ; freq
-input_14=0       ; Q
-input_15=-4      ; gain
-input_16=5       ; highshelf
-input_17=10000   ; freq
-input_18=0       ; Q
-input_19=-4      ; gain
-input_20=6       ; peaking
-input_21=600     ; freq
-input_22=2       ; Q
-input_23=-6      ; gain
-input_24=6       ; peaking
-input_25=600     ; freq
-input_26=2       ; Q
-input_27=-6      ; gain
-input_28=6       ; peaking
-input_29=1500    ; freq
-input_30=2       ; Q
-input_31=-4      ; gain
-input_32=6       ; peaking
-input_33=1500    ; freq
-input_34=2       ; Q
-input_35=-4      ; gain
diff --git a/cras-config/clapper/byt-max98090 b/cras-config/clapper/byt-max98090
deleted file mode 100644
index 8046a8a..0000000
--- a/cras-config/clapper/byt-max98090
+++ /dev/null
@@ -1,107 +0,0 @@
-[Speaker]
-  volume_curve = explicit
-  db_at_100 = -600
-  db_at_99 = -600
-  db_at_98 = -700
-  db_at_97 = -700
-  db_at_96 = -800
-  db_at_95 = -800
-  db_at_94 = -900
-  db_at_93 = -900
-  db_at_92 = -900
-  db_at_91 = -1000
-  db_at_90 = -1000
-  db_at_89 = -1100
-  db_at_88 = -1100
-  db_at_87 = -1100
-  db_at_86 = -1200
-  db_at_85 = -1200
-  db_at_84 = -1300
-  db_at_83 = -1300
-  db_at_82 = -1300
-  db_at_81 = -1300
-  db_at_80 = -1400
-  db_at_79 = -1400
-  db_at_78 = -1400
-  db_at_77 = -1400
-  db_at_76 = -1500
-  db_at_75 = -1500
-  db_at_74 = -1500
-  db_at_73 = -1500
-  db_at_72 = -1600
-  db_at_71 = -1600
-  db_at_70 = -1600
-  db_at_69 = -1600
-  db_at_68 = -1700
-  db_at_67 = -1700
-  db_at_66 = -1700
-  db_at_65 = -1700
-  db_at_64 = -1800
-  db_at_63 = -1800
-  db_at_62 = -1800
-  db_at_61 = -1800
-  db_at_60 = -1900
-  db_at_59 = -1900
-  db_at_58 = -1900
-  db_at_57 = -1900
-  db_at_56 = -2000
-  db_at_55 = -2000
-  db_at_54 = -2000
-  db_at_53 = -2000
-  db_at_52 = -2100
-  db_at_51 = -2100
-  db_at_50 = -2100
-  db_at_49 = -2100
-  db_at_48 = -2300
-  db_at_47 = -2300
-  db_at_46 = -2300
-  db_at_45 = -2300
-  db_at_44 = -2500
-  db_at_43 = -2500
-  db_at_42 = -2500
-  db_at_41 = -2500
-  db_at_40 = -2700
-  db_at_39 = -2700
-  db_at_38 = -2700
-  db_at_37 = -2700
-  db_at_36 = -3000
-  db_at_35 = -3000
-  db_at_34 = -3000
-  db_at_33 = -3000
-  db_at_32 = -3200
-  db_at_31 = -3200
-  db_at_30 = -3200
-  db_at_29 = -3200
-  db_at_28 = -3500
-  db_at_27 = -3700
-  db_at_26 = -3700
-  db_at_25 = -3700
-  db_at_24 = -3800
-  db_at_23 = -3800
-  db_at_22 = -3900
-  db_at_21 = -3900
-  db_at_20 = -4100
-  db_at_19 = -4100
-  db_at_18 = -4100
-  db_at_17 = -4100
-  db_at_16 = -4400
-  db_at_15 = -4400
-  db_at_14 = -4400
-  db_at_13 = -4400
-  db_at_12 = -4800
-  db_at_11 = -4800
-  db_at_10 = -4800
-  db_at_9 = -4800
-  db_at_8 = -5200
-  db_at_7 = -5200
-  db_at_6 = -5200
-  db_at_5 = -5200
-  db_at_4 = -5800
-  db_at_3 = -5800
-  db_at_2 = -5800
-  db_at_1 = -5800
-  db_at_0 = -6200
-[Headphone]
-  volume_curve = simple_step
-  volume_step = 50
-  max_volume = 0
diff --git a/cras-config/clapper/dsp.ini b/cras-config/clapper/dsp.ini
deleted file mode 100644
index 970625a..0000000
--- a/cras-config/clapper/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}
-
-[eq2]
-library=builtin
-label=eq2
-input_0={src:0}
-input_1={src:1}
-output_2={intermediate:0}
-output_3={intermediate:1}
-input_4=2       ; highpass
-input_5=250     ; freq
-input_6=8       ; Q
-input_7=0       ; gain
-input_8=2       ; highpass
-input_9=250     ; freq
-input_10=8       ; Q
-input_11=0       ; gain
-input_12=6       ; peaking
-input_13=1000    ; freq
-input_14=4       ; Q
-input_15=5       ; gain
-input_16=6       ; peaking
-input_17=2000    ; freq
-input_18=1       ; Q
-input_19=-6      ; gain
-input_20=6       ; peaking
-input_21=2000    ; freq
-input_22=4       ; Q
-input_23=5       ; gain
-input_24=6       ; peaking
-input_25=10000   ; freq
-input_26=1       ; Q
-input_27=9       ; gain
-input_28=6       ; peaking
-input_29=10000   ; freq
-input_30=1       ; Q
-input_31=9       ; gain
-input_32=1       ; lowpass
-input_33=16000   ; freq
-input_34=0       ; Q
-input_35=0       ; gain
-input_36=6       ; peaking
-input_37=4000    ; freq
-input_38=4       ; Q
-input_39=4       ; gain
-input_40=0       ; none
-input_41=0       ; freq
-input_42=0       ; Q
-input_43=0       ; gain
-input_44=1       ; lowpass
-input_45=16000   ; freq
-input_46=0       ; Q
-input_47=0       ; gain
-input_48=0       ; none
-input_49=0       ; freq
-input_50=0       ; Q
-input_51=0       ; gain
-
-[drc]
-library=builtin
-label=drc
-input_0={intermediate:0}
-input_1={intermediate:1}
-output_2={dst:0}
-output_3={dst:1}
-input_4=1         ; emphasis_disabled
-input_5=0         ; f
-input_6=1         ; enable
-input_7=-30       ; threshold
-input_8=22        ; knee
-input_9=12        ; ratio
-input_10=0.003     ; attack
-input_11=0.25      ; release
-input_12=0         ; boost
-input_13=150       ; f
-input_14=1         ; enable
-input_15=-32       ; threshold
-input_16=22        ; knee
-input_17=10        ; 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=22        ; knee
-input_25=12        ; ratio
-input_26=0.003     ; attack
-input_27=0.25      ; release
-input_28=0         ; boost
diff --git a/cras-config/cyan-cheets b/cras-config/cyan-cheets
new file mode 120000
index 0000000..d8fcd90
--- /dev/null
+++ b/cras-config/cyan-cheets
@@ -0,0 +1 @@
+cyan
\ No newline at end of file
diff --git a/cras-config/enguarde/byt-max98090 b/cras-config/enguarde/byt-max98090
deleted file mode 100644
index 9d54d42..0000000
--- a/cras-config/enguarde/byt-max98090
+++ /dev/null
@@ -1,107 +0,0 @@
-[Speaker]
-  volume_curve = explicit
-  db_at_100 = -300
-  db_at_99 = -350
-  db_at_98 = -350
-  db_at_97 = -400
-  db_at_96 = -450
-  db_at_95 = -450
-  db_at_94 = -500
-  db_at_93 = -550
-  db_at_92 = -550
-  db_at_91 = -600
-  db_at_90 = -600
-  db_at_89 = -650
-  db_at_88 = -700
-  db_at_87 = -700
-  db_at_86 = -750
-  db_at_85 = -800
-  db_at_84 = -800
-  db_at_83 = -850
-  db_at_82 = -900
-  db_at_81 = -900
-  db_at_80 = -950
-  db_at_79 = -1000
-  db_at_78 = -1000
-  db_at_77 = -1050
-  db_at_76 = -1100
-  db_at_75 = -1100
-  db_at_74 = -1150
-  db_at_73 = -1200
-  db_at_72 = -1200
-  db_at_71 = -1250
-  db_at_70 = -1250
-  db_at_69 = -1300
-  db_at_68 = -1350
-  db_at_67 = -1350
-  db_at_66 = -1400
-  db_at_65 = -1450
-  db_at_64 = -1450
-  db_at_63 = -1500
-  db_at_62 = -1550
-  db_at_61 = -1550
-  db_at_60 = -1600
-  db_at_59 = -1650
-  db_at_58 = -1700
-  db_at_57 = -1750
-  db_at_56 = -1800
-  db_at_55 = -1850
-  db_at_54 = -1850
-  db_at_53 = -1900
-  db_at_52 = -1950
-  db_at_51 = -2000
-  db_at_50 = -2050
-  db_at_49 = -2100
-  db_at_48 = -2150
-  db_at_47 = -2200
-  db_at_46 = -2250
-  db_at_45 = -2300
-  db_at_44 = -2350
-  db_at_43 = -2350
-  db_at_42 = -2400
-  db_at_41 = -2450
-  db_at_40 = -2500
-  db_at_39 = -2550
-  db_at_38 = -2600
-  db_at_37 = -2650
-  db_at_36 = -2700
-  db_at_35 = -2750
-  db_at_34 = -2800
-  db_at_33 = -2850
-  db_at_32 = -2850
-  db_at_31 = -2900
-  db_at_30 = -2950
-  db_at_29 = -3000
-  db_at_28 = -3050
-  db_at_27 = -3100
-  db_at_26 = -3150
-  db_at_25 = -3200
-  db_at_24 = -3250
-  db_at_23 = -3300
-  db_at_22 = -3350
-  db_at_21 = -3350
-  db_at_20 = -3400
-  db_at_19 = -3450
-  db_at_18 = -3500
-  db_at_17 = -3550
-  db_at_16 = -3600
-  db_at_15 = -3650
-  db_at_14 = -3750
-  db_at_13 = -3800
-  db_at_12 = -3900
-  db_at_11 = -3950
-  db_at_10 = -4050
-  db_at_9 = -4100
-  db_at_8 = -4200
-  db_at_7 = -4250
-  db_at_6 = -4350
-  db_at_5 = -4400
-  db_at_4 = -4500
-  db_at_3 = -4550
-  db_at_2 = -4650
-  db_at_1 = -4700
-  db_at_0 = -4800
-[Headphone]
-  volume_curve = simple_step
-  volume_step = 50
-  max_volume = 0
diff --git a/cras-config/enguarde/dsp.ini b/cras-config/enguarde/dsp.ini
deleted file mode 100644
index 2e7a5ee..0000000
--- a/cras-config/enguarde/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=1         ; emphasis_disabled
-input_5=0         ; f
-input_6=1         ; enable
-input_7=-31       ; threshold
-input_8=27        ; knee
-input_9=7.084     ; ratio
-input_10=0.003     ; attack
-input_11=0.25      ; release
-input_12=0         ; boost
-input_13=750       ; f
-input_14=0         ; 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=1500      ; 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=450     ; freq
-input_6=0       ; Q
-input_7=0       ; gain
-input_8=2       ; highpass
-input_9=450     ; freq
-input_10=0       ; Q
-input_11=0       ; gain
-input_12=6       ; peaking
-input_13=750     ; freq
-input_14=6       ; Q
-input_15=-5      ; gain
-input_16=6       ; peaking
-input_17=1500    ; freq
-input_18=3       ; Q
-input_19=-3      ; gain
-input_20=6       ; peaking
-input_21=1050    ; freq
-input_22=6       ; Q
-input_23=-5      ; gain
-input_24=6       ; peaking
-input_25=510     ; freq
-input_26=6       ; Q
-input_27=-9      ; gain
-input_28=6       ; peaking
-input_29=475     ; freq
-input_30=6       ; Q
-input_31=-4      ; gain
-input_32=6       ; peaking
-input_33=1000    ; freq
-input_34=3       ; Q
-input_35=-9      ; gain
-input_36=6       ; peaking
-input_37=2025    ; freq
-input_38=3       ; Q
-input_39=-3      ; gain
-input_40=6       ; peaking
-input_41=2100    ; freq
-input_42=3       ; Q
-input_43=-6      ; gain
-input_44=0       ; none
-input_45=0       ; freq
-input_46=0       ; Q
-input_47=0       ; gain
-input_48=6       ; peaking
-input_49=725     ; freq
-input_50=6       ; Q
-input_51=-6      ; gain
diff --git a/cras-config/expresso/byt-max98090 b/cras-config/expresso/byt-max98090
deleted file mode 100644
index 1e2dfe9..0000000
--- a/cras-config/expresso/byt-max98090
+++ /dev/null
@@ -1,107 +0,0 @@
-[Speaker]
-  volume_curve = explicit
-  db_at_100 = -200
-  db_at_99 = -250
-  db_at_98 = -300
-  db_at_97 = -350
-  db_at_96 = -400
-  db_at_95 = -450
-  db_at_94 = -500
-  db_at_93 = -500
-  db_at_92 = -550
-  db_at_91 = -600
-  db_at_90 = -650
-  db_at_89 = -700
-  db_at_88 = -750
-  db_at_87 = -800
-  db_at_86 = -850
-  db_at_85 = -900
-  db_at_84 = -950
-  db_at_83 = -1000
-  db_at_82 = -1050
-  db_at_81 = -1050
-  db_at_80 = -1100
-  db_at_79 = -1150
-  db_at_78 = -1200
-  db_at_77 = -1250
-  db_at_76 = -1300
-  db_at_75 = -1350
-  db_at_74 = -1400
-  db_at_73 = -1450
-  db_at_72 = -1500
-  db_at_71 = -1550
-  db_at_70 = -1600
-  db_at_69 = -1650
-  db_at_68 = -1650
-  db_at_67 = -1700
-  db_at_66 = -1750
-  db_at_65 = -1800
-  db_at_64 = -1850
-  db_at_63 = -1900
-  db_at_62 = -1950
-  db_at_61 = -2000
-  db_at_60 = -2050
-  db_at_59 = -2100
-  db_at_58 = -2150
-  db_at_57 = -2200
-  db_at_56 = -2200
-  db_at_55 = -2250
-  db_at_54 = -2300
-  db_at_53 = -2350
-  db_at_52 = -2400
-  db_at_51 = -2450
-  db_at_50 = -2500
-  db_at_49 = -2550
-  db_at_48 = -2600
-  db_at_47 = -2650
-  db_at_46 = -2700
-  db_at_45 = -2750
-  db_at_44 = -2800
-  db_at_43 = -2800
-  db_at_42 = -2850
-  db_at_41 = -2900
-  db_at_40 = -2950
-  db_at_39 = -3000
-  db_at_38 = -3050
-  db_at_37 = -3100
-  db_at_36 = -3150
-  db_at_35 = -3200
-  db_at_34 = -3250
-  db_at_33 = -3300
-  db_at_32 = -3350
-  db_at_31 = -3350
-  db_at_30 = -3400
-  db_at_29 = -3450
-  db_at_28 = -3500
-  db_at_27 = -3550
-  db_at_26 = -3600
-  db_at_25 = -3650
-  db_at_24 = -3700
-  db_at_23 = -3750
-  db_at_22 = -3800
-  db_at_21 = -3850
-  db_at_20 = -3900
-  db_at_19 = -3950
-  db_at_18 = -3950
-  db_at_17 = -4000
-  db_at_16 = -4050
-  db_at_15 = -4100
-  db_at_14 = -4150
-  db_at_13 = -4200
-  db_at_12 = -4250
-  db_at_11 = -4300
-  db_at_10 = -4350
-  db_at_9 = -4400
-  db_at_8 = -4450
-  db_at_7 = -4500
-  db_at_6 = -4500
-  db_at_5 = -4550
-  db_at_4 = -4600
-  db_at_3 = -4650
-  db_at_2 = -4700
-  db_at_1 = -4750
-  db_at_0 = -4800
-[Headphone]
-  volume_curve = simple_step
-  volume_step = 50
-  max_volume = 0
diff --git a/cras-config/expresso/dsp.ini b/cras-config/expresso/dsp.ini
deleted file mode 100644
index e91622f..0000000
--- a/cras-config/expresso/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=1         ; emphasis_disabled
-input_5=0         ; f
-input_6=1         ; enable
-input_7=-13       ; threshold
-input_8=11        ; knee
-input_9=12.238    ; ratio
-input_10=0.003     ; attack
-input_11=0.25      ; release
-input_12=1         ; boost
-input_13=400       ; f
-input_14=1         ; enable
-input_15=-16       ; threshold
-input_16=16        ; knee
-input_17=12        ; ratio
-input_18=0.003     ; attack
-input_19=0.25      ; release
-input_20=2         ; boost
-input_21=2500      ; f
-input_22=1         ; enable
-input_23=-21       ; threshold
-input_24=25        ; 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=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=2       ; Q
-input_15=-9      ; gain
-input_16=6       ; peaking
-input_17=600     ; freq
-input_18=2       ; Q
-input_19=-9      ; gain
-input_20=6       ; peaking
-input_21=800     ; freq
-input_22=3       ; Q
-input_23=-9      ; gain
-input_24=6       ; peaking
-input_25=800     ; freq
-input_26=3       ; Q
-input_27=-9      ; gain
-input_28=6       ; peaking
-input_29=4000    ; freq
-input_30=3       ; Q
-input_31=-11     ; gain
-input_32=6       ; peaking
-input_33=4000    ; freq
-input_34=3       ; Q
-input_35=-11     ; gain
-input_36=6       ; peaking
-input_37=2200    ; freq
-input_38=2       ; Q
-input_39=-2      ; gain
-input_40=5       ; highshelf
-input_41=10000   ; freq
-input_42=1       ; Q
-input_43=2       ; gain
-input_44=5       ; highshelf
-input_45=10000   ; freq
-input_46=1       ; Q
-input_47=2       ; gain
-input_48=0       ; none
-input_49=0       ; freq
-input_50=0       ; Q
-input_51=0       ; gain
diff --git a/cras-config/glimmer/byt-max98090 b/cras-config/glimmer-cheets/byt-max98090
similarity index 100%
rename from cras-config/glimmer/byt-max98090
rename to cras-config/glimmer-cheets/byt-max98090
diff --git a/cras-config/glimmer/dsp.ini b/cras-config/glimmer-cheets/dsp.ini
similarity index 100%
rename from cras-config/glimmer/dsp.ini
rename to cras-config/glimmer-cheets/dsp.ini
diff --git a/cras-config/gnawty/OLAY/byt-max98090 b/cras-config/gnawty/OLAY/byt-max98090
deleted file mode 100644
index e121f1d..0000000
--- a/cras-config/gnawty/OLAY/byt-max98090
+++ /dev/null
@@ -1,107 +0,0 @@
-[Speaker]

-  volume_curve = explicit

-  db_at_100 = -200

-  db_at_99 = -200

-  db_at_98 = -250

-  db_at_97 = -250

-  db_at_96 = -300

-  db_at_95 = -300

-  db_at_94 = -350

-  db_at_93 = -350

-  db_at_92 = -400

-  db_at_91 = -400

-  db_at_90 = -400

-  db_at_89 = -450

-  db_at_88 = -450

-  db_at_87 = -450

-  db_at_86 = -450

-  db_at_85 = -500

-  db_at_84 = -500

-  db_at_83 = -500

-  db_at_82 = -550

-  db_at_81 = -550

-  db_at_80 = -600

-  db_at_79 = -600

-  db_at_78 = -650

-  db_at_77 = -650

-  db_at_76 = -700

-  db_at_75 = -700

-  db_at_74 = -750

-  db_at_73 = -750

-  db_at_72 = -800

-  db_at_71 = -800

-  db_at_70 = -850

-  db_at_69 = -850

-  db_at_68 = -900

-  db_at_67 = -900

-  db_at_66 = -950

-  db_at_65 = -950

-  db_at_64 = -1000

-  db_at_63 = -1000

-  db_at_62 = -1050

-  db_at_61 = -1050

-  db_at_60 = -1100

-  db_at_59 = -1100

-  db_at_58 = -1150

-  db_at_57 = -1150

-  db_at_56 = -1200

-  db_at_55 = -1200

-  db_at_54 = -1250

-  db_at_53 = -1250

-  db_at_52 = -1300

-  db_at_51 = -1300

-  db_at_50 = -1350

-  db_at_49 = -1350

-  db_at_48 = -1400

-  db_at_47 = -1400

-  db_at_46 = -1450

-  db_at_45 = -1450

-  db_at_44 = -1500

-  db_at_43 = -1550

-  db_at_42 = -1600

-  db_at_41 = -1650

-  db_at_40 = -1700

-  db_at_39 = -1750

-  db_at_38 = -1850

-  db_at_37 = -1900

-  db_at_36 = -2000

-  db_at_35 = -2100

-  db_at_34 = -2200

-  db_at_33 = -2300

-  db_at_32 = -2400

-  db_at_31 = -2450

-  db_at_30 = -2500

-  db_at_29 = -2550

-  db_at_28 = -2600

-  db_at_27 = -2650

-  db_at_26 = -2700

-  db_at_25 = -2750

-  db_at_24 = -2800

-  db_at_23 = -2850

-  db_at_22 = -2950

-  db_at_21 = -3000

-  db_at_20 = -3100

-  db_at_19 = -3150

-  db_at_18 = -3250

-  db_at_17 = -3300

-  db_at_16 = -3400

-  db_at_15 = -3450

-  db_at_14 = -3550

-  db_at_13 = -3600

-  db_at_12 = -3700

-  db_at_11 = -3750

-  db_at_10 = -3850

-  db_at_9 = -3900

-  db_at_8 = -4000

-  db_at_7 = -4050

-  db_at_6 = -4150

-  db_at_5 = -4200

-  db_at_4 = -4300

-  db_at_3 = -4350

-  db_at_2 = -4450

-  db_at_1 = -4500

-  db_at_0 = -4600

-[Headphone]

-  volume_curve = simple_step

-  volume_step = 70

-  max_volume = 0

diff --git a/cras-config/gnawty/OLAY/dsp.ini b/cras-config/gnawty/OLAY/dsp.ini
deleted file mode 100644
index 5f9a349..0000000
--- a/cras-config/gnawty/OLAY/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=-27       ; threshold
-input_8=31        ; knee
-input_9=15.018    ; ratio
-input_10=0.003     ; attack
-input_11=0.25      ; release
-input_12=1.5       ; boost
-input_13=250       ; f
-input_14=1         ; enable
-input_15=-32       ; threshold
-input_16=34        ; knee
-input_17=12.817    ; ratio
-input_18=0.003     ; attack
-input_19=0.25      ; release
-input_20=2         ; boost
-input_21=1800      ; f
-input_22=1         ; enable
-input_23=-38       ; threshold
-input_24=37        ; knee
-input_25=9.921     ; ratio
-input_26=0.003     ; attack
-input_27=0.25      ; release
-input_28=2.5       ; 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=0       ; Q
-input_7=-8.8    ; gain
-input_8=2       ; highpass
-input_9=250     ; freq
-input_10=0       ; Q
-input_11=-8.3    ; gain
-input_12=6       ; peaking
-input_13=649     ; freq
-input_14=2.5     ; Q
-input_15=-8.8    ; gain
-input_16=6       ; peaking
-input_17=649     ; freq
-input_18=2.5     ; Q
-input_19=-8.8    ; gain
-input_20=6       ; peaking
-input_21=1200    ; freq
-input_22=3.5     ; Q
-input_23=2.4     ; gain
-input_24=6       ; peaking
-input_25=1200    ; freq
-input_26=3.5     ; Q
-input_27=2.4     ; gain
-input_28=6       ; peaking
-input_29=10000   ; freq
-input_30=4.5     ; Q
-input_31=4.5     ; gain
-input_32=6       ; peaking
-input_33=10000   ; freq
-input_34=4.5     ; Q
-input_35=4.5     ; gain
-input_36=6       ; peaking
-input_37=6070    ; freq
-input_38=3.8     ; Q
-input_39=3.5     ; gain
-input_40=6       ; peaking
-input_41=6070    ; freq
-input_42=3.8     ; Q
-input_43=3       ; gain
-input_44=5       ; highshelf
-input_45=8083    ; freq
-input_46=1       ; Q
-input_47=3.5     ; gain
-input_48=5       ; highshelf
-input_49=8083    ; freq
-input_50=1       ; Q
-input_51=3.5     ; gain
-input_52=6       ; peaking
-input_53=3232    ; freq
-input_54=3.3685  ; Q
-input_55=-7.3    ; gain
-input_56=6       ; peaking
-input_57=3232    ; freq
-input_58=3.3685  ; Q
-input_59=-7.3    ; gain
diff --git a/cras-config/gnawty/byt-max98090 b/cras-config/gnawty/byt-max98090
deleted file mode 100644
index 1e2dfe9..0000000
--- a/cras-config/gnawty/byt-max98090
+++ /dev/null
@@ -1,107 +0,0 @@
-[Speaker]
-  volume_curve = explicit
-  db_at_100 = -200
-  db_at_99 = -250
-  db_at_98 = -300
-  db_at_97 = -350
-  db_at_96 = -400
-  db_at_95 = -450
-  db_at_94 = -500
-  db_at_93 = -500
-  db_at_92 = -550
-  db_at_91 = -600
-  db_at_90 = -650
-  db_at_89 = -700
-  db_at_88 = -750
-  db_at_87 = -800
-  db_at_86 = -850
-  db_at_85 = -900
-  db_at_84 = -950
-  db_at_83 = -1000
-  db_at_82 = -1050
-  db_at_81 = -1050
-  db_at_80 = -1100
-  db_at_79 = -1150
-  db_at_78 = -1200
-  db_at_77 = -1250
-  db_at_76 = -1300
-  db_at_75 = -1350
-  db_at_74 = -1400
-  db_at_73 = -1450
-  db_at_72 = -1500
-  db_at_71 = -1550
-  db_at_70 = -1600
-  db_at_69 = -1650
-  db_at_68 = -1650
-  db_at_67 = -1700
-  db_at_66 = -1750
-  db_at_65 = -1800
-  db_at_64 = -1850
-  db_at_63 = -1900
-  db_at_62 = -1950
-  db_at_61 = -2000
-  db_at_60 = -2050
-  db_at_59 = -2100
-  db_at_58 = -2150
-  db_at_57 = -2200
-  db_at_56 = -2200
-  db_at_55 = -2250
-  db_at_54 = -2300
-  db_at_53 = -2350
-  db_at_52 = -2400
-  db_at_51 = -2450
-  db_at_50 = -2500
-  db_at_49 = -2550
-  db_at_48 = -2600
-  db_at_47 = -2650
-  db_at_46 = -2700
-  db_at_45 = -2750
-  db_at_44 = -2800
-  db_at_43 = -2800
-  db_at_42 = -2850
-  db_at_41 = -2900
-  db_at_40 = -2950
-  db_at_39 = -3000
-  db_at_38 = -3050
-  db_at_37 = -3100
-  db_at_36 = -3150
-  db_at_35 = -3200
-  db_at_34 = -3250
-  db_at_33 = -3300
-  db_at_32 = -3350
-  db_at_31 = -3350
-  db_at_30 = -3400
-  db_at_29 = -3450
-  db_at_28 = -3500
-  db_at_27 = -3550
-  db_at_26 = -3600
-  db_at_25 = -3650
-  db_at_24 = -3700
-  db_at_23 = -3750
-  db_at_22 = -3800
-  db_at_21 = -3850
-  db_at_20 = -3900
-  db_at_19 = -3950
-  db_at_18 = -3950
-  db_at_17 = -4000
-  db_at_16 = -4050
-  db_at_15 = -4100
-  db_at_14 = -4150
-  db_at_13 = -4200
-  db_at_12 = -4250
-  db_at_11 = -4300
-  db_at_10 = -4350
-  db_at_9 = -4400
-  db_at_8 = -4450
-  db_at_7 = -4500
-  db_at_6 = -4500
-  db_at_5 = -4550
-  db_at_4 = -4600
-  db_at_3 = -4650
-  db_at_2 = -4700
-  db_at_1 = -4750
-  db_at_0 = -4800
-[Headphone]
-  volume_curve = simple_step
-  volume_step = 50
-  max_volume = 0
diff --git a/cras-config/gnawty/dsp.ini b/cras-config/gnawty/dsp.ini
deleted file mode 100644
index e91622f..0000000
--- a/cras-config/gnawty/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=1         ; emphasis_disabled
-input_5=0         ; f
-input_6=1         ; enable
-input_7=-13       ; threshold
-input_8=11        ; knee
-input_9=12.238    ; ratio
-input_10=0.003     ; attack
-input_11=0.25      ; release
-input_12=1         ; boost
-input_13=400       ; f
-input_14=1         ; enable
-input_15=-16       ; threshold
-input_16=16        ; knee
-input_17=12        ; ratio
-input_18=0.003     ; attack
-input_19=0.25      ; release
-input_20=2         ; boost
-input_21=2500      ; f
-input_22=1         ; enable
-input_23=-21       ; threshold
-input_24=25        ; 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=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=2       ; Q
-input_15=-9      ; gain
-input_16=6       ; peaking
-input_17=600     ; freq
-input_18=2       ; Q
-input_19=-9      ; gain
-input_20=6       ; peaking
-input_21=800     ; freq
-input_22=3       ; Q
-input_23=-9      ; gain
-input_24=6       ; peaking
-input_25=800     ; freq
-input_26=3       ; Q
-input_27=-9      ; gain
-input_28=6       ; peaking
-input_29=4000    ; freq
-input_30=3       ; Q
-input_31=-11     ; gain
-input_32=6       ; peaking
-input_33=4000    ; freq
-input_34=3       ; Q
-input_35=-11     ; gain
-input_36=6       ; peaking
-input_37=2200    ; freq
-input_38=2       ; Q
-input_39=-2      ; gain
-input_40=5       ; highshelf
-input_41=10000   ; freq
-input_42=1       ; Q
-input_43=2       ; gain
-input_44=5       ; highshelf
-input_45=10000   ; freq
-input_46=1       ; Q
-input_47=2       ; gain
-input_48=0       ; none
-input_49=0       ; freq
-input_50=0       ; Q
-input_51=0       ; gain
diff --git a/cras-config/gnawty/get_device_config_dir b/cras-config/gnawty/get_device_config_dir
deleted file mode 100755
index 95adbc0..0000000
--- a/cras-config/gnawty/get_device_config_dir
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/bin/sh
-# Copyright 2015 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.
-
-VPD_CACHE_FILE="/mnt/stateful_partition/unencrypted/cache/vpd/filtered.txt"
-if [ -e "${VPD_CACHE_FILE}" ]; then
-  CUSTOMIZATION_ID="$(sed -nre 's/^"customization_id"="(.+)"$/\1/p' \
-                      <"${VPD_CACHE_FILE}")"
-  SERIES="${CUSTOMIZATION_ID##*-}"
-fi
-echo "/etc/cras/${SERIES}"
\ No newline at end of file
diff --git a/cras-config/heli/byt-max98090 b/cras-config/heli/byt-max98090
deleted file mode 100644
index 7e8b38b..0000000
--- a/cras-config/heli/byt-max98090
+++ /dev/null
@@ -1,107 +0,0 @@
-[Speaker]
-  volume_curve = explicit
-  db_at_100 = 0
-  db_at_99 = 0
-  db_at_98 = -50
-  db_at_97 = -50
-  db_at_96 = -50
-  db_at_95 = -100
-  db_at_94 = -100
-  db_at_93 = -150
-  db_at_92 = -150
-  db_at_91 = -150
-  db_at_90 = -200
-  db_at_89 = -200
-  db_at_88 = -200
-  db_at_87 = -250
-  db_at_86 = -250
-  db_at_85 = -300
-  db_at_84 = -300
-  db_at_83 = -350
-  db_at_82 = -350
-  db_at_81 = -400
-  db_at_80 = -450
-  db_at_79 = -500
-  db_at_78 = -500
-  db_at_77 = -550
-  db_at_76 = -600
-  db_at_75 = -650
-  db_at_74 = -650
-  db_at_73 = -700
-  db_at_72 = -750
-  db_at_71 = -800
-  db_at_70 = -800
-  db_at_69 = -850
-  db_at_68 = -900
-  db_at_67 = -950
-  db_at_66 = -950
-  db_at_65 = -1000
-  db_at_64 = -1050
-  db_at_63 = -1100
-  db_at_62 = -1100
-  db_at_61 = -1150
-  db_at_60 = -1200
-  db_at_59 = -1300
-  db_at_58 = -1350
-  db_at_57 = -1450
-  db_at_56 = -1500
-  db_at_55 = -1600
-  db_at_54 = -1650
-  db_at_53 = -1750
-  db_at_52 = -1800
-  db_at_51 = -1900
-  db_at_50 = -1950
-  db_at_49 = -2050
-  db_at_48 = -2100
-  db_at_47 = -2200
-  db_at_46 = -2250
-  db_at_45 = -2350
-  db_at_44 = -2450
-  db_at_43 = -2500
-  db_at_42 = -2600
-  db_at_41 = -2650
-  db_at_40 = -2750
-  db_at_39 = -2800
-  db_at_38 = -2900
-  db_at_37 = -2950
-  db_at_36 = -3050
-  db_at_35 = -3100
-  db_at_34 = -3200
-  db_at_33 = -3250
-  db_at_32 = -3350
-  db_at_31 = -3400
-  db_at_30 = -3500
-  db_at_29 = -3600
-  db_at_28 = -3650
-  db_at_27 = -3750
-  db_at_26 = -3800
-  db_at_25 = -3900
-  db_at_24 = -3950
-  db_at_23 = -4050
-  db_at_22 = -4100
-  db_at_21 = -4200
-  db_at_20 = -4250
-  db_at_19 = -4350
-  db_at_18 = -4400
-  db_at_17 = -4500
-  db_at_16 = -4550
-  db_at_15 = -4650
-  db_at_14 = -4750
-  db_at_13 = -4800
-  db_at_12 = -4900
-  db_at_11 = -4950
-  db_at_10 = -5050
-  db_at_9 = -5100
-  db_at_8 = -5200
-  db_at_7 = -5250
-  db_at_6 = -5350
-  db_at_5 = -5400
-  db_at_4 = -5500
-  db_at_3 = -5550
-  db_at_2 = -5650
-  db_at_1 = -5700
-  db_at_0 = -5800
-[Headphone]
-  volume_curve = simple_step
-  volume_step = 70
-  max_volume = 0
diff --git a/cras-config/heli/dsp.ini b/cras-config/heli/dsp.ini
deleted file mode 100644
index f425b50..0000000
--- a/cras-config/heli/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=11        ; knee
-input_9=12.2      ; ratio
-input_10=0.003     ; attack
-input_11=0.25      ; release
-input_12=1         ; boost
-input_13=400       ; f
-input_14=1         ; enable
-input_15=-22       ; threshold
-input_16=13        ; knee
-input_17=12        ; ratio
-input_18=0.003     ; attack
-input_19=0.25      ; release
-input_20=1         ; boost
-input_21=2000      ; f
-input_22=1         ; enable
-input_23=-27       ; threshold
-input_24=22        ; 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=6       ; peaking
-input_5=1800    ; freq
-input_6=3       ; Q
-input_7=-7      ; gain
-input_8=6       ; peaking
-input_9=1800    ; freq
-input_10=3       ; Q
-input_11=-7      ; gain
-input_12=6       ; peaking
-input_13=3500    ; freq
-input_14=3       ; Q
-input_15=-6      ; gain
-input_16=6       ; peaking
-input_17=3500    ; freq
-input_18=3       ; Q
-input_19=-6      ; gain
-input_20=6       ; peaking
-input_21=750     ; freq
-input_22=5       ; Q
-input_23=-11     ; gain
-input_24=6       ; peaking
-input_25=750     ; freq
-input_26=5       ; Q
-input_27=-11     ; gain
-input_28=6       ; peaking
-input_29=11000   ; freq
-input_30=3       ; Q
-input_31=-12     ; gain
-input_32=6       ; peaking
-input_33=11000   ; freq
-input_34=3       ; Q
-input_35=-12     ; gain
-input_36=6       ; peaking
-input_37=8000    ; freq
-input_38=3       ; Q
-input_39=-10     ; gain
-input_40=6       ; peaking
-input_41=8000    ; freq
-input_42=3       ; Q
-input_43=-10     ; gain
-input_44=2       ; highpass
-input_45=350     ; freq
-input_46=0       ; Q
-input_47=2       ; gain
-input_48=2       ; highpass
-input_49=350     ; freq
-input_50=0       ; Q
-input_51=2       ; gain
-input_52=6       ; peaking
-input_53=1000    ; freq
-input_54=5       ; Q
-input_55=-7      ; gain
-input_56=6       ; peaking
-input_57=1000    ; freq
-input_58=5       ; Q
-input_59=-7      ; gain
diff --git a/cras-config/kip/KIP14/byt-max98090 b/cras-config/kip/KIP14/byt-max98090
deleted file mode 100644
index b87abda..0000000
--- a/cras-config/kip/KIP14/byt-max98090
+++ /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 = -800
diff --git a/cras-config/kip/KIP14/dsp.ini b/cras-config/kip/KIP14/dsp.ini
deleted file mode 100644
index c5d562d..0000000
--- a/cras-config/kip/KIP14/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=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=650     ; freq
-input_14=1       ; Q
-input_15=-6      ; gain
-input_16=6       ; peaking
-input_17=650     ; freq
-input_18=1       ; Q
-input_19=-6      ; gain
-input_20=6       ; peaking
-input_21=1000    ; freq
-input_22=1       ; Q
-input_23=-6      ; gain
-input_24=6       ; peaking
-input_25=1000    ; freq
-input_26=1       ; Q
-input_27=-6      ; gain
-input_28=6       ; peaking
-input_29=250     ; freq
-input_30=1       ; Q
-input_31=0       ; gain
-input_32=6       ; peaking
-input_33=250     ; freq
-input_34=1       ; Q
-input_35=0       ; gain
-input_36=6       ; peaking
-input_37=350     ; freq
-input_38=1       ; Q
-input_39=0       ; gain
-input_40=6       ; peaking
-input_41=350     ; freq
-input_42=1       ; Q
-input_43=0       ; gain
-input_44=6       ; peaking
-input_45=350     ; freq
-input_46=1       ; Q
-input_47=0       ; gain
-input_48=6       ; peaking
-input_49=350     ; freq
-input_50=1       ; Q
-input_51=0       ; gain
-input_52=6       ; peaking
-input_53=8000    ; freq
-input_54=1       ; Q
-input_55=0       ; gain
-input_56=6       ; peaking
-input_57=8000    ; freq
-input_58=1       ; Q
-input_59=0       ; gain
-input_60=6       ; peaking
-input_61=10000   ; freq
-input_62=1       ; Q
-input_63=4       ; gain
-input_64=6       ; peaking
-input_65=10000   ; freq
-input_66=1       ; Q
-input_67=4       ; gain
diff --git a/cras-config/kip/dsp.ini b/cras-config/kip/dsp.ini
deleted file mode 100644
index 7c94fc0..0000000
--- a/cras-config/kip/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/kip/get_device_config_dir b/cras-config/kip/get_device_config_dir
deleted file mode 100755
index 95adbc0..0000000
--- a/cras-config/kip/get_device_config_dir
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/bin/sh
-# Copyright 2015 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.
-
-VPD_CACHE_FILE="/mnt/stateful_partition/unencrypted/cache/vpd/filtered.txt"
-if [ -e "${VPD_CACHE_FILE}" ]; then
-  CUSTOMIZATION_ID="$(sed -nre 's/^"customization_id"="(.+)"$/\1/p' \
-                      <"${VPD_CACHE_FILE}")"
-  SERIES="${CUSTOMIZATION_ID##*-}"
-fi
-echo "/etc/cras/${SERIES}"
\ No newline at end of file
diff --git a/cras-config/orco/byt-max98090 b/cras-config/orco/byt-max98090
deleted file mode 100644
index 828e514..0000000
--- a/cras-config/orco/byt-max98090
+++ /dev/null
@@ -1,107 +0,0 @@
-[Speaker]
-  volume_curve = explicit
-  db_at_100 = -420
-  db_at_99 = -450
-  db_at_98 = -500
-  db_at_97 = -550
-  db_at_96 = -600
-  db_at_95 = -650
-  db_at_94 = -700
-  db_at_93 = -750
-  db_at_92 = -750
-  db_at_91 = -800
-  db_at_90 = -850
-  db_at_89 = -900
-  db_at_88 = -950
-  db_at_87 = -1000
-  db_at_86 = -1050
-  db_at_85 = -1100
-  db_at_84 = -1100
-  db_at_83 = -1150
-  db_at_82 = -1200
-  db_at_81 = -1250
-  db_at_80 = -1300
-  db_at_79 = -1350
-  db_at_78 = -1400
-  db_at_77 = -1450
-  db_at_76 = -1450
-  db_at_75 = -1500
-  db_at_74 = -1550
-  db_at_73 = -1600
-  db_at_72 = -1650
-  db_at_71 = -1700
-  db_at_70 = -1750
-  db_at_69 = -1800
-  db_at_68 = -1800
-  db_at_67 = -1850
-  db_at_66 = -1900
-  db_at_65 = -1950
-  db_at_64 = -2000
-  db_at_63 = -2050
-  db_at_62 = -2100
-  db_at_61 = -2150
-  db_at_60 = -2150
-  db_at_59 = -2200
-  db_at_58 = -2250
-  db_at_57 = -2300
-  db_at_56 = -2350
-  db_at_55 = -2400
-  db_at_54 = -2450
-  db_at_53 = -2500
-  db_at_52 = -2500
-  db_at_51 = -2550
-  db_at_50 = -2600
-  db_at_49 = -2650
-  db_at_48 = -2700
-  db_at_47 = -2750
-  db_at_46 = -2800
-  db_at_45 = -2850
-  db_at_44 = -2850
-  db_at_43 = -2900
-  db_at_42 = -2950
-  db_at_41 = -3000
-  db_at_40 = -3050
-  db_at_39 = -3100
-  db_at_38 = -3150
-  db_at_37 = -3200
-  db_at_36 = -3200
-  db_at_35 = -3250
-  db_at_34 = -3300
-  db_at_33 = -3350
-  db_at_32 = -3400
-  db_at_31 = -3450
-  db_at_30 = -3500
-  db_at_29 = -3550
-  db_at_28 = -3550
-  db_at_27 = -3600
-  db_at_26 = -3650
-  db_at_25 = -3700
-  db_at_24 = -3750
-  db_at_23 = -3800
-  db_at_22 = -3850
-  db_at_21 = -3900
-  db_at_20 = -3900
-  db_at_19 = -3950
-  db_at_18 = -4000
-  db_at_17 = -4050
-  db_at_16 = -4100
-  db_at_15 = -4150
-  db_at_14 = -4200
-  db_at_13 = -4250
-  db_at_12 = -4250
-  db_at_11 = -4300
-  db_at_10 = -4350
-  db_at_9 = -4400
-  db_at_8 = -4450
-  db_at_7 = -4500
-  db_at_6 = -4550
-  db_at_5 = -4600
-  db_at_4 = -4600
-  db_at_3 = -4650
-  db_at_2 = -4700
-  db_at_1 = -4750
-  db_at_0 = -4800
-[Headphone]
-  volume_curve = simple_step
-  volume_step = 70
-  max_volume = 0
diff --git a/cras-config/orco/dsp.ini b/cras-config/orco/dsp.ini
deleted file mode 100644
index acba61f..0000000
--- a/cras-config/orco/dsp.ini
+++ /dev/null
@@ -1,62 +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=6       ; peaking
-input_5=760     ; freq
-input_6=5.7989  ; Q
-input_7=-4.6    ; gain
-input_8=6       ; peaking
-input_9=750     ; freq
-input_10=4.9917  ; Q
-input_11=-4.1    ; gain
-input_12=6       ; peaking
-input_13=3300    ; freq
-input_14=3.4615  ; Q
-input_15=-4.6    ; gain
-input_16=6       ; peaking
-input_17=2300    ; freq
-input_18=1.18    ; Q
-input_19=1.2     ; gain
-input_20=6       ; peaking
-input_21=5200    ; freq
-input_22=3.2775  ; Q
-input_23=0.7     ; gain
-input_24=6       ; peaking
-input_25=6300    ; freq
-input_26=1.4737  ; Q
-input_27=-4      ; gain
-input_28=6       ; peaking
-input_29=2200    ; freq
-input_30=1.4737  ; Q
-input_31=1.7     ; gain
-input_32=6       ; peaking
-input_33=8600    ; freq
-input_34=1.807   ; Q
-input_35=1.2     ; gain
-input_36=2       ; highpass
-input_37=450     ; freq
-input_38=0       ; Q
-input_39=1.2     ; gain
-input_40=2       ; highpass
-input_41=450     ; freq
-input_42=0       ; Q
-input_43=0       ; gain
diff --git a/cras-config/quawks/byt-max98090 b/cras-config/quawks/byt-max98090
deleted file mode 100644
index 64be2b5..0000000
--- a/cras-config/quawks/byt-max98090
+++ /dev/null
@@ -1,206 +0,0 @@
-[Speaker]
-  volume_curve = explicit
-  db_at_100 = -400
-  db_at_99 = -400
-  db_at_98 = -400
-  db_at_97 = -556
-  db_at_96 = -556
-  db_at_95 = -556
-  db_at_94 = -712
-  db_at_93 = -712
-  db_at_92 = -712
-  db_at_91 = -712
-  db_at_90 = -712
-  db_at_89 = -868
-  db_at_88 = -868
-  db_at_87 = -868
-  db_at_86 = -868
-  db_at_85 = -1024
-  db_at_84 = -1024
-  db_at_83 = -1024
-  db_at_82 = -1024
-  db_at_81 = -1180
-  db_at_80 = -1180
-  db_at_79 = -1180
-  db_at_78 = -1180
-  db_at_77 = -1336
-  db_at_76 = -1336
-  db_at_75 = -1336
-  db_at_74 = -1336
-  db_at_73 = -1492
-  db_at_72 = -1492
-  db_at_71 = -1492
-  db_at_70 = -1492
-  db_at_69 = -1648
-  db_at_68 = -1648
-  db_at_67 = -1648
-  db_at_66 = -1648
-  db_at_65 = -1804
-  db_at_64 = -1804
-  db_at_63 = -1804
-  db_at_62 = -1804
-  db_at_61 = -1960
-  db_at_60 = -1960
-  db_at_59 = -1960
-  db_at_58 = -2116
-  db_at_57 = -2116
-  db_at_56 = -2116
-  db_at_55 = -2116
-  db_at_54 = -2116
-  db_at_53 = -2272
-  db_at_52 = -2272
-  db_at_51 = -2272
-  db_at_50 = -2272
-  db_at_49 = -2428
-  db_at_48 = -2428
-  db_at_47 = -2428
-  db_at_46 = -2584
-  db_at_45 = -2584
-  db_at_44 = -2584
-  db_at_43 = -2584
-  db_at_42 = -2584
-  db_at_41 = -2740
-  db_at_40 = -2740
-  db_at_39 = -2740
-  db_at_38 = -2740
-  db_at_37 = -2896
-  db_at_36 = -2896
-  db_at_35 = -2896
-  db_at_34 = -3052
-  db_at_33 = -3052
-  db_at_32 = -3052
-  db_at_31 = -3052
-  db_at_30 = -3052
-  db_at_29 = -3208
-  db_at_28 = -3208
-  db_at_27 = -3208
-  db_at_26 = -3208
-  db_at_25 = -3364
-  db_at_24 = -3364
-  db_at_23 = -3364
-  db_at_22 = -3520
-  db_at_21 = -3520
-  db_at_20 = -3520
-  db_at_19 = -3520
-  db_at_18 = -3676
-  db_at_17 = -3676
-  db_at_16 = -3676
-  db_at_15 = -3676
-  db_at_14 = -3676
-  db_at_13 = -3832
-  db_at_12 = -3832
-  db_at_11 = -3832
-  db_at_10 = -3988
-  db_at_9 = -3988
-  db_at_8 = -3988
-  db_at_7 = -3988
-  db_at_6 = -3988
-  db_at_5 = -4144
-  db_at_4 = -4144
-  db_at_3 = -4144
-  db_at_2 = -4144
-  db_at_1 = -4300
-  db_at_0 = -4300
-[Headphone]
-  volume_curve = explicit
-  db_at_100 = -300
-  db_at_99 = -300
-  db_at_98 = -300
-  db_at_97 = -540
-  db_at_96 = -540
-  db_at_95 = -540
-  db_at_94 = -540
-  db_at_93 = -780
-  db_at_92 = -780
-  db_at_91 = -780
-  db_at_90 = -780
-  db_at_89 = -1020
-  db_at_88 = -1020
-  db_at_87 = -1020
-  db_at_86 = -1020
-  db_at_85 = -1260
-  db_at_84 = -1260
-  db_at_83 = -1260
-  db_at_82 = -1260
-  db_at_81 = -1500
-  db_at_80 = -1500
-  db_at_79 = -1500
-  db_at_78 = -1500
-  db_at_77 = -1740
-  db_at_76 = -1740
-  db_at_75 = -1740
-  db_at_74 = -1740
-  db_at_73 = -1980
-  db_at_72 = -1980
-  db_at_71 = -1980
-  db_at_70 = -1980
-  db_at_69 = -2220
-  db_at_68 = -2220
-  db_at_67 = -2220
-  db_at_66 = -2220
-  db_at_65 = -2460
-  db_at_64 = -2460
-  db_at_63 = -2460
-  db_at_62 = -2460
-  db_at_61 = -2700
-  db_at_60 = -2700
-  db_at_59 = -2700
-  db_at_58 = -2700
-  db_at_57 = -2940
-  db_at_56 = -2940
-  db_at_55 = -2940
-  db_at_54 = -2940
-  db_at_53 = -3180
-  db_at_52 = -3180
-  db_at_51 = -3180
-  db_at_50 = -3180
-  db_at_49 = -3420
-  db_at_48 = -3420
-  db_at_47 = -3420
-  db_at_46 = -3660
-  db_at_45 = -3660
-  db_at_44 = -3660
-  db_at_43 = -3660
-  db_at_42 = -3660
-  db_at_41 = -3900
-  db_at_40 = -3900
-  db_at_39 = -3900
-  db_at_38 = -3900
-  db_at_37 = -4140
-  db_at_36 = -4140
-  db_at_35 = -4140
-  db_at_34 = -4140
-  db_at_33 = -4380
-  db_at_32 = -4380
-  db_at_31 = -4380
-  db_at_30 = -4380
-  db_at_29 = -4620
-  db_at_28 = -4620
-  db_at_27 = -4620
-  db_at_26 = -4860
-  db_at_25 = -4860
-  db_at_24 = -4860
-  db_at_23 = -4860
-  db_at_22 = -4860
-  db_at_21 = -5100
-  db_at_20 = -5100
-  db_at_19 = -5100
-  db_at_18 = -5340
-  db_at_17 = -5340
-  db_at_16 = -5340
-  db_at_15 = -5340
-  db_at_14 = -5340
-  db_at_13 = -5580
-  db_at_12 = -5580
-  db_at_11 = -5580
-  db_at_10 = -5580
-  db_at_9 = -5820
-  db_at_8 = -5820
-  db_at_7 = -5820
-  db_at_6 = -6060
-  db_at_5 = -6060
-  db_at_4 = -6060
-  db_at_3 = -6060
-  db_at_2 = -6060
-  db_at_1 = -6300
-  db_at_0 = -6300
diff --git a/cras-config/quawks/dsp.ini b/cras-config/quawks/dsp.ini
deleted file mode 100644
index 4d388f5..0000000
--- a/cras-config/quawks/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=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=120       ; 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=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=120     ; freq
-input_6=0       ; Q
-input_7=0       ; gain
-input_8=2       ; highpass
-input_9=120     ; freq
-input_10=0       ; Q
-input_11=0       ; gain
-input_12=6       ; peaking
-input_13=450     ; freq
-input_14=6       ; Q
-input_15=-9      ; gain
-input_16=6       ; peaking
-input_17=450     ; freq
-input_18=6       ; Q
-input_19=-9      ; gain
-input_20=6       ; peaking
-input_21=730     ; freq
-input_22=6       ; Q
-input_23=-6      ; gain
-input_24=6       ; peaking
-input_25=730     ; freq
-input_26=6       ; Q
-input_27=-6      ; gain
-input_28=6       ; peaking
-input_29=930     ; freq
-input_30=3       ; Q
-input_31=-6      ; gain
-input_32=6       ; peaking
-input_33=930     ; freq
-input_34=3       ; Q
-input_35=-6      ; gain
-input_36=6       ; peaking
-input_37=1250    ; freq
-input_38=6       ; Q
-input_39=-6      ; gain
-input_40=6       ; peaking
-input_41=1250    ; freq
-input_42=6       ; Q
-input_43=-6      ; gain
-input_44=1       ; lowpass
-input_45=16000   ; freq
-input_46=0       ; Q
-input_47=0       ; gain
-input_48=1       ; lowpass
-input_49=16000   ; freq
-input_50=0       ; Q
-input_51=0       ; gain
diff --git a/cras-config/squawks/byt-max98090 b/cras-config/squawks/byt-max98090
deleted file mode 100644
index e4d7045..0000000
--- a/cras-config/squawks/byt-max98090
+++ /dev/null
@@ -1,206 +0,0 @@
-[Speaker]
-  volume_curve = explicit
-  db_at_100 = -400
-  db_at_99 = -400
-  db_at_98 = -400
-  db_at_97 = -592
-  db_at_96 = -592
-  db_at_95 = -592
-  db_at_94 = -592
-  db_at_93 = -784
-  db_at_92 = -784
-  db_at_91 = -784
-  db_at_90 = -784
-  db_at_89 = -976
-  db_at_88 = -976
-  db_at_87 = -976
-  db_at_86 = -976
-  db_at_85 = -1168
-  db_at_84 = -1168
-  db_at_83 = -1168
-  db_at_82 = -1168
-  db_at_81 = -1360
-  db_at_80 = -1360
-  db_at_79 = -1360
-  db_at_78 = -1360
-  db_at_77 = -1552
-  db_at_76 = -1552
-  db_at_75 = -1552
-  db_at_74 = -1552
-  db_at_73 = -1744
-  db_at_72 = -1744
-  db_at_71 = -1744
-  db_at_70 = -1744
-  db_at_69 = -1936
-  db_at_68 = -1936
-  db_at_67 = -1936
-  db_at_66 = -1936
-  db_at_65 = -2128
-  db_at_64 = -2128
-  db_at_63 = -2128
-  db_at_62 = -2128
-  db_at_61 = -2320
-  db_at_60 = -2320
-  db_at_59 = -2320
-  db_at_58 = -2320
-  db_at_57 = -2512
-  db_at_56 = -2512
-  db_at_55 = -2512
-  db_at_54 = -2512
-  db_at_53 = -2704
-  db_at_52 = -2704
-  db_at_51 = -2704
-  db_at_50 = -2704
-  db_at_49 = -2896
-  db_at_48 = -2896
-  db_at_47 = -2896
-  db_at_46 = -2896
-  db_at_45 = -3088
-  db_at_44 = -3088
-  db_at_43 = -3088
-  db_at_42 = -3088
-  db_at_41 = -3280
-  db_at_40 = -3280
-  db_at_39 = -3280
-  db_at_38 = -3280
-  db_at_37 = -3472
-  db_at_36 = -3472
-  db_at_35 = -3472
-  db_at_34 = -3472
-  db_at_33 = -3664
-  db_at_32 = -3664
-  db_at_31 = -3664
-  db_at_30 = -3856
-  db_at_29 = -3856
-  db_at_28 = -3856
-  db_at_27 = -3856
-  db_at_26 = -3856
-  db_at_25 = -4048
-  db_at_24 = -4048
-  db_at_23 = -4048
-  db_at_22 = -4048
-  db_at_21 = -4240
-  db_at_20 = -4240
-  db_at_19 = -4240
-  db_at_18 = -4240
-  db_at_17 = -4432
-  db_at_16 = -4432
-  db_at_15 = -4432
-  db_at_14 = -4624
-  db_at_13 = -4624
-  db_at_12 = -4624
-  db_at_11 = -4624
-  db_at_10 = -4816
-  db_at_9 = -4816
-  db_at_8 = -4816
-  db_at_7 = -4816
-  db_at_6 = -4816
-  db_at_5 = -5008
-  db_at_4 = -5008
-  db_at_3 = -5008
-  db_at_2 = -5008
-  db_at_1 = -5200
-  db_at_0 = -5200
-[Headphone]
-  volume_curve = explicit
-  db_at_100 = -300
-  db_at_99 = -300
-  db_at_98 = -300
-  db_at_97 = -540
-  db_at_96 = -540
-  db_at_95 = -540
-  db_at_94 = -540
-  db_at_93 = -780
-  db_at_92 = -780
-  db_at_91 = -780
-  db_at_90 = -780
-  db_at_89 = -1020
-  db_at_88 = -1020
-  db_at_87 = -1020
-  db_at_86 = -1020
-  db_at_85 = -1260
-  db_at_84 = -1260
-  db_at_83 = -1260
-  db_at_82 = -1260
-  db_at_81 = -1500
-  db_at_80 = -1500
-  db_at_79 = -1500
-  db_at_78 = -1500
-  db_at_77 = -1740
-  db_at_76 = -1740
-  db_at_75 = -1740
-  db_at_74 = -1740
-  db_at_73 = -1980
-  db_at_72 = -1980
-  db_at_71 = -1980
-  db_at_70 = -1980
-  db_at_69 = -2220
-  db_at_68 = -2220
-  db_at_67 = -2220
-  db_at_66 = -2220
-  db_at_65 = -2460
-  db_at_64 = -2460
-  db_at_63 = -2460
-  db_at_62 = -2460
-  db_at_61 = -2700
-  db_at_60 = -2700
-  db_at_59 = -2700
-  db_at_58 = -2700
-  db_at_57 = -2940
-  db_at_56 = -2940
-  db_at_55 = -2940
-  db_at_54 = -2940
-  db_at_53 = -3180
-  db_at_52 = -3180
-  db_at_51 = -3180
-  db_at_50 = -3180
-  db_at_49 = -3420
-  db_at_48 = -3420
-  db_at_47 = -3420
-  db_at_46 = -3660
-  db_at_45 = -3660
-  db_at_44 = -3660
-  db_at_43 = -3660
-  db_at_42 = -3660
-  db_at_41 = -3900
-  db_at_40 = -3900
-  db_at_39 = -3900
-  db_at_38 = -3900
-  db_at_37 = -4140
-  db_at_36 = -4140
-  db_at_35 = -4140
-  db_at_34 = -4140
-  db_at_33 = -4380
-  db_at_32 = -4380
-  db_at_31 = -4380
-  db_at_30 = -4380
-  db_at_29 = -4620
-  db_at_28 = -4620
-  db_at_27 = -4620
-  db_at_26 = -4860
-  db_at_25 = -4860
-  db_at_24 = -4860
-  db_at_23 = -4860
-  db_at_22 = -4860
-  db_at_21 = -5100
-  db_at_20 = -5100
-  db_at_19 = -5100
-  db_at_18 = -5340
-  db_at_17 = -5340
-  db_at_16 = -5340
-  db_at_15 = -5340
-  db_at_14 = -5340
-  db_at_13 = -5580
-  db_at_12 = -5580
-  db_at_11 = -5580
-  db_at_10 = -5580
-  db_at_9 = -5820
-  db_at_8 = -5820
-  db_at_7 = -5820
-  db_at_6 = -6060
-  db_at_5 = -6060
-  db_at_4 = -6060
-  db_at_3 = -6060
-  db_at_2 = -6060
-  db_at_1 = -6300
-  db_at_0 = -6300
diff --git a/cras-config/squawks/dsp.ini b/cras-config/squawks/dsp.ini
deleted file mode 100644
index c32c80a..0000000
--- a/cras-config/squawks/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=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=160       ; 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=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=2       ; highpass
-input_13=200     ; freq
-input_14=0       ; Q
-input_15=-9      ; gain
-input_16=2       ; highpass
-input_17=200     ; freq
-input_18=0       ; Q
-input_19=-6      ; gain
-input_20=6       ; peaking
-input_21=520     ; freq
-input_22=6       ; Q
-input_23=-15     ; gain
-input_24=6       ; peaking
-input_25=550     ; freq
-input_26=6       ; Q
-input_27=-15     ; gain
-input_28=6       ; peaking
-input_29=1100    ; freq
-input_30=6       ; Q
-input_31=-12     ; gain
-input_32=6       ; peaking
-input_33=1100    ; freq
-input_34=6       ; Q
-input_35=-12     ; gain
-input_36=6       ; peaking
-input_37=770     ; freq
-input_38=3       ; Q
-input_39=-9      ; gain
-input_40=6       ; peaking
-input_41=770     ; freq
-input_42=3       ; Q
-input_43=-9      ; gain
-input_44=1       ; lowpass
-input_45=16000   ; freq
-input_46=0       ; Q
-input_47=0       ; gain
-input_48=1       ; lowpass
-input_49=16000   ; freq
-input_50=0       ; Q
-input_51=0       ; gain
diff --git a/cras-config/winky/byt-max98090 b/cras-config/winky/byt-max98090
deleted file mode 100644
index 821cdb3..0000000
--- a/cras-config/winky/byt-max98090
+++ /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 = -3000
-  db_at_23 = -3000
-  db_at_22 = -3000
-  db_at_21 = -3000
-  db_at_20 = -3300
-  db_at_19 = -3300
-  db_at_18 = -3300
-  db_at_17 = -3300
-  db_at_16 = -3700
-  db_at_15 = -3700
-  db_at_14 = -3700
-  db_at_13 = -3700
-  db_at_12 = -4100
-  db_at_11 = -4100
-  db_at_10 = -4100
-  db_at_9 = -4100
-  db_at_8 = -4700
-  db_at_7 = -4700
-  db_at_6 = -4700
-  db_at_5 = -4700
-  db_at_4 = -5400
-  db_at_3 = -5400
-  db_at_2 = -5400
-  db_at_1 = -5400
-  db_at_0 = -6500
-[Headphone]
-  volume_curve = explicit
-  db_at_100 = -900
-  db_at_99 = -900
-  db_at_98 = -900
-  db_at_97 = -900
-  db_at_96 = -1000
-  db_at_95 = -1000
-  db_at_94 = -1000
-  db_at_93 = -1000
-  db_at_92 = -1200
-  db_at_91 = -1200
-  db_at_90 = -1200
-  db_at_89 = -1200
-  db_at_88 = -1400
-  db_at_87 = -1400
-  db_at_86 = -1400
-  db_at_85 = -1400
-  db_at_84 = -1600
-  db_at_83 = -1600
-  db_at_82 = -1600
-  db_at_81 = -1600
-  db_at_80 = -1800
-  db_at_79 = -1800
-  db_at_78 = -1800
-  db_at_77 = -1800
-  db_at_76 = -2000
-  db_at_75 = -2000
-  db_at_74 = -2000
-  db_at_73 = -2000
-  db_at_72 = -2200
-  db_at_71 = -2200
-  db_at_70 = -2200
-  db_at_69 = -2200
-  db_at_68 = -2300
-  db_at_67 = -2300
-  db_at_66 = -2300
-  db_at_65 = -2300
-  db_at_64 = -2500
-  db_at_63 = -2500
-  db_at_62 = -2500
-  db_at_61 = -2500
-  db_at_60 = -2700
-  db_at_59 = -2700
-  db_at_58 = -2700
-  db_at_57 = -2700
-  db_at_56 = -2900
-  db_at_55 = -2900
-  db_at_54 = -2900
-  db_at_53 = -2900
-  db_at_52 = -3100
-  db_at_51 = -3100
-  db_at_50 = -3100
-  db_at_49 = -3100
-  db_at_48 = -3300
-  db_at_47 = -3300
-  db_at_46 = -3300
-  db_at_45 = -3300
-  db_at_44 = -3500
-  db_at_43 = -3500
-  db_at_42 = -3500
-  db_at_41 = -3500
-  db_at_40 = -3700
-  db_at_39 = -3700
-  db_at_38 = -3700
-  db_at_37 = -3700
-  db_at_36 = -4000
-  db_at_35 = -4000
-  db_at_34 = -4000
-  db_at_33 = -4000
-  db_at_32 = -4300
-  db_at_31 = -4300
-  db_at_30 = -4300
-  db_at_29 = -4300
-  db_at_28 = -4600
-  db_at_27 = -4600
-  db_at_26 = -4600
-  db_at_25 = -4600
-  db_at_24 = -4900
-  db_at_23 = -4900
-  db_at_22 = -4900
-  db_at_21 = -4900
-  db_at_20 = -5200
-  db_at_19 = -5200
-  db_at_18 = -5200
-  db_at_17 = -5200
-  db_at_16 = -5500
-  db_at_15 = -5500
-  db_at_14 = -5500
-  db_at_13 = -5500
-  db_at_12 = -5800
-  db_at_11 = -5800
-  db_at_10 = -5800
-  db_at_9 = -5800
-  db_at_8 = -6200
-  db_at_7 = -6200
-  db_at_6 = -6200
-  db_at_5 = -6200
-  db_at_4 = -6600
-  db_at_3 = -6600
-  db_at_2 = -6600
-  db_at_1 = -6600
-  db_at_0 = -7000
diff --git a/cras-config/winky/dsp.ini b/cras-config/winky/dsp.ini
deleted file mode 100644
index 2055b1f..0000000
--- a/cras-config/winky/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}
-
-[eq2]
-library=builtin
-label=eq2
-input_0={src:0}
-input_1={src:1}
-output_2={intermediate:0}
-output_3={intermediate:1}
-input_4=2       ; highpass
-input_5=200     ; freq
-input_6=0       ; Q
-input_7=-8      ; gain
-input_8=2       ; highpass
-input_9=200     ; freq
-input_10=0       ; Q
-input_11=-8      ; gain
-input_12=5       ; highshelf
-input_13=20      ; freq
-input_14=0       ; Q
-input_15=-8      ; gain
-input_16=5       ; highshelf
-input_17=20      ; freq
-input_18=0       ; Q
-input_19=-8      ; gain
-input_20=6       ; peaking
-input_21=400     ; freq
-input_22=2       ; Q
-input_23=4       ; gain
-input_24=6       ; peaking
-input_25=400     ; freq
-input_26=2       ; Q
-input_27=4       ; gain
-input_28=6       ; peaking
-input_29=850     ; freq
-input_30=2.5     ; Q
-input_31=-5.2    ; gain
-input_32=6       ; peaking
-input_33=850     ; freq
-input_34=2.5     ; Q
-input_35=-5.2    ; gain
-input_36=6       ; peaking
-input_37=2400    ; freq
-input_38=1.2     ; Q
-input_39=2       ; gain
-input_40=6       ; peaking
-input_41=2400    ; freq
-input_42=1.2     ; Q
-input_43=2       ; gain
-input_44=6       ; peaking
-input_45=5250    ; freq
-input_46=2       ; Q
-input_47=-11     ; gain
-input_48=6       ; peaking
-input_49=5250    ; freq
-input_50=2       ; Q
-input_51=-11     ; gain
-input_52=6       ; peaking
-input_53=9000    ; freq
-input_54=1       ; Q
-input_55=7.5     ; gain
-input_56=6       ; peaking
-input_57=9000    ; freq
-input_58=1       ; Q
-input_59=7.5     ; gain
-
-[drc]
-library=builtin
-label=drc
-input_0={intermediate:0}
-input_1={intermediate:1}
-output_2={dst:0}
-output_3={dst:1}
-input_4=1         ; emphasis_disabled
-input_5=0         ; f
-input_6=1         ; enable
-input_7=-11       ; threshold
-input_8=0         ; knee
-input_9=999       ; ratio
-input_10=0.001     ; attack
-input_11=0.8       ; release
-input_12=4.4       ; boost
-input_13=20000     ; f
-input_14=0         ; enable
-input_15=-11       ; threshold
-input_16=0         ; knee
-input_17=999       ; ratio
-input_18=0.1       ; attack
-input_19=0.8       ; release
-input_20=4.7       ; boost
-input_21=21000     ; f
-input_22=0         ; enable
-input_23=-11       ; threshold
-input_24=0         ; knee
-input_25=999       ; ratio
-input_26=0.1       ; attack
-input_27=0.8       ; release
-input_28=4.7       ; boost
diff --git a/cras/.gitignore b/cras/.gitignore
index ea2c721..40d4c80 100644
--- a/cras/.gitignore
+++ b/cras/.gitignore
@@ -7,11 +7,15 @@
 tags
 libcras.pc
 src/mix_unittest
+src/cras_monitor
 src/cras_test_client
 src/*_unittest
+/.__autoconf_trace_data
+/.elibtoolized
 /Makefile
 /Makefile.in
 /aclocal.m4
+/ar-lib
 autom4te.cache/
 /compile
 /config.guess
@@ -24,6 +28,10 @@
 /libtool
 /ltmain.sh
 /missing
+/test-driver
+src/*.la
+src/*.log
+src/*.trs
 src/.libs/
 src/Makefile
 src/Makefile.in
@@ -52,5 +60,6 @@
 src/dsp/.dirstamp
 src/dsp/tests/.deps/
 src/dsp/tests/.dirstamp
+src/dsp_util_test
 src/eq2_test
 src/eq_test
diff --git a/cras/README b/cras/README
index 58ad088..bb7004a 100644
--- a/cras/README
+++ b/cras/README
@@ -48,7 +48,19 @@
 [<output-node-name>] ; Name of the mixer control for this output.
   <config-option> = <config-value>
 
-output-node-name is the mixer control name, e.g. "Headphone" or "Speaker".
+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
+    config, i.e. HiFi.conf
+  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
+    'amixer scontrols' command.
+
+Note that an output node matches to the output-node-name label in card config by
+priorty ordered above. For example if a node has UCM device, it will first
+search the config file for the UCM device name. When not found, jack name will
+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:
diff --git a/cras/README.dbus-api b/cras/README.dbus-api
index daa480a..819f068 100644
--- a/cras/README.dbus-api
+++ b/cras/README.dbus-api
@@ -96,6 +96,10 @@
 				uint64 StableDeviceId
 					The stable ID does not change due to
 					device plug/unplug or reboot.
+				uint64 StableDeviceIdNew
+					The new stable ID. Keeping both stable
+					ID and stable ID new is for backward
+					compatibility.
 				boolean Active
 					Whether this node is currently used
 					for output/input. There is one active
@@ -107,6 +111,12 @@
 				string MicPositions
 					The string formed by floating numbers
 					describing the position of mic array.
+				string HotwordModels
+					A string of comma-separated hotword
+					language model locales supported by this
+					node. e.g. "en_au,en_gb,en_us"
+					The string is empty if the node type is
+					not HOTWORD.
 
 		void SetActiveOutputNode(uint64 node_id);
 
@@ -135,6 +145,28 @@
 
 			Returns the number of streams currently using output hardware.
 
+		void SetGlobalOutputChannelRemix(int32 num_channels,
+						 array:double coefficient)
+
+			Sets the conversion matrix for global output channel
+			remixing. The coefficient array represents an N * N
+			conversion matrix M, where N is num_channels, with
+			M[i][j] = coefficient[i * N + j].
+			The remix is done by multiplying the conversion matrix
+			to each N-channel PCM data, i.e M * [L, R] = [L', R']
+			For example, coefficient [0.1, 0.9, 0.4, 0.6] will
+			result in:
+			L' = 0.1 * L + 0.9 * R
+			R' = 0.4 * L + 0.6 * R
+
+		int32 SetHotwordModel(uint64_t node_id, string model_name)
+
+			Set the hotword language model on the specified node.
+			The node must have type HOTWORD and the model_name must
+			be one of the supported locales returned by
+			GetNodes() HotwordModels string.
+			Returns 0 on success, or a negative errno on failure.
+
 Signals		OutputVolumeChanged(int32 volume)
 
 			Indicates that the output volume level has changed.
diff --git a/cras/configure.ac b/cras/configure.ac
index d13715d..97d0849 100644
--- a/cras/configure.ac
+++ b/cras/configure.ac
@@ -4,8 +4,13 @@
 AC_INIT([cras], [0.1], [dgreid@chromium.org],
              [cras], [http://www.chromium.org/])
 AC_PREREQ([2.59])
+
+AC_CANONICAL_TARGET
+
 AM_INIT_AUTOMAKE([1.10 -Wall no-define])
 #AC_CONFIG_HEADERS([config.h])
+
+m4_ifdef([AM_PROG_AR], [AM_PROG_AR])
 AC_PROG_LIBTOOL
 AC_PROG_CC
 # c++ unit test (gtest).
@@ -16,8 +21,21 @@
 #AC_CONFIG_FILES([Makefile src/Makefile libcras.pc])
 
 PKG_CHECK_MODULES([LIBSPEEX], [ speexdsp >= 1.2 ])
-PKG_CHECK_MODULES([ASOUNDLIB], [ alsa >= 1.0.27 ])
-PKG_CHECK_MODULES([DBUS], [ dbus-1 >= 1.4.12 ])
+PKG_CHECK_MODULES([ASOUNDLIB], [ alsa >= 1.1.0 ])
+
+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
+    PKG_CHECK_MODULES([DBUS], [ dbus-1 >= 1.4.12 ])
+    DBUS_CFLAGS+=-DCRAS_DBUS
+    AC_SUBST(DBUS_CFLAGS)
+else
+    DBUS_CFLAGS=
+    AC_SUBST(DBUS_CFLAGS)
+    DBUS_LIBS=
+    AC_SUBST(DBUS_LIBS)
+fi
+
 PKG_CHECK_MODULES([SBC], [ sbc >= 1.0 ])
 AC_CHECK_LIB(asound, snd_pcm_ioplug_create,,
 	     AC_ERROR([*** libasound has no external plugin SDK]), -ldl)
@@ -46,6 +64,20 @@
 ALSA_PLUGIN_DIR="$plugindir"
 AC_SUBST(ALSA_PLUGIN_DIR)
 
+# Determine CRAS configuration directory.
+eval cras_config_file_dir="$sysconfdir/cras"
+AC_DEFINE_UNQUOTED(CRAS_CONFIG_FILE_DIR, "$cras_config_file_dir",
+                   [directory containing CRAS configuration])
+
+# CRAS socket dir
+AC_ARG_WITH(socketdir,
+    AS_HELP_STRING([--with-socketdir=dir],
+        [path where CRAS stores its sockets]),
+    socketdir="$withval",
+    socketdir="/run/cras")
+AC_DEFINE_UNQUOTED(CRAS_SOCKET_FILE_DIR, "$socketdir",
+                   [directory containing CRAS socket files])
+
 # Get iniparser library and include locations
 AC_ARG_WITH([iniparser-include-path],
   [AS_HELP_STRING([--with-iniparser-include-path],
@@ -61,8 +93,68 @@
           [INIPARSER_LIBS='-liniparser'])
 AC_SUBST([INIPARSER_LIBS])
 
+# SSE4_2 support
+AC_ARG_ENABLE(sse42, [AS_HELP_STRING([--enable-sse42],[enable SSE42 optimizations])], have_sse42=$enableval, have_sse42=yes)
+if  test "x$target_cpu" != xx86_64; then
+	have_sse42=no
+fi
+if test "$have_sse42" = "yes"; then
+        AC_DEFINE(HAVE_SSE42,1,[Define to enable SSE42 optimizations.])
+	SSE42_CFLAGS="-DOPS_SSE42 -msse4.2 -ffast-math"
+fi
+AM_CONDITIONAL(HAVE_SSE42, test "$have_sse42" = "yes")
+AC_SUBST(SSE42_CFLAGS)
+
+# AVX support
+AC_ARG_ENABLE(avx, [AS_HELP_STRING([--enable-avx],[enable AVX optimizations])], have_avx=$enableval, have_avx=yes)
+if  test "x$target_cpu" != xx86_64; then
+	have_avx=no
+fi
+if test "$have_avx" = "yes"; then
+        AC_DEFINE(HAVE_AVX,1,[Define to enable AVX optimizations.])
+	AVX_CFLAGS="-DOPS_AVX -mavx -ffast-math"
+fi
+AM_CONDITIONAL(HAVE_AVX, test "$have_avx" = "yes")
+AC_SUBST(AVX_CFLAGS)
+
+# AVX2 support
+AC_ARG_ENABLE(avx2, [AS_HELP_STRING([--enable-avx2],[enable AVX2 optimizations])], have_avx2=$enableval, have_avx2=yes)
+if  test "x$target_cpu" != xx86_64; then
+	have_avx2=no
+fi
+if test "$have_avx2" = "yes"; then
+        AC_DEFINE(HAVE_AVX2,1,[Define to enable AVX2 optimizations.])
+	AVX2_CFLAGS="-DOPS_AVX2 -mavx2 -ffast-math"
+fi
+AM_CONDITIONAL(HAVE_AVX2, test "$have_avx2" = "yes")
+AC_SUBST(AVX2_CFLAGS)
+
+# FMA support
+AC_ARG_ENABLE(fma, [AS_HELP_STRING([--enable-fma],[enable FMA optimizations])], have_fma=$enableval, have_fma=yes)
+if  test "x$target_cpu" != xx86_64; then
+	have_fma=no
+fi
+if test "$have_fma" = "yes"; then
+        AC_DEFINE(HAVE_FMA,1,[Define to enable FMA optimizations.])
+	FMA_CFLAGS="-DOPS_FMA -mavx2 -mfma -ffast-math"
+fi
+AM_CONDITIONAL(HAVE_FMA, test "$have_fma" = "yes")
+AC_SUBST(FMA_CFLAGS)
+
 AC_OUTPUT([
 	Makefile
 	src/Makefile
 	libcras.pc
 ])
+
+AS_IF([test "$have_sse42" = "yes"], ENABLE_SSE42=yes, ENABLE_SSE42=no)
+AS_IF([test "$have_avx" = "yes"], ENABLE_AVX=yes, ENABLE_AVX=no)
+AS_IF([test "$have_avx2" = "yes"], ENABLE_AVX2=yes, ENABLE_AVX2=no)
+AS_IF([test "$have_fma" = "yes"], ENABLE_FMA=yes, ENABLE_FMA=no)
+
+echo "
+Enable SSE42:                  ${ENABLE_SSE42}
+Enable AVX:                    ${ENABLE_AVX}
+Enable AVX2:                   ${ENABLE_AVX2}
+Enable FMA:                    ${ENABLE_FMA}
+"
diff --git a/cras/src/Android.mk b/cras/src/Android.mk
index 63d6110..fc2ee4c 100644
--- a/cras/src/Android.mk
+++ b/cras/src/Android.mk
@@ -5,13 +5,12 @@
 LOCAL_SRC_FILES := \
 	common/cras_audio_format.c \
 	common/cras_config.c \
+	common/cras_file_wait.c \
 	common/cras_util.c \
 	common/edid_utils.c \
 	libcras/cras_client.c \
 	libcras/cras_helpers.c
 
-LOCAL_SRC_FILES_arm := arm/shm.S
-
 LOCAL_SHARED_LIBRARIES := \
 	libtinyalsa
 
@@ -20,6 +19,9 @@
 	$(LOCAL_PATH)/libcras \
 	external/tinyalsa/include
 
+LOCAL_CFLAGS += \
+	-DCRAS_SOCKET_FILE_DIR=\"/var/run/cras\"
+
 LOCAL_MODULE := libcras
 
 include $(BUILD_STATIC_LIBRARY)
diff --git a/cras/src/Makefile.am b/cras/src/Makefile.am
index b71d667..e83e948 100644
--- a/cras/src/Makefile.am
+++ b/cras/src/Makefile.am
@@ -5,14 +5,69 @@
 AUTOMAKE_OPTIONS = subdir-objects
 ACLOCAL_AMFLAGS = ${ACLOCAL_FLAGS}
 
-COMMON_CPPFLAGS = -O2 -Wall -Werror -Wno-error=cpp
+if HAVE_SSE42
+CRAS_SSE4_2 = libcrasmix_sse42.la
+else
+CRAS_SSE4_2 =
+endif
 
-bin_PROGRAMS = cras cras_test_client
+if HAVE_AVX
+CRAS_AVX = libcrasmix_avx.la
+else
+CRAS_AVX =
+endif
+
+if HAVE_AVX2
+CRAS_AVX2 = libcrasmix_avx2.la
+else
+CRAS_AVX2 =
+endif
+
+if HAVE_FMA
+CRAS_FMA = libcrasmix_fma.la
+else
+CRAS_FMA =
+endif
+
+CRAS_UT_TMPDIR_CFLAGS=-DCRAS_UT_TMPDIR=\"/tmp\"
+COMMON_CPPFLAGS = -O2 -Wall -Werror -Wno-error=cpp
+COMMON_SIMD_CPPFLAGS = -O3 -Wall -Werror -Wno-error=cpp
+
+bin_PROGRAMS = cras cras_test_client cras_monitor cras_router
+
+if HAVE_DBUS
+CRAS_DBUS_SOURCES = \
+	common/cras_sbc_codec.c \
+	server/cras_bt_manager.c \
+	server/cras_bt_adapter.c \
+	server/cras_bt_device.c \
+	server/cras_bt_transport.c \
+	server/cras_bt_endpoint.c \
+	server/cras_bt_player.c \
+	server/cras_bt_io.c \
+	server/cras_bt_profile.c \
+	server/cras_dbus.c \
+	server/cras_dbus_util.c \
+	server/cras_dbus_control.c \
+	server/cras_hfp_ag_profile.c \
+	server/cras_hfp_iodev.c \
+	server/cras_hfp_info.c \
+	server/cras_hfp_slc.c \
+	server/cras_a2dp_endpoint.c \
+	server/cras_a2dp_info.c \
+	server/cras_a2dp_iodev.c \
+	server/cras_telephony.c
+else
+CRAS_DBUS_SOURCES =
+endif
+
 cras_SOURCES = \
+	$(CRAS_DBUS_SOURCES) \
 	common/cras_audio_format.c \
 	common/cras_checksum.c \
 	common/cras_config.c \
 	common/cras_metrics.c \
+	common/cras_shm.c \
 	common/cras_util.c \
 	common/dumper.c \
 	common/edid_utils.c \
@@ -27,30 +82,21 @@
 	dsp/eq.c \
 	dsp/eq2.c \
 	server/audio_thread.c \
+	server/buffer_share.c \
 	server/config/cras_card_config.c \
 	server/config/cras_device_blacklist.c \
 	server/cras.c \
-	server/cras_a2dp_endpoint.c \
-	server/cras_a2dp_info.c \
-	server/cras_a2dp_iodev.c \
 	server/cras_alert.c \
 	server/cras_alsa_card.c \
 	server/cras_alsa_helpers.c \
 	server/cras_alsa_io.c \
 	server/cras_alsa_jack.c \
 	server/cras_alsa_mixer.c \
+	server/cras_alsa_mixer_name.c \
 	server/cras_alsa_ucm.c \
+	server/cras_alsa_ucm_section.c \
 	server/cras_audio_area.c \
-	server/cras_bt_manager.c \
-	server/cras_bt_adapter.c \
-	server/cras_bt_device.c \
-	server/cras_bt_transport.c \
-	server/cras_bt_endpoint.c \
-	server/cras_bt_io.c \
-	server/cras_bt_profile.c \
-	server/cras_dbus.c \
-	server/cras_dbus_util.c \
-	server/cras_dbus_control.c \
+	server/cras_device_monitor.c \
 	server/cras_dsp.c \
 	server/cras_dsp_ini.c \
 	server/cras_dsp_mod_builtin.c \
@@ -60,23 +106,19 @@
 	server/cras_expr.c \
 	server/cras_fmt_conv.c \
 	server/cras_gpio_jack.c \
-	server/cras_hfp_ag_profile.c \
-	server/cras_hfp_info.c \
-	server/cras_hfp_iodev.c \
-	server/cras_hfp_slc.c \
 	server/cras_iodev.c \
 	server/cras_iodev_list.c \
 	server/cras_loopback_iodev.c \
 	server/cras_main_message.c \
 	server/cras_mix.c \
+	server/cras_observer.c \
+	server/cras_ramp.c \
 	server/cras_rclient.c \
 	server/cras_rstream.c \
-	server/buffer_share.c \
-	common/cras_sbc_codec.c \
+	server/cras_utf8.c \
 	server/cras_server.c \
 	server/cras_server_metrics.c \
 	server/cras_system_state.c \
-	server/cras_telephony.c \
 	server/cras_tm.c \
 	server/cras_udev.c \
 	server/cras_volume_curve.c \
@@ -91,14 +133,74 @@
 	-I$(top_srcdir)/src/dsp -I$(top_srcdir)/src/server \
 	-I$(top_srcdir)/src/server/config \
 	$(DBUS_CFLAGS) $(SBC_CFLAGS)
-cras_LDADD = -lpthread -lasound -lrt -liniparser -ludev -ldl -lm -lspeexdsp \
+
+cras_LDADD = \
+	libcrasmix.la \
+	$(CRAS_SSE4_2) \
+	$(CRAS_AVX) \
+	$(CRAS_AVX2) \
+	$(CRAS_FMA) \
+	-lpthread -lasound -lrt -liniparser -ludev -ldl -lm -lspeexdsp \
 	$(SBC_LIBS) \
 	$(DBUS_LIBS)
 
+noinst_LTLIBRARIES = \
+	$(CRAS_SSE4_2) \
+	$(CRAS_AVX) \
+	$(CRAS_AVX2) \
+	$(CRAS_FMA) \
+	libcrasmix.la
+
+libcrasmix_la_SOURCES = \
+	server/cras_mix_ops.c
+
+libcrasmix_la_CFLAGS = \
+	$(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \
+	-I$(top_srcdir)/src/dsp -I$(top_srcdir)/src/server \
+	-I$(top_srcdir)/src/server/config \
+	$(DBUS_CFLAGS) $(SBC_CFLAGS)
+
+libcrasmix_sse42_la_SOURCES = \
+	server/cras_mix_ops.c
+
+libcrasmix_sse42_la_CFLAGS = \
+	$(COMMON_SIMD_CPPFLAGS) -I$(top_srcdir)/src/common \
+	-I$(top_srcdir)/src/dsp -I$(top_srcdir)/src/server \
+	-I$(top_srcdir)/src/server/config \
+	$(DBUS_CFLAGS) $(SSE42_CFLAGS)
+
+libcrasmix_avx_la_SOURCES = \
+	server/cras_mix_ops.c
+
+libcrasmix_avx_la_CFLAGS = \
+	$(COMMON_SIMD_CPPFLAGS) -I$(top_srcdir)/src/common \
+	-I$(top_srcdir)/src/dsp -I$(top_srcdir)/src/server \
+	-I$(top_srcdir)/src/server/config \
+	$(DBUS_CFLAGS) $(AVX_CFLAGS)
+
+libcrasmix_avx2_la_SOURCES = \
+	server/cras_mix_ops.c
+
+libcrasmix_avx2_la_CFLAGS = \
+	$(COMMON_SIMD_CPPFLAGS) -I$(top_srcdir)/src/common \
+	-I$(top_srcdir)/src/dsp -I$(top_srcdir)/src/server \
+	-I$(top_srcdir)/src/server/config \
+	$(DBUS_CFLAGS) $(AVX2_CFLAGS)
+
+libcrasmix_fma_la_SOURCES = \
+	server/cras_mix_ops.c
+
+libcrasmix_fma_la_CFLAGS = \
+	$(COMMON_SIMD_CPPFLAGS) -I$(top_srcdir)/src/common \
+	-I$(top_srcdir)/src/dsp -I$(top_srcdir)/src/server \
+	-I$(top_srcdir)/src/server/config \
+	$(DBUS_CFLAGS) $(FMA_CFLAGS)
+
 lib_LTLIBRARIES = libcras.la
 libcras_la_SOURCES = \
 	common/cras_audio_format.c \
 	common/cras_config.c \
+	common/cras_file_wait.c \
 	common/cras_util.c \
 	common/edid_utils.c \
 	libcras/cras_client.c \
@@ -150,27 +252,37 @@
 $(hide_install)-asound_module_pcm_crasLTLIBRARIES: install-libLTLIBRARIES
 $(hide_install)-asound_module_ctl_crasLTLIBRARIES: install-libLTLIBRARIES
 
+if HAVE_DBUS
+DBUS_TESTS = \
+	a2dp_info_unittest \
+	a2dp_iodev_unittest \
+	alsa_io_unittest \
+	bt_device_unittest \
+	bt_io_unittest \
+	hfp_iodev_unittest \
+	hfp_slc_unittest
+else
+DBUS_TESTS =
+endif
+
 TESTS = \
+	$(DBUS_TESTS) \
 	audio_area_unittest \
 	audio_format_unittest \
 	audio_thread_unittest \
-	a2dp_info_unittest \
-	a2dp_iodev_unittest \
 	alert_unittest \
 	alsa_card_unittest \
 	alsa_helpers_unittest \
-	alsa_io_unittest \
 	alsa_jack_unittest \
 	alsa_mixer_unittest \
 	alsa_ucm_unittest \
 	array_unittest \
-	bt_device_unittest \
-	bt_io_unittest \
 	byte_buffer_unittest \
 	card_config_unittest \
 	checksum_unittest \
 	cras_client_unittest \
 	cras_tm_unittest \
+	device_monitor_unittest \
 	dev_stream_unittest \
 	device_blacklist_unittest \
 	dsp_core_unittest \
@@ -180,23 +292,26 @@
 	dumper_unittest \
 	edid_utils_unittest \
 	expr_unittest \
+	file_wait_unittest \
 	fmt_conv_unittest \
 	hfp_info_unittest \
-	hfp_iodev_unittest \
-	hfp_slc_unittest \
 	buffer_share_unittest \
 	iodev_list_unittest \
 	iodev_unittest \
 	loopback_iodev_unittest \
 	mix_unittest \
 	linear_resampler_unittest \
+	observer_unittest \
+	ramp_unittest \
 	rate_estimator_unittest \
 	rclient_unittest \
 	rstream_unittest \
 	shm_unittest \
+	server_metrics_unittest \
 	softvol_curve_unittest \
 	stream_list_unittest \
 	system_state_unittest \
+	utf8_unittest \
 	util_unittest \
 	volume_curve_unittest
 
@@ -209,6 +324,21 @@
 
 tests/cras_test_client.c: common/cras_version.h
 
+cras_monitor_SOURCES = tests/cras_monitor.c
+cras_monitor_LDADD = -lm libcras.la
+cras_monitor_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/libcras \
+	-I$(top_srcdir)/src/common -I$(top_builddir)/src/common
+
+tests/cras_monitor.c: common/cras_version.h
+
+cras_router_SOURCES = tests/cras_router.c
+cras_router_LDADD = -lm libcras.la
+cras_router_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/libcras \
+        -I$(top_srcdir)/src/common -I$(top_srcdir)/src/dsp \
+	-I$(top_srcdir)/src/server -I$(top_builddir)/src/common
+
+tests/cras_router.c: common/cras_version.h
+
 CLEANFILES = common/cras_version.h
 .PHONY: common/cras_version.h
 common/cras_version.h:
@@ -228,6 +358,7 @@
 	crossover_test \
 	crossover2_test \
 	drc_test \
+	dsp_util_test \
 	eq_test \
 	eq2_test \
 	cmpraw
@@ -248,6 +379,10 @@
 drc_test_LDADD = -lrt -lm
 drc_test_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/dsp
 
+dsp_util_test_SOURCES = dsp/tests/dsp_util_test.c dsp/dsp_util.c
+dsp_util_test_LDADD = -lm
+dsp_util_test_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/dsp -Wno-error=strict-aliasing
+
 eq_test_SOURCES = dsp/biquad.c dsp/eq.c dsp/dsp_util.c dsp/tests/eq_test.c \
 	dsp/tests/dsp_test_util.c dsp/tests/raw.c
 eq_test_LDADD = -lrt -lm
@@ -271,7 +406,8 @@
 alert_unittest_LDADD = -lgtest -lpthread
 
 alsa_card_unittest_SOURCES = tests/alsa_card_unittest.cc \
-	server/cras_alsa_card.c
+	server/cras_alsa_card.c server/cras_alsa_mixer_name.c \
+	server/cras_alsa_ucm_section.c
 alsa_card_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) \
 	-I$(top_srcdir)/src/common \
 	-I$(top_srcdir)/src/server \
@@ -296,6 +432,7 @@
 	-I$(top_srcdir)/src/common
 audio_format_unittest_LDADD = -lgtest -lpthread
 
+if HAVE_DBUS
 a2dp_info_unittest_SOURCES = tests/a2dp_info_unittest.cc \
 	server/cras_a2dp_info.c
 a2dp_info_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/server \
@@ -307,27 +444,37 @@
 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)
+endif
 
 alsa_io_unittest_SOURCES = tests/alsa_io_unittest.cc server/softvol_curve.c \
-	common/sfh.c
+	common/sfh.c \
+	server/cras_alsa_ucm_section.c \
+	server/cras_alsa_mixer_name.c
 alsa_io_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/server \
-	-I$(top_srcdir)/src/common $(DBUS_CFLAGS)
+	-I$(top_srcdir)/src/common \
+	-I$(top_srcdir)/src/server/config
 alsa_io_unittest_LDADD = -lgtest -lpthread
 
 alsa_jack_unittest_SOURCES = tests/alsa_jack_unittest.cc \
-	server/cras_alsa_jack.c
+	server/cras_alsa_jack.c \
+	server/cras_alsa_ucm_section.c \
+	server/cras_alsa_mixer_name.c
 alsa_jack_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \
 	-I$(top_srcdir)/src/server
 alsa_jack_unittest_LDADD = -lgtest -lpthread
 
-alsa_mixer_unittest_SOURCES = tests/alsa_mixer_unittest.cc
+alsa_mixer_unittest_SOURCES = tests/alsa_mixer_unittest.cc \
+	server/cras_alsa_mixer_name.c \
+	server/cras_alsa_ucm_section.c
 alsa_mixer_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) \
 	-I$(top_srcdir)/src/common \
 	-I$(top_srcdir)/src/server \
 	-I$(top_srcdir)/src/server/config
 alsa_mixer_unittest_LDADD = -lgtest -lpthread
 
-alsa_ucm_unittest_SOURCES = tests/alsa_ucm_unittest.cc
+alsa_ucm_unittest_SOURCES = tests/alsa_ucm_unittest.cc \
+	server/cras_alsa_mixer_name.c \
+	server/cras_alsa_ucm_section.c
 alsa_ucm_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) \
 	-I$(top_srcdir)/src/common \
 	-I$(top_srcdir)/src/server \
@@ -343,32 +490,28 @@
 	-I$(top_srcdir)/src/common -I$(top_srcdir)/src/server
 audio_thread_unittest_LDADD = -lgtest -lpthread -lrt
 
+if HAVE_DBUS
 bt_device_unittest_SOURCES = tests/bt_device_unittest.cc \
 	server/cras_bt_device.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)
 
-bt_io_unittest_SOURCES = tests/bt_io_unittest.cc
+bt_io_unittest_SOURCES = tests/bt_io_unittest.cc common/sfh.c
 bt_io_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/server \
 	-I$(top_srcdir)/src/common $(DBUS_CFLAGS)
 bt_io_unittest_LDADD = -lgtest -lpthread $(DBUS_LIBS)
-
-bt_profile_unittest_SOURCES = tests/bt_profile_unittest.cc tests/dbus_test.cc \
-	server/cras_bt_profile.c
-bt_profile_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \
-	-I$(top_srcdir)/src/server $(DBUS_CFLAGS)
-bt_profile_unittest_LDADD = -lgtest -lpthread $(DBUS_LIBS)
+endif
 
 byte_buffer_unittest_SOURCES = tests/byte_buffer_unittest.cc
-byte_buffer_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/server
+byte_buffer_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common
 byte_buffer_unittest_LDADD = -lgtest -lpthread
 
 card_config_unittest_SOURCES = tests/card_config_unittest.cc \
 	server/config/cras_card_config.c
 card_config_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) \
 	-I$(top_srcdir)/src/common -I$(top_srcdir)/src/server \
-	-I$(top_srcdir)/src/server/config
+	-I$(top_srcdir)/src/server/config $(CRAS_UT_TMPDIR_CFLAGS)
 card_config_unittest_LDADD = -lgtest -liniparser -lpthread
 
 checksum_unittest_SOURCES = tests/checksum_unittest.cc common/cras_checksum.c
@@ -376,7 +519,7 @@
 checksum_unittest_LDADD = -lgtest -lpthread
 
 cras_client_unittest_SOURCES = tests/cras_client_unittest.cc \
-	common/cras_config.c common/cras_util.c
+	common/cras_config.c common/cras_util.c common/cras_file_wait.c
 cras_client_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \
 	-I$(top_srcdir)/src/libcras
 cras_client_unittest_LDADD = -lgtest -lpthread -lspeexdsp
@@ -396,9 +539,14 @@
 	server/config/cras_device_blacklist.c
 device_blacklist_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) \
 	-I$(top_srcdir)/src/common -I$(top_srcdir)/src/server \
-	-I$(top_srcdir)/src/server/config
+	-I$(top_srcdir)/src/server/config $(CRAS_UT_TMPDIR_CFLAGS)
 device_blacklist_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 \
+	-I$(top_srcdir)/src/server
+device_monitor_unittest_LDADD = -lgtest -lpthread
+
 dsp_core_unittest_SOURCES = tests/dsp_core_unittest.cc dsp/eq.c dsp/eq2.c \
 	dsp/biquad.c dsp/dsp_util.c dsp/crossover.c dsp/crossover2.c dsp/drc.c \
 	dsp/drc_kernel.c dsp/drc_math.c
@@ -439,6 +587,12 @@
 	-I$(top_srcdir)/src/server
 expr_unittest_LDADD = -lgtest -lpthread
 
+file_wait_unittest_SOURCES = tests/file_wait_unittest.cc \
+	common/cras_file_wait.c common/cras_util.c
+file_wait_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \
+	$(CRAS_UT_TMPDIR_CFLAGS)
+file_wait_unittest_LDADD = -lgtest -lpthread
+
 fmt_conv_unittest_SOURCES = tests/fmt_conv_unittest.cc server/cras_fmt_conv.c
 fmt_conv_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \
 	 -I$(top_srcdir)/src/server
@@ -449,6 +603,7 @@
 	-I$(top_srcdir)/src/server
 hfp_info_unittest_LDADD = -lgtest -lpthread
 
+if HAVE_DBUS
 hfp_iodev_unittest_SOURCES = tests/hfp_iodev_unittest.cc \
 	server/cras_hfp_iodev.c common/sfh.c
 hfp_iodev_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \
@@ -460,6 +615,7 @@
 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)
+endif
 
 buffer_share_unittest_SOURCES = tests/buffer_share_unittest.cc \
 	server/buffer_share.c
@@ -489,7 +645,13 @@
 mix_unittest_SOURCES = tests/mix_unittest.cc server/cras_mix.c
 mix_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \
 	 -I$(top_srcdir)/src/server
-mix_unittest_LDADD = -lgtest -lpthread
+mix_unittest_LDADD = libcrasmix.la \
+	$(CRAS_SSE4_2) \
+	$(CRAS_AVX) \
+	$(CRAS_AVX2) \
+	$(CRAS_FMA) \
+	-lgtest \
+	-lpthread
 
 linear_resampler_unittest_SOURCES = tests/linear_resampler_unittest.cc \
 	server/linear_resampler.c server/cras_audio_area.c
@@ -497,20 +659,36 @@
 	 -I$(top_srcdir)/src/server
 linear_resampler_unittest_LDADD = -lgtest -lpthread
 
+observer_unittest_SOURCES = tests/observer_unittest.cc
+observer_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \
+	-I$(top_srcdir)/src/server
+observer_unittest_LDADD = -lgtest -lpthread
+
+ramp_unittest_SOURCES = tests/ramp_unittest.cc
+ramp_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \
+	-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_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \
 	 -I$(top_srcdir)/src/server
 rate_estimator_unittest_LDADD = -lgtest -lpthread
 
-rclient_unittest_SOURCES = tests/rclient_unittest.cc server/cras_rclient.c
+rclient_unittest_SOURCES = tests/rclient_unittest.cc
 rclient_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \
-	 -I$(top_srcdir)/src/server
+	 -I$(top_srcdir)/src/server $(CRAS_UT_TMPDIR_CFLAGS)
 rclient_unittest_LDADD = -lgtest -lpthread
 
-rstream_unittest_SOURCES = tests/rstream_unittest.cc server/cras_rstream.c
+rstream_unittest_SOURCES = tests/rstream_unittest.cc server/cras_rstream.c \
+	common/cras_shm.c
 rstream_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \
 	 -I$(top_srcdir)/src/server
-rstream_unittest_LDADD = -lasound -lgtest -lpthread
+rstream_unittest_LDADD = -lasound -lgtest -lpthread -lrt
+
+server_metrics_unittest_SOURCES = tests/server_metrics_unittest.cc
+server_metrics_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \
+	-I$(top_srcdir)/src/server
+server_metrics_unittest_LDADD = -lgtest -lpthread
 
 shm_unittest_SOURCES = tests/shm_unittest.cc
 shm_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common
@@ -528,12 +706,17 @@
 stream_list_unittest_LDADD = -lgtest -lpthread
 
 system_state_unittest_SOURCES = tests/system_state_unittest.cc \
-	server/cras_system_state.c
+	server/cras_system_state.c common/cras_shm.c
 system_state_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) \
 	-I$(top_srcdir)/src/common -I$(top_srcdir)/src/server \
 	-I$(top_srcdir)/src/server/config
 system_state_unittest_LDADD = -lgtest -lrt -lpthread
 
+utf8_unittest_SOURCES = tests/utf8_unittest.cc server/cras_utf8.c
+utf8_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \
+	-I$(top_srcdir)/src/server
+utf8_unittest_LDADD = -lgtest -lpthread
+
 util_unittest_SOURCES = tests/util_unittest.cc common/cras_util.c \
 	common/cras_config.c
 util_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common
diff --git a/cras/src/arm/shm.S b/cras/src/arm/shm.S
deleted file mode 100644
index dcea684..0000000
--- a/cras/src/arm/shm.S
+++ /dev/null
@@ -1,55 +0,0 @@
-#include <asm/unistd.h> /* For system call numbers. */
-#define MAX_ERRNO 4095  /* For recognizing system call error returns. */
-
-#define __bionic_asm_custom_entry(f)
-#define __bionic_asm_custom_end(f)
-#define __bionic_asm_function_type @function
-
-#include <machine/asm.h>
-
-#define ENTRY(f) \
-    .text; \
-    .globl f; \
-    .align __bionic_asm_align; \
-    .type f, __bionic_asm_function_type; \
-    f: \
-    __bionic_asm_custom_entry(f); \
-    .cfi_startproc \
-
-#define END(f) \
-    .cfi_endproc; \
-    .size f, .-f; \
-    __bionic_asm_custom_end(f) \
-
-ENTRY(shmat)
-    mov     ip, r7
-    ldr     r7, =__NR_shmat
-    swi     #0
-    mov     r7, ip
-    cmn     r0, #(MAX_ERRNO + 1)
-    bxls    lr
-    neg     r0, r0
-    b       __set_errno_internal
-END(shmat)
-
-ENTRY(shmdt)
-    mov     ip, r7
-    ldr     r7, =__NR_shmdt
-    swi     #0
-    mov     r7, ip
-    cmn     r0, #(MAX_ERRNO + 1)
-    bxls    lr
-    neg     r0, r0
-    b       __set_errno_internal
-END(shmdt)
-
-ENTRY(shmget)
-    mov     ip, r7
-    ldr     r7, =__NR_shmget
-    swi     #0
-    mov     r7, ip
-    cmn     r0, #(MAX_ERRNO + 1)
-    bxls    lr
-    neg     r0, r0
-    b       __set_errno_internal
-END(shmget)
diff --git a/cras/src/server/byte_buffer.h b/cras/src/common/byte_buffer.h
similarity index 100%
rename from cras/src/server/byte_buffer.h
rename to cras/src/common/byte_buffer.h
diff --git a/cras/src/common/cras_audio_format.h b/cras/src/common/cras_audio_format.h
index a77bc84..516a18d 100644
--- a/cras/src/common/cras_audio_format.h
+++ b/cras/src/common/cras_audio_format.h
@@ -6,13 +6,41 @@
 #ifndef CRAS_AUDIO_FORMAT_H_
 #define CRAS_AUDIO_FORMAT_H_
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 #include <stdint.h>
 #include <string.h>
 
 #ifdef __ANDROID__
+#include <hardware/audio.h>
 #include <tinyalsa/asoundlib.h>
 #define PCM_FORMAT_WIDTH(format) pcm_format_to_bits(format)
 typedef enum pcm_format snd_pcm_format_t;
+
+/* libasound audio formats. */
+#define SND_PCM_FORMAT_UNKNOWN -1
+#define SND_PCM_FORMAT_U8       1
+#define SND_PCM_FORMAT_S16_LE   2
+#define SND_PCM_FORMAT_S24_LE   6
+#define SND_PCM_FORMAT_S32_LE  10
+
+static inline int audio_format_to_cras_format(audio_format_t audio_format)
+{
+    switch (audio_format) {
+    case AUDIO_FORMAT_PCM_16_BIT:
+        return SND_PCM_FORMAT_S16_LE;
+    case AUDIO_FORMAT_PCM_8_BIT:
+        return SND_PCM_FORMAT_U8;
+    case AUDIO_FORMAT_PCM_32_BIT:
+        return SND_PCM_FORMAT_S32_LE;
+    case AUDIO_FORMAT_PCM_8_24_BIT:
+        return SND_PCM_FORMAT_S24_LE;
+    default:
+        return SND_PCM_FORMAT_UNKNOWN;
+    }
+}
 #else
 #include <alsa/asoundlib.h>
 #define PCM_FORMAT_WIDTH(format) snd_pcm_format_physical_width(format)
@@ -125,4 +153,8 @@
 float **cras_channel_conv_matrix_create(const struct cras_audio_format *in,
 					const struct cras_audio_format *out);
 
+#ifdef __cplusplus
+}
+#endif
+
 #endif /* CRAS_AUDIO_FORMAT_H_ */
diff --git a/cras/src/common/cras_config.c b/cras/src/common/cras_config.c
index 9a8e812..29e9fa1 100644
--- a/cras/src/common/cras_config.c
+++ b/cras/src/common/cras_config.c
@@ -13,5 +13,5 @@
 	/* This directory is created by the upstart script, eventually it would
 	 * be nice to make this more dynamic, but it isn't needed right now for
 	 * Chrome OS. */
-	return "/var/run/cras";
+	return CRAS_SOCKET_FILE_DIR;
 }
diff --git a/cras/src/common/cras_config.h b/cras/src/common/cras_config.h
index 96a84c7..c1731c7 100644
--- a/cras/src/common/cras_config.h
+++ b/cras/src/common/cras_config.h
@@ -12,7 +12,9 @@
 #define CRAS_CLIENT_RT_THREAD_PRIORITY 10
 #define CRAS_CLIENT_NICENESS_LEVEL -10
 #define CRAS_SOCKET_FILE ".cras_socket"
-#define CRAS_CONFIG_FILE_DIR "/etc/cras"
+
+/* CRAS_CONFIG_FILE_DIR is defined as $sysconfdir/cras by the configure
+   script. */
 
 /* Gets the path to save UDS socket files. */
 const char *cras_config_get_system_socket_file_dir();
diff --git a/cras/src/common/cras_file_wait.c b/cras/src/common/cras_file_wait.c
new file mode 100644
index 0000000..809bc68
--- /dev/null
+++ b/cras/src/common/cras_file_wait.c
@@ -0,0 +1,315 @@
+/* Copyright 2016 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 <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <sys/inotify.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "cras_file_wait.h"
+
+#define CRAS_FILE_WAIT_EVENT_MIN_SIZE sizeof(struct inotify_event)
+#define CRAS_FILE_WAIT_EVENT_SIZE (CRAS_FILE_WAIT_EVENT_MIN_SIZE + NAME_MAX + 1)
+#define CRAS_FILE_WAIT_FLAG_MOCK_RACE (1 << 31)
+
+struct cras_file_wait {
+	cras_file_wait_callback_t callback;
+	void *callback_context;
+	const char *file_path;
+	size_t file_path_len;
+	char *watch_path;
+	char *watch_dir;
+	char *watch_file_name;
+	size_t watch_file_name_len;
+	int inotify_fd;
+	int watch_id;
+	char event_buf[CRAS_FILE_WAIT_EVENT_SIZE];
+	cras_file_wait_flag_t flags;
+};
+
+int cras_file_wait_get_fd(struct cras_file_wait *file_wait)
+{
+	if (!file_wait)
+		return -EINVAL;
+	if (file_wait->inotify_fd < 0)
+		return -EINVAL;
+	return file_wait->inotify_fd;
+}
+
+/* Defined for the unittest. */
+void cras_file_wait_mock_race_condition(struct cras_file_wait *file_wait);
+void cras_file_wait_mock_race_condition(struct cras_file_wait *file_wait)
+{
+	if (file_wait)
+		file_wait->flags |= CRAS_FILE_WAIT_FLAG_MOCK_RACE;
+}
+
+void cras_file_wait_destroy(struct cras_file_wait *file_wait)
+{
+	if (!file_wait)
+		return;
+	if (file_wait->inotify_fd >= 0)
+		close(file_wait->inotify_fd);
+	free(file_wait);
+}
+
+static int cras_file_wait_rm_watch(struct cras_file_wait *file_wait)
+{
+	int rc;
+
+	file_wait->watch_path[0] = 0;
+	file_wait->watch_dir[0] = 0;
+	file_wait->watch_file_name[0] = 0;
+	file_wait->watch_file_name_len = 0;
+	if (file_wait->inotify_fd >= 0 && file_wait->watch_id >= 0) {
+		rc = inotify_rm_watch(file_wait->inotify_fd,
+				      file_wait->watch_id);
+		file_wait->watch_id = -1;
+		if (rc < 0)
+			return -errno;
+	}
+	return 0;
+}
+
+int cras_file_wait_process_event(struct cras_file_wait *file_wait,
+				 struct inotify_event *event)
+{
+	cras_file_wait_event_t file_wait_event;
+
+	syslog(LOG_DEBUG, "file_wait->watch_id: %d, event->wd: %d"
+	       ", event->mask: %x, event->name: %s",
+	       file_wait->watch_id, event->wd, event->mask,
+	       event->len ? event->name : "");
+
+	if (event->wd != file_wait->watch_id)
+		return 0;
+
+	if (event->mask & IN_IGNORED) {
+		/* The watch has been removed. */
+		file_wait->watch_id = -1;
+		return cras_file_wait_rm_watch(file_wait);
+	}
+
+	if (event->len == 0 ||
+	    memcmp(event->name, file_wait->watch_file_name,
+		   file_wait->watch_file_name_len + 1) != 0) {
+		/* Some file we don't care about. */
+		return 0;
+	}
+
+	if ((event->mask & (IN_CREATE|IN_MOVED_TO)) != 0)
+		file_wait_event = CRAS_FILE_WAIT_EVENT_CREATED;
+	else if ((event->mask & (IN_DELETE|IN_MOVED_FROM)) != 0)
+		file_wait_event = CRAS_FILE_WAIT_EVENT_DELETED;
+	else
+		return 0;
+
+	/* Found the file! */
+	if (strcmp(file_wait->watch_path, file_wait->file_path) == 0) {
+		/* Tell the caller about this creation or deletion. */
+		file_wait->callback(file_wait->callback_context,
+				    file_wait_event, event->name);
+	} else {
+		/* Remove the watch for this file, move on. */
+		return cras_file_wait_rm_watch(file_wait);
+	}
+	return 0;
+}
+
+int cras_file_wait_dispatch(struct cras_file_wait *file_wait)
+{
+	struct inotify_event *event;
+	char *watch_dir_end;
+	size_t watch_dir_len;
+	char *watch_file_start;
+	size_t watch_path_len;
+	int rc = 0;
+	int flags;
+	ssize_t read_rc;
+	ssize_t read_offset;
+
+	if (!file_wait)
+		return -EINVAL;
+
+	/* If we have a file-descriptor, then read it and see what's up. */
+	if (file_wait->inotify_fd >= 0) {
+		read_offset = 0;
+		read_rc = read(file_wait->inotify_fd, file_wait->event_buf,
+			       CRAS_FILE_WAIT_EVENT_SIZE);
+		if (read_rc < 0) {
+			rc = -errno;
+			if ((rc == -EAGAIN || rc == -EWOULDBLOCK)
+			    && file_wait->watch_id < 0) {
+				/* Really nothing to read yet: we need to
+				 * setup a watch. */
+				rc = 0;
+			}
+		} else if (read_rc < CRAS_FILE_WAIT_EVENT_MIN_SIZE) {
+			rc = -EIO;
+		} else if (file_wait->watch_id < 0) {
+			/* Processing messages related to old watches. */
+			rc = 0;
+		} else while (rc == 0 && read_offset < read_rc) {
+			event = (struct inotify_event *)
+				(file_wait->event_buf + read_offset);
+			read_offset += sizeof(*event) + event->len;
+			rc = cras_file_wait_process_event(file_wait, event);
+		}
+	}
+
+	/* Report errors from above here. */
+	if (rc != 0)
+		return rc;
+
+	if (file_wait->watch_id >= 0) {
+		/* Assume that the watch that we have is the right one. */
+		return 0;
+	}
+
+	/* Initialize inotify if we haven't already. */
+	if (file_wait->inotify_fd < 0) {
+		file_wait->inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC);
+		if (file_wait->inotify_fd < 0)
+			return -errno;
+	}
+
+	/* Figure out what we need to watch next. */
+	rc = -ENOENT;
+	strcpy(file_wait->watch_dir, file_wait->file_path);
+	watch_dir_len = file_wait->file_path_len;
+
+	while (rc == -ENOENT) {
+		strcpy(file_wait->watch_path, file_wait->watch_dir);
+		watch_path_len = watch_dir_len;
+
+		/* Find the end of the parent directory. */
+		watch_dir_end = file_wait->watch_dir + watch_dir_len - 1;
+		while (watch_dir_end > file_wait->watch_dir &&
+		       *watch_dir_end != '/')
+			watch_dir_end--;
+		watch_file_start = watch_dir_end + 1;
+		/* Treat consecutive '/' characters as one. */
+		while (watch_dir_end > file_wait->watch_dir &&
+		       *(watch_dir_end - 1) == '/')
+		       watch_dir_end--;
+		watch_dir_len = watch_dir_end - file_wait->watch_dir;
+
+		if (watch_dir_len == 0) {
+			/* We're looking for a file in the current directory. */
+			strcpy(file_wait->watch_file_name,
+			       file_wait->watch_path);
+			file_wait->watch_file_name_len = watch_path_len;
+			strcpy(file_wait->watch_dir, ".");
+			watch_dir_len = 1;
+		} else {
+			/* Copy out the file name that we're looking for, and
+			 * mark the end of the directory path. */
+			strcpy(file_wait->watch_file_name, watch_file_start);
+			file_wait->watch_file_name_len =
+				watch_path_len -
+				(watch_file_start - file_wait->watch_dir);
+			*watch_dir_end = 0;
+		}
+
+		if (file_wait->flags & CRAS_FILE_WAIT_FLAG_MOCK_RACE) {
+			/* For testing only. */
+			mknod(file_wait->watch_path, S_IFREG | 0600, 0);
+			file_wait->flags &= ~CRAS_FILE_WAIT_FLAG_MOCK_RACE;
+		}
+
+		flags = IN_CREATE|IN_MOVED_TO|IN_DELETE|IN_MOVED_FROM;
+		file_wait->watch_id =
+			inotify_add_watch(file_wait->inotify_fd,
+					  file_wait->watch_dir, flags);
+		if (file_wait->watch_id < 0) {
+			rc = -errno;
+			continue;
+		}
+
+		/* Satisfy the race condition between existence of the
+		 * file and creation of the watch. */
+		rc = access(file_wait->watch_path, F_OK);
+		if (rc < 0) {
+			rc = -errno;
+			if (rc == -ENOENT) {
+				/* As expected, the file still doesn't exist. */
+				rc = 0;
+			}
+			continue;
+		}
+
+		/* The file we're looking for exists. */
+		if (strcmp(file_wait->watch_path, file_wait->file_path) == 0) {
+			file_wait->callback(file_wait->callback_context,
+					    CRAS_FILE_WAIT_EVENT_CREATED,
+					    file_wait->watch_file_name);
+			return 0;
+		}
+
+		/* Start over again. */
+		rc = cras_file_wait_rm_watch(file_wait);
+		if (rc != 0)
+			return rc;
+		rc = -ENOENT;
+		strcpy(file_wait->watch_dir, file_wait->file_path);
+		watch_dir_len = file_wait->file_path_len;
+	}
+
+	/* Get out for permissions problems for example. */
+	return rc;
+}
+
+int cras_file_wait_create(const char *file_path,
+			  cras_file_wait_flag_t flags,
+			  cras_file_wait_callback_t callback,
+			  void *callback_context,
+			  struct cras_file_wait **file_wait_out)
+{
+	struct cras_file_wait *file_wait;
+	size_t file_path_len;
+	int rc;
+
+	if (!file_path || !*file_path || !callback || !file_wait_out)
+		return -EINVAL;
+	*file_wait_out = NULL;
+
+	/* Create a struct cras_file_wait to track waiting for this file. */
+	file_path_len = strlen(file_path);
+	file_wait = (struct cras_file_wait *)
+		    calloc(1, sizeof(*file_wait) + ((file_path_len + 1) * 5));
+	if (!file_wait)
+		return -ENOMEM;
+	file_wait->callback = callback;
+	file_wait->callback_context = callback_context;
+	file_wait->inotify_fd = -1;
+	file_wait->watch_id = -1;
+	file_wait->file_path_len = file_path_len;
+	file_wait->flags = flags;
+
+	/* We've allocated memory such that the file_path, watch_path,
+	 * watch_dir, and watch_file_name data are appended to the end of
+	 * our cras_file_wait structure. */
+	file_wait->file_path = (const char *)file_wait + sizeof(*file_wait);
+	file_wait->watch_path = (char *)file_wait->file_path +
+				file_path_len + 1;
+	file_wait->watch_dir = file_wait->watch_path + file_path_len + 1;
+	file_wait->watch_file_name = file_wait->watch_dir + file_path_len + 1;
+	memcpy((void *)file_wait->file_path, file_path, file_path_len + 1);
+
+	/* Setup the first watch. If that fails unexpectedly, then we destroy
+	 * the file wait structure immediately. */
+	rc = cras_file_wait_dispatch(file_wait);
+	if (rc != 0)
+		cras_file_wait_destroy(file_wait);
+	else
+		*file_wait_out = file_wait;
+	return rc;
+}
diff --git a/cras/src/common/cras_file_wait.h b/cras/src/common/cras_file_wait.h
new file mode 100644
index 0000000..2803582
--- /dev/null
+++ b/cras/src/common/cras_file_wait.h
@@ -0,0 +1,129 @@
+/* Copyright 2016 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_FILE_WAIT_H_
+#define CRAS_FILE_WAIT_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Structure used to track the current progress of a file wait. */
+struct cras_file_wait;
+
+/* Flags type for file wait. */
+typedef unsigned int cras_file_wait_flag_t;
+
+/* No flags. */
+#define CRAS_FILE_WAIT_FLAG_NONE           ((cras_file_wait_flag_t)0)
+
+/* File wait events. */
+typedef enum cras_file_wait_event {
+	CRAS_FILE_WAIT_EVENT_NONE,
+	CRAS_FILE_WAIT_EVENT_CREATED,
+	CRAS_FILE_WAIT_EVENT_DELETED,
+} cras_file_wait_event_t;
+
+/*
+ * File wait callback function.
+ *
+ * Called for cras_file_wait events. Do not call cras_file_wait_destroy()
+ * from this function.
+ *
+ * Args:
+ *    context - Context pointer passed to cras_file_wait_start().
+ *    event - Event that has occurred related to this file wait.
+ *    filename - Filename associated with the event.
+ */
+typedef void (*cras_file_wait_callback_t)(void *context,
+					  cras_file_wait_event_t event,
+					  const char *filename);
+
+/*
+ * Wait for the existence of a file.
+ *
+ * Setup a watch with the aim of determining if the given file path exists. If
+ * any parent directory is missing, then the appropriate watch is created to
+ * watch for the parent (or it's parent). Watches are created or renewed while
+ * this file wait structure exists.
+ *
+ * The callback function will be called with event CRAS_FILE_WAIT_EVENT_CREATED
+ * when the file is created, moved into the directory, or if it already exists
+ * when this function is called.
+ *
+ * After the file is found future deletion and creation events for the file can
+ * be observed using the same file_wait structure and callback. When the file
+ * is deleted or moved out of it's parent, the callback is called with event
+ * CRAS_FILE_WAIT_EVENT_DELETED.
+ *
+ * Call cras_file_wait_destroy() to cancel the wait anytime and cleanup
+ * resources.
+ *
+ * Args:
+ *    file_path - Path of the file or directory that must exist.
+ *    flags - CRAS_FILE_WAIT_FLAG_* values bit-wise orred together. Set to
+ *            CRAS_FILE_WAIT_FLAG_NONE for now.
+ *    callback - Callback function to execute to notify of file existence.
+ *    callback_context - Context pointer passed to the callback function.
+ *    file_wait_out - Pointer to file wait structure that is initialized.
+ *
+ * Returns:
+ *    - 0 for success, or negative on error.
+ *    - On error cras_file_wait_destroy() need not be called.
+ */
+int cras_file_wait_create(const char *file_path,
+			  cras_file_wait_flag_t flags,
+			  cras_file_wait_callback_t callback,
+			  void *callback_context,
+                          struct cras_file_wait **file_wait_out);
+
+/* Returns the file-descriptor to poll for a file wait.
+ *
+ * Poll for POLLIN on this file decriptor. When there is data available, call
+ * cras_file_wait_continue() on the associated file_wait structure.
+ *
+ * Args:
+ *    file_wait - The associated cras_file_wait structure initialized by
+ *                cras_file_wait_start().
+ *
+ * Returns:
+ *    Non-negative file descriptor number, or -EINVAL if the file_wait
+ *    structure is NULL or otherwise invalid.
+ */
+int cras_file_wait_get_fd(struct cras_file_wait *file_wait);
+
+/* Dispatch a file wait event.
+ *
+ * Call this function when the file descriptor from cras_file_wait_fd() has
+ * data ready (POLLIN). This function will call the callback provided to
+ * cras_file_wait_start when there is a relevant event.
+ *
+ * Args:
+ *    file_wait - The associated cras_file_wait structure initialized by
+ *                cras_file_wait_start().
+ *
+ * Returns:
+ *    - 0 for success, non-zero on error.
+ *    - -EAGAIN or -EWOULDBLOCK when this function would have blocked.
+ */
+int cras_file_wait_dispatch(struct cras_file_wait *file_wait);
+
+/* Destroy a file wait structure.
+ *
+ * This function can be called to cleanup a cras_file_wait structure, and it
+ * will interrupt any wait that is in progress; the pointer is subsequently
+ * invalid.
+ *
+ * Args:
+ *    file_wait - The cras_file_wait structure initialized by
+ *                cras_file_wait_start();
+ */
+void cras_file_wait_destroy(struct cras_file_wait *file_wait);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* CRAS_FILE_WAIT_H_ */
diff --git a/cras/src/common/cras_iodev_info.h b/cras/src/common/cras_iodev_info.h
index 857a361..9b1dbb5 100644
--- a/cras/src/common/cras_iodev_info.h
+++ b/cras/src/common/cras_iodev_info.h
@@ -13,16 +13,20 @@
 #define CRAS_NODE_TYPE_BUFFER_SIZE 32
 #define CRAS_NODE_MIC_POS_BUFFER_SIZE 128
 #define CRAS_NODE_NAME_BUFFER_SIZE 64
+#define CRAS_NODE_HOTWORD_MODEL_BUFFER_SIZE 16
 
 /* Identifying information about an IO device.
  *    idx - iodev index.
  *    name - Name displayed to the user.
  *    stable_id - ID that does not change due to device plug/unplug or reboot.
+ *    stable_id_new - New stable_id, it will be deprecated and be put on
+ *        stable_id.
  */
 struct __attribute__ ((__packed__)) cras_iodev_info {
 	uint32_t idx;
 	char name[CRAS_IODEV_NAME_BUFFER_SIZE];
 	uint32_t stable_id;
+	uint32_t stable_id_new;
 };
 
 /* Identifying information about an ionode on an iodev.
@@ -34,9 +38,13 @@
  *    volume - per-node volume (0-100)
  *    capture_gain - per-node capture gain/attenuation (in 100*dBFS)
  *    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.
+ *    stable_id_new - New stable_id, it will be deprecated and be put on
+ *        stable_id.
  *    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.
  */
 struct __attribute__ ((__packed__)) cras_ionode_info {
 	uint32_t iodev_idx;
@@ -48,9 +56,12 @@
 	int32_t capture_gain;
 	int32_t left_right_swapped;
 	uint32_t type_enum;
+	uint32_t stable_id;
+	uint32_t stable_id_new;
 	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];
 };
 
 /* This is used in the cras_client_set_node_attr API.
diff --git a/cras/src/common/cras_messages.h b/cras/src/common/cras_messages.h
index e678c19..ab962df 100644
--- a/cras/src/common/cras_messages.h
+++ b/cras/src/common/cras_messages.h
@@ -19,6 +19,7 @@
 #define CRAS_PROTO_VER 1
 #define CRAS_SERV_MAX_MSG_SIZE 256
 #define CRAS_CLIENT_MAX_MSG_SIZE 256
+#define CRAS_HOTWORD_NAME_MAX_SIZE 8
 
 /* Message IDs. */
 enum CRAS_SERVER_MESSAGE_ID {
@@ -44,6 +45,10 @@
 	CRAS_SERVER_TEST_DEV_COMMAND,
 	CRAS_SERVER_SUSPEND,
 	CRAS_SERVER_RESUME,
+	CRAS_CONFIG_GLOBAL_REMIX,
+	CRAS_SERVER_GET_HOTWORD_MODELS,
+	CRAS_SERVER_SET_HOTWORD_MODEL,
+	CRAS_SERVER_REGISTER_NOTIFICATION,
 };
 
 enum CRAS_CLIENT_MESSAGE_ID {
@@ -51,6 +56,18 @@
 	CRAS_CLIENT_CONNECTED,
 	CRAS_CLIENT_STREAM_CONNECTED,
 	CRAS_CLIENT_AUDIO_DEBUG_INFO_READY,
+	CRAS_CLIENT_GET_HOTWORD_MODELS_READY,
+	/* System status messages */
+	CRAS_CLIENT_OUTPUT_VOLUME_CHANGED,
+	CRAS_CLIENT_OUTPUT_MUTE_CHANGED,
+	CRAS_CLIENT_CAPTURE_GAIN_CHANGED,
+	CRAS_CLIENT_CAPTURE_MUTE_CHANGED,
+	CRAS_CLIENT_NODES_CHANGED,
+	CRAS_CLIENT_ACTIVE_NODE_CHANGED,
+	CRAS_CLIENT_OUTPUT_NODE_VOLUME_CHANGED,
+	CRAS_CLIENT_NODE_LEFT_RIGHT_SWAPPED_CHANGED,
+	CRAS_CLIENT_INPUT_NODE_GAIN_CHANGED,
+	CRAS_CLIENT_NUM_ACTIVE_STREAMS_CHANGED,
 };
 
 /* Messages that control the server. These are sent from the client to affect
@@ -352,6 +369,74 @@
 	m->length = sizeof(*m);
 }
 
+/* Configures the global remix converter. */
+struct __attribute__ ((__packed__)) cras_config_global_remix {
+	struct cras_server_message header;
+	unsigned int num_channels;
+	float coefficient[];
+};
+
+static inline void cras_fill_config_global_remix_command(
+		struct cras_config_global_remix *m,
+		unsigned int num_channels,
+		float *coeff,
+		unsigned int count)
+{
+	m->header.id = CRAS_CONFIG_GLOBAL_REMIX;
+	m->header.length = sizeof(*m) + count * sizeof(*coeff);
+	m->num_channels = num_channels;
+	memcpy(m->coefficient, coeff, count * sizeof(*coeff));
+}
+
+/* Get supported hotword models. */
+struct __attribute__ ((__packed__)) cras_get_hotword_models {
+	struct cras_server_message header;
+	cras_node_id_t node_id;
+};
+
+static inline void cras_fill_get_hotword_models_message(
+		struct cras_get_hotword_models *m,
+		cras_node_id_t node_id)
+{
+	m->header.id = CRAS_SERVER_GET_HOTWORD_MODELS;
+	m->header.length = sizeof(*m);
+	m->node_id = node_id;
+}
+
+/* Set desired hotword model. */
+struct __attribute__ ((__packed__)) cras_set_hotword_model {
+	struct cras_server_message header;
+	cras_node_id_t node_id;
+	char model_name[CRAS_HOTWORD_NAME_MAX_SIZE];
+};
+
+static inline void cras_fill_set_hotword_model_message(
+		struct cras_set_hotword_model *m,
+		cras_node_id_t node_id,
+		const char *model_name)
+{
+	m->header.id = CRAS_SERVER_SET_HOTWORD_MODEL;
+	m->header.length = sizeof(*m);
+	m->node_id = node_id;
+	memcpy(m->model_name, model_name, CRAS_HOTWORD_NAME_MAX_SIZE);
+}
+
+struct __attribute__ ((__packed__)) cras_register_notification {
+		struct cras_server_message header;
+		uint32_t msg_id;
+		int do_register;
+};
+static inline void cras_fill_register_notification_message(
+		struct cras_register_notification *m,
+		enum CRAS_CLIENT_MESSAGE_ID msg_id,
+		int do_register)
+{
+	m->header.id = CRAS_SERVER_REGISTER_NOTIFICATION;
+	m->header.length = sizeof(*m);
+	m->msg_id = msg_id;
+	m->do_register = do_register;
+}
+
 /*
  * Messages sent from server to client.
  */
@@ -360,27 +445,25 @@
 struct __attribute__ ((__packed__)) cras_client_connected {
 	struct cras_client_message header;
 	uint32_t client_id;
-	key_t shm_key;
 };
 static inline void cras_fill_client_connected(
 		struct cras_client_connected *m,
-		size_t client_id,
-		key_t shm_key)
+		size_t client_id)
 {
 	m->client_id = client_id;
-	m->shm_key = shm_key;
 	m->header.id = CRAS_CLIENT_CONNECTED;
 	m->header.length = sizeof(struct cras_client_connected);
 }
 
-/* Reply from server that a stream has been successfully added. */
+/*
+ * Reply from server that a stream has been successfully added.
+ * Two file descriptors are added, input shm followed by out shm.
+ */
 struct __attribute__ ((__packed__)) cras_client_stream_connected {
 	struct cras_client_message header;
 	int32_t err;
 	cras_stream_id_t stream_id;
 	struct cras_audio_format_packed format;
-	int32_t input_shm_key;
-	int32_t output_shm_key;
 	uint32_t shm_max_size;
 };
 static inline void cras_fill_client_stream_connected(
@@ -388,15 +471,11 @@
 		int err,
 		cras_stream_id_t stream_id,
 		struct cras_audio_format *format,
-		int input_shm_key,
-		int output_shm_key,
 		size_t shm_max_size)
 {
 	m->err = err;
 	m->stream_id = stream_id;
 	pack_cras_audio_format(&m->format, format);
-	m->input_shm_key = input_shm_key;
-	m->output_shm_key = output_shm_key;
 	m->shm_max_size = shm_max_size;
 	m->header.id = CRAS_CLIENT_STREAM_CONNECTED;
 	m->header.length = sizeof(struct cras_client_stream_connected);
@@ -413,6 +492,148 @@
 	m->header.length = sizeof(*m);
 }
 
+/* Sent from server to client when hotword models info is ready. */
+struct cras_client_get_hotword_models_ready {
+	struct cras_client_message header;
+	int32_t hotword_models_size;
+	uint8_t hotword_models[0];
+};
+static inline void cras_fill_client_get_hotword_models_ready(
+		struct cras_client_get_hotword_models_ready *m,
+		const char *hotword_models,
+		size_t hotword_models_size)
+{
+	m->header.id = CRAS_CLIENT_GET_HOTWORD_MODELS_READY;
+	m->header.length = sizeof(*m) + hotword_models_size;
+	m->hotword_models_size = hotword_models_size;
+	memcpy(m->hotword_models, hotword_models, hotword_models_size);
+}
+
+/* System status messages sent from server to client when state changes. */
+struct __attribute__ ((__packed__)) cras_client_volume_changed {
+	struct cras_client_message header;
+	int32_t volume;
+};
+static inline void cras_fill_client_output_volume_changed(
+		struct cras_client_volume_changed *m, int32_t volume)
+{
+	m->header.id = CRAS_CLIENT_OUTPUT_VOLUME_CHANGED;
+	m->header.length = sizeof(*m);
+	m->volume = volume;
+}
+static inline void cras_fill_client_capture_gain_changed(
+		struct cras_client_volume_changed *m, int32_t gain)
+{
+	m->header.id = CRAS_CLIENT_CAPTURE_GAIN_CHANGED;
+	m->header.length = sizeof(*m);
+	m->volume = gain;
+}
+
+struct __attribute__ ((__packed__)) cras_client_mute_changed {
+	struct cras_client_message header;
+	int32_t muted;
+	int32_t user_muted;
+	int32_t mute_locked;
+};
+static inline void cras_fill_client_output_mute_changed(
+		struct cras_client_mute_changed *m, int32_t muted,
+		int32_t user_muted, int32_t mute_locked)
+{
+	m->header.id = CRAS_CLIENT_OUTPUT_MUTE_CHANGED;
+	m->header.length = sizeof(*m);
+	m->muted = muted;
+	m->user_muted = user_muted;
+	m->mute_locked = mute_locked;
+}
+static inline void cras_fill_client_capture_mute_changed(
+		struct cras_client_mute_changed *m, int32_t muted,
+		int32_t mute_locked)
+{
+	m->header.id = CRAS_CLIENT_CAPTURE_MUTE_CHANGED;
+	m->header.length = sizeof(*m);
+	m->muted = muted;
+	m->user_muted = 0;
+	m->mute_locked = mute_locked;
+}
+
+struct __attribute__ ((__packed__)) cras_client_nodes_changed {
+	struct cras_client_message header;
+};
+static inline void cras_fill_client_nodes_changed(
+		struct cras_client_nodes_changed *m)
+{
+	m->header.id = CRAS_CLIENT_NODES_CHANGED;
+	m->header.length = sizeof(*m);
+}
+
+struct __attribute__ ((__packed__)) cras_client_active_node_changed {
+	struct cras_client_message header;
+	uint32_t direction;
+	cras_node_id_t node_id;
+};
+static inline void cras_fill_client_active_node_changed (
+		struct cras_client_active_node_changed *m,
+		enum CRAS_STREAM_DIRECTION direction,
+		cras_node_id_t node_id)
+{
+	m->header.id = CRAS_CLIENT_ACTIVE_NODE_CHANGED;
+	m->header.length = sizeof(*m);
+	m->direction = direction;
+	m->node_id = node_id;
+};
+
+struct __attribute__ ((__packed__)) cras_client_node_value_changed {
+	struct cras_client_message header;
+	cras_node_id_t node_id;
+	int32_t value;
+};
+static inline void cras_fill_client_output_node_volume_changed (
+		struct cras_client_node_value_changed *m,
+		cras_node_id_t node_id,
+		int32_t volume)
+{
+	m->header.id = CRAS_CLIENT_OUTPUT_NODE_VOLUME_CHANGED;
+	m->header.length = sizeof(*m);
+	m->node_id = node_id;
+	m->value = volume;
+};
+static inline void cras_fill_client_node_left_right_swapped_changed (
+		struct cras_client_node_value_changed *m,
+		cras_node_id_t node_id,
+		int swapped)
+{
+	m->header.id = CRAS_CLIENT_NODE_LEFT_RIGHT_SWAPPED_CHANGED;
+	m->header.length = sizeof(*m);
+	m->node_id = node_id;
+	m->value = swapped;
+};
+static inline void cras_fill_client_input_node_gain_changed (
+		struct cras_client_node_value_changed *m,
+		cras_node_id_t node_id,
+		int32_t gain)
+{
+	m->header.id = CRAS_CLIENT_INPUT_NODE_GAIN_CHANGED;
+	m->header.length = sizeof(*m);
+	m->node_id = node_id;
+	m->value = gain;
+};
+
+struct __attribute__ ((__packed__)) cras_client_num_active_streams_changed {
+	struct cras_client_message header;
+	uint32_t direction;
+	uint32_t num_active_streams;
+};
+static inline void cras_fill_client_num_active_streams_changed (
+		struct cras_client_num_active_streams_changed *m,
+		enum CRAS_STREAM_DIRECTION direction,
+		uint32_t num_active_streams)
+{
+	m->header.id = CRAS_CLIENT_NUM_ACTIVE_STREAMS_CHANGED;
+	m->header.length = sizeof(*m);
+	m->direction = direction;
+	m->num_active_streams = num_active_streams;
+};
+
 /*
  * Messages specific to passing audio between client and server
  */
diff --git a/cras/src/common/cras_observer_ops.h b/cras/src/common/cras_observer_ops.h
new file mode 100644
index 0000000..7265d98
--- /dev/null
+++ b/cras/src/common/cras_observer_ops.h
@@ -0,0 +1,54 @@
+/* Copyright 2016 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_OBSERVER_OPS_H
+#define CRAS_OBSERVER_OPS_H
+
+#include "cras_types.h"
+
+/* Observation of CRAS state.
+ * Unless otherwise specified, all notifications only contain the data value
+ * reflecting the current state: it is possible that multiple notifications
+ * are queued within CRAS before being sent to the client.
+ */
+struct cras_observer_ops {
+	/* System output volume changed. */
+	void (*output_volume_changed)(void *context, int32_t volume);
+	/* System output mute changed. */
+	void (*output_mute_changed)(void *context,
+				    int muted, int user_muted, int mute_locked);
+	/* System input/capture gain changed. */
+	void (*capture_gain_changed)(void *context, int32_t gain);
+	/* System input/capture mute changed. */
+	void (*capture_mute_changed)(void *context, int muted, int mute_locked);
+	/* Device or node topology changed. */
+	void (*nodes_changed)(void *context);
+	/* Active node changed. A notification is sent for every change.
+	 * When there is no active node, node_id is 0. */
+	void (*active_node_changed)(void *context,
+				    enum CRAS_STREAM_DIRECTION dir,
+				    cras_node_id_t node_id);
+	/* Output node volume changed. */
+	void (*output_node_volume_changed)(void *context,
+					   cras_node_id_t node_id,
+					   int32_t volume);
+	/* Node left/right swapped state change. */
+	void (*node_left_right_swapped_changed)(void *context,
+						cras_node_id_t node_id,
+						int swapped);
+	/* Input gain changed. */
+	void (*input_node_gain_changed)(void *context,
+					cras_node_id_t node_id,
+					int32_t gain);
+	/* Suspend state changed. */
+	void (*suspend_changed)(void *context,
+				int suspended);
+	/* Number of active streams changed. */
+	void (*num_active_streams_changed)(void *context,
+					   enum CRAS_STREAM_DIRECTION dir,
+					   uint32_t num_active_streams);
+};
+
+#endif /* CRAS_OBSERVER_OPS_H */
diff --git a/cras/src/common/cras_shm.c b/cras/src/common/cras_shm.c
new file mode 100644
index 0000000..6b4085a
--- /dev/null
+++ b/cras/src/common/cras_shm.c
@@ -0,0 +1,98 @@
+/* Copyright 2016 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 <sys/cdefs.h>
+#include <sys/mman.h>
+#ifdef __BIONIC__
+#include <cutils/ashmem.h>
+#else
+#include <sys/shm.h>
+#endif
+#include <errno.h>
+#include <syslog.h>
+#include <string.h>
+
+#include "cras_shm.h"
+
+#ifdef __BIONIC__
+
+int cras_shm_open_rw (const char *name, size_t size)
+{
+	int fd;
+
+	/* Eliminate the / in the shm_name. */
+	if (name[0] == '/')
+		name++;
+	fd = ashmem_create_region(name, size);
+	if (fd < 0) {
+		fd = -errno;
+		syslog(LOG_ERR, "failed to ashmem_create_region %s: %s\n",
+		       name, strerror(-fd));
+	}
+	return fd;
+}
+
+int cras_shm_reopen_ro (const char *name, int fd)
+{
+	/* After mmaping the ashmem read/write, change it's protection
+	   bits to disallow further write access. */
+	if (ashmem_set_prot_region(fd, PROT_READ) != 0) {
+		fd = -errno;
+		syslog(LOG_ERR,
+		       "failed to ashmem_set_prot_region %s: %s\n",
+		       name, strerror(-fd));
+	}
+	return fd;
+}
+
+void cras_shm_close_unlink (const char *name, int fd)
+{
+	close(fd);
+}
+
+#else
+
+int cras_shm_open_rw (const char *name, size_t size)
+{
+	int fd;
+	int rc;
+
+	fd = shm_open(name, O_CREAT | O_EXCL | O_RDWR, 0600);
+	if (fd < 0) {
+		fd = -errno;
+		syslog(LOG_ERR, "failed to shm_open %s: %s\n",
+		       name, strerror(-fd));
+		return fd;
+	}
+	rc = ftruncate(fd, size);
+	if (rc) {
+		rc = -errno;
+		syslog(LOG_ERR, "failed to set size of shm %s: %s\n",
+		       name, strerror(-rc));
+		return rc;
+	}
+	return fd;
+}
+
+int cras_shm_reopen_ro (const char *name, int fd)
+{
+	/* Open a read-only copy to dup and pass to clients. */
+	fd = shm_open(name, O_RDONLY, 0);
+	if (fd < 0) {
+		fd = -errno;
+		syslog(LOG_ERR,
+		       "Failed to re-open shared memory '%s' read-only: %s",
+		       name, strerror(-fd));
+	}
+	return fd;
+}
+
+void cras_shm_close_unlink (const char *name, int fd)
+{
+	shm_unlink(name);
+	close(fd);
+}
+
+#endif
diff --git a/cras/src/common/cras_shm.h b/cras/src/common/cras_shm.h
index 0033e69..9a54132 100644
--- a/cras/src/common/cras_shm.h
+++ b/cras/src/common/cras_shm.h
@@ -106,14 +106,30 @@
 	return offset;
 }
 
-/* Get a pointer to the current read buffer */
+/* Get the number of frames readable in current read buffer */
 static inline
-uint8_t *cras_shm_get_curr_read_buffer(const struct cras_audio_shm *shm)
+unsigned cras_shm_get_curr_read_frames(const struct cras_audio_shm *shm)
 {
 	unsigned i = shm->area->read_buf_idx & CRAS_SHM_BUFFERS_MASK;
+	unsigned read_offset, write_offset;
 
-	return cras_shm_buff_for_idx(shm, i) +
+	read_offset =
 		cras_shm_check_read_offset(shm, shm->area->read_offset[i]);
+	write_offset =
+		cras_shm_check_write_offset(shm, shm->area->write_offset[i]);
+
+	if (read_offset > write_offset)
+		return 0;
+	else
+		return (write_offset - read_offset) / shm->config.frame_bytes;
+}
+
+/* Get the base of the current read buffer. */
+static inline
+uint8_t *cras_shm_get_read_buffer_base(const struct cras_audio_shm *shm)
+{
+	unsigned i = shm->area->read_buf_idx & CRAS_SHM_BUFFERS_MASK;
+	return cras_shm_buff_for_idx(shm, i);
 }
 
 /* Get the base of the current write buffer. */
@@ -246,23 +262,27 @@
 	return shm->config.used_size / shm->config.frame_bytes;
 }
 
-/* Flags an overrun if writing would cause one. */
-static inline void cras_shm_check_write_overrun(struct cras_audio_shm *shm)
+/* Flags an overrun if writing would cause one and reset the write offset.
+ * Return 1 if overrun happens, otherwise return 0. */
+static inline int cras_shm_check_write_overrun(struct cras_audio_shm *shm)
 {
+	int ret = 0;
 	size_t write_buf_idx = shm->area->write_buf_idx & CRAS_SHM_BUFFERS_MASK;
-	size_t read_buf_idx = shm->area->read_buf_idx & CRAS_SHM_BUFFERS_MASK;
 
 	if (!shm->area->write_in_progress[write_buf_idx]) {
 		unsigned int used_size = shm->config.used_size;
 
-		if (write_buf_idx != read_buf_idx)
+		if (shm->area->write_offset[write_buf_idx]) {
 			shm->area->num_overruns++; /* Will over-write unread */
+			ret = 1;
+		}
 
 		memset(cras_shm_buff_for_idx(shm, write_buf_idx), 0, used_size);
 
 		shm->area->write_in_progress[write_buf_idx] = 1;
 		shm->area->write_offset[write_buf_idx] = 0;
 	}
+	return ret;
 }
 
 /* Increment the write pointer for the current buffer. */
@@ -463,4 +483,31 @@
 	memcpy(&shm->config, &shm->area->config, sizeof(shm->config));
 }
 
+/* Open a read/write shared memory area with the given name.
+ * Args:
+ *    name - Name of the shared-memory area.
+ *    size - Size of the shared-memory area.
+ * Returns:
+ *    >= 0 file descriptor value, or negative errno value on error.
+ */
+int cras_shm_open_rw (const char *name, size_t size);
+
+/* Reopen an existing shared memory area read-only.
+ * Args:
+ *    name - Name of the shared-memory area.
+ *    fd - Existing file descriptor.
+ * Returns:
+ *    >= 0 new file descriptor value, or negative errno value on error.
+ */
+int cras_shm_reopen_ro (const char *name, int fd);
+
+/* Close and delete a shared memory area.
+ * Args:
+ *    name - Name of the shared-memory area.
+ *    fd - Existing file descriptor.
+ * Returns:
+ *    >= 0 new file descriptor value, or negative errno value on error.
+ */
+void cras_shm_close_unlink (const char *name, int fd);
+
 #endif /* CRAS_SHM_H_ */
diff --git a/cras/src/common/cras_types.h b/cras/src/common/cras_types.h
index f88abc6..6c0ce0f 100644
--- a/cras/src/common/cras_types.h
+++ b/cras/src/common/cras_types.h
@@ -53,7 +53,7 @@
 enum CRAS_STREAM_DIRECTION {
 	CRAS_STREAM_OUTPUT,
 	CRAS_STREAM_INPUT,
-	CRAS_STREAM_UNIFIED, /* This has been removed, don't use it. */
+	CRAS_STREAM_UNDEFINED,
 	CRAS_STREAM_POST_MIX_PRE_DSP,
 	CRAS_NUM_DIRECTIONS
 };
@@ -105,8 +105,29 @@
 /* Types of audio streams. */
 enum CRAS_STREAM_TYPE {
 	CRAS_STREAM_TYPE_DEFAULT,
+	CRAS_STREAM_TYPE_MULTIMEDIA,
+	CRAS_STREAM_TYPE_VOICE_COMMUNICATION,
+	CRAS_STREAM_TYPE_SPEECH_RECOGNITION,
+	CRAS_STREAM_TYPE_PRO_AUDIO,
+	CRAS_STREAM_NUM_TYPES,
 };
 
+#define ENUM_STR(x) case x: return #x;
+
+static inline const char *cras_stream_type_str(
+		enum CRAS_STREAM_TYPE stream_type)
+{
+	switch(stream_type) {
+	ENUM_STR(CRAS_STREAM_TYPE_DEFAULT)
+	ENUM_STR(CRAS_STREAM_TYPE_MULTIMEDIA)
+	ENUM_STR(CRAS_STREAM_TYPE_VOICE_COMMUNICATION)
+	ENUM_STR(CRAS_STREAM_TYPE_SPEECH_RECOGNITION)
+	ENUM_STR(CRAS_STREAM_TYPE_PRO_AUDIO)
+	default:
+		return "INVALID_STREAM_TYPE";
+	}
+}
+
 /* Information about a client attached to the server. */
 struct __attribute__ ((__packed__)) cras_attached_client_info {
 	uint32_t id;
@@ -139,6 +160,7 @@
 #define CRAS_MAX_IODEVS 20
 #define CRAS_MAX_IONODES 20
 #define CRAS_MAX_ATTACHED_CLIENTS 20
+#define CRAS_HOTWORD_STRING_SIZE 256
 #define MAX_DEBUG_DEVS 4
 #define MAX_DEBUG_STREAMS 8
 #define AUDIO_THREAD_EVENT_LOG_SIZE (1024*6)
@@ -148,8 +170,11 @@
 	AUDIO_THREAD_WAKE,
 	AUDIO_THREAD_SLEEP,
 	AUDIO_THREAD_READ_AUDIO,
+	AUDIO_THREAD_READ_AUDIO_TSTAMP,
 	AUDIO_THREAD_READ_AUDIO_DONE,
+	AUDIO_THREAD_READ_OVERRUN,
 	AUDIO_THREAD_FILL_AUDIO,
+	AUDIO_THREAD_FILL_AUDIO_TSTAMP,
 	AUDIO_THREAD_FILL_AUDIO_DONE,
 	AUDIO_THREAD_WRITE_STREAMS_WAIT,
 	AUDIO_THREAD_WRITE_STREAMS_WAIT_TO,
@@ -175,6 +200,11 @@
 	AUDIO_THREAD_IODEV_CB,
 	AUDIO_THREAD_PB_MSG,
 	AUDIO_THREAD_ODEV_NO_STREAMS,
+	AUDIO_THREAD_ODEV_START,
+	AUDIO_THREAD_ODEV_LEAVE_NO_STREAMS,
+	AUDIO_THREAD_ODEV_DEFAULT_NO_STREAMS,
+	AUDIO_THREAD_FILL_ODEV_ZEROS,
+	AUDIO_THREAD_SEVERE_UNDERRUN,
 };
 
 struct __attribute__ ((__packed__)) audio_thread_event {
@@ -202,12 +232,15 @@
 	uint32_t num_channels;
 	double est_rate_ratio;
 	uint8_t direction;
+	uint32_t num_underruns;
+	uint32_t num_severe_underruns;
 };
 
 struct __attribute__ ((__packed__)) audio_stream_debug_info {
 	uint64_t stream_id;
 	uint32_t dev_idx;
 	uint32_t direction;
+	uint32_t stream_type;
 	uint32_t buffer_frames;
 	uint32_t cb_threshold;
 	uint32_t flags;
@@ -215,6 +248,7 @@
 	uint32_t num_channels;
 	uint32_t longest_fetch_sec;
 	uint32_t longest_fetch_nsec;
+	uint32_t num_overruns;
 	int8_t channel_layout[CRAS_CH_MAX];
 };
 
@@ -238,6 +272,12 @@
  *    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.
@@ -264,7 +304,7 @@
  *        use it.
  */
 #define CRAS_SERVER_STATE_VERSION 2
-struct __attribute__ ((__packed__)) cras_server_state {
+struct __attribute__ ((packed, aligned(4))) cras_server_state {
 	uint32_t state_version;
 	uint32_t volume;
 	int32_t min_volume_dBFS;
@@ -274,6 +314,7 @@
 	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;
@@ -310,6 +351,7 @@
  *      lowered priority.
  *    usb_vendor_id - vendor ID if the device is on the USB bus.
  *    usb_product_id - product ID if the device is on the USB bus.
+ *    usb_serial_number - serial number if the device is on the USB bus.
  *    usb_desc_checksum - the checksum of the USB descriptors if the device
  *      is on the USB bus.
  */
@@ -317,11 +359,13 @@
 	ALSA_CARD_TYPE_INTERNAL,
 	ALSA_CARD_TYPE_USB,
 };
+#define USB_SERIAL_NUMBER_BUFFER_SIZE 64
 struct __attribute__ ((__packed__)) cras_alsa_card_info {
 	enum CRAS_ALSA_CARD_TYPE card_type;
 	uint32_t card_index;
 	uint32_t usb_vendor_id;
 	uint32_t usb_product_id;
+	char usb_serial_number[USB_SERIAL_NUMBER_BUFFER_SIZE];
 	uint32_t usb_desc_checksum;
 };
 
@@ -342,17 +386,36 @@
 	CRAS_NODE_TYPE_INTERNAL_SPEAKER,
 	CRAS_NODE_TYPE_HEADPHONE,
 	CRAS_NODE_TYPE_HDMI,
+	CRAS_NODE_TYPE_HAPTIC,
+	CRAS_NODE_TYPE_LINEOUT,
 	/* These value can be used for input nodes. */
-	CRAS_NODE_TYPE_INTERNAL_MIC,
 	CRAS_NODE_TYPE_MIC,
-	CRAS_NODE_TYPE_AOKR,
+	CRAS_NODE_TYPE_HOTWORD,
 	CRAS_NODE_TYPE_POST_MIX_PRE_DSP,
 	CRAS_NODE_TYPE_POST_DSP,
 	/* These value can be used for both output and input nodes. */
 	CRAS_NODE_TYPE_USB,
 	CRAS_NODE_TYPE_BLUETOOTH,
-	CRAS_NODE_TYPE_KEYBOARD_MIC,
 	CRAS_NODE_TYPE_UNKNOWN,
 };
 
+/* Position values to described where a node locates on the system.
+ * NODE_POSITION_EXTERNAL - The node works only when peripheral
+ *     is plugged.
+ * NODE_POSITION_INTERNAL - The node lives on the system and doesn't
+ *     have specific direction.
+ * NODE_POSITION_FRONT - The node locates on the side of system that
+ *     faces user.
+ * NODE_POSITION_REAR - The node locates on the opposite side of
+ *     the system that faces user.
+ * NODE_POSITION_KEYBOARD - The node locates under the keyboard.
+ */
+enum CRAS_NODE_POSITION {
+	NODE_POSITION_EXTERNAL,
+	NODE_POSITION_INTERNAL,
+	NODE_POSITION_FRONT,
+	NODE_POSITION_REAR,
+	NODE_POSITION_KEYBOARD,
+};
+
 #endif /* CRAS_TYPES_H_ */
diff --git a/cras/src/common/cras_util.c b/cras/src/common/cras_util.c
index ab8d9b7..bcc513e 100644
--- a/cras/src/common/cras_util.c
+++ b/cras/src/common/cras_util.c
@@ -3,18 +3,25 @@
  * found in the LICENSE file.
  */
 
+#define _GNU_SOURCE /* For ppoll() */
+
 #include <errno.h>
 #include <fcntl.h>
 #include <pthread.h>
+#include <poll.h>
 #include <sched.h>
+#include <stdlib.h>
 #include <string.h>
+#include <syslog.h>
+#include <sys/param.h>
 #include <sys/resource.h>
 #include <sys/socket.h>
 #include <sys/syscall.h>
-#include <syslog.h>
 #include <sys/types.h>
 #include <unistd.h>
 
+#include "cras_util.h"
+
 int cras_set_rt_scheduling(int rt_lim)
 {
 	struct rlimit rl;
@@ -26,8 +33,6 @@
 		       (unsigned) rt_lim, errno);
 		return -EACCES;
 	}
-
-	syslog(LOG_INFO, "set rlimit success\n");
 	return 0;
 }
 
@@ -40,8 +45,10 @@
 	sched_param.sched_priority = priority;
 
 	err = pthread_setschedparam(pthread_self(), SCHED_RR, &sched_param);
-	if (err < 0)
-		syslog(LOG_WARNING, "Set sched params for thread\n");
+	if (err)
+		syslog(LOG_WARNING,
+		       "Failed to set thread sched params to priority %d"
+		       ", rc: %d\n", priority, err);
 
 	return err;
 }
@@ -56,7 +63,9 @@
 	 * has been granted permission to adjust nice values on the system.
 	 */
 	rc = setpriority(PRIO_PROCESS, syscall(__NR_gettid), nice);
-	syslog(LOG_DEBUG, "Set nice to %d %s.", nice, rc ? "Fail" : "Success");
+	if (rc)
+		syslog(LOG_WARNING, "Failed to set nice to %d, rc: %d",
+		       nice, rc);
 
 	return rc;
 }
@@ -85,58 +94,167 @@
 	return fcntl(fd, F_SETFL, fl & ~O_NONBLOCK);
 }
 
-int cras_send_with_fd(int sockfd, void *buf, size_t len, int fd)
+int cras_send_with_fds(int sockfd, const void *buf, size_t len, int *fd,
+		       unsigned int num_fds)
 {
 	struct msghdr msg = {0};
 	struct iovec iov;
 	struct cmsghdr *cmsg;
-	char control[CMSG_SPACE(sizeof(int))];
+	char *control;
+	const unsigned int control_size = CMSG_SPACE(sizeof(*fd) * num_fds);
+	int rc;
+
+	control = calloc(control_size, 1);
 
 	msg.msg_iov = &iov;
 	msg.msg_iovlen = 1;
-	iov.iov_base = buf;
+	iov.iov_base = (void *)buf;
 	iov.iov_len = len;
 
 	msg.msg_control = control;
-	msg.msg_controllen = sizeof(control);
+	msg.msg_controllen = control_size;
+
 	cmsg = CMSG_FIRSTHDR(&msg);
 	cmsg->cmsg_level = SOL_SOCKET;
 	cmsg->cmsg_type = SCM_RIGHTS;
-	cmsg->cmsg_len = CMSG_LEN(sizeof(int));
-	memcpy(CMSG_DATA(cmsg), &fd, sizeof(fd));
-	msg.msg_controllen = cmsg->cmsg_len;
+	cmsg->cmsg_len = CMSG_LEN(sizeof(*fd) * num_fds);
+	memcpy(CMSG_DATA(cmsg), fd, sizeof(*fd) * num_fds);
 
-	return sendmsg(sockfd, &msg, 0);
+	rc = sendmsg(sockfd, &msg, 0);
+	free(control);
+	return rc;
 }
 
-int cras_recv_with_fd(int sockfd, void *buf, size_t len, int *fd)
+int cras_recv_with_fds(int sockfd, void *buf, size_t len, int *fd,
+		       unsigned int *num_fds)
 {
 	struct msghdr msg = {0};
 	struct iovec iov;
 	struct cmsghdr *cmsg;
-	char control[CMSG_SPACE(sizeof(int))];
+	char *control;
+	const unsigned int control_size = CMSG_SPACE(sizeof(*fd) * *num_fds);
 	int rc;
+	int i;
 
-	*fd = -1;
+	control = calloc(control_size, 1);
+
+	for (i = 0; i < *num_fds; i++)
+		fd[i] = -1;
+
 	msg.msg_iov = &iov;
 	msg.msg_iovlen = 1;
 	iov.iov_base = buf;
 	iov.iov_len = len;
 	msg.msg_control = control;
-	msg.msg_controllen = sizeof(control);
+	msg.msg_controllen = control_size;
 
 	rc = recvmsg(sockfd, &msg, 0);
-	if (rc < 0)
-		return rc;
+	if (rc < 0) {
+		rc = -errno;
+		goto exit;
+	}
 
 	for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
 	     cmsg = CMSG_NXTHDR(&msg, cmsg)) {
 		if (cmsg->cmsg_level == SOL_SOCKET
 		    && cmsg->cmsg_type == SCM_RIGHTS) {
-			memcpy(fd, CMSG_DATA(cmsg), sizeof(*fd));
+			size_t fd_size = cmsg->cmsg_len - sizeof(*cmsg);
+			*num_fds = MIN(*num_fds, fd_size / sizeof(*fd));
+			memcpy(fd, CMSG_DATA(cmsg), *num_fds * sizeof(*fd));
 			break;
 		}
 	}
 
+exit:
+	free(control);
 	return rc;
 }
+
+int cras_poll(struct pollfd *fds, nfds_t nfds, struct timespec *timeout,
+	      const sigset_t *sigmask)
+{
+	struct timespec now;
+	struct timespec future;
+	struct pollfd *fd = fds;
+	nfds_t i;
+	int rc = 0;
+
+	if (timeout) {
+		/* Treat a negative timeout as valid (but timed-out) since
+		 * this function could update timeout to have negative tv_sec
+		 * or tv_nsec. */
+		if (timeout->tv_sec < 0 || timeout->tv_nsec < 0)
+			return -ETIMEDOUT;
+		rc = clock_gettime(CLOCK_MONOTONIC_RAW, &future);
+		if (rc < 0)
+			return -errno;
+		add_timespecs(&future, timeout);
+	}
+
+	for (i = 0; i < nfds; i++) {
+		fd->revents = 0;
+		fd++;
+	}
+
+	rc = ppoll(fds, nfds, timeout, sigmask);
+	if (rc == 0 && timeout) {
+		rc = -ETIMEDOUT;
+	}
+	else if (rc < 0) {
+		rc = -errno;
+	}
+
+	if (timeout) {
+		clock_gettime(CLOCK_MONOTONIC_RAW, &now);
+		subtract_timespecs(&future, &now, timeout);
+	}
+
+	return rc;
+}
+
+int wait_for_dev_input_access()
+{
+	/* Wait for /dev/input/event* files to become accessible by
+	 * having group 'input'.  Setting these files to have 'rw'
+	 * access to group 'input' is done through a udev rule
+	 * installed by adhd into /lib/udev/rules.d.
+	 *
+	 * Wait for up to 2 seconds for the /dev/input/event* files to be
+	 * readable by gavd.
+	 *
+	 * TODO(thutt): This could also be done with a udev enumerate
+	 *              and then a udev monitor.
+	 */
+	const unsigned max_iterations = 4;
+	unsigned i = 0;
+
+	while (i < max_iterations) {
+		int		   readable;
+		struct timeval	   timeout;
+		const char * const pathname = "/dev/input/event0";
+
+		timeout.tv_sec	= 0;
+		timeout.tv_usec = 500000;   /* 1/2 second. */
+		readable = access(pathname, R_OK);
+
+		/* If the file could be opened, then the udev rule has been
+		 * applied and gavd can read the event files.  If there are no
+		 * event files, then we don't need to wait.
+		 *
+		 * If access does not become available, then headphone &
+		 * microphone jack autoswitching will not function properly.
+		 */
+		if (readable == 0 || (readable == -1 && errno == ENOENT)) {
+			/* Access allowed, or file does not exist. */
+			break;
+		}
+		if (readable != -1 || errno != EACCES) {
+			syslog(LOG_ERR, "Bad access for input devs.");
+			return errno;
+		}
+		select(1, NULL, NULL, NULL, &timeout);
+		++i;
+	}
+
+	return 0;
+}
diff --git a/cras/src/common/cras_util.h b/cras/src/common/cras_util.h
index 30d301c..237cff6 100644
--- a/cras/src/common/cras_util.h
+++ b/cras/src/common/cras_util.h
@@ -10,6 +10,7 @@
 extern "C" {
 #endif
 
+#include <poll.h>
 #include <time.h>
 
 #include "cras_types.h"
@@ -75,12 +76,14 @@
 /* Makes a file descriptor blocking. */
 int cras_make_fd_blocking(int fd);
 
-/* Send data in buf to the socket with an extra file descriptor. */
-int cras_send_with_fd(int sockfd, const void *buf, size_t len, int fd);
+/* Send data in buf to the socket attach the fds. */
+int cras_send_with_fds(int sockfd, const void *buf, size_t len, int *fd,
+		       unsigned int num_fds);
 
-/* Receive data in buf from the socket. If we also receive a file
-descriptor, put it in *fd, otherwise set *fd to -1. */
-int cras_recv_with_fd(int sockfd, const void *buf, size_t len, int *fd);
+/* Receive data in buf from the socket. If file descriptors are received, put
+ * them in *fd, otherwise set *fd to -1. */
+int cras_recv_with_fds(int sockfd, void *buf, size_t len, int *fd,
+		       unsigned int *num_fds);
 
 /* This must be written a million times... */
 static inline void subtract_timespecs(const struct timespec *end,
@@ -154,6 +157,19 @@
 	return ts->tv_sec * 1000 + (ts->tv_nsec + 999999) / 1000000;
 }
 
+/* Convert milliseconds to timespec. */
+static inline void ms_to_timespec(time_t milliseconds, struct timespec *ts)
+{
+	ts->tv_sec = milliseconds / 1000;
+	ts->tv_nsec = (milliseconds % 1000) * 1000000;
+}
+
+/* Returns non-zero if the given timespec is non-zero. */
+static inline int timespec_is_nonzero(const struct timespec *ts) {
+	return ts && (ts->tv_sec != 0 ||
+		      (ts->tv_sec == 0 && ts->tv_nsec != 0));
+}
+
 /* Calculates frames since time beg. */
 static inline unsigned int cras_frames_since_time(const struct timespec *beg,
 						  unsigned int rate)
@@ -168,6 +184,39 @@
 	return cras_time_to_frames(&time_since, rate);
 }
 
+/* Poll on the given file descriptors.
+ *
+ * See ppoll(). This implementation changes the value of timeout to the
+ * remaining time, and returns negative error codes on error.
+ *
+ * Args:
+ *    fds - Array of pollfd structures.
+ *    nfds - Number of pollfd structures.
+ *    timeout - Timeout time updated upon return with remaining time. The
+ *              timeout value may be updated to become invalid (negative
+ *              tv_nsec or negative tv_sec). In that case, -tv_nsec is the
+ *              number of nanoseconds by which the polling exceeded the
+ *              supplied timeout. The function immediately returns with
+ *              -ETIMEOUT if tv_nsec is negative, simplifying loops that
+ *              rely on the returned remaining timeout.
+ *    sigmask - Signal mask while in the poll.
+ *
+ * Returns:
+ *    Positive when file decriptors are ready.
+ *    Zero if no file descriptors are ready and timeout is NULL.
+ *    -ETIMEDOUT when no file descriptors are ready and a timeout specified.
+ *    Other negative error codes specified in the ppoll() man page.
+ */
+int cras_poll(struct pollfd *fds, nfds_t nfds, struct timespec *timeout,
+              const sigset_t *sigmask);
+
+/* Wait for /dev/input/event* files to become accessible.
+ *
+ * Returns:
+ *   Zero on success. Otherwise a negative error code.
+ */
+int wait_for_dev_input_access();
+
 #ifdef __cplusplus
 } /* extern "C" */
 #endif
diff --git a/cras/src/common/edid_utils.c b/cras/src/common/edid_utils.c
index 7664d19..4a1a0a6 100644
--- a/cras/src/common/edid_utils.c
+++ b/cras/src/common/edid_utils.c
@@ -328,8 +328,8 @@
 			(edid_ext[dbp + DBCA_FORMAT]>>3) & 0xf;
 		unsigned char dbca_rate = edid_ext[dbp + DBCA_RATE];
 
-		fprintf(outfile, "Audio: %d channel %s: ",
-			edid_ext[dbp + DBCA_FORMAT] & 0x7,
+		fprintf(outfile, "Audio: %d channels %s: ",
+			(edid_ext[dbp + DBCA_FORMAT] & 0x7) + 1,
 			sad_audio_type[atype]);
 
 		if (dbca_rate & 0x40)
diff --git a/cras/src/dsp/drc.c b/cras/src/dsp/drc.c
index 5c63fd7..9f35deb 100644
--- a/cras/src/dsp/drc.c
+++ b/cras/src/dsp/drc.c
@@ -170,8 +170,8 @@
 /* Initializes the emphasis and deemphasis filter */
 static void init_emphasis_eq(struct drc *drc)
 {
-	struct biquad e = {0};
-	struct biquad d = {0};
+	struct biquad e = { 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f };
+	struct biquad d = { 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f };
 	int i, j;
 
 	float stage_gain = drc_get_param(drc, 0, PARAM_FILTER_STAGE_GAIN);
@@ -256,40 +256,66 @@
 		dk_free(&drc->kernel[i]);
 }
 
-#if defined(__ARM_NEON__)
-#include <arm_neon.h>
-static void sum3(float *data, float *data1, float *data2, int n)
+// Note gcc 4.9+ with -O2 on aarch64 produces vectorized version of C
+// that is comparable performance, but twice as large.  -O1 and -Os produce
+// small but slower code (4x slower than Neon).
+#if defined(__aarch64__)
+static void sum3(float *data, const float *data1, const float *data2, int n)
 {
-	float32x4_t x, y, z;
 	int count = n / 4;
 	int i;
 
 	if (count) {
 		__asm__ __volatile(
 			"1:                                         \n"
-			"vld1.32 {%e[x],%f[x]}, [%[data1]]!         \n"
-			"vld1.32 {%e[y],%f[y]}, [%[data2]]!         \n"
-			"vld1.32 {%e[z],%f[z]}, [%[data]]           \n"
-			"vadd.f32 %q[y], %q[x]                      \n"
-			"vadd.f32 %q[z], %q[y]                      \n"
-			"vst1.32 {%e[z],%f[z]}, [%[data]]!          \n"
+			"ld1 {v0.4s}, [%[data1]], #16               \n"
+			"ld1 {v1.4s}, [%[data2]], #16               \n"
+			"ld1 {v2.4s}, [%[data]]                     \n"
+			"fadd v0.4s, v0.4s, v1.4s                   \n"
+			"fadd v0.4s, v0.4s, v2.4s                   \n"
+			"st1 {v0.4s}, [%[data]], #16                \n"
+			"subs %w[count], %w[count], #1              \n"
+			"b.ne 1b                                    \n"
+			: /* output */
+			  [data]"+r"(data),
+			  [data1]"+r"(data1),
+			  [data2]"+r"(data2),
+			  [count]"+r"(count)
+			: /* input */
+			: /* clobber */
+			  "v0", "v1", "v2", "memory", "cc"
+			);
+	}
+
+	n &= 3;
+	for (i = 0; i < n; i++)
+		data[i] += data1[i] + data2[i];
+}
+#elif defined(__ARM_NEON__)
+static void sum3(float *data, const float *data1, const float *data2, int n)
+{
+	int count = n / 4;
+	int i;
+
+	if (count) {
+		__asm__ __volatile(
+			"1:                                         \n"
+			"vld1.32 {q0}, [%[data1]]!                  \n"
+			"vld1.32 {q1}, [%[data2]]!                  \n"
+			"vld1.32 {q2}, [%[data]]                    \n"
+			"vadd.f32 q0, q0, q1                        \n"
+			"vadd.f32 q0, q0, q2                        \n"
+			"vst1.32 {q0}, [%[data]]!                   \n"
 			"subs %[count], #1                          \n"
 			"bne 1b                                     \n"
 			: /* output */
-			  "=r"(data),
-			  "=r"(data1),
-			  "=r"(data2),
-			  "=r"(count),
-			  [x]"=&w"(x),
-			  [y]"=&w"(y),
-			  [z]"=&w"(z)
+			  [data]"+r"(data),
+			  [data1]"+r"(data1),
+			  [data2]"+r"(data2),
+			  [count]"+r"(count)
 			: /* input */
-			  [data]"0"(data),
-			  [data1]"1"(data1),
-			  [data2]"2"(data2),
-			  [count]"3"(count)
 			: /* clobber */
-			  "memory", "cc"
+			  "q0", "q1", "q2", "memory", "cc"
 			);
 	}
 
@@ -299,7 +325,7 @@
 }
 #elif defined(__SSE3__)
 #include <emmintrin.h>
-static void sum3(float *data, float *data1, float *data2, int n)
+static void sum3(float *data, const float *data1, const float *data2, int n)
 {
 	__m128 x, y, z;
 	int count = n / 4;
@@ -320,18 +346,14 @@
 			"sub $1, %[count]                           \n"
 			"jne 1b                                     \n"
 			: /* output */
-			  "=r"(data),
-			  "=r"(data1),
-			  "=r"(data2),
-			  "=r"(count),
-			  [x]"=&x"(x),
-			  [y]"=&x"(y),
-			  [z]"=&x"(z)
+			  [data]"+r"(data),
+			  [data1]"+r"(data1),
+			  [data2]"+r"(data2),
+			  [count]"+r"(count),
+			  [x]"=x"(x),
+			  [y]"=x"(y),
+			  [z]"=x"(z)
 			: /* input */
-			  [data]"0"(data),
-			  [data1]"1"(data1),
-			  [data2]"2"(data2),
-			  [count]"3"(count)
 			: /* clobber */
 			  "memory", "cc"
 			);
@@ -342,7 +364,7 @@
 		data[i] += data1[i] + data2[i];
 }
 #else
-static void sum3(float *data, float *data1, float *data2, int n)
+static void sum3(float *data, const float *data1, const float *data2, int n)
 {
 	int i;
 	for (i = 0; i < n; i++)
diff --git a/cras/src/dsp/drc_kernel.c b/cras/src/dsp/drc_kernel.c
index 56154ad..bb922b6 100644
--- a/cras/src/dsp/drc_kernel.c
+++ b/cras/src/dsp/drc_kernel.c
@@ -406,42 +406,62 @@
 
 /* For a division of frames, take the absolute values of left channel and right
  * channel, store the maximum of them in output. */
-#ifdef __ARM_NEON__
-#include <arm_neon.h>
-static inline void max_abs_division(float *output, float *data0, float *data1)
+#if defined(__aarch64__)
+static inline void max_abs_division(float *output,
+				    const float *data0, const float *data1)
 {
-	float32x4_t x, y;
 	int count = DIVISION_FRAMES / 4;
 
 	__asm__ __volatile__(
 		"1:                                     \n"
-		"vld1.32 {%e[x],%f[x]}, [%[data0]]!     \n"
-		"vld1.32 {%e[y],%f[y]}, [%[data1]]!     \n"
-		"vabs.f32 %q[x], %q[x]                  \n"
-		"vabs.f32 %q[y], %q[y]                  \n"
-		"vmax.f32 %q[x], %q[y]                  \n"
-		"vst1.32 {%e[x],%f[x]}, [%[output]]!    \n"
+		"ld1 {v0.4s}, [%[data0]], #16           \n"
+		"ld1 {v1.4s}, [%[data1]], #16           \n"
+		"fabs v0.4s, v0.4s                      \n"
+		"fabs v1.4s, v1.4s                      \n"
+		"fmax v0.4s, v0.4s, v1.4s               \n"
+		"st1 {v0.4s}, [%[output]], #16          \n"
+		"subs %w[count], %w[count], #1          \n"
+		"b.ne 1b                                \n"
+		: /* output */
+		  [data0]"+r"(data0),
+		  [data1]"+r"(data1),
+		  [output]"+r"(output),
+		  [count]"+r"(count)
+		: /* input */
+		: /* clobber */
+		  "v0", "v1", "memory", "cc"
+		);
+}
+#elif defined(__ARM_NEON__)
+static inline void max_abs_division(float *output,
+				    const float *data0, const float *data1)
+{
+	int count = DIVISION_FRAMES / 4;
+
+	__asm__ __volatile__(
+		"1:                                     \n"
+		"vld1.32 {q0}, [%[data0]]!              \n"
+		"vld1.32 {q1}, [%[data1]]!              \n"
+		"vabs.f32 q0, q0                        \n"
+		"vabs.f32 q1, q1                        \n"
+		"vmax.f32 q0, q1                        \n"
+		"vst1.32 {q0}, [%[output]]!             \n"
 		"subs %[count], #1                      \n"
 		"bne 1b                                 \n"
 		: /* output */
-		  "=r"(data0),
-		  "=r"(data1),
-		  "=r"(output),
-		  "=r"(count),
-		  [x]"=&w"(x),
-		  [y]"=&w"(y)
+		  [data0]"+r"(data0),
+		  [data1]"+r"(data1),
+		  [output]"+r"(output),
+		  [count]"+r"(count)
 		: /* input */
-		  [data0]"0"(data0),
-		  [data1]"1"(data1),
-		  [output]"2"(output),
-		  [count]"3"(count)
 		: /* clobber */
-		  "memory", "cc"
+		  "q0", "q1", "memory", "cc"
 		);
 }
 #elif defined(__SSE3__)
 #include <emmintrin.h>
-static inline void max_abs_division(float *output, float *data0, float *data1)
+static inline void max_abs_division(float *output,
+				    const float *data0, const float *data1)
 {
 	__m128 x, y;
 	int count = DIVISION_FRAMES / 4;
@@ -473,7 +493,8 @@
 		);
 }
 #else
-static inline void max_abs_division(float *output, float *data0, float *data1)
+static inline void max_abs_division(float *output,
+				    const float *data0, const float *data1)
 {
 	int i;
 	for (i = 0; i < DIVISION_FRAMES; i++)
@@ -545,6 +566,7 @@
 
 /* Calculate compress_gain from the envelope and apply total_gain to compress
  * the next output division. */
+/* TODO(fbarchard): Port to aarch64 */
 #if defined(__ARM_NEON__)
 #include <arm_neon.h>
 static void dk_compress_output(struct drc_kernel *dk)
diff --git a/cras/src/dsp/drc_math.h b/cras/src/dsp/drc_math.h
index 1cd0671..eec2061 100644
--- a/cras/src/dsp/drc_math.h
+++ b/cras/src/dsp/drc_math.h
@@ -12,7 +12,22 @@
 
 #include <stddef.h>
 #include <math.h>
+
+#ifndef __BIONIC__
 #include <ieee754.h>
+#else
+union ieee754_float
+{
+	float f;
+	/* Little endian float fields */
+	struct
+	{
+		unsigned int mantissa:23;
+		unsigned int exponent:8;
+		unsigned int negative:1;
+	} ieee;
+};
+#endif
 
 /* Uncomment to use the slow but accurate functions. */
 /* #define SLOW_DB_TO_LINEAR */
@@ -54,13 +69,13 @@
 void drc_math_init();
 
 /* Rounds the input number to the nearest integer */
-#ifdef __arm__
+#if defined(__arm__)
 static inline float round_int(float x)
 {
 	return x < 0 ? (int)(x - 0.5f) : (int)(x + 0.5f);
 }
 #else
-#define round_int rintf /* glibc will use roundss if SSE4.1 is available */
+#define round_int rintf /* Uses frintx for arm64 and roundss for SSE4.1 */
 #endif
 
 static inline float decibels_to_linear(float decibels)
diff --git a/cras/src/dsp/dsp_util.c b/cras/src/dsp/dsp_util.c
index 6e99a6f..6de9a4e 100644
--- a/cras/src/dsp/dsp_util.c
+++ b/cras/src/dsp/dsp_util.c
@@ -1,9 +1,8 @@
-/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+/* Copyright 2013 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 <fpu_control.h>
 #include "dsp_util.h"
 
 #ifndef max
@@ -21,6 +20,115 @@
 #undef deinterleave_stereo
 #undef interleave_stereo
 
+/* Converts shorts in range of -32768 to 32767 to floats in range of
+ * -1.0f to 1.0f.
+ * scvtf instruction accepts fixed point ints, so sxtl is used to lengthen
+ * shorts to int with sign extension.
+ */
+#ifdef __aarch64__
+static void deinterleave_stereo(int16_t *input, float *output1,
+				float *output2, int frames)
+{
+	int chunk = frames >> 3;
+	frames &= 7;
+	/* Process 8 frames (16 samples) each loop. */
+	/* L0 R0 L1 R1 L2 R2 L3 R3... -> L0 L1 L2 L3... R0 R1 R2 R3... */
+	if (chunk) {
+		__asm__ __volatile__ (
+			"1:                                         \n"
+			"ld2  {v2.8h, v3.8h}, [%[input]], #32       \n"
+			"subs %w[chunk], %w[chunk], #1              \n"
+			"sxtl   v0.4s, v2.4h                        \n"
+			"sxtl2  v1.4s, v2.8h                        \n"
+			"sxtl   v2.4s, v3.4h                        \n"
+			"sxtl2  v3.4s, v3.8h                        \n"
+			"scvtf  v0.4s, v0.4s, #15                   \n"
+			"scvtf  v1.4s, v1.4s, #15                   \n"
+			"scvtf  v2.4s, v2.4s, #15                   \n"
+			"scvtf  v3.4s, v3.4s, #15                   \n"
+			"st1    {v0.4s, v1.4s}, [%[output1]], #32   \n"
+			"st1    {v2.4s, v3.4s}, [%[output2]], #32   \n"
+			"b.ne   1b                                  \n"
+			: /* output */
+			  [chunk]"+r"(chunk),
+			  [input]"+r"(input),
+			  [output1]"+r"(output1),
+			  [output2]"+r"(output2)
+			: /* input */
+			: /* clobber */
+			  "v0", "v1", "v2", "v3", "memory", "cc"
+			);
+	}
+
+	/* The remaining samples. */
+	while (frames--) {
+		*output1++ = *input++ / 32768.0f;
+		*output2++ = *input++ / 32768.0f;
+	}
+}
+#define deinterleave_stereo deinterleave_stereo
+
+/* Converts floats in range of -1.0f to 1.0f to shorts in range of
+ * -32768 to 32767 with rounding to nearest, with ties (0.5) rounding away
+ * from zero.
+ * Rounding is achieved by using fcvtas instruction. (a = away)
+ * The float scaled to a range of -32768 to 32767 by adding 15 to the exponent.
+ * Add to exponent is equivalent to multiply for exponent range of 0 to 239,
+ * which is 2.59 * 10^33.  A signed saturating add (sqadd) limits exponents
+ * from 240 to 255 to clamp to 255.
+ * For very large values, beyond +/- 2 billion, fcvtas will clamp the result
+ * to the min or max value that fits an int.
+ * For other values, sqxtn clamps the output to -32768 to 32767 range.
+ */
+static void interleave_stereo(float *input1, float *input2,
+			      int16_t *output, int frames)
+{
+	/* Process 4 frames (8 samples) each loop. */
+	/* L0 L1 L2 L3, R0 R1 R2 R3 -> L0 R0 L1 R1, L2 R2 L3 R3 */
+	int chunk = frames >> 2;
+	frames &= 3;
+
+	if (chunk) {
+		__asm__ __volatile__ (
+			"dup    v2.4s, %w[scale]                    \n"
+			"1:                                         \n"
+			"ld1    {v0.4s}, [%[input1]], #16           \n"
+			"ld1    {v1.4s}, [%[input2]], #16           \n"
+			"subs   %w[chunk], %w[chunk], #1            \n"
+			"sqadd  v0.4s, v0.4s, v2.4s                 \n"
+			"sqadd  v1.4s, v1.4s, v2.4s                 \n"
+			"fcvtas v0.4s, v0.4s                        \n"
+			"fcvtas v1.4s, v1.4s                        \n"
+			"sqxtn  v0.4h, v0.4s                        \n"
+			"sqxtn  v1.4h, v1.4s                        \n"
+			"st2    {v0.4h, v1.4h}, [%[output]], #16    \n"
+			"b.ne   1b                                  \n"
+			: /* output */
+			  [chunk]"+r"(chunk),
+			  [input1]"+r"(input1),
+			  [input2]"+r"(input2),
+			  [output]"+r"(output)
+			: /* input */
+			  [scale]"r"(15 << 23)
+			: /* clobber */
+			  "v0", "v1", "v2", "memory", "cc"
+			);
+	}
+
+	/* The remaining samples */
+	while (frames--) {
+		float f;
+		f = *input1++ * 32768.0f;
+		f += (f >= 0) ? 0.5f : -0.5f;
+		*output++ = max(-32768, min(32767, (int)(f)));
+		f = *input2++ * 32768.0f;
+		f += (f >= 0) ? 0.5f : -0.5f;
+		*output++ = max(-32768, min(32767, (int)(f)));
+	}
+}
+#define interleave_stereo interleave_stereo
+#endif
+
 #ifdef __ARM_NEON__
 #include <arm_neon.h>
 
@@ -66,6 +174,16 @@
 }
 #define deinterleave_stereo deinterleave_stereo
 
+/* Converts floats in range of -1.0f to 1.0f to shorts in range of
+ * -32768 to 32767 with rounding to nearest, with ties (0.5) rounding away
+ * from zero.
+ * Rounding is achieved by adding 0.5 or -0.5 adjusted for fixed point
+ * precision, and then converting float to fixed point using vcvt instruction
+ * which truncated toward zero.
+ * For very large values, beyond +/- 2 billion, vcvt will clamp the result
+ * to the min or max value that fits an int.
+ * For other values, vqmovn clamps the output to -32768 to 32767 range.
+ */
 static void interleave_stereo(float *input1, float *input2,
 			      int16_t *output, int frames)
 {
@@ -100,15 +218,11 @@
 			"vst2.16 {d2-d3}, [%[output]]!		    \n"
 			"bne 1b					    \n"
 			: /* output */
-			  "=r"(chunk),
-			  "=r"(input1),
-			  "=r"(input2),
-			  "=r"(output)
+			  [chunk]"+r"(chunk),
+			  [input1]"+r"(input1),
+			  [input2]"+r"(input2),
+			  [output]"+r"(output)
 			: /* input */
-			  [chunk]"0"(chunk),
-			  [input1]"1"(input1),
-			  [input2]"2"(input2),
-			  [output]"3"(output),
 			  [pos]"w"(pos),
 			  [neg]"w"(neg)
 			: /* clobber */
@@ -119,21 +233,30 @@
 	/* The remaining samples */
 	while (frames--) {
 		float f;
-		f = *input1++;
-		f += (f > 0) ? (0.5f / 32768.0f) : (-0.5f / 32768.0f);
-		*output++ = max(-32768, min(32767, (int)(f * 32768.0f)));
-		f = *input2++;
-		f += (f > 0) ? (0.5f / 32768.0f) : (-0.5f / 32768.0f);
-		*output++ = max(-32768, min(32767, (int)(f * 32768.0f)));
+		f = *input1++ * 32768.0f;
+		f += (f >= 0) ? 0.5f : -0.5f;
+		*output++ = max(-32768, min(32767, (int)(f)));
+		f = *input2++ * 32768.0f;
+		f += (f >= 0) ? 0.5f : -0.5f;
+		*output++ = max(-32768, min(32767, (int)(f)));
 	}
 }
 #define interleave_stereo interleave_stereo
-
 #endif
 
 #ifdef __SSE3__
 #include <emmintrin.h>
 
+/* Converts shorts in range of -32768 to 32767 to floats in range of
+ * -1.0f to 1.0f.
+ * pslld and psrad shifts are used to isolate the low and high word, but
+ * each in a different range:
+ * The low word is shifted to the high bits in range 0x80000000 .. 0x7fff0000.
+ * The high word is shifted to the low bits in range 0x00008000 .. 0x00007fff.
+ * cvtdq2ps converts ints to floats as is.
+ * mulps is used to normalize the range of the low and high words, adjusting
+ * for high and low words being in different range.
+ */
 static void deinterleave_stereo(int16_t *input, float *output1,
 				float *output2, int frames)
 {
@@ -190,6 +313,12 @@
 }
 #define deinterleave_stereo deinterleave_stereo
 
+/* Converts floats in range of -1.0f to 1.0f to shorts in range of
+ * -32768 to 32767 with rounding to nearest, with ties (0.5) rounding to
+ * even.
+ * For very large values, beyond +/- 2 billion, cvtps2dq will produce
+ * 0x80000000 and packssdw will clamp -32768.
+ */
 static void interleave_stereo(float *input1, float *input2,
 			      int16_t *output, int frames)
 {
@@ -203,13 +332,13 @@
 			"1:                                         \n"
 			"lddqu (%[input1]), %%xmm0                  \n"
 			"lddqu (%[input2]), %%xmm2                  \n"
+			"add $16, %[input1]                         \n"
+			"add $16, %[input2]                         \n"
 			"movaps %%xmm0, %%xmm1                      \n"
 			"unpcklps %%xmm2, %%xmm0                    \n"
 			"unpckhps %%xmm2, %%xmm1                    \n"
-			"add $16, %[input1]                         \n"
-			"add $16, %[input2]                         \n"
-			"mulps %[scale_2_15], %%xmm0                \n"
-			"mulps %[scale_2_15], %%xmm1                \n"
+			"paddsw %[scale_2_15], %%xmm0               \n"
+			"paddsw %[scale_2_15], %%xmm1               \n"
 			"cvtps2dq %%xmm0, %%xmm0                    \n"
 			"cvtps2dq %%xmm1, %%xmm1                    \n"
 			"packssdw %%xmm1, %%xmm0                    \n"
@@ -218,16 +347,13 @@
 			"sub $1, %[chunk]                           \n"
 			"jnz 1b                                     \n"
 			: /* output */
-			  "=r"(chunk),
-			  "=r"(input1),
-			  "=r"(input2),
-			  "=r"(output)
+			  [chunk]"+r"(chunk),
+			  [input1]"+r"(input1),
+			  [input2]"+r"(input2),
+			  [output]"+r"(output)
 			: /* input */
-			  [chunk]"0"(chunk),
-			  [input1]"1"(input1),
-			  [input2]"2"(input2),
-			  [output]"3"(output),
-			  [scale_2_15]"x"(_mm_set1_ps(1.0f*(1<<15)))
+			  [scale_2_15]"x"(_mm_set1_epi32(15 << 23)),
+			  [clamp_large]"x"(_mm_set1_ps(32767.0f))
 			: /* clobber */
 			  "xmm0", "xmm1", "xmm2", "memory", "cc"
 			);
@@ -236,16 +362,15 @@
 	/* The remaining samples */
 	while (frames--) {
 		float f;
-		f = *input1++;
-		f += (f > 0) ? (0.5f / 32768.0f) : (-0.5f / 32768.0f);
-		*output++ = max(-32768, min(32767, (int)(f * 32768.0f)));
-		f = *input2++;
-		f += (f > 0) ? (0.5f / 32768.0f) : (-0.5f / 32768.0f);
-		*output++ = max(-32768, min(32767, (int)(f * 32768.0f)));
+		f = *input1++ * 32768.0f;
+		f += (f >= 0) ? 0.5f : -0.5f;
+		*output++ = max(-32768, min(32767, (int)(f)));
+		f = *input2++ * 32768.0f;
+		f += (f >= 0) ? 0.5f : -0.5f;
+		*output++ = max(-32768, min(32767, (int)(f)));
 	}
 }
 #define interleave_stereo interleave_stereo
-
 #endif
 
 void dsp_util_deinterleave(int16_t *input, float *const *output, int channels,
@@ -287,15 +412,9 @@
 
 	for (i = 0; i < frames; i++)
 		for (j = 0; j < channels; j++) {
-			int16_t i16;
 			float f = *(input_ptr[j]++) * 32768.0f;
-			if (f > 32767)
-				i16 = 32767;
-			else if (f < -32768)
-				i16 = -32768;
-			else
-				i16 = (int16_t) (f > 0 ? f + 0.5f : f - 0.5f);
-			*output++ = i16;
+			f += (f >= 0) ? 0.5f : -0.5f;
+			*output++ = max(-32768, min(32767, (int)(f)));
 		}
 }
 
@@ -305,10 +424,21 @@
 	unsigned int mxcsr;
 	mxcsr = __builtin_ia32_stmxcsr();
 	__builtin_ia32_ldmxcsr(mxcsr | 0x8040);
+#elif defined(__aarch64__)
+	uint64_t cw;
+	__asm__ __volatile__ (
+		"mrs    %0, fpcr			    \n"
+		"orr    %0, %0, #0x1000000		    \n"
+		"msr    fpcr, %0			    \n"
+		"isb					    \n"
+		: "=r"(cw) :: "memory");
 #elif defined(__arm__)
-	int cw;
-	_FPU_GETCW(cw);
-	_FPU_SETCW(cw | (1 << 24));
+	uint32_t cw;
+	__asm__ __volatile__ (
+		"vmrs   %0, fpscr			    \n"
+		"orr    %0, %0, #0x1000000		    \n"
+		"vmsr   fpscr, %0			    \n"
+		: "=r"(cw) :: "memory");
 #else
 #warning "Don't know how to disable denorms. Performace may suffer."
 #endif
diff --git a/cras/src/dsp/tests/dsp_util_test.c b/cras/src/dsp/tests/dsp_util_test.c
new file mode 100644
index 0000000..3a22db3
--- /dev/null
+++ b/cras/src/dsp/tests/dsp_util_test.c
@@ -0,0 +1,376 @@
+/* Copyright 2016 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>  /* for abs() */
+#include <stdio.h>  /* for printf() */
+#include <string.h> /* for memset() */
+#include <stdint.h> /* for uint64 definition */
+#include <stdlib.h> /* for exit() definition */
+#include <time.h> /* for clock_gettime */
+
+#include "../drc_math.h"
+#include "../dsp_util.h"
+
+
+/* Constant for converting time to milliseconds. */
+#define BILLION 1000000000LL
+/* Number of iterations for performance testing. */
+#define ITERATIONS 400000
+
+#if defined(__aarch64__)
+int16_t float_to_short(float a) {
+	int32_t ret;
+	asm volatile ("fcvtas %s[ret], %s[a]\n"
+		      "sqxtn %h[ret], %s[ret]\n"
+		      : [ret] "=w" (ret)
+		      : [a] "w" (a)
+		      :);
+	return (int16_t)(ret);
+}
+#else
+int16_t float_to_short(float a) {
+	a += (a >= 0) ? 0.5f : -0.5f;
+	return (int16_t)(max(-32768, min(32767, a)));
+}
+#endif
+
+void dsp_util_deinterleave_reference(int16_t *input, float *const *output,
+				     int channels, int frames)
+{
+	float *output_ptr[channels];
+	int i, j;
+
+	for (i = 0; i < channels; i++)
+		output_ptr[i] = output[i];
+
+	for (i = 0; i < frames; i++)
+		for (j = 0; j < channels; j++)
+			*(output_ptr[j]++) = *input++ / 32768.0f;
+}
+
+void dsp_util_interleave_reference(float *const *input, int16_t *output,
+				   int channels, int frames)
+{
+	float *input_ptr[channels];
+	int i, j;
+
+	for (i = 0; i < channels; i++)
+		input_ptr[i] = input[i];
+
+	for (i = 0; i < frames; i++)
+		for (j = 0; j < channels; j++) {
+			float f = *(input_ptr[j]++) * 32768.0f;
+			*output++ = float_to_short(f);
+		}
+}
+
+/* Use fixed size allocation to avoid performance fluctuation of allocation. */
+#define MAXSAMPLES 4096
+#define MINSAMPLES 256
+/* PAD buffer to check for overflows. */
+#define PAD 4096
+
+void TestRounding(float in, int16_t expected, int samples)
+{
+	int i;
+	int max_diff;
+	int d;
+
+	short* in_shorts = (short*) malloc(MAXSAMPLES * 2 * 2 + PAD);
+	float* out_floats_left_c = (float*) malloc(MAXSAMPLES * 4 + PAD);
+	float* out_floats_right_c = (float*) malloc(MAXSAMPLES * 4 + PAD);
+	float* out_floats_left_opt = (float*) malloc(MAXSAMPLES * 4 + PAD);
+	float* out_floats_right_opt = (float*) malloc(MAXSAMPLES * 4 + PAD);
+	short* out_shorts_c = (short*) malloc(MAXSAMPLES * 2 * 2 + PAD);
+	short* out_shorts_opt = (short*) malloc(MAXSAMPLES * 2 * 2 + PAD);
+
+	memset(in_shorts, 0xfb, MAXSAMPLES * 2 * 2 + PAD);
+	memset(out_floats_left_c, 0xfb, MAXSAMPLES * 4 + PAD);
+	memset(out_floats_right_c, 0xfb, MAXSAMPLES * 4 + PAD);
+	memset(out_floats_left_opt, 0xfb, MAXSAMPLES * 4 + PAD);
+	memset(out_floats_right_opt, 0xfb, MAXSAMPLES * 4 + PAD);
+	memset(out_shorts_c, 0xfb, MAXSAMPLES * 2 * 2 + PAD);
+	memset(out_shorts_opt, 0xfb, MAXSAMPLES * 2 * 2 + PAD);
+
+	float *out_floats_ptr_c[2];
+	float *out_floats_ptr_opt[2];
+
+	out_floats_ptr_c[0] = out_floats_left_c;
+	out_floats_ptr_c[1] = out_floats_right_c;
+	out_floats_ptr_opt[0] = out_floats_left_opt;
+	out_floats_ptr_opt[1] = out_floats_right_opt;
+
+	for (i = 0; i < MAXSAMPLES; ++i) {
+		out_floats_left_c[i] = in;
+		out_floats_right_c[i] = in;
+	}
+
+	/*  reference C interleave */
+	dsp_util_interleave_reference(out_floats_ptr_c, out_shorts_c, 2,
+				      samples);
+
+	/* measure optimized interleave */
+	for (i = 0; i < ITERATIONS; ++i) {
+		dsp_util_interleave(out_floats_ptr_c, out_shorts_opt, 2,
+				    samples);
+	}
+
+	max_diff = 0;
+	for (i = 0; i < (MAXSAMPLES * 2 + PAD / 2); ++i) {
+		d = abs(out_shorts_c[i] - out_shorts_opt[i]);
+		if (d > max_diff) {
+			max_diff = d;
+		}
+	}
+	printf("test interleave compare %6d, %10f %13f %6d %6d %6d %s\n",
+		max_diff, in, in * 32768.0f, out_shorts_c[0], out_shorts_opt[0],
+		expected,
+		max_diff == 0 ? "PASS" : (out_shorts_opt[0] == expected ?
+		"EXPECTED DIFFERENCE" : "UNEXPECTED DIFFERENCE"));
+
+	/* measure reference C deinterleave */
+	dsp_util_deinterleave_reference(in_shorts, out_floats_ptr_c, 2,
+					samples);
+
+	/* measure optimized deinterleave */
+	dsp_util_deinterleave(in_shorts, out_floats_ptr_opt, 2, samples);
+
+	d = memcmp(out_floats_ptr_c[0], out_floats_ptr_opt[0], samples * 4);
+	if (d) printf("left compare %d, %f %f\n", d, out_floats_ptr_c[0][0],
+		      out_floats_ptr_opt[0][0]);
+	d = memcmp(out_floats_ptr_c[1], out_floats_ptr_opt[1], samples * 4);
+	if (d) printf("right compare %d, %f %f\n", d, out_floats_ptr_c[1][0],
+		      out_floats_ptr_opt[1][0]);
+
+	free(in_shorts);
+	free(out_floats_left_c);
+	free(out_floats_right_c);
+	free(out_floats_left_opt);
+	free(out_floats_right_opt);
+	free(out_shorts_c);
+	free(out_shorts_opt);
+}
+
+int main(int argc, char **argv)
+{
+	float e = 0.000000001f;
+	int samples = 16;
+
+	dsp_enable_flush_denormal_to_zero();
+
+	// Print headings for TestRounding output.
+	printf("test interleave compare maxdif,     float,   float * 32k      "
+	       "C   SIMD expect pass\n");
+
+	// test clamping
+	TestRounding(1.0f, 32767, samples);
+	TestRounding(-1.0f, -32768, samples);
+	TestRounding(1.1f, 32767, samples);
+	TestRounding(-1.1f, -32768, samples);
+	TestRounding(2000000000.f / 32768.f, 32767, samples);
+	TestRounding(-2000000000.f / 32768.f, -32768, samples);
+
+	/* Infinity produces zero on arm64. */
+#if defined(__aarch64__)
+#define EXPECTED_INF_RESULT 0
+#define EXPECTED_NEGINF_RESULT 0
+#elif defined(__i386__) || defined(__x86_64__)
+#define EXPECTED_INF_RESULT -32768
+#define EXPECTED_NEGINF_RESULT 0
+#else
+#define EXPECTED_INF_RESULT 32767
+#define EXPECTED_NEGINF_RESULT -32768
+#endif
+
+	TestRounding(5000000000.f / 32768.f, EXPECTED_INF_RESULT, samples);
+	TestRounding(-5000000000.f / 32768.f, EXPECTED_NEGINF_RESULT, samples);
+
+	// test infinity
+	union ieee754_float inf;
+	inf.ieee.negative = 0;
+	inf.ieee.exponent = 0xfe;
+	inf.ieee.mantissa = 0x7fffff;
+	TestRounding(inf.f, EXPECTED_INF_RESULT, samples);  // expect fail
+	inf.ieee.negative = 1;
+	inf.ieee.exponent = 0xfe;
+	inf.ieee.mantissa = 0x7fffff;
+	TestRounding(inf.f, EXPECTED_NEGINF_RESULT, samples);  // expect fail
+
+	// test rounding
+	TestRounding(0.25f, 8192, samples);
+	TestRounding(-0.25f, -8192, samples);
+	TestRounding(0.50f, 16384, samples);
+	TestRounding(-0.50f, -16384, samples);
+	TestRounding(1.0f / 32768.0f, 1, samples);
+	TestRounding(-1.0f / 32768.0f, -1, samples);
+	TestRounding(1.0f / 32768.0f + e, 1, samples);
+	TestRounding(-1.0f / 32768.0f - e, -1, samples);
+	TestRounding(1.0f / 32768.0f - e, 1, samples);
+	TestRounding(-1.0f / 32768.0f + e, -1, samples);
+
+	/* Rounding on 'tie' is different for Intel. */
+#if defined(__i386__) || defined(__x86_64__)
+	TestRounding(0.5f / 32768.0f, 0, samples);  /* Expect round to even */
+	TestRounding(-0.5f / 32768.0f, 0, samples);
+#else
+	TestRounding(0.5f / 32768.0f, 1, samples);  /* Expect round away */
+	TestRounding(-0.5f / 32768.0f, -1, samples);
+#endif
+
+	TestRounding(0.5f / 32768.0f + e, 1, samples);
+	TestRounding(-0.5f / 32768.0f - e, 1, samples);
+	TestRounding(0.5f / 32768.0f - e, 0, samples);
+	TestRounding(-0.5f / 32768.0f + e, 0, samples);
+
+	TestRounding(1.5f / 32768.0f, 2, samples);
+	TestRounding(-1.5f / 32768.0f, -2, samples);
+	TestRounding(1.5f / 32768.0f + e, 2, samples);
+	TestRounding(-1.5f / 32768.0f - e, -2, samples);
+	TestRounding(1.5f / 32768.0f - e, 1, samples);
+	TestRounding(-1.5f / 32768.0f + e, -1, samples);
+
+	/* Test denormals */
+	union ieee754_float denorm;
+	denorm.ieee.negative = 0;
+	denorm.ieee.exponent = 0;
+	denorm.ieee.mantissa = 1;
+	TestRounding(denorm.f, 0, samples);
+	denorm.ieee.negative = 1;
+	denorm.ieee.exponent = 0;
+	denorm.ieee.mantissa = 1;
+	TestRounding(denorm.f, 0, samples);
+
+	/* Test NaNs. Caveat Results vary by implementation. */
+#if defined(__i386__) || defined(__x86_64__)
+#define EXPECTED_NAN_RESULT -32768
+#else
+#define EXPECTED_NAN_RESULT 0
+#endif
+	union ieee754_float nan;  /* Quiet NaN */
+	nan.ieee.negative = 0;
+	nan.ieee.exponent = 0xff;
+	nan.ieee.mantissa = 0x400001;
+	TestRounding(nan.f, EXPECTED_NAN_RESULT, samples);
+	nan.ieee.negative = 0;
+	nan.ieee.exponent = 0xff;
+	nan.ieee.mantissa = 0x000001;  /* Signalling NaN */
+	TestRounding(nan.f, EXPECTED_NAN_RESULT, samples);
+
+	/* Test Performance */
+	uint64_t diff;
+	struct timespec start, end;
+	int i;
+	int d;
+
+	short* in_shorts = (short*) malloc(MAXSAMPLES * 2 * 2 + PAD);
+	float* out_floats_left_c = (float*) malloc(MAXSAMPLES * 4 + PAD);
+	float* out_floats_right_c = (float*) malloc(MAXSAMPLES * 4 + PAD);
+	float* out_floats_left_opt = (float*) malloc(MAXSAMPLES * 4 + PAD);
+	float* out_floats_right_opt = (float*) malloc(MAXSAMPLES * 4 + PAD);
+	short* out_shorts_c = (short*) malloc(MAXSAMPLES * 2 * 2 + PAD);
+	short* out_shorts_opt = (short*) malloc(MAXSAMPLES * 2 * 2 + PAD);
+
+	memset(in_shorts, 0x11, MAXSAMPLES * 2 * 2 + PAD);
+	memset(out_floats_left_c, 0x22, MAXSAMPLES * 4 + PAD);
+	memset(out_floats_right_c, 0x33, MAXSAMPLES * 4 + PAD);
+	memset(out_floats_left_opt, 0x44, MAXSAMPLES * 4 + PAD);
+	memset(out_floats_right_opt, 0x55, MAXSAMPLES * 4 + PAD);
+	memset(out_shorts_c, 0x66, MAXSAMPLES * 2 * 2 + PAD);
+	memset(out_shorts_opt, 0x66, MAXSAMPLES * 2 * 2 + PAD);
+
+	float *out_floats_ptr_c[2];
+	float *out_floats_ptr_opt[2];
+
+	out_floats_ptr_c[0] = out_floats_left_c;
+	out_floats_ptr_c[1] = out_floats_right_c;
+	out_floats_ptr_opt[0] = out_floats_left_opt;
+	out_floats_ptr_opt[1] = out_floats_right_opt;
+
+	/* Benchmark dsp_util_interleave */
+	for (samples = MAXSAMPLES; samples >= MINSAMPLES; samples /= 2) {
+
+		/* measure original C interleave */
+		clock_gettime(CLOCK_MONOTONIC, &start); /* mark start time */
+		for (i = 0; i < ITERATIONS; ++i) {
+			dsp_util_interleave_reference(out_floats_ptr_c,
+						      out_shorts_c,
+						      2, samples);
+		}
+		clock_gettime(CLOCK_MONOTONIC, &end); /* mark the end time */
+		diff = (BILLION * (end.tv_sec - start.tv_sec) +
+			end.tv_nsec - start.tv_nsec) / 1000000;
+		printf("interleave   ORIG size = %6d, elapsed time = %llu ms\n",
+		       samples, (long long unsigned int) diff);
+
+		/* measure optimized interleave */
+		clock_gettime(CLOCK_MONOTONIC, &start); /* mark start time */
+		for (i = 0; i < ITERATIONS; ++i) {
+			dsp_util_interleave(out_floats_ptr_c, out_shorts_opt, 2,
+					    samples);
+		}
+		clock_gettime(CLOCK_MONOTONIC, &end); /* mark the end time */
+		diff = (BILLION * (end.tv_sec - start.tv_sec) +
+			end.tv_nsec - start.tv_nsec) / 1000000;
+		printf("interleave   SIMD size = %6d, elapsed time = %llu ms\n",
+		       samples, (long long unsigned int) diff);
+
+		/* Test C and SIMD output match */
+		d = memcmp(out_shorts_c, out_shorts_opt,
+			   MAXSAMPLES * 2 * 2 + PAD);
+		if (d) printf("interleave compare %d, %d %d, %d %d\n", d,
+			      out_shorts_c[0], out_shorts_c[1],
+			      out_shorts_opt[0], out_shorts_opt[1]);
+	}
+
+	/* Benchmark dsp_util_deinterleave */
+	for (samples = MAXSAMPLES; samples >= MINSAMPLES; samples /= 2) {
+
+		/* Measure original C deinterleave */
+		clock_gettime(CLOCK_MONOTONIC, &start); /* mark start time */
+		for (i = 0; i < ITERATIONS; ++i) {
+			dsp_util_deinterleave_reference(in_shorts,
+							out_floats_ptr_c,
+							2, samples);
+		}
+		clock_gettime(CLOCK_MONOTONIC, &end); /* mark the end time */
+		diff = (BILLION * (end.tv_sec - start.tv_sec) +
+			end.tv_nsec - start.tv_nsec) / 1000000;
+			printf("deinterleave ORIG size = %6d, "
+			       "elapsed time = %llu ms\n",
+			       samples, (long long unsigned int) diff);
+
+		/* Measure optimized deinterleave */
+		clock_gettime(CLOCK_MONOTONIC, &start); /* mark start time */
+		for (i = 0; i < ITERATIONS; ++i) {
+			dsp_util_deinterleave(in_shorts, out_floats_ptr_opt, 2,
+					      samples);
+		}
+		clock_gettime(CLOCK_MONOTONIC, &end); /* mark the end time */
+		diff = (BILLION * (end.tv_sec - start.tv_sec) +
+			end.tv_nsec - start.tv_nsec) / 1000000;
+		printf("deinterleave SIMD size = %6d, elapsed time = %llu ms\n",
+			samples, (long long unsigned int) diff);
+
+		/* Test C and SIMD output match */
+		d = memcmp(out_floats_ptr_c[0], out_floats_ptr_opt[0],
+			   samples * 4);
+		if (d) printf("left compare %d, %f %f\n", d,
+			      out_floats_ptr_c[0][0], out_floats_ptr_opt[0][0]);
+		d = memcmp(out_floats_ptr_c[1], out_floats_ptr_opt[1],
+			   samples * 4);
+		if (d) printf("right compare %d, %f %f\n", d,
+			      out_floats_ptr_c[1][0], out_floats_ptr_opt[1][0]);
+	}
+
+	free(in_shorts);
+	free(out_floats_left_c);
+	free(out_floats_right_c);
+	free(out_floats_left_opt);
+	free(out_floats_right_opt);
+	free(out_shorts_c);
+	free(out_shorts_opt);
+
+	return 0;
+}
\ No newline at end of file
diff --git a/cras/src/libcras/cras_client.c b/cras/src/libcras/cras_client.c
index 8ff1ea4..98acde1 100644
--- a/cras/src/libcras/cras_client.c
+++ b/cras/src/libcras/cras_client.c
@@ -30,12 +30,15 @@
 #include <limits.h>
 #include <poll.h>
 #include <pthread.h>
+#include <stdbool.h>
 #include <stdint.h>
+#include <sys/eventfd.h>
 #include <sys/ipc.h>
+#include <sys/mman.h>
 #include <sys/param.h>
-#include <sys/shm.h>
 #include <sys/signal.h>
 #include <sys/socket.h>
+#include <sys/timerfd.h>
 #include <sys/types.h>
 #include <sys/un.h>
 #include <syslog.h>
@@ -43,27 +46,17 @@
 
 #include "cras_client.h"
 #include "cras_config.h"
+#include "cras_file_wait.h"
 #include "cras_messages.h"
+#include "cras_observer_ops.h"
 #include "cras_shm.h"
 #include "cras_types.h"
 #include "cras_util.h"
 #include "utlist.h"
 
-#ifdef __ANDROID__
-void *shmat(int shmid, const void *shmaddr, int shmflg);
-int shmdt(const void *shmaddr);
-int shmget(key_t key, size_t size, int shmflg);
-
-long __set_errno_internal(int n) {
-  errno = n;
-  return -1;
-}
-#endif
-
 static const size_t MAX_CMD_MSG_LEN = 256;
-static const size_t SERVER_CONNECT_TIMEOUT_NS = 500000000;
 static const size_t SERVER_SHUTDOWN_TIMEOUT_US = 500000;
-static const size_t SERVER_FIRST_MESSAGE_TIMEOUT_NS = 500000000;
+static const size_t SERVER_CONNECT_TIMEOUT_MS = 1000;
 
 /* Commands sent from the user to the running client. */
 enum {
@@ -72,6 +65,7 @@
 	CLIENT_REMOVE_STREAM,
 	CLIENT_SET_STREAM_VOLUME_SCALER,
 	CLIENT_SERVER_CONNECT,
+	CLIENT_SERVER_CONNECT_ASYNC,
 };
 
 struct command_msg {
@@ -108,10 +102,17 @@
 	cras_stream_id_t stream_id;
 };
 
+enum CRAS_THREAD_STATE {
+	CRAS_THREAD_STOP,	/* Isn't (shouldn't be) running. */
+	CRAS_THREAD_WARMUP,	/* Is started, but not fully functional: waiting
+				 * for resources to be ready for example. */
+	CRAS_THREAD_RUNNING,	/* Is running and fully functional. */
+};
+
 /* Manage information for a thread. */
 struct thread_state {
 	pthread_t tid;
-	unsigned  running;
+	enum CRAS_THREAD_STATE state;
 };
 
 /* Parameters used when setting up a capture or playback stream. See comment
@@ -155,45 +156,180 @@
 	struct cras_client *client;
 	struct cras_stream_params *config;
 	struct cras_audio_shm capture_shm;
+	int capture_shm_size;
 	struct cras_audio_shm play_shm;
+	int play_shm_size;
 	struct client_stream *prev, *next;
 };
 
+/* State of the socket. */
+typedef enum cras_socket_state {
+	CRAS_SOCKET_STATE_DISCONNECTED,
+		/* Not connected. Also used to cleanup the current connection
+		 * before restarting the connection attempt. */
+	CRAS_SOCKET_STATE_WAIT_FOR_SOCKET,
+		/* Waiting for the socket file to exist. Socket file existence
+		 * is monitored using cras_file_wait. */
+	CRAS_SOCKET_STATE_WAIT_FOR_WRITABLE,
+		/* Waiting for the socket to have something at the other end. */
+	CRAS_SOCKET_STATE_FIRST_MESSAGE,
+		/* Waiting for the first messages from the server and set our
+		 * client ID. */
+	CRAS_SOCKET_STATE_CONNECTED,
+		/* The socket is connected and working. */
+	CRAS_SOCKET_STATE_ERROR_DELAY,
+		/* There was an error during one of the above states. Sleep for
+		 * a bit before continuing. If this state could not be initiated
+		 * then we move to the DISCONNECTED state and notify via the
+		 * connection callback. */
+} cras_socket_state_t;
+
 /* Represents a client used to communicate with the audio server.
  * id - Unique identifier for this client, negative until connected.
- * server_fd Incoming messages from server.
+ * server_fd - Incoming messages from server.
+ * server_fd_state - State of the server's socket.
+ * server_event_fd - Eventfd to wait on until a connection is established.
  * stream_fds - Pipe for attached streams.
  * command_fds - Pipe for user commands to thread.
  * command_reply_fds - Pipe for acking/nacking command messages from thread.
- * sock_dir - Directory where the local audio socket can be found.
+ * sock_file - Server communication socket file.
+ * sock_file_wait - Structure used to monitor existence of the socket file.
+ * sock_file_exists - Set to true when the socket file exists.
  * running - The client thread will run while this is non zero.
  * next_stream_id - ID to give the next stream.
+ * stream_start_cond - Condition used during stream startup.
+ * stream_start_lock - Lock used during stream startup.
  * tid - Thread ID of the client thread started by "cras_client_run_thread".
  * last_command_result - Passes back the result of the last user command.
  * streams - Linked list of streams attached to this client.
  * server_state - RO shared memory region holding server state.
  * debug_info_callback - Function to call when debug info is received.
+ * get_hotword_models_cb_t - Function to call when hotword models info is ready.
+ * server_err_cb - Function to call when failed to read messages from server.
+ * server_err_user_arg - User argument for server_err_cb.
+ * server_connection_cb - Function to called when a connection state changes.
+ * server_connection_user_arg - User argument for server_connection_cb.
+ * thread_priority_cb - Function to call for setting audio thread priority.
+ * observer_ops - Functions to call when system state changes.
+ * observer_context - Context passed to client in state change callbacks.
  */
 struct cras_client {
 	int id;
 	int server_fd;
+	cras_socket_state_t server_fd_state;
+	int server_event_fd;
 	int stream_fds[2];
 	int command_fds[2];
 	int command_reply_fds[2];
-	const char *sock_dir;
+	const char *sock_file;
+	struct cras_file_wait *sock_file_wait;
+	bool sock_file_exists;
 	struct thread_state thread;
 	cras_stream_id_t next_stream_id;
+	pthread_cond_t stream_start_cond;
+	pthread_mutex_t stream_start_lock;
 	int last_command_result;
 	struct client_stream *streams;
 	const struct cras_server_state *server_state;
 	void (*debug_info_callback)(struct cras_client *);
+	get_hotword_models_cb_t get_hotword_models_cb;
+	cras_server_error_cb_t server_err_cb;
+	cras_connection_status_cb_t server_connection_cb;
+	void *server_connection_user_arg;
+	cras_thread_priority_cb_t thread_priority_cb;
+	struct cras_observer_ops observer_ops;
+	void *observer_context;
 };
 
 /*
+ * Holds the client pointer plus internal book keeping.
+ *
+ * client - The client
+ * server_state_rwlock - lock to make the client's server_state thread-safe.
+ */
+struct client_int {
+	struct cras_client client;
+	pthread_rwlock_t server_state_rwlock;
+};
+
+#define to_client_int(cptr) \
+((struct client_int *)((char *)cptr - offsetof(struct client_int, client)))
+
+/*
  * Local Helpers
  */
 
+static int client_thread_rm_stream(struct cras_client *client,
+				   cras_stream_id_t stream_id);
 static int handle_message_from_server(struct cras_client *client);
+static int reregister_notifications(struct cras_client *client);
+
+/*
+ * Unlock the server_state_rwlock if lock_rc is 0.
+ *
+ * Args:
+ *    client - The CRAS client pointer.
+ *    lock_rc - The result of server_state_rdlock or
+ *              server_state_wrlock.
+ */
+static void server_state_unlock(const struct cras_client *client,
+				int lock_rc)
+{
+	struct client_int *client_int;
+
+	if (!client)
+		return;
+	client_int = to_client_int(client);
+	if (lock_rc == 0)
+		pthread_rwlock_unlock(&client_int->server_state_rwlock);
+}
+
+/*
+ * Lock the server_state_rwlock for reading.
+ *
+ * Also checks that the server_state pointer is valid.
+ *
+ * Args:
+ *    client - The CRAS client pointer.
+ * Returns:
+ *    0 for success, positive error code on error.
+ *    Returns EINVAL if the server state pointer is NULL.
+ */
+static int server_state_rdlock(const struct cras_client *client)
+{
+	struct client_int *client_int;
+	int lock_rc;
+
+	if (!client)
+		return EINVAL;
+	client_int = to_client_int(client);
+	lock_rc = pthread_rwlock_rdlock(&client_int->server_state_rwlock);
+	if (lock_rc != 0)
+		return lock_rc;
+	if (!client->server_state) {
+		pthread_rwlock_unlock(&client_int->server_state_rwlock);
+		return EINVAL;
+	}
+	return 0;
+}
+
+/*
+ * Lock the server_state_rwlock for writing.
+ *
+ * Args:
+ *    client - The CRAS client pointer.
+ * Returns:
+ *    0 for success, positive error code on error.
+ */
+static int server_state_wrlock(const struct cras_client *client)
+{
+	struct client_int *client_int;
+
+	if (!client)
+		return EINVAL;
+	client_int = to_client_int(client);
+	return pthread_rwlock_wrlock(&client_int->server_state_rwlock);
+}
 
 /* Get the stream pointer from a stream id. */
 static struct client_stream *stream_from_id(const struct cras_client *client,
@@ -205,129 +341,610 @@
 	return out;
 }
 
-/* Waits until we have heard back from the server so that we know we are
- * connected.  The connected success/failure message is always the first message
- * the server sends. Return non zero if client is connected to the server. A
- * return code of zero means that the client is not connected to the server. */
-static int check_server_connected_wait(struct cras_client *client)
+/*
+ * Fill a pollfd structure with the current server fd and events.
+ */
+void server_fill_pollfd(const struct cras_client *client,
+			struct pollfd *poll_fd)
 {
-	struct pollfd pollfd;
-	int rc;
-	struct timespec timeout, now;
+	int events = 0;
 
-	clock_gettime(CLOCK_MONOTONIC_RAW, &now);
-	timeout.tv_sec = 0;
-	timeout.tv_nsec = SERVER_FIRST_MESSAGE_TIMEOUT_NS;
-	add_timespecs(&timeout, &now);
-
-	pollfd.fd = client->server_fd;
-	pollfd.events = POLLIN;
-
-	while (timespec_after(&timeout, &now) > 0 && client->id < 0) {
-		rc = ppoll(&pollfd, 1, &timeout, NULL);
-		if (rc <= 0 && rc != -EAGAIN)
-			return 0; /* Timeout or error. */
-		if (pollfd.revents & POLLIN) {
-			rc = handle_message_from_server(client);
-			if (rc < 0)
-				return 0;
-		}
-		clock_gettime(CLOCK_MONOTONIC_RAW, &now);
+	poll_fd->fd = client->server_fd;
+	switch (client->server_fd_state) {
+	case CRAS_SOCKET_STATE_DISCONNECTED:
+		break;
+	case CRAS_SOCKET_STATE_WAIT_FOR_SOCKET:
+	case CRAS_SOCKET_STATE_FIRST_MESSAGE:
+	case CRAS_SOCKET_STATE_CONNECTED:
+	case CRAS_SOCKET_STATE_ERROR_DELAY:
+		events = POLLIN;
+		break;
+	case CRAS_SOCKET_STATE_WAIT_FOR_WRITABLE:
+		events = POLLOUT;
+		break;
 	}
-
-	return client->id >= 0;
+	poll_fd->events = events;
+	poll_fd->revents = 0;
 }
 
-/* Waits until the fd is writable or the specified time has passed. Returns 0 if
- * the fd is writable, -1 for timeout or other error. */
-static int wait_until_fd_writable(int fd, int timeout_ns)
+/*
+ * Change the server_fd_state.
+ */
+static void server_fd_move_to_state(struct cras_client *client,
+				    cras_socket_state_t state)
 {
-	struct pollfd pollfd;
-	struct timespec timeout;
+	if (state == client->server_fd_state)
+		return;
+
+	client->server_fd_state = state;
+}
+
+/*
+ * Action to take when in state ERROR_DELAY.
+ *
+ * In this state we want to sleep for a few seconds before retrying the
+ * connection to the audio server.
+ *
+ * If server_fd is negative: create a timer and setup server_fd with the
+ * timer's fd. If server_fd is not negative and there is input, then assume
+ * that the timer has expired, and restart the connection by moving to
+ * WAIT_FOR_SOCKET state.
+ */
+static int error_delay_next_action(struct cras_client *client,
+				   int poll_revents)
+{
 	int rc;
+	struct itimerspec timeout;
 
-	timeout.tv_sec = 0;
-	timeout.tv_nsec = timeout_ns;
+	if (client->server_fd == -1) {
+		client->server_fd = timerfd_create(
+					CLOCK_MONOTONIC,
+					TFD_NONBLOCK|TFD_CLOEXEC);
+		if (client->server_fd == -1) {
+			rc = -errno;
+			syslog(LOG_ERR,
+			       "cras_client: Could not create timerfd: %s",
+			       strerror(-rc));
+			return rc;
+		}
 
-	pollfd.fd = fd;
-	pollfd.events = POLLOUT;
+		/* Setup a relative timeout of 2 seconds. */
+		memset(&timeout, 0, sizeof(timeout));
+		timeout.it_value.tv_sec = 2;
+		rc = timerfd_settime(client->server_fd, 0, &timeout, NULL);
+		if (rc != 0) {
+			rc = -errno;
+			syslog(LOG_ERR,
+			       "cras_client: Could not set timeout: %s",
+			       strerror(-rc));
+			return rc;
+		}
+		return 0;
+	} else if ((poll_revents & POLLIN) == 0) {
+		return 0;
+	}
 
-	rc = ppoll(&pollfd, 1, &timeout, NULL);
-	if (rc <= 0)
-		return -1;
+	/* Move to the next state: close the timer fd first. */
+	close(client->server_fd);
+	client->server_fd = -1;
+	server_fd_move_to_state(client, CRAS_SOCKET_STATE_WAIT_FOR_SOCKET);
 	return 0;
 }
 
-/* Opens the server socket and connects to it. */
-static int connect_to_server(struct cras_client *client)
+/*
+ * Action to take when in WAIT_FOR_SOCKET state.
+ *
+ * In this state we are waiting for the socket file to exist. The existence of
+ * the socket file is continually monitored using the cras_file_wait structure
+ * and a separate fd. When the sock_file_exists boolean is modified, the state
+ * machine is invoked.
+ *
+ * If the socket file exists, then we move to the WAIT_FOR_WRITABLE state.
+ */
+static void wait_for_socket_next_action(struct cras_client *client)
+{
+	if (client->sock_file_exists)
+		server_fd_move_to_state(
+			client, CRAS_SOCKET_STATE_WAIT_FOR_WRITABLE);
+}
+
+/*
+ * Action to take when in WAIT_FOR_WRITABLE state.
+ *
+ * In this state we are initiating a connection the server and waiting for the
+ * server to ready for incoming messages.
+ *
+ * Create the socket to the server, and wait while a connect request results in
+ * -EINPROGRESS. Otherwise, we assume that the socket file will be deleted by
+ * the server and the server_fd_state will be changed in
+ * sock_file_wait_dispatch().
+ */
+static int wait_for_writable_next_action(struct cras_client *client,
+					 int poll_revents)
 {
 	int rc;
 	struct sockaddr_un address;
 
-	if (client->server_fd >= 0)
-		close(client->server_fd);
-	client->server_fd = socket(PF_UNIX, SOCK_SEQPACKET, 0);
-	if (client->server_fd < 0) {
-		syslog(LOG_ERR, "cras_client: %s: Socket failed.", __func__);
-		return client->server_fd;
+	if (client->server_fd == -1) {
+		client->server_fd = socket(PF_UNIX, SOCK_SEQPACKET, 0);
+		if (client->server_fd < 0) {
+			rc = -errno;
+			syslog(LOG_ERR, "cras_client: server socket failed: %s",
+			       strerror(-rc));
+			return rc;
+		}
+	}
+	else if ((poll_revents & POLLOUT) == 0) {
+		return 0;
 	}
 
-	memset(&address, 0, sizeof(struct sockaddr_un));
-
-	address.sun_family = AF_UNIX;
-	client->sock_dir = cras_config_get_system_socket_file_dir();
-	assert(client->sock_dir);
-	snprintf(address.sun_path, sizeof(address.sun_path),
-		 "%s/%s", client->sock_dir, CRAS_SOCKET_FILE);
-
 	/* We make the file descriptor non-blocking when we do connect(), so we
-	 * don't block indifinitely. */
+	 * don't block indefinitely. */
 	cras_make_fd_nonblocking(client->server_fd);
+
+	memset(&address, 0, sizeof(struct sockaddr_un));
+	address.sun_family = AF_UNIX;
+	strcpy(address.sun_path, client->sock_file);
 	rc = connect(client->server_fd, (struct sockaddr *)&address,
 		     sizeof(struct sockaddr_un));
-
-	if (rc == -1 && errno == EINPROGRESS) {
-		rc = wait_until_fd_writable(client->server_fd,
-					    SERVER_CONNECT_TIMEOUT_NS);
+	if (rc != 0) {
+		rc = -errno;
+		/* For -EINPROGRESS, we wait for POLLOUT on the server_fd.
+		 * Otherwise CRAS is not running and we assume that the socket
+		 * file will be deleted and recreated. Notification of that will
+		 * happen via the sock_file_wait_dispatch(). */
+		if (rc == -ECONNREFUSED) {
+			/* CRAS is not running, don't log this error and just
+			 * stay in this state waiting sock_file_wait_dispatch()
+			 * to move the state machine. */
+			close(client->server_fd);
+			client->server_fd = -1;
+		}
+		else if (rc != -EINPROGRESS) {
+			syslog(LOG_ERR,
+			       "cras_client: server connect failed: %s",
+			       strerror(-rc));
+			return rc;
+		}
+		return 0;
 	}
 
 	cras_make_fd_blocking(client->server_fd);
+	server_fd_move_to_state(client, CRAS_SOCKET_STATE_FIRST_MESSAGE);
+	return 0;
+}
+
+/*
+ * Action to take when transitioning to the CONNECTED state.
+ */
+static int connect_transition_action(struct cras_client *client)
+{
+	eventfd_t event_value;
+	int rc;
+
+	rc = reregister_notifications(client);
+	if (rc < 0)
+		return rc;
+
+	server_fd_move_to_state(client, CRAS_SOCKET_STATE_CONNECTED);
+	/* Notify anyone waiting on this state change that we're
+	 * connected. */
+	eventfd_read(client->server_event_fd, &event_value);
+	eventfd_write(client->server_event_fd, 1);
+	if (client->server_connection_cb)
+		client->server_connection_cb(
+				client, CRAS_CONN_STATUS_CONNECTED,
+				client->server_connection_user_arg);
+	return 0;
+}
+
+/*
+ * Action to take when in the FIRST_MESSAGE state.
+ *
+ * We are waiting for the first message from the server. When our client ID has
+ * been set, then we can move to the CONNECTED state.
+ */
+static int first_message_next_action(struct cras_client *client,
+				     int poll_revents)
+{
+	int rc;
+
+	if (client->server_fd < 0)
+		return -EINVAL;
+
+	if ((poll_revents & POLLIN) == 0)
+		return 0;
+
+	rc = handle_message_from_server(client);
+	if (rc < 0) {
+		syslog(LOG_ERR, "handle first message: %s", strerror(-rc));
+	} else if (client->id >= 0) {
+		rc = connect_transition_action(client);
+	} else {
+		syslog(LOG_ERR, "did not get ID after first message!");
+		rc = -EINVAL;
+	}
+	return rc;
+}
+
+/*
+ * Play nice and shutdown the server socket.
+ */
+static inline int shutdown_and_close_socket(int sockfd)
+{
+	int rc;
+	uint8_t buffer[CRAS_CLIENT_MAX_MSG_SIZE];
+	struct timeval tv;
+
+	tv.tv_sec = 0;
+	tv.tv_usec = SERVER_SHUTDOWN_TIMEOUT_US;
+	setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv));
+
+	rc = shutdown(sockfd, SHUT_WR);
+	if (rc < 0)
+		return rc;
+	/* Wait until the socket is closed by the peer. */
+	for (;;) {
+		rc = recv(sockfd, buffer, sizeof(buffer), 0);
+		if (rc <= 0)
+			break;
+	}
+	return close(sockfd);
+}
+
+/*
+ * Action to take when disconnecting from the server.
+ *
+ * Clean up the server socket, and the server_state pointer. Move to the next
+ * logical state.
+ */
+static void disconnect_transition_action(struct cras_client *client, bool force)
+{
+	eventfd_t event_value;
+	cras_socket_state_t old_state = client->server_fd_state;
+	struct client_stream *s;
+	int lock_rc;
+
+	/* Stop all playing streams.
+	 * TODO(muirj): Pause and resume streams. */
+	DL_FOREACH(client->streams, s) {
+		s->config->err_cb(client, s->id, -ENOTCONN,
+				  s->config->user_data);
+		client_thread_rm_stream(client, s->id);
+	}
+
+	/* Clean up the server_state pointer. */
+	lock_rc = server_state_wrlock(client);
+	if (client->server_state) {
+		munmap((void *)client->server_state,
+		       sizeof(*client->server_state));
+		client->server_state = NULL;
+	}
+	server_state_unlock(client, lock_rc);
+
+	/* Our ID is unknown now. */
+	client->id = -1;
+
+	/* Clean up the server fd. */
+	if (client->server_fd >= 0) {
+		if (!force)
+			shutdown_and_close_socket(client->server_fd);
+		else
+			close(client->server_fd);
+		client->server_fd = -1;
+	}
+
+	/* Reset the server_event_fd value to 0 (and cause subsequent threads
+	 * waiting on the connection to wait). */
+	eventfd_read(client->server_event_fd, &event_value);
+
+	switch (old_state) {
+	case CRAS_SOCKET_STATE_DISCONNECTED:
+		/* Do nothing: already disconnected. */
+		break;
+	case CRAS_SOCKET_STATE_ERROR_DELAY:
+		/* We're disconnected and there was a failure to setup
+		 * automatic reconnection, so call the server error
+		 * callback now. */
+		server_fd_move_to_state(
+			client, CRAS_SOCKET_STATE_DISCONNECTED);
+		if (client->server_connection_cb)
+			client->server_connection_cb(
+					client, CRAS_CONN_STATUS_FAILED,
+					client->server_connection_user_arg);
+		else if (client->server_err_cb)
+			client->server_err_cb(
+				client, client->server_connection_user_arg);
+		break;
+	case CRAS_SOCKET_STATE_WAIT_FOR_SOCKET:
+	case CRAS_SOCKET_STATE_WAIT_FOR_WRITABLE:
+	case CRAS_SOCKET_STATE_FIRST_MESSAGE:
+		/* We are running this state transition while a connection is
+		 * in progress for an error case. When there is no error, we
+		 * come into this function in the DISCONNECTED state. */
+		server_fd_move_to_state(
+			client, CRAS_SOCKET_STATE_ERROR_DELAY);
+		break;
+	case CRAS_SOCKET_STATE_CONNECTED:
+		/* Disconnected from CRAS (for an error), wait for the socket
+		 * file to be (re)created. */
+		server_fd_move_to_state(
+			client, CRAS_SOCKET_STATE_WAIT_FOR_SOCKET);
+		/* Notify the caller that we aren't connected anymore. */
+		if (client->server_connection_cb)
+			client->server_connection_cb(
+					client, CRAS_CONN_STATUS_DISCONNECTED,
+					client->server_connection_user_arg);
+		break;
+	}
+}
+
+static int server_fd_dispatch(struct cras_client *client, int poll_revents)
+{
+	int rc = 0;
+	cras_socket_state_t old_state;
+
+	if ((poll_revents & POLLHUP) != 0) {
+		/* Error or disconnect: cleanup and make a state change now. */
+		disconnect_transition_action(client, true);
+	}
+	old_state = client->server_fd_state;
+
+	switch (client->server_fd_state) {
+	case CRAS_SOCKET_STATE_DISCONNECTED:
+		/* Assume that we've taken the necessary actions. */
+		return -ENOTCONN;
+	case CRAS_SOCKET_STATE_ERROR_DELAY:
+		rc = error_delay_next_action(client, poll_revents);
+		break;
+	case CRAS_SOCKET_STATE_WAIT_FOR_SOCKET:
+		wait_for_socket_next_action(client);
+		break;
+	case CRAS_SOCKET_STATE_WAIT_FOR_WRITABLE:
+		rc = wait_for_writable_next_action(client, poll_revents);
+		break;
+	case CRAS_SOCKET_STATE_FIRST_MESSAGE:
+		rc = first_message_next_action(client, poll_revents);
+		break;
+	case CRAS_SOCKET_STATE_CONNECTED:
+		if ((poll_revents & POLLIN) != 0)
+			rc = handle_message_from_server(client);
+		break;
+	}
 
 	if (rc != 0) {
-		close(client->server_fd);
-		client->server_fd = -1;
-		syslog(LOG_ERR, "cras_client: %s: Connect server failed.",
-		       __func__);
+		/* If there is an error, then start-over. */
+		rc = server_fd_dispatch(client, POLLHUP);
+	} else if (old_state != client->server_fd_state) {
+		/* There was a state change, process the new state now. */
+		rc = server_fd_dispatch(client, 0);
 	}
+	return rc;
+}
+
+/*
+ * Start connecting to the server if we aren't already.
+ */
+static int server_connect(struct cras_client *client)
+{
+	if (client->server_fd_state != CRAS_SOCKET_STATE_DISCONNECTED)
+		return 0;
+	/* Start waiting for the server socket to exist. */
+	server_fd_move_to_state(client, CRAS_SOCKET_STATE_WAIT_FOR_SOCKET);
+	return server_fd_dispatch(client, 0);
+}
+
+/*
+ * Disconnect from the server if we haven't already.
+ */
+static void server_disconnect(struct cras_client *client)
+{
+	if (client->server_fd_state == CRAS_SOCKET_STATE_DISCONNECTED)
+		return;
+	/* Set the disconnected state first so that the disconnect
+	 * transition doesn't move the server state to ERROR_DELAY. */
+	server_fd_move_to_state(client, CRAS_SOCKET_STATE_DISCONNECTED);
+	disconnect_transition_action(client, false);
+}
+
+/*
+ * Called when something happens to the socket file.
+ */
+static void sock_file_wait_callback(void *context, cras_file_wait_event_t event,
+				    const char *filename)
+{
+	struct cras_client *client = (struct cras_client *)context;
+	switch (event) {
+	case CRAS_FILE_WAIT_EVENT_CREATED:
+		client->sock_file_exists = 1;
+		switch (client->server_fd_state) {
+		case CRAS_SOCKET_STATE_DISCONNECTED:
+		case CRAS_SOCKET_STATE_ERROR_DELAY:
+		case CRAS_SOCKET_STATE_FIRST_MESSAGE:
+		case CRAS_SOCKET_STATE_CONNECTED:
+			break;
+		case CRAS_SOCKET_STATE_WAIT_FOR_SOCKET:
+		case CRAS_SOCKET_STATE_WAIT_FOR_WRITABLE:
+			/* The socket file exists. Tell the server state
+			 * machine. */
+			server_fd_dispatch(client, 0);
+			break;
+		}
+		break;
+	case CRAS_FILE_WAIT_EVENT_DELETED:
+		client->sock_file_exists = 0;
+		switch (client->server_fd_state) {
+		case CRAS_SOCKET_STATE_DISCONNECTED:
+			break;
+		case CRAS_SOCKET_STATE_WAIT_FOR_SOCKET:
+		case CRAS_SOCKET_STATE_WAIT_FOR_WRITABLE:
+		case CRAS_SOCKET_STATE_ERROR_DELAY:
+		case CRAS_SOCKET_STATE_FIRST_MESSAGE:
+		case CRAS_SOCKET_STATE_CONNECTED:
+			/* Restart the connection process. */
+			server_disconnect(client);
+			server_connect(client);
+			break;
+		}
+		break;
+	case CRAS_FILE_WAIT_EVENT_NONE:
+		break;
+	}
+}
+
+/*
+ * Service the sock_file_wait's fd.
+ *
+ * If the socket file is deleted, then cause a disconnect from the server.
+ * Otherwise, start a reconnect depending on the server_fd_state.
+ */
+static int sock_file_wait_dispatch(struct cras_client *client,
+				   int poll_revents)
+{
+	int rc;
+
+	if ((poll_revents & POLLIN) == 0)
+		return 0;
+
+	rc = cras_file_wait_dispatch(client->sock_file_wait);
+	if (rc == -EAGAIN || rc == -EWOULDBLOCK)
+		rc = 0;
+	else if (rc != 0)
+		syslog(LOG_ERR, "cras_file_wait_dispatch: %s", strerror(-rc));
+	return rc;
+}
+
+/*
+ * Waits until we have heard back from the server so that we know we are
+ * connected.
+ *
+ * The connected success/failure message is always the first message the server
+ * sends. Return non zero if client is connected to the server. A return code
+ * of zero means that the client is not connected to the server.
+ */
+static int check_server_connected_wait(struct cras_client *client,
+				       struct timespec *timeout)
+{
+	int rc = 0;
+	struct pollfd poll_fd;
+
+	poll_fd.fd = client->server_event_fd;
+	poll_fd.events = POLLIN;
+	poll_fd.revents = 0;
+
+	/* The server_event_fd is only read and written by the functions
+	 * that connect to the server. When a connection is established the
+	 * eventfd has a value of 1 and cras_poll will return immediately
+	 * with 1. When there is no connection to the server, then this
+	 * function waits until the timeout has expired or a non-zero value
+	 * is written to the server_event_fd. */
+	while (rc == 0)
+		rc = cras_poll(&poll_fd, 1, timeout, NULL);
+	return rc > 0;
+}
+
+/* Returns non-zero if the thread is running (not stopped). */
+static inline int thread_is_running(struct thread_state *thread)
+{
+	return thread->state != CRAS_THREAD_STOP;
+}
+
+/*
+ * Opens the server socket and connects to it.
+ * Args:
+ *    client - Client pointer created with cras_client_create().
+ *    timeout - Connection timeout.
+ * Returns:
+ *    0 for success, negative error code on failure.
+ */
+static int connect_to_server(struct cras_client *client,
+			     struct timespec *timeout,
+			     bool use_command_thread)
+{
+	int rc;
+	struct pollfd poll_fd[2];
+	struct timespec connected_timeout;
+
+	if (!client)
+		return -EINVAL;
+
+	if (thread_is_running(&client->thread) && use_command_thread) {
+		rc = cras_client_connect_async(client);
+		if (rc == 0) {
+			rc = check_server_connected_wait(client, timeout);
+			return rc ? 0 : -ESHUTDOWN;
+		}
+	}
+
+	connected_timeout.tv_sec = 0;
+	connected_timeout.tv_nsec = 0;
+	if (check_server_connected_wait(client, &connected_timeout))
+		return 0;
+
+	poll_fd[0].fd = cras_file_wait_get_fd(client->sock_file_wait);
+	poll_fd[0].events = POLLIN;
+
+	rc = server_connect(client);
+	while(rc == 0) {
+		// Wait until we've connected or until there is a timeout.
+		// Meanwhile handle incoming actions on our fds.
+
+		server_fill_pollfd(client, &(poll_fd[1]));
+		rc = cras_poll(poll_fd, 2, timeout, NULL);
+		if (rc <= 0)
+			continue;
+
+		if (poll_fd[0].revents) {
+			rc = sock_file_wait_dispatch(
+					client, poll_fd[0].revents);
+			continue;
+		}
+
+		if (poll_fd[1].revents) {
+			rc = server_fd_dispatch(client, poll_fd[1].revents);
+			if (rc == 0 &&
+			    client->server_fd_state ==
+					CRAS_SOCKET_STATE_CONNECTED)
+				break;
+		}
+	}
+
+	if (rc != 0)
+		syslog(LOG_ERR, "cras_client: Connect server failed: %s",
+		       strerror(-rc));
 
 	return rc;
 }
 
-/* Tries to connect to the server.  Waits for the initial message from the
- * server.  This will happen near instantaneously if the server is already
- * running.*/
-static int connect_to_server_wait(struct cras_client *client)
+static int connect_to_server_wait_retry(struct cras_client *client,
+					int timeout_ms,
+					bool use_command_thread)
 {
-	unsigned int retries = 4;
-	const unsigned int retry_delay_ms = 200;
+	struct timespec timeout_value;
+	struct timespec *timeout;
 
-	assert(client);
-
-	/* Ignore sig pipe as it will be handled when we write to the socket. */
-	signal(SIGPIPE, SIG_IGN);
-
-	while (--retries) {
-		/* If connected, wait for the first message from the server
-		 * indicating it's ready. */
-		if (connect_to_server(client) == 0 &&
-		    check_server_connected_wait(client))
-				return 0;
-
-		/* If we didn't succeed, wait and try again. */
-		usleep(retry_delay_ms * 1000);
+	if (timeout_ms < 0) {
+		timeout = NULL;
+	} else {
+		timeout = &timeout_value;
+		ms_to_timespec(timeout_ms, timeout);
 	}
 
-	return -EIO;
+	/* If connected, wait for the first message from the server
+	 * indicating it's ready. */
+	return connect_to_server(client, timeout, use_command_thread);
+}
+
+/*
+ * Tries to connect to the server.  Waits for the initial message from the
+ * server.  This will happen near instantaneously if the server is already
+ * running.
+ */
+static int connect_to_server_wait(struct cras_client *client,
+				  bool use_command_thread)
+{
+	return connect_to_server_wait_retry(
+			client, SERVER_CONNECT_TIMEOUT_MS, use_command_thread);
 }
 
 /*
@@ -359,23 +976,27 @@
 {
 	struct pollfd pollfds[2];
 	int nread = 0;
+	int nfds = 1;
 	int rc;
 	char tmp;
 
-	pollfds[0].fd = read_fd;
+	pollfds[0].fd = wake_fd;
 	pollfds[0].events = POLLIN;
-	pollfds[1].fd = wake_fd;
-	pollfds[1].events = POLLIN;
+	if (read_fd >= 0) {
+		nfds++;
+		pollfds[1].fd = read_fd;
+		pollfds[1].events = POLLIN;
+	}
 
-	rc = poll(pollfds, 2, -1);
+	rc = poll(pollfds, nfds, -1);
 	if (rc < 0)
 		return rc;
-	if (pollfds[0].revents & POLLIN) {
+	if (read_fd >= 0 && pollfds[1].revents & POLLIN) {
 		nread = read(read_fd, buf, len);
 		if (nread != (int)len)
 			return -EIO;
 	}
-	if (pollfds[1].revents & POLLIN) {
+	if (pollfds[0].revents & POLLIN) {
 		rc = read(wake_fd, &tmp, 1);
 		if (rc < 0)
 			return rc;
@@ -383,14 +1004,22 @@
 
 	return nread;
 }
-
-/* Check if doing format conversion and configure a capture buffer appropriately
- * before passing to the client. */
+/* Check the availability and configures a capture buffer.
+ * Args:
+ *     stream - The input stream to configure buffer for.
+ *     captured_frames - To be filled with the pointer to the beginning of
+ *         captured buffer.
+ *     num_frames - Number of captured frames.
+ * Returns:
+ *     Number of frames available in captured_frames.
+ */
 static unsigned int config_capture_buf(struct client_stream *stream,
 				       uint8_t **captured_frames,
 				       unsigned int num_frames)
 {
-	*captured_frames = cras_shm_get_curr_read_buffer(&stream->capture_shm);
+	/* Always return the beginning of the read buffer because Chrome expects
+	 * so. */
+	*captured_frames = cras_shm_get_read_buffer_base(&stream->capture_shm);
 
 	/* Don't ask for more frames than the client desires. */
 	if (stream->flags & BULK_AUDIO_OK)
@@ -398,6 +1027,12 @@
 	else
 		num_frames = MIN(num_frames, stream->config->cb_threshold);
 
+	/* If shm readable frames is less than client requests, that means
+	 * overrun has happened in server side. Don't send partial corrupted
+	 * buffer to client. */
+	if (cras_shm_get_curr_read_frames(&stream->capture_shm) < num_frames)
+		return 0;
+
 	return num_frames;
 }
 
@@ -432,6 +1067,9 @@
 	}
 
 	num_frames = config_capture_buf(stream, &captured_frames, num_frames);
+	if (num_frames == 0)
+		return 0;
+
 	cras_timespec_to_timespec(&ts, &stream->capture_shm.area->ts);
 
 	if (config->unified_cb)
@@ -542,6 +1180,20 @@
 	return rc;
 }
 
+static void audio_thread_set_priority(struct client_stream *stream)
+{
+	/* Use provided callback to set priority if available. */
+	if (stream->client->thread_priority_cb) {
+		stream->client->thread_priority_cb(stream->client);
+		return;
+	}
+
+	/* Try to get RT scheduling, if that fails try to set the nice value. */
+	if (cras_set_rt_scheduling(CRAS_CLIENT_RT_THREAD_PRIORITY) ||
+	    cras_set_thread_priority(CRAS_CLIENT_RT_THREAD_PRIORITY))
+		cras_set_nice_level(CRAS_CLIENT_NICENESS_LEVEL);
+}
+
 /* Listens to the audio socket for messages from the server indicating that
  * the stream needs to be serviced.  One of these runs per stream. */
 static void *audio_thread(void *arg)
@@ -549,19 +1201,26 @@
 	struct client_stream *stream = (struct client_stream *)arg;
 	int thread_terminated = 0;
 	struct audio_message aud_msg;
+	int aud_fd;
 	int num_read;
 
 	if (arg == NULL)
 		return (void *)-EIO;
 
-	/* Try to get RT scheduling, if that fails try to set the nice value. */
-	if (cras_set_rt_scheduling(CRAS_CLIENT_RT_THREAD_PRIORITY) ||
-	    cras_set_thread_priority(CRAS_CLIENT_RT_THREAD_PRIORITY))
-		cras_set_nice_level(CRAS_CLIENT_NICENESS_LEVEL);
+	audio_thread_set_priority(stream);
 
-	while (stream->thread.running && !thread_terminated) {
+	/* Notify the control thread that we've started. */
+	pthread_mutex_lock(&stream->client->stream_start_lock);
+	pthread_cond_broadcast(&stream->client->stream_start_cond);
+	pthread_mutex_unlock(&stream->client->stream_start_lock);
+
+	while (thread_is_running(&stream->thread) && !thread_terminated) {
+		/* While we are warming up, aud_fd may not be valid and some
+		 * shared memory resources may not yet be available. */
+		aud_fd = (stream->thread.state == CRAS_THREAD_WARMUP) ?
+			 -1 : stream->aud_fd;
 		num_read = read_with_wake_fd(stream->wake_fds[0],
-					     stream->aud_fd,
+					     aud_fd,
 					     (uint8_t *)&aud_msg,
 					     sizeof(aud_msg));
 		if (num_read < 0)
@@ -599,6 +1258,77 @@
 	return 0;
 }
 
+/* Stop the audio thread for the given stream.
+ * Args:
+ *    stream - Stream for which to stop the audio thread.
+ *    join - When non-zero, attempt to join the audio thread (wait for it to
+ *           complete).
+ */
+static void stop_aud_thread(struct client_stream *stream, int join)
+{
+	if (thread_is_running(&stream->thread)) {
+		stream->thread.state = CRAS_THREAD_STOP;
+		wake_aud_thread(stream);
+		if (join)
+			pthread_join(stream->thread.tid, NULL);
+	}
+
+	if (stream->wake_fds[0] >= 0) {
+		close(stream->wake_fds[0]);
+		close(stream->wake_fds[1]);
+		stream->wake_fds[0] = -1;
+	}
+}
+
+/* Start the audio thread for this stream.
+ * Returns when the thread has started and is waiting.
+ * Args:
+ *    stream - The stream that needs an audio thread.
+ * Returns:
+ *    0 for success, or a negative error code.
+ */
+static int start_aud_thread(struct client_stream *stream)
+{
+	int rc;
+	struct timespec future;
+
+	rc = pipe(stream->wake_fds);
+	if (rc < 0) {
+		rc = -errno;
+		syslog(LOG_ERR, "cras_client: pipe: %s", strerror(-rc));
+		return rc;
+	}
+
+	stream->thread.state = CRAS_THREAD_WARMUP;
+
+	pthread_mutex_lock(&stream->client->stream_start_lock);
+	rc = pthread_create(&stream->thread.tid, NULL, audio_thread, stream);
+	if (rc) {
+		pthread_mutex_unlock(&stream->client->stream_start_lock);
+		syslog(LOG_ERR,
+		       "cras_client: Couldn't create audio stream: %s",
+		       strerror(rc));
+		stream->thread.state = CRAS_THREAD_STOP;
+		stop_aud_thread(stream, 0);
+		return -rc;
+	}
+
+	clock_gettime(CLOCK_MONOTONIC, &future);
+	future.tv_sec += 2; /* Wait up to two seconds. */
+	rc = pthread_cond_timedwait(&stream->client->stream_start_cond,
+				    &stream->client->stream_start_lock, &future);
+	pthread_mutex_unlock(&stream->client->stream_start_lock);
+	if (rc != 0) {
+		/* Something is very wrong: try to cancel the thread and don't
+		 * wait for it. */
+		syslog(LOG_ERR, "cras_client: Client thread not responding: %s",
+		       strerror(rc));
+		stop_aud_thread(stream, 0);
+		return -rc;
+	}
+	return 0;
+}
+
 /*
  * Client thread.
  */
@@ -630,20 +1360,14 @@
 }
 
 /* Gets the shared memory region used to share audio data with the server. */
-static int config_shm(struct cras_audio_shm *shm, int key, size_t size)
+static int config_shm(struct cras_audio_shm *shm, int shm_fd, size_t size)
 {
-	int shmid;
-
-	shmid = shmget(key, size, 0600);
-	if (shmid < 0) {
-		syslog(LOG_ERR,
-		       "cras_client: shmget failed to get shm for stream.");
-		return shmid;
-	}
-	shm->area = (struct cras_audio_shm_area *)shmat(shmid, NULL, 0);
+	shm->area = (struct cras_audio_shm_area *)mmap(
+			NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED,
+			shm_fd, 0);
 	if (shm->area == (struct cras_audio_shm_area *)-1) {
 		syslog(LOG_ERR,
-		       "cras_client: shmat failed to attach shm for stream.");
+		       "cras_client: mmap failed to map shm for stream.");
 		return errno;
 	}
 	/* Copy server shm config locally. */
@@ -655,10 +1379,12 @@
 /* Release shm areas if references to them are held. */
 static void free_shm(struct client_stream *stream)
 {
-	if (stream->capture_shm.area)
-		shmdt(stream->capture_shm.area);
-	if (stream->play_shm.area)
-		shmdt(stream->play_shm.area);
+	if (stream->capture_shm.area) {
+		munmap(stream->capture_shm.area, stream->capture_shm_size);
+	}
+	if (stream->play_shm.area) {
+		munmap(stream->play_shm.area, stream->play_shm_size);
+	}
 	stream->capture_shm.area = NULL;
 	stream->play_shm.area = NULL;
 }
@@ -667,65 +1393,58 @@
  * format converter, configure the shared memory region, and start the audio
  * thread that will handle requests from the server. */
 static int stream_connected(struct client_stream *stream,
-			    const struct cras_client_stream_connected *msg)
+			    const struct cras_client_stream_connected *msg,
+			    const int stream_fds[2], const unsigned int num_fds)
 {
 	int rc;
 	struct cras_audio_format mfmt;
 
-	if (msg->err) {
+	if (msg->err || num_fds != 2) {
 		syslog(LOG_ERR, "cras_client: Error Setting up stream %d\n",
 		       msg->err);
-		return msg->err;
+		rc = msg->err;
+		goto err_ret;
 	}
 
 	unpack_cras_audio_format(&mfmt, &msg->format);
 
 	if (cras_stream_has_input(stream->direction)) {
 		rc = config_shm(&stream->capture_shm,
-				msg->input_shm_key,
+				stream_fds[0],
 				msg->shm_max_size);
 		if (rc < 0) {
 			syslog(LOG_ERR,
 			       "cras_client: Error configuring capture shm");
 			goto err_ret;
 		}
+		stream->capture_shm_size = msg->shm_max_size;
 	}
 
 	if (cras_stream_uses_output_hw(stream->direction)) {
 		rc = config_shm(&stream->play_shm,
-				msg->output_shm_key,
+				stream_fds[1],
 				msg->shm_max_size);
 		if (rc < 0) {
 			syslog(LOG_ERR,
 			       "cras_client: Error configuring playback shm");
 			goto err_ret;
 		}
+		stream->play_shm_size = msg->shm_max_size;
 
 		cras_shm_set_volume_scaler(&stream->play_shm,
 					   stream->volume_scaler);
 	}
 
-	rc = pipe(stream->wake_fds);
-	if (rc < 0) {
-		goto err_ret;
-	}
+	stream->thread.state = CRAS_THREAD_RUNNING;
+	wake_aud_thread(stream);
 
-	stream->thread.running = 1;
-
-	rc = pthread_create(&stream->thread.tid, NULL, audio_thread, stream);
-	if (rc) {
-		syslog(LOG_ERR,
-		       "cras_client: Couldn't create audio stream.");
-		stream->thread.running = 0;
-		goto err_ret;
-	}
-
+	close(stream_fds[0]);
+	close(stream_fds[1]);
 	return 0;
 err_ret:
-	if (stream->wake_fds[0] >= 0) {
-		close(stream->wake_fds[0]);
-		close(stream->wake_fds[1]);
-	}
+	stop_aud_thread(stream, 1);
+	close(stream_fds[0]);
+	close(stream_fds[1]);
 	free_shm(stream);
 	return rc;
 }
@@ -741,7 +1460,8 @@
 	/* Create a socket pair for the server to notify of audio events. */
 	rc = socketpair(AF_UNIX, SOCK_STREAM, 0, sock);
 	if (rc != 0) {
-		syslog(LOG_ERR, "cras_client: socketpair fails.");
+		rc = -errno;
+		syslog(LOG_ERR, "cras_client: socketpair: %s", strerror(-rc));
 		goto fail;
 	}
 
@@ -754,8 +1474,8 @@
 				  stream->flags,
 				  stream->config->format,
 				  dev_idx);
-	rc = cras_send_with_fd(client->server_fd, &serv_msg, sizeof(serv_msg),
-			       sock[1]);
+	rc = cras_send_with_fds(client->server_fd, &serv_msg, sizeof(serv_msg),
+			       &sock[1], 1);
 	if (rc != sizeof(serv_msg)) {
 		rc = EIO;
 		syslog(LOG_ERR,
@@ -790,15 +1510,15 @@
 	/* Find the hotword device index. */
 	if ((stream->flags & HOTWORD_STREAM) == HOTWORD_STREAM &&
 			dev_idx == NO_DEVICE) {
-		int aokr_idx;
-		aokr_idx = cras_client_get_first_dev_type_idx(client,
-				CRAS_NODE_TYPE_AOKR, CRAS_STREAM_INPUT);
-		if (aokr_idx < 0) {
+		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 aokr_idx;
+			return hotword_idx;
 		}
-		dev_idx = aokr_idx;
+		dev_idx = hotword_idx;
 	}
 
 	/* Find an available stream id. */
@@ -812,11 +1532,19 @@
 	*stream_id_out = new_id;
 	stream->client = client;
 
-	/* send a message to the server asking that the stream be started. */
-	rc = send_connect_message(client, stream, dev_idx);
+	/* Start the audio thread. */
+	rc = start_aud_thread(stream);
 	if (rc != 0)
 		return rc;
 
+	/* Start the thread associated with this stream. */
+	/* send a message to the server asking that the stream be started. */
+	rc = send_connect_message(client, stream, dev_idx);
+	if (rc != 0) {
+		stop_aud_thread(stream, 1);
+		return rc;
+	}
+
 	/* Add the stream to the linked list */
 	DL_APPEND(client->streams, stream);
 
@@ -837,19 +1565,16 @@
 		return 0;
 
 	/* Tell server to remove. */
-	cras_fill_disconnect_stream_message(&msg, stream_id);
-	rc = write(client->server_fd, &msg, sizeof(msg));
-	if (rc < 0)
-		syslog(LOG_ERR,
-		       "cras_client: error removing stream from server\n");
-
-	/* And shut down locally. */
-	if (stream->thread.running) {
-		stream->thread.running = 0;
-		wake_aud_thread(stream);
-		pthread_join(stream->thread.tid, NULL);
+	if (client->server_fd_state == CRAS_SOCKET_STATE_CONNECTED) {
+		cras_fill_disconnect_stream_message(&msg, stream_id);
+		rc = write(client->server_fd, &msg, sizeof(msg));
+		if (rc < 0)
+			syslog(LOG_ERR,
+			       "cras_client: error removing stream from server\n");
 	}
 
+	/* And shut down locally. */
+	stop_aud_thread(stream, 1);
 
 	free_shm(stream);
 
@@ -857,10 +1582,6 @@
 	if (stream->aud_fd >= 0)
 		close(stream->aud_fd);
 
-	if (stream->wake_fds[0] >= 0) {
-		close(stream->wake_fds[0]);
-		close(stream->wake_fds[1]);
-	}
 	free(stream->config);
 	free(stream);
 
@@ -886,38 +1607,52 @@
 }
 
 /* Attach to the shm region containing the server state. */
-static int client_attach_shm(struct cras_client *client, key_t shm_key)
+static int client_attach_shm(struct cras_client *client, int shm_fd)
 {
-	int shmid;
+	int lock_rc;
+	int rc;
 
-	/* Should only happen once per client lifetime. */
-	if (client->server_state)
-		return -EBUSY;
-
-	shmid = shmget(shm_key, sizeof(*(client->server_state)), 0400);
-	if (shmid < 0) {
-		syslog(LOG_ERR,
-		       "cras_client: shmget failed to get shm for client.");
-		return shmid;
+	lock_rc = server_state_wrlock(client);
+	if (client->server_state) {
+		rc = -EBUSY;
+		goto error;
 	}
-	client->server_state = (struct cras_server_state *)
-			shmat(shmid, NULL, SHM_RDONLY);
-	if (client->server_state == (void *)-1) {
-		client->server_state = NULL;
+
+	client->server_state = (struct cras_server_state *)mmap(
+			NULL, sizeof(*client->server_state),
+			PROT_READ, MAP_SHARED, shm_fd, 0);
+	rc = -errno;
+	close(shm_fd);
+	if (client->server_state == (struct cras_server_state *)-1) {
 		syslog(LOG_ERR,
-		       "cras_client: shmat failed to attach shm for client.");
-		return errno;
+		       "cras_client: mmap failed to map shm for client: %s",
+		       strerror(-rc));
+		goto error;
 	}
 
 	if (client->server_state->state_version != CRAS_SERVER_STATE_VERSION) {
-		shmdt(client->server_state);
+		munmap((void *)client->server_state,
+		       sizeof(*client->server_state));
 		client->server_state = NULL;
-		syslog(LOG_ERR,
-		       "cras_client: Unknown server_state version.");
-		return -EINVAL;
+		rc = -EINVAL;
+		syslog(LOG_ERR, "cras_client: Unknown server_state version.");
+	} else {
+		rc = 0;
 	}
 
-	return 0;
+error:
+	server_state_unlock(client, lock_rc);
+	return rc;
+}
+
+static void cras_client_get_hotword_models_ready(
+		struct cras_client *client,
+		const char *hotword_models)
+{
+	if (!client->get_hotword_models_cb)
+		return;
+	client->get_hotword_models_cb(client, hotword_models);
+	client->get_hotword_models_cb = NULL;
 }
 
 /* Handles messages from the cras server. */
@@ -927,19 +1662,22 @@
 	struct cras_client_message *msg;
 	int rc = 0;
 	int nread;
+	int server_fds[2];
+	unsigned int num_fds = 2;
 
 	msg = (struct cras_client_message *)buf;
-	nread = recv(client->server_fd, buf, sizeof(buf), 0);
-	if (nread < (int)sizeof(msg->length))
-		goto read_error;
-	if ((int)msg->length != nread)
-		goto read_error;
+	nread = cras_recv_with_fds(client->server_fd, buf, sizeof(buf),
+				   server_fds, &num_fds);
+	if (nread < (int)sizeof(msg->length) || (int)msg->length != nread)
+		return -EIO;
 
 	switch (msg->id) {
 	case CRAS_CLIENT_CONNECTED: {
 		struct cras_client_connected *cmsg =
 			(struct cras_client_connected *)msg;
-		rc = client_attach_shm(client, cmsg->shm_key);
+		if (num_fds != 1)
+			return -EINVAL;
+		rc = client_attach_shm(client, server_fds[0]);
 		if (rc)
 			return rc;
 		client->id = cmsg->client_id;
@@ -953,7 +1691,7 @@
 			stream_from_id(client, cmsg->stream_id);
 		if (stream == NULL)
 			break;
-		rc = stream_connected(stream, cmsg);
+		rc = stream_connected(stream, cmsg, server_fds, num_fds);
 		if (rc < 0)
 			stream->config->err_cb(stream->client,
 					       stream->id,
@@ -965,26 +1703,127 @@
 		if (client->debug_info_callback)
 			client->debug_info_callback(client);
 		break;
+	case CRAS_CLIENT_GET_HOTWORD_MODELS_READY: {
+		struct cras_client_get_hotword_models_ready *cmsg =
+			(struct cras_client_get_hotword_models_ready *)msg;
+		cras_client_get_hotword_models_ready(client,
+				(const char *)cmsg->hotword_models);
+		break;
+	}
+	case CRAS_CLIENT_OUTPUT_VOLUME_CHANGED: {
+		struct cras_client_volume_changed *cmsg =
+			(struct cras_client_volume_changed *)msg;
+		if (client->observer_ops.output_volume_changed)
+			client->observer_ops.output_volume_changed(
+					client->observer_context,
+					cmsg->volume);
+		break;
+	}
+	case CRAS_CLIENT_OUTPUT_MUTE_CHANGED: {
+		struct cras_client_mute_changed *cmsg =
+			(struct cras_client_mute_changed *)msg;
+		if (client->observer_ops.output_mute_changed)
+			client->observer_ops.output_mute_changed(
+					client->observer_context,
+					cmsg->muted,
+					cmsg->user_muted,
+					cmsg->mute_locked);
+		break;
+	}
+	case CRAS_CLIENT_CAPTURE_GAIN_CHANGED: {
+		struct cras_client_volume_changed *cmsg =
+			(struct cras_client_volume_changed *)msg;
+		if (client->observer_ops.capture_gain_changed)
+			client->observer_ops.capture_gain_changed(
+					client->observer_context,
+					cmsg->volume);
+		break;
+	}
+	case CRAS_CLIENT_CAPTURE_MUTE_CHANGED: {
+		struct cras_client_mute_changed *cmsg =
+			(struct cras_client_mute_changed *)msg;
+		if (client->observer_ops.capture_mute_changed)
+			client->observer_ops.capture_mute_changed(
+					client->observer_context,
+					cmsg->muted,
+					cmsg->mute_locked);
+		break;
+	}
+	case CRAS_CLIENT_NODES_CHANGED: {
+		if (client->observer_ops.nodes_changed)
+			client->observer_ops.nodes_changed(
+					client->observer_context);
+		break;
+	}
+	case CRAS_CLIENT_ACTIVE_NODE_CHANGED: {
+		struct cras_client_active_node_changed *cmsg =
+			(struct cras_client_active_node_changed *)msg;
+		enum CRAS_STREAM_DIRECTION direction =
+			(enum CRAS_STREAM_DIRECTION)cmsg->direction;
+		if (client->observer_ops.active_node_changed)
+			client->observer_ops.active_node_changed(
+					client->observer_context,
+					direction, cmsg->node_id);
+		break;
+	}
+	case CRAS_CLIENT_OUTPUT_NODE_VOLUME_CHANGED: {
+		struct cras_client_node_value_changed *cmsg =
+			(struct cras_client_node_value_changed *)msg;
+		if (client->observer_ops.output_node_volume_changed)
+			client->observer_ops.output_node_volume_changed(
+					client->observer_context,
+					cmsg->node_id,
+					cmsg->value);
+		break;
+	}
+	case CRAS_CLIENT_NODE_LEFT_RIGHT_SWAPPED_CHANGED: {
+		struct cras_client_node_value_changed *cmsg =
+			(struct cras_client_node_value_changed *)msg;
+		if (client->observer_ops.node_left_right_swapped_changed)
+			client->observer_ops.node_left_right_swapped_changed(
+					client->observer_context,
+					cmsg->node_id,
+					cmsg->value);
+		break;
+	}
+	case CRAS_CLIENT_INPUT_NODE_GAIN_CHANGED: {
+		struct cras_client_node_value_changed *cmsg =
+			(struct cras_client_node_value_changed *)msg;
+		if (client->observer_ops.input_node_gain_changed)
+			client->observer_ops.input_node_gain_changed(
+					client->observer_context,
+					cmsg->node_id,
+					cmsg->value);
+		break;
+	}
+	case CRAS_CLIENT_NUM_ACTIVE_STREAMS_CHANGED: {
+		struct cras_client_num_active_streams_changed *cmsg =
+		    (struct cras_client_num_active_streams_changed *)msg;
+		enum CRAS_STREAM_DIRECTION direction =
+			(enum CRAS_STREAM_DIRECTION)cmsg->direction;
+		if (client->observer_ops.num_active_streams_changed)
+			client->observer_ops.num_active_streams_changed(
+					client->observer_context,
+					direction, cmsg->num_active_streams);
+		break;
+	}
 	default:
 		break;
 	}
 
 	return 0;
-read_error:
-	rc = connect_to_server_wait(client);
-	if (rc < 0) {
-		client->thread.running = 0;
-		return -EIO;
-	}
-	return 0;
 }
 
 /* Handles messages from streams to this client. */
-static int handle_stream_message(struct cras_client *client)
+static int handle_stream_message(struct cras_client *client,
+				 int poll_revents)
 {
 	struct stream_msg msg;
 	int rc;
 
+	if ((poll_revents & POLLIN) == 0)
+		return 0;
+
 	rc = read(client->stream_fds[0], &msg, sizeof(msg));
 	if (rc < 0)
 		syslog(LOG_ERR, "cras_client: Stream read failed %d\n", errno);
@@ -996,12 +1835,16 @@
 }
 
 /* Handles messages from users to this client. */
-static int handle_command_message(struct cras_client *client)
+static int handle_command_message(struct cras_client *client,
+				  int poll_revents)
 {
 	uint8_t buf[MAX_CMD_MSG_LEN];
 	struct command_msg *msg = (struct command_msg *)buf;
 	int rc, to_read;
 
+	if ((poll_revents & POLLIN) == 0)
+		return 0;
+
 	rc = read(client->command_fds[0], buf, sizeof(msg->len));
 	if (rc != sizeof(msg->len) || msg->len > MAX_CMD_MSG_LEN) {
 		rc = -EIO;
@@ -1014,13 +1857,6 @@
 		goto cmd_msg_complete;
 	}
 
-	if (!check_server_connected_wait(client))
-		if (connect_to_server_wait(client) < 0) {
-			syslog(LOG_ERR, "cras_client: Lost server connection.");
-			rc = -EIO;
-			goto cmd_msg_complete;
-		}
-
 	switch (msg->msg_id) {
 	case CLIENT_STOP: {
 		struct client_stream *s;
@@ -1030,7 +1866,7 @@
 			client_thread_rm_stream(client, s->id);
 
 		/* And stop this client */
-		client->thread.running = 0;
+		client->thread.state = CRAS_THREAD_STOP;
 		rc = 0;
 		break;
 	}
@@ -1055,7 +1891,10 @@
 		break;
 	}
 	case CLIENT_SERVER_CONNECT:
-		rc = connect_to_server_wait(client);
+		rc = connect_to_server_wait(client, false);
+		break;
+	case CLIENT_SERVER_CONNECT_ASYNC:
+		rc = server_connect(client);
 		break;
 	default:
 		assert(0);
@@ -1076,43 +1915,58 @@
 static void *client_thread(void *arg)
 {
 	struct cras_client *client = (struct cras_client *)arg;
+	struct pollfd pollfds[4];
+	int (*cbs[4])(struct cras_client *client, int poll_revents);
+	unsigned int num_pollfds, i;
+	int rc;
 
 	if (arg == NULL)
 		return (void *)-EINVAL;
 
-	while (client->thread.running) {
-		struct pollfd pollfds[3];
-		int (*cbs[3])(struct cras_client *client);
-		unsigned int num_pollfds, i;
-		int rc;
-
+	while (thread_is_running(&client->thread)) {
 		num_pollfds = 0;
-		if (client->server_fd >= 0) {
-			cbs[num_pollfds] = handle_message_from_server;
-			pollfds[num_pollfds].fd = client->server_fd;
+
+		rc = cras_file_wait_get_fd(client->sock_file_wait);
+		if (rc >= 0) {
+			cbs[num_pollfds] = sock_file_wait_dispatch;
+			pollfds[num_pollfds].fd = rc;
 			pollfds[num_pollfds].events = POLLIN;
+			pollfds[num_pollfds].revents = 0;
+			num_pollfds++;
+		}
+		else
+			syslog(LOG_ERR, "file wait fd: %d", rc);
+		if (client->server_fd >= 0) {
+			cbs[num_pollfds] = server_fd_dispatch;
+			server_fill_pollfd(client, &(pollfds[num_pollfds]));
 			num_pollfds++;
 		}
 		if (client->command_fds[0] >= 0) {
 			cbs[num_pollfds] = handle_command_message;
 			pollfds[num_pollfds].fd = client->command_fds[0];
 			pollfds[num_pollfds].events = POLLIN;
+			pollfds[num_pollfds].revents = 0;
 			num_pollfds++;
 		}
 		if (client->stream_fds[0] >= 0) {
 			cbs[num_pollfds] = handle_stream_message;
 			pollfds[num_pollfds].fd = client->stream_fds[0];
 			pollfds[num_pollfds].events = POLLIN;
+			pollfds[num_pollfds].revents = 0;
 			num_pollfds++;
 		}
 
 		rc = poll(pollfds, num_pollfds, -1);
-		if (rc < 0)
+		if (rc <= 0)
 			continue;
 
 		for (i = 0; i < num_pollfds; i++) {
-			if (pollfds[i].revents & POLLIN)
-				cbs[i](client);
+			/* Only do one at a time, since some messages may
+			 * result in change to other fds. */
+			if (pollfds[i].revents) {
+				cbs[i](client, pollfds[i].revents);
+				break;
+			}
 		}
 	}
 
@@ -1129,7 +1983,7 @@
 				struct command_msg *msg)
 {
 	int rc, cmd_res;
-	if (client == NULL || !client->thread.running)
+	if (client == NULL || !thread_is_running(&client->thread))
 		return -EINVAL;
 
 	rc = write(client->command_fds[1], msg, msg->len);
@@ -1176,23 +2030,25 @@
 static int write_message_to_server(struct cras_client *client,
 				   const struct cras_server_message *msg)
 {
-	if (write(client->server_fd, msg, msg->length) !=
-			(ssize_t)msg->length) {
-		int rc = 0;
+	ssize_t write_rc = -EPIPE;
 
-		/* Write to server failed, try to re-connect. */
-		if (client->thread.running)
-			rc = send_simple_cmd_msg(client, 0,
-						 CLIENT_SERVER_CONNECT);
-		else
-			rc = connect_to_server_wait(client);
-		if (rc < 0)
-			return rc;
-		if (write(client->server_fd, msg, msg->length) !=
-				(ssize_t)msg->length)
-			return -EINVAL;
+	if (client->server_fd_state == CRAS_SOCKET_STATE_CONNECTED ||
+	    client->server_fd_state == CRAS_SOCKET_STATE_FIRST_MESSAGE) {
+		write_rc = write(client->server_fd, msg, msg->length);
+		if (write_rc < 0)
+			write_rc = -errno;
 	}
-	return 0;
+
+	if (write_rc != (ssize_t)msg->length &&
+	    client->server_fd_state != CRAS_SOCKET_STATE_FIRST_MESSAGE)
+		return -EPIPE;
+
+	if (write_rc < 0)
+		return write_rc;
+	else if (write_rc != (ssize_t)msg->length)
+		return -EIO;
+	else
+		return 0;
 }
 
 /*
@@ -1201,14 +2057,77 @@
 
 int cras_client_create(struct cras_client **client)
 {
+	const char *sock_dir;
+	size_t sock_file_size;
 	int rc;
+	struct client_int *client_int;
+	pthread_condattr_t cond_attr;
 
-	*client = (struct cras_client *)calloc(1, sizeof(struct cras_client));
-	if (*client == NULL)
+	/* Ignore SIGPIPE while using this API. */
+	signal(SIGPIPE, SIG_IGN);
+
+	sock_dir = cras_config_get_system_socket_file_dir();
+	if (!sock_dir)
 		return -ENOMEM;
+
+	client_int = (struct client_int *)calloc(1, sizeof(*client_int));
+	if (!client_int)
+		return -ENOMEM;
+	*client = &client_int->client;
 	(*client)->server_fd = -1;
 	(*client)->id = -1;
 
+	rc = pthread_rwlock_init(&client_int->server_state_rwlock, NULL);
+	if (rc != 0) {
+		syslog(LOG_ERR, "cras_client: Could not init state rwlock.");
+		rc = -rc;
+		goto free_client;
+	}
+
+	rc = pthread_mutex_init(&(*client)->stream_start_lock, NULL);
+	if (rc != 0) {
+		syslog(LOG_ERR, "cras_client: Could not init start lock.");
+		rc = -rc;
+		goto free_rwlock;
+	}
+
+	pthread_condattr_init(&cond_attr);
+	pthread_condattr_setclock(&cond_attr, CLOCK_MONOTONIC);
+	rc = pthread_cond_init(&(*client)->stream_start_cond, &cond_attr);
+	pthread_condattr_destroy(&cond_attr);
+	if (rc != 0) {
+		syslog(LOG_ERR, "cras_client: Could not init start cond.");
+		rc = -rc;
+		goto free_lock;
+	}
+
+	(*client)->server_event_fd = eventfd(0, EFD_CLOEXEC|EFD_NONBLOCK);
+	if ((*client)->server_event_fd < 0) {
+		syslog(LOG_ERR, "cras_client: Could not setup server eventfd.");
+		rc = -errno;
+		goto free_cond;
+	}
+
+	sock_file_size = strlen(sock_dir) + strlen(CRAS_SOCKET_FILE) + 2;
+	(*client)->sock_file = (const char *)malloc(sock_file_size);
+	if (!(*client)->sock_file) {
+		rc = -ENOMEM;
+		goto free_error;
+	}
+	snprintf((char *)(*client)->sock_file, sock_file_size, "%s/%s", sock_dir,
+		 CRAS_SOCKET_FILE);
+
+	rc = cras_file_wait_create((*client)->sock_file,
+				   CRAS_FILE_WAIT_FLAG_NONE,
+				   sock_file_wait_callback, *client,
+				   &(*client)->sock_file_wait);
+	if (rc != 0 && rc != -ENOENT) {
+		syslog(LOG_ERR, "cras_client: Could not setup watch for '%s'.",
+		       (*client)->sock_file);
+		goto free_error;
+	}
+	(*client)->sock_file_exists = (rc == 0);
+
 	/* Pipes used by the main thread and the client thread to send commands
 	 * and replies. */
 	rc = pipe((*client)->command_fds);
@@ -1227,53 +2146,51 @@
 
 	return 0;
 free_error:
-	free(*client);
+	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_cond:
+	pthread_cond_destroy(&(*client)->stream_start_cond);
+free_lock:
+	pthread_mutex_destroy(&(*client)->stream_start_lock);
+free_rwlock:
+	pthread_rwlock_destroy(&client_int->server_state_rwlock);
+free_client:
 	*client = NULL;
+	free(client_int);
 	return rc;
 }
 
-static inline
-int shutdown_and_close_socket(int sockfd)
-{
-	int rc;
-	uint8_t buffer[CRAS_CLIENT_MAX_MSG_SIZE];
-	struct timeval tv;
-
-	tv.tv_sec = 0;
-	tv.tv_usec = SERVER_SHUTDOWN_TIMEOUT_US;
-	setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv));
-
-	rc = shutdown(sockfd, SHUT_WR);
-	if (rc < 0)
-		return rc;
-	/* Wait until the socket is closed by the peer. */
-	for (;;) {
-		rc = recv(sockfd, buffer, sizeof(buffer), 0);
-		if (rc <= 0)
-			break;
-	}
-	return close(sockfd);
-}
-
 void cras_client_destroy(struct cras_client *client)
 {
+	struct client_int *client_int;
 	if (client == NULL)
 		return;
+	client_int = to_client_int(client);
+	client->server_connection_cb = NULL;
+	client->server_err_cb = NULL;
 	cras_client_stop(client);
-	if (client->server_state)
-		shmdt(client->server_state);
-	if (client->server_fd >= 0)
-		shutdown_and_close_socket(client->server_fd);
+	server_disconnect(client);
 	close(client->command_fds[0]);
 	close(client->command_fds[1]);
 	close(client->stream_fds[0]);
 	close(client->stream_fds[1]);
-	free(client);
+	cras_file_wait_destroy(client->sock_file_wait);
+	pthread_rwlock_destroy(&client_int->server_state_rwlock);
+	free((void *)client->sock_file);
+	free(client_int);
 }
 
 int cras_client_connect(struct cras_client *client)
 {
-	return connect_to_server(client);
+	return connect_to_server(client, NULL, true);
+}
+
+int cras_client_connect_timeout(struct cras_client *client,
+				unsigned int timeout_ms)
+{
+	return connect_to_server_wait_retry(client, timeout_ms, true);
 }
 
 int cras_client_connected_wait(struct cras_client *client)
@@ -1281,6 +2198,11 @@
 	return send_simple_cmd_msg(client, 0, CLIENT_SERVER_CONNECT);
 }
 
+int cras_client_connect_async(struct cras_client *client)
+{
+	return send_simple_cmd_msg(client, 0, CLIENT_SERVER_CONNECT_ASYNC);
+}
+
 struct cras_stream_params *cras_client_stream_params_create(
 		enum CRAS_STREAM_DIRECTION direction,
 		size_t buffer_frames,
@@ -1530,84 +2452,155 @@
 	return write_message_to_server(client, &msg.header);
 }
 
-size_t cras_client_get_system_volume(struct cras_client *client)
+size_t cras_client_get_system_volume(const struct cras_client *client)
 {
-	if (!client || !client->server_state)
+	size_t volume;
+	int lock_rc;
+
+	lock_rc = server_state_rdlock(client);
+	if (lock_rc)
 		return 0;
-	return client->server_state->volume;
+
+	volume = client->server_state->volume;
+	server_state_unlock(client, lock_rc);
+	return volume;
 }
 
-long cras_client_get_system_capture_gain(struct cras_client *client)
+long cras_client_get_system_capture_gain(const struct cras_client *client)
 {
-	if (!client || !client->server_state)
+	long gain;
+	int lock_rc;
+
+	lock_rc = server_state_rdlock(client);
+	if (lock_rc)
 		return 0;
-	return client->server_state->capture_gain;
+
+	gain = client->server_state->capture_gain;
+	server_state_unlock(client, lock_rc);
+	return gain;
 }
 
-int cras_client_get_system_muted(struct cras_client *client)
+int cras_client_get_system_muted(const struct cras_client *client)
 {
-	if (!client || !client->server_state)
+	int muted;
+	int lock_rc;
+
+	lock_rc = server_state_rdlock(client);
+	if (lock_rc)
 		return 0;
-	return client->server_state->mute;
+
+	muted = client->server_state->mute;
+	server_state_unlock(client, lock_rc);
+	return muted;
 }
 
-int cras_client_get_user_muted(struct cras_client *client)
+int cras_client_get_user_muted(const struct cras_client *client)
 {
-	if (!client || !client->server_state)
+	int muted;
+	int lock_rc;
+
+	lock_rc = server_state_rdlock(client);
+	if (lock_rc)
 		return 0;
-	return client->server_state->user_mute;
+
+	muted = client->server_state->user_mute;
+	server_state_unlock(client, lock_rc);
+	return muted;
 }
 
-int cras_client_get_system_capture_muted(struct cras_client *client)
+int cras_client_get_system_capture_muted(const struct cras_client *client)
 {
-	if (!client || !client->server_state)
+	int muted;
+	int lock_rc;
+
+	lock_rc = server_state_rdlock(client);
+	if (lock_rc)
 		return 0;
-	return client->server_state->capture_mute;
+
+	muted = client->server_state->capture_mute;
+	server_state_unlock(client, lock_rc);
+	return muted;
 }
 
-long cras_client_get_system_min_volume(struct cras_client *client)
+long cras_client_get_system_min_volume(const struct cras_client *client)
 {
-	if (!client || !client->server_state)
+	long min_volume;
+	int lock_rc;
+
+	lock_rc = server_state_rdlock(client);
+	if (lock_rc)
 		return 0;
-	return client->server_state->min_volume_dBFS;
+
+	min_volume = client->server_state->min_volume_dBFS;
+	server_state_unlock(client, lock_rc);
+	return min_volume;
 }
 
-long cras_client_get_system_max_volume(struct cras_client *client)
+long cras_client_get_system_max_volume(const struct cras_client *client)
 {
-	if (!client || !client->server_state)
+	long max_volume;
+	int lock_rc;
+
+	lock_rc = server_state_rdlock(client);
+	if (lock_rc)
 		return 0;
-	return client->server_state->max_volume_dBFS;
+
+	max_volume = client->server_state->max_volume_dBFS;
+	server_state_unlock(client, lock_rc);
+	return max_volume;
 }
 
-long cras_client_get_system_min_capture_gain(struct cras_client *client)
+long cras_client_get_system_min_capture_gain(const struct cras_client *client)
 {
-	if (!client || !client->server_state)
+	long min_gain;
+	int lock_rc;
+
+	lock_rc = server_state_rdlock(client);
+	if (lock_rc)
 		return 0;
-	return client->server_state->min_capture_gain;
+
+	min_gain = client->server_state->min_capture_gain;
+	server_state_unlock(client, lock_rc);
+	return min_gain;
 }
 
-long cras_client_get_system_max_capture_gain(struct cras_client *client)
+long cras_client_get_system_max_capture_gain(const struct cras_client *client)
 {
-	if (!client || !client->server_state)
+	long max_gain;
+	int lock_rc;
+
+	lock_rc = server_state_rdlock(client);
+	if (lock_rc)
 		return 0;
-	return client->server_state->max_capture_gain;
+
+	max_gain = client->server_state->max_capture_gain;
+	server_state_unlock(client, lock_rc);
+	return max_gain;
 }
 
 const struct audio_debug_info *cras_client_get_audio_debug_info(
-		struct cras_client *client)
+		const struct cras_client *client)
 {
-	if (!client || !client->server_state)
-		return NULL;
+	const struct audio_debug_info *debug_info;
+	int lock_rc;
 
-	return &client->server_state->audio_debug_info;
+	lock_rc = server_state_rdlock(client);
+	if (lock_rc)
+		return 0;
+
+	debug_info = &client->server_state->audio_debug_info;
+	server_state_unlock(client, lock_rc);
+	return debug_info;
 }
 
-unsigned cras_client_get_num_active_streams(struct cras_client *client,
+unsigned cras_client_get_num_active_streams(const struct cras_client *client,
 					    struct timespec *ts)
 {
 	unsigned num_streams, version, i;
+	int lock_rc;
 
-	if (!client || !client->server_state)
+	lock_rc = server_state_rdlock(client);
+	if (lock_rc)
 		return 0;
 
 read_active_streams_again:
@@ -1625,30 +2618,40 @@
 	if (end_server_state_read(client->server_state, version))
 		goto read_active_streams_again;
 
+	server_state_unlock(client, lock_rc);
 	return num_streams;
 }
 
 int cras_client_run_thread(struct cras_client *client)
 {
-	if (client == NULL || client->thread.running)
+	int rc;
+
+	if (client == NULL)
 		return -EINVAL;
+	if (thread_is_running(&client->thread))
+		return 0;
 
 	assert(client->command_reply_fds[0] == -1 &&
 	       client->command_reply_fds[1] == -1);
 
-	client->thread.running = 1;
 	if (pipe(client->command_reply_fds) < 0)
 		return -EIO;
-	if (pthread_create(&client->thread.tid, NULL, client_thread, client))
-		return -ENOMEM;
+	client->thread.state = CRAS_THREAD_RUNNING;
+	rc = pthread_create(&client->thread.tid, NULL, client_thread, client);
+	if (rc) {
+		client->thread.state = CRAS_THREAD_STOP;
+		return -rc;
+	}
 
 	return 0;
 }
 
 int cras_client_stop(struct cras_client *client)
 {
-	if (client == NULL || !client->thread.running)
+	if (client == NULL)
 		return -EINVAL;
+	if (!thread_is_running(&client->thread))
+		return 0;
 
 	send_simple_cmd_msg(client, 0, CLIENT_STOP);
 	pthread_join(client->thread.tid, NULL);
@@ -1661,6 +2664,29 @@
 	return 0;
 }
 
+void cras_client_set_server_error_cb(struct cras_client *client,
+				     cras_server_error_cb_t err_cb,
+				     void *user_arg)
+{
+	client->server_err_cb = err_cb;
+	client->server_connection_user_arg = user_arg;
+}
+
+void cras_client_set_connection_status_cb(
+		struct cras_client *client,
+		cras_connection_status_cb_t connection_cb,
+		void *user_arg)
+{
+	client->server_connection_cb = connection_cb;
+	client->server_connection_user_arg = user_arg;
+}
+
+void cras_client_set_thread_priority_cb(struct cras_client *client,
+					cras_thread_priority_cb_t cb)
+{
+	client->thread_priority_cb = cb;
+}
+
 int cras_client_get_output_devices(const struct cras_client *client,
 				   struct cras_iodev_info *devs,
 				   struct cras_ionode_info *nodes,
@@ -1668,12 +2694,12 @@
 {
 	const struct cras_server_state *state;
 	unsigned avail_devs, avail_nodes, version;
+	int lock_rc;
 
-	if (!client)
+	lock_rc = server_state_rdlock(client);
+	if (lock_rc)
 		return -EINVAL;
 	state = client->server_state;
-	if (!state)
-		return -EINVAL;
 
 read_outputs_again:
 	version = begin_server_state_read(state);
@@ -1683,6 +2709,7 @@
 	memcpy(nodes, state->output_nodes, avail_nodes * sizeof(*nodes));
 	if (end_server_state_read(state, version))
 		goto read_outputs_again;
+	server_state_unlock(client, lock_rc);
 
 	*num_devs = avail_devs;
 	*num_nodes = avail_nodes;
@@ -1697,12 +2724,12 @@
 {
 	const struct cras_server_state *state;
 	unsigned avail_devs, avail_nodes, version;
+	int lock_rc;
 
+	lock_rc = server_state_rdlock(client);
 	if (!client)
 		return -EINVAL;
 	state = client->server_state;
-	if (!state)
-		return -EINVAL;
 
 read_inputs_again:
 	version = begin_server_state_read(state);
@@ -1712,6 +2739,7 @@
 	memcpy(nodes, state->input_nodes, avail_nodes * sizeof(*nodes));
 	if (end_server_state_read(state, version))
 		goto read_inputs_again;
+	server_state_unlock(client, lock_rc);
 
 	*num_devs = avail_devs;
 	*num_nodes = avail_nodes;
@@ -1725,12 +2753,12 @@
 {
 	const struct cras_server_state *state;
 	unsigned num, version;
+	int lock_rc;
 
-	if (!client)
+	lock_rc = server_state_rdlock(client);
+	if (lock_rc)
 		return -EINVAL;
 	state = client->server_state;
-	if (!state)
-		return 0;
 
 read_clients_again:
 	version = begin_server_state_read(state);
@@ -1738,6 +2766,7 @@
 	memcpy(clients, state->client_info, num * sizeof(*clients));
 	if (end_server_state_read(state, version))
 		goto read_clients_again;
+	server_state_unlock(client, lock_rc);
 
 	return num;
 }
@@ -2072,6 +3101,23 @@
 	return rc;
 }
 
+int cras_client_config_global_remix(struct cras_client *client,
+				    unsigned num_channels,
+				    float *coefficient)
+{
+	struct cras_config_global_remix *msg;
+	int rc;
+
+	msg = (struct cras_config_global_remix *)malloc(sizeof(*msg) +
+			num_channels * num_channels * sizeof(*coefficient));
+	cras_fill_config_global_remix_command(msg, num_channels,
+					      coefficient,
+					      num_channels * num_channels);
+	rc = write_message_to_server(client, &msg->header);
+	free(msg);
+	return rc;
+}
+
 /* Return the index of the device used for listening to hotwords. */
 int cras_client_get_first_dev_type_idx(const struct cras_client *client,
 				       enum CRAS_NODE_TYPE type,
@@ -2082,12 +3128,12 @@
 	unsigned int i;
 	const struct cras_ionode_info *node_list;
 	unsigned int num_nodes;
+	int lock_rc;
 
-	if (!client)
+	lock_rc = server_state_rdlock(client);
+	if (lock_rc)
 		return -EINVAL;
 	state = client->server_state;
-	if (!state)
-		return -EINVAL;
 
 read_nodes_again:
 	version = begin_server_state_read(state);
@@ -2099,11 +3145,15 @@
 		num_nodes = state->num_input_nodes;
 	}
 	for (i = 0; i < num_nodes; i++) {
-		if ((enum CRAS_NODE_TYPE)node_list[i].type_enum == type)
-			return node_list[i].iodev_idx;
+		if ((enum CRAS_NODE_TYPE)node_list[i].type_enum == type) {
+			int ret_idx = node_list[i].iodev_idx;
+			server_state_unlock(client, lock_rc);
+			return ret_idx;
+		}
 	}
 	if (end_server_state_read(state, version))
 		goto read_nodes_again;
+	server_state_unlock(client, lock_rc);
 
 	return -ENODEV;
 }
@@ -2115,3 +3165,238 @@
 	cras_fill_suspend_message(&msg, suspend);
 	return write_message_to_server(client, &msg);
 }
+
+int cras_client_get_hotword_models(struct cras_client *client,
+				     cras_node_id_t node_id,
+				     get_hotword_models_cb_t cb)
+{
+	struct cras_get_hotword_models msg;
+
+	if (!client)
+		return -EINVAL;
+	client->get_hotword_models_cb = cb;
+
+	cras_fill_get_hotword_models_message(&msg, node_id);
+	return write_message_to_server(client, &msg.header);
+}
+
+int cras_client_set_hotword_model(struct cras_client *client,
+				  cras_node_id_t node_id,
+				  const char *model_name)
+{
+	struct cras_set_hotword_model msg;
+
+	cras_fill_set_hotword_model_message(&msg, node_id, model_name);
+	return write_message_to_server(client, &msg.header);
+}
+
+void cras_client_set_state_change_callback_context(
+		struct cras_client *client, void *context)
+{
+	if (!client)
+		return;
+	client->observer_context = context;
+}
+
+static int cras_send_register_notification(struct cras_client *client,
+					   enum CRAS_CLIENT_MESSAGE_ID msg_id,
+					   int do_register)
+{
+	struct cras_register_notification msg;
+	int rc;
+
+	/* This library automatically re-registers notifications when
+	 * reconnecting, so we can ignore message send failure due to no
+	 * connection. */
+	cras_fill_register_notification_message(&msg, msg_id, do_register);
+	rc = write_message_to_server(client, &msg.header);
+	if (rc == -EPIPE)
+		rc = 0;
+	return rc;
+}
+
+int cras_client_set_output_volume_changed_callback(
+		struct cras_client *client,
+		cras_client_output_volume_changed_callback cb)
+{
+	if (!client)
+		return -EINVAL;
+	client->observer_ops.output_volume_changed = cb;
+	return cras_send_register_notification(
+			client, CRAS_CLIENT_OUTPUT_VOLUME_CHANGED, cb != NULL);
+}
+
+int cras_client_set_output_mute_changed_callback(
+		struct cras_client *client,
+		cras_client_output_mute_changed_callback cb)
+{
+	if (!client)
+		return -EINVAL;
+	client->observer_ops.output_mute_changed = cb;
+	return cras_send_register_notification(
+			client, CRAS_CLIENT_OUTPUT_MUTE_CHANGED, cb != NULL);
+}
+
+int cras_client_set_capture_gain_changed_callback(
+		struct cras_client *client,
+		cras_client_capture_gain_changed_callback cb)
+{
+	if (!client)
+		return -EINVAL;
+	client->observer_ops.capture_gain_changed = cb;
+	return cras_send_register_notification(
+			client, CRAS_CLIENT_CAPTURE_GAIN_CHANGED, cb != NULL);
+}
+
+int cras_client_set_capture_mute_changed_callback(
+		struct cras_client *client,
+		cras_client_capture_mute_changed_callback cb)
+{
+	if (!client)
+		return -EINVAL;
+	client->observer_ops.capture_mute_changed = cb;
+	return cras_send_register_notification(
+			client, CRAS_CLIENT_CAPTURE_MUTE_CHANGED, cb != NULL);
+}
+
+int cras_client_set_nodes_changed_callback(
+		struct cras_client *client,
+		cras_client_nodes_changed_callback cb)
+{
+	if (!client)
+		return -EINVAL;
+	client->observer_ops.nodes_changed = cb;
+	return cras_send_register_notification(
+			client, CRAS_CLIENT_NODES_CHANGED, cb != NULL);
+}
+
+int cras_client_set_active_node_changed_callback(
+		struct cras_client *client,
+		cras_client_active_node_changed_callback cb)
+{
+	if (!client)
+		return -EINVAL;
+	client->observer_ops.active_node_changed = cb;
+	return cras_send_register_notification(
+		   client, CRAS_CLIENT_ACTIVE_NODE_CHANGED, cb != NULL);
+}
+
+int cras_client_set_output_node_volume_changed_callback(
+		struct cras_client *client,
+		cras_client_output_node_volume_changed_callback cb)
+{
+	if (!client)
+		return -EINVAL;
+	client->observer_ops.output_node_volume_changed = cb;
+	return cras_send_register_notification(
+		    client, CRAS_CLIENT_OUTPUT_NODE_VOLUME_CHANGED, cb != NULL);
+}
+
+int cras_client_set_node_left_right_swapped_changed_callback(
+		struct cras_client *client,
+		cras_client_node_left_right_swapped_changed_callback cb)
+{
+	if (!client)
+		return -EINVAL;
+	client->observer_ops.node_left_right_swapped_changed = cb;
+	return cras_send_register_notification(
+	       client, CRAS_CLIENT_NODE_LEFT_RIGHT_SWAPPED_CHANGED, cb != NULL);
+}
+
+int cras_client_set_input_node_gain_changed_callback(
+		struct cras_client *client,
+		cras_client_input_node_gain_changed_callback cb)
+{
+	if (!client)
+		return -EINVAL;
+	client->observer_ops.input_node_gain_changed = cb;
+	return cras_send_register_notification(
+		       client, CRAS_CLIENT_INPUT_NODE_GAIN_CHANGED, cb != NULL);
+}
+
+int cras_client_set_num_active_streams_changed_callback(
+		struct cras_client *client,
+		cras_client_num_active_streams_changed_callback cb)
+{
+	if (!client)
+		return -EINVAL;
+	client->observer_ops.num_active_streams_changed = cb;
+	return cras_send_register_notification(
+	      client, CRAS_CLIENT_NUM_ACTIVE_STREAMS_CHANGED, cb != NULL);
+}
+
+static int reregister_notifications(struct cras_client *client)
+{
+	int rc;
+
+	if (client->observer_ops.output_volume_changed) {
+		rc = cras_client_set_output_volume_changed_callback(
+				client,
+				client->observer_ops.output_volume_changed);
+		if (rc != 0)
+			return rc;
+	}
+	if (client->observer_ops.output_mute_changed) {
+		rc = cras_client_set_output_mute_changed_callback(
+				client,
+				client->observer_ops.output_mute_changed);
+		if (rc != 0)
+			return rc;
+	}
+	if (client->observer_ops.capture_gain_changed) {
+		rc = cras_client_set_capture_gain_changed_callback(
+				client,
+				client->observer_ops.capture_gain_changed);
+		if (rc != 0)
+			return rc;
+	}
+	if (client->observer_ops.capture_mute_changed) {
+		rc = cras_client_set_capture_mute_changed_callback(
+				client,
+				client->observer_ops.capture_mute_changed);
+		if (rc != 0)
+			return rc;
+	}
+	if (client->observer_ops.nodes_changed) {
+		rc = cras_client_set_nodes_changed_callback(
+				client, client->observer_ops.nodes_changed);
+		if (rc != 0)
+			return rc;
+	}
+	if (client->observer_ops.active_node_changed) {
+		rc = cras_client_set_active_node_changed_callback(
+				client,
+				client->observer_ops.active_node_changed);
+		if (rc != 0)
+			return rc;
+	}
+	if (client->observer_ops.output_node_volume_changed) {
+		rc = cras_client_set_output_node_volume_changed_callback(
+			    client,
+			    client->observer_ops.output_node_volume_changed);
+		if (rc != 0)
+			return rc;
+	}
+	if (client->observer_ops.node_left_right_swapped_changed) {
+		rc = cras_client_set_node_left_right_swapped_changed_callback(
+			  client,
+			  client->observer_ops.node_left_right_swapped_changed);
+		if (rc != 0)
+			return rc;
+	}
+	if (client->observer_ops.input_node_gain_changed) {
+		rc = cras_client_set_input_node_gain_changed_callback(
+				client,
+				client->observer_ops.input_node_gain_changed);
+		if (rc != 0)
+			return rc;
+	}
+	if (client->observer_ops.num_active_streams_changed) {
+		rc = cras_client_set_num_active_streams_changed_callback(
+			       client,
+			       client->observer_ops.num_active_streams_changed);
+		if (rc != 0)
+			return rc;
+	}
+	return 0;
+}
diff --git a/cras/src/libcras/cras_client.h b/cras/src/libcras/cras_client.h
index 614dec4..d17b650 100644
--- a/cras/src/libcras/cras_client.h
+++ b/cras/src/libcras/cras_client.h
@@ -1,6 +1,46 @@
 /* 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.
+ *
+ * This API creates multiple threads, one for control, and a thread per audio
+ * stream. The control thread is used to receive messages and notifications
+ * from the audio server, and manage the per-stream threads. API calls below
+ * may send messages to the control thread, or directly to the server. It is
+ * required that the control thread is running in order to support audio
+ * streams and notifications from the server.
+ *
+ * The API has multiple initialization sequences, but some of those can block
+ * while waiting for a response from the server.
+ *
+ * The following is the non-blocking API initialization sequence:
+ *	cras_client_create()
+ *      cras_client_set_connection_status_cb()                       (optional)
+ *      cras_client_run_thread()
+ *      cras_client_connect_async()
+ *
+ * The connection callback is executed asynchronously from the control thread
+ * when the connection has been established. The connection callback should be
+ * used to turn on or off interactions with any API call that communicates with
+ * the audio server or starts/stops audio streams. The above is implemented by
+ * cras_helper_create_connect_async().
+ *
+ * The following alternative (deprecated) initialization sequence can ensure
+ * that the connection is established synchronously.
+ *
+ * Just connect to the server (no control thread):
+ *      cras_client_create()
+ *      cras_client_set_server_connection_cb()                       (optional)
+ *   one of:
+ *      cras_client_connect()                                  (blocks forever)
+ *   or
+ *      cras_client_connect_timeout()                      (blocks for timeout)
+ *
+ * For API calls below that require the control thread to be running:
+ *      cras_client_run_thread();
+ *      cras_client_connected_wait();                     (blocks up to 1 sec.)
+ *
+ * The above minus setting the connection callback is implemented within
+ * cras_helper_create_connect().
  */
 
 #ifndef CRAS_CLIENT_H_
@@ -10,6 +50,7 @@
 extern "C" {
 #endif
 
+#include <stdbool.h>
 #include <stdint.h>
 #include <sys/select.h>
 
@@ -29,7 +70,9 @@
  *    sample_time - Playback time for the first sample read/written.
  *    user_arg - Value passed to add_stream;
  * Return:
- *    0 on success, or a negative number if there is a stream-fatal error.
+ *    Returns the number of frames read or written on success, or a negative
+ *    number if there is a stream-fatal error. Returns EOF when the end of the
+ *    stream is reached.
  */
 typedef int (*cras_playback_cb_t)(struct cras_client *client,
 				  cras_stream_id_t stream_id,
@@ -49,7 +92,9 @@
  *    playback_time - Playback time for the first sample written.
  *    user_arg - Value passed to add_stream;
  * Return:
- *    0 on success, or a negative number if there is a stream-fatal error.
+ *    Returns the number of frames read or written on success, or a negative
+ *    number if there is a stream-fatal error. Returns EOF when the end of the
+ *    stream is reached.
  */
 typedef int (*cras_unified_cb_t)(struct cras_client *client,
 				 cras_stream_id_t stream_id,
@@ -60,12 +105,78 @@
 				 const struct timespec *playback_time,
 				 void *user_arg);
 
-/* Callback for handling errors. */
+/* Callback for handling stream errors.
+ * Args:
+ *    client - The client created with cras_client_create().
+ *    stream_id - The ID for this stream.
+ *    error - The error code,
+ *    user_arg - The argument defined in cras_client_*_params_create().
+ */
 typedef int (*cras_error_cb_t)(struct cras_client *client,
 			       cras_stream_id_t stream_id,
 			       int error,
 			       void *user_arg);
 
+/* Callback for handling server error. DEPRECATED
+ *
+ * Deprecated by cras_server_connection_status_cb_t: use that instead.
+ * This is equivalent to CRAS_CONN_STATUS_FAILED.
+ *
+ * This callback is executed rarely: only when the connection to the server has
+ * already been interrupted and could not be re-established due to resource
+ * allocation failure (memory or file-descriptors). The caller may attempt
+ * to reestablish communication once those resources are available with
+ * cras_client_connect_async(), or (blocking) cras_client_connect().
+ *
+ * Args:
+ *    client - The client created with cras_client_create().
+ *    user_arg - The argument defined in cras_client_set_server_errro_cb().
+ */
+typedef void (*cras_server_error_cb_t)(struct cras_client *client,
+				       void *user_arg);
+
+/* Server connection status. */
+typedef enum cras_connection_status {
+	CRAS_CONN_STATUS_FAILED,
+		/* Resource allocation problem. Free resources, and retry the
+		 * connection with cras_client_connect_async(), or (blocking)
+		 * cras_client_connect(). Do not call cras_client_connect(),
+		 * cras_client_connect_timeout(), or cras_client_destroy()
+		 * from the callback. */
+	CRAS_CONN_STATUS_DISCONNECTED,
+		/* The control thread is attempting to reconnect to the
+		 * server in the background. Any attempt to access the
+		 * server will fail or block (see
+		 * cras_client_set_server_message_blocking(). */
+	CRAS_CONN_STATUS_CONNECTED,
+		/* Connection is established. All state change callbacks
+		 * have been re-registered, but audio streams must be
+		 * restarted, and node state data must be updated. */
+} cras_connection_status_t;
+
+/* Callback for handling server connection status.
+ *
+ * See also cras_client_set_connection_status_cb(). Do not call
+ * cras_client_connect(), cras_client_connect_timeout(), or
+ * cras_client_destroy() from this callback.
+ *
+ * Args:
+ *    client - The client created with cras_client_create().
+ *    status - The status of the connection to the server.
+ *    user_arg - The argument defined in
+ *               cras_client_set_connection_status_cb().
+ */
+typedef void (*cras_connection_status_cb_t)(struct cras_client *client,
+					    cras_connection_status_t status,
+					    void *user_arg);
+
+/* Callback for setting thread priority. */
+typedef void (*cras_thread_priority_cb_t)(struct cras_client *client);
+
+/* Callback for handling get hotword models reply. */
+typedef void (*get_hotword_models_cb_t)(struct cras_client *client,
+					const char *hotword_models);
+
 /*
  * Client handling.
  */
@@ -86,6 +197,7 @@
 void cras_client_destroy(struct cras_client *client);
 
 /* Connects a client to the running server.
+ * Waits forever (until interrupted or connected).
  * Args:
  *    client - pointer returned from "cras_client_create".
  * Returns:
@@ -93,8 +205,46 @@
  */
 int cras_client_connect(struct cras_client *client);
 
-/* Waits for the server to indicate that the client is connected.  Useful to
- * ensure that any information about the server is up to date.
+/* Connects a client to the running server, retries until timeout.
+ * Args:
+ *    client - pointer returned from "cras_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).
+ */
+int cras_client_connect_timeout(struct cras_client *client,
+				unsigned int timeout_ms);
+
+/* Begins running the client control thread.
+ *
+ * Required for stream operations and other operations noted below.
+ *
+ * Args:
+ *    client - the client to start (from cras_client_create).
+ * Returns:
+ *    0 on success or if the thread is already running, -EINVAL if the client
+ *    pointer is NULL, or the negative result of pthread_create().
+ */
+int cras_client_run_thread(struct cras_client *client);
+
+/* Stops running a client.
+ * This function is executed automatically by cras_client_destroy().
+ * Args:
+ *    client - the client to stop (from cras_client_create).
+ * Returns:
+ *    0 on success or if the thread was already stopped, -EINVAL if the client
+ *    isn't valid.
+ */
+int cras_client_stop(struct cras_client *client);
+
+/* Wait up to 1 second for the client thread to complete the server connection.
+ *
+ * After cras_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
+ * cras_client_run_thread() has not yet been executed, or cras_client_stop()
+ * was executed and thread isn't running, then this function returns -EINVAL.
+ *
  * Args:
  *    client - pointer returned from "cras_client_create".
  * Returns:
@@ -102,24 +252,65 @@
  */
 int cras_client_connected_wait(struct cras_client *client);
 
-/* Begins running a client.
+/* Ask the client control thread to connect to the audio server.
+ *
+ * After cras_client_run_thread() is executed, this function can be used
+ * to ask the control thread to connect to the audio server asynchronously.
+ * The callback set with cras_client_set_connection_status_cb() will be
+ * executed when the connection is established.
+ *
  * Args:
- *    client - the client to start (from cras_client_create).
+ *    client - The client from cras_client_create().
  * Returns:
- *    0 on success, -EINVAL if the client pointer is NULL, or -ENOMEM if there
- *    isn't enough memory to start the thread.
+ *    0 on success, or a negative error code on failure (from errno.h).
+ *    -EINVAL if the client pointer is invalid or the control thread is
+ *    not running.
  */
-int cras_client_run_thread(struct cras_client *client);
+int cras_client_connect_async(struct cras_client *client);
 
-/* Stops running a client.
+/* Sets server error callback. DEPRECATED
+ *
+ * See cras_server_error_cb_t for more information about this callback.
+ *
  * Args:
- *    client - the client to stop (from cras_client_create).
- * Returns:
- *    0 on success, -EINVAL if the client isn't valid or isn't running.
+ *    client - The client from cras_client_create.
+ *    err_cb - The callback function to register.
+ *    user_arg - Pointer that will be passed to the callback.
  */
-int cras_client_stop(struct cras_client *client);
+void cras_client_set_server_error_cb(struct cras_client *client,
+				     cras_server_error_cb_t err_cb,
+				     void *user_arg);
+
+/* Sets server connection status callback.
+ *
+ * See cras_connection_status_t for a description of the connection states
+ * and appropriate user action.
+ *
+ * Args:
+ *    client - The client from cras_client_create.
+ *    connection_cb - The callback function to register.
+ *    user_arg - Pointer that will be passed to the callback.
+ */
+void cras_client_set_connection_status_cb(
+		struct cras_client *client,
+		cras_connection_status_cb_t connection_cb,
+		void *user_arg);
+
+/* Sets callback for setting thread priority.
+ * Args:
+ *    client - The client from cras_client_create.
+ *    cb - The thread priority callback.
+ */
+void cras_client_set_thread_priority_cb(struct cras_client *client,
+					cras_thread_priority_cb_t cb);
 
 /* Returns the current list of output devices.
+ *
+ * Requires that the connection to the server has been established.
+ *
+ * Data is copied and thus can become out of date. This call must be
+ * re-executed to get updates.
+ *
  * Args:
  *    client - The client from cras_client_create.
  *    devs - Array that will be filled with device info.
@@ -137,6 +328,12 @@
 				   size_t *num_devs, size_t *num_nodes);
 
 /* Returns the current list of input devices.
+ *
+ * Requires that the connection to the server has been established.
+ *
+ * Data is copied and thus can become out of date. This call must be
+ * re-executed to get updates.
+ *
  * Args:
  *    client - The client from cras_client_create.
  *    devs - Array that will be filled with device info.
@@ -154,6 +351,12 @@
 				  size_t *num_devs, size_t *num_nodes);
 
 /* Returns the current list of clients attached to the server.
+ *
+ * Requires that the connection to the server has been established.
+ *
+ * Data is copied and thus can become out of date. This call must be
+ * re-executed to get updates.
+ *
  * Args:
  *    client - This client (from cras_client_create).
  *    clients - Array that will be filled with a list of attached clients.
@@ -169,24 +372,36 @@
 
 /* Find a node info with the matching node id.
  *
+ * Requires that the connection to the server has been established.
+ *
+ * Data is copied and thus can become out of date. This call must be
+ * re-executed to get updates.
+ *
  * Args:
- *    dev_name - The prefix of the iodev name.
- *    node_name - The prefix of the ionode name.
- *    dev_info - The information about the iodev will be returned here.
+ *    client - This client (from cras_client_create).
+ *    input - Non-zero for input nodes, zero for output nodes.
+ *    node_id - The node id to look for.
  *    node_info - The information about the ionode will be returned here.
  * Returns:
- *    0 if successful, -1 if the node cannot be found.
+ *    0 if successful, negative on error; -ENOENT if the node cannot be found.
  */
 int cras_client_get_node_by_id(const struct cras_client *client,
 			       int input,
 			       const cras_node_id_t node_id,
 			       struct cras_ionode_info* node_info);
 
-/* Checks if the output device with the given name is currently plugged in.  For
- * internal devices this checks that jack state, for USB devices this will
- * always be true if they are present.  The name parameter can be the
- * complete name or any unique prefix of the name.  If the name is not unique
- * the first matching name will be checked.
+/* Checks if the output device with the given name is currently plugged in.
+ *
+ * For internal devices this checks that jack state, for USB devices this will
+ * always be true if they are present. The name parameter can be the complete
+ * name or any unique prefix of the name. If the name is not unique the first
+ * matching name will be checked.
+ *
+ * Requires that the connection to the server has been established.
+ *
+ * Data is copied and thus can become out of date. This call must be
+ * re-executed to get updates.
+ *
  * Args:
  *    client - The client from cras_client_create.
  *    name - Name of the device to check.
@@ -196,12 +411,15 @@
 int cras_client_output_dev_plugged(const struct cras_client *client,
 				   const char *name);
 
-/* Sets an attribute of an ionode on a device.
+/* Set the value of an attribute of an ionode.
+ *
  * Args:
  *    client - The client from cras_client_create.
  *    node_id - The id of the ionode.
  *    attr - the attribute we want to change.
  *    value - the value we want to set.
+ * Returns:
+ *    Returns 0 for success, negative on error (from errno.h).
  */
 int cras_client_set_node_attr(struct cras_client *client,
 			      cras_node_id_t node_id,
@@ -209,6 +427,7 @@
 			      int value);
 
 /* Select the preferred node for playback/capture.
+ *
  * Args:
  *    client - The client from cras_client_create.
  *    direction - The direction of the ionode.
@@ -220,6 +439,7 @@
 			    cras_node_id_t node_id);
 
 /* Adds an active node for playback/capture.
+ *
  * Args:
  *    client - The client from cras_client_create.
  *    direction - The direction of the ionode.
@@ -231,6 +451,7 @@
 				cras_node_id_t node_id);
 
 /* Removes an active node for playback/capture.
+ *
  * Args:
  *    client - The client from cras_client_create.
  *    direction - The direction of the ionode.
@@ -243,6 +464,7 @@
 
 
 /* Asks the server to reload dsp plugin configuration from the ini file.
+ *
  * Args:
  *    client - The client from cras_client_create.
  * Returns:
@@ -251,6 +473,7 @@
 int cras_client_reload_dsp(struct cras_client *client);
 
 /* Asks the server to dump current dsp information to syslog.
+ *
  * Args:
  *    client - The client from cras_client_create.
  * Returns:
@@ -259,6 +482,7 @@
 int cras_client_dump_dsp_info(struct cras_client *client);
 
 /* Asks the server to dump current audio thread information.
+ *
  * Args:
  *    client - The client from cras_client_create.
  *    cb - A function to call when the data is received.
@@ -328,6 +552,10 @@
 void cras_client_stream_params_destroy(struct cras_stream_params *params);
 
 /* Creates a new stream and return the stream id or < 0 on error.
+ *
+ * Requires execution of cras_client_run_thread(), and an active connection
+ * to the audio server.
+ *
  * Args:
  *    client - The client to add the stream to (from cras_client_create).
  *    stream_id_out - On success will be filled with the new stream id.
@@ -342,6 +570,10 @@
 			   struct cras_stream_params *config);
 
 /* Creates a pinned stream and return the stream id or < 0 on error.
+ *
+ * Requires execution of cras_client_run_thread(), and an active connection
+ * to the audio server.
+ *
  * Args:
  *    client - The client to add the stream to (from cras_client_create).
  *    dev_idx - Index of the device to attach the newly created stream.
@@ -358,6 +590,9 @@
 				  struct cras_stream_params *config);
 
 /* Removes a currently playing/capturing stream.
+ *
+ * Requires execution of cras_client_run_thread().
+ *
  * Args:
  *    client - Client to remove the stream (returned from cras_client_create).
  *    stream_id - ID returned from cras_client_add_stream to identify the stream
@@ -369,6 +604,9 @@
 			  cras_stream_id_t stream_id);
 
 /* Sets the volume scaling factor for the given stream.
+ *
+ * Requires execution of cras_client_run_thread().
+ *
  * Args:
  *    client - Client owning the stream.
  *    stream_id - ID returned from cras_client_add_stream.
@@ -382,8 +620,11 @@
  * System level functions.
  */
 
-/* Sets the volume of the system.  Volume here ranges from 0 to 100, and will be
- * translated to dB based on the output-specific volume curve.
+/* Sets the volume of the system.
+ *
+ * Volume here ranges from 0 to 100, and will be translated to dB based on the
+ * output-specific volume curve.
+ *
  * Args:
  *    client - The client from cras_client_create.
  *    volume - 0-100 the new volume index.
@@ -393,9 +634,11 @@
  */
 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.
+/* 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.
@@ -406,6 +649,7 @@
 int cras_client_set_system_capture_gain(struct cras_client *client, long gain);
 
 /* Sets the mute state of the system.
+ *
  * Args:
  *    client - The client from cras_client_create.
  *    mute - 0 is un-mute, 1 is muted.
@@ -415,8 +659,10 @@
  */
 int cras_client_set_system_mute(struct cras_client *client, int mute);
 
-/* Sets the user mute state of the system.  This is used for mutes caused by
- * user interaction.  Like the mute key.
+/* Sets the user mute state of the system.
+ *
+ * This is used for mutes caused by user interaction. Like the mute key.
+ *
  * Args:
  *    client - The client from cras_client_create.
  *    mute - 0 is un-mute, 1 is muted.
@@ -426,8 +672,10 @@
  */
 int cras_client_set_user_mute(struct cras_client *client, int mute);
 
-/* Sets the mute locked state of the system. Changing mute state is impossible
- * when this flag is set to locked.
+/* Sets the mute locked state of the system.
+ *
+ * Changing mute state is impossible when this flag is set to locked.
+ *
  * Args:
  *    client - The client from cras_client_create.
  *    locked - 0 is un-locked, 1 is locked.
@@ -437,8 +685,10 @@
  */
 int cras_client_set_system_mute_locked(struct cras_client *client, int locked);
 
-/* Sets the capture mute state of the system.  Recordings will be muted when
- * this is set.
+/* Sets the capture mute state of the system.
+ *
+ * Recordings will be muted when this is set.
+ *
  * Args:
  *    client - The client from cras_client_create.
  *    mute - 0 is un-mute, 1 is muted.
@@ -448,8 +698,10 @@
  */
 int cras_client_set_system_capture_mute(struct cras_client *client, int mute);
 
-/* Sets the capture mute locked state of the system. Changing mute state is
- * impossible when this flag is set to locked.
+/* Sets the capture mute locked state of the system.
+ *
+ * Changing mute state is impossible when this flag is set to locked.
+ *
  * Args:
  *    client - The client from cras_client_create.
  *    locked - 0 is un-locked, 1 is locked.
@@ -461,44 +713,59 @@
 					       int locked);
 
 /* Gets the current system volume.
+ *
+ * Requires that the connection to the server has been established.
+ *
  * Args:
  *    client - The client from cras_client_create.
  * Returns:
  *    The current system volume between 0 and 100.
  */
-size_t cras_client_get_system_volume(struct cras_client *client);
+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(struct cras_client *client);
+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.
+ *
  * Args:
  *    client - The client from cras_client_create.
  * Returns:
  *    0 if not muted, 1 if it is.
  */
-int cras_client_get_system_muted(struct cras_client *client);
+int cras_client_get_system_muted(const struct cras_client *client);
 
 /* Gets the current user mute state.
+ *
+ * Requires that the connection to the server has been established.
+ *
  * Args:
  *    client - The client from cras_client_create.
  * Returns:
  *    0 if not muted, 1 if it is.
  */
-int cras_client_get_user_muted(struct cras_client *client);
+int cras_client_get_user_muted(const struct cras_client *client);
 
-/* Gets the current system captue mute state.
+/* Gets the current system capture mute state.
+ *
+ * Requires that the connection to the server has been established.
+ *
  * Args:
  *    client - The client from cras_client_create.
  * Returns:
  *    0 if capture is not muted, 1 if it is.
  */
-int cras_client_get_system_capture_muted(struct cras_client *client);
+int cras_client_get_system_capture_muted(const struct cras_client *client);
 
 /* Gets the current minimum system volume.
  * Args:
@@ -507,7 +774,7 @@
  *    The minimum value for the current output device in dBFS * 100.  This is
  *    the level of attenuation at volume == 1.
  */
-long cras_client_get_system_min_volume(struct cras_client *client);
+long cras_client_get_system_min_volume(const struct cras_client *client);
 
 /* Gets the current maximum system volume.
  * Args:
@@ -516,25 +783,35 @@
  *    The maximum value for the current output device in dBFS * 100.  This is
  *    the level of attenuation at volume == 100.
  */
-long cras_client_get_system_max_volume(struct cras_client *client);
+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.
+ *
  * Args:
  *    client - The client from cras_client_create.
  * Returns:
  *    The minimum capture gain for the current input device in dBFS * 100.
  */
-long cras_client_get_system_min_capture_gain(struct cras_client *client);
+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(struct cras_client *client);
+long cras_client_get_system_max_capture_gain(const struct cras_client *client);
 
 /* Gets audio debug info.
+ *
+ * Requires that the connection to the server has been established.
+ * Access to the resulting pointer is not thread-safe.
+ *
  * Args:
  *    client - The client from cras_client_create.
  * Returns:
@@ -542,20 +819,24 @@
  *    calling cras_client_update_audio_debug_info.
  */
 const struct audio_debug_info *cras_client_get_audio_debug_info(
-		struct cras_client *client);
+		const struct cras_client *client);
 
-/* Gets the number of streams currently attached to the server.  This is the
- * total number of capture and playback streams.  If the ts argument is
- * not null, then it will be filled with the last time audio was played or
- * recorded.  ts will be set to the current time if streams are currently
+/* Gets the number of streams currently attached to the server.
+ *
+ * This is the total number of capture and playback streams. If the ts argument
+ * is not null, then it will be filled with the last time audio was played or
+ * recorded. ts will be set to the current time if streams are currently
  * active.
+ *
+ * Requires that the connection to the server has been established.
+ *
  * Args:
  *    client - The client from cras_client_create.
  *    ts - Filled with the timestamp of the last stream.
  * Returns:
  *    The number of active streams.
  */
-unsigned cras_client_get_num_active_streams(struct cras_client *client,
+unsigned cras_client_get_num_active_streams(const struct cras_client *client,
 					    struct timespec *ts);
 
 
@@ -598,6 +879,7 @@
 				     struct timespec *delay);
 
 /* Set the volume of the given output node. Only for output nodes.
+ *
  * Args:
  *    client - The client from cras_client_create.
  *    node_id - ID of the node.
@@ -608,6 +890,7 @@
 				uint8_t volume);
 
 /* Swap the left and right channel of the given node.
+ *
  * Args:
  *    client - The client from cras_client_create.
  *    node_id - ID of the node.
@@ -617,6 +900,7 @@
 					cras_node_id_t node_id, int enable);
 
 /* Set the capture gain of the given input node.  Only for input nodes.
+ *
  * Args:
  *    client - The client from cras_client_create.
  *    node_id - ID of the node.
@@ -627,6 +911,7 @@
 				      long gain);
 
 /* Add a test iodev to the iodev list.
+ *
  * Args:
  *    client - The client from cras_client_create.
  *    type - The type of test iodev, see cras_types.h
@@ -635,6 +920,7 @@
 			       enum TEST_IODEV_TYPE type);
 
 /* Send a test command to a test iodev.
+ *
  * Args:
  *    client - The client from cras_client_create.
  *    iodev_idx - The index of the test iodev.
@@ -649,8 +935,11 @@
 				   const uint8_t *data);
 
 /* Finds the first device that contains a node of the given type.
- * This is used for finding a special device list the internal speaker of the
- * AOKR device.
+ *
+ * This is used for finding a special hotword device.
+ *
+ * Requires that the connection to the server has been established.
+ *
  * Args:
  *    client - The client from cras_client_create.
  *    type - The type of device to find.
@@ -662,13 +951,220 @@
 				       enum CRAS_STREAM_DIRECTION direction);
 
 /* Sets the suspend state of audio playback and capture.
+ *
  * Set this before putting the system into suspend.
+ *
  * Args:
  *    client - The client from cras_client_create.
  *    suspend - Suspend the system if non-zero, otherwise resume.
  */
 int cras_client_set_suspend(struct cras_client *client, int suspend);
 
+/* Configures the global converter for output remixing.
+ *
+ * Args:
+ *    client - The client from cras_client_create.
+ *    num_channels - Number of output channels.
+ *    coefficient - Float array representing |num_channels| * |num_channels|
+ *        matrix. Channels of mixed PCM output will be remixed by
+ *        multiplying this matrix.
+ */
+int cras_client_config_global_remix(struct cras_client *client,
+				    unsigned num_channels,
+				    float *coefficient);
+
+/* Gets the set of supported hotword language models on a node. The supported
+ * models may differ on different nodes.
+ *
+ * Args:
+ *    client - The client from cras_client_create.
+ *    node_id - ID of a hotword input node (CRAS_NODE_TYPE_HOTWORD).
+ *    cb - The function to be called when hotword models are ready.
+ * Returns:
+ *    0 on success.
+ */
+int cras_client_get_hotword_models(struct cras_client *client,
+				     cras_node_id_t node_id,
+				     get_hotword_models_cb_t cb);
+
+/* Sets the hotword language model on a node. If there are existing streams on
+ * the hotword input node when this function is called, they need to be closed
+ * then re-opend for the model change to take effect.
+ * Args:
+ *    client - The client from cras_client_create.
+ *    node_id - ID of a hotword input node (CRAS_NODE_TYPE_HOTWORD).
+ *    model_name - Name of the model to use, e.g. "en_us".
+ * Returns:
+ *    0 on success.
+ *    -EINVAL if client or node_id is invalid.
+ *    -ENOENT if the specified model is not found.
+ */
+int cras_client_set_hotword_model(struct cras_client *client,
+				  cras_node_id_t node_id,
+				  const char *model_name);
+
+/* Set the context pointer for system state change callbacks.
+ * Args:
+ *    client - The client from cras_client_create.
+ *    context - The context pointer passed to all callbacks.
+ */
+void cras_client_set_state_change_callback_context(
+		struct cras_client *client, void *context);
+
+/* Output volume change callback.
+ *
+ * Args:
+ *    context - Context pointer set with
+ *              cras_client_set_state_change_callback_context().
+ *    volume - The system output volume, ranging from 0 to 100.
+ */
+typedef void (*cras_client_output_volume_changed_callback)(
+		void* context, int32_t volume);
+
+/* Output mute change callback.
+ *
+ * Args:
+ *    context - Context pointer set with
+ *              cras_client_set_state_change_callback_context().
+ *    muted - Non-zero when the audio is muted, zero otherwise.
+ *    user_muted - Non-zero when the audio has been muted by the
+ *                 user, zero otherwise.
+ *    mute_locked - Non-zero when the mute funcion is locked,
+ *                  zero otherwise.
+ */
+typedef void (*cras_client_output_mute_changed_callback)(
+		void* context, int muted, int user_muted, int mute_locked);
+
+/* Capture gain change callback.
+ *
+ * Args:
+ *    context - Context pointer set with
+ *              cras_client_set_state_change_callback_context().
+ *    gain - The system capture gain, in centi-decibels.
+ */
+typedef void (*cras_client_capture_gain_changed_callback)(
+		void* context, int32_t gain);
+
+/* Capture mute change callback.
+ *
+ * Args:
+ *    context - Context pointer set with
+ *              cras_client_set_state_change_callback_context().
+ *    muted - Non-zero when the audio is muted, zero otherwise.
+ *    mute_locked - Non-zero when the mute funcion is locked,
+ *                  zero otherwise.
+ */
+typedef void (*cras_client_capture_mute_changed_callback)(
+		void* context, int muted, int mute_locked);
+
+/* Nodes change callback.
+ *
+ * Args:
+ *    context - Context pointer set with
+ *              cras_client_set_state_change_callback_context().
+ */
+typedef void (*cras_client_nodes_changed_callback)(void* context);
+
+/* Active node change callback.
+ *
+ * Args:
+ *    context - Context pointer set with
+ *              cras_client_set_state_change_callback_context().
+ *    direction - Indicates the direction of the selected node.
+ *    node_id - The ID of the selected node. Special device ID values
+ *              defined by CRAS_SPECIAL_DEVICE will be used when no other
+ *              device or node is selected or between selections.
+ */
+typedef void (*cras_client_active_node_changed_callback)(
+    void* context, enum CRAS_STREAM_DIRECTION direction,
+    cras_node_id_t node_id);
+
+/* Output node volume change callback.
+ *
+ * Args:
+ *    context - Context pointer set with
+ *              cras_client_set_state_change_callback_context().
+ *    node_id - The ID of the output node.
+ *    volume - The volume for this node with range 0 to 100.
+ */
+typedef void (*cras_client_output_node_volume_changed_callback)(
+		void* context, cras_node_id_t node_id, int32_t volume);
+
+/* Node left right swapped change callback.
+ *
+ * Args:
+ *    context - Context pointer set with
+ *              cras_client_set_state_change_callback_context().
+ *    node_id - The ID of the node.
+ *    swapped - Non-zero if the node is left-right swapped, zero otherwise.
+ */
+typedef void (*cras_client_node_left_right_swapped_changed_callback)(
+		void* context, cras_node_id_t node_id, int swapped);
+
+/* Input node gain change callback.
+ * Args:
+ *    context - Context pointer set with
+ *              cras_client_set_state_change_callback_context().
+ *    node_id - The ID of the input node.
+ *    gain - The gain for this node in centi-decibels.
+ */
+typedef void (*cras_client_input_node_gain_changed_callback)(
+		void* context, cras_node_id_t node_id, int32_t gain);
+
+/* Number of active streams change callback.
+ *
+ * Args:
+ *    context - Context pointer set with
+ *              cras_client_set_state_change_callback_context().
+ *    direction - Indicates the direction of the stream's node.
+ *    num_active_streams - The number of active streams.
+ */
+typedef void (*cras_client_num_active_streams_changed_callback)(
+		void* context, enum CRAS_STREAM_DIRECTION direction,
+		uint32_t num_active_streams);
+
+/* Set system state information callbacks.
+ * NOTE: These callbacks are executed from the client control thread.
+ * Each state change callback is given the context pointer set with
+ * cras_client_set_state_change_callback_context(). The context pointer is
+ * NULL by default.
+ * Args:
+ *    client - The client from cras_client_create.
+ *    cb - The callback, or NULL to disable the call-back.
+ * Returns:
+ *    0 for success or negative errno error code on error.
+ */
+int cras_client_set_output_volume_changed_callback(
+		struct cras_client *client,
+		cras_client_output_volume_changed_callback cb);
+int cras_client_set_output_mute_changed_callback(
+		struct cras_client *client,
+		cras_client_output_mute_changed_callback cb);
+int cras_client_set_capture_gain_changed_callback(
+		struct cras_client *client,
+		cras_client_capture_gain_changed_callback cb);
+int cras_client_set_capture_mute_changed_callback(
+		struct cras_client *client,
+		cras_client_capture_mute_changed_callback cb);
+int cras_client_set_nodes_changed_callback(
+		struct cras_client *client,
+		cras_client_nodes_changed_callback cb);
+int cras_client_set_active_node_changed_callback(
+		struct cras_client *client,
+		cras_client_active_node_changed_callback cb);
+int cras_client_set_output_node_volume_changed_callback(
+		struct cras_client *client,
+		cras_client_output_node_volume_changed_callback cb);
+int cras_client_set_node_left_right_swapped_changed_callback(
+		struct cras_client *client,
+		cras_client_node_left_right_swapped_changed_callback cb);
+int cras_client_set_input_node_gain_changed_callback(
+		struct cras_client *client,
+		cras_client_input_node_gain_changed_callback cb);
+int cras_client_set_num_active_streams_changed_callback(
+		struct cras_client *client,
+		cras_client_num_active_streams_changed_callback cb);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/cras/src/libcras/cras_helpers.c b/cras/src/libcras/cras_helpers.c
index 9312a94..3db8f6a 100644
--- a/cras/src/libcras/cras_helpers.c
+++ b/cras/src/libcras/cras_helpers.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2015 The Chromium OS Authors. All rights reserved.
+/* Copyright 2015 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.
  */
@@ -52,6 +52,33 @@
 	return 0;
 }
 
+int cras_helper_create_connect_async(struct cras_client **client,
+				     cras_connection_status_cb_t connection_cb,
+				     void *user_arg)
+{
+	int rc;
+
+	rc = cras_client_create(client);
+	if (rc < 0)
+		return rc;
+
+	cras_client_set_connection_status_cb(*client, connection_cb, user_arg);
+
+	rc = cras_client_run_thread(*client);
+	if (rc < 0)
+		goto client_start_error;
+
+	rc = cras_client_connect_async(*client);
+	if (rc < 0)
+		goto client_start_error;
+
+	return 0;
+
+client_start_error:
+	cras_client_destroy(*client);
+	return rc;
+}
+
 int cras_helper_create_connect(struct cras_client **client)
 {
 	int rc;
diff --git a/cras/src/libcras/cras_helpers.h b/cras/src/libcras/cras_helpers.h
index e9343c0..ad54c5c 100644
--- a/cras/src/libcras/cras_helpers.h
+++ b/cras/src/libcras/cras_helpers.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2015 The Chromium OS Authors. All rights reserved.
+/* Copyright 2015 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.
  */
@@ -9,7 +9,27 @@
 extern "C" {
 #endif
 
+/* Creates and connects a client to the running server asynchronously.
+ *
+ * When the connection has been established the connection_cb is executed
+ * with the appropriate state. See cras_connection_status_cb_t for more
+ * information.
+ *
+ * Args:
+ *    client - Filled with a pointer to the new client.
+ *    connection_cb - The connection status callback function.
+ *    user_arg - Argument passed to the connection status callback.
+ * Returns:
+ *    0 on success, or a negative error code on failure (from errno.h).
+ */
+int cras_helper_create_connect_async(struct cras_client **client,
+				     cras_connection_status_cb_t connection_cb,
+				     void *user_arg);
+
 /* Creates and connects a client to the running server.
+ *
+ * Waits forever (or interrupt) for the server to be available.
+ *
  * Args:
  *    client - Filled with a pointer to the new client.
  * Returns:
diff --git a/cras/src/server/audio_thread.c b/cras/src/server/audio_thread.c
index 2422e48..8b34914 100644
--- a/cras/src/server/audio_thread.c
+++ b/cras/src/server/audio_thread.c
@@ -42,6 +42,9 @@
 	AUDIO_THREAD_STOP,
 	AUDIO_THREAD_DUMP_THREAD_INFO,
 	AUDIO_THREAD_DRAIN_STREAM,
+	AUDIO_THREAD_CONFIG_GLOBAL_REMIX,
+	AUDIO_THREAD_DEV_START_RAMP,
+	AUDIO_THREAD_REMOVE_CALLBACK,
 };
 
 struct audio_thread_msg {
@@ -49,11 +52,21 @@
 	enum AUDIO_THREAD_COMMAND id;
 };
 
+struct audio_thread_config_global_remix {
+	struct audio_thread_msg header;
+	struct cras_fmt_conv *fmt_conv;
+};
+
 struct audio_thread_open_device_msg {
 	struct audio_thread_msg header;
 	struct cras_iodev *dev;
 };
 
+struct audio_thread_rm_callback_msg {
+	struct audio_thread_msg header;
+	int fd;
+};
+
 struct audio_thread_add_rm_stream_msg {
 	struct audio_thread_msg header;
 	struct cras_rstream *stream;
@@ -66,8 +79,16 @@
 	struct audio_debug_info *info;
 };
 
+struct audio_thread_dev_start_ramp_msg {
+	struct audio_thread_msg header;
+	struct cras_iodev *dev;
+	enum CRAS_IODEV_RAMP_REQUEST request;
+};
+
 /* Audio thread logging. */
 struct audio_thread_event_log *atlog;
+/* Global fmt converter used to remix output channels. */
+static struct cras_fmt_conv *remix_converter = NULL;
 
 static struct iodev_callback_list *iodev_callbacks;
 static struct timespec longest_wake;
@@ -184,36 +205,10 @@
 	return 0;
 }
 
-/* Put 'frames' worth of zero samples into odev. */
-static int fill_odev_zeros(struct cras_iodev *odev, unsigned int frames)
-{
-	struct cras_audio_area *area = NULL;
-	unsigned int frame_bytes, frames_written;
-	int rc;
-	uint8_t *buf;
-
-	frame_bytes = cras_get_format_bytes(odev->ext_format);
-	while (frames > 0) {
-		frames_written = frames;
-		rc = cras_iodev_get_output_buffer(odev, &area, &frames_written);
-		if (rc < 0) {
-			syslog(LOG_ERR, "fill zeros fail: %d", rc);
-			return rc;
-		}
-		/* This assumes consecutive channel areas. */
-		buf = area->channels[0].buf;
-		memset(buf, 0, frames_written * frame_bytes);
-		cras_iodev_put_output_buffer(odev, buf, frames_written);
-		frames -= frames_written;
-	}
-
-	return 0;
-}
-
 /* Builds an initial buffer to avoid an underrun. Adds min_level of latency. */
 static void fill_odevs_zeros_min_level(struct cras_iodev *odev)
 {
-	fill_odev_zeros(odev, odev->min_buffer_level);
+	cras_iodev_fill_odev_zeros(odev, odev->min_buffer_level);
 }
 
 static void thread_rm_open_adev(struct audio_thread *thread,
@@ -238,6 +233,8 @@
 	struct open_dev *open_dev;
 	struct cras_iodev *dev;
 	struct dev_stream *out;
+	struct timespec init_cb_ts;
+	const struct timespec *stream_ts;
 	unsigned int i;
 	int rc = 0;
 
@@ -252,8 +249,19 @@
 		if (out)
 			continue;
 
+		/* If open device already has stream, get the first stream
+		 * and use its next callback time to align with. Otherwise
+		 * use the timestamp now as the initial callback time for
+		 * new stream.
+		 */
+		if (dev->streams &&
+		    (stream_ts = dev_stream_next_cb_ts(dev->streams)))
+			init_cb_ts = *stream_ts;
+		else
+			clock_gettime(CLOCK_MONOTONIC_RAW, &init_cb_ts);
+
 		out = dev_stream_create(stream, dev->info.idx,
-					dev->ext_format, dev);
+					dev->ext_format, dev, &init_cb_ts);
 		if (!out) {
 			rc = -EINVAL;
 			break;
@@ -392,6 +400,20 @@
 	return 0;
 }
 
+/* Handles messages from the main thread to start ramping on a device. */
+static int thread_dev_start_ramp(struct audio_thread *thread,
+				 struct cras_iodev *iodev,
+				 enum CRAS_IODEV_RAMP_REQUEST request)
+{
+	/* Do nothing if device wasn't already in the active dev list. */
+	struct open_dev *adev = find_adev(
+			thread->open_devs[iodev->direction], iodev);
+	if (!adev)
+		return -EINVAL;
+	return cras_iodev_start_ramp(iodev, request);
+}
+
+
 /* Return non-zero if the stream is attached to any device. */
 static int thread_find_stream(struct audio_thread *thread,
 			      struct cras_rstream *rstream)
@@ -572,8 +594,13 @@
 		const struct timespec *next_cb_ts;
 		struct timespec now;
 
-		if (cras_shm_callback_pending(shm) && fd >= 0)
+		clock_gettime(CLOCK_MONOTONIC_RAW, &now);
+
+		if (cras_shm_callback_pending(shm) && fd >= 0) {
 			flush_old_aud_messages(shm, fd);
+			cras_rstream_record_fetch_interval(dev_stream->stream,
+							   &now);
+		}
 
 		if (cras_shm_get_frames(shm) < 0)
 			cras_rstream_set_is_draining(rstream, 1);
@@ -587,7 +614,6 @@
 
 		/* Check if it's time to get more data from this stream.
 		 * Allowing for waking up half a little early. */
-		clock_gettime(CLOCK_MONOTONIC_RAW, &now);
 		add_timespecs(&now, &playback_wake_fuzz_ts);
 		if (!timespec_after(&now, next_cb_ts))
 			continue;
@@ -749,6 +775,9 @@
 	di->min_cb_level = adev->dev->min_cb_level;
 	di->max_cb_level = adev->dev->max_cb_level;
 	di->direction = adev->dev->direction;
+	di->num_underruns = cras_iodev_get_num_underruns(adev->dev);
+	di->num_severe_underruns = cras_iodev_get_num_severe_underruns(
+			adev->dev);
 	if (fmt) {
 		di->frame_rate = fmt->frame_rate;
 		di->num_channels = fmt->num_channels;
@@ -773,6 +802,7 @@
 	si->stream_id = stream->stream->stream_id;
 	si->dev_idx = dev_idx;
 	si->direction = stream->stream->direction;
+	si->stream_type = stream->stream->stream_type;
 	si->buffer_frames = stream->stream->buffer_frames;
 	si->cb_threshold = stream->stream->cb_threshold;
 	si->frame_rate = stream->stream->format.frame_rate;
@@ -781,6 +811,7 @@
 	       sizeof(si->channel_layout));
 	si->longest_fetch_sec = stream->stream->longest_fetch_interval.tv_sec;
 	si->longest_fetch_nsec = stream->stream->longest_fetch_interval.tv_nsec;
+	si->num_overruns = cras_shm_num_overruns(&stream->stream->shm);
 
 	longest_wake.tv_sec = 0;
 	longest_wake.tv_nsec = 0;
@@ -894,6 +925,33 @@
 		ret = thread_drain_stream(thread, rmsg->stream);
 		break;
 	}
+	case AUDIO_THREAD_REMOVE_CALLBACK: {
+		struct audio_thread_rm_callback_msg *rmsg;
+
+		rmsg = (struct audio_thread_rm_callback_msg *)msg;
+		audio_thread_rm_callback(rmsg->fd);
+		break;
+	}
+	case AUDIO_THREAD_CONFIG_GLOBAL_REMIX: {
+		struct audio_thread_config_global_remix *rmsg;
+		void *rsp;
+
+		/* Respond the pointer to the old remix converter, so it can be
+		 * freed later in main thread. */
+		rsp = (void *)remix_converter;
+
+		rmsg = (struct audio_thread_config_global_remix *)msg;
+		remix_converter = rmsg->fmt_conv;
+
+		return write(thread->to_main_fds[1], &rsp, sizeof(rsp));
+	}
+	case AUDIO_THREAD_DEV_START_RAMP: {
+		struct audio_thread_dev_start_ramp_msg *rmsg;
+
+		rmsg = (struct audio_thread_dev_start_ramp_msg*)msg;
+		ret = thread_dev_start_ramp(thread, rmsg->dev, rmsg->request);
+		break;
+	}
 	default:
 		ret = -EINVAL;
 		break;
@@ -945,10 +1003,9 @@
 	struct open_dev *adev;
 	struct timespec sleep_time;
 	double est_rate;
-	unsigned int hw_level;
 	int ret = 0;
-	int rc;
-	int frames_to_play_in_sleep;
+	unsigned int frames_to_play_in_sleep;
+	unsigned int hw_level = 0;
 
 	DL_FOREACH(thread->open_devs[CRAS_STREAM_OUTPUT], adev)
 		ret += get_next_stream_wake_from_list(
@@ -956,33 +1013,17 @@
 				min_ts);
 
 	DL_FOREACH(thread->open_devs[CRAS_STREAM_OUTPUT], adev) {
-		rc = cras_iodev_frames_queued(adev->dev);
-		hw_level = (rc < 0) ? 0 : rc;
+		if (!cras_iodev_odev_should_wake(adev->dev))
+			continue;
 
-		adev->wake_ts = *now;
+		frames_to_play_in_sleep = cras_iodev_frames_to_play_in_sleep(
+				adev->dev, &hw_level, &adev->wake_ts);
+		if (!timespec_is_nonzero(&adev->wake_ts))
+			adev->wake_ts = *now;
 
 		est_rate = adev->dev->ext_format->frame_rate *
 				cras_iodev_get_est_rate_ratio(adev->dev);
 
-
-	        if (adev->dev->streams)
-			/* Use the estimated dev rate to schedule that audio
-			 * thread will wake up when hw_level drops to 0.
-			 */
-			frames_to_play_in_sleep = hw_level;
-		else {
-			/* When device has no stream and we fill zeros,
-			 * use the estimated dev rate to schedule that audio
-			 * thread will wake up when hw_level drops to
-			 * min_cb_level.
-			 */
-			if (hw_level > adev->dev->min_cb_level)
-				frames_to_play_in_sleep = hw_level -
-					adev->dev->min_cb_level;
-			else
-				frames_to_play_in_sleep = 0;
-		}
-
 		ATLOG(atlog,
 	              AUDIO_THREAD_SET_DEV_WAKE,
 		      adev->dev->info.idx,
@@ -1017,7 +1058,7 @@
 	if (!adev->dev->active_node)
 		return 1;
 
-	if (adev->dev->active_node->type == CRAS_NODE_TYPE_AOKR &&
+	if (adev->dev->active_node->type == CRAS_NODE_TYPE_HOTWORD &&
 	    !adev->input_streaming)
 		return 1;
 
@@ -1047,38 +1088,6 @@
 	return ret;
 }
 
-/* When an odev is open but no streams are attached, play zeros.
- * Args:
- *    odev - the output device to be filled.
- */
-int fill_output_no_streams(struct open_dev *adev)
-{
-	unsigned int hw_level;
-	unsigned int fr_to_write;
-	int rc;
-	struct cras_iodev *odev = adev->dev;
-	unsigned int target_hw_level = odev->min_cb_level * 2;
-
-	rc = cras_iodev_frames_queued(odev);
-	if (rc < 0)
-		return rc;
-	hw_level = rc;
-
-	fr_to_write = cras_iodev_buffer_avail(odev, hw_level);
-
-	if (hw_level <= target_hw_level)
-		fill_odev_zeros(odev, MIN(target_hw_level - hw_level,
-					  fr_to_write));
-
-	ATLOG(atlog,
-				    AUDIO_THREAD_ODEV_NO_STREAMS,
-				    odev->info.idx,
-				    hw_level,
-				    odev->min_cb_level);
-
-	return 0;
-}
-
 static int output_stream_fetch(struct audio_thread *thread)
 {
 	struct open_dev *odev_list = thread->open_devs[CRAS_STREAM_OUTPUT];
@@ -1137,6 +1146,7 @@
 {
 	struct cras_iodev *odev = adev->dev;
 	unsigned int hw_level;
+	struct timespec hw_tstamp;
 	unsigned int frames, fr_to_req;
 	snd_pcm_sframes_t written;
 	snd_pcm_uframes_t total_written = 0;
@@ -1144,23 +1154,35 @@
 	uint8_t *dst = NULL;
 	struct cras_audio_area *area = NULL;
 
-	if (!odev->streams)
-		return fill_output_no_streams(adev);
+	/* Possibly fill zeros for no_stream state and possibly transit state.
+	 */
+	rc = cras_iodev_prepare_output_before_write_samples(odev);
+	if (rc < 0) {
+		syslog(LOG_ERR, "Failed to prepare output dev for write");
+		return rc;
+	}
 
-	rc = cras_iodev_frames_queued(odev);
+	if (cras_iodev_state(odev) != CRAS_IODEV_STATE_NORMAL_RUN)
+		return 0;
+
+	rc = cras_iodev_frames_queued(odev, &hw_tstamp);
 	if (rc < 0)
 		return rc;
 	hw_level = rc;
-	if (hw_level < odev->min_cb_level / 2)
-		adev->coarse_rate_adjust = 1;
-	else if (hw_level > odev->max_cb_level * 2)
-		adev->coarse_rate_adjust = -1;
-	else
-		adev->coarse_rate_adjust = 0;
 
-	if (cras_iodev_update_rate(odev, hw_level))
-		update_estimated_rate(thread, adev);
+	ATLOG(atlog, AUDIO_THREAD_FILL_AUDIO_TSTAMP, adev->dev->info.idx,
+	      hw_tstamp.tv_sec, hw_tstamp.tv_nsec);
+	if (timespec_is_nonzero(&hw_tstamp)) {
+		if (hw_level < odev->min_cb_level / 2)
+			adev->coarse_rate_adjust = 1;
+		else if (hw_level > odev->max_cb_level * 2)
+			adev->coarse_rate_adjust = -1;
+		else
+			adev->coarse_rate_adjust = 0;
 
+		if (cras_iodev_update_rate(odev, hw_level, &hw_tstamp))
+			update_estimated_rate(thread, adev);
+	}
 	ATLOG(atlog, AUDIO_THREAD_FILL_AUDIO,
 				    adev->dev->info.idx, hw_level, 0);
 
@@ -1195,17 +1217,13 @@
 		total_written += written;
 	}
 
-	/* If we haven't started the device and wrote samples, then start it. */
-	if (total_written || hw_level) {
-		if (!odev->dev_running(odev))
-			return -1;
-	} else if (odev->min_cb_level < odev->buffer_size) {
-		/* Empty hardware and nothing written, zero fill it. */
-		fill_odev_zeros(adev->dev, odev->min_cb_level);
-	}
+	/* Empty hardware and nothing written, zero fill it if it is running. */
+	if (!hw_level && !total_written &&
+	    odev->min_cb_level < odev->buffer_size)
+		cras_iodev_output_underrun(odev);
 
 	ATLOG(atlog, AUDIO_THREAD_FILL_AUDIO_DONE,
-				    total_written, 0, 0);
+			hw_level, total_written, odev->min_cb_level);
 	return 0;
 }
 
@@ -1231,8 +1249,15 @@
 
 		rc = write_output_samples(thread, adev);
 		if (rc < 0) {
-			/* Device error, close it. */
-			thread_rm_open_adev(thread, adev);
+			if (rc == -EPIPE) {
+				/* Handle severe underrun. */
+				ATLOG(atlog, AUDIO_THREAD_SEVERE_UNDERRUN,
+				      adev->dev->info.idx, 0, 0);
+				cras_iodev_reset_request(adev->dev);
+			} else {
+				/* Device error, close it. */
+				thread_rm_open_adev(thread, adev);
+			}
 		}
 	}
 
@@ -1261,6 +1286,7 @@
 	struct cras_audio_shm *shm;
 	struct dev_stream *stream;
 	int delay;
+	unsigned int avail;
 
 	/* TODO(dgreid) - Setting delay from last dev only. */
 	delay = input_delay_frames(adev);
@@ -1269,10 +1295,13 @@
 		rstream = stream->stream;
 
 		shm = cras_rstream_input_shm(rstream);
-		cras_shm_check_write_overrun(shm);
+		if (cras_shm_check_write_overrun(shm))
+			ATLOG(atlog, AUDIO_THREAD_READ_OVERRUN,
+			      adev->dev->info.idx, rstream->stream_id,
+			      shm->area->num_overruns);
 		dev_stream_set_delay(stream, delay);
-		write_limit = MIN(write_limit,
-				  dev_stream_capture_avail(stream));
+		avail = dev_stream_capture_avail(stream);
+		write_limit = MIN(write_limit, avail);
 	}
 
 	return write_limit;
@@ -1281,41 +1310,44 @@
 /* Read samples from an input device to the specified stream.
  * Args:
  *    adev - The device to capture samples from.
- *    dev_index - The index of the device being read from, only used to special
- *      case the first read.
  * Returns 0 on success.
  */
 static int capture_to_streams(struct audio_thread *thread,
-			      struct open_dev *adev,
-			      unsigned int dev_index)
+			      struct open_dev *adev)
 {
 	struct cras_iodev *idev = adev->dev;
-	snd_pcm_uframes_t remainder, hw_level;
+	snd_pcm_uframes_t remainder, hw_level, cap_limit;
+	struct timespec hw_tstamp;
 	int rc;
 
-	rc = cras_iodev_frames_queued(idev);
+	rc = cras_iodev_frames_queued(idev, &hw_tstamp);
 	if (rc < 0)
 		return rc;
 	hw_level = rc;
-	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 (hw_level)
-		adev->input_streaming = 1;
+	ATLOG(atlog, AUDIO_THREAD_READ_AUDIO_TSTAMP, idev->info.idx,
+	      hw_tstamp.tv_sec, hw_tstamp.tv_nsec);
+	if (timespec_is_nonzero(&hw_tstamp)) {
+		if (hw_level)
+			adev->input_streaming = 1;
 
-	if (cras_iodev_update_rate(idev, hw_level))
-		update_estimated_rate(thread, adev);
+		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(thread, adev);
+	}
 
-	remainder = MIN(hw_level, get_stream_limit_set_delay(adev, hw_level));
+	cap_limit = get_stream_limit_set_delay(adev, hw_level);
+	remainder = MIN(hw_level, cap_limit);
 
 	ATLOG(atlog, AUDIO_THREAD_READ_AUDIO,
 				    idev->info.idx, hw_level, remainder);
 
-	if (!idev->dev_running(idev))
+	if (cras_iodev_state(idev) != CRAS_IODEV_STATE_NORMAL_RUN)
 		return 0;
 
 	while (remainder > 0) {
@@ -1334,8 +1366,10 @@
 			unsigned int area_offset;
 
 			area_offset = cras_iodev_stream_offset(idev, stream);
-			this_read = dev_stream_capture(stream, area,
-						       area_offset, dev_index);
+			this_read = dev_stream_capture(
+				stream, area, area_offset,
+				cras_iodev_get_software_gain_scaler(idev));
+
 			cras_iodev_stream_written(idev, stream, this_read);
 		}
 		if (adev->dev->streams)
@@ -1362,52 +1396,82 @@
 {
 	struct open_dev *idev_list = thread->open_devs[CRAS_STREAM_INPUT];
 	struct open_dev *adev;
-	unsigned int dev_index = 0;
 
 	DL_FOREACH(idev_list, adev) {
 		if (!cras_iodev_is_open(adev->dev))
 			continue;
-		if (capture_to_streams(thread, adev, dev_index) < 0)
+		if (capture_to_streams(thread, adev) < 0)
 			thread_rm_open_adev(thread, adev);
-		dev_index++;
 	}
 
 	return 0;
 }
 
+/*
+ * Set wake_ts for this device to be the earliest wake up time for
+ * dev_streams.
+ */
+static int set_input_dev_wake_ts(struct open_dev *adev)
+{
+	int rc;
+	struct timespec level_tstamp, wake_time_out, min_ts, now;
+	unsigned int curr_level;
+	struct dev_stream *stream;
+
+	/* Limit the sleep time to 20 seconds. */
+	min_ts.tv_sec = 20;
+	min_ts.tv_nsec = 0;
+	clock_gettime(CLOCK_MONOTONIC_RAW, &now);
+	add_timespecs(&min_ts, &now);
+
+	curr_level = cras_iodev_frames_queued(adev->dev, &level_tstamp);
+	if (!timespec_is_nonzero(&level_tstamp))
+		clock_gettime(CLOCK_MONOTONIC_RAW, &level_tstamp);
+
+	/*
+	 * Loop through streams to find the earliest time audio thread
+	 * should wake up.
+	 */
+	DL_FOREACH(adev->dev->streams, stream) {
+		rc = dev_stream_wake_time(
+			stream,
+			curr_level,
+			&level_tstamp,
+			&wake_time_out);
+
+		if (rc < 0)
+			return rc;
+
+		if (timespec_after(&min_ts, &wake_time_out)) {
+			min_ts = wake_time_out;
+		}
+	}
+	adev->wake_ts = min_ts;
+	return 0;
+}
+
 static int send_captured_samples(struct audio_thread *thread)
 {
 	struct open_dev *idev_list = thread->open_devs[CRAS_STREAM_INPUT];
 	struct open_dev *adev;
-	struct timespec now;
+	int rc;
 
 	// TODO(dgreid) - once per rstream, not once per dev_stream.
 	DL_FOREACH(idev_list, adev) {
 		struct dev_stream *stream;
-		unsigned int min_needed = adev->dev->max_cb_level;
-		unsigned int curr_level;
 
 		if (!cras_iodev_is_open(adev->dev))
 			continue;
 
-		curr_level = cras_iodev_frames_queued(adev->dev);
-
+		/* Post samples to rstream if there are enough samples. */
 		DL_FOREACH(adev->dev->streams, stream) {
 			dev_stream_capture_update_rstream(stream);
-			min_needed = MIN(min_needed,
-					 dev_stream_capture_avail(stream));
 		}
 
-		if (min_needed > curr_level)
-			min_needed -= curr_level;
-		else
-			min_needed = 0;
-
-		clock_gettime(CLOCK_MONOTONIC_RAW, &now);
-		cras_frames_to_time(min_needed + 10,
-				    adev->dev->ext_format->frame_rate,
-				    &adev->wake_ts);
-		add_timespecs(&adev->wake_ts, &now);
+		/* Set wake_ts for this device. */
+		rc = set_input_dev_wake_ts(adev);
+		if (rc < 0)
+			return rc;
 	}
 
 	return 0;
@@ -1587,7 +1651,8 @@
 static int audio_thread_post_message(struct audio_thread *thread,
 				     struct audio_thread_msg *msg)
 {
-	int rc, err;
+	int err;
+	void *rsp;
 
 	err = write(thread->to_thread_fds[1], msg, msg->length);
 	if (err < 0) {
@@ -1595,13 +1660,68 @@
 		return err;
 	}
 	/* Synchronous action, wait for response. */
-	err = read(thread->to_main_fds[0], &rc, sizeof(rc));
+	err = read(thread->to_main_fds[0], &rsp, sizeof(rsp));
 	if (err < 0) {
 		syslog(LOG_ERR, "Failed to read reply from thread.");
 		return err;
 	}
 
-	return rc;
+	return (intptr_t)rsp;
+}
+
+static void init_open_device_msg(struct audio_thread_open_device_msg *msg,
+				 enum AUDIO_THREAD_COMMAND id,
+				 struct cras_iodev *dev)
+{
+	memset(msg, 0, sizeof(*msg));
+	msg->header.id = id;
+	msg->header.length = sizeof(*msg);
+	msg->dev = dev;
+}
+
+static void init_add_rm_stream_msg(struct audio_thread_add_rm_stream_msg *msg,
+				   enum AUDIO_THREAD_COMMAND id,
+				   struct cras_rstream *stream,
+				   struct cras_iodev **devs,
+				   unsigned int num_devs)
+{
+	memset(msg, 0, sizeof(*msg));
+	msg->header.id = id;
+	msg->header.length = sizeof(*msg);
+	msg->stream = stream;
+	msg->devs = devs;
+	msg->num_devs = num_devs;
+}
+
+static void init_dump_debug_info_msg(
+		struct audio_thread_dump_debug_info_msg *msg,
+		struct audio_debug_info *info)
+{
+	memset(msg, 0, sizeof(*msg));
+	msg->header.id = AUDIO_THREAD_DUMP_THREAD_INFO;
+	msg->header.length = sizeof(*msg);
+	msg->info = info;
+}
+
+static void init_config_global_remix_msg(
+		struct audio_thread_config_global_remix *msg)
+{
+	memset(msg, 0, sizeof(*msg));
+	msg->header.id = AUDIO_THREAD_CONFIG_GLOBAL_REMIX;
+	msg->header.length = sizeof(*msg);
+}
+
+static void init_device_start_ramp_msg(
+		struct audio_thread_dev_start_ramp_msg *msg,
+		enum AUDIO_THREAD_COMMAND id,
+		struct cras_iodev *dev,
+		enum CRAS_IODEV_RAMP_REQUEST request)
+{
+	memset(msg, 0, sizeof(*msg));
+	msg->header.id = id;
+	msg->header.length = sizeof(*msg);
+	msg->dev = dev;
+	msg->request = request;
 }
 
 /* Exported Interface */
@@ -1618,11 +1738,8 @@
 	if (!thread->started)
 		return -EINVAL;
 
-	msg.header.id = AUDIO_THREAD_ADD_STREAM;
-	msg.header.length = sizeof(struct audio_thread_add_rm_stream_msg);
-	msg.stream = stream;
-	msg.devs = devs;
-	msg.num_devs = num_devs;
+	init_add_rm_stream_msg(&msg, AUDIO_THREAD_ADD_STREAM, stream,
+			       devs, num_devs);
 	return audio_thread_post_message(thread, &msg.header);
 }
 
@@ -1634,10 +1751,8 @@
 
 	assert(thread && stream);
 
-	msg.header.id = AUDIO_THREAD_DISCONNECT_STREAM;
-	msg.header.length = sizeof(struct audio_thread_add_rm_stream_msg);
-	msg.stream = stream;
-	msg.devs = &dev;
+	init_add_rm_stream_msg(&msg, AUDIO_THREAD_DISCONNECT_STREAM, stream,
+			       &dev, 0);
 	return audio_thread_post_message(thread, &msg.header);
 }
 
@@ -1648,9 +1763,8 @@
 
 	assert(thread && stream);
 
-	msg.header.id = AUDIO_THREAD_DRAIN_STREAM;
-	msg.header.length = sizeof(struct audio_thread_add_rm_stream_msg);
-	msg.stream = stream;
+	init_add_rm_stream_msg(&msg, AUDIO_THREAD_DRAIN_STREAM, stream,
+			       NULL, 0);
 	return audio_thread_post_message(thread, &msg.header);
 }
 
@@ -1659,12 +1773,78 @@
 {
 	struct audio_thread_dump_debug_info_msg msg;
 
-	msg.header.id = AUDIO_THREAD_DUMP_THREAD_INFO;
-	msg.header.length = sizeof(msg);
-	msg.info = info;
+	init_dump_debug_info_msg(&msg, info);
 	return audio_thread_post_message(thread, &msg.header);
 }
 
+int audio_thread_rm_callback_sync(struct audio_thread *thread, int fd) {
+	struct audio_thread_rm_callback_msg msg;
+
+	memset(&msg, 0, sizeof(msg));
+	msg.header.id = AUDIO_THREAD_REMOVE_CALLBACK;
+	msg.header.length = sizeof(msg);
+	msg.fd = fd;
+
+	return audio_thread_post_message(thread, &msg.header);
+}
+
+int audio_thread_config_global_remix(struct audio_thread *thread,
+				     unsigned int num_channels,
+				     const float *coefficient)
+{
+	int err;
+	int identity_remix = 1;
+	unsigned int i, j;
+	struct audio_thread_config_global_remix msg;
+	void *rsp;
+
+	init_config_global_remix_msg(&msg);
+
+	/* Check if the coefficients represent an identity matrix for remix
+	 * conversion, which means no remix at all. If so then leave the
+	 * converter as NULL. */
+	for (i = 0; i < num_channels; i++) {
+		if (coefficient[i * num_channels + i] != 1.0f) {
+			identity_remix = 0;
+			break;
+		}
+		for (j = i + 1; j < num_channels; j++) {
+			if (coefficient[i * num_channels + j] != 0 ||
+			    coefficient[j * num_channels + i] != 0)
+				identity_remix = 0;
+				break;
+		}
+	}
+
+	if (!identity_remix) {
+		msg.fmt_conv = cras_channel_remix_conv_create(num_channels,
+							      coefficient);
+		if (NULL == msg.fmt_conv)
+			return -ENOMEM;
+	}
+
+	err = write(thread->to_thread_fds[1], &msg, msg.header.length);
+	if (err < 0) {
+		syslog(LOG_ERR, "Failed to post message to thread.");
+		return err;
+	}
+	/* Synchronous action, wait for response. */
+	err = read(thread->to_main_fds[0], &rsp, sizeof(rsp));
+	if (err < 0) {
+		syslog(LOG_ERR, "Failed to read reply from thread.");
+		return err;
+	}
+
+	if (rsp)
+		cras_fmt_conv_destroy((struct cras_fmt_conv *)rsp);
+	return 0;
+}
+
+struct cras_fmt_conv *audio_thread_get_global_remix_converter()
+{
+	return remix_converter;
+}
+
 struct audio_thread *audio_thread_create()
 {
 	int rc;
@@ -1704,12 +1884,11 @@
 	struct audio_thread_open_device_msg msg;
 
 	assert(thread && dev);
+
 	if (!thread->started)
 		return -EINVAL;
 
-	msg.header.id = AUDIO_THREAD_ADD_OPEN_DEV;
-	msg.header.length = sizeof(struct audio_thread_open_device_msg);
-	msg.dev = dev;
+	init_open_device_msg(&msg, AUDIO_THREAD_ADD_OPEN_DEV, dev);
 	return audio_thread_post_message(thread, &msg.header);
 }
 
@@ -1722,9 +1901,23 @@
 	if (!thread->started)
 		return -EINVAL;
 
-	msg.header.id = AUDIO_THREAD_RM_OPEN_DEV;
-	msg.header.length = sizeof(struct audio_thread_open_device_msg);
-	msg.dev = dev;
+	init_open_device_msg(&msg, AUDIO_THREAD_RM_OPEN_DEV, dev);
+	return audio_thread_post_message(thread, &msg.header);
+}
+
+int audio_thread_dev_start_ramp(struct audio_thread *thread,
+				struct cras_iodev *dev,
+				enum CRAS_IODEV_RAMP_REQUEST request)
+{
+	struct audio_thread_dev_start_ramp_msg msg;
+
+	assert(thread && dev);
+
+	if (!thread->started)
+		return -EINVAL;
+
+	init_device_start_ramp_msg(&msg, AUDIO_THREAD_DEV_START_RAMP,
+				   dev, request);
 	return audio_thread_post_message(thread, &msg.header);
 }
 
@@ -1765,5 +1958,8 @@
 		close(thread->to_main_fds[1]);
 	}
 
+	if (remix_converter)
+		cras_fmt_conv_destroy(remix_converter);
+
 	free(thread);
 }
diff --git a/cras/src/server/audio_thread.h b/cras/src/server/audio_thread.h
index 2d45daa..1131e0c 100644
--- a/cras/src/server/audio_thread.h
+++ b/cras/src/server/audio_thread.h
@@ -9,6 +9,7 @@
 #include <pthread.h>
 #include <stdint.h>
 
+#include "cras_iodev.h"
 #include "cras_types.h"
 
 struct buffer_share;
@@ -104,6 +105,14 @@
  */
 void audio_thread_rm_callback(int fd);
 
+/* Removes a thread_callback from main thread.
+ * Args:
+ *     thread - The thread to remove callback from.
+ *     fd - The file descriptor of the previous added callback.
+ */
+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);
 
@@ -163,4 +172,29 @@
 int audio_thread_dump_thread_info(struct audio_thread *thread,
 				  struct audio_debug_info *info);
 
+/* Configures the global converter for output remixing. Called by main
+ * thread. */
+int audio_thread_config_global_remix(struct audio_thread *thread,
+				     unsigned int num_channels,
+				     const float *coefficient);
+
+/* Gets the global remix converter. */
+struct cras_fmt_conv *audio_thread_get_global_remix_converter();
+
+
+/* Start ramping on a device.
+ *
+ * Ramping is started/updated in audio thread. This function lets the main
+ * thread request that the audio thread start ramping.
+ *
+ * Args:
+ *   thread - a pointer to the audio thread.
+ *   dev - the device to start ramping.
+ *   request - Check the docstrings of CRAS_IODEV_RAMP_REQUEST.
+ * Returns:
+ *    0 on success, negative if error.
+ */
+int audio_thread_dev_start_ramp(struct audio_thread *thread,
+				struct cras_iodev *dev,
+				enum CRAS_IODEV_RAMP_REQUEST request);
 #endif /* AUDIO_THREAD_H_ */
diff --git a/cras/src/server/config/cras_card_config.c b/cras/src/server/config/cras_card_config.c
index eac01ba..ba39ce9 100644
--- a/cras/src/server/config/cras_card_config.c
+++ b/cras/src/server/config/cras_card_config.c
@@ -49,6 +49,7 @@
 		ini_key[MAX_KEY_LEN] = 0;
 		dB_values[i] = iniparser_getint(card_config->ini, ini_key, 0);
 	}
+	syslog(LOG_INFO, "Explicit volume curve found for %s.", control_name);
 	return cras_volume_curve_create_explicit(dB_values);
 }
 
@@ -97,7 +98,7 @@
 	const char *curve_type;
 
 	if (card_config == NULL || control_name == NULL)
-		return cras_volume_curve_create_default();
+		return NULL;
 
 	snprintf(ini_key, MAX_KEY_LEN, "%s:volume_curve", control_name);
 	ini_key[MAX_KEY_LEN] = 0;
@@ -107,6 +108,6 @@
 		return create_simple_step_curve(card_config, control_name);
 	if (curve_type && strcmp(curve_type, "explicit") == 0)
 		return create_explicit_curve(card_config, control_name);
-	syslog(LOG_INFO, "No configure curve found for %s.", control_name);
-	return cras_volume_curve_create_default();
+	syslog(LOG_DEBUG, "No configure curve found for %s.", control_name);
+	return NULL;
 }
diff --git a/cras/src/server/config/cras_card_config.h b/cras/src/server/config/cras_card_config.h
index 000b8ab..6ec9ad1 100644
--- a/cras/src/server/config/cras_card_config.h
+++ b/cras/src/server/config/cras_card_config.h
@@ -29,8 +29,7 @@
  * Args:
  *    card_config - Card configuration returned by cras_card_config_create()
  * Returns:
- *    The specialized curve for the control if there is one, otherwise the
- *    default volume curve.
+ *    The specialized curve for the control if there is one, otherwise NULL.
  */
 struct cras_volume_curve *cras_card_config_get_volume_curve_for_control(
 		const struct cras_card_config *card_config,
diff --git a/cras/src/server/cras.c b/cras/src/server/cras.c
index 5deaa5a..29806a8 100644
--- a/cras/src/server/cras.c
+++ b/cras/src/server/cras.c
@@ -17,6 +17,8 @@
 	{"dsp_config", required_argument, 0, 'd'},
 	{"syslog_mask", required_argument, 0, 'l'},
 	{"device_config_dir", required_argument, 0, 'c'},
+	{"disable_profile", required_argument, 0, 'D'},
+	{"internal_ucm_suffix", required_argument, 0, 'u'},
 	{0, 0, 0, 0}
 };
 
@@ -35,6 +37,8 @@
 	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;
+	const char *internal_ucm_suffix = NULL;
+	unsigned int profile_disable_mask = 0;
 
 	set_signals();
 
@@ -61,6 +65,28 @@
 		case 'd':
 			dsp_config = optarg;
 			break;
+		/* --disable_profile option takes list of profile names separated by ',' */
+		case 'D':
+			while ((optarg != NULL) && (*optarg != 0)) {
+				if (strncmp(optarg, "hfp", 3) == 0) {
+					profile_disable_mask |= CRAS_SERVER_PROFILE_MASK_HFP;
+				}
+				if (strncmp(optarg, "hsp", 3) == 0) {
+					profile_disable_mask |= CRAS_SERVER_PROFILE_MASK_HSP;
+				}
+				if (strncmp(optarg, "a2dp", 4) == 0) {
+					profile_disable_mask |= CRAS_SERVER_PROFILE_MASK_A2DP;
+				}
+				optarg = strchr(optarg, ',');
+				if (optarg != NULL) {
+					optarg++;
+				}
+			}
+			break;
+		case 'u':
+			if (*optarg != 0)
+				internal_ucm_suffix = optarg;
+			break;
 		default:
 			break;
 		}
@@ -83,11 +109,11 @@
 	/* Initialize system. */
 	cras_server_init();
 	cras_system_state_init(device_config_dir);
+	if (internal_ucm_suffix)
+		cras_system_state_set_internal_ucm_suffix(internal_ucm_suffix);
 	cras_dsp_init(dsp_config);
 	cras_iodev_list_init();
 
 	/* Start the server. */
-	cras_server_run();
-
-	return 0;
+	return cras_server_run(profile_disable_mask);
 }
diff --git a/cras/src/server/cras_a2dp_endpoint.c b/cras/src/server/cras_a2dp_endpoint.c
index 6de184d..3afe8b9 100644
--- a/cras/src/server/cras_a2dp_endpoint.c
+++ b/cras/src/server/cras_a2dp_endpoint.c
@@ -13,23 +13,12 @@
 #include "cras_iodev.h"
 #include "cras_bt_constants.h"
 #include "cras_bt_endpoint.h"
-#include "cras_hfp_ag_profile.h"
-#include "cras_main_message.h"
 #include "cras_system_state.h"
 #include "cras_util.h"
 
 #define A2DP_SOURCE_ENDPOINT_PATH "/org/chromium/Cras/Bluetooth/A2DPSource"
 #define A2DP_SINK_ENDPOINT_PATH   "/org/chromium/Cras/Bluetooth/A2DPSink"
 
-enum A2DP_COMMAND {
-	A2DP_FORCE_SUSPEND,
-};
-
-struct a2dp_msg {
-	struct cras_main_message header;
-	enum A2DP_COMMAND cmd;
-	struct cras_iodev *dev;
-};
 
 /* Pointers for the only connected a2dp device. */
 static struct a2dp {
@@ -37,27 +26,6 @@
 	struct cras_bt_device *device;
 } connected_a2dp;
 
-/*
- * Force suspends a cras_iodev when unexpect error occurs.
- */
-static void cras_a2dp_force_suspend(struct cras_iodev *dev)
-{
-	int err;
-	struct a2dp_msg msg;
-
-	msg.header.type = CRAS_MAIN_A2DP;
-	msg.header.length = sizeof(msg);
-	msg.cmd = A2DP_FORCE_SUSPEND;
-	msg.dev = dev;
-
-	err = cras_main_message_send((struct cras_main_message *)&msg);
-	if (err < 0) {
-		syslog(LOG_ERR, "Failed to post message to main thread");
-		return;
-	}
-	return;
-}
-
 static int cras_a2dp_get_capabilities(struct cras_bt_endpoint *endpoint,
 				      void *capabilities, int *len)
 {
@@ -163,61 +131,20 @@
 	return 0;
 }
 
-static void cras_a2dp_start(struct cras_bt_endpoint *endpoint,
+static void cras_a2dp_set_configuration(struct cras_bt_endpoint *endpoint,
 			    struct cras_bt_transport *transport)
 {
-	syslog(LOG_INFO, "Creating iodev for A2DP device");
+	struct cras_bt_device *device;
 
-	if (connected_a2dp.iodev) {
-		syslog(LOG_WARNING,
-		       "Replacing existing endpoint configuration");
-		a2dp_iodev_destroy(connected_a2dp.iodev);
-	}
-
-	/* When A2DP-only device connected, suspend all HFP/HSP audio
-	 * gateways. */
-	if (!cras_bt_device_supports_profile(
-			cras_bt_transport_device(transport),
-			CRAS_BT_DEVICE_PROFILE_HFP_HANDSFREE |
-			CRAS_BT_DEVICE_PROFILE_HSP_HEADSET))
-		cras_hfp_ag_suspend();
-
-
-	connected_a2dp.iodev = a2dp_iodev_create(transport,
-				  cras_a2dp_force_suspend);
-	connected_a2dp.device = cras_bt_transport_device(transport);
-
-	if (!connected_a2dp.iodev)
-		syslog(LOG_WARNING, "Failed to create a2dp iodev");
+	device = cras_bt_transport_device(transport);
+	cras_bt_device_a2dp_configured(device);
 }
 
 static void cras_a2dp_suspend(struct cras_bt_endpoint *endpoint,
 			      struct cras_bt_transport *transport)
 {
-	cras_a2dp_suspend_connected_device();
-}
-
-/* Handles a2dp messages in main thread.
- */
-static void a2dp_handle_message(struct cras_main_message *msg, void *arg)
-{
-	struct a2dp_msg *a2dp_msg = (struct a2dp_msg *)msg;
-
-	switch (a2dp_msg->cmd) {
-	case A2DP_FORCE_SUSPEND:
-		/* If the iodev to force suspend no longer active,
-		 * ignore the message. */
-		if (connected_a2dp.iodev != a2dp_msg->dev)
-			break;
-		a2dp_iodev_destroy(connected_a2dp.iodev);
-		connected_a2dp.iodev = NULL;
-		connected_a2dp.device = NULL;
-		break;
-	default:
-		syslog(LOG_ERR, "Unhandled a2dp command");
-		break;
-	}
-	return;
+	struct cras_bt_device *device = cras_bt_transport_device(transport);
+	cras_a2dp_suspend_connected_device(device);
 }
 
 static void a2dp_transport_state_changed(struct cras_bt_endpoint *endpoint,
@@ -244,29 +171,52 @@
 
 	.get_capabilities = cras_a2dp_get_capabilities,
 	.select_configuration = cras_a2dp_select_configuration,
-	.start = cras_a2dp_start,
+	.set_configuration = cras_a2dp_set_configuration,
 	.suspend = cras_a2dp_suspend,
 	.transport_state_changed = a2dp_transport_state_changed
 };
 
 int cras_a2dp_endpoint_create(DBusConnection *conn)
 {
-	cras_main_message_add_handler(CRAS_MAIN_A2DP,
-				      a2dp_handle_message, NULL);
 	return cras_bt_endpoint_add(conn, &cras_a2dp_endpoint);
 }
 
+void cras_a2dp_start(struct cras_bt_device *device)
+{
+	struct cras_bt_transport *transport = cras_a2dp_endpoint.transport;
+
+	if (!transport || device != cras_bt_transport_device(transport)) {
+		syslog(LOG_ERR, "Device and active transport not match.");
+		return;
+	}
+
+	if (connected_a2dp.iodev) {
+		syslog(LOG_WARNING,
+		       "Replacing existing endpoint configuration");
+		a2dp_iodev_destroy(connected_a2dp.iodev);
+	}
+
+	connected_a2dp.iodev = a2dp_iodev_create(transport);
+	connected_a2dp.device = cras_bt_transport_device(transport);
+
+	if (!connected_a2dp.iodev)
+		syslog(LOG_WARNING, "Failed to create a2dp iodev");
+}
+
 struct cras_bt_device *cras_a2dp_connected_device()
 {
 	return connected_a2dp.device;
 }
 
-void cras_a2dp_suspend_connected_device()
+void cras_a2dp_suspend_connected_device(struct cras_bt_device *device)
 {
+	if (connected_a2dp.device != device)
+		return;
+
 	if (connected_a2dp.iodev) {
 		syslog(LOG_INFO, "Destroying iodev for A2DP device");
 		a2dp_iodev_destroy(connected_a2dp.iodev);
 		connected_a2dp.iodev = NULL;
 		connected_a2dp.device = NULL;
 	}
-}
\ No newline at end of file
+}
diff --git a/cras/src/server/cras_a2dp_endpoint.h b/cras/src/server/cras_a2dp_endpoint.h
index 397ff4b..1ebd00d 100644
--- a/cras/src/server/cras_a2dp_endpoint.h
+++ b/cras/src/server/cras_a2dp_endpoint.h
@@ -8,6 +8,8 @@
 
 #include <dbus/dbus.h>
 
+struct cras_iodev;
+
 int cras_a2dp_endpoint_create(DBusConnection *conn);
 
 /* Gets the connected a2dp device, NULL is returned when there's none. */
@@ -16,6 +18,9 @@
 /* Suspends the connected a2dp device, the purpose is to remove a2dp iodev
  * to release a2dp audio before sending dbus message to disconnect a2dp
  * device. */
-void cras_a2dp_suspend_connected_device();
+void cras_a2dp_suspend_connected_device(struct cras_bt_device *device);
+
+/* Starts A2DP output by creating the cras_iodev. */
+void cras_a2dp_start(struct cras_bt_device *device);
 
 #endif /* CRAS_A2DP_ENDPOINT_H_ */
diff --git a/cras/src/server/cras_a2dp_info.c b/cras/src/server/cras_a2dp_info.c
index 18a8945..80a498c 100644
--- a/cras/src/server/cras_a2dp_info.c
+++ b/cras/src/server/cras_a2dp_info.c
@@ -115,7 +115,7 @@
 
 static int avdtp_write(int stream_fd, struct a2dp_info *a2dp)
 {
-	int err;
+	int err, samples;
 	struct rtp_header *header;
 	struct rtp_payload *payload;
 
@@ -135,13 +135,16 @@
 	if (err < 0)
 		return -errno;
 
+	/* Returns the number of samples in frame. */
+	samples = a2dp->samples;
+
 	/* Reset some data */
 	a2dp->a2dp_buf_used = sizeof(*header) + sizeof(*payload);
 	a2dp->frame_count = 0;
 	a2dp->samples = 0;
 	a2dp->seq_num++;
 
-	return err;
+	return samples;
 }
 
 int a2dp_encode(struct a2dp_info *a2dp, const void *pcm_buf, int pcm_buf_size,
diff --git a/cras/src/server/cras_a2dp_info.h b/cras/src/server/cras_a2dp_info.h
index 9bb5ce3..eaf00d5 100644
--- a/cras/src/server/cras_a2dp_info.h
+++ b/cras/src/server/cras_a2dp_info.h
@@ -78,7 +78,7 @@
 		int format_bytes, size_t link_mtu);
 
 /*
- * Writes samples using a2dp, returns numbewr of bytes written.
+ * Writes samples using a2dp, returns number of frames written.
  * Args:
  *    a2dp: The a2dp info object.
  *    stream_fd: The file descriptor to send stream to.
diff --git a/cras/src/server/cras_a2dp_iodev.c b/cras/src/server/cras_a2dp_iodev.c
index a4450ff..a9551a1 100644
--- a/cras/src/server/cras_a2dp_iodev.c
+++ b/cras/src/server/cras_a2dp_iodev.c
@@ -14,6 +14,8 @@
 #include "audio_thread.h"
 #include "audio_thread_log.h"
 #include "byte_buffer.h"
+#include "cras_iodev_list.h"
+#include "cras_a2dp_endpoint.h"
 #include "cras_a2dp_info.h"
 #include "cras_a2dp_iodev.h"
 #include "cras_audio_area.h"
@@ -27,23 +29,28 @@
 #define PCM_BUF_MAX_SIZE_FRAMES (4096*4)
 #define PCM_BUF_MAX_SIZE_BYTES (PCM_BUF_MAX_SIZE_FRAMES * 4)
 
+/* Child of cras_iodev to handle bluetooth A2DP streaming.
+ * Members:
+ *    base - The cras_iodev structure "base class"
+ *    a2dp - The codec and encoded state of a2dp_io.
+ *    transport - The transport object for bluez media API.
+ *    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.
+ *    pre_fill_complete - Flag to note if socket pre-fill is completed.
+ *    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.
+ */
 struct a2dp_io {
 	struct cras_iodev base;
 	struct a2dp_info a2dp;
 	struct cras_bt_transport *transport;
-	a2dp_force_suspend_cb force_suspend_cb;
 	unsigned sock_depth_frames;
-
-	/* To hold the pcm samples. */
 	struct byte_buffer *pcm_buf;
-
-	/* Has the socket been filled once. */
+	int destroyed;
 	int pre_fill_complete;
-
-	/* Accumulated frames written to a2dp socket. Will need this info
-	 * together with the device open time stamp to get how many virtual
-	 * buffer is queued there.
-	 */
 	uint64_t bt_written_frames;
 	struct timespec dev_open_time;
 };
@@ -114,7 +121,8 @@
 }
 
 
-static int frames_queued(const struct cras_iodev *iodev)
+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);
@@ -122,7 +130,7 @@
 			a2dp_queued_frames(&a2dpio->a2dp) +
 			buf_queued_bytes(a2dpio->pcm_buf) /
 				cras_get_format_bytes(iodev->format);
-
+	clock_gettime(CLOCK_MONOTONIC_RAW, tstamp);
 	return MIN(iodev->buffer_size,
 		   MAX(estimate_queued_frames, local_queued_frames));
 }
@@ -139,6 +147,11 @@
 		return err;
 	}
 
+	/* Apply the node's volume after transport is acquired. Doing this
+	 * is necessary because the volume can not sync to hardware until
+	 * it is opened. */
+	iodev->set_volume(iodev);
+
 	/* Assert format is set before opening device. */
 	if (iodev->format == NULL)
 		return -EINVAL;
@@ -181,16 +194,25 @@
 {
 	int err;
 	struct a2dp_io *a2dpio = (struct a2dp_io *)iodev;
+	struct cras_bt_device *device;
 
 	if (!a2dpio->transport)
 		return 0;
 
-	audio_thread_rm_callback(cras_bt_transport_fd(a2dpio->transport));
+	/* Remove audio thread callback and sync before releasing
+	 * the transport. */
+	audio_thread_rm_callback_sync(
+			cras_iodev_list_get_audio_thread(),
+			cras_bt_transport_fd(a2dpio->transport));
 
-	err = cras_bt_transport_release(a2dpio->transport);
+	err = cras_bt_transport_release(a2dpio->transport,
+					!a2dpio->destroyed);
 	if (err < 0)
 		syslog(LOG_ERR, "transport_release failed");
 
+	device = cras_bt_transport_device(a2dpio->transport);
+	if (device)
+		cras_bt_device_cancel_suspend(device);
 	a2dp_drain(&a2dpio->a2dp);
 	byte_buffer_destroy(a2dpio->pcm_buf);
 	cras_iodev_free_format(iodev);
@@ -198,12 +220,6 @@
 	return 0;
 }
 
-static int is_open(const struct cras_iodev *iodev)
-{
-	struct a2dp_io *a2dpio = (struct a2dp_io *)iodev;
-	return cras_bt_transport_fd(a2dpio->transport) > 0;
-}
-
 static int pre_fill_socket(struct a2dp_io *a2dpio)
 {
 	static const uint16_t zero_buffer[1024 * 2];
@@ -245,15 +261,22 @@
  */
 static int flush_data(void *arg)
 {
-	const struct cras_iodev *iodev = (const struct cras_iodev *)arg;
+	struct cras_iodev *iodev = (struct cras_iodev *)arg;
 	int processed;
 	size_t format_bytes;
-	int err = 0;
 	int written = 0;
+	int queued_frames;
 	struct a2dp_io *a2dpio;
+	struct cras_bt_device *device;
 
 	a2dpio = (struct a2dp_io *)iodev;
 	format_bytes = cras_get_format_bytes(iodev->format);
+	device = cras_bt_transport_device(a2dpio->transport);
+
+	/* If bt device has been destroyed, this a2dp iodev will soon be
+	 * destroyed as well. */
+	if (device == NULL)
+		return -EINVAL;
 
 encode_more:
 	while (buf_queued_bytes(a2dpio->pcm_buf)) {
@@ -283,40 +306,46 @@
 				    written,
 				    a2dp_queued_frames(&a2dpio->a2dp), 0);
 	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);
 		return 0;
 	} else if (written < 0) {
-		if (a2dpio->force_suspend_cb)
-			a2dpio->force_suspend_cb(&a2dpio->base);
-		err = written;
-		goto write_done;
-	} else if (written == 0) {
-		goto write_done;
+		/* Suspend a2dp immediately when receives error other than
+		 * EAGAIN. */
+		cras_bt_device_cancel_suspend(device);
+		cras_bt_device_schedule_suspend(device, 0);
+		return written;
 	}
 
-	if (buf_queued_bytes(a2dpio->pcm_buf))
+	/* Data succcessfully written to a2dp socket, cancel any scheduled
+	 * suspend timer. */
+	cras_bt_device_cancel_suspend(device);
+
+	/* If it looks okay to write more and we do have queued data, try
+	 * encode more. But avoid the case when PCM buffer level is too close
+	 * to min_buffer_level so that another A2DP write could causes underrun.
+	 */
+	queued_frames = buf_queued_bytes(a2dpio->pcm_buf) / format_bytes;
+	if (written && (iodev->min_buffer_level + written < queued_frames))
 		goto encode_more;
 
-write_done:
 	/* everything written. */
 	audio_thread_enable_callback(
 			cras_bt_transport_fd(a2dpio->transport), 0);
 
-	return err;
-}
-
-static int dev_running(const struct cras_iodev *iodev)
-{
-	return is_open(iodev);
+	return 0;
 }
 
 static int delay_frames(const struct cras_iodev *iodev)
 {
 	const struct a2dp_io *a2dpio = (struct a2dp_io *)iodev;
+	struct timespec tstamp;
 
 	/* The number of frames in the pcm buffer plus two mtu packets */
-	return frames_queued(iodev) + a2dpio->sock_depth_frames;
+	return frames_queued(iodev, &tstamp) + a2dpio->sock_depth_frames;
 }
 
 static int get_buffer(struct cras_iodev *iodev,
@@ -376,7 +405,24 @@
 	return 0;
 }
 
-static void update_active_node(struct cras_iodev *iodev, unsigned node_idx)
+static void set_volume(struct cras_iodev *iodev)
+{
+	size_t volume;
+	struct a2dp_io *a2dpio = (struct a2dp_io *)iodev;
+	struct cras_bt_device *device =
+			cras_bt_transport_device(a2dpio->transport);
+
+	if (!cras_bt_device_get_use_hardware_volume(device))
+		return;
+
+	volume = iodev->active_node->volume * 127 / 100;
+
+	if (a2dpio->transport)
+		cras_bt_transport_set_volume(a2dpio->transport, volume);
+}
+
+static void update_active_node(struct cras_iodev *iodev, unsigned node_idx,
+			       unsigned dev_enabled)
 {
 }
 
@@ -394,8 +440,7 @@
 	destroy_a2dp(&a2dpio->a2dp);
 }
 
-struct cras_iodev *a2dp_iodev_create(struct cras_bt_transport *transport,
-				     a2dp_force_suspend_cb force_suspend_cb)
+struct cras_iodev *a2dp_iodev_create(struct cras_bt_transport *transport)
 {
 	int err;
 	struct a2dp_io *a2dpio;
@@ -417,7 +462,6 @@
 		syslog(LOG_ERR, "Fail to init a2dp");
 		goto error;
 	}
-	a2dpio->force_suspend_cb = force_suspend_cb;
 
 	iodev = &a2dpio->base;
 
@@ -438,11 +482,10 @@
 			cras_bt_device_object_path(device),
 			strlen(cras_bt_device_object_path(device)),
 			strlen(cras_bt_device_object_path(device)));
+	iodev->info.stable_id_new = iodev->info.stable_id;
 
 	iodev->open_dev = open_dev;
-	iodev->is_open = is_open; /* Needed by thread_add_stream */
 	iodev->frames_queued = frames_queued;
-	iodev->dev_running = dev_running;
 	iodev->delay_frames = delay_frames;
 	iodev->get_buffer = get_buffer;
 	iodev->put_buffer = put_buffer;
@@ -450,7 +493,7 @@
 	iodev->close_dev = close_dev;
 	iodev->update_supported_formats = update_supported_formats;
 	iodev->update_active_node = update_active_node;
-	iodev->software_volume_needed = 1;
+	iodev->set_volume = set_volume;
 
 	/* Create a dummy ionode */
 	node = (struct cras_ionode *)calloc(1, sizeof(*node));
@@ -481,6 +524,7 @@
 	struct a2dp_io *a2dpio = (struct a2dp_io *)iodev;
 	struct cras_bt_device *device;
 
+	a2dpio->destroyed = 1;
 	device = cras_bt_transport_device(a2dpio->transport);
 
 	/* A2DP does output only */
diff --git a/cras/src/server/cras_a2dp_iodev.h b/cras/src/server/cras_a2dp_iodev.h
index 0055c9c..e33af6c 100644
--- a/cras/src/server/cras_a2dp_iodev.h
+++ b/cras/src/server/cras_a2dp_iodev.h
@@ -10,20 +10,13 @@
 
 struct cras_iodev;
 
-/* Callback to force suspend a a2dp iodev. */
-typedef void (*a2dp_force_suspend_cb)(struct cras_iodev *iodev);
-
 /*
  * Creates an a2dp iodev from transport object.
  * Args:
  *    transport - The transport to create a2dp iodev for
- *    force_suspend_cb - The callback to trigger when severe error occurs
- *        during transmitting audio, used to force suspend an a2dp iodev
- *        outside the life cycle controlled by bluetooth daemon.
  */
 struct cras_iodev *a2dp_iodev_create(
-		struct cras_bt_transport *transport,
-		a2dp_force_suspend_cb force_suspend_cb);
+		struct cras_bt_transport *transport);
 
 /*
  * Destroys a2dp iodev.
diff --git a/cras/src/server/cras_alert.c b/cras/src/server/cras_alert.c
index dbea44d..6c97592 100644
--- a/cras/src/server/cras_alert.c
+++ b/cras/src/server/cras_alert.c
@@ -4,7 +4,9 @@
  */
 
 #include <errno.h>
+#include <stddef.h>
 #include <stdlib.h>
+#include <string.h>
 
 #include "cras_alert.h"
 #include "utlist.h"
@@ -16,10 +18,19 @@
 	struct cras_alert_cb_list *prev, *next;
 };
 
+/* A list of data args to callbacks. Variable-length structure. */
+struct cras_alert_data {
+	struct cras_alert_data *prev, *next;
+	/* This field must be the last in this structure. */
+	char buf[];
+};
+
 struct cras_alert {
 	int pending;
+	unsigned int flags;
 	cras_alert_prepare prepare;
 	struct cras_alert_cb_list *callbacks;
+	struct cras_alert_data *data;
 	struct cras_alert *prev, *next;
 };
 
@@ -28,13 +39,15 @@
 /* If there is any alert pending. */
 static int has_alert_pending;
 
-struct cras_alert *cras_alert_create(cras_alert_prepare prepare)
+struct cras_alert *cras_alert_create(cras_alert_prepare prepare,
+				     unsigned int flags)
 {
 	struct cras_alert *alert;
 	alert = calloc(1, sizeof(*alert));
 	if (!alert)
 		return NULL;
 	alert->prepare = prepare;
+	alert->flags = flags;
 	DL_APPEND(all_alerts, alert);
 	return alert;
 }
@@ -79,13 +92,26 @@
 static void cras_alert_process(struct cras_alert *alert)
 {
 	struct cras_alert_cb_list *cb;
+	struct cras_alert_data *data;
 
-	if (alert->pending) {
-		alert->pending = 0;
-		if (alert->prepare)
-			alert->prepare(alert);
+	if (!alert->pending)
+		return;
+
+	alert->pending = 0;
+	if (alert->prepare)
+		alert->prepare(alert);
+
+	if (!alert->data) {
 		DL_FOREACH(alert->callbacks, cb)
-			cb->callback(cb->arg);
+			cb->callback(cb->arg, NULL);
+	}
+
+	/* Have data arguments, pass each to the callbacks. */
+	DL_FOREACH(alert->data, data) {
+		DL_FOREACH(alert->callbacks, cb)
+			cb->callback(cb->arg, (void *)data->buf);
+		DL_DELETE(alert->data, data);
+		free(data);
 	}
 }
 
@@ -95,6 +121,27 @@
 	has_alert_pending = 1;
 }
 
+void cras_alert_pending_data(struct cras_alert *alert,
+			     void *data, size_t data_size)
+{
+	struct cras_alert_data *d;
+
+	alert->pending = 1;
+	has_alert_pending = 1;
+	d = calloc(1, offsetof(struct cras_alert_data, buf) + data_size);
+	memcpy(d->buf, data, data_size);
+
+	if (!(alert->flags & CRAS_ALERT_FLAG_KEEP_ALL_DATA) && alert->data) {
+		/* There will never be more than one item in the list. */
+		free(alert->data);
+		alert->data = NULL;
+	}
+
+	/* Even when there is only one item, it is important to use DL_APPEND
+	 * here so that d's next and prev pointers are setup correctly. */
+	DL_APPEND(alert->data, d);
+}
+
 void cras_alert_process_all_pending_alerts()
 {
 	struct cras_alert *alert;
@@ -109,6 +156,7 @@
 void cras_alert_destroy(struct cras_alert *alert)
 {
 	struct cras_alert_cb_list *cb;
+	struct cras_alert_data *data;
 
 	if (!alert)
 		return;
@@ -118,6 +166,11 @@
 		free(cb);
 	}
 
+	DL_FOREACH(alert->data, data) {
+		DL_DELETE(alert->data, data);
+		free(data);
+	}
+
 	alert->callbacks = NULL;
 	DL_DELETE(all_alerts, alert);
 	free(alert);
diff --git a/cras/src/server/cras_alert.h b/cras/src/server/cras_alert.h
index ffdf143..3fee90b 100644
--- a/cras/src/server/cras_alert.h
+++ b/cras/src/server/cras_alert.h
@@ -32,19 +32,33 @@
 struct cras_alert;
 
 /* Callback functions to be notified when settings change. arg is a user
- * provided argument that will be passed back. */
-typedef void (*cras_alert_cb)(void *arg);
+ * provided argument that will be passed back, data is extra info about the
+ * signal if available.
+ */
+typedef void (*cras_alert_cb)(void *arg, void *data);
 typedef void (*cras_alert_prepare)(struct cras_alert *alert);
 
+/* Flags for alerts. */
+enum CRAS_ALERT_FLAGS {
+	/* By default, alerts will only keep the last copy of the data
+	 * specified in cras_alert_pending_data as an optimization - then
+	 * the callback is executed once with the latest value, rather than
+	 * for every value. In some cases, it is important to send the data
+	 * with every change. Use this flag to enable that behavior. */
+	CRAS_ALERT_FLAG_KEEP_ALL_DATA = 1 << 0,
+};
+
 /* Creates an alert.
  * Args:
  *    prepare - A function which will be called before calling the callbacks.
  *        The prepare function should update the system state in the shared
  *        memory to be consistent. It can be NULL if not needed.
+ *    flags - 0 for defauts, or ORed values from enum CRAS_ALERT_FLAGS.
  * Returns:
  *    A pointer to the alert, NULL if out of memory.
  */
-struct cras_alert *cras_alert_create(cras_alert_prepare prepare);
+struct cras_alert *cras_alert_create(cras_alert_prepare prepare,
+				     unsigned int flags);
 
 /* Adds a callback to the alert.
  * Args:
@@ -77,6 +91,20 @@
  */
 void cras_alert_pending(struct cras_alert *alert);
 
+/* Marks an alert as pending. We don't call the callbacks immediately when an
+ * alert becomes pending, but will do that when
+ * cras_alert_process_all_pending_alerts() is called.
+ * By default only the last data value supplied here is provided as an
+ * argument to the callback. To have the callback executed with every
+ * data value, call cras_alert_keep_all_data() (see above).
+ * Args:
+ *    alert - A pointer to the alert.
+ *    data - A pointer to data that is copied and passed to the callback.
+ *    data_size - Size of the data.
+ */
+void cras_alert_pending_data(struct cras_alert *alert,
+			     void *data, size_t data_size);
+
 /* Processes all alerts that are pending.
  *
  * For all pending alerts, its prepare function will be called, then the
diff --git a/cras/src/server/cras_alsa_card.c b/cras/src/server/cras_alsa_card.c
index 080a719..16a6ef5 100644
--- a/cras/src/server/cras_alsa_card.c
+++ b/cras/src/server/cras_alsa_card.c
@@ -3,8 +3,11 @@
  * found in the LICENSE file.
  */
 
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE /* For asprintf */
+#endif
+
 #include <alsa/asoundlib.h>
-#include <alsa/use-case.h>
 #include <syslog.h>
 
 #include "cras_alsa_card.h"
@@ -16,6 +19,7 @@
 #include "cras_config.h"
 #include "cras_iodev.h"
 #include "cras_iodev_list.h"
+#include "cras_system_state.h"
 #include "cras_types.h"
 #include "cras_util.h"
 #include "utlist.h"
@@ -23,6 +27,7 @@
 #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_COUPLED_OUTPUT_SIZE 4
 
 struct iodev_list_node {
 	struct cras_iodev *iodev;
@@ -30,12 +35,21 @@
 	struct iodev_list_node *prev, *next;
 };
 
+/* Keeps an fd that is registered with system state.  A list of fds must be
+ * kept so that they can be removed when the card is destroyed. */
+struct hctl_poll_fd {
+	int fd;
+	struct hctl_poll_fd *prev, *next;
+};
+
 /* Holds information about each sound card on the system.
  * name - of the form hw:XX,YY.
  * 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.
- * ucm - ALSA use case manager if available.
+ * ucm - CRAS use case manager if available.
+ * hctl - ALSA high-level control interface.
+ * hctl_poll_fds - List of fds registered with cras_system_state.
  * config - Config info for this card, can be NULL if none found.
  */
 struct cras_alsa_card {
@@ -43,23 +57,12 @@
 	size_t card_index;
 	struct iodev_list_node *iodevs;
 	struct cras_alsa_mixer *mixer;
-	snd_use_case_mgr_t *ucm;
+	struct cras_use_case_mgr *ucm;
+	snd_hctl_t *hctl;
+	struct hctl_poll_fd *hctl_poll_fds;
 	struct cras_card_config *config;
 };
 
-/* Checks if there are any devices with direction already in the card.
- */
-int is_first_dev(struct cras_alsa_card *alsa_card,
-		 enum CRAS_STREAM_DIRECTION direction)
-{
-	struct iodev_list_node *node;
-
-	DL_FOREACH(alsa_card->iodevs, node)
-		if (node->direction == direction)
-			return 0;
-	return 1;
-}
-
 /* Creates an iodev for the given device.
  * Args:
  *    alsa_card - the alsa_card the device will be added to.
@@ -69,23 +72,40 @@
  *    dev_id - The id string of the device.
  *    device_index - 0 based index, value of "YY" in "hw:XX,YY".
  *    direction - Input or output.
+ * Returns:
+ *    Pointer to the created iodev, or NULL on error.
+ *    other negative error code otherwise.
  */
-void create_iodev_for_device(struct cras_alsa_card *alsa_card,
-			     struct cras_alsa_card_info *info,
-			     const char *card_name,
-			     const char *dev_name,
-			     const char *dev_id,
-			     unsigned device_index,
-			     enum CRAS_STREAM_DIRECTION direction)
+struct cras_iodev *create_iodev_for_device(
+		struct cras_alsa_card *alsa_card,
+		struct cras_alsa_card_info *info,
+		const char *card_name,
+		const char *dev_name,
+		const char *dev_id,
+		unsigned device_index,
+		enum CRAS_STREAM_DIRECTION direction)
 {
 	struct iodev_list_node *new_dev;
-	int first;
+	struct iodev_list_node *node;
+	int first = 1;
 
-	first = is_first_dev(alsa_card, direction);
+	/* Find whether this is the first device in this direction, and
+	 * avoid duplicate device indexes. */
+	DL_FOREACH(alsa_card->iodevs, node) {
+		if (node->direction != direction)
+			continue;
+		first = 0;
+		if (alsa_iodev_index(node->iodev) == device_index) {
+			syslog(LOG_DEBUG,
+			       "Skipping duplicate device for %s:%s:%s [%u]",
+			       card_name, dev_name, dev_id, device_index);
+			return node->iodev;
+		}
+	}
 
 	new_dev = calloc(1, sizeof(*new_dev));
 	if (new_dev == NULL)
-		return;
+		return NULL;
 
 	new_dev->direction = direction;
 	new_dev->iodev = alsa_iodev_create(info->card_index,
@@ -96,15 +116,18 @@
 					   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_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);
 		free(new_dev);
-		return;
+		return NULL;
 	}
 
 	syslog(LOG_DEBUG, "New %s device %u:%d",
@@ -113,6 +136,21 @@
 	       device_index);
 
 	DL_APPEND(alsa_card->iodevs, new_dev);
+	return new_dev->iodev;
+}
+
+/* Returns non-zero if this card has hctl jacks.
+ */
+static int card_has_hctl_jack(struct cras_alsa_card *alsa_card)
+{
+	struct iodev_list_node *node;
+
+	/* Find the first device that has an hctl jack. */
+	DL_FOREACH(alsa_card->iodevs, node) {
+		if (alsa_iodev_has_hctl_jacks(node->iodev))
+			return 1;
+	}
+	return 0;
 }
 
 /* Check if a device should be ignored for this card. Returns non-zero if the
@@ -132,20 +170,244 @@
 }
 
 /* Filters an array of mixer control names. Keep a name if it is
- * specified in the ucm config, otherwise set it to NULL */
-static void filter_mixer_names(snd_use_case_mgr_t *ucm,
-			       const char *names[],
-			       size_t names_len)
+ * specified in the ucm config. */
+static struct mixer_name *filter_controls(struct cras_use_case_mgr *ucm,
+					  struct mixer_name *controls)
 {
-	size_t i;
-	for (i = 0; i < names_len; i++) {
-		char *dev = ucm_get_dev_for_mixer(ucm, names[i],
+	struct mixer_name *control;
+	DL_FOREACH(controls, control) {
+		char *dev = ucm_get_dev_for_mixer(ucm, control->name,
 						  CRAS_STREAM_OUTPUT);
-		if (dev)
-			free(dev);
-		else
-			names[i] = NULL;
+		if (!dev)
+			DL_DELETE(controls, control);
 	}
+	return controls;
+}
+
+/* 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)
+{
+	struct cras_alsa_card *card;
+
+	card = (struct cras_alsa_card *)arg;
+	if (card == NULL) {
+		syslog(LOG_ERR, "Invalid card from control event.");
+		return;
+	}
+
+	/* handle_events will trigger the callback registered with each control
+	 * that has changed. */
+	snd_hctl_handle_events(card->hctl);
+}
+
+static int add_controls_and_iodevs_by_matching(
+		struct cras_alsa_card_info *info,
+		struct cras_device_blacklist *blacklist,
+		struct cras_alsa_card *alsa_card,
+		const char *card_name,
+		snd_ctl_t *handle)
+{
+	struct mixer_name *coupled_controls = NULL;
+	int dev_idx;
+	snd_pcm_info_t *dev_info;
+	struct mixer_name *extra_controls = NULL;
+	int rc = 0;
+
+	snd_pcm_info_alloca(&dev_info);
+
+	if (alsa_card->ucm) {
+		char *extra_main_volume;
+
+		/* Filter the extra output mixer names */
+		extra_controls =
+			filter_controls(alsa_card->ucm,
+				mixer_name_add(extra_controls, "IEC958",
+					       CRAS_STREAM_OUTPUT,
+					       MIXER_NAME_VOLUME));
+
+		/* Get the extra main volume control. */
+		extra_main_volume = ucm_get_flag(alsa_card->ucm,
+						 "ExtraMainVolume");
+		if (extra_main_volume) {
+			extra_controls =
+				mixer_name_add(extra_controls,
+					       extra_main_volume,
+					       CRAS_STREAM_OUTPUT,
+					       MIXER_NAME_MAIN_VOLUME);
+			free(extra_main_volume);
+		}
+		mixer_name_dump(extra_controls, "extra controls");
+
+		/* Check if coupled controls has been specified for speaker. */
+		coupled_controls = ucm_get_coupled_mixer_names(
+					alsa_card->ucm, "Speaker");
+		mixer_name_dump(coupled_controls, "coupled controls");
+	}
+
+	/* Add controls to mixer by name matching. */
+	rc = cras_alsa_mixer_add_controls_by_name_matching(
+			alsa_card->mixer,
+			extra_controls,
+			coupled_controls);
+	if (rc) {
+		syslog(LOG_ERR, "Fail adding controls to mixer for %s.",
+		       alsa_card->name);
+		goto error;
+	}
+
+	/* Go through every device. */
+	dev_idx = -1;
+	while (1) {
+		rc = snd_ctl_pcm_next_device(handle, &dev_idx);
+		if (rc < 0)
+			goto error;
+		if (dev_idx < 0)
+			break;
+
+		snd_pcm_info_set_device(dev_info, dev_idx);
+		snd_pcm_info_set_subdevice(dev_info, 0);
+
+		/* 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)) {
+			struct cras_iodev *iodev =
+				create_iodev_for_device(
+					alsa_card,
+					info,
+					card_name,
+					snd_pcm_info_get_name(dev_info),
+					snd_pcm_info_get_id(dev_info),
+					dev_idx,
+					CRAS_STREAM_OUTPUT);
+			if (iodev) {
+				rc = alsa_iodev_legacy_complete_init(
+					iodev);
+				if (rc < 0)
+					goto error;
+			}
+		}
+
+		/* Check for capture devices. */
+		snd_pcm_info_set_stream(
+			dev_info, SND_PCM_STREAM_CAPTURE);
+		if (snd_ctl_pcm_info(handle, dev_info) == 0) {
+			struct cras_iodev *iodev =
+				create_iodev_for_device(
+					alsa_card,
+					info,
+					card_name,
+					snd_pcm_info_get_name(dev_info),
+					snd_pcm_info_get_id(dev_info),
+					dev_idx,
+					CRAS_STREAM_INPUT);
+			if (iodev) {
+				rc = alsa_iodev_legacy_complete_init(
+					iodev);
+				if (rc < 0)
+					goto error;
+			}
+		}
+	}
+error:
+	mixer_name_free(coupled_controls);
+	mixer_name_free(extra_controls);
+	return rc;
+}
+
+static int add_controls_and_iodevs_with_ucm(
+		struct cras_alsa_card_info *info,
+		struct cras_alsa_card *alsa_card,
+		const char *card_name,
+		snd_ctl_t *handle)
+{
+	snd_pcm_info_t *dev_info;
+	struct iodev_list_node *node;
+	int rc = 0;
+	struct ucm_section *section;
+	struct ucm_section *ucm_sections;
+
+	snd_pcm_info_alloca(&dev_info);
+
+	/* Get info on the devices specified in the UCM config. */
+	ucm_sections = ucm_get_sections(alsa_card->ucm);
+	if (!ucm_sections) {
+		syslog(LOG_ERR,
+		       "Could not retrieve any UCM SectionDevice"
+		       " info for '%s'.", card_name);
+		rc = -ENOENT;
+		goto error;
+	}
+
+	/* Create all of the controls first. */
+	DL_FOREACH(ucm_sections, section) {
+		rc = cras_alsa_mixer_add_controls_in_section(
+				alsa_card->mixer, section);
+		if (rc) {
+			syslog(LOG_ERR, "Failed adding controls to"
+					" mixer for '%s:%s'",
+					card_name,
+					section->name);
+			goto error;
+		}
+	}
+
+	/* Create all of the devices. */
+	DL_FOREACH(ucm_sections, section) {
+		snd_pcm_info_set_device(dev_info, section->dev_idx);
+		snd_pcm_info_set_subdevice(dev_info, 0);
+		if (section->dir == CRAS_STREAM_OUTPUT)
+			snd_pcm_info_set_stream(
+				dev_info, SND_PCM_STREAM_PLAYBACK);
+		else if (section->dir == CRAS_STREAM_INPUT)
+			snd_pcm_info_set_stream(
+				dev_info, SND_PCM_STREAM_CAPTURE);
+		else {
+			syslog(LOG_ERR, "Unexpected direction: %d",
+			       section->dir);
+			rc = -EINVAL;
+			goto error;
+		}
+
+		if (snd_ctl_pcm_info(handle, dev_info)) {
+			syslog(LOG_ERR,
+			       "Could not get info for device: %s",
+			       section->name);
+			continue;
+		}
+
+		create_iodev_for_device(
+			alsa_card, info, card_name,
+			snd_pcm_info_get_name(dev_info),
+			snd_pcm_info_get_id(dev_info),
+			section->dev_idx, section->dir);
+	}
+
+	/* Setup jacks and controls for the devices. */
+	DL_FOREACH(ucm_sections, section) {
+		DL_FOREACH(alsa_card->iodevs, node) {
+			if (node->direction == section->dir &&
+			    alsa_iodev_index(node->iodev) ==
+			    section->dev_idx)
+				break;
+		}
+		if (node) {
+			rc = alsa_iodev_ucm_add_nodes_and_jacks(
+				node->iodev, section);
+			if (rc < 0)
+				goto error;
+		}
+	}
+
+	DL_FOREACH(alsa_card->iodevs, node) {
+		alsa_iodev_ucm_complete_init(node->iodev);
+	}
+
+error:
+	ucm_section_free_list(ucm_sections);
+	return rc;
 }
 
 /*
@@ -155,19 +417,14 @@
 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_blacklist *blacklist,
+		const char *ucm_suffix)
 {
 	snd_ctl_t *handle = NULL;
-	int rc, dev_idx;
+	int rc, n;
 	snd_ctl_card_info_t *card_info;
 	const char *card_name;
-	snd_pcm_info_t *dev_info;
 	struct cras_alsa_card *alsa_card;
-	const char *output_names_extra[] = {
-		"IEC958",
-	};
-	size_t output_names_extra_size = ARRAY_SIZE(output_names_extra);
-	char *extra_main_volume = NULL;
 
 	if (info->card_index >= MAX_ALSA_CARDS) {
 		syslog(LOG_ERR,
@@ -177,7 +434,6 @@
 	}
 
 	snd_ctl_card_info_alloca(&card_info);
-	snd_pcm_info_alloca(&dev_info);
 
 	alsa_card = calloc(1, sizeof(*alsa_card));
 	if (alsa_card == NULL)
@@ -214,67 +470,99 @@
 		syslog(LOG_DEBUG, "No config file for %s", alsa_card->name);
 
 	/* Create a use case manager if a configuration is available. */
-	alsa_card->ucm = ucm_create(card_name);
+	if (ucm_suffix) {
+		char *ucm_name;
+		if (asprintf(&ucm_name, "%s.%s", card_name, ucm_suffix) == -1) {
+			syslog(LOG_ERR, "Error creating ucm name");
+			goto error_bail;
+		}
+		alsa_card->ucm = ucm_create(ucm_name);
+		syslog(LOG_INFO, "Card %s (%s) has UCM: %s",
+		       alsa_card->name, ucm_name,
+		       alsa_card->ucm ? "yes" : "no");
+		free(ucm_name);
+	} else {
+		alsa_card->ucm = ucm_create(card_name);
+		syslog(LOG_INFO, "Card %s (%s) has UCM: %s",
+		       alsa_card->name, card_name,
+		       alsa_card->ucm ? "yes" : "no");
+	}
 
-	/* Filter the extra output mixer names */
-	if (alsa_card->ucm)
-		filter_mixer_names(alsa_card->ucm, output_names_extra,
-				   output_names_extra_size);
-	else
-		output_names_extra_size = 0;
+	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);
+		alsa_card->hctl = NULL;
+	} else {
+		rc = snd_hctl_nonblock(alsa_card->hctl, 1);
+		if (rc < 0) {
+			syslog(LOG_ERR,
+			    "failed to nonblock hctl for %s", alsa_card->name);
+			goto error_bail;
+		}
 
-	/* Check if an extra main volume has been specified. */
-	if (alsa_card->ucm)
-		extra_main_volume = ucm_get_flag(alsa_card->ucm,
-						 "ExtraMainVolume");
+		rc = snd_hctl_load(alsa_card->hctl);
+		if (rc < 0) {
+			syslog(LOG_ERR,
+			       "failed to load hctl for %s", alsa_card->name);
+			goto error_bail;
+		}
+	}
+
 	/* Create one mixer per card. */
-	alsa_card->mixer = cras_alsa_mixer_create(alsa_card->name,
-						  alsa_card->config,
-						  output_names_extra,
-						  output_names_extra_size,
-						  extra_main_volume);
-	free(extra_main_volume);
+	alsa_card->mixer = cras_alsa_mixer_create(alsa_card->name);
+
 	if (alsa_card->mixer == NULL) {
 		syslog(LOG_ERR, "Fail opening mixer for %s.", alsa_card->name);
 		goto error_bail;
 	}
 
-	dev_idx = -1;
-	while (1) {
-		rc = snd_ctl_pcm_next_device(handle, &dev_idx);
-		if (rc < 0) {
-			cras_alsa_card_destroy(alsa_card);
-			snd_ctl_close(handle);
-			return NULL;
+	if (alsa_card->ucm && ucm_has_fully_specified_ucm_flag(alsa_card->ucm))
+		rc = add_controls_and_iodevs_with_ucm(
+				info, alsa_card, card_name, handle);
+	else
+		rc = add_controls_and_iodevs_by_matching(
+				info, blacklist, alsa_card, card_name, handle);
+	if (rc)
+		goto error_bail;
+
+	n = alsa_card->hctl ?
+		snd_hctl_poll_descriptors_count(alsa_card->hctl) : 0;
+	if (n != 0 && card_has_hctl_jack(alsa_card)) {
+		struct hctl_poll_fd *registered_fd;
+		struct pollfd *pollfds;
+		int i;
+
+		pollfds = malloc(n * sizeof(*pollfds));
+		if (pollfds == NULL) {
+			rc = -ENOMEM;
+			goto error_bail;
 		}
-		if (dev_idx < 0)
-			break;
 
-		snd_pcm_info_set_device(dev_info, dev_idx);
-		snd_pcm_info_set_subdevice(dev_info, 0);
-
-		/* 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))
-			create_iodev_for_device(alsa_card,
-						info,
-						card_name,
-						snd_pcm_info_get_name(dev_info),
-						snd_pcm_info_get_id(dev_info),
-						dev_idx,
-						CRAS_STREAM_OUTPUT);
-
-		/* Check for capture devices. */
-		snd_pcm_info_set_stream(dev_info, SND_PCM_STREAM_CAPTURE);
-		if (snd_ctl_pcm_info(handle, dev_info) == 0)
-			create_iodev_for_device(alsa_card,
-						info,
-						card_name,
-						snd_pcm_info_get_name(dev_info),
-						snd_pcm_info_get_id(dev_info),
-						dev_idx,
-						CRAS_STREAM_INPUT);
+		n = snd_hctl_poll_descriptors(alsa_card->hctl, pollfds, n);
+		for (i = 0; i < n; i++) {
+			registered_fd = calloc(1, sizeof(*registered_fd));
+			if (registered_fd == NULL) {
+				free(pollfds);
+				rc = -ENOMEM;
+				goto error_bail;
+			}
+			registered_fd->fd = pollfds[i].fd;
+			DL_APPEND(alsa_card->hctl_poll_fds, registered_fd);
+			rc = cras_system_add_select_fd(
+					registered_fd->fd,
+					alsa_control_event_pending,
+					alsa_card);
+			if (rc < 0) {
+				DL_DELETE(alsa_card->hctl_poll_fds,
+					  registered_fd);
+				free(pollfds);
+				goto error_bail;
+			}
+		}
+		free(pollfds);
 	}
 
 	snd_ctl_close(handle);
@@ -283,19 +571,14 @@
 error_bail:
 	if (handle != NULL)
 		snd_ctl_close(handle);
-	if (alsa_card->ucm)
-		ucm_destroy(alsa_card->ucm);
-	if (alsa_card->mixer)
-		cras_alsa_mixer_destroy(alsa_card->mixer);
-	if (alsa_card->config)
-		cras_card_config_destroy(alsa_card->config);
-	free(alsa_card);
+	cras_alsa_card_destroy(alsa_card);
 	return NULL;
 }
 
 void cras_alsa_card_destroy(struct cras_alsa_card *alsa_card)
 {
 	struct iodev_list_node *curr;
+	struct hctl_poll_fd *poll_fd;
 
 	if (alsa_card == NULL)
 		return;
@@ -305,9 +588,17 @@
 		DL_DELETE(alsa_card->iodevs, curr);
 		free(curr);
 	}
+	DL_FOREACH(alsa_card->hctl_poll_fds, poll_fd) {
+		cras_system_rm_select_fd(poll_fd->fd);
+		DL_DELETE(alsa_card->hctl_poll_fds, poll_fd);
+		free(poll_fd);
+	}
+	if (alsa_card->hctl)
+		snd_hctl_close(alsa_card->hctl);
 	if (alsa_card->ucm)
 		ucm_destroy(alsa_card->ucm);
-	cras_alsa_mixer_destroy(alsa_card->mixer);
+	if (alsa_card->mixer)
+		cras_alsa_mixer_destroy(alsa_card->mixer);
 	if (alsa_card->config)
 		cras_card_config_destroy(alsa_card->config);
 	free(alsa_card);
diff --git a/cras/src/server/cras_alsa_card.h b/cras/src/server/cras_alsa_card.h
index 7d0daf7..705b8e7 100644
--- a/cras/src/server/cras_alsa_card.h
+++ b/cras/src/server/cras_alsa_card.h
@@ -25,6 +25,7 @@
  *    device_config_dir - The directory of device configs which contains the
  *                        volume curves.
  *    blacklist - 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
  *    by calling cras_alsa_card_destroy or NULL on error.
@@ -32,7 +33,8 @@
 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_blacklist *blacklist,
+		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 b3ec733..a8719c2 100644
--- a/cras/src/server/cras_alsa_helpers.c
+++ b/cras/src/server/cras_alsa_helpers.c
@@ -22,6 +22,9 @@
 /* Assert the channel is defined in CRAS_CHANNELS. */
 #define ALSA_CH_VALID(ch) ((ch >= SND_CHMAP_FL) && (ch <= SND_CHMAP_FRC))
 
+/* Time difference between two consecutive underrun logs. */
+#define UNDERRUN_LOG_TIME_SECS 30
+
 /* Chances to give mmap_begin to work. */
 static const size_t MAX_MMAP_BEGIN_ATTEMPTS = 3;
 /* Time to sleep between resume attempts. */
@@ -82,6 +85,8 @@
 			idx = fmt->channel_layout[ch];
 			if (idx == -1)
 				continue;
+			if ((unsigned)idx >= (*chmap)->map.channels)
+				continue;
 			if ((*chmap)->map.pos[idx] != CH_TO_ALSA(ch)) {
 				matches = 0;
 				break;
@@ -255,6 +260,47 @@
 	return snd_pcm_drain(handle);
 }
 
+int cras_alsa_resume_appl_ptr(snd_pcm_t *handle, snd_pcm_uframes_t ahead)
+{
+	int rc;
+	snd_pcm_uframes_t period_frames, buffer_frames;
+	snd_pcm_sframes_t to_move, avail_frames;
+	rc = snd_pcm_avail(handle);
+	if (rc == -EPIPE || rc == -ESTRPIPE) {
+		cras_alsa_attempt_resume(handle);
+		avail_frames = 0;
+	} else if (rc < 0) {
+		syslog(LOG_ERR, "Fail to get avail frames: %s",
+		       snd_strerror(rc));
+		return rc;
+	} else {
+		avail_frames = rc;
+	}
+
+	rc = snd_pcm_get_params(handle, &buffer_frames, &period_frames);
+	if (rc < 0) {
+		syslog(LOG_ERR, "Fail to get buffer size: %s",
+		       snd_strerror(rc));
+		return rc;
+	}
+
+	to_move = avail_frames - buffer_frames + ahead;
+	if (to_move > 0) {
+		rc = snd_pcm_forward(handle, to_move);
+	} else if (to_move < 0) {
+		rc = snd_pcm_rewind(handle, -to_move);
+	} else {
+		return 0;
+	}
+
+	if (rc < 0) {
+		syslog(LOG_ERR, "Fail to resume appl_ptr: %s",
+		       snd_strerror(rc));
+		return rc;
+	}
+	return 0;
+}
+
 int cras_alsa_set_channel_map(snd_pcm_t *handle,
 			      struct cras_audio_format *fmt)
 {
@@ -417,7 +463,8 @@
 }
 
 int cras_alsa_set_hwparams(snd_pcm_t *handle, struct cras_audio_format *format,
-			   snd_pcm_uframes_t *buffer_frames)
+			   snd_pcm_uframes_t *buffer_frames, int period_wakeup,
+			   unsigned int dma_period_time)
 {
 	unsigned int rate, ret_rate;
 	int err;
@@ -444,13 +491,32 @@
 		syslog(LOG_ERR, "Setting interleaved %s\n", snd_strerror(err));
 		return err;
 	}
-	/* Try to disable ALSA wakeups, we'll keep a timer. */
-	if (snd_pcm_hw_params_can_disable_period_wakeup(hwparams)) {
+	/* If period_wakeup flag is not set, try to disable ALSA wakeups,
+	 * we'll keep a timer. */
+	if (!period_wakeup &&
+	    snd_pcm_hw_params_can_disable_period_wakeup(hwparams)) {
 		err = snd_pcm_hw_params_set_period_wakeup(handle, hwparams, 0);
 		if (err < 0)
 			syslog(LOG_WARNING, "disabling wakeups %s\n",
 			       snd_strerror(err));
 	}
+	/* Setup the period time so that the hardware pulls the right amount
+	 * of data at the right time. */
+	if (dma_period_time) {
+		int dir = 0;
+		unsigned int original = dma_period_time;
+
+		err = snd_pcm_hw_params_set_period_time_near(
+				handle, hwparams, &dma_period_time, &dir);
+		if (err < 0) {
+			syslog(LOG_ERR, "could not set period time: %s",
+			       snd_strerror(err));
+			return err;
+		} else if (original != dma_period_time) {
+			syslog(LOG_DEBUG, "period time set to: %u",
+			       dma_period_time);
+		}
+	}
 	/* Set the sample format. */
 	err = snd_pcm_hw_params_set_format(handle, hwparams,
 					   format->format);
@@ -505,11 +571,10 @@
 		       ret_rate, format->num_channels, format->format);
 		return err;
 	}
-
 	return 0;
 }
 
-int cras_alsa_set_swparams(snd_pcm_t *handle)
+int cras_alsa_set_swparams(snd_pcm_t *handle, int *enable_htimestamp)
 {
 	int err;
 	snd_pcm_sw_params_t *swparams;
@@ -546,7 +611,53 @@
 		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));
 		return err;
@@ -555,22 +666,63 @@
 }
 
 int cras_alsa_get_avail_frames(snd_pcm_t *handle, snd_pcm_uframes_t buf_size,
-			       snd_pcm_uframes_t *used)
+			       snd_pcm_uframes_t severe_underrun_frames,
+			       const char *dev_name,
+			       snd_pcm_uframes_t *avail,
+			       struct timespec *tstamp,
+			       unsigned int *underruns)
 {
 	snd_pcm_sframes_t frames;
 	int rc = 0;
+	static struct timespec tstamp_last_underrun_log =
+			{.tv_sec = 0, .tv_nsec = 0};
 
+	/* Use snd_pcm_avail still to ensure that the hardware pointer is
+	 * up to date. Otherwise, we could use the deprecated snd_pcm_hwsync().
+	 * IMO this is a deficiency in the ALSA API.
+	 */
 	frames = snd_pcm_avail(handle);
-	if (frames == -EPIPE || frames == -ESTRPIPE) {
-		cras_alsa_attempt_resume(handle);
-		frames = 0;
-	} else if (frames < 0) {
-		syslog(LOG_INFO, "pcm_avail error %s\n", snd_strerror(frames));
+	if (frames >= 0)
+		rc = snd_pcm_htimestamp(handle, avail, tstamp);
+	else
 		rc = frames;
-		frames = 0;
-	} else if (frames > (snd_pcm_sframes_t)buf_size)
-		frames = buf_size;
-	*used = frames;
+	if (rc == -EPIPE || rc == -ESTRPIPE) {
+		cras_alsa_attempt_resume(handle);
+		rc = 0;
+		goto error;
+	} else if (rc < 0) {
+		syslog(LOG_ERR, "pcm_avail error %s, %s\n",
+		       dev_name, snd_strerror(rc));
+		goto error;
+	} else if (frames >= (snd_pcm_sframes_t)buf_size) {
+		struct timespec tstamp_now;
+		*underruns = *underruns + 1;
+		clock_gettime(CLOCK_MONOTONIC_RAW, &tstamp_now);
+		/* Limit the log rate. */
+		if ((tstamp_now.tv_sec - tstamp_last_underrun_log.tv_sec) >
+		    UNDERRUN_LOG_TIME_SECS) {
+			syslog(LOG_ERR,
+			       "pcm_avail returned frames larger than buf_size: "
+			       "%s: %ld > %lu for %u times\n",
+			       dev_name, frames, buf_size, *underruns);
+			tstamp_last_underrun_log.tv_sec = tstamp_now.tv_sec;
+			tstamp_last_underrun_log.tv_nsec = tstamp_now.tv_nsec;
+		}
+		if ((frames - (snd_pcm_sframes_t)buf_size) >
+		    (snd_pcm_sframes_t)severe_underrun_frames) {
+			rc = -EPIPE;
+			goto error;
+		} else {
+			frames = buf_size;
+		}
+	}
+	*avail = frames;
+	return 0;
+
+error:
+	*avail = 0;
+	tstamp->tv_sec = 0;
+	tstamp->tv_nsec = 0;
 	return rc;
 }
 
@@ -607,6 +759,24 @@
 	return rc;
 }
 
+int cras_alsa_mmap_get_whole_buffer(snd_pcm_t *handle, uint8_t **dst,
+				    unsigned int *underruns)
+{
+	snd_pcm_uframes_t offset;
+	/* The purpose of calling cras_alsa_mmap_begin is to get the base
+	 * address of the buffer. The requested and retrieved frames are not
+	 * meaningful here.
+	 * However, we need to set a non-zero requested frames to get a
+	 * non-zero retrieved frames. This is to avoid the error checking in
+	 * snd_pcm_mmap_begin, where it judges retrieved frames being 0 as a
+	 * failure.
+	 */
+	snd_pcm_uframes_t frames = 1;
+
+	return cras_alsa_mmap_begin(
+			handle, 0, dst, &offset, &frames, underruns);
+}
+
 int cras_alsa_mmap_begin(snd_pcm_t *handle, unsigned int format_bytes,
 			 uint8_t **dst, snd_pcm_uframes_t *offset,
 			 snd_pcm_uframes_t *frames, unsigned int *underruns)
diff --git a/cras/src/server/cras_alsa_helpers.h b/cras/src/server/cras_alsa_helpers.h
index 6513a3e..33c3390 100644
--- a/cras/src/server/cras_alsa_helpers.h
+++ b/cras/src/server/cras_alsa_helpers.h
@@ -73,6 +73,36 @@
  */
 int cras_alsa_pcm_drain(snd_pcm_t *handle);
 
+/* Forward/rewind appl_ptr so it becomes ahead of hw_ptr by fuzz samples.
+ * After moving appl_ptr, device can play the new samples as quick as possible.
+ *    avail = buffer_frames - appl_ptr + hw_ptr
+ * => hw_ptr - appl_ptr = avail - buffer_frames.
+ * The difference between hw_ptr and app_ptr can be inferred from snd_pcm_avail.
+ * So the amount of frames to forward appl_ptr is
+ * avail - buffer_frames + fuzz.
+ * When hw_ptr is wrapped around boundary, this value may be negative. Use
+ * snd_pcm_rewind to move appl_ptr backward.
+ *
+ * Case 1: avail - buffer_frames + fuzz > 0
+ *
+ * -------|----------|-----------------------------------
+ *      app_ptr     hw_ptr
+ *        |------------->| forward target
+ *
+ * Case 2: avail - buffer_frames + fuzz < 0
+ *
+ * -------|----------|-----------------------------------
+ *      hw_ptr      app_ptr
+ *           |<------| rewind target
+ *
+ * Args:
+ *    handle - Filled with a pointer to the opened pcm.
+ *    ahead - Number of frames appl_ptr should be ahead of hw_ptr.
+ * Returns:
+ *    0 on success. A negative error code on failure.
+ */
+int cras_alsa_resume_appl_ptr(snd_pcm_t *handle, snd_pcm_uframes_t ahead);
+
 /* Probes properties of the alsa device.
  * Args:
  *    dev - Path to the alsa device to test.
@@ -95,30 +125,55 @@
  *    handle - The open PCM to configure.
  *    format - The audio format desired for playback/capture.
  *    buffer_frames - Number of frames in the ALSA buffer.
+ *    period_wakeup - Flag to determine if period_wakeup is required
+ *                      0 - disable, 1 - enable
+ *    dma_period_time - If non-zero, set the dma period time to this value
+ *                      (in microseconds).
  * Returns:
  *    0 on success, negative error on failure.
  */
 int cras_alsa_set_hwparams(snd_pcm_t *handle, struct cras_audio_format *format,
-			   snd_pcm_uframes_t *buffer_frames);
+			   snd_pcm_uframes_t *buffer_frames, int period_wakeup,
+			   unsigned int dma_period_time);
 
 /* 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 cras_alsa_set_swparams(snd_pcm_t *handle, int *enable_htimestamp);
 
 /* Get the number of used frames in the alsa buffer.
+ *
+ * When underrun is not severe, this function masks the underrun situation
+ * and set avail as 0. When underrun is severe, returns -EPIPE so caller
+ * can handle it.
  * Args:
- *    handle - The open PCM to configure.
- *    buf_size - Number of frames in the ALSA buffer.
- *    used - Filled with the number of used frames.
+ *    handle[in] - The open PCM to configure.
+ *    buf_size[in] - Number of frames in the ALSA buffer.
+ *    severe_underrun_frames[in] - Number of frames as the threshold for severe
+ *                                 underrun.
+ *    dev_name[in] - Device name for logging.
+ *    avail[out] - Filled with the number of frames available in the buffer.
+ *    tstamp[out] - Filled with the hardware timestamp for the available frames.
+ *                  This value is {0, 0} when the device hasn't actually started
+ *                  reading or writing frames.
+ *    underruns[in,out] - Pointer to the underrun counter updated if there was
+ *                        an underrun.
  * Returns:
- *    0 on success, negative error on failure.
+ *    0 on success, negative error on failure. -EPIPE if severe underrun
+ *    happens.
  */
 int cras_alsa_get_avail_frames(snd_pcm_t *handle, snd_pcm_uframes_t buf_size,
-			       snd_pcm_uframes_t *used);
+			       snd_pcm_uframes_t severe_underrun_frames,
+			       const char *dev_name,
+			       snd_pcm_uframes_t *avail,
+			       struct timespec *tstamp,
+			       unsigned int *underruns);
 
 /* Get the current alsa delay, make sure it's no bigger than the buffer size.
  * Args:
@@ -131,6 +186,18 @@
 int cras_alsa_get_delay_frames(snd_pcm_t *handle, snd_pcm_uframes_t buf_size,
 			       snd_pcm_sframes_t *delay);
 
+/* Wrapper for snd_pcm_mmap_begin where only buffer is concerned.
+ * Offset and frames from cras_alsa_mmap_begin are neglected.
+ * Args:
+ *    handle - The open PCM to configure.
+ *    dst - Pointer set to the area for reading/writing the audio.
+ *    underruns - counter to increment if an under-run occurs.
+ * Returns:
+ *    zero on success, negative error code for fatal errors.
+ */
+int cras_alsa_mmap_get_whole_buffer(snd_pcm_t *handle, uint8_t **dst,
+				    unsigned int *underrun);
+
 /* Wrapper for snd_pcm_mmap_begin
  * Args:
  *    handle - The open PCM to configure.
diff --git a/cras/src/server/cras_alsa_io.c b/cras/src/server/cras_alsa_io.c
index ff4b323..80a8c3b 100644
--- a/cras/src/server/cras_alsa_io.c
+++ b/cras/src/server/cras_alsa_io.c
@@ -4,7 +4,6 @@
  */
 
 #include <alsa/asoundlib.h>
-#include <alsa/use-case.h>
 #include <errno.h>
 #include <limits.h>
 #include <stdio.h>
@@ -23,10 +22,11 @@
 #include "cras_alsa_ucm.h"
 #include "cras_audio_area.h"
 #include "cras_config.h"
-#include "cras_dbus_util.h"
+#include "cras_utf8.h"
 #include "cras_iodev.h"
 #include "cras_iodev_list.h"
 #include "cras_messages.h"
+#include "cras_ramp.h"
 #include "cras_rclient.h"
 #include "cras_shm.h"
 #include "cras_system_state.h"
@@ -38,7 +38,7 @@
 #include "utlist.h"
 
 #define MAX_ALSA_DEV_NAME_LENGTH 9 /* Alsa names "hw:XX,YY" + 1 for null. */
-#define AOKR_DEV "Wake on Voice"
+#define HOTWORD_DEV "Wake on Voice"
 #define DEFAULT "(default)"
 #define HDMI "HDMI"
 #define INTERNAL_MICROPHONE "Internal Mic"
@@ -46,24 +46,34 @@
 #define KEYBOARD_MIC "Keyboard Mic"
 #define USB "USB"
 
-/* For USB, pad the output buffer.  This avoids a situation where there isn't a
+/*
+ * For USB, pad the output buffer.  This avoids a situation where there isn't a
  * complete URB's worth of audio ready to be transmitted when it is requested.
  * The URB interval does track directly to the audio clock, making it hard to
- * predict the exact interval. */
+ * predict the exact interval.
+ */
 #define USB_EXTRA_BUFFER_FRAMES 768
 
+/*
+ * When snd_pcm_avail returns a value that is greater than buffer size,
+ * we know there is an underrun. If the number of underrun samples
+ * (avail - buffer_size) is greater than SEVERE_UNDERRUN_MS * rate,
+ * it is a severe underrun. Main thread should disable and then enable
+ * device to recover it from underrun.
+ */
+#define SEVERE_UNDERRUN_MS 5000
 
-/* This extends cras_ionode to include alsa-specific information.
+/*
+ * This extends cras_ionode to include alsa-specific information.
  * Members:
  *    mixer_output - From cras_alsa_mixer.
- *    jack_curve - In absense of a mixer output, holds a volume curve to use
- *        when this jack is plugged.
- *    jack - The jack associated with the jack_curve (if it exists).
+ *    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;
-	struct cras_volume_curve *jack_curve;
+	struct cras_volume_curve *volume_curve;
 	const struct cras_alsa_jack *jack;
 };
 
@@ -71,52 +81,175 @@
 	struct cras_ionode base;
 	struct mixer_control* mixer_input;
 	const struct cras_alsa_jack *jack;
+	int8_t *channel_layout;
 };
 
-/* Child of cras_iodev, alsa_io handles ALSA interaction for sound devices.
+/*
+ * 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").
+ * 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".
  * next_ionode_index - The index we will give to the next ionode. Each ionode
  *     have a unique index within the iodev.
  * card_type - the type of the card this iodev belongs.
  * 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.
+ * 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
+                          since device is created. When severe underrun occurs
+                          a possible action is to close/open device.
  * alsa_stream - Playback or capture type.
  * mixer - Alsa mixer used to control volume and mute of the device.
+ * config - Card config for this alsa device.
  * jack_list - List of alsa jack controls for this device.
- * ucm - ALSA use case manager, if configuration is found.
+ * 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.
+ * is_free_running - true if device is playing zeros in the buffer without
+ *                   user filling meaningful data. The device buffer is filled
+ *                   with zeros. In this state, appl_ptr remains the same
+ *                   while hw_ptr keeps running ahead.
+ * filled_zeros_for_draining - The number of zeros filled for draining.
+ * severe_underrun_frames - The threshold for severe underrun.
+ * default_volume_curve - Default volume curve that converts from an index
+ *                        to dBFS.
  */
 struct alsa_io {
 	struct cras_iodev base;
 	char *dev;
+	char *dev_name;
+	char *dev_id;
 	uint32_t device_index;
 	uint32_t next_ionode_index;
 	enum CRAS_ALSA_CARD_TYPE card_type;
 	int is_first;
+	int fully_specified;
+	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;
+	const struct cras_card_config *config;
 	struct cras_alsa_jack_list *jack_list;
-	snd_use_case_mgr_t *ucm;
+	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 is_free_running;
+	unsigned int filled_zeros_for_draining;
+	snd_pcm_uframes_t severe_underrun_frames;
+	struct cras_volume_curve *default_volume_curve;
 };
 
 static void init_device_settings(struct alsa_io *aio);
 
+static int alsa_iodev_set_active_node(struct cras_iodev *iodev,
+				      struct cras_ionode *ionode,
+				      unsigned dev_enabled);
+
+/*
+ * Defines the default values of nodes.
+ */
+static const struct {
+	const char *name;
+	enum CRAS_NODE_TYPE type;
+	enum CRAS_NODE_POSITION position;
+} node_defaults[] = {
+	{
+		.name = DEFAULT,
+		.type = CRAS_NODE_TYPE_UNKNOWN,
+		.position = NODE_POSITION_INTERNAL,
+	},
+	{
+		.name = INTERNAL_SPEAKER,
+		.type = CRAS_NODE_TYPE_INTERNAL_SPEAKER,
+		.position = NODE_POSITION_INTERNAL,
+	},
+	{
+		.name = INTERNAL_MICROPHONE,
+		.type = CRAS_NODE_TYPE_MIC,
+		.position = NODE_POSITION_INTERNAL,
+	},
+	{
+		.name = KEYBOARD_MIC,
+		.type = CRAS_NODE_TYPE_MIC,
+		.position = NODE_POSITION_KEYBOARD,
+	},
+	{
+		.name = HDMI,
+		.type = CRAS_NODE_TYPE_HDMI,
+		.position = NODE_POSITION_EXTERNAL,
+	},
+	{
+		.name = "IEC958",
+		.type = CRAS_NODE_TYPE_HDMI,
+		.position = NODE_POSITION_EXTERNAL,
+	},
+	{
+		.name = "Headphone",
+		.type = CRAS_NODE_TYPE_HEADPHONE,
+		.position = NODE_POSITION_EXTERNAL,
+	},
+	{
+		.name = "Front Headphone",
+		.type = CRAS_NODE_TYPE_HEADPHONE,
+		.position = NODE_POSITION_EXTERNAL,
+	},
+	{
+		.name = "Front Mic",
+		.type = CRAS_NODE_TYPE_MIC,
+		.position = NODE_POSITION_FRONT,
+	},
+	{
+		.name = "Rear Mic",
+		.type = CRAS_NODE_TYPE_MIC,
+		.position = NODE_POSITION_REAR,
+	},
+	{
+		.name = "Mic",
+		.type = CRAS_NODE_TYPE_MIC,
+		.position = NODE_POSITION_EXTERNAL,
+	},
+	{
+		.name = HOTWORD_DEV,
+		.type = CRAS_NODE_TYPE_HOTWORD,
+		.position = NODE_POSITION_INTERNAL,
+	},
+	{
+		.name = "Haptic",
+		.type = CRAS_NODE_TYPE_HAPTIC,
+		.position = NODE_POSITION_INTERNAL,
+	},
+	{
+		.name = "Rumbler",
+		.type = CRAS_NODE_TYPE_HAPTIC,
+		.position = NODE_POSITION_INTERNAL,
+	},
+	{
+		.name = "Line Out",
+		.type = CRAS_NODE_TYPE_LINEOUT,
+		.position = NODE_POSITION_EXTERNAL,
+	},
+};
+
 /*
  * iodev callbacks.
  */
 
-static int frames_queued(const struct cras_iodev *iodev)
+static int frames_queued(const struct cras_iodev *iodev,
+			 struct timespec *tstamp)
 {
 	struct alsa_io *aio = (struct alsa_io *)iodev;
 	int rc;
@@ -124,10 +257,17 @@
 
 	rc = cras_alsa_get_avail_frames(aio->handle,
 					aio->base.buffer_size,
-					&frames);
-	if (rc < 0)
+					aio->severe_underrun_frames,
+					iodev->info.name,
+					&frames, tstamp,
+					&aio->num_underruns);
+	if (rc < 0) {
+		if (rc == -EPIPE)
+			aio->num_severe_underruns++;
 		return rc;
-
+	}
+	if (!aio->enable_htimestamp)
+		clock_gettime(CLOCK_MONOTONIC_RAW, tstamp);
 	if (iodev->direction == CRAS_STREAM_INPUT)
 		return (int)frames;
 
@@ -154,18 +294,23 @@
 {
 	struct alsa_io *aio = (struct alsa_io *)iodev;
 
+	/* Removes audio thread callback from main thread. */
 	if (aio->poll_fd >= 0)
-		audio_thread_rm_callback(aio->poll_fd);
+		audio_thread_rm_callback_sync(
+				cras_iodev_list_get_audio_thread(),
+				aio->poll_fd);
 	if (!aio->handle)
 		return 0;
 	cras_alsa_pcm_close(aio->handle);
 	aio->handle = NULL;
+	aio->is_free_running = 0;
+	aio->filled_zeros_for_draining = 0;
 	cras_iodev_free_format(&aio->base);
 	cras_iodev_free_audio_area(&aio->base);
 	return 0;
 }
 
-static int dummy_aokr_cb(void *arg)
+static int dummy_hotword_cb(void *arg)
 {
 	/* Only need this once. */
 	struct alsa_io *aio = (struct alsa_io *)arg;
@@ -178,6 +323,7 @@
 {
 	struct alsa_io *aio = (struct alsa_io *)iodev;
 	snd_pcm_t *handle;
+	int period_wakeup;
 	int rc;
 
 	/* This is called after the first stream added so configure for it.
@@ -186,6 +332,11 @@
 	if (iodev->format == NULL)
 		return -EINVAL;
 	aio->num_underruns = 0;
+	aio->is_free_running = 0;
+	aio->filled_zeros_for_draining = 0;
+	aio->severe_underrun_frames =
+			SEVERE_UNDERRUN_MS * iodev->format->frame_rate / 1000;
+
 	cras_iodev_init_audio_area(iodev, iodev->format->num_channels);
 
 	syslog(LOG_DEBUG, "Configure alsa device %s rate %zuHz, %zu channels",
@@ -196,8 +347,12 @@
 	if (rc < 0)
 		return rc;
 
+	/* If it's a wake on voice device, period_wakeups are required. */
+	period_wakeup = (iodev->active_node->type == CRAS_NODE_TYPE_HOTWORD);
+
 	rc = cras_alsa_set_hwparams(handle, iodev->format,
-				    &iodev->buffer_size);
+				    &iodev->buffer_size, period_wakeup,
+				    aio->dma_period_set_microsecs);
 	if (rc < 0) {
 		cras_alsa_pcm_close(handle);
 		return rc;
@@ -212,7 +367,7 @@
 	}
 
 	/* Configure software params. */
-	rc = cras_alsa_set_swparams(handle);
+	rc = cras_alsa_set_swparams(handle, &aio->enable_htimestamp);
 	if (rc < 0) {
 		cras_alsa_pcm_close(handle);
 		return rc;
@@ -223,7 +378,7 @@
 	init_device_settings(aio);
 
 	aio->poll_fd = -1;
-	if (iodev->active_node->type == CRAS_NODE_TYPE_AOKR) {
+	if (iodev->active_node->type == CRAS_NODE_TYPE_HOTWORD) {
 		struct pollfd *ufds;
 		int count, i;
 
@@ -255,7 +410,8 @@
 		free(ufds);
 
 		if (aio->poll_fd >= 0)
-			audio_thread_add_callback(aio->poll_fd, dummy_aokr_cb,
+			audio_thread_add_callback(aio->poll_fd,
+						  dummy_hotword_cb,
 						  aio);
 	}
 
@@ -266,38 +422,45 @@
 	return 0;
 }
 
-static int is_open(const struct cras_iodev *iodev)
+/*
+ * Check if ALSA device is opened by checking if handle is valid.
+ * Note that to fully open a cras_iodev, ALSA device is opened first, then there
+ * are some device init settings to be done in init_device_settings.
+ * Therefore, when setting volume/mute/gain in init_device_settings,
+ * cras_iodev is not in CRAS_IODEV_STATE_OPEN yet. We need to check if handle
+ * is valid when setting those properties, instead of checking
+ * cras_iodev_is_open.
+ */
+static int has_handle(const struct alsa_io *aio)
 {
-	struct alsa_io *aio = (struct alsa_io *)iodev;
-
 	return !!aio->handle;
 }
 
-static int dev_running(const struct cras_iodev *iodev)
+static int start(const struct cras_iodev *iodev)
 {
 	struct alsa_io *aio = (struct alsa_io *)iodev;
 	snd_pcm_t *handle = aio->handle;
 	int rc;
 
 	if (snd_pcm_state(handle) == SND_PCM_STATE_RUNNING)
-		return 1;
+		return 0;
 
 	if (snd_pcm_state(handle) == SND_PCM_STATE_SUSPENDED) {
 		rc = cras_alsa_attempt_resume(handle);
 		if (rc < 0) {
 			syslog(LOG_ERR, "Resume error: %s", snd_strerror(rc));
-			return 0;
+			return rc;
 		}
 		cras_iodev_reset_rate_estimator(iodev);
 	} else {
 		rc = cras_alsa_pcm_start(handle);
 		if (rc < 0) {
 			syslog(LOG_ERR, "Start error: %s", snd_strerror(rc));
-			return 0;
+			return rc;
 		}
 	}
 
-	return 1;
+	return 0;
 }
 
 static int get_buffer(struct cras_iodev *iodev,
@@ -351,9 +514,10 @@
 	return 0;
 }
 
- /* Gets the first plugged node in list. This is used as the
-  * default node to set as active.
-  */
+/*
+ * Gets the first plugged node in list. This is used as the
+ * default node to set as active.
+ */
 static struct cras_ionode *first_plugged_node(struct cras_iodev *iodev)
 {
 	struct cras_ionode *n;
@@ -368,19 +532,21 @@
 	return iodev->nodes;
 }
 
-static void update_active_node(struct cras_iodev *iodev, unsigned node_idx)
+static void update_active_node(struct cras_iodev *iodev, unsigned node_idx,
+			       unsigned dev_enabled)
 {
 	struct cras_ionode *n;
 
 	/* If a node exists for node_idx, set it as active. */
 	DL_FOREACH(iodev->nodes, n) {
 		if (n->idx == node_idx) {
-			alsa_iodev_set_active_node(iodev, n);
+			alsa_iodev_set_active_node(iodev, n, dev_enabled);
 			return;
 		}
 	}
 
-	alsa_iodev_set_active_node(iodev, first_plugged_node(iodev));
+	alsa_iodev_set_active_node(iodev, first_plugged_node(iodev),
+				   dev_enabled);
 }
 
 static int update_channel_layout(struct cras_iodev *iodev)
@@ -390,6 +556,20 @@
 	snd_pcm_uframes_t buf_size = 0;
 	int err = 0;
 
+	/* If the capture channel map is specified in UCM, prefer it over
+	 * what ALSA provides. */
+	if (aio->ucm && (iodev->direction == CRAS_STREAM_INPUT)) {
+		struct alsa_input_node *input =
+			(struct alsa_input_node *)iodev->active_node;
+
+		if (input->channel_layout) {
+			memcpy(iodev->format->channel_layout,
+			       input->channel_layout,
+			       CRAS_CH_MAX * sizeof(*input->channel_layout));
+			return 0;
+		}
+	}
+
 	err = cras_alsa_pcm_open(&handle, aio->dev, aio->alsa_stream);
 	if (err < 0) {
 		syslog(LOG_ERR, "snd_pcm_open_failed: %s", snd_strerror(err));
@@ -398,7 +578,8 @@
 
 	/* Sets frame rate and channel count to alsa device before
 	 * we test channel mapping. */
-	err = cras_alsa_set_hwparams(handle, iodev->format, &buf_size);
+	err = cras_alsa_set_hwparams(handle, iodev->format, &buf_size, 0,
+				     aio->dma_period_set_microsecs);
 	if (err < 0) {
 		cras_alsa_pcm_close(handle);
 		return err;
@@ -410,6 +591,24 @@
 	return err;
 }
 
+static int set_hotword_model(struct cras_iodev *iodev, const char *model_name)
+{
+	struct alsa_io *aio = (struct alsa_io *)iodev;
+	if (!aio->ucm)
+		return -EINVAL;
+
+	return ucm_set_hotword_model(aio->ucm, model_name);
+}
+
+static char *get_hotword_models(struct cras_iodev *iodev)
+{
+	struct alsa_io *aio = (struct alsa_io *)iodev;
+	if (!aio->ucm)
+		return NULL;
+
+	return ucm_get_hotword_models(aio->ucm);
+}
+
 /*
  * Alsa helper functions.
  */
@@ -424,38 +623,38 @@
 	return (struct alsa_input_node *)aio->base.active_node;
 }
 
-/* Gets the curve for the active output. */
+/*
+ * Gets the curve for the active output node. If the node doesn't have volume
+ * curve specified, return the default volume curve of the parent iodev.
+ */
 static const struct cras_volume_curve *get_curve_for_output_node(
 		const struct alsa_io *aio,
-		const struct alsa_output_node *aout)
+		const struct alsa_output_node *node)
 {
-	struct cras_volume_curve *curve = NULL;
-	if (aout) {
-		curve = cras_alsa_mixer_get_output_volume_curve(
-				aout->mixer_output);
-		if (curve)
-			return curve;
-		else if (aout->jack_curve)
-			return aout->jack_curve;
-	}
-	return cras_alsa_mixer_default_volume_curve(aio->mixer);
+	if (node && node->volume_curve)
+		return node->volume_curve;
+	return aio->default_volume_curve;
 }
 
-/* Gets the curve for the active output. */
+/*
+ * Gets the curve for the active output.
+ */
 static const struct cras_volume_curve *get_curve_for_active_output(
 		const struct alsa_io *aio)
 {
-	struct alsa_output_node *aout = get_active_output(aio);
-	return get_curve_for_output_node(aio, aout);
+	struct alsa_output_node *node = get_active_output(aio);
+	return get_curve_for_output_node(aio, node);
 }
 
-/* Informs the system of the volume limits for this device. */
+/*
+ * Informs the system of the volume limits for this device.
+ */
 static void set_alsa_volume_limits(struct alsa_io *aio)
 {
 	const struct cras_volume_curve *curve;
 
 	/* Only set the limits if the dev is active. */
-	if (!is_open(&aio->base))
+	if (!has_handle(aio))
 		return;
 
 	curve = get_curve_for_active_output(aio);
@@ -464,12 +663,14 @@
 			curve->get_dBFS(curve, CRAS_MAX_SYSTEM_VOLUME));
 }
 
-/* Sets the alsa mute state for this iodev. */
-static void set_alsa_mute(const struct alsa_io *aio, int muted)
+/*
+ * Sets the alsa mute control for this iodev.
+ */
+static void set_alsa_mute_control(const struct alsa_io *aio, int muted)
 {
 	struct alsa_output_node *aout;
 
-	if (!is_open(&aio->base))
+	if (!has_handle(aio))
 		return;
 
 	aout = get_active_output(aio);
@@ -479,16 +680,16 @@
 		aout ? aout->mixer_output : NULL);
 }
 
-/* Sets the volume of the playback device to the specified level. Receives a
+/*
+ * Sets the volume of the playback device to the specified level. Receives a
  * volume index from the system settings, ranging from 0 to 100, converts it to
- * dB using the volume curve, and sends the dB value to alsa. Handles mute and
- * unmute, including muting when volume is zero. */
+ * dB using the volume curve, and sends the dB value to alsa.
+ */
 static void set_alsa_volume(struct cras_iodev *iodev)
 {
 	const struct alsa_io *aio = (const struct alsa_io *)iodev;
 	const struct cras_volume_curve *curve;
 	size_t volume;
-	int mute;
 	struct alsa_output_node *aout;
 
 	assert(aio);
@@ -496,11 +697,10 @@
 		return;
 
 	/* Only set the volume if the dev is active. */
-	if (!is_open(&aio->base))
+	if (!has_handle(aio))
 		return;
 
 	volume = cras_system_get_volume();
-	mute = cras_system_get_mute();
 	curve = get_curve_for_active_output(aio);
 	if (curve == NULL)
 		return;
@@ -517,13 +717,20 @@
 		aio->mixer,
 		curve->get_dBFS(curve, volume),
 		aout ? aout->mixer_output : NULL);
-	/* Mute for zero. */
-	set_alsa_mute(aio, mute || (volume == 0));
 }
 
-/* Sets the capture gain to the current system input gain level, given in dBFS.
+static void set_alsa_mute(struct cras_iodev *iodev)
+{
+	/* Mute for zero. */
+	const struct alsa_io *aio = (const struct alsa_io *)iodev;
+	set_alsa_mute_control(aio, cras_system_get_mute());
+}
+
+/*
+ * Sets the capture gain to the current system input gain level, given in dBFS.
  * Set mute based on the system mute state.  This gain can be positive or
- * negative and might be adjusted often if and app is running an AGC. */
+ * negative and might be adjusted often if an app is running an AGC.
+ */
 static void set_alsa_capture_gain(struct cras_iodev *iodev)
 {
 	const struct alsa_io *aio = (const struct alsa_io *)iodev;
@@ -535,13 +742,17 @@
 		return;
 
 	/* Only set the volume if the dev is active. */
-	if (!is_open(&aio->base))
+	if (!has_handle(aio))
 		return;
+	gain = cras_iodev_adjust_active_node_gain(
+				iodev, cras_system_get_capture_gain());
 
-	gain = cras_system_get_capture_gain();
+	/* Set hardware gain to 0dB if software gain is needed. */
+	if (cras_iodev_software_volume_needed(iodev))
+		gain = 0;
+
 	ain = get_active_input(aio);
-	if (ain)
-		gain += ain->base.capture_gain;
+
 	cras_alsa_mixer_set_capture_dBFS(
 			aio->mixer,
 			gain,
@@ -551,7 +762,9 @@
 					 ain ? ain->mixer_input : NULL);
 }
 
-/* Swaps the left and right channels of the given node. */
+/*
+ * Swaps the left and right channels of the given node.
+ */
 static int set_alsa_node_swapped(struct cras_iodev *iodev,
 				 struct cras_ionode *node, int enable)
 {
@@ -560,8 +773,10 @@
 	return ucm_enable_swap_mode(aio->ucm, node->name, enable);
 }
 
-/* Initializes the device settings and registers for callbacks when system
- * settings have been changed.
+/*
+ * Initializes the device settings according to system volume, mute, gain
+ * settings.
+ * Updates system capture gain limits based on current active device/node.
  */
 static void init_device_settings(struct alsa_io *aio)
 {
@@ -570,16 +785,29 @@
 	if (aio->base.direction == CRAS_STREAM_OUTPUT) {
 		set_alsa_volume_limits(aio);
 		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;
-		cras_system_set_capture_gain_limits(
-			cras_alsa_mixer_get_minimum_capture_gain(aio->mixer,
-								 mixer_input),
-			cras_alsa_mixer_get_maximum_capture_gain(aio->mixer,
-								 mixer_input));
+
+		if (cras_iodev_software_volume_needed(&aio->base)) {
+			min_capture_gain = DEFAULT_MIN_CAPTURE_GAIN;
+			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);
 	}
 }
@@ -588,7 +816,8 @@
  * Functions run in the main server context.
  */
 
-/* Frees resources used by the alsa iodev.
+/*
+ * Frees resources used by the alsa iodev.
  * Args:
  *    iodev - the iodev to free the resources from.
  */
@@ -604,7 +833,7 @@
 	DL_FOREACH(aio->base.nodes, node) {
 		if (aio->base.direction == CRAS_STREAM_OUTPUT) {
 			aout = (struct alsa_output_node *)node;
-			cras_volume_curve_destroy(aout->jack_curve);
+			cras_volume_curve_destroy(aout->volume_curve);
 		}
 		cras_iodev_rm_node(&aio->base, node);
 		free(node->softvol_scalers);
@@ -614,15 +843,23 @@
 	free((void *)aio->dsp_name_default);
 	cras_iodev_free_resources(&aio->base);
 	free(aio->dev);
+	if (aio->dev_id)
+		free(aio->dev_id);
+	if (aio->dev_name)
+		free(aio->dev_name);
 }
 
-/* Returns true if this is the first internal device */
+/*
+ * Returns true if this is the first internal device.
+ */
 static int first_internal_device(struct alsa_io *aio)
 {
 	return aio->is_first && aio->card_type == ALSA_CARD_TYPE_INTERNAL;
 }
 
-/* Returns true if there is already a node created with the given name */
+/*
+ * Returns true if there is already a node created with the given name.
+ */
 static int has_node(struct alsa_io *aio, const char *name)
 {
 	struct cras_ionode *node;
@@ -634,7 +871,9 @@
 	return 0;
 }
 
-/* Returns true if string s ends with the given suffix */
+/*
+ * Returns true if string s ends with the given suffix.
+ */
 int endswith(const char *s, const char *suffix)
 {
 	size_t n = strlen(s);
@@ -642,7 +881,9 @@
 	return n >= m && !strcmp(s + (n - m), suffix);
 }
 
-/* Drop the node name and replace it with node type.  */
+/*
+ * Drop the node name and replace it with node type.
+ */
 static void drop_node_name(struct cras_ionode *node)
 {
 	if (node->type == CRAS_NODE_TYPE_USB)
@@ -657,28 +898,14 @@
 	}
 }
 
-/* Sets the initial plugged state and type of a node based on its
+/*
+ * Sets the initial plugged state and type of a node based on its
  * name. Chrome will assign priority to nodes base on node type.
  */
 static void set_node_initial_state(struct cras_ionode *node,
 				   enum CRAS_ALSA_CARD_TYPE card_type)
 {
-	static const struct {
-		const char *name;
-		int initial_plugged;
-		enum CRAS_NODE_TYPE type;
-	} node_defaults[] = {
-		{ DEFAULT, 1, CRAS_NODE_TYPE_UNKNOWN},
-		{ INTERNAL_SPEAKER, 1, CRAS_NODE_TYPE_INTERNAL_SPEAKER },
-		{ INTERNAL_MICROPHONE, 1, CRAS_NODE_TYPE_INTERNAL_MIC },
-		{ KEYBOARD_MIC, 1, CRAS_NODE_TYPE_KEYBOARD_MIC },
-		{ HDMI, 0, CRAS_NODE_TYPE_HDMI },
-		{ "IEC958", 0, CRAS_NODE_TYPE_HDMI },
-		{ "Headphone", 0, CRAS_NODE_TYPE_HEADPHONE },
-		{ "Front Headphone", 0, CRAS_NODE_TYPE_HEADPHONE },
-		{ "Mic", 0, CRAS_NODE_TYPE_MIC },
-		{ AOKR_DEV, 1, CRAS_NODE_TYPE_AOKR },
-	};
+
 	unsigned i;
 
 	node->volume = 100;
@@ -687,7 +914,9 @@
 	for (i = 0; i < ARRAY_SIZE(node_defaults); i++)
 		if (!strncmp(node->name, node_defaults[i].name,
 			     strlen(node_defaults[i].name))) {
-			node->plugged = node_defaults[i].initial_plugged;
+			node->position = node_defaults[i].position;
+			node->plugged = (node->position
+					!= NODE_POSITION_EXTERNAL);
 			node->type = node_defaults[i].type;
 			if (node->plugged)
 				gettimeofday(&node->plugged_time, NULL);
@@ -715,40 +944,15 @@
 	/* Regardless of the node name of a USB headset (it can be "Speaker"),
 	 * set it's type to usb.
 	 */
-	if (card_type == ALSA_CARD_TYPE_USB)
+	if (card_type == ALSA_CARD_TYPE_USB) {
 		node->type = CRAS_NODE_TYPE_USB;
+		node->position = NODE_POSITION_EXTERNAL;
+	}
 
 	if (!is_utf8_string(node->name))
 		drop_node_name(node);
 }
 
-static const char *get_output_node_name(struct alsa_io *aio,
-	struct mixer_control *cras_output)
-{
-	if (cras_output)
-		return cras_alsa_mixer_get_control_name(cras_output);
-
-	if (first_internal_device(aio) && !has_node(aio, INTERNAL_SPEAKER)) {
-		if (strstr(aio->base.info.name, HDMI))
-			return HDMI;
-		return INTERNAL_SPEAKER;
-	} else {
-		return DEFAULT;
-	}
-}
-
-static const char *get_input_node_name(struct alsa_io *aio,
-	struct mixer_control *cras_input)
-{
-	if (cras_input)
-		return cras_alsa_mixer_get_control_name(cras_input);
-
-	if (first_internal_device(aio) && !has_node(aio, INTERNAL_MICROPHONE))
-		return INTERNAL_MICROPHONE;
-	else
-		return DEFAULT;
-}
-
 static int get_ucm_flag_integer(struct alsa_io *aio,
 				const char *flag_name,
 				int *result)
@@ -815,8 +1019,11 @@
 		return;
 	}
 
-	/* Use software volume for HDMI output */
-	if (output->base.type == CRAS_NODE_TYPE_HDMI)
+	/* Use software volume for HDMI output and nodes without volume mixer
+	 * control. */
+	if ((output->base.type == CRAS_NODE_TYPE_HDMI) ||
+	    (!cras_alsa_mixer_has_main_volume(mixer) &&
+	     !cras_alsa_mixer_has_volume(output->mixer_output)))
 		output->base.software_volume_needed = 1;
 
 	/* Use software volume if the usb device's volume range is smaller
@@ -833,101 +1040,241 @@
 		       output->base.name);
 }
 
-/* Callback for listing mixer outputs.  The mixer will call this once for each
- * output associated with this device.  Most commonly this is used to tell the
- * device it has Headphones and Speakers. */
-static void new_output(struct mixer_control *cras_output,
-		       void *callback_arg)
+static void set_input_node_software_volume_needed(
+	struct alsa_input_node *input, struct alsa_io *aio)
 {
-	struct alsa_io *aio;
-	struct alsa_output_node *output;
-	const char *name;
+	long max_software_gain;
+	int rc;
 
-	aio = (struct alsa_io *)callback_arg;
+	input->base.software_volume_needed = 0;
+	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 (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);
+}
+
+static void set_input_default_node_gain(struct alsa_input_node *input,
+					struct alsa_io *aio)
+{
+	long default_node_gain;
+	int rc;
+
+	if (!aio->ucm)
+		return;
+
+	rc = ucm_get_default_node_gain(aio->ucm, input->base.name,
+					 &default_node_gain);
+	if (rc)
+		return;
+
+	input->base.capture_gain = default_node_gain;
+}
+
+static void check_auto_unplug_output_node(struct alsa_io *aio,
+					  struct cras_ionode *node,
+					  int plugged)
+{
+	struct cras_ionode *tmp;
+
+	if (!auto_unplug_output_node(aio))
+		return;
+
+	/* Auto unplug internal speaker if any output node has been created */
+	if (!strcmp(node->name, INTERNAL_SPEAKER) && plugged) {
+		DL_FOREACH(aio->base.nodes, tmp)
+			if (tmp->plugged && (tmp != node))
+				cras_iodev_set_node_attr(node,
+							 IONODE_ATTR_PLUGGED,
+							 0);
+	} else {
+		DL_FOREACH(aio->base.nodes, tmp) {
+			if (!strcmp(tmp->name, INTERNAL_SPEAKER))
+				cras_iodev_set_node_attr(tmp,
+							 IONODE_ATTR_PLUGGED,
+							 !plugged);
+		}
+	}
+}
+
+/*
+ * Callback for listing mixer outputs. The mixer will call this once for each
+ * output associated with this device. Most commonly this is used to tell the
+ * device it has Headphones and Speakers.
+ */
+static struct alsa_output_node *new_output(struct alsa_io *aio,
+					   struct mixer_control *cras_output,
+					   const char *name)
+{
+	struct alsa_output_node *output;
+	syslog(LOG_DEBUG, "New output node for '%s'", name);
 	if (aio == NULL) {
 		syslog(LOG_ERR, "Invalid aio when listing outputs.");
-		return;
+		return NULL;
 	}
 	output = (struct alsa_output_node *)calloc(1, sizeof(*output));
 	if (output == NULL) {
 		syslog(LOG_ERR, "Out of memory when listing outputs.");
-		return;
+		return NULL;
 	}
 	output->base.dev = &aio->base;
 	output->base.idx = aio->next_ionode_index++;
+	output->base.stable_id = SuperFastHash(name,
+					       strlen(name),
+					       aio->base.info.stable_id);
+	output->base.stable_id_new = SuperFastHash(name,
+						   strlen(name),
+						   aio->base.info.stable_id_new
+						   );
 	output->mixer_output = cras_output;
-	name = get_output_node_name(aio, cras_output);
+
+	/* Volume curve. */
+	output->volume_curve = cras_card_config_get_volume_curve_for_control(
+			aio->config,
+			name ? name
+			     : cras_alsa_mixer_get_control_name(cras_output));
+
 	strncpy(output->base.name, name, sizeof(output->base.name) - 1);
 	set_node_initial_state(&output->base, aio->card_type);
 	set_output_node_software_volume_needed(output, aio);
 
-	/* Auto unplug internal speaker if any output node has been created */
-	if (auto_unplug_output_node(aio) && !strcmp(name, INTERNAL_SPEAKER)) {
-		struct cras_ionode *tmp;
-		DL_FOREACH(aio->base.nodes, tmp)
-			if (tmp->plugged)
-				output->base.plugged = 0;
-	}
-
 	cras_iodev_add_node(&aio->base, &output->base);
+
+	check_auto_unplug_output_node(aio, &output->base, output->base.plugged);
+	return output;
 }
 
-static void _new_input(struct mixer_control *cras_input,
-		       const char *name,
-		       struct alsa_io *aio)
+static void new_output_by_mixer_control(struct mixer_control *cras_output,
+				        void *callback_arg)
+{
+	struct alsa_io *aio = (struct alsa_io *)callback_arg;
+	char node_name[CRAS_IODEV_NAME_BUFFER_SIZE];
+	const char *ctl_name;
+
+	ctl_name = cras_alsa_mixer_get_control_name(cras_output);
+	if (!ctl_name)
+	        return;
+
+	if (aio->card_type == ALSA_CARD_TYPE_USB) {
+		snprintf(node_name, sizeof(node_name), "%s: %s",
+			aio->base.info.name, ctl_name);
+		new_output(aio, cras_output, node_name);
+	} else {
+		new_output(aio, cras_output, ctl_name);
+	}
+}
+
+static void check_auto_unplug_input_node(struct alsa_io *aio,
+					 struct cras_ionode *node,
+					 int plugged)
+{
+	struct cras_ionode *tmp;
+	if (!auto_unplug_input_node(aio))
+		return;
+
+	/* Auto unplug internal mic if any input node has already
+	 * been created */
+	if (!strcmp(node->name, INTERNAL_MICROPHONE) && plugged) {
+		DL_FOREACH(aio->base.nodes, tmp)
+			if (tmp->plugged && (tmp != node))
+				cras_iodev_set_node_attr(node,
+							 IONODE_ATTR_PLUGGED,
+							 0);
+	} else {
+		DL_FOREACH(aio->base.nodes, tmp)
+			if (!strcmp(tmp->name, INTERNAL_MICROPHONE))
+				cras_iodev_set_node_attr(tmp,
+							 IONODE_ATTR_PLUGGED,
+							 !plugged);
+	}
+}
+
+static struct alsa_input_node *new_input(struct alsa_io *aio,
+		struct mixer_control *cras_input, const char *name)
 {
 	struct alsa_input_node *input;
 	char *mic_positions;
+	int err;
 
 	input = (struct alsa_input_node *)calloc(1, sizeof(*input));
 	if (input == NULL) {
 		syslog(LOG_ERR, "Out of memory when listing inputs.");
-		return;
+		return NULL;
 	}
 	input->base.dev = &aio->base;
 	input->base.idx = aio->next_ionode_index++;
+	input->base.stable_id = SuperFastHash(name,
+					      strlen(name),
+					      aio->base.info.stable_id);
+	input->base.stable_id_new = SuperFastHash(name,
+						  strlen(name),
+						  aio->base.info.stable_id_new);
 	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);
 
-	/* Check mic positions only for internal mic. */
-	if (aio->ucm && input->base.type == CRAS_NODE_TYPE_INTERNAL_MIC) {
-		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);
+	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));
+		err = ucm_get_capture_chmap_for_dev(aio->ucm, name,
+						    input->channel_layout);
+		if (err) {
+			free(input->channel_layout);
+			input->channel_layout = 0;
 		}
 	}
 
-	/* Auto unplug internal mic if any input node has already
-	 * been created */
-	if (auto_unplug_input_node(aio) && !strcmp(name, INTERNAL_MICROPHONE)) {
-		struct cras_ionode *tmp;
-		DL_FOREACH(aio->base.nodes, tmp)
-			if (tmp->plugged)
-				input->base.plugged = 0;
-	}
-
 	cras_iodev_add_node(&aio->base, &input->base);
+	check_auto_unplug_input_node(aio, &input->base,
+				     input->base.plugged);
+	return input;
 }
 
-static void new_input(struct mixer_control *cras_input,
-		      void *callback_arg)
+static void new_input_by_mixer_control(struct mixer_control *cras_input,
+				       void *callback_arg)
 {
-	struct alsa_io *aio;
-	const char* name;
-	aio = (struct alsa_io *)callback_arg;
-	name = get_input_node_name(aio, cras_input);
-	_new_input(cras_input, name, aio);
+	struct alsa_io *aio = (struct alsa_io *)callback_arg;
+	char node_name[CRAS_IODEV_NAME_BUFFER_SIZE];
+	const char *ctl_name = cras_alsa_mixer_get_control_name(cras_input);
+
+	if (aio->card_type == ALSA_CARD_TYPE_USB) {
+		snprintf(node_name , sizeof(node_name), "%s: %s",
+			 aio->base.info.name, ctl_name);
+		new_input(aio, cras_input, node_name);
+	} else {
+		new_input(aio, cras_input, ctl_name);
+	}
 }
 
-static void new_input_by_name(const char *name, struct alsa_io *aio)
-{
-	_new_input(NULL, name, aio);
-}
-
-/* Finds the output node associated with the jack. Returns NULL if not found. */
+/*
+ * Finds the output node associated with the jack. Returns NULL if not found.
+ */
 static struct alsa_output_node *get_output_node_from_jack(
 		struct alsa_io *aio, const struct cras_alsa_jack *jack)
 {
@@ -935,13 +1282,16 @@
 	struct cras_ionode *node = NULL;
 	struct alsa_output_node *aout = NULL;
 
-	mixer_output = cras_alsa_jack_get_mixer_output(jack);
-	if (mixer_output == NULL) {
-		/* no mixer output, search by node. */
-		DL_SEARCH_SCALAR_WITH_CAST(aio->base.nodes, node, aout,
-					   jack, jack);
+	/* Search by jack first. */
+	DL_SEARCH_SCALAR_WITH_CAST(aio->base.nodes, node, aout,
+				   jack, jack);
+	if (aout)
 		return aout;
-	}
+
+	/* Search by mixer control next. */
+	mixer_output = cras_alsa_jack_get_mixer_output(jack);
+	if (mixer_output == NULL)
+		return NULL;
 
 	DL_SEARCH_SCALAR_WITH_CAST(aio->base.nodes, node, aout,
 				   mixer_output, mixer_output);
@@ -967,9 +1317,11 @@
 	return ain;
 }
 
-/* Returns the dsp name specified in the ucm config. If there is a dsp
+/*
+ * Returns the dsp name specified in the ucm config. If there is a dsp
  * name specified for the jack of the active node, use that. Otherwise
- * use the default dsp name for the alsa_io device. */
+ * use the default dsp name for the alsa_io device.
+ */
 static const char *get_active_dsp_name(struct alsa_io *aio)
 {
 	struct cras_ionode *node = aio->base.active_node;
@@ -986,7 +1338,34 @@
 	return cras_alsa_jack_get_dsp_name(jack) ? : aio->dsp_name_default;
 }
 
-/* Callback that is called when an output jack is plugged or unplugged. */
+/*
+ * Creates volume curve for the node associated with given jack.
+ */
+static struct cras_volume_curve *create_volume_curve_for_jack(
+		const struct cras_card_config *config,
+		const struct cras_alsa_jack *jack)
+{
+	struct cras_volume_curve *curve;
+	const char *name;
+
+	/* Use jack's UCM device name as key to get volume curve. */
+	name = cras_alsa_jack_get_ucm_device(jack);
+	curve = cras_card_config_get_volume_curve_for_control(config, name);
+	if (curve)
+		return curve;
+
+	/* Use alsa jack's name as key to get volume curve. */
+	name = cras_alsa_jack_get_name(jack);
+	curve = cras_card_config_get_volume_curve_for_control(config, name);
+	if (curve)
+		return curve;
+
+	return NULL;
+}
+
+/*
+ * Callback that is called when an output jack is plugged or unplugged.
+ */
 static void jack_output_plug_event(const struct cras_alsa_jack *jack,
 				    int plugged,
 				    void *arg)
@@ -1000,37 +1379,42 @@
 
 	aio = (struct alsa_io *)arg;
 	node = get_output_node_from_jack(aio, jack);
+	jack_name = cras_alsa_jack_get_name(jack);
+	if (!strcmp(jack_name, "Speaker Phantom Jack"))
+		jack_name = INTERNAL_SPEAKER;
 
 	/* If there isn't a node for this jack, create one. */
 	if (node == NULL) {
-		node = (struct alsa_output_node *)calloc(1, sizeof(*node));
-		if (node == NULL) {
-			syslog(LOG_ERR, "Out of memory creating jack node.");
+		if (aio->fully_specified) {
+			/* When fully specified, can't have new nodes. */
+			syslog(LOG_ERR, "No matching output node for jack %s!",
+			       jack_name);
 			return;
 		}
-		node->base.dev = &aio->base;
-		node->base.idx = aio->next_ionode_index++;
-		jack_name = cras_alsa_jack_get_name(jack);
-		node->jack_curve = cras_alsa_mixer_create_volume_curve_for_name(
-				aio->mixer, jack_name);
-		node->jack = jack;
-		/* Speaker phantom jack is actually for internal speaker. */
-		if (!strcmp(jack_name, "Speaker Phantom Jack"))
-			jack_name = INTERNAL_SPEAKER;
-		strncpy(node->base.name, jack_name,
-			sizeof(node->base.name) - 1);
-		set_node_initial_state(&node->base, aio->card_type);
-		set_output_node_software_volume_needed(node, aio);
+		node = new_output(aio, NULL, jack_name);
+		if (node == NULL)
+			return;
+
 		cras_alsa_jack_update_node_type(jack, &(node->base.type));
-		cras_iodev_add_node(&aio->base, &node->base);
-	} else if (!node->jack) {
-		/* If we already have the node, associate with the jack. */
-		jack_name = cras_alsa_jack_get_name(jack);
-		node->jack_curve = cras_alsa_mixer_create_volume_curve_for_name(
-				aio->mixer, jack_name);
-		node->jack = jack;
 	}
 
+	if (!node->jack) {
+		if (aio->fully_specified)
+			syslog(LOG_ERR,
+			       "Jack '%s' was found to match output node '%s'."
+			       " Please fix your UCM configuration to match.",
+			       jack_name, node->base.name);
+
+		/* If we already have the node, associate with the jack. */
+		node->jack = jack;
+		if (node->volume_curve == NULL)
+			node->volume_curve = create_volume_curve_for_jack(
+					aio->config, jack);
+	}
+
+	syslog(LOG_DEBUG, "%s plugged: %d, %s", jack_name, plugged,
+	       cras_alsa_mixer_get_control_name(node->mixer_output));
+
 	cras_alsa_jack_update_monitor_name(jack, node->base.name,
 					   sizeof(node->base.name));
 	/* The name got from jack might be an invalid UTF8 string. */
@@ -1039,66 +1423,63 @@
 
 	cras_iodev_set_node_attr(&node->base, IONODE_ATTR_PLUGGED, plugged);
 
-	if (auto_unplug_output_node(aio)) {
-		struct cras_ionode *tmp;
-		DL_FOREACH(aio->base.nodes, tmp) {
-			if (!strcmp(tmp->name, INTERNAL_SPEAKER))
-				cras_iodev_set_node_attr(tmp,
-							 IONODE_ATTR_PLUGGED,
-							 !plugged);
-		}
-	}
+	check_auto_unplug_output_node(aio, &node->base, plugged);
 }
 
-/* Callback that is called when an input jack is plugged or unplugged. */
+/*
+ * Callback that is called when an input jack is plugged or unplugged.
+ */
 static void jack_input_plug_event(const struct cras_alsa_jack *jack,
 				  int plugged,
 				  void *arg)
 {
 	struct alsa_io *aio;
 	struct alsa_input_node *node;
+	struct mixer_control *cras_input;
 	const char *jack_name;
 
 	if (arg == NULL)
 		return;
 	aio = (struct alsa_io *)arg;
 	node = get_input_node_from_jack(aio, jack);
+	jack_name = cras_alsa_jack_get_name(jack);
 
 	/* If there isn't a node for this jack, create one. */
 	if (node == NULL) {
-		node = (struct alsa_input_node *)calloc(1, sizeof(*node));
-		if (node == NULL) {
-			syslog(LOG_ERR, "Out of memory creating jack node.");
+		if (aio->fully_specified) {
+			/* When fully specified, can't have new nodes. */
+			syslog(LOG_ERR, "No matching input node for jack %s!",
+			       jack_name);
 			return;
 		}
-		node->base.dev = &aio->base;
-		node->base.idx = aio->next_ionode_index++;
-		jack_name = cras_alsa_jack_get_name(jack);
-		node->jack = jack;
-		node->mixer_input = cras_alsa_jack_get_mixer_input(jack);
-		strncpy(node->base.name, jack_name,
-			sizeof(node->base.name) - 1);
-		set_node_initial_state(&node->base, aio->card_type);
-		cras_iodev_add_node(&aio->base, &node->base);
-	} else if (!node->jack) {
-		/* If we already have the node, associate with the jack. */
+		cras_input = cras_alsa_jack_get_mixer_input(jack);
+		node = new_input(aio, cras_input, jack_name);
+		if (node == NULL)
+			return;
+	}
+
+	syslog(LOG_DEBUG, "%s plugged: %d, %s", jack_name, plugged,
+	       cras_alsa_mixer_get_control_name(node->mixer_input));
+
+	/* If we already have the node, associate with the jack. */
+	if (!node->jack) {
+		if (aio->fully_specified)
+			syslog(LOG_ERR,
+			       "Jack '%s' was found to match input node '%s'."
+			       " Please fix your UCM configuration to match.",
+			       jack_name, node->base.name);
 		node->jack = jack;
 	}
 
 	cras_iodev_set_node_attr(&node->base, IONODE_ATTR_PLUGGED, plugged);
 
-	if (auto_unplug_input_node(aio)) {
-		struct cras_ionode *tmp;
-		DL_FOREACH(aio->base.nodes, tmp)
-			if (!strcmp(tmp->name, INTERNAL_MICROPHONE))
-				cras_iodev_set_node_attr(tmp,
-							 IONODE_ATTR_PLUGGED,
-							 !plugged);
-	}
+	check_auto_unplug_input_node(aio, &node->base, plugged);
 }
 
-/* Sets the name of the given iodev, using the name and index of the card
- * combined with the device index and direction */
+/*
+ * Sets the name of the given iodev, using the name and index of the card
+ * combined with the device index and direction.
+ */
 static void set_iodev_name(struct cras_iodev *dev,
 			   const char *card_name,
 			   const char *dev_name,
@@ -1106,7 +1487,8 @@
 			   size_t device_index,
 			   enum CRAS_ALSA_CARD_TYPE card_type,
 			   size_t usb_vid,
-			   size_t usb_pid)
+			   size_t usb_pid,
+			   char *usb_serial_number)
 {
 	snprintf(dev->info.name,
 		 sizeof(dev->info.name),
@@ -1117,18 +1499,20 @@
 		 device_index);
 	dev->info.name[ARRAY_SIZE(dev->info.name) - 1] = '\0';
 	syslog(LOG_DEBUG, "Add device name=%s", dev->info.name);
-	dev->info.stable_id = SuperFastHash(dev->info.name,
-					    strlen(dev->info.name),
-					    strlen(dev->info.name));
+
+	dev->info.stable_id = SuperFastHash(card_name,
+					    strlen(card_name),
+					    strlen(card_name));
+	dev->info.stable_id = SuperFastHash(dev_name,
+					    strlen(dev_name),
+					    dev->info.stable_id);
 
 	switch (card_type) {
 	case ALSA_CARD_TYPE_INTERNAL:
-		dev->info.stable_id = SuperFastHash((const char *)&card_index,
-						    sizeof(card_index),
-						    dev->info.stable_id);
 		dev->info.stable_id = SuperFastHash((const char *)&device_index,
 						    sizeof(device_index),
 						    dev->info.stable_id);
+		dev->info.stable_id_new = dev->info.stable_id;
 		break;
 	case ALSA_CARD_TYPE_USB:
 		dev->info.stable_id = SuperFastHash((const char *)&usb_vid,
@@ -1137,16 +1521,46 @@
 		dev->info.stable_id = SuperFastHash((const char *)&usb_pid,
 						    sizeof(usb_pid),
 						    dev->info.stable_id);
+		dev->info.stable_id_new =
+			SuperFastHash(usb_serial_number,
+				      strlen(usb_serial_number),
+				      dev->info.stable_id);
+		break;
+	default:
+		dev->info.stable_id_new = dev->info.stable_id;
 		break;
 	}
-	syslog(LOG_DEBUG, "Stable ID=%08x", dev->info.stable_id);
+	syslog(LOG_DEBUG, "Stable ID=%08x, New Stable ID=%08x",
+	       dev->info.stable_id, dev->info.stable_id_new);
 }
 
-/* Updates the supported sample rates and channel counts. */
+static int get_fixed_rate(struct alsa_io *aio)
+{
+	const char *name;
+
+	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;
+	}
+
+	return ucm_get_sample_rate_for_dev(aio->ucm, name, aio->base.direction);
+}
+
+/*
+ * Updates the supported sample rates and channel counts.
+ */
 static int update_supported_formats(struct cras_iodev *iodev)
 {
 	struct alsa_io *aio = (struct alsa_io *)iodev;
 	int err;
+	int fixed_rate;
 
 	free(iodev->supported_rates);
 	iodev->supported_rates = NULL;
@@ -1159,10 +1573,26 @@
 					&iodev->supported_rates,
 					&iodev->supported_channel_counts,
 					&iodev->supported_formats);
-	return err;
+	if (err)
+		return err;
+
+	if (aio->ucm) {
+		/* Allow UCM to override supplied rates. */
+		fixed_rate = get_fixed_rate(aio);
+		if (fixed_rate > 0) {
+			free(iodev->supported_rates);
+			iodev->supported_rates = (size_t*)malloc(
+					2 * sizeof(iodev->supported_rates[0]));
+			iodev->supported_rates[0] = fixed_rate;
+			iodev->supported_rates[1] = 0;
+		}
+	}
+	return 0;
 }
 
-/* Builds software volume scalers for output nodes in the device. */
+/*
+ * Builds software volume scalers for output nodes in the device.
+ */
 static void build_softvol_scalers(struct alsa_io *aio)
 {
 	struct cras_ionode *ionode;
@@ -1178,6 +1608,190 @@
 	}
 }
 
+static void enable_active_ucm(struct alsa_io *aio, int plugged)
+{
+	const struct cras_alsa_jack *jack;
+	const char *name;
+
+	if (aio->base.direction == CRAS_STREAM_OUTPUT) {
+		struct alsa_output_node *active = get_active_output(aio);
+		if (!active)
+			return;
+		name = active->base.name;
+		jack = active->jack;
+	} else {
+		struct alsa_input_node *active = get_active_input(aio);
+		if (!active)
+			return;
+		name = active->base.name;
+		jack = active->jack;
+	}
+
+	if (jack)
+		cras_alsa_jack_enable_ucm(jack, plugged);
+	else if (aio->ucm)
+		ucm_set_enabled(aio->ucm, name, plugged);
+}
+
+static int fill_whole_buffer_with_zeros(struct cras_iodev *iodev)
+{
+	struct alsa_io *aio = (struct alsa_io *)iodev;
+	int rc;
+	uint8_t *dst = NULL;
+	size_t format_bytes;
+
+	/* Fill whole buffer with zeros. */
+	rc = cras_alsa_mmap_get_whole_buffer(
+			aio->handle, &dst, &aio->num_underruns);
+
+	if (rc < 0) {
+		syslog(LOG_ERR, "Failed to get whole buffer: %s",
+		       snd_strerror(rc));
+		return rc;
+	}
+
+	format_bytes = cras_get_format_bytes(iodev->format);
+	memset(dst, 0, iodev->buffer_size * format_bytes);
+
+	return 0;
+}
+
+static int adjust_appl_ptr(struct cras_iodev *odev)
+{
+	struct alsa_io *aio = (struct alsa_io *)odev;
+
+	/* Move appl_ptr to min_buffer_level + min_cb_level frames ahead of
+	 * hw_ptr when resuming from free run or adjusting appl_ptr from
+	 * underrun. */
+	return cras_alsa_resume_appl_ptr(
+			aio->handle,
+			odev->min_buffer_level + odev->min_cb_level);
+}
+
+static int alsa_output_underrun(struct cras_iodev *odev)
+{
+	int rc;
+	/* Fill whole buffer with zeros. This avoids samples left in buffer causing
+	 * noise when device plays them. */
+	rc = fill_whole_buffer_with_zeros(odev);
+	if (rc)
+		return rc;
+	/* Adjust appl_ptr to leave underrun. */
+	return adjust_appl_ptr(odev);
+}
+
+static int possibly_enter_free_run(struct cras_iodev *odev)
+{
+	struct alsa_io *aio = (struct alsa_io *)odev;
+	int rc;
+	unsigned int hw_level, fr_to_write;
+	unsigned int target_hw_level = odev->min_cb_level * 2;
+	struct timespec hw_tstamp;
+
+	if (aio->is_free_running)
+		return 0;
+
+	/* Check if all valid samples are played.
+	 * If all valid samples are played, fill whole buffer with zeros. */
+	rc = cras_iodev_frames_queued(odev, &hw_tstamp);
+	if (rc < 0)
+		return rc;
+	hw_level = rc;
+
+	if (hw_level < aio->filled_zeros_for_draining || hw_level == 0) {
+		rc = fill_whole_buffer_with_zeros(odev);
+		if (rc < 0)
+			return rc;
+		aio->is_free_running = 1;
+		return 0;
+	}
+
+	/* Fill some zeros to drain valid samples. */
+	fr_to_write = cras_iodev_buffer_avail(odev, hw_level);
+
+	if (hw_level <= target_hw_level) {
+		fr_to_write = MIN(target_hw_level - hw_level, fr_to_write);
+		rc = cras_iodev_fill_odev_zeros(odev, fr_to_write);
+		if (rc)
+			return rc;
+		aio->filled_zeros_for_draining += fr_to_write;
+	}
+
+	return 0;
+}
+
+static int leave_free_run(struct cras_iodev *odev)
+{
+	struct alsa_io *aio = (struct alsa_io *)odev;
+	int rc;
+
+	if (!aio->is_free_running)
+		return 0;
+
+	rc = adjust_appl_ptr(odev);
+	if (rc) {
+		syslog(LOG_ERR, "device %s failed to leave free run, rc = %d",
+		       odev->info.name, rc);
+		return rc;
+	}
+	aio->is_free_running = 0;
+	aio->filled_zeros_for_draining = 0;
+
+	return 0;
+}
+
+/*
+ * Free run state is the optimization of no_stream playback on alsa_io.
+ * The whole buffer will be filled with zeros. Device can play these zeros
+ * indefinitely. When there is new meaningful sample, appl_ptr should be
+ * resumed to some distance ahead of hw_ptr.
+ */
+static int no_stream(struct cras_iodev *odev, int enable)
+{
+	if (enable)
+		return possibly_enter_free_run(odev);
+	else
+		return leave_free_run(odev);
+}
+
+static int output_should_wake(const struct cras_iodev *odev)
+{
+	struct alsa_io *aio = (struct alsa_io *)odev;
+	if (aio->is_free_running)
+		return 0;
+	else
+		return ((cras_iodev_state(odev) ==
+					CRAS_IODEV_STATE_NO_STREAM_RUN) ||
+		        (cras_iodev_state(odev) ==
+					CRAS_IODEV_STATE_NORMAL_RUN));
+}
+
+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;
+	return aio->num_severe_underruns;
+}
+
+static void set_default_hotword_model(struct cras_iodev *iodev)
+{
+	const char *default_model = "en_us";
+	cras_node_id_t node_id;
+
+	if (!iodev->active_node ||
+	     iodev->active_node->type != CRAS_NODE_TYPE_HOTWORD)
+		return;
+
+	node_id = cras_make_node_id(iodev->info.idx, iodev->active_node->idx);
+	/* This is a no-op if the default_model is not supported */
+	cras_iodev_list_set_hotword_model(node_id, default_model);
+}
+
 /*
  * Exported Interface.
  */
@@ -1190,14 +1804,16 @@
 				     enum CRAS_ALSA_CARD_TYPE card_type,
 				     int is_first,
 				     struct cras_alsa_mixer *mixer,
-				     snd_use_case_mgr_t *ucm,
+				     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,
-				     size_t usb_pid)
+				     size_t usb_pid,
+				     char *usb_serial_number)
 {
 	struct alsa_io *aio;
 	struct cras_iodev *iodev;
-	int err;
 
 	if (direction != CRAS_STREAM_INPUT && direction != CRAS_STREAM_OUTPUT)
 		return NULL;
@@ -1212,6 +1828,19 @@
 	aio->card_type = card_type;
 	aio->is_first = is_first;
 	aio->handle = NULL;
+	aio->num_severe_underruns = 0;
+	if (dev_name) {
+		aio->dev_name = strdup(dev_name);
+		if (!aio->dev_name)
+			goto cleanup_iodev;
+	}
+	if (dev_id) {
+		aio->dev_id = strdup(dev_id);
+		if (!aio->dev_id)
+			goto cleanup_iodev;
+	}
+	aio->is_free_running = 0;
+	aio->filled_zeros_for_draining = 0;
 	aio->dev = (char *)malloc(MAX_ALSA_DEV_NAME_LENGTH);
 	if (aio->dev == NULL)
 		goto cleanup_iodev;
@@ -1228,35 +1857,45 @@
 	} else {
 		aio->alsa_stream = SND_PCM_STREAM_PLAYBACK;
 		aio->base.set_volume = set_alsa_volume;
-		aio->base.set_mute = set_alsa_volume;
+		aio->base.set_mute = set_alsa_mute;
+		aio->base.output_underrun = alsa_output_underrun;
 	}
 	iodev->open_dev = open_dev;
 	iodev->close_dev = close_dev;
-	iodev->is_open = is_open;
 	iodev->update_supported_formats = update_supported_formats;
 	iodev->frames_queued = frames_queued;
 	iodev->delay_frames = delay_frames;
 	iodev->get_buffer = get_buffer;
 	iodev->put_buffer = put_buffer;
 	iodev->flush_buffer = flush_buffer;
-	iodev->dev_running = dev_running;
+	iodev->start = start;
 	iodev->update_active_node = update_active_node;
 	iodev->update_channel_layout = update_channel_layout;
+	iodev->set_hotword_model = set_hotword_model;
+	iodev->get_hotword_models = get_hotword_models;
+	iodev->no_stream = no_stream;
+	iodev->output_should_wake = output_should_wake;
+	iodev->get_num_underruns = get_num_underruns;
+	iodev->get_num_severe_underruns = get_num_severe_underruns;
+	iodev->set_swap_mode_for_node = cras_iodev_dsp_set_swap_mode_for_node;
+
 	if (card_type == ALSA_CARD_TYPE_USB)
 		iodev->min_buffer_level = USB_EXTRA_BUFFER_FRAMES;
 
-	err = cras_alsa_fill_properties(aio->dev, aio->alsa_stream,
-					&iodev->supported_rates,
-					&iodev->supported_channel_counts,
-					&iodev->supported_formats);
-	if (err < 0 || iodev->supported_rates[0] == 0 ||
-	    iodev->supported_channel_counts[0] == 0 ||
-	    iodev->supported_formats[0] == 0) {
-		syslog(LOG_ERR, "cras_alsa_fill_properties: %s", strerror(err));
+	iodev->ramp = cras_ramp_create();
+	if (iodev->ramp == NULL)
 		goto cleanup_iodev;
-	}
 
 	aio->mixer = mixer;
+	aio->config = config;
+	if (direction == CRAS_STREAM_OUTPUT) {
+		aio->default_volume_curve =
+				cras_card_config_get_volume_curve_for_control(
+						config, "Default");
+		if (aio->default_volume_curve == NULL)
+			aio->default_volume_curve =
+					cras_volume_curve_create_default();
+	}
 	aio->ucm = ucm;
 	if (ucm) {
 		unsigned int level;
@@ -1272,30 +1911,79 @@
 		level = ucm_get_min_buffer_level(ucm);
 		if (level && 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,
-		       card_type, usb_vid, usb_pid);
+		       card_type, usb_vid, usb_pid, usb_serial_number);
 
-	/* Create output nodes for mixer controls, such as Headphone
-	 * and Speaker, only for the first device. */
-	if (direction == CRAS_STREAM_OUTPUT && is_first)
-		cras_alsa_mixer_list_outputs(mixer, new_output, aio);
-	else if (direction == CRAS_STREAM_INPUT && is_first)
-		cras_alsa_mixer_list_inputs(mixer, new_input, aio);
-
-	/* Find any jack controls for this device. */
-	aio->jack_list = cras_alsa_jack_list_create(
+	aio->jack_list =
+		cras_alsa_jack_list_create(
 			card_index,
 			card_name,
 			device_index,
 			is_first,
 			mixer,
 			ucm,
+			hctl,
 			direction,
 			direction == CRAS_STREAM_OUTPUT ?
 				     jack_output_plug_event :
 				     jack_input_plug_event,
 			aio);
+	if (!aio->jack_list)
+		goto cleanup_iodev;
+
+	/* HDMI outputs don't have volume adjustment, do it in software. */
+	if (direction == CRAS_STREAM_OUTPUT && strstr(dev_name, HDMI))
+		iodev->software_volume_needed = 1;
+
+	/* Add this now so that cleanup of the iodev (in case of error or card
+	 * card removal will function as expected. */
+	if (direction == CRAS_STREAM_OUTPUT)
+		cras_iodev_list_add_output(&aio->base);
+	else
+		cras_iodev_list_add_input(&aio->base);
+	return &aio->base;
+
+cleanup_iodev:
+	free_alsa_iodev_resources(aio);
+	free(aio);
+	return NULL;
+}
+
+int alsa_iodev_legacy_complete_init(struct cras_iodev *iodev)
+{
+	struct alsa_io *aio = (struct alsa_io *)iodev;
+	const char *dev_name;
+	const char *dev_id;
+	enum CRAS_STREAM_DIRECTION direction;
+	int err;
+	int is_first;
+	struct cras_alsa_mixer *mixer;
+
+	if (!aio)
+		return -EINVAL;
+	direction = iodev->direction;
+	dev_name = aio->dev_name;
+	dev_id = aio->dev_id;
+	is_first = aio->is_first;
+	mixer = aio->mixer;
+
+	/* Create output nodes for mixer controls, such as Headphone
+	 * and Speaker, only for the first device. */
+	if (direction == CRAS_STREAM_OUTPUT && is_first)
+		cras_alsa_mixer_list_outputs(mixer,
+				new_output_by_mixer_control, aio);
+	else if (direction == CRAS_STREAM_INPUT && is_first)
+		cras_alsa_mixer_list_inputs(mixer,
+				new_input_by_mixer_control, aio);
+
+	err = cras_alsa_jack_list_find_jacks_by_name_matching(aio->jack_list);
+	if (err)
+		return err;
 
 	/* Create nodes for jacks that aren't associated with an
 	 * already existing node. Get an initial read of the jacks for
@@ -1309,50 +1997,131 @@
 	 * which really don't have an internal device. */
 	if ((direction == CRAS_STREAM_OUTPUT) &&
 			!no_create_default_output_node(aio)) {
-		if (!aio->base.nodes || (first_internal_device(aio) &&
-					 !has_node(aio, INTERNAL_SPEAKER) &&
-					 !has_node(aio, HDMI)))
-			new_output(NULL, aio);
+		if (first_internal_device(aio) &&
+		    !has_node(aio, INTERNAL_SPEAKER) &&
+		    !has_node(aio, HDMI)) {
+			if (strstr(aio->base.info.name, HDMI))
+				new_output(aio, NULL, HDMI);
+			else
+				new_output(aio, NULL, INTERNAL_SPEAKER);
+		} else if (!aio->base.nodes) {
+			new_output(aio, NULL, DEFAULT);
+		}
 	} else if ((direction == CRAS_STREAM_INPUT) &&
 			!no_create_default_input_node(aio)) {
 		if (first_internal_device(aio) &&
 		    !has_node(aio, INTERNAL_MICROPHONE))
-			new_input_by_name(INTERNAL_MICROPHONE, aio);
+			new_input(aio, NULL, INTERNAL_MICROPHONE);
 		else if (strstr(dev_name, KEYBOARD_MIC))
-			new_input_by_name(KEYBOARD_MIC, aio);
-		else if (dev_id && strstr(dev_id, AOKR_DEV))
-			new_input_by_name(AOKR_DEV, aio);
+			new_input(aio, NULL, KEYBOARD_MIC);
+		else if (dev_id && strstr(dev_id, HOTWORD_DEV))
+			new_input(aio, NULL, HOTWORD_DEV);
 		else if (!aio->base.nodes)
-			new_input_by_name(DEFAULT, aio);
+			new_input(aio, NULL, DEFAULT);
 	}
 
-	/* HDMI outputs don't have volume adjustment, do it in software. */
-	if (direction == CRAS_STREAM_OUTPUT && strstr(dev_name, HDMI))
-		iodev->software_volume_needed = 1;
-
 	/* Build software volume scalers. */
 	if (direction == CRAS_STREAM_OUTPUT)
 		build_softvol_scalers(aio);
 
 	/* Set the active node as the best node we have now. */
 	alsa_iodev_set_active_node(&aio->base,
-				   first_plugged_node(&aio->base));
-	if (direction == CRAS_STREAM_OUTPUT)
-		cras_iodev_list_add_output(&aio->base);
-	else
-		cras_iodev_list_add_input(&aio->base);
+				   first_plugged_node(&aio->base),
+				   0);
 
 	/* Set plugged for the first USB device per card when it appears. */
-	if (card_type == ALSA_CARD_TYPE_USB && is_first)
+	if (aio->card_type == ALSA_CARD_TYPE_USB && is_first)
 		cras_iodev_set_node_attr(iodev->active_node,
 					 IONODE_ATTR_PLUGGED, 1);
 
-	return &aio->base;
+	set_default_hotword_model(iodev);
 
-cleanup_iodev:
-	free_alsa_iodev_resources(aio);
-	free(aio);
-	return NULL;
+	return 0;
+}
+
+int alsa_iodev_ucm_add_nodes_and_jacks(struct cras_iodev *iodev,
+				       struct ucm_section *section)
+{
+	struct alsa_io *aio = (struct alsa_io *)iodev;
+	struct mixer_control *control;
+	struct alsa_input_node *input_node = NULL;
+	struct cras_alsa_jack *jack;
+	struct alsa_output_node *output_node = NULL;
+	int rc;
+
+	if (!aio || !section)
+		return -EINVAL;
+	if ((uint32_t)section->dev_idx != aio->device_index)
+		return -EINVAL;
+
+	/* This iodev is fully specified. Avoid automatic node creation. */
+	aio->fully_specified = 1;
+
+	/* Check here in case the DmaPeriodMicrosecs flag has only been
+	 * specified on one of many device entries with the same PCM. */
+	if (!aio->dma_period_set_microsecs)
+		aio->dma_period_set_microsecs =
+			ucm_get_dma_period_for_dev(aio->ucm, section->name);
+
+	/* Create a node matching this section. If there is a matching
+	 * control use that, otherwise make a node without a control. */
+	control = cras_alsa_mixer_get_control_for_section(aio->mixer, section);
+	if (iodev->direction == CRAS_STREAM_OUTPUT) {
+		output_node = new_output(aio, control, section->name);
+		if (!output_node)
+			return -ENOMEM;
+	} else if (iodev->direction == CRAS_STREAM_INPUT) {
+		input_node = new_input(aio, control, section->name);
+		if (!input_node)
+			return -ENOMEM;
+	}
+
+	/* Find any jack controls for this device. */
+	rc = cras_alsa_jack_list_add_jack_for_section(
+					aio->jack_list, section, &jack);
+	if (rc)
+		return rc;
+
+	/* Associated the jack with the node. */
+	if (jack) {
+		if (output_node) {
+			output_node->jack = jack;
+			if (!output_node->volume_curve)
+				output_node->volume_curve =
+					create_volume_curve_for_jack(
+						aio->config, jack);
+		} else if (input_node) {
+			input_node->jack = jack;
+		}
+	}
+	return 0;
+}
+
+void alsa_iodev_ucm_complete_init(struct cras_iodev *iodev)
+{
+	struct alsa_io *aio = (struct alsa_io *)iodev;
+
+	if (!iodev)
+		return;
+
+	/* Get an initial read of the jacks for this device. */
+	cras_alsa_jack_list_report(aio->jack_list);
+
+	/* Build software volume scaler. */
+	if (iodev->direction == CRAS_STREAM_OUTPUT)
+		build_softvol_scalers(aio);
+
+	/* Set the active node as the best node we have now. */
+	alsa_iodev_set_active_node(&aio->base,
+				   first_plugged_node(&aio->base),
+				   0);
+
+	/* Set plugged for the first USB device per card when it appears. */
+	if (aio->card_type == ALSA_CARD_TYPE_USB && aio->is_first)
+		cras_iodev_set_node_attr(iodev->active_node,
+					 IONODE_ATTR_PLUGGED, 1);
+
+	set_default_hotword_model(iodev);
 }
 
 void alsa_iodev_destroy(struct cras_iodev *iodev)
@@ -1373,9 +2142,22 @@
 
 	/* Free resources when device successfully removed. */
 	free_alsa_iodev_resources(aio);
+	cras_volume_curve_destroy(aio->default_volume_curve);
 	free(iodev);
 }
 
+unsigned alsa_iodev_index(struct cras_iodev *iodev)
+{
+	struct alsa_io *aio = (struct alsa_io *)iodev;
+	return aio->device_index;
+}
+
+int alsa_iodev_has_hctl_jacks(struct cras_iodev *iodev)
+{
+	struct alsa_io *aio = (struct alsa_io *)iodev;
+	return cras_alsa_jack_list_has_hctl_jacks(aio->jack_list);
+}
+
 static void alsa_iodev_unmute_node(struct alsa_io *aio,
 				   struct cras_ionode *ionode)
 {
@@ -1388,7 +2170,7 @@
 	 * active mixer output and mute all others, otherwise just set
 	 * the node as active and set the volume curve. */
 	if (mixer) {
-		set_alsa_mute(aio, 1);
+		set_alsa_mute_control(aio, 1);
 		/* Unmute the active mixer output, mute all others. */
 		DL_FOREACH(aio->base.nodes, node) {
 			output = (struct alsa_output_node *)node;
@@ -1399,35 +2181,27 @@
 	}
 }
 
-static void enable_jack_ucm(struct alsa_io *aio, int plugged)
-{
-	if (aio->base.direction == CRAS_STREAM_OUTPUT) {
-		struct alsa_output_node *active = get_active_output(aio);
-		if (active)
-			cras_alsa_jack_enable_ucm(active->jack, plugged);
-	} else {
-		struct alsa_input_node *active = get_active_input(aio);
-		if (active)
-			cras_alsa_jack_enable_ucm(active->jack, plugged);
-	}
-}
-
-int alsa_iodev_set_active_node(struct cras_iodev *iodev,
-			       struct cras_ionode *ionode)
+static int alsa_iodev_set_active_node(struct cras_iodev *iodev,
+				      struct cras_ionode *ionode,
+				      unsigned dev_enabled)
 {
 	struct alsa_io *aio = (struct alsa_io *)iodev;
 
-	if (iodev->active_node == ionode)
+	if (iodev->active_node == ionode) {
+		enable_active_ucm(aio, dev_enabled);
+		init_device_settings(aio);
 		return 0;
+	}
 
-	enable_jack_ucm(aio, 0);
-	if (iodev->direction == CRAS_STREAM_OUTPUT)
+	/* Disable jack ucm before switching node. */
+	enable_active_ucm(aio, 0);
+	if (dev_enabled && (iodev->direction == CRAS_STREAM_OUTPUT))
 		alsa_iodev_unmute_node(aio, ionode);
 
 	cras_iodev_set_active_node(iodev, ionode);
 	aio->base.dsp_name = get_active_dsp_name(aio);
 	cras_iodev_update_dsp(iodev);
-	enable_jack_ucm(aio, 1);
+	enable_active_ucm(aio, dev_enabled);
 	/* 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 561516f..0b3e548 100644
--- a/cras/src/server/cras_alsa_io.h
+++ b/cras/src/server/cras_alsa_io.h
@@ -7,12 +7,14 @@
 #define CRAS_ALSA_IO_H_
 
 #include <alsa/asoundlib.h>
-#include <alsa/use-case.h>
 
+#include "cras_card_config.h"
 #include "cras_types.h"
 
 struct cras_alsa_mixer;
 struct cras_ionode;
+struct cras_use_case_mgr;
+struct ucm_section;
 
 /* Initializes an alsa iodev.
  * Args:
@@ -24,10 +26,13 @@
  *    card_type - the type of the card this iodev belongs.
  *    is_first - if this is the first iodev on the card.
  *    mixer - The mixer for the alsa device.
- *    ucm - ALSA use case manager if available.
- *    direciton - input or output.
+ *    config - Card config for this alsa device.
+ *    ucm - CRAS use case manager if available.
+ *    hctl - high-level control manager if available.
+ *    direction - input or output.
  *    usb_vid - vendor ID of USB device.
  *    usb_pid - product ID of USB device.
+ *    usb_serial_number - serial number of USB device.
  * Returns:
  *    A pointer to the newly created iodev if successful, NULL otherwise.
  */
@@ -39,30 +44,50 @@
 				     enum CRAS_ALSA_CARD_TYPE card_type,
 				     int is_first,
 				     struct cras_alsa_mixer *mixer,
-				     snd_use_case_mgr_t *ucm,
+				     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,
-				     size_t usb_pid);
+				     size_t usb_pid,
+				     char *usb_serial_number);
+
+/* Complete initializeation of this iodev with the legacy method.
+ * Add IO nodes and find jacks for this iodev with magic sauce, then choose
+ * the current active node.
+ * Args:
+ *    iodev - ALSA io device associated with the IO nodes.
+ *    section - UCM section information if available (or NULL).
+ * Returns:
+ *    0 for success, negative error code on error.
+ */
+int alsa_iodev_legacy_complete_init(struct cras_iodev *iodev);
+
+/* Add IO nodes and jacks for this iodev using UCM data.
+ * Args:
+ *    iodev - ALSA io device associated with the given section.
+ *    section - UCM section information.
+ * Returns:
+ *    0 for success, negative error code on error.
+ */
+int alsa_iodev_ucm_add_nodes_and_jacks(struct cras_iodev *iodev,
+				       struct ucm_section *section);
+
+/* Complete initialization of this iodev with fully-spec UCM data.
+ * After all UCM devices associated with the same iodev have been processed
+ * this is called to finish iodev setup.
+ * Args:
+ *    iodev - ALSA io device.
+ */
+void alsa_iodev_ucm_complete_init(struct cras_iodev *iodev);
 
 /* Destroys an alsa_iodev created with alsa_iodev_create. */
 void alsa_iodev_destroy(struct cras_iodev *iodev);
 
-/* Sets the active node of an alsa mixer.  Used to switch form Speaker to
- * Headphones or vice-versa.
- * Args:
- *    iodev - An iodev created with alsa_iodev_create.
- *    ionode - The node to activate.
- */
-int alsa_iodev_set_active_node(struct cras_iodev *iodev,
-			       struct cras_ionode *ionode);
+/* Returns the ALSA device index for the given ALSA iodev. */
+unsigned alsa_iodev_index(struct cras_iodev *iodev);
 
-/* Sets the active input of an alsa mixer.  Used to switch between different
- * Microphones.
- * Args:
- *    iodev - An iodev created with alsa_iodev_create.
- *    ionode - The input to activate.
- */
-int alsa_iodev_set_active_input(struct cras_iodev *iodev,
-				struct cras_ionode *ionode);
+/* Returns whether this IODEV has ALSA hctl jacks. */
+int alsa_iodev_has_hctl_jacks(struct cras_iodev *iodev);
 
 #endif /* CRAS_ALSA_IO_H_ */
diff --git a/cras/src/server/cras_alsa_jack.c b/cras/src/server/cras_alsa_jack.c
index 6f4275e..2f69e3b 100644
--- a/cras/src/server/cras_alsa_jack.c
+++ b/cras/src/server/cras_alsa_jack.c
@@ -5,6 +5,7 @@
 
 #include <alsa/asoundlib.h>
 #include <linux/input.h>
+#include <regex.h>
 #include <syslog.h>
 
 #include "cras_alsa_jack.h"
@@ -19,6 +20,7 @@
 
 static const unsigned int DISPLAY_INFO_RETRY_DELAY_MS = 200;
 static const unsigned int DISPLAY_INFO_MAX_RETRIES = 10;
+static const unsigned int DISPLAY_INFO_GPIO_MAX_RETRIES = 25;
 
 /* Constants used to retrieve monitor name from ELD buffer. */
 static const unsigned int ELD_MNL_MASK = 31;
@@ -96,12 +98,15 @@
 };
 
 /* Contains all Jacks for a given device.
- *    hctl - alsa hcontrol for this device.
+ *    hctl - alsa hcontrol for this device's card
+ *         - not opened by the jack list.
  *    mixer - cras mixer for the card providing this device.
+ *    ucm - CRAS use case manager if available.
+ *    card_index - Index ALSA uses to refer to the card.  The X in "hw:X".
+ *    card_name - The name of the card.
  *    device_index - Index ALSA uses to refer to the device.  The Y in "hw:X,Y".
  *    is_first_device - whether this device is the first device on the card.
- *    registered_fds - list of fds registered with system, to be removed upon
- *        destruction.
+ *    direction - Input or output.
  *    change_callback - function to call when the state of a jack changes.
  *    callback_data - data to pass back to the callback.
  *    jacks - list of jacks for this device.
@@ -109,15 +114,30 @@
 struct cras_alsa_jack_list {
 	snd_hctl_t *hctl;
 	struct cras_alsa_mixer *mixer;
-	snd_use_case_mgr_t *ucm;
+	struct cras_use_case_mgr *ucm;
+	unsigned int card_index;
+	const char *card_name;
 	size_t device_index;
 	int is_first_device;
-	struct jack_poll_fd *registered_fds;
+	enum CRAS_STREAM_DIRECTION direction;
 	jack_state_change_callback *change_callback;
 	void *callback_data;
 	struct cras_alsa_jack *jacks;
 };
 
+/* Used to contain information needed while looking through GPIO jacks.
+ *    jack_list - The current jack_list.
+ *    section - An associated UCM section.
+ *    result_jack - The resulting jack.
+ *    rc - The return code for the operation.
+ */
+struct gpio_switch_list_data {
+	struct cras_alsa_jack_list *jack_list;
+	struct ucm_section *section;
+	struct cras_alsa_jack *result_jack;
+	int rc;
+};
+
 /*
  * Local Helpers.
  */
@@ -130,7 +150,7 @@
 #define LONG(x)			((x) / BITS_PER_LONG)
 #define IS_BIT_SET(bit, array)	!!((array[LONG(bit)]) & (1UL << OFF(bit)))
 
-static unsigned sys_input_get_switch_state(int fd, unsigned sw, unsigned *state)
+static int sys_input_get_switch_state(int fd, unsigned sw, unsigned *state)
 {
 	unsigned long bits[NBITS(SW_CNT)];
 	const unsigned long switch_no = sw;
@@ -158,6 +178,39 @@
 	return jack;
 }
 
+static void cras_free_jack(struct cras_alsa_jack *jack,
+			   int rm_select_fd)
+{
+	if (!jack)
+		return;
+
+	free(jack->ucm_device);
+	free((void *)jack->edid_file);
+	if (jack->display_info_timer)
+		cras_tm_cancel_timer(cras_system_state_get_tm(),
+				     jack->display_info_timer);
+
+	if (jack->is_gpio) {
+		free(jack->gpio.device_name);
+		if (jack->gpio.fd >= 0) {
+			if (rm_select_fd)
+				cras_system_rm_select_fd(jack->gpio.fd);
+			close(jack->gpio.fd);
+		}
+	}
+
+	/*
+	 * Remove the jack callback set on hctl. Otherwise, snd_hctl_close will
+	 * trigger a callback while iodev might already be destroyed.
+	 */
+	if (!jack->is_gpio && jack->elem)
+		snd_hctl_elem_set_callback(jack->elem, NULL);
+
+	free((void *)jack->override_type_name);
+	free((void *)jack->dsp_name);
+	free(jack);
+}
+
 /* Gets the current plug state of the jack */
 static int get_jack_current_state(struct cras_alsa_jack *jack)
 {
@@ -251,8 +304,11 @@
 		cras_tm_cancel_timer(tm, jack->display_info_timer);
 		jack->display_info_timer = NULL;
 	}
-	if (retry)
-		jack->display_info_retries = DISPLAY_INFO_MAX_RETRIES;
+	if (retry) {
+		jack->display_info_retries =
+				jack->is_gpio ? DISPLAY_INFO_GPIO_MAX_RETRIES
+					      : DISPLAY_INFO_MAX_RETRIES;
+	}
 
 	if (!get_jack_current_state(jack))
 		goto report_jack_state;
@@ -295,8 +351,8 @@
 static void gpio_switch_initial_state(struct cras_alsa_jack *jack)
 {
 	unsigned v;
-	unsigned r = sys_input_get_switch_state(jack->gpio.fd,
-						jack->gpio.switch_event, &v);
+	int r = sys_input_get_switch_state(jack->gpio.fd,
+					   jack->gpio.switch_event, &v);
 	jack->gpio.current_state = r == 0 ? v : 0;
 	jack_state_change_cb(jack, 1);
 }
@@ -387,46 +443,118 @@
 	return rc;
 }
 
-
-/* open_and_monitor_gpio:
- *
- *   Opens a /dev/input/event file associated with a headphone /
- *   microphone jack and watches it for activity.
- *   Returns 0 when a jack has been successfully added.
- */
-static int open_and_monitor_gpio(struct cras_alsa_jack_list *jack_list,
-				 enum CRAS_STREAM_DIRECTION direction,
-				 const char *card_name,
-				 const char *pathname,
-				 unsigned switch_event)
+static int create_jack_for_gpio(struct cras_alsa_jack_list *jack_list,
+				const char *pathname,
+				const char *dev_name,
+				unsigned switch_event,
+				struct cras_alsa_jack **out_jack)
 {
 	struct cras_alsa_jack *jack;
 	unsigned long bits[NBITS(SW_CNT)];
+	const char *card_name = jack_list->card_name;
 	int r;
 
+	if (!out_jack)
+		return -EINVAL;
+	*out_jack = NULL;
+
 	jack = cras_alloc_jack(1);
 	if (jack == NULL)
 		return -ENOMEM;
 
 	jack->gpio.fd = gpio_switch_open(pathname);
 	if (jack->gpio.fd == -1) {
-		free(jack);
-		return -EIO;
+		r = -EIO;
+		goto error;
 	}
 
 	jack->gpio.switch_event = switch_event;
 	jack->jack_list = jack_list;
-	jack->gpio.device_name = sys_input_get_device_name(pathname);
+	jack->gpio.device_name = strdup(dev_name);
+	if (!jack->gpio.device_name) {
+		r = -ENOMEM;
+		goto error;
+	}
 
-	if (!jack->gpio.device_name ||
-	    !strstr(jack->gpio.device_name, card_name) ||
+	if (!strstr(jack->gpio.device_name, card_name) ||
 	    (gpio_switch_eviocgbit(jack->gpio.fd, bits, sizeof(bits)) < 0) ||
 	    !IS_BIT_SET(switch_event, bits)) {
-		close(jack->gpio.fd);
-		free(jack->gpio.device_name);
-		free(jack);
+		r = -EIO;
+		goto error;
+	}
+
+	*out_jack = jack;
+	return 0;
+
+error:
+	/* Not yet registered with system select. */
+	cras_free_jack(jack, 0);
+	return r;
+}
+
+/* Take ownership and finish setup of the jack.
+ * Add the jack to the jack_list if everything goes well, or destroy it.
+ */
+static int cras_complete_gpio_jack(struct gpio_switch_list_data *data,
+				   struct cras_alsa_jack *jack,
+				   unsigned switch_event)
+{
+	struct cras_alsa_jack_list *jack_list = data->jack_list;
+	enum CRAS_STREAM_DIRECTION direction = jack_list->direction;
+	int r;
+
+	if (jack->ucm_device) {
+		jack->edid_file = ucm_get_edid_file_for_dev(jack_list->ucm,
+							    jack->ucm_device);
+		jack->dsp_name = ucm_get_dsp_name(
+			jack->jack_list->ucm, jack->ucm_device, direction);
+	}
+
+	r = sys_input_get_switch_state(jack->gpio.fd, switch_event,
+				       &jack->gpio.current_state);
+	if (r < 0) {
+		cras_free_jack(jack, 0);
 		return -EIO;
 	}
+	r = cras_system_add_select_fd(jack->gpio.fd,
+				      gpio_switch_callback, jack);
+	if (r < 0) {
+		/* Not yet registered with system select. */
+		cras_free_jack(jack, 0);
+		return r;
+	}
+
+	DL_APPEND(jack_list->jacks, jack);
+	if (!data->result_jack)
+		data->result_jack = jack;
+	else if (data->section)
+		syslog(LOG_ERR,
+		       "More than one jack for SectionDevice '%s'.",
+		       data->section->name);
+	return 0;
+}
+
+/* open_and_monitor_gpio:
+ *
+ *   Opens a /dev/input/event file associated with a headphone /
+ *   microphone jack and watches it for activity.
+ *   Returns 0 when a jack has been successfully added.
+ */
+static int open_and_monitor_gpio(struct gpio_switch_list_data *data,
+				 const char *pathname,
+				 const char *dev_name,
+				 unsigned switch_event)
+{
+	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;
+
+	r = create_jack_for_gpio(jack_list, pathname, dev_name,
+				 switch_event, &jack);
+	if (r != 0)
+		return r;
 
 	if (jack_list->ucm)
 		jack->ucm_device =
@@ -435,15 +563,10 @@
 					     direction);
 
 	if (!gpio_jack_match_device(jack, jack_list, card_name, direction)) {
-		close(jack->gpio.fd);
-		free(jack->gpio.device_name);
-		free(jack);
+		cras_free_jack(jack, 0);
 		return -EIO;
 	}
 
-
-	DL_APPEND(jack_list->jacks, jack);
-
 	if (direction == CRAS_STREAM_OUTPUT &&
 	    (strstr(jack->gpio.device_name, "Headphone") ||
 	     strstr(jack->gpio.device_name, "Headset")))
@@ -456,10 +579,6 @@
 			jack_list->mixer,
 			"HDMI");
 
-	if (jack->ucm_device)
-		jack->edid_file = ucm_get_edid_file_for_dev(jack_list->ucm,
-							    jack->ucm_device);
-
 	if (jack->ucm_device && direction == CRAS_STREAM_INPUT) {
 		char *control_name;
 		control_name = ucm_get_cap_control(jack->jack_list->ucm,
@@ -471,100 +590,202 @@
 					control_name);
 	}
 
-        if (jack->ucm_device) {
-                jack->dsp_name = ucm_get_dsp_name(
-                        jack->jack_list->ucm, jack->ucm_device, direction);
-        }
-
-	sys_input_get_switch_state(jack->gpio.fd, switch_event,
-				   &jack->gpio.current_state);
-	r = cras_system_add_select_fd(jack->gpio.fd,
-				      gpio_switch_callback, jack);
-	return r;
+	return cras_complete_gpio_jack(data, jack, switch_event);
 }
 
-static int wait_for_dev_input_access()
+static int open_and_monitor_gpio_with_section(
+			struct gpio_switch_list_data *data,
+			const char *pathname,
+			unsigned switch_event)
 {
-	/* Wait for /dev/input/event* files to become accessible by
-	 * having group 'input'.  Setting these files to have 'rw'
-	 * access to group 'input' is done through a udev rule
-	 * installed by adhd into /lib/udev/rules.d.
-	 *
-	 * Wait for up to 2 seconds for the /dev/input/event* files to be
-	 * readable by gavd.
-	 *
-	 * TODO(thutt): This could also be done with a udev enumerate
-	 *              and then a udev monitor.
-	 */
-	const unsigned max_iterations = 4;
-	unsigned i = 0;
+	struct cras_alsa_jack *jack;
+	struct cras_alsa_jack_list *jack_list = data->jack_list;
+	struct ucm_section *section = data->section;
+	enum CRAS_STREAM_DIRECTION direction = jack_list->direction;
+	int r;
 
-	while (i < max_iterations) {
-		int		   readable;
-		struct timeval	   timeout;
-		const char * const pathname = "/dev/input/event0";
+	r = create_jack_for_gpio(jack_list, pathname, section->jack_name,
+				 switch_event, &jack);
+	if (r != 0)
+		return r;
 
-		timeout.tv_sec	= 0;
-		timeout.tv_usec = 500000;   /* 1/2 second. */
-		readable = access(pathname, O_RDONLY);
-
-		/* If the file could be opened, then the udev rule has been
-		 * applied and gavd can read the event files.  If there are no
-		 * event files, then we don't need to wait.
-		 *
-		 * If access does not become available, then headphone &
-		 * microphone jack autoswitching will not function properly.
-		 */
-		if (readable == 0 || (readable == -1 && errno == ENOENT)) {
-			/* Access allowed, or file does not exist. */
-			break;
-		}
-		if (readable != -1 || errno != EACCES) {
-			syslog(LOG_ERR, "Bad access for input devs.");
-			return errno;
-		}
-		select(1, NULL, NULL, NULL, &timeout);
-		++i;
+	jack->ucm_device = strdup(section->name);
+	if (!jack->ucm_device) {
+		cras_free_jack(jack, 0);
+		return -ENOMEM;
 	}
 
-	return 0;
+	if (direction == CRAS_STREAM_OUTPUT)
+		jack->mixer_output = cras_alsa_mixer_get_control_for_section(
+						jack_list->mixer, section);
+	else if (direction == CRAS_STREAM_INPUT)
+		jack->mixer_input = cras_alsa_mixer_get_control_for_section(
+						jack_list->mixer, section);
+
+	return cras_complete_gpio_jack(data, jack, switch_event);
 }
 
-static void find_gpio_jacks(struct cras_alsa_jack_list *jack_list,
-			    unsigned int card_index,
-			    const char *card_name,
-			    enum CRAS_STREAM_DIRECTION direction)
+/* Monitor GPIO switches for this jack_list.
+ * Args:
+ *    data - Data for GPIO switch search.
+ *    dev_path - Device full path.
+ *    dev_name - Device name.
+ * Returns:
+ *    0 for success, or negative on error. Assumes success if no jack is
+ *    found, or if the jack could not be accessed.
+ */
+static int gpio_switches_monitor_device(struct gpio_switch_list_data *data,
+					const char *dev_path,
+					const char *dev_name)
+{
+	static const int out_switches[] = {SW_HEADPHONE_INSERT,
+					   SW_LINEOUT_INSERT};
+	static const int in_switches[] = {SW_MICROPHONE_INSERT};
+	int sw;
+	const int *switches = out_switches;
+	int num_switches = ARRAY_SIZE(out_switches);
+	int success = 1;
+	int rc = 0;
+
+	if (data->section && data->section->jack_switch >= 0) {
+		switches = &data->section->jack_switch;
+		num_switches = 1;
+	}
+	else if (data->jack_list->direction == CRAS_STREAM_INPUT) {
+		switches = in_switches;
+		num_switches = ARRAY_SIZE(in_switches);
+	}
+
+	/* Assume that -EIO is returned for jacks that we shouldn't
+	 * be looking at, but stop trying if we run into another
+	 * type of error.
+	 */
+	for (sw = 0; (rc == 0 || rc == -EIO)
+		     && sw < num_switches; sw++) {
+		if (data->section)
+			rc = open_and_monitor_gpio_with_section(
+				data, dev_path, switches[sw]);
+		else
+			rc = open_and_monitor_gpio(
+				data, dev_path, dev_name, switches[sw]);
+		if (rc != 0 && rc != -EIO)
+			success = 0;
+	}
+
+	if (success)
+		return 0;
+	return rc;
+}
+
+static int gpio_switch_list_with_section(const char *dev_path,
+					 const char *dev_name,
+					 void *arg)
+{
+	struct gpio_switch_list_data *data =
+		(struct gpio_switch_list_data *)arg;
+
+	if (strcmp(dev_name, data->section->jack_name)) {
+		/* No match: continue searching. */
+		return 0;
+	}
+
+	data->rc = gpio_switches_monitor_device(data, dev_path, dev_name);
+	/* Found the only possible match: stop searching. */
+	return 1;
+}
+
+/* Match the given jack name to the given regular expression.
+ * Args:
+ *    jack_name - The jack's name.
+ *    re - Regular expression string.
+ * Returns:
+ *    Non-zero for success, or 0 for failure.
+ */
+static int jack_matches_regex(const char *jack_name, const char *re)
+{
+	regmatch_t m[1];
+	regex_t regex;
+	int rc;
+
+	rc = regcomp(&regex, re, REG_EXTENDED);
+	if (rc != 0) {
+		syslog(LOG_ERR, "Failed to compile regular expression: %s", re);
+		return 0;
+	}
+
+	rc = regexec(&regex, jack_name, ARRAY_SIZE(m), m, 0) == 0;
+	regfree(&regex);
+	return rc;
+}
+
+static int gpio_switch_list_by_matching(const char *dev_path,
+					const char *dev_name,
+					void *arg)
+{
+	struct gpio_switch_list_data *data =
+		(struct gpio_switch_list_data *)arg;
+
+	if (data->jack_list->direction == CRAS_STREAM_INPUT) {
+		if (!jack_matches_regex(dev_name, "^.*Mic Jack$") &&
+		    !jack_matches_regex(dev_name, "^.*Headset Jack$")) {
+			/* Continue searching. */
+			return 0;
+		}
+	}
+	else if (data->jack_list->direction == CRAS_STREAM_OUTPUT) {
+		if (!jack_matches_regex(dev_name, "^.*Headphone Jack$") &&
+		    !jack_matches_regex(dev_name, "^.*Headset Jack$") &&
+		    !jack_matches_regex(dev_name, "^.*HDMI Jack$")) {
+			/* Continue searching. */
+			return 0;
+		}
+	}
+
+	data->rc = gpio_switches_monitor_device(data, dev_path, dev_name);
+	/* Stop searching for failure. */
+	return data->rc;
+}
+
+/* Find GPIO jacks for this jack_list.
+ * Args:
+ *    jack_list - Jack list to add to.
+ *    section - UCM section.
+ *    result_jack - Filled with a pointer to the resulting cras_alsa_jack.
+ * Returns:
+ *    0 for success, or negative on error. Assumes success if no jack is
+ *    found, or if the jack could not be accessed.
+ */
+static int find_gpio_jacks(struct cras_alsa_jack_list *jack_list,
+			   struct ucm_section *section,
+			   struct cras_alsa_jack **result_jack)
 {
 	/* GPIO switches are on Arm-based machines, and are
 	 * only associated with on-board devices.
 	 */
-	char *devices[32];
-	unsigned n_devices;
-	unsigned i;
-	static const int out_switches[] = {SW_HEADPHONE_INSERT,
-					   SW_LINEOUT_INSERT};
-	static const int in_switches[] = {SW_MICROPHONE_INSERT};
+	struct gpio_switch_list_data data;
+	int rc;
 
-	if (wait_for_dev_input_access())
-		return;
-
-	n_devices = gpio_get_switch_names(direction, devices,
-					  ARRAY_SIZE(devices));
-	for (i = 0; i < n_devices; ++i) {
-		int sw;
-		const int *switches = out_switches;
-		int num_switches = ARRAY_SIZE(out_switches);
-
-		if (direction == CRAS_STREAM_INPUT) {
-			switches = in_switches;
-			num_switches = ARRAY_SIZE(in_switches);
-		}
-
-		for (sw = 0; sw < num_switches; sw++)
-			open_and_monitor_gpio(jack_list, direction, card_name,
-					      devices[i], switches[sw]);
-		free(devices[i]);
+	rc = wait_for_dev_input_access();
+	if (rc != 0) {
+		syslog(LOG_WARNING, "Could not access /dev/input/event0: %s",
+		       strerror(rc));
+		return 0;
 	}
+
+	data.jack_list = jack_list;
+	data.section = section;
+	data.result_jack = NULL;
+	data.rc = 0;
+
+	if (section)
+		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)
+		*result_jack = data.result_jack;
+	return data.rc;
 }
 
 /* Callback from alsa when a jack control changes.  This is registered with
@@ -599,23 +820,6 @@
 	return 0;
 }
 
-/* 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)
-{
-	struct cras_alsa_jack_list *jack_list;
-
-	jack_list = (struct cras_alsa_jack_list *)arg;
-	if (jack_list == NULL) {
-		syslog(LOG_ERR, "Invalid jack_list from control event.");
-		return;
-	}
-
-	/* handle_events will trigger the callback registered with each control
-	 * that has changed. */
-	snd_hctl_handle_events(jack_list->hctl);
-}
-
 /* Determines the device associated with this jack if any.  If the device cannot
  * be determined (common case), assume device 0. */
 static unsigned int hctl_jack_device_index(const char *name)
@@ -659,63 +863,10 @@
 	return 0;
 }
 
-/* Registers each poll fd (one per jack) with the system so that they are passed
- * to select in the main loop. */
-static int add_jack_poll_fds(struct cras_alsa_jack_list *jack_list)
-{
-	struct pollfd *pollfds;
-	nfds_t n;
-	unsigned int i;
-	int rc = 0;
-
-	n = snd_hctl_poll_descriptors_count(jack_list->hctl);
-	if (n == 0)
-		return 0;
-
-	pollfds = malloc(n * sizeof(*pollfds));
-	if (pollfds == NULL)
-		return -ENOMEM;
-
-	n = snd_hctl_poll_descriptors(jack_list->hctl, pollfds, n);
-	for (i = 0; i < n; i++) {
-		struct jack_poll_fd *registered_fd;
-
-		registered_fd = calloc(1, sizeof(*registered_fd));
-		if (registered_fd == NULL) {
-			rc = -ENOMEM;
-			break;
-		}
-		registered_fd->fd = pollfds[i].fd;
-		DL_APPEND(jack_list->registered_fds, registered_fd);
-		rc = cras_system_add_select_fd(registered_fd->fd,
-					       alsa_control_event_pending,
-					       jack_list);
-		if (rc < 0)
-			break;
-	}
-	free(pollfds);
-	return rc;
-}
-
-/* Cancels registration of each poll fd (one per jack) with the system. */
-static void remove_jack_poll_fds(struct cras_alsa_jack_list *jack_list)
-{
-	struct jack_poll_fd *registered_fd;
-
-	DL_FOREACH(jack_list->registered_fds, registered_fd) {
-		cras_system_rm_select_fd(registered_fd->fd);
-		DL_DELETE(jack_list->registered_fds, registered_fd);
-		free(registered_fd);
-	}
-}
-
 /* Looks for any JACK controls.  Monitors any found controls for changes and
  * decides to route based on plug/unlpug events. */
-static int find_jack_controls(struct cras_alsa_jack_list *jack_list,
-			      const char *device_name,
-			      enum CRAS_STREAM_DIRECTION direction)
+static int find_jack_controls(struct cras_alsa_jack_list *jack_list)
 {
-	int rc;
 	snd_hctl_elem_t *elem;
 	struct cras_alsa_jack *jack;
 	const char *name;
@@ -731,8 +882,14 @@
 	static const char eld_control_name[] = "ELD";
 	const char * const *jack_names;
 	unsigned int num_jack_names;
+	char device_name[6];
 
-	if (direction == CRAS_STREAM_OUTPUT) {
+	if (!jack_list->hctl) {
+		syslog(LOG_WARNING, "Can't search hctl for jacks.");
+		return 0;
+	}
+
+	if (jack_list->direction == CRAS_STREAM_OUTPUT) {
 		jack_names = output_jack_base_names;
 		num_jack_names = ARRAY_SIZE(output_jack_base_names);
 	} else {
@@ -740,21 +897,6 @@
 		num_jack_names = ARRAY_SIZE(input_jack_base_names);
 	}
 
-	rc = snd_hctl_open(&jack_list->hctl, device_name, SND_CTL_NONBLOCK);
-	if (rc < 0) {
-		syslog(LOG_ERR, "failed to get hctl for %s", device_name);
-		return rc;
-	}
-	rc = snd_hctl_nonblock(jack_list->hctl, 1);
-	if (rc < 0) {
-		syslog(LOG_ERR, "failed to nonblock hctl for %s", device_name);
-		return rc;
-	}
-	rc = snd_hctl_load(jack_list->hctl);
-	if (rc < 0) {
-		syslog(LOG_ERR, "failed to load hctl for %s", device_name);
-		return rc;
-	}
 	for (elem = snd_hctl_first_elem(jack_list->hctl); elem != NULL;
 			elem = snd_hctl_elem_next(elem)) {
 		snd_ctl_elem_iface_t iface;
@@ -779,7 +921,7 @@
 		snd_hctl_elem_set_callback(elem, hctl_jack_cb);
 		snd_hctl_elem_set_callback_private(elem, jack);
 
-		if (direction == CRAS_STREAM_OUTPUT)
+		if (jack_list->direction == CRAS_STREAM_OUTPUT)
 			jack->mixer_output =
 				cras_alsa_mixer_get_output_matching_name(
 					jack_list->mixer,
@@ -787,9 +929,9 @@
 		if (jack_list->ucm)
 			jack->ucm_device =
 				ucm_get_dev_for_jack(jack_list->ucm, name,
-						     direction);
+						     jack_list->direction);
 
-		if (jack->ucm_device && direction == CRAS_STREAM_INPUT) {
+		if (jack->ucm_device && jack_list->direction == CRAS_STREAM_INPUT) {
 			char *control_name;
 			control_name = ucm_get_cap_control(jack->jack_list->ucm,
 						       jack->ucm_device);
@@ -803,7 +945,7 @@
 		if (jack->ucm_device) {
 			jack->dsp_name = ucm_get_dsp_name(
 				jack->jack_list->ucm, jack->ucm_device,
-				direction);
+				jack_list->direction);
 			jack->override_type_name = ucm_get_override_type_name(
 				jack->jack_list->ucm, jack->ucm_device);
 		}
@@ -811,6 +953,8 @@
 
 	/* Look up ELD controls */
 	DL_FOREACH(jack_list->jacks, jack) {
+		if (jack->is_gpio || jack->eld_control)
+			continue;
 		name = snd_hctl_elem_get_name(jack->elem);
 		if (!is_jack_hdmi_dp(name))
 			continue;
@@ -827,14 +971,6 @@
 		}
 	}
 
-	/* If we have found jacks, have the poll fds passed to select in the
-	 * main loop. */
-	if (jack_list->jacks != NULL) {
-		rc = add_jack_poll_fds(jack_list);
-		if (rc < 0)
-			return rc;
-	}
-
 	return 0;
 }
 
@@ -842,30 +978,141 @@
  * Exported Interface.
  */
 
+int cras_alsa_jack_list_find_jacks_by_name_matching(
+	struct cras_alsa_jack_list *jack_list)
+{
+	int rc;
+
+	rc = find_jack_controls(jack_list);
+	if (rc != 0)
+		return rc;
+
+	return find_gpio_jacks(jack_list, NULL, NULL);
+}
+
+static int find_hctl_jack_for_section(
+		struct cras_alsa_jack_list *jack_list,
+		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;
+
+	if (!jack_list->hctl) {
+		syslog(LOG_WARNING, "Can't search hctl for jacks.");
+		return -ENODEV;
+	}
+
+	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_CARD);
+	snd_ctl_elem_id_set_device(elem_id, jack_list->device_index);
+	snd_ctl_elem_id_set_name(elem_id, section->jack_name);
+	elem = snd_hctl_find_elem(jack_list->hctl, elem_id);
+	if (!elem)
+		return -ENOENT;
+
+	syslog(LOG_DEBUG, "Found Jack: %s for %s",
+	       section->jack_name, section->name);
+
+	jack = cras_alloc_jack(0);
+	if (jack == NULL)
+		return -ENOMEM;
+	jack->elem = elem;
+	jack->jack_list = jack_list;
+
+	jack->ucm_device = strdup(section->name);
+	if (!jack->ucm_device) {
+		free(jack);
+		return -ENOMEM;
+	}
+	if (jack_list->direction == CRAS_STREAM_OUTPUT)
+		jack->mixer_output = cras_alsa_mixer_get_control_for_section(
+					jack_list->mixer, section);
+	else if (jack_list->direction == CRAS_STREAM_INPUT)
+		jack->mixer_input = cras_alsa_mixer_get_control_for_section(
+					jack_list->mixer, section);
+
+	jack->dsp_name = ucm_get_dsp_name(
+		jack->jack_list->ucm, jack->ucm_device,
+		jack_list->direction);
+
+	snd_hctl_elem_set_callback(elem, hctl_jack_cb);
+	snd_hctl_elem_set_callback_private(elem, jack);
+	DL_APPEND(jack_list->jacks, jack);
+	if (result_jack)
+		*result_jack = jack;
+
+	if (!strcmp(jack->ucm_device, "HDMI") ||
+	    !strcmp(jack->ucm_device, "DP"))
+		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;
+	return 0;
+}
+
+/*
+ * Exported Interface.
+ */
+
+int cras_alsa_jack_list_add_jack_for_section(
+	struct cras_alsa_jack_list *jack_list,
+	struct ucm_section *ucm_section,
+	struct cras_alsa_jack **result_jack)
+{
+	if (result_jack)
+		*result_jack = NULL;
+	if (!ucm_section)
+		return -EINVAL;
+
+	if (!ucm_section->jack_name) {
+		/* No jacks defined for this device. */
+		return 0;
+	}
+
+	if (!ucm_section->jack_type) {
+		syslog(LOG_ERR,
+		       "Must specify the JackType for jack '%s' in '%s'.",
+		       ucm_section->jack_name, ucm_section->name);
+		return -EINVAL;
+	}
+
+	if (!strcmp(ucm_section->jack_type, "hctl")) {
+		return find_hctl_jack_for_section(
+				jack_list, ucm_section, result_jack);
+	} else if (!strcmp(ucm_section->jack_type, "gpio")) {
+		return find_gpio_jacks(jack_list, ucm_section, result_jack);
+	} else {
+		syslog(LOG_ERR,
+		       "Invalid JackType '%s' in '%s'.",
+		       ucm_section->jack_type, ucm_section->name);
+		return -EINVAL;
+	}
+}
+
 struct cras_alsa_jack_list *cras_alsa_jack_list_create(
 		unsigned int card_index,
 		const char *card_name,
 		unsigned int device_index,
 		int is_first_device,
 		struct cras_alsa_mixer *mixer,
-		snd_use_case_mgr_t *ucm,
+		struct cras_use_case_mgr *ucm,
+		snd_hctl_t *hctl,
 		enum CRAS_STREAM_DIRECTION direction,
 		jack_state_change_callback *cb,
 		void *cb_data)
 {
 	struct cras_alsa_jack_list *jack_list;
-	char device_name[6];
 
 	if (direction != CRAS_STREAM_INPUT && direction != CRAS_STREAM_OUTPUT)
 		return NULL;
 
-	/* Enforce alsa limits. */
-	if (card_index >= 32 || device_index >= 32) {
-		syslog(LOG_ERR, "Jack List: Invalid card/dev %u/%u",
-		       card_index, device_index);
-		return NULL;
-	}
-
 	jack_list = (struct cras_alsa_jack_list *)calloc(1, sizeof(*jack_list));
 	if (jack_list == NULL)
 		return NULL;
@@ -874,17 +1121,12 @@
 	jack_list->callback_data = cb_data;
 	jack_list->mixer = mixer;
 	jack_list->ucm = ucm;
+	jack_list->hctl = hctl;
+	jack_list->card_index = card_index;
+	jack_list->card_name = card_name;
 	jack_list->device_index = device_index;
 	jack_list->is_first_device = is_first_device;
-
-	snprintf(device_name, sizeof(device_name), "hw:%d", card_index);
-
-	if (find_jack_controls(jack_list, device_name, direction) != 0) {
-		cras_alsa_jack_list_destroy(jack_list);
-		return NULL;
-	}
-
-	find_gpio_jacks(jack_list, card_index, card_name, direction);
+	jack_list->direction = direction;
 
 	return jack_list;
 }
@@ -895,32 +1137,26 @@
 
 	if (jack_list == NULL)
 		return;
-	remove_jack_poll_fds(jack_list);
 	DL_FOREACH(jack_list->jacks, jack) {
 		DL_DELETE(jack_list->jacks, jack);
-		free(jack->ucm_device);
-
-		free((void *)jack->edid_file);
-		if (jack->display_info_timer)
-			cras_tm_cancel_timer(cras_system_state_get_tm(),
-					     jack->display_info_timer);
-
-		if (jack->is_gpio) {
-			free(jack->gpio.device_name);
-			close(jack->gpio.fd);
-		}
-
-		if (jack->override_type_name)
-			free((void *)jack->override_type_name);
-
-		free((void *)jack->dsp_name);
-		free(jack);
+		cras_free_jack(jack, 1);
 	}
-	if (jack_list->hctl)
-		snd_hctl_close(jack_list->hctl);
 	free(jack_list);
 }
 
+int cras_alsa_jack_list_has_hctl_jacks(struct cras_alsa_jack_list *jack_list)
+{
+	struct cras_alsa_jack *jack;
+
+	if (!jack_list)
+		return 0;
+	DL_FOREACH(jack_list->jacks, jack) {
+		if (!jack->is_gpio)
+			return 1;
+	}
+	return 0;
+}
+
 struct mixer_control *cras_alsa_jack_get_mixer_output(
 		const struct cras_alsa_jack *jack)
 {
@@ -958,6 +1194,11 @@
 	return snd_hctl_elem_get_name(jack->elem);
 }
 
+const char *cras_alsa_jack_get_ucm_device(const struct cras_alsa_jack *jack)
+{
+	return jack->ucm_device;
+}
+
 void cras_alsa_jack_update_monitor_name(const struct cras_alsa_jack *jack,
 					char *name_buf,
 					unsigned int buf_size)
diff --git a/cras/src/server/cras_alsa_jack.h b/cras/src/server/cras_alsa_jack.h
index 9b25adf..b944898 100644
--- a/cras/src/server/cras_alsa_jack.h
+++ b/cras/src/server/cras_alsa_jack.h
@@ -27,8 +27,8 @@
 					  int plugged,
 					  void *data);
 
-/* Creates a jack list.  The list holds all the interesting ALSA jacks for this
- * device.  These jacks will be for headphones, speakers, HDMI, etc.
+/* Creates a jack list. The jacks can be added later by name matching or
+ * fully specified UCM.
  * Args:
  *    card_index - Index ALSA uses to refer to the card.  The X in "hw:X".
  *    card_name - The name of the card (used to find gpio jacks).
@@ -37,7 +37,8 @@
  *    mixer - The mixer associated with this card, used to find controls that
  *      correspond to jacks.  For instance "Headphone switch" for "Headphone
  *      Jack".
- *    ucm - ALSA use case manager if available.
+ *    ucm - CRAS use case manager if available.
+ *    hctl - ALSA high-level control interface if available.
  *    direction - Input or output, look for mic or headphone jacks.
  *    cb - Function to call when a jack state changes.
  *    cb_data - Passed to the callback when called.
@@ -50,17 +51,48 @@
 		unsigned int device_index,
 		int is_first_device,
 		struct cras_alsa_mixer *mixer,
-		snd_use_case_mgr_t *ucm,
+		struct cras_use_case_mgr *ucm,
+		snd_hctl_t *hctl,
 		enum CRAS_STREAM_DIRECTION direction,
 		jack_state_change_callback *cb,
 		void *cb_data);
 
+/* Finds jacks by name matching.
+ * The list holds all the interesting ALSA jacks for this
+ * device. These jacks will be for headphones, speakers, HDMI, etc.
+ * Args:
+ *   jack_list - A pointer to a jack list.
+ * Returns:
+ *   0 on success. Error code if there is a failure.
+ */
+int cras_alsa_jack_list_find_jacks_by_name_matching(
+	struct cras_alsa_jack_list *jack_list);
+
+/* Add the jack defined by the UCM section information.
+ * Args:
+ *   jack_list - A pointer to a jack list.
+ *   ucm_section - UCM section data.
+ *   result_jack - Resulting jack that was added.
+ * Returns:
+ *   0 on success. Error code if there is a failure.
+ */
+int cras_alsa_jack_list_add_jack_for_section(
+	struct cras_alsa_jack_list *jack_list,
+	struct ucm_section *ucm_section,
+	struct cras_alsa_jack **result_jack);
+
 /* Destroys a jack list created with cras_alsa_jack_list_create.
  * Args:
  *    jack_list - The list to destroy.
  */
 void cras_alsa_jack_list_destroy(struct cras_alsa_jack_list *jack_list);
 
+/* Returns non-zero if the jack list has hctl jacks.
+ * Args:
+ *    jack_list - The list check.
+ */
+int cras_alsa_jack_list_has_hctl_jacks(struct cras_alsa_jack_list *jack_list);
+
 /* Gets the mixer output associated with the given jack.
  * Args:
  *    jack - The jack to query for a mixer output.
@@ -91,6 +123,12 @@
  */
 const char *cras_alsa_jack_get_name(const struct cras_alsa_jack *jack);
 
+/* Gets the ucm device of a jack.
+ * Args:
+ *    jack - The alsa jack.
+ */
+const char *cras_alsa_jack_get_ucm_device(const struct cras_alsa_jack *jack);
+
 
 void cras_alsa_jack_update_monitor_name(const struct cras_alsa_jack *jack,
 					char *name_buf,
diff --git a/cras/src/server/cras_alsa_mixer.c b/cras/src/server/cras_alsa_mixer.c
index fe3e2d0..4cb665a 100644
--- a/cras/src/server/cras_alsa_mixer.c
+++ b/cras/src/server/cras_alsa_mixer.c
@@ -4,40 +4,67 @@
  */
 
 #include <alsa/asoundlib.h>
+#include <limits.h>
 #include <stdio.h>
 #include <syslog.h>
 
 #include "cras_alsa_mixer.h"
-#include "cras_card_config.h"
+#include "cras_alsa_mixer_name.h"
+#include "cras_alsa_ucm.h"
 #include "cras_util.h"
-#include "cras_volume_curve.h"
 #include "utlist.h"
 
+#define MIXER_CONTROL_VOLUME_DB_INVALID LONG_MAX
+
 /* Represents an ALSA control element. Each device can have several of these,
  * each potentially having independent volume and mute controls.
  * elem - ALSA mixer element.
  * has_volume - non-zero indicates there is a volume control.
  * has_mute - non-zero indicates there is a mute switch.
+ * max_volume_dB - the maximum volume for this control, or
+ *                 MIXER_CONTROL_VOLUME_DB_INVALID.
+ * min_volume_dB - the minimum volume for this control, or
+ *                 MIXER_CONTROL_VOLUME_DB_INVALID.
  */
-struct mixer_control {
+struct mixer_control_element {
 	snd_mixer_elem_t *elem;
 	int has_volume;
 	int has_mute;
-	struct mixer_control *prev, *next;
-};
-
-/* Represents an ALSA control element related to a specific output such as
- * speakers or headphones.  A device can have several of these, each potentially
- * having independent volume and mute controls.
- * max_volume_dB - Maximum volume available in the volume control.
- * min_volume_dB - Minimum volume available in the volume control.
- * volume_curve - Curve for this output.
- */
-struct mixer_output_control {
-	struct mixer_control base;
 	long max_volume_dB;
 	long min_volume_dB;
-	struct cras_volume_curve *volume_curve;
+	struct mixer_control_element *prev, *next;
+};
+
+/* Represents an ALSA control element related to a specific input/output
+ * node such as speakers or headphones. A device can have several of these,
+ * each potentially having independent volume and mute controls.
+ *
+ * Each will have at least one mixer_control_element. For cases where there
+ * are separate control elements for left/right channels (for example),
+ * additional mixer_control_elements are added.
+ *
+ * For controls with volume it is assumed that all elements have the same
+ * range.
+ *
+ * name - Name of the control (typicially this is the same as the name of the
+ *        mixer_control_element when there is one, or the name of the UCM
+ *        parent when there are multiple).
+ * dir - Control direction, OUTPUT or INPUT only.
+ * elements - The mixer_control_elements that are driven by this control.
+ * has_volume - non-zero indicates there is a volume control.
+ * has_mute - non-zero indicates there is a mute switch.
+ * max_volume_dB - Maximum volume available in the volume control.
+ * min_volume_dB - Minimum volume available in the volume control.
+ */
+struct mixer_control {
+	const char *name;
+	enum CRAS_STREAM_DIRECTION dir;
+	struct mixer_control_element *elements;
+	int has_volume;
+	int has_mute;
+	long max_volume_dB;
+	long min_volume_dB;
+	struct mixer_control *prev, *next;
 };
 
 /* Holds a reference to the opened mixer and the volume controls.
@@ -46,11 +73,9 @@
  * playback_switch - Switch used to mute the device.
  * main_capture_controls - List of capture gain controls (normally 'Capture').
  * capture_switch - Switch used to mute the capture stream.
- * volume_curve - Default volume curve that converts from an index to dBFS.
  * max_volume_dB - Maximum volume available in main volume controls.  The dBFS
  *   value setting will be applied relative to this.
  * min_volume_dB - Minimum volume available in main volume controls.
- * config - Config info for this card, can be NULL if none found.
  */
 struct cras_alsa_mixer {
 	snd_mixer_t *mixer;
@@ -60,95 +85,404 @@
 	struct mixer_control *main_capture_controls;
 	struct mixer_control *input_controls;
 	snd_mixer_elem_t *capture_switch;
-	struct cras_volume_curve *volume_curve;
 	long max_volume_dB;
 	long min_volume_dB;
-	const struct cras_card_config *config;
 };
 
 /* Wrapper for snd_mixer_open and helpers.
  * Args:
  *    mixdev - Name of the device to open the mixer for.
- * Returns:
- *    pointer to opened mixer on success, NULL on failure.
+ *    mixer - Pointer filled with the opened mixer on success, NULL on failure.
  */
-static snd_mixer_t *alsa_mixer_open(const char *mixdev)
+static void alsa_mixer_open(const char *mixdev,
+			    snd_mixer_t **mixer)
 {
-	snd_mixer_t *mixer = NULL;
 	int rc;
-	rc = snd_mixer_open(&mixer, 0);
-	if (rc < 0)
-		return NULL;
-	rc = snd_mixer_attach(mixer, mixdev);
-	if (rc < 0)
+
+	*mixer = NULL;
+	rc = snd_mixer_open(mixer, 0);
+	if (rc < 0) {
+		syslog(LOG_ERR, "snd_mixer_open: %d: %s", rc, strerror(-rc));
+		return;
+	}
+	rc = snd_mixer_attach(*mixer, mixdev);
+	if (rc < 0) {
+		syslog(LOG_ERR, "snd_mixer_attach: %d: %s", rc, strerror(-rc));
 		goto fail_after_open;
-	rc = snd_mixer_selem_register(mixer, NULL, NULL);
-	if (rc < 0)
+	}
+	rc = snd_mixer_selem_register(*mixer, NULL, NULL);
+	if (rc < 0) {
+		syslog(LOG_ERR, "snd_mixer_selem_register: %d: %s", rc, strerror(-rc));
 		goto fail_after_open;
-	rc = snd_mixer_load(mixer);
-	if (rc < 0)
+	}
+	rc = snd_mixer_load(*mixer);
+	if (rc < 0) {
+		syslog(LOG_ERR, "snd_mixer_load: %d: %s", rc, strerror(-rc));
 		goto fail_after_open;
-	return mixer;
+	}
+	return;
 
 fail_after_open:
-	snd_mixer_close(mixer);
-	return NULL;
+	snd_mixer_close(*mixer);
+	*mixer = NULL;
 }
 
-/* Checks if the given element's name is in the list. */
-static int name_in_list(const char *name,
-			const char * const list[],
-			size_t len)
+static struct mixer_control_element *mixer_control_element_create(
+					snd_mixer_elem_t *elem,
+					enum CRAS_STREAM_DIRECTION dir)
 {
-	size_t i;
+	struct mixer_control_element *c;
+	long min, max;
 
-	for (i = 0; i < len; i++)
-		if (list[i] && strcmp(list[i], name) == 0)
-			return 1;
+	if (!elem)
+		return NULL;
+
+	c = (struct mixer_control_element *)calloc(1, sizeof(*c));
+	if (!c) {
+		syslog(LOG_ERR, "No memory for mixer_control_elem.");
+		return NULL;
+	}
+
+	c->elem = elem;
+	c->max_volume_dB = MIXER_CONTROL_VOLUME_DB_INVALID;
+	c->min_volume_dB = MIXER_CONTROL_VOLUME_DB_INVALID;
+
+	if (dir == CRAS_STREAM_OUTPUT) {
+		c->has_mute = snd_mixer_selem_has_playback_switch(elem);
+
+		if (snd_mixer_selem_has_playback_volume(elem) &&
+		    snd_mixer_selem_get_playback_dB_range(
+						elem, &min, &max) == 0) {
+			c->max_volume_dB = max;
+			c->min_volume_dB = min;
+			c->has_volume = 1;
+		}
+	}
+	else if (dir == CRAS_STREAM_INPUT) {
+		c->has_mute = snd_mixer_selem_has_capture_switch(elem);
+
+		if (snd_mixer_selem_has_capture_volume(elem) &&
+		    snd_mixer_selem_get_capture_dB_range(
+						elem, &min, &max) == 0) {
+			c->max_volume_dB = max;
+			c->min_volume_dB = min;
+			c->has_volume = 1;
+		}
+	}
+
+	return c;
+}
+
+static void mixer_control_destroy(struct mixer_control *control) {
+	struct mixer_control_element *elem;
+
+	if (!control)
+		return;
+
+	DL_FOREACH(control->elements, elem) {
+		DL_DELETE(control->elements, elem);
+		free(elem);
+	}
+	if (control->name)
+		free((void *)control->name);
+	free(control);
+}
+
+static void mixer_control_destroy_list(struct mixer_control *control_list)
+{
+	struct mixer_control *control;
+	if (!control_list)
+		return;
+	DL_FOREACH(control_list, control) {
+		DL_DELETE(control_list, control);
+		mixer_control_destroy(control);
+	}
+}
+
+static int mixer_control_add_element(struct mixer_control *control,
+				     snd_mixer_elem_t *snd_elem)
+{
+	struct mixer_control_element *elem;
+
+	if (!control)
+		return -EINVAL;
+
+	elem = mixer_control_element_create(snd_elem, control->dir);
+	if (!elem)
+		return -ENOMEM;
+
+	DL_APPEND(control->elements, elem);
+
+	if (elem->has_volume) {
+		if (!control->has_volume)
+			control->has_volume = 1;
+
+		/* Assume that all elements have a common volume range, and
+		 * that both min and max values are valid if one of the two
+		 * is valid. */
+		if (control->min_volume_dB ==
+		    MIXER_CONTROL_VOLUME_DB_INVALID) {
+			control->min_volume_dB = elem->min_volume_dB;
+			control->max_volume_dB = elem->max_volume_dB;
+		} else if (control->min_volume_dB != elem->min_volume_dB ||
+			   control->max_volume_dB != elem->max_volume_dB) {
+			syslog(LOG_WARNING,
+			    "Element '%s' of control '%s' has different"
+			    "volume range: [%ld:%ld] ctrl: [%ld:%ld]",
+			    snd_mixer_selem_get_name(elem->elem),
+			    control->name,
+			    elem->min_volume_dB, elem->max_volume_dB,
+			    control->min_volume_dB, control->max_volume_dB);
+		}
+	}
+
+	if (elem->has_mute && !control->has_mute)
+		control->has_mute = 1;
 	return 0;
 }
 
+static int mixer_control_create(struct mixer_control **control,
+				const char *name,
+				snd_mixer_elem_t *elem,
+				enum CRAS_STREAM_DIRECTION dir)
+{
+	struct mixer_control *c;
+	int rc = 0;
+
+	if (!control)
+		return -EINVAL;
+
+	c = (struct mixer_control *)calloc(1, sizeof(*c));
+	if (!c) {
+		syslog(LOG_ERR, "No memory for mixer_control: %s", name);
+		rc = -ENOMEM;
+		goto error;
+	}
+
+	c->dir = dir;
+	c->min_volume_dB = MIXER_CONTROL_VOLUME_DB_INVALID;
+	c->max_volume_dB = MIXER_CONTROL_VOLUME_DB_INVALID;
+
+	if (!name && elem)
+		name = snd_mixer_selem_get_name(elem);
+	if (!name) {
+		syslog(LOG_ERR, "Control does not have a name.");
+		rc = -EINVAL;
+		goto error;
+	}
+
+	c->name = strdup(name);
+	if (!c->name) {
+		syslog(LOG_ERR, "No memory for control's name: %s", name);
+		rc = -ENOMEM;
+		goto error;
+	}
+
+	if (elem && (rc = mixer_control_add_element(c, elem)))
+		goto error;
+
+	*control = c;
+	return 0;
+
+error:
+	mixer_control_destroy(c);
+	*control = NULL;
+	return rc;
+}
+
+/* Creates a mixer_control by finding mixer element names in simple mixer
+ * interface.
+ * Args:
+ *    control[out] - Storage for resulting pointer to mixer_control.
+ *    cmix[in] - Parent alsa mixer.
+ *    name[in] - Optional name of the control. Input NULL to take the name of
+ *               the first element from mixer_names.
+ *    mixer_names[in] - Names of the ASLA mixer control elements. Must not
+ *                      be empty.
+ *    dir[in] - Control direction: CRAS_STREAM_OUTPUT or CRAS_STREAM_INPUT.
+ * Returns:
+ *    Returns 0 for success, negative error code otherwise. *control is
+ *    initialized to NULL on error, or has a valid pointer for success.
+ */
+static int mixer_control_create_by_name(
+		struct mixer_control **control,
+		struct cras_alsa_mixer *cmix,
+		const char *name,
+		struct mixer_name *mixer_names,
+		enum CRAS_STREAM_DIRECTION dir)
+{
+	snd_mixer_selem_id_t *sid;
+	snd_mixer_elem_t *elem;
+	struct mixer_control *c;
+	struct mixer_name *m_name;
+	int rc;
+
+	if (!control)
+		return -EINVAL;
+	*control = NULL;
+	if (!mixer_names)
+		return -EINVAL;
+	if (!name) {
+		/* Assume that we're using the first name in the list of mixer
+		 * names. */
+		name = mixer_names->name;
+	}
+
+	rc = mixer_control_create(&c, name, NULL, dir);
+	if (rc)
+		return rc;
+
+	snd_mixer_selem_id_malloc(&sid);
+
+	DL_FOREACH(mixer_names, m_name) {
+		snd_mixer_selem_id_set_index(sid, 0);
+		snd_mixer_selem_id_set_name(sid, m_name->name);
+		elem = snd_mixer_find_selem(cmix->mixer, sid);
+		if (!elem) {
+			mixer_control_destroy(c);
+			snd_mixer_selem_id_free(sid);
+			syslog(LOG_ERR, "Unable to find simple control %s, 0",
+			       m_name->name);
+			return -ENOENT;
+		}
+		rc = mixer_control_add_element(c, elem);
+		if (rc) {
+			mixer_control_destroy(c);
+			snd_mixer_selem_id_free(sid);
+			return rc;
+		}
+	}
+
+	snd_mixer_selem_id_free(sid);
+	*control = c;
+	return 0;
+}
+
+static int mixer_control_set_dBFS(
+		const struct mixer_control *control, long to_set)
+{
+	const struct mixer_control_element *elem = NULL;
+	int rc = -EINVAL;
+	if (!control)
+		return rc;
+	DL_FOREACH(control->elements, elem) {
+		if(elem->has_volume) {
+			if (control->dir == CRAS_STREAM_OUTPUT)
+				rc = snd_mixer_selem_set_playback_dB_all(
+						elem->elem, to_set, 1);
+			else if (control->dir == CRAS_STREAM_INPUT)
+				rc = snd_mixer_selem_set_capture_dB_all(
+						elem->elem, to_set, 1);
+			if (rc)
+				break;
+			syslog(LOG_DEBUG, "%s:%s volume set to %ld",
+			       control->name,
+			       snd_mixer_selem_get_name(elem->elem),
+			       to_set);
+		}
+	}
+	if (rc && elem) {
+		syslog(LOG_ERR, "Failed to set volume of '%s:%s': %d",
+		       control->name,
+		       snd_mixer_selem_get_name(elem->elem), rc);
+	}
+	return rc;
+}
+
+static int mixer_control_get_dBFS(
+		const struct mixer_control *control, long *to_get)
+{
+	const struct mixer_control_element *elem = NULL;
+	int rc = -EINVAL;
+	if (!control || !to_get)
+		return -EINVAL;
+	DL_FOREACH(control->elements, elem) {
+		if (elem->has_volume) {
+			if (control->dir == CRAS_STREAM_OUTPUT)
+				rc = snd_mixer_selem_get_playback_dB(
+						elem->elem,
+						SND_MIXER_SCHN_FRONT_LEFT,
+						to_get);
+			else if (control->dir == CRAS_STREAM_INPUT)
+				rc = snd_mixer_selem_get_capture_dB(
+						elem->elem,
+						SND_MIXER_SCHN_FRONT_LEFT,
+						to_get);
+			/* Assume all of the elements of this control have
+			 * the same value. */
+			break;
+		}
+	}
+	if (rc && elem) {
+		syslog(LOG_ERR, "Failed to get volume of '%s:%s': %d",
+		       control->name,
+		       snd_mixer_selem_get_name(elem->elem), rc);
+	}
+	return rc;
+}
+
+static int mixer_control_set_mute(
+		const struct mixer_control *control, int muted)
+{
+	const struct mixer_control_element *elem = NULL;
+	int rc;
+	if (!control)
+		return -EINVAL;
+	DL_FOREACH(control->elements, elem) {
+		if(elem->has_mute) {
+			if (control->dir == CRAS_STREAM_OUTPUT)
+				rc = snd_mixer_selem_set_playback_switch_all(
+					elem->elem, !muted);
+			else if (control->dir == CRAS_STREAM_INPUT)
+				rc = snd_mixer_selem_set_capture_switch_all(
+					elem->elem, !muted);
+			if (rc)
+				break;
+		}
+	}
+	if (rc && elem) {
+		syslog(LOG_ERR, "Failed to mute '%s:%s': %d",
+		       control->name,
+		       snd_mixer_selem_get_name(elem->elem), rc);
+	}
+	return rc;
+}
+
 /* Adds the main volume control to the list and grabs the first seen playback
  * switch to use for mute. */
 static int add_main_volume_control(struct cras_alsa_mixer *cmix,
 				   snd_mixer_elem_t *elem)
 {
 	if (snd_mixer_selem_has_playback_volume(elem)) {
+		long range;
 		struct mixer_control *c, *next;
-		long min, max;
+		int rc = mixer_control_create(&c, NULL, elem, CRAS_STREAM_OUTPUT);
+		if (rc)
+			return rc;
 
-		c = (struct mixer_control *)calloc(1, sizeof(*c));
-		if (c == NULL) {
-			syslog(LOG_ERR, "No memory for mixer.");
-			return -ENOMEM;
+		if (c->has_volume) {
+			cmix->max_volume_dB += c->max_volume_dB;
+			cmix->min_volume_dB += c->min_volume_dB;
 		}
 
-		c->elem = elem;
-
-		if (snd_mixer_selem_get_playback_dB_range(elem,
-							  &min,
-							  &max) == 0) {
-			cmix->max_volume_dB += max;
-			cmix->min_volume_dB += min;
-		}
-
+		range = c->max_volume_dB - c->min_volume_dB;
 		DL_FOREACH(cmix->main_volume_controls, next) {
-			long next_min, next_max;
-			snd_mixer_selem_get_playback_dB_range(next->elem,
-							      &next_min,
-							      &next_max);
-			if (max - min > next_max - next_min)
+			if (range > next->max_volume_dB - next->min_volume_dB)
 				break;
 		}
 
+		syslog(LOG_DEBUG, "Add main volume control %s\n", c->name);
 		DL_INSERT(cmix->main_volume_controls, next, c);
 	}
 
 	/* If cmix doesn't yet have a playback switch and this is a playback
 	 * switch, use it. */
 	if (cmix->playback_switch == NULL &&
-			snd_mixer_selem_has_playback_switch(elem))
+			snd_mixer_selem_has_playback_switch(elem)) {
+		syslog(LOG_DEBUG, "Using '%s' as playback switch.",
+		       snd_mixer_selem_get_name(elem));
 		cmix->playback_switch = elem;
+	}
 
 	return 0;
 }
@@ -164,99 +498,62 @@
 
 	if (snd_mixer_selem_has_capture_volume(elem)) {
 		struct mixer_control *c;
+		int rc = mixer_control_create(&c, NULL, elem, CRAS_STREAM_INPUT);
+		if (rc)
+			return rc;
 
-		c = (struct mixer_control *)calloc(1, sizeof(*c));
-		if (c == NULL) {
-			syslog(LOG_ERR, "No memory for control.");
-			return -ENOMEM;
-		}
-
-		c->elem = elem;
-
-		syslog(LOG_DEBUG,
-		       "Add capture control %s\n",
-		       snd_mixer_selem_get_name(elem));
+		syslog(LOG_DEBUG, "Add main capture control %s\n", c->name);
 		DL_APPEND(cmix->main_capture_controls, c);
 	}
 
 	/* If cmix doesn't yet have a capture switch and this is a capture
 	 * switch, use it. */
 	if (cmix->capture_switch == NULL &&
-	    snd_mixer_selem_has_capture_switch(elem))
+	    snd_mixer_selem_has_capture_switch(elem)) {
+		syslog(LOG_DEBUG, "Using '%s' as capture switch.",
+		       snd_mixer_selem_get_name(elem));
 		cmix->capture_switch = elem;
+	}
 
 	return 0;
 }
 
-/* Creates a volume curve for a new output. */
-static struct cras_volume_curve *create_volume_curve_for_output(
-		const struct cras_alsa_mixer *cmix,
-		snd_mixer_elem_t *elem)
-{
-	const char *output_name;
-
-	output_name = snd_mixer_selem_get_name(elem);
-	return cras_card_config_get_volume_curve_for_control(cmix->config,
-							     output_name);
-}
-
-/* Adds an output control to the list. */
-static int add_output_control(struct cras_alsa_mixer *cmix,
-			      snd_mixer_elem_t *elem)
+/* Adds a control to the list. */
+static int add_control_with_name(struct cras_alsa_mixer *cmix,
+				 enum CRAS_STREAM_DIRECTION dir,
+				 snd_mixer_elem_t *elem,
+				 const char *name)
 {
 	int index; /* Index part of mixer simple element */
 	struct mixer_control *c;
-	struct mixer_output_control *output;
-	long min, max;
+	int rc;
 
 	index = snd_mixer_selem_get_index(elem);
-	syslog(LOG_DEBUG, "Add output control: %s,%d\n",
-	       snd_mixer_selem_get_name(elem), index);
+	syslog(LOG_DEBUG, "Add %s control: %s,%d\n",
+	       dir == CRAS_STREAM_OUTPUT ? "output" : "input",
+	       name, index);
 
-	output = (struct mixer_output_control *)calloc(1, sizeof(*output));
-	if (output == NULL) {
-		syslog(LOG_ERR, "No memory for output control.");
-		return -ENOMEM;
-	}
+	rc = mixer_control_create(&c, name, elem, dir);
+	if (rc)
+		return rc;
 
-	if (snd_mixer_selem_get_playback_dB_range(elem, &min, &max) == 0) {
-		output->max_volume_dB = max;
-		output->min_volume_dB = min;
-	}
-	output->volume_curve = create_volume_curve_for_output(cmix, elem);
+	if (c->has_volume)
+		syslog(LOG_DEBUG, "Control '%s' volume range: [%ld:%ld]",
+		       c->name, c->min_volume_dB, c->max_volume_dB);
 
-	c = &output->base;
-	c->elem = elem;
-	c->has_volume = snd_mixer_selem_has_playback_volume(elem);
-	c->has_mute = snd_mixer_selem_has_playback_switch(elem);
-	DL_APPEND(cmix->output_controls, c);
-
+	if (dir == CRAS_STREAM_OUTPUT)
+		DL_APPEND(cmix->output_controls, c);
+	else if (dir == CRAS_STREAM_INPUT)
+		DL_APPEND(cmix->input_controls, c);
 	return 0;
 }
 
-/* Adds an input control to the list. */
-static int add_input_control(struct cras_alsa_mixer *cmix,
-			      snd_mixer_elem_t *elem)
+static int add_control(struct cras_alsa_mixer *cmix,
+		       enum CRAS_STREAM_DIRECTION dir,
+		       snd_mixer_elem_t *elem)
 {
-	int index; /* Index part of mixer simple element */
-	struct mixer_control *c;
-
-	index = snd_mixer_selem_get_index(elem);
-	syslog(LOG_DEBUG, "Add input control: %s,%d\n",
-	       snd_mixer_selem_get_name(elem), index);
-
-	c = (struct mixer_control *)calloc(1, sizeof(*c));
-	if (c == NULL) {
-		syslog(LOG_ERR, "No memory for input control.");
-		return -ENOMEM;
-	}
-
-	c->elem = elem;
-	c->has_volume = snd_mixer_selem_has_capture_volume(elem);
-	c->has_mute = snd_mixer_selem_has_capture_switch(elem);
-	DL_APPEND(cmix->input_controls, c);
-
-	return 0;
+	return add_control_with_name(cmix, dir, elem,
+				     snd_mixer_selem_get_name(elem));
 }
 
 static void list_controls(struct mixer_control *control_list,
@@ -276,27 +573,96 @@
 	struct mixer_control *c;
 
 	DL_FOREACH(control_list, c) {
-		const char *elem_name;
-
-		elem_name = snd_mixer_selem_get_name(c->elem);
-		if (elem_name == NULL)
-			continue;
-		if (strstr(name, elem_name))
+		if (strstr(name, c->name))
 			return c;
 	}
 	return NULL;
 }
 
+/* Creates a mixer_control with multiple control elements. */
+static int add_control_with_coupled_mixers(
+				struct cras_alsa_mixer *cmix,
+				enum CRAS_STREAM_DIRECTION dir,
+				const char *name,
+				struct mixer_name *coupled_controls)
+{
+	struct mixer_control *c;
+	int rc;
+
+	rc = mixer_control_create_by_name(
+		 &c, cmix, name, coupled_controls, dir);
+	if (rc)
+		return rc;
+	syslog(LOG_DEBUG, "Add %s control: %s\n",
+	       dir == CRAS_STREAM_OUTPUT ? "output" : "input",
+	       c->name);
+	mixer_name_dump(coupled_controls, "  elements");
+
+	if (c->has_volume)
+		syslog(LOG_DEBUG, "Control '%s' volume range: [%ld:%ld]",
+		       c->name, c->min_volume_dB, c->max_volume_dB);
+
+	if (dir == CRAS_STREAM_OUTPUT)
+		DL_APPEND(cmix->output_controls, c);
+	else if (dir == CRAS_STREAM_INPUT)
+		DL_APPEND(cmix->input_controls, c);
+	return 0;
+}
+
+static int add_control_by_name(struct cras_alsa_mixer *cmix,
+			       enum CRAS_STREAM_DIRECTION dir,
+			       const char *name)
+{
+	struct mixer_control *c;
+	struct mixer_name *m_name;
+	int rc;
+
+	m_name = mixer_name_add(NULL, name, dir, MIXER_NAME_VOLUME);
+	if (!m_name)
+		return -ENOMEM;
+
+	rc = mixer_control_create_by_name(&c, cmix, name, m_name, dir);
+	mixer_name_free(m_name);
+	if (rc)
+		return rc;
+	syslog(LOG_DEBUG, "Add %s control: %s\n",
+	       dir == CRAS_STREAM_OUTPUT ? "output" : "input",
+	       c->name);
+
+	if (c->has_volume)
+		syslog(LOG_DEBUG, "Control '%s' volume range: [%ld:%ld]",
+		       c->name, c->min_volume_dB, c->max_volume_dB);
+
+	if (dir == CRAS_STREAM_OUTPUT)
+		DL_APPEND(cmix->output_controls, c);
+	else if (dir == CRAS_STREAM_INPUT)
+		DL_APPEND(cmix->input_controls, c);
+	return 0;
+}
+
 /*
  * Exported interface.
  */
 
-struct cras_alsa_mixer *cras_alsa_mixer_create(
-		const char *card_name,
-		const struct cras_card_config *config,
-		const char *output_names_extra[],
-		size_t output_names_extra_size,
-		const char *extra_main_volume)
+struct cras_alsa_mixer *cras_alsa_mixer_create(const char *card_name)
+{
+	struct cras_alsa_mixer *cmix;
+
+	cmix = (struct cras_alsa_mixer *)calloc(1, sizeof(*cmix));
+	if (cmix == NULL)
+		return NULL;
+
+	syslog(LOG_DEBUG, "Add mixer for device %s", card_name);
+
+	alsa_mixer_open(card_name, &cmix->mixer);
+
+	return cmix;
+}
+
+int cras_alsa_mixer_add_controls_by_name_matching(
+		struct cras_alsa_mixer *cmix,
+		struct mixer_name *extra_controls,
+		struct mixer_name *coupled_controls)
 {
 	/* Names of controls for main system volume. */
 	static const char * const main_volume_names[] = {
@@ -321,73 +687,131 @@
 		"Mic",
 		"Microphone",
 	};
+
+	struct mixer_name *default_controls = NULL;
 	snd_mixer_elem_t *elem;
-	struct cras_alsa_mixer *cmix;
+	int extra_main_volume = 0;
 	snd_mixer_elem_t *other_elem = NULL;
 	long other_dB_range = 0;
+	int rc = 0;
 
-	cmix = (struct cras_alsa_mixer *)calloc(1, sizeof(*cmix));
-	if (cmix == NULL)
-		return NULL;
-
-	syslog(LOG_DEBUG, "Add mixer for device %s", card_name);
-
-	cmix->mixer = alsa_mixer_open(card_name);
+	/* Note that there is no mixer on some cards. This is acceptable. */
 	if (cmix->mixer == NULL) {
 		syslog(LOG_DEBUG, "Couldn't open mixer.");
-		free(cmix);
-		return NULL;
+		return 0;
 	}
 
-	cmix->config = config;
-	cmix->volume_curve =
-		cras_card_config_get_volume_curve_for_control(cmix->config,
-							      "Default");
+	default_controls = mixer_name_add_array(default_controls,
+				output_names, ARRAY_SIZE(output_names),
+				CRAS_STREAM_OUTPUT, MIXER_NAME_VOLUME);
+	default_controls = mixer_name_add_array(default_controls,
+				input_names, ARRAY_SIZE(input_names),
+				CRAS_STREAM_INPUT, MIXER_NAME_VOLUME);
+	default_controls =
+		mixer_name_add_array(default_controls,
+			main_volume_names, ARRAY_SIZE(main_volume_names),
+			CRAS_STREAM_OUTPUT, MIXER_NAME_MAIN_VOLUME);
+	default_controls =
+		mixer_name_add_array(default_controls,
+			main_capture_names, ARRAY_SIZE(main_capture_names),
+			CRAS_STREAM_INPUT, MIXER_NAME_MAIN_VOLUME);
+	extra_main_volume =
+		mixer_name_find(extra_controls, NULL,
+				CRAS_STREAM_OUTPUT,
+				MIXER_NAME_MAIN_VOLUME) != NULL;
 
 	/* Find volume and mute controls. */
 	for(elem = snd_mixer_first_elem(cmix->mixer);
 			elem != NULL; elem = snd_mixer_elem_next(elem)) {
 		const char *name;
+		struct mixer_name *control;
+		int found = 0;
 
 		name = snd_mixer_selem_get_name(elem);
 		if (name == NULL)
 			continue;
 
-		if (!extra_main_volume &&
-		    name_in_list(name, main_volume_names,
-				 ARRAY_SIZE(main_volume_names))) {
-			if (add_main_volume_control(cmix, elem) != 0) {
-				cras_alsa_mixer_destroy(cmix);
-				return NULL;
+		/* Find a matching control. */
+		control = mixer_name_find(default_controls, name,
+					  CRAS_STREAM_OUTPUT,
+					  MIXER_NAME_UNDEFINED);
+
+		/* If our extra controls contain a main volume
+		 * entry, and we found a main volume entry, then
+		 * skip it. */
+		if (extra_main_volume &&
+		    control && control->type == MIXER_NAME_MAIN_VOLUME)
+			control = NULL;
+
+		/* If we didn't match any of the defaults, match
+		 * the extras list. */
+		if (!control)
+			control = mixer_name_find(extra_controls, name,
+					  CRAS_STREAM_OUTPUT,
+					  MIXER_NAME_UNDEFINED);
+
+		if (control) {
+			int rc = -1;
+			switch(control->type) {
+			case MIXER_NAME_MAIN_VOLUME:
+				rc = add_main_volume_control(cmix, elem);
+				break;
+			case MIXER_NAME_VOLUME:
+				/* TODO(dgreid) - determine device index. */
+				rc = add_control(
+					cmix, CRAS_STREAM_OUTPUT, elem);
+				break;
+			case MIXER_NAME_UNDEFINED:
+				rc = -EINVAL;
+				break;
 			}
-		} else if (name_in_list(name, main_capture_names,
-					ARRAY_SIZE(main_capture_names))) {
-			if (add_main_capture_control(cmix, elem) != 0) {
-				cras_alsa_mixer_destroy(cmix);
-				return NULL;
+			if (rc) {
+				syslog(LOG_ERR,
+				       "Failed to add mixer control '%s'"
+				       " with type '%d'",
+				       control->name, control->type);
+				return rc;
 			}
-		} else if (name_in_list(name, output_names,
-					ARRAY_SIZE(output_names))
-			   || name_in_list(name, output_names_extra,
-					   output_names_extra_size)) {
-			/* TODO(dgreid) - determine device index. */
-			if (add_output_control(cmix, elem) != 0) {
-				cras_alsa_mixer_destroy(cmix);
-				return NULL;
+			found = 1;
+		}
+
+		/* Find a matching input control. */
+		control = mixer_name_find(default_controls, name,
+					  CRAS_STREAM_INPUT,
+					  MIXER_NAME_UNDEFINED);
+
+		/* If we didn't match any of the defaults, match
+		   the extras list */
+		if (!control)
+			control = mixer_name_find(extra_controls, name,
+					  CRAS_STREAM_INPUT,
+					  MIXER_NAME_UNDEFINED);
+
+		if (control) {
+			int rc = -1;
+			switch(control->type) {
+			case MIXER_NAME_MAIN_VOLUME:
+				rc = add_main_capture_control(cmix, elem);
+				break;
+			case MIXER_NAME_VOLUME:
+				rc = add_control(
+					cmix, CRAS_STREAM_INPUT, elem);
+				break;
+			case MIXER_NAME_UNDEFINED:
+				rc = -EINVAL;
+				break;
 			}
-		} else if (name_in_list(name, input_names,
-					ARRAY_SIZE(input_names))) {
-			if (add_input_control(cmix, elem) != 0) {
-				cras_alsa_mixer_destroy(cmix);
-				return NULL;
+			if (rc) {
+				syslog(LOG_ERR,
+				       "Failed to add mixer control '%s'"
+				       " with type '%d'",
+				       control->name, control->type);
+				return rc;
 			}
-		} else if (extra_main_volume &&
-			   !strcmp(name, extra_main_volume)) {
-			if (add_main_volume_control(cmix, elem) != 0) {
-				cras_alsa_mixer_destroy(cmix);
-				return NULL;
-			}
-		} else if (snd_mixer_selem_has_playback_volume(elem)) {
+			found = 1;
+		}
+
+		if (!found && snd_mixer_selem_has_playback_volume(elem)) {
 			/* Temporarily cache one elem whose name is not
 			 * in the list above, but has a playback volume
 			 * control and the largest volume range. */
@@ -405,56 +829,96 @@
 		}
 	}
 
+	/* Handle coupled output names for speaker */
+	if (coupled_controls) {
+		rc = add_control_with_coupled_mixers(
+				cmix, CRAS_STREAM_OUTPUT,
+				"Speaker", coupled_controls);
+		if (rc) {
+			syslog(LOG_ERR, "Could not add coupled output");
+			return rc;
+		}
+	}
+
 	/* If there is no volume control and output control found,
 	 * use the volume control which has the largest volume range
 	 * in the mixer as a main volume control. */
 	if (!cmix->main_volume_controls && !cmix->output_controls &&
 	    other_elem) {
-		if (add_main_volume_control(cmix, other_elem) != 0) {
-			cras_alsa_mixer_destroy(cmix);
-			return NULL;
+		rc = add_main_volume_control(cmix, other_elem);
+		if (rc) {
+			syslog(LOG_ERR, "Could not add other volume control");
+			return rc;
 		}
 	}
 
-	return cmix;
+	return rc;
+}
+
+int cras_alsa_mixer_add_controls_in_section(
+		struct cras_alsa_mixer *cmix,
+		struct ucm_section *section)
+{
+	int rc;
+
+	/* Note that there is no mixer on some cards. This is acceptable. */
+	if (cmix->mixer == NULL) {
+		syslog(LOG_DEBUG, "Couldn't open mixer.");
+		return 0;
+	}
+
+	if (!section) {
+		syslog(LOG_ERR, "No UCM SectionDevice specified.");
+		return -EINVAL;
+	}
+
+	/* TODO(muirj) - Extra main volume controls when fully-specified. */
+
+	if (section->mixer_name) {
+		rc = add_control_by_name(
+				cmix, section->dir, section->mixer_name);
+		if (rc) {
+			syslog(LOG_ERR, "Could not add mixer control '%s': %s",
+			       section->mixer_name, strerror(-rc));
+			return rc;
+		}
+	}
+
+	if (section->coupled) {
+		rc = add_control_with_coupled_mixers(
+				cmix, section->dir,
+				section->name, section->coupled);
+		if (rc) {
+			syslog(LOG_ERR, "Could not add coupled control: %s",
+			       strerror(-rc));
+			return rc;
+		}
+	}
+	return 0;
 }
 
 void cras_alsa_mixer_destroy(struct cras_alsa_mixer *cras_mixer)
 {
-	struct mixer_control *c;
-
 	assert(cras_mixer);
 
-	DL_FOREACH(cras_mixer->main_volume_controls, c) {
-		DL_DELETE(cras_mixer->main_volume_controls, c);
-		free(c);
-	}
-	DL_FOREACH(cras_mixer->main_capture_controls, c) {
-		DL_DELETE(cras_mixer->main_capture_controls, c);
-		free(c);
-	}
-	DL_FOREACH(cras_mixer->output_controls, c) {
-		struct mixer_output_control *output;
-		output = (struct mixer_output_control *)c;
-		cras_volume_curve_destroy(output->volume_curve);
-		DL_DELETE(cras_mixer->output_controls, c);
-		free(output);
-	}
-	DL_FOREACH(cras_mixer->input_controls, c) {
-		DL_DELETE(cras_mixer->input_controls, c);
-		free(c);
-	}
-	cras_volume_curve_destroy(cras_mixer->volume_curve);
-	snd_mixer_close(cras_mixer->mixer);
+	mixer_control_destroy_list(cras_mixer->main_volume_controls);
+	mixer_control_destroy_list(cras_mixer->main_capture_controls);
+	mixer_control_destroy_list(cras_mixer->output_controls);
+	mixer_control_destroy_list(cras_mixer->input_controls);
+	if (cras_mixer->mixer)
+		snd_mixer_close(cras_mixer->mixer);
 	free(cras_mixer);
 }
 
-const struct cras_volume_curve *cras_alsa_mixer_default_volume_curve(
+int cras_alsa_mixer_has_main_volume(
 		const struct cras_alsa_mixer *cras_mixer)
 {
-	assert(cras_mixer);
-	assert(cras_mixer->volume_curve);
-	return cras_mixer->volume_curve;
+	return !!cras_mixer->main_volume_controls;
+}
+
+int cras_alsa_mixer_has_volume(const struct mixer_control *mixer_control)
+{
+	return mixer_control && mixer_control->has_volume;
 }
 
 void cras_alsa_mixer_set_dBFS(struct cras_alsa_mixer *cras_mixer,
@@ -462,8 +926,6 @@
 			      struct mixer_control *mixer_output)
 {
 	struct mixer_control *c;
-	struct mixer_output_control *output;
-	output = (struct mixer_output_control *)mixer_output;
 	long to_set;
 
 	assert(cras_mixer);
@@ -472,8 +934,8 @@
 	 * combined max of the master controls and the current output.
 	 */
 	to_set = dBFS + cras_mixer->max_volume_dB;
-	if (mixer_output)
-		to_set += output->max_volume_dB;
+	if (cras_alsa_mixer_has_volume(mixer_output))
+		to_set += mixer_output->max_volume_dB;
 	/* Go through all the controls, set the volume level for each,
 	 * taking the value closest but greater than the desired volume.  If the
 	 * entire volume can't be set on the current control, move on to the
@@ -482,17 +944,16 @@
 	 * set to 0dB. */
 	DL_FOREACH(cras_mixer->main_volume_controls, c) {
 		long actual_dB;
-		snd_mixer_selem_set_playback_dB_all(c->elem, to_set, 1);
-		snd_mixer_selem_get_playback_dB(c->elem,
-						SND_MIXER_SCHN_FRONT_LEFT,
-						&actual_dB);
+
+		if (!c->has_volume)
+			continue;
+		mixer_control_set_dBFS(c, to_set);
+		mixer_control_get_dBFS(c, &actual_dB);
 		to_set -= actual_dB;
 	}
 	/* Apply the rest to the output-specific control. */
-	if (mixer_output && mixer_output->elem && mixer_output->has_volume)
-		snd_mixer_selem_set_playback_dB_all(mixer_output->elem,
-						    to_set,
-						    1);
+	if (cras_alsa_mixer_has_volume(mixer_output))
+		mixer_control_set_dBFS(mixer_output, to_set);
 }
 
 long cras_alsa_mixer_get_dB_range(struct cras_alsa_mixer *cras_mixer)
@@ -505,11 +966,10 @@
 long cras_alsa_mixer_get_output_dB_range(
 		struct mixer_control *mixer_output)
 {
-	struct mixer_output_control *output;
-	if (!mixer_output || !mixer_output->elem || !mixer_output->has_volume)
+	if (!cras_alsa_mixer_has_volume(mixer_output))
 		return 0;
-	output = (struct mixer_output_control *)mixer_output;
-	return output->max_volume_dB - output->min_volume_dB;
+
+	return mixer_output->max_volume_dB - mixer_output->min_volume_dB;
 }
 
 void cras_alsa_mixer_set_capture_dBFS(struct cras_alsa_mixer *cras_mixer,
@@ -528,18 +988,17 @@
 	 * set the rest of the controls should be set to 0dB. */
 	DL_FOREACH(cras_mixer->main_capture_controls, c) {
 		long actual_dB;
-		snd_mixer_selem_set_capture_dB_all(c->elem, to_set, 1);
-		snd_mixer_selem_get_capture_dB(c->elem,
-					       SND_MIXER_SCHN_FRONT_LEFT,
-					       &actual_dB);
+
+		if (!c->has_volume)
+			continue;
+		mixer_control_set_dBFS(c, to_set);
+		mixer_control_get_dBFS(c, &actual_dB);
 		to_set -= actual_dB;
 	}
 
 	/* Apply the reset to input specific control */
-	if (mixer_input && mixer_input->elem && mixer_input->has_volume)
-		snd_mixer_selem_set_capture_dB_all(mixer_input->elem,
-						   to_set, 1);
-	assert(cras_mixer);
+	if (cras_alsa_mixer_has_volume(mixer_input))
+		mixer_control_set_dBFS(mixer_input, to_set);
 }
 
 long cras_alsa_mixer_get_minimum_capture_gain(
@@ -547,38 +1006,34 @@
 		struct mixer_control *mixer_input)
 {
 	struct mixer_control *c;
-	long min, max, total_min;
+	long total_min = 0;
 
 	assert(cmix);
-	total_min = 0;
 	DL_FOREACH(cmix->main_capture_controls, c)
-		if (snd_mixer_selem_get_capture_dB_range(c->elem,
-							 &min, &max) == 0)
-			total_min += min;
-
-	if (mixer_input && snd_mixer_selem_get_capture_dB_range(
-			mixer_input->elem, &min, &max) == 0)
-		total_min += min;
+		if (c->has_volume)
+			total_min += c->min_volume_dB;
+	if (mixer_input &&
+	    mixer_input->has_volume)
+		total_min += mixer_input->min_volume_dB;
 
 	return total_min;
 }
 
-long cras_alsa_mixer_get_maximum_capture_gain(struct cras_alsa_mixer *cmix,
+long cras_alsa_mixer_get_maximum_capture_gain(
+		struct cras_alsa_mixer *cmix,
 		struct mixer_control *mixer_input)
 {
 	struct mixer_control *c;
-	long min, max, total_max;
+	long total_max = 0;
 
 	assert(cmix);
-	total_max = 0;
 	DL_FOREACH(cmix->main_capture_controls, c)
-		if (snd_mixer_selem_get_capture_dB_range(c->elem,
-							 &min, &max) == 0)
-			total_max += max;
+		if (c->has_volume)
+			total_max += c->max_volume_dB;
 
-	if (mixer_input && snd_mixer_selem_get_capture_dB_range(
-			mixer_input->elem, &min, &max) == 0)
-		total_max += max;
+	if (mixer_input &&
+	    mixer_input->has_volume)
+		total_max += mixer_input->max_volume_dB;
 
 	return total_max;
 }
@@ -588,14 +1043,14 @@
 			      struct mixer_control *mixer_output)
 {
 	assert(cras_mixer);
+
 	if (cras_mixer->playback_switch) {
 		snd_mixer_selem_set_playback_switch_all(
 				cras_mixer->playback_switch, !muted);
-		return;
 	}
-	if (mixer_output && mixer_output->has_mute)
-		snd_mixer_selem_set_playback_switch_all(
-				mixer_output->elem, !muted);
+	if (mixer_output && mixer_output->has_mute) {
+		mixer_control_set_mute(mixer_output, muted);
+	}
 }
 
 void cras_alsa_mixer_set_capture_mute(struct cras_alsa_mixer *cras_mixer,
@@ -609,8 +1064,7 @@
 		return;
 	}
 	if (mixer_input && mixer_input->has_mute)
-		snd_mixer_selem_set_capture_switch_all(
-				mixer_input->elem, !muted);
+		mixer_control_set_mute(mixer_input, muted);
 }
 
 void cras_alsa_mixer_list_outputs(struct cras_alsa_mixer *cras_mixer,
@@ -632,44 +1086,77 @@
 const char *cras_alsa_mixer_get_control_name(
 		const struct mixer_control *control)
 {
-	return snd_mixer_selem_get_name(control->elem);
+	if (!control)
+		return NULL;
+	return control->name;
+}
+
+struct mixer_control *cras_alsa_mixer_get_control_matching_name(
+		struct cras_alsa_mixer *cras_mixer,
+		enum CRAS_STREAM_DIRECTION dir, const char *name,
+		int create_missing)
+{
+	struct mixer_control *c;
+
+	assert(cras_mixer);
+	if (!name)
+		return NULL;
+
+	if (dir == CRAS_STREAM_OUTPUT) {
+		c = get_control_matching_name(
+				cras_mixer->output_controls, name);
+	} else if (dir == CRAS_STREAM_INPUT) {
+		c = get_control_matching_name(
+				cras_mixer->input_controls, name);
+	} else {
+		return NULL;
+        }
+
+	/* TODO: Allowing creation of a new control is a workaround: we
+	 * should pass the input names in ucm config to
+	 * cras_alsa_mixer_create. */
+	if (!c && cras_mixer->mixer && create_missing) {
+		int rc = add_control_by_name(cras_mixer, dir, name);
+		if (rc)
+			return NULL;
+		c = cras_alsa_mixer_get_control_matching_name(
+				cras_mixer, dir, name, 0);
+	}
+	return c;
+}
+
+struct mixer_control *cras_alsa_mixer_get_control_for_section(
+		struct cras_alsa_mixer *cras_mixer,
+		const struct ucm_section *section)
+{
+	assert(cras_mixer && section);
+	if (section->mixer_name) {
+		return cras_alsa_mixer_get_control_matching_name(
+			   cras_mixer, section->dir, section->mixer_name, 0);
+	} else if (section->coupled) {
+		return cras_alsa_mixer_get_control_matching_name(
+			   cras_mixer, section->dir, section->name, 0);
+	}
+	return NULL;
 }
 
 struct mixer_control *cras_alsa_mixer_get_output_matching_name(
-		const struct cras_alsa_mixer *cras_mixer,
+		struct cras_alsa_mixer *cras_mixer,
 		const char * const name)
 {
-	assert(cras_mixer);
-	return get_control_matching_name(cras_mixer->output_controls, name);
+	return cras_alsa_mixer_get_control_matching_name(
+			cras_mixer, CRAS_STREAM_OUTPUT, name, 0);
 }
 
 struct mixer_control *cras_alsa_mixer_get_input_matching_name(
 		struct cras_alsa_mixer *cras_mixer,
 		const char *name)
 {
-	struct mixer_control *c = NULL;
-	snd_mixer_elem_t *elem;
-
-	assert(cras_mixer);
-	c = get_control_matching_name(cras_mixer->input_controls, name);
-	if (c)
-		return c;
-
-	/* TODO: This is a workaround, we should pass the input names in
-	 * ucm config to cras_alsa_mixer_create. */
-	for (elem = snd_mixer_first_elem(cras_mixer->mixer);
-			elem != NULL; elem = snd_mixer_elem_next(elem)) {
-		const char *control_name;
-		control_name = snd_mixer_selem_get_name(elem);
-
-		if (control_name == NULL)
-			continue;
-		if (strcmp(name, control_name) == 0) {
-			if (add_input_control(cras_mixer, elem) == 0)
-				return cras_mixer->input_controls->prev;
-		}
-	}
-	return NULL;
+	/* TODO: Allowing creation of a new control is a workaround: we
+	 * should pass the input names in ucm config to
+	 * cras_alsa_mixer_create. */
+	return cras_alsa_mixer_get_control_matching_name(
+			cras_mixer, CRAS_STREAM_INPUT, name, 1);
 }
 
 int cras_alsa_mixer_set_output_active_state(
@@ -679,28 +1166,5 @@
 	assert(output);
 	if (!output->has_mute)
 		return -1;
-	return snd_mixer_selem_set_playback_switch_all(output->elem, active);
-}
-
-struct cras_volume_curve *cras_alsa_mixer_create_volume_curve_for_name(
-		const struct cras_alsa_mixer *cmix,
-		const char *name)
-{
-	if (cmix != NULL)
-		return cras_card_config_get_volume_curve_for_control(
-				cmix->config, name);
-	else
-		return cras_card_config_get_volume_curve_for_control(NULL,
-								     name);
-}
-
-struct cras_volume_curve *cras_alsa_mixer_get_output_volume_curve(
-		const struct mixer_control *control)
-{
-	struct mixer_output_control *output;
-	output = (struct mixer_output_control *)control;
-	if (output)
-		return output->volume_curve;
-	else
-		return NULL;
+	return mixer_control_set_mute(output, !active);
 }
diff --git a/cras/src/server/cras_alsa_mixer.h b/cras/src/server/cras_alsa_mixer.h
index b2521ff..4e0ca1b 100644
--- a/cras/src/server/cras_alsa_mixer.h
+++ b/cras/src/server/cras_alsa_mixer.h
@@ -9,6 +9,8 @@
 #include <alsa/asoundlib.h>
 #include <iniparser.h>
 
+#include "cras_types.h"
+
 /* cras_alsa_mixer represents the alsa mixer interface for an alsa card.  It
  * houses the volume and mute controls as well as playback switches for
  * headphones and mic.
@@ -18,26 +20,43 @@
 struct cras_alsa_mixer;
 struct cras_volume_curve;
 struct cras_card_config;
+struct mixer_name;
+struct ucm_section;
 
 /* Creates a cras_alsa_mixer instance for the given alsa device.
  * Args:
  *    card_name - Name of the card to open a mixer for.  This is an alsa name of
  *      the form "hw:X" where X ranges from 0 to 31 inclusive.
- *    config - Config info for this card, can be NULL if none found.
- *    output_names_extra - An array of extra output mixer control names. The
- *      array may contain NULL entries which should be ignored.
- *    output_names_extra_size - The length of the output_names_extra array.
- *    extra_main_volume - Name of extra main volume if any.
  * Returns:
  *    A pointer to the newly created cras_alsa_mixer which must later be freed
- *    by calling cras_alsa_mixer_destroy.
+ *    by calling cras_alsa_mixer_destroy. The control in the mixer is not added
+ *    yet.
  */
-struct cras_alsa_mixer *cras_alsa_mixer_create(
-		const char *card_name,
-		const struct cras_card_config *config,
-		const char *output_names_extra[],
-		size_t output_names_extra_size,
-		const char *extra_main_volume);
+struct cras_alsa_mixer *cras_alsa_mixer_create(const char *card_name);
+
+/* Adds controls to a cras_alsa_mixer from the given UCM section.
+ * Args:
+ *    cmix - A pointer to cras_alsa_mixer.
+ *    section - A UCM section.
+ * Returns:
+ *    0 on success. Negative error code otherwise.
+ */
+int cras_alsa_mixer_add_controls_in_section(
+		struct cras_alsa_mixer *cmix,
+		struct ucm_section *section);
+
+/* Adds controls to a cras_alsa_mixer instance by name matching.
+ * Args:
+ *    cmix - A pointer to cras_alsa_mixer.
+ *    extra_controls - A list array of extra mixer control names, always added.
+ *    coupled_controls - A list of coupled mixer control names.
+ * Returns:
+ *    0 on success. Other error code if error happens.
+ */
+int cras_alsa_mixer_add_controls_by_name_matching(
+		struct cras_alsa_mixer *cmix,
+		struct mixer_name *extra_controls,
+		struct mixer_name *coupled_controls);
 
 /* Destroys a cras_alsa_mixer that was returned from cras_alsa_mixer_create.
  * Args:
@@ -46,11 +65,11 @@
  */
 void cras_alsa_mixer_destroy(struct cras_alsa_mixer *cras_mixer);
 
-/* Gets the default volume curve for this mixer.  This curve will be used if
- * there is not output-node specific curve to use.
- */
-const struct cras_volume_curve *cras_alsa_mixer_default_volume_curve(
-		const struct cras_alsa_mixer *mixer);
+/* Returns if the mixer has any main volume control. */
+int cras_alsa_mixer_has_main_volume(const struct cras_alsa_mixer *cras_mixer);
+
+/* Returns if the mixer control supports volume adjust. */
+int cras_alsa_mixer_has_volume(const struct mixer_control *mixer_control);
 
 /* Sets the output volume for the device associated with this mixer.
  * Args:
@@ -114,9 +133,10 @@
 
 /* Sets the playback switch for the device.
  * Args:
- *    cras_mixer - Mixer to set the volume in.
+ *    cras_mixer - Mixer to set the playback switch.
  *    muted - 1 if muted, 0 if not.
- *    mixer_output - The mixer output to mute if no master mute.
+ *    mixer_output - The output specific mixer control to mute/unmute. Pass NULL
+ *                   to skip it.
  */
 void cras_alsa_mixer_set_mute(struct cras_alsa_mixer *cras_mixer,
 			      int muted,
@@ -154,6 +174,35 @@
 const char *cras_alsa_mixer_get_control_name(
 		const struct mixer_control *control);
 
+/* Returns the mixer control matching the given direction and name.
+ * Args:
+ *    cras_mixer - Mixer to search for a control.
+ *    dir - Control's direction (OUTPUT or INPUT).
+ *    name - Name to search for.
+ *    create_missing - When non-zero, attempt to create a new control with
+ *		       the given name.
+ * Returns:
+ *    A pointer to the matching mixer control, or NULL if none found.
+ */
+struct mixer_control *cras_alsa_mixer_get_control_matching_name(
+		struct cras_alsa_mixer *cras_mixer,
+		enum CRAS_STREAM_DIRECTION dir, const char *name,
+		int create_missing);
+
+/* Returns the mixer control associated with the given section.
+ * The control is the one that matches 'mixer_name', or if that is not defined
+ * then it will be the control matching 'section->name', based on the
+ * coupled mixer controls.
+ * Args:
+ *    cras_mixer - Mixer to search for a control.
+ *    section - Associated UCM section.
+ * Returns:
+ *    A pointer to the associated mixer control, or NULL if none found.
+ */
+struct mixer_control *cras_alsa_mixer_get_control_for_section(
+		struct cras_alsa_mixer *cras_mixer,
+		const struct ucm_section *section);
+
 /* Finds the output that matches the given string.  Used to match Jacks to Mixer
  * elements.
  * Args:
@@ -163,7 +212,7 @@
  *    A pointer to the output with a mixer control that matches "name".
  */
 struct mixer_control *cras_alsa_mixer_get_output_matching_name(
-		const struct cras_alsa_mixer *cras_mixer,
+		struct cras_alsa_mixer *cras_mixer,
 		const char *name);
 
 /* Finds the mixer control for that matches the control name of input control
@@ -183,16 +232,4 @@
 		struct mixer_control *output,
 		int active);
 
-/* Returns a volume curve for the given output node name.  The name can be that
- * of a control or of a Jack.  Looks for an entry in the ini file (See README
- * for format), or falls back to the default volume curve if the ini file
- * doesn't specify a curve for this output. */
-struct cras_volume_curve *cras_alsa_mixer_create_volume_curve_for_name(
-		const struct cras_alsa_mixer *cmix,
-		const char *name);
-
-/* Returns a volume curve stored in the output control element, can be null. */
-struct cras_volume_curve *cras_alsa_mixer_get_output_volume_curve(
-		const struct mixer_control *control);
-
 #endif /* _CRAS_ALSA_MIXER_H */
diff --git a/cras/src/server/cras_alsa_mixer_name.c b/cras/src/server/cras_alsa_mixer_name.c
new file mode 100644
index 0000000..7f887b9
--- /dev/null
+++ b/cras/src/server/cras_alsa_mixer_name.c
@@ -0,0 +1,134 @@
+/* Copyright 2016 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 <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+
+#include "cras_alsa_mixer_name.h"
+#include "utlist.h"
+
+struct mixer_name *mixer_name_add(struct mixer_name *names,
+				  const char *name,
+				  enum CRAS_STREAM_DIRECTION dir,
+				  mixer_name_type type)
+{
+	struct mixer_name *m_name;
+
+	if (!name)
+		return names;
+
+	m_name = (struct mixer_name *)calloc(1, sizeof(struct mixer_name));
+	if (!m_name)
+		return names;
+
+	m_name->name = strdup(name);
+	if (!m_name->name) {
+		free(m_name);
+		return names;
+	}
+	m_name->dir = dir;
+	m_name->type = type;
+
+	DL_APPEND(names, m_name);
+	return names;
+}
+
+struct mixer_name *mixer_name_add_array(struct mixer_name *names,
+					const char * const *name_array,
+					size_t name_array_size,
+					enum CRAS_STREAM_DIRECTION dir,
+					mixer_name_type type)
+{
+	size_t i;
+	for (i = 0; i < name_array_size; i++)
+		names = mixer_name_add(names, name_array[i], dir, type);
+	return names;
+}
+
+void mixer_name_free(struct mixer_name *names)
+{
+	struct mixer_name *m_name;
+	DL_FOREACH(names, m_name) {
+		DL_DELETE(names, m_name);
+		free((void*)m_name->name);
+		free(m_name);
+	}
+}
+
+struct mixer_name *mixer_name_find(struct mixer_name *names,
+				   const char *name,
+				   enum CRAS_STREAM_DIRECTION dir,
+				   mixer_name_type type)
+{
+	if (!name && type == MIXER_NAME_UNDEFINED)
+		return NULL;
+
+	struct mixer_name *m_name;
+	DL_FOREACH(names, m_name) {
+		/* Match the direction. */
+		if (dir != m_name->dir)
+			continue;
+		/* Match the type unless the type is UNDEFINED. */
+		if (type != MIXER_NAME_UNDEFINED &&
+		    type != m_name->type)
+			continue;
+		/* Match the name if it is non-NULL, or return the first
+		 * item with the correct type when the name is not defined. */
+		if ((type != MIXER_NAME_UNDEFINED && !name) ||
+		    (name && !strcmp(m_name->name, name)))
+			return m_name;
+	}
+	return NULL;
+}
+
+static const char *mixer_name_type_str(enum CRAS_STREAM_DIRECTION dir,
+				       mixer_name_type type)
+{
+	switch (dir) {
+	case CRAS_STREAM_OUTPUT:
+		switch (type) {
+		case MIXER_NAME_VOLUME:
+			return "output volume";
+		case MIXER_NAME_MAIN_VOLUME:
+			return "main volume";
+		case MIXER_NAME_UNDEFINED:
+			break;
+		}
+		break;
+	case CRAS_STREAM_INPUT:
+		switch (type) {
+		case MIXER_NAME_VOLUME:
+			return "input volume";
+		case MIXER_NAME_MAIN_VOLUME:
+			return "main capture";
+		case MIXER_NAME_UNDEFINED:
+			break;
+		}
+		break;
+	case CRAS_STREAM_UNDEFINED:
+	case CRAS_STREAM_POST_MIX_PRE_DSP:
+	case CRAS_NUM_DIRECTIONS:
+		break;
+	}
+	return "undefined";
+}
+
+void mixer_name_dump(struct mixer_name *names, const char *message)
+{
+	struct mixer_name *m_name;
+
+	if (!names) {
+		syslog(LOG_DEBUG, "%s: empty", message);
+		return;
+	}
+
+	syslog(LOG_DEBUG, "%s:", message);
+	DL_FOREACH(names, m_name) {
+		const char *type_str =
+			mixer_name_type_str(m_name->dir, m_name->type);
+		syslog(LOG_DEBUG, "    %s %s", m_name->name, type_str);
+	}
+}
diff --git a/cras/src/server/cras_alsa_mixer_name.h b/cras/src/server/cras_alsa_mixer_name.h
new file mode 100644
index 0000000..0a91454
--- /dev/null
+++ b/cras/src/server/cras_alsa_mixer_name.h
@@ -0,0 +1,104 @@
+/* Copyright 2016 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_MIXER_NAME_H
+#define _CRAS_ALSA_MIXER_NAME_H
+
+#include "cras_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Type of mixer control. */
+typedef enum mixer_name_type {
+	MIXER_NAME_UNDEFINED,
+	MIXER_NAME_MAIN_VOLUME,
+	MIXER_NAME_VOLUME,
+} mixer_name_type;
+
+/* Represents a list of mixer names found in ALSA. */
+struct mixer_name {
+	const char* name;
+	enum CRAS_STREAM_DIRECTION dir;
+	mixer_name_type type;
+	struct mixer_name *prev, *next;
+};
+
+/* Add a name to the list.
+ *
+ * Args:
+ *    names - A list of controls (may be NULL).
+ *    name - The name to add.
+ *    dir - The direction for this control.
+ *    type - The type control being added.
+ *
+ * Returns:
+ *    Returns the new head of the list (which changes only
+ *    when names is NULL).
+ */
+struct mixer_name *mixer_name_add(struct mixer_name *names,
+				  const char *name,
+				  enum CRAS_STREAM_DIRECTION dir,
+				  mixer_name_type type);
+
+/* Add an array of name to the list.
+ *
+ * Args:
+ *    names - A list of controls (may be NULL).
+ *    name_array - The names to add.
+ *    name_array_size - The size of name_array.
+ *    dir - The direction for these controls.
+ *    type - The type controls being added.
+ *
+ * Returns:
+ *    Returns the new head of the list (which changes only
+ *    when names is NULL).
+ */
+struct mixer_name *mixer_name_add_array(struct mixer_name *names,
+					const char * const *name_array,
+					size_t name_array_size,
+					enum CRAS_STREAM_DIRECTION dir,
+					mixer_name_type type);
+
+/* Frees a list of names.
+ *
+ * Args:
+ *    names - A list of names.
+ */
+void mixer_name_free(struct mixer_name *names);
+
+/* Find the mixer_name for the given direction, name, and type.
+ *
+ * Args:
+ *    names - A list of names (may be NULL).
+ *    name - The name to find, or NULL to match by type.
+
+ *    dir - The direction to match.
+ *    type - The type to match, or MIXER_NAME_UNDEFINED to
+ *           match by name only.
+ *
+ * Returns:
+ *    Returns a pointer to the matching struct mixer_name or NULL if
+ *    not found.
+ */
+struct mixer_name *mixer_name_find(struct mixer_name *names,
+				   const char *name,
+				   enum CRAS_STREAM_DIRECTION dir,
+				   mixer_name_type type);
+
+/* Dump the list of mixer names to DEBUG logs.
+ *
+ * Args:
+ *    names - A list of names to dump.
+ *    message - A message to print beforehand.
+ */
+void mixer_name_dump(struct mixer_name *names, const char *message);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _CRAS_ALSA_MIXER_NAME_H */
diff --git a/cras/src/server/cras_alsa_ucm.c b/cras/src/server/cras_alsa_ucm.c
index c39f61c..8aa4891 100644
--- a/cras/src/server/cras_alsa_ucm.c
+++ b/cras/src/server/cras_alsa_ucm.c
@@ -5,12 +5,16 @@
 
 #include <alsa/asoundlib.h>
 #include <alsa/use-case.h>
+#include <ctype.h>
+#include <string.h>
 #include <syslog.h>
 
 #include "cras_alsa_ucm.h"
+#include "utlist.h"
 
-static const char default_verb[] = "HiFi";
 static const char jack_var[] = "JackName";
+static const char jack_type_var[] = "JackType";
+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";
@@ -20,18 +24,59 @@
 static const char mixer_var[] = "MixerName";
 static const char swap_mode_suffix[] = "Swap Mode";
 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 capture_device_name_var[] = "CapturePCM";
+static const char capture_device_rate_var[] = "CaptureRate";
+static const char capture_channel_map_var[] = "CaptureChannelMap";
+static const char coupled_mixers[] = "CoupledMixers";
+/* Set this value in a SectionDevice to specify the maximum software gain in dBm
+ * and enable software gain on this node. */
+static const char max_software_gain[] = "MaxSoftwareGain";
+/* Set this value in a SectionDevice to specify the default node gain in dBm. */
+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";
 
-static int device_enabled(snd_use_case_mgr_t *mgr, const char *dev)
+/* Use case verbs corresponding to CRAS_STREAM_TYPE. */
+static const char *use_case_verbs[] = {
+	"HiFi",
+	"Multimedia",
+	"Voice Call",
+	"Speech",
+	"Pro Audio",
+};
+
+/* Represents a list of section names found in UCM. */
+struct section_name {
+	const char* name;
+	struct section_name  *prev, *next;
+};
+
+struct cras_use_case_mgr {
+	snd_use_case_mgr_t *mgr;
+	const char *name;
+	unsigned int avail_use_cases;
+	enum CRAS_STREAM_TYPE use_case;
+};
+
+static inline const char *uc_verb(struct cras_use_case_mgr *mgr)
+{
+	return use_case_verbs[mgr->use_case];
+}
+
+static int device_enabled(struct cras_use_case_mgr *mgr, const char *dev)
 {
 	const char **list;
 	unsigned int i;
 	int num_devs;
 	int enabled = 0;
 
-	num_devs = snd_use_case_get_list(mgr, "_enadevs", &list);
+	num_devs = snd_use_case_get_list(mgr->mgr, "_enadevs", &list);
 	if (num_devs <= 0)
 		return 0;
 
@@ -45,13 +90,13 @@
 	return enabled;
 }
 
-static int modifier_enabled(snd_use_case_mgr_t *mgr, const char *mod)
+static int modifier_enabled(struct cras_use_case_mgr *mgr, const char *mod)
 {
 	const char **list;
 	unsigned int mod_idx;
 	int num_mods;
 
-	num_mods = snd_use_case_get_list(mgr, "_enamods", &list);
+	num_mods = snd_use_case_get_list(mgr->mgr, "_enamods", &list);
 	if (num_mods <= 0)
 		return 0;
 
@@ -63,26 +108,43 @@
 	return (mod_idx < (unsigned int)num_mods);
 }
 
-static int get_var(snd_use_case_mgr_t *mgr, const char *var, const char *dev,
-		   const char *verb, const char **value)
+static int get_var(struct cras_use_case_mgr *mgr, const char *var,
+		   const char *dev, const char *verb, const char **value)
 {
 	char *id;
 	int rc;
+	size_t len = strlen(var) + strlen(dev) + strlen(verb) + 4;
 
-	id = (char *)malloc(strlen(var) + strlen(dev) + strlen(verb) + 4);
+	id = (char *)malloc(len);
 	if (!id)
 		return -ENOMEM;
-	sprintf(id, "=%s/%s/%s", var, dev, verb);
-	rc = snd_use_case_get(mgr, id, value);
+	snprintf(id, len, "=%s/%s/%s", var, dev, verb);
+	rc = snd_use_case_get(mgr->mgr, id, value);
 
 	free((void *)id);
 	return rc;
 }
 
-static int ucm_set_modifier_enabled(snd_use_case_mgr_t *mgr, const char *mod,
-				    int enable)
+static int get_int(struct cras_use_case_mgr *mgr, const char *var,
+		   const char *dev, const char *verb, int *value)
 {
-	return snd_use_case_set(mgr, enable ? "_enamod" : "_dismod", mod);
+	const char *str_value;
+	int rc;
+
+	if (!value)
+		return -EINVAL;
+	rc = get_var(mgr, var, dev, verb, &str_value);
+	if (rc != 0)
+		return rc;
+	*value = atoi(str_value);
+	free((void *)str_value);
+	return 0;
+}
+
+static int ucm_set_modifier_enabled(struct cras_use_case_mgr *mgr,
+				    const char *mod, int enable)
+{
+	return snd_use_case_set(mgr->mgr, enable ? "_enamod" : "_dismod", mod);
 }
 
 static int ucm_str_ends_with_suffix(const char *str, const char *suffix)
@@ -96,7 +158,7 @@
 	return strncmp(str + len_str - len_suffix, suffix, len_suffix) == 0;
 }
 
-static int ucm_section_exists_with_name(snd_use_case_mgr_t *mgr,
+static int ucm_section_exists_with_name(struct cras_use_case_mgr *mgr,
 		const char *name, const char *identifier)
 {
 	const char **list;
@@ -104,7 +166,7 @@
 	int num_entries;
 	int exist = 0;
 
-	num_entries = snd_use_case_get_list(mgr, identifier, &list);
+	num_entries = snd_use_case_get_list(mgr->mgr, identifier, &list);
 	if (num_entries <= 0)
 		return 0;
 
@@ -122,7 +184,7 @@
 	return exist;
 }
 
-static int ucm_section_exists_with_suffix(snd_use_case_mgr_t *mgr,
+static int ucm_section_exists_with_suffix(struct cras_use_case_mgr *mgr,
 		const char *suffix, const char *identifier)
 {
 	const char **list;
@@ -130,7 +192,7 @@
 	int num_entries;
 	int exist = 0;
 
-	num_entries = snd_use_case_get_list(mgr, identifier, &list);
+	num_entries = snd_use_case_get_list(mgr->mgr, identifier, &list);
 	if (num_entries <= 0)
 		return 0;
 
@@ -148,28 +210,44 @@
 	return exist;
 }
 
-static int ucm_mod_exists_with_suffix(snd_use_case_mgr_t *mgr,
-				const char *suffix)
+static int ucm_mod_exists_with_suffix(struct cras_use_case_mgr *mgr,
+				      const char *suffix)
 {
-	return ucm_section_exists_with_suffix(mgr, suffix, "_modifiers/HiFi");
+	char *identifier;
+	int rc;
+
+	identifier = snd_use_case_identifier("_modifiers/%s", uc_verb(mgr));
+	rc = ucm_section_exists_with_suffix(mgr, suffix, identifier);
+	free(identifier);
+	return rc;
 }
 
-static int ucm_mod_exists_with_name(snd_use_case_mgr_t *mgr, const char *name)
+static int ucm_mod_exists_with_name(struct cras_use_case_mgr *mgr,
+				    const char *name)
 {
-	return ucm_section_exists_with_name(mgr, name, "_modifiers/HiFi");
+	char *identifier;
+	int rc;
+
+	identifier = snd_use_case_identifier("_modifiers/%s", uc_verb(mgr));
+	rc = ucm_section_exists_with_name(mgr, name, identifier);
+	free(identifier);
+	return rc;
 }
 
-static char *ucm_get_section_for_var(snd_use_case_mgr_t *mgr, const char *var,
-				     const char *value, const char *identifier,
-				     enum CRAS_STREAM_DIRECTION direction)
+/* Get a list of section names whose variable is the matched value. */
+static struct section_name * ucm_get_sections_for_var(
+		struct cras_use_case_mgr *mgr,
+		const char *var, const char *value,
+		const char *identifier,
+		enum CRAS_STREAM_DIRECTION direction)
 {
 	const char **list;
-	char *section_name = NULL;
+	struct section_name *section_names = NULL, *s_name;
 	unsigned int i;
 	int num_entries;
 	int rc;
 
-	num_entries = snd_use_case_get_list(mgr, identifier, &list);
+	num_entries = snd_use_case_get_list(mgr->mgr, identifier, &list);
 	if (num_entries <= 0)
 		return NULL;
 
@@ -181,43 +259,52 @@
 		if (!list[i])
 			continue;
 
-		/* Skip mic seciton for output, only check mic for input. */
-		if (!strcmp(list[i], "Mic")) {
-			if (direction == CRAS_STREAM_OUTPUT)
-				continue;
-		} else {
-			if (direction == CRAS_STREAM_INPUT)
-				continue;
-		}
-
-		rc = get_var(mgr, var, list[i], default_verb, &this_value);
+		rc = get_var(mgr, var, list[i], uc_verb(mgr), &this_value);
 		if (rc)
 			continue;
 
 		if (!strcmp(value, this_value)) {
-			section_name = strdup(list[i]);
-			free((void *)this_value);
-			break;
+			s_name = (struct section_name *)malloc(
+					sizeof(struct section_name));
+
+			if (!s_name) {
+				syslog(LOG_ERR, "Failed to allocate memory");
+				free((void *)this_value);
+				break;
+			}
+
+			s_name->name = strdup(list[i]);
+			DL_APPEND(section_names, s_name);
 		}
 		free((void *)this_value);
 	}
 
 	snd_use_case_free_list(list, num_entries);
-	return section_name;
+	return section_names;
 }
 
-static char *ucm_get_dev_for_var(snd_use_case_mgr_t *mgr, const char *var,
-			  const char *value, enum CRAS_STREAM_DIRECTION dir) {
-	return ucm_get_section_for_var(mgr, var, value, "_devices/HiFi", dir);
+static struct section_name *ucm_get_devices_for_var(
+		struct cras_use_case_mgr *mgr,
+		const char *var, const char *value,
+		enum CRAS_STREAM_DIRECTION dir)
+{
+	char *identifier;
+	struct section_name *section_names;
+
+	identifier = snd_use_case_identifier("_devices/%s", uc_verb(mgr));
+	section_names = ucm_get_sections_for_var(mgr, var, value, identifier,
+						 dir);
+	free(identifier);
+	return section_names;
 }
 
 static const char *ucm_get_playback_device_name_for_dev(
-	snd_use_case_mgr_t *mgr, const char *dev)
+		struct cras_use_case_mgr *mgr, const char *dev)
 {
 	const char *name = NULL;
 	int rc;
 
-	rc = get_var(mgr, playback_device_name_var, dev, default_verb, &name);
+	rc = get_var(mgr, playback_device_name_var, dev, uc_verb(mgr), &name);
 	if (rc)
 		return NULL;
 
@@ -225,66 +312,140 @@
 }
 
 static const char *ucm_get_capture_device_name_for_dev(
-	snd_use_case_mgr_t *mgr, const char *dev)
+		struct cras_use_case_mgr *mgr, const char *dev)
 {
 	const char *name = NULL;
 	int rc;
 
-	rc = get_var(mgr, capture_device_name_var, dev, default_verb, &name);
+	rc = get_var(mgr, capture_device_name_var, dev, uc_verb(mgr), &name);
 	if (rc)
 		return NULL;
 
 	return name;
 }
 
+/* Get a list of mixer names specified in a UCM variable separated by ",".
+ * E.g. "Left Playback,Right Playback".
+ */
+static struct mixer_name *ucm_get_mixer_names(struct cras_use_case_mgr *mgr,
+				const char *dev, const char* var,
+				enum CRAS_STREAM_DIRECTION dir,
+				mixer_name_type type)
+{
+	const char *names_in_string = NULL;
+	int rc;
+	char *tokens, *name, *laststr;
+	struct mixer_name *names = NULL;
+
+	rc = get_var(mgr, var, dev, uc_verb(mgr), &names_in_string);
+	if (rc)
+		return NULL;
+
+	tokens = strdup(names_in_string);
+	name = strtok_r(tokens, ",", &laststr);
+	while (name != NULL) {
+		names = mixer_name_add(names, name, dir, type);
+		name = strtok_r(NULL, ",", &laststr);
+	}
+	free((void*)names_in_string);
+	free(tokens);
+	return names;
+}
+
 /* Exported Interface */
 
-snd_use_case_mgr_t *ucm_create(const char *name)
+struct cras_use_case_mgr *ucm_create(const char *name)
 {
-	snd_use_case_mgr_t *mgr;
+	struct cras_use_case_mgr *mgr;
 	int rc;
+	const char **list;
+	int num_verbs, i, j;
 
 	if (!name)
 		return NULL;
 
-	rc = snd_use_case_mgr_open(&mgr, name);
-	if (rc) {
-		syslog(LOG_ERR, "Can not open ucm for card %s, rc = %d",
-		       name, rc);
+	mgr = (struct cras_use_case_mgr *)malloc(sizeof(*mgr));
+	if (!mgr)
 		return NULL;
+
+	rc = snd_use_case_mgr_open(&mgr->mgr, name);
+	if (rc) {
+		syslog(LOG_WARNING, "Can not open ucm for card %s, rc = %d",
+		       name, rc);
+		goto cleanup;
 	}
 
-	rc = snd_use_case_set(mgr, "_verb", default_verb);
-	if (rc) {
-		syslog(LOG_ERR, "Can not set verb %s for card %s, rc = %d",
-		       default_verb, name, rc);
-		ucm_destroy(mgr);
-		return NULL;
+	mgr->name = name;
+	mgr->avail_use_cases = 0;
+	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) {
+			if (strcmp(list[i], use_case_verbs[j]) == 0)
+				break;
+		}
+		if (j < CRAS_STREAM_NUM_TYPES)
+			mgr->avail_use_cases |= (1 << j);
 	}
+	if (num_verbs > 0)
+		snd_use_case_free_list(list, num_verbs);
+
+	rc = ucm_set_use_case(mgr, CRAS_STREAM_TYPE_DEFAULT);
+	if (rc)
+		goto cleanup_mgr;
 
 	return mgr;
+
+cleanup_mgr:
+	snd_use_case_mgr_close(mgr->mgr);
+cleanup:
+	free(mgr);
+	return NULL;
 }
 
-void ucm_destroy(snd_use_case_mgr_t *mgr)
+void ucm_destroy(struct cras_use_case_mgr *mgr)
 {
-	snd_use_case_mgr_close(mgr);
+	snd_use_case_mgr_close(mgr->mgr);
+	free(mgr);
 }
 
-int ucm_swap_mode_exists(snd_use_case_mgr_t *mgr)
+int ucm_set_use_case(struct cras_use_case_mgr *mgr,
+		     enum CRAS_STREAM_TYPE use_case)
+{
+	int rc;
+
+	if (mgr->avail_use_cases & (1 << use_case)) {
+		mgr->use_case = use_case;
+	} else {
+		syslog(LOG_ERR, "Unavailable use case %d for card %s",
+		       use_case, mgr->name);
+		return -1;
+	}
+
+	rc = snd_use_case_set(mgr->mgr, "_verb", uc_verb(mgr));
+	if (rc) {
+		syslog(LOG_ERR, "Can not set verb %s for card %s, rc = %d",
+		       uc_verb(mgr), mgr->name, rc);
+		return rc;
+	}
+
+	return 0;
+}
+
+int ucm_swap_mode_exists(struct cras_use_case_mgr *mgr)
 {
 	return ucm_mod_exists_with_suffix(mgr, swap_mode_suffix);
 }
 
-int ucm_enable_swap_mode(snd_use_case_mgr_t *mgr, const char *node_name,
-		       int enable)
+int ucm_enable_swap_mode(struct cras_use_case_mgr *mgr, const char *node_name,
+			 int enable)
 {
 	char *swap_mod = NULL;
 	int rc;
-	swap_mod = (char *)malloc(strlen(node_name) + 1 +
-			strlen(swap_mode_suffix) + 1);
+	size_t len = strlen(node_name) + 1 + strlen(swap_mode_suffix) + 1;
+	swap_mod = (char *)malloc(len);
 	if (!swap_mod)
 		return -ENOMEM;
-	sprintf(swap_mod, "%s %s", node_name, swap_mode_suffix);
+	snprintf(swap_mod, len, "%s %s", node_name, swap_mode_suffix);
 	if (!ucm_mod_exists_with_name(mgr, swap_mod)) {
 		syslog(LOG_ERR, "Can not find swap mode modifier %s.", swap_mod);
 		free((void *)swap_mod);
@@ -299,22 +460,22 @@
 	return rc;
 }
 
-int ucm_set_enabled(snd_use_case_mgr_t *mgr, const char *dev, int enable)
+int ucm_set_enabled(struct cras_use_case_mgr *mgr, const char *dev, int enable)
 {
 	if (device_enabled(mgr, dev) == !!enable)
 		return 0;
-
-	return snd_use_case_set(mgr, enable ? "_enadev" : "_disdev", dev);
+	syslog(LOG_DEBUG, "UCM %s %s", enable ? "enable" : "disable", dev);
+	return snd_use_case_set(mgr->mgr, enable ? "_enadev" : "_disdev", dev);
 }
 
-char *ucm_get_flag(snd_use_case_mgr_t *mgr, const char *flag_name)
+char *ucm_get_flag(struct cras_use_case_mgr *mgr, const char *flag_name)
 {
 	char *flag_value = NULL;
 	const char *value;
 	int rc;
 
 	/* Set device to empty string since flag is specified in verb section */
-	rc = get_var(mgr, flag_name, "", default_verb, &value);
+	rc = get_var(mgr, flag_name, "", uc_verb(mgr), &value);
 	if (!rc) {
 		flag_value = strdup(value);
 		free((void *)value);
@@ -323,13 +484,13 @@
 	return flag_value;
 }
 
-char *ucm_get_cap_control(snd_use_case_mgr_t *mgr, const char *ucm_dev)
+char *ucm_get_cap_control(struct cras_use_case_mgr *mgr, const char *ucm_dev)
 {
 	char *control_name = NULL;
 	const char *value;
 	int rc;
 
-	rc = get_var(mgr, cap_var, ucm_dev, default_verb, &value);
+	rc = get_var(mgr, cap_var, ucm_dev, uc_verb(mgr), &value);
 	if (!rc) {
 		control_name = strdup(value);
 		free((void *)value);
@@ -338,13 +499,13 @@
 	return control_name;
 }
 
-char *ucm_get_mic_positions(snd_use_case_mgr_t *mgr)
+char *ucm_get_mic_positions(struct cras_use_case_mgr *mgr)
 {
 	char *control_name = NULL;
 	const char *value;
 	int rc;
 
-	rc = get_var(mgr, mic_positions, "", default_verb, &value);
+	rc = get_var(mgr, mic_positions, "", uc_verb(mgr), &value);
 	if (!rc) {
 		control_name = strdup(value);
 		free((void *)value);
@@ -353,13 +514,13 @@
 	return control_name;
 }
 
-const char *ucm_get_override_type_name(snd_use_case_mgr_t *mgr,
-					const char *dev)
+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, default_verb,
+	rc = get_var(mgr, override_type_name_var, dev, uc_verb(mgr),
 		     &override_type_name);
 	if (rc)
 		return NULL;
@@ -367,31 +528,71 @@
 	return override_type_name;
 }
 
-char *ucm_get_dev_for_jack(snd_use_case_mgr_t *mgr, const char *jack,
+char *ucm_get_dev_for_jack(struct cras_use_case_mgr *mgr, const char *jack,
 			   enum CRAS_STREAM_DIRECTION direction)
 {
-	return ucm_get_dev_for_var(mgr, jack_var, jack, direction);
+	struct section_name *section_names, *c;
+	char *ret = NULL;
+
+	section_names = ucm_get_devices_for_var(mgr, jack_var, jack, direction);
+
+	DL_FOREACH(section_names, c) {
+		if (!strcmp(c->name, "Mic")) {
+			/* Skip mic section for output */
+			if (direction == CRAS_STREAM_OUTPUT)
+				continue;
+		} else {
+			/* Only check mic for input. */
+			if (direction == CRAS_STREAM_INPUT)
+				continue;
+		}
+		ret = strdup(c->name);
+		break;
+	}
+
+	DL_FOREACH(section_names, c) {
+		DL_DELETE(section_names, c);
+		free((void*)c->name);
+		free(c);
+	}
+
+	return ret;
 }
 
-char *ucm_get_dev_for_mixer(snd_use_case_mgr_t *mgr, const char *mixer,
+char *ucm_get_dev_for_mixer(struct cras_use_case_mgr *mgr, const char *mixer,
 			    enum CRAS_STREAM_DIRECTION dir)
 {
-	return ucm_get_dev_for_var(mgr, mixer_var, mixer, dir);
+	struct section_name *section_names, *c;
+	char *ret = NULL;
+
+	section_names = ucm_get_devices_for_var(mgr, mixer_var, mixer, dir);
+
+	if (section_names)
+		ret = strdup(section_names->name);
+
+	DL_FOREACH(section_names, c) {
+		DL_DELETE(section_names, c);
+		free((void*)c->name);
+		free(c);
+	}
+
+	return ret;
 }
 
-const char *ucm_get_edid_file_for_dev(snd_use_case_mgr_t *mgr, const char *dev)
+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, default_verb, &file_name);
+	rc = get_var(mgr, edid_var, dev, uc_verb(mgr), &file_name);
 	if (rc)
 		return NULL;
 
 	return file_name;
 }
 
-const char *ucm_get_dsp_name(snd_use_case_mgr_t *mgr, const char *ucm_dev,
+const char *ucm_get_dsp_name(struct cras_use_case_mgr *mgr, const char *ucm_dev,
 			     int direction)
 {
 	const char *var = (direction == CRAS_STREAM_OUTPUT)
@@ -400,44 +601,71 @@
 	const char *dsp_name = NULL;
 	int rc;
 
-	rc = get_var(mgr, var, ucm_dev, default_verb, &dsp_name);
+	rc = get_var(mgr, var, ucm_dev, uc_verb(mgr), &dsp_name);
 	if (rc)
 		return NULL;
 
 	return dsp_name;
 }
 
-const char *ucm_get_dsp_name_default(snd_use_case_mgr_t *mgr, int direction)
+const char *ucm_get_dsp_name_default(struct cras_use_case_mgr *mgr,
+				     int direction)
 {
 	return ucm_get_dsp_name(mgr, "", direction);
 }
 
-unsigned int ucm_get_min_buffer_level(snd_use_case_mgr_t *mgr)
+unsigned int ucm_get_min_buffer_level(struct cras_use_case_mgr *mgr)
 {
-	const char *val = NULL;
+	int value;
 	int rc;
 
-	rc = get_var(mgr, min_buffer_level_var, "", default_verb, &val);
+	rc = get_int(mgr, min_buffer_level_var, "", uc_verb(mgr), &value);
 	if (rc)
 		return 0;
 
-	return atoi(val);
+	return value;
 }
 
-unsigned int ucm_get_disable_software_volume(snd_use_case_mgr_t *mgr)
+unsigned int ucm_get_disable_software_volume(struct cras_use_case_mgr *mgr)
 {
-	const char *val = NULL;
+	int value;
 	int rc;
 
-	rc = get_var(mgr, disable_software_volume, "", default_verb, &val);
+	rc = get_int(mgr, disable_software_volume, "", uc_verb(mgr), &value);
 	if (rc)
 		return 0;
 
-	return atoi(val);
+	return value;
+}
+
+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)
+{
+	int value;
+	int rc;
+
+	rc = get_int(mgr, default_node_gain, dev, uc_verb(mgr), &value);
+	if (rc)
+		return rc;
+	*gain = value;
+	return 0;
 }
 
 const char *ucm_get_device_name_for_dev(
-	snd_use_case_mgr_t *mgr, const char *dev,
+	struct cras_use_case_mgr *mgr, const char *dev,
 	enum CRAS_STREAM_DIRECTION direction)
 {
 	if (direction == CRAS_STREAM_OUTPUT)
@@ -446,3 +674,384 @@
 		return ucm_get_capture_device_name_for_dev(mgr, dev);
 	return NULL;
 }
+
+int ucm_get_sample_rate_for_dev(struct cras_use_case_mgr *mgr, const char *dev,
+				enum CRAS_STREAM_DIRECTION direction)
+{
+	int value;
+	int rc;
+	const char *var_name;
+
+	if (direction == CRAS_STREAM_OUTPUT)
+		var_name = playback_device_rate_var;
+	else if (direction == CRAS_STREAM_INPUT)
+		var_name = capture_device_rate_var;
+	else
+		return -EINVAL;
+
+	rc = get_int(mgr, var_name, dev, uc_verb(mgr), &value);
+	if (rc)
+		return rc;
+
+	return value;
+}
+
+int ucm_get_capture_chmap_for_dev(struct cras_use_case_mgr *mgr,
+				  const char *dev,
+				  int8_t *channel_layout)
+{
+	const char *var_str;
+	char *tokens, *token;
+	int i, rc;
+
+	rc = get_var(mgr, capture_channel_map_var, dev, uc_verb(mgr), &var_str);
+	if (rc)
+		return rc;
+
+	tokens = strdup(var_str);
+	token = strtok(tokens, " ");
+	for (i = 0; token && (i < CRAS_CH_MAX); i++) {
+		channel_layout[i] = atoi(token);
+		token = strtok(NULL, " ");
+	}
+
+	free((void *)tokens);
+	free((void *)var_str);
+	return (i == CRAS_CH_MAX) ? 0 : -EINVAL;
+}
+
+struct mixer_name *ucm_get_coupled_mixer_names(
+		struct cras_use_case_mgr *mgr, const char *dev)
+{
+	return ucm_get_mixer_names(mgr, dev, coupled_mixers,
+				   CRAS_STREAM_OUTPUT,
+				   MIXER_NAME_VOLUME);
+}
+
+static int get_device_index_from_target(const char *target_device_name)
+{
+	/* Expects a string in the form: hw:card-name,<num> */
+	const char *pos = target_device_name;
+	if (!pos)
+		return -1;
+	while (*pos && *pos != ',')
+		++pos;
+	if (*pos == ',') {
+		++pos;
+		return atoi(pos);
+	}
+	return -1;
+}
+
+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;
+	char *identifier;
+
+	/* Find the list of all mixers using the control names defined in
+	 * the header definintion for this function.  */
+	identifier = snd_use_case_identifier("_devices/%s", uc_verb(mgr));
+	num_devs = snd_use_case_get_list(mgr->mgr, identifier, &list);
+	free(identifier);
+
+	/* snd_use_case_get_list fills list with pairs of device name and
+	 * comment, so device names are in even-indexed elements. */
+	for (i = 0; i < num_devs; i += 2) {
+		enum CRAS_STREAM_DIRECTION dir = CRAS_STREAM_UNDEFINED;
+		int dev_idx = -1;
+		const char *dev_name = strdup(list[i]);
+		const char *jack_name;
+		const char *jack_type;
+		const char *mixer_name;
+		struct mixer_name *m_name;
+		int rc;
+		const char *target_device_name;
+
+		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 (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);
+	}
+
+	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);
+	return NULL;
+}
+
+char *ucm_get_hotword_models(struct cras_use_case_mgr *mgr)
+{
+	const char **list;
+	int i, num_entries;
+	int models_len = 0;
+	char *models = NULL;
+	const char *tmp;
+	char *identifier;
+
+	identifier = snd_use_case_identifier("_modifiers/%s", uc_verb(mgr));
+	num_entries = snd_use_case_get_list(mgr->mgr, identifier, &list);
+	free(identifier);
+	if (num_entries <= 0)
+		return 0;
+	models = (char *)malloc(num_entries * 8);
+	for (i = 0; i < num_entries; i+=2) {
+		if (!list[i])
+			continue;
+		if (0 == strncmp(list[i], hotword_model_prefix,
+				 strlen(hotword_model_prefix))) {
+			tmp = list[i] + strlen(hotword_model_prefix);
+			while (isspace(*tmp))
+				tmp++;
+			strcpy(models + models_len, tmp);
+			models_len += strlen(tmp);
+			if (i + 2 >= num_entries)
+				models[models_len] = '\0';
+			else
+				models[models_len++] = ',';
+		}
+	}
+	snd_use_case_free_list(list, num_entries);
+
+	return models;
+}
+
+int ucm_set_hotword_model(struct cras_use_case_mgr *mgr, const char *model)
+{
+	const char **list;
+	int num_enmods, mod_idx;
+	char *model_mod = NULL;
+	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, model);
+	if (!ucm_mod_exists_with_name(mgr, model_mod)) {
+		free((void *)model_mod);
+		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;
+
+	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);
+
+	return 0;
+}
+
+int ucm_has_fully_specified_ucm_flag(struct cras_use_case_mgr *mgr)
+{
+	char *flag;
+	int ret = 0;
+	flag = ucm_get_flag(mgr, fully_specified_ucm_var);
+	if (!flag)
+		return 0;
+	ret = !strcmp(flag, "1");
+	free(flag);
+	return ret;
+}
+
+const char *ucm_get_mixer_name_for_dev(struct cras_use_case_mgr *mgr, const char *dev)
+{
+	const char *name = NULL;
+	int rc;
+
+	rc = get_var(mgr, mixer_var, dev, uc_verb(mgr), &name);
+	if (rc)
+		return NULL;
+
+	return name;
+}
+
+struct mixer_name *ucm_get_main_volume_names(struct cras_use_case_mgr *mgr)
+{
+	return ucm_get_mixer_names(mgr, "", main_volume_names,
+				   CRAS_STREAM_OUTPUT, MIXER_NAME_MAIN_VOLUME);
+}
+
+int ucm_list_section_devices_by_device_name(
+		struct cras_use_case_mgr *mgr,
+		enum CRAS_STREAM_DIRECTION direction,
+		const char *device_name,
+		ucm_list_section_devices_callback cb,
+		void *cb_arg)
+{
+	int listed= 0;
+	struct section_name *section_names, *c;
+	const char* var;
+	char *identifier;
+
+	if (direction == CRAS_STREAM_OUTPUT)
+		var = playback_device_name_var;
+	else if (direction == CRAS_STREAM_INPUT)
+		var = capture_device_name_var;
+	else
+		return 0;
+
+	identifier = snd_use_case_identifier("_devices/%s", uc_verb(mgr));
+	section_names = ucm_get_sections_for_var(
+		mgr, var, device_name, identifier, direction);
+	free(identifier);
+	if (!section_names)
+		return 0;
+
+	DL_FOREACH(section_names, c) {
+		cb(c->name, cb_arg);
+		listed++;
+	}
+
+	DL_FOREACH(section_names, c) {
+		DL_DELETE(section_names, c);
+		free((void*)c->name);
+		free(c);
+	}
+	return listed;
+}
+
+const char *ucm_get_jack_name_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;
+}
+
+const char *ucm_get_jack_type_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")) {
+		syslog(LOG_ERR, "Unknown jack type: %s", name);
+		return NULL;
+	}
+	return name;
+}
+
+int ucm_get_jack_switch_for_dev(struct cras_use_case_mgr *mgr, const char *dev)
+{
+	int value;
+
+	int rc = get_int(mgr, jack_switch_var, dev, uc_verb(mgr), &value);
+	if (rc || value < 0)
+		return -1;
+	return value;
+}
+
+unsigned int ucm_get_dma_period_for_dev(struct cras_use_case_mgr *mgr,
+					const char *dev)
+{
+	int value;
+
+	int rc = get_int(mgr, dma_period_var, dev, uc_verb(mgr), &value);
+	if (rc || value < 0)
+		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 97bad12..622bbf8 100644
--- a/cras/src/server/cras_alsa_ucm.h
+++ b/cras/src/server/cras_alsa_ucm.h
@@ -7,16 +7,19 @@
 #define _CRAS_ALSA_UCM_H
 
 #include <alsa/asoundlib.h>
-#include <alsa/use-case.h>
 
+#include "cras_alsa_mixer_name.h"
+#include "cras_alsa_ucm_section.h"
 #include "cras_types.h"
 
+struct cras_use_case_mgr;
+
 /* Helpers to access UCM configuration for a card if any is provided.
  * This configuration can specify how to enable or disable certain inputs and
  * outputs on the card.
  */
 
-/* Creates a snd_use_case_mgr_t instance for the given card name if there is a
+/* Creates a cras_use_case_mgr instance for the given card name if there is a
  * matching ucm configuration.  It there is a matching UCM config, then it will
  * be configured to the default state.
  *
@@ -24,163 +27,198 @@
  *    name - Name of the card to match against the UCM card list.
  * Returns:
  *    A pointer to the use case manager if found, otherwise NULL.  The pointer
- *    must later be freed with snd_use_case_mgr_close().
+ *    must later be freed with ucm_destroy().
  */
-snd_use_case_mgr_t *ucm_create(const char *name);
+struct cras_use_case_mgr *ucm_create(const char *name);
 
-/* Destroys a snd_use_case_mgr_t that was returned from ucm_create.
+
+/* Destroys a cras_use_case_mgr that was returned from ucm_create.
  * Args:
- *    alsa_ucm - The snd_use_case_mgr_t pointer returned from alsa_ucm_create.
+ *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
  */
-void ucm_destroy(snd_use_case_mgr_t *mgr);
+void ucm_destroy(struct cras_use_case_mgr *mgr);
+
+/* Sets the new use case for the given cras_use_case_mgr.
+ * Args:
+ *    mgr - The cras_use_case_mgr pointer returned from ucm_create.
+ *    use_case - The new use case to be set.
+ * Returns:
+ *    0 on success or negative error code on failure.
+ */
+int ucm_set_use_case(struct cras_use_case_mgr *mgr,
+		     enum CRAS_STREAM_TYPE use_case);
 
 /* Checks if modifier for left right swap mode exists in ucm.
  * Args:
- *    alsa_ucm - The snd_use_case_mgr_t pointer returned from alsa_ucm_create.
+ *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
  * Returns:
  *    1 if it exists, 0 otherwise.
  */
-int ucm_swap_mode_exists(snd_use_case_mgr_t *mgr);
+int ucm_swap_mode_exists(struct cras_use_case_mgr *mgr);
 
 /* Enables or disables swap mode for the given node_name. First checks
  * if the modifier is already enabled or disabled.
  * Args:
- *    alsa_ucm - The snd_use_case_mgr_t pointer returned from alsa_ucm_create.
+ *    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_swap_mode(snd_use_case_mgr_t *mgr, const char *node_name,
-			int enable);
+int ucm_enable_swap_mode(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:
- *    alsa_ucm - The snd_use_case_mgr_t pointer returned from alsa_ucm_create.
+ *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
  *    dev - The ucm device to enable of disable.
  *    enable - Enable device if non-zero.
  * Returns:
  *    0 on success or negative error code on failure.
  */
-int ucm_set_enabled(snd_use_case_mgr_t *mgr, const char *dev, int enable);
+int ucm_set_enabled(struct cras_use_case_mgr *mgr, const char *dev, int enable);
 
 /* Gets the value of given flag name.
  * Args:
- *    mgr - The snd_use_case_mgr_t pointer returned from alsa_ucm_create.
+ *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
  *    flag_name - The name of the flag.
  * Returns:
  *    A pointer to the allocated string containing the flag value, or
  *    NULL if the flag is not set.
  */
-char *ucm_get_flag(snd_use_case_mgr_t *mgr, const char *flag_name);
+char *ucm_get_flag(struct cras_use_case_mgr *mgr, const char *flag_name);
 
 /* Gets the capture control name which associated with given ucm device.
  * Args:
- *    mgr - The snd_use_case_mgr_t pointer returned from alsa_ucm_create.
+ *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
  *    ucm_dev - The ucm device to get capture control for.
  * Returns:
  *    A pointer to the allocated string containing the name of the capture
  *    control, or NULL if no capture control is found.
  */
-char *ucm_get_cap_control(snd_use_case_mgr_t *mgr, const char *ucm_dev);
+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 snd_use_case_mgr_t pointer returned from alsa_ucm_create.
+ *    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(snd_use_case_mgr_t *mgr);
+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:
- *    mgr - The snd_use_case_mgr_t pointer returned from alsa_ucm_create.
+ *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
  *    ucm_dev - The ucm device to override node type.
  * Returns:
  *    A pointer to the allocated string containing the new type name,
  *    or NULL if no override_type_name is found.
  */
-const char *ucm_get_override_type_name(snd_use_case_mgr_t *mgr,
-					const char *ucm_dev);
+const char *ucm_get_override_type_name(struct cras_use_case_mgr *mgr,
+				       const char *ucm_dev);
 
 /* Gets the name of the ucm device for the given jack name.
  * Args:
- *    mgr - The snd_use_case_mgr_t pointer returned from alsa_ucm_create.
+ *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
  *    jack - The name of the jack to search for.
  *    direction - input or output
  * Rreturns:
  *    A pointer to the allocated string containing the name of the device, or
  *    NULL if no device is found.
  */
-char *ucm_get_dev_for_jack(snd_use_case_mgr_t *mgr, const char *jack,
+char *ucm_get_dev_for_jack(struct cras_use_case_mgr *mgr, const char *jack,
 			   enum CRAS_STREAM_DIRECTION direction);
 
 /* Gets the name of the ucm device for the given mixer name.
  * Args:
- *    mgr - The snd_use_case_mgr_t pointer returned from alsa_ucm_create.
+ *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
  *    mixer - The name of the mixer control to search for.
  *    dir - input or output
  * Rreturns:
  *    A pointer to the allocated string containing the name of the device, or
  *    NULL if no device is found.
  */
-char *ucm_get_dev_for_mixer(snd_use_case_mgr_t *mgr, const char *mixer,
+char *ucm_get_dev_for_mixer(struct cras_use_case_mgr *mgr, const char *mixer,
 			    enum CRAS_STREAM_DIRECTION dir);
 
 /* If there is an EDID file variable specified for dev, return it.  The EDID
  * file will be used for HDMI devices so supported audio formats can be checked.
  * Args:
- *    mgr - The snd_use_case_mgr_t pointer returned from alsa_ucm_create.
+ *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
  *    dev - The device to check for an EDID file.
  * Returns:
  *    A string containing the name of the edid file on Success (Must be freed
  *    later).  NULL if none found.
  */
-const char *ucm_get_edid_file_for_dev(snd_use_case_mgr_t *mgr, const char *dev);
+const char *ucm_get_edid_file_for_dev(struct cras_use_case_mgr *mgr,
+				      const char *dev);
 
 /* Gets the dsp name which is associated with the given ucm device.
  * Args:
- *    mgr - The snd_use_case_mgr_t pointer returned from alsa_ucm_create.
+ *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
  *    ucm_dev - The ucm device to get dsp name for.
  *    direction - playback(CRAS_STREAM_OUTPUT) or capture(CRAS_STREAM_INPUT).
  * Returns:
  *    A pointer to the allocated string containing the dsp name, or NULL if no
  *    dsp name is found.
  */
-const char *ucm_get_dsp_name(snd_use_case_mgr_t *mgr, const char *ucm_dev,
-			      int direction);
+const char *ucm_get_dsp_name(struct cras_use_case_mgr *mgr, const char *ucm_dev,
+			     int direction);
 
 /* Gets the default dsp name.
  * Args:
- *    mgr - The snd_use_case_mgr_t pointer returned from alsa_ucm_create.
+ *    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(snd_use_case_mgr_t *mgr, int direction);
+const char *ucm_get_dsp_name_default(struct cras_use_case_mgr *mgr,
+				     int direction);
 
 /* Gets the minimum buffer level for an output.  This level will add latency to
  * all streams playing on the output, but can be used to work around an
  * unreliable dma residue.
  * Args:
- *    mgr - The snd_use_case_mgr_t pointer returned from alsa_ucm_create.
+ *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
  */
-unsigned int ucm_get_min_buffer_level(snd_use_case_mgr_t *mgr);
+unsigned int ucm_get_min_buffer_level(struct cras_use_case_mgr *mgr);
 
 /* Gets the flag for disabling software volume.
  * Args:
- *    mgr - The snd_use_case_mgr_t pointer returned from alsa_ucm_create.
+ *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
  */
-unsigned int ucm_get_disable_software_volume(snd_use_case_mgr_t *mgr);
+unsigned int ucm_get_disable_software_volume(struct cras_use_case_mgr *mgr);
+
+/* 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;
+ * 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 device name of this device on the card..
  *
  * Args:
- *    mgr - The snd_use_case_mgr_t pointer returned from alsa_ucm_create.
+ *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
  *    dev - The device to check for device name
  *    direction - playback(CRAS_STREAM_OUTPUT) or capture(CRAS_STREAM_INPUT).
  * Returns:
@@ -189,6 +227,205 @@
  *    "card_name:device_index".
  */
 const char *ucm_get_device_name_for_dev(
-		snd_use_case_mgr_t *mgr, const char *dev,
+		struct cras_use_case_mgr *mgr, const char *dev,
 		enum CRAS_STREAM_DIRECTION direction);
+
+/* Gets the sample rate 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 sample rate.
+ *    direction - playback(CRAS_STREAM_OUTPUT) or capture(CRAS_STREAM_INPUT).
+ * Returns:
+ *    The sample rate if specified, or negative error if not.
+ */
+int ucm_get_sample_rate_for_dev(struct cras_use_case_mgr *mgr, const char *dev,
+				enum CRAS_STREAM_DIRECTION direction);
+
+/* Gets the capture channel map for this device.
+ * Args:
+ *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
+ *    dev - The device to check for capture channel map.
+ *    channel_layout - The channel layout to fill.
+ */
+int ucm_get_capture_chmap_for_dev(struct cras_use_case_mgr *mgr,
+				  const char *dev,
+				  int8_t *channel_layout);
+
+/* Gets the mixer names for the coupled mixer controls 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 coupled mixer controls.
+ * Returns:
+ *    A list of cras_alsa_control.
+ */
+struct mixer_name *ucm_get_coupled_mixer_names(
+		struct cras_use_case_mgr *mgr, const char *dev);
+
+/* Gets a list of UCM sections
+ *
+ * The data includes the represented devices and their controls.
+ *
+ * Args:
+ *    mgr - The cras_use_case_mgr pointer return from alsa_ucm_create.
+ *
+ * Returns:
+ *    A list of ucm_section or NULL. Free it with ucm_section_free_list().
+ */
+struct ucm_section *ucm_get_sections(struct cras_use_case_mgr *mgr);
+
+/* Gets the list of supported hotword model names.
+ * Args:
+ *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
+ * Returns:
+ *    String containing comma separated model names, e.g 'en,fr,zh'. Needs
+ *    to be freed by caller.
+ */
+char *ucm_get_hotword_models(struct cras_use_case_mgr *mgr);
+
+/* Sets the desired hotword model.
+ * 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_set_hotword_model(struct cras_use_case_mgr *mgr, const char *model);
+
+/* Checks if this card has fully specified UCM config.
+ *
+ * Args:
+ *   mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
+ * Returns:
+ *   1 if this UCM uses fully specified UCM config. 0 otherwise.
+ */
+int ucm_has_fully_specified_ucm_flag(struct cras_use_case_mgr *mgr);
+
+/* Gets the 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_mixer_name_for_dev(struct cras_use_case_mgr *mgr,
+				       const char *dev);
+
+/* Gets the mixer names for the main volume controls on the card.
+ *
+ * The main volume controls in the list are considered in series.
+ * If 3 controls are specified, MainVolumeNames "A,B,C", with dB ranges
+ * A=-10dB~0dB, B=-20dB~0dB, C=-30dB~0dB, then applying -35dB overall volume
+ * sets A=-10dB, B=-20dB, C=-5dB.
+ * The volume control affects all output on this card, e.g.
+ * speaker and headphone.
+ *
+ * Args:
+ *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
+ * Returns:
+ *    names - A list of mixer_name.
+ */
+struct mixer_name *ucm_get_main_volume_names(struct cras_use_case_mgr *mgr);
+
+/* The callback to be provided with a reference to the section name.
+ *
+ * Args:
+ *    section_name: The name of a SectionDevice in UCM.
+ *    arg - Argument to pass to this callback.
+ */
+typedef void (*ucm_list_section_devices_callback)(
+		const char *section_name, void *arg);
+
+/* Invokes the provided callback once for each section with matched device name.
+ *
+ * Iterate through each SectionDevice in UCM of this card. Invoke callback if
+ * "PlaybackPCM" for output or "CapturePCM" for input of the section matches
+ * the specified device_name.
+ *
+ * Args:
+ *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
+ *    device_name - A string for device name of format "card_name:device_index".
+ *    cb - Function to call for each section.
+ *    cb_arg - Argument to pass to cb.
+ * Returns:
+ *    Number of sections listed.
+ */
+int ucm_list_section_devices_by_device_name(
+		struct cras_use_case_mgr *mgr,
+		enum CRAS_STREAM_DIRECTION direction,
+		const char *device_name,
+		ucm_list_section_devices_callback cb,
+		void *cb_arg);
+
+/* Gets the jack 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 jack name.
+ * Returns:
+ *    A pointer to the allocated string containing the jack name, or NULL
+ *    if no jack name is found.
+ */
+const char *ucm_get_jack_name_for_dev(struct cras_use_case_mgr *mgr,
+				      const char *dev);
+
+/* Gets the jack type 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 jack type, or NULL
+ *    if no jack type is found or the found jack type is invalid. The valid
+ *    jack types are "hctl" or "gpio".
+ */
+const char *ucm_get_jack_type_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
+ * by measuring the impedance on the other end. In that case we want each
+ * jack to have it's own I/O node so that each can have it's own volume
+ * settings. This allows us to specify the jack used more exactly.
+ * Valid values are defined in /usr/include/linux/input.h.
+ * Args:
+ *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
+ *    dev - The device to check.
+ * Returns:
+ *    A value >= 0 when the switch is defined, or -1 otherwise.
+ */
+int ucm_get_jack_switch_for_dev(struct cras_use_case_mgr *mgr, const char *dev);
+
+/* Gets the DMA period time in microseconds for the given device.
+ *
+ * Args:
+ *    mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
+ *    dev - The device to check.
+ * Returns:
+ *    A value > 0, or 0 if no period is defined.
+ */
+unsigned int ucm_get_dma_period_for_dev(struct cras_use_case_mgr *mgr,
+					const char *dev);
+
+/* Gets the flag of optimization for no stream state.
+ * This flag enables no_stream ops in alsa_io.
+ * 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_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
new file mode 100644
index 0000000..179706d
--- /dev/null
+++ b/cras/src/server/cras_alsa_ucm_section.c
@@ -0,0 +1,129 @@
+/* Copyright 2016 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 <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+
+#include "cras_alsa_ucm_section.h"
+#include "cras_alsa_mixer_name.h"
+#include "utlist.h"
+
+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);
+	mixer_name_free(section->coupled);
+	free(section);
+}
+
+void ucm_section_free_list(struct ucm_section *sections)
+{
+	struct ucm_section *section;
+	DL_FOREACH(sections, section) {
+		DL_DELETE(sections, section);
+		ucm_section_free(section);
+	}
+}
+
+struct ucm_section *ucm_section_create(const char *name,
+				       int dev_idx,
+				       enum CRAS_STREAM_DIRECTION dir,
+				       const char *jack_name,
+				       const char *jack_type)
+{
+	struct ucm_section *section_list = NULL;
+	struct ucm_section *section;
+
+	if (!name)
+		return NULL;
+
+	section = (struct ucm_section *)
+		  calloc(1, sizeof(struct ucm_section));
+	if (!section)
+		return NULL;
+
+	section->dev_idx = dev_idx;
+	section->dir = dir;
+	section->name = strdup(name);
+	if (!section->name)
+		goto error;
+
+	if (jack_name) {
+		section->jack_name = strdup(jack_name);
+		if (!section->jack_name)
+			goto error;
+	}
+	if (jack_type) {
+		section->jack_type = strdup(jack_type);
+		if (!section->jack_type)
+			goto error;
+	}
+	/* Default to -1 which means auto-detect. */
+	section->jack_switch = -1;
+
+	/* Make sure to initialize this item as a list. */
+	DL_APPEND(section_list, section);
+	return section_list;
+
+error:
+	ucm_section_free(section);
+	return NULL;
+}
+
+int ucm_section_set_mixer_name(struct ucm_section *section,
+			       const char *name)
+{
+	if (!section || !name)
+		return -EINVAL;
+
+	if (section->mixer_name)
+		free((void *)section->mixer_name);
+	section->mixer_name = strdup(name);
+	if (!section->mixer_name)
+		return -ENOMEM;
+	return 0;
+}
+
+int ucm_section_add_coupled(struct ucm_section *section,
+			    const char *name,
+			    mixer_name_type type)
+{
+	struct mixer_name *m_name;
+
+	if (!section || !name || type == MIXER_NAME_UNDEFINED)
+		return -EINVAL;
+
+	m_name = mixer_name_add(NULL, name, section->dir, type);
+	if (!m_name)
+		return -ENOMEM;
+	DL_APPEND(section->coupled, m_name);
+	return 0;
+}
+
+int ucm_section_concat_coupled(struct ucm_section *section,
+			       struct mixer_name *coupled)
+{
+	if (!section || !coupled)
+		return -EINVAL;
+	DL_CONCAT(section->coupled, coupled);
+	return 0;
+}
+
+void ucm_section_dump(struct ucm_section *section)
+{
+	syslog(LOG_DEBUG, "section: %s [%d] (%s)",
+		section->name, section->dev_idx,
+		section->dir == CRAS_STREAM_OUTPUT ? "output" : "input");
+	syslog(LOG_DEBUG, "  jack: %s %s",
+		section->jack_name, section->jack_type);
+	syslog(LOG_DEBUG, "  mixer_name: %s", section->mixer_name);
+	mixer_name_dump(section->coupled, "  coupled");
+}
diff --git a/cras/src/server/cras_alsa_ucm_section.h b/cras/src/server/cras_alsa_ucm_section.h
new file mode 100644
index 0000000..bd0c2ef
--- /dev/null
+++ b/cras/src/server/cras_alsa_ucm_section.h
@@ -0,0 +1,105 @@
+/* Copyright 2016 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_UCM_SECTION_H
+#define _CRAS_ALSA_UCM_SECTION_H
+
+#include "cras_types.h"
+#include "cras_alsa_mixer_name.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Represents an ALSA UCM section. */
+struct ucm_section {
+	const char *name;                /* Section name. */
+	int dev_idx;                     /* Device PCM index. */
+	enum CRAS_STREAM_DIRECTION dir;  /* Output or Input. */
+	const char *jack_name;           /* Associated jack's name. */
+	const char *jack_type;           /* Associated jack's type. */
+	int jack_switch;                 /* Switch number for jack from
+	                                  * linux/input.h, or -1. */
+	const char *mixer_name;          /* MixerName value. */
+	struct mixer_name *coupled;      /* CoupledMixers value. */
+	struct ucm_section *prev, *next;
+};
+
+/* Create a single UCM section.
+ *
+ * Args:
+ *    name - Section name (must not be NULL).
+ *    dev_idx - Section's device index (PCM number).
+ *    dir - Device direction: INPUT or OUTPUT.
+ *    jack_name - Name of an associated jack (or NULL).
+ *    jack_type - Type of the associated jack (or NULL).
+ *
+ * Returns:
+ *    A valid pointer on success, NULL for memory allocation error.
+ */
+struct ucm_section *ucm_section_create(const char *name,
+				       int dev_idx,
+				       enum CRAS_STREAM_DIRECTION dir,
+				       const char *jack_name,
+				       const char *jack_type);
+
+/* Sets the mixer_name value for the given section.
+ *
+ * Args:
+ *    section - Section to manipulate.
+ *    name - The name of the control.
+ *
+ * Returns:
+ *    0 for success, -EINVAL for invalid arguments, or -ENOMEM.
+ */
+int ucm_section_set_mixer_name(struct ucm_section *section,
+			       const char *name);
+
+/* Add a single coupled control to this section.
+ * Control has the same direction as the section.
+ *
+ * Args:
+ *    section - Section to manipulate.
+ *    name - Coupled control name to add.
+ *    type - The type of control.
+ *
+ * Returns:
+ *    0 for success, -EINVAL for invalid arguments, or -ENOMEM.
+ */
+int ucm_section_add_coupled(struct ucm_section *section,
+			    const char *name,
+			    mixer_name_type type);
+
+/* Concatenate a list of coupled controls to this section.
+ *
+ * Args:
+ *    section - Section to manipulate.
+ *    coupled - Coupled control names to add.
+ *
+ * Returns:
+ *    0 for success, -EINVAL for invalid arguments (NULL args).
+ */
+int ucm_section_concat_coupled(struct ucm_section *section,
+			       struct mixer_name *coupled);
+
+/* Frees a list of sections.
+ *
+ * Args:
+ *    sections - List of sections to free.
+ */
+void ucm_section_free_list(struct ucm_section *sections);
+
+/* Dump details on this section to syslog(LOG_DEBUG).
+ *
+ * Args:
+ *    section - Section to dump.
+ */
+void ucm_section_dump(struct ucm_section *section);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _CRAS_ALSA_MIXER_NAME_H */
diff --git a/cras/src/server/cras_audio_area.c b/cras/src/server/cras_audio_area.c
index 3e30a02..5def7ea 100644
--- a/cras/src/server/cras_audio_area.c
+++ b/cras/src/server/cras_audio_area.c
@@ -27,22 +27,14 @@
 				  const struct cras_audio_format *dst_fmt,
 				  const struct cras_audio_area *src,
 				  unsigned int src_offset,
-				  unsigned int skip_zero)
+				  float software_gain_scaler)
 {
 	unsigned int src_idx, dst_idx;
 	unsigned int ncopy;
-	unsigned int dst_format_bytes = cras_get_format_bytes(dst_fmt);
 	uint8_t *schan, *dchan;
 
 	ncopy = MIN(src->frames - src_offset, dst->frames - dst_offset);
 
-	/* TODO(dgreid) - make it so this isn't needed, can copy first stream of
-	 * each channel. */
-	if (!skip_zero)
-		memset(dst->channels[0].buf +
-				dst_offset * dst->channels[0].step_bytes, 0,
-		       ncopy * dst_format_bytes);
-
 	/* TODO(dgreid) - this replaces a memcpy, it needs to be way faster. */
 	for (src_idx = 0; src_idx < src->num_channels; src_idx++) {
 
@@ -56,10 +48,11 @@
 			dchan = dst->channels[dst_idx].buf +
 				dst_offset * dst->channels[dst_idx].step_bytes;
 
-			cras_mix_add_stride(dst_fmt->format, dchan, schan,
+			cras_mix_add_scale_stride(dst_fmt->format, dchan, schan,
 					    ncopy,
 					    dst->channels[dst_idx].step_bytes,
-					    src->channels[src_idx].step_bytes);
+					    src->channels[src_idx].step_bytes,
+					    software_gain_scaler);
 		}
 	}
 
diff --git a/cras/src/server/cras_audio_area.h b/cras/src/server/cras_audio_area.h
index 8771a5e..1111ee4 100644
--- a/cras/src/server/cras_audio_area.h
+++ b/cras/src/server/cras_audio_area.h
@@ -64,7 +64,7 @@
  *    format - The format of dst area.
  *    src - The source audio area.
  *    src_offset - The offset of src audio area in frames.
- *    skip_zero - Skip zeroing the area before copying the data.
+ *    software_gain_scaler - The software gain scaler needed.
  * Returns the number of frames copied.
  */
 unsigned int cras_audio_area_copy(const struct cras_audio_area *dst,
@@ -72,7 +72,7 @@
 				  const struct cras_audio_format *dst_fmt,
 				  const struct cras_audio_area *src,
 				  unsigned int src_offset,
-				  unsigned int skip_zero);
+				  float software_gain_scaler);
 
 /*
  * Destroys a cras_audio_area.
diff --git a/cras/src/server/cras_bt_adapter.c b/cras/src/server/cras_bt_adapter.c
index d050f2d..f84e01e 100644
--- a/cras/src/server/cras_bt_adapter.c
+++ b/cras/src/server/cras_bt_adapter.c
@@ -50,6 +50,7 @@
 	}
 
 	/* dev_id = 0 for hci0 */
+	dev_info.type = 0;
 	dev_info.dev_id = atoi(pos + 3);
 	err = ioctl(ctl, HCIGETDEVINFO, (void *)&dev_info);
 	if (err) {
@@ -111,6 +112,9 @@
 {
 	struct cras_bt_adapter *adapter;
 
+	if (object_path == NULL)
+		return NULL;
+
 	DL_FOREACH(adapters, adapter) {
 		if (strcmp(adapter->object_path, object_path) == 0)
 			return adapter;
diff --git a/cras/src/server/cras_bt_constants.h b/cras/src/server/cras_bt_constants.h
index 23ec8dd..13a1737 100644
--- a/cras/src/server/cras_bt_constants.h
+++ b/cras/src/server/cras_bt_constants.h
@@ -13,9 +13,9 @@
 #define BLUEZ_INTERFACE_MEDIA "org.bluez.Media1"
 #define BLUEZ_INTERFACE_MEDIA_ENDPOINT "org.bluez.MediaEndpoint1"
 #define BLUEZ_INTERFACE_MEDIA_TRANSPORT "org.bluez.MediaTransport1"
+#define BLUEZ_INTERFACE_PLAYER "org.bluez.MediaPlayer1"
 #define BLUEZ_INTERFACE_PROFILE "org.bluez.Profile1"
 #define BLUEZ_PROFILE_MGMT_INTERFACE "org.bluez.ProfileManager1"
-
 /* Remove once our D-Bus header files are updated to define this. */
 #ifndef DBUS_INTERFACE_OBJECT_MANAGER
 #define DBUS_INTERFACE_OBJECT_MANAGER "org.freedesktop.DBus.ObjectManager"
diff --git a/cras/src/server/cras_bt_device.c b/cras/src/server/cras_bt_device.c
index 195eddc..682edba 100644
--- a/cras/src/server/cras_bt_device.c
+++ b/cras/src/server/cras_bt_device.c
@@ -18,6 +18,7 @@
 #include <syslog.h>
 
 #include "bluetooth.h"
+#include "cras_a2dp_endpoint.h"
 #include "cras_bt_adapter.h"
 #include "cras_bt_device.h"
 #include "cras_bt_constants.h"
@@ -35,46 +36,69 @@
 #define DEFAULT_HFP_MTU_BYTES 48
 
 
+static const unsigned int PROFILE_SWITCH_DELAY_MS = 500;
+
+/* Check profile connections every 2 seconds and rerty 30 times maximum.
+ * Attemp to connect profiles which haven't been ready every 3 retries.
+ */
+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;
+
 /* Object to represent a general bluetooth device, and used to
  * associate with some CRAS modules if it supports audio.
  * Members:
+ *    conn - The dbus connection object used to send message to bluetoothd.
  *    object_path - Object path of the bluetooth device.
- *    adapter - The adapter object associates with this device.
+ *    adapter - The object path of the adapter associates with this device.
  *    address - The BT address of this device.
  *    name - The readable name of this device.
  *    bluetooth_class - The bluetooth class of this device.
  *    paired - If this device is paired.
  *    trusted - If this device is trusted.
  *    connected - If this devices is connected.
+ *    connected_profiles - OR'ed all connected audio profiles.
  *    profiles - OR'ed by all audio profiles this device supports.
  *    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.
- *    a2dp_delay_timer - The timer used to delay the allocation of HFP/HSP
- *        stuff until a2dp connection is established.
+ *    conn_watch_retries - The retry count for conn_watch_timer.
+ *    conn_watch_timer - The timer used to watch connected profiles and start
+ *        BT audio input/ouput when all profiles are ready.
+ *    suspend_timer - The timer used to suspend device.
+ *    switch_profile_timer - The timer used to delay enabling iodev after
+ *        profile switch.
  *    append_iodev_cb - The callback to trigger when an iodev is appended.
  */
 struct cras_bt_device {
+	DBusConnection *conn;
 	char *object_path;
-	struct cras_bt_adapter *adapter;
+	char *adapter_obj_path;
 	char *address;
 	char *name;
 	uint32_t bluetooth_class;
 	int paired;
 	int trusted;
 	int connected;
+	enum cras_bt_device_profile connected_profiles;
 	enum cras_bt_device_profile profiles;
 	struct cras_iodev *bt_iodevs[CRAS_NUM_DIRECTIONS];
 	unsigned int active_profile;
-	struct cras_timer *a2dp_delay_timer;
+	int use_hardware_volume;
+	int conn_watch_retries;
+	struct cras_timer *conn_watch_timer;
+	struct cras_timer *suspend_timer;
+	struct cras_timer *switch_profile_timer;
 	void (*append_iodev_cb)(void *data);
 
 	struct cras_bt_device *prev, *next;
 };
 
 enum BT_DEVICE_COMMAND {
-	BT_DEVICE_SWITCH_PROFILE_ON_CLOSE,
-	BT_DEVICE_SWITCH_PROFILE_ON_OPEN,
+	BT_DEVICE_CANCEL_SUSPEND,
+	BT_DEVICE_SCHEDULE_SUSPEND,
+	BT_DEVICE_SWITCH_PROFILE,
+	BT_DEVICE_SWITCH_PROFILE_ENABLE_DEV,
 };
 
 struct bt_device_msg {
@@ -82,6 +106,7 @@
 	enum BT_DEVICE_COMMAND cmd;
 	struct cras_bt_device *device;
 	struct cras_iodev *dev;
+	unsigned int arg;
 };
 
 static struct cras_bt_device *devices;
@@ -114,7 +139,8 @@
 		return 0;
 }
 
-struct cras_bt_device *cras_bt_device_create(const char *object_path)
+struct cras_bt_device *cras_bt_device_create(DBusConnection *conn,
+					     const char *object_path)
 {
 	struct cras_bt_device *device;
 
@@ -122,6 +148,7 @@
 	if (device == NULL)
 		return NULL;
 
+	device->conn = conn;
 	device->object_path = strdup(object_path);
 	if (device->object_path == NULL) {
 		free(device);
@@ -133,6 +160,20 @@
 	return device;
 }
 
+static void on_connect_profile_reply(DBusPendingCall *pending_call, void *data)
+{
+	DBusMessage *reply;
+
+	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, "Connect profile message replied error: %s",
+			dbus_message_get_error_name(reply));
+
+	dbus_message_unref(reply);
+}
+
 static void on_disconnect_reply(DBusPendingCall *pending_call, void *data)
 {
 	DBusMessage *reply;
@@ -146,6 +187,51 @@
 	dbus_message_unref(reply);
 }
 
+int cras_bt_device_connect_profile(DBusConnection *conn,
+				   struct cras_bt_device *device,
+				   const char *uuid)
+{
+	DBusMessage *method_call;
+	DBusError dbus_error;
+	DBusPendingCall *pending_call;
+
+	method_call = dbus_message_new_method_call(
+			BLUEZ_SERVICE,
+			device->object_path,
+			BLUEZ_INTERFACE_DEVICE,
+			"ConnectProfile");
+	if (!method_call)
+		return -ENOMEM;
+
+	if (!dbus_message_append_args(method_call,
+				      DBUS_TYPE_STRING,
+				      &uuid,
+				      DBUS_TYPE_INVALID))
+		return -ENOMEM;
+
+	dbus_error_init(&dbus_error);
+
+	pending_call = NULL;
+	if (!dbus_connection_send_with_reply(conn,
+					     method_call,
+					     &pending_call,
+					     DBUS_TIMEOUT_USE_DEFAULT)) {
+		dbus_message_unref(method_call);
+		syslog(LOG_ERR, "Failed to send Disconnect message");
+		return -EIO;
+	}
+
+	dbus_message_unref(method_call);
+	if (!dbus_pending_call_set_notify(pending_call,
+					  on_connect_profile_reply,
+					  conn, NULL)) {
+		dbus_pending_call_cancel(pending_call);
+		dbus_pending_call_unref(pending_call);
+		return -EIO;
+	}
+	return 0;
+}
+
 int cras_bt_device_disconnect(DBusConnection *conn,
 			      struct cras_bt_device *device)
 {
@@ -186,8 +272,15 @@
 
 void cras_bt_device_destroy(struct cras_bt_device *device)
 {
+	struct cras_tm *tm = cras_system_state_get_tm();
 	DL_DELETE(devices, device);
 
+	if (device->conn_watch_timer)
+		cras_tm_cancel_timer(tm, device->conn_watch_timer);
+	if (device->switch_profile_timer)
+		cras_tm_cancel_timer(tm, device->switch_profile_timer);
+	if (device->suspend_timer)
+		cras_tm_cancel_timer(tm, device->suspend_timer);
 	free(device->object_path);
 	free(device->address);
 	free(device->name);
@@ -248,7 +341,7 @@
 struct cras_bt_adapter *cras_bt_device_adapter(
 	const struct cras_bt_device *device)
 {
-	return device->adapter;
+	return cras_bt_adapter_get(device->adapter_obj_path);
 }
 
 const char *cras_bt_device_address(const struct cras_bt_device *device)
@@ -304,7 +397,7 @@
 
 static void bt_device_switch_profile(struct cras_bt_device *device,
 				     struct cras_iodev *bt_iodev,
-				     int on_open);
+				     int enable_dev);
 
 void cras_bt_device_rm_iodev(struct cras_bt_device *device,
 			     struct cras_iodev *iodev)
@@ -347,6 +440,11 @@
 		cras_bt_device_set_active_profile(device, 0);
 }
 
+void cras_bt_device_a2dp_configured(struct cras_bt_device *device)
+{
+	device->connected_profiles |= CRAS_BT_DEVICE_PROFILE_A2DP_SINK;
+}
+
 int cras_bt_device_has_a2dp(struct cras_bt_device *device)
 {
 	struct cras_iodev *odev = device->bt_iodevs[CRAS_STREAM_OUTPUT];
@@ -361,27 +459,43 @@
 	struct cras_iodev *idev = device->bt_iodevs[CRAS_STREAM_INPUT];
 
 	return cras_bt_device_has_a2dp(device) &&
-		(!idev || !idev->is_open(idev));
+		(!idev || !cras_iodev_is_open(idev));
 }
 
-void cras_bt_device_add_a2dp_delay_timer(struct cras_bt_device *device,
-					struct cras_timer *timer)
+int cras_bt_device_audio_gateway_initialized(struct cras_bt_device *device)
 {
-	device->a2dp_delay_timer = timer;
-}
+	int rc = 0;
+	struct cras_tm *tm;
 
-void cras_bt_device_cancel_a2dp_delay_timer(struct cras_bt_device *device)
-{
-	struct cras_tm *tm = cras_system_state_get_tm();
+	/* Marks HFP/HSP as connected. This is what connection watcher
+	 * checks. */
+	device->connected_profiles |=
+			(CRAS_BT_DEVICE_PROFILE_HFP_HANDSFREE |
+			 CRAS_BT_DEVICE_PROFILE_HSP_HEADSET);
 
-	if (device->a2dp_delay_timer)
-		cras_tm_cancel_timer(tm, device->a2dp_delay_timer);
-	device->a2dp_delay_timer = NULL;
-}
+	/* If this is a HFP/HSP only headset, no need to wait for A2DP. */
+	if (!cras_bt_device_supports_profile(
+			device, CRAS_BT_DEVICE_PROFILE_A2DP_SINK)) {
 
-void cras_bt_device_rm_a2dp_delay_timer(struct cras_bt_device *device)
-{
-	device->a2dp_delay_timer = NULL;
+		syslog(LOG_DEBUG,
+		       "Start HFP audio gateway as A2DP is not supported");
+
+		rc = cras_hfp_ag_start(device);
+		if (rc) {
+			syslog(LOG_ERR, "Start audio gateway failed");
+			return rc;
+		}
+		if (device->conn_watch_timer) {
+			tm = cras_system_state_get_tm();
+			cras_tm_cancel_timer(tm, device->conn_watch_timer);
+			device->conn_watch_timer = NULL;
+		}
+	} else {
+		syslog(LOG_DEBUG, "HFP audio gateway is connected but A2DP "
+				  "is not connected yet");
+	}
+
+	return rc;
 }
 
 int cras_bt_device_get_active_profile(const struct cras_bt_device *device)
@@ -434,10 +548,120 @@
 	}
 }
 
+static int cras_bt_device_is_profile_connected(
+		const struct cras_bt_device *device,
+		enum cras_bt_device_profile profile)
+{
+	return !!(device->connected_profiles & profile);
+}
+
+static void bt_device_schedule_suspend(struct cras_bt_device *device,
+				       unsigned int msec);
+
+/* Callback used to periodically check if supported profiles are connected. */
+static void bt_device_conn_watch_cb(struct cras_timer *timer, void *arg)
+{
+	struct cras_tm *tm;
+	struct cras_bt_device *device = (struct cras_bt_device *)arg;
+
+	device->conn_watch_timer = NULL;
+
+	/* 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)
+			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)
+			cras_bt_device_connect_profile(
+					device->conn, device, HFP_HF_UUID);
+		goto arm_retry_timer;
+	}
+
+	if (cras_bt_device_is_profile_connected(
+			device, CRAS_BT_DEVICE_PROFILE_A2DP_SINK)) {
+		/* When A2DP-only device connected, suspend all HFP/HSP audio
+		 * gateways. */
+		if (!cras_bt_device_supports_profile(device,
+				CRAS_BT_DEVICE_PROFILE_HFP_HANDSFREE |
+				CRAS_BT_DEVICE_PROFILE_HSP_HEADSET))
+			cras_hfp_ag_suspend();
+
+		cras_a2dp_start(device);
+	}
+
+	if (cras_bt_device_is_profile_connected(
+			device, CRAS_BT_DEVICE_PROFILE_HFP_HANDSFREE))
+		cras_hfp_ag_start(device);
+	return;
+
+arm_retry_timer:
+
+	syslog(LOG_DEBUG, "conn_watch_retries: %d", device->conn_watch_retries);
+
+	if (--device->conn_watch_retries) {
+		tm = cras_system_state_get_tm();
+		device->conn_watch_timer = cras_tm_create_timer(tm,
+				CONN_WATCH_PERIOD_MS,
+				bt_device_conn_watch_cb, device);
+	} else {
+		syslog(LOG_ERR, "Connection watch timeout.");
+		bt_device_schedule_suspend(device, 0);
+	}
+}
+
+static void cras_bt_device_start_new_conn_watch_timer(
+		struct cras_bt_device *device)
+{
+	struct cras_tm *tm = cras_system_state_get_tm();
+
+	if (device->conn_watch_timer) {
+		cras_tm_cancel_timer(tm, device->conn_watch_timer);
+	}
+	device->conn_watch_retries = CONN_WATCH_MAX_RETRIES;
+	device->conn_watch_timer = cras_tm_create_timer(tm,
+			CONN_WATCH_PERIOD_MS,
+			bt_device_conn_watch_cb, device);
+}
+
+static 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) {
+		cras_bt_profile_on_device_disconnected(device);
+		/* Device is disconnected, resets connected profiles. */
+		device->connected_profiles = 0;
+	}
+
+	device->connected = value;
+
+	if (device->connected) {
+		cras_bt_device_start_new_conn_watch_timer(device);
+	} else if (device->conn_watch_timer) {
+		cras_tm_cancel_timer(tm, device->conn_watch_timer);
+		device->conn_watch_timer = NULL;
+	}
+}
+
 void cras_bt_device_update_properties(struct cras_bt_device *device,
 				      DBusMessageIter *properties_array_iter,
 				      DBusMessageIter *invalidated_array_iter)
 {
+
+	int get_profile = 0;
+
 	while (dbus_message_iter_get_arg_type(properties_array_iter) !=
 	       DBUS_TYPE_INVALID) {
 		DBusMessageIter properties_dict_iter, variant_iter;
@@ -459,16 +683,14 @@
 			dbus_message_iter_get_basic(&variant_iter, &value);
 
 			if (strcmp(key, "Adapter") == 0) {
-				device->adapter = cras_bt_adapter_get(value);
-
+				free(device->adapter_obj_path);
+				device->adapter_obj_path = strdup(value);
 			} else if (strcmp(key, "Address") == 0) {
 				free(device->address);
 				device->address = strdup(value);
-
 			} else if (strcmp(key, "Alias") == 0) {
 				free(device->name);
 				device->name = strdup(value);
-
 			}
 
 		} else if (type == DBUS_TYPE_UINT32) {
@@ -489,10 +711,7 @@
 			} else if (strcmp(key, "Trusted") == 0) {
 				device->trusted = value;
 			} else if (strcmp(key, "Connected") == 0) {
-				if (device->connected && !value)
-					cras_bt_profile_on_device_disconnected(
-							device);
-				device->connected = value;
+				cras_bt_device_set_connected(device, value);
 			}
 
 		} else if (strcmp(
@@ -508,6 +727,8 @@
 				const char *uuid;
 				enum cras_bt_device_profile profile;
 
+				get_profile = 1;
+
 				dbus_message_iter_get_basic(&uuid_array_iter,
 							    &uuid);
 				profile = cras_bt_device_profile_from_uuid(
@@ -531,7 +752,8 @@
 		dbus_message_iter_get_basic(invalidated_array_iter, &key);
 
 		if (strcmp(key, "Adapter") == 0) {
-			device->adapter = NULL;
+			free(device->adapter_obj_path);
+			device->adapter_obj_path = NULL;
 		} else if (strcmp(key, "Address") == 0) {
 			free(device->address);
 			device->address = NULL;
@@ -552,6 +774,15 @@
 
 		dbus_message_iter_next(invalidated_array_iter);
 	}
+
+	/* If updated properties includes 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 (get_profile && device->connected) {
+		cras_bt_device_start_new_conn_watch_timer(device);
+	}
 }
 
 /* Converts bluetooth address string into sockaddr structure. The address
@@ -632,6 +863,13 @@
 		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);
+		goto error;
+	}
+
 	return sk;
 
 error:
@@ -644,8 +882,10 @@
 {
 	struct sco_options so;
 	socklen_t len = sizeof(so);
+	struct cras_bt_adapter *adapter;
 
-	if (cras_bt_adapter_on_usb(device->adapter))
+	adapter = cras_bt_adapter_get(device->adapter_obj_path);
+	if (cras_bt_adapter_on_usb(adapter))
 		return DEFAULT_HFP_MTU_BYTES;
 
 	if (getsockopt(sco_socket, SOL_SCO, SCO_OPTIONS, &so, &len) < 0) {
@@ -655,15 +895,57 @@
 	return so.mtu;
 }
 
-int cras_bt_device_set_speaker_gain(struct cras_bt_device *device, int gain)
+void cras_bt_device_set_use_hardware_volume(struct cras_bt_device *device,
+					    int use_hardware_volume)
 {
-	struct hfp_slc_handle *slc_handle;
+	struct cras_iodev *iodev;
 
-	slc_handle = cras_hfp_ag_get_slc(device);
-	if (!slc_handle)
-		return -EINVAL;
+	device->use_hardware_volume = use_hardware_volume;
+	iodev = device->bt_iodevs[CRAS_STREAM_OUTPUT];
+	if (iodev)
+		iodev->software_volume_needed = !use_hardware_volume;
+}
 
-	return hfp_event_speaker_gain(slc_handle, gain);
+int cras_bt_device_get_use_hardware_volume(struct cras_bt_device *device)
+{
+	return device->use_hardware_volume;
+}
+
+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)
+{
+	memset(msg, 0, sizeof(*msg));
+	msg->header.type = CRAS_MAIN_BT;
+	msg->header.length = sizeof(*msg);
+	msg->cmd = cmd;
+	msg->device = device;
+	msg->dev = dev;
+	msg->arg = arg;
+}
+
+int cras_bt_device_cancel_suspend(struct cras_bt_device *device)
+{
+	struct bt_device_msg msg;
+	int rc;
+
+	init_bt_device_msg(&msg, BT_DEVICE_CANCEL_SUSPEND, device, NULL, 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)
+{
+	struct bt_device_msg msg;
+	int rc;
+
+	init_bt_device_msg(&msg, BT_DEVICE_SCHEDULE_SUSPEND, device,
+			   NULL, msec);
+	rc = cras_main_message_send((struct cras_main_message *)&msg);
+	return rc;
 }
 
 /* This diagram describes how the profile switching happens. When
@@ -688,33 +970,26 @@
  *  | bt device        +------------+                              |
  *  +--------------------------------------------------------------+
  */
-int cras_bt_device_switch_profile_on_open(struct cras_bt_device *device,
-					  struct cras_iodev *bt_iodev)
+int cras_bt_device_switch_profile_enable_dev(struct cras_bt_device *device,
+					     struct cras_iodev *bt_iodev)
 {
 	struct bt_device_msg msg;
 	int rc;
 
-	msg.header.type = CRAS_MAIN_BT;
-	msg.header.length = sizeof(msg);
-	msg.cmd = BT_DEVICE_SWITCH_PROFILE_ON_OPEN;
-	msg.device = device;
-	msg.dev = bt_iodev;
-
+	init_bt_device_msg(&msg, BT_DEVICE_SWITCH_PROFILE_ENABLE_DEV,
+			   device, bt_iodev, 0);
 	rc = cras_main_message_send((struct cras_main_message *)&msg);
 	return rc;
 }
 
-int cras_bt_device_switch_profile_on_close(struct cras_bt_device *device,
-					   struct cras_iodev *bt_iodev)
+int cras_bt_device_switch_profile(struct cras_bt_device *device,
+				  struct cras_iodev *bt_iodev)
 {
 	struct bt_device_msg msg;
 	int rc;
 
-	msg.header.type = CRAS_MAIN_BT;
-	msg.header.length = sizeof(msg);
-	msg.cmd = BT_DEVICE_SWITCH_PROFILE_ON_CLOSE;
-	msg.device = device;
-	msg.dev = bt_iodev;
+	init_bt_device_msg(&msg, BT_DEVICE_SWITCH_PROFILE,
+			   device, bt_iodev, 0);
 	rc = cras_main_message_send((struct cras_main_message *)&msg);
 	return rc;
 }
@@ -724,19 +999,45 @@
 	struct cras_iodev *iodev;
 
 	iodev = device->bt_iodevs[CRAS_STREAM_INPUT];
-	if (iodev && iodev->is_open(iodev))
+	if (iodev && cras_iodev_is_open(iodev))
 		cras_bt_io_update_buffer_size(iodev);
 	iodev = device->bt_iodevs[CRAS_STREAM_OUTPUT];
-	if (iodev && iodev->is_open(iodev))
+	if (iodev && cras_iodev_is_open(iodev))
 		cras_bt_io_update_buffer_size(iodev);
 }
 
+static void profile_switch_delay_cb(struct cras_timer *timer, void *arg)
+{
+	struct cras_bt_device *device = (struct cras_bt_device *)arg;
+	struct cras_iodev *iodev;
+
+	device->switch_profile_timer = NULL;
+	iodev = device->bt_iodevs[CRAS_STREAM_OUTPUT];
+	if (!iodev)
+		return;
+	iodev->update_active_node(iodev, 0, 1);
+	cras_iodev_list_enable_dev(iodev);
+}
+
+static void bt_device_switch_profile_with_delay(struct cras_bt_device *device,
+						unsigned int delay_ms)
+{
+	struct cras_tm *tm = cras_system_state_get_tm();
+
+	if (device->switch_profile_timer) {
+		cras_tm_cancel_timer(tm, device->switch_profile_timer);
+		device->switch_profile_timer = NULL;
+	}
+	device->switch_profile_timer = cras_tm_create_timer(
+			tm, delay_ms, profile_switch_delay_cb, device);
+}
+
 /* Switches associated bt iodevs to use the active profile. This is
  * achieved by close the iodevs, update their active nodes, and then
  * finally reopen them. */
 static void bt_device_switch_profile(struct cras_bt_device *device,
 				     struct cras_iodev *bt_iodev,
-				     int on_open)
+				     int enable_dev)
 {
 	struct cras_iodev *iodev;
 	int was_enabled[CRAS_NUM_DIRECTIONS] = {0};
@@ -759,28 +1060,76 @@
 		iodev = device->bt_iodevs[dir];
 		if (!iodev)
 			continue;
+
 		/* If the iodev was active or this profile switching is
 		 * triggered at opening iodev, add it to active dev list.
+		 * However for the output iodev, adding it back to active dev
+		 * list could cause immediate switching from HFP to A2DP if
+		 * there exists an output stream. Certain headset/speaker
+		 * would fail to playback afterwards when the switching happens
+		 * too soon, so put this task in a delayed callback.
 		 */
 		if (was_enabled[dir] ||
-		    (on_open && iodev == bt_iodev)) {
-			iodev->update_active_node(iodev, 0);
-			cras_iodev_list_enable_dev(iodev);
+		    (enable_dev && iodev == bt_iodev)) {
+			if (dir == CRAS_STREAM_INPUT) {
+				iodev->update_active_node(iodev, 0, 1);
+				cras_iodev_list_enable_dev(iodev);
+			} else {
+				bt_device_switch_profile_with_delay(
+						device,
+						PROFILE_SWITCH_DELAY_MS);
+			}
 		}
 	}
 }
 
+static void bt_device_suspend_cb(struct cras_timer *timer, void *arg)
+{
+	struct cras_bt_device *device = (struct cras_bt_device *)arg;
+
+	device->suspend_timer = NULL;
+
+	cras_a2dp_suspend_connected_device(device);
+	cras_hfp_ag_suspend_connected_device(device);
+}
+
+static void bt_device_schedule_suspend(struct cras_bt_device *device,
+				       unsigned int msec)
+{
+	struct cras_tm *tm = cras_system_state_get_tm();
+
+	if (device->suspend_timer)
+		return;
+	device->suspend_timer = cras_tm_create_timer(tm, msec,
+			bt_device_suspend_cb, device);
+}
+
+static void bt_device_cancel_suspend(struct cras_bt_device *device)
+{
+	struct cras_tm *tm = cras_system_state_get_tm();
+	if (device->suspend_timer == NULL)
+		return;
+	cras_tm_cancel_timer(tm, device->suspend_timer);
+	device->suspend_timer = NULL;
+}
+
 static void bt_device_process_msg(struct cras_main_message *msg, void *arg)
 {
 	struct bt_device_msg *bt_msg = (struct bt_device_msg *)msg;
 
 	switch (bt_msg->cmd) {
-	case BT_DEVICE_SWITCH_PROFILE_ON_CLOSE:
+	case BT_DEVICE_SWITCH_PROFILE:
 		bt_device_switch_profile(bt_msg->device, bt_msg->dev, 0);
 		break;
-	case BT_DEVICE_SWITCH_PROFILE_ON_OPEN:
+	case BT_DEVICE_SWITCH_PROFILE_ENABLE_DEV:
 		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);
+		break;
+	case BT_DEVICE_CANCEL_SUSPEND:
+		bt_device_cancel_suspend(bt_msg->device);
+		break;
 	default:
 		break;
 	}
@@ -791,3 +1140,22 @@
 	cras_main_message_add_handler(CRAS_MAIN_BT,
 				      bt_device_process_msg, NULL);
 }
+
+void cras_bt_device_update_hardware_volume(struct cras_bt_device *device,
+					   int volume)
+{
+	struct cras_iodev *iodev;
+
+	iodev = device->bt_iodevs[CRAS_STREAM_OUTPUT];
+	if (iodev == NULL)
+		return;
+
+	/* Check if this BT device is okay to use hardware volume. If not
+	 * then ignore the reported volume change event.
+	 */
+	if (!cras_bt_device_get_use_hardware_volume(device))
+		return;
+
+	iodev->active_node->volume = volume;
+	cras_iodev_list_notify_node_volume(iodev->active_node);
+}
diff --git a/cras/src/server/cras_bt_device.h b/cras/src/server/cras_bt_device.h
index 2bc0fe1..50468ab 100644
--- a/cras/src/server/cras_bt_device.h
+++ b/cras/src/server/cras_bt_device.h
@@ -26,7 +26,8 @@
 
 enum cras_bt_device_profile cras_bt_device_profile_from_uuid(const char *uuid);
 
-struct cras_bt_device *cras_bt_device_create(const char *object_path);
+struct cras_bt_device *cras_bt_device_create(DBusConnection *conn,
+					     const char *object_path);
 void cras_bt_device_destroy(struct cras_bt_device *device);
 void cras_bt_device_reset();
 
@@ -54,6 +55,18 @@
 int cras_bt_device_supports_profile(const struct cras_bt_device *device,
 				    enum cras_bt_device_profile profile);
 
+/* Sets if the BT audio device should use hardware volume.
+ * Args:
+ *    device - The remote bluetooth audio device.
+ *    use_hardware_volume - Set to true to indicate hardware volume
+ *        is preferred over software volume.
+ */
+void cras_bt_device_set_use_hardware_volume(struct cras_bt_device *device,
+					    int use_hardware_volume);
+
+/* Gets if the BT audio device should use hardware volume. */
+int cras_bt_device_get_use_hardware_volume(struct cras_bt_device *device);
+
 /* Forces disconnect the bt device. Used when handling audio error
  * that we want to make the device be completely disconnected from
  * host to reflect the state that an error has occurred.
@@ -73,13 +86,6 @@
 /* Queries the preffered mtu value for SCO socket. */
 int cras_bt_device_sco_mtu(struct cras_bt_device *device, int sco_socket);
 
-/* Sets the speaker gain for bt device, note this is for HFP/HSP mode.
- * Args:
- *    device - The device object to set speaker gain.
- *    gain - value between 0-100.
- */
-int cras_bt_device_set_speaker_gain(struct cras_bt_device *device, int gain);
-
 /* Appends an iodev to bt device.
  * Args:
  *    device - The device to append iodev to.
@@ -105,25 +111,25 @@
 void cras_bt_device_set_active_profile(struct cras_bt_device *device,
 				       unsigned int profile);
 
-/* Calls this function after configures the active profile of bt device
- * will reactivate the bt_ios associated with the bluetooth device. So it
- * can switch to the new active profile at open.
+/* Switches profile after the active profile of bt device has changed and
+ * enables bt iodev immediately. This function is used for profile switching
+ * at iodev open.
  * Args:
  *    device - The bluetooth device.
  *    bt_iodev - The iodev triggers the reactivaion.
  */
-int cras_bt_device_switch_profile_on_open(struct cras_bt_device *device,
-					  struct cras_iodev *bt_iodev);
+int cras_bt_device_switch_profile_enable_dev(struct cras_bt_device *device,
+					     struct cras_iodev *bt_iodev);
 
-/* Calls this function after configures the active profile of bt device
- * will reactivate the bt_ios associated with the bluetooth device. So it
- * can switch to the new active profile at device close.
+/* Switches profile after the active profile of bt device has changed. This
+ * function is used when we want to switch profile without changing the
+ * iodev's status.
  * Args:
  *    device - The bluetooth device.
  *    bt_iodev - The iodev triggers the reactivaion.
  */
-int cras_bt_device_switch_profile_on_close(struct cras_bt_device *device,
-					   struct cras_iodev *bt_iodev);
+int cras_bt_device_switch_profile(struct cras_bt_device *device,
+				  struct cras_iodev *bt_iodev);
 
 /* Calls this function when the buffer size of an underlying profile iodev
  * has changed and update it for the virtual bt iodev. */
@@ -139,14 +145,26 @@
  */
 int cras_bt_device_can_switch_to_a2dp(struct cras_bt_device *device);
 
-/* Adds an a2dp delay timer to this device. */
-void cras_bt_device_add_a2dp_delay_timer(struct cras_bt_device *device,
-					 struct cras_timer *timer);
+/* Updates the volume to bt_device when a volume change event is reported. */
+void cras_bt_device_update_hardware_volume(struct cras_bt_device *device,
+					   int volume);
 
-/* Cancels the a2dp delay timer if it's not been trigered yet. */
-void cras_bt_device_cancel_a2dp_delay_timer(struct cras_bt_device *device);
+/* Notifies bt_device that a2dp connection is configured. */
+void cras_bt_device_a2dp_configured(struct cras_bt_device *device);
 
-/* Removes any a2dp delay timer from this device. */
-void cras_bt_device_rm_a2dp_delay_timer(struct cras_bt_device *device);
+/* Cancels any scheduled suspension of device. */
+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);
+
+/* Notifies bt device that audio gateway is initialized.
+ * Args:
+ *   device - The bluetooth device.
+ * Returns:
+ *   0 on success, error code otherwise.
+ */
+int cras_bt_device_audio_gateway_initialized(struct cras_bt_device *device);
 
 #endif /* CRAS_BT_DEVICE_H_ */
diff --git a/cras/src/server/cras_bt_endpoint.c b/cras/src/server/cras_bt_endpoint.c
index e3013c0..52cc362 100644
--- a/cras/src/server/cras_bt_endpoint.c
+++ b/cras/src/server/cras_bt_endpoint.c
@@ -41,15 +41,6 @@
 	"</node>\n"
 
 
-static void cras_bt_endpoint_start(struct cras_bt_endpoint *endpoint,
-				   struct cras_bt_transport *transport)
-{
-	cras_bt_transport_set_endpoint(transport, endpoint);
-	endpoint->transport = transport;
-
-	endpoint->start(endpoint, transport);
-}
-
 static void cras_bt_endpoint_suspend(struct cras_bt_endpoint *endpoint)
 {
 	if (!endpoint->transport)
@@ -115,7 +106,9 @@
 		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
 	}
 
-	cras_bt_endpoint_start(endpoint, transport);
+	cras_bt_transport_set_endpoint(transport, endpoint);
+	endpoint->transport = transport;
+	endpoint->set_configuration(endpoint, transport);
 
 	reply = dbus_message_new_method_return(message);
 	if (!reply)
diff --git a/cras/src/server/cras_bt_endpoint.h b/cras/src/server/cras_bt_endpoint.h
index c9c3e33..be56070 100644
--- a/cras/src/server/cras_bt_endpoint.h
+++ b/cras/src/server/cras_bt_endpoint.h
@@ -24,8 +24,8 @@
 				    void *capabilities, int len,
 				    void *configuration);
 
-	void (*start)(struct cras_bt_endpoint *endpoint,
-		      struct cras_bt_transport *transport);
+	void (*set_configuration)(struct cras_bt_endpoint *endpoint,
+				  struct cras_bt_transport *transport);
 	void (*suspend)(struct cras_bt_endpoint *endpoint,
 			struct cras_bt_transport *transport);
 
diff --git a/cras/src/server/cras_bt_io.c b/cras/src/server/cras_bt_io.c
index 88c046d..1363d90 100644
--- a/cras/src/server/cras_bt_io.c
+++ b/cras/src/server/cras_bt_io.c
@@ -7,9 +7,10 @@
 
 #include "cras_bt_io.h"
 #include "cras_bt_device.h"
-#include "cras_dbus_util.h"
+#include "cras_utf8.h"
 #include "cras_iodev.h"
 #include "cras_iodev_list.h"
+#include "sfh.h"
 #include "utlist.h"
 
 #define DEFAULT_BT_DEVICE_NAME "BLUETOOTH"
@@ -66,6 +67,9 @@
 	n->base.idx = btio->next_node_id++;
 	n->base.type = CRAS_NODE_TYPE_BLUETOOTH;
 	n->base.volume = 100;
+	n->base.stable_id = dev->info.stable_id;
+	n->base.stable_id_new = dev->info.stable_id_new;
+	n->base.max_software_gain = 0;
 	gettimeofday(&n->base.plugged_time, NULL);
 
 	strcpy(n->base.name, dev->info.name);
@@ -129,7 +133,7 @@
 	    iodev->direction == CRAS_STREAM_INPUT) {
 		bt_switch_to_profile(btio->device,
 				     CRAS_BT_DEVICE_PROFILE_HFP_AUDIOGATEWAY);
-		cras_bt_device_switch_profile_on_open(btio->device, iodev);
+		cras_bt_device_switch_profile_enable_dev(btio->device, iodev);
 		return -EAGAIN;
 	}
 
@@ -206,8 +210,7 @@
 	    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_on_close(btio->device,
-						       iodev);
+		cras_bt_device_switch_profile(btio->device, iodev);
 	}
 
 	rc = dev->close_dev(dev);
@@ -223,32 +226,22 @@
 
 	if (dev->active_node)
 		dev->active_node->volume = iodev->active_node->volume;
-	if (dev->set_volume)
+
+	/* The parent bt_iodev could set software_volume_needed flag for cases
+	 * that software volume provides better experience across profiles
+	 * (HFP and A2DP). Otherwise, use the profile specific implementation
+	 * to adjust volume. */
+	if (dev->set_volume && !iodev->software_volume_needed)
 		dev->set_volume(dev);
 }
 
-static int is_open(const struct cras_iodev *iodev)
-{
-	struct cras_iodev *dev = active_profile_dev(iodev);
-	if (!dev)
-		return 0;
-	return dev->is_open(dev);
-}
-
-static int frames_queued(const struct cras_iodev *iodev)
+static int frames_queued(const struct cras_iodev *iodev,
+			 struct timespec *tstamp)
 {
 	struct cras_iodev *dev = active_profile_dev(iodev);
 	if (!dev)
 		return -EINVAL;
-	return dev->frames_queued(dev);
-}
-
-static int dev_running(const struct cras_iodev *iodev)
-{
-	struct cras_iodev *dev = active_profile_dev(iodev);
-	if (!dev)
-		return -EINVAL;
-	return dev->dev_running(dev);
+	return dev->frames_queued(dev, tstamp);
 }
 
 static int delay_frames(const struct cras_iodev *iodev)
@@ -288,7 +281,8 @@
 /* If the first private iodev doesn't match the active profile stored on
  * device, select to the correct private iodev.
  */
-static void update_active_node(struct cras_iodev *iodev, unsigned node_idx)
+static void update_active_node(struct cras_iodev *iodev, unsigned node_idx,
+			       unsigned dev_enabled)
 {
 	struct bt_io *btio = (struct bt_io *)iodev;
 	struct cras_ionode *node;
@@ -307,10 +301,7 @@
 			active->profile = n->profile;
 			active->profile_dev = n->profile_dev;
 
-			/* Fill all volume related stuff to/from the
-			 * profile dev. */
-			iodev->software_volume_needed =
-				active->profile_dev->software_volume_needed;
+			/* Set volume for the new profile. */
 			set_bt_volume(iodev);
 		}
 	}
@@ -338,11 +329,10 @@
 	iodev->direction = dev->direction;
 	strcpy(iodev->info.name, dev->info.name);
 	iodev->info.stable_id = dev->info.stable_id;
+	iodev->info.stable_id_new = dev->info.stable_id_new;
 
 	iodev->open_dev = open_dev;
-	iodev->is_open = is_open; /* Needed by thread_add_stream */
 	iodev->frames_queued = frames_queued;
-	iodev->dev_running = dev_running;
 	iodev->delay_frames = delay_frames;
 	iodev->get_buffer = get_buffer;
 	iodev->put_buffer = put_buffer;
@@ -350,8 +340,9 @@
 	iodev->close_dev = close_dev;
 	iodev->update_supported_formats = update_supported_formats;
 	iodev->update_active_node = update_active_node;
-	iodev->software_volume_needed = dev->software_volume_needed;
+	iodev->software_volume_needed = 1;
 	iodev->set_volume = set_bt_volume;
+	iodev->no_stream = cras_iodev_default_no_stream_playback;
 
 	/* Create the dummy node set to plugged so it's the only node exposed
 	 * to UI, and point it to the first profile dev. */
@@ -363,6 +354,11 @@
 	active->base.type = CRAS_NODE_TYPE_BLUETOOTH;
 	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_new = active->base.stable_id;
 	active->profile = profile;
 	active->profile_dev = dev;
 	gettimeofday(&active->base.plugged_time, NULL);
@@ -450,7 +446,7 @@
 	    cras_bt_device_can_switch_to_a2dp(btio->device)) {
 		bt_switch_to_profile(btio->device,
 				     CRAS_BT_DEVICE_PROFILE_A2DP_SOURCE);
-		cras_bt_device_switch_profile_on_open(btio->device, bt_iodev);
+		cras_bt_device_switch_profile(btio->device, bt_iodev);
 		syslog(LOG_ERR, "Switch to A2DP on append");
 	}
 	return 0;
@@ -522,7 +518,7 @@
 
 	/* The node of active profile could have been removed, update it.
 	 * Return err when fail to locate the active profile dev. */
-	update_active_node(bt_iodev, 0);
+	update_active_node(bt_iodev, 0, 1);
 	btnode = (struct bt_node *)bt_iodev->active_node;
 	if ((btnode->profile == 0) || (btnode->profile_dev == NULL))
 		return -EINVAL;
diff --git a/cras/src/server/cras_bt_manager.c b/cras/src/server/cras_bt_manager.c
index be0bd88..8d6c97a 100644
--- a/cras/src/server/cras_bt_manager.c
+++ b/cras/src/server/cras_bt_manager.c
@@ -15,6 +15,7 @@
 #include "cras_bt_adapter.h"
 #include "cras_bt_device.h"
 #include "cras_bt_endpoint.h"
+#include "cras_bt_player.h"
 #include "cras_bt_profile.h"
 #include "cras_bt_transport.h"
 #include "utlist.h"
@@ -38,6 +39,7 @@
 				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",
@@ -57,7 +59,7 @@
 			cras_bt_device_update_properties(
 				device, properties_array_iter, NULL);
 		} else {
-			device = cras_bt_device_create(object_path);
+			device = cras_bt_device_create(conn, object_path);
 			if (device) {
 				cras_bt_device_update_properties(
 					device, properties_array_iter, NULL);
diff --git a/cras/src/server/cras_bt_player.c b/cras/src/server/cras_bt_player.c
new file mode 100644
index 0000000..3821721
--- /dev/null
+++ b/cras/src/server/cras_bt_player.c
@@ -0,0 +1,179 @@
+/* Copyright (c) 2016 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include <dbus/dbus.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <syslog.h>
+
+#include "cras_bt_constants.h"
+#include "cras_bt_adapter.h"
+#include "cras_bt_player.h"
+#include "cras_dbus_util.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)
+{
+	DBusMessage *reply;
+
+	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, "RegisterPlayer returned error: %s",
+		       dbus_message_get_error_name(reply));
+		dbus_message_unref(reply);
+		return;
+	}
+
+	dbus_message_unref(reply);
+}
+
+static int cras_bt_add_player(DBusConnection *conn,
+			      const struct cras_bt_adapter *adapter,
+			      struct cras_bt_player *player)
+{
+	const char *adapter_path;
+	DBusMessage *method_call;
+	DBusMessageIter message_iter, dict;
+	DBusPendingCall *pending_call;
+
+	adapter_path = cras_bt_adapter_object_path(adapter);
+	method_call = dbus_message_new_method_call(BLUEZ_SERVICE,
+						   adapter_path,
+						   BLUEZ_INTERFACE_MEDIA,
+						   "RegisterPlayer");
+	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,
+				       &player->object_path);
+
+	dbus_message_iter_open_container(&message_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,
+			 &player->playback_status);
+	append_key_value(&dict, "Identity", DBUS_TYPE_STRING,
+			 DBUS_TYPE_STRING_AS_STRING,
+			 &player->identity);
+	append_key_value(&dict, "LoopStatus", DBUS_TYPE_STRING,
+			 DBUS_TYPE_STRING_AS_STRING,
+			 &player->loop_status);
+	append_key_value(&dict, "Position", DBUS_TYPE_INT64,
+			 DBUS_TYPE_INT64_AS_STRING, &player->position);
+	append_key_value(&dict, "Shuffle", DBUS_TYPE_BOOLEAN,
+			 DBUS_TYPE_BOOLEAN_AS_STRING, &player->shuffle);
+	append_key_value(&dict, "CanGoNext", DBUS_TYPE_BOOLEAN,
+			 DBUS_TYPE_BOOLEAN_AS_STRING, &player->can_go_next);
+	append_key_value(&dict, "CanGoPrevious", DBUS_TYPE_BOOLEAN,
+			 DBUS_TYPE_BOOLEAN_AS_STRING, &player->can_go_prev);
+	append_key_value(&dict, "CanPlay", DBUS_TYPE_BOOLEAN,
+			 DBUS_TYPE_BOOLEAN_AS_STRING, &player->can_play);
+	append_key_value(&dict, "CanPause", DBUS_TYPE_BOOLEAN,
+			 DBUS_TYPE_BOOLEAN_AS_STRING, &player->can_pause);
+	append_key_value(&dict, "CanControl", DBUS_TYPE_BOOLEAN,
+			 DBUS_TYPE_BOOLEAN_AS_STRING, &player->can_control);
+
+	dbus_message_iter_close_container(&message_iter, &dict);
+
+	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_player_registered,
+					  player, NULL)) {
+		dbus_pending_call_cancel(pending_call);
+		dbus_pending_call_unref(pending_call);
+		return -ENOMEM;
+	}
+	return 0;
+}
+
+
+/* Note that player properties will be used mostly for AVRCP qualification and
+ * not for normal use cases. The corresponding media events won't be routed by
+ * CRAS until we have a plan to provide general system API to handle media
+ * control.
+ */
+static struct cras_bt_player player = {
+	.object_path = CRAS_DEFAULT_PLAYER,
+	.playback_status = "playing",
+	.identity = "DefaultPlayer",
+	.loop_status = "None",
+	.shuffle = 0,
+	.position = 0,
+	.can_go_next = 0,
+	.can_go_prev = 0,
+	.can_play = 0,
+	.can_pause = 0,
+	.can_control = 0,
+	.message_cb = NULL,
+};
+
+static DBusHandlerResult cras_bt_player_handle_message(DBusConnection *conn,
+						       DBusMessage *message,
+						       void *arg)
+{
+	const char *msg = dbus_message_get_member(message);
+
+	if (player.message_cb)
+		player.message_cb(msg);
+
+	return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+int cras_bt_player_create(DBusConnection *conn)
+{
+	static const DBusObjectPathVTable player_vtable = {
+	        .message_function = cras_bt_player_handle_message
+	};
+
+	DBusError dbus_error;
+	struct cras_bt_adapter **adapters;
+	size_t num_adapters, i;
+
+	dbus_error_init(&dbus_error);
+
+	if (!dbus_connection_register_object_path(conn,
+						  player.object_path,
+						  &player_vtable,
+						  &dbus_error)) {
+		syslog(LOG_ERR, "Cannot register player %s",
+		       player.object_path);
+		dbus_error_free(&dbus_error);
+		return -ENOMEM;
+	}
+
+	num_adapters = cras_bt_adapter_get_list(&adapters);
+	for (i = 0; i < num_adapters; ++i)
+		cras_bt_add_player(conn, adapters[i], &player);
+	free(adapters);
+	return 0;
+}
+
+int cras_bt_register_player(DBusConnection *conn,
+			    const struct cras_bt_adapter *adapter)
+{
+	return cras_bt_add_player(conn, adapter, &player);
+}
diff --git a/cras/src/server/cras_bt_player.h b/cras/src/server/cras_bt_player.h
new file mode 100644
index 0000000..cce3710
--- /dev/null
+++ b/cras/src/server/cras_bt_player.h
@@ -0,0 +1,49 @@
+/* Copyright (c) 2016 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_PLAYER_H_
+#define CRAS_BT_PLAYER_H_
+
+#include <dbus/dbus.h>
+#include <stdbool.h>
+
+#include "cras_bt_adapter.h"
+
+
+/* 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;
+	const char *loop_status;
+	int position;
+	bool can_go_next;
+	bool can_go_prev;
+	bool can_play;
+	bool can_pause;
+	bool can_control;
+	bool shuffle;
+	void (*message_cb)(const char *message);
+};
+
+/* Creates a player object and register it to bluetoothd.
+ * Args:
+ *    conn - The dbus connection.
+ */
+int cras_bt_player_create(DBusConnection *conn);
+
+/* Registers created player to bluetoothd. This is used when an bluetooth
+ * adapter got enumerated.
+ * Args:
+ *    conn - The dbus connection.
+ *    adapter - The enumerated bluetooth adapter.
+ */
+int cras_bt_register_player(DBusConnection *conn,
+			    const struct cras_bt_adapter *adapter);
+
+#endif /* CRAS_BT_PLAYER_H_ */
\ No newline at end of file
diff --git a/cras/src/server/cras_bt_profile.c b/cras/src/server/cras_bt_profile.c
index fedc344..9f898aa 100644
--- a/cras/src/server/cras_bt_profile.c
+++ b/cras/src/server/cras_bt_profile.c
@@ -111,7 +111,7 @@
 	if (!device) {
 		syslog(LOG_ERR, "Device %s not found at %s new connection",
 		       object_path, profile_path);
-		device = cras_bt_device_create(object_path);
+		device = cras_bt_device_create(conn, object_path);
 	}
 
 	err = profile->new_connection(conn, profile, device, fd);
diff --git a/cras/src/server/cras_bt_transport.c b/cras/src/server/cras_bt_transport.c
index 1927403..4037cfd 100644
--- a/cras/src/server/cras_bt_transport.c
+++ b/cras/src/server/cras_bt_transport.c
@@ -31,6 +31,7 @@
 	int fd;
 	uint16_t read_mtu;
 	uint16_t write_mtu;
+	int volume;
 
 	struct cras_bt_endpoint *endpoint;
 	struct cras_bt_transport *prev, *next;
@@ -57,6 +58,7 @@
 	dbus_connection_ref(transport->conn);
 
 	transport->fd = -1;
+	transport->volume = -1;
 
 	DL_APPEND(transports, transport);
 
@@ -147,11 +149,6 @@
 	return transport->profile;
 }
 
-int cras_bt_transport_codec(const struct cras_bt_transport *transport)
-{
-	return transport->codec;
-}
-
 int cras_bt_transport_configuration(const struct cras_bt_transport *transport,
 				    void *configuration, int len)
 {
@@ -170,22 +167,11 @@
 	return transport->state;
 }
 
-struct cras_bt_endpoint *cras_bt_transport_endpoint(
-	const struct cras_bt_transport *transport)
-{
-	return transport->endpoint;
-}
-
 int cras_bt_transport_fd(const struct cras_bt_transport *transport)
 {
 	return transport->fd;
 }
 
-uint16_t cras_bt_transport_read_mtu(const struct cras_bt_transport *transport)
-{
-	return transport->read_mtu;
-}
-
 uint16_t cras_bt_transport_write_mtu(const struct cras_bt_transport *transport)
 {
 	return transport->write_mtu;
@@ -213,18 +199,23 @@
 				transport);
 }
 
-void cras_bt_transport_fill_properties(struct cras_bt_transport *transport,
-				       int fd, const char *uuid)
+/* Updates bt_device when certain transport property has changed. */
+static void cras_bt_transport_update_device(struct cras_bt_transport *transport)
 {
-	transport->device = cras_bt_device_get(transport->object_path);
-	transport->profile = cras_bt_device_profile_from_uuid(uuid);
-	free(transport->configuration);
+	if (!transport->device)
+		return;
 
-	/* For HFP, the configuration is just the file descriptor of
-	 * the rfcomm socket */
-	transport->configuration = (int *)malloc(sizeof(fd));
-	memcpy(transport->configuration, &fd, sizeof(fd));
-	transport->configuration_len = sizeof(fd);
+	/* When the transport has non-negaive volume, it means the remote
+	 * BT audio devices supports AVRCP absolute volume. Set the flag in bt
+	 * device to use hardware volume. Also map the volume value from 0-127
+	 * to 0-100.
+	 */
+	if (transport->volume != -1) {
+		cras_bt_device_set_use_hardware_volume(transport->device, 1);
+		cras_bt_device_update_hardware_volume(
+				transport->device,
+				transport->volume * 100 / 127);
+	}
 }
 
 void cras_bt_transport_update_properties(
@@ -285,7 +276,9 @@
 				       "transport properties",
 				       obj_path);
 				transport->device =
-					cras_bt_device_create(obj_path);
+					cras_bt_device_create(transport->conn,
+							      obj_path);
+				cras_bt_transport_update_device(transport);
 			}
 		} else if (strcmp(
 				dbus_message_iter_get_signature(&variant_iter),
@@ -308,6 +301,12 @@
 				transport->configuration_len = len;
 			}
 
+		} else if (strcmp(key, "Volume") == 0) {
+			uint16_t volume;
+
+			dbus_message_iter_get_basic(&variant_iter, &volume);
+			transport->volume = volume;
+			cras_bt_transport_update_device(transport);
 		}
 
 		dbus_message_iter_next(properties_array_iter);
@@ -338,6 +337,69 @@
 	}
 }
 
+static void on_transport_volume_set(DBusPendingCall *pending_call, void *data)
+{
+	DBusMessage *reply;
+
+	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, "Set absolute volume returned error: %s",
+		       dbus_message_get_error_name(reply));
+	dbus_message_unref(reply);
+}
+
+int cras_bt_transport_set_volume(struct cras_bt_transport *transport,
+				 uint16_t volume)
+{
+	const char *key = "Volume";
+	const char *interface = BLUEZ_INTERFACE_MEDIA_TRANSPORT;
+	DBusMessage *method_call;
+	DBusMessageIter message_iter, variant;
+	DBusPendingCall *pending_call;
+
+	method_call = dbus_message_new_method_call(
+		BLUEZ_SERVICE,
+		transport->object_path,
+		DBUS_INTERFACE_PROPERTIES,
+		"Set");
+	if (!method_call)
+		return -ENOMEM;
+
+	dbus_message_iter_init_append(method_call, &message_iter);
+
+	dbus_message_iter_append_basic(&message_iter, DBUS_TYPE_STRING,
+				       &interface);
+	dbus_message_iter_append_basic(&message_iter, DBUS_TYPE_STRING, &key);
+
+	dbus_message_iter_open_container(&message_iter, DBUS_TYPE_VARIANT,
+					 DBUS_TYPE_UINT16_AS_STRING, &variant);
+	dbus_message_iter_append_basic(&variant, DBUS_TYPE_UINT16, &volume);
+	dbus_message_iter_close_container(&message_iter, &variant);
+
+	if (!dbus_connection_send_with_reply(transport->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,
+					  on_transport_volume_set,
+					  NULL, NULL)) {
+		dbus_pending_call_cancel(pending_call);
+		dbus_pending_call_unref(pending_call);
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
 int cras_bt_transport_acquire(struct cras_bt_transport *transport)
 {
 	DBusMessage *method_call, *reply;
@@ -453,9 +515,30 @@
 	return 0;
 }
 
-int cras_bt_transport_release(struct cras_bt_transport *transport)
+/* Callback to trigger when transport release completed. */
+static void cras_bt_on_transport_release(DBusPendingCall *pending_call,
+					 void *data)
+{
+	DBusMessage *reply;
+
+	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_WARNING, "Release transport returned error: %s",
+		       dbus_message_get_error_name(reply));
+		dbus_message_unref(reply);
+		return;
+	}
+
+	dbus_message_unref(reply);
+}
+
+int cras_bt_transport_release(struct cras_bt_transport *transport,
+			      unsigned int blocking)
 {
 	DBusMessage *method_call, *reply;
+	DBusPendingCall *pending_call;
 	DBusError dbus_error;
 
 	if (transport->fd < 0)
@@ -475,30 +558,53 @@
 	if (!method_call)
 		return -ENOMEM;
 
-	dbus_error_init(&dbus_error);
+	if (blocking) {
+		dbus_error_init(&dbus_error);
 
-	reply = dbus_connection_send_with_reply_and_block(
-		transport->conn,
-		method_call,
-		DBUS_TIMEOUT_USE_DEFAULT,
-		&dbus_error);
-	if (!reply) {
-		syslog(LOG_ERR, "Failed to release transport %s: %s",
-		       transport->object_path, dbus_error.message);
-		dbus_error_free(&dbus_error);
+		reply = dbus_connection_send_with_reply_and_block(
+			transport->conn,
+			method_call,
+			DBUS_TIMEOUT_USE_DEFAULT,
+			&dbus_error);
+		if (!reply) {
+			syslog(LOG_ERR, "Failed to release transport %s: %s",
+			       transport->object_path, dbus_error.message);
+			dbus_error_free(&dbus_error);
+			dbus_message_unref(method_call);
+			return -EIO;
+		}
+
 		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, "Release returned error: %s",
+			       dbus_message_get_error_name(reply));
+			dbus_message_unref(reply);
+			return -EIO;
+		}
 
-	if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) {
-		syslog(LOG_ERR, "Release returned error: %s",
-		       dbus_message_get_error_name(reply));
 		dbus_message_unref(reply);
-		return -EIO;
-	}
+	} else {
+		if (!dbus_connection_send_with_reply(
+				transport->conn,
+				method_call,
+				&pending_call,
+				DBUS_TIMEOUT_USE_DEFAULT)) {
+			dbus_message_unref(method_call);
+			return -ENOMEM;
+		}
 
-	dbus_message_unref(reply);
+		dbus_message_unref(method_call);
+		if (!pending_call)
+			return -EIO;
+
+		if (!dbus_pending_call_set_notify(pending_call,
+						  cras_bt_on_transport_release,
+						  transport, NULL)) {
+			dbus_pending_call_cancel(pending_call);
+			dbus_pending_call_unref(pending_call);
+			return -ENOMEM;
+		}
+	}
 	return 0;
 }
diff --git a/cras/src/server/cras_bt_transport.h b/cras/src/server/cras_bt_transport.h
index e073727..bafd9d2 100644
--- a/cras/src/server/cras_bt_transport.h
+++ b/cras/src/server/cras_bt_transport.h
@@ -38,28 +38,14 @@
 	const struct cras_bt_transport *transport);
 enum cras_bt_device_profile cras_bt_transport_profile(
 	const struct cras_bt_transport *transport);
-int cras_bt_transport_codec(const struct cras_bt_transport *transport);
 int cras_bt_transport_configuration(const struct cras_bt_transport *transport,
 				    void *configuration, int len);
 enum cras_bt_transport_state cras_bt_transport_state(
 	const struct cras_bt_transport *transport);
-struct cras_bt_endpoint *cras_bt_transport_endpoint(
-	const struct cras_bt_transport *transport);
 
 int cras_bt_transport_fd(const struct cras_bt_transport *transport);
-uint16_t cras_bt_transport_read_mtu(const struct cras_bt_transport *transport);
 uint16_t cras_bt_transport_write_mtu(const struct cras_bt_transport *transport);
 
-/* Fills the necessary fields in cras_bt_tranport for cras_bt_profile
- * to create cras_iodev.
- * Args:
- *    transport - The transport object carries the fields
- *    fd - File descriptor of the rfcomm socket
- *    uuid - The UUID of the profile
- */
-void cras_bt_transport_fill_properties(struct cras_bt_transport *transport,
-		int fd, const char *uuid);
-
 void cras_bt_transport_update_properties(
 	struct cras_bt_transport *transport,
 	DBusMessageIter *properties_array_iter,
@@ -67,6 +53,23 @@
 
 int cras_bt_transport_try_acquire(struct cras_bt_transport *transport);
 int cras_bt_transport_acquire(struct cras_bt_transport *transport);
-int cras_bt_transport_release(struct cras_bt_transport *transport);
+
+/* Releases the cras_bt_transport.
+ * Args:
+ *    transport - The transport object to release
+ *    blocking - True to send release dbus message in blocking mode, otherwise
+ *        in non-block mode.
+ */
+int cras_bt_transport_release(struct cras_bt_transport *transport,
+			      unsigned int blocking);
+
+/* Sets the volume to cras_bt_transport. Note that the volume gets applied
+ * to BT headset only when the transport is in ACTIVE state.
+ * Args:
+ *    transport - The transport object to set volume to.
+ *    volume - The desired volume value, range in [0-127].
+ */
+int cras_bt_transport_set_volume(struct cras_bt_transport *transport,
+				 uint16_t volume);
 
 #endif /* CRAS_BT_TRANSPORT_H_ */
diff --git a/cras/src/server/cras_dbus_control.c b/cras/src/server/cras_dbus_control.c
index 11e86e1..2100224 100644
--- a/cras/src/server/cras_dbus_control.c
+++ b/cras/src/server/cras_dbus_control.c
@@ -10,10 +10,12 @@
 #include <string.h>
 #include <syslog.h>
 
+#include "audio_thread.h"
 #include "cras_dbus.h"
 #include "cras_dbus_control.h"
 #include "cras_dbus_util.h"
 #include "cras_iodev_list.h"
+#include "cras_observer.h"
 #include "cras_system_state.h"
 #include "cras_util.h"
 #include "utlist.h"
@@ -91,6 +93,14 @@
     "    <method name=\"GetNumberOfActiveInputStreams\">\n"             \
     "      <arg name=\"num\" type=\"i\" 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"  \
+    "    </method>\n"                                                   \
+    "    <method name=\"SetHotwordModel\">\n"                           \
+    "      <arg name=\"node_id\" type=\"t\" direction=\"in\"/>\n"       \
+    "      <arg name=\"model_name\" type=\"s\" direction=\"in\"/>\n"    \
+    "    </method>\n"                                                   \
     "  </interface>\n"                                                  \
     "  <interface name=\"" DBUS_INTERFACE_INTROSPECTABLE "\">\n"        \
     "    <method name=\"Introspect\">\n"                                \
@@ -101,9 +111,9 @@
 
 struct cras_dbus_control {
 	DBusConnection *conn;
+	struct cras_observer_client *observer;
 };
 static struct cras_dbus_control dbus_control;
-static cras_node_id_t last_output, last_input;
 
 /* helper to extract a single argument from a DBus message. */
 static int get_single_arg(DBusMessage *message, int dbus_type, void *arg)
@@ -126,7 +136,7 @@
 }
 
 /* Helper to send an empty reply. */
-static void send_empty_reply(DBusMessage *message)
+static void send_empty_reply(DBusConnection *conn, DBusMessage *message)
 {
 	DBusMessage *reply;
 	dbus_uint32_t serial = 0;
@@ -135,7 +145,27 @@
 	if (!reply)
 		return;
 
-	dbus_connection_send(dbus_control.conn, reply, &serial);
+	dbus_connection_send(conn, reply, &serial);
+
+	dbus_message_unref(reply);
+}
+
+/* Helper to send an int32 reply. */
+static void send_int32_reply(DBusConnection *conn,
+			     DBusMessage *message,
+			     dbus_int32_t value)
+{
+	DBusMessage *reply;
+	dbus_uint32_t serial = 0;
+
+	reply = dbus_message_new_method_return(message);
+	if (!reply)
+		return;
+
+	dbus_message_append_args(reply,
+				 DBUS_TYPE_INT32, &value,
+				 DBUS_TYPE_INVALID);
+	dbus_connection_send(conn, reply, &serial);
 
 	dbus_message_unref(reply);
 }
@@ -155,7 +185,7 @@
 
 	cras_system_set_volume(new_vol);
 
-	send_empty_reply(message);
+	send_empty_reply(conn, message);
 
 	return DBUS_HANDLER_RESULT_HANDLED;
 }
@@ -184,7 +214,7 @@
 
 	cras_iodev_list_set_node_attr(id, IONODE_ATTR_VOLUME, new_vol);
 
-	send_empty_reply(message);
+	send_empty_reply(conn, message);
 
 	return DBUS_HANDLER_RESULT_HANDLED;
 }
@@ -214,7 +244,7 @@
 	cras_iodev_list_set_node_attr(id, IONODE_ATTR_SWAP_LEFT_RIGHT,
 				      swap);
 
-	send_empty_reply(message);
+	send_empty_reply(conn, message);
 
 	return DBUS_HANDLER_RESULT_HANDLED;
 }
@@ -233,7 +263,7 @@
 
 	cras_system_set_mute(new_mute);
 
-	send_empty_reply(message);
+	send_empty_reply(conn, message);
 
 	return DBUS_HANDLER_RESULT_HANDLED;
 }
@@ -252,7 +282,7 @@
 
 	cras_system_set_user_mute(new_mute);
 
-	send_empty_reply(message);
+	send_empty_reply(conn, message);
 
 	return DBUS_HANDLER_RESULT_HANDLED;
 }
@@ -270,7 +300,7 @@
 
 	cras_system_set_suspended(suspend);
 
-	send_empty_reply(message);
+	send_empty_reply(conn, message);
 
 	return DBUS_HANDLER_RESULT_HANDLED;
 }
@@ -289,7 +319,7 @@
 
 	cras_system_set_capture_gain(new_gain);
 
-	send_empty_reply(message);
+	send_empty_reply(conn, message);
 
 	return DBUS_HANDLER_RESULT_HANDLED;
 }
@@ -318,7 +348,7 @@
 
 	cras_iodev_list_set_node_attr(id, IONODE_ATTR_CAPTURE_GAIN, new_gain);
 
-	send_empty_reply(message);
+	send_empty_reply(conn, message);
 
 	return DBUS_HANDLER_RESULT_HANDLED;
 }
@@ -337,7 +367,7 @@
 
 	cras_system_set_capture_mute(new_mute);
 
-	send_empty_reply(message);
+	send_empty_reply(conn, message);
 
 	return DBUS_HANDLER_RESULT_HANDLED;
 }
@@ -371,7 +401,7 @@
 				 DBUS_TYPE_BOOLEAN, &user_muted,
 				 DBUS_TYPE_INVALID);
 
-	dbus_connection_send(dbus_control.conn, reply, &serial);
+	dbus_connection_send(conn, reply, &serial);
 
 	dbus_message_unref(reply);
 
@@ -389,7 +419,8 @@
 	dbus_bool_t is_input;
 	dbus_uint64_t id;
 	const char *dev_name = dev->name;
-	dbus_uint64_t stable_dev_id = dev->stable_id;
+	dbus_uint64_t stable_dev_id = node->stable_id;
+	dbus_uint64_t stable_dev_id_new = node->stable_id_new;
 	const char *node_type = node->type;
 	const char *node_name = node->name;
 	const char *mic_positions = node->mic_positions;
@@ -398,6 +429,7 @@
 		node->plugged_time.tv_usec;
 	dbus_uint64_t node_volume = node->volume;
 	dbus_int64_t node_capture_gain = node->capture_gain;
+	char *models, *empty_models = "";
 
 	is_input = (direction == CRAS_STREAM_INPUT);
 	id = node->iodev_idx;
@@ -419,6 +451,9 @@
 	if (!append_key_value(&dict, "StableDeviceId", DBUS_TYPE_UINT64,
 			      DBUS_TYPE_UINT64_AS_STRING, &stable_dev_id))
 		return FALSE;
+	if (!append_key_value(&dict, "StableDeviceIdNew", DBUS_TYPE_UINT64,
+			      DBUS_TYPE_UINT64_AS_STRING, &stable_dev_id_new))
+		return FALSE;
 	if (!append_key_value(&dict, "Type", DBUS_TYPE_STRING,
 			      DBUS_TYPE_STRING_AS_STRING, &node_type))
 		return FALSE;
@@ -440,6 +475,16 @@
 	if (!append_key_value(&dict, "NodeCaptureGain", DBUS_TYPE_INT64,
 			      DBUS_TYPE_INT64_AS_STRING, &node_capture_gain))
 		return FALSE;
+
+	models = cras_iodev_list_get_hotword_models(id);
+	if (!append_key_value(&dict, "HotwordModels", DBUS_TYPE_STRING,
+			      DBUS_TYPE_STRING_AS_STRING,
+			      models ? &models : &empty_models)) {
+		free(models);
+		return FALSE;
+	}
+	free(models);
+
 	if (!dbus_message_iter_close_container(iter, &dict))
 		return FALSE;
 
@@ -496,7 +541,7 @@
 		return DBUS_HANDLER_RESULT_NEED_MEMORY;
 	if (!append_nodes(CRAS_STREAM_INPUT, &array))
 		return DBUS_HANDLER_RESULT_NEED_MEMORY;
-	dbus_connection_send(dbus_control.conn, reply, &serial);
+	dbus_connection_send(conn, reply, &serial);
 	dbus_message_unref(reply);
 
 	return DBUS_HANDLER_RESULT_HANDLED;
@@ -517,7 +562,7 @@
 
 	cras_iodev_list_select_node(direction, id);
 
-	send_empty_reply(message);
+	send_empty_reply(conn, message);
 
 	return DBUS_HANDLER_RESULT_HANDLED;
 }
@@ -537,7 +582,7 @@
 
 	cras_iodev_list_add_active_node(direction, id);
 
-	send_empty_reply(message);
+	send_empty_reply(conn, message);
 
 	return DBUS_HANDLER_RESULT_HANDLED;
 }
@@ -557,7 +602,7 @@
 
         cras_iodev_list_rm_active_node(direction, id);
 
-        send_empty_reply(message);
+        send_empty_reply(conn, message);
 
         return DBUS_HANDLER_RESULT_HANDLED;
 }
@@ -567,18 +612,7 @@
 	DBusMessage *message,
 	void *arg)
 {
-	DBusMessage *reply;
-	dbus_uint32_t serial = 0;
-	dbus_int32_t num;
-
-	reply = dbus_message_new_method_return(message);
-	num = cras_system_state_get_active_streams();
-	dbus_message_append_args(reply,
-				 DBUS_TYPE_INT32, &num,
-				 DBUS_TYPE_INVALID);
-	dbus_connection_send(dbus_control.conn, reply, &serial);
-	dbus_message_unref(reply);
-
+	send_int32_reply(conn, message, cras_system_state_get_active_streams());
 	return DBUS_HANDLER_RESULT_HANDLED;
 }
 
@@ -587,23 +621,14 @@
 	DBusMessage *message,
 	void *arg)
 {
-	DBusMessage *reply;
-	dbus_uint32_t serial = 0;
 	dbus_int32_t num = 0;
 	unsigned i;
 
-	reply = dbus_message_new_method_return(message);
-
 	for (i = 0; i < CRAS_NUM_DIRECTIONS; i++) {
 		if (cras_stream_uses_input_hw(i))
 			num += cras_system_state_get_active_streams_by_direction(i);
 	}
-
-	dbus_message_append_args(reply,
-				 DBUS_TYPE_INT32, &num,
-				 DBUS_TYPE_INVALID);
-	dbus_connection_send(dbus_control.conn, reply, &serial);
-	dbus_message_unref(reply);
+	send_int32_reply(conn, message, num);
 
 	return DBUS_HANDLER_RESULT_HANDLED;
 }
@@ -613,23 +638,85 @@
 	DBusMessage *message,
 	void *arg)
 {
-	DBusMessage *reply;
-	dbus_uint32_t serial = 0;
 	dbus_int32_t num = 0;
 	unsigned i;
 
-	reply = dbus_message_new_method_return(message);
-
 	for (i = 0; i < CRAS_NUM_DIRECTIONS; i++) {
 		if (cras_stream_uses_output_hw(i))
 			num += cras_system_state_get_active_streams_by_direction(i);
 	}
+	send_int32_reply(conn, message, num);
 
-	dbus_message_append_args(reply,
-				 DBUS_TYPE_INT32, &num,
-				 DBUS_TYPE_INVALID);
-	dbus_connection_send(dbus_control.conn, reply, &serial);
-	dbus_message_unref(reply);
+	return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static DBusHandlerResult handle_set_global_output_channel_remix(
+	DBusConnection *conn,
+	DBusMessage *message,
+	void *arg)
+{
+	dbus_int32_t num_channels;
+	double *coeff_array;
+	dbus_int32_t count;
+	DBusError dbus_error;
+	float *coefficient;
+	int i;
+
+	dbus_error_init(&dbus_error);
+
+	if (!dbus_message_get_args(message, &dbus_error,
+			DBUS_TYPE_INT32, &num_channels,
+			DBUS_TYPE_ARRAY,
+			      DBUS_TYPE_DOUBLE, &coeff_array, &count,
+			      DBUS_TYPE_INVALID)) {
+		syslog(LOG_WARNING, "Set global output channel remix error: %s",
+			dbus_error.message);
+		dbus_error_free(&dbus_error);
+		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+	}
+
+	coefficient = (float *)calloc(count, sizeof(*coefficient));
+	if (!coefficient)
+		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+	for (i = 0; i < count; i++)
+		coefficient[i] = coeff_array[i];
+
+	audio_thread_config_global_remix(
+			cras_iodev_list_get_audio_thread(),
+			num_channels,
+			coefficient);
+
+	send_empty_reply(conn, message);
+	free(coefficient);
+	return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static DBusHandlerResult handle_set_hotword_model(
+	DBusConnection *conn,
+	DBusMessage *message,
+	void *arg)
+{
+	cras_node_id_t id;
+	const char *model_name;
+	DBusError dbus_error;
+	dbus_int32_t ret;
+
+	dbus_error_init(&dbus_error);
+
+	if (!dbus_message_get_args(message, &dbus_error,
+				   DBUS_TYPE_UINT64, &id,
+				   DBUS_TYPE_STRING, &model_name,
+				   DBUS_TYPE_INVALID)) {
+		syslog(LOG_WARNING,
+		       "Bad method received: %s",
+		       dbus_error.message);
+		dbus_error_free(&dbus_error);
+		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+	}
+
+	ret = cras_iodev_list_set_hotword_model(id, model_name);
+	send_int32_reply(conn, message, ret);
 
 	return DBUS_HANDLER_RESULT_HANDLED;
 }
@@ -751,8 +838,18 @@
 						   "GetNumberOfActiveOutputStreams")) {
 		return handle_get_num_active_streams_use_output_hw(
 				conn, message, arg);
+	} else if (dbus_message_is_method_call(message,
+					       CRAS_CONTROL_INTERFACE,
+					       "SetGlobalOutputChannelRemix")) {
+		return handle_set_global_output_channel_remix(
+				conn, message, arg);
+	} else if (dbus_message_is_method_call(message,
+					       CRAS_CONTROL_INTERFACE,
+					       "SetHotwordModel")) {
+		return handle_set_hotword_model(conn, message, arg);
 	}
 
+
 	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
 }
 
@@ -771,11 +868,11 @@
 
 /* Handlers for system updates that generate DBus signals. */
 
-static void signal_volume(void *arg)
+static void signal_output_volume(void *context, int32_t volume)
 {
+	struct cras_dbus_control *control = (struct cras_dbus_control *)context;
 	dbus_uint32_t serial = 0;
 	DBusMessage *msg;
-	dbus_int32_t volume;
 
 	msg = create_dbus_message("OutputVolumeChanged");
 	if (!msg)
@@ -785,16 +882,16 @@
 	dbus_message_append_args(msg,
 				 DBUS_TYPE_INT32, &volume,
 				 DBUS_TYPE_INVALID);
-	dbus_connection_send(dbus_control.conn, msg, &serial);
+	dbus_connection_send(control->conn, msg, &serial);
 	dbus_message_unref(msg);
 }
 
-static void signal_mute(void *arg)
+static void signal_output_mute(void *context, int muted, int user_muted,
+			       int mute_locked)
 {
+	struct cras_dbus_control *control = (struct cras_dbus_control *)context;
 	dbus_uint32_t serial = 0;
 	DBusMessage *msg;
-	dbus_bool_t muted;
-	dbus_bool_t user_muted;
 
 	msg = create_dbus_message("OutputMuteChanged");
 	if (!msg)
@@ -806,48 +903,47 @@
 				 DBUS_TYPE_BOOLEAN, &muted,
 				 DBUS_TYPE_BOOLEAN, &user_muted,
 				 DBUS_TYPE_INVALID);
-	dbus_connection_send(dbus_control.conn, msg, &serial);
+	dbus_connection_send(control->conn, msg, &serial);
 	dbus_message_unref(msg);
 }
 
-static void signal_capture_gain(void *arg)
+static void signal_capture_gain(void *context, int32_t gain)
 {
+	struct cras_dbus_control *control = (struct cras_dbus_control *)context;
 	dbus_uint32_t serial = 0;
 	DBusMessage *msg;
-	dbus_int32_t gain;
 
 	msg = create_dbus_message("InputGainChanged");
 	if (!msg)
 		return;
 
-	gain = cras_system_get_capture_gain();
 	dbus_message_append_args(msg,
 				 DBUS_TYPE_INT32, &gain,
 				 DBUS_TYPE_INVALID);
-	dbus_connection_send(dbus_control.conn, msg, &serial);
+	dbus_connection_send(control->conn, msg, &serial);
 	dbus_message_unref(msg);
 }
 
-static void signal_capture_mute(void *arg)
+static void signal_capture_mute(void *context, int muted, int mute_locked)
 {
+	struct cras_dbus_control *control = (struct cras_dbus_control *)context;
 	dbus_uint32_t serial = 0;
 	DBusMessage *msg;
-	dbus_bool_t muted;
 
 	msg = create_dbus_message("InputMuteChanged");
 	if (!msg)
 		return;
 
-	muted = cras_system_get_capture_mute();
 	dbus_message_append_args(msg,
 				 DBUS_TYPE_BOOLEAN, &muted,
 				 DBUS_TYPE_INVALID);
-	dbus_connection_send(dbus_control.conn, msg, &serial);
+	dbus_connection_send(control->conn, msg, &serial);
 	dbus_message_unref(msg);
 }
 
-static void signal_nodes_changed(void *arg)
+static void signal_nodes_changed(void *context)
 {
+	struct cras_dbus_control *control = (struct cras_dbus_control *)context;
 	dbus_uint32_t serial = 0;
 	DBusMessage *msg;
 
@@ -855,112 +951,96 @@
 	if (!msg)
 		return;
 
-	dbus_connection_send(dbus_control.conn, msg, &serial);
+	dbus_connection_send(control->conn, msg, &serial);
 	dbus_message_unref(msg);
 }
 
-static void signal_with_node_id(const char *name, cras_node_id_t id)
+static void signal_active_node_changed(void *context,
+				       enum CRAS_STREAM_DIRECTION dir,
+				       cras_node_id_t node_id)
 {
+	struct cras_dbus_control *control = (struct cras_dbus_control *)context;
 	DBusMessage *msg;
 	dbus_uint32_t serial = 0;
 
-	msg = create_dbus_message(name);
+	msg = create_dbus_message((dir == CRAS_STREAM_OUTPUT)
+			? "ActiveOutputNodeChanged"
+			: "ActiveInputNodeChanged");
 	if (!msg)
 		return;
 	dbus_message_append_args(msg,
-				 DBUS_TYPE_UINT64, &id,
+				 DBUS_TYPE_UINT64, &node_id,
 				 DBUS_TYPE_INVALID);
-	dbus_connection_send(dbus_control.conn, msg, &serial);
+	dbus_connection_send(control->conn, msg, &serial);
 	dbus_message_unref(msg);
 }
 
-static void signal_active_node_changed(void *arg)
-{
-	cras_node_id_t output, input;
-
-	output = cras_iodev_list_get_active_node_id(CRAS_STREAM_OUTPUT);
-	input = cras_iodev_list_get_active_node_id(CRAS_STREAM_INPUT);
-
-	if (last_output != output) {
-		last_output = output;
-		signal_with_node_id("ActiveOutputNodeChanged", output);
-	}
-
-	if (last_input != input) {
-		last_input = input;
-		signal_with_node_id("ActiveInputNodeChanged", input);
-	}
-}
-
 /* Called by iodev_list when a node volume changes. */
-static void signal_node_volume_changed(cras_node_id_t id,
-				       int volume)
+static void signal_node_volume_changed(void *context,
+				       cras_node_id_t node_id,
+				       int32_t volume)
 {
+	struct cras_dbus_control *control = (struct cras_dbus_control *)context;
 	dbus_uint32_t serial = 0;
 	DBusMessage *msg;
-	dbus_uint64_t node_id;
-	dbus_int32_t node_volume;
 
 	msg = create_dbus_message("OutputNodeVolumeChanged");
 	if (!msg)
 		return;
 
-	node_id = id;
-	node_volume = volume;
 	dbus_message_append_args(msg,
 				 DBUS_TYPE_UINT64, &node_id,
-				 DBUS_TYPE_INT32, &node_volume,
+				 DBUS_TYPE_INT32, &volume,
 				 DBUS_TYPE_INVALID);
-	dbus_connection_send(dbus_control.conn, msg, &serial);
+	dbus_connection_send(control->conn, msg, &serial);
 	dbus_message_unref(msg);
 }
 
-static void signal_node_capture_gain_changed(cras_node_id_t id,
+static void signal_node_capture_gain_changed(void *context,
+					     cras_node_id_t node_id,
 					     int capture_gain)
 {
+	struct cras_dbus_control *control = (struct cras_dbus_control *)context;
 	dbus_uint32_t serial = 0;
 	DBusMessage *msg;
-	dbus_uint64_t node_id;
-	dbus_int32_t node_capture_gain;
 
 	msg = create_dbus_message("InputNodeGainChanged");
 	if (!msg)
 		return;
 
-	node_id = id;
-	node_capture_gain = capture_gain;
 	dbus_message_append_args(msg,
 				 DBUS_TYPE_UINT64, &node_id,
-				 DBUS_TYPE_INT32, &node_capture_gain,
+				 DBUS_TYPE_INT32, &capture_gain,
 				 DBUS_TYPE_INVALID);
-	dbus_connection_send(dbus_control.conn, msg, &serial);
+	dbus_connection_send(control->conn, msg, &serial);
 	dbus_message_unref(msg);
 }
 
-static void signal_node_left_right_swapped_changed(cras_node_id_t id,
-				       int swapped)
+static void signal_node_left_right_swapped_changed(void *context,
+						   cras_node_id_t node_id,
+						   int swapped)
 {
+	struct cras_dbus_control *control = (struct cras_dbus_control *)context;
 	dbus_uint32_t serial = 0;
 	DBusMessage *msg;
-	dbus_uint64_t node_id;
-	dbus_bool_t node_left_right_swapped;
 
 	msg = create_dbus_message("NodeLeftRightSwappedChanged");
 	if (!msg)
 		return;
 
-	node_id = id;
-	node_left_right_swapped = swapped;
 	dbus_message_append_args(msg,
 				 DBUS_TYPE_UINT64, &node_id,
-				 DBUS_TYPE_BOOLEAN, &node_left_right_swapped,
+				 DBUS_TYPE_BOOLEAN, &swapped,
 				 DBUS_TYPE_INVALID);
-	dbus_connection_send(dbus_control.conn, msg, &serial);
+	dbus_connection_send(control->conn, msg, &serial);
 	dbus_message_unref(msg);
 }
 
-static void signal_num_active_streams_changed(void *arg)
+static void signal_num_active_streams_changed(void *context,
+					      enum CRAS_STREAM_DIRECTION dir,
+					      uint32_t num_active_streams)
 {
+	struct cras_dbus_control *control = (struct cras_dbus_control *)context;
 	dbus_uint32_t serial = 0;
 	DBusMessage *msg;
 	dbus_int32_t num;
@@ -973,7 +1053,7 @@
 	dbus_message_append_args(msg,
 				 DBUS_TYPE_INT32, &num,
 				 DBUS_TYPE_INVALID);
-	dbus_connection_send(dbus_control.conn, msg, &serial);
+	dbus_connection_send(control->conn, msg, &serial);
 	dbus_message_unref(msg);
 }
 
@@ -986,6 +1066,7 @@
 	};
 
 	DBusError dbus_error;
+	struct cras_observer_ops observer_ops;
 
 	dbus_control.conn = conn;
 	dbus_connection_ref(dbus_control.conn);
@@ -1001,19 +1082,21 @@
 		return;
 	}
 
-	cras_system_register_volume_changed_cb(signal_volume, 0);
-	cras_system_register_mute_changed_cb(signal_mute, 0);
-	cras_system_register_capture_gain_changed_cb(signal_capture_gain, 0);
-	cras_system_register_capture_mute_changed_cb(signal_capture_mute, 0);
-	cras_system_register_active_streams_changed_cb(
-		signal_num_active_streams_changed, 0);
-	cras_iodev_list_register_nodes_changed_cb(signal_nodes_changed, 0);
-	cras_iodev_list_register_active_node_changed_cb(
-		signal_active_node_changed, 0);
-	cras_iodev_list_set_node_volume_callbacks(
-		signal_node_volume_changed, signal_node_capture_gain_changed);
-	cras_iodev_list_set_node_left_right_swapped_callbacks(
-		signal_node_left_right_swapped_changed);
+	memset(&observer_ops, 0, sizeof(observer_ops));
+	observer_ops.output_volume_changed = signal_output_volume;
+	observer_ops.output_mute_changed = signal_output_mute;
+	observer_ops.capture_gain_changed = signal_capture_gain;
+	observer_ops.capture_mute_changed = signal_capture_mute;
+	observer_ops.num_active_streams_changed =
+			signal_num_active_streams_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;
+	observer_ops.output_node_volume_changed = signal_node_volume_changed;
+	observer_ops.node_left_right_swapped_changed =
+			signal_node_left_right_swapped_changed;
+
+	dbus_control.observer = cras_observer_add(&observer_ops, &dbus_control);
 }
 
 void cras_dbus_control_stop()
@@ -1021,19 +1104,11 @@
 	if (!dbus_control.conn)
 		return;
 
-	cras_system_remove_volume_changed_cb(signal_volume, 0);
-	cras_system_remove_mute_changed_cb(signal_mute, 0);
-	cras_system_remove_capture_gain_changed_cb(signal_capture_gain, 0);
-	cras_system_remove_capture_mute_changed_cb(signal_capture_mute, 0);
-	cras_system_remove_active_streams_changed_cb(
-		signal_num_active_streams_changed, 0);
-	cras_iodev_list_remove_nodes_changed_cb(signal_nodes_changed, 0);
-	cras_iodev_list_remove_active_node_changed_cb(
-		signal_active_node_changed, 0);
-
 	dbus_connection_unregister_object_path(dbus_control.conn,
 					       CRAS_ROOT_OBJECT_PATH);
 
 	dbus_connection_unref(dbus_control.conn);
 	dbus_control.conn = NULL;
+	cras_observer_remove(dbus_control.observer);
+	dbus_control.observer = NULL;
 }
diff --git a/cras/src/server/cras_dbus_util.c b/cras/src/server/cras_dbus_util.c
index 696d0af..b204279 100644
--- a/cras/src/server/cras_dbus_util.c
+++ b/cras/src/server/cras_dbus_util.c
@@ -28,8 +28,3 @@
 
        return TRUE;
 }
-
-int is_utf8_string(const char* string)
-{
-	return !!dbus_validate_utf8(string, NULL);
-}
diff --git a/cras/src/server/cras_dbus_util.h b/cras/src/server/cras_dbus_util.h
index 7e6368d..6e9215a 100644
--- a/cras/src/server/cras_dbus_util.h
+++ b/cras/src/server/cras_dbus_util.h
@@ -18,11 +18,3 @@
 dbus_bool_t append_key_value(DBusMessageIter *iter, const char *key,
 			     int type, const char *type_string,
 			     void *value);
-
-/* Checks if a string is a valid utf-8 string.
- * Args:
- *    string - a string.
- * Returns:
- *    1 if it is a valid utf-8 string. 0 otherwise.
-*/
-int is_utf8_string(const char* string);
diff --git a/cras/src/server/cras_device_monitor.c b/cras/src/server/cras_device_monitor.c
new file mode 100644
index 0000000..be9d086
--- /dev/null
+++ b/cras/src/server/cras_device_monitor.c
@@ -0,0 +1,101 @@
+/* Copyright 2016 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_device_monitor.h"
+#include "cras_iodev_list.h"
+#include "cras_main_message.h"
+
+enum CRAS_DEVICE_MONITOR_MSG_TYPE {
+	RESET_DEVICE,
+	SET_MUTE_STATE,
+};
+
+struct cras_device_monitor_message {
+	struct cras_main_message header;
+	enum CRAS_DEVICE_MONITOR_MSG_TYPE message_type;
+	struct cras_iodev *iodev;
+};
+
+static void init_device_msg(
+		struct cras_device_monitor_message *msg,
+		enum CRAS_DEVICE_MONITOR_MSG_TYPE type,
+		struct cras_iodev *iodev)
+{
+	memset(msg, 0, sizeof(*msg));
+	msg->header.type = CRAS_MAIN_MONITOR_DEVICE;
+	msg->header.length = sizeof(*msg);
+	msg->message_type = type;
+	msg->iodev = iodev;
+}
+
+int cras_device_monitor_reset_device(struct cras_iodev *iodev)
+{
+	struct cras_device_monitor_message msg;
+	int err;
+
+	init_device_msg(&msg, RESET_DEVICE, iodev);
+	err = cras_main_message_send((struct cras_main_message *)&msg);
+	if (err < 0) {
+		syslog(LOG_ERR, "Failed to send device message %d",
+		       RESET_DEVICE);
+		return err;
+	}
+	return 0;
+}
+
+int cras_device_monitor_set_device_mute_state(struct cras_iodev *iodev)
+{
+	struct cras_device_monitor_message msg;
+	int err;
+
+	init_device_msg(&msg, SET_MUTE_STATE, iodev);
+	err = cras_main_message_send((struct cras_main_message *)&msg);
+	if (err < 0) {
+		syslog(LOG_ERR, "Failed to send device message %d",
+		       SET_MUTE_STATE);
+		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.
+ * Let main thread follow the disable/enable sequence in iodev_list
+ * to properly close/open the device while enabling/disabling fallback
+ * device.
+ */
+static void handle_device_message(struct cras_main_message *msg, void *arg)
+{
+	struct cras_device_monitor_message *device_msg =
+			(struct cras_device_monitor_message *)msg;
+	struct cras_iodev *iodev = device_msg->iodev;
+
+	switch (device_msg->message_type) {
+	case RESET_DEVICE:
+		syslog(LOG_ERR, "trying to recover device 0x%x by resetting it",
+		       iodev->info.idx);
+		cras_iodev_list_disable_dev(iodev);
+		cras_iodev_list_enable_dev(iodev);
+		break;
+	case SET_MUTE_STATE:
+		cras_iodev_set_mute(iodev);
+		break;
+	default:
+		syslog(LOG_ERR, "Unknown device message type %u",
+		       device_msg->message_type);
+		break;
+	}
+}
+
+int cras_device_monitor_init()
+{
+	cras_main_message_add_handler(CRAS_MAIN_MONITOR_DEVICE,
+				      handle_device_message, NULL);
+	return 0;
+}
diff --git a/cras/src/server/cras_device_monitor.h b/cras/src/server/cras_device_monitor.h
new file mode 100644
index 0000000..21aa9f6
--- /dev/null
+++ b/cras/src/server/cras_device_monitor.h
@@ -0,0 +1,20 @@
+/* Copyright 2016 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_DEVICE_MONITOR_H_
+#define CRAS_DEVICE_MONITOR_H_
+
+#include "cras_iodev.h"
+
+/* Asks main thread to reset a device */
+int cras_device_monitor_reset_device(struct cras_iodev *iodev);
+
+/* Asks main thread to set mute/unmute state on a device. */
+int cras_device_monitor_set_device_mute_state(struct cras_iodev *iodev);
+
+/* Initializes device monitor and sets main thread callback. */
+int cras_device_monitor_init();
+
+#endif /* CRAS_DEVICE_MONITOR_H_ */
diff --git a/cras/src/server/cras_dsp.c b/cras/src/server/cras_dsp.c
index 99d00e3..8052a04 100644
--- a/cras/src/server/cras_dsp.c
+++ b/cras/src/server/cras_dsp.c
@@ -45,6 +45,7 @@
 	cras_expr_env_set_variable_boolean(env, "disable_eq", 0);
 	cras_expr_env_set_variable_boolean(env, "disable_drc", 0);
 	cras_expr_env_set_variable_string(env, "dsp_name", "");
+	cras_expr_env_set_variable_boolean(env, "swap_lr_disabled", 1);
 }
 
 static struct pipeline *prepare_pipeline(struct cras_dsp_context *ctx)
@@ -58,9 +59,9 @@
 	pipeline = cras_dsp_pipeline_create(ini, &ctx->env, purpose);
 
 	if (pipeline) {
-		syslog(LOG_ERR, "pipeline created");
+		syslog(LOG_DEBUG, "pipeline created");
 	} else {
-		syslog(LOG_ERR, "cannot create pipeline");
+		syslog(LOG_DEBUG, "cannot create pipeline");
 		goto bail;
 	}
 
@@ -170,12 +171,19 @@
 	free(ctx);
 }
 
-void cras_dsp_set_variable(struct cras_dsp_context *ctx, const char *key,
+void cras_dsp_set_variable_string(struct cras_dsp_context *ctx, const char *key,
 			   const char *value)
 {
 	cras_expr_env_set_variable_string(&ctx->env, key, value);
 }
 
+void cras_dsp_set_variable_boolean(struct cras_dsp_context *ctx,
+				   const char *key,
+				   char value)
+{
+	cras_expr_env_set_variable_boolean(&ctx->env, key, value);
+}
+
 void cras_dsp_load_pipeline(struct cras_dsp_context *ctx)
 {
 	cmd_load_pipeline(ctx);
diff --git a/cras/src/server/cras_dsp.h b/cras/src/server/cras_dsp.h
index fa2fd95..18c45f7 100644
--- a/cras/src/server/cras_dsp.h
+++ b/cras/src/server/cras_dsp.h
@@ -40,9 +40,13 @@
 /* Frees a dsp context. */
 void cras_dsp_context_free(struct cras_dsp_context *ctx);
 
-/* Sets a configuration variable in the context. */
-void cras_dsp_set_variable(struct cras_dsp_context *ctx, const char *key,
-			   const char *value);
+/* Sets a configuration string variable in the context. */
+void cras_dsp_set_variable_string(struct cras_dsp_context *ctx, const char *key,
+				  const char *value);
+
+/* Sets a configuration boolean variable in the context. */
+void cras_dsp_set_variable_boolean(struct cras_dsp_context *ctx, const char *key,
+				   char value);
 
 /* Loads the pipeline to the context. This should be called again when
  * new values of configuration variables may change the plugin
@@ -72,19 +76,6 @@
 /* Number of channels input. */
 unsigned int cras_dsp_num_input_channels(const struct cras_dsp_context *ctx);
 
-/* Wait for the previous asynchronous requests to finish. The
- * asynchronous requests include:
- *
- * cras_dsp_context_free()
- * cras_dsp_set_variable()
- * cras_dsp_load_pipeline()
- * cras_dsp_reload_ini()
- * cras_dsp_dump_info()
- *
- * This is mainly used for testing.
- */
-void cras_dsp_sync();
-
 #ifdef __cplusplus
 } /* extern "C" */
 #endif
diff --git a/cras/src/server/cras_dsp_ini.c b/cras/src/server/cras_dsp_ini.c
index b34a198..a4014c8 100644
--- a/cras/src/server/cras_dsp_ini.c
+++ b/cras/src/server/cras_dsp_ini.c
@@ -3,6 +3,7 @@
  * found in the LICENSE file.
  */
 
+#include <errno.h>
 #include <stdlib.h>
 #include <syslog.h>
 #include "cras_dsp_ini.h"
@@ -192,6 +193,124 @@
 	}
 }
 
+/* Adds a port to a plugin with specified flow id and direction. */
+static void add_audio_port(struct ini *ini,
+			   struct plugin *plugin,
+			   int flow_id,
+			   enum port_direction port_direction)
+{
+	struct port *p;
+	p = ARRAY_APPEND_ZERO(&plugin->ports);
+	p->type = PORT_AUDIO;
+	p->flow_id = flow_id;
+	p->init_value = 0;
+	p->direction = port_direction;
+}
+
+/* Fills fields for a swap_lr plugin.*/
+static void fill_swap_lr_plugin(struct ini *ini,
+				struct plugin *plugin,
+				int input_flowid_0,
+				int input_flowid_1,
+				int output_flowid_0,
+				int output_flowid_1)
+{
+	plugin->title = "swap_lr";
+	plugin->library = "builtin";
+	plugin->label = "swap_lr";
+	plugin->purpose = "playback";
+	plugin->disable_expr = cras_expr_expression_parse("swap_lr_disabled");
+
+	add_audio_port(ini, plugin, input_flowid_0, PORT_INPUT);
+	add_audio_port(ini, plugin, input_flowid_1, PORT_INPUT);
+	add_audio_port(ini, plugin, output_flowid_0, PORT_OUTPUT);
+	add_audio_port(ini, plugin, output_flowid_1, PORT_OUTPUT);
+}
+
+/* Adds a new flow with name. If there is already a flow with the name, returns
+ * INVALID_FLOW_ID.
+ */
+static int add_new_flow(struct ini *ini, const char *name)
+{
+	struct flow *flow;
+	int i = lookup_flow(ini, name);
+	if (i != -1)
+		return INVALID_FLOW_ID;
+	i = ARRAY_COUNT(&ini->flows);
+	flow = ARRAY_APPEND_ZERO(&ini->flows);
+	flow->name = name;
+	return i;
+}
+
+/* Finds the first playback sink plugin in ini. */
+struct plugin *find_first_playback_sink_plugin(struct ini *ini)
+{
+	int i;
+	struct plugin *plugin;
+
+	FOR_ARRAY_ELEMENT(&ini->plugins, i, plugin) {
+		if (strcmp(plugin->library, "builtin") != 0)
+			continue;
+		if (strcmp(plugin->label, "sink") != 0)
+			continue;
+		if (!plugin->purpose ||
+		    strcmp(plugin->purpose, "playback") != 0)
+			continue;
+		return plugin;
+	}
+
+	return NULL;
+}
+
+/* Inserts a swap_lr plugin before sink. Handles the port change such that
+ * the port originally connects to sink will connect to swap_lr.
+ */
+static int insert_swap_lr_plugin(struct ini *ini)
+{
+	struct plugin *swap_lr, *sink;
+	int sink_input_flowid_0, sink_input_flowid_1;
+	int swap_lr_output_flowid_0, swap_lr_output_flowid_1;
+
+	/* Only add swap_lr plugin for two-channel playback dsp.
+	 * TODO(cychiang): Handle multiple sinks if needed.
+	 */
+	sink = find_first_playback_sink_plugin(ini);
+	if ((sink == NULL) || ARRAY_COUNT(&sink->ports) != 2)
+		return 0;
+
+	/* Gets the original flow ids of the sink input ports. */
+	sink_input_flowid_0 = ARRAY_ELEMENT(&sink->ports, 0)->flow_id;
+	sink_input_flowid_1 = ARRAY_ELEMENT(&sink->ports, 1)->flow_id;
+
+	/* Create new flow ids for swap_lr output ports. */
+	swap_lr_output_flowid_0 = add_new_flow(ini, "{swap_lr_out:0}");
+	swap_lr_output_flowid_1 = add_new_flow(ini, "{swap_lr_out:1}");
+
+	if (swap_lr_output_flowid_0 == INVALID_FLOW_ID ||
+	    swap_lr_output_flowid_1 == INVALID_FLOW_ID) {
+		syslog(LOG_ERR, "Can not create flow id for swap_lr_out");
+		return -EINVAL;
+	}
+
+	/* Creates a swap_lr plugin and sets the input and output ports. */
+	swap_lr = ARRAY_APPEND_ZERO(&ini->plugins);
+	fill_swap_lr_plugin(ini,
+			    swap_lr,
+			    sink_input_flowid_0,
+			    sink_input_flowid_1,
+			    swap_lr_output_flowid_0,
+			    swap_lr_output_flowid_1);
+
+	/* Look up first sink again because ini->plugins could be realloc'ed */
+	sink = find_first_playback_sink_plugin(ini);
+
+	/* The flow ids of sink input ports should be changed to flow ids of
+	 * {swap_lr_out:0}, {swap_lr_out:1}. */
+	ARRAY_ELEMENT(&sink->ports, 0)->flow_id = swap_lr_output_flowid_0;
+	ARRAY_ELEMENT(&sink->ports, 1)->flow_id = swap_lr_output_flowid_1;
+
+	return 0;
+}
 
 struct ini *cras_dsp_ini_create(const char *ini_filename)
 {
@@ -200,6 +319,7 @@
 	int nsec, i;
 	const char *sec_name;
 	struct plugin *plugin;
+	int rc;
 
 	ini = calloc(1, sizeof(struct ini));
 	if (!ini) {
@@ -223,6 +343,13 @@
 			goto bail;
 	}
 
+	/* Insert a swap_lr plugin before sink. */
+	rc = insert_swap_lr_plugin(ini);
+	if (rc < 0) {
+		syslog(LOG_ERR, "failed to insert swap_lr plugin");
+		goto bail;
+	}
+
 	/* Fill flow info now because now the plugin array won't change */
 	fill_flow_info(ini);
 
diff --git a/cras/src/server/cras_dsp_mod_builtin.c b/cras/src/server/cras_dsp_mod_builtin.c
index de93cb0..c694879 100644
--- a/cras/src/server/cras_dsp_mod_builtin.c
+++ b/cras/src/server/cras_dsp_mod_builtin.c
@@ -56,6 +56,56 @@
 }
 
 /*
+ *  swap_lr module functions
+ */
+static int swap_lr_instantiate(struct dsp_module *module,
+			       unsigned long sample_rate)
+{
+	module->data = calloc(4, sizeof(float*));
+	return 0;
+}
+
+static void swap_lr_connect_port(struct dsp_module *module,
+				 unsigned long port, float *data_location)
+{
+	float **ports;
+	ports = (float **)module->data;
+	ports[port] = data_location;
+}
+
+static void swap_lr_run(struct dsp_module *module,
+			unsigned long sample_count)
+{
+	size_t i;
+	float **ports = (float **)module->data;
+
+	/* This module runs dsp in-place, so ports[0] == ports[2],
+	 * ports[1] == ports[3]. Here we swap data on two channels.
+	 */
+	for (i = 0; i < sample_count; i++) {
+		float temp = ports[0][i];
+		ports[2][i] = ports[1][i];
+		ports[3][i] = temp;
+	}
+}
+
+static void swap_lr_deinstantiate(struct dsp_module *module)
+{
+	free(module->data);
+}
+
+static void swap_lr_init_module(struct dsp_module *module)
+{
+	module->instantiate = &swap_lr_instantiate;
+	module->connect_port = &swap_lr_connect_port;
+	module->get_delay = &empty_get_delay;
+	module->run = &swap_lr_run;
+	module->deinstantiate = &swap_lr_deinstantiate;
+	module->free_module = &empty_free_module;
+	module->get_properties = &empty_get_properties;
+}
+
+/*
  *  invert_lr module functions
  */
 static int invert_lr_instantiate(struct dsp_module *module,
@@ -424,6 +474,8 @@
 		eq2_init_module(module);
 	} else if (strcmp(plugin->label, "drc") == 0) {
 		drc_init_module(module);
+	} else if (strcmp(plugin->label, "swap_lr") == 0) {
+		swap_lr_init_module(module);
 	} else {
 		empty_init_module(module);
 	}
diff --git a/cras/src/server/cras_dsp_pipeline.c b/cras/src/server/cras_dsp_pipeline.c
index f05c936..332ed1c 100644
--- a/cras/src/server/cras_dsp_pipeline.c
+++ b/cras/src/server/cras_dsp_pipeline.c
@@ -412,7 +412,7 @@
 		ini, "sink", purpose, env);
 
 	if (!source || !sink) {
-		syslog(LOG_INFO,
+		syslog(LOG_DEBUG,
 		       "no enabled source or sink found %p/%p for %s",
 		       source, sink, purpose);
 		return NULL;
diff --git a/cras/src/server/cras_empty_iodev.c b/cras/src/server/cras_empty_iodev.c
index ef59209..4616cd3 100644
--- a/cras/src/server/cras_empty_iodev.c
+++ b/cras/src/server/cras_empty_iodev.c
@@ -70,20 +70,10 @@
  * iodev callbacks.
  */
 
-static int is_open(const struct cras_iodev *iodev)
+static int frames_queued(const struct cras_iodev *iodev,
+			 struct timespec *tstamp)
 {
-	struct empty_iodev *empty_iodev = (struct empty_iodev *)iodev;
-
-	return empty_iodev->open;
-}
-
-static int dev_running(const struct cras_iodev *iodev)
-{
-	return 1;
-}
-
-static int frames_queued(const struct cras_iodev *iodev)
-{
+	clock_gettime(CLOCK_MONOTONIC_RAW, tstamp);
 	return current_level(iodev);
 }
 
@@ -125,11 +115,15 @@
 		      unsigned *frames)
 {
 	struct empty_iodev *empty_iodev = (struct empty_iodev *)iodev;
+	unsigned int avail, current;
 
-	if (iodev->direction == CRAS_STREAM_OUTPUT)
-		*frames = MIN(*frames, EMPTY_FRAMES - current_level(iodev));
-	else
-		*frames = MIN(*frames, current_level(iodev));
+	if (iodev->direction == CRAS_STREAM_OUTPUT) {
+		avail = EMPTY_FRAMES - current_level(iodev);
+		*frames = MIN(*frames, avail);
+	} else {
+		current = current_level(iodev);
+		*frames = MIN(*frames, current);
+	}
 
 	iodev->area->frames = *frames;
 	cras_audio_area_config_buf_pointers(iodev->area, iodev->format,
@@ -163,13 +157,18 @@
 static int flush_buffer(struct cras_iodev *iodev)
 {
 	struct empty_iodev *empty_iodev = (struct empty_iodev *)iodev;
+
 	empty_iodev->buffer_level = current_level(iodev);
-	if (iodev->direction == CRAS_STREAM_INPUT)
+	if (iodev->direction == CRAS_STREAM_INPUT) {
 		empty_iodev->buffer_level = 0;
+		clock_gettime(CLOCK_MONOTONIC_RAW,
+			      &empty_iodev->last_buffer_access);
+	}
 	return 0;
 }
 
-static void update_active_node(struct cras_iodev *iodev, unsigned node_idx)
+static void update_active_node(struct cras_iodev *iodev, unsigned node_idx,
+			       unsigned dev_enabled)
 {
 }
 
@@ -195,18 +194,17 @@
 	iodev->supported_rates = empty_supported_rates;
 	iodev->supported_channel_counts = empty_supported_channel_counts;
 	iodev->supported_formats = empty_supported_formats;
-	iodev->buffer_size = EMPTY_BUFFER_SIZE;
+	iodev->buffer_size = EMPTY_FRAMES;
 
 	iodev->open_dev = open_dev;
 	iodev->close_dev = close_dev;
-	iodev->is_open = is_open;
 	iodev->frames_queued = frames_queued;
 	iodev->delay_frames = delay_frames;
 	iodev->get_buffer = get_buffer;
 	iodev->put_buffer = put_buffer;
 	iodev->flush_buffer = flush_buffer;
-	iodev->dev_running = dev_running;
 	iodev->update_active_node = update_active_node;
+	iodev->no_stream = cras_iodev_default_no_stream_playback;
 
 	/* Create a dummy ionode */
 	node = (struct cras_ionode *)calloc(1, sizeof(*node));
diff --git a/cras/src/server/cras_fmt_conv.c b/cras/src/server/cras_fmt_conv.c
index d1be315..729b347 100644
--- a/cras/src/server/cras_fmt_conv.c
+++ b/cras/src/server/cras_fmt_conv.c
@@ -610,6 +610,58 @@
 	free(conv);
 }
 
+struct cras_fmt_conv *cras_channel_remix_conv_create(
+		unsigned int num_channels,
+		const float *coefficient)
+{
+	struct cras_fmt_conv *conv;
+	unsigned out_ch, in_ch;
+
+	conv = calloc(1, sizeof(*conv));
+	if (conv == NULL)
+		return NULL;
+	conv->in_fmt.num_channels = num_channels;
+	conv->out_fmt.num_channels = num_channels;
+
+	conv->ch_conv_mtx = cras_channel_conv_matrix_alloc(num_channels,
+							   num_channels);
+	/* Convert the coeffiencnt array to conversion matrix. */
+	for (out_ch = 0; out_ch < num_channels; out_ch++)
+		for (in_ch = 0; in_ch < num_channels; in_ch++)
+			conv->ch_conv_mtx[out_ch][in_ch] =
+				coefficient[in_ch + out_ch * num_channels];
+
+	conv->num_converters = 1;
+	conv->tmp_bufs[0] = malloc(4 * /* width in bytes largest format. */
+				   num_channels);
+	return conv;
+}
+
+void cras_channel_remix_convert(struct cras_fmt_conv *conv,
+				const struct cras_audio_format *fmt,
+				uint8_t *in_buf,
+				size_t nframes)
+{
+	unsigned ch, fr;
+	int16_t *tmp = (int16_t *)conv->tmp_bufs[0];
+	int16_t *buf = (int16_t *)in_buf;
+
+	/* Do remix only when input buffer has the same number of channels. */
+	if (fmt->num_channels != conv->in_fmt.num_channels)
+		return;
+
+	for (fr = 0; fr < nframes; fr++) {
+		for (ch = 0; ch < conv->in_fmt.num_channels; ch++)
+			tmp[ch] = multiply_buf_with_coef(
+				conv->ch_conv_mtx[ch],
+				buf,
+				conv->in_fmt.num_channels);
+		for (ch = 0; ch < conv->in_fmt.num_channels; ch++)
+			buf[ch] = tmp[ch];
+		buf += conv->in_fmt.num_channels;
+	}
+}
+
 const struct cras_audio_format *cras_fmt_conv_in_format(
 		const struct cras_fmt_conv *conv)
 {
diff --git a/cras/src/server/cras_fmt_conv.h b/cras/src/server/cras_fmt_conv.h
index 518a710..732a517 100644
--- a/cras/src/server/cras_fmt_conv.h
+++ b/cras/src/server/cras_fmt_conv.h
@@ -25,6 +25,29 @@
 					   size_t pre_linear_resample);
 void cras_fmt_conv_destroy(struct cras_fmt_conv *conv);
 
+/* Creates the format converter for channel remixing. The conversion takes
+ * a N by N float matrix, to multiply each N-channels sample.
+ * Args:
+ *    num_channels - Number of channels of PCM data.
+ *    coefficient - Float array of length N * N representing the conversion
+ *        matrix, where matrix[i][j] corresponds to coefficient[i * N + j]
+ */
+struct cras_fmt_conv *cras_channel_remix_conv_create(
+		unsigned int num_channels,
+		const float *coefficient);
+
+/* Converts nframes of sample from in_buf, using given remix converter.
+ * Args:
+ *    conv - The format converter.
+ *    fmt - The format of the buffer to convert.
+ *    in_buf - The buffer to convert.
+ *    nframes - The number of frames to convert.
+ */
+void cras_channel_remix_convert(struct cras_fmt_conv *conv,
+				const struct cras_audio_format *fmt,
+				uint8_t *in_buf,
+				size_t nframes);
+
 /* Get the input format of the converter. */
 const struct cras_audio_format *cras_fmt_conv_in_format(
 		const struct cras_fmt_conv *conv);
diff --git a/cras/src/server/cras_gpio_jack.c b/cras/src/server/cras_gpio_jack.c
index e8c5f51..29784c4 100644
--- a/cras/src/server/cras_gpio_jack.c
+++ b/cras/src/server/cras_gpio_jack.c
@@ -7,11 +7,11 @@
 #include <linux/input.h>
 #include <stdio.h>
 #include <string.h>
+#include <syslog.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
 #include <libudev.h>
-#include <regex.h>
 
 #include "cras_util.h"
 #include "cras_gpio_jack.h"
@@ -41,31 +41,6 @@
 	return ioctl(fd, EVIOCGSW(n_bytes), bits);
 }
 
-static void compile_regex(regex_t *regex, const char *str)
-{
-	int r;
-	r = regcomp(regex, str, REG_EXTENDED);
-	assert(r == 0);
-}
-
-static int jack_matches_string(const char *jack, const char *re)
-{
-	regmatch_t m[1];
-	regex_t regex;
-	unsigned success;
-
-	compile_regex(&regex, re);
-	success = regexec(&regex, jack, ARRAY_SIZE(m), m, 0) == 0;
-	regfree(&regex);
-	return success;
-}
-
-/* sys_input_get_device_name:
- *
- *   Returns the heap-allocated device name of a /dev/input/event*
- *   pathname.  Caller is responsible for releasing.
- *
- */
 char *sys_input_get_device_name(const char *path)
 {
 	char name[256];
@@ -75,27 +50,22 @@
 		gpio_switch_eviocgname(fd, name, sizeof(name));
 		close(fd);
 		return strdup(name);
-	} else
+	} else {
+		syslog(LOG_WARNING, "Could not open '%s': %s",
+		       path, strerror(errno));
 		return NULL;
+	}
 }
 
-/* gpio_get_switch_names:
- *
- *    Fills 'names' with up to 'n_names' entries of
- *    '/dev/input/event*' pathnames which are associated with a GPIO
- *    jack of the specified 'direction'.
- *
- *  Returns the number of filenames found.
- *
- */
-unsigned gpio_get_switch_names(enum CRAS_STREAM_DIRECTION direction,
-			       char **names, size_t n_names)
+void gpio_switch_list_for_each(gpio_switch_list_callback callback, void *arg)
 {
 	struct udev *udev;
 	struct udev_enumerate *enumerate;
 	struct udev_list_entry *dl;
 	struct udev_list_entry *dev_list_entry;
-	unsigned n = 0;
+
+	if (!callback)
+		return;
 
 	udev = udev_new();
 	assert(udev != NULL);
@@ -110,7 +80,6 @@
 								       path);
 		const char *devnode = udev_device_get_devnode(dev);
 		char *ioctl_name;
-		int save;
 
 		if (devnode == NULL)
 			continue;
@@ -119,22 +88,12 @@
 		if (ioctl_name == NULL)
 			continue;
 
-		save = ((direction == CRAS_STREAM_INPUT &&
-			 jack_matches_string(ioctl_name, "^.*Mic Jack$"))) ||
-			(direction == CRAS_STREAM_OUTPUT &&
-			 jack_matches_string(ioctl_name,
-					     "^.*Headphone Jack$")) ||
-			(jack_matches_string(ioctl_name,
-					     "^.*Headset Jack$")) ||
-			(direction == CRAS_STREAM_OUTPUT &&
-			 jack_matches_string(ioctl_name, "^.*HDMI Jack$"));
-
-		if (save && n < n_names)
-			names[n++] = strdup(devnode);
-
+		if (callback(devnode, ioctl_name, arg)) {
+			free(ioctl_name);
+			break;
+		}
 		free(ioctl_name);
 	}
 	udev_enumerate_unref(enumerate);
 	udev_unref(udev);
-	return n;
 }
diff --git a/cras/src/server/cras_gpio_jack.h b/cras/src/server/cras_gpio_jack.h
index 0ac3ddb..1c1a2a8 100644
--- a/cras/src/server/cras_gpio_jack.h
+++ b/cras/src/server/cras_gpio_jack.h
@@ -8,13 +8,41 @@
 
 #include "cras_types.h"
 
+struct mixer_name;
+
 int gpio_switch_open(const char *pathname);
 int gpio_switch_read(int fd, void *buf, size_t n_bytes);
 
 int gpio_switch_eviocgbit(int fd, void *buf, size_t n_bytes);
 int gpio_switch_eviocgsw(int fd, void *bits, size_t n_bytes);
 
-unsigned gpio_get_switch_names(enum CRAS_STREAM_DIRECTION direction,
-                               char **names, size_t n_names);
+/* sys_input_get_device_name:
+ *
+ *   Returns the heap-allocated device name of a /dev/input/event*
+ *   pathname.  Caller is responsible for releasing.
+ */
 char *sys_input_get_device_name(const char *path);
+
+/* List for each callback function.
+ *
+ * Args:
+ *    dev_path - Full path to the GPIO device.
+ *    dev_name - The name of the GPIO device.
+ *    arg - The argument passed to gpio_switch_list_for_each.
+ *
+ * Returns:
+ *    0 to continue searching, non-zero otherwise.
+ */
+typedef int (*gpio_switch_list_callback)(const char *dev_path,
+				         const char *dev_name,
+				         void *arg);
+
+/* Execute the given callback on each GPIO device.
+ *
+ * Args:
+ *    callback - The callback to execute.
+ *    arg - An argument to pass to the callback.
+ */
+void gpio_switch_list_for_each(gpio_switch_list_callback callback, void *arg);
+
 #endif
diff --git a/cras/src/server/cras_hfp_ag_profile.c b/cras/src/server/cras_hfp_ag_profile.c
index 9ad4841..29c3e0d 100644
--- a/cras/src/server/cras_hfp_ag_profile.c
+++ b/cras/src/server/cras_hfp_ag_profile.c
@@ -17,7 +17,6 @@
 #include "cras_hfp_iodev.h"
 #include "cras_hfp_slc.h"
 #include "cras_system_state.h"
-#include "cras_tm.h"
 #include "utlist.h"
 
 #define STR(s) #s
@@ -71,8 +70,6 @@
 	"  </attribute>"						\
 	"</record>"
 
-static const unsigned int A2DP_RETRY_DELAY_MS = 500;
-static const unsigned int A2DP_MAX_RETRIES = 10;
 
 /* Object representing the audio gateway role for HFP/HSP.
  * Members:
@@ -116,8 +113,6 @@
 	if (ag->slc_handle)
 		hfp_slc_destroy(ag->slc_handle);
 
-	cras_bt_device_cancel_a2dp_delay_timer(ag->device);
-
 	/* If the bt device is not using a2dp, do a deeper clean up
 	 * to force disconnect it. */
 	if (!cras_bt_device_has_a2dp(ag->device))
@@ -137,82 +132,22 @@
 	return 0;
 }
 
-/* Creates the iodevs to start the audio gateway. */
-static int start_audio_gateway(struct audio_gateway *ag)
-{
-	ag->info = hfp_info_create();
-	ag->idev = hfp_iodev_create(CRAS_STREAM_INPUT, ag->device,
-				    ag->slc_handle,
-				    ag->profile, ag->info);
-	ag->odev = hfp_iodev_create(CRAS_STREAM_OUTPUT, ag->device,
-				    ag->slc_handle,
-				    ag->profile, ag->info);
-
-	if (!ag->idev && !ag->odev) {
-		destroy_audio_gateway(ag);
-		return -ENOMEM;
-	}
-
-	return 0;
-}
-
 static void cras_hfp_ag_release(struct cras_bt_profile *profile)
 {
 	cras_hfp_ag_suspend();
 }
 
-/* Checks if a2dp connection is present. If not then delay the
- * start of audio gateway until the max number of retry is reached.
- */
-static void a2dp_delay_cb(struct cras_timer *timer, void *arg)
-{
-	struct audio_gateway *ag = (struct audio_gateway *)arg;
-	struct cras_tm *tm = cras_system_state_get_tm();
-
-	cras_bt_device_rm_a2dp_delay_timer(ag->device);
-
-	if (cras_bt_device_has_a2dp(ag->device))
-		goto start_ag;
-
-	if (--ag->a2dp_delay_retries == 0)
-		goto start_ag;
-
-	cras_bt_device_add_a2dp_delay_timer(
-			ag->device,
-			cras_tm_create_timer(tm, A2DP_RETRY_DELAY_MS,
-					     a2dp_delay_cb, ag));
-	return;
-
-start_ag:
-	if (start_audio_gateway(ag))
-		syslog(LOG_ERR, "Start audio gateway failed");
-}
-
+/* Callback triggered when SLC is initialized.  */
 static int cras_hfp_ag_slc_initialized(struct hfp_slc_handle *handle)
 {
 	struct audio_gateway *ag;
-	struct cras_tm *tm = cras_system_state_get_tm();
-	int rc;
 
 	DL_SEARCH_SCALAR(connected_ags, ag, slc_handle, handle);
 	if (!ag)
 		return -EINVAL;
 
-	/* This is a HFP/HSP only headset. */
-	if (!cras_bt_device_supports_profile(
-			ag->device, CRAS_BT_DEVICE_PROFILE_A2DP_SINK)) {
-		rc = start_audio_gateway(ag);
-		if (rc)
-			syslog(LOG_ERR, "Start audio gateway failed");
-		return rc;
-	}
-
-	ag->a2dp_delay_retries = A2DP_MAX_RETRIES;
-	cras_bt_device_add_a2dp_delay_timer(
-			ag->device,
-			cras_tm_create_timer(tm, A2DP_RETRY_DELAY_MS,
-					     a2dp_delay_cb, ag));
-	return 0;
+	/* Defer the starting of audio gateway to bt_device. */
+	return cras_bt_device_audio_gateway_initialized(ag->device);
 }
 
 static int cras_hfp_ag_slc_disconnected(struct hfp_slc_handle *handle)
@@ -270,7 +205,7 @@
 	/* Kick out any previously connected a2dp iodev. */
 	a2dp_device = cras_a2dp_connected_device();
 	if (a2dp_device && a2dp_device != device) {
-		cras_a2dp_suspend_connected_device();
+		cras_a2dp_suspend_connected_device(a2dp_device);
 		cras_bt_device_disconnect(new_ag->conn, a2dp_device);
 	}
 }
@@ -299,6 +234,7 @@
 	ag->profile = cras_bt_device_profile_from_uuid(profile->uuid);
 	ag->slc_handle = hfp_slc_create(rfcomm_fd,
 					0,
+					device,
 					cras_hfp_ag_slc_initialized,
 					cras_hfp_ag_slc_disconnected);
 	DL_APPEND(connected_ags, ag);
@@ -360,7 +296,7 @@
 	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, NULL,
+	ag->slc_handle = hfp_slc_create(rfcomm_fd, 1, device, NULL,
 					cras_hfp_ag_slc_disconnected);
 	DL_APPEND(connected_ags, ag);
 	cras_hfp_ag_slc_initialized(ag->slc_handle);
@@ -380,6 +316,30 @@
 	.cancel = cras_hfp_ag_cancel
 };
 
+int cras_hfp_ag_start(struct cras_bt_device *device)
+{
+	struct audio_gateway *ag;
+
+	DL_SEARCH_SCALAR(connected_ags, ag, device, device);
+	if (ag == NULL)
+		return -EEXIST;
+
+	ag->info = hfp_info_create();
+	ag->idev = hfp_iodev_create(CRAS_STREAM_INPUT, ag->device,
+				    ag->slc_handle,
+				    ag->profile, ag->info);
+	ag->odev = hfp_iodev_create(CRAS_STREAM_OUTPUT, ag->device,
+				    ag->slc_handle,
+				    ag->profile, ag->info);
+
+	if (!ag->idev && !ag->odev) {
+		destroy_audio_gateway(ag);
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
 void cras_hfp_ag_suspend()
 {
 	struct audio_gateway *ag;
@@ -387,6 +347,15 @@
 		destroy_audio_gateway(ag);
 }
 
+void cras_hfp_ag_suspend_connected_device(struct cras_bt_device *device)
+{
+	struct audio_gateway *ag;
+
+	DL_SEARCH_SCALAR(connected_ags, ag, device, device);
+	if (ag)
+		destroy_audio_gateway(ag);
+}
+
 struct hfp_slc_handle *cras_hfp_ag_get_active_handle()
 {
 	/* Returns the first handle for HFP qualification. In future we
diff --git a/cras/src/server/cras_hfp_ag_profile.h b/cras/src/server/cras_hfp_ag_profile.h
index 01fec69..bc3e042 100644
--- a/cras/src/server/cras_hfp_ag_profile.h
+++ b/cras/src/server/cras_hfp_ag_profile.h
@@ -45,10 +45,16 @@
 /* Adds a profile instance for HSP AG (Headset Profile Audio Gateway). */
 int cras_hsp_ag_profile_create(DBusConnection *conn);
 
+/* Starts the HFP audio gateway for audio input/output. */
+int cras_hfp_ag_start(struct cras_bt_device *device);
+
 /* Suspends all connected audio gateways, used to stop HFP/HSP audio when
  * an A2DP only device is connected. */
 void cras_hfp_ag_suspend();
 
+/* Suspends audio gateway associated with given bt device. */
+void cras_hfp_ag_suspend_connected_device(struct cras_bt_device *device);
+
 /* Gets the active SLC handle. Used for HFP qualification. */
 struct hfp_slc_handle *cras_hfp_ag_get_active_handle();
 
diff --git a/cras/src/server/cras_hfp_info.c b/cras/src/server/cras_hfp_info.c
index 2bf1c1f..d85d285 100644
--- a/cras/src/server/cras_hfp_info.c
+++ b/cras/src/server/cras_hfp_info.c
@@ -11,6 +11,7 @@
 
 #include "audio_thread.h"
 #include "byte_buffer.h"
+#include "cras_iodev_list.h"
 #include "cras_hfp_info.h"
 #include "utlist.h"
 
@@ -353,7 +354,9 @@
 	if (!info->started)
 		return 0;
 
-	audio_thread_rm_callback(info->fd);
+	audio_thread_rm_callback_sync(
+		cras_iodev_list_get_audio_thread(),
+		info->fd);
 
 	close(info->fd);
 	info->fd = 0;
diff --git a/cras/src/server/cras_hfp_info.h b/cras/src/server/cras_hfp_info.h
index a6a48c7..6d980a9 100644
--- a/cras/src/server/cras_hfp_info.h
+++ b/cras/src/server/cras_hfp_info.h
@@ -47,10 +47,11 @@
  * Args:
  *    info - The hfp_info holding the buffer to query.
  *    dev - The iodev to indicate which buffer to query, playback
- *    or capture, depend on its direction.
+ *          or capture, depending on its direction.
  */
 int hfp_buf_queued(struct hfp_info *info, const struct cras_iodev *dev);
 
+
 /* Gets how many bytes of the buffer are used.
  * Args:
  *    info - The hfp_info holding buffer.
diff --git a/cras/src/server/cras_hfp_iodev.c b/cras/src/server/cras_hfp_iodev.c
index 717f82f..10314e6 100644
--- a/cras/src/server/cras_hfp_iodev.c
+++ b/cras/src/server/cras_hfp_iodev.c
@@ -22,7 +22,6 @@
 	struct cras_bt_device *device;
 	struct hfp_slc_handle *slc;
 	struct hfp_info *info;
-	int opened;
 };
 
 static int update_supported_formats(struct cras_iodev *iodev)
@@ -49,13 +48,17 @@
 	return 0;
 }
 
-static int frames_queued(const struct cras_iodev *iodev)
+static int frames_queued(const struct cras_iodev *iodev,
+			 struct timespec *tstamp)
 {
 	struct hfp_io *hfpio = (struct hfp_io *)iodev;
 
 	if (!hfp_info_running(hfpio->info))
 		return -1;
 
+	/* Do not enable timestamp mechanism on HFP device because last time
+	 * stamp might be a long time ago and it is not really useful. */
+	clock_gettime(CLOCK_MONOTONIC_RAW, tstamp);
 	return hfp_buf_queued(hfpio->info, iodev);
 }
 
@@ -65,7 +68,7 @@
 	struct hfp_io *hfpio = (struct hfp_io *)data;
 	struct cras_iodev *iodev = &hfpio->base;
 
-	if (!iodev->is_open(iodev))
+	if (!cras_iodev_is_open(iodev))
 		return;
 	iodev->buffer_size = hfp_buf_size(hfpio->info, iodev);
 	cras_bt_device_iodev_buffer_size_changed(hfpio->device);
@@ -101,7 +104,6 @@
 	hfp_set_call_status(hfpio->slc, 1);
 
 	iodev->buffer_size = hfp_buf_size(hfpio->info, iodev);
-	hfpio->opened = 1;
 
 	return 0;
 error:
@@ -113,7 +115,6 @@
 {
 	struct hfp_io *hfpio = (struct hfp_io *)iodev;
 
-	hfpio->opened = 0;
 	hfp_info_rm_iodev(hfpio->info, iodev);
 	if (hfp_info_running(hfpio->info) && !hfp_info_has_iodev(hfpio->info)) {
 		hfp_info_stop(hfpio->info);
@@ -125,20 +126,23 @@
 	return 0;
 }
 
-static int is_open(const struct cras_iodev *iodev)
+static void set_hfp_volume(struct cras_iodev *iodev)
 {
+	size_t volume;
 	struct hfp_io *hfpio = (struct hfp_io *)iodev;
-	return hfpio->opened;
-}
 
-static int dev_running(const struct cras_iodev *iodev)
-{
-	return iodev->is_open(iodev);
+	volume = cras_system_get_volume();
+	if (iodev->active_node)
+		volume = cras_iodev_adjust_node_volume(iodev->active_node, volume);
+
+	hfp_event_speaker_gain(hfpio->slc, volume);
 }
 
 static int delay_frames(const struct cras_iodev *iodev)
 {
-	return frames_queued(iodev);
+	struct timespec tstamp;
+
+	return frames_queued(iodev, &tstamp);
 }
 
 static int get_buffer(struct cras_iodev *iodev,
@@ -186,7 +190,8 @@
 	return 0;
 }
 
-static void update_active_node(struct cras_iodev *iodev, unsigned node_idx)
+static void update_active_node(struct cras_iodev *iodev, unsigned node_idx,
+			       unsigned dev_enabled)
 {
 }
 
@@ -235,11 +240,10 @@
 			cras_bt_device_object_path(device),
 			strlen(cras_bt_device_object_path(device)),
 			strlen(cras_bt_device_object_path(device)));
+	iodev->info.stable_id_new = iodev->info.stable_id;
 
 	iodev->open_dev= open_dev;
-	iodev->is_open = is_open;
 	iodev->frames_queued = frames_queued;
-	iodev->dev_running = dev_running;
 	iodev->delay_frames = delay_frames;
 	iodev->get_buffer = get_buffer;
 	iodev->put_buffer = put_buffer;
@@ -247,7 +251,7 @@
 	iodev->close_dev = close_dev;
 	iodev->update_supported_formats = update_supported_formats;
 	iodev->update_active_node = update_active_node;
-	iodev->software_volume_needed = 1;
+	iodev->set_volume = set_hfp_volume;
 
 	node = (struct cras_ionode *)calloc(1, sizeof(*node));
 	node->dev = iodev;
diff --git a/cras/src/server/cras_hfp_iodev.h b/cras/src/server/cras_hfp_iodev.h
index 7057641..3a073d0 100644
--- a/cras/src/server/cras_hfp_iodev.h
+++ b/cras/src/server/cras_hfp_iodev.h
@@ -6,7 +6,7 @@
 #ifndef CRAS_HFP_IODEV_H_
 #define CRAS_HFP_IODEV_H_
 
-#include "cras_bt_transport.h"
+#include "cras_bt_device.h"
 #include "cras_hfp_info.h"
 #include "cras_types.h"
 
diff --git a/cras/src/server/cras_hfp_slc.c b/cras/src/server/cras_hfp_slc.c
index 330df97..cc51055 100644
--- a/cras/src/server/cras_hfp_slc.c
+++ b/cras/src/server/cras_hfp_slc.c
@@ -6,6 +6,7 @@
 #include <sys/socket.h>
 #include <syslog.h>
 
+#include "cras_bt_device.h"
 #include "cras_telephony.h"
 #include "cras_hfp_ag_profile.h"
 #include "cras_hfp_slc.h"
@@ -52,6 +53,7 @@
  *    callheld - Current callheld status of AG stored in SLC.
  *    ind_event_report - Activate status of indicator events reporting.
  *    telephony - A reference of current telephony handle.
+ *    device - The associated bt device.
  */
 struct hfp_slc_handle {
 	char buf[SLC_BUF_SIZE_BYTES];
@@ -69,6 +71,7 @@
 	int service;
 	int callheld;
 	int ind_event_report;
+	struct cras_bt_device *device;
 
 	struct cras_telephony_handle *telephony;
 };
@@ -418,10 +421,13 @@
 		return -EINVAL;
 	}
 
-	// TODO(hychao): set mic/speaker gain
-	gain = atoi(&cmd[7]);
-	syslog(LOG_ERR, "Reported gain level %d for %s", gain,
-			cmd[5] == 'S' ? "speaker" : "microphone");
+	/* Map 0 to the smallest non-zero scale 6/100, and 15 to
+	 * 100/100 full. */
+	if (cmd[5] == 'S') {
+		gain = atoi(&cmd[7]);
+		cras_bt_device_update_hardware_volume(handle->device,
+						      (gain + 1) * 100 / 16);
+	}
 
 	return hfp_send(handle, "OK");
 }
@@ -610,6 +616,7 @@
 
 struct hfp_slc_handle *hfp_slc_create(int fd,
 				      int is_hsp,
+				      struct cras_bt_device *device,
 				      hfp_slc_init_cb init_cb,
 				      hfp_slc_disconnect_cb disconnect_cb)
 {
@@ -621,6 +628,7 @@
 
 	handle->rfcomm_fd = fd;
 	handle->is_hsp = is_hsp;
+	handle->device = device;
 	handle->init_cb = init_cb;
 	handle->disconnect_cb = disconnect_cb;
 	handle->cli_active = 0;
diff --git a/cras/src/server/cras_hfp_slc.h b/cras/src/server/cras_hfp_slc.h
index 65908f1..dd0d05f 100644
--- a/cras/src/server/cras_hfp_slc.h
+++ b/cras/src/server/cras_hfp_slc.h
@@ -19,12 +19,15 @@
  * Args:
  *    fd - the rfcomm fd used to initialize service level connection
  *    is_hsp - if the slc handle is created for headset profile
+ *    device - The bt device associated with the created slc object
  *    init_cb - the callback function to be triggered when a service level
  *        connection is initialized.
  *    disconnect_cb - the callback function to be triggered when the service
  *        level connection is disconnected.
  */
-struct hfp_slc_handle *hfp_slc_create(int fd, int is_hsp, hfp_slc_init_cb init_cb,
+struct hfp_slc_handle *hfp_slc_create(int fd, int is_hsp,
+				      struct cras_bt_device *device,
+				      hfp_slc_init_cb init_cb,
 				      hfp_slc_disconnect_cb disconnect_cb);
 
 /* Destroys an hfp_slc_handle. */
diff --git a/cras/src/server/cras_iodev.c b/cras/src/server/cras_iodev.c
index ed2df84..69e65db 100644
--- a/cras/src/server/cras_iodev.c
+++ b/cras/src/server/cras_iodev.c
@@ -10,13 +10,18 @@
 #include <syslog.h>
 #include <time.h>
 
+#include "audio_thread.h"
+#include "audio_thread_log.h"
 #include "buffer_share.h"
 #include "cras_audio_area.h"
+#include "cras_device_monitor.h"
 #include "cras_dsp.h"
 #include "cras_dsp_pipeline.h"
+#include "cras_fmt_conv.h"
 #include "cras_iodev.h"
 #include "cras_iodev_list.h"
 #include "cras_mix.h"
+#include "cras_ramp.h"
 #include "cras_rstream.h"
 #include "cras_system_state.h"
 #include "cras_util.h"
@@ -25,6 +30,10 @@
 #include "rate_estimator.h"
 #include "softvol_curve.h"
 
+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 struct timespec rate_estimation_window_sz = {
 	20, 0 /* 20 sec. */
 };
@@ -32,6 +41,215 @@
 
 static void cras_iodev_alloc_dsp(struct cras_iodev *iodev);
 
+static int default_no_stream_playback(struct cras_iodev *odev)
+{
+	int rc;
+	unsigned int hw_level, fr_to_write;
+	unsigned int target_hw_level = odev->min_cb_level * 2;
+	struct timespec hw_tstamp;
+
+	/* The default action for no stream playback is to fill zeros. */
+	rc = cras_iodev_frames_queued(odev, &hw_tstamp);
+	if (rc < 0)
+		return rc;
+	hw_level = rc;
+
+	ATLOG(atlog, AUDIO_THREAD_ODEV_DEFAULT_NO_STREAMS,
+	      odev->info.idx, hw_level, target_hw_level);
+
+	fr_to_write = cras_iodev_buffer_avail(odev, hw_level);
+	if (hw_level <= target_hw_level) {
+		fr_to_write = MIN(target_hw_level - hw_level, fr_to_write);
+		return cras_iodev_fill_odev_zeros(odev, fr_to_write);
+	}
+	return 0;
+}
+
+static int cras_iodev_start(struct cras_iodev *iodev)
+{
+	int rc;
+	if (!cras_iodev_is_open(iodev))
+		return -EPERM;
+	if (!iodev->start) {
+		syslog(LOG_ERR,
+		       "start called on device %s not supporting start ops",
+		       iodev->info.name);
+		return -EINVAL;
+	}
+	rc = iodev->start(iodev);
+	if (rc)
+		return rc;
+	iodev->state = CRAS_IODEV_STATE_NORMAL_RUN;
+	return 0;
+}
+
+/* Gets the number of frames ready for this device to play.
+ * It is the minimum number of available samples in dev_streams.
+ */
+static unsigned int dev_playback_frames(struct cras_iodev* odev)
+{
+	struct dev_stream *curr;
+	int frames = 0;
+
+	DL_FOREACH(odev->streams, curr) {
+		int dev_frames;
+
+		/* If this is a single output dev stream, updates the latest
+		 * number of frames for playback. */
+		if (dev_stream_attached_devs(curr) == 1)
+			dev_stream_update_frames(curr);
+
+		dev_frames = dev_stream_playback_frames(curr);
+		/* Do not handle stream error or end of draining in this
+		 * function because they should be handled in write_streams. */
+		if (dev_frames < 0)
+			continue;
+		if (!dev_frames) {
+			if(cras_rstream_get_is_draining(curr->stream))
+				continue;
+			else
+				return 0;
+		}
+		if (frames == 0)
+			frames = dev_frames;
+		else
+			frames = MIN(dev_frames, frames);
+	}
+	return frames;
+}
+
+/* Let device enter/leave no stream playback.
+ * Args:
+ *    iodev[in] - The output device.
+ *    enable[in] - 1 to enter no stream playback, 0 to leave.
+ * Returns:
+ *    0 on success. Negative error code on failure.
+ */
+static int cras_iodev_no_stream_playback_transition(struct cras_iodev *odev,
+						    int enable)
+{
+	int rc;
+
+	if (odev->direction != CRAS_STREAM_OUTPUT)
+		return -EINVAL;
+
+	/* This function is for transition between normal run and
+	 * no stream run state.
+	 */
+	if ((odev->state != CRAS_IODEV_STATE_NORMAL_RUN) &&
+	    (odev->state != CRAS_IODEV_STATE_NO_STREAM_RUN))
+		return -EINVAL;
+
+	if (enable) {
+		ATLOG(atlog, AUDIO_THREAD_ODEV_NO_STREAMS,
+		      odev->info.idx, 0, 0);
+	} else {
+		ATLOG(atlog, AUDIO_THREAD_ODEV_LEAVE_NO_STREAMS,
+		      odev->info.idx, 0, 0);
+	}
+
+	rc = odev->no_stream(odev, enable);
+	if (rc < 0)
+		return rc;
+	if (enable)
+		odev->state = CRAS_IODEV_STATE_NO_STREAM_RUN;
+	else
+		odev->state = CRAS_IODEV_STATE_NORMAL_RUN;
+	return 0;
+}
+
+/* Determines if the output device should mute. It considers system mute,
+ * system volume, and active node volume on the device. */
+static int output_should_mute(struct cras_iodev *odev)
+{
+	/* System mute has highest priority. */
+	if (cras_system_get_mute())
+		return 1;
+
+	/* consider system volume and active node volume. */
+	return cras_iodev_is_zero_volume(odev);
+}
+
+int cras_iodev_is_zero_volume(const struct cras_iodev *odev)
+{
+	size_t system_volume;
+	unsigned int adjusted_node_volume;
+
+	system_volume = cras_system_get_volume();
+	if (odev->active_node) {
+		adjusted_node_volume = cras_iodev_adjust_node_volume(
+				odev->active_node, system_volume);
+		return (adjusted_node_volume == 0);
+	}
+	return (system_volume == 0);
+}
+
+/* Output device state transition diagram:
+ *
+ *                           ----------------
+ *  -------------<-----------| S0  Closed   |------<-------.
+ *  |                        ----------------              |
+ *  |                           |   iodev_list enables     |
+ *  |                           |   device and adds to     |
+ *  |                           V   audio thread           | iodev_list removes
+ *  |                        ----------------              | device from
+ *  |                        | S1  Open     |              | audio_thread and
+ *  |                        ----------------              | closes device
+ *  | Device with dummy start       |                      |
+ *  | ops transits into             | Sample is ready      |
+ *  | no stream state right         V                      |
+ *  | after open.            ----------------              |
+ *  |                        | S2  Normal   |              |
+ *  |                        ----------------              |
+ *  |                           |        ^                 |
+ *  |       There is no stream  |        | Sample is ready |
+ *  |                           V        |                 |
+ *  |                        ----------------              |
+ *  ------------->-----------| S3 No Stream |------->------
+ *                           ----------------
+ *
+ *  Device in open_devs can be in one of S1, S2, S3.
+ *
+ * cras_iodev_output_event_sample_ready change device state from S1 or S3 into
+ * S2.
+ */
+static int cras_iodev_output_event_sample_ready(struct cras_iodev *odev)
+{
+	if (odev->state == CRAS_IODEV_STATE_OPEN ||
+	    odev->state == CRAS_IODEV_STATE_NO_STREAM_RUN) {
+		/* 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->state == CRAS_IODEV_STATE_OPEN) {
+		/* S1 => S2:
+		 * If device is not started yet, and there is sample ready from
+		 * stream, fill 1 min_cb_level of zeros first and fill sample
+		 * from stream later.
+		 * Starts the device here to finish state transition. */
+		cras_iodev_fill_odev_zeros(odev, odev->min_cb_level);
+		ATLOG(atlog, AUDIO_THREAD_ODEV_START,
+				odev->info.idx, odev->min_cb_level, 0);
+		return cras_iodev_start(odev);
+	} else if (odev->state == CRAS_IODEV_STATE_NO_STREAM_RUN) {
+		/* S3 => S2:
+		 * Device in no stream state get sample ready. Leave no stream
+		 * state and transit to normal run state.*/
+		return cras_iodev_no_stream_playback_transition(odev, 0);
+	} else {
+		syslog(LOG_ERR,
+		       "Device %s in state %d received sample ready event",
+		       odev->info.name, odev->state);
+		return -EINVAL;
+	}
+	return 0;
+}
+
 /*
  * Exported Interface.
  */
@@ -106,10 +324,8 @@
 	return iodev->supported_formats[0];
 }
 
-/* Set default channel count and layout to an iodev.
- * iodev->format->num_channels is from get_best_channel_count.
- */
-static void set_default_channel_count_layout(struct cras_iodev *iodev)
+/* Set default channel layout to an iodev. */
+static void set_default_channel_layout(struct cras_iodev *iodev)
 {
 	int8_t default_layout[CRAS_CH_MAX];
 	size_t i;
@@ -117,7 +333,6 @@
 	for (i = 0; i < CRAS_CH_MAX; i++)
 		default_layout[i] = i < iodev->format->num_channels ? i : -1;
 
-	iodev->ext_format->num_channels = iodev->format->num_channels;
 	cras_audio_format_set_channel_layout(iodev->format, default_layout);
 	cras_audio_format_set_channel_layout(iodev->ext_format, default_layout);
 }
@@ -151,9 +366,9 @@
 	}
 }
 
-/* Modifies the format to the one that will be presented to the device after
- * any format changes from the DSP. */
-static inline void adjust_dev_fmt_for_dsp(const struct cras_iodev *iodev)
+/* Modifies the number of channels in device format to the one that will be
+ * presented to the device after any channel changes from the DSP. */
+static inline void adjust_dev_channel_for_dsp(const struct cras_iodev *iodev)
 {
 	struct cras_dsp_context *ctx = iodev->dsp_context;
 
@@ -183,12 +398,25 @@
 static void update_channel_layout(struct cras_iodev *iodev)
 {
 	int rc;
+
+	/*
+	 * Output devices like internal speakers and headphones are 2-channel
+	 * and do not need to update channel layout.
+	 * For HDMI and USB devices that might have more than 2 channels, update
+	 * channel layout only if more than 2 channel is requested.
+	 */
+	if (iodev->direction == CRAS_STREAM_OUTPUT &&
+	    iodev->format->num_channels <= 2) {
+		set_default_channel_layout(iodev);
+		return;
+	}
+
 	if (iodev->update_channel_layout == NULL)
 		return;
 
 	rc = iodev->update_channel_layout(iodev);
 	if (rc < 0) {
-		set_default_channel_count_layout(iodev);
+		set_default_channel_layout(iodev);
 	} else {
 		cras_audio_format_set_channel_layout(
 				iodev->ext_format,
@@ -221,11 +449,17 @@
 			}
 		}
 
+		/* Finds the actual rate of device before allocating DSP
+		 * because DSP needs to use the rate of device, not rate of
+		 * stream. */
+		actual_rate = get_best_rate(iodev, fmt->frame_rate);
+		iodev->format->frame_rate = actual_rate;
+		iodev->ext_format->frame_rate = actual_rate;
+
 		cras_iodev_alloc_dsp(iodev);
 		if (iodev->dsp_context)
-			adjust_dev_fmt_for_dsp(iodev);
+			adjust_dev_channel_for_dsp(iodev);
 
-		actual_rate = get_best_rate(iodev, fmt->frame_rate);
 		actual_num_channels = get_best_channel_count(iodev,
 					iodev->format->num_channels);
 		actual_format = get_best_pcm_format(iodev, fmt->format);
@@ -235,8 +469,6 @@
 			rc = -EINVAL;
 			goto error;
 		}
-		iodev->format->frame_rate = actual_rate;
-		iodev->ext_format->frame_rate = actual_rate;
 		iodev->format->format = actual_format;
 		iodev->ext_format->format = actual_format;
 		if (iodev->format->num_channels != actual_num_channels) {
@@ -269,14 +501,42 @@
 
 void cras_iodev_update_dsp(struct cras_iodev *iodev)
 {
+	char swap_lr_disabled = 1;
+
 	if (!iodev->dsp_context)
 		return;
 
-	cras_dsp_set_variable(iodev->dsp_context, "dsp_name",
-			      iodev->dsp_name ? : "");
+	cras_dsp_set_variable_string(iodev->dsp_context, "dsp_name",
+				     iodev->dsp_name ? : "");
+
+	if (iodev->active_node && iodev->active_node->left_right_swapped)
+		swap_lr_disabled = 0;
+
+	cras_dsp_set_variable_boolean(iodev->dsp_context, "swap_lr_disabled",
+				      swap_lr_disabled);
+
 	cras_dsp_load_pipeline(iodev->dsp_context);
 }
 
+
+int cras_iodev_dsp_set_swap_mode_for_node(struct cras_iodev *iodev,
+					   struct cras_ionode *node, int enable)
+{
+	if (node->left_right_swapped == enable)
+		return 0;
+
+	/* Sets left_right_swapped property on the node. It will be used
+	 * when cras_iodev_update_dsp is called. */
+	node->left_right_swapped = enable;
+
+	/* Possibly updates dsp if the node is active on the device and there
+	 * is dsp context. If dsp context is not created yet,
+	 * cras_iodev_update_dsp returns right away. */
+	if (iodev->active_node == node)
+		cras_iodev_update_dsp(iodev);
+	return 0;
+}
+
 void cras_iodev_free_format(struct cras_iodev *iodev)
 {
 	free(iodev->format);
@@ -309,6 +569,8 @@
 {
 	cras_iodev_free_dsp(iodev);
 	rate_estimator_destroy(iodev->rate_est);
+	if (iodev->ramp)
+		cras_ramp_destroy(iodev->ramp);
 }
 
 static void cras_iodev_alloc_dsp(struct cras_iodev *iodev)
@@ -321,7 +583,7 @@
 		purpose = "capture";
 
 	cras_iodev_free_dsp(iodev);
-	iodev->dsp_context = cras_dsp_context_new(iodev->ext_format->frame_rate,
+	iodev->dsp_context = cras_dsp_context_new(iodev->format->frame_rate,
 						  purpose);
 	cras_iodev_update_dsp(iodev);
 }
@@ -351,7 +613,7 @@
 	node->plugged = plugged;
 	if (plugged) {
 		gettimeofday(&node->plugged_time, NULL);
-	} else {
+	} else if (node == node->dev->active_node) {
 		cras_iodev_list_disable_dev(node->dev);
 	}
 	cras_iodev_list_notify_nodes_changed();
@@ -445,7 +707,7 @@
 				struct cras_ionode *node)
 {
 	iodev->active_node = node;
-	cras_iodev_list_notify_active_node_changed();
+	cras_iodev_list_notify_active_node_changed(iodev->direction);
 }
 
 float cras_iodev_get_software_volume_scaler(struct cras_iodev *iodev)
@@ -460,6 +722,16 @@
 	return softvol_get_scaler(volume);
 }
 
+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;
+}
+
 int cras_iodev_add_stream(struct cras_iodev *iodev,
 			  struct dev_stream *stream)
 {
@@ -502,6 +774,12 @@
 		buffer_share_destroy(iodev->buf_state);
 		iodev->buf_state = NULL;
 		iodev->min_cb_level = old_min_cb_level;
+		/* Let output device transit into no stream state if it's
+		 * in normal run state now. Leave input device in normal
+		 * run state. */
+		if ((iodev->direction == CRAS_STREAM_OUTPUT) &&
+		    (iodev->state == CRAS_IODEV_STATE_NORMAL_RUN))
+			cras_iodev_no_stream_playback_transition(iodev, 1);
 	}
 	return ret;
 }
@@ -554,15 +832,44 @@
 	iodev->min_cb_level = MIN(iodev->buffer_size / 2, cb_level);
 	iodev->max_cb_level = 0;
 
+	iodev->reset_request_pending = 0;
+	iodev->state = CRAS_IODEV_STATE_OPEN;
+
+	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)
+			iodev->state = CRAS_IODEV_STATE_OPEN;
+		else
+			iodev->state = CRAS_IODEV_STATE_NO_STREAM_RUN;
+	} else {
+		/* Input device starts running right after opening.
+		 * No stream state is only for output device. Input device
+		 * should be in normal run state. */
+		iodev->state = CRAS_IODEV_STATE_NORMAL_RUN;
+	}
+
 	return 0;
 }
 
+enum CRAS_IODEV_STATE cras_iodev_state(const struct cras_iodev *iodev)
+{
+	return iodev->state;
+}
+
 int cras_iodev_close(struct cras_iodev *iodev)
 {
-	if (!iodev->is_open(iodev))
+	int rc;
+	if (!cras_iodev_is_open(iodev))
 		return 0;
 
-	return iodev->close_dev(iodev);
+	rc = iodev->close_dev(iodev);
+	if (rc)
+		return rc;
+	iodev->state = CRAS_IODEV_STATE_CLOSE;
+	if (iodev->ramp)
+		cras_ramp_reset(iodev->ramp);
+	return 0;
 }
 
 int cras_iodev_put_input_buffer(struct cras_iodev *iodev, unsigned int nframes)
@@ -575,12 +882,28 @@
 				 unsigned int nframes)
 {
 	const struct cras_audio_format *fmt = iodev->format;
+	struct cras_fmt_conv * remix_converter =
+			audio_thread_get_global_remix_converter();
+	struct cras_ramp_action ramp_action = {
+		.type = CRAS_RAMP_ACTION_NONE,
+		.scaler = 0.0f,
+		.increment = 0.0f,
+	};
+	float software_volume_scaler;
+	int software_volume_needed = cras_iodev_software_volume_needed(iodev);
 
 	if (iodev->pre_dsp_hook)
 		iodev->pre_dsp_hook(frames, nframes, iodev->ext_format,
 				    iodev->pre_dsp_hook_cb_data);
 
-	if (cras_system_get_mute()) {
+	if (iodev->ramp) {
+		ramp_action = cras_ramp_get_current_action(iodev->ramp);
+	}
+
+	/* Mute samples if adjusted volume is 0 or system is muted, plus
+	 * that this device is not ramping. */
+	if (output_should_mute(iodev) &&
+	    ramp_action.type != CRAS_RAMP_ACTION_PARTIAL) {
 		const unsigned int frame_bytes = cras_get_format_bytes(fmt);
 		cras_mix_mute_buffer(frames, frame_bytes, nframes);
 	} else {
@@ -590,16 +913,42 @@
 			iodev->post_dsp_hook(frames, nframes, fmt,
 					     iodev->post_dsp_hook_cb_data);
 
-		if (cras_iodev_software_volume_needed(iodev)) {
-			unsigned int nsamples = nframes * fmt->num_channels;
-			float scaler =
+		/* Compute scaler for software volume if needed. */
+		if (software_volume_needed) {
+			software_volume_scaler =
 				cras_iodev_get_software_volume_scaler(iodev);
+		}
 
+		if (ramp_action.type == CRAS_RAMP_ACTION_PARTIAL) {
+			/* Scale with increment for ramp and possibly
+			 * software volume using cras_scale_buffer_increment.*/
+			float starting_scaler = ramp_action.scaler;
+			float increment = ramp_action.increment;
+
+			if (software_volume_needed) {
+				starting_scaler *= software_volume_scaler;
+				increment *= software_volume_scaler;
+			}
+
+			cras_scale_buffer_increment(
+					fmt->format, frames, nframes,
+					starting_scaler, increment,
+					fmt->num_channels);
+			cras_ramp_update_ramped_frames(iodev->ramp, nframes);
+		} else if (software_volume_needed) {
+			/* Just scale for software volume using
+			 * cras_scale_buffer. */
+			unsigned int nsamples = nframes * fmt->num_channels;
 			cras_scale_buffer(fmt->format, frames,
-					  nsamples, scaler);
+					  nsamples, software_volume_scaler);
 		}
 	}
 
+	if (remix_converter)
+		cras_channel_remix_convert(remix_converter,
+				   iodev->format,
+				   frames,
+				   nframes);
 	rate_estimator_add_frames(iodev->rate_est, nframes);
 	return iodev->put_buffer(iodev, nframes);
 }
@@ -612,10 +961,17 @@
 	const unsigned int frame_bytes = cras_get_format_bytes(fmt);
 	uint8_t *hw_buffer;
 	int rc;
+	unsigned frame_requested = *frames;
 
 	rc = iodev->get_buffer(iodev, area, frames);
 	if (rc < 0 || *frames == 0)
 		return rc;
+	if (*frames > frame_requested) {
+		syslog(LOG_ERR,
+		       "frames returned from get_buffer is greater than "
+		       "requested: %u > %u", *frames, frame_requested);
+		return -EINVAL;
+	}
 
 	/* TODO(dgreid) - This assumes interleaved audio. */
 	hw_buffer = (*area)->channels[0].buf;
@@ -632,15 +988,23 @@
 				 struct cras_audio_area **area,
 				 unsigned *frames)
 {
-	return iodev->get_buffer(iodev, area, frames);
+	int rc;
+	unsigned frame_requested = *frames;
+
+	rc = iodev->get_buffer(iodev, area, frames);
+	if (*frames > frame_requested) {
+		syslog(LOG_ERR,
+		       "frames returned from get_buffer is greater than "
+		       "requested: %u > %u", *frames, frame_requested);
+		return -EINVAL;
+	}
+	return rc;
 }
 
-int cras_iodev_update_rate(struct cras_iodev *iodev, unsigned int level)
+int cras_iodev_update_rate(struct cras_iodev *iodev, unsigned int level,
+			   struct timespec *level_tstamp)
 {
-	struct timespec now;
-
-	clock_gettime(CLOCK_MONOTONIC_RAW, &now);
-	return rate_estimator_check(iodev->rate_est, level, &now);
+	return rate_estimator_check(iodev->rate_est, level, level_tstamp);
 }
 
 int cras_iodev_reset_rate_estimator(const struct cras_iodev *iodev)
@@ -676,11 +1040,12 @@
 	return delay;
 }
 
-int cras_iodev_frames_queued(struct cras_iodev *iodev)
+int cras_iodev_frames_queued(struct cras_iodev *iodev,
+			     struct timespec *hw_tstamp)
 {
 	int rc;
 
-	rc = iodev->frames_queued(iodev);
+	rc = iodev->frames_queued(iodev, hw_tstamp);
 	if (rc < 0 || iodev->direction == CRAS_STREAM_INPUT)
 		return rc;
 
@@ -716,3 +1081,208 @@
 	iodev->post_dsp_hook = loop_cb;
 	iodev->post_dsp_hook_cb_data = cb_data;
 }
+
+int cras_iodev_fill_odev_zeros(struct cras_iodev *odev, unsigned int frames)
+{
+	struct cras_audio_area *area = NULL;
+	unsigned int frame_bytes, frames_written;
+	int rc;
+	uint8_t *buf;
+
+	if (odev->direction != CRAS_STREAM_OUTPUT)
+		return -EINVAL;
+
+	ATLOG(atlog, AUDIO_THREAD_FILL_ODEV_ZEROS, odev->info.idx, frames, 0);
+
+	frame_bytes = cras_get_format_bytes(odev->ext_format);
+	while (frames > 0) {
+		frames_written = frames;
+		rc = cras_iodev_get_output_buffer(odev, &area, &frames_written);
+		if (rc < 0) {
+			syslog(LOG_ERR, "fill zeros fail: %d", rc);
+			return rc;
+		}
+
+		/* This assumes consecutive channel areas. */
+		buf = area->channels[0].buf;
+		memset(buf, 0, frames_written * frame_bytes);
+		cras_iodev_put_output_buffer(odev, buf, frames_written);
+		frames -= frames_written;
+	}
+
+	return 0;
+}
+
+int cras_iodev_output_underrun(struct cras_iodev *odev) {
+	if (odev->output_underrun)
+		return odev->output_underrun(odev);
+	else
+		return cras_iodev_fill_odev_zeros(odev, odev->min_cb_level);
+}
+
+int cras_iodev_odev_should_wake(const struct cras_iodev *odev)
+{
+	if (odev->direction != CRAS_STREAM_OUTPUT)
+		return 0;
+
+	if (odev->output_should_wake)
+		return odev->output_should_wake(odev);
+
+	/* Do not wake up for device not started yet. */
+	return (odev->state == CRAS_IODEV_STATE_NORMAL_RUN ||
+	        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)
+{
+	int rc;
+
+	rc = cras_iodev_frames_queued(odev, hw_tstamp);
+	*hw_level = (rc < 0) ? 0 : rc;
+
+	if (odev->streams) {
+		/* Schedule that audio thread will wake up when
+		 * hw_level drops to 0.
+		 * This should not cause underrun because audio thread
+		 * should be waken up by the reply from client. */
+		return *hw_level;
+	}
+	/* When this device has no stream, schedule audio thread to wake up
+	 * when hw_level drops to min_cb_level so audio thread can fill
+	 * zeros to it. */
+	if (*hw_level > odev->min_cb_level)
+		return *hw_level - odev->min_cb_level;
+	else
+		return 0;
+}
+
+int cras_iodev_default_no_stream_playback(struct cras_iodev *odev, int enable)
+{
+	if (enable)
+		return default_no_stream_playback(odev);
+	return 0;
+}
+
+int cras_iodev_prepare_output_before_write_samples(struct cras_iodev *odev)
+{
+	int may_enter_normal_run;
+	enum CRAS_IODEV_STATE state;
+
+	if (odev->direction != CRAS_STREAM_OUTPUT)
+		return -EINVAL;
+
+	state = cras_iodev_state(odev);
+
+	may_enter_normal_run = (state == CRAS_IODEV_STATE_OPEN ||
+		                state == CRAS_IODEV_STATE_NO_STREAM_RUN);
+
+	if (may_enter_normal_run && dev_playback_frames(odev))
+		return cras_iodev_output_event_sample_ready(odev);
+
+	/* no_stream ops is called every cycle in no_stream state. */
+	if (state == CRAS_IODEV_STATE_NO_STREAM_RUN)
+		return odev->no_stream(odev, 1);
+
+	return 0;
+}
+
+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;
+}
+
+unsigned int cras_iodev_get_num_severe_underruns(const struct cras_iodev *iodev)
+{
+	if (iodev->get_num_severe_underruns)
+		return iodev->get_num_severe_underruns(iodev);
+	return 0;
+}
+
+int cras_iodev_reset_request(struct cras_iodev* iodev)
+{
+	/* Ignore requests if there is a pending request.
+	 * This function sends the request from audio thread to main
+	 * thread when audio thread finds a device is in a bad state
+	 * e.g. severe underrun. Before main thread receives the
+	 * request and resets device, audio thread might try to send
+	 * multiple requests because it finds device is still in bad
+	 * state. We should ignore requests in this cause. Otherwise,
+	 * main thread will reset device multiple times.
+	 * The flag is cleared in cras_iodev_open.
+	 * */
+	if (iodev->reset_request_pending)
+		return 0;
+	iodev->reset_request_pending = 1;
+	return cras_device_monitor_reset_device(iodev);
+}
+
+static void ramp_mute_callback(void *data)
+{
+	struct cras_iodev *odev = (struct cras_iodev *)data;
+	cras_device_monitor_set_device_mute_state(odev);
+}
+
+/* Used in audio thread. Check the docstrings of CRAS_IODEV_RAMP_REQUEST. */
+int cras_iodev_start_ramp(struct cras_iodev *odev,
+			  enum CRAS_IODEV_RAMP_REQUEST request)
+{
+	cras_ramp_cb cb = NULL;
+	void *cb_data = NULL;
+	int rc, up;
+	float duration_secs;
+
+	/* Ignores request if device is closed. */
+	if (!cras_iodev_is_open(odev))
+		return 0;
+
+	switch (request) {
+	case CRAS_IODEV_RAMP_REQUEST_UP_UNMUTE:
+		up = 1;
+		duration_secs = RAMP_UNMUTE_DURATION_SECS;
+		break;
+	case CRAS_IODEV_RAMP_REQUEST_UP_START_PLAYBACK:
+		up = 1;
+		duration_secs = RAMP_NEW_STREAM_DURATION_SECS;
+		break;
+	/* Unmute -> mute. Callback to set mute state should be called after
+	 * ramping is done. */
+	case CRAS_IODEV_RAMP_REQUEST_DOWN_MUTE:
+		up = 0;
+		duration_secs = RAMP_MUTE_DURATION_SECS;
+		cb = ramp_mute_callback;
+		cb_data = (void*)odev;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* Starts ramping. */
+	rc = cras_ramp_start(
+			odev->ramp, up,
+			duration_secs * odev->format->frame_rate,
+			cb, cb_data);
+
+	if (rc)
+		return rc;
+
+	/* Mute -> unmute case, unmute state should be set after ramping is
+	 * started so device can start playing with samples close to 0. */
+	if (request == CRAS_IODEV_RAMP_REQUEST_UP_UNMUTE)
+		cras_device_monitor_set_device_mute_state(odev);
+
+	return 0;
+}
+
+int cras_iodev_set_mute(struct cras_iodev* iodev)
+{
+	if (!cras_iodev_is_open(iodev))
+		return 0;
+
+	if (iodev->set_mute)
+		iodev->set_mute(iodev);
+	return 0;
+}
diff --git a/cras/src/server/cras_iodev.h b/cras/src/server/cras_iodev.h
index d5ccb36..51a0003 100644
--- a/cras/src/server/cras_iodev.h
+++ b/cras/src/server/cras_iodev.h
@@ -19,6 +19,7 @@
 #include "cras_messages.h"
 
 struct buffer_share;
+struct cras_ramp;
 struct cras_rstream;
 struct cras_audio_area;
 struct cras_audio_format;
@@ -33,6 +34,17 @@
 			       const struct cras_audio_format *fmt,
 			       void *cb_data);
 
+/* State of an iodev.
+ * no_stream state is only supported on output device.
+ * Open state is only supported for device supporting start ops.
+ */
+enum CRAS_IODEV_STATE {
+	CRAS_IODEV_STATE_CLOSE = 0,
+	CRAS_IODEV_STATE_OPEN = 1,
+	CRAS_IODEV_STATE_NORMAL_RUN = 2,
+	CRAS_IODEV_STATE_NO_STREAM_RUN = 3,
+};
+
 /* Holds an output/input node for this device.  An ionode is a control that
  * can be switched on and off such as headphones or speakers.
  * Members:
@@ -44,13 +56,20 @@
  *    capture_gain - per-node capture gain/attenuation (in 100*dBFS)
  *    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.
+ *    active_hotword_model - name of the currently selected hotword model.
  *    softvol_scalers - pointer to software volume scalers.
- *    software_volume_needed - True if the volume range of the node is
- *      smaller than desired.
+ *    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.
+ *    max_software_gain - The maximum software gain in dBm if needed.
+ *    stable_id - id for node that doesn't change after unplug/plug.
+ *    stable_id_new - New stable_id, it will be deprecated and be put on
+ *      stable_id.
  */
 struct cras_ionode {
 	struct cras_iodev *dev;
@@ -61,10 +80,15 @@
 	long capture_gain;
 	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];
+	char active_hotword_model[CRAS_NODE_HOTWORD_MODEL_BUFFER_SIZE];
 	float *softvol_scalers;
 	int software_volume_needed;
+	long max_software_gain;
+	unsigned int stable_id;
+	unsigned int stable_id_new;
 	struct cras_ionode *prev, *next;
 };
 
@@ -76,18 +100,40 @@
  * set_swap_mode_for_node - Function to call to set swap mode for the node.
  * open_dev - Opens the device.
  * close_dev - Closes the device if it is open.
- * is_open - Checks if the device has been openned.
  * update_supported_formats - Refresh supported frame rates and channel counts.
- * frames_queued - The number of frames in the audio buffer.
+ * frames_queued - The number of frames in the audio buffer, and fills tstamp
+ *                 with the associated timestamp. The timestamp is {0, 0} when
+ *                 the device hasn't started processing data (and on error).
  * delay_frames - The delay of the next sample in frames.
  * get_buffer - Returns a buffer to read/write to/from.
  * put_buffer - Marks a buffer from get_buffer as read/written.
  * flush_buffer - Flushes the buffer and return the number of frames flushed.
- * dev_running - Checks if the device is playing or recording, return 1 if it's
- *     running, return 0 if not.
- * update_active_node - Update the active node using the selected/plugged state.
+ * start - Starts running device. This is optionally supported on output device.
+ *         If device supports this ops, device can be in CRAS_IODEV_STATE_OPEN
+ *         state after being opened.
+ *         If device does not support this ops, then device will be in
+ *         CRAS_IODEV_STATE_NO_STREAM_RUN.
+ * no_stream - (Optional) When there is no stream, we let device keep running
+ *             for some time to save the time to open device for the next
+ *             stream. This is the no stream state of an output device.
+ *             The default action of no stream state is to fill zeros
+ *             periodically. Device can implement this function to define
+ *             its own optimization of entering/exiting no stream state.
+ * output_should_wake - (Optional) Checks if audio thread should schedule a
+ *                      wake for this output device. The default condition is
+ *                      whether the device is running. Device can implement this
+ *                      function to use its own condition.
+ * output_underrun - (Optional) Handle output device underrun.
+ * update_active_node - Update the active node when the selected device/node has
+ *     changed.
  * update_channel_layout - Update the channel layout base on set iodev->format,
  *     expect the best available layout be filled to iodev->format.
+ * 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.
  * format - The audio format being rendered or captured to hardware.
  * ext_format - The audio format that is visible to the rest of the system.
  *     This can be different than the hardware if the device dsp changes it.
@@ -107,6 +153,8 @@
  * is_enabled - True if this iodev is enabled, false otherwise.
  * software_volume_needed - True if volume control is not supported by hardware.
  * streams - List of audio streams serviced by dev.
+ * state - Device is in one of close, open, normal, or no_stream state defined
+ *         in enum CRAS_IODEV_STATE.
  * min_cb_level - min callback level of any stream attached.
  * max_cb_level - max callback level of any stream attached.
  * buf_state - If multiple streams are writing to this device, then this
@@ -118,6 +166,9 @@
  *     reference.
  * pre_dsp_hook_cb_data - Callback data that will be passing to pre_dsp_hook.
  * post_dsp_hook_cb_data - Callback data that will be passing to post_dsp_hook.
+ * reset_request_pending - The flag for pending reset request.
+ * ramp - The cras_ramp struct to control ramping up/down at mute/unmute and
+ *        start of playback.
  */
 struct cras_iodev {
 	void (*set_volume)(struct cras_iodev *iodev);
@@ -129,18 +180,27 @@
 				      int enable);
 	int (*open_dev)(struct cras_iodev *iodev);
 	int (*close_dev)(struct cras_iodev *iodev);
-	int (*is_open)(const struct cras_iodev *iodev);
 	int (*update_supported_formats)(struct cras_iodev *iodev);
-	int (*frames_queued)(const struct cras_iodev *iodev);
+	int (*frames_queued)(const struct cras_iodev *iodev,
+			     struct timespec *tstamp);
 	int (*delay_frames)(const struct cras_iodev *iodev);
 	int (*get_buffer)(struct cras_iodev *iodev,
 			  struct cras_audio_area **area,
 			  unsigned *frames);
 	int (*put_buffer)(struct cras_iodev *iodev, unsigned nwritten);
 	int (*flush_buffer)(struct cras_iodev *iodev);
-	int (*dev_running)(const struct cras_iodev *iodev);
-	void (*update_active_node)(struct cras_iodev *iodev, unsigned node_idx);
+	int (*start)(const struct cras_iodev *iodev);
+	int (*output_should_wake)(const struct cras_iodev *iodev);
+	int (*output_underrun)(struct cras_iodev *iodev);
+	int (*no_stream)(struct cras_iodev *iodev, int enable);
+	void (*update_active_node)(struct cras_iodev *iodev,
+				   unsigned node_idx, unsigned dev_enabled);
 	int (*update_channel_layout)(struct cras_iodev *iodev);
+	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);
 	struct cras_audio_format *format;
 	struct cras_audio_format *ext_format;
 	struct rate_estimator *rate_est;
@@ -159,6 +219,7 @@
 	int is_enabled;
 	int software_volume_needed;
 	struct dev_stream *streams;
+	enum CRAS_IODEV_STATE state;
 	unsigned int min_cb_level;
 	unsigned int max_cb_level;
 	struct buffer_share *buf_state;
@@ -167,10 +228,44 @@
 	loopback_hook_t post_dsp_hook;
 	void *pre_dsp_hook_cb_data;
 	void *post_dsp_hook_cb_data;
+	int reset_request_pending;
+	struct cras_ramp* ramp;
 	struct cras_iodev *prev, *next;
 };
 
 /*
+ * Ramp request used in cras_iodev_start_ramp.
+ *
+ * - CRAS_IODEV_RAMP_REQUEST_UP_UNMUTE: Mute->unmute.
+ *   Change device to unmute state after ramping is stared,
+ *                 that is, (a) in the plot.
+ *
+ *                                  ____
+ *                            .... /
+ *                      _____/
+ *                          (a)
+ *
+ * - CRAS_IODEV_RAMP_REQUEST_DOWN_MUTE: Unmute->mute.
+ *   Change device to mute state after ramping is done, that is,
+ *                 (b) in the plot.
+ *
+ *                      _____
+ *                           \....
+ *                                \____
+ *                                (b)
+ *
+ * - 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.
+ */
+
+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,
+};
+
+/*
  * Utility functions to be used by iodev implementations.
  */
 
@@ -251,6 +346,22 @@
  */
 void cras_iodev_update_dsp(struct cras_iodev *iodev);
 
+
+/* Sets swap mode on a node using dsp. This function can be called when
+ * dsp pipline is not created yet. It will take effect when dsp pipeline
+ * is created later. If there is dsp pipeline, this function causes the dsp
+ * pipeline to be reloaded and swap mode takes effect right away.
+ * Args:
+ *    iodev - device to be changed for swap mode.
+ *    node - the node to be changed for swap mode.
+ *    enable - 1 to enable swap mode, 0 otherwise.
+ * Returns:
+ *    0 on success, error code on failure.
+ */
+int cras_iodev_dsp_set_swap_mode_for_node(struct cras_iodev *iodev,
+					   struct cras_ionode *node,
+					   int enable);
+
 /* Handles a plug event happening on this node.
  * Args:
  *    node - ionode on which a plug event was detected.
@@ -303,6 +414,16 @@
 	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)
@@ -316,6 +437,30 @@
 	return iodev->active_node->software_volume_needed;
 }
 
+/* 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;
+}
+
+/* Gets the software gain scaler should be applied on the deivce.
+ * Args:
+ *    iodev - The device.
+ * Returns:
+ *    A scaler translated from system gain and active node gain dBm value.
+ *    Returns 1.0 if software gain is not needed. */
+float cras_iodev_get_software_gain_scaler(const struct cras_iodev *iodev);
+
 /* Gets the software volume scaler of the iodev. The scaler should only be
  * applied if the device needs software volume. */
 float cras_iodev_get_software_volume_scaler(struct cras_iodev *iodev);
@@ -346,6 +491,9 @@
  */
 unsigned int cras_iodev_all_streams_written(struct cras_iodev *iodev);
 
+/* Return the state of an iodev. */
+enum CRAS_IODEV_STATE cras_iodev_state(const struct cras_iodev *iodev);
+
 /* Open an iodev, does setup and invokes the open_dev callback. */
 int cras_iodev_open(struct cras_iodev *iodev, unsigned int cb_level);
 
@@ -383,7 +531,8 @@
 				 unsigned *frames);
 
 /* Update the estimated sample rate of the device. */
-int cras_iodev_update_rate(struct cras_iodev *iodev, unsigned int level);
+int cras_iodev_update_rate(struct cras_iodev *iodev, unsigned int level,
+			   struct timespec *level_tstamp);
 
 /* Resets the rate estimator of the device. */
 int cras_iodev_reset_rate_estimator(const struct cras_iodev *iodev);
@@ -395,8 +544,16 @@
 /* Get the delay from DSP processing in frames. */
 int cras_iodev_get_dsp_delay(const struct cras_iodev *iodev);
 
-/* Returns the number of frames in the hardware buffer. */
-int cras_iodev_frames_queued(struct cras_iodev *iodev);
+/* Returns the number of frames in the hardware buffer.
+ * Args:
+ *    iodev - The device.
+ *    tstamp - The associated hardware time stamp.
+ * Returns:
+ *    Number of frames in the hardware buffer.
+ *    Returns -EPIPE if there is severe underrun.
+ */
+int cras_iodev_frames_queued(struct cras_iodev *iodev,
+			     struct timespec *tstamp);
 
 /* Get the delay for input/output in frames. */
 static inline int cras_iodev_delay_frames(const struct cras_iodev *iodev)
@@ -407,7 +564,7 @@
 /* Returns true if the device is open. */
 static inline int cras_iodev_is_open(const struct cras_iodev *iodev)
 {
-	if (iodev && iodev->is_open(iodev))
+	if (iodev && iodev->state != CRAS_IODEV_STATE_CLOSE)
 		return 1;
 	return 0;
 }
@@ -422,4 +579,126 @@
 				       loopback_hook_t loop_cb,
 				       void *cb_data);
 
+/* Put 'frames' worth of zero samples into odev. */
+int cras_iodev_fill_odev_zeros(struct cras_iodev *odev, unsigned int frames);
+
+/* Gets the number of frames to play when audio thread sleeps.
+ * Args:
+ *    iodev[in] - The device.
+ *    hw_level[out] - Pointer to number of frames in hardware.
+ *    hw_tstamp[out] - Pointer to the timestamp for hw_level.
+ * Returns:
+ *    Number of frames to play in sleep for this output device.
+ */
+unsigned int cras_iodev_frames_to_play_in_sleep(struct cras_iodev *odev,
+						unsigned int *hw_level,
+						struct timespec *hw_tstamp);
+
+/* Checks if audio thread should wake for this output device.
+ * Args:
+ *    iodev[in] - The output device.
+ * Returns:
+ *    1 if audio thread should wake for this output device. 0 otherwise.
+ */
+int cras_iodev_odev_should_wake(const struct cras_iodev *odev);
+
+/* The default implementation of no_stream ops.
+ * The default behavior is to fill some zeros when entering no stream state.
+ * Note that when a device in no stream state enters into no stream state again,
+ * device needs to fill some zeros again.
+ * Do nothing to leave no stream state.
+ * Args:
+ *    iodev[in] - The output device.
+ *    enable[in] - 1 to enter no stream playback, 0 to leave.
+ * Returns:
+ *    0 on success. Negative error code on failure.
+ * */
+int cras_iodev_default_no_stream_playback(struct cras_iodev *odev, int enable);
+
+
+/* Get current state of iodev.
+ * Args:
+ *    iodev[in] - The device.
+ * Returns:
+ *    One of states defined in CRAS_IODEV_STATE.
+ */
+enum CRAS_IODEV_STATE cras_iodev_state(const struct cras_iodev *iodev);
+
+/* Possibly transit state for output device.
+ * Check if this output device needs to transit from open state/no_stream state
+ * into normal run state. If device does not need transition and is still in
+ * no stream state, call no_stream ops to do its work for one cycle.
+ * Args:
+ *    odev[in] - The output device.
+ * Returns:
+ *    0 on success. Negative error code on failure.
+ */
+int cras_iodev_prepare_output_before_write_samples(struct cras_iodev *odev);
+
+/* Get number of underruns recorded so far.
+ * Args:
+ *    iodev[in] - The device.
+ * Returns:
+ *    An unsigned int for number of underruns recorded.
+ */
+unsigned int cras_iodev_get_num_underruns(const struct cras_iodev *iodev);
+
+/* Get number of severe underruns recorded so far.
+ * Args:
+ *    iodev[in] - The device.
+ * Returns:
+ *    An unsigned int for number of severe underruns recorded since iodev
+ *    was created.
+ */
+unsigned int cras_iodev_get_num_severe_underruns(
+		const struct cras_iodev *iodev);
+
+/* Request main thread to re-open device. This should be used in audio thread
+ * when it finds device is in a bad state. The request will be ignored if
+ * there is still a pending request.
+ * Args:
+ *    iodev[in] - The device.
+ * Returns:
+ *    0 on success. Negative error code on failure.
+ */
+int cras_iodev_reset_request(struct cras_iodev* iodev);
+
+/* Handle output underrun.
+ * Args:
+ *    odev[in] - The output device.
+ * Returns:
+ *    0 on success. Negative error code on failure.
+ */
+int cras_iodev_output_underrun(struct cras_iodev *odev);
+
+/* Start ramping samples up/down on a device.
+ * Args:
+ *    iodev[in] - The device.
+ *    request[in] - The request type. Check the docstrings of
+ *                  CRAS_IODEV_RAMP_REQUEST.
+ * Returns:
+ *    0 on success. Negative error code on failure.
+ */
+int cras_iodev_start_ramp(struct cras_iodev *odev,
+			  enum CRAS_IODEV_RAMP_REQUEST request);
+
+/* Set iodev to mute/unmute state.
+ * Args:
+ *    iodev[in] - The device.
+ * Returns:
+ *    0 on success. Negative error code on failure.
+ */
+int cras_iodev_set_mute(struct cras_iodev* iodev);
+
+/*
+ * Checks if an output iodev's volume is zero.
+ * If there is an active node, check the adjusted node volume.
+ * If there is no active node, check system volume.
+ * Args:
+ *    odev[in] - The device.
+ * Returns:
+ *    1 if device's volume is 0. 0 otherwise.
+ */
+int cras_iodev_is_zero_volume(const struct cras_iodev *odev);
+
 #endif /* CRAS_IODEV_H_ */
diff --git a/cras/src/server/cras_iodev_list.c b/cras/src/server/cras_iodev_list.c
index 354bfb7..b89767e 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_observer.h"
 #include "cras_rstream.h"
 #include "cras_server.h"
 #include "cras_tm.h"
@@ -33,14 +34,18 @@
 
 /* List of enabled input/output devices.
  *    dev - The device.
+ *    init_timer - Timer for a delayed call to init this iodev.
  */
 struct enabled_dev {
 	struct cras_iodev *dev;
+	struct cras_timer *init_timer;
 	struct enabled_dev *prev, *next;
 };
 
 /* 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. */
+static struct cras_observer_client *list_observer;
 /* Keep a list of enabled inputs and outputs. */
 static struct enabled_dev *enabled_devs[CRAS_NUM_DIRECTIONS];
 /* Keep an empty device per direction. */
@@ -48,14 +53,7 @@
 /* Keep a constantly increasing index for iodevs. Index 0 is reserved
  * to mean "no device". */
 static uint32_t next_iodev_idx = MAX_SPECIAL_DEVICE_IDX;
-/* Called when the nodes are added/removed. */
-static struct cras_alert *nodes_changed_alert;
-/* Called when the active output/input is changed */
-static struct cras_alert *active_node_changed_alert;
-/* Call when the volume of a node changes. */
-static node_volume_callback_t node_volume_callback;
-static node_volume_callback_t node_input_gain_callback;
-static node_left_right_swapped_callback_t node_left_right_swapped_callback;
+
 /* Call when a device is enabled or disabled. */
 static device_enabled_callback_t device_enabled_callback;
 static void *device_enabled_cb_data;
@@ -67,9 +65,9 @@
 static struct cras_timer *idle_timer;
 /* Flag to indicate that the stream list is disconnected from audio thread. */
 static int stream_list_suspended = 0;
+/* If init device failed, retry after 1 second. */
+static const unsigned int INIT_DEV_DELAY_MS = 1000;
 
-static void nodes_changed_prepare(struct cras_alert *alert);
-static void active_node_changed_prepare(struct cras_alert *alert);
 static void idle_dev_check(struct cras_timer *timer, void *data);
 
 static struct cras_iodev *find_dev(size_t dev_index)
@@ -152,7 +150,7 @@
 
 	DL_FOREACH(devs[dev->direction].iodevs, tmp)
 		if (tmp == dev) {
-			if (dev->is_open(dev))
+			if (cras_iodev_is_open(dev))
 				return -EBUSY;
 			DL_DELETE(devs[dev->direction].iodevs, dev);
 			devs[dev->direction].size--;
@@ -178,21 +176,35 @@
 	}
 }
 
-static const char *node_type_to_str(enum CRAS_NODE_TYPE type)
+static const char *node_type_to_str(struct cras_ionode *node)
 {
-	switch (type) {
+	switch (node->type) {
 	case CRAS_NODE_TYPE_INTERNAL_SPEAKER:
 		return "INTERNAL_SPEAKER";
 	case CRAS_NODE_TYPE_HEADPHONE:
 		return "HEADPHONE";
 	case CRAS_NODE_TYPE_HDMI:
 		return "HDMI";
-	case CRAS_NODE_TYPE_INTERNAL_MIC:
-		return "INTERNAL_MIC";
+	case CRAS_NODE_TYPE_HAPTIC:
+		return "HAPTIC";
 	case CRAS_NODE_TYPE_MIC:
-		return "MIC";
-	case CRAS_NODE_TYPE_AOKR:
-		return "AOKR";
+		switch (node->position) {
+		case NODE_POSITION_INTERNAL:
+			return "INTERNAL_MIC";
+		case NODE_POSITION_FRONT:
+			return "FRONT_MIC";
+		case NODE_POSITION_REAR:
+			return "REAR_MIC";
+		case NODE_POSITION_KEYBOARD:
+			return "KEYBOARD_MIC";
+		case NODE_POSITION_EXTERNAL:
+		default:
+			return "MIC";
+		}
+	case CRAS_NODE_TYPE_HOTWORD:
+		return "HOTWORD";
+	case CRAS_NODE_TYPE_LINEOUT:
+		return "LINEOUT";
 	case CRAS_NODE_TYPE_POST_MIX_PRE_DSP:
 		return "POST_MIX_LOOPBACK";
 	case CRAS_NODE_TYPE_POST_DSP:
@@ -201,8 +213,6 @@
 		return "USB";
 	case CRAS_NODE_TYPE_BLUETOOTH:
 		return "BLUETOOTH";
-	case CRAS_NODE_TYPE_KEYBOARD_MIC:
-		return "KEYBOARD_MIC";
 	case CRAS_NODE_TYPE_UNKNOWN:
 	default:
 		return "UNKNOWN";
@@ -231,10 +241,14 @@
 			node_info->volume = node->volume;
 			node_info->capture_gain = node->capture_gain;
 			node_info->left_right_swapped = node->left_right_swapped;
+			node_info->stable_id = node->stable_id;
+			node_info->stable_id_new = node->stable_id_new;
 			strcpy(node_info->mic_positions, node->mic_positions);
 			strcpy(node_info->name, node->name);
+			strcpy(node_info->active_hotword_model,
+				node->active_hotword_model);
 			snprintf(node_info->type, sizeof(node_info->type), "%s",
-				node_type_to_str(node->type));
+				node_type_to_str(node));
 			node_info->type_enum = node->type;
 			node_info++;
 			i++;
@@ -270,25 +284,69 @@
 
 /* Called when the system volume changes.  Pass the current volume setting to
  * the default output if it is active. */
-void sys_vol_change(void *data)
+static void sys_vol_change(void *context, int32_t volume)
 {
 	struct cras_iodev *dev;
 
 	DL_FOREACH(devs[CRAS_STREAM_OUTPUT].iodevs, dev) {
-		if (dev->set_volume && dev->is_open(dev))
+		if (dev->set_volume && cras_iodev_is_open(dev))
 			dev->set_volume(dev);
 	}
 }
 
+/*
+ * Checks if a device should start ramping for mute/unmute change.
+ * Device must meet all the conditions:
+ *
+ * - Device is enabled in iodev_list.
+ * - Device has ramp support.
+ * - Device is in normal run state, that is, it must be running with valid
+ *   streams.
+ * - Device volume, which considers both system volume and adjusted active
+ *   node volume, is not zero. If device volume is zero, all the samples are
+ *   suppressed to zero and there is no need to ramp.
+ */
+static int device_should_start_ramp_for_mute(const struct cras_iodev *dev)
+{
+	return (cras_iodev_list_dev_is_enabled(dev) && dev->ramp &&
+		cras_iodev_state(dev) == CRAS_IODEV_STATE_NORMAL_RUN &&
+		!cras_iodev_is_zero_volume(dev));
+}
+
 /* Called when the system mute state changes.  Pass the current mute setting
  * to the default output if it is active. */
-void sys_mute_change(void *data)
+static void sys_mute_change(void *context, int muted, int user_muted,
+			    int mute_locked)
 {
 	struct cras_iodev *dev;
+	int should_mute = muted || user_muted;
 
 	DL_FOREACH(devs[CRAS_STREAM_OUTPUT].iodevs, dev) {
-		if (dev->set_mute && dev->is_open(dev))
-			dev->set_mute(dev);
+		if (device_should_start_ramp_for_mute(dev)) {
+			/*
+			 * Start ramping in audio thread and set mute/unmute
+			 * state on device. This should only be done when
+			 * device is running with valid streams.
+			 *
+			 * 1. Mute -> Unmute: Set device unmute state after
+			 *                    ramping is started.
+			 * 2. Unmute -> Mute: Set device mute state after
+			 *                    ramping is done.
+			 *
+			 * The above transition will be handled by
+			 * cras_iodev_ramp_start.
+			 */
+			audio_thread_dev_start_ramp(
+					audio_thread,
+					dev,
+					(should_mute ?
+					 CRAS_IODEV_RAMP_REQUEST_DOWN_MUTE :
+					 CRAS_IODEV_RAMP_REQUEST_UP_UNMUTE));
+
+		} else {
+			/* For device without ramp, just set its mute state. */
+			cras_iodev_set_mute(dev);
+		}
 	}
 }
 
@@ -297,7 +355,7 @@
 	const struct cras_rstream *rstream;
 
 	DL_FOREACH(stream_list_get(stream_list), rstream) {
-		if (rstream->pinned_dev_idx == dev_idx)
+		if (rstream->is_pinned && (rstream->pinned_dev_idx == dev_idx))
 			return 1;
 	}
 	return 0;
@@ -431,9 +489,9 @@
 }
 
 /* Called when the system audio is suspended or resumed. */
-void sys_suspend_change(void *data)
+void sys_suspend_change(void *arg, int suspended)
 {
-	if (cras_system_get_suspended())
+	if (suspended)
 		suspend_devs();
 	else
 		resume_devs();
@@ -441,28 +499,122 @@
 
 /* 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 *data)
+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 && dev->is_open(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. */
-void sys_cap_mute_change(void *data)
+static void sys_cap_mute_change(void *context, int muted, int mute_locked)
 {
 	struct cras_iodev *dev;
 
 	DL_FOREACH(devs[CRAS_STREAM_INPUT].iodevs, dev) {
-		if (dev->set_capture_mute && dev->is_open(dev))
+		if (dev->set_capture_mute && cras_iodev_is_open(dev))
 			dev->set_capture_mute(dev);
 	}
 }
 
+static int disable_device(struct enabled_dev *edev);
+static int enable_device(struct cras_iodev *dev);
+
+static void possibly_disable_fallback(enum CRAS_STREAM_DIRECTION dir)
+{
+	struct enabled_dev *edev;
+
+	DL_FOREACH(enabled_devs[dir], edev) {
+		if (edev->dev == fallback_devs[dir])
+			disable_device(edev);
+	}
+}
+
+static void possibly_enable_fallback(enum CRAS_STREAM_DIRECTION dir)
+{
+	if (!cras_iodev_list_dev_is_enabled(fallback_devs[dir]))
+		enable_device(fallback_devs[dir]);
+}
+
+static int init_and_attach_streams(struct cras_iodev *dev)
+{
+	int rc;
+	enum CRAS_STREAM_DIRECTION dir = dev->direction;
+	struct cras_rstream *stream;
+
+	/* If called after suspend, for example bluetooth
+	 * profile switching, don't add back the stream list. */
+	if (!stream_list_suspended) {
+		/* If there are active streams to attach to this device,
+		 * open it. */
+		DL_FOREACH(stream_list_get(stream_list), stream) {
+			if (stream->direction != dir || stream->is_pinned)
+				continue;
+			rc = init_device(dev, stream);
+			if (rc) {
+				syslog(LOG_ERR, "Enable %s failed, rc = %d",
+				       dev->info.name, rc);
+				return rc;
+			}
+			audio_thread_add_stream(audio_thread,
+						stream, &dev, 1);
+		}
+	}
+	return 0;
+}
+
+static void init_device_cb(struct cras_timer *timer, void *arg)
+{
+	int rc;
+	struct enabled_dev *edev = (struct enabled_dev *)arg;
+	struct cras_iodev *dev = edev->dev;
+
+	edev->init_timer = NULL;
+	if (cras_iodev_is_open(dev))
+		return;
+
+	rc = init_and_attach_streams(dev);
+	if (rc < 0)
+		syslog(LOG_ERR, "Init device retry failed");
+	else
+		possibly_disable_fallback(dev->direction);
+}
+
+static void schedule_init_device_retry(struct enabled_dev *edev)
+{
+	struct cras_tm *tm = cras_system_state_get_tm();
+
+	if (edev->init_timer == NULL)
+		edev->init_timer = cras_tm_create_timer(
+				tm, INIT_DEV_DELAY_MS, init_device_cb, edev);
+}
+
+static int pinned_stream_added(struct cras_rstream *rstream)
+{
+	struct cras_iodev *dev;
+	int rc;
+
+	/* Check that the target device is valid for pinned streams. */
+	dev = find_dev(rstream->pinned_dev_idx);
+	if (!dev)
+		return -EINVAL;
+
+	/* Make sure the active node is configured properly, it could be
+	 * disabled when last normal stream removed. */
+	dev->update_active_node(dev, dev->active_node->idx, 1);
+
+	/* Negative EAGAIN code indicates dev will be opened later. */
+	rc = init_device(dev, rstream);
+	if (rc && (rc != -EAGAIN))
+		return rc;
+
+	return audio_thread_add_stream(audio_thread, rstream, &dev, 1);
+}
+
 static int stream_added_cb(struct cras_rstream *rstream)
 {
 	struct enabled_dev *edev;
@@ -473,20 +625,8 @@
 	if (stream_list_suspended)
 		return 0;
 
-	/* Check that the target device is valid for pinned streams. */
-	if (rstream->is_pinned) {
-		struct cras_iodev *dev;
-		dev = find_dev(rstream->pinned_dev_idx);
-		if (!dev)
-			return -EINVAL;
-
-		/* Negative EAGAIN code indicates dev will be opened later. */
-		rc = init_device(dev, rstream);
-		if (rc && (rc != -EAGAIN))
-			return rc;
-
-		return audio_thread_add_stream(audio_thread, rstream, &dev, 1);
-	}
+	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. */
@@ -496,17 +636,39 @@
 			syslog(LOG_ERR, "too many enabled devices");
 			break;
 		}
-		/* Negative EAGAIN code indicates dev will be opened later. */
+
 		rc = init_device(edev->dev, rstream);
-		if (rc && (rc != -EAGAIN))
-			return rc;
+		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);
+			continue;
+		}
 
 		iodevs[num_iodevs++] = edev->dev;
 	}
-	rc = audio_thread_add_stream(audio_thread, rstream, iodevs, num_iodevs);
-	if (rc) {
-		syslog(LOG_ERR, "adding stream to thread fail");
-		return rc;
+	if (num_iodevs) {
+		rc = audio_thread_add_stream(audio_thread, rstream,
+					     iodevs, num_iodevs);
+		if (rc) {
+			syslog(LOG_ERR, "adding stream to thread fail");
+			return rc;
+		}
+	} else {
+		/* Enable fallback device if no other iodevs can be initialized
+		 * 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
+		 * so client won't be blocked.
+		 * The enabled fallback device will be disabled when
+		 * cras_iodev_list_select_node() is called to re-select the
+		 * active node.
+		 */
+		possibly_enable_fallback(rstream->direction);
 	}
 	return 0;
 }
@@ -545,8 +707,10 @@
 	struct cras_iodev *dev;
 
 	dev = find_dev(rstream->pinned_dev_idx);
-	if (!cras_iodev_list_dev_is_enabled(dev))
+	if (!cras_iodev_list_dev_is_enabled(dev)) {
 		close_dev(dev);
+		dev->update_active_node(dev, dev->active_node->idx, 0);
+	}
 }
 
 /* Returns the number of milliseconds left to drain this stream.  This is passed
@@ -568,22 +732,10 @@
 	return 0;
 }
 
-static int disable_device(struct enabled_dev *edev);
-
-static void possibly_disable_fallback(enum CRAS_STREAM_DIRECTION dir)
-{
-	struct enabled_dev *edev;
-
-	DL_FOREACH(enabled_devs[dir], edev) {
-		if (edev->dev == fallback_devs[dir])
-			disable_device(edev);
-	}
-}
-
 static int enable_device(struct cras_iodev *dev)
 {
+	int rc;
 	struct enabled_dev *edev;
-	struct cras_rstream *stream;
 	enum CRAS_STREAM_DIRECTION dir = dev->direction;
 
 	DL_FOREACH(enabled_devs[dir], edev) {
@@ -593,22 +745,16 @@
 
 	edev = calloc(1, sizeof(*edev));
 	edev->dev = dev;
+	edev->init_timer = NULL;
 	DL_APPEND(enabled_devs[dir], edev);
 	dev->is_enabled = 1;
 
-	/* If enable_device is called after suspend, for example bluetooth
-	 * profile switching, don't add back the stream list. */
-	if (!stream_list_suspended) {
-		/* If there are active streams to attach to this device,
-		 * open it. */
-		DL_FOREACH(stream_list_get(stream_list), stream) {
-			if (stream->direction == dir && !stream->is_pinned) {
-				init_device(dev, stream);
-				audio_thread_add_stream(audio_thread,
-							stream, &dev, 1);
-			}
-		}
+	rc = init_and_attach_streams(dev);
+	if (rc < 0) {
+		schedule_init_device_retry(edev);
+		return rc;
 	}
+
 	if (device_enabled_callback)
 		device_enabled_callback(dev, 1, device_enabled_cb_data);
 
@@ -622,6 +768,11 @@
 	struct cras_rstream *stream;
 
 	DL_DELETE(enabled_devs[dir], edev);
+	if (edev->init_timer) {
+		cras_tm_cancel_timer(cras_system_state_get_tm(),
+				     edev->init_timer);
+		edev->init_timer = NULL;
+	}
 	free(edev);
 	dev->is_enabled = 0;
 
@@ -634,6 +785,7 @@
 	if (device_enabled_callback)
 		device_enabled_callback(dev, 0, device_enabled_cb_data);
 	close_dev(dev);
+	dev->update_active_node(dev, dev->active_node->idx, 0);
 
 	return 0;
 }
@@ -644,14 +796,16 @@
 
 void cras_iodev_list_init()
 {
-	cras_system_register_volume_changed_cb(sys_vol_change, NULL);
-	cras_system_register_mute_changed_cb(sys_mute_change, NULL);
-	cras_system_register_suspend_cb(sys_suspend_change, NULL);
-	cras_system_register_capture_gain_changed_cb(sys_cap_gain_change, NULL);
-	cras_system_register_capture_mute_changed_cb(sys_cap_mute_change, NULL);
-	nodes_changed_alert = cras_alert_create(nodes_changed_prepare);
-	active_node_changed_alert = cras_alert_create(
-		active_node_changed_prepare);
+	struct cras_observer_ops observer_ops;
+
+	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;
 
 	/* Create the audio stream list for the system. */
 	stream_list = stream_list_create(stream_added_cb, stream_removed_cb,
@@ -684,20 +838,15 @@
 
 void cras_iodev_list_deinit()
 {
-	cras_system_remove_volume_changed_cb(sys_vol_change, NULL);
-	cras_system_remove_mute_changed_cb(sys_vol_change, NULL);
-	cras_system_remove_suspend_cb(sys_suspend_change, NULL);
-	cras_system_remove_capture_gain_changed_cb(sys_cap_gain_change, NULL);
-	cras_system_remove_capture_mute_changed_cb(sys_cap_mute_change, NULL);
-	cras_alert_destroy(nodes_changed_alert);
-	cras_alert_destroy(active_node_changed_alert);
-	nodes_changed_alert = NULL;
-	active_node_changed_alert = NULL;
+	if (list_observer) {
+		cras_observer_remove(list_observer);
+		list_observer = NULL;
+	}
 	audio_thread_destroy(audio_thread);
 	stream_list_destroy(stream_list);
 }
 
-int cras_iodev_list_dev_is_enabled(struct cras_iodev *dev)
+int cras_iodev_list_dev_is_enabled(const struct cras_iodev *dev)
 {
 	struct enabled_dev *edev;
 
@@ -713,7 +862,7 @@
 {
 	possibly_disable_fallback(dev->direction);
 	enable_device(dev);
-	cras_iodev_list_notify_active_node_changed();
+	cras_iodev_list_notify_active_node_changed(dev->direction);
 }
 
 void cras_iodev_list_add_active_node(enum CRAS_STREAM_DIRECTION dir,
@@ -724,23 +873,36 @@
 	if (!new_dev || new_dev->direction != dir)
 		return;
 
-	new_dev->update_active_node(new_dev, node_index_of(node_id));
+	new_dev->update_active_node(new_dev, node_index_of(node_id), 1);
 	cras_iodev_list_enable_dev(new_dev);
 }
 
 void cras_iodev_list_disable_dev(struct cras_iodev *dev)
 {
-	struct enabled_dev *edev;
+	struct enabled_dev *edev, *edev_to_disable = NULL;
+
+	int is_the_only_enabled_device = 1;
 
 	DL_FOREACH(enabled_devs[dev->direction], edev) {
-		if (edev->dev == dev) {
-			disable_device(edev);
-			if (!enabled_devs[dev->direction])
-				enable_device(fallback_devs[dev->direction]);
-			cras_iodev_list_notify_active_node_changed();
-			return;
-		}
+		if (edev->dev == dev)
+			edev_to_disable = edev;
+		else
+			is_the_only_enabled_device = 0;
 	}
+
+	if (!edev_to_disable)
+		return;
+
+	/* If the device to be closed is the only enabled device, we should
+	 * enable the fallback device first then disable the target
+	 * device. */
+	if (is_the_only_enabled_device)
+		enable_device(fallback_devs[dev->direction]);
+
+	disable_device(edev_to_disable);
+
+	cras_iodev_list_notify_active_node_changed(dev->direction);
+	return;
 }
 
 void cras_iodev_list_rm_active_node(enum CRAS_STREAM_DIRECTION dir,
@@ -866,46 +1028,45 @@
 	cras_system_state_update_complete();
 }
 
-int cras_iodev_list_register_nodes_changed_cb(cras_alert_cb cb, void *arg)
+char *cras_iodev_list_get_hotword_models(cras_node_id_t node_id)
 {
-	return cras_alert_add_callback(nodes_changed_alert, cb, arg);
+	struct cras_iodev *dev = NULL;
+
+	dev = find_dev(dev_index_of(node_id));
+	if (!dev || !dev->get_hotword_models ||
+	    (dev->active_node->type != CRAS_NODE_TYPE_HOTWORD))
+		return NULL;
+
+	return dev->get_hotword_models(dev);
 }
 
-int cras_iodev_list_remove_nodes_changed_cb(cras_alert_cb cb, void *arg)
+int cras_iodev_list_set_hotword_model(cras_node_id_t node_id,
+				      const char *model_name)
 {
-	return cras_alert_rm_callback(nodes_changed_alert, cb, arg);
+	int ret;
+	struct cras_iodev *dev =
+			 find_dev(dev_index_of(node_id));
+	if (!dev || !dev->get_hotword_models ||
+	    (dev->active_node->type != CRAS_NODE_TYPE_HOTWORD))
+		return -EINVAL;
+
+	ret = dev->set_hotword_model(dev, model_name);
+	if (!ret)
+		strncpy(dev->active_node->active_hotword_model, model_name,
+			sizeof(dev->active_node->active_hotword_model) - 1);
+	return ret;
 }
 
 void cras_iodev_list_notify_nodes_changed()
 {
-	cras_alert_pending(nodes_changed_alert);
+	cras_observer_notify_nodes();
 }
 
-static void nodes_changed_prepare(struct cras_alert *alert)
+void cras_iodev_list_notify_active_node_changed(
+		enum CRAS_STREAM_DIRECTION direction)
 {
-	cras_iodev_list_update_device_list();
-}
-
-int cras_iodev_list_register_active_node_changed_cb(cras_alert_cb cb,
-						    void *arg)
-{
-	return cras_alert_add_callback(active_node_changed_alert, cb, arg);
-}
-
-int cras_iodev_list_remove_active_node_changed_cb(cras_alert_cb cb,
-						  void *arg)
-{
-	return cras_alert_rm_callback(active_node_changed_alert, cb, arg);
-}
-
-void cras_iodev_list_notify_active_node_changed()
-{
-	cras_alert_pending(active_node_changed_alert);
-}
-
-static void active_node_changed_prepare(struct cras_alert *alert)
-{
-	cras_iodev_list_update_device_list();
+	cras_observer_notify_active_node(direction,
+			cras_iodev_list_get_active_node_id(direction));
 }
 
 void cras_iodev_list_select_node(enum CRAS_STREAM_DIRECTION direction,
@@ -913,6 +1074,8 @@
 {
 	struct cras_iodev *new_dev = NULL;
 	struct enabled_dev *edev;
+        int new_node_already_enabled = 0;
+	int rc;
 
 	/* find the devices for the id. */
 	new_dev = find_dev(dev_index_of(node_id));
@@ -925,16 +1088,46 @@
 	if (new_dev && new_dev->direction != direction)
 		return;
 
-	DL_FOREACH(enabled_devs[direction], edev)
-		disable_device(edev);
-
-	if (new_dev) {
-		new_dev->update_active_node(new_dev, node_index_of(node_id));
-		enable_device(new_dev);
-	} else {
-		enable_device(fallback_devs[direction]);
+	/* Determine whether the new device and node are already enabled - if
+	 * they are, the selection algorithm should avoid disabling the new
+	 * device. */
+	DL_FOREACH(enabled_devs[direction], edev) {
+		if (edev->dev == new_dev &&
+		    edev->dev->active_node->idx == node_index_of(node_id)) {
+			new_node_already_enabled = 1;
+			break;
+		}
 	}
-	cras_iodev_list_notify_active_node_changed();
+
+	/* Enable fallback device during the transition so client will not be
+	 * blocked in this duration, which is as long as 300 ms on some boards
+	 * before new device is opened.
+	 * Note that the fallback node is not needed if the new node is already
+	 * enabled - the new node will remain enabled. */
+	if (!new_node_already_enabled)
+		possibly_enable_fallback(direction);
+
+	/* 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)) {
+			disable_device(edev);
+		}
+	}
+
+	if (new_dev && !new_node_already_enabled) {
+		new_dev->update_active_node(new_dev, node_index_of(node_id), 1);
+		rc = enable_device(new_dev);
+		if (rc == 0) {
+			/* Disable fallback device after new device is enabled.
+			 * Leave the fallback device enabled if new_dev failed
+			 * to open, or the new_dev == NULL case. */
+			possibly_disable_fallback(direction);
+		}
+	}
+
+	cras_iodev_list_notify_active_node_changed(direction);
 }
 
 int cras_iodev_list_set_node_attr(cras_node_id_t node_id,
@@ -948,45 +1141,29 @@
 		return -EINVAL;
 
 	rc = cras_iodev_set_node_attr(node, attr, value);
-	cras_iodev_list_notify_nodes_changed();
 	return rc;
 }
 
-void cras_iodev_list_set_node_volume_callbacks(node_volume_callback_t volume_cb,
-					       node_volume_callback_t gain_cb)
-{
-	node_volume_callback = volume_cb;
-	node_input_gain_callback = gain_cb;
-}
-
-void cras_iodev_list_set_node_left_right_swapped_callbacks(
-		node_left_right_swapped_callback_t swapped_cb)
-{
-	node_left_right_swapped_callback = swapped_cb;
-}
-
 void cras_iodev_list_notify_node_volume(struct cras_ionode *node)
 {
 	cras_node_id_t id = cras_make_node_id(node->dev->info.idx, node->idx);
-
-	if (node_volume_callback)
-		node_volume_callback(id, node->volume);
+	cras_iodev_list_update_device_list();
+	cras_observer_notify_output_node_volume(id, node->volume);
 }
 
 void cras_iodev_list_notify_node_left_right_swapped(struct cras_ionode *node)
 {
 	cras_node_id_t id = cras_make_node_id(node->dev->info.idx, node->idx);
-
-	if (node_left_right_swapped_callback)
-		node_left_right_swapped_callback(id, node->left_right_swapped);
+	cras_iodev_list_update_device_list();
+	cras_observer_notify_node_left_right_swapped(id,
+						     node->left_right_swapped);
 }
 
 void cras_iodev_list_notify_node_capture_gain(struct cras_ionode *node)
 {
 	cras_node_id_t id = cras_make_node_id(node->dev->info.idx, node->idx);
-
-	if (node_input_gain_callback)
-		node_input_gain_callback(id, node->capture_gain);
+	cras_iodev_list_update_device_list();
+	cras_observer_notify_input_node_gain(id, node->capture_gain);
 }
 
 void cras_iodev_list_add_test_dev(enum TEST_IODEV_TYPE type)
@@ -1022,6 +1199,12 @@
 int cras_iodev_list_set_device_enabled_callback(
 		device_enabled_callback_t device_enabled_cb, void *cb_data)
 {
+	if (!device_enabled_cb) {
+		device_enabled_callback = NULL;
+		device_enabled_cb_data = NULL;
+		return 0;
+	}
+
 	/* TODO(chinyue): Allow multiple callbacks to be registered. */
 	if (device_enabled_callback) {
 		syslog(LOG_ERR, "Device enabled callback already registered.");
diff --git a/cras/src/server/cras_iodev_list.h b/cras/src/server/cras_iodev_list.h
index 1c6af8a..3599f1b 100644
--- a/cras/src/server/cras_iodev_list.h
+++ b/cras/src/server/cras_iodev_list.h
@@ -11,7 +11,6 @@
 
 #include <stdint.h>
 
-#include "cras_alert.h"
 #include "cras_types.h"
 
 struct cras_iodev;
@@ -22,9 +21,6 @@
 struct cras_audio_format;
 struct stream_list;
 
-typedef void (*node_volume_callback_t)(cras_node_id_t, int);
-typedef void (*node_left_right_swapped_callback_t)(cras_node_id_t, int);
-
 /* Device enabled/disabled callback.
  * enabled=1 when a device is enabled, enabled=0 when a device is disabled.
  */
@@ -117,41 +113,23 @@
 /* Stores the node list in the shared memory server state region. */
 void cras_iodev_list_update_node_list();
 
-/* Adds a callback to call when the nodes are added/removed.
- * Args:
- *    cb - Function to call when there is a change.
- *    arg - Value to pass back to callback.
- */
-int cras_iodev_list_register_nodes_changed_cb(cras_alert_cb cb, void *arg);
+/* Gets the supported hotword models of an ionode. Caller should free
+ * the returned string after use. */
+char *cras_iodev_list_get_hotword_models(cras_node_id_t node_id);
 
-/* Removes a callback to call when the nodes are added/removed.
- * Args:
- *    cb - Function to call when there is a change.
- *    arg - Value to pass back to callback.
- */
-int cras_iodev_list_remove_nodes_changed_cb(cras_alert_cb cb, void *arg);
+/* Sets the desired hotword model to an ionode. */
+int cras_iodev_list_set_hotword_model(cras_node_id_t id,
+				      const char *model_name);
 
 /* Notify that nodes are added/removed. */
 void cras_iodev_list_notify_nodes_changed();
 
-/* Adds a callback to call when the active output/input node changes.
+/* Notify that active node is changed for the given direction.
  * Args:
- *    cb - Function to call when there is a change.
- *    arg - Value to pass back to callback.
+ *    direction - Direction of the node.
  */
-int cras_iodev_list_register_active_node_changed_cb(cras_alert_cb cb,
-						    void *arg);
-
-/* Removes a callback to call when the active output/input node changes.
- * Args:
- *    cb - Function to call when there is a change.
- *    arg - Value to pass back to callback.
- */
-int cras_iodev_list_remove_active_node_changed_cb(cras_alert_cb cb,
-						  void *arg);
-
-/* Notify that active output/input node is changed. */
-void cras_iodev_list_notify_active_node_changed();
+void cras_iodev_list_notify_active_node_changed(
+		enum CRAS_STREAM_DIRECTION direction);
 
 /* Sets an attribute of an ionode on a device.
  * Args:
@@ -173,7 +151,7 @@
 				 cras_node_id_t node_id);
 
 /* Checks if an iodev is enabled. */
-int cras_iodev_list_dev_is_enabled(struct cras_iodev *dev);
+int cras_iodev_list_dev_is_enabled(const struct cras_iodev *dev);
 
 /* Enables an iodev. If the fallback device was already enabled, this
  * call will disable it. */
@@ -202,21 +180,12 @@
 /* Returns 1 if the node is selected, 0 otherwise. */
 int cras_iodev_list_node_selected(struct cras_ionode *node);
 
-/* Sets the function to call when a node volume changes. */
-void cras_iodev_list_set_node_volume_callbacks(node_volume_callback_t volume_cb,
-					       node_volume_callback_t gain_cb);
-
 /* Notify the current volume of the given node. */
 void cras_iodev_list_notify_node_volume(struct cras_ionode *node);
 
 /* Notify the current capture gain of the given node. */
 void cras_iodev_list_notify_node_capture_gain(struct cras_ionode *node);
 
-/* Sets the function to call when a node's left right channel swapping state
- * is changes. */
-void cras_iodev_list_set_node_left_right_swapped_callbacks(
-				node_left_right_swapped_callback_t swapped_cb);
-
 /* Notify the current left right channel swapping state of the given node. */
 void cras_iodev_list_notify_node_left_right_swapped(struct cras_ionode *node);
 
diff --git a/cras/src/server/cras_loopback_iodev.c b/cras/src/server/cras_loopback_iodev.c
index bdc21f7..ff33aca 100644
--- a/cras/src/server/cras_loopback_iodev.c
+++ b/cras/src/server/cras_loopback_iodev.c
@@ -38,13 +38,11 @@
 };
 
 /* loopack iodev.  Keep state of a loopback device.
- *    open - Is the device open.
  *    sample_buffer - Pointer to sample buffer.
  */
 struct loopback_iodev {
 	struct cras_iodev base;
 	enum CRAS_LOOPBACK_TYPE loopback_type;
-	int open;
 	struct timespec last_filled;
 	struct byte_buffer *sample_buffer;
 };
@@ -119,19 +117,8 @@
  * iodev callbacks.
  */
 
-static int is_open(const struct cras_iodev *iodev)
-{
-	struct loopback_iodev *loopdev = (struct loopback_iodev *)iodev;
-
-	return loopdev && loopdev->open;
-}
-
-static int dev_running(const struct cras_iodev *iodev)
-{
-	return is_open(iodev);
-}
-
-static int frames_queued(const struct cras_iodev *iodev)
+static int frames_queued(const struct cras_iodev *iodev,
+			 struct timespec *hw_tstamp)
 {
 	struct loopback_iodev *loopdev = (struct loopback_iodev *)iodev;
 	struct byte_buffer *sbuf = loopdev->sample_buffer;
@@ -155,13 +142,15 @@
 				      &loopdev->last_filled);
 		}
 	}
-
+	*hw_tstamp = loopdev->last_filled;
 	return buf_queued_bytes(sbuf) / frame_bytes;
 }
 
 static int delay_frames(const struct cras_iodev *iodev)
 {
-	return frames_queued(iodev);
+	struct timespec tstamp;
+
+	return frames_queued(iodev, &tstamp);
 }
 
 static int close_record_dev(struct cras_iodev *iodev)
@@ -170,7 +159,6 @@
 	struct byte_buffer *sbuf = loopdev->sample_buffer;
 	struct cras_iodev *edev;
 
-	loopdev->open = 0;
 	cras_iodev_free_format(iodev);
 	cras_iodev_free_audio_area(iodev);
 	buf_reset(sbuf);
@@ -188,7 +176,6 @@
 	struct cras_iodev *edev;
 
 	cras_iodev_init_audio_area(iodev, iodev->format->num_channels);
-	loopdev->open = 1;
 	clock_gettime(CLOCK_MONOTONIC_RAW, &loopdev->last_filled);
 
 	edev = cras_iodev_list_get_first_enabled_iodev(CRAS_STREAM_OUTPUT);
@@ -207,8 +194,9 @@
 	struct loopback_iodev *loopdev = (struct loopback_iodev *)iodev;
 	struct byte_buffer *sbuf = loopdev->sample_buffer;
 	unsigned int frame_bytes = cras_get_format_bytes(iodev->format);
+	unsigned int avail_frames = buf_readable_bytes(sbuf) / frame_bytes;
 
-	*frames = buf_readable_bytes(sbuf) / frame_bytes;
+	*frames = MIN(avail_frames, *frames);
 	iodev->area->frames = *frames;
 	cras_audio_area_config_buf_pointers(iodev->area, iodev->format,
 					    buf_read_pointer(sbuf));
@@ -237,7 +225,8 @@
 	return 0;
 }
 
-static void update_active_node(struct cras_iodev *iodev, unsigned node_idx)
+static void update_active_node(struct cras_iodev *iodev, unsigned node_idx,
+			       unsigned dev_enabled)
 {
 }
 
@@ -266,14 +255,13 @@
 	iodev->info.stable_id = SuperFastHash(iodev->info.name,
 					      strlen(iodev->info.name),
 					      strlen(iodev->info.name));
+	iodev->info.stable_id_new = iodev->info.stable_id;
 
 	iodev->supported_rates = loopback_supported_rates;
 	iodev->supported_channel_counts = loopback_supported_channel_counts;
 	iodev->supported_formats = loopback_supported_formats;
 	iodev->buffer_size = LOOPBACK_BUFFER_SIZE;
 
-	iodev->is_open = is_open;
-	iodev->dev_running = dev_running;
 	iodev->frames_queued = frames_queued;
 	iodev->delay_frames = delay_frames;
 	iodev->update_active_node = update_active_node;
@@ -317,6 +305,10 @@
 	node->type = node_type;
 	node->plugged = 1;
 	node->volume = 100;
+	node->stable_id = iodev->info.stable_id;
+	node->stable_id_new = iodev->info.stable_id_new;
+	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 950d0db..1187102 100644
--- a/cras/src/server/cras_main_message.c
+++ b/cras/src/server/cras_main_message.c
@@ -114,4 +114,4 @@
 	cras_system_add_select_fd(main_msg_fds[0],
 				  handle_main_messages,
 				  NULL);
-}
\ No newline at end of file
+}
diff --git a/cras/src/server/cras_main_message.h b/cras/src/server/cras_main_message.h
index f4267c1..ba6b02a 100644
--- a/cras/src/server/cras_main_message.h
+++ b/cras/src/server/cras_main_message.h
@@ -16,6 +16,7 @@
 	CRAS_MAIN_A2DP,
 	CRAS_MAIN_BT,
 	CRAS_MAIN_METRICS,
+	CRAS_MAIN_MONITOR_DEVICE,
 };
 
 /* Structure of the header of the message handled by main thread.
@@ -43,4 +44,4 @@
 /* Initialize the message handling mechanism in main thread. */
 void cras_main_message_init();
 
-#endif /* CRAS_MAIN_MESSAGE_H_ */
\ No newline at end of file
+#endif /* CRAS_MAIN_MESSAGE_H_ */
diff --git a/cras/src/server/cras_mix.c b/cras/src/server/cras_mix.c
index d2d5e3c..b0694fd 100644
--- a/cras/src/server/cras_mix.c
+++ b/cras/src/server/cras_mix.c
@@ -6,591 +6,75 @@
 #include <stdint.h>
 
 #include "cras_system_state.h"
+#include "cras_mix.h"
+#include "cras_mix_ops.h"
 
-#define MAX_VOLUME_TO_SCALE 0.9999999
-#define MIN_VOLUME_TO_SCALE 0.0000001
+static const struct cras_mix_ops *ops = &mixer_ops;
 
-/*
- * Signed 16 bit little endian functions.
- */
-
-static void cras_mix_add_clip_s16_le(int16_t *dst,
-				     const int16_t *src,
-				     size_t count)
+static const struct cras_mix_ops* get_mixer_ops(unsigned int cpu_flags)
 {
-	int32_t sum;
-	size_t i;
+#if defined HAVE_FMA
+	if (cpu_flags & CPU_X86_FMA)
+		return &mixer_ops_fma;
+#endif
+#if defined HAVE_AVX2
+	if (cpu_flags & CPU_X86_AVX2)
+		return &mixer_ops_avx2;
+#endif
+#if defined HAVE_AVX
+	if (cpu_flags & CPU_X86_AVX)
+		return &mixer_ops_avx;
+#endif
+#if defined HAVE_SSE42
+	if (cpu_flags & CPU_X86_SSE4_2)
+		return &mixer_ops_sse42;
+#endif
 
-	for (i = 0; i < count; i++) {
-		sum = dst[i] + src[i];
-		if (sum > INT16_MAX)
-			sum = INT16_MAX;
-		else if (sum < INT16_MIN)
-			sum = INT16_MIN;
-		dst[i] = sum;
-	}
+	/* default C implementation */
+	return &mixer_ops;
 }
 
-/* Adds src into dst, after scaling by vol.
- * Just hard limits to the min and max S16 value, can be improved later. */
-static void scale_add_clip_s16_le(int16_t *dst,
-				  const int16_t *src,
-				  size_t count,
-				  float vol)
+void cras_mix_init(unsigned int flags)
 {
-	int32_t sum;
-	size_t i;
-
-	if (vol > MAX_VOLUME_TO_SCALE)
-		return cras_mix_add_clip_s16_le(dst, src, count);
-
-	for (i = 0; i < count; i++) {
-		sum = dst[i] + (int16_t)(src[i] * vol);
-		if (sum > INT16_MAX)
-			sum = INT16_MAX;
-		else if (sum < INT16_MIN)
-			sum = INT16_MIN;
-		dst[i] = sum;
-	}
+	ops = get_mixer_ops(flags);
 }
 
-/* Adds the first stream to the mix.  Don't need to mix, just setup to the new
- * values. If volume is 1.0, just memcpy. */
-static void copy_scaled_s16_le(int16_t *dst,
-			       const int16_t *src,
-			       size_t count,
-			       float volume_scaler)
-{
-	int i;
-
-	if (volume_scaler > MAX_VOLUME_TO_SCALE) {
-		memcpy(dst, src, count * sizeof(*src));
-		return;
-	}
-
-	for (i = 0; i < count; i++)
-		dst[i] = src[i] * volume_scaler;
-}
-
-static void cras_scale_buffer_s16_le(uint8_t *buffer, unsigned int count,
-				     float scaler)
-{
-	int i;
-	int16_t *out = (int16_t *)buffer;
-
-	if (scaler > MAX_VOLUME_TO_SCALE)
-		return;
-
-	if (scaler < MIN_VOLUME_TO_SCALE) {
-		memset(out, 0, count * sizeof(*out));
-		return;
-	}
-
-	for (i = 0; i < count; i++)
-		out[i] *= scaler;
-}
-
-static void cras_mix_add_s16_le(uint8_t *dst, uint8_t *src,
-				unsigned int count, unsigned int index,
-				int mute, float mix_vol)
-{
-	int16_t *out = (int16_t *)dst;
-	int16_t *in = (int16_t *)src;
-
-	if (mute || (mix_vol < MIN_VOLUME_TO_SCALE)) {
-		if (index == 0)
-			memset(out, 0, count * sizeof(*out));
-		return;
-	}
-
-	if (index == 0)
-		return copy_scaled_s16_le(out, in, count, mix_vol);
-
-	scale_add_clip_s16_le(out, in, count, mix_vol);
-}
-
-void cras_mix_add_stride_s16_le(uint8_t *dst, uint8_t *src,
-				unsigned int dst_stride,
-				unsigned int src_stride,
-				unsigned int count)
-{
-	unsigned int i;
-
-	for (i = 0; i < count; i++) {
-		int32_t sum;
-		sum = *(int16_t *)dst + *(int16_t *)src;
-		if (sum > INT16_MAX)
-			sum = INT16_MAX;
-		else if (sum < INT16_MIN)
-			sum = INT16_MIN;
-		*(int16_t*)dst = sum;
-		dst += dst_stride;
-		src += src_stride;
-	}
-}
-
-/*
- * Signed 24 bit little endian functions.
- */
-
-static void cras_mix_add_clip_s24_le(int32_t *dst,
-				     const int32_t *src,
-				     size_t count)
-{
-	int32_t sum;
-	size_t i;
-
-	for (i = 0; i < count; i++) {
-		sum = dst[i] + src[i];
-		if (sum > 0x007fffff)
-			sum = 0x007fffff;
-		else if (sum < (int32_t)0xff800000)
-			sum = (int32_t)0xff800000;
-		dst[i] = sum;
-	}
-}
-
-/* Adds src into dst, after scaling by vol.
- * Just hard limits to the min and max S24 value, can be improved later. */
-static void scale_add_clip_s24_le(int32_t *dst,
-				  const int32_t *src,
-				  size_t count,
-				  float vol)
-{
-	int32_t sum;
-	size_t i;
-
-	if (vol > MAX_VOLUME_TO_SCALE)
-		return cras_mix_add_clip_s24_le(dst, src, count);
-
-	for (i = 0; i < count; i++) {
-		sum = dst[i] + (int32_t)(src[i] * vol);
-		if (sum > 0x007fffff)
-			sum = 0x007fffff;
-		else if (sum < (int32_t)0xff800000)
-			sum = (int32_t)0xff800000;
-		dst[i] = sum;
-	}
-}
-
-/* Adds the first stream to the mix.  Don't need to mix, just setup to the new
- * values. If volume is 1.0, just memcpy. */
-static void copy_scaled_s24_le(int32_t *dst,
-			       const int32_t *src,
-			       size_t count,
-			       float volume_scaler)
-{
-	int i;
-
-	if (volume_scaler > MAX_VOLUME_TO_SCALE) {
-		memcpy(dst, src, count * sizeof(*src));
-		return;
-	}
-
-	for (i = 0; i < count; i++)
-		dst[i] = src[i] * volume_scaler;
-}
-
-static void cras_scale_buffer_s24_le(uint8_t *buffer, unsigned int count,
-				     float scaler)
-{
-	int i;
-	int32_t *out = (int32_t *)buffer;
-
-	if (scaler > MAX_VOLUME_TO_SCALE)
-		return;
-
-	if (scaler < MIN_VOLUME_TO_SCALE) {
-		memset(out, 0, count * sizeof(*out));
-		return;
-	}
-
-	for (i = 0; i < count; i++)
-		out[i] *= scaler;
-}
-
-static void cras_mix_add_s24_le(uint8_t *dst, uint8_t *src,
-				unsigned int count, unsigned int index,
-				int mute, float mix_vol)
-{
-	int32_t *out = (int32_t *)dst;
-	int32_t *in = (int32_t *)src;
-
-	if (mute || (mix_vol < MIN_VOLUME_TO_SCALE)) {
-		if (index == 0)
-			memset(out, 0, count * sizeof(*out));
-		return;
-	}
-
-	if (index == 0)
-		return copy_scaled_s24_le(out, in, count, mix_vol);
-
-	scale_add_clip_s24_le(out, in, count, mix_vol);
-}
-
-void cras_mix_add_stride_s24_le(uint8_t *dst, uint8_t *src,
-				unsigned int dst_stride,
-				unsigned int src_stride,
-				unsigned int count)
-{
-	unsigned int i;
-
-	for (i = 0; i < count; i++) {
-		int32_t sum;
-		sum = *(int32_t *)dst + *(int32_t *)src;
-		if (sum > 0x007fffff)
-			sum = 0x007fffff;
-		else if (sum < (int32_t)0xff800000)
-			sum = (int32_t)0xff800000;
-		*(int32_t*)dst = sum;
-		dst += dst_stride;
-		src += src_stride;
-	}
-}
-
-/*
- * Signed 32 bit little endian functions.
- */
-
-static void cras_mix_add_clip_s32_le(int32_t *dst,
-				     const int32_t *src,
-				     size_t count)
-{
-	int64_t sum;
-	size_t i;
-
-	for (i = 0; i < count; i++) {
-		sum = (int64_t)dst[i] + (int64_t)src[i];
-		if (sum > INT32_MAX)
-			sum = INT32_MAX;
-		else if (sum < INT32_MIN)
-			sum = INT32_MIN;
-		dst[i] = sum;
-	}
-}
-
-/* Adds src into dst, after scaling by vol.
- * Just hard limits to the min and max S32 value, can be improved later. */
-static void scale_add_clip_s32_le(int32_t *dst,
-				  const int32_t *src,
-				  size_t count,
-				  float vol)
-{
-	int64_t sum;
-	size_t i;
-
-	if (vol > MAX_VOLUME_TO_SCALE)
-		return cras_mix_add_clip_s32_le(dst, src, count);
-
-	for (i = 0; i < count; i++) {
-		sum = (int64_t)dst[i] + (int64_t)(src[i] * vol);
-		if (sum > INT32_MAX)
-			sum = INT32_MAX;
-		else if (sum < INT32_MIN)
-			sum = INT32_MIN;
-		dst[i] = sum;
-	}
-}
-
-/* Adds the first stream to the mix.  Don't need to mix, just setup to the new
- * values. If volume is 1.0, just memcpy. */
-static void copy_scaled_s32_le(int32_t *dst,
-			       const int32_t *src,
-			       size_t count,
-			       float volume_scaler)
-{
-	int i;
-
-	if (volume_scaler > MAX_VOLUME_TO_SCALE) {
-		memcpy(dst, src, count * sizeof(*src));
-		return;
-	}
-
-	for (i = 0; i < count; i++)
-		dst[i] = src[i] * volume_scaler;
-}
-
-static void cras_scale_buffer_s32_le(uint8_t *buffer, unsigned int count,
-				     float scaler)
-{
-	int i;
-	int32_t *out = (int32_t *)buffer;
-
-	if (scaler > MAX_VOLUME_TO_SCALE)
-		return;
-
-	if (scaler < MIN_VOLUME_TO_SCALE) {
-		memset(out, 0, count * sizeof(*out));
-		return;
-	}
-
-	for (i = 0; i < count; i++)
-		out[i] *= scaler;
-}
-
-static void cras_mix_add_s32_le(uint8_t *dst, uint8_t *src,
-				unsigned int count, unsigned int index,
-				int mute, float mix_vol)
-{
-	int32_t *out = (int32_t *)dst;
-	int32_t *in = (int32_t *)src;
-
-	if (mute || (mix_vol < MIN_VOLUME_TO_SCALE)) {
-		if (index == 0)
-			memset(out, 0, count * sizeof(*out));
-		return;
-	}
-
-	if (index == 0)
-		return copy_scaled_s32_le(out, in, count, mix_vol);
-
-	scale_add_clip_s32_le(out, in, count, mix_vol);
-}
-
-void cras_mix_add_stride_s32_le(uint8_t *dst, uint8_t *src,
-				unsigned int dst_stride,
-				unsigned int src_stride,
-				unsigned int count)
-{
-	unsigned int i;
-
-	for (i = 0; i < count; i++) {
-		int64_t sum;
-		sum = *(int32_t *)dst + *(int32_t *)src;
-		if (sum > INT32_MAX)
-			sum = INT32_MAX;
-		else if (sum < INT32_MIN)
-			sum = INT32_MIN;
-		*(int32_t*)dst = sum;
-		dst += dst_stride;
-		src += src_stride;
-	}
-}
-
-/*
- * Signed 24 bit little endian in three bytes functions.
- */
-
-/* Convert 3bytes Signed 24bit integer to a Signed 32bit integer.
- * Just a helper function. */
-static inline void convert_single_s243le_to_s32le(int32_t *dst,
-						  const uint8_t *src)
-{
-	*dst = 0;
-	memcpy((uint8_t *)dst + 1, src, 3);
-}
-
-static inline void convert_single_s32le_to_s243le(uint8_t *dst,
-						  const int32_t *src)
-{
-	memcpy(dst, (uint8_t *)src + 1, 3);
-}
-
-static void cras_mix_add_clip_s24_3le(uint8_t *dst,
-				      const uint8_t *src,
-				      size_t count)
-{
-	int64_t sum;
-	int32_t dst_frame;
-	int32_t src_frame;
-	size_t i;
-
-	for (i = 0; i < count; i++, dst += 3, src += 3) {
-		convert_single_s243le_to_s32le(&dst_frame, dst);
-		convert_single_s243le_to_s32le(&src_frame, src);
-		sum = (int64_t)dst_frame + (int64_t)src_frame;
-		if (sum > INT32_MAX)
-			sum = INT32_MAX;
-		else if (sum < INT32_MIN)
-			sum = INT32_MIN;
-		dst_frame = (int32_t)sum;
-		convert_single_s32le_to_s243le(dst, &dst_frame);
-	}
-}
-
-/* Adds src into dst, after scaling by vol.
- * Just hard limits to the min and max S24 value, can be improved later. */
-static void scale_add_clip_s24_3le(uint8_t *dst,
-				   const uint8_t *src,
-				   size_t count,
-				   float vol)
-{
-	int64_t sum;
-	int32_t dst_frame;
-	int32_t src_frame;
-	size_t i;
-
-	if (vol > MAX_VOLUME_TO_SCALE)
-		return cras_mix_add_clip_s24_3le(dst, src, count);
-
-	for (i = 0; i < count; i++, dst += 3, src += 3) {
-		convert_single_s243le_to_s32le(&dst_frame, dst);
-		convert_single_s243le_to_s32le(&src_frame, src);
-		sum = (int64_t)dst_frame + (int64_t)(src_frame * vol);
-		if (sum > INT32_MAX)
-			sum = INT32_MAX;
-		else if (sum < INT32_MIN)
-			sum = INT32_MIN;
-		dst_frame = (int32_t)sum;
-		convert_single_s32le_to_s243le(dst, &dst_frame);
-	}
-}
-
-/* Adds the first stream to the mix.  Don't need to mix, just setup to the new
- * values. If volume is 1.0, just memcpy. */
-static void copy_scaled_s24_3le(uint8_t *dst,
-			        const uint8_t *src,
-			        size_t count,
-			        float volume_scaler)
-{
-	int32_t frame;
-	size_t i;
-
-	if (volume_scaler > MAX_VOLUME_TO_SCALE) {
-		memcpy(dst, src, 3 * count * sizeof(*src));
-		return;
-	}
-
-	for (i = 0; i < count; i++, dst += 3, src += 3) {
-		convert_single_s243le_to_s32le(&frame, src);
-		frame *= volume_scaler;
-		convert_single_s32le_to_s243le(dst, &frame);
-	}
-}
-
-static void cras_scale_buffer_s24_3le(uint8_t *buffer, unsigned int count,
-				      float scaler)
-{
-	int32_t frame;
-	int i;
-
-	if (scaler > MAX_VOLUME_TO_SCALE)
-		return;
-
-	if (scaler < MIN_VOLUME_TO_SCALE) {
-		memset(buffer, 0, 3 * count * sizeof(*buffer));
-		return;
-	}
-
-	for (i = 0; i < count; i++, buffer += 3) {
-		convert_single_s243le_to_s32le(&frame, buffer);
-		frame *= scaler;
-		convert_single_s32le_to_s243le(buffer, &frame);
-	}
-}
-
-static void cras_mix_add_s24_3le(uint8_t *dst, uint8_t *src,
-				 unsigned int count, unsigned int index,
-				 int mute, float mix_vol)
-{
-	uint8_t *out = dst;
-	uint8_t *in = src;
-
-	if (mute || (mix_vol < MIN_VOLUME_TO_SCALE)) {
-		if (index == 0)
-			memset(out, 0, 3 * count * sizeof(*out));
-		return;
-	}
-
-	if (index == 0)
-		return copy_scaled_s24_3le(out, in, count, mix_vol);
-
-	scale_add_clip_s24_3le(out, in, count, mix_vol);
-}
-
-void cras_mix_add_stride_s24_3le(uint8_t *dst, uint8_t *src,
-				 unsigned int dst_stride,
-				 unsigned int src_stride,
-				 unsigned int count)
-{
-	unsigned int i;
-	int64_t sum;
-	int32_t dst_frame;
-	int32_t src_frame;
-
-	for (i = 0; i < count; i++) {
-		convert_single_s243le_to_s32le(&dst_frame, dst);
-		convert_single_s243le_to_s32le(&src_frame, src);
-		sum = (int64_t)dst_frame + (int64_t)src_frame;
-		if (sum > INT32_MAX)
-			sum = INT32_MAX;
-		else if (sum < INT32_MIN)
-			sum = INT32_MIN;
-		dst_frame = (int32_t)sum;
-		convert_single_s32le_to_s243le(dst, &dst_frame);
-		dst += dst_stride;
-		src += src_stride;
-	}
-}
 /*
  * Exported Interface
  */
 
+void cras_scale_buffer_increment(snd_pcm_format_t fmt, uint8_t *buff,
+				 unsigned int frame, float scaler,
+				 float increment, int channel)
+{
+	ops->scale_buffer_increment(fmt, buff, frame * channel, scaler,
+				    increment, channel);
+}
+
 void cras_scale_buffer(snd_pcm_format_t fmt, uint8_t *buff, unsigned int count,
 		       float scaler)
 {
-	switch (fmt) {
-	case SND_PCM_FORMAT_S16_LE:
-		return cras_scale_buffer_s16_le(buff, count, scaler);
-	case SND_PCM_FORMAT_S24_LE:
-		return cras_scale_buffer_s24_le(buff, count, scaler);
-	case SND_PCM_FORMAT_S32_LE:
-		return cras_scale_buffer_s32_le(buff, count, scaler);
-	case SND_PCM_FORMAT_S24_3LE:
-		return cras_scale_buffer_s24_3le(buff, count, scaler);
-	default:
-		break;
-	}
+	ops->scale_buffer(fmt, buff, count, scaler);
 }
 
 void cras_mix_add(snd_pcm_format_t fmt, uint8_t *dst, uint8_t *src,
 		  unsigned int count, unsigned int index,
 		  int mute, float mix_vol)
 {
-	switch (fmt) {
-	case SND_PCM_FORMAT_S16_LE:
-		return cras_mix_add_s16_le(dst, src, count, index, mute,
-					   mix_vol);
-	case SND_PCM_FORMAT_S24_LE:
-		return cras_mix_add_s24_le(dst, src, count, index, mute,
-					   mix_vol);
-	case SND_PCM_FORMAT_S32_LE:
-		return cras_mix_add_s32_le(dst, src, count, index, mute,
-					   mix_vol);
-	case SND_PCM_FORMAT_S24_3LE:
-		return cras_mix_add_s24_3le(dst, src, count, index, mute,
-					    mix_vol);
-	default:
-		break;
-	}
+	ops->add(fmt, dst, src, count, index, mute, mix_vol);
 }
 
-void cras_mix_add_stride(snd_pcm_format_t fmt, uint8_t *dst, uint8_t *src,
+void cras_mix_add_scale_stride(snd_pcm_format_t fmt, uint8_t *dst, uint8_t *src,
 			 unsigned int count, unsigned int dst_stride,
-			 unsigned int src_stride)
+			 unsigned int src_stride, float scaler)
 {
-	switch (fmt) {
-	case SND_PCM_FORMAT_S16_LE:
-		return cras_mix_add_stride_s16_le(dst, src, dst_stride,
-						  src_stride, count);
-	case SND_PCM_FORMAT_S24_LE:
-		return cras_mix_add_stride_s24_le(dst, src, dst_stride,
-						  src_stride, count);
-	case SND_PCM_FORMAT_S32_LE:
-		return cras_mix_add_stride_s32_le(dst, src, dst_stride,
-						  src_stride, count);
-	case SND_PCM_FORMAT_S24_3LE:
-		return cras_mix_add_stride_s24_3le(dst, src, dst_stride,
-						   src_stride, count);
-	default:
-		break;
-	}
+	ops->add_scale_stride(fmt, dst, src, count, dst_stride, src_stride,
+			      scaler);
 }
 
 size_t cras_mix_mute_buffer(uint8_t *dst,
 			    size_t frame_bytes,
 			    size_t count)
 {
-	memset(dst, 0, count * frame_bytes);
-	return count;
+	return ops->mute_buffer(dst, frame_bytes, count);
 }
diff --git a/cras/src/server/cras_mix.h b/cras/src/server/cras_mix.h
index 0013b3a..c16614a 100644
--- a/cras/src/server/cras_mix.h
+++ b/cras/src/server/cras_mix.h
@@ -8,6 +8,28 @@
 
 struct cras_audio_shm;
 
+/* SIMD optimisation flags */
+#define CPU_X86_SSE4_2		1
+#define CPU_X86_AVX			2
+#define CPU_X86_AVX2			4
+#define CPU_X86_FMA			8
+
+void cras_mix_init(unsigned int flags);
+
+/* Scale the given buffer with the provided scaler and increment.
+ * Args:
+ *    fmt - The format (SND_PCM_FORMAT_*)
+ *    buff - Buffer of samples to scale.
+ *    frame - The number of frames to render.
+ *    scaler - Amount to scale samples (0.0 - 1.0).
+ *    increment - The increment(+/-) of scaler at each frame. The scaler after
+ *                increasing/descreasing will be clipped to (0.0 - 1.0).
+ *    channel - Number of samples in a frame.
+ */
+void cras_scale_buffer_increment(snd_pcm_format_t fmt, uint8_t *buff,
+				 unsigned int frame, float scaler,
+				 float increment, int channel);
+
 /* Scale the given buffer with the provided scaler.
  * Args:
  *    fmt - The format (SND_PCM_FORMAT_*)
@@ -41,10 +63,11 @@
  *    count - The number of samples to mix.
  *    dst_stride - Stride between channel samples in dst in bytes.
  *    src_stride - Stride between channel samples in src in bytes.
+ *    scaler - Amount to scale samples.
  */
-void cras_mix_add_stride(snd_pcm_format_t fmt, uint8_t *dst, uint8_t *src,
+void cras_mix_add_scale_stride(snd_pcm_format_t fmt, uint8_t *dst, uint8_t *src,
 			 unsigned int count, unsigned int dst_stride,
-			 unsigned int src_stride);
+			 unsigned int src_stride, float scaler);
 
 /* Mutes the given buffer.
  * Args:
diff --git a/cras/src/server/cras_mix_ops.c b/cras/src/server/cras_mix_ops.c
new file mode 100644
index 0000000..f12f783
--- /dev/null
+++ b/cras/src/server/cras_mix_ops.c
@@ -0,0 +1,862 @@
+/* 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 <stdint.h>
+
+#include "cras_system_state.h"
+#include "cras_mix_ops.h"
+
+#define MAX_VOLUME_TO_SCALE 0.9999999
+#define MIN_VOLUME_TO_SCALE 0.0000001
+
+/* function suffixes for SIMD ops */
+#ifdef OPS_SSE42
+	#define OPS(a) a ## _sse42
+#elif OPS_AVX
+	#define OPS(a) a ## _avx
+#elif OPS_AVX2
+	#define OPS(a) a ## _avx2
+#elif OPS_FMA
+	#define OPS(a) a ## _fma
+#else
+	#define OPS(a) a
+#endif
+
+/* Checks if the scaler needs a scaling operation.
+ * We skip scaling for scaler too close to 1.0.
+ * Note that this is not subjected to MAX_VOLUME_TO_SCALE
+ * and MIN_VOLUME_TO_SCALE. */
+static inline int need_to_scale(float scaler) {
+	return (scaler < 0.99 || scaler > 1.01);
+}
+
+/*
+ * Signed 16 bit little endian functions.
+ */
+
+static void cras_mix_add_clip_s16_le(int16_t *dst,
+				     const int16_t *src,
+				     size_t count)
+{
+	int32_t sum;
+	size_t i;
+
+	for (i = 0; i < count; i++) {
+		sum = dst[i] + src[i];
+		if (sum > INT16_MAX)
+			sum = INT16_MAX;
+		else if (sum < INT16_MIN)
+			sum = INT16_MIN;
+		dst[i] = sum;
+	}
+}
+
+/* Adds src into dst, after scaling by vol.
+ * Just hard limits to the min and max S16 value, can be improved later. */
+static void scale_add_clip_s16_le(int16_t *dst,
+				  const int16_t *src,
+				  size_t count,
+				  float vol)
+{
+	int32_t sum;
+	size_t i;
+
+	if (vol > MAX_VOLUME_TO_SCALE)
+		return cras_mix_add_clip_s16_le(dst, src, count);
+
+	for (i = 0; i < count; i++) {
+		sum = dst[i] + (int16_t)(src[i] * vol);
+		if (sum > INT16_MAX)
+			sum = INT16_MAX;
+		else if (sum < INT16_MIN)
+			sum = INT16_MIN;
+		dst[i] = sum;
+	}
+}
+
+/* Adds the first stream to the mix.  Don't need to mix, just setup to the new
+ * values. If volume is 1.0, just memcpy. */
+static void copy_scaled_s16_le(int16_t *dst,
+			       const int16_t *src,
+			       size_t count,
+			       float volume_scaler)
+{
+	int i;
+
+	if (volume_scaler > MAX_VOLUME_TO_SCALE) {
+		memcpy(dst, src, count * sizeof(*src));
+		return;
+	}
+
+	for (i = 0; i < count; i++)
+		dst[i] = src[i] * volume_scaler;
+}
+
+static void cras_scale_buffer_inc_s16_le(uint8_t *buffer, unsigned int count,
+					 float scaler, float increment, int step)
+{
+	int i = 0, j;
+	int16_t *out = (int16_t *)buffer;
+
+	if (scaler > MAX_VOLUME_TO_SCALE && increment > 0)
+		return;
+
+	if (scaler < MIN_VOLUME_TO_SCALE && increment < 0) {
+		memset(out, 0, count * sizeof(*out));
+		return;
+	}
+
+	while (i + step <= count) {
+		for (j = 0; j < step; j++) {
+			if (scaler > MAX_VOLUME_TO_SCALE) {
+			} else if (scaler < MIN_VOLUME_TO_SCALE) {
+				out[i] = 0;
+			} else {
+				out[i] *= scaler;
+			}
+			i++;
+		}
+		scaler += increment;
+	}
+}
+
+static void cras_scale_buffer_s16_le(uint8_t *buffer, unsigned int count,
+				     float scaler)
+{
+	int i;
+	int16_t *out = (int16_t *)buffer;
+
+	if (scaler > MAX_VOLUME_TO_SCALE)
+		return;
+
+	if (scaler < MIN_VOLUME_TO_SCALE) {
+		memset(out, 0, count * sizeof(*out));
+		return;
+	}
+
+	for (i = 0; i < count; i++)
+		out[i] *= scaler;
+}
+
+static void cras_mix_add_s16_le(uint8_t *dst, uint8_t *src,
+				unsigned int count, unsigned int index,
+				int mute, float mix_vol)
+{
+	int16_t *out = (int16_t *)dst;
+	int16_t *in = (int16_t *)src;
+
+	if (mute || (mix_vol < MIN_VOLUME_TO_SCALE)) {
+		if (index == 0)
+			memset(out, 0, count * sizeof(*out));
+		return;
+	}
+
+	if (index == 0)
+		return copy_scaled_s16_le(out, in, count, mix_vol);
+
+	scale_add_clip_s16_le(out, in, count, mix_vol);
+}
+
+static void cras_mix_add_scale_stride_s16_le(uint8_t *dst, uint8_t *src,
+				unsigned int dst_stride,
+				unsigned int src_stride,
+				unsigned int count,
+				float scaler)
+{
+	unsigned int i;
+
+	/* optimise the loops for vectorization */
+	if (dst_stride == src_stride && dst_stride == 2) {
+
+		for (i = 0; i < count; i++) {
+			int32_t sum;
+			if (need_to_scale(scaler))
+				sum = *(int16_t *)dst +
+						*(int16_t *)src * scaler;
+			else
+				sum = *(int16_t *)dst + *(int16_t *)src;
+			if (sum > INT16_MAX)
+				sum = INT16_MAX;
+			else if (sum < INT16_MIN)
+				sum = INT16_MIN;
+			*(int16_t*)dst = sum;
+			dst += 2;
+			src += 2;
+		}
+	} else if (dst_stride == src_stride && dst_stride == 4) {
+
+		for (i = 0; i < count; i++) {
+			int32_t sum;
+			if (need_to_scale(scaler))
+				sum = *(int16_t *)dst +
+						*(int16_t *)src * scaler;
+			else
+				sum = *(int16_t *)dst + *(int16_t *)src;
+			if (sum > INT16_MAX)
+				sum = INT16_MAX;
+			else if (sum < INT16_MIN)
+				sum = INT16_MIN;
+			*(int16_t*)dst = sum;
+			dst += 4;
+			src += 4;
+		}
+	} else {
+		for (i = 0; i < count; i++) {
+			int32_t sum;
+			if (need_to_scale(scaler))
+				sum = *(int16_t *)dst +
+						*(int16_t *)src * scaler;
+			else
+				sum = *(int16_t *)dst + *(int16_t *)src;
+			if (sum > INT16_MAX)
+				sum = INT16_MAX;
+			else if (sum < INT16_MIN)
+				sum = INT16_MIN;
+			*(int16_t*)dst = sum;
+			dst += dst_stride;
+			src += src_stride;
+		}
+	}
+}
+
+/*
+ * Signed 24 bit little endian functions.
+ */
+
+static void cras_mix_add_clip_s24_le(int32_t *dst,
+				     const int32_t *src,
+				     size_t count)
+{
+	int32_t sum;
+	size_t i;
+
+	for (i = 0; i < count; i++) {
+		sum = dst[i] + src[i];
+		if (sum > 0x007fffff)
+			sum = 0x007fffff;
+		else if (sum < (int32_t)0xff800000)
+			sum = (int32_t)0xff800000;
+		dst[i] = sum;
+	}
+}
+
+/* Adds src into dst, after scaling by vol.
+ * Just hard limits to the min and max S24 value, can be improved later. */
+static void scale_add_clip_s24_le(int32_t *dst,
+				  const int32_t *src,
+				  size_t count,
+				  float vol)
+{
+	int32_t sum;
+	size_t i;
+
+	if (vol > MAX_VOLUME_TO_SCALE)
+		return cras_mix_add_clip_s24_le(dst, src, count);
+
+	for (i = 0; i < count; i++) {
+		sum = dst[i] + (int32_t)(src[i] * vol);
+		if (sum > 0x007fffff)
+			sum = 0x007fffff;
+		else if (sum < (int32_t)0xff800000)
+			sum = (int32_t)0xff800000;
+		dst[i] = sum;
+	}
+}
+
+/* Adds the first stream to the mix.  Don't need to mix, just setup to the new
+ * values. If volume is 1.0, just memcpy. */
+static void copy_scaled_s24_le(int32_t *dst,
+			       const int32_t *src,
+			       size_t count,
+			       float volume_scaler)
+{
+	int i;
+
+	if (volume_scaler > MAX_VOLUME_TO_SCALE) {
+		memcpy(dst, src, count * sizeof(*src));
+		return;
+	}
+
+	for (i = 0; i < count; i++)
+		dst[i] = src[i] * volume_scaler;
+}
+
+static void cras_scale_buffer_inc_s24_le(uint8_t *buffer, unsigned int count,
+					 float scaler, float increment, int step)
+{
+	int i = 0, j;
+	int32_t *out = (int32_t *)buffer;
+
+	if (scaler > MAX_VOLUME_TO_SCALE && increment > 0)
+		return;
+
+	if (scaler < MIN_VOLUME_TO_SCALE && increment < 0) {
+		memset(out, 0, count * sizeof(*out));
+		return;
+	}
+
+	while (i + step <= count) {
+		for (j = 0; j < step; j++) {
+			if (scaler > MAX_VOLUME_TO_SCALE) {
+			} else if (scaler < MIN_VOLUME_TO_SCALE) {
+				out[i] = 0;
+			} else {
+				out[i] *= scaler;
+			}
+			i++;
+		}
+		scaler += increment;
+	}
+}
+
+static void cras_scale_buffer_s24_le(uint8_t *buffer, unsigned int count,
+				     float scaler)
+{
+	int i;
+	int32_t *out = (int32_t *)buffer;
+
+	if (scaler > MAX_VOLUME_TO_SCALE)
+		return;
+
+	if (scaler < MIN_VOLUME_TO_SCALE) {
+		memset(out, 0, count * sizeof(*out));
+		return;
+	}
+
+	for (i = 0; i < count; i++)
+		out[i] *= scaler;
+}
+
+static void cras_mix_add_s24_le(uint8_t *dst, uint8_t *src,
+				unsigned int count, unsigned int index,
+				int mute, float mix_vol)
+{
+	int32_t *out = (int32_t *)dst;
+	int32_t *in = (int32_t *)src;
+
+	if (mute || (mix_vol < MIN_VOLUME_TO_SCALE)) {
+		if (index == 0)
+			memset(out, 0, count * sizeof(*out));
+		return;
+	}
+
+	if (index == 0)
+		return copy_scaled_s24_le(out, in, count, mix_vol);
+
+	scale_add_clip_s24_le(out, in, count, mix_vol);
+}
+
+static void cras_mix_add_scale_stride_s24_le(uint8_t *dst, uint8_t *src,
+				unsigned int dst_stride,
+				unsigned int src_stride,
+				unsigned int count,
+				float scaler)
+{
+	unsigned int i;
+
+	/* optimise the loops for vectorization */
+	if (dst_stride == src_stride && dst_stride == 4) {
+
+		for (i = 0; i < count; i++) {
+			int32_t sum;
+			if (need_to_scale(scaler))
+				sum = *(int32_t *)dst +
+						*(int32_t *)src * scaler;
+			else
+				sum = *(int32_t *)dst + *(int32_t *)src;
+			if (sum > 0x007fffff)
+				sum = 0x007fffff;
+			else if (sum < (int32_t)0xff800000)
+				sum = (int32_t)0xff800000;
+			*(int32_t*)dst = sum;
+			dst += 4;
+			src += 4;
+		}
+	} else {
+
+		for (i = 0; i < count; i++) {
+			int32_t sum;
+			if (need_to_scale(scaler))
+				sum = *(int32_t *)dst +
+						*(int32_t *)src * scaler;
+			else
+				sum = *(int32_t *)dst + *(int32_t *)src;
+			if (sum > 0x007fffff)
+				sum = 0x007fffff;
+			else if (sum < (int32_t)0xff800000)
+				sum = (int32_t)0xff800000;
+			*(int32_t*)dst = sum;
+			dst += dst_stride;
+			src += src_stride;
+		}
+	}
+}
+
+/*
+ * Signed 32 bit little endian functions.
+ */
+
+static void cras_mix_add_clip_s32_le(int32_t *dst,
+				     const int32_t *src,
+				     size_t count)
+{
+	int64_t sum;
+	size_t i;
+
+	for (i = 0; i < count; i++) {
+		sum = (int64_t)dst[i] + (int64_t)src[i];
+		if (sum > INT32_MAX)
+			sum = INT32_MAX;
+		else if (sum < INT32_MIN)
+			sum = INT32_MIN;
+		dst[i] = sum;
+	}
+}
+
+/* Adds src into dst, after scaling by vol.
+ * Just hard limits to the min and max S32 value, can be improved later. */
+static void scale_add_clip_s32_le(int32_t *dst,
+				  const int32_t *src,
+				  size_t count,
+				  float vol)
+{
+	int64_t sum;
+	size_t i;
+
+	if (vol > MAX_VOLUME_TO_SCALE)
+		return cras_mix_add_clip_s32_le(dst, src, count);
+
+	for (i = 0; i < count; i++) {
+		sum = (int64_t)dst[i] + (int64_t)(src[i] * vol);
+		if (sum > INT32_MAX)
+			sum = INT32_MAX;
+		else if (sum < INT32_MIN)
+			sum = INT32_MIN;
+		dst[i] = sum;
+	}
+}
+
+/* Adds the first stream to the mix.  Don't need to mix, just setup to the new
+ * values. If volume is 1.0, just memcpy. */
+static void copy_scaled_s32_le(int32_t *dst,
+			       const int32_t *src,
+			       size_t count,
+			       float volume_scaler)
+{
+	int i;
+
+	if (volume_scaler > MAX_VOLUME_TO_SCALE) {
+		memcpy(dst, src, count * sizeof(*src));
+		return;
+	}
+
+	for (i = 0; i < count; i++)
+		dst[i] = src[i] * volume_scaler;
+}
+
+static void cras_scale_buffer_inc_s32_le(uint8_t *buffer, unsigned int count,
+					 float scaler, float increment, int step)
+{
+	int i = 0, j;
+	int32_t *out = (int32_t *)buffer;
+
+	if (scaler > MAX_VOLUME_TO_SCALE && increment > 0)
+		return;
+
+	if (scaler < MIN_VOLUME_TO_SCALE && increment < 0) {
+		memset(out, 0, count * sizeof(*out));
+		return;
+	}
+
+	while (i + step <= count) {
+		for (j = 0; j < step; j++) {
+			if (scaler > MAX_VOLUME_TO_SCALE) {
+			} else if (scaler < MIN_VOLUME_TO_SCALE) {
+				out[i] = 0;
+			} else {
+				out[i] *= scaler;
+			}
+			i++;
+		}
+		scaler += increment;
+	}
+}
+
+static void cras_scale_buffer_s32_le(uint8_t *buffer, unsigned int count,
+				     float scaler)
+{
+	int i;
+	int32_t *out = (int32_t *)buffer;
+
+	if (scaler > MAX_VOLUME_TO_SCALE)
+		return;
+
+	if (scaler < MIN_VOLUME_TO_SCALE) {
+		memset(out, 0, count * sizeof(*out));
+		return;
+	}
+
+	for (i = 0; i < count; i++)
+		out[i] *= scaler;
+}
+
+static void cras_mix_add_s32_le(uint8_t *dst, uint8_t *src,
+				unsigned int count, unsigned int index,
+				int mute, float mix_vol)
+{
+	int32_t *out = (int32_t *)dst;
+	int32_t *in = (int32_t *)src;
+
+	if (mute || (mix_vol < MIN_VOLUME_TO_SCALE)) {
+		if (index == 0)
+			memset(out, 0, count * sizeof(*out));
+		return;
+	}
+
+	if (index == 0)
+		return copy_scaled_s32_le(out, in, count, mix_vol);
+
+	scale_add_clip_s32_le(out, in, count, mix_vol);
+}
+
+static void cras_mix_add_scale_stride_s32_le(uint8_t *dst, uint8_t *src,
+				unsigned int dst_stride,
+				unsigned int src_stride,
+				unsigned int count,
+				float scaler)
+{
+	unsigned int i;
+
+	/* optimise the loops for vectorization */
+	if (dst_stride == src_stride && dst_stride == 4) {
+
+		for (i = 0; i < count; i++) {
+			int64_t sum;
+			if (need_to_scale(scaler))
+				sum = *(int32_t *)dst +
+						*(int32_t *)src * scaler;
+			else
+				sum = *(int32_t *)dst + *(int32_t *)src;
+			if (sum > INT32_MAX)
+				sum = INT32_MAX;
+			else if (sum < INT32_MIN)
+				sum = INT32_MIN;
+			*(int32_t*)dst = sum;
+			dst += 4;
+			src += 4;
+		}
+	} else {
+
+		for (i = 0; i < count; i++) {
+			int64_t sum;
+			if (need_to_scale(scaler))
+				sum = *(int32_t *)dst +
+						*(int32_t *)src * scaler;
+			else
+				sum = *(int32_t *)dst + *(int32_t *)src;
+			if (sum > INT32_MAX)
+				sum = INT32_MAX;
+			else if (sum < INT32_MIN)
+				sum = INT32_MIN;
+			*(int32_t*)dst = sum;
+			dst += dst_stride;
+			src += src_stride;
+		}
+	}
+}
+
+/*
+ * Signed 24 bit little endian in three bytes functions.
+ */
+
+/* Convert 3bytes Signed 24bit integer to a Signed 32bit integer.
+ * Just a helper function. */
+static inline void convert_single_s243le_to_s32le(int32_t *dst,
+						  const uint8_t *src)
+{
+	*dst = 0;
+	memcpy((uint8_t *)dst + 1, src, 3);
+}
+
+static inline void convert_single_s32le_to_s243le(uint8_t *dst,
+						  const int32_t *src)
+{
+	memcpy(dst, (uint8_t *)src + 1, 3);
+}
+
+static void cras_mix_add_clip_s24_3le(uint8_t *dst,
+				      const uint8_t *src,
+				      size_t count)
+{
+	int64_t sum;
+	int32_t dst_frame;
+	int32_t src_frame;
+	size_t i;
+
+	for (i = 0; i < count; i++, dst += 3, src += 3) {
+		convert_single_s243le_to_s32le(&dst_frame, dst);
+		convert_single_s243le_to_s32le(&src_frame, src);
+		sum = (int64_t)dst_frame + (int64_t)src_frame;
+		if (sum > INT32_MAX)
+			sum = INT32_MAX;
+		else if (sum < INT32_MIN)
+			sum = INT32_MIN;
+		dst_frame = (int32_t)sum;
+		convert_single_s32le_to_s243le(dst, &dst_frame);
+	}
+}
+
+/* Adds src into dst, after scaling by vol.
+ * Just hard limits to the min and max S24 value, can be improved later. */
+static void scale_add_clip_s24_3le(uint8_t *dst,
+				   const uint8_t *src,
+				   size_t count,
+				   float vol)
+{
+	int64_t sum;
+	int32_t dst_frame;
+	int32_t src_frame;
+	size_t i;
+
+	if (vol > MAX_VOLUME_TO_SCALE)
+		return cras_mix_add_clip_s24_3le(dst, src, count);
+
+	for (i = 0; i < count; i++, dst += 3, src += 3) {
+		convert_single_s243le_to_s32le(&dst_frame, dst);
+		convert_single_s243le_to_s32le(&src_frame, src);
+		sum = (int64_t)dst_frame + (int64_t)(src_frame * vol);
+		if (sum > INT32_MAX)
+			sum = INT32_MAX;
+		else if (sum < INT32_MIN)
+			sum = INT32_MIN;
+		dst_frame = (int32_t)sum;
+		convert_single_s32le_to_s243le(dst, &dst_frame);
+	}
+}
+
+/* Adds the first stream to the mix.  Don't need to mix, just setup to the new
+ * values. If volume is 1.0, just memcpy. */
+static void copy_scaled_s24_3le(uint8_t *dst,
+			        const uint8_t *src,
+			        size_t count,
+			        float volume_scaler)
+{
+	int32_t frame;
+	size_t i;
+
+	if (volume_scaler > MAX_VOLUME_TO_SCALE) {
+		memcpy(dst, src, 3 * count * sizeof(*src));
+		return;
+	}
+
+	for (i = 0; i < count; i++, dst += 3, src += 3) {
+		convert_single_s243le_to_s32le(&frame, src);
+		frame *= volume_scaler;
+		convert_single_s32le_to_s243le(dst, &frame);
+	}
+}
+
+static void cras_scale_buffer_inc_s24_3le(uint8_t *buffer, unsigned int count,
+					  float scaler, float increment, int step)
+{
+	int32_t frame;
+	int i = 0, j;
+
+	if (scaler > MAX_VOLUME_TO_SCALE && increment > 0)
+		return;
+
+	if (scaler < MIN_VOLUME_TO_SCALE && increment < 0) {
+		memset(buffer, 0, 3 * count * sizeof(*buffer));
+		return;
+	}
+
+	while (i + step <= count) {
+		for (j = 0; j < step; j++) {
+			convert_single_s243le_to_s32le(&frame, buffer);
+
+			if (scaler > MAX_VOLUME_TO_SCALE) {
+			} else if (scaler < MIN_VOLUME_TO_SCALE) {
+				frame = 0;
+			} else {
+				frame *= scaler;
+			}
+
+			convert_single_s32le_to_s243le(buffer, &frame);
+
+			i++;
+			buffer += 3;
+		}
+		scaler += increment;
+	}
+}
+
+static void cras_scale_buffer_s24_3le(uint8_t *buffer, unsigned int count,
+				      float scaler)
+{
+	int32_t frame;
+	int i;
+
+	if (scaler > MAX_VOLUME_TO_SCALE)
+		return;
+
+	if (scaler < MIN_VOLUME_TO_SCALE) {
+		memset(buffer, 0, 3 * count * sizeof(*buffer));
+		return;
+	}
+
+	for (i = 0; i < count; i++, buffer += 3) {
+		convert_single_s243le_to_s32le(&frame, buffer);
+		frame *= scaler;
+		convert_single_s32le_to_s243le(buffer, &frame);
+	}
+}
+
+static void cras_mix_add_s24_3le(uint8_t *dst, uint8_t *src,
+				 unsigned int count, unsigned int index,
+				 int mute, float mix_vol)
+{
+	uint8_t *out = dst;
+	uint8_t *in = src;
+
+	if (mute || (mix_vol < MIN_VOLUME_TO_SCALE)) {
+		if (index == 0)
+			memset(out, 0, 3 * count * sizeof(*out));
+		return;
+	}
+
+	if (index == 0)
+		return copy_scaled_s24_3le(out, in, count, mix_vol);
+
+	scale_add_clip_s24_3le(out, in, count, mix_vol);
+}
+
+static void cras_mix_add_scale_stride_s24_3le(uint8_t *dst, uint8_t *src,
+				 unsigned int dst_stride,
+				 unsigned int src_stride,
+				 unsigned int count,
+				 float scaler)
+{
+	unsigned int i;
+	int64_t sum;
+	int32_t dst_frame;
+	int32_t src_frame;
+
+	for (i = 0; i < count; i++) {
+		convert_single_s243le_to_s32le(&dst_frame, dst);
+		convert_single_s243le_to_s32le(&src_frame, src);
+		if (need_to_scale(scaler))
+			sum = (int64_t)dst_frame + (int64_t)src_frame * scaler;
+		else
+			sum = (int64_t)dst_frame + (int64_t)src_frame;
+		if (sum > INT32_MAX)
+			sum = INT32_MAX;
+		else if (sum < INT32_MIN)
+			sum = INT32_MIN;
+		dst_frame = (int32_t)sum;
+		convert_single_s32le_to_s243le(dst, &dst_frame);
+		dst += dst_stride;
+		src += src_stride;
+	}
+}
+
+static void scale_buffer_increment(snd_pcm_format_t fmt, uint8_t *buff,
+				   unsigned int count, float scaler,
+				   float increment, int step)
+{
+	switch (fmt) {
+	case SND_PCM_FORMAT_S16_LE:
+		return cras_scale_buffer_inc_s16_le(buff, count, scaler,
+						    increment, step);
+	case SND_PCM_FORMAT_S24_LE:
+		return cras_scale_buffer_inc_s24_le(buff, count, scaler,
+						    increment, step);
+	case SND_PCM_FORMAT_S32_LE:
+		return cras_scale_buffer_inc_s32_le(buff, count, scaler,
+						    increment, step);
+	case SND_PCM_FORMAT_S24_3LE:
+		return cras_scale_buffer_inc_s24_3le(buff, count, scaler,
+						     increment, step);
+	default:
+		break;
+	}
+}
+
+static void scale_buffer(snd_pcm_format_t fmt, uint8_t *buff, unsigned int count,
+		       float scaler)
+{
+	switch (fmt) {
+	case SND_PCM_FORMAT_S16_LE:
+		return cras_scale_buffer_s16_le(buff, count, scaler);
+	case SND_PCM_FORMAT_S24_LE:
+		return cras_scale_buffer_s24_le(buff, count, scaler);
+	case SND_PCM_FORMAT_S32_LE:
+		return cras_scale_buffer_s32_le(buff, count, scaler);
+	case SND_PCM_FORMAT_S24_3LE:
+		return cras_scale_buffer_s24_3le(buff, count, scaler);
+	default:
+		break;
+	}
+}
+
+static void mix_add(snd_pcm_format_t fmt, uint8_t *dst, uint8_t *src,
+		  unsigned int count, unsigned int index,
+		  int mute, float mix_vol)
+{
+	switch (fmt) {
+	case SND_PCM_FORMAT_S16_LE:
+		return cras_mix_add_s16_le(dst, src, count, index, mute,
+					   mix_vol);
+	case SND_PCM_FORMAT_S24_LE:
+		return cras_mix_add_s24_le(dst, src, count, index, mute,
+					   mix_vol);
+	case SND_PCM_FORMAT_S32_LE:
+		return cras_mix_add_s32_le(dst, src, count, index, mute,
+					   mix_vol);
+	case SND_PCM_FORMAT_S24_3LE:
+		return cras_mix_add_s24_3le(dst, src, count, index, mute,
+					    mix_vol);
+	default:
+		break;
+	}
+}
+
+static void mix_add_scale_stride(snd_pcm_format_t fmt, uint8_t *dst,
+			uint8_t *src, unsigned int count,
+			unsigned int dst_stride, unsigned int src_stride,
+			float scaler)
+{
+	switch (fmt) {
+	case SND_PCM_FORMAT_S16_LE:
+		return cras_mix_add_scale_stride_s16_le(dst, src, dst_stride,
+						  src_stride, count, scaler);
+	case SND_PCM_FORMAT_S24_LE:
+		return cras_mix_add_scale_stride_s24_le(dst, src, dst_stride,
+						  src_stride, count, scaler);
+	case SND_PCM_FORMAT_S32_LE:
+		return cras_mix_add_scale_stride_s32_le(dst, src, dst_stride,
+						  src_stride, count, scaler);
+	case SND_PCM_FORMAT_S24_3LE:
+		return cras_mix_add_scale_stride_s24_3le(dst, src, dst_stride,
+						   src_stride, count, scaler);
+	default:
+		break;
+	}
+}
+
+static size_t mix_mute_buffer(uint8_t *dst,
+			    size_t frame_bytes,
+			    size_t count)
+{
+	memset(dst, 0, count * frame_bytes);
+	return count;
+}
+
+const struct cras_mix_ops OPS(mixer_ops) = {
+	.scale_buffer = scale_buffer,
+	.scale_buffer_increment = scale_buffer_increment,
+	.add = mix_add,
+	.add_scale_stride = mix_add_scale_stride,
+	.mute_buffer = mix_mute_buffer,
+};
diff --git a/cras/src/server/cras_mix_ops.h b/cras/src/server/cras_mix_ops.h
new file mode 100644
index 0000000..8246a07
--- /dev/null
+++ b/cras/src/server/cras_mix_ops.h
@@ -0,0 +1,49 @@
+/* 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.
+ */
+
+#ifndef CRAS_MIX_OPS_H_
+#define CRAS_MIX_OPS_H_
+
+#include <stdint.h>
+
+#include "cras_system_state.h"
+
+extern const struct cras_mix_ops mixer_ops;
+extern const struct cras_mix_ops mixer_ops_sse42;
+extern const struct cras_mix_ops mixer_ops_avx;
+extern const struct cras_mix_ops mixer_ops_avx2;
+extern const struct cras_mix_ops mixer_ops_fma;
+
+/* Struct containing ops to implement mix/scale on a buffer of samples.
+ * Different architecture can provide different implementations and wraps
+ * the implementations into cras_mix_ops.
+ * Different sample formats will be handled by different implementations.
+ * The usage of each operation is explained in cras_mix.h
+ *
+ * Members:
+ *   scale_buffer_increment: See cras_scale_buffer_increment.
+ *   scale_buffer: See cras_scale_buffer.
+ *   add: See cras_mix_add.
+ *   add_scale_stride: See cras_mix_add_scale_stride.
+ *   mute_buffer: cras_mix_mute_buffer.
+ */
+struct cras_mix_ops {
+	void (*scale_buffer_increment)(snd_pcm_format_t fmt, uint8_t *buff,
+				       unsigned int count, float scaler,
+				       float increment, int step);
+	void (*scale_buffer)(snd_pcm_format_t fmt, uint8_t *buff,
+			unsigned int count, float scaler);
+	void (*add)(snd_pcm_format_t fmt, uint8_t *dst, uint8_t *src,
+		  unsigned int count, unsigned int index,
+		  int mute, float mix_vol);
+	void (*add_scale_stride)(snd_pcm_format_t fmt, uint8_t *dst,
+			uint8_t *src, unsigned int count,
+			unsigned int dst_stride, unsigned int src_stride,
+			float scaler);
+	size_t (*mute_buffer)(uint8_t *dst,
+			    size_t frame_bytes,
+			    size_t count);
+};
+#endif
diff --git a/cras/src/server/cras_observer.c b/cras/src/server/cras_observer.c
new file mode 100644
index 0000000..d0366d5
--- /dev/null
+++ b/cras/src/server/cras_observer.c
@@ -0,0 +1,507 @@
+/* Copyright 2016 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_observer.h"
+
+#include "cras_alert.h"
+#include "cras_iodev_list.h"
+#include "utlist.h"
+
+struct cras_observer_client {
+	struct cras_observer_ops ops;
+	void *context;
+	struct cras_observer_client *next, *prev;
+};
+
+struct cras_observer_alerts {
+	struct cras_alert *output_volume;
+	struct cras_alert *output_mute;
+	struct cras_alert *capture_gain;
+	struct cras_alert *capture_mute;
+	struct cras_alert *nodes;
+	struct cras_alert *active_node;
+	struct cras_alert *output_node_volume;
+	struct cras_alert *node_left_right_swapped;
+	struct cras_alert *input_node_gain;
+	struct cras_alert *suspend_changed;
+	/* If all events for active streams went through a single alert then
+         * we might miss some because the alert code does not send every
+         * alert message. To ensure that the event sent contains the correct
+         * number of active streams per direction, make the alerts
+         * per-direciton. */
+	struct cras_alert *num_active_streams[CRAS_NUM_DIRECTIONS];
+};
+
+struct cras_observer_server {
+	struct cras_observer_alerts alerts;
+	struct cras_observer_client *clients;
+};
+
+struct cras_observer_alert_data_volume {
+	int32_t volume;
+};
+
+struct cras_observer_alert_data_mute {
+	int muted;
+	int user_muted;
+	int mute_locked;
+};
+
+struct cras_observer_alert_data_active_node {
+	enum CRAS_STREAM_DIRECTION direction;
+	cras_node_id_t node_id;
+};
+
+struct cras_observer_alert_data_node_volume {
+	cras_node_id_t node_id;
+	int32_t volume;
+};
+
+struct cras_observer_alert_data_node_lr_swapped {
+	cras_node_id_t node_id;
+	int swapped;
+};
+
+struct cras_observer_alert_data_suspend {
+	int suspended;
+};
+
+struct cras_observer_alert_data_streams {
+	enum CRAS_STREAM_DIRECTION direction;
+	uint32_t num_active_streams;
+};
+
+/* Global observer instance. */
+static struct cras_observer_server *g_observer;
+
+/* Empty observer ops. */
+static struct cras_observer_ops g_empty_ops;
+
+/*
+ * Alert handlers for delayed callbacks.
+ */
+
+static void output_volume_alert(void *arg, void *data)
+{
+	struct cras_observer_client *client;
+	struct cras_observer_alert_data_volume *volume_data =
+		(struct cras_observer_alert_data_volume *)data;
+
+	DL_FOREACH(g_observer->clients, client) {
+		if (client->ops.output_volume_changed)
+			client->ops.output_volume_changed(
+					client->context,
+					volume_data->volume);
+	}
+}
+
+static void output_mute_alert(void *arg, void *data)
+{
+	struct cras_observer_client *client;
+	struct cras_observer_alert_data_mute *mute_data =
+		(struct cras_observer_alert_data_mute *)data;
+
+	DL_FOREACH(g_observer->clients, client) {
+		if (client->ops.output_mute_changed)
+			client->ops.output_mute_changed(
+					client->context,
+					mute_data->muted,
+					mute_data->user_muted,
+					mute_data->mute_locked);
+	}
+}
+
+static void capture_gain_alert(void *arg, void *data)
+{
+	struct cras_observer_client *client;
+	struct cras_observer_alert_data_volume *volume_data =
+		(struct cras_observer_alert_data_volume *)data;
+
+	DL_FOREACH(g_observer->clients, client) {
+		if (client->ops.capture_gain_changed)
+			client->ops.capture_gain_changed(
+					client->context,
+					volume_data->volume);
+	}
+}
+
+static void capture_mute_alert(void *arg, void *data)
+{
+	struct cras_observer_client *client;
+	struct cras_observer_alert_data_mute *mute_data =
+		(struct cras_observer_alert_data_mute *)data;
+
+	DL_FOREACH(g_observer->clients, client) {
+		if (client->ops.capture_mute_changed)
+			client->ops.capture_mute_changed(
+					client->context,
+					mute_data->muted,
+					mute_data->mute_locked);
+	}
+}
+
+static void nodes_prepare(struct cras_alert *alert)
+{
+	cras_iodev_list_update_device_list();
+}
+
+static void nodes_alert(void *arg, void *data)
+{
+	struct cras_observer_client *client;
+
+	DL_FOREACH(g_observer->clients, client) {
+		if (client->ops.nodes_changed)
+			client->ops.nodes_changed(client->context);
+	}
+}
+
+static void active_node_alert(void *arg, void *data)
+{
+	struct cras_observer_client *client;
+	struct cras_observer_alert_data_active_node *node_data =
+		(struct cras_observer_alert_data_active_node *)data;
+
+	DL_FOREACH(g_observer->clients, client) {
+		if (client->ops.active_node_changed)
+			client->ops.active_node_changed(
+					client->context,
+					node_data->direction,
+					node_data->node_id);
+	}
+}
+
+static void output_node_volume_alert(void *arg, void *data)
+{
+	struct cras_observer_client *client;
+	struct cras_observer_alert_data_node_volume *node_data =
+		(struct cras_observer_alert_data_node_volume *)data;
+
+	DL_FOREACH(g_observer->clients, client) {
+		if (client->ops.output_node_volume_changed)
+			client->ops.output_node_volume_changed(
+					client->context,
+					node_data->node_id,
+					node_data->volume);
+	}
+}
+
+static void node_left_right_swapped_alert(void *arg, void *data)
+{
+	struct cras_observer_client *client;
+	struct cras_observer_alert_data_node_lr_swapped *node_data =
+		(struct cras_observer_alert_data_node_lr_swapped *)data;
+
+	DL_FOREACH(g_observer->clients, client) {
+		if (client->ops.node_left_right_swapped_changed)
+			client->ops.node_left_right_swapped_changed(
+					client->context,
+					node_data->node_id,
+					node_data->swapped);
+	}
+}
+
+static void input_node_gain_alert(void *arg, void *data)
+{
+	struct cras_observer_client *client;
+	struct cras_observer_alert_data_node_volume *node_data =
+		(struct cras_observer_alert_data_node_volume *)data;
+
+	DL_FOREACH(g_observer->clients, client) {
+		if (client->ops.input_node_gain_changed)
+			client->ops.input_node_gain_changed(
+					client->context,
+					node_data->node_id,
+					node_data->volume);
+	}
+}
+
+static void suspend_changed_alert(void *arg, void *data)
+{
+	struct cras_observer_client *client;
+	struct cras_observer_alert_data_suspend *suspend_data =
+		(struct cras_observer_alert_data_suspend *)data;
+
+	DL_FOREACH(g_observer->clients, client) {
+		if (client->ops.suspend_changed)
+			client->ops.suspend_changed(
+					client->context,
+					suspend_data->suspended);
+	}
+}
+
+static void num_active_streams_alert(void *arg, void *data)
+{
+	struct cras_observer_client *client;
+	struct cras_observer_alert_data_streams *streams_data =
+		(struct cras_observer_alert_data_streams *)data;
+
+	DL_FOREACH(g_observer->clients, client) {
+		if (client->ops.num_active_streams_changed)
+			client->ops.num_active_streams_changed(
+					client->context,
+					streams_data->direction,
+					streams_data->num_active_streams);
+	}
+}
+
+static int cras_observer_server_set_alert(struct cras_alert **alert,
+					  cras_alert_cb cb,
+					  cras_alert_prepare prepare,
+					  unsigned int flags)
+{
+	*alert = cras_alert_create(prepare, flags);
+	if (!*alert)
+		return -ENOMEM;
+	return cras_alert_add_callback(*alert, cb, NULL);
+}
+
+#define CRAS_OBSERVER_SET_ALERT(alert,prepare,flags) \
+	do { \
+		rc = cras_observer_server_set_alert( \
+			&g_observer->alerts.alert, alert##_alert, \
+			prepare, flags); \
+		if (rc) \
+			goto error; \
+	} while(0)
+
+#define CRAS_OBSERVER_SET_ALERT_WITH_DIRECTION(alert,direction) \
+	do { \
+		rc = cras_observer_server_set_alert( \
+			&g_observer->alerts.alert[direction], \
+			alert##_alert, NULL, 0); \
+		if (rc) \
+			goto error; \
+	} while(0)
+
+/*
+ * Public interface
+ */
+
+int cras_observer_server_init()
+{
+	int rc;
+
+	memset(&g_empty_ops, 0, sizeof(g_empty_ops));
+	g_observer = (struct cras_observer_server *)
+			calloc(1, sizeof(struct cras_observer_server));
+	if (!g_observer)
+		return -ENOMEM;
+
+	CRAS_OBSERVER_SET_ALERT(output_volume, NULL, 0);
+	CRAS_OBSERVER_SET_ALERT(output_mute, NULL, 0);
+	CRAS_OBSERVER_SET_ALERT(capture_gain, NULL, 0);
+	CRAS_OBSERVER_SET_ALERT(capture_mute, NULL, 0);
+	CRAS_OBSERVER_SET_ALERT(nodes, nodes_prepare, 0);
+	CRAS_OBSERVER_SET_ALERT(active_node, nodes_prepare,
+				CRAS_ALERT_FLAG_KEEP_ALL_DATA);
+	CRAS_OBSERVER_SET_ALERT(output_node_volume, NULL, 0);
+	CRAS_OBSERVER_SET_ALERT(node_left_right_swapped, NULL, 0);
+	CRAS_OBSERVER_SET_ALERT(input_node_gain, NULL, 0);
+	CRAS_OBSERVER_SET_ALERT(suspend_changed, NULL, 0);
+
+	CRAS_OBSERVER_SET_ALERT_WITH_DIRECTION(
+		num_active_streams, CRAS_STREAM_OUTPUT);
+	CRAS_OBSERVER_SET_ALERT_WITH_DIRECTION(
+		num_active_streams, CRAS_STREAM_INPUT);
+	CRAS_OBSERVER_SET_ALERT_WITH_DIRECTION(
+		num_active_streams, CRAS_STREAM_POST_MIX_PRE_DSP);
+	return 0;
+
+error:
+	cras_observer_server_free();
+	return rc;
+}
+
+void cras_observer_server_free()
+{
+	if (!g_observer)
+		return;
+	cras_alert_destroy(g_observer->alerts.output_volume);
+	cras_alert_destroy(g_observer->alerts.output_mute);
+	cras_alert_destroy(g_observer->alerts.capture_gain);
+	cras_alert_destroy(g_observer->alerts.capture_mute);
+	cras_alert_destroy(g_observer->alerts.nodes);
+	cras_alert_destroy(g_observer->alerts.active_node);
+	cras_alert_destroy(g_observer->alerts.output_node_volume);
+	cras_alert_destroy(g_observer->alerts.node_left_right_swapped);
+	cras_alert_destroy(g_observer->alerts.input_node_gain);
+	cras_alert_destroy(g_observer->alerts.suspend_changed);
+	cras_alert_destroy(g_observer->alerts.num_active_streams[
+							CRAS_STREAM_OUTPUT]);
+	cras_alert_destroy(g_observer->alerts.num_active_streams[
+							CRAS_STREAM_INPUT]);
+	cras_alert_destroy(g_observer->alerts.num_active_streams[
+						CRAS_STREAM_POST_MIX_PRE_DSP]);
+	free(g_observer);
+	g_observer = NULL;
+}
+
+int cras_observer_ops_are_empty(const struct cras_observer_ops *ops)
+{
+	return memcmp(ops, &g_empty_ops, sizeof(*ops)) == 0;
+}
+
+void cras_observer_get_ops(const struct cras_observer_client *client,
+			   struct cras_observer_ops *ops)
+{
+	if (!ops)
+		return;
+	if (!client)
+		memset(ops, 0, sizeof(*ops));
+	else
+		memcpy(ops, &client->ops, sizeof(*ops));
+}
+
+void cras_observer_set_ops(struct cras_observer_client *client,
+			   const struct cras_observer_ops *ops)
+{
+	if (!client)
+		return;
+	if (!ops)
+		memset(&client->ops, 0, sizeof(client->ops));
+	else
+		memcpy(&client->ops, ops, sizeof(client->ops));
+}
+
+struct cras_observer_client *cras_observer_add(
+			const struct cras_observer_ops *ops,
+			void *context)
+{
+	struct cras_observer_client *client;
+
+	client = (struct cras_observer_client *)calloc(1, sizeof(*client));
+	if (!client)
+		return NULL;
+	client->context = context;
+	DL_APPEND(g_observer->clients, client);
+	cras_observer_set_ops(client, ops);
+	return client;
+}
+
+void cras_observer_remove(struct cras_observer_client *client)
+{
+	if (!client)
+		return;
+	DL_DELETE(g_observer->clients, client);
+	free(client);
+}
+
+/*
+ * Public interface for notifiers.
+ */
+
+void cras_observer_notify_output_volume(int32_t volume)
+{
+	struct cras_observer_alert_data_volume data;
+
+	data.volume = volume;
+	cras_alert_pending_data(g_observer->alerts.output_volume,
+				&data, sizeof(data));
+}
+
+void cras_observer_notify_output_mute(int muted, int user_muted,
+				      int mute_locked)
+{
+	struct cras_observer_alert_data_mute data;
+
+	data.muted = muted;
+	data.user_muted = user_muted;
+	data.mute_locked = mute_locked;
+	cras_alert_pending_data(g_observer->alerts.output_mute,
+				&data, sizeof(data));
+}
+
+void cras_observer_notify_capture_gain(int32_t gain)
+{
+	struct cras_observer_alert_data_volume data;
+
+	data.volume = gain;
+	cras_alert_pending_data(g_observer->alerts.capture_gain,
+				&data, sizeof(data));
+}
+
+void cras_observer_notify_capture_mute(int muted, int mute_locked)
+{
+	struct cras_observer_alert_data_mute data;
+
+	data.muted = muted;
+	data.user_muted = 0;
+	data.mute_locked = mute_locked;
+	cras_alert_pending_data(g_observer->alerts.capture_mute,
+				&data, sizeof(data));
+}
+
+void cras_observer_notify_nodes(void)
+{
+	cras_alert_pending(g_observer->alerts.nodes);
+}
+
+void cras_observer_notify_active_node(enum CRAS_STREAM_DIRECTION dir,
+				      cras_node_id_t node_id)
+{
+	struct cras_observer_alert_data_active_node data;
+
+	data.direction = dir;
+	data.node_id = node_id;
+	cras_alert_pending_data(g_observer->alerts.active_node,
+				&data, sizeof(data));
+}
+
+void cras_observer_notify_output_node_volume(cras_node_id_t node_id,
+					     int32_t volume)
+{
+	struct cras_observer_alert_data_node_volume data;
+
+	data.node_id = node_id;
+	data.volume = volume;
+	cras_alert_pending_data(g_observer->alerts.output_node_volume,
+				&data, sizeof(data));
+}
+
+void cras_observer_notify_node_left_right_swapped(cras_node_id_t node_id,
+						  int swapped)
+{
+	struct cras_observer_alert_data_node_lr_swapped data;
+
+	data.node_id = node_id;
+	data.swapped = swapped;
+	cras_alert_pending_data(g_observer->alerts.node_left_right_swapped,
+				&data, sizeof(data));
+}
+
+void cras_observer_notify_input_node_gain(cras_node_id_t node_id,
+					  int32_t gain)
+{
+	struct cras_observer_alert_data_node_volume data;
+
+	data.node_id = node_id;
+	data.volume = gain;
+	cras_alert_pending_data(g_observer->alerts.input_node_gain,
+				&data, sizeof(data));
+}
+
+void cras_observer_notify_suspend_changed(int suspended)
+{
+	struct cras_observer_alert_data_suspend data;
+
+	data.suspended = suspended;
+	cras_alert_pending_data(g_observer->alerts.suspend_changed,
+				&data, sizeof(data));
+}
+
+void cras_observer_notify_num_active_streams(enum CRAS_STREAM_DIRECTION dir,
+					     uint32_t num_active_streams)
+{
+	struct cras_observer_alert_data_streams data;
+	struct cras_alert *alert;
+
+	data.direction = dir;
+	data.num_active_streams = num_active_streams;
+	alert = g_observer->alerts.num_active_streams[dir];
+	if (!alert)
+		return;
+
+	cras_alert_pending_data(alert, &data, sizeof(data));
+}
diff --git a/cras/src/server/cras_observer.h b/cras/src/server/cras_observer.h
new file mode 100644
index 0000000..bbbaf89
--- /dev/null
+++ b/cras/src/server/cras_observer.h
@@ -0,0 +1,97 @@
+/* Copyright 2016 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_OBSERVER_H
+#define CRAS_OBSERVER_H
+
+#include "cras_observer_ops.h"
+
+struct cras_observer_client;
+
+/* Add an observer.
+ * Args:
+ *    ops - Set callback function pointers in the operations that should be
+ *          called for state changes, or NULL otherwise.
+ *    context - Context pointer passed to the callbacks.
+ * Returns:
+ *    Valid pointer to the client reference, or NULL on memory allocation
+ *    error.
+ */
+struct cras_observer_client *cras_observer_add(
+			const struct cras_observer_ops *ops,
+			void *context);
+
+/* Retrieve the observed state changes.
+ * Args:
+ *    client - The client to query.
+ *    ops - Filled with the current values in the callback table.
+ */
+void cras_observer_get_ops(const struct cras_observer_client *client,
+			   struct cras_observer_ops *ops);
+
+/* Update the observed state changes.
+ * Args:
+ *    client - The client to modify.
+ *    ops - Set callback function pointers in the operations that should be
+ *          called for state changes, or NULL otherwise.
+ */
+void cras_observer_set_ops(struct cras_observer_client *client,
+			   const struct cras_observer_ops *ops);
+
+/* Returns non-zero if the given ops are empty. */
+int cras_observer_ops_are_empty(const struct cras_observer_ops *ops);
+
+/* Remove this observer client.
+ * Args:
+ *    client - The client to remove.
+ */
+void cras_observer_remove(struct cras_observer_client *client);
+
+/* Initialize the observer server. */
+int cras_observer_server_init();
+
+/* Destroy the observer server. */
+void cras_observer_server_free();
+
+/* Notify observers of output volume change. */
+void cras_observer_notify_output_volume(int32_t volume);
+
+/* Notify observers of output mute change. */
+void cras_observer_notify_output_mute(int muted, int user_muted,
+				      int mute_locked);
+
+/* Notify observers of capture gain change. */
+void cras_observer_notify_capture_gain(int32_t gain);
+
+/* Notify observers of capture mute change. */
+void cras_observer_notify_capture_mute(int muted, int mute_locked);
+
+/* Notify observers of a nodes list change. */
+void cras_observer_notify_nodes(void);
+
+/* Notify observers of active output node change. */
+void cras_observer_notify_active_node(enum CRAS_STREAM_DIRECTION dir,
+				      cras_node_id_t node_id);
+
+/* Notify observers of output node volume change. */
+void cras_observer_notify_output_node_volume(cras_node_id_t node_id,
+					     int32_t volume);
+
+/* Notify observers of node left-right swap change. */
+void cras_observer_notify_node_left_right_swapped(cras_node_id_t node_id,
+						  int swapped);
+
+/* Notify observers of input node gain change. */
+void cras_observer_notify_input_node_gain(cras_node_id_t node_id,
+					  int32_t gain);
+
+/* Notify observers of suspend state changed. */
+void cras_observer_notify_suspend_changed(int suspended);
+
+/* Notify observers of the number of active streams. */
+void cras_observer_notify_num_active_streams(enum CRAS_STREAM_DIRECTION dir,
+					     uint32_t num_active_streams);
+
+#endif /* CRAS_OBSERVER_H */
diff --git a/cras/src/server/cras_ramp.c b/cras/src/server/cras_ramp.c
new file mode 100644
index 0000000..e83dc51
--- /dev/null
+++ b/cras/src/server/cras_ramp.c
@@ -0,0 +1,150 @@
+/* Copyright 2016 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_ramp.h"
+
+
+/*
+ * State of cras_ramp:
+ *   CRAS_RAMP_STATE_IDLE: No ramping is started, or a ramping is already done.
+ *   CRAS_RAMP_STATE_UP: Ramping up from 0 to 1.
+ *   CRAS_RAMP_STATE_DOWN: Ramping down from certain scaler to 0.
+ */
+enum CRAS_RAMP_STATE {
+	CRAS_RAMP_STATE_IDLE,
+	CRAS_RAMP_STATE_UP,
+	CRAS_RAMP_STATE_DOWN,
+};
+
+/*
+ * Struct to hold ramping information.
+ * Members:
+ *   state: Current state. One of CRAS_RAMP_STATE.
+ *   ramped_frames: Number of frames that have passed after starting ramping.
+ *   duration_frames: The targeted number of frames for whole ramping duration.
+ *   increment: The scaler increment that should be added to scaler for
+ *              every frame.
+ *   start_scaler: The initial scaler.
+ *   cb: Callback function to call after ramping is done.
+ *   cb_data: Data passed to cb.
+ */
+struct cras_ramp {
+	enum CRAS_RAMP_STATE state;
+	int ramped_frames;
+	int duration_frames;
+	float increment;
+	float start_scaler;
+	void (*cb)(void *data);
+	void *cb_data;
+};
+
+void cras_ramp_destroy(struct cras_ramp* ramp)
+{
+	free(ramp);
+}
+
+struct cras_ramp* cras_ramp_create()
+{
+	struct cras_ramp* ramp;
+	ramp = (struct cras_ramp*)malloc(sizeof(*ramp));
+	if (ramp == NULL) {
+		return NULL;
+	}
+	cras_ramp_reset(ramp);
+	return ramp;
+}
+
+int cras_ramp_reset(struct cras_ramp *ramp) {
+	ramp->state = CRAS_RAMP_STATE_IDLE;
+	ramp->ramped_frames = 0;
+	ramp->duration_frames = 0;
+	ramp->increment = 0;
+	ramp->start_scaler = 1.0;
+	return 0;
+}
+
+int cras_ramp_start(struct cras_ramp *ramp, int is_up, int duration_frames,
+		    cras_ramp_cb cb, void *cb_data)
+{
+	struct cras_ramp_action action;
+
+	/* Get current scaler position so it can serve as new start scaler. */
+	action = cras_ramp_get_current_action(ramp);
+	if (action.type == CRAS_RAMP_ACTION_INVALID)
+		return -EINVAL;
+
+        /* Set initial scaler to current scaler so ramping up/down can be
+         * smoothly switched. */
+	if (is_up) {
+		ramp->state = CRAS_RAMP_STATE_UP;
+		if (action.type == CRAS_RAMP_ACTION_NONE)
+			ramp->start_scaler = 0;
+		else
+			ramp->start_scaler = action.scaler;
+		ramp->increment = (1 - ramp->start_scaler) / duration_frames;
+	} else {
+		ramp->state = CRAS_RAMP_STATE_DOWN;
+		if (action.type == CRAS_RAMP_ACTION_NONE)
+			ramp->start_scaler = 1;
+		else
+			ramp->start_scaler = action.scaler;
+
+		ramp->increment = -ramp->start_scaler / duration_frames;
+	}
+	ramp->ramped_frames = 0;
+	ramp->duration_frames = duration_frames;
+	ramp->cb = cb;
+	ramp->cb_data = cb_data;
+	return 0;
+}
+
+struct cras_ramp_action cras_ramp_get_current_action(const struct cras_ramp *ramp)
+{
+	struct cras_ramp_action action;
+
+	if (ramp->ramped_frames < 0) {
+		action.type = CRAS_RAMP_ACTION_INVALID;
+		action.scaler = 1.0;
+		action.increment = 0.0;
+		return action;
+	}
+
+	switch (ramp->state) {
+	case CRAS_RAMP_STATE_IDLE:
+		action.type = CRAS_RAMP_ACTION_NONE;
+		action.scaler = 1.0;
+		action.increment = 0.0;
+		break;
+	case CRAS_RAMP_STATE_DOWN:
+		action.type = CRAS_RAMP_ACTION_PARTIAL;
+		action.scaler = ramp->start_scaler +
+				ramp->ramped_frames * ramp->increment;
+		action.increment = ramp->increment;
+		break;
+	case CRAS_RAMP_STATE_UP:
+		action.type = CRAS_RAMP_ACTION_PARTIAL;
+		action.scaler = ramp->start_scaler +
+				ramp->ramped_frames * ramp->increment;
+		action.increment = ramp->increment;
+		break;
+	}
+	return action;
+}
+
+int cras_ramp_update_ramped_frames(
+		struct cras_ramp *ramp, int num_frames)
+{
+	if (ramp->state == CRAS_RAMP_STATE_IDLE)
+		return -EINVAL;
+	ramp->ramped_frames += num_frames;
+	if (ramp->ramped_frames >= ramp->duration_frames) {
+		ramp->state = CRAS_RAMP_STATE_IDLE;
+		if (ramp->cb)
+			ramp->cb(ramp->cb_data);
+	}
+	return 0;
+}
diff --git a/cras/src/server/cras_ramp.h b/cras/src/server/cras_ramp.h
new file mode 100644
index 0000000..5b1acfb
--- /dev/null
+++ b/cras/src/server/cras_ramp.h
@@ -0,0 +1,74 @@
+/* Copyright 2016 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_RAMP_H_
+#define CRAS_RAMP_H_
+
+#include "cras_iodev.h"
+
+struct cras_ramp;
+
+/*
+ * Infomation telling user how to do ramping.
+ * action CRAS_RAMP_ACTION_NONE: No scale should be applied.
+ * action CRAS_RAMP_ACTION_PARTIAL: scale sample by sample starting from scaler
+ *                                  and increase increment for each sample.
+ * action CRAS_RAMP_ACTION_INVALID: There is an error in cras_ramp.
+ */
+enum CRAS_RAMP_ACTION_TYPE {
+	CRAS_RAMP_ACTION_NONE,
+	CRAS_RAMP_ACTION_PARTIAL,
+	CRAS_RAMP_ACTION_INVALID,
+};
+
+/*
+ * Struct to hold current ramping action for user.
+ * Members:
+ *   type: See CRAS_RAMP_ACTION_TYPE.
+ *   scaler: The initial scaler to be applied.
+ *   increment: The scaler increment that should be added to scaler for every
+ *              frame.
+ */
+struct cras_ramp_action {
+	enum CRAS_RAMP_ACTION_TYPE type;
+	float scaler;
+	float increment;
+};
+
+typedef void (*cras_ramp_cb)(void *arg);
+
+/* Creates a ramp. */
+struct cras_ramp* cras_ramp_create();
+
+/* Destroys a ramp. */
+void cras_ramp_destroy(struct cras_ramp* ramp);
+
+/* Starts ramping up from 0 to 1 or from 1 to 0 for duration_frames frames.
+ * Args:
+ *   ramp[in]: The ramp struct to start.
+ *   is_up[in]: 1 to ramp up and 0 to ramp down.
+ *   duration_frames[in]: Ramp duration in frames.
+ *   cb[in]: The callback function to call after ramping is done. User can set
+ *           cb to turn off speaker/headphone switch after ramping down
+ *           is done.
+ *   cb_data[in]: The data passed to callback function.
+ * Returns:
+ *   0 on success; negative error code on failure.
+ */
+int cras_ramp_start(struct cras_ramp *ramp, int is_up, int duration_frames,
+		    cras_ramp_cb cb, void *cb_data);
+
+/* Resets ramp and cancels current ramping. */
+int cras_ramp_reset(struct cras_ramp *ramp);
+
+/* Gets current ramp action. */
+struct cras_ramp_action cras_ramp_get_current_action(
+		const struct cras_ramp *ramp);
+
+/* Updates number of samples that went through ramping. */
+int cras_ramp_update_ramped_frames(
+		struct cras_ramp *ramp, int num_frames);
+
+#endif /* CRAS_RAMP_H_ */
diff --git a/cras/src/server/cras_rclient.c b/cras/src/server/cras_rclient.c
index 957bdf3..6a68351 100644
--- a/cras/src/server/cras_rclient.c
+++ b/cras/src/server/cras_rclient.c
@@ -13,6 +13,7 @@
 #include "cras_iodev.h"
 #include "cras_iodev_list.h"
 #include "cras_messages.h"
+#include "cras_observer.h"
 #include "cras_rclient.h"
 #include "cras_rstream.h"
 #include "cras_system_state.h"
@@ -26,6 +27,7 @@
  *  fd - Connection for client communication.
  */
 struct cras_rclient {
+	struct cras_observer_client *observer;
 	size_t id;
 	int fd;
 };
@@ -40,6 +42,7 @@
 	struct cras_audio_format remote_fmt;
 	struct cras_rstream_config stream_config;
 	int rc;
+	int stream_fds[2];
 
 	unpack_cras_audio_format(&remote_fmt, &msg->format);
 
@@ -77,10 +80,10 @@
 			0, /* No error. */
 			msg->stream_id,
 			&remote_fmt,
-			cras_rstream_input_shm_key(stream),
-			cras_rstream_output_shm_key(stream),
 			cras_rstream_get_total_shm_size(stream));
-	rc = cras_rclient_send_message(client, &reply.header);
+	stream_fds[0] = cras_rstream_input_shm_fd(stream);
+	stream_fds[1] = cras_rstream_output_shm_fd(stream);
+	rc = cras_rclient_send_message(client, &reply.header, stream_fds, 2);
 	if (rc < 0) {
 		syslog(LOG_ERR, "Failed to send connected messaged\n");
 		stream_list_rm(cras_iodev_list_get_stream_list(),
@@ -93,8 +96,8 @@
 reply_err:
 	/* Send the error code to the client. */
 	cras_fill_client_stream_connected(&reply, rc, msg->stream_id,
-					  &remote_fmt, 0, 0, 0);
-	cras_rclient_send_message(client, &reply.header);
+					  &remote_fmt, 0);
+	cras_rclient_send_message(client, &reply.header, NULL, 0);
 
 	if (aud_fd >= 0)
 		close(aud_fd);
@@ -122,7 +125,211 @@
 	state = cras_system_state_get_no_lock();
 	audio_thread_dump_thread_info(cras_iodev_list_get_audio_thread(),
 				      &state->audio_debug_info);
-	cras_rclient_send_message(client, &msg.header);
+	cras_rclient_send_message(client, &msg.header, NULL, 0);
+}
+
+static void handle_get_hotword_models(struct cras_rclient *client,
+				      cras_node_id_t node_id)
+{
+	struct cras_client_get_hotword_models_ready *msg;
+	char *hotword_models;
+	unsigned hotword_models_size;
+	uint8_t buf[CRAS_CLIENT_MAX_MSG_SIZE];
+
+	msg = (struct cras_client_get_hotword_models_ready *)buf;
+	hotword_models = cras_iodev_list_get_hotword_models(node_id);
+	if (!hotword_models)
+		goto empty_reply;
+	hotword_models_size = strlen(hotword_models);
+	if (hotword_models_size + sizeof(*msg) > CRAS_CLIENT_MAX_MSG_SIZE) {
+		free(hotword_models);
+		goto empty_reply;
+	}
+
+	cras_fill_client_get_hotword_models_ready(msg, hotword_models,
+						  hotword_models_size);
+	cras_rclient_send_message(client, &msg->header, NULL, 0);
+	free(hotword_models);
+	return;
+
+empty_reply:
+	cras_fill_client_get_hotword_models_ready(msg, NULL, 0);
+	cras_rclient_send_message(client, &msg->header, NULL, 0);
+}
+
+/* Client notification callback functions. */
+
+static void send_output_volume_changed(void *context, int32_t volume)
+{
+	struct cras_client_volume_changed msg;
+	struct cras_rclient *client = (struct cras_rclient *)context;
+
+	cras_fill_client_output_volume_changed(&msg, volume);
+	cras_rclient_send_message(client, &msg.header, NULL, 0);
+}
+
+static void send_output_mute_changed(void *context, int muted,
+				     int user_muted, int mute_locked)
+{
+	struct cras_client_mute_changed msg;
+	struct cras_rclient *client = (struct cras_rclient *)context;
+
+	cras_fill_client_output_mute_changed(&msg, muted,
+					     user_muted, mute_locked);
+	cras_rclient_send_message(client, &msg.header, NULL, 0);
+}
+
+static void send_capture_gain_changed(void *context, int32_t gain)
+{
+	struct cras_client_volume_changed msg;
+	struct cras_rclient *client = (struct cras_rclient *)context;
+
+	cras_fill_client_capture_gain_changed(&msg, gain);
+	cras_rclient_send_message(client, &msg.header, NULL, 0);
+}
+
+static void send_capture_mute_changed(void *context, int muted, int mute_locked)
+{
+	struct cras_client_mute_changed msg;
+	struct cras_rclient *client = (struct cras_rclient *)context;
+
+	cras_fill_client_capture_mute_changed(&msg, muted, mute_locked);
+	cras_rclient_send_message(client, &msg.header, NULL, 0);
+}
+
+static void send_nodes_changed(void *context)
+{
+	struct cras_client_nodes_changed msg;
+	struct cras_rclient *client = (struct cras_rclient *)context;
+
+	cras_fill_client_nodes_changed(&msg);
+	cras_rclient_send_message(client, &msg.header, NULL, 0);
+}
+
+static void send_active_node_changed(void *context,
+				     enum CRAS_STREAM_DIRECTION dir,
+				     cras_node_id_t node_id)
+{
+	struct cras_client_active_node_changed msg;
+	struct cras_rclient *client = (struct cras_rclient *)context;
+
+	cras_fill_client_active_node_changed(&msg, dir, node_id);
+	cras_rclient_send_message(client, &msg.header, NULL, 0);
+}
+
+static void send_output_node_volume_changed(void *context,
+					    cras_node_id_t node_id,
+					    int32_t volume)
+{
+	struct cras_client_node_value_changed msg;
+	struct cras_rclient *client = (struct cras_rclient *)context;
+
+	cras_fill_client_output_node_volume_changed(&msg, node_id, volume);
+	cras_rclient_send_message(client, &msg.header, NULL, 0);
+}
+
+static void send_node_left_right_swapped_changed(void *context,
+						 cras_node_id_t node_id,
+						 int swapped)
+{
+	struct cras_client_node_value_changed msg;
+	struct cras_rclient *client = (struct cras_rclient *)context;
+
+	cras_fill_client_node_left_right_swapped_changed(
+						&msg, node_id, swapped);
+	cras_rclient_send_message(client, &msg.header, NULL, 0);
+}
+
+static void send_input_node_gain_changed(void *context,
+					 cras_node_id_t node_id,
+					 int32_t gain)
+{
+	struct cras_client_node_value_changed msg;
+	struct cras_rclient *client = (struct cras_rclient *)context;
+
+	cras_fill_client_input_node_gain_changed(&msg, node_id, gain);
+	cras_rclient_send_message(client, &msg.header, NULL, 0);
+}
+
+static void send_num_active_streams_changed(void *context,
+					    enum CRAS_STREAM_DIRECTION dir,
+					    uint32_t num_active_streams)
+{
+	struct cras_client_num_active_streams_changed msg;
+	struct cras_rclient *client = (struct cras_rclient *)context;
+
+	cras_fill_client_num_active_streams_changed(
+					&msg, dir, num_active_streams);
+	cras_rclient_send_message(client, &msg.header, NULL, 0);
+}
+
+static void register_for_notification(struct cras_rclient *client,
+				      enum CRAS_CLIENT_MESSAGE_ID msg_id,
+				      int do_register)
+{
+	struct cras_observer_ops observer_ops;
+	int empty;
+
+	cras_observer_get_ops(client->observer, &observer_ops);
+
+	switch (msg_id) {
+	case CRAS_CLIENT_OUTPUT_VOLUME_CHANGED:
+		observer_ops.output_volume_changed =
+			do_register ? send_output_volume_changed : NULL;
+		break;
+	case CRAS_CLIENT_OUTPUT_MUTE_CHANGED:
+		observer_ops.output_mute_changed =
+			do_register ? send_output_mute_changed : NULL;
+		break;
+	case CRAS_CLIENT_CAPTURE_GAIN_CHANGED:
+		observer_ops.capture_gain_changed =
+			do_register ? send_capture_gain_changed : NULL;
+		break;
+	case CRAS_CLIENT_CAPTURE_MUTE_CHANGED:
+		observer_ops.capture_mute_changed =
+			do_register ? send_capture_mute_changed : NULL;
+		break;
+	case CRAS_CLIENT_NODES_CHANGED:
+		observer_ops.nodes_changed =
+			do_register ? send_nodes_changed : NULL;
+		break;
+	case CRAS_CLIENT_ACTIVE_NODE_CHANGED:
+		observer_ops.active_node_changed =
+			do_register ? send_active_node_changed : NULL;
+		break;
+	case CRAS_CLIENT_OUTPUT_NODE_VOLUME_CHANGED:
+		observer_ops.output_node_volume_changed =
+			do_register ? send_output_node_volume_changed : NULL;
+		break;
+	case CRAS_CLIENT_NODE_LEFT_RIGHT_SWAPPED_CHANGED:
+		observer_ops.node_left_right_swapped_changed =
+		    do_register ? send_node_left_right_swapped_changed : NULL;
+		break;
+	case CRAS_CLIENT_INPUT_NODE_GAIN_CHANGED:
+		observer_ops.input_node_gain_changed =
+			do_register ? send_input_node_gain_changed : NULL;
+		break;
+	case CRAS_CLIENT_NUM_ACTIVE_STREAMS_CHANGED:
+		observer_ops.num_active_streams_changed =
+			do_register ? send_num_active_streams_changed : NULL;
+		break;
+	default:
+		syslog(LOG_ERR,
+		       "Invalid client notification message ID: %u", msg_id);
+		break;
+	}
+
+	empty = cras_observer_ops_are_empty(&observer_ops);
+	if (client->observer) {
+		if (empty) {
+			cras_observer_remove(client->observer);
+			client->observer = NULL;
+		} else {
+			cras_observer_set_ops(client->observer, &observer_ops);
+		}
+	} else if (!empty) {
+		client->observer = cras_observer_add(&observer_ops, client);
+	}
 }
 
 /*
@@ -135,16 +342,18 @@
 {
 	struct cras_rclient *client;
 	struct cras_client_connected msg;
+	int state_fd;
 
-	client = calloc(1, sizeof(struct cras_rclient));
+	client = (struct cras_rclient *)calloc(1, sizeof(struct cras_rclient));
 	if (!client)
 		return NULL;
 
 	client->fd = fd;
 	client->id = id;
 
-	cras_fill_client_connected(&msg, client->id, cras_sys_state_shm_key());
-	cras_rclient_send_message(client, &msg.header);
+	cras_fill_client_connected(&msg, client->id);
+	state_fd = cras_sys_state_shm_fd();
+	cras_rclient_send_message(client, &msg.header, &state_fd, 1);
 
 	return client;
 }
@@ -152,6 +361,7 @@
 /* Removes all streams that the client owns and destroys it. */
 void cras_rclient_destroy(struct cras_rclient *client)
 {
+	cras_observer_remove(client->observer);
 	stream_list_rm_all_client_streams(
 			cras_iodev_list_get_stream_list(), client);
 	free(client);
@@ -260,8 +470,9 @@
 	case CRAS_SERVER_TEST_DEV_COMMAND: {
 		const struct cras_test_dev_command *m =
 			(const struct cras_test_dev_command *)msg;
-		cras_iodev_list_test_dev_command(m->iodev_idx, m->command,
-						 m->data_len, m->data);
+		cras_iodev_list_test_dev_command(
+			m->iodev_idx, (enum CRAS_TEST_IODEV_CMD)m->command,
+			m->data_len, m->data);
 		break;
 	}
 	case CRAS_SERVER_SUSPEND:
@@ -270,6 +481,35 @@
 	case CRAS_SERVER_RESUME:
 		cras_system_set_suspended(0);
 		break;
+	case CRAS_CONFIG_GLOBAL_REMIX: {
+		const struct cras_config_global_remix *m =
+			(const struct cras_config_global_remix *)msg;
+		audio_thread_config_global_remix(
+				cras_iodev_list_get_audio_thread(),
+				m->num_channels,
+				m->coefficient);
+		break;
+	}
+	case CRAS_SERVER_GET_HOTWORD_MODELS: {
+		handle_get_hotword_models(client,
+			((const struct cras_get_hotword_models *)msg)->node_id);
+		break;
+	}
+	case CRAS_SERVER_SET_HOTWORD_MODEL: {
+		const struct cras_set_hotword_model *m =
+			(const struct cras_set_hotword_model *)msg;
+		cras_iodev_list_set_hotword_model(m->node_id,
+						  m->model_name);
+		break;
+	}
+	case CRAS_SERVER_REGISTER_NOTIFICATION: {
+		const struct cras_register_notification *m =
+			(struct cras_register_notification *)msg;
+		register_for_notification(
+			client, (enum CRAS_CLIENT_MESSAGE_ID)m->msg_id,
+			m->do_register);
+		break;
+	}
 	default:
 		break;
 	}
@@ -279,8 +519,11 @@
 
 /* Sends a message to the client. */
 int cras_rclient_send_message(const struct cras_rclient *client,
-			      const struct cras_client_message *msg)
+			      const struct cras_client_message *msg,
+			      int *fds,
+			      unsigned int num_fds)
 {
-	return write(client->fd, msg, msg->length);
+	return cras_send_with_fds(client->fd, (const void *)msg, msg->length,
+				  fds, num_fds);
 }
 
diff --git a/cras/src/server/cras_rclient.h b/cras/src/server/cras_rclient.h
index 57cf04a..26e4f3b 100644
--- a/cras/src/server/cras_rclient.h
+++ b/cras/src/server/cras_rclient.h
@@ -44,10 +44,14 @@
  * Args:
  *    client - The client to send the message to.
  *    msg - The message to send.
+ *    fds - Array of file descriptors or null
+ *    num_fds - Number of entries in the fds array.
  * Returns:
  *    number of bytes written on success, otherwise a negative error code.
  */
 int cras_rclient_send_message(const struct cras_rclient *client,
-			      const struct cras_client_message *msg);
+			      const struct cras_client_message *msg,
+			      int *fds,
+			      unsigned int num_fds);
 
 #endif /* CRAS_RCLIENT_H_ */
diff --git a/cras/src/server/cras_rstream.c b/cras/src/server/cras_rstream.c
index 5296b5d..8b9b046 100644
--- a/cras/src/server/cras_rstream.c
+++ b/cras/src/server/cras_rstream.c
@@ -2,8 +2,11 @@
  * 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/shm.h>
+#include <sys/mman.h>
+#include <sys/types.h>
 #include <syslog.h>
 
 #include "cras_audio_area.h"
@@ -21,8 +24,7 @@
 		     struct cras_audio_shm *shm,
 		     struct rstream_shm_info *shm_info)
 {
-	size_t used_size, samples_size, total_size, frame_bytes;
-	int loops = 0;
+	size_t used_size, samples_size, frame_bytes;
 	const struct cras_audio_format *fmt = &stream->format;
 
 	if (shm->area != NULL) /* already setup */
@@ -32,25 +34,24 @@
 			fmt->num_channels;
 	used_size = stream->buffer_frames * frame_bytes;
 	samples_size = used_size * CRAS_NUM_SHM_BUFFERS;
-	total_size = sizeof(struct cras_audio_shm_area) + samples_size;
+	shm_info->length = sizeof(struct cras_audio_shm_area) + samples_size;
 
-	/* Find an available shm key. */
-	do {
-		shm_info->shm_key = getpid() + stream->stream_id + loops;
-		shm_info->shm_id = shmget(shm_info->shm_key,
-					  total_size,
-					  IPC_CREAT | IPC_EXCL | 0660);
-	} while (shm_info->shm_id < 0 && loops++ < 100);
-	if (shm_info->shm_id < 0) {
-		syslog(LOG_ERR, "shmget");
-		return shm_info->shm_id;
+	snprintf(shm_info->shm_name, sizeof(shm_info->shm_name),
+		 "/cras-%d-stream-%08x", getpid(), stream->stream_id);
+
+	shm_info->shm_fd = cras_shm_open_rw(shm_info->shm_name, shm_info->length);
+	if (shm_info->shm_fd < 0)
+		return shm_info->shm_fd;
+
+	/* mmap shm. */
+	shm->area = mmap(NULL, shm_info->length,
+			 PROT_READ | PROT_WRITE, MAP_SHARED,
+			 shm_info->shm_fd, 0);
+	if (shm->area == (struct cras_audio_shm_area *)-1) {
+		close(shm_info->shm_fd);
+		return errno;
 	}
 
-	/* Attach to shm and clear it. */
-	shm->area = shmat(shm_info->shm_id, NULL, 0);
-	if (shm->area == (void *)-1)
-		return -ENOMEM;
-	memset(shm->area, 0, total_size);
 	cras_shm_set_volume_scaler(shm, 1.0);
 	/* Set up config and copy to shared area. */
 	cras_shm_set_frame_bytes(shm, frame_bytes);
@@ -84,6 +85,7 @@
 /* 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,
 				     struct cras_rclient *client,
@@ -114,6 +116,11 @@
 		syslog(LOG_ERR, "rstream: Invalid direction.\n");
 		return -EINVAL;
 	}
+	if (stream_type < CRAS_STREAM_TYPE_DEFAULT ||
+	    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");
 		return -EINVAL;
@@ -130,6 +137,7 @@
 	int rc;
 
 	rc = verify_rstream_parameters(config->direction, config->format,
+				       config->stream_type,
 				       config->buffer_frames,
 				       config->cb_threshold, config->client,
 				       stream_out);
@@ -178,9 +186,9 @@
 	cras_system_state_stream_removed(stream->direction);
 	close(stream->fd);
 	if (stream->shm.area != NULL) {
-		shmdt(stream->shm.area);
-		shmctl(stream->shm_info.shm_id, IPC_RMID,
-		       (void *)stream->shm.area);
+		munmap(stream->shm.area, stream->shm_info.length);
+		cras_shm_close_unlink(stream->shm_info.shm_name,
+				      stream->shm_info.shm_fd);
 		cras_audio_area_destroy(stream->audio_area);
 	}
 	buffer_share_destroy(stream->buf_state);
@@ -197,10 +205,19 @@
 		if (timespec_after(&ts, &rstream->longest_fetch_interval))
 			rstream->longest_fetch_interval = ts;
 	}
-	rstream->last_fetch_ts = *now;
 }
 
-int cras_rstream_request_audio(const struct cras_rstream *stream)
+static void init_audio_message(struct audio_message *msg,
+			       enum CRAS_AUDIO_MESSAGE_ID id,
+			       uint32_t frames)
+{
+	memset(msg, 0, sizeof(*msg));
+	msg->id = id;
+	msg->frames = frames;
+}
+
+int cras_rstream_request_audio(struct cras_rstream *stream,
+			       const struct timespec *now)
 {
 	struct audio_message msg;
 	int rc;
@@ -209,9 +226,13 @@
 	if (stream->direction != CRAS_STREAM_OUTPUT)
 		return 0;
 
-	msg.id = AUDIO_MESSAGE_REQUEST_DATA;
-	msg.frames = stream->cb_threshold;
+	stream->last_fetch_ts = *now;
+
+	init_audio_message(&msg, AUDIO_MESSAGE_REQUEST_DATA,
+			   stream->cb_threshold);
 	rc = write(stream->fd, &msg, sizeof(msg));
+	if (rc < 0)
+		return -errno;
 	return rc;
 }
 
@@ -222,9 +243,10 @@
 
 	cras_shm_buffer_write_complete(&stream->shm);
 
-	msg.id = AUDIO_MESSAGE_DATA_READY;
-	msg.frames = count;
+	init_audio_message(&msg, AUDIO_MESSAGE_DATA_READY, count);
 	rc = write(stream->fd, &msg, sizeof(msg));
+	if (rc < 0)
+		return -errno;
 	return rc;
 }
 
@@ -234,8 +256,10 @@
 	int rc;
 
 	rc = read(stream->fd, &msg, sizeof(msg));
-	if (rc < 0 || msg.error < 0)
-		return -EIO;
+	if (rc < 0)
+		return -errno;
+	if (msg.error < 0)
+		return msg.error;
 	return 0;
 }
 
@@ -312,7 +336,8 @@
 void cras_rstream_update_queued_frames(struct cras_rstream *rstream)
 {
 	const struct cras_audio_shm *shm = cras_rstream_output_shm(rstream);
-	rstream->queued_frames = cras_shm_get_frames(shm);
+	rstream->queued_frames = MIN(cras_shm_get_frames(shm),
+				     rstream->buffer_frames);
 }
 
 unsigned int cras_rstream_playable_frames(struct cras_rstream *rstream,
diff --git a/cras/src/server/cras_rstream.h b/cras/src/server/cras_rstream.h
index 36709a7..1a22717 100644
--- a/cras/src/server/cras_rstream.h
+++ b/cras/src/server/cras_rstream.h
@@ -16,12 +16,14 @@
 struct dev_mix;
 
 /* Holds identifiers for an shm segment.
- *  shm_key - Key shared with client to access shm.
- *  shm_id - Returned from shmget.
+ *  shm_fd - File descriptor shared with client to access shm.
+ *  shm_name - Name of the shm area.
+ *  length - Size of the shm region.
  */
 struct rstream_shm_info {
-	int shm_key;
-	int shm_id;
+	int shm_fd;
+	char shm_name[NAME_MAX];
+	size_t length;
 };
 
 /* Holds informations about the master active device.
@@ -200,15 +202,15 @@
 }
 
 /* Gets the shm key used to find the outputshm region. */
-static inline int cras_rstream_output_shm_key(const struct cras_rstream *stream)
+static inline int cras_rstream_output_shm_fd(const struct cras_rstream *stream)
 {
-	return stream->shm_info.shm_key;
+	return stream->shm_info.shm_fd;
 }
 
 /* Gets the shm key used to find the input shm region. */
-static inline int cras_rstream_input_shm_key(const struct cras_rstream *stream)
+static inline int cras_rstream_input_shm_fd(const struct cras_rstream *stream)
 {
-	return stream->shm_info.shm_key;
+	return stream->shm_info.shm_fd;
 }
 
 /* Gets the total size of shm memory allocated. */
@@ -254,7 +256,8 @@
 					const struct timespec *now);
 
 /* Requests min_req frames from the client. */
-int cras_rstream_request_audio(const struct cras_rstream *stream);
+int cras_rstream_request_audio(struct cras_rstream *stream,
+			       const struct timespec *now);
 
 /* Tells a capture client that count frames are ready. */
 int cras_rstream_audio_ready(struct cras_rstream *stream, size_t count);
diff --git a/cras/src/server/cras_server.c b/cras/src/server/cras_server.c
index 18aef8a..ff5e601 100644
--- a/cras/src/server/cras_server.c
+++ b/cras/src/server/cras_server.c
@@ -5,7 +5,9 @@
 
 #define _GNU_SOURCE /* Needed for Linux socket credential passing. */
 
+#ifdef CRAS_DBUS
 #include <dbus/dbus.h>
+#endif
 #include <errno.h>
 #include <poll.h>
 #include <stdint.h>
@@ -21,25 +23,32 @@
 #include <syslog.h>
 #include <unistd.h>
 
+#ifdef CRAS_DBUS
+#include "cras_a2dp_endpoint.h"
 #include "cras_bt_manager.h"
 #include "cras_bt_device.h"
-#include "cras_a2dp_endpoint.h"
-#include "cras_config.h"
+#include "cras_bt_player.h"
 #include "cras_dbus.h"
 #include "cras_dbus_control.h"
 #include "cras_hfp_ag_profile.h"
+#include "cras_telephony.h"
+#endif
+#include "cras_alert.h"
+#include "cras_config.h"
+#include "cras_device_monitor.h"
 #include "cras_iodev_list.h"
 #include "cras_main_message.h"
 #include "cras_messages.h"
 #include "cras_metrics.h"
+#include "cras_observer.h"
 #include "cras_rclient.h"
 #include "cras_server.h"
 #include "cras_server_metrics.h"
 #include "cras_system_state.h"
-#include "cras_telephony.h"
 #include "cras_tm.h"
 #include "cras_udev.h"
 #include "cras_util.h"
+#include "cras_mix.h"
 #include "utlist.h"
 
 /* Store a list of clients that are attached to the server.
@@ -108,9 +117,10 @@
 	struct cras_server_message *msg;
 	int nread;
 	int fd;
+	unsigned int num_fds = 1;
 
 	msg = (struct cras_server_message *)buf;
-	nread = cras_recv_with_fd(client->fd, buf, sizeof(buf), &fd);
+	nread = cras_recv_with_fds(client->fd, buf, sizeof(buf), &fd, &num_fds);
 	if (nread < sizeof(msg->length))
 		goto read_error;
 	if (msg->length != nread)
@@ -121,7 +131,14 @@
 read_error:
 	if (fd != -1)
 		close(fd);
-	syslog(LOG_DEBUG, "read err, removing client %zu", client->id);
+	switch (nread) {
+	case 0:
+		break;
+	default:
+		syslog(LOG_DEBUG, "read err [%d] '%s', removing client %zu",
+		       -nread, strerror(-nread), client->id);
+		break;
+	}
 	remove_client(client);
 }
 
@@ -301,6 +318,60 @@
 		cras_metrics_log_event(kNoCodecsFoundMetric);
 }
 
+#if defined(__amd64__)
+/* CPU detection - probaby best to move this elsewhere */
+static void cpuid(unsigned int *eax, unsigned int *ebx, unsigned int *ecx,
+	          unsigned int *edx, unsigned int op)
+{
+	__asm__ __volatile__ (
+		"cpuid"
+		: "=a" (*eax),
+		  "=b" (*ebx),
+		  "=c" (*ecx),
+		  "=d" (*edx)
+		: "a" (op), "c" (0)
+    );
+}
+
+static unsigned int cpu_x86_flags(void)
+{
+	unsigned int eax, ebx, ecx, edx, id;
+	unsigned int cpu_flags = 0;
+
+	cpuid(&id, &ebx, &ecx, &edx, 0);
+
+	if (id >= 1) {
+		cpuid(&eax, &ebx, &ecx, &edx, 1);
+
+		if (ecx & (1 << 20))
+			cpu_flags |= CPU_X86_SSE4_2;
+
+		if (ecx & (1 << 28))
+			cpu_flags |= CPU_X86_AVX;
+
+		if (ecx & (1 << 12))
+			cpu_flags |= CPU_X86_FMA;
+	}
+
+	if (id >= 7) {
+		cpuid(&eax, &ebx, &ecx, &edx, 7);
+
+		if (ebx & (1 << 5))
+			cpu_flags |= CPU_X86_AVX2;
+	}
+
+	return cpu_flags;
+}
+#endif
+
+int cpu_get_flags(void)
+{
+#if defined(__amd64__)
+	return cpu_x86_flags();
+#endif
+	return 0;
+}
+
 /*
  * Exported Interface.
  */
@@ -310,6 +381,12 @@
 	/* Log to syslog. */
 	openlog("cras_server", LOG_PID, LOG_USER);
 
+	/* Initialize global observer. */
+	cras_observer_server_init();
+
+	/* init mixer with CPU capabilities */
+	cras_mix_init(cpu_get_flags());
+
 	/* Allow clients to register callbacks for file descriptors.
 	 * add_select_fd and rm_select_fd will add and remove file descriptors
 	 * from the list that are passed to select in the main loop below. */
@@ -320,11 +397,12 @@
 	return 0;
 }
 
-int cras_server_run()
+int cras_server_run(unsigned int profile_disable_mask)
 {
 	static const unsigned int OUTPUT_CHECK_MS = 5 * 1000;
-
+#ifdef CRAS_DBUS
 	DBusConnection *dbus_conn;
+#endif
 	int socket_fd = -1;
 	int rc = 0;
 	const char *sockdir;
@@ -341,20 +419,30 @@
 	pollfds = malloc(sizeof(*pollfds) * pollfds_size);
 
 	cras_udev_start_sound_subsystem_monitor();
+#ifdef CRAS_DBUS
 	cras_bt_device_start_monitor();
+#endif
 
 	cras_server_metrics_init();
 
+	cras_device_monitor_init();
+
+#ifdef CRAS_DBUS
 	dbus_threads_init_default();
 	dbus_conn = cras_dbus_connect_system_bus();
 	if (dbus_conn) {
 		cras_bt_start(dbus_conn);
-		cras_hfp_ag_profile_create(dbus_conn);
-		cras_hsp_ag_profile_create(dbus_conn);
+		if (!(profile_disable_mask & CRAS_SERVER_PROFILE_MASK_HFP))
+			cras_hfp_ag_profile_create(dbus_conn);
+		if (!(profile_disable_mask & CRAS_SERVER_PROFILE_MASK_HSP))
+			cras_hsp_ag_profile_create(dbus_conn);
 		cras_telephony_start(dbus_conn);
-		cras_a2dp_endpoint_create(dbus_conn);
+		if (!(profile_disable_mask & CRAS_SERVER_PROFILE_MASK_A2DP))
+			cras_a2dp_endpoint_create(dbus_conn);
+		cras_bt_player_create(dbus_conn);
 		cras_dbus_control_start(dbus_conn);
 	}
+#endif
 
 	socket_fd = socket(PF_UNIX, SOCK_SEQPACKET, 0);
 	if (socket_fd < 0) {
@@ -464,8 +552,10 @@
 
 		cleanup_select_fds(&server_instance);
 
+#ifdef CRAS_DBUS
 		if (dbus_conn)
 			cras_dbus_dispatch(dbus_conn);
+#endif
 
 		cras_alert_process_all_pending_alerts();
 	}
@@ -476,6 +566,7 @@
 		unlink(addr.sun_path);
 	}
 	free(pollfds);
+	cras_observer_server_free();
 	return rc;
 }
 
@@ -484,5 +575,5 @@
 	struct attached_client *client;
 
 	DL_FOREACH(server_instance.clients_head, client)
-		cras_rclient_send_message(client->client, msg);
+		cras_rclient_send_message(client->client, msg, NULL, 0);
 }
diff --git a/cras/src/server/cras_server.h b/cras/src/server/cras_server.h
index a52279b..aff9b7a 100644
--- a/cras/src/server/cras_server.h
+++ b/cras/src/server/cras_server.h
@@ -9,6 +9,13 @@
 #ifndef CRAS_SERVER_H_
 #define CRAS_SERVER_H_
 
+/*
+ * Bitmask for cras_server_run() argument profile_disable_mask
+ */
+#define CRAS_SERVER_PROFILE_MASK_HFP	(1 << 0)
+#define CRAS_SERVER_PROFILE_MASK_HSP	(1 << 1)
+#define CRAS_SERVER_PROFILE_MASK_A2DP	(1 << 2)
+
 struct cras_client_message;
 
 /* Initialize some server setup. Mainly to add the select handler first
@@ -19,7 +26,7 @@
 /* Runs the CRAS server.  Open the main socket and begin listening for
  * connections and for messages from clients that have connected.
  */
-int cras_server_run();
+int cras_server_run(unsigned int profile_disable_mask);
 
 /* Send a message to all attached clients. */
 void cras_server_send_to_all_clients(const struct cras_client_message *msg);
diff --git a/cras/src/server/cras_server_metrics.c b/cras/src/server/cras_server_metrics.c
index 966d515..c824407 100644
--- a/cras/src/server/cras_server_metrics.c
+++ b/cras/src/server/cras_server_metrics.c
@@ -5,6 +5,7 @@
 
 #include <errno.h>
 #include <stdio.h>
+#include <string.h>
 #include <syslog.h>
 
 #include "cras_metrics.h"
@@ -24,16 +25,24 @@
 	unsigned data;
 };
 
+static void init_longest_fetch_delay_msg(
+		struct cras_server_metrics_message *msg,
+		enum CRAS_SERVER_METRICS_TYPE type,
+		unsigned data)
+{
+	memset(msg, 0, sizeof(*msg));
+	msg->header.type = CRAS_MAIN_METRICS;
+	msg->header.length = sizeof(*msg);
+	msg->metrics_type = type;
+	msg->data = data;
+}
+
 int cras_server_metrics_longest_fetch_delay(unsigned delay_msec)
 {
 	struct cras_server_metrics_message msg;
 	int err;
 
-	msg.header.type = CRAS_MAIN_METRICS;
-	msg.header.length = sizeof(msg);
-	msg.metrics_type = LONGEST_FETCH_DELAY;
-	msg.data = delay_msec;
-
+	init_longest_fetch_delay_msg(&msg, LONGEST_FETCH_DELAY, delay_msec);
 	err = cras_main_message_send((struct cras_main_message *)&msg);
 	if (err < 0) {
 		syslog(LOG_ERR, "Failed to send metrics message");
@@ -43,7 +52,7 @@
 	return 0;
 }
 
-void metrics_longest_fetch_delay(unsigned delay_msec)
+static void metrics_longest_fetch_delay(unsigned delay_msec)
 {
 	static const int fetch_delay_min_msec = 1;
 	static const int fetch_delay_max_msec = 10000;
@@ -56,7 +65,7 @@
 				   fetch_delay_nbuckets);
 }
 
-void handle_metrics_message(struct cras_main_message *msg, void *arg)
+static void handle_metrics_message(struct cras_main_message *msg, void *arg)
 {
 	struct cras_server_metrics_message *metrics_msg =
 			(struct cras_server_metrics_message *)msg;
@@ -76,4 +85,4 @@
 	cras_main_message_add_handler(CRAS_MAIN_METRICS,
 				      handle_metrics_message, NULL);
 	return 0;
-}
\ No newline at end of file
+}
diff --git a/cras/src/server/cras_system_state.c b/cras/src/server/cras_system_state.c
index 26e573f..3483ebc 100644
--- a/cras/src/server/cras_system_state.c
+++ b/cras/src/server/cras_system_state.c
@@ -3,17 +3,20 @@
  * found in the LICENSE file.
  */
 
+#include <fcntl.h>
 #include <pthread.h>
 #include <string.h>
 #include <stdlib.h>
+#include <sys/mman.h>
 #include <sys/param.h>
-#include <sys/shm.h>
 #include <sys/stat.h>
 #include <syslog.h>
 
 #include "cras_alsa_card.h"
 #include "cras_config.h"
 #include "cras_device_blacklist.h"
+#include "cras_observer.h"
+#include "cras_shm.h"
 #include "cras_system_state.h"
 #include "cras_tm.h"
 #include "cras_types.h"
@@ -28,17 +31,14 @@
 /* The system state.
  * Members:
  *    exp_state - The exported system state shared with clients.
- *    shm_key - Key for shm area of system_state struct.
- *    shm_id - Id for shm area of system_state struct.
+ *    shm_name - Name of posix shm region for exported state.
+ *    shm_fd - fd for shm area of system_state struct.
+ *    shm_fd_ro - fd for shm area of system_state struct, opened read-only.
+ *    shm_size - Size of the shm area.
  *    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.
- *    volume_alert - Called when the system volume changes.
- *    mute_alert - Called when the system mute state changes.
- *    suspend_alert - Called when the audio suspend state changes.
- *    capture_gain_alert - Called when the capture gain changes.
- *    capture_mute_alert - Called when the capture mute changes.
- *    volume_limits_alert - Called when the volume limits are changed.
- *    active_streams_alert - Called when the number of active streams changes.
  *    cards - A list of active sound cards in the system.
  *    update_lock - Protects the update_count, as audio threads can update the
  *      stream count.
@@ -46,17 +46,13 @@
  */
 static struct {
 	struct cras_server_state *exp_state;
-	key_t shm_key;
-	int shm_id;
+	char shm_name[NAME_MAX];
+	int shm_fd;
+	int shm_fd_ro;
+	size_t shm_size;
 	const char *device_config_dir;
+	const char *internal_ucm_suffix;
 	struct cras_device_blacklist *device_blacklist;
-	struct cras_alert *volume_alert;
-	struct cras_alert *mute_alert;
-	struct cras_alert *suspend_alert;
-	struct cras_alert *capture_gain_alert;
-	struct cras_alert *capture_mute_alert;
-	struct cras_alert *volume_limits_alert;
-	struct cras_alert *active_streams_alert;
 	struct card_list *cards;
 	pthread_mutex_t update_lock;
 	struct cras_tm *tm;
@@ -74,25 +70,26 @@
 void cras_system_state_init(const char *device_config_dir)
 {
 	struct cras_server_state *exp_state;
-	unsigned loops = 0;
 	int rc;
 
-	/* Find an available shm key. */
-	do {
-		state.shm_key = getpid() + rand();
-		state.shm_id = shmget(state.shm_key, sizeof(*exp_state),
-				      IPC_CREAT | IPC_EXCL | 0640);
-	} while (state.shm_id < 0 && loops++ < 100);
-	if (state.shm_id < 0) {
-		syslog(LOG_ERR, "Fatal: system state can't shmget");
-		exit(state.shm_id);
-	}
+	state.shm_size = sizeof(*exp_state);
 
-	exp_state = shmat(state.shm_id, NULL, 0);
-	if (exp_state == (void *)-1) {
-		syslog(LOG_ERR, "Fatal: system state can't shmat");
+	snprintf(state.shm_name, sizeof(state.shm_name), "/cras-%d", getpid());
+	state.shm_fd = cras_shm_open_rw(state.shm_name, state.shm_size);
+	if (state.shm_fd < 0)
+		exit(state.shm_fd);
+
+	/* mmap shm. */
+	exp_state = mmap(NULL, state.shm_size,
+			 PROT_READ | PROT_WRITE, MAP_SHARED,
+			 state.shm_fd, 0);
+	if (exp_state == (struct cras_server_state *)-1)
 		exit(-ENOMEM);
-	}
+
+	/* Open a read-only copy to dup and pass to clients. */
+	state.shm_fd_ro = cras_shm_reopen_ro(state.shm_name, state.shm_fd);
+	if (state.shm_fd_ro < 0)
+		exit(state.shm_fd_ro);
 
 	/* Initial system state. */
 	exp_state->state_version = CRAS_SERVER_STATE_VERSION;
@@ -101,6 +98,7 @@
 	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;
@@ -121,15 +119,7 @@
 	 * Device blacklist is common to all boards so we do not need
 	 * to change device blacklist at run time. */
 	state.device_config_dir = device_config_dir;
-
-	/* Initialize alerts. */
-	state.volume_alert = cras_alert_create(NULL);
-	state.mute_alert = cras_alert_create(NULL);
-	state.suspend_alert = cras_alert_create(NULL);
-	state.capture_gain_alert = cras_alert_create(NULL);
-	state.capture_mute_alert = cras_alert_create(NULL);
-	state.volume_limits_alert = cras_alert_create(NULL);
-	state.active_streams_alert = cras_alert_create(NULL);
+	state.internal_ucm_suffix = NULL;
 
 	state.tm = cras_tm_init();
 	if (!state.tm) {
@@ -142,6 +132,11 @@
 		cras_device_blacklist_create(CRAS_CONFIG_FILE_DIR);
 }
 
+void cras_system_state_set_internal_ucm_suffix(const char *internal_ucm_suffix)
+{
+	state.internal_ucm_suffix = internal_ucm_suffix;
+}
+
 void cras_system_state_deinit()
 {
 	/* Free any resources used.  This prevents unit tests from leaking. */
@@ -151,26 +146,12 @@
 	cras_tm_deinit(state.tm);
 
 	if (state.exp_state) {
-		shmdt(state.exp_state);
-		shmctl(state.shm_id, IPC_RMID, (void *)state.exp_state);
+		munmap(state.exp_state, state.shm_size);
+		cras_shm_close_unlink(state.shm_name, state.shm_fd);
+		if (state.shm_fd_ro != state.shm_fd)
+			close(state.shm_fd_ro);
 	}
 
-	cras_alert_destroy(state.volume_alert);
-	cras_alert_destroy(state.mute_alert);
-	cras_alert_destroy(state.suspend_alert);
-	cras_alert_destroy(state.capture_gain_alert);
-	cras_alert_destroy(state.capture_mute_alert);
-	cras_alert_destroy(state.volume_limits_alert);
-	cras_alert_destroy(state.active_streams_alert);
-
-	state.volume_alert = NULL;
-	state.mute_alert = NULL;
-	state.suspend_alert = NULL;
-	state.capture_gain_alert = NULL;
-	state.capture_mute_alert = NULL;
-	state.volume_limits_alert = NULL;
-	state.active_streams_alert = NULL;
-
 	pthread_mutex_destroy(&state.update_lock);
 }
 
@@ -180,7 +161,7 @@
 		syslog(LOG_DEBUG, "system volume set out of range %zu", volume);
 
 	state.exp_state->volume = MIN(volume, CRAS_MAX_SYSTEM_VOLUME);
-	cras_alert_pending(state.volume_alert);
+	cras_observer_notify_output_volume(state.exp_state->volume);
 }
 
 size_t cras_system_get_volume()
@@ -188,21 +169,14 @@
 	return state.exp_state->volume;
 }
 
-int cras_system_register_volume_changed_cb(cras_alert_cb cb, void *arg)
-{
-	return cras_alert_add_callback(state.volume_alert, cb, arg);
-}
-
-int cras_system_remove_volume_changed_cb(cras_alert_cb cb, void *arg)
-{
-	return cras_alert_rm_callback(state.volume_alert, cb, arg);
-}
-
 void cras_system_set_capture_gain(long gain)
 {
-	state.exp_state->capture_gain =
-		MAX(gain, state.exp_state->min_capture_gain);
-	cras_alert_pending(state.capture_gain_alert);
+	/* 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()
@@ -210,20 +184,20 @@
 	return state.exp_state->capture_gain;
 }
 
-int cras_system_register_capture_gain_changed_cb(cras_alert_cb cb, void *arg)
+void cras_system_notify_mute(void)
 {
-	return cras_alert_add_callback(state.capture_gain_alert, cb, arg);
-}
-
-int cras_system_remove_capture_gain_changed_cb(cras_alert_cb cb, void *arg)
-{
-	return cras_alert_rm_callback(state.capture_gain_alert, cb, arg);
+	cras_observer_notify_output_mute(state.exp_state->mute,
+					 state.exp_state->user_mute,
+					 state.exp_state->mute_locked);
 }
 
 void cras_system_set_user_mute(int mute)
 {
+	if (state.exp_state->user_mute == !!mute)
+		return;
+
 	state.exp_state->user_mute = !!mute;
-	cras_alert_pending(state.mute_alert);
+	cras_system_notify_mute();
 }
 
 void cras_system_set_mute(int mute)
@@ -231,16 +205,20 @@
 	if (state.exp_state->mute_locked)
 		return;
 
+	if (state.exp_state->mute == !!mute)
+		return;
+
 	state.exp_state->mute = !!mute;
-	cras_alert_pending(state.mute_alert);
+	cras_system_notify_mute();
 }
 
 void cras_system_set_mute_locked(int locked)
 {
-	state.exp_state->mute_locked = !!locked;
+	if (state.exp_state->mute_locked == !!locked)
+		return;
 
-	if (!state.exp_state->mute_locked)
-		cras_alert_pending(state.mute_alert);
+	state.exp_state->mute_locked = !!locked;
+	cras_system_notify_mute();
 }
 
 int cras_system_get_mute()
@@ -263,14 +241,10 @@
 	return state.exp_state->mute_locked;
 }
 
-int cras_system_register_mute_changed_cb(cras_alert_cb cb, void *arg)
+void cras_system_notify_capture_mute(void)
 {
-	return cras_alert_add_callback(state.mute_alert, cb, arg);
-}
-
-int cras_system_remove_mute_changed_cb(cras_alert_cb cb, void *arg)
-{
-	return cras_alert_rm_callback(state.mute_alert, cb, arg);
+	cras_observer_notify_capture_mute(state.exp_state->capture_mute,
+					  state.exp_state->capture_mute_locked);
 }
 
 void cras_system_set_capture_mute(int mute)
@@ -279,15 +253,13 @@
 		return;
 
 	state.exp_state->capture_mute = !!mute;
-	cras_alert_pending(state.capture_mute_alert);
+	cras_system_notify_capture_mute();
 }
 
 void cras_system_set_capture_mute_locked(int locked)
 {
 	state.exp_state->capture_mute_locked = !!locked;
-
-	if (!state.exp_state->capture_mute_locked)
-		cras_alert_pending(state.capture_mute_alert);
+	cras_system_notify_capture_mute();
 }
 
 int cras_system_get_capture_mute()
@@ -300,16 +272,6 @@
 	return state.exp_state->capture_mute_locked;
 }
 
-int cras_system_register_capture_mute_changed_cb(cras_alert_cb cb, void *arg)
-{
-	return cras_alert_add_callback(state.capture_mute_alert, cb, arg);
-}
-
-int cras_system_remove_capture_mute_changed_cb(cras_alert_cb cb, void *arg)
-{
-	return cras_alert_rm_callback(state.capture_mute_alert, cb, arg);
-}
-
 int cras_system_get_suspended()
 {
 	return state.exp_state->suspended;
@@ -318,24 +280,13 @@
 void cras_system_set_suspended(int suspended)
 {
 	state.exp_state->suspended = suspended;
-	cras_alert_pending(state.suspend_alert);
-}
-
-int cras_system_register_suspend_cb(cras_alert_cb cb, void *arg)
-{
-	return cras_alert_add_callback(state.suspend_alert, cb, arg);
-}
-
-int cras_system_remove_suspend_cb(cras_alert_cb cb, void *arg)
-{
-	return cras_alert_rm_callback(state.suspend_alert, cb, arg);
+	cras_observer_notify_suspend_changed(suspended);
 }
 
 void cras_system_set_volume_limits(long min, long max)
 {
 	state.exp_state->min_volume_dBFS = min;
 	state.exp_state->max_volume_dBFS = max;
-	cras_alert_pending(state.volume_limits_alert);
 }
 
 long cras_system_get_min_volume()
@@ -348,21 +299,12 @@
 	return state.exp_state->max_volume_dBFS;
 }
 
-int cras_system_register_volume_limits_changed_cb(cras_alert_cb cb, void *arg)
-{
-	return cras_alert_add_callback(state.volume_limits_alert, cb, arg);
-}
-
-int cras_system_remove_volume_limits_changed_cb(cras_alert_cb cb, void *arg)
-{
-	return cras_alert_rm_callback(state.volume_limits_alert, cb, arg);
-}
-
 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;
-	cras_alert_pending(state.volume_limits_alert);
+	/* 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()
@@ -390,9 +332,13 @@
 		if (card_index == cras_alsa_card_get_index(card->card))
 			return -EINVAL;
 	}
-	alsa_card = cras_alsa_card_create(alsa_card_info,
-					  state.device_config_dir,
-					  state.device_blacklist);
+	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);
 	if (alsa_card == NULL)
 		return -ENOMEM;
 	card = calloc(1, sizeof(*card));
@@ -472,7 +418,8 @@
 	s->num_streams_attached++;
 
 	cras_system_state_update_complete();
-	cras_alert_pending(state.active_streams_alert);
+	cras_observer_notify_num_active_streams(
+		direction, s->num_active_streams[direction]);
 }
 
 void cras_system_state_stream_removed(enum CRAS_STREAM_DIRECTION direction)
@@ -496,7 +443,8 @@
 	s->num_active_streams[direction]--;
 
 	cras_system_state_update_complete();
-	cras_alert_pending(state.active_streams_alert);
+	cras_observer_notify_num_active_streams(
+		direction, s->num_active_streams[direction]);
 }
 
 unsigned cras_system_state_get_active_streams()
@@ -514,16 +462,6 @@
 	return state.exp_state->num_active_streams[direction];
 }
 
-int cras_system_register_active_streams_changed_cb(cras_alert_cb cb, void *arg)
-{
-	return cras_alert_add_callback(state.active_streams_alert, cb, arg);
-}
-
-int cras_system_remove_active_streams_changed_cb(cras_alert_cb cb, void *arg)
-{
-	return cras_alert_rm_callback(state.active_streams_alert, cb, arg);
-}
-
 void cras_system_state_get_last_stream_active_time(struct cras_timespec *ts)
 {
 	*ts = state.exp_state->last_active_stream_time;
@@ -575,9 +513,9 @@
 	return state.exp_state;
 }
 
-key_t cras_sys_state_shm_key()
+key_t cras_sys_state_shm_fd()
 {
-	return state.shm_key;
+	return state.shm_fd_ro;
 }
 
 struct cras_tm *cras_system_state_get_tm()
diff --git a/cras/src/server/cras_system_state.h b/cras/src/server/cras_system_state.h
index 71dd9ec..f0bb98a 100644
--- a/cras/src/server/cras_system_state.h
+++ b/cras/src/server/cras_system_state.h
@@ -15,7 +15,6 @@
 
 #include <stddef.h>
 
-#include "cras_alert.h"
 #include "cras_types.h"
 
 #define CRAS_MAX_SYSTEM_VOLUME 100
@@ -36,46 +35,19 @@
 void cras_system_state_init(const char *device_config_dir);
 void cras_system_state_deinit();
 
+/* Sets the suffix string to control which UCM config fo load. */
+void cras_system_state_set_internal_ucm_suffix(const char *internal_ucm_suffix);
+
 /* Sets the system volume.  Will be applied by the active device. */
 void cras_system_set_volume(size_t volume);
 /* Gets the current system volume. */
 size_t cras_system_get_volume();
 
-/* Adds a callback to call when the volume changes.
- * Args:
- *    cb - Function to call when volume changes.
- *    arg - Value to pass back to callback.
- */
-int cras_system_register_volume_changed_cb(cras_alert_cb cb, void *arg);
-
-/* Removes a callback to call when the volume changes.  Only removes the entry
- * if both cb and arg match the values passed to the register function.
- * Args:
- *    cb - Function to call when volume changes.
- *    arg - Value to passed back to callback.
- */
-int cras_system_remove_volume_changed_cb(cras_alert_cb cb, void *arg);
-
 /* 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. */
 long cras_system_get_capture_gain();
 
-/* Adds a callback to call when the capture volume changes.
- * Args:
- *    cb - Function to call when capture volume changes.
- *    arg - Value to pass back to callback.
- */
-int cras_system_register_capture_gain_changed_cb(cras_alert_cb cb, void *arg);
-
-/* Removes a callback to call when the capture volume changes.  Only removes the
- * entry if both cb and arg match the values passed to the register function.
- * Args:
- *    cb - Function to call when capture volume changes.
- *    arg - Value to passed back to callback.
- */
-int cras_system_remove_capture_gain_changed_cb(cras_alert_cb cb, void *arg);
-
 /* Sets if the system is muted by the user. */
 void cras_system_set_user_mute(int muted);
 /* Sets if the system is muted for . */
@@ -100,27 +72,6 @@
  */
 void cras_system_set_suspended(int suspend);
 
-/* Adds a callback to call when the suspend state changes. */
-int cras_system_register_suspend_cb(cras_alert_cb cb, void *arg);
-
-/* Removes a callback to call when the suspend state changes. */
-int cras_system_remove_suspend_cb(cras_alert_cb cb, void *arg);
-
-/* Adds a callback to call when the mute state changes.
- * Args:
- *    cb - Function to call when mute state changes.
- *    arg - Value to pass back to callback.
- */
-int cras_system_register_mute_changed_cb(cras_alert_cb cb, void *arg);
-
-/* Removes a callback to call when the mute state changes.  Only removes the
- * entry if both cb and arg match the values passed to the register function.
- * Args:
- *    cb - Function to call when volume changes.
- *    arg - Value to passed back to callback.
- */
-int cras_system_remove_mute_changed_cb(cras_alert_cb cb, void *arg);
-
 /* Sets if the system capture path is muted or not. */
 void cras_system_set_capture_mute(int muted);
 /* Sets if the system capture path muting is locked or not. */
@@ -130,22 +81,6 @@
 /* Gets if the system capture path muting is locked or not. */
 int cras_system_get_capture_mute_locked();
 
-/* Adds a callback to call when the capture mute state changes.
- * Args:
- *    cb - Function to call when the capture mute state changes.
- *    arg - Value to pass back to callback.
- */
-int cras_system_register_capture_mute_changed_cb(cras_alert_cb cb, void *arg);
-
-/* Removes a callback to call when the capture mute state changes.  Only removes
- * the entry if both cb and arg match the values passed to the register
- * function.
- * Args:
- *    cb - Function to call when volume changes.
- *    arg - Value to passed back to callback.
- */
-int cras_system_remove_capture_mute_changed_cb(cras_alert_cb cb, void *arg);
-
 /* Sets the value in dB of the MAX and MIN volume settings.  This will allow
  * clients to query what range of control is available.  Both arguments are
  * specified as dB * 100.
@@ -159,22 +94,6 @@
 /* Returns the dB value when volume = CRAS_MAX_SYSTEM_VOLUME, in dB * 100. */
 long cras_system_get_max_volume();
 
-/* Adds a callback to call when the volume limits change.
- * Args:
- *    cb - Function to call when there is a change.
- *    arg - Value to pass back to callback.
- */
-int cras_system_register_volume_limits_changed_cb(cras_alert_cb cb, void *arg);
-
-/* Removes a callback to call when the volume limits change.  Only removes
- * the entry if both cb and arg match the values passed to the register
- * function.
- * Args:
- *    cb - Function to call when there is a change.
- *    arg - Value to passed back to callback.
- */
-int cras_system_remove_volume_limits_changed_cb(cras_alert_cb cb, void *arg);
-
 /* 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.
@@ -282,22 +201,6 @@
 unsigned cras_system_state_get_active_streams_by_direction(
 	enum CRAS_STREAM_DIRECTION direction);
 
-/* Adds a callback to call when the number of active streams changes.
- * Args:
- *    cb - Function to call when there is a change.
- *    arg - Value to pass back to callback.
- */
-int cras_system_register_active_streams_changed_cb(cras_alert_cb cb, void *arg);
-
-/* Removes a callback to call when the number of active streams changes.
- * Only removes the entry if both cb and arg match the values passed to the
- * register function.
- * Args:
- *    cb - Function to call when there is a change.
- *    arg - Value to passed back to callback.
- */
-int cras_system_remove_active_streams_changed_cb(cras_alert_cb cb, void *arg);
-
 /* Fills ts with the time the last stream was removed from the system, the time
  * the stream count went to zero.
  */
@@ -350,8 +253,8 @@
  * log.  Don't add calls to this function. */
 struct cras_server_state *cras_system_state_get_no_lock();
 
-/* Returns the shm key for the server_state structure. */
-key_t cras_sys_state_shm_key();
+/* Returns the shm fd for the server_state structure. */
+key_t cras_sys_state_shm_fd();
 
 /* Returns the timer manager. */
 struct cras_tm *cras_system_state_get_tm();
diff --git a/cras/src/server/cras_tm.c b/cras/src/server/cras_tm.c
index 525d7c4..2eb9fe1 100644
--- a/cras/src/server/cras_tm.c
+++ b/cras/src/server/cras_tm.c
@@ -127,13 +127,24 @@
 void cras_tm_call_callbacks(struct cras_tm *tm)
 {
 	struct timespec now;
-	struct cras_timer *t;
+	struct cras_timer *t, *next;
 
 	clock_gettime(CLOCK_MONOTONIC_RAW, &now);
 
-	DL_FOREACH(tm->timers, t)
+	/* Don't use DL_FOREACH to iterate timers because in each loop the
+	 * next timer pointer is stored for later access but it could be
+	 * cancelled and freed in current timer's callback causing invalid
+	 * memory access. */
+	t = tm->timers;
+	while (t) {
+		next = t->next;
 		if (timespec_sooner(&t->ts, &now)) {
 			t->cb(t, t->cb_data);
+			/* Update next timer because it could have been modified
+			 * in t->cb(). */
+			next = t->next;
 			cras_tm_cancel_timer(tm, t);
 		}
+		t = next;
+	}
 }
diff --git a/cras/src/server/cras_udev.c b/cras/src/server/cras_udev.c
index f95b21b..2ba1fcd 100644
--- a/cras/src/server/cras_udev.c
+++ b/cras/src/server/cras_udev.c
@@ -252,24 +252,33 @@
 			       struct udev_device *dev)
 {
 	const char *sysattr;
-	dev = udev_device_get_parent_with_subsystem_devtype(dev,
-							    "usb",
-							    "usb_device");
-	if (!dev)
+	struct udev_device *parent_dev =
+		udev_device_get_parent_with_subsystem_devtype(dev,
+							      "usb",
+							      "usb_device");
+	if (!parent_dev)
 		return;
 
-	sysattr = udev_device_get_sysattr_value(dev, "idVendor");
+	sysattr = udev_device_get_sysattr_value(parent_dev, "idVendor");
 	if (sysattr)
 		card_info->usb_vendor_id = strtol(sysattr, NULL, 16);
-	sysattr = udev_device_get_sysattr_value(dev, "idProduct");
+	sysattr = udev_device_get_sysattr_value(parent_dev, "idProduct");
 	if (sysattr)
 		card_info->usb_product_id = strtol(sysattr, NULL, 16);
+	sysattr = udev_device_get_sysattr_value(parent_dev, "serial");
+	if (sysattr) {
+		strncpy(card_info->usb_serial_number, sysattr,
+			USB_SERIAL_NUMBER_BUFFER_SIZE - 1);
+		card_info->usb_serial_number[USB_SERIAL_NUMBER_BUFFER_SIZE - 1]
+			= '\0';
+	}
 
-	card_info->usb_desc_checksum = calculate_desc_checksum(dev);
+	card_info->usb_desc_checksum = calculate_desc_checksum(parent_dev);
 
-	syslog(LOG_ERR, "USB card: vendor:%04x, product:%04x, checksum:%08x",
+	syslog(LOG_ERR, "USB card: vendor:%04x, product:%04x, serial num:%s, "
+	       "checksum:%08x",
 		card_info->usb_vendor_id, card_info->usb_product_id,
-		card_info->usb_desc_checksum);
+		card_info->usb_serial_number, card_info->usb_desc_checksum);
 }
 
 static void device_add_alsa(struct udev_device *dev,
@@ -278,6 +287,7 @@
 			    unsigned internal)
 {
 	struct cras_alsa_card_info card_info;
+	memset(&card_info, 0, sizeof(card_info));
 
 	udev_delay_for_alsa();
 	card_info.card_index = card;
diff --git a/cras/src/server/cras_utf8.c b/cras/src/server/cras_utf8.c
new file mode 100644
index 0000000..d3780d3
--- /dev/null
+++ b/cras/src/server/cras_utf8.c
@@ -0,0 +1,198 @@
+/* Copyright 2016 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 <stdlib.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+#ifdef CRAS_DBUS
+#include <dbus/dbus.h>
+#endif
+
+#include "cras_utf8.h"
+#include "cras_util.h"
+
+static const uint8_t kUTF8ByteOrderMask[3] = { 0xef, 0xbb, 0xbf };
+
+typedef struct u8range {
+	uint8_t min;
+	uint8_t max;
+} u8range_t;
+
+static const u8range_t kUTF8TwoByteSeq[] = {
+	{ 0xc2, 0xdf },
+	{ 0x80, 0xbf },
+	{ 0, 0 }
+};
+
+static const u8range_t kUTF8ByteSeqE0[] = {
+	{ 0xe0, 0xe0 },
+	{ 0xa0, 0xbf },
+	{ 0x80, 0xbf },
+	{ 0, 0 }
+};
+
+static const u8range_t kUTF8ByteSeqE1EC[] = {
+	{ 0xe1, 0xec },
+	{ 0x80, 0xbf },
+	{ 0x80, 0xbf },
+	{ 0, 0 }
+};
+
+static const u8range_t kUTF8ByteSeqED[] = {
+	{ 0xed, 0xed },
+	{ 0x80, 0x9f },
+	{ 0x80, 0xbf },
+	{ 0, 0 }
+};
+
+static const u8range_t kUTF8ByteSeqEEEF[] = {
+	{ 0xee, 0xef },
+	{ 0x80, 0xbf },
+	{ 0x80, 0xbf },
+	{ 0, 0 }
+};
+
+static const u8range_t kUTF8ByteSeqF0[] = {
+	{ 0xf0, 0xf0 },
+	{ 0x90, 0xbf },
+	{ 0x80, 0xbf },
+	{ 0x80, 0xbf },
+	{ 0, 0 }
+};
+
+static const u8range_t kUTF8ByteSeqF1F3[] = {
+	{ 0xf1, 0xf3 },
+	{ 0x80, 0xbf },
+	{ 0x80, 0xbf },
+	{ 0x80, 0xbf },
+	{ 0, 0 }
+};
+
+static const u8range_t kUTF8ByteSeqF4[] = {
+	{ 0xf4, 0xf4 },
+	{ 0x80, 0x8f },
+	{ 0x80, 0xbf },
+	{ 0x80, 0xbf },
+	{ 0, 0 }
+};
+
+static const u8range_t kUTF8NullRange[] = {
+	{ 0, 0 }
+};
+
+typedef struct utf8seq {
+	const u8range_t *ranges;
+} utf8seq_t;
+
+static const utf8seq_t kUTF8Sequences[] = {
+	{ kUTF8TwoByteSeq },
+	{ kUTF8ByteSeqE0 },
+	{ kUTF8ByteSeqE1EC },
+	{ kUTF8ByteSeqED },
+	{ kUTF8ByteSeqEEEF },
+	{ kUTF8ByteSeqF0 },
+	{ kUTF8ByteSeqF1F3 },
+	{ kUTF8ByteSeqF4 },
+	{ kUTF8NullRange }
+};
+
+int valid_utf8_string(const char *string, size_t *bad_pos)
+{
+	int bom_chars = 0;
+	uint8_t byte;
+	const char *pos = string;
+	int ret = 1;
+	const utf8seq_t *seq = NULL;
+	const u8range_t *range = NULL;
+
+	if (!pos) {
+		ret = 0;
+		goto error;
+	}
+
+	while ((byte = (uint8_t)*(pos++))) {
+		if (!range || range->min == 0) {
+			if (byte < 128) {
+				/* Ascii character. */
+				continue;
+			}
+
+			if (bom_chars < ARRAY_SIZE(kUTF8ByteOrderMask)) {
+				if (byte == kUTF8ByteOrderMask[bom_chars]) {
+					bom_chars++;
+					continue;
+				} else {
+					/* Characters not matching BOM.
+					 * Rewind and assume that there is
+					 * no BOM. */
+					bom_chars =
+					        ARRAY_SIZE(kUTF8ByteOrderMask);
+                                        pos = string;
+					continue;
+				}
+			}
+
+			/* Find the matching sequence of characters by
+			 * matching the first character in the sequence.
+			 */
+			seq = kUTF8Sequences;
+			while (seq->ranges->min != 0) {
+				if (byte >= seq->ranges->min &&
+				    byte <= seq->ranges->max) {
+					/* Matching sequence. */
+					break;
+				}
+				seq++;
+			}
+
+			if (seq->ranges->min == 0) {
+				/* Could not find a matching sequence. */
+				ret = 0;
+				goto error;
+			}
+
+			/* Found the appropriate sequence. */
+			range = seq->ranges + 1;
+			continue;
+		}
+
+		if (byte >= range->min && byte <= range->max) {
+			range++;
+			continue;
+		}
+
+		/* This character doesn't belong in UTF8. */
+		ret = 0;
+		goto error;
+	}
+
+	if (range && range->min != 0) {
+	        /* Stopped in the middle of a sequence. */
+	        ret = 0;
+	}
+
+error:
+	if (bad_pos)
+		*bad_pos = pos - string - 1;
+	return ret;
+}
+
+#ifdef CRAS_DBUS
+/* Use the DBus implementation if available to ensure that the UTF-8
+ * sequences match those expected by the DBus implementation. */
+
+int is_utf8_string(const char *string)
+{
+	return !!dbus_validate_utf8(string, NULL);
+}
+
+#else
+
+int is_utf8_string (const char *string) {
+	return valid_utf8_string(string, NULL);
+}
+
+#endif
diff --git a/cras/src/server/cras_utf8.h b/cras/src/server/cras_utf8.h
new file mode 100644
index 0000000..ab0fda0
--- /dev/null
+++ b/cras/src/server/cras_utf8.h
@@ -0,0 +1,36 @@
+/* Copyright 2016 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 <sys/types.h>
+
+/* Checks if a string is valid UTF-8.
+ *
+ * Supports 1 to 4 character UTF-8 sequences. Passes tests here:
+ *    https://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt
+ *
+ * Exceptions: The following unicode non-characters are allowed:
+ *    U+FFFE, U+FFFF, U+FDD0 - U+FDEF, U+nFFFE (n = 1 - 10),
+ *    U+nFFFD (n = 1 - 10).
+ *
+ * Args:
+ *    string[in] - a string.
+ *    bad_pos[out] - position of the first bad character.
+ *
+ * Returns:
+ *    1 if it is a vlid utf-8 string. 0 otherwise.
+ *    bad_pos contains the strlen() of the string if it is
+ *    valid.
+ */
+int valid_utf8_string(const char *string, size_t *bad_pos);
+
+/* Checks if a string is a valid utf-8 string.
+ *
+ * Args:
+ *    string[in] - a string.
+ *
+ * Returns:
+ *    1 if it is a valid utf-8 string. 0 otherwise.
+ */
+int is_utf8_string(const char* string);
diff --git a/cras/src/server/dev_stream.c b/cras/src/server/dev_stream.c
index a80b948..4d893cc 100644
--- a/cras/src/server/dev_stream.c
+++ b/cras/src/server/dev_stream.c
@@ -23,19 +23,31 @@
  */
 static const int coarse_rate_adjust_step = 3;
 
+/*
+ * Allow capture callback to fire this much earlier than the scheduled
+ * next_cb_ts to avoid an extra wake of audio thread.
+ */
+static const struct timespec capture_callback_fuzz_ts = {
+	.tv_sec = 0,
+	.tv_nsec = 1000000, /* 1 ms. */
+};
+
 struct dev_stream *dev_stream_create(struct cras_rstream *stream,
 				     unsigned int dev_id,
 				     const struct cras_audio_format *dev_fmt,
-				     void *dev_ptr)
+				     void *dev_ptr,
+				     struct timespec *cb_ts)
 {
 	struct dev_stream *out;
 	struct cras_audio_format *stream_fmt = &stream->format;
 	int rc = 0;
-	unsigned int max_frames;
+	unsigned int max_frames, dev_frames, buf_bytes;
+	const struct cras_audio_format *ofmt;
 
 	out = calloc(1, sizeof(*out));
 	out->dev_id = dev_id;
 	out->stream = stream;
+	out->dev_rate = dev_fmt->frame_rate;
 
 	if (stream->direction == CRAS_STREAM_OUTPUT) {
 		max_frames = MAX(stream->buffer_frames,
@@ -63,33 +75,28 @@
 		return NULL;
 	}
 
-	if (out->conv) {
-		unsigned int dev_frames;
-		unsigned int buf_bytes;
-		const struct cras_audio_format *ofmt =
-				cras_fmt_conv_out_format(out->conv);
+	ofmt = cras_fmt_conv_out_format(out->conv);
 
-		dev_frames = (stream->direction == CRAS_STREAM_OUTPUT)
-			? cras_fmt_conv_in_frames_to_out(out->conv,
-							 stream->buffer_frames)
-			: cras_fmt_conv_out_frames_to_in(out->conv,
-							 stream->buffer_frames);
+	dev_frames = (stream->direction == CRAS_STREAM_OUTPUT)
+		? cras_fmt_conv_in_frames_to_out(out->conv,
+						 stream->buffer_frames)
+		: cras_fmt_conv_out_frames_to_in(out->conv,
+						 stream->buffer_frames);
 
-		out->conv_buffer_size_frames = 2 * MAX(dev_frames,
-						       stream->buffer_frames);
+	out->conv_buffer_size_frames = 2 * MAX(dev_frames,
+					       stream->buffer_frames);
 
-		/* Create conversion buffer and area using the output format
-		 * of the format converter. Note that this format might not be
-		 * identical to stream_fmt for capture. */
-		buf_bytes = out->conv_buffer_size_frames * cras_get_format_bytes(ofmt);
-		out->conv_buffer = byte_buffer_create(buf_bytes);
-		out->conv_area = cras_audio_area_create(ofmt->num_channels);
-	}
+	/* Create conversion buffer and area using the output format
+	 * of the format converter. Note that this format might not be
+	 * identical to stream_fmt for capture. */
+	buf_bytes = out->conv_buffer_size_frames * cras_get_format_bytes(ofmt);
+	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);
-	clock_gettime(CLOCK_MONOTONIC_RAW, &stream->next_cb_ts);
+	stream->next_cb_ts = *cb_ts;
 
 	if (stream->direction != CRAS_STREAM_OUTPUT) {
 		struct timespec extra_sleep;
@@ -255,7 +262,7 @@
 static unsigned int capture_copy_converted_to_stream(
 		struct dev_stream *dev_stream,
 		struct cras_rstream *rstream,
-		unsigned int dev_index)
+		float software_gain_scaler)
 {
 	struct cras_audio_shm *shm;
 	uint8_t *stream_samples;
@@ -306,7 +313,8 @@
 
 		cras_audio_area_copy(rstream->audio_area, offset,
 				     &rstream->format,
-				     dev_stream->conv_area, 0, 1);
+				     dev_stream->conv_area, 0,
+				     software_gain_scaler);
 
 		buf_increment_read(dev_stream->conv_buffer,
 				   write_frames * frame_bytes);
@@ -326,7 +334,7 @@
 unsigned int dev_stream_capture(struct dev_stream *dev_stream,
 			const struct cras_audio_area *area,
 			unsigned int area_offset,
-			unsigned int dev_idx)
+			float software_gain_scaler)
 {
 	struct cras_rstream *rstream = dev_stream->stream;
 	struct cras_audio_shm *shm;
@@ -343,7 +351,8 @@
 			dev_stream,
 			area->channels[0].buf + area_offset * format_bytes,
 			area->frames - area_offset);
-		capture_copy_converted_to_stream(dev_stream, rstream, dev_idx);
+		capture_copy_converted_to_stream(dev_stream, rstream,
+						 software_gain_scaler);
 	} else {
 		unsigned int offset =
 			cras_rstream_dev_offset(rstream, dev_stream->dev_id);
@@ -360,7 +369,9 @@
 
 		nread = cras_audio_area_copy(rstream->audio_area, offset,
 					     &rstream->format, area,
-					     area_offset, 1);
+					     area_offset,
+					     software_gain_scaler);
+
 		ATLOG(atlog, AUDIO_THREAD_CAPTURE_WRITE,
 					    rstream->stream_id,
 					    nread,
@@ -469,18 +480,32 @@
 	return 0;
 }
 
+static int late_enough_for_capture_callback(struct dev_stream *dev_stream)
+{
+	struct timespec now;
+	struct cras_rstream *rstream = dev_stream->stream;
+	clock_gettime(CLOCK_MONOTONIC_RAW, &now);
+	add_timespecs(&now, &capture_callback_fuzz_ts);
+	return timespec_after(&now, &rstream->next_cb_ts);
+}
+
 int dev_stream_capture_update_rstream(struct dev_stream *dev_stream)
 {
 	struct cras_rstream *rstream = dev_stream->stream;
-	unsigned int str_cb_threshold = cras_rstream_get_cb_threshold(rstream);
-	unsigned int frames_ready = str_cb_threshold;
-	struct timespec now;
+	unsigned int frames_ready = cras_rstream_get_cb_threshold(rstream);
+	int rc;
 
 	cras_rstream_update_input_write_pointer(rstream);
 
-	/* If it isn't time for this stream then skip it. */
-	clock_gettime(CLOCK_MONOTONIC_RAW, &now);
+	/*
+	 * For stream without BULK_AUDIO_OK flag, if it isn't time for
+	 * this stream then skip it.
+	 */
+	if (!(rstream->flags & BULK_AUDIO_OK) &&
+	    !late_enough_for_capture_callback(dev_stream))
+		return 0;
 
+	/* If there is not enough data for one callback, skip it. */
 	if (!cras_rstream_input_level_met(rstream))
 		return 0;
 
@@ -493,7 +518,18 @@
 				    frames_ready,
 				    rstream->shm.area->read_buf_idx);
 
-	return cras_rstream_audio_ready(rstream, frames_ready);
+	rc = cras_rstream_audio_ready(rstream, frames_ready);
+
+	if (rc < 0)
+		return rc;
+
+	/* Update next callback time according to perfect schedule. */
+	add_timespecs(&rstream->next_cb_ts,
+		      &rstream->sleep_interval_ts);
+	/* Reset schedule if the schedule is missed. */
+	check_next_wake_time(dev_stream);
+
+	return 0;
 }
 
 void cras_set_playback_timestamp(size_t frame_rate,
@@ -582,9 +618,7 @@
 	struct cras_rstream *rstream = dev_stream->stream;
 	int rc;
 
-	cras_rstream_record_fetch_interval(dev_stream->stream, now);
-
-	rc = cras_rstream_request_audio(dev_stream->stream);
+	rc = cras_rstream_request_audio(dev_stream->stream, now);
 	if (rc < 0)
 		return rc;
 
@@ -607,3 +641,98 @@
 
 	return stream->fd;
 }
+
+/*
+ * Needed frames from this device such that written frames in shm meets
+ * cb_threshold.
+ */
+static int get_input_needed_frames(struct dev_stream *dev_stream,
+				   unsigned int curr_level)
+{
+	struct cras_rstream *rstream = dev_stream->stream;
+	unsigned int rstream_level = cras_rstream_level(rstream);
+	unsigned int dev_offset = cras_rstream_dev_offset(
+			rstream, dev_stream->dev_id);
+	unsigned int needed_for_stream;
+
+	/*
+	 * rstream_level + def_offset is the number of frames written to shm
+	 * from this device.
+	 */
+	if (rstream_level + dev_offset > rstream->cb_threshold) {
+		/* Enough frames from this device for this stream. */
+		return 0;
+	}
+
+	/*
+	 * Needed number of frames in shm such that written frames in shm meets
+	 * cb_threshold.
+	 */
+	needed_for_stream = rstream->cb_threshold - rstream_level - dev_offset;
+
+	/* Convert the number of frames from stream format to device format. */
+	return cras_fmt_conv_out_frames_to_in(dev_stream->conv,
+					      needed_for_stream);
+
+}
+
+/*
+ * Gets proper wake up time for an input stream. It considers both
+ * time for samples to reach one callback level, and the time for next callback.
+ */
+static int get_input_wake_time(struct dev_stream *dev_stream,
+			       unsigned int curr_level,
+			       struct timespec *level_tstamp,
+			       struct timespec *wake_time_out)
+{
+	struct cras_rstream *rstream = dev_stream->stream;
+	struct timespec time_for_sample;
+	int needed_frames_from_device;
+
+	needed_frames_from_device = get_input_needed_frames(
+			dev_stream, curr_level);
+
+	/*
+	 * If current frames in the device can provide needed amount for stream,
+	 * there is no need to wait.
+	 */
+	if (curr_level >= needed_frames_from_device)
+		needed_frames_from_device = 0;
+
+	else
+		needed_frames_from_device -= curr_level;
+
+	cras_frames_to_time(needed_frames_from_device,
+			    dev_stream->dev_rate,
+			    &time_for_sample);
+
+	add_timespecs(&time_for_sample, level_tstamp);
+
+	/* Select the time that is later so both sample and time conditions
+	 * are met. */
+	if (timespec_after(&time_for_sample, &rstream->next_cb_ts)) {
+		*wake_time_out =  time_for_sample;
+	} else {
+		*wake_time_out =  rstream->next_cb_ts;
+	}
+
+	return 0;
+}
+
+int dev_stream_wake_time(struct dev_stream *dev_stream,
+			 unsigned int curr_level,
+			 struct timespec *level_tstamp,
+			 struct timespec *wake_time_out)
+{
+	if (dev_stream->stream->direction == CRAS_STREAM_OUTPUT) {
+		/*
+                 * TODO(cychiang) Implement the method for output stream.
+		 * The logic should be similar to what
+		 * get_next_stream_wake_from_list in audio_thread.c is doing.
+		 */
+		return -EINVAL;
+	}
+
+	return get_input_wake_time(dev_stream, curr_level, level_tstamp,
+				   wake_time_out);
+}
diff --git a/cras/src/server/dev_stream.h b/cras/src/server/dev_stream.h
index 8f68a67..c4af825 100644
--- a/cras/src/server/dev_stream.h
+++ b/cras/src/server/dev_stream.h
@@ -27,7 +27,8 @@
  *    conv - Sample rate or format converter.
  *    conv_buffer - The buffer for converter if needed.
  *    conv_buffer_size_frames - Size of conv_buffer in frames.
- *    skip_mix - Don't mix this next time streams are mixed.
+ *    dev_rate - Sampling rate of device. This is set when dev_stream is
+ *               created.
  */
 struct dev_stream {
 	unsigned int dev_id;
@@ -36,14 +37,14 @@
 	struct byte_buffer *conv_buffer;
 	struct cras_audio_area *conv_area;
 	unsigned int conv_buffer_size_frames;
-	unsigned int skip_mix;
+	size_t dev_rate;
 	struct dev_stream *prev, *next;
 };
 
 struct dev_stream *dev_stream_create(struct cras_rstream *stream,
 				     unsigned int dev_id,
 				     const struct cras_audio_format *dev_fmt,
-				     void *dev_ptr);
+				     void *dev_ptr, struct timespec *cb_ts);
 void dev_stream_destroy(struct dev_stream *dev_stream);
 
 /*
@@ -87,12 +88,12 @@
  *    dev_stream - The struct holding the stream to mix to.
  *    area - The area to copy audio from.
  *    area_offset - The offset at which to start reading from area.
- *    index - The index of the buffer to copy to the dev stream.
+ *    software_gain_scaler - The software gain scaler.
  */
 unsigned int dev_stream_capture(struct dev_stream *dev_stream,
 			const struct cras_audio_area *area,
 			unsigned int area_offset,
-			unsigned int dev_index);
+			float software_gain_scaler);
 
 /* Returns the number of iodevs this stream has attached to. */
 int dev_stream_attached_devs(const struct dev_stream *dev_stream);
@@ -155,6 +156,22 @@
 					const struct timespec *now);
 
 /*
+ * Gets the wake up time for a dev_stream.
+ * For an input stream, it considers both needed samples and proper time
+ * interval between each callbacks.
+ * Args:
+ *   dev_stream[in]: The dev_stream to check wake up time.
+ *   curr_level[in]: The current level of device.
+ *   wake_time_out[out]: A timespec for wake up time.
+ * Returns:
+ *   0 on success; negative error code on failure.
+ */
+int dev_stream_wake_time(struct dev_stream *dev_stream,
+			 unsigned int curr_level,
+			 struct timespec *level_tstamp,
+			 struct timespec *wake_time_out);
+
+/*
  * Returns a non-negative fd if the fd is expecting a message and should be
  * added to the list of descriptors to poll.
  */
diff --git a/cras/src/server/linear_resampler.c b/cras/src/server/linear_resampler.c
index 33b028b..e014e57 100644
--- a/cras/src/server/linear_resampler.c
+++ b/cras/src/server/linear_resampler.c
@@ -35,6 +35,8 @@
 	struct linear_resampler *lr;
 
 	lr = (struct linear_resampler *)calloc(1, sizeof(*lr));
+	if (!lr)
+		return NULL;
 	lr->num_channels = num_channels;
 	lr->format_bytes = format_bytes;
 
diff --git a/cras/src/server/softvol_curve.c b/cras/src/server/softvol_curve.c
index f025693..d98ca4c 100644
--- a/cras/src/server/softvol_curve.c
+++ b/cras/src/server/softvol_curve.c
@@ -117,7 +117,6 @@
 float *softvol_build_from_curve(const struct cras_volume_curve *curve)
 {
 	float *scalers;
-	long max_dBFS, diff;
 	unsigned int volume;
 
 	if (!curve)
@@ -127,12 +126,16 @@
 	if (!scalers)
 		return NULL;
 
-	max_dBFS = curve->get_dBFS(curve, MAX_VOLUME);
-	scalers[MAX_VOLUME] = 1.0;
-
-	for (volume = 0; volume < MAX_VOLUME; volume++) {
-		diff = curve->get_dBFS(curve, volume) - max_dBFS;
-		scalers[volume] = convert_softvol_scaler_from_dB(diff);
+	/* When software volume is used, it is assumed all volume curve values
+	 * are relative to 0 dBFS when converting to scale. If a positive dBFS
+	 * value is specified in curve config, it will be treated as invalid
+	 * and clip to 1.0 in scale.
+	 */
+	for (volume = 0; volume <= MAX_VOLUME; volume++) {
+		scalers[volume] = convert_softvol_scaler_from_dB(
+				curve->get_dBFS(curve, volume));
+		if (scalers[volume] > 1.0)
+			scalers[volume] = 1.0;
 	}
 
 	return scalers;
diff --git a/cras/src/server/test_iodev.c b/cras/src/server/test_iodev.c
index 72a0f3d..4955726 100644
--- a/cras/src/server/test_iodev.c
+++ b/cras/src/server/test_iodev.c
@@ -36,7 +36,6 @@
 
 struct test_iodev {
 	struct cras_iodev base;
-	int open;
 	int fd;
 	struct byte_buffer *audbuff;
 	unsigned int fmt_bytes;
@@ -46,19 +45,8 @@
  * iodev callbacks.
  */
 
-static int is_open(const struct cras_iodev *iodev)
-{
-	struct test_iodev *testio = (struct test_iodev *)iodev;
-
-	return testio->open;
-}
-
-static int dev_running(const struct cras_iodev *iodev)
-{
-	return 1;
-}
-
-static int frames_queued(const struct cras_iodev *iodev)
+static int frames_queued(const struct cras_iodev *iodev,
+			 struct timespec *tstamp)
 {
 	struct test_iodev *testio = (struct test_iodev *)iodev;
 	int available;
@@ -66,6 +54,7 @@
 	if (testio->fd < 0)
 		return 0;
 	ioctl(testio->fd, FIONREAD, &available);
+	clock_gettime(CLOCK_MONOTONIC_RAW, tstamp);
 	return available / testio->fmt_bytes;
 }
 
@@ -78,7 +67,6 @@
 {
 	struct test_iodev *testio = (struct test_iodev *)iodev;
 
-	testio->open = 0;
 	byte_buffer_destroy(testio->audbuff);
 	testio->audbuff = NULL;
 	cras_iodev_free_audio_area(iodev);
@@ -93,7 +81,6 @@
 		return -EINVAL;
 
 	cras_iodev_init_audio_area(iodev, iodev->format->num_channels);
-	testio->open = 1;
 	testio->fmt_bytes = cras_get_format_bytes(iodev->format);
 	testio->audbuff = byte_buffer_create(TEST_BUFFER_SIZE *
 						testio->fmt_bytes);
@@ -161,14 +148,18 @@
 	return nread;
 }
 
-static void update_active_node(struct cras_iodev *iodev, unsigned node_idx)
+static void update_active_node(struct cras_iodev *iodev, unsigned node_idx,
+			       unsigned dev_enabled)
 {
 }
 
 static void play_file_as_hotword(struct test_iodev *testio, const char *path)
 {
 	if (testio->fd >= 0) {
-		audio_thread_rm_callback(testio->fd);
+		/* Remove audio thread callback from main thread. */
+		audio_thread_rm_callback_sync(
+				cras_iodev_list_get_audio_thread(),
+				testio->fd);
 		close(testio->fd);
 	}
 
@@ -204,7 +195,6 @@
 
 	iodev->open_dev = open_dev;
 	iodev->close_dev = close_dev;
-	iodev->is_open = is_open;
 	iodev->frames_queued = frames_queued;
 	iodev->delay_frames = delay_frames;
 	if (type == TEST_IODEV_HOTWORD)
@@ -212,7 +202,6 @@
 	else
 		iodev->get_buffer = get_buffer;
 	iodev->put_buffer = put_buffer;
-	iodev->dev_running = dev_running;
 	iodev->update_active_node = update_active_node;
 
 	/* Create a dummy ionode */
@@ -220,10 +209,12 @@
 	node->dev = iodev;
 	node->plugged = 1;
 	if (type == TEST_IODEV_HOTWORD)
-		node->type = CRAS_NODE_TYPE_AOKR;
+		node->type = CRAS_NODE_TYPE_HOTWORD;
 	else
 		node->type = CRAS_NODE_TYPE_UNKNOWN;
 	node->volume = 100;
+	node->software_volume_needed = 0;
+	node->max_software_gain = 0;
 	strcpy(node->name, "(default)");
 	cras_iodev_add_node(iodev, node);
 	cras_iodev_set_active_node(iodev, node);
@@ -267,7 +258,7 @@
 {
 	struct test_iodev *testio = (struct test_iodev *)iodev;
 
-	if (!is_open(iodev))
+	if (!cras_iodev_is_open(iodev))
 		return;
 
 	switch (command) {
diff --git a/cras/src/tests/a2dp_iodev_unittest.cc b/cras/src/tests/a2dp_iodev_unittest.cc
index 88f058d..6e8e3c7 100644
--- a/cras/src/tests/a2dp_iodev_unittest.cc
+++ b/cras/src/tests/a2dp_iodev_unittest.cc
@@ -20,7 +20,7 @@
 
 #define FAKE_OBJECT_PATH "/fake/obj/path"
 
-#define MAX_A2DP_ENCODE_CALLS 2
+#define MAX_A2DP_ENCODE_CALLS 8
 #define MAX_A2DP_WRITE_CALLS 4
 
 static struct cras_bt_transport *fake_transport;
@@ -108,7 +108,7 @@
   ResetStubData();
 
   cras_bt_device_name_ret = NULL;
-  iodev = a2dp_iodev_create(fake_transport, NULL);
+  iodev = a2dp_iodev_create(fake_transport);
 
   ASSERT_NE(iodev, (void *)NULL);
   ASSERT_EQ(iodev->direction, CRAS_STREAM_OUTPUT);
@@ -131,7 +131,7 @@
 
   cras_bt_device_name_ret = fake_device_name;
   /* Assert iodev name matches the bt device's name */
-  iodev = a2dp_iodev_create(fake_transport, NULL);
+  iodev = a2dp_iodev_create(fake_transport);
   ASSERT_STREQ(fake_device_name, iodev->info.name);
 
   a2dp_iodev_destroy(iodev);
@@ -143,7 +143,7 @@
   ResetStubData();
 
   init_a2dp_return_val = -1;
-  iodev = a2dp_iodev_create(fake_transport, NULL);
+  iodev = a2dp_iodev_create(fake_transport);
 
   ASSERT_EQ(iodev, (void *)NULL);
   ASSERT_EQ(1, cras_bt_transport_configuration_called);
@@ -158,7 +158,7 @@
   struct cras_iodev *iodev;
 
   ResetStubData();
-  iodev = a2dp_iodev_create(fake_transport, NULL);
+  iodev = a2dp_iodev_create(fake_transport);
 
   iodev_set_format(iodev, &format);
   iodev->open_dev(iodev);
@@ -180,7 +180,7 @@
   unsigned frames;
 
   ResetStubData();
-  iodev = a2dp_iodev_create(fake_transport, NULL);
+  iodev = a2dp_iodev_create(fake_transport);
 
   iodev_set_format(iodev, &format);
   iodev->open_dev(iodev);
@@ -239,10 +239,11 @@
 TEST(A2dpIoInif, FramesQueued) {
   struct cras_iodev *iodev;
   struct cras_audio_area *area;
+  struct timespec tstamp;
   unsigned frames;
 
   ResetStubData();
-  iodev = a2dp_iodev_create(fake_transport, NULL);
+  iodev = a2dp_iodev_create(fake_transport);
 
   iodev_set_format(iodev, &format);
   time_now.tv_sec = 0;
@@ -267,7 +268,9 @@
   time_now.tv_nsec = 1000000;
   iodev->put_buffer(iodev, 300);
   write_callback(write_callback_data);
-  EXPECT_EQ(350, iodev->frames_queued(iodev));
+  EXPECT_EQ(350, 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;
@@ -277,9 +280,11 @@
   a2dp_encode_processed_bytes_val[0] = 800;
   write_callback(write_callback_data);
   /* 1000000 nsec has passed, estimated queued frames adjusted by 44 */
-  EXPECT_EQ(256, iodev->frames_queued(iodev));
+  EXPECT_EQ(256, iodev->frames_queued(iodev, &tstamp));
   EXPECT_EQ(1200, pcm_buf_size_val[0]);
   EXPECT_EQ(400, pcm_buf_size_val[1]);
+  EXPECT_EQ(tstamp.tv_sec, time_now.tv_sec);
+  EXPECT_EQ(tstamp.tv_nsec, time_now.tv_nsec);
 
   /* Queued frames and new put buffer are all written */
   a2dp_encode_processed_bytes_val[0] = 400;
@@ -295,7 +300,57 @@
   a2dp_encode_processed_bytes_val[0] = 600;
   iodev->put_buffer(iodev, 200);
   EXPECT_EQ(1200, pcm_buf_size_val[0]);
-  EXPECT_EQ(200, iodev->frames_queued(iodev));
+  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);
+}
+
+TEST(A2dpIo, FlushAtLowBufferLevel) {
+  struct cras_iodev *iodev;
+  struct cras_audio_area *area;
+  struct timespec tstamp;
+  unsigned frames;
+
+  ResetStubData();
+  iodev = a2dp_iodev_create(fake_transport);
+
+  iodev_set_format(iodev, &format);
+  time_now.tv_sec = 0;
+  time_now.tv_nsec = 0;
+  iodev->open_dev(iodev);
+  ASSERT_NE(write_callback, (void *)NULL);
+
+  ASSERT_EQ(iodev->min_buffer_level, 400);
+
+  frames = 700;
+  iodev->get_buffer(iodev, &area, &frames);
+  ASSERT_EQ(700, frames);
+  ASSERT_EQ(700, area->frames);
+
+  /* Fake 111 frames in pre-fill*/
+  a2dp_encode_processed_bytes_val[0] = 111;
+  a2dp_write_return_val[0] = -EAGAIN;
+
+  /* First call to a2dp_encode() processed 800 bytes. */
+  a2dp_encode_processed_bytes_val[1] = 800;
+  a2dp_encode_processed_bytes_val[2] = 0;
+  a2dp_write_return_val[1] = 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.
+   */
+  a2dp_encode_processed_bytes_val[3] = 800;
+  a2dp_encode_processed_bytes_val[4] = 0;
+  a2dp_write_return_val[2] = -EAGAIN;
+
+  time_now.tv_nsec = 10000000;
+  iodev->put_buffer(iodev, 700);
+
+  time_now.tv_nsec = 20000000;
+  EXPECT_EQ(500, iodev->frames_queued(iodev, &tstamp));
+  EXPECT_EQ(tstamp.tv_sec, time_now.tv_sec);
+  EXPECT_EQ(tstamp.tv_nsec, time_now.tv_nsec);
 }
 
 } // namespace
@@ -320,7 +375,8 @@
   return 0;
 }
 
-int cras_bt_transport_release(struct cras_bt_transport *transport)
+int cras_bt_transport_release(struct cras_bt_transport *transport,
+    unsigned int blocking)
 {
   cras_bt_transport_release_called++;
   return 0;
@@ -342,6 +398,11 @@
   return cras_bt_transport_write_mtu_ret;
 }
 
+int cras_bt_transport_set_volume(struct cras_bt_transport *transport,
+    uint16_t volume)
+{
+  return 0;
+}
 
 void cras_iodev_free_format(struct cras_iodev *iodev)
 {
@@ -409,6 +470,22 @@
   cras_bt_device_rm_iodev_called++;
 }
 
+int cras_bt_device_get_use_hardware_volume(struct cras_bt_device *device)
+{
+  return 0;
+}
+
+int cras_bt_device_cancel_suspend(struct cras_bt_device *device)
+{
+  return 0;
+}
+
+int cras_bt_device_schedule_suspend(struct cras_bt_device *device,
+                                    unsigned int msec)
+{
+  return 0;
+}
+
 int init_a2dp(struct a2dp_info *a2dp, a2dp_sbc_t *sbc)
 {
   init_a2dp_called++;
@@ -479,6 +556,10 @@
   dummy_audio_area->channels[0].buf = base_buffer;
 }
 
+struct audio_thread *cras_iodev_list_get_audio_thread()
+{
+  return NULL;
+}
 // From audio_thread
 struct audio_thread_event_log *atlog;
 
@@ -487,7 +568,8 @@
   write_callback_data = data;
 }
 
-void audio_thread_rm_callback(int fd) {
+int audio_thread_rm_callback_sync(struct audio_thread *thread, int fd) {
+  return 0;
 }
 
 void audio_thread_enable_callback(int fd, int enabled) {
diff --git a/cras/src/tests/alert_unittest.cc b/cras/src/tests/alert_unittest.cc
index e1073cf..f013c1b 100644
--- a/cras/src/tests/alert_unittest.cc
+++ b/cras/src/tests/alert_unittest.cc
@@ -11,11 +11,12 @@
 
 namespace {
 
-void callback1(void *arg);
-void callback2(void *arg);
+void callback1(void *arg, void *data);
+void callback2(void *arg, void *data);
 void prepare(struct cras_alert *alert);
 
 static int cb1_called = 0;
+static void *cb1_data;
 static int cb2_called = 0;
 static int cb2_set_pending = 0;
 static int prepare_called = 0;
@@ -28,7 +29,7 @@
 }
 
 TEST(Alert, OneCallback) {
-  struct cras_alert *alert = cras_alert_create(NULL);
+  struct cras_alert *alert = cras_alert_create(NULL, 0);
   cras_alert_add_callback(alert, &callback1, NULL);
   ResetStub();
   cras_alert_pending(alert);
@@ -38,8 +39,67 @@
   cras_alert_destroy(alert);
 }
 
+TEST(Alert, OneCallbackPost2Call1) {
+  struct cras_alert *alert = cras_alert_create(NULL, 0);
+  cras_alert_add_callback(alert, &callback1, NULL);
+  ResetStub();
+  // Alert twice, callback should only be called once.
+  cras_alert_pending(alert);
+  cras_alert_pending(alert);
+  EXPECT_EQ(0, cb1_called);
+  cras_alert_process_all_pending_alerts();
+  EXPECT_EQ(1, cb1_called);
+  cras_alert_destroy(alert);
+}
+
+TEST(Alert, OneCallbackWithData) {
+  struct cras_alert *alert = cras_alert_create(NULL, 0);
+  const char *data = "ThisIsMyData";
+  cras_alert_add_callback(alert, &callback1, NULL);
+  ResetStub();
+  cras_alert_pending_data(alert, (void *)data, strlen(data) + 1);
+  EXPECT_EQ(0, cb1_called);
+  cras_alert_process_all_pending_alerts();
+  EXPECT_EQ(1, cb1_called);
+  EXPECT_EQ(0, strcmp(data, (const char *)cb1_data));
+  cras_alert_destroy(alert);
+}
+
+TEST(Alert, OneCallbackTwoDataCalledOnce) {
+  struct cras_alert *alert = cras_alert_create(NULL, 0);
+  const char *data = "ThisIsMyData";
+  const char *data2 = "ThisIsMyData2";
+  cras_alert_add_callback(alert, &callback1, NULL);
+  ResetStub();
+  // Callback called with last data only.
+  cras_alert_pending_data(alert, (void *)data, strlen(data) + 1);
+  cras_alert_pending_data(alert, (void *)data2, strlen(data2) + 1);
+  EXPECT_EQ(0, cb1_called);
+  cras_alert_process_all_pending_alerts();
+  EXPECT_EQ(1, cb1_called);
+  EXPECT_EQ(0, strcmp(data2, (const char *)cb1_data));
+  cras_alert_destroy(alert);
+}
+
+TEST(Alert, OneCallbackTwoDataKeepAll) {
+  struct cras_alert *alert = cras_alert_create(
+                                 NULL, CRAS_ALERT_FLAG_KEEP_ALL_DATA);
+  const char *data = "ThisIsMyData";
+  const char *data2 = "ThisIsMyData2";
+  cras_alert_add_callback(alert, &callback1, NULL);
+  ResetStub();
+  // Callbacks with data should each be called.
+  cras_alert_pending_data(alert, (void *)data, strlen(data) + 1);
+  cras_alert_pending_data(alert, (void *)data2, strlen(data2) + 1);
+  EXPECT_EQ(0, cb1_called);
+  cras_alert_process_all_pending_alerts();
+  EXPECT_EQ(2, cb1_called);
+  EXPECT_EQ(0, strcmp(data2, (const char *)cb1_data));
+  cras_alert_destroy(alert);
+}
+
 TEST(Alert, TwoCallbacks) {
-  struct cras_alert *alert = cras_alert_create(NULL);
+  struct cras_alert *alert = cras_alert_create(NULL, 0);
   cras_alert_add_callback(alert, &callback1, NULL);
   cras_alert_add_callback(alert, &callback2, NULL);
   ResetStub();
@@ -53,7 +113,7 @@
 }
 
 TEST(Alert, NoPending) {
-  struct cras_alert *alert = cras_alert_create(NULL);
+  struct cras_alert *alert = cras_alert_create(NULL, 0);
   cras_alert_add_callback(alert, &callback1, NULL);
   ResetStub();
   EXPECT_EQ(0, cb1_called);
@@ -63,8 +123,8 @@
 }
 
 TEST(Alert, PendingInCallback) {
-  struct cras_alert *alert1 = cras_alert_create(NULL);
-  struct cras_alert *alert2 = cras_alert_create(NULL);
+  struct cras_alert *alert1 = cras_alert_create(NULL, 0);
+  struct cras_alert *alert2 = cras_alert_create(NULL, 0);
   cras_alert_add_callback(alert1, &callback1, NULL);
   cras_alert_add_callback(alert2, &callback2, alert1);
   ResetStub();
@@ -80,7 +140,7 @@
 }
 
 TEST(Alert, Prepare) {
-  struct cras_alert *alert = cras_alert_create(prepare);
+  struct cras_alert *alert = cras_alert_create(prepare, 0);
   cras_alert_add_callback(alert, &callback1, NULL);
   ResetStub();
   cras_alert_pending(alert);
@@ -92,8 +152,8 @@
 }
 
 TEST(Alert, TwoAlerts) {
-  struct cras_alert *alert1 = cras_alert_create(prepare);
-  struct cras_alert *alert2 = cras_alert_create(prepare);
+  struct cras_alert *alert1 = cras_alert_create(prepare, 0);
+  struct cras_alert *alert2 = cras_alert_create(prepare, 0);
   cras_alert_add_callback(alert1, &callback1, NULL);
   cras_alert_add_callback(alert2, &callback2, NULL);
 
@@ -128,12 +188,13 @@
   cras_alert_destroy_all();
 }
 
-void callback1(void *arg)
+void callback1(void *arg, void *data)
 {
   cb1_called++;
+  cb1_data = data;
 }
 
-void callback2(void *arg)
+void callback2(void *arg, void *data)
 {
   cb2_called++;
   if (cb2_set_pending) {
diff --git a/cras/src/tests/alsa_card_unittest.cc b/cras/src/tests/alsa_card_unittest.cc
index a5c4fec..741c6b4 100644
--- a/cras/src/tests/alsa_card_unittest.cc
+++ b/cras/src/tests/alsa_card_unittest.cc
@@ -4,14 +4,19 @@
 
 #include <gtest/gtest.h>
 #include <iniparser.h>
+#include <map>
 #include <stdio.h>
+#include <syslog.h>
+#include <sys/param.h>
 
 extern "C" {
 #include "cras_alsa_card.h"
 #include "cras_alsa_io.h"
 #include "cras_alsa_mixer.h"
+#include "cras_alsa_ucm.h"
 #include "cras_types.h"
 #include "cras_util.h"
+#include "utlist.h"
 }
 
 namespace {
@@ -20,9 +25,22 @@
 static struct cras_alsa_mixer *cras_alsa_mixer_create_return;
 static size_t cras_alsa_mixer_destroy_called;
 static size_t cras_alsa_iodev_create_called;
-static struct cras_iodev *cras_alsa_iodev_create_return;
+static struct cras_iodev **cras_alsa_iodev_create_return;
+static struct cras_iodev *cras_alsa_iodev_create_default_return[] = {
+  reinterpret_cast<struct cras_iodev *>(2),
+  reinterpret_cast<struct cras_iodev *>(3),
+  reinterpret_cast<struct cras_iodev *>(4),
+  reinterpret_cast<struct cras_iodev *>(5),
+};
+static size_t cras_alsa_iodev_create_return_size;
+static size_t cras_alsa_iodev_legacy_complete_init_called;
+static size_t cras_alsa_iodev_ucm_add_nodes_and_jacks_called;
+static size_t cras_alsa_iodev_ucm_complete_init_called;
 static size_t cras_alsa_iodev_destroy_called;
 static struct cras_iodev *cras_alsa_iodev_destroy_arg;
+static size_t cras_alsa_iodev_index_called;
+static std::map<struct cras_iodev *, unsigned int> cras_alsa_iodev_index_return;
+static int alsa_iodev_has_hctl_jacks_return;
 static size_t snd_ctl_open_called;
 static size_t snd_ctl_open_return;
 static size_t snd_ctl_close_called;
@@ -38,6 +56,21 @@
 static size_t snd_ctl_pcm_info_rets_index;
 static size_t snd_ctl_card_info_called;
 static int snd_ctl_card_info_ret;
+static size_t snd_hctl_open_called;
+static int snd_hctl_open_return_value;
+static int snd_hctl_close_called;
+static size_t snd_hctl_nonblock_called;
+static snd_hctl_t *snd_hctl_open_pointer_val;
+static size_t snd_hctl_load_called;
+static int snd_hctl_load_return_value;
+static struct pollfd *snd_hctl_poll_descriptors_fds;
+static size_t snd_hctl_poll_descriptors_num_fds;
+static size_t snd_hctl_poll_descriptors_called;
+static size_t cras_system_add_select_fd_called;
+static std::vector<int> cras_system_add_select_fd_values;
+static size_t cras_system_rm_select_fd_called;
+static std::vector<int> cras_system_rm_select_fd_values;
+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;
@@ -49,14 +82,30 @@
 static char ucm_get_flag_name[64];
 static char* device_config_dir;
 static const char* cras_card_config_dir;
+static struct mixer_name *ucm_get_coupled_mixer_names_return_value;
+static struct mixer_name *coupled_output_names_value;
+static int ucm_has_fully_specified_ucm_flag_return_value;
+static int ucm_get_sections_called;
+static struct ucm_section *ucm_get_sections_return_value;
+static size_t cras_alsa_mixer_add_controls_in_section_called;
+static int cras_alsa_mixer_add_controls_in_section_return_value;
 
 static void ResetStubData() {
   cras_alsa_mixer_create_called = 0;
   cras_alsa_mixer_create_return = reinterpret_cast<struct cras_alsa_mixer *>(1);
   cras_alsa_mixer_destroy_called = 0;
+  cras_alsa_iodev_destroy_arg = NULL;
   cras_alsa_iodev_create_called = 0;
-  cras_alsa_iodev_create_return = reinterpret_cast<struct cras_iodev *>(2);
+  cras_alsa_iodev_create_return = cras_alsa_iodev_create_default_return;
+  cras_alsa_iodev_create_return_size =
+      ARRAY_SIZE(cras_alsa_iodev_create_default_return);
+  cras_alsa_iodev_legacy_complete_init_called = 0;
+  cras_alsa_iodev_ucm_add_nodes_and_jacks_called = 0;
+  cras_alsa_iodev_ucm_complete_init_called = 0;
   cras_alsa_iodev_destroy_called = 0;
+  cras_alsa_iodev_index_called = 0;
+  cras_alsa_iodev_index_return.clear();
+  alsa_iodev_has_hctl_jacks_return = 1;
   snd_ctl_open_called = 0;
   snd_ctl_open_return = 0;
   snd_ctl_close_called = 0;
@@ -70,6 +119,22 @@
   snd_ctl_pcm_info_rets_index = 0;
   snd_ctl_card_info_called = 0;
   snd_ctl_card_info_ret = 0;
+  snd_hctl_open_called = 0;
+  snd_hctl_open_return_value = 0;
+  snd_hctl_open_pointer_val = reinterpret_cast<snd_hctl_t *>(0x4323);
+  snd_hctl_load_called = 0;
+  snd_hctl_load_return_value = 0;
+  snd_hctl_close_called = 0;
+  snd_hctl_nonblock_called = 0;
+  snd_hctl_poll_descriptors_num_fds = 0;
+  snd_hctl_poll_descriptors_called = 0;
+  snd_hctl_handle_events_called = 0;
+  snd_hctl_poll_descriptors_num_fds = 0;
+  snd_hctl_poll_descriptors_called = 0;
+  cras_system_add_select_fd_called = 0;
+  cras_system_add_select_fd_values.clear();
+  cras_system_rm_select_fd_called = 0;
+  cras_system_rm_select_fd_values.clear();
   iniparser_freedict_called = 0;
   iniparser_load_called = 0;
   fake_blacklist = reinterpret_cast<struct cras_device_blacklist *>(3);
@@ -81,6 +146,14 @@
   memset(ucm_get_flag_name, 0, sizeof(ucm_get_flag_name));
   device_config_dir = reinterpret_cast<char *>(3);
   cras_card_config_dir = NULL;
+  ucm_get_coupled_mixer_names_return_value = NULL;
+  mixer_name_free(coupled_output_names_value);
+  coupled_output_names_value = NULL;
+  ucm_has_fully_specified_ucm_flag_return_value = 0;
+  ucm_get_sections_called = 0;
+  ucm_get_sections_return_value = NULL;
+  cras_alsa_mixer_add_controls_in_section_called = 0;
+  cras_alsa_mixer_add_controls_in_section_return_value = 0;
 }
 
 TEST(AlsaCard, CreateFailInvalidCard) {
@@ -90,7 +163,8 @@
   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_blacklist, NULL);
   EXPECT_EQ(static_cast<struct cras_alsa_card *>(NULL), c);
   EXPECT_EQ(snd_ctl_close_called, snd_ctl_open_called);
   EXPECT_EQ(cras_alsa_mixer_create_called, cras_alsa_mixer_destroy_called);
@@ -104,7 +178,8 @@
   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_blacklist, NULL);
   EXPECT_EQ(static_cast<struct cras_alsa_card *>(NULL), c);
   EXPECT_EQ(snd_ctl_close_called, snd_ctl_open_called);
   EXPECT_EQ(1, cras_alsa_mixer_create_called);
@@ -119,12 +194,129 @@
   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_blacklist, NULL);
   EXPECT_EQ(static_cast<struct cras_alsa_card *>(NULL), c);
   EXPECT_EQ(1, snd_ctl_open_called);
   EXPECT_EQ(0, snd_ctl_close_called);
-  EXPECT_EQ(cras_alsa_mixer_create_called, cras_alsa_mixer_destroy_called);
   EXPECT_EQ(iniparser_load_called, iniparser_freedict_called);
+  EXPECT_EQ(0, cras_alsa_mixer_create_called);
+}
+
+TEST(AlsaCard, CreateFailHctlOpen) {
+  struct cras_alsa_card *c;
+  cras_alsa_card_info card_info;
+
+  ResetStubData();
+  card_info.card_type = ALSA_CARD_TYPE_INTERNAL;
+  card_info.card_index = 0;
+  snd_hctl_open_pointer_val = NULL;
+  snd_hctl_open_return_value = -1;
+
+  c = cras_alsa_card_create(&card_info, device_config_dir,
+                            fake_blacklist, NULL);
+  EXPECT_NE(static_cast<struct cras_alsa_card *>(NULL), c);
+  EXPECT_EQ(1, snd_ctl_open_called);
+  EXPECT_EQ(1, snd_ctl_close_called);
+  EXPECT_EQ(iniparser_load_called, iniparser_freedict_called);
+  EXPECT_EQ(1, snd_hctl_open_called);
+  EXPECT_EQ(0, snd_hctl_nonblock_called);
+  EXPECT_EQ(0, snd_hctl_load_called);
+  EXPECT_EQ(1, cras_alsa_mixer_create_called);
+}
+
+TEST(AlsaCard, CreateFailHctlLoad) {
+  struct cras_alsa_card *c;
+  cras_alsa_card_info card_info;
+
+  ResetStubData();
+  card_info.card_type = ALSA_CARD_TYPE_INTERNAL;
+  card_info.card_index = 0;
+  snd_hctl_load_return_value = -1;
+
+  c = cras_alsa_card_create(&card_info, device_config_dir,
+                            fake_blacklist, NULL);
+  EXPECT_EQ(static_cast<struct cras_alsa_card *>(NULL), c);
+  EXPECT_EQ(1, snd_ctl_open_called);
+  EXPECT_EQ(1, snd_ctl_close_called);
+  EXPECT_EQ(iniparser_load_called, iniparser_freedict_called);
+  EXPECT_EQ(1, snd_hctl_open_called);
+  EXPECT_EQ(1, snd_hctl_nonblock_called);
+  EXPECT_EQ(1, snd_hctl_load_called);
+  EXPECT_EQ(0, cras_alsa_mixer_create_called);
+}
+
+TEST(AlsaCard, AddSelectForHctlNoDevices) {
+  struct pollfd poll_fds[] = {
+    {3, 0, 0},
+  };
+
+  struct cras_alsa_card *c;
+  cras_alsa_card_info card_info;
+
+  ResetStubData();
+  card_info.card_type = ALSA_CARD_TYPE_INTERNAL;
+  card_info.card_index = 0;
+  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, NULL);
+  EXPECT_NE(static_cast<struct cras_alsa_card *>(NULL), c);
+  EXPECT_EQ(1, snd_ctl_open_called);
+  EXPECT_EQ(1, snd_ctl_close_called);
+  EXPECT_EQ(iniparser_load_called, iniparser_freedict_called);
+  EXPECT_EQ(1, snd_hctl_open_called);
+  EXPECT_EQ(1, snd_hctl_nonblock_called);
+  EXPECT_EQ(1, snd_hctl_load_called);
+  EXPECT_EQ(1, cras_alsa_mixer_create_called);
+  EXPECT_EQ(0, cras_system_add_select_fd_called);
+  cras_alsa_card_destroy(c);
+  EXPECT_EQ(0, cras_system_rm_select_fd_called);
+}
+
+TEST(AlsaCard, AddSelectForHctlWithDevices) {
+  struct pollfd poll_fds[] = {
+    {3, 0, 0},
+  };
+  int dev_nums[] = {0};
+  int info_rets[] = {0, -1};
+
+  struct cras_alsa_card *c;
+  cras_alsa_card_info card_info;
+
+  ResetStubData();
+  snd_ctl_pcm_next_device_set_devs_size = ARRAY_SIZE(dev_nums);
+  snd_ctl_pcm_next_device_set_devs = dev_nums;
+  snd_ctl_pcm_info_rets_size = ARRAY_SIZE(info_rets);
+  snd_ctl_pcm_info_rets = info_rets;
+  card_info.card_type = ALSA_CARD_TYPE_INTERNAL;
+  card_info.card_index = 0;
+  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, NULL);
+  EXPECT_NE(static_cast<struct cras_alsa_card *>(NULL), c);
+  EXPECT_EQ(snd_ctl_close_called, snd_ctl_open_called);
+  EXPECT_EQ(2, snd_ctl_pcm_next_device_called);
+  EXPECT_EQ(1, cras_alsa_iodev_create_called);
+  EXPECT_EQ(0, cras_alsa_iodev_index_called);
+  EXPECT_EQ(1, snd_ctl_card_info_called);
+  EXPECT_EQ(1, ucm_create_called);
+  EXPECT_EQ(1, ucm_get_dev_for_mixer_called);
+  EXPECT_EQ(1, ucm_get_flag_called);
+  EXPECT_EQ(0, strcmp(ucm_get_flag_name, "ExtraMainVolume"));
+  EXPECT_EQ(cras_card_config_dir, device_config_dir);
+  EXPECT_EQ(iniparser_load_called, iniparser_freedict_called);
+  EXPECT_EQ(1, snd_hctl_open_called);
+  EXPECT_EQ(1, snd_hctl_nonblock_called);
+  EXPECT_EQ(1, snd_hctl_load_called);
+  EXPECT_EQ(1, cras_alsa_mixer_create_called);
+  ASSERT_EQ(1, cras_system_add_select_fd_called);
+  EXPECT_EQ(3, cras_system_add_select_fd_values[0]);
+  cras_alsa_card_destroy(c);
+  EXPECT_EQ(ARRAY_SIZE(poll_fds), cras_system_rm_select_fd_called);
 }
 
 TEST(AlsaCard, CreateFailCtlCardInfo) {
@@ -135,7 +327,8 @@
   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_blacklist, NULL);
   EXPECT_EQ(static_cast<struct cras_alsa_card *>(NULL), c);
   EXPECT_EQ(1, snd_ctl_open_called);
   EXPECT_EQ(1, snd_ctl_close_called);
@@ -150,12 +343,16 @@
   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_blacklist, 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_pcm_next_device_called);
   EXPECT_EQ(0, cras_alsa_iodev_create_called);
+  EXPECT_EQ(0, cras_alsa_iodev_legacy_complete_init_called);
   EXPECT_EQ(1, cras_alsa_card_get_index(c));
+  EXPECT_EQ(0, ucm_get_sections_called);
+  EXPECT_EQ(0, cras_alsa_mixer_add_controls_in_section_called);
 
   cras_alsa_card_destroy(c);
   EXPECT_EQ(0, cras_alsa_iodev_destroy_called);
@@ -171,7 +368,8 @@
   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_blacklist, NULL);
   EXPECT_EQ(static_cast<struct cras_alsa_card *>(NULL), c);
   EXPECT_EQ(cras_alsa_mixer_create_called, cras_alsa_mixer_destroy_called);
   EXPECT_EQ(snd_ctl_open_called, snd_ctl_close_called);
@@ -191,22 +389,27 @@
   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_blacklist, NULL);
   EXPECT_NE(static_cast<struct cras_alsa_card *>(NULL), c);
   EXPECT_EQ(snd_ctl_close_called, snd_ctl_open_called);
   EXPECT_EQ(2, snd_ctl_pcm_next_device_called);
   EXPECT_EQ(1, cras_alsa_iodev_create_called);
+  EXPECT_EQ(1, cras_alsa_iodev_legacy_complete_init_called);
+  EXPECT_EQ(0, cras_alsa_iodev_index_called);
   EXPECT_EQ(1, snd_ctl_card_info_called);
   EXPECT_EQ(1, ucm_create_called);
   EXPECT_EQ(1, ucm_get_dev_for_mixer_called);
   EXPECT_EQ(1, ucm_get_flag_called);
   EXPECT_EQ(0, strcmp(ucm_get_flag_name, "ExtraMainVolume"));
   EXPECT_EQ(cras_card_config_dir, device_config_dir);
+  EXPECT_EQ(0, ucm_get_sections_called);
+  EXPECT_EQ(0, cras_alsa_mixer_add_controls_in_section_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, cras_alsa_iodev_destroy_arg);
+  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);
 }
@@ -222,20 +425,23 @@
   snd_ctl_pcm_next_device_set_devs = dev_nums;
   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;
   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_blacklist, NULL);
   EXPECT_NE(static_cast<struct cras_alsa_card *>(NULL), c);
   EXPECT_EQ(snd_ctl_close_called, snd_ctl_open_called);
   EXPECT_EQ(2, snd_ctl_pcm_next_device_called);
   EXPECT_EQ(1, snd_ctl_card_info_called);
   EXPECT_EQ(0, cras_alsa_iodev_create_called);
+  EXPECT_EQ(0, cras_alsa_iodev_legacy_complete_init_called);
   EXPECT_EQ(cras_card_config_dir, device_config_dir);
 
   cras_alsa_card_destroy(c);
   EXPECT_EQ(0, cras_alsa_iodev_destroy_called);
-  EXPECT_EQ(cras_alsa_iodev_create_return, cras_alsa_iodev_destroy_arg);
+  EXPECT_EQ(NULL, 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);
 }
@@ -253,17 +459,55 @@
   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_blacklist, NULL);
   EXPECT_NE(static_cast<struct cras_alsa_card *>(NULL), c);
   EXPECT_EQ(snd_ctl_close_called, snd_ctl_open_called);
   EXPECT_EQ(3, snd_ctl_pcm_next_device_called);
   EXPECT_EQ(2, cras_alsa_iodev_create_called);
+  EXPECT_EQ(2, cras_alsa_iodev_legacy_complete_init_called);
+  EXPECT_EQ(1, cras_alsa_iodev_index_called);
   EXPECT_EQ(1, snd_ctl_card_info_called);
   EXPECT_EQ(cras_card_config_dir, device_config_dir);
+  EXPECT_EQ(0, ucm_get_sections_called);
+  EXPECT_EQ(0, cras_alsa_mixer_add_controls_in_section_called);
 
   cras_alsa_card_destroy(c);
   EXPECT_EQ(2, cras_alsa_iodev_destroy_called);
-  EXPECT_EQ(cras_alsa_iodev_create_return, cras_alsa_iodev_destroy_arg);
+  EXPECT_EQ(cras_alsa_iodev_create_return[1], 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);
+}
+
+TEST(AlsaCard, CreateTwoDuplicateDeviceIndex) {
+  struct cras_alsa_card *c;
+  int dev_nums[] = {0, 0};
+  int info_rets[] = {0, -1, 0};
+  cras_alsa_card_info card_info;
+
+  ResetStubData();
+  snd_ctl_pcm_next_device_set_devs_size = ARRAY_SIZE(dev_nums);
+  snd_ctl_pcm_next_device_set_devs = dev_nums;
+  snd_ctl_pcm_info_rets_size = ARRAY_SIZE(info_rets);
+  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, NULL);
+  EXPECT_NE(static_cast<struct cras_alsa_card *>(NULL), c);
+  EXPECT_EQ(snd_ctl_close_called, snd_ctl_open_called);
+  EXPECT_EQ(3, snd_ctl_pcm_next_device_called);
+  EXPECT_EQ(1, cras_alsa_iodev_create_called);
+  EXPECT_EQ(2, cras_alsa_iodev_legacy_complete_init_called);
+  EXPECT_EQ(1, cras_alsa_iodev_index_called);
+  EXPECT_EQ(1, snd_ctl_card_info_called);
+  EXPECT_EQ(cras_card_config_dir, device_config_dir);
+  EXPECT_EQ(0, ucm_get_sections_called);
+  EXPECT_EQ(0, cras_alsa_mixer_add_controls_in_section_called);
+
+  cras_alsa_card_destroy(c);
+  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);
 }
@@ -281,16 +525,21 @@
   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_blacklist, NULL);
   EXPECT_NE(static_cast<struct cras_alsa_card *>(NULL), c);
   EXPECT_EQ(snd_ctl_close_called, snd_ctl_open_called);
   EXPECT_EQ(2, snd_ctl_pcm_next_device_called);
   EXPECT_EQ(1, cras_alsa_iodev_create_called);
+  EXPECT_EQ(1, cras_alsa_iodev_legacy_complete_init_called);
+  EXPECT_EQ(0, cras_alsa_iodev_index_called);
   EXPECT_EQ(cras_card_config_dir, device_config_dir);
+  EXPECT_EQ(0, ucm_get_sections_called);
+  EXPECT_EQ(0, cras_alsa_mixer_add_controls_in_section_called);
 
   cras_alsa_card_destroy(c);
   EXPECT_EQ(1, cras_alsa_iodev_destroy_called);
-  EXPECT_EQ(cras_alsa_iodev_create_return, cras_alsa_iodev_destroy_arg);
+  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);
 }
@@ -308,16 +557,21 @@
   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_blacklist, NULL);
   EXPECT_NE(static_cast<struct cras_alsa_card *>(NULL), c);
   EXPECT_EQ(snd_ctl_close_called, snd_ctl_open_called);
   EXPECT_EQ(2, snd_ctl_pcm_next_device_called);
   EXPECT_EQ(2, cras_alsa_iodev_create_called);
+  EXPECT_EQ(2, cras_alsa_iodev_legacy_complete_init_called);
+  EXPECT_EQ(0, cras_alsa_iodev_index_called);
   EXPECT_EQ(cras_card_config_dir, device_config_dir);
+  EXPECT_EQ(0, ucm_get_sections_called);
+  EXPECT_EQ(0, cras_alsa_mixer_add_controls_in_section_called);
 
   cras_alsa_card_destroy(c);
   EXPECT_EQ(2, cras_alsa_iodev_destroy_called);
-  EXPECT_EQ(cras_alsa_iodev_create_return, cras_alsa_iodev_destroy_arg);
+  EXPECT_EQ(cras_alsa_iodev_create_return[1], 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);
 }
@@ -335,30 +589,253 @@
   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_blacklist, NULL);
   EXPECT_NE(static_cast<struct cras_alsa_card *>(NULL), c);
   EXPECT_EQ(snd_ctl_close_called, snd_ctl_open_called);
   EXPECT_EQ(3, snd_ctl_pcm_next_device_called);
   EXPECT_EQ(2, cras_alsa_iodev_create_called);
+  EXPECT_EQ(2, cras_alsa_iodev_legacy_complete_init_called);
+  EXPECT_EQ(0, cras_alsa_iodev_index_called);
   EXPECT_EQ(cras_card_config_dir, device_config_dir);
+  EXPECT_EQ(0, ucm_get_sections_called);
+  EXPECT_EQ(0, cras_alsa_mixer_add_controls_in_section_called);
 
   cras_alsa_card_destroy(c);
   EXPECT_EQ(2, cras_alsa_iodev_destroy_called);
-  EXPECT_EQ(cras_alsa_iodev_create_return, cras_alsa_iodev_destroy_arg);
+  EXPECT_EQ(cras_alsa_iodev_create_return[1], 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);
 }
 
+TEST(AlsaCard, CreateOneOutputWithCoupledMixers) {
+  struct cras_alsa_card *c;
+  int dev_nums[] = {0};
+  int info_rets[] = {0, -1};
+  struct mixer_name *mixer_name_1, *mixer_name_2;
+  /* Use strdup because cras_alsa_card_create will delete it. */
+  const char *name1 = strdup("MixerName1"), *name2 = strdup("MixerName2");
+
+  cras_alsa_card_info card_info;
+
+  ResetStubData();
+  snd_ctl_pcm_next_device_set_devs_size = ARRAY_SIZE(dev_nums);
+  snd_ctl_pcm_next_device_set_devs = dev_nums;
+  snd_ctl_pcm_info_rets_size = ARRAY_SIZE(info_rets);
+  snd_ctl_pcm_info_rets = info_rets;
+  card_info.card_type = ALSA_CARD_TYPE_INTERNAL;
+  card_info.card_index = 0;
+
+  /* Creates a list of mixer names as return value of
+   * ucm_get_coupled_mixer_names. */
+  mixer_name_1 = (struct mixer_name*)malloc(sizeof(*mixer_name_1));
+  mixer_name_2 = (struct mixer_name*)malloc(sizeof(*mixer_name_2));
+  mixer_name_1->name = name1;
+  mixer_name_2->name = name2;
+  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, NULL);
+
+  EXPECT_NE(static_cast<struct cras_alsa_card *>(NULL), c);
+  EXPECT_EQ(snd_ctl_close_called, snd_ctl_open_called);
+  EXPECT_EQ(2, snd_ctl_pcm_next_device_called);
+  EXPECT_EQ(1, cras_alsa_iodev_create_called);
+  EXPECT_EQ(1, cras_alsa_iodev_legacy_complete_init_called);
+  EXPECT_EQ(0, cras_alsa_iodev_index_called);
+  EXPECT_EQ(1, snd_ctl_card_info_called);
+  EXPECT_EQ(1, ucm_create_called);
+  EXPECT_EQ(1, ucm_get_dev_for_mixer_called);
+  EXPECT_EQ(1, ucm_get_flag_called);
+  EXPECT_EQ(0, strcmp(ucm_get_flag_name, "ExtraMainVolume"));
+  EXPECT_EQ(cras_card_config_dir, device_config_dir);
+  EXPECT_EQ(0, ucm_get_sections_called);
+  EXPECT_EQ(0, cras_alsa_mixer_add_controls_in_section_called);
+
+  /* Checks cras_alsa_card_create can handle the list and pass the names to
+   * cras_alsa_mixer_create. */
+  struct mixer_name *m_name = coupled_output_names_value;
+  EXPECT_EQ(0, m_name ? strcmp(m_name->name, "MixerName1") : 1);
+  if (m_name)
+    m_name = m_name->next;
+  EXPECT_EQ(0, m_name ? strcmp(m_name->name, "MixerName2") : 1);
+
+  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);
+
+  mixer_name_free(coupled_output_names_value);
+  coupled_output_names_value = NULL;
+}
+
+TEST(AlsaCard, CreateFullyUCMNoSections) {
+  struct cras_alsa_card *c;
+  cras_alsa_card_info card_info;
+
+  ResetStubData();
+  card_info.card_type = ALSA_CARD_TYPE_INTERNAL;
+  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, NULL);
+  EXPECT_EQ(static_cast<struct cras_alsa_card *>(NULL), c);
+  EXPECT_EQ(snd_ctl_close_called, snd_ctl_open_called);
+  EXPECT_EQ(0, cras_alsa_iodev_create_called);
+  EXPECT_EQ(0, cras_alsa_iodev_ucm_complete_init_called);
+  EXPECT_EQ(1, snd_ctl_card_info_called);
+  EXPECT_EQ(1, ucm_get_sections_called);
+  EXPECT_EQ(0, cras_alsa_mixer_add_controls_in_section_called);
+
+  cras_alsa_card_destroy(c);
+  EXPECT_EQ(1, ucm_destroy_called);
+  EXPECT_EQ(0, cras_alsa_iodev_destroy_called);
+  EXPECT_EQ(NULL, 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,
+                               "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);
+  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,
+                               "my-sound-card Headset Jack", "gpio");
+  ucm_section_set_mixer_name(section, "CAPTURE");
+
+  section = ucm_section_create("Internal Mic", 0, 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,
+                               "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);
+  ucm_section_set_mixer_name(section, "HDMI");
+  DL_APPEND(sections, section);
+
+  return sections;
+}
+
+TEST(AlsaCard, CreateFullyUCMFailureOnControls) {
+  struct cras_alsa_card *c;
+  cras_alsa_card_info card_info;
+
+  ResetStubData();
+  card_info.card_type = ALSA_CARD_TYPE_INTERNAL;
+  card_info.card_index = 0;
+  ucm_has_fully_specified_ucm_flag_return_value = 1;
+  ucm_get_sections_return_value = GenerateUcmSections();
+  ASSERT_NE(ucm_get_sections_return_value, (struct ucm_section *)NULL);
+
+  cras_alsa_mixer_add_controls_in_section_return_value = -EINVAL;
+
+  c = cras_alsa_card_create(&card_info, device_config_dir,
+                            fake_blacklist, NULL);
+
+  EXPECT_EQ(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, cras_alsa_mixer_add_controls_in_section_called);
+  EXPECT_EQ(0, cras_alsa_iodev_create_called);
+  EXPECT_EQ(0, cras_alsa_iodev_ucm_complete_init_called);
+
+  cras_alsa_card_destroy(c);
+  EXPECT_EQ(1, ucm_destroy_called);
+  EXPECT_EQ(0, cras_alsa_iodev_destroy_called);
+  EXPECT_EQ(NULL, 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);
+}
+
+TEST(AlsaCard, CreateFullyUCMFourDevicesFiveSections) {
+  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_alsa_iodev_index_return[cras_alsa_iodev_create_return[0]] = 0;
+  cras_alsa_iodev_index_return[cras_alsa_iodev_create_return[1]] = 0;
+  cras_alsa_iodev_index_return[cras_alsa_iodev_create_return[2]] = 1;
+  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, 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(5, snd_ctl_pcm_info_called);
+  EXPECT_EQ(5, cras_alsa_mixer_add_controls_in_section_called);
+  EXPECT_EQ(4, cras_alsa_iodev_create_called);
+  EXPECT_EQ(5, cras_alsa_iodev_ucm_add_nodes_and_jacks_called);
+  EXPECT_EQ(4, cras_alsa_iodev_ucm_complete_init_called);
+
+  cras_alsa_card_destroy(c);
+  EXPECT_EQ(1, ucm_destroy_called);
+  EXPECT_EQ(4, cras_alsa_iodev_destroy_called);
+  EXPECT_EQ(cras_alsa_iodev_create_return[3], 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);
+}
+
+
 /* Stubs */
 
 extern "C" {
-struct cras_alsa_mixer *cras_alsa_mixer_create(
-    const char *card_name, const struct cras_card_config *config,
-    const char *output_names_extra[], size_t output_names_extra_size,
-    const char *extra_master_volume) {
+struct cras_alsa_mixer *cras_alsa_mixer_create(const char *card_name) {
   cras_alsa_mixer_create_called++;
   return cras_alsa_mixer_create_return;
 }
+
+int cras_alsa_mixer_add_controls_by_name_matching(
+    struct cras_alsa_mixer* cmix,
+    struct mixer_name *extra_controls,
+    struct mixer_name *coupled_controls) {
+  /* Duplicate coupled_output_names to verify in the end of unittest
+   * because names will get freed later in cras_alsa_card_create. */
+  struct mixer_name *control;
+  DL_FOREACH(coupled_controls, control) {
+    coupled_output_names_value =
+      mixer_name_add(coupled_output_names_value,
+                     control->name,
+                     CRAS_STREAM_OUTPUT,
+                     control->type);
+  }
+  return 0;
+}
+
 void cras_alsa_mixer_destroy(struct cras_alsa_mixer *cras_mixer) {
   cras_alsa_mixer_destroy_called++;
 }
@@ -371,17 +848,46 @@
 				     enum CRAS_ALSA_CARD_TYPE card_type,
 				     int is_first,
 				     struct cras_alsa_mixer *mixer,
-				     snd_use_case_mgr_t *ucm,
+				     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,
-                                     size_t usb_pid) {
+				     size_t usb_vid,
+				     size_t usb_pid,
+				     char *usb_serial_number) {
+  struct cras_iodev *result = NULL;
+  if (cras_alsa_iodev_create_called < cras_alsa_iodev_create_return_size)
+    result = cras_alsa_iodev_create_return[cras_alsa_iodev_create_called];
   cras_alsa_iodev_create_called++;
-  return cras_alsa_iodev_create_return;
+  return result;
 }
 void alsa_iodev_destroy(struct cras_iodev *iodev) {
   cras_alsa_iodev_destroy_called++;
   cras_alsa_iodev_destroy_arg = iodev;
 }
+int alsa_iodev_legacy_complete_init(struct cras_iodev *iodev) {
+  cras_alsa_iodev_legacy_complete_init_called++;
+  return 0;
+}
+int alsa_iodev_ucm_add_nodes_and_jacks(struct cras_iodev *iodev,
+				       struct ucm_section *section) {
+  cras_alsa_iodev_ucm_add_nodes_and_jacks_called++;
+  return 0;
+}
+void alsa_iodev_ucm_complete_init(struct cras_iodev *iodev) {
+  cras_alsa_iodev_ucm_complete_init_called++;
+}
+unsigned alsa_iodev_index(struct cras_iodev *iodev) {
+  std::map<struct cras_iodev *, unsigned int>::iterator i;
+  cras_alsa_iodev_index_called++;
+  i = cras_alsa_iodev_index_return.find(iodev);
+  if (i != cras_alsa_iodev_index_return.end())
+    return i->second;
+  return 0;
+}
+int alsa_iodev_has_hctl_jacks(struct cras_iodev *iodev) {
+  return alsa_iodev_has_hctl_jacks_return;
+}
 
 size_t snd_pcm_info_sizeof() {
   return 10;
@@ -444,6 +950,52 @@
 const char *snd_ctl_card_info_get_id(const snd_ctl_card_info_t *obj) {
   return "TestId";
 }
+int snd_hctl_open(snd_hctl_t **hctlp, const char *name, int mode) {
+  *hctlp = snd_hctl_open_pointer_val;
+  snd_hctl_open_called++;
+  return snd_hctl_open_return_value;
+}
+int snd_hctl_nonblock(snd_hctl_t *hctl, int nonblock) {
+  snd_hctl_nonblock_called++;
+  return 0;
+}
+int snd_hctl_load(snd_hctl_t *hctl) {
+  snd_hctl_load_called++;
+  return snd_hctl_load_return_value;
+}
+int snd_hctl_close(snd_hctl_t *hctl) {
+  snd_hctl_close_called++;
+  return 0;
+}
+int snd_hctl_poll_descriptors_count(snd_hctl_t *hctl) {
+  return snd_hctl_poll_descriptors_num_fds;
+}
+int snd_hctl_poll_descriptors(snd_hctl_t *hctl,
+                              struct pollfd *pfds,
+                              unsigned int space) {
+  unsigned int num = MIN(space, snd_hctl_poll_descriptors_num_fds);
+  memcpy(pfds, snd_hctl_poll_descriptors_fds, num * sizeof(*pfds));
+  snd_hctl_poll_descriptors_called++;
+  return num;
+}
+int snd_hctl_handle_events(snd_hctl_t *hctl) {
+  snd_hctl_handle_events_called++;
+  return 0;
+}
+
+int cras_system_add_select_fd(int fd,
+			      void (*callback)(void *data),
+			      void *callback_data)
+{
+  cras_system_add_select_fd_called++;
+  cras_system_add_select_fd_values.push_back(fd);
+  return 0;
+}
+void cras_system_rm_select_fd(int fd)
+{
+  cras_system_rm_select_fd_called++;
+  cras_system_rm_select_fd_values.push_back(fd);
+}
 
 struct cras_card_config *cras_card_config_create(const char *config_path,
 						 const char *card_name)
@@ -473,32 +1025,69 @@
   return cras_device_blacklist_check_retval;
 }
 
-snd_use_case_mgr_t* ucm_create(const char* name) {
+struct cras_use_case_mgr *ucm_create(const char* name) {
   ucm_create_called++;
-  return reinterpret_cast<snd_use_case_mgr_t*>(0x44);
+  return reinterpret_cast<struct cras_use_case_mgr *>(0x44);
 }
 
-void ucm_destroy(snd_use_case_mgr_t* mgr) {
+void ucm_destroy(struct cras_use_case_mgr *mgr) {
   ucm_destroy_called++;
 }
 
-char *ucm_get_dev_for_mixer(snd_use_case_mgr_t *mgr, const char *mixer)
+char *ucm_get_dev_for_mixer(struct cras_use_case_mgr *mgr, const char *mixer,
+                            enum CRAS_STREAM_DIRECTION dir)
 {
   ucm_get_dev_for_mixer_called++;
   return strdup("device");
 }
 
-char *ucm_get_flag(snd_use_case_mgr_t *mgr, const char *flag_name) {
+char *ucm_get_flag(struct cras_use_case_mgr *mgr, const char *flag_name) {
   ucm_get_flag_called++;
   strncpy(ucm_get_flag_name, flag_name, sizeof(ucm_get_flag_name));
   return NULL;
 }
 
+struct mixer_name *ucm_get_coupled_mixer_names(
+    struct cras_use_case_mgr *mgr, const char *dev)
+{
+  return ucm_get_coupled_mixer_names_return_value;
+}
+
+int ucm_has_fully_specified_ucm_flag(struct cras_use_case_mgr *mgr)
+{
+  return ucm_has_fully_specified_ucm_flag_return_value;
+}
+
+struct ucm_section *ucm_get_sections(struct cras_use_case_mgr *mgr)
+{
+  ucm_get_sections_called++;
+  return ucm_get_sections_return_value;
+}
+
+int cras_alsa_mixer_add_controls_in_section(
+		struct cras_alsa_mixer *cmix,
+		struct ucm_section *section)
+{
+  cras_alsa_mixer_add_controls_in_section_called++;
+  return cras_alsa_mixer_add_controls_in_section_return_value;
+}
+
+void ucm_free_mixer_names(struct mixer_name *names)
+{
+  struct mixer_name *m;
+  DL_FOREACH(names, m) {
+    DL_DELETE(names, m);
+    free((void*)m->name);
+    free(m);
+  }
+}
+
 } /* extern "C" */
 
 }  //  namespace
 
 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/alsa_helpers_unittest.cc b/cras/src/tests/alsa_helpers_unittest.cc
index 6cb6805..f774a50 100644
--- a/cras/src/tests/alsa_helpers_unittest.cc
+++ b/cras/src/tests/alsa_helpers_unittest.cc
@@ -3,12 +3,28 @@
 // found in the LICENSE file.
 
 #include <gtest/gtest.h>
+#include <vector>
 
 extern "C" {
 // For static function test.
 #include "cras_alsa_helpers.c"
 }
 
+static int snd_pcm_sw_params_set_tstamp_type_called;
+static int snd_pcm_sw_params_set_tstamp_mode_called;
+static snd_pcm_uframes_t snd_pcm_htimestamp_avail_ret_val;
+static timespec snd_pcm_htimestamp_tstamp_ret_val;
+static std::vector<int> snd_pcm_sw_params_ret_vals;
+
+static void ResetStubData() {
+  snd_pcm_sw_params_set_tstamp_type_called = 0;
+  snd_pcm_sw_params_set_tstamp_mode_called = 0;
+  snd_pcm_htimestamp_avail_ret_val = 0;
+  snd_pcm_htimestamp_tstamp_ret_val.tv_sec = 0;
+  snd_pcm_htimestamp_tstamp_ret_val.tv_nsec = 0;
+  snd_pcm_sw_params_ret_vals.clear();
+}
+
 namespace {
 
 static snd_pcm_chmap_query_t *create_chmap_cap(snd_pcm_chmap_type type,
@@ -147,8 +163,167 @@
   cras_audio_format_destroy(fmt);
 }
 
+TEST(AlsaHelper, Htimestamp) {
+  snd_pcm_t *dummy_handle = reinterpret_cast<snd_pcm_t*>(0x1);
+  snd_pcm_uframes_t used;
+  snd_pcm_uframes_t severe_underrun_frames = 480;
+  struct timespec tstamp;
+  unsigned int underruns = 0;
+  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;
+  snd_pcm_htimestamp_avail_ret_val = 20000;
+  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,
+                             dev_name, &used, &tstamp, &underruns);
+  EXPECT_EQ(used, snd_pcm_htimestamp_avail_ret_val);
+  EXPECT_EQ(tstamp.tv_sec, snd_pcm_htimestamp_tstamp_ret_val.tv_sec);
+  EXPECT_EQ(tstamp.tv_nsec, snd_pcm_htimestamp_tstamp_ret_val.tv_nsec);
+}
+
+TEST(AlsaHelper, GetAvailFramesSevereUnderrun) {
+  snd_pcm_t *dummy_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;
+  struct timespec tstamp;
+  unsigned int underruns = 0;
+  int rc;
+  const char *dev_name = "dev_name";
+
+  ResetStubData();
+  snd_pcm_htimestamp_avail_ret_val = buffer_size + severe_underrun_frames + 1;
+  rc = cras_alsa_get_avail_frames(dummy_handle, buffer_size,
+                                  severe_underrun_frames, dev_name,
+                                  &avail, &tstamp, &underruns);
+  // Returns -EPIPE when severe underrun happens.
+  EXPECT_EQ(rc, -EPIPE);
+  EXPECT_EQ(1, underruns);
+
+  ResetStubData();
+  snd_pcm_htimestamp_avail_ret_val = buffer_size + severe_underrun_frames;
+  rc = cras_alsa_get_avail_frames(dummy_handle, buffer_size,
+                                  severe_underrun_frames, dev_name,
+                                  &avail, &tstamp, &underruns);
+  // Underrun which is not severe enough will be masked.
+  // avail will be adjusted to buffer_size.
+  EXPECT_EQ(avail, buffer_size);
+  EXPECT_EQ(rc, 0);
+  EXPECT_EQ(2, underruns);
+
+  ResetStubData();
+  snd_pcm_htimestamp_avail_ret_val = buffer_size;
+  rc = cras_alsa_get_avail_frames(dummy_handle, buffer_size,
+                                  severe_underrun_frames, dev_name,
+                                  &avail, &tstamp, &underruns);
+  // When avail == buffer_size, num_underruns will be increased.
+  EXPECT_EQ(avail, buffer_size);
+  EXPECT_EQ(rc, 0);
+  EXPECT_EQ(3, underruns);
+
+  ResetStubData();
+  snd_pcm_htimestamp_avail_ret_val = buffer_size - 1;
+  rc = cras_alsa_get_avail_frames(dummy_handle, buffer_size,
+                                  severe_underrun_frames, dev_name,
+                                  &avail, &tstamp, &underruns);
+  // When avail < buffer_size, there is no underrun.
+  EXPECT_EQ(avail, buffer_size - 1);
+  EXPECT_EQ(rc, 0);
+  EXPECT_EQ(3, underruns);
+}
 } // namespace
 
+extern "C" {
+
+int snd_pcm_sw_params_current(snd_pcm_t *pcm, snd_pcm_sw_params_t *params) {
+  return 0;
+}
+
+int snd_pcm_sw_params_get_boundary(const snd_pcm_sw_params_t *params,
+                                   snd_pcm_uframes_t *val) {
+  return 0;
+}
+
+int snd_pcm_sw_params_set_stop_threshold(snd_pcm_t *pcm,
+                                         snd_pcm_sw_params_t *params,
+                                         snd_pcm_uframes_t val) {
+  return 0;
+}
+
+int snd_pcm_sw_params_set_start_threshold(snd_pcm_t *pcm,
+                                          snd_pcm_sw_params_t *params,
+                                          snd_pcm_uframes_t val) {
+  return 0;
+}
+
+int snd_pcm_sw_params_set_period_event(snd_pcm_t *pcm,
+                                       snd_pcm_sw_params_t *params, int val) {
+  return 0;
+}
+
+int snd_pcm_sw_params_set_tstamp_mode(snd_pcm_t *pcm,
+                                      snd_pcm_sw_params_t *params,
+                                      snd_pcm_tstamp_t val) {
+  snd_pcm_sw_params_set_tstamp_mode_called++;
+  return 0;
+}
+
+int snd_pcm_sw_params_set_tstamp_type(snd_pcm_t *pcm,
+                                      snd_pcm_sw_params_t *params,
+                                      snd_pcm_tstamp_type_t val) {
+  snd_pcm_sw_params_set_tstamp_type_called++;
+  return 0;
+}
+
+int snd_pcm_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t *params) {
+  int rc;
+
+  if (snd_pcm_sw_params_ret_vals.size() == 0)
+    return 0;
+  rc = snd_pcm_sw_params_ret_vals.back();
+  snd_pcm_sw_params_ret_vals.pop_back();
+  return rc;
+}
+
+snd_pcm_sframes_t snd_pcm_avail(snd_pcm_t *pcm) {
+  return snd_pcm_htimestamp_avail_ret_val;
+}
+
+int snd_pcm_htimestamp(snd_pcm_t *pcm, snd_pcm_uframes_t *avail,
+                       snd_htimestamp_t *tstamp) {
+  *avail = snd_pcm_htimestamp_avail_ret_val;
+  *tstamp = snd_pcm_htimestamp_tstamp_ret_val;
+  return 0;
+}
+
+}
+
 int main(int argc, char **argv) {
   ::testing::InitGoogleTest(&argc, argv);
   return RUN_ALL_TESTS();
diff --git a/cras/src/tests/alsa_io_unittest.cc b/cras/src/tests/alsa_io_unittest.cc
index 6edf9ba..dc58cd7 100644
--- a/cras/src/tests/alsa_io_unittest.cc
+++ b/cras/src/tests/alsa_io_unittest.cc
@@ -2,8 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <stdio.h>
 #include <gtest/gtest.h>
+#include <map>
+#include <stdio.h>
+#include <syslog.h>
+#include <vector>
 
 extern "C" {
 
@@ -17,6 +20,8 @@
 #include "cras_alsa_io.c"
 }
 
+#define BUFFER_SIZE 8192
+
 //  Data for simulating functions stubbed below.
 static int cras_alsa_open_called;
 static int cras_iodev_append_stream_ret;
@@ -38,6 +43,9 @@
     *cras_alsa_mixer_get_maximum_capture_gain_mixer_input;
 static size_t cras_alsa_mixer_list_outputs_called;
 static size_t cras_alsa_mixer_list_inputs_called;
+static size_t cras_alsa_mixer_get_control_for_section_called;
+static struct mixer_control *
+    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;
@@ -57,6 +65,7 @@
 static size_t sys_get_capture_mute_called;
 static int sys_get_capture_mute_return_value;
 static struct cras_alsa_mixer *fake_mixer = (struct cras_alsa_mixer *)1;
+static struct cras_card_config *fake_config = (struct cras_card_config *)2;
 static struct mixer_control **cras_alsa_mixer_list_outputs_outputs;
 static size_t cras_alsa_mixer_list_outputs_outputs_length;
 static struct mixer_control **cras_alsa_mixer_list_inputs_outputs;
@@ -65,25 +74,36 @@
 static std::vector<struct mixer_control *>
     cras_alsa_mixer_set_output_active_state_outputs;
 static std::vector<int> cras_alsa_mixer_set_output_active_state_values;
-static size_t cras_alsa_mixer_default_volume_curve_called;
-static cras_volume_curve *fake_curve;
 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;
+static struct mixer_control *cras_alsa_jack_get_mixer_input_ret;
 static size_t cras_alsa_mixer_get_output_volume_curve_called;
-static struct cras_volume_curve *cras_alsa_mixer_get_output_volume_curve_value;
+typedef std::map<const struct mixer_control*, std::string> ControlNameMap;
+static ControlNameMap cras_alsa_mixer_get_control_name_values;
+static size_t cras_alsa_mixer_get_control_name_called;
 static size_t cras_alsa_jack_list_create_called;
+static size_t cras_alsa_jack_list_find_jacks_by_name_matching_called;
+static size_t cras_alsa_jack_list_add_jack_for_section_called;
+static struct cras_alsa_jack *
+    cras_alsa_jack_list_add_jack_for_section_result_jack;
 static size_t cras_alsa_jack_list_destroy_called;
+static int cras_alsa_jack_list_has_hctl_jacks_return_val;
 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_dev_name[] = "TestDev";
+static char test_dev_id[] = "TestDevId";
+static size_t cras_iodev_add_node_called;
+static struct cras_ionode *cras_iodev_set_node_attr_ionode;
 static size_t cras_iodev_set_node_attr_called;
 static enum ionode_attr cras_iodev_set_node_attr_attr;
 static int cras_iodev_set_node_attr_value;
 static unsigned cras_alsa_jack_enable_ucm_called;
+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;
@@ -97,9 +117,36 @@
 static size_t ucm_enable_swap_mode_called;
 static int is_utf8_string_ret_value;
 static char *cras_alsa_jack_update_monitor_fake_name = 0;
-static int cras_alsa_jack_get_name_ret_called;
+static int cras_alsa_jack_get_name_called;
 static const char *cras_alsa_jack_get_name_ret_value = 0;
 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_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;
+static int cras_alsa_attempt_resume_called;
+static snd_hctl_t *fake_hctl = (snd_hctl_t *)2;
+static size_t ucm_get_dma_period_for_dev_called;
+static unsigned int ucm_get_dma_period_for_dev_ret;
+static int cras_card_config_get_volume_curve_for_control_called;
+typedef std::map<std::string, struct cras_volume_curve *> VolCurveMap;
+static VolCurveMap cras_card_config_get_volume_curve_vals;
+static int cras_alsa_mmap_get_whole_buffer_called;
+static int cras_iodev_fill_odev_zeros_called;
+static unsigned int cras_iodev_fill_odev_zeros_frames;
+static int cras_iodev_frames_queued_ret;
+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;
 
 void ResetStubData() {
   cras_alsa_open_called = 0;
@@ -118,6 +165,8 @@
   alsa_mixer_get_dB_range_called = 0;
   alsa_mixer_get_output_dB_range_called = 0;
   alsa_mixer_set_capture_mute_called = 0;
+  cras_alsa_mixer_get_control_for_section_called = 0;
+  cras_alsa_mixer_get_control_for_section_return_value = NULL;
   cras_alsa_mixer_list_outputs_called = 0;
   cras_alsa_mixer_list_outputs_outputs_length = 0;
   cras_alsa_mixer_list_inputs_called = 0;
@@ -125,17 +174,26 @@
   cras_alsa_mixer_set_output_active_state_called = 0;
   cras_alsa_mixer_set_output_active_state_outputs.clear();
   cras_alsa_mixer_set_output_active_state_values.clear();
-  cras_alsa_mixer_default_volume_curve_called = 0;
   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;
-  cras_alsa_mixer_get_output_volume_curve_value = NULL;
+  cras_alsa_jack_get_mixer_output_ret = NULL;
+  cras_alsa_jack_get_mixer_input_ret = NULL;
+  cras_alsa_mixer_get_control_name_values.clear();
+  cras_alsa_mixer_get_control_name_called = 0;
   cras_alsa_jack_list_create_called = 0;
+  cras_alsa_jack_list_find_jacks_by_name_matching_called = 0;
+  cras_alsa_jack_list_add_jack_for_section_called = 0;
+  cras_alsa_jack_list_add_jack_for_section_result_jack = NULL;
   cras_alsa_jack_list_destroy_called = 0;
+  cras_alsa_jack_list_has_hctl_jacks_return_val = 1;
+  cras_iodev_add_node_called = 0;
   cras_iodev_set_node_attr_called = 0;
   cras_alsa_jack_enable_ucm_called = 0;
+  ucm_set_enabled_called = 0;
   cras_iodev_update_dsp_called = 0;
   cras_iodev_update_dsp_name = 0;
   ucm_get_dsp_name_default_called = 0;
@@ -148,25 +206,67 @@
   ucm_enable_swap_mode_ret_value = 0;
   ucm_enable_swap_mode_called = 0;
   is_utf8_string_ret_value = 1;
-  cras_alsa_jack_get_name_ret_called = 0;
+  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_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;
+  cras_alsa_attempt_resume_called = 0;
+  ucm_get_dma_period_for_dev_called = 0;
+  ucm_get_dma_period_for_dev_ret = 0;
+  cras_alsa_mmap_get_whole_buffer_called = 0;
+  cras_iodev_fill_odev_zeros_called = 0;
+  cras_iodev_fill_odev_zeros_frames = 0;
+  cras_iodev_frames_queued_ret = 0;
+  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();
 }
 
-static long fake_get_dBFS(const cras_volume_curve *curve, size_t volume)
+static long fake_get_dBFS(const struct cras_volume_curve *curve, size_t volume)
 {
+  fake_get_dBFS_volume_curve_val = curve;
   return (volume - 100) * 100;
 }
+static cras_volume_curve default_curve = {
+  .get_dBFS = fake_get_dBFS,
+};
+
+static struct cras_iodev *alsa_iodev_create_with_default_parameters(
+    size_t card_index,
+    const char *dev_id,
+    enum CRAS_ALSA_CARD_TYPE card_type,
+    int is_first,
+    struct cras_alsa_mixer *mixer,
+    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");
+}
 
 namespace {
 
 TEST(AlsaIoInit, InitializeInvalidDirection) {
   struct alsa_io *aio;
 
-  aio = (struct alsa_io *)alsa_iodev_create(0, test_card_name, 0, test_dev_name,
-                                            NULL, ALSA_CARD_TYPE_INTERNAL, 0,
-                                            fake_mixer, NULL,
-                                            CRAS_NUM_DIRECTIONS, 0, 0);
+  aio = (struct alsa_io *)alsa_iodev_create_with_default_parameters(
+      0, NULL, ALSA_CARD_TYPE_INTERNAL, 0, fake_mixer, fake_config, NULL,
+      CRAS_NUM_DIRECTIONS);
   ASSERT_EQ(aio, (void *)NULL);
 }
 
@@ -175,19 +275,24 @@
   struct cras_alsa_mixer * const fake_mixer = (struct cras_alsa_mixer*)2;
 
   ResetStubData();
-  aio = (struct alsa_io *)alsa_iodev_create(0, test_card_name, 0, test_dev_name,
-                                            NULL, ALSA_CARD_TYPE_INTERNAL, 1,
-                                            fake_mixer, NULL,
-                                            CRAS_STREAM_OUTPUT, 0, 0);
-  ASSERT_NE(aio, (void *)NULL);
+  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));
+  /* 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(1, cras_alsa_fill_properties_called);
+  EXPECT_EQ(0, 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);
+  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);
+  EXPECT_EQ(0, strcmp(test_dev_id, aio->dev_id));
 
   alsa_iodev_destroy((struct cras_iodev *)aio);
   EXPECT_EQ(1, cras_iodev_free_resources_called);
@@ -198,40 +303,49 @@
   struct cras_alsa_mixer * const fake_mixer = (struct cras_alsa_mixer*)2;
 
   ResetStubData();
-  aio = (struct alsa_io *)alsa_iodev_create(0, test_card_name, 0, test_dev_name,
-                                            NULL, ALSA_CARD_TYPE_INTERNAL, 0,
-                                            fake_mixer, NULL,
-                                            CRAS_STREAM_OUTPUT, 0, 0);
-
+  aio = (struct alsa_io *)alsa_iodev_create_with_default_parameters(
+      0, NULL, ALSA_CARD_TYPE_INTERNAL, 0, fake_mixer, fake_config, NULL,
+      CRAS_STREAM_OUTPUT);
+  ASSERT_EQ(0, alsa_iodev_legacy_complete_init((struct cras_iodev *)aio));
+  EXPECT_EQ(2, cras_card_config_get_volume_curve_for_control_called);
   ASSERT_STREQ("(default)", aio->base.active_node->name);
   ASSERT_EQ(1, aio->base.active_node->plugged);
+  ASSERT_EQ((void *)no_stream, (void *)aio->base.no_stream);
+  ASSERT_EQ((void *)output_should_wake, (void *)aio->base.output_should_wake);
   alsa_iodev_destroy((struct cras_iodev *)aio);
 
-  aio = (struct alsa_io *)alsa_iodev_create(0, test_card_name, 0, test_dev_name,
-                                            NULL, ALSA_CARD_TYPE_INTERNAL, 1,
-                                            fake_mixer, NULL,
-                                            CRAS_STREAM_OUTPUT, 0, 0);
-
+  aio = (struct alsa_io *)alsa_iodev_create_with_default_parameters(
+      0, NULL, 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));
+  EXPECT_EQ(4, cras_card_config_get_volume_curve_for_control_called);
   ASSERT_STREQ("Speaker", aio->base.active_node->name);
   ASSERT_EQ(1, aio->base.active_node->plugged);
+  ASSERT_EQ((void *)no_stream, (void *)aio->base.no_stream);
+  ASSERT_EQ((void *)output_should_wake, (void *)aio->base.output_should_wake);
   alsa_iodev_destroy((struct cras_iodev *)aio);
 
-  aio = (struct alsa_io *)alsa_iodev_create(0, test_card_name, 0, test_dev_name,
-                                            NULL, ALSA_CARD_TYPE_INTERNAL, 0,
-                                            fake_mixer, NULL,
-                                            CRAS_STREAM_INPUT, 0, 0);
-
+  aio = (struct alsa_io *)alsa_iodev_create_with_default_parameters(
+      0, NULL, ALSA_CARD_TYPE_INTERNAL, 0, fake_mixer, fake_config, NULL,
+      CRAS_STREAM_INPUT);
+  ASSERT_EQ(0, alsa_iodev_legacy_complete_init((struct cras_iodev *)aio));
+  /* No more call to get volume curve for input device. */
+  EXPECT_EQ(4, cras_card_config_get_volume_curve_for_control_called);
   ASSERT_STREQ("(default)", aio->base.active_node->name);
   ASSERT_EQ(1, aio->base.active_node->plugged);
+  ASSERT_EQ((void *)no_stream, (void *)aio->base.no_stream);
+  ASSERT_EQ((void *)output_should_wake, (void *)aio->base.output_should_wake);
   alsa_iodev_destroy((struct cras_iodev *)aio);
 
-  aio = (struct alsa_io *)alsa_iodev_create(0, test_card_name, 0, test_dev_name,
-                                            NULL, ALSA_CARD_TYPE_INTERNAL, 1,
-                                            fake_mixer, NULL,
-                                            CRAS_STREAM_INPUT, 0, 0);
-
+  aio = (struct alsa_io *)alsa_iodev_create_with_default_parameters(
+      0, NULL, ALSA_CARD_TYPE_INTERNAL, 1, fake_mixer, fake_config, NULL,
+      CRAS_STREAM_INPUT);
+  ASSERT_EQ(0, alsa_iodev_legacy_complete_init((struct cras_iodev *)aio));
+  EXPECT_EQ(4, cras_card_config_get_volume_curve_for_control_called);
   ASSERT_STREQ("Internal Mic", aio->base.active_node->name);
   ASSERT_EQ(1, aio->base.active_node->plugged);
+  ASSERT_EQ((void *)no_stream, (void *)aio->base.no_stream);
+  ASSERT_EQ((void *)output_should_wake, (void *)aio->base.output_should_wake);
   alsa_iodev_destroy((struct cras_iodev *)aio);
 }
 
@@ -240,11 +354,11 @@
   struct cras_alsa_mixer * const fake_mixer = (struct cras_alsa_mixer*)2;
 
   ResetStubData();
-  aio = (struct alsa_io *)alsa_iodev_create(0, test_card_name, 0, test_dev_name,
-                                            NULL, ALSA_CARD_TYPE_USB, 1,
-                                            fake_mixer, NULL,
-                                            CRAS_STREAM_OUTPUT, 0, 0);
-
+  aio = (struct alsa_io *)alsa_iodev_create_with_default_parameters(
+      0, NULL, ALSA_CARD_TYPE_USB, 1, fake_mixer, fake_config, NULL,
+      CRAS_STREAM_OUTPUT);
+  ASSERT_EQ(0, alsa_iodev_legacy_complete_init((struct cras_iodev *)aio));
+  EXPECT_EQ(2, cras_card_config_get_volume_curve_for_control_called);
   ASSERT_STREQ("(default)", aio->base.active_node->name);
   ASSERT_EQ(1, aio->base.active_node->plugged);
   EXPECT_EQ(1, cras_iodev_set_node_attr_called);
@@ -252,11 +366,11 @@
   EXPECT_EQ(1, cras_iodev_set_node_attr_value);
   alsa_iodev_destroy((struct cras_iodev *)aio);
 
-  aio = (struct alsa_io *)alsa_iodev_create(0, test_card_name, 0, test_dev_name,
-                                            NULL, ALSA_CARD_TYPE_USB, 1,
-                                            fake_mixer, NULL,
-                                            CRAS_STREAM_INPUT, 0, 0);
-
+  aio = (struct alsa_io *)alsa_iodev_create_with_default_parameters(
+      0, NULL, ALSA_CARD_TYPE_USB, 1, fake_mixer, fake_config, NULL,
+      CRAS_STREAM_INPUT);
+  ASSERT_EQ(0, alsa_iodev_legacy_complete_init((struct cras_iodev *)aio));
+  EXPECT_EQ(2, cras_card_config_get_volume_curve_for_control_called);
   ASSERT_STREQ("(default)", aio->base.active_node->name);
   ASSERT_EQ(1, aio->base.active_node->plugged);
   EXPECT_EQ(2, cras_iodev_set_node_attr_called);
@@ -268,28 +382,34 @@
 TEST(AlsaIoInit, OpenPlayback) {
   struct cras_iodev *iodev;
   struct cras_audio_format format;
+  struct alsa_io *aio;
 
   ResetStubData();
-  iodev = alsa_iodev_create(0, test_card_name, 0, test_dev_name,
-                            NULL, ALSA_CARD_TYPE_INTERNAL, 0,
-                            fake_mixer, NULL,
-                            CRAS_STREAM_OUTPUT, 0, 0);
-
+  iodev = alsa_iodev_create_with_default_parameters(0, NULL,
+                                                    ALSA_CARD_TYPE_INTERNAL, 0,
+                                                    fake_mixer, fake_config,
+                                                    NULL, CRAS_STREAM_OUTPUT);
+  ASSERT_EQ(0, alsa_iodev_legacy_complete_init(iodev));
+  EXPECT_EQ(2, cras_card_config_get_volume_curve_for_control_called);
+  aio = (struct alsa_io *)iodev;
+  format.frame_rate = 48000;
   cras_iodev_set_format(iodev, &format);
-  fake_curve =
-      static_cast<struct cras_volume_curve *>(calloc(1, sizeof(*fake_curve)));
-  fake_curve->get_dBFS = fake_get_dBFS;
 
+  // Test that these flags are cleared after open_dev.
+  aio->is_free_running = 1;
+  aio->filled_zeros_for_draining = 512;
   iodev->open_dev(iodev);
   EXPECT_EQ(1, 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);
   EXPECT_EQ(0, cras_iodev_set_node_attr_called);
+  EXPECT_EQ(0, aio->is_free_running);
+  EXPECT_EQ(0, aio->filled_zeros_for_draining);
+  EXPECT_EQ(SEVERE_UNDERRUN_MS * format.frame_rate / 1000,
+            aio->severe_underrun_frames);
 
   alsa_iodev_destroy(iodev);
-  free(fake_curve);
-  fake_curve = NULL;
   free(fake_format);
 }
 
@@ -297,26 +417,30 @@
   struct cras_iodev *iodev;
 
   ResetStubData();
-  iodev = alsa_iodev_create(0, test_card_name, 0, test_dev_name,
-                            NULL, ALSA_CARD_TYPE_INTERNAL, 1,
-                            fake_mixer, NULL,
-                            CRAS_STREAM_OUTPUT, 0, 0);
+  iodev = alsa_iodev_create_with_default_parameters(0, NULL,
+                                                    ALSA_CARD_TYPE_INTERNAL, 1,
+                                                    fake_mixer, fake_config,
+                                                    NULL, CRAS_STREAM_OUTPUT);
+  ASSERT_EQ(0, alsa_iodev_legacy_complete_init(iodev));
+  EXPECT_EQ(2, cras_card_config_get_volume_curve_for_control_called);
   EXPECT_EQ(0, cras_iodev_set_node_attr_called);
   alsa_iodev_destroy(iodev);
 
   ResetStubData();
-  iodev = alsa_iodev_create(0, test_card_name, 0, test_dev_name,
-                            NULL, ALSA_CARD_TYPE_USB, 0,
-                            fake_mixer, NULL,
-                            CRAS_STREAM_OUTPUT, 0, 0);
+  iodev = alsa_iodev_create_with_default_parameters(0, NULL, ALSA_CARD_TYPE_USB,
+                                                    0, fake_mixer, fake_config,
+                                                    NULL, CRAS_STREAM_OUTPUT);
+  ASSERT_EQ(0, alsa_iodev_legacy_complete_init(iodev));
+  EXPECT_EQ(2, cras_card_config_get_volume_curve_for_control_called);
   EXPECT_EQ(0, cras_iodev_set_node_attr_called);
   alsa_iodev_destroy(iodev);
 
   ResetStubData();
-  iodev = alsa_iodev_create(0, test_card_name, 0, test_dev_name,
-                            NULL, ALSA_CARD_TYPE_USB, 1,
-                            fake_mixer, NULL,
-                            CRAS_STREAM_OUTPUT, 0, 0);
+  iodev = alsa_iodev_create_with_default_parameters(0, NULL, ALSA_CARD_TYPE_USB,
+                                                    1, fake_mixer, fake_config,
+                                                    NULL, CRAS_STREAM_OUTPUT);
+  ASSERT_EQ(0, alsa_iodev_legacy_complete_init(iodev));
+  EXPECT_EQ(2, cras_card_config_get_volume_curve_for_control_called);
   // Should assume USB devs are plugged when they appear.
   EXPECT_EQ(1, cras_iodev_set_node_attr_called);
   EXPECT_EQ(IONODE_ATTR_PLUGGED, cras_iodev_set_node_attr_attr);
@@ -330,10 +454,11 @@
   alsa_mixer_get_dB_range_value = 1000;
   alsa_mixer_get_output_dB_range_value = 1000;
   ResetStubData();
-  iodev = alsa_iodev_create(0, test_card_name, 0, test_dev_name,
-                            NULL, ALSA_CARD_TYPE_USB, 1,
-                            fake_mixer, NULL,
-                            CRAS_STREAM_OUTPUT, 0, 0);
+  iodev = alsa_iodev_create_with_default_parameters(0, NULL, ALSA_CARD_TYPE_USB,
+                                                    1, fake_mixer, fake_config,
+                                                    NULL, CRAS_STREAM_OUTPUT);
+  ASSERT_EQ(0, alsa_iodev_legacy_complete_init(iodev));
+  EXPECT_EQ(2, cras_card_config_get_volume_curve_for_control_called);
   EXPECT_EQ(1, alsa_mixer_get_dB_range_called);
   EXPECT_EQ(1, alsa_mixer_get_output_dB_range_called);
   EXPECT_EQ(1, iodev->active_node->software_volume_needed);
@@ -342,34 +467,112 @@
   alsa_mixer_get_dB_range_value = 3000;
   alsa_mixer_get_output_dB_range_value = 2000;
   ResetStubData();
-  iodev = alsa_iodev_create(0, test_card_name, 0, test_dev_name,
-                            NULL, ALSA_CARD_TYPE_USB, 1,
-                            fake_mixer, NULL,
-                            CRAS_STREAM_OUTPUT, 0, 0);
+  iodev = alsa_iodev_create_with_default_parameters(0, NULL, ALSA_CARD_TYPE_USB,
+                                                    1, fake_mixer, fake_config,
+                                                    NULL, CRAS_STREAM_OUTPUT);
+  ASSERT_EQ(0, alsa_iodev_legacy_complete_init(iodev));
+  EXPECT_EQ(2, cras_card_config_get_volume_curve_for_control_called);
   EXPECT_EQ(1, alsa_mixer_get_dB_range_called);
   EXPECT_EQ(1, alsa_mixer_get_output_dB_range_called);
   EXPECT_EQ(0, iodev->active_node->software_volume_needed);
   alsa_iodev_destroy(iodev);
 }
 
+TEST(AlsaIoInit, UseSoftwareGain) {
+  struct cras_iodev *iodev;
+  struct cras_use_case_mgr * const fake_ucm = (struct cras_use_case_mgr*)3;
+
+  /* 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, 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(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 softare 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);
+
+  /* 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;
+
+  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 dBm.
+  ucm_get_default_node_gain_values["Internal Mic"] = default_node_gain;
+
+  // 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 dBm.
+  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));
+
+  alsa_iodev_destroy(iodev);
+}
+
 TEST(AlsaIoInit, RouteBasedOnJackCallback) {
   struct alsa_io *aio;
   struct cras_alsa_mixer * const fake_mixer = (struct cras_alsa_mixer*)2;
 
   ResetStubData();
-  aio = (struct alsa_io *)alsa_iodev_create(0, test_card_name, 0, test_dev_name,
-                                            NULL, ALSA_CARD_TYPE_INTERNAL, 1,
-                                            fake_mixer, NULL,
-                                            CRAS_STREAM_OUTPUT, 0, 0);
+  aio = (struct alsa_io *)alsa_iodev_create_with_default_parameters(
+      0, NULL, ALSA_CARD_TYPE_INTERNAL, 1, fake_mixer, fake_config, NULL,
+      CRAS_STREAM_OUTPUT);
   ASSERT_NE(aio, (void *)NULL);
+  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(1, cras_alsa_fill_properties_called);
+  EXPECT_EQ(0, cras_alsa_fill_properties_called);
   EXPECT_EQ(1, cras_alsa_mixer_list_outputs_called);
   EXPECT_EQ(1, cras_alsa_jack_list_create_called);
-
-  fake_curve =
-    static_cast<struct cras_volume_curve *>(calloc(1, sizeof(*fake_curve)));
-  fake_curve->get_dBFS = fake_get_dBFS;
+  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);
 
   cras_alsa_jack_list_create_cb(NULL, 1, cras_alsa_jack_list_create_cb_data);
   EXPECT_EQ(1, cras_iodev_set_node_attr_called);
@@ -382,8 +585,6 @@
 
   alsa_iodev_destroy((struct cras_iodev *)aio);
   EXPECT_EQ(1, cras_alsa_jack_list_destroy_called);
-  free(fake_curve);
-  fake_curve = NULL;
 }
 
 TEST(AlsaIoInit, RouteBasedOnInputJackCallback) {
@@ -391,18 +592,17 @@
   struct cras_alsa_mixer * const fake_mixer = (struct cras_alsa_mixer*)2;
 
   ResetStubData();
-  aio = (struct alsa_io *)alsa_iodev_create(0, test_card_name, 0, test_dev_name,
-                                            NULL, ALSA_CARD_TYPE_INTERNAL, 0,
-                                            fake_mixer, NULL,
-                                            CRAS_STREAM_INPUT, 0, 0);
+  aio = (struct alsa_io *)alsa_iodev_create_with_default_parameters(
+      0, NULL, ALSA_CARD_TYPE_INTERNAL, 0, fake_mixer, fake_config, NULL,
+      CRAS_STREAM_INPUT);
   ASSERT_NE(aio, (void *)NULL);
-  EXPECT_EQ(SND_PCM_STREAM_CAPTURE, aio->alsa_stream);
-  EXPECT_EQ(1, cras_alsa_fill_properties_called);
-  EXPECT_EQ(1, cras_alsa_jack_list_create_called);
+  ASSERT_EQ(0, alsa_iodev_legacy_complete_init((struct cras_iodev *)aio));
 
-  fake_curve =
-    static_cast<struct cras_volume_curve *>(calloc(1, sizeof(*fake_curve)));
-  fake_curve->get_dBFS = fake_get_dBFS;
+  EXPECT_EQ(SND_PCM_STREAM_CAPTURE, aio->alsa_stream);
+  EXPECT_EQ(0, 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);
 
   cras_alsa_jack_list_create_cb(NULL, 1, cras_alsa_jack_list_create_cb_data);
   EXPECT_EQ(1, cras_iodev_set_node_attr_called);
@@ -415,21 +615,20 @@
 
   alsa_iodev_destroy((struct cras_iodev *)aio);
   EXPECT_EQ(1, cras_alsa_jack_list_destroy_called);
-  free(fake_curve);
-  fake_curve = NULL;
 }
 
 TEST(AlsaIoInit, InitializeCapture) {
   struct alsa_io *aio;
 
   ResetStubData();
-  aio = (struct alsa_io *)alsa_iodev_create(0, test_card_name, 0, test_dev_name,
-                                            NULL, ALSA_CARD_TYPE_INTERNAL, 1,
-                                            fake_mixer, NULL,
-                                            CRAS_STREAM_INPUT, 0, 0);
+  aio = (struct alsa_io *)alsa_iodev_create_with_default_parameters(
+      0, NULL, ALSA_CARD_TYPE_INTERNAL, 1, fake_mixer, fake_config, NULL,
+      CRAS_STREAM_INPUT);
   ASSERT_NE(aio, (void *)NULL);
+  ASSERT_EQ(0, alsa_iodev_legacy_complete_init((struct cras_iodev *)aio));
+
   EXPECT_EQ(SND_PCM_STREAM_CAPTURE, aio->alsa_stream);
-  EXPECT_EQ(1, cras_alsa_fill_properties_called);
+  EXPECT_EQ(0, cras_alsa_fill_properties_called);
   EXPECT_EQ(1, cras_alsa_mixer_list_inputs_called);
 
   alsa_iodev_destroy((struct cras_iodev *)aio);
@@ -438,12 +637,16 @@
 TEST(AlsaIoInit, OpenCapture) {
   struct cras_iodev *iodev;
   struct cras_audio_format format;
+  struct alsa_io *aio;
 
-  iodev = alsa_iodev_create(0, test_card_name, 0, test_dev_name,
-                            NULL, ALSA_CARD_TYPE_INTERNAL, 0,
-                            fake_mixer, NULL,
-                            CRAS_STREAM_INPUT, 0, 0);
+  iodev = alsa_iodev_create_with_default_parameters(0, NULL,
+                                                    ALSA_CARD_TYPE_INTERNAL, 0,
+                                                    fake_mixer, fake_config,
+                                                    NULL, CRAS_STREAM_INPUT);
+  ASSERT_EQ(0, alsa_iodev_legacy_complete_init(iodev));
 
+  aio = (struct alsa_io *)iodev;
+  format.frame_rate = 48000;
   cras_iodev_set_format(iodev, &format);
 
   ResetStubData();
@@ -457,6 +660,84 @@
   EXPECT_EQ(1, sys_get_capture_mute_called);
   EXPECT_EQ(1, alsa_mixer_set_capture_mute_called);
   EXPECT_EQ(1, cras_alsa_start_called);
+  EXPECT_EQ(SEVERE_UNDERRUN_MS * format.frame_rate / 1000,
+            aio->severe_underrun_frames);
+
+  alsa_iodev_destroy(iodev);
+  free(fake_format);
+}
+
+TEST(AlsaIoInit, OpenCaptureSetCaptureGainWithDefaultNodeGain) {
+  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;
+
+  ResetStubData();
+  // Set default node gain to -1000 dBm.
+  ucm_get_default_node_gain_values["Internal Mic"] = default_node_gain;
+
+  // 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));
+
+  cras_iodev_set_format(iodev, &format);
+
+  // 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 dBm.
+  sys_get_capture_gain_return_value = system_gain;
+
+  iodev->open_dev(iodev);
+  iodev->close_dev(iodev);
+
+  // Hardware gain is set to 2000 - 1000 dBm.
+  EXPECT_EQ(system_gain + default_node_gain, alsa_mixer_set_capture_dBFS_value);
+
+  alsa_iodev_destroy(iodev);
+  free(fake_format);
+}
+
+TEST(AlsaIoInit, OpenCaptureSetCaptureGainWithSoftwareGain) {
+  struct cras_iodev *iodev;
+  struct cras_audio_format format;
+  struct cras_use_case_mgr * const fake_ucm = (struct cras_use_case_mgr*)3;
+
+  /* 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,
+                                                    CRAS_STREAM_INPUT);
+  ASSERT_EQ(0, alsa_iodev_legacy_complete_init(iodev));
+
+  cras_iodev_set_format(iodev, &format);
+
+  /* System gain is set to 1000dBm */
+  sys_get_capture_gain_return_value = 1000;
+
+  iodev->open_dev(iodev);
+  iodev->close_dev(iodev);
+
+  /* Hardware gain is set to 0dB when software gain is used. */
+  EXPECT_EQ(0, alsa_mixer_set_capture_dBFS_value);
+
+  /* Test the case where software gain is not needed. */
+  iodev->active_node->software_volume_needed = 0;
+  iodev->open_dev(iodev);
+  iodev->close_dev(iodev);
+
+  /* Hardware gain is set to 1000dBm as got from system capture gain.*/
+  EXPECT_EQ(1000, alsa_mixer_set_capture_dBFS_value);
 
   alsa_iodev_destroy(iodev);
   free(fake_format);
@@ -467,12 +748,63 @@
   struct cras_alsa_mixer * const fake_mixer = (struct cras_alsa_mixer*)2;
 
   ResetStubData();
-  iodev = alsa_iodev_create(0, test_card_name, 0, test_dev_name,
-                            NULL, ALSA_CARD_TYPE_INTERNAL, 0,
-                            fake_mixer, NULL,
-                            CRAS_STREAM_OUTPUT, 0, 0);
+  iodev = alsa_iodev_create_with_default_parameters(0, NULL,
+                                                    ALSA_CARD_TYPE_INTERNAL, 0,
+                                                    fake_mixer, fake_config,
+                                                    NULL,
+                                                    CRAS_STREAM_OUTPUT);
+  ASSERT_EQ(0, alsa_iodev_legacy_complete_init(iodev));
+  EXPECT_EQ(2, cras_card_config_get_volume_curve_for_control_called);
 
-  iodev->update_active_node(iodev, 0);
+  iodev->update_active_node(iodev, 0, 1);
+
+  alsa_iodev_destroy(iodev);
+}
+
+TEST(AlsaIoInit, StartDevice) {
+  struct cras_iodev *iodev;
+  int rc;
+
+  ResetStubData();
+  iodev = alsa_iodev_create_with_default_parameters(0, NULL,
+                                                    ALSA_CARD_TYPE_INTERNAL, 0,
+                                                    NULL, fake_config, NULL,
+                                                    CRAS_STREAM_OUTPUT);
+  ASSERT_EQ(0, alsa_iodev_legacy_complete_init(iodev));
+  EXPECT_EQ(2, cras_card_config_get_volume_curve_for_control_called);
+
+  // Return right away if it is already running.
+  snd_pcm_state_ret = SND_PCM_STATE_RUNNING;
+  rc = iodev->start(iodev);
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(0, cras_alsa_start_called);
+
+  // Otherwise, start the device.
+  snd_pcm_state_ret = SND_PCM_STATE_SETUP;
+  rc = iodev->start(iodev);
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(1, cras_alsa_start_called);
+
+  alsa_iodev_destroy(iodev);
+}
+
+TEST(AlsaIoInit, ResumeDevice) {
+  struct cras_iodev *iodev;
+  int rc;
+
+  ResetStubData();
+  iodev = alsa_iodev_create_with_default_parameters(0, NULL,
+                                                    ALSA_CARD_TYPE_INTERNAL, 0,
+                                                    NULL, fake_config, NULL,
+                                                    CRAS_STREAM_OUTPUT);
+  ASSERT_EQ(0, alsa_iodev_legacy_complete_init(iodev));
+  EXPECT_EQ(2, cras_card_config_get_volume_curve_for_control_called);
+
+  // Attempt to resume if the device is suspended.
+  snd_pcm_state_ret = SND_PCM_STATE_SUSPENDED;
+  rc = iodev->start(iodev);
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(1, cras_alsa_attempt_resume_called);
 
   alsa_iodev_destroy(iodev);
 }
@@ -480,15 +812,15 @@
 TEST(AlsaIoInit, DspNameDefault) {
   struct alsa_io *aio;
   struct cras_alsa_mixer * const fake_mixer = (struct cras_alsa_mixer*)2;
-  snd_use_case_mgr_t * const fake_ucm = (snd_use_case_mgr_t*)3;
+  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(0, test_card_name, 0, test_dev_name,
-                                            NULL, ALSA_CARD_TYPE_INTERNAL, 0,
-                                            fake_mixer, fake_ucm,
-                                            CRAS_STREAM_OUTPUT, 0, 0);
-  ASSERT_NE(aio, (void *)NULL);
+  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(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_default_called);
   EXPECT_EQ(1, cras_alsa_jack_get_dsp_name_called);
@@ -500,17 +832,16 @@
 TEST(AlsaIoInit, DspNameJackOverride) {
   struct alsa_io *aio;
   struct cras_alsa_mixer * const fake_mixer = (struct cras_alsa_mixer*)2;
-  snd_use_case_mgr_t * const fake_ucm = (snd_use_case_mgr_t*)3;
+  struct cras_use_case_mgr * const fake_ucm = (struct cras_use_case_mgr*)3;
   const struct cras_alsa_jack *jack = (struct cras_alsa_jack*)4;
 
   ResetStubData();
   ucm_get_dsp_name_default_value = "default_dsp";
   cras_alsa_jack_get_dsp_name_value = "override_dsp";
-  aio = (struct alsa_io *)alsa_iodev_create(0, test_card_name, 0, test_dev_name,
-                                            NULL, ALSA_CARD_TYPE_INTERNAL, 0,
-                                            fake_mixer, fake_ucm,
-                                            CRAS_STREAM_OUTPUT, 0, 0);
-  ASSERT_NE(aio, (void *)NULL);
+  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_default_called);
   EXPECT_EQ(1, cras_alsa_jack_get_dsp_name_called);
@@ -522,13 +853,13 @@
   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);
+  alsa_iodev_set_active_node(&aio->base, aio->base.nodes->next, 1);
   EXPECT_EQ(2, cras_alsa_jack_get_dsp_name_called);
   EXPECT_EQ(2, cras_iodev_update_dsp_called);
   EXPECT_STREQ("override_dsp", cras_iodev_update_dsp_name);
 
   // Mark the default node as active.
-  alsa_iodev_set_active_node(&aio->base, aio->base.nodes);
+  alsa_iodev_set_active_node(&aio->base, aio->base.nodes, 1);
   EXPECT_EQ(1, ucm_get_dsp_name_default_called);
   EXPECT_EQ(3, cras_alsa_jack_get_dsp_name_called);
   EXPECT_EQ(3, cras_iodev_update_dsp_called);
@@ -540,15 +871,14 @@
 TEST(AlsaIoInit, NodeTypeOverride) {
   struct alsa_io *aio;
   struct cras_alsa_mixer * const fake_mixer = (struct cras_alsa_mixer*)2;
-  snd_use_case_mgr_t * const fake_ucm = (snd_use_case_mgr_t*)3;
+  struct cras_use_case_mgr * const fake_ucm = (struct cras_use_case_mgr*)3;
   const struct cras_alsa_jack *jack = (struct cras_alsa_jack*)4;
 
   ResetStubData();
-  aio = (struct alsa_io *)alsa_iodev_create(0, test_card_name, 0, test_dev_name,
-                                            NULL, ALSA_CARD_TYPE_INTERNAL, 0,
-                                            fake_mixer, fake_ucm,
-                                            CRAS_STREAM_OUTPUT, 0, 0);
-  ASSERT_NE(aio, (void *)NULL);
+  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));
   // Add the jack node.
   cras_alsa_jack_list_create_cb(jack, 1, cras_alsa_jack_list_create_cb_data);
   // Verify that cras_alsa_jack_update_node_type is called when an output device
@@ -561,27 +891,28 @@
 TEST(AlsaIoInit, SwapMode) {
   struct alsa_io *aio;
   struct cras_alsa_mixer * const fake_mixer = (struct cras_alsa_mixer*)2;
-  snd_use_case_mgr_t * const fake_ucm = (snd_use_case_mgr_t*)3;
+  struct cras_use_case_mgr * const fake_ucm = (struct cras_use_case_mgr*)3;
   struct cras_ionode * const fake_node = (cras_ionode *)4;
   ResetStubData();
   // Stub replies that swap mode does not exist.
   ucm_swap_mode_exists_ret_value = 0;
 
-  aio = (struct alsa_io *)alsa_iodev_create(0, test_card_name, 0, test_dev_name,
-                                            NULL, ALSA_CARD_TYPE_INTERNAL, 0,
-                                            fake_mixer, fake_ucm,
-                                            CRAS_STREAM_OUTPUT, 0, 0);
-  ASSERT_NE(aio, (void *)NULL);
-  EXPECT_EQ(NULL, aio->base.set_swap_mode_for_node);
+  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));
+
+  aio->base.set_swap_mode_for_node((cras_iodev*)aio, fake_node, 1);
+  /* Swap mode is implemented by dsp. */
+  EXPECT_EQ(1, cras_iodev_dsp_set_swap_mode_for_node_called);
 
   // Stub replies that swap mode exists.
   ucm_swap_mode_exists_ret_value = 1;
 
-  aio = (struct alsa_io *)alsa_iodev_create(0, test_card_name, 0, test_dev_name,
-                                            NULL, ALSA_CARD_TYPE_INTERNAL, 0,
-                                            fake_mixer, fake_ucm,
-                                            CRAS_STREAM_OUTPUT, 0, 0);
-  ASSERT_NE(aio, (void *)NULL);
+  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));
   // Enable swap mode.
   aio->base.set_swap_mode_for_node((cras_iodev*)aio, fake_node, 1);
 
@@ -602,23 +933,20 @@
   ResetStubData();
   outputs[0] = reinterpret_cast<struct mixer_control *>(3);
   outputs[1] = reinterpret_cast<struct mixer_control *>(4);
-  fake_curve =
-    static_cast<struct cras_volume_curve *>(calloc(1, sizeof(*fake_curve)));
-  fake_curve->get_dBFS = fake_get_dBFS;
-  cras_alsa_mixer_get_output_volume_curve_value = fake_curve;
   cras_alsa_mixer_list_outputs_outputs = outputs;
   cras_alsa_mixer_list_outputs_outputs_length = ARRAY_SIZE(outputs);
-  aio = (struct alsa_io *)alsa_iodev_create(0, test_card_name, 0, test_dev_name,
-                                            NULL, ALSA_CARD_TYPE_INTERNAL, 1,
-                                            fake_mixer, NULL,
-                                            CRAS_STREAM_OUTPUT, 0, 0);
-  ASSERT_NE(aio, (void *)NULL);
+  aio = (struct alsa_io *)alsa_iodev_create_with_default_parameters(
+      0, NULL, 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));
+  /* Two mixer controls calls get volume curve. */
+  EXPECT_EQ(4, cras_card_config_get_volume_curve_for_control_called);
   EXPECT_EQ(SND_PCM_STREAM_PLAYBACK, aio->alsa_stream);
   EXPECT_EQ(1, cras_alsa_mixer_list_outputs_called);
 
   ResetStubData();
   rc = alsa_iodev_set_active_node((struct cras_iodev *)aio,
-                                  aio->base.nodes->next);
+                                  aio->base.nodes->next, 1);
   EXPECT_EQ(0, rc);
   EXPECT_EQ(0, alsa_mixer_set_mute_called);
   EXPECT_EQ(0, alsa_mixer_set_dBFS_called);
@@ -628,11 +956,11 @@
   EXPECT_EQ(outputs[1], cras_alsa_mixer_set_output_active_state_outputs[1]);
   EXPECT_EQ(1, cras_alsa_mixer_set_output_active_state_values[1]);
   EXPECT_EQ(1, cras_iodev_update_dsp_called);
-  EXPECT_EQ(2, cras_alsa_jack_enable_ucm_called);
+  // No jack is defined, and UCM is not used.
+  EXPECT_EQ(0, cras_alsa_jack_enable_ucm_called);
+  EXPECT_EQ(0, ucm_set_enabled_called);
 
   alsa_iodev_destroy((struct cras_iodev *)aio);
-  free(fake_curve);
-  fake_curve = NULL;
 }
 
 //  Test handling of different amounts of outputs.
@@ -645,29 +973,21 @@
   ResetStubData();
   outputs[0] = reinterpret_cast<struct mixer_control *>(3);
   outputs[1] = reinterpret_cast<struct mixer_control *>(4);
-  fake_curve =
-    static_cast<struct cras_volume_curve *>(calloc(1, sizeof(*fake_curve)));
-  fake_curve->get_dBFS = fake_get_dBFS;
-  cras_alsa_mixer_get_output_volume_curve_value = fake_curve;
   cras_alsa_mixer_list_outputs_outputs = outputs;
   cras_alsa_mixer_list_outputs_outputs_length = ARRAY_SIZE(outputs);
-  aio = (struct alsa_io *)alsa_iodev_create(0, test_card_name, 0, test_dev_name,
-                                            NULL, ALSA_CARD_TYPE_INTERNAL, 1,
-                                            fake_mixer, NULL,
-                                            CRAS_STREAM_OUTPUT, 0, 0);
-  ASSERT_NE(aio, (void *)NULL);
+  aio = (struct alsa_io *)alsa_iodev_create_with_default_parameters(
+      0, NULL, 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));
+  EXPECT_EQ(4, cras_card_config_get_volume_curve_for_control_called);
   EXPECT_EQ(SND_PCM_STREAM_PLAYBACK, aio->alsa_stream);
   EXPECT_EQ(1, cras_alsa_mixer_list_outputs_called);
-  // This will be called three times because there will be
-  // two default node (because the output control's name is "")
-  // and one speaker node (because it is the first internal device).
-  EXPECT_EQ(3, cras_alsa_mixer_get_output_volume_curve_called);
 
   aio->handle = (snd_pcm_t *)0x24;
 
   ResetStubData();
   rc = alsa_iodev_set_active_node((struct cras_iodev *)aio,
-                                  aio->base.nodes->next);
+                                  aio->base.nodes->next, 1);
   EXPECT_EQ(0, rc);
   EXPECT_EQ(2, alsa_mixer_set_mute_called);
   EXPECT_EQ(outputs[1], alsa_mixer_set_mute_output);
@@ -679,11 +999,503 @@
   EXPECT_EQ(outputs[1], cras_alsa_mixer_set_output_active_state_outputs[1]);
   EXPECT_EQ(1, cras_alsa_mixer_set_output_active_state_values[1]);
   EXPECT_EQ(1, cras_iodev_update_dsp_called);
-  EXPECT_EQ(2, cras_alsa_jack_enable_ucm_called);
+  // No jacks defined, and UCM is not used.
+  EXPECT_EQ(0, cras_alsa_jack_enable_ucm_called);
+  EXPECT_EQ(0, ucm_set_enabled_called);
 
   alsa_iodev_destroy((struct cras_iodev *)aio);
-  free(fake_curve);
-  fake_curve = NULL;
+}
+
+TEST(AlsaOutputNode, TwoJacksHeadphoneLineout) {
+  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;
+  struct mixer_control *output;
+  struct ucm_section *section;
+
+  ResetStubData();
+  output = reinterpret_cast<struct mixer_control *>(3);
+  cras_alsa_mixer_get_control_name_values[output] = "Headphone";
+
+  // Create the iodev
+  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);
+  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");
+  ucm_section_set_mixer_name(section, "Headphone");
+  cras_alsa_jack_list_add_jack_for_section_result_jack =
+      reinterpret_cast<struct cras_alsa_jack *>(10);
+  cras_alsa_mixer_get_control_for_section_return_value = output;
+  ASSERT_EQ(0, alsa_iodev_ucm_add_nodes_and_jacks(iodev, section));
+  EXPECT_EQ(4, cras_card_config_get_volume_curve_for_control_called);
+  ucm_section_free_list(section);
+
+  // Second node 'Line Out'
+  section = ucm_section_create("Line Out", 0, 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);
+  cras_alsa_mixer_get_control_for_section_return_value = output;
+  ASSERT_EQ(0, alsa_iodev_ucm_add_nodes_and_jacks(iodev, section));
+  EXPECT_EQ(7, cras_card_config_get_volume_curve_for_control_called);
+  ucm_section_free_list(section);
+
+  // Both nodes are associated with the same mixer output. Different jack plug
+  // report should trigger different node attribute change.
+  cras_alsa_jack_get_mixer_output_ret = output;
+  jack_output_plug_event(reinterpret_cast<struct cras_alsa_jack *>(10), 0, aio);
+  EXPECT_STREQ(cras_iodev_set_node_attr_ionode->name, "Headphone");
+
+  jack_output_plug_event(reinterpret_cast<struct cras_alsa_jack *>(20), 0, aio);
+  EXPECT_STREQ(cras_iodev_set_node_attr_ionode->name, "Line Out");
+
+  alsa_iodev_destroy(iodev);
+}
+
+TEST(AlsaOutputNode, OutputsFromUCM) {
+  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 - Headset Jack";
+  struct mixer_control *outputs[2];
+  int rc;
+  struct ucm_section *section;
+
+  ResetStubData();
+  outputs[0] = reinterpret_cast<struct mixer_control *>(3);
+  outputs[1] = reinterpret_cast<struct mixer_control *>(4);
+  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]] = INTERNAL_SPEAKER;
+  cras_alsa_mixer_get_control_name_values[outputs[1]] = "Headphone";
+  ucm_get_dma_period_for_dev_ret = 1000;
+
+  // 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);
+  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);
+  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);
+  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);
+  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");
+  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;
+  cras_alsa_mixer_get_control_for_section_return_value = outputs[1];
+  ASSERT_EQ(0, alsa_iodev_ucm_add_nodes_and_jacks(iodev, section));
+  ucm_section_free_list(section);
+  /* New nodes creation calls get volume curve once, NULL jack doesn't make
+   * more calls. */
+  EXPECT_EQ(5, cras_card_config_get_volume_curve_for_control_called);
+
+  // Jack plug of an unkonwn device should do nothing.
+  cras_alsa_jack_get_mixer_output_ret = NULL;
+  cras_alsa_jack_get_name_ret_value = "Some other jack";
+  jack_output_plug_event(reinterpret_cast<struct cras_alsa_jack *>(4), 0, aio);
+  EXPECT_EQ(0, cras_iodev_set_node_attr_called);
+
+  // Complete initialization, and make first node active.
+  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);
+
+  aio->handle = (snd_pcm_t *)0x24;
+
+  ResetStubData();
+  rc = alsa_iodev_set_active_node(iodev, aio->base.nodes->next, 1);
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(2, alsa_mixer_set_mute_called);
+  EXPECT_EQ(outputs[1], alsa_mixer_set_mute_output);
+  EXPECT_EQ(1, alsa_mixer_set_dBFS_called);
+  EXPECT_EQ(outputs[1], alsa_mixer_set_dBFS_output);
+  ASSERT_EQ(2, cras_alsa_mixer_set_output_active_state_called);
+  EXPECT_EQ(outputs[0], cras_alsa_mixer_set_output_active_state_outputs[0]);
+  EXPECT_EQ(0, cras_alsa_mixer_set_output_active_state_values[0]);
+  EXPECT_EQ(outputs[1], cras_alsa_mixer_set_output_active_state_outputs[1]);
+  EXPECT_EQ(1, cras_alsa_mixer_set_output_active_state_values[1]);
+  EXPECT_EQ(1, cras_iodev_update_dsp_called);
+  EXPECT_EQ(1, cras_alsa_jack_enable_ucm_called);
+  EXPECT_EQ(1, ucm_set_enabled_called);
+
+  // Simulate jack plug event.
+  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_attr_called);
+
+  alsa_iodev_destroy(iodev);
+}
+
+TEST(AlsaOutputNode, OutputNoControlsUCM) {
+  struct alsa_io *aio;
+  struct cras_use_case_mgr * const fake_ucm = (struct cras_use_case_mgr*)3;
+  struct cras_iodev *iodev;
+  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_OUTPUT);
+  ASSERT_NE(iodev, (void *)NULL);
+  aio = reinterpret_cast<struct alsa_io *>(iodev);
+  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);
+  // 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));
+  EXPECT_EQ(2, cras_card_config_get_volume_curve_for_control_called);
+  EXPECT_EQ(1, cras_alsa_mixer_get_control_for_section_called);
+  EXPECT_EQ(1, cras_iodev_add_node_called);
+  ucm_section_free_list(section);
+
+  // Complete initialization, and make first node active.
+  alsa_iodev_ucm_complete_init(iodev);
+  EXPECT_EQ(SND_PCM_STREAM_PLAYBACK, aio->alsa_stream);
+  EXPECT_EQ(0, cras_alsa_mixer_get_control_name_called);
+  EXPECT_EQ(1, cras_iodev_update_dsp_called);
+  EXPECT_EQ(0, cras_alsa_jack_enable_ucm_called);
+  EXPECT_EQ(1, ucm_set_enabled_called);
+
+  alsa_iodev_destroy(iodev);
+}
+
+TEST(AlsaOutputNode, OutputFromJackUCM) {
+  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 - Headset Jack";
+  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_OUTPUT);
+  ASSERT_NE(iodev, (void *)NULL);
+  aio = reinterpret_cast<struct alsa_io *>(iodev);
+  EXPECT_EQ(1, cras_card_config_get_volume_curve_for_control_called);
+
+  // 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");
+  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);
+  EXPECT_EQ(1, cras_iodev_add_node_called);
+  EXPECT_EQ(1, cras_alsa_jack_list_add_jack_for_section_called);
+  ucm_section_free_list(section);
+
+  // Complete initialization, and make first node active.
+  alsa_iodev_ucm_complete_init(iodev);
+  EXPECT_EQ(SND_PCM_STREAM_PLAYBACK, aio->alsa_stream);
+  EXPECT_EQ(0, cras_alsa_mixer_get_control_name_called);
+  EXPECT_EQ(1, cras_iodev_update_dsp_called);
+  EXPECT_EQ(1, cras_alsa_jack_enable_ucm_called);
+  EXPECT_EQ(0, ucm_set_enabled_called);
+
+  alsa_iodev_destroy(iodev);
+}
+
+TEST(AlsaOutputNode, InputsFromUCM) {
+  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 mixer_control *inputs[2];
+  struct cras_iodev *iodev;
+  static const char *jack_name = "TestCard - Headset Jack";
+  int rc;
+  struct ucm_section *section;
+
+  ResetStubData();
+  inputs[0] = reinterpret_cast<struct mixer_control *>(3);
+  inputs[1] = reinterpret_cast<struct mixer_control *>(4);
+  cras_alsa_mixer_list_inputs_outputs = inputs;
+  cras_alsa_mixer_list_inputs_outputs_length = ARRAY_SIZE(inputs);
+  cras_alsa_mixer_get_control_name_values[inputs[0]] = "Internal Mic";
+  cras_alsa_mixer_get_control_name_values[inputs[1]] = "Mic";
+
+  // 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_INPUT);
+  ASSERT_NE(iodev, (void *)NULL);
+  aio = reinterpret_cast<struct alsa_io *>(iodev);
+
+  // 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);
+  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));
+  ucm_section_free_list(section);
+
+  // 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;
+  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");
+  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.
+  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);
+  EXPECT_EQ(0, cras_iodev_set_node_attr_called);
+
+  // Simulate jack plug event.
+  cras_alsa_jack_get_mixer_input_ret = inputs[1];
+  cras_alsa_jack_get_name_ret_value = jack_name;
+  jack_input_plug_event(reinterpret_cast<struct cras_alsa_jack *>(4), 0, aio);
+  EXPECT_EQ(1, cras_iodev_set_node_attr_called);
+
+  // Complete initialization, and make first node active.
+  alsa_iodev_ucm_complete_init(iodev);
+  EXPECT_EQ(SND_PCM_STREAM_CAPTURE, 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, 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);
+
+  aio->handle = (snd_pcm_t *)0x24;
+
+  ResetStubData();
+  rc = alsa_iodev_set_active_node(iodev, aio->base.nodes->next, 1);
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(1, alsa_mixer_set_capture_dBFS_called);
+  EXPECT_EQ(inputs[1], alsa_mixer_set_capture_dBFS_input);
+  EXPECT_EQ(0, alsa_mixer_set_capture_dBFS_value);
+  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);
+
+  alsa_iodev_destroy(iodev);
+}
+
+TEST(AlsaOutputNode, InputNoControlsUCM) {
+  struct alsa_io *aio;
+  struct cras_use_case_mgr * const fake_ucm = (struct cras_use_case_mgr*)3;
+  struct cras_iodev *iodev;
+  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.
+  section = ucm_section_create(INTERNAL_MICROPHONE, 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;
+  ASSERT_EQ(0, alsa_iodev_ucm_add_nodes_and_jacks(iodev, section));
+  EXPECT_EQ(1, cras_alsa_jack_list_add_jack_for_section_called);
+  EXPECT_EQ(1, cras_alsa_mixer_get_control_for_section_called);
+  EXPECT_EQ(0, cras_alsa_mixer_get_control_name_called);
+  EXPECT_EQ(1, cras_iodev_add_node_called);
+  ucm_section_free_list(section);
+
+  // Complete initialization, and make first node active.
+  alsa_iodev_ucm_complete_init(iodev);
+  EXPECT_EQ(SND_PCM_STREAM_CAPTURE, aio->alsa_stream);
+  EXPECT_EQ(0, cras_alsa_mixer_get_control_name_called);
+  EXPECT_EQ(1, cras_iodev_update_dsp_called);
+  EXPECT_EQ(0, cras_alsa_jack_enable_ucm_called);
+  EXPECT_EQ(1, ucm_set_enabled_called);
+
+  alsa_iodev_destroy(iodev);
+}
+
+TEST(AlsaOutputNode, InputFromJackUCM) {
+  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 - Headset Jack";
+  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("Mic", 0, 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);
+  EXPECT_EQ(1, cras_alsa_jack_list_add_jack_for_section_called);
+  ucm_section_free_list(section);
+
+  // Complete initialization, and make first node active.
+  alsa_iodev_ucm_complete_init(iodev);
+  EXPECT_EQ(SND_PCM_STREAM_CAPTURE, aio->alsa_stream);
+  EXPECT_EQ(0, cras_alsa_mixer_get_control_name_called);
+  EXPECT_EQ(1, cras_iodev_update_dsp_called);
+  EXPECT_EQ(1, cras_alsa_jack_enable_ucm_called);
+  EXPECT_EQ(0, ucm_set_enabled_called);
+
+  alsa_iodev_destroy(iodev);
+}
+
+TEST(AlsaOutputNode, AutoUnplugOutputNode) {
+  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 mixer_control *outputs[2];
+  const struct cras_alsa_jack *jack = (struct cras_alsa_jack*)4;
+
+  ResetStubData();
+  outputs[0] = reinterpret_cast<struct mixer_control *>(5);
+  outputs[1] = reinterpret_cast<struct mixer_control *>(6);
+
+  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]] = INTERNAL_SPEAKER;
+  cras_alsa_mixer_get_control_name_values[outputs[1]] = "Headphone";
+  auto_unplug_output_node_ret = 1;
+
+  aio = (struct alsa_io *)alsa_iodev_create_with_default_parameters(
+      0, NULL, ALSA_CARD_TYPE_INTERNAL, 1, fake_mixer, fake_config, fake_ucm,
+      CRAS_STREAM_OUTPUT);
+
+  ASSERT_EQ(0, alsa_iodev_legacy_complete_init((struct cras_iodev *)aio));
+  EXPECT_EQ(3, cras_card_config_get_volume_curve_for_control_called);
+  EXPECT_EQ(1, cras_alsa_mixer_list_outputs_called);
+  EXPECT_EQ(2, cras_alsa_mixer_get_control_name_called);
+
+  // Assert that the the internal speaker is plugged and other nodes aren't.
+  ASSERT_NE(aio->base.nodes, (void *)NULL);
+  EXPECT_EQ(aio->base.nodes->plugged, 1);
+  ASSERT_NE(aio->base.nodes->next, (void *)NULL);
+  EXPECT_EQ(aio->base.nodes->next->plugged, 0);
+
+  // Plug headphone jack
+  cras_alsa_jack_get_name_ret_value = "Headphone Jack";
+  is_utf8_string_ret_value = 1;
+  cras_alsa_jack_get_mixer_output_ret = outputs[1];
+  cras_alsa_jack_list_create_cb(jack, 1, cras_alsa_jack_list_create_cb_data);
+
+  // Assert internal speaker is auto unplugged
+  EXPECT_EQ(aio->base.nodes->plugged, 0);
+  EXPECT_EQ(aio->base.nodes->next->plugged, 1);
+
+  alsa_iodev_destroy((struct cras_iodev *)aio);
+}
+
+TEST(AlsaOutputNode, AutoUnplugInputNode) {
+  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 mixer_control *inputs[2];
+  const struct cras_alsa_jack *jack = (struct cras_alsa_jack*)4;
+
+  ResetStubData();
+  inputs[0] = reinterpret_cast<struct mixer_control *>(5);
+  inputs[1] = reinterpret_cast<struct mixer_control *>(6);
+
+  cras_alsa_mixer_list_inputs_outputs = inputs;
+  cras_alsa_mixer_list_inputs_outputs_length = ARRAY_SIZE(inputs);
+
+  cras_alsa_mixer_get_control_name_values[inputs[0]] = INTERNAL_MICROPHONE;
+  cras_alsa_mixer_get_control_name_values[inputs[1]] = "Mic";
+  auto_unplug_input_node_ret = 1;
+
+  aio = (struct alsa_io *)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((struct cras_iodev *)aio));
+  EXPECT_EQ(1, cras_alsa_mixer_list_inputs_called);
+  EXPECT_EQ(2, cras_alsa_mixer_get_control_name_called);
+
+  // Assert that the the internal speaker is plugged and other nodes aren't.
+  ASSERT_NE(aio->base.nodes, (void *)NULL);
+  EXPECT_EQ(aio->base.nodes->plugged, 1);
+  ASSERT_NE(aio->base.nodes->next, (void *)NULL);
+  EXPECT_EQ(aio->base.nodes->next->plugged, 0);
+
+  // Plug headphone jack
+  cras_alsa_jack_get_name_ret_value = "Mic Jack";
+  is_utf8_string_ret_value = 1;
+  cras_alsa_jack_get_mixer_input_ret = inputs[1];
+  cras_alsa_jack_list_create_cb(jack, 1, cras_alsa_jack_list_create_cb_data);
+
+  // Assert internal speaker is auto unplugged
+  EXPECT_EQ(aio->base.nodes->plugged, 0);
+  EXPECT_EQ(aio->base.nodes->next->plugged, 1);
+
+  alsa_iodev_destroy((struct cras_iodev *)aio);
 }
 
 TEST(AlsaInitNode, SetNodeInitialState) {
@@ -699,6 +1511,7 @@
   ASSERT_EQ(0, node.plugged);
   ASSERT_EQ(0, node.plugged_time.tv_sec);
   ASSERT_EQ(CRAS_NODE_TYPE_UNKNOWN, node.type);
+  ASSERT_EQ(NODE_POSITION_EXTERNAL, node.position);
 
   memset(&node, 0, sizeof(node));
   node.dev = &dev;
@@ -708,6 +1521,7 @@
   ASSERT_EQ(1, node.plugged);
   ASSERT_GT(node.plugged_time.tv_sec, 0);
   ASSERT_EQ(CRAS_NODE_TYPE_INTERNAL_SPEAKER, node.type);
+  ASSERT_EQ(NODE_POSITION_INTERNAL, node.position);
 
   memset(&node, 0, sizeof(node));
   node.dev = &dev;
@@ -716,7 +1530,8 @@
   set_node_initial_state(&node, ALSA_CARD_TYPE_INTERNAL);
   ASSERT_EQ(1, node.plugged);
   ASSERT_GT(node.plugged_time.tv_sec, 0);
-  ASSERT_EQ(CRAS_NODE_TYPE_INTERNAL_MIC, node.type);
+  ASSERT_EQ(CRAS_NODE_TYPE_MIC, node.type);
+  ASSERT_EQ(NODE_POSITION_INTERNAL, node.position);
 
   memset(&node, 0, sizeof(node));
   node.dev = &dev;
@@ -726,6 +1541,7 @@
   ASSERT_EQ(0, node.plugged);
   ASSERT_EQ(0, node.plugged_time.tv_sec);
   ASSERT_EQ(CRAS_NODE_TYPE_HDMI, node.type);
+  ASSERT_EQ(NODE_POSITION_EXTERNAL, node.position);
 
   memset(&node, 0, sizeof(node));
   node.dev = &dev;
@@ -734,6 +1550,7 @@
   set_node_initial_state(&node, ALSA_CARD_TYPE_INTERNAL);
   ASSERT_EQ(0, node.plugged);
   ASSERT_EQ(CRAS_NODE_TYPE_HDMI, node.type);
+  ASSERT_EQ(NODE_POSITION_EXTERNAL, node.position);
 
   memset(&node, 0, sizeof(node));
   node.dev = &dev;
@@ -742,6 +1559,7 @@
   set_node_initial_state(&node, ALSA_CARD_TYPE_INTERNAL);
   ASSERT_EQ(0, node.plugged);
   ASSERT_EQ(CRAS_NODE_TYPE_HDMI, node.type);
+  ASSERT_EQ(NODE_POSITION_EXTERNAL, node.position);
 
   memset(&node, 0, sizeof(node));
   node.dev = &dev;
@@ -750,6 +1568,7 @@
   set_node_initial_state(&node, ALSA_CARD_TYPE_INTERNAL);
   ASSERT_EQ(0, node.plugged);
   ASSERT_EQ(CRAS_NODE_TYPE_HDMI, node.type);
+  ASSERT_EQ(NODE_POSITION_EXTERNAL, node.position);
 
   memset(&node, 0, sizeof(node));
   node.dev = &dev;
@@ -758,6 +1577,7 @@
   set_node_initial_state(&node, ALSA_CARD_TYPE_INTERNAL);
   ASSERT_EQ(0, node.plugged);
   ASSERT_EQ(CRAS_NODE_TYPE_HEADPHONE, node.type);
+  ASSERT_EQ(NODE_POSITION_EXTERNAL, node.position);
 
   memset(&node, 0, sizeof(node));
   node.dev = &dev;
@@ -766,6 +1586,7 @@
   set_node_initial_state(&node, ALSA_CARD_TYPE_INTERNAL);
   ASSERT_EQ(0, node.plugged);
   ASSERT_EQ(CRAS_NODE_TYPE_HEADPHONE, node.type);
+  ASSERT_EQ(NODE_POSITION_EXTERNAL, node.position);
 
   memset(&node, 0, sizeof(node));
   node.dev = &dev;
@@ -774,6 +1595,25 @@
   set_node_initial_state(&node, ALSA_CARD_TYPE_INTERNAL);
   ASSERT_EQ(0, node.plugged);
   ASSERT_EQ(CRAS_NODE_TYPE_MIC, node.type);
+  ASSERT_EQ(NODE_POSITION_EXTERNAL, node.position);
+
+  memset(&node, 0, sizeof(node));
+  node.dev = &dev;
+  strcpy(node.name, "Front Mic");
+  dev.direction = CRAS_STREAM_INPUT;
+  set_node_initial_state(&node, ALSA_CARD_TYPE_INTERNAL);
+  ASSERT_EQ(1, node.plugged);
+  ASSERT_EQ(CRAS_NODE_TYPE_MIC, node.type);
+  ASSERT_EQ(NODE_POSITION_FRONT, node.position);
+
+  memset(&node, 0, sizeof(node));
+  node.dev = &dev;
+  strcpy(node.name, "Rear Mic");
+  dev.direction = CRAS_STREAM_INPUT;
+  set_node_initial_state(&node, ALSA_CARD_TYPE_INTERNAL);
+  ASSERT_EQ(1, node.plugged);
+  ASSERT_EQ(CRAS_NODE_TYPE_MIC, node.type);
+  ASSERT_EQ(NODE_POSITION_REAR, node.position);
 
   memset(&node, 0, sizeof(node));
   node.dev = &dev;
@@ -782,6 +1622,7 @@
   set_node_initial_state(&node, ALSA_CARD_TYPE_INTERNAL);
   ASSERT_EQ(0, node.plugged);
   ASSERT_EQ(CRAS_NODE_TYPE_MIC, node.type);
+  ASSERT_EQ(NODE_POSITION_EXTERNAL, node.position);
 
   memset(&node, 0, sizeof(node));
   node.dev = &dev;
@@ -790,6 +1631,7 @@
   set_node_initial_state(&node, ALSA_CARD_TYPE_USB);
   ASSERT_EQ(0, node.plugged);
   ASSERT_EQ(CRAS_NODE_TYPE_USB, node.type);
+  ASSERT_EQ(NODE_POSITION_EXTERNAL, node.position);
 
   memset(&node, 0, sizeof(node));
   node.dev = &dev;
@@ -798,6 +1640,7 @@
   set_node_initial_state(&node, ALSA_CARD_TYPE_INTERNAL);
   ASSERT_EQ(0, node.plugged);
   ASSERT_EQ(CRAS_NODE_TYPE_MIC, node.type);
+  ASSERT_EQ(NODE_POSITION_EXTERNAL, node.position);
 
   memset(&node, 0, sizeof(node));
   node.dev = &dev;
@@ -807,6 +1650,27 @@
   ASSERT_EQ(1, node.plugged);
   ASSERT_GT(node.plugged_time.tv_sec, 0);
   ASSERT_EQ(CRAS_NODE_TYPE_USB, node.type);
+  ASSERT_EQ(NODE_POSITION_EXTERNAL, node.position);
+
+  memset(&node, 0, sizeof(node));
+  node.dev = &dev;
+  strcpy(node.name, "Haptic");
+  dev.direction = CRAS_STREAM_OUTPUT;
+  set_node_initial_state(&node, ALSA_CARD_TYPE_INTERNAL);
+  ASSERT_EQ(1, node.plugged);
+  ASSERT_GT(node.plugged_time.tv_sec, 0);
+  ASSERT_EQ(CRAS_NODE_TYPE_HAPTIC, node.type);
+  ASSERT_EQ(NODE_POSITION_INTERNAL, node.position);
+
+  memset(&node, 0, sizeof(node));
+  node.dev = &dev;
+  strcpy(node.name, "Rumbler");
+  dev.direction = CRAS_STREAM_OUTPUT;
+  set_node_initial_state(&node, ALSA_CARD_TYPE_INTERNAL);
+  ASSERT_EQ(1, node.plugged);
+  ASSERT_GT(node.plugged_time.tv_sec, 0);
+  ASSERT_EQ(CRAS_NODE_TYPE_HAPTIC, node.type);
+  ASSERT_EQ(NODE_POSITION_INTERNAL, node.position);
 }
 
 TEST(AlsaInitNode, SetNodeInitialStateDropInvalidUTF8NodeName) {
@@ -843,15 +1707,14 @@
 TEST(AlsaIoInit, HDMIJackUpdateInvalidUTF8MonitorName) {
   struct alsa_io *aio;
   struct cras_alsa_mixer * const fake_mixer = (struct cras_alsa_mixer*)2;
-  snd_use_case_mgr_t * const fake_ucm = (snd_use_case_mgr_t*)3;
+  struct cras_use_case_mgr * const fake_ucm = (struct cras_use_case_mgr*)3;
   const struct cras_alsa_jack *jack = (struct cras_alsa_jack*)4;
 
   ResetStubData();
-  aio = (struct alsa_io *)alsa_iodev_create(0, test_card_name, 0, test_dev_name,
-                                            NULL, ALSA_CARD_TYPE_INTERNAL, 0,
-                                            fake_mixer, fake_ucm,
-                                            CRAS_STREAM_OUTPUT, 0, 0);
-  ASSERT_NE(aio, (void *)NULL);
+  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));
 
   // Prepare the stub data such that the jack will be identified as an
   // HDMI jack, and thus the callback creates an HDMI node.
@@ -864,7 +1727,7 @@
   // Add the jack node.
   cras_alsa_jack_list_create_cb(jack, 1, cras_alsa_jack_list_create_cb_data);
 
-  EXPECT_EQ(1, cras_alsa_jack_get_name_ret_called);
+  EXPECT_EQ(2, cras_alsa_jack_get_name_called);
   ASSERT_EQ(CRAS_NODE_TYPE_HDMI, aio->base.nodes->next->type);
   // The node name should be "HDMI".
   ASSERT_STREQ("HDMI", aio->base.nodes->next->name);
@@ -877,43 +1740,95 @@
   protected:
     virtual void SetUp() {
       ResetStubData();
-      aio_output_ = (struct alsa_io *)alsa_iodev_create(
-          0, test_card_name, 0, test_dev_name, NULL,
-          ALSA_CARD_TYPE_INTERNAL, 0,
-          fake_mixer, NULL,
-          CRAS_STREAM_OUTPUT, 0, 0);
+      output_control_ = reinterpret_cast<struct mixer_control *>(10);
+      cras_alsa_mixer_list_outputs_outputs = &output_control_;
+      cras_alsa_mixer_list_outputs_outputs_length = 1;
+      cras_alsa_mixer_get_control_name_values[output_control_] = "Speaker";
+      cras_alsa_mixer_list_outputs_outputs_length = 1;
+      aio_output_ = (struct alsa_io *)alsa_iodev_create_with_default_parameters(
+          0, NULL,
+          ALSA_CARD_TYPE_INTERNAL, 1,
+          fake_mixer, fake_config, NULL,
+          CRAS_STREAM_OUTPUT);
+      alsa_iodev_legacy_complete_init((struct cras_iodev *)aio_output_);
+      EXPECT_EQ(2, cras_card_config_get_volume_curve_for_control_called);
+
+      struct cras_ionode *node;
+      int count = 0;
+      DL_FOREACH(aio_output_->base.nodes, node) {
+        printf("node %d \n", count);
+      }
       aio_output_->base.direction = CRAS_STREAM_OUTPUT;
-      aio_input_ = (struct alsa_io *)alsa_iodev_create(
-          0, test_card_name, 0, test_dev_name, NULL,
-          ALSA_CARD_TYPE_INTERNAL, 0,
-          fake_mixer, NULL,
-          CRAS_STREAM_INPUT, 0, 0);
-      aio_input_->base.direction = CRAS_STREAM_INPUT;
       fmt_.frame_rate = 44100;
       fmt_.num_channels = 2;
       fmt_.format = SND_PCM_FORMAT_S16_LE;
-      aio_input_->base.format = &fmt_;
       aio_output_->base.format = &fmt_;
       cras_alsa_get_avail_frames_ret = -1;
-      fake_curve =
-        static_cast<struct cras_volume_curve *>(calloc(1, sizeof(*fake_curve)));
-      fake_curve->get_dBFS = fake_get_dBFS;
     }
 
     virtual void TearDown() {
       alsa_iodev_destroy((struct cras_iodev *)aio_output_);
-      alsa_iodev_destroy((struct cras_iodev *)aio_input_);
       cras_alsa_get_avail_frames_ret = 0;
-      free(fake_curve);
-      fake_curve = NULL;
     }
 
+  struct mixer_control *output_control_;
   struct alsa_io *aio_output_;
-  struct alsa_io *aio_input_;
   struct cras_audio_format fmt_;
 };
 
-TEST_F(AlsaVolumeMuteSuite, SetVolumeAndMute) {
+TEST_F(AlsaVolumeMuteSuite, GetDefaultVolumeCurve) {
+  int rc;
+  struct cras_audio_format *fmt;
+
+  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;
+
+  rc = aio_output_->base.open_dev(&aio_output_->base);
+  ASSERT_EQ(0, rc);
+  EXPECT_EQ(&default_curve, fake_get_dBFS_volume_curve_val);
+
+  aio_output_->base.set_volume(&aio_output_->base);
+  EXPECT_EQ(&default_curve, fake_get_dBFS_volume_curve_val);
+}
+
+TEST_F(AlsaVolumeMuteSuite, GetVolumeCurveFromNode)
+{
+  int rc;
+  struct cras_audio_format *fmt;
+  struct cras_alsa_jack *jack = (struct cras_alsa_jack*)4;
+  struct cras_ionode *node;
+  struct cras_volume_curve hp_curve = {
+    .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";
+  cras_card_config_get_volume_curve_vals["Headphone"] = &hp_curve;
+  cras_alsa_jack_list_create_cb(jack, 1, cras_alsa_jack_list_create_cb_data);
+  EXPECT_EQ(1, cras_alsa_jack_update_node_type_called);
+  EXPECT_EQ(3, cras_card_config_get_volume_curve_for_control_called);
+
+  // Switch to node 'Headphone'.
+  node = aio_output_->base.nodes->next;
+  aio_output_->base.active_node = node;
+
+  rc = aio_output_->base.open_dev(&aio_output_->base);
+  ASSERT_EQ(0, rc);
+  EXPECT_EQ(&hp_curve, fake_get_dBFS_volume_curve_val);
+
+  aio_output_->base.set_volume(&aio_output_->base);
+  EXPECT_EQ(&hp_curve, fake_get_dBFS_volume_curve_val);
+}
+
+TEST_F(AlsaVolumeMuteSuite, SetVolume) {
   int rc;
   struct cras_audio_format *fmt;
   const size_t fake_system_volume = 55;
@@ -930,33 +1845,23 @@
   ASSERT_EQ(0, rc);
   EXPECT_EQ(1, alsa_mixer_set_dBFS_called);
   EXPECT_EQ(fake_system_volume_dB, alsa_mixer_set_dBFS_value);
-  EXPECT_EQ(1, alsa_mixer_set_mute_called);
-  EXPECT_EQ(0, alsa_mixer_set_mute_value);
 
-  alsa_mixer_set_mute_called = 0;
-  alsa_mixer_set_mute_value = 0;
   alsa_mixer_set_dBFS_called = 0;
   alsa_mixer_set_dBFS_value = 0;
   sys_get_volume_return_value = 50;
   sys_get_volume_called = 0;
   aio_output_->base.set_volume(&aio_output_->base);
   EXPECT_EQ(1, sys_get_volume_called);
-  EXPECT_EQ(1, alsa_mixer_set_mute_called);
-  EXPECT_EQ(0, alsa_mixer_set_mute_value);
   EXPECT_EQ(1, alsa_mixer_set_dBFS_called);
   EXPECT_EQ(-5000, alsa_mixer_set_dBFS_value);
-  EXPECT_EQ(NULL, alsa_mixer_set_dBFS_output);
+  EXPECT_EQ(output_control_, alsa_mixer_set_dBFS_output);
 
-  alsa_mixer_set_mute_called = 0;
-  alsa_mixer_set_mute_value = 0;
   alsa_mixer_set_dBFS_called = 0;
   alsa_mixer_set_dBFS_value = 0;
   sys_get_volume_return_value = 0;
   sys_get_volume_called = 0;
   aio_output_->base.set_volume(&aio_output_->base);
   EXPECT_EQ(1, sys_get_volume_called);
-  EXPECT_EQ(1, alsa_mixer_set_mute_called);
-  EXPECT_EQ(1, alsa_mixer_set_mute_value);
   EXPECT_EQ(1, alsa_mixer_set_dBFS_called);
   EXPECT_EQ(-10000, alsa_mixer_set_dBFS_value);
 
@@ -973,10 +1878,243 @@
   free(fmt);
 }
 
+TEST_F(AlsaVolumeMuteSuite, SetMute) {
+  int muted;
+
+  aio_output_->handle = (snd_pcm_t *)0x24;
+
+  // Test mute.
+  ResetStubData();
+  muted = 1;
+
+  sys_get_mute_return_value = muted;
+
+  aio_output_->base.set_mute(&aio_output_->base);
+
+  EXPECT_EQ(1, sys_get_mute_called);
+  EXPECT_EQ(1, alsa_mixer_set_mute_called);
+  EXPECT_EQ(muted, alsa_mixer_set_mute_value);
+  EXPECT_EQ(output_control_, alsa_mixer_set_mute_output);
+
+  // Test unmute.
+  ResetStubData();
+  muted = 0;
+
+  sys_get_mute_return_value = muted;
+
+  aio_output_->base.set_mute(&aio_output_->base);
+
+  EXPECT_EQ(1, sys_get_mute_called);
+  EXPECT_EQ(1, alsa_mixer_set_mute_called);
+  EXPECT_EQ(muted, alsa_mixer_set_mute_value);
+  EXPECT_EQ(output_control_, alsa_mixer_set_mute_output);
+}
+
+//  Test free run.
+class AlsaFreeRunTestSuite: public testing::Test {
+  protected:
+    virtual void SetUp() {
+      ResetStubData();
+      memset(&aio, 0, sizeof(aio));
+      fmt_.format = SND_PCM_FORMAT_S16_LE;
+      fmt_.frame_rate = 48000;
+      fmt_.num_channels = 2;
+      aio.base.format = &fmt_;
+      aio.base.buffer_size = BUFFER_SIZE;
+      aio.base.min_cb_level = 240;
+    }
+
+    virtual void TearDown() {
+    }
+
+  struct alsa_io aio;
+  struct cras_audio_format fmt_;
+};
+
+TEST_F(AlsaFreeRunTestSuite, FillWholeBufferWithZeros) {
+  int rc;
+  int16_t *zeros;
+
+  cras_alsa_mmap_begin_buffer = (uint8_t *)calloc(
+      BUFFER_SIZE * 2 * 2,
+      sizeof(*cras_alsa_mmap_begin_buffer));
+  memset(cras_alsa_mmap_begin_buffer, 0xff,
+         sizeof(*cras_alsa_mmap_begin_buffer));
+
+  rc = fill_whole_buffer_with_zeros(&aio.base);
+
+  EXPECT_EQ(0, rc);
+  zeros = (int16_t *)calloc(BUFFER_SIZE * 2, sizeof(*zeros));
+  EXPECT_EQ(0, memcmp(zeros, cras_alsa_mmap_begin_buffer, BUFFER_SIZE * 2 * 2));
+
+  free(zeros);
+  free(cras_alsa_mmap_begin_buffer);
+}
+
+TEST_F(AlsaFreeRunTestSuite, EnterFreeRunAlreadyFreeRunning) {
+  int rc;
+
+  // Device is in free run state, no need to fill zeros or fill whole buffer.
+  aio.is_free_running = 1;
+
+  rc = no_stream(&aio.base, 1);
+
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(0, cras_alsa_mmap_get_whole_buffer_called);
+  EXPECT_EQ(0, cras_iodev_fill_odev_zeros_called);
+  EXPECT_EQ(0, cras_iodev_fill_odev_zeros_frames);
+}
+
+TEST_F(AlsaFreeRunTestSuite, EnterFreeRunNotDrainedYetNeedToFillZeros) {
+  int rc;
+
+  // Device is not in free run state. There are still valid samples to play.
+  // The number of valid samples is less than min_cb_level * 2.
+  // Need to fill zeros targeting min_cb_level * 2 = 480.
+  // The number of zeros to be filled is 480 - 200 = 280.
+  cras_iodev_frames_queued_ret = 200;
+  cras_iodev_buffer_avail_ret = BUFFER_SIZE - cras_iodev_frames_queued_ret;
+
+  rc = no_stream(&aio.base, 1);
+
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(0, cras_alsa_mmap_get_whole_buffer_called);
+  EXPECT_EQ(1, cras_iodev_fill_odev_zeros_called);
+  EXPECT_EQ(280, cras_iodev_fill_odev_zeros_frames);
+  EXPECT_EQ(280, aio.filled_zeros_for_draining);
+  EXPECT_EQ(0, aio.is_free_running);
+}
+
+TEST_F(AlsaFreeRunTestSuite, EnterFreeRunNotDrainedYetNoNeedToFillZeros) {
+  int rc;
+
+  // Device is not in free run state. There are still valid samples to play.
+  // The number of valid samples is more than min_cb_level * 2.
+  // No need to fill zeros.
+  cras_iodev_frames_queued_ret = 500;
+  cras_iodev_buffer_avail_ret = BUFFER_SIZE - cras_iodev_frames_queued_ret;
+
+  rc = no_stream(&aio.base, 1);
+
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(0, cras_alsa_mmap_get_whole_buffer_called);
+  EXPECT_EQ(0, cras_iodev_fill_odev_zeros_called);
+  EXPECT_EQ(0, aio.is_free_running);
+}
+
+TEST_F(AlsaFreeRunTestSuite, EnterFreeRunDrained) {
+  int rc;
+
+  // Device is not in free run state. There are still valid samples to play.
+  // The number of valid samples is less than filled zeros.
+  // Should enter free run state and fill whole buffer with zeros.
+  cras_iodev_frames_queued_ret = 40;
+  cras_iodev_buffer_avail_ret = BUFFER_SIZE - cras_iodev_frames_queued_ret;
+  aio.filled_zeros_for_draining = 100;
+
+  rc = no_stream(&aio.base, 1);
+
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(1, cras_alsa_mmap_get_whole_buffer_called);
+  EXPECT_EQ(0, cras_iodev_fill_odev_zeros_called);
+  EXPECT_EQ(1, aio.is_free_running);
+}
+
+TEST_F(AlsaFreeRunTestSuite, EnterFreeRunNoSamples) {
+  int rc;
+
+  // Device is not in free run state. There is no sample to play.
+  // Should enter free run state and fill whole buffer with zeros.
+  cras_iodev_frames_queued_ret = 0;
+  cras_iodev_buffer_avail_ret = BUFFER_SIZE - cras_iodev_frames_queued_ret;
+
+  rc = no_stream(&aio.base, 1);
+
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(1, cras_alsa_mmap_get_whole_buffer_called);
+  EXPECT_EQ(0, cras_iodev_fill_odev_zeros_called);
+  EXPECT_EQ(1, aio.is_free_running);
+}
+
+TEST_F(AlsaFreeRunTestSuite, OutputShouldWake) {
+
+  aio.is_free_running = 1;
+
+  EXPECT_EQ(0, output_should_wake(&aio.base));
+
+  aio.is_free_running = 0;
+  aio.base.state = CRAS_IODEV_STATE_NO_STREAM_RUN;
+  EXPECT_EQ(1, output_should_wake(&aio.base));
+
+  aio.base.state = CRAS_IODEV_STATE_NORMAL_RUN;
+  EXPECT_EQ(1, output_should_wake(&aio.base));
+
+  aio.base.state = CRAS_IODEV_STATE_OPEN;
+  EXPECT_EQ(0, output_should_wake(&aio.base));
+}
+
+TEST_F(AlsaFreeRunTestSuite, LeaveFreeRunNotInFreeRun) {
+  int rc;
+
+  rc = no_stream(&aio.base, 0);
+
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(0, cras_alsa_resume_appl_ptr_called);
+}
+
+TEST_F(AlsaFreeRunTestSuite, LeaveFreeRunInFreeRun) {
+  int rc;
+
+  aio.is_free_running = 1;
+  aio.filled_zeros_for_draining = 100;
+  aio.base.min_buffer_level = 512;
+
+  rc = no_stream(&aio.base, 0);
+
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(1, cras_alsa_resume_appl_ptr_called);
+  EXPECT_EQ(aio.base.min_buffer_level + aio.base.min_cb_level,
+            cras_alsa_resume_appl_ptr_ahead);
+  EXPECT_EQ(0, aio.is_free_running);
+  EXPECT_EQ(0, aio.filled_zeros_for_draining);
+}
+
+// Reuse AlsaFreeRunTestSuite for output underrun handling because they are
+// similar.
+TEST_F(AlsaFreeRunTestSuite, OutputUnderrun) {
+  int rc;
+  int16_t *zeros;
+
+  cras_alsa_mmap_begin_buffer = (uint8_t *)calloc(
+      BUFFER_SIZE * 2 * 2,
+      sizeof(*cras_alsa_mmap_begin_buffer));
+  memset(cras_alsa_mmap_begin_buffer, 0xff,
+         sizeof(*cras_alsa_mmap_begin_buffer));
+
+  // Ask alsa_io to handle output underrun.
+  rc = alsa_output_underrun(&aio.base);
+  EXPECT_EQ(0, rc);
+
+  // mmap buffer should be filled with zeros.
+  zeros = (int16_t *)calloc(BUFFER_SIZE * 2, sizeof(*zeros));
+  EXPECT_EQ(0, memcmp(zeros, cras_alsa_mmap_begin_buffer, BUFFER_SIZE * 2 * 2));
+
+  // appl_ptr should be moved to min_buffer_level + min_cb_level ahead of
+  // hw_ptr.
+  EXPECT_EQ(1, cras_alsa_resume_appl_ptr_called);
+  EXPECT_EQ(aio.base.min_buffer_level + aio.base.min_cb_level,
+            cras_alsa_resume_appl_ptr_ahead);
+
+  free(zeros);
+  free(cras_alsa_mmap_begin_buffer);
+}
+
+
 }  //  namespace
 
 int main(int argc, char **argv) {
   ::testing::InitGoogleTest(&argc, argv);
+  openlog(NULL, LOG_PERROR, LOG_USER);
   return RUN_ALL_TESTS();
 }
 
@@ -1003,6 +2141,22 @@
   return 0;
 }
 
+char *cras_iodev_list_get_hotword_models(cras_node_id_t node_id)
+{
+	return NULL;
+}
+
+int cras_iodev_list_set_hotword_model(cras_node_id_t node_id,
+				      const char *model_name)
+{
+	return 0;
+}
+
+struct audio_thread *cras_iodev_list_get_audio_thread()
+{
+  return NULL;
+}
+
 //  From alsa helper.
 int cras_alsa_set_channel_map(snd_pcm_t *handle,
 			      struct cras_audio_format *fmt)
@@ -1055,18 +2209,24 @@
   return 0;
 }
 int cras_alsa_set_hwparams(snd_pcm_t *handle, struct cras_audio_format *format,
-			   snd_pcm_uframes_t *buffer_size)
+			   snd_pcm_uframes_t *buffer_size, int period_wakeup,
+			   unsigned int dma_period_time)
 {
   return 0;
 }
-int cras_alsa_set_swparams(snd_pcm_t *handle)
+int cras_alsa_set_swparams(snd_pcm_t *handle, int *enable_htimestamp)
 {
   return 0;
 }
 int cras_alsa_get_avail_frames(snd_pcm_t *handle, snd_pcm_uframes_t buf_size,
-			       snd_pcm_uframes_t *used)
+                               snd_pcm_uframes_t severe_underrun_frames,
+                               const char* dev_name,
+                               snd_pcm_uframes_t *used,
+                               struct timespec *tstamp,
+                               unsigned int *num_underruns)
 {
   *used = cras_alsa_get_avail_frames_avail;
+  clock_gettime(CLOCK_MONOTONIC_RAW, tstamp);
   return cras_alsa_get_avail_frames_ret;
 }
 int cras_alsa_get_delay_frames(snd_pcm_t *handle, snd_pcm_uframes_t buf_size,
@@ -1090,6 +2250,7 @@
 }
 int cras_alsa_attempt_resume(snd_pcm_t *handle)
 {
+  cras_alsa_attempt_resume_called++;
   return 0;
 }
 
@@ -1101,7 +2262,7 @@
 
 snd_pcm_state_t snd_pcm_state(snd_pcm_t *handle)
 {
-  return SND_PCM_STATE_RUNNING;
+  return snd_pcm_state_ret;
 }
 
 const char *snd_strerror(int errnum)
@@ -1109,10 +2270,23 @@
   return "Alsa Error in UT";
 }
 
+struct mixer_control *cras_alsa_mixer_get_control_for_section(
+		struct cras_alsa_mixer *cras_mixer,
+		const struct ucm_section *section)
+{
+  cras_alsa_mixer_get_control_for_section_called++;
+  return cras_alsa_mixer_get_control_for_section_return_value;
+}
+
 const char *cras_alsa_mixer_get_control_name(
 		const struct mixer_control *control)
 {
-  return "";
+  ControlNameMap::iterator it;
+  cras_alsa_mixer_get_control_name_called++;
+  it = cras_alsa_mixer_get_control_name_values.find(control);
+  if (it == cras_alsa_mixer_get_control_name_values.end())
+    return "";
+  return it->second.c_str();
 }
 
 //  From system_state.
@@ -1147,6 +2321,8 @@
 
 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++;
 }
 
@@ -1218,13 +2394,6 @@
   }
 }
 
-struct cras_volume_curve *cras_alsa_mixer_create_volume_curve_for_name(
-		const struct cras_alsa_mixer *cmix,
-		const char *name)
-{
-	return NULL;
-}
-
 int cras_alsa_mixer_set_output_active_state(
 		struct mixer_control *output,
 		int active)
@@ -1235,13 +2404,6 @@
   return 0;
 }
 
-const struct cras_volume_curve *cras_alsa_mixer_default_volume_curve(
-		const struct cras_alsa_mixer *cras_mixer)
-{
-  cras_alsa_mixer_default_volume_curve_called++;
-  return fake_curve;
-}
-
 void cras_volume_curve_destroy(struct cras_volume_curve *curve)
 {
 }
@@ -1251,7 +2413,7 @@
 {
 	cras_alsa_mixer_get_minimum_capture_gain_called++;
 	cras_alsa_mixer_get_minimum_capture_gain_mixer_input = mixer_input;
-	return 0;
+	return cras_alsa_mixer_get_minimum_capture_gain_ret_value;
 }
 
 long cras_alsa_mixer_get_maximum_capture_gain(struct cras_alsa_mixer *cmix,
@@ -1259,14 +2421,17 @@
 {
 	cras_alsa_mixer_get_maximum_capture_gain_called++;
 	cras_alsa_mixer_get_maximum_capture_gain_mixer_input = mixer_input;
-	return 0;
+	return cras_alsa_mixer_get_maximum_capture_gain_ret_value;
 }
 
-struct cras_volume_curve *cras_alsa_mixer_get_output_volume_curve(
-		const struct mixer_control *control)
+int cras_alsa_mixer_has_main_volume(const struct cras_alsa_mixer *cras_mixer)
 {
-  cras_alsa_mixer_get_output_volume_curve_called++;
-  return cras_alsa_mixer_get_output_volume_curve_value;
+  return 1;
+}
+
+int cras_alsa_mixer_has_volume(const struct mixer_control *mixer_control)
+{
+  return 1;
 }
 
 // From cras_alsa_jack
@@ -1276,7 +2441,8 @@
 		unsigned int device_index,
 		int check_gpio_jack,
 		struct cras_alsa_mixer *mixer,
-		snd_use_case_mgr_t *ucm,
+                struct cras_use_case_mgr *ucm,
+		snd_hctl_t *hctl,
 		enum CRAS_STREAM_DIRECTION direction,
 		jack_state_change_callback *cb,
 		void *cb_data)
@@ -1287,11 +2453,34 @@
   return (struct cras_alsa_jack_list *)0xfee;
 }
 
+int cras_alsa_jack_list_find_jacks_by_name_matching(
+	struct cras_alsa_jack_list *jack_list)
+{
+  cras_alsa_jack_list_find_jacks_by_name_matching_called++;
+  return 0;
+}
+
+int cras_alsa_jack_list_add_jack_for_section(
+	struct cras_alsa_jack_list *jack_list,
+	struct ucm_section *ucm_section,
+	struct cras_alsa_jack **result_jack)
+{
+  cras_alsa_jack_list_add_jack_for_section_called++;
+  if (result_jack)
+    *result_jack = cras_alsa_jack_list_add_jack_for_section_result_jack;
+  return 0;
+}
+
 void cras_alsa_jack_list_destroy(struct cras_alsa_jack_list *jack_list)
 {
   cras_alsa_jack_list_destroy_called++;
 }
 
+int cras_alsa_jack_list_has_hctl_jacks(struct cras_alsa_jack_list *jack_list)
+{
+  return cras_alsa_jack_list_has_hctl_jacks_return_val;
+}
+
 void cras_alsa_jack_list_report(const struct cras_alsa_jack_list *jack_list)
 {
 }
@@ -1302,7 +2491,7 @@
 
 const char *cras_alsa_jack_get_name(const struct cras_alsa_jack *jack)
 {
-  cras_alsa_jack_get_name_ret_called++;
+  cras_alsa_jack_get_name_called++;
   return cras_alsa_jack_get_name_ret_value;
 }
 
@@ -1312,7 +2501,8 @@
   return jack ? cras_alsa_jack_get_dsp_name_value : NULL;
 }
 
-const char *ucm_get_dsp_name_default(snd_use_case_mgr_t *mgr, int direction)
+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)
@@ -1324,49 +2514,122 @@
 struct mixer_control *cras_alsa_jack_get_mixer_output(
     const struct cras_alsa_jack *jack)
 {
-  return NULL;
+  return cras_alsa_jack_get_mixer_output_ret;
 }
 
 struct mixer_control *cras_alsa_jack_get_mixer_input(
 		const struct cras_alsa_jack *jack)
 {
-  return NULL;
+  return cras_alsa_jack_get_mixer_input_ret;
 }
 
-int ucm_set_enabled(snd_use_case_mgr_t *mgr, const char *dev, int enabled) {
+int ucm_set_enabled(
+    struct cras_use_case_mgr *mgr, const char *dev, int enabled) {
+  ucm_set_enabled_called++;
   return 0;
 }
 
-char *ucm_get_flag(snd_use_case_mgr_t *mgr, const char *flag_name) {
+char *ucm_get_flag(struct cras_use_case_mgr *mgr, const char *flag_name) {
+  char *ret = (char *)malloc(8);
+  if ((!strcmp(flag_name, "AutoUnplugInputNode") &&
+       auto_unplug_input_node_ret) ||
+      (!strcmp(flag_name, "AutoUnplugOutputNode") &&
+       auto_unplug_output_node_ret)) {
+    snprintf(ret, 8, "%s", "1");
+    return ret;
+  }
+
   return NULL;
 }
 
-char *ucm_get_mic_positions(snd_use_case_mgr_t *mgr) {
+char *ucm_get_mic_positions(struct cras_use_case_mgr *mgr) {
   return NULL;
 }
 
-int ucm_swap_mode_exists(snd_use_case_mgr_t *mgr)
+int ucm_swap_mode_exists(struct cras_use_case_mgr *mgr)
 {
   return ucm_swap_mode_exists_ret_value;
 }
 
-int ucm_enable_swap_mode(snd_use_case_mgr_t *mgr, const char *node_name,
+int ucm_enable_swap_mode(struct cras_use_case_mgr *mgr, const char *node_name,
                          int enable)
 {
   ucm_enable_swap_mode_called++;
   return ucm_enable_swap_mode_ret_value;
 }
 
-unsigned int ucm_get_min_buffer_level(snd_use_case_mgr_t *mgr)
+unsigned int ucm_get_min_buffer_level(struct cras_use_case_mgr *mgr)
 {
   return 0;
 }
 
-unsigned int ucm_get_disable_software_volume(snd_use_case_mgr_t *mgr)
+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_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;
+}
+
+int ucm_set_hotword_model(struct cras_use_case_mgr *mgr, const char *model)
+{
+  return 0;
+}
+
+unsigned int ucm_get_dma_period_for_dev(struct cras_use_case_mgr *mgr,
+                                        const char *dev)
+{
+  ucm_get_dma_period_for_dev_called++;
+  return ucm_get_dma_period_for_dev_ret;
+}
+
+int ucm_get_sample_rate_for_dev(struct cras_use_case_mgr *mgr, const char *dev,
+				enum CRAS_STREAM_DIRECTION direction)
+{
+  return -EINVAL;
+}
+
+int ucm_get_capture_chmap_for_dev(struct cras_use_case_mgr *mgr,
+          const char *dev,
+          int8_t *channel_layout)
+{
+  return -EINVAL;
+}
+
+struct cras_volume_curve *cras_volume_curve_create_default()
+{
+  return &default_curve;
+}
+
+struct cras_volume_curve *cras_card_config_get_volume_curve_for_control(
+    const struct cras_card_config *card_config,
+    const char *control_name)
+{
+  VolCurveMap::iterator it;
+  cras_card_config_get_volume_curve_for_control_called++;
+  if (!control_name)
+    return NULL;
+  it = cras_card_config_get_volume_curve_vals.find(control_name);
+  if (it == cras_card_config_get_volume_curve_vals.end())
+    return NULL;
+  return it->second;
+}
+
 void cras_iodev_free_format(struct cras_iodev *iodev)
 {
 }
@@ -1375,6 +2638,8 @@
 			  const struct cras_audio_format *fmt)
 {
   fake_format = (struct cras_audio_format *)calloc(1, sizeof(*fake_format));
+  // Copy the content of format from fmt into format of iodev.
+  memcpy(fake_format, fmt, sizeof(*fake_format));
   iodev->format = fake_format;
   return 0;
 }
@@ -1386,6 +2651,8 @@
 void audio_thread_destroy(audio_thread* thread) {
 }
 
+
+
 void cras_iodev_update_dsp(struct cras_iodev *iodev)
 {
   cras_iodev_update_dsp_called++;
@@ -1396,13 +2663,17 @@
 			     enum ionode_attr attr, int value)
 {
   cras_iodev_set_node_attr_called++;
+  cras_iodev_set_node_attr_ionode = ionode;
   cras_iodev_set_node_attr_attr = attr;
   cras_iodev_set_node_attr_value = value;
+  if (ionode && (attr == IONODE_ATTR_PLUGGED))
+    ionode->plugged = value;
   return 0;
 }
 
 void cras_iodev_add_node(struct cras_iodev *iodev, struct cras_ionode *node)
 {
+  cras_iodev_add_node_called++;
   DL_APPEND(iodev->nodes, node);
 }
 
@@ -1436,6 +2707,22 @@
   cras_alsa_jack_update_node_type_called++;
 }
 
+const char *cras_alsa_jack_get_ucm_device(const struct cras_alsa_jack *jack)
+{
+  return NULL;
+}
+
+int ucm_get_default_node_gain(struct cras_use_case_mgr *mgr, const char *dev,
+                              long *gain)
+{
+  if (ucm_get_default_node_gain_values.find(dev) ==
+      ucm_get_default_node_gain_values.end())
+    return 1;
+
+  *gain = ucm_get_default_node_gain_values[dev];
+  return 0;
+}
+
 void cras_iodev_init_audio_area(struct cras_iodev *iodev,
                                 int num_channels) {
 }
@@ -1443,6 +2730,29 @@
 void cras_iodev_free_audio_area(struct cras_iodev *iodev) {
 }
 
+int cras_iodev_reset_rate_estimator(const struct cras_iodev *iodev)
+{
+  return 0;
+}
+
+int cras_iodev_frames_queued(struct cras_iodev *iodev, struct timespec *tstamp)
+{
+  clock_gettime(CLOCK_MONOTONIC_RAW, tstamp);
+  return cras_iodev_frames_queued_ret;
+}
+
+int cras_iodev_buffer_avail(struct cras_iodev *iodev, unsigned hw_level)
+{
+  return cras_iodev_buffer_avail_ret;
+}
+
+int cras_iodev_fill_odev_zeros(struct cras_iodev *odev, unsigned int frames)
+{
+  cras_iodev_fill_odev_zeros_called++;
+  cras_iodev_fill_odev_zeros_frames = 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)
@@ -1457,9 +2767,51 @@
 {
 }
 
+int audio_thread_rm_callback_sync(struct audio_thread *thread, int fd) {
+  return 0;
+}
+
 int is_utf8_string(const char* string)
 {
   return is_utf8_string_ret_value;
 }
 
+int cras_alsa_mmap_get_whole_buffer(snd_pcm_t *handle, uint8_t **dst,
+				    unsigned int *underruns)
+{
+  snd_pcm_uframes_t offset, frames;
+
+  cras_alsa_mmap_get_whole_buffer_called++;
+  return cras_alsa_mmap_begin(handle, 0, dst, &offset, &frames, underruns);
+}
+
+int cras_alsa_resume_appl_ptr(snd_pcm_t *handle, snd_pcm_uframes_t ahead)
+{
+  cras_alsa_resume_appl_ptr_called++;
+  cras_alsa_resume_appl_ptr_ahead = ahead;
+  return 0;
+}
+
+int cras_iodev_default_no_stream_playback(struct cras_iodev *odev, int enable)
+{
+  return 0;
+}
+
+enum CRAS_IODEV_STATE cras_iodev_state(const struct cras_iodev *iodev)
+{
+  return iodev->state;
+}
+
+int cras_iodev_dsp_set_swap_mode_for_node(struct cras_iodev *iodev,
+                                          struct cras_ionode *node,
+                                          int enable)
+{
+  cras_iodev_dsp_set_swap_mode_for_node_called++;
+  return 0;
+}
+
+struct cras_ramp* cras_ramp_create() {
+  return (struct cras_ramp*)0x1;
+}
+
 }
diff --git a/cras/src/tests/alsa_jack_unittest.cc b/cras/src/tests/alsa_jack_unittest.cc
index 2bfe6f6..e188f87 100644
--- a/cras/src/tests/alsa_jack_unittest.cc
+++ b/cras/src/tests/alsa_jack_unittest.cc
@@ -4,14 +4,19 @@
 
 #include <deque>
 #include <linux/input.h>
+#include <map>
 #include <poll.h>
 #include <stdio.h>
 #include <sys/param.h>
 #include <gtest/gtest.h>
 #include <string>
+#include <syslog.h>
+#include <vector>
 
 extern "C" {
 #include "cras_alsa_jack.h"
+#include "cras_alsa_ucm_section.h"
+#include "cras_gpio_jack.h"
 #include "cras_tm.h"
 #include "cras_types.h"
 #include "cras_util.h"
@@ -27,13 +32,7 @@
 #define LONG(x)			((x) / BITS_PER_LONG)
 #define IS_BIT_SET(bit, array)	!!((array[LONG(bit)]) & (1UL << OFF(bit)))
 
-static size_t snd_hctl_open_called;
-static int snd_hctl_open_return_value;
-static snd_hctl_t *snd_hctl_open_pointer_val;
-static size_t snd_hctl_load_called;
-static int snd_hctl_load_return_value;
 static int fake_jack_cb_plugged;
-static int snd_hctl_close_called;
 static void *fake_jack_cb_data;
 static size_t fake_jack_cb_called;
 unsigned int snd_hctl_elem_get_device_return_val;
@@ -47,14 +46,13 @@
 static size_t snd_hctl_elem_set_callback_called;
 static snd_hctl_elem_t *snd_hctl_elem_set_callback_obj;
 static snd_hctl_elem_callback_t snd_hctl_elem_set_callback_value;
-static struct pollfd *snd_hctl_poll_descriptors_fds;
-static size_t snd_hctl_poll_descriptors_num_fds;
-static size_t snd_hctl_poll_descriptors_called;
+static size_t snd_hctl_find_elem_called;
+static std::vector<snd_hctl_elem_t *> snd_hctl_find_elem_return_vals;
+static std::map<std::string, size_t> snd_ctl_elem_id_set_name_map;
 static size_t cras_system_add_select_fd_called;
 static std::vector<int> cras_system_add_select_fd_values;
 static size_t cras_system_rm_select_fd_called;
 static std::vector<int> cras_system_rm_select_fd_values;
-static size_t snd_hctl_handle_events_called;
 static size_t snd_hctl_elem_set_callback_private_called;
 static void *snd_hctl_elem_set_callback_private_value;
 static size_t snd_hctl_elem_get_hctl_called;
@@ -62,20 +60,22 @@
 static size_t snd_ctl_elem_value_get_boolean_called;
 static int snd_ctl_elem_value_get_boolean_return_value;
 static void *fake_jack_cb_arg;
-static size_t snd_hctl_nonblock_called;
 static struct cras_alsa_mixer *fake_mixer;
 static size_t cras_alsa_mixer_get_output_matching_name_called;
 static size_t cras_alsa_mixer_get_input_matching_name_called;
-static struct mixer_output_control *
+static size_t cras_alsa_mixer_get_control_for_section_called;
+static struct mixer_control *
     cras_alsa_mixer_get_output_matching_name_return_value;
-struct mixer_volume_control *
+static struct mixer_control *
     cras_alsa_mixer_get_input_matching_name_return_value;
-static size_t gpio_get_switch_names_called;
-static size_t gpio_get_switch_names_count;
+static struct mixer_control *
+    cras_alsa_mixer_get_control_for_section_return_value;
+static size_t gpio_switch_list_for_each_called;
+static std::vector<std::string> gpio_switch_list_for_each_dev_paths;
+static std::vector<std::string> gpio_switch_list_for_each_dev_names;
 static size_t gpio_switch_open_called;
 static size_t gpio_switch_eviocgsw_called;
 static size_t gpio_switch_eviocgbit_called;
-static size_t sys_input_get_device_name_called;
 static unsigned ucm_get_dev_for_jack_called;
 static unsigned ucm_get_cap_control_called;
 static char *ucm_get_cap_control_value;
@@ -87,20 +87,17 @@
 static size_t ucm_get_dsp_name_called;
 static unsigned ucm_get_override_type_name_called;
 static char *ucm_get_device_name_for_dev_value;
+static snd_hctl_t *fake_hctl = (snd_hctl_t *)2;
 
 static void ResetStubData() {
-  gpio_get_switch_names_called = 0;
-  gpio_get_switch_names_count = 0;
+  gpio_switch_list_for_each_called = 0;
+  gpio_switch_list_for_each_dev_paths.clear();
+  gpio_switch_list_for_each_dev_paths.push_back("/dev/input/event3");
+  gpio_switch_list_for_each_dev_paths.push_back("/dev/input/event2");
+  gpio_switch_list_for_each_dev_names.clear();
   gpio_switch_open_called = 0;
   gpio_switch_eviocgsw_called = 0;
   gpio_switch_eviocgbit_called = 0;
-  sys_input_get_device_name_called = 0;
-  snd_hctl_open_called = 0;
-  snd_hctl_open_return_value = 0;
-  snd_hctl_open_pointer_val = reinterpret_cast<snd_hctl_t *>(0x4323);
-  snd_hctl_load_called = 0;
-  snd_hctl_load_return_value = 0;
-  snd_hctl_close_called = 0;
   snd_hctl_elem_get_device_return_val = 0;
   snd_hctl_elem_get_device_called = 0;
   snd_hctl_first_elem_called = 0;
@@ -110,26 +107,30 @@
   snd_hctl_elem_next_ret_vals_poped.clear();
   snd_hctl_elem_get_name_called = 0;
   snd_hctl_elem_set_callback_called = 0;
-  snd_hctl_poll_descriptors_num_fds = 0;
-  snd_hctl_poll_descriptors_called = 0;
+  snd_hctl_elem_set_callback_obj = NULL;
+  snd_hctl_elem_set_callback_value = NULL;
+  snd_hctl_find_elem_called = 0;
+  snd_hctl_find_elem_return_vals.clear();
+  snd_ctl_elem_id_set_name_map.clear();
   cras_system_add_select_fd_called = 0;
   cras_system_add_select_fd_values.clear();
   cras_system_rm_select_fd_called = 0;
   cras_system_rm_select_fd_values.clear();
-  snd_hctl_handle_events_called = 0;
   snd_hctl_elem_set_callback_private_called = 0;
   snd_hctl_elem_get_hctl_called = 0;
   snd_ctl_elem_value_get_boolean_called = 0;
   fake_jack_cb_called = 0;
   fake_jack_cb_plugged = 0;
   fake_jack_cb_arg = reinterpret_cast<void *>(0x987);
-  snd_hctl_nonblock_called = 0;
   fake_mixer = reinterpret_cast<struct cras_alsa_mixer *>(0x789);
   cras_alsa_mixer_get_output_matching_name_called = 0;
   cras_alsa_mixer_get_input_matching_name_called = 0;
+  cras_alsa_mixer_get_control_for_section_called = 0;
   cras_alsa_mixer_get_output_matching_name_return_value =
-      reinterpret_cast<struct mixer_output_control *>(0x456);
+      reinterpret_cast<struct mixer_control *>(0x456);
   cras_alsa_mixer_get_input_matching_name_return_value = NULL;
+  cras_alsa_mixer_get_control_for_section_return_value =
+      reinterpret_cast<struct mixer_control *>(0x456);
   ucm_get_dev_for_jack_called = 0;
   ucm_get_cap_control_called = 0;
   ucm_get_cap_control_value = NULL;
@@ -157,54 +158,23 @@
             ucm_set_enabled_value);
 }
 
-TEST(AlsaJacks, CreateFailInvalidParams) {
-  EXPECT_EQ(NULL, cras_alsa_jack_list_create(32, "c1", 0, 1,
-                                             fake_mixer,
-                                             NULL,
-                                             CRAS_STREAM_OUTPUT,
-                                             fake_jack_cb,
-                                             fake_jack_cb_arg));
-  EXPECT_EQ(0, snd_hctl_open_called);
-  EXPECT_EQ(NULL, cras_alsa_jack_list_create(0, "c1", 32, 1,
-                                             fake_mixer,
-                                             NULL,
-                                             CRAS_STREAM_OUTPUT,
-                                             fake_jack_cb,
-                                             fake_jack_cb_arg));
-  EXPECT_EQ(0, snd_hctl_open_called);
-}
-
-TEST(AlsaJacks, CreateFailOpen) {
+TEST(AlsaJacks, CreateNullHctl) {
+  struct cras_alsa_jack_list *jack_list;
   ResetStubData();
-  snd_hctl_open_return_value = -1;
-  snd_hctl_open_pointer_val = NULL;
-  EXPECT_EQ(NULL, cras_alsa_jack_list_create(0, "c1", 0, 1,
-                                             fake_mixer,
-					     NULL,
-                                             CRAS_STREAM_OUTPUT,
-                                             fake_jack_cb,
-                                             fake_jack_cb_arg));
-  EXPECT_EQ(1, snd_hctl_open_called);
-}
-
-TEST(AlsaJacks, CreateFailLoad) {
-  ResetStubData();
-  snd_hctl_load_return_value = -1;
-  gpio_get_switch_names_count = ~0;
-  EXPECT_EQ(NULL, cras_alsa_jack_list_create(0, "c1", 0, 1,
-                                             fake_mixer,
-					     NULL,
-                                             CRAS_STREAM_OUTPUT,
-                                             fake_jack_cb,
-                                             fake_jack_cb_arg));
-  EXPECT_EQ(0, gpio_get_switch_names_called);
+  jack_list = cras_alsa_jack_list_create(0, "c1", 0, 1,
+                                         fake_mixer,
+                                         NULL, NULL,
+                                         CRAS_STREAM_OUTPUT,
+                                         fake_jack_cb,
+                                         fake_jack_cb_arg);
+  ASSERT_NE(static_cast<struct cras_alsa_jack_list *>(NULL), jack_list);
+  EXPECT_EQ(0, cras_alsa_jack_list_find_jacks_by_name_matching(jack_list));
+  EXPECT_EQ(1, gpio_switch_list_for_each_called);
   EXPECT_EQ(0, gpio_switch_open_called);
   EXPECT_EQ(0, gpio_switch_eviocgsw_called);
   EXPECT_EQ(0, gpio_switch_eviocgbit_called);
-  EXPECT_EQ(0, sys_input_get_device_name_called);
-  EXPECT_EQ(1, snd_hctl_open_called);
-  EXPECT_EQ(1, snd_hctl_load_called);
-  EXPECT_EQ(1, snd_hctl_close_called);
+
+  cras_alsa_jack_list_destroy(jack_list);
 }
 
 TEST(AlsaJacks, CreateNoElements) {
@@ -212,33 +182,29 @@
 
   ResetStubData();
   snd_hctl_first_elem_return_val = NULL;
-  gpio_get_switch_names_count = 0;
   jack_list = cras_alsa_jack_list_create(0, "c1", 0, 1,
                                          fake_mixer,
-					 NULL,
+                                         NULL, fake_hctl,
                                          CRAS_STREAM_OUTPUT,
                                          fake_jack_cb,
                                          fake_jack_cb_arg);
   ASSERT_NE(static_cast<struct cras_alsa_jack_list *>(NULL), jack_list);
-  EXPECT_EQ(1, gpio_get_switch_names_called);
+  EXPECT_EQ(0, cras_alsa_jack_list_find_jacks_by_name_matching(jack_list));
+  EXPECT_EQ(1, gpio_switch_list_for_each_called);
   EXPECT_EQ(0, gpio_switch_open_called);
   EXPECT_EQ(0, gpio_switch_eviocgsw_called);
   EXPECT_EQ(0, gpio_switch_eviocgbit_called);
-  EXPECT_EQ(0, sys_input_get_device_name_called);
-  EXPECT_EQ(1, snd_hctl_open_called);
-  EXPECT_EQ(1, snd_hctl_load_called);
   EXPECT_EQ(1, snd_hctl_first_elem_called);
   EXPECT_EQ(0, snd_hctl_elem_next_called);
 
   cras_alsa_jack_list_destroy(jack_list);
-  EXPECT_EQ(1, snd_hctl_close_called);
 }
 
 static struct cras_alsa_jack_list *run_test_with_elem_list(
     CRAS_STREAM_DIRECTION direction,
     std::string *elems,
     unsigned int device_index,
-    snd_use_case_mgr_t *ucm,
+    struct cras_use_case_mgr *ucm,
     size_t nelems,
     size_t nhdmi_jacks,
     size_t njacks) {
@@ -255,16 +221,15 @@
                                          device_index,
                                          1,
                                          fake_mixer,
-					 ucm,
+                                         ucm, fake_hctl,
                                          direction,
                                          fake_jack_cb,
                                          fake_jack_cb_arg);
   if (jack_list == NULL)
     return jack_list;
+  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, snd_hctl_open_called);
-  EXPECT_EQ(1, snd_hctl_load_called);
   EXPECT_EQ(1 + nhdmi_jacks, snd_hctl_first_elem_called);
   EXPECT_EQ(njacks, snd_hctl_elem_set_callback_called);
 
@@ -282,6 +247,53 @@
   return jack_list;
 }
 
+static struct cras_alsa_jack_list *run_test_with_section(
+    CRAS_STREAM_DIRECTION direction,
+    std::string *elems,
+    size_t nelems,
+    unsigned int device_index,
+    struct cras_use_case_mgr *ucm,
+    struct ucm_section *ucm_section,
+    int add_jack_rc,
+    size_t njacks) {
+  struct cras_alsa_jack_list *jack_list;
+  struct cras_alsa_jack *jack;
+
+  for (size_t i = 0; i < nelems; i++) {
+    snd_ctl_elem_id_set_name_map[elems[i]] = i;
+    snd_hctl_find_elem_return_vals.push_back(
+        reinterpret_cast<snd_hctl_elem_t*>(&elems[i]));
+  }
+
+  jack_list = cras_alsa_jack_list_create(0,
+                                         "card_name",
+                                         device_index,
+                                         1,
+                                         fake_mixer,
+                                         ucm, fake_hctl,
+                                         direction,
+                                         fake_jack_cb,
+                                         fake_jack_cb_arg);
+  if (jack_list == NULL)
+    return jack_list;
+  EXPECT_EQ(add_jack_rc,
+      cras_alsa_jack_list_add_jack_for_section(jack_list, ucm_section, &jack));
+  if (add_jack_rc == 0) {
+    EXPECT_EQ(njacks, ucm_get_dsp_name_called);
+    EXPECT_NE(jack, reinterpret_cast<struct cras_alsa_jack *>(NULL));
+  } else {
+    EXPECT_EQ(jack, reinterpret_cast<struct cras_alsa_jack *>(NULL));
+  }
+  if (add_jack_rc != 0 || njacks != ucm_get_dsp_name_called) {
+      cras_alsa_jack_list_destroy(jack_list);
+      return NULL;
+  }
+  EXPECT_EQ(njacks, snd_hctl_elem_set_callback_called);
+  EXPECT_EQ(njacks, cras_alsa_mixer_get_control_for_section_called);
+
+  return jack_list;
+}
+
 TEST(AlsaJacks, ReportNull) {
   cras_alsa_jack_list_report(NULL);
 }
@@ -298,47 +310,48 @@
   jack_list = run_test_with_elem_list(CRAS_STREAM_OUTPUT,
                                       elem_names,
                                       0,
-				      NULL,
+                                      NULL,
                                       ARRAY_SIZE(elem_names),
                                       0,
                                       0);
   ASSERT_NE(static_cast<struct cras_alsa_jack_list *>(NULL), jack_list);
 
   cras_alsa_jack_list_destroy(jack_list);
-  EXPECT_EQ(1, snd_hctl_close_called);
+  EXPECT_EQ(0, cras_system_rm_select_fd_called);
 }
 
 TEST(AlsaJacks, CreateGPIOHp) {
   struct cras_alsa_jack_list *jack_list;
 
   ResetStubData();
-  gpio_get_switch_names_count = ~0;
+  gpio_switch_list_for_each_dev_names.push_back("some-other-device");
+  gpio_switch_list_for_each_dev_names.push_back("c1 Headphone Jack");
   eviocbit_ret[LONG(SW_HEADPHONE_INSERT)] |= 1 << OFF(SW_HEADPHONE_INSERT);
   gpio_switch_eviocgbit_fd = 2;
   snd_hctl_first_elem_return_val = NULL;
   jack_list = cras_alsa_jack_list_create(0, "c1", 0, 1,
                                          fake_mixer,
-					 NULL,
+                                         NULL, fake_hctl,
                                          CRAS_STREAM_OUTPUT,
                                          fake_jack_cb,
                                          fake_jack_cb_arg);
   ASSERT_NE(static_cast<struct cras_alsa_jack_list *>(NULL), jack_list);
-
+  EXPECT_EQ(0, cras_alsa_jack_list_find_jacks_by_name_matching(jack_list));
   cras_alsa_jack_list_destroy(jack_list);
-  EXPECT_EQ(1, gpio_get_switch_names_called);
+  EXPECT_EQ(1, gpio_switch_list_for_each_called);
   EXPECT_GT(gpio_switch_open_called, 1);
   EXPECT_EQ(1, gpio_switch_eviocgsw_called);
   EXPECT_GT(gpio_switch_eviocgbit_called, 1);
-  EXPECT_GT(sys_input_get_device_name_called, 1);
   EXPECT_EQ(1, cras_system_add_select_fd_called);
-  EXPECT_EQ(1, snd_hctl_close_called);
+  EXPECT_EQ(1, cras_system_rm_select_fd_called);
 }
 
 TEST(AlsaJacks, CreateGPIOMic) {
   struct cras_alsa_jack_list *jack_list;
   ResetStubData();
   ucm_get_dev_for_jack_return = true;
-  gpio_get_switch_names_count = ~0;
+  gpio_switch_list_for_each_dev_names.push_back("c1 Mic Jack");
+  gpio_switch_list_for_each_dev_names.push_back("c1 Headphone Jack");
   eviocbit_ret[LONG(SW_MICROPHONE_INSERT)] |= 1 << OFF(SW_MICROPHONE_INSERT);
   gpio_switch_eviocgbit_fd = 3;
   snd_hctl_first_elem_return_val = NULL;
@@ -346,7 +359,7 @@
 
   // Freed in destroy.
   cras_alsa_mixer_get_input_matching_name_return_value =
-      reinterpret_cast<struct mixer_volume_control *>(malloc(1));
+      reinterpret_cast<struct mixer_control *>(malloc(1));
 
   jack_list = cras_alsa_jack_list_create(
       0,
@@ -354,11 +367,13 @@
       0,
       1,
       fake_mixer,
-      reinterpret_cast<snd_use_case_mgr_t*>(0x55),
+      reinterpret_cast<struct cras_use_case_mgr *>(0x55),
+      fake_hctl,
       CRAS_STREAM_INPUT,
       fake_jack_cb,
       fake_jack_cb_arg);
   ASSERT_NE(static_cast<struct cras_alsa_jack_list *>(NULL), jack_list);
+  EXPECT_EQ(0, cras_alsa_jack_list_find_jacks_by_name_matching(jack_list));
   EXPECT_EQ(ucm_get_cap_control_called, 1);
   EXPECT_EQ(cras_alsa_mixer_get_input_matching_name_called, 1);
   cras_alsa_jack_list_destroy(jack_list);
@@ -368,17 +383,19 @@
   struct cras_alsa_jack_list *jack_list;
 
   ResetStubData();
-  gpio_get_switch_names_count = ~0;
+  gpio_switch_list_for_each_dev_names.push_back("c1 HDMI Jack");
+  gpio_switch_list_for_each_dev_names.push_back("c1 Mic Jack");
   eviocbit_ret[LONG(SW_LINEOUT_INSERT)] |= 1 << OFF(SW_LINEOUT_INSERT);
   gpio_switch_eviocgbit_fd = 3;
   snd_hctl_first_elem_return_val = NULL;
   jack_list = cras_alsa_jack_list_create(0, "c1", 0, 1,
                                          fake_mixer,
-					 NULL,
+                                         NULL, fake_hctl,
                                          CRAS_STREAM_OUTPUT,
                                          fake_jack_cb,
                                          fake_jack_cb_arg);
   ASSERT_NE(static_cast<struct cras_alsa_jack_list *>(NULL), jack_list);
+  EXPECT_EQ(0, cras_alsa_jack_list_find_jacks_by_name_matching(jack_list));
   EXPECT_EQ(1, gpio_switch_eviocgsw_called);
 
   fake_jack_cb_called = 0;
@@ -387,38 +404,43 @@
   EXPECT_EQ(1, fake_jack_cb_called);
 
   cras_alsa_jack_list_destroy(jack_list);
-  EXPECT_EQ(1, gpio_get_switch_names_called);
+  EXPECT_EQ(1, gpio_switch_list_for_each_called);
   EXPECT_GT(gpio_switch_open_called, 1);
   EXPECT_GT(gpio_switch_eviocgbit_called, 1);
-  EXPECT_GT(sys_input_get_device_name_called, 1);
   EXPECT_EQ(1, cras_system_add_select_fd_called);
-  EXPECT_EQ(1, snd_hctl_close_called);
+  EXPECT_EQ(1, cras_system_rm_select_fd_called);
 }
 
 void run_gpio_jack_test(
     int device_index,
     int is_first_device,
     enum CRAS_STREAM_DIRECTION direction,
-    int should_create_jack)
+    int should_create_jack,
+    const char* jack_name)
 {
   struct cras_alsa_jack_list *jack_list;
-  snd_use_case_mgr_t *ucm = reinterpret_cast<snd_use_case_mgr_t*>(0x55);
+  struct cras_use_case_mgr *ucm =
+      reinterpret_cast<struct cras_use_case_mgr *>(0x55);
 
-  gpio_get_switch_names_count = ~0;
+  gpio_switch_list_for_each_dev_names.push_back("some-other-device one");
   gpio_switch_eviocgbit_fd = 2;
-  if (direction == CRAS_STREAM_OUTPUT)
+  if (direction == CRAS_STREAM_OUTPUT) {
     eviocbit_ret[LONG(SW_HEADPHONE_INSERT)] |= 1 << OFF(SW_HEADPHONE_INSERT);
-  else
+  } else {
     eviocbit_ret[LONG(SW_MICROPHONE_INSERT)] |= 1 << OFF(SW_MICROPHONE_INSERT);
+  }
+  gpio_switch_list_for_each_dev_names.push_back(jack_name);
   snd_hctl_first_elem_return_val = NULL;
 
-  jack_list = cras_alsa_jack_list_create(0, "c1", device_index, is_first_device,
+  jack_list = cras_alsa_jack_list_create(0, "c1", device_index,
+                                         is_first_device,
                                          fake_mixer,
-                                         ucm,
+                                         ucm, fake_hctl,
                                          direction,
                                          fake_jack_cb,
                                          fake_jack_cb_arg);
   ASSERT_NE(static_cast<struct cras_alsa_jack_list *>(NULL), jack_list);
+  EXPECT_EQ(0, cras_alsa_jack_list_find_jacks_by_name_matching(jack_list));
 
   cras_alsa_jack_list_report(jack_list);
   EXPECT_EQ(should_create_jack, fake_jack_cb_plugged);
@@ -440,7 +462,8 @@
   ucm_get_device_name_for_dev_value = strdup("hw:c1,1");
 
   run_gpio_jack_test(
-      device_index, is_first_device, direction, should_create_jack);
+      device_index, is_first_device, direction, should_create_jack,
+      "c1 Headset Jack");
 }
 
 TEST(AlsaJacks, CreateGPIOHpUCMCapturePCMMatched) {
@@ -456,7 +479,8 @@
   ucm_get_device_name_for_dev_value = strdup("hw:c1,1");
 
   run_gpio_jack_test(
-      device_index, is_first_device, direction, should_create_jack);
+      device_index, is_first_device, direction, should_create_jack,
+      "c1 Mic Jack");
 }
 
 TEST(AlsaJacks, CreateGPIOHpUCMPlaybackPCMNotMatched) {
@@ -472,7 +496,8 @@
   ucm_get_device_name_for_dev_value = strdup("hw:c1,2");
 
   run_gpio_jack_test(
-      device_index, is_first_device, direction, should_create_jack);
+      device_index, is_first_device, direction, should_create_jack,
+      "c1 Headset Jack");
 }
 
 TEST(AlsaJacks, CreateGPIOHpUCMPlaybackPCMNotSpecifiedFirstDevice) {
@@ -488,7 +513,8 @@
   ucm_get_device_name_for_dev_value = NULL;
 
   run_gpio_jack_test(
-      device_index, is_first_device, direction, should_create_jack);
+      device_index, is_first_device, direction, should_create_jack,
+      "c1 Headset Jack");
 }
 
 TEST(AlsaJacks, CreateGPIOHpUCMPlaybackPCMNotSpecifiedSecondDevice) {
@@ -504,7 +530,8 @@
   ucm_get_device_name_for_dev_value = NULL;
 
   run_gpio_jack_test(
-      device_index, is_first_device, direction, should_create_jack);
+      device_index, is_first_device, direction, should_create_jack,
+      "c1 Headset Jack");
 }
 
 TEST(AlsaJacks, CreateGPIOHpNoUCMFirstDevice) {
@@ -520,7 +547,8 @@
   ucm_get_device_name_for_dev_value = NULL;
 
   run_gpio_jack_test(
-      device_index, is_first_device, direction, should_create_jack);
+      device_index, is_first_device, direction, should_create_jack,
+      "c1 Headset Jack");
 }
 
 TEST(AlsaJacks, CreateGPIOHpNoUCMSecondDevice) {
@@ -536,7 +564,44 @@
   ucm_get_device_name_for_dev_value = NULL;
 
   run_gpio_jack_test(
-      device_index, is_first_device, direction, should_create_jack);
+      device_index, is_first_device, direction, should_create_jack,
+      "c1 Headset Jack");
+}
+
+TEST(AlsaJacks, CreateGPIOMicNoUCMFirstDeviceMicJack) {
+  int device_index = 1;
+  int is_first_device = 1;
+  enum CRAS_STREAM_DIRECTION direction = CRAS_STREAM_INPUT;
+  int should_create_jack = 1;
+
+  ResetStubData();
+
+  // 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;
+
+  // Mic Jack is a valid name for microphone jack.
+  run_gpio_jack_test(
+      device_index, is_first_device, direction, should_create_jack,
+      "c1 Mic Jack");
+}
+
+TEST(AlsaJacks, CreateGPIOMicNoUCMFirstDeviceHeadsetJack) {
+  int device_index = 1;
+  int is_first_device = 1;
+  enum CRAS_STREAM_DIRECTION direction = CRAS_STREAM_INPUT;
+  int should_create_jack = 1;
+
+  ResetStubData();
+
+  // 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;
+
+  // Headset Jack is a valid name for microphone jack.
+  run_gpio_jack_test(
+      device_index, is_first_device, direction, should_create_jack,
+      "c1 Headset Jack");
 }
 
 TEST(AlsaJacks, GPIOHdmiWithEdid) {
@@ -545,7 +610,7 @@
   ResetStubData();
   ucm_get_dev_for_jack_return = 1;
   edid_file_ret = static_cast<char*>(calloc(1, 1));  // Freed in destroy.
-  gpio_get_switch_names_count = ~0;
+  gpio_switch_list_for_each_dev_names.push_back("c1 HDMI Jack");
   eviocbit_ret[LONG(SW_LINEOUT_INSERT)] |= 1 << OFF(SW_LINEOUT_INSERT);
   gpio_switch_eviocgbit_fd = 3;
   snd_hctl_first_elem_return_val = NULL;
@@ -555,11 +620,13 @@
       0,
       1,
       fake_mixer,
-      reinterpret_cast<snd_use_case_mgr_t*>(0x55),
+      reinterpret_cast<struct cras_use_case_mgr *>(0x55),
+      fake_hctl,
       CRAS_STREAM_OUTPUT,
       fake_jack_cb,
       fake_jack_cb_arg);
   ASSERT_NE(static_cast<cras_alsa_jack_list*>(NULL), jack_list);
+  EXPECT_EQ(0, cras_alsa_jack_list_find_jacks_by_name_matching(jack_list));
   EXPECT_EQ(1, gpio_switch_eviocgsw_called);
 
   // EDID shouldn't open, callback should be skipped until re-try.
@@ -568,34 +635,34 @@
   EXPECT_EQ(0, fake_jack_cb_called);
 
   cras_alsa_jack_list_destroy(jack_list);
-  EXPECT_EQ(1, gpio_get_switch_names_called);
+  EXPECT_EQ(1, gpio_switch_list_for_each_called);
   EXPECT_GT(gpio_switch_open_called, 1);
   EXPECT_GT(gpio_switch_eviocgbit_called, 1);
-  EXPECT_GT(sys_input_get_device_name_called, 1);
   EXPECT_EQ(1, cras_system_add_select_fd_called);
-  EXPECT_EQ(1, snd_hctl_close_called);
+  EXPECT_EQ(1, cras_system_rm_select_fd_called);
 }
 
 TEST(AlsaJacks, CreateGPIOHpNoNameMatch) {
   struct cras_alsa_jack_list *jack_list;
 
   ResetStubData();
-  gpio_get_switch_names_count = ~0;
+  gpio_switch_list_for_each_dev_names.push_back("some-other-device one");
+  gpio_switch_list_for_each_dev_names.push_back("some-other-device two");
   snd_hctl_first_elem_return_val = NULL;
   jack_list = cras_alsa_jack_list_create(0, "c2", 0, 1,
                                          fake_mixer,
-					 NULL,
+                                         NULL, fake_hctl,
                                          CRAS_STREAM_OUTPUT,
                                          fake_jack_cb,
                                          fake_jack_cb_arg);
   ASSERT_NE(static_cast<struct cras_alsa_jack_list *>(NULL), jack_list);
+  EXPECT_EQ(0, cras_alsa_jack_list_find_jacks_by_name_matching(jack_list));
 
   cras_alsa_jack_list_destroy(jack_list);
-  EXPECT_EQ(1, gpio_get_switch_names_called);
-  EXPECT_GT(gpio_switch_open_called, 1);
-  EXPECT_GT(sys_input_get_device_name_called, 1);
+  EXPECT_EQ(1, gpio_switch_list_for_each_called);
+  EXPECT_EQ(0, gpio_switch_open_called);
   EXPECT_EQ(0, cras_system_add_select_fd_called);
-  EXPECT_EQ(1, snd_hctl_close_called);
+  EXPECT_EQ(0, cras_system_rm_select_fd_called);
 }
 
 TEST(AlsaJacks, CreateOneHpJack) {
@@ -604,24 +671,20 @@
     "Headphone Jack, klasdjf",
     "Mic Jack",
   };
-  struct pollfd poll_fds[] = {
-    {3, 0, 0},
-  };
   struct cras_alsa_jack_list *jack_list;
 
   ResetStubData();
-  snd_hctl_poll_descriptors_fds = poll_fds;
-  snd_hctl_poll_descriptors_num_fds = ARRAY_SIZE(poll_fds);
   jack_list = run_test_with_elem_list(CRAS_STREAM_OUTPUT,
                                       elem_names,
                                       0,
-				      NULL,
+                                      NULL,
                                       ARRAY_SIZE(elem_names),
                                       0,
                                       1);
   ASSERT_NE(static_cast<struct cras_alsa_jack_list *>(NULL), jack_list);
-  EXPECT_EQ(ARRAY_SIZE(poll_fds), cras_system_add_select_fd_called);
-  EXPECT_EQ(3, cras_system_add_select_fd_values[0]);
+  ASSERT_NE(reinterpret_cast<snd_hctl_elem_callback_t>(NULL),
+            snd_hctl_elem_set_callback_value);
+  EXPECT_EQ(1, snd_hctl_elem_set_callback_called);
 
   snd_hctl_elem_get_hctl_return_value = reinterpret_cast<snd_hctl_t *>(0x33);
   snd_hctl_elem_get_name_called = 0;
@@ -641,9 +704,9 @@
   EXPECT_EQ(1, fake_jack_cb_called);
 
   cras_alsa_jack_list_destroy(jack_list);
-  EXPECT_EQ(ARRAY_SIZE(poll_fds), cras_system_rm_select_fd_called);
-  EXPECT_EQ(3, cras_system_rm_select_fd_values[0]);
-  EXPECT_EQ(1, snd_hctl_close_called);
+  EXPECT_EQ(2, snd_hctl_elem_set_callback_called);
+  EXPECT_EQ(reinterpret_cast<snd_hctl_elem_callback_t>(NULL),
+            snd_hctl_elem_set_callback_value);
 }
 
 TEST(AlsaJacks, CreateOneMicJack) {
@@ -660,14 +723,20 @@
   jack_list = run_test_with_elem_list(CRAS_STREAM_INPUT,
                                       elem_names,
                                       0,
-				      NULL,
+                                      NULL,
                                       ARRAY_SIZE(elem_names),
                                       0,
                                       1);
   ASSERT_NE(static_cast<struct cras_alsa_jack_list *>(NULL), jack_list);
+  ASSERT_NE(reinterpret_cast<snd_hctl_elem_callback_t>(NULL),
+            snd_hctl_elem_set_callback_value);
+  EXPECT_EQ(1, snd_hctl_elem_set_callback_called);
 
   cras_alsa_jack_list_destroy(jack_list);
-  EXPECT_EQ(1, snd_hctl_close_called);
+  EXPECT_EQ(0, cras_system_rm_select_fd_called);
+  EXPECT_EQ(2, snd_hctl_elem_set_callback_called);
+  EXPECT_EQ(reinterpret_cast<snd_hctl_elem_callback_t>(NULL),
+            snd_hctl_elem_set_callback_value);
 }
 
 TEST(AlsaJacks, CreateHDMIJacksWithELD) {
@@ -677,14 +746,9 @@
     "ELD",
     "HDMI/DP,pcm=4 Jack"
   };
-  struct pollfd poll_fds[] = {
-    {0},
-  };
   struct cras_alsa_jack_list *jack_list;
 
   ResetStubData();
-  snd_hctl_poll_descriptors_fds = poll_fds;
-  snd_hctl_poll_descriptors_num_fds = ARRAY_SIZE(poll_fds);
   snd_hctl_elem_get_device_return_val = 3;
 
   jack_list = run_test_with_elem_list(
@@ -700,7 +764,6 @@
   /* Assert get device is called for the ELD control */
   EXPECT_EQ(1, snd_hctl_elem_get_device_called);
   cras_alsa_jack_list_destroy(jack_list);
-  EXPECT_EQ(1, snd_hctl_close_called);
 }
 
 TEST(AlsaJacks, CreateOneHpTwoHDMIJacks) {
@@ -711,26 +774,19 @@
     "HDMI/DP,pcm=6 Jack",
     "Mic Jack",
   };
-  struct pollfd poll_fds[] = {
-    {5, 0, 0},
-  };
   struct cras_alsa_jack_list *jack_list;
 
   ResetStubData();
-  snd_hctl_poll_descriptors_fds = poll_fds;
-  snd_hctl_poll_descriptors_num_fds = ARRAY_SIZE(poll_fds);
   ucm_get_dev_for_jack_return = true;
   jack_list = run_test_with_elem_list(
       CRAS_STREAM_OUTPUT,
       elem_names,
       5,
-      reinterpret_cast<snd_use_case_mgr_t*>(0x55),
+      reinterpret_cast<struct cras_use_case_mgr *>(0x55),
       ARRAY_SIZE(elem_names),
       1,
       1);
   ASSERT_NE(static_cast<struct cras_alsa_jack_list *>(NULL), jack_list);
-  EXPECT_EQ(ARRAY_SIZE(poll_fds), cras_system_add_select_fd_called);
-  EXPECT_EQ(5, cras_system_add_select_fd_values[0]);
 
   snd_hctl_elem_get_hctl_return_value = reinterpret_cast<snd_hctl_t *>(0x33);
   snd_hctl_elem_get_name_called = 0;
@@ -750,9 +806,149 @@
   EXPECT_EQ(1, fake_jack_cb_called);
 
   cras_alsa_jack_list_destroy(jack_list);
-  EXPECT_EQ(ARRAY_SIZE(poll_fds), cras_system_rm_select_fd_called);
-  EXPECT_EQ(5, cras_system_rm_select_fd_values[0]);
-  EXPECT_EQ(1, snd_hctl_close_called);
+}
+
+TEST(AlsaJacks, CreateHCTLHeadphoneJackFromUCM) {
+  std::string elem_names[] = {
+    "HP/DP,pcm=5 Jack",
+    "Headphone Jack",
+  };
+  struct cras_alsa_jack_list *jack_list;
+  struct ucm_section *section;
+
+  section = ucm_section_create("Headphone", 0, CRAS_STREAM_OUTPUT,
+                               "Headphone Jack", "hctl");
+
+  ResetStubData();
+  ucm_get_dev_for_jack_return = true;
+
+  jack_list = run_test_with_section(
+      CRAS_STREAM_OUTPUT,
+      elem_names,
+      ARRAY_SIZE(elem_names),
+      5,
+      reinterpret_cast<struct cras_use_case_mgr *>(0x55),
+      section,
+      0,
+      1);
+  ASSERT_NE(reinterpret_cast<struct cras_alsa_jack_list *>(NULL), jack_list);
+
+  snd_hctl_elem_get_hctl_return_value = reinterpret_cast<snd_hctl_t *>(0x33);
+  snd_ctl_elem_value_get_boolean_return_value = 1;
+  snd_hctl_elem_set_callback_value(
+      reinterpret_cast<snd_hctl_elem_t *>(&elem_names[1]), 0);
+  EXPECT_EQ(1, snd_hctl_elem_get_name_called);
+  EXPECT_EQ(1, fake_jack_cb_plugged);
+  EXPECT_EQ(1, fake_jack_cb_called);
+  EXPECT_EQ(fake_jack_cb_arg, fake_jack_cb_data);
+  EXPECT_EQ(reinterpret_cast<snd_hctl_elem_t *>(&elem_names[1]),
+            snd_hctl_elem_set_callback_obj);
+
+  fake_jack_cb_called = 0;
+  cras_alsa_jack_list_report(jack_list);
+  EXPECT_EQ(1, fake_jack_cb_plugged);
+  EXPECT_EQ(1, fake_jack_cb_called);
+
+  ucm_section_free_list(section);
+  cras_alsa_jack_list_destroy(jack_list);
+}
+
+TEST(AlsaJacks, CreateGPIOHeadphoneJackFromUCM) {
+  struct cras_alsa_jack_list *jack_list;
+  struct cras_alsa_jack *jack;
+  struct ucm_section *section;
+
+  section = ucm_section_create("Headphone", 0, CRAS_STREAM_OUTPUT,
+                               "c1 Headphone Jack", "gpio");
+
+  ResetStubData();
+  gpio_switch_list_for_each_dev_names.push_back("some-other-device");
+  gpio_switch_list_for_each_dev_names.push_back("c1 Headphone Jack");
+  eviocbit_ret[LONG(SW_HEADPHONE_INSERT)] |= 1 << OFF(SW_HEADPHONE_INSERT);
+  gpio_switch_eviocgbit_fd = 2;
+  snd_hctl_first_elem_return_val = NULL;
+  jack_list = cras_alsa_jack_list_create(0, "c1", 0, 1,
+                                         fake_mixer,
+                                         NULL, fake_hctl,
+                                         CRAS_STREAM_OUTPUT,
+                                         fake_jack_cb,
+                                         fake_jack_cb_arg);
+  ASSERT_NE(static_cast<struct cras_alsa_jack_list *>(NULL), jack_list);
+  EXPECT_EQ(0, cras_alsa_jack_list_add_jack_for_section(
+                   jack_list, section, &jack));
+  EXPECT_EQ(1, gpio_switch_list_for_each_called);
+  EXPECT_GT(gpio_switch_open_called, 1);
+  EXPECT_EQ(1, gpio_switch_eviocgsw_called);
+  EXPECT_GT(gpio_switch_eviocgbit_called, 1);
+  EXPECT_EQ(1, cras_system_add_select_fd_called);
+  EXPECT_EQ(1, cras_alsa_mixer_get_control_for_section_called);
+
+  fake_jack_cb_called = 0;
+  ucm_get_dev_for_jack_return = true;
+  cras_alsa_jack_list_report(jack_list);
+  EXPECT_EQ(1, fake_jack_cb_plugged);
+  EXPECT_EQ(1, fake_jack_cb_called);
+  EXPECT_EQ(fake_jack_cb_arg, fake_jack_cb_data);
+
+  ucm_section_free_list(section);
+  cras_alsa_jack_list_destroy(jack_list);
+  EXPECT_EQ(1, cras_system_rm_select_fd_called);
+}
+
+TEST(AlsaJacks, BadJackTypeFromUCM) {
+  std::string elem_names[] = {
+    "HP/DP,pcm=5 Jack",
+    "Headphone Jack",
+  };
+  struct cras_alsa_jack_list *jack_list;
+  struct ucm_section *section;
+
+  section = ucm_section_create("Headphone", 0, CRAS_STREAM_OUTPUT,
+                               "Headphone Jack", "badtype");
+
+  ResetStubData();
+  ucm_get_dev_for_jack_return = true;
+
+  jack_list = run_test_with_section(
+      CRAS_STREAM_OUTPUT,
+      elem_names,
+      ARRAY_SIZE(elem_names),
+      5,
+      reinterpret_cast<struct cras_use_case_mgr *>(0x55),
+      section,
+      -22,
+      1);
+  EXPECT_EQ(reinterpret_cast<struct cras_alsa_jack_list *>(NULL), jack_list);
+
+  ucm_section_free_list(section);
+}
+
+TEST(AlsaJacks, NoJackTypeFromUCM) {
+  std::string elem_names[] = {
+    "HP/DP,pcm=5 Jack",
+    "Headphone Jack",
+  };
+  struct cras_alsa_jack_list *jack_list;
+  struct ucm_section *section;
+
+  section = ucm_section_create("Headphone", 0, CRAS_STREAM_OUTPUT,
+                               "Headphone Jack", NULL);
+
+  ResetStubData();
+  ucm_get_dev_for_jack_return = true;
+
+  jack_list = run_test_with_section(
+      CRAS_STREAM_OUTPUT,
+      elem_names,
+      ARRAY_SIZE(elem_names),
+      5,
+      reinterpret_cast<struct cras_use_case_mgr *>(0x55),
+      section,
+      -22,
+      1);
+  EXPECT_EQ(reinterpret_cast<struct cras_alsa_jack_list *>(NULL), jack_list);
+
+  ucm_section_free_list(section);
 }
 
 /* Stubs */
@@ -775,19 +971,6 @@
 }
 
 // From alsa-lib hcontrol.c
-int snd_hctl_open(snd_hctl_t **hctlp, const char *name, int mode) {
-  *hctlp = snd_hctl_open_pointer_val;
-  snd_hctl_open_called++;
-  return snd_hctl_open_return_value;
-}
-int snd_hctl_load(snd_hctl_t *hctl) {
-  snd_hctl_load_called++;
-  return snd_hctl_load_return_value;
-}
-int snd_hctl_close(snd_hctl_t *hctl) {
-  snd_hctl_close_called++;
-  return 0;
-}
 unsigned int snd_hctl_elem_get_device(const snd_hctl_elem_t *obj) {
   snd_hctl_elem_get_device_called = 1;
   return snd_hctl_elem_get_device_return_val;
@@ -826,21 +1009,6 @@
   snd_hctl_elem_set_callback_obj = obj;
   snd_hctl_elem_set_callback_value = val;
 }
-int snd_hctl_poll_descriptors_count(snd_hctl_t *hctl) {
-  return snd_hctl_poll_descriptors_num_fds;
-}
-int snd_hctl_poll_descriptors(snd_hctl_t *hctl,
-                              struct pollfd *pfds,
-                              unsigned int space) {
-  unsigned int num = MIN(space, snd_hctl_poll_descriptors_num_fds);
-  memcpy(pfds, snd_hctl_poll_descriptors_fds, num * sizeof(*pfds));
-  snd_hctl_poll_descriptors_called++;
-  return num;
-}
-int snd_hctl_handle_events(snd_hctl_t *hctl) {
-  snd_hctl_handle_events_called++;
-  return 0;
-}
 void snd_hctl_elem_set_callback_private(snd_hctl_elem_t *obj, void * val) {
   snd_hctl_elem_set_callback_private_called++;
   snd_hctl_elem_set_callback_private_value = val;
@@ -855,10 +1023,29 @@
 int snd_hctl_elem_read(snd_hctl_elem_t *elem, snd_ctl_elem_value_t * value) {
   return 0;
 }
-int snd_hctl_nonblock(snd_hctl_t *hctl, int nonblock) {
-  snd_hctl_nonblock_called++;
-  return 0;
+snd_hctl_elem_t *snd_hctl_find_elem(snd_hctl_t *hctl,
+                                    const snd_ctl_elem_id_t *id) {
+  const size_t* index = reinterpret_cast<const size_t*>(id);
+  snd_hctl_find_elem_called++;
+  if (*index < snd_hctl_find_elem_return_vals.size())
+    return snd_hctl_find_elem_return_vals[*index];
+  return NULL;
 }
+void snd_ctl_elem_id_set_interface(snd_ctl_elem_id_t *obj,
+                                   snd_ctl_elem_iface_t val) {
+}
+void snd_ctl_elem_id_set_device(snd_ctl_elem_id_t *obj, unsigned int val) {
+}
+void snd_ctl_elem_id_set_name(snd_ctl_elem_id_t *obj, const char *val) {
+  size_t *obj_id = reinterpret_cast<size_t*>(obj);
+  std::map<std::string, size_t>::iterator id_name_it =
+      snd_ctl_elem_id_set_name_map.find(val);
+  if (id_name_it != snd_ctl_elem_id_set_name_map.end())
+    *obj_id = id_name_it->second;
+  else
+    *obj_id = INT_MAX;
+}
+
 // From alsa-lib control.c
 int snd_ctl_elem_value_get_boolean(const snd_ctl_elem_value_t *obj,
                                    unsigned int idx) {
@@ -867,7 +1054,7 @@
 }
 
 // From cras_alsa_mixer
-struct mixer_output_control *cras_alsa_mixer_get_output_matching_name(
+struct mixer_control *cras_alsa_mixer_get_output_matching_name(
     const struct cras_alsa_mixer *cras_mixer,
     size_t device_index,
     const char * const name)
@@ -876,7 +1063,7 @@
   return cras_alsa_mixer_get_output_matching_name_return_value;
 }
 
-struct mixer_volume_control *cras_alsa_mixer_get_input_matching_name(
+struct mixer_control *cras_alsa_mixer_get_input_matching_name(
     struct cras_alsa_mixer *cras_mixer,
     const char *control_name)
 {
@@ -884,10 +1071,12 @@
   return cras_alsa_mixer_get_input_matching_name_return_value;
 }
 
-char *sys_input_get_device_name(const char *path)
+struct mixer_control *cras_alsa_mixer_get_control_for_section(
+    struct cras_alsa_mixer *cras_mixer,
+    struct ucm_section *section)
 {
-  sys_input_get_device_name_called++;
-  return strdup("c1 Headphone Jack");
+  cras_alsa_mixer_get_control_for_section_called++;
+  return cras_alsa_mixer_get_control_for_section_return_value;
 }
 
 int gpio_switch_eviocgbit(int fd, void *buf, size_t n_bytes)
@@ -925,6 +1114,7 @@
    * unittest.
    */
   assert(0);
+  return 0;
 }
 
 int gpio_switch_open(const char *pathname)
@@ -937,38 +1127,33 @@
   return 0;
 }
 
-unsigned gpio_get_switch_names(enum CRAS_STREAM_DIRECTION direction,
-                               char **names, size_t n_names)
+void gpio_switch_list_for_each(gpio_switch_list_callback callback, void *arg)
 {
-  unsigned i, ub;
-  static const char *dummy[] = {
-      "/dev/input/event3",
-      "/dev/input/event2",
-  };
+  size_t i = 0;
 
-  ++gpio_get_switch_names_called;
+  ++gpio_switch_list_for_each_called;
 
-  ub = gpio_get_switch_names_count;
-  if (ub > ARRAY_SIZE(dummy))
-    ub = ARRAY_SIZE(dummy);
-
-  for (i = 0; i < ub; ++i) {
-    names[i] = strdup(dummy[i]);
+  while (i < gpio_switch_list_for_each_dev_names.size() &&
+         i < gpio_switch_list_for_each_dev_paths.size()) {
+    callback(gpio_switch_list_for_each_dev_paths[i].c_str(),
+             gpio_switch_list_for_each_dev_names[i].c_str(),
+             arg);
+    i++;
   }
-  return ub;
 }
 
-int ucm_set_enabled(snd_use_case_mgr_t *mgr, const char *dev, int enable) {
+int ucm_set_enabled(
+    struct cras_use_case_mgr *mgr, const char *dev, int enable) {
   ucm_set_enabled_value = enable;
   return 0;
 }
 
-char *ucm_get_cap_control(snd_use_case_mgr_t *mgr, const char *ucm_dev) {
+char *ucm_get_cap_control(struct cras_use_case_mgr *mgr, const char *ucm_dev) {
   ++ucm_get_cap_control_called;
   return ucm_get_cap_control_value;
 }
 
-char *ucm_get_dev_for_jack(snd_use_case_mgr_t *mgr, const char *jack,
+char *ucm_get_dev_for_jack(struct cras_use_case_mgr *mgr, const char *jack,
                            CRAS_STREAM_DIRECTION direction) {
   ++ucm_get_dev_for_jack_called;
   if (ucm_get_dev_for_jack_return)
@@ -976,25 +1161,25 @@
   return NULL;
 }
 
-const char *ucm_get_dsp_name(snd_use_case_mgr_t *mgr, const char *ucm_dev,
+const char *ucm_get_dsp_name(struct cras_use_case_mgr *mgr, const char *ucm_dev,
                        int direction) {
   ++ucm_get_dsp_name_called;
   return NULL;
 }
 
-const char *ucm_get_edid_file_for_dev(snd_use_case_mgr_t *mgr,
+const char *ucm_get_edid_file_for_dev(struct cras_use_case_mgr *mgr,
 				      const char *dev) {
   return edid_file_ret;
 }
 
-const char *ucm_get_override_type_name(snd_use_case_mgr_t *mgr,
+const char *ucm_get_override_type_name(struct cras_use_case_mgr *mgr,
                                        const char *ucm_dev)
 {
   ++ucm_get_override_type_name_called;
   return NULL;
 }
 
-const char *ucm_get_device_name_for_dev(snd_use_case_mgr_t *mgr,
+const char *ucm_get_device_name_for_dev(struct cras_use_case_mgr *mgr,
                                         const char *dev,
                                         enum CRAS_STREAM_DIRECTION direction)
 {
@@ -1030,11 +1215,18 @@
   return 0;
 }
 
+// Overwrite this function so unittest can run without 2 seconds of wait
+// in find_gpio_jacks.
+int wait_for_dev_input_access() {
+  return 0;
+}
+
 } /* extern "C" */
 
 }  //  namespace
 
 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/alsa_mixer_unittest.cc b/cras/src/tests/alsa_mixer_unittest.cc
index 6820e26..c689835 100644
--- a/cras/src/tests/alsa_mixer_unittest.cc
+++ b/cras/src/tests/alsa_mixer_unittest.cc
@@ -4,13 +4,18 @@
 
 #include <stdio.h>
 #include <gtest/gtest.h>
+#include <map>
+#include <string>
+#include <syslog.h>
 #include <vector>
 
 extern "C" {
 #include "cras_alsa_mixer.h"
+#include "cras_alsa_mixer_name.h"
 #include "cras_types.h"
 #include "cras_util.h"
 #include "cras_volume_curve.h"
+#include "utlist.h"
 
 //  Include C file to test static functions and use the definition of some
 //  structure.
@@ -66,7 +71,6 @@
 static int snd_mixer_selem_get_capture_dB_called;
 static long *snd_mixer_selem_get_capture_dB_return_values;
 static int snd_mixer_selem_get_capture_dB_return_values_length;
-static size_t cras_card_config_get_volume_curve_for_control_called;
 static size_t cras_volume_curve_destroy_called;
 static size_t snd_mixer_selem_get_playback_dB_range_called;
 static size_t snd_mixer_selem_get_playback_dB_range_values_length;
@@ -79,6 +83,9 @@
 static size_t iniparser_getstring_return_index;
 static size_t iniparser_getstring_return_length;
 static char **iniparser_getstring_returns;
+static size_t snd_mixer_find_selem_called;
+static std::map<std::string, snd_mixer_elem_t*> snd_mixer_find_elem_map;
+static std::string snd_mixer_find_elem_id_name;
 
 static void ResetStubData() {
   iniparser_getstring_return_index = 0;
@@ -128,7 +135,6 @@
   snd_mixer_selem_get_capture_dB_called = 0;
   snd_mixer_selem_get_capture_dB_return_values = static_cast<long *>(NULL);
   snd_mixer_selem_get_capture_dB_return_values_length = 0;
-  cras_card_config_get_volume_curve_for_control_called = 0;
   cras_volume_curve_destroy_called = 0;
   snd_mixer_selem_get_playback_dB_range_called = 0;
   snd_mixer_selem_get_playback_dB_range_values_length = 0;
@@ -138,6 +144,19 @@
   snd_mixer_selem_get_capture_dB_range_values_length = 0;
   snd_mixer_selem_get_capture_dB_range_min_values = static_cast<long *>(NULL);
   snd_mixer_selem_get_capture_dB_range_max_values = static_cast<long *>(NULL);
+  snd_mixer_find_selem_called = 0;
+  snd_mixer_find_elem_map.clear();
+  snd_mixer_find_elem_id_name.clear();
+}
+
+struct cras_alsa_mixer *create_mixer_and_add_controls_by_name_matching(
+    const char *card_name,
+    struct mixer_name *extra_controls,
+    struct mixer_name *coupled_controls) {
+  struct cras_alsa_mixer *cmix = cras_alsa_mixer_create(card_name);
+  cras_alsa_mixer_add_controls_by_name_matching(
+      cmix, extra_controls, coupled_controls);
+  return cmix;
 }
 
 TEST(AlsaMixer, CreateFailOpen) {
@@ -145,10 +164,9 @@
 
   ResetStubData();
   snd_mixer_open_return_value = -1;
-  c = cras_alsa_mixer_create("hw:0", NULL, NULL, 0, NULL);
-  EXPECT_EQ(static_cast<struct cras_alsa_mixer *>(NULL), c);
+  c = cras_alsa_mixer_create("hw:0");
+  EXPECT_NE(static_cast<struct cras_alsa_mixer *>(NULL), c);
   EXPECT_EQ(1, snd_mixer_open_called);
-  EXPECT_EQ(0, snd_mixer_close_called);
 }
 
 TEST(AlsaMixer, CreateFailAttach) {
@@ -156,8 +174,8 @@
 
   ResetStubData();
   snd_mixer_attach_return_value = -1;
-  c = cras_alsa_mixer_create("hw:0", NULL, NULL, 0, NULL);
-  EXPECT_EQ(static_cast<struct cras_alsa_mixer *>(NULL), c);
+  c = cras_alsa_mixer_create("hw:0");
+  EXPECT_NE(static_cast<struct cras_alsa_mixer *>(NULL), c);
   EXPECT_EQ(1, snd_mixer_open_called);
   EXPECT_EQ(1, snd_mixer_attach_called);
   EXPECT_EQ(0, strcmp(snd_mixer_attach_mixdev, "hw:0"));
@@ -169,8 +187,8 @@
 
   ResetStubData();
   snd_mixer_selem_register_return_value = -1;
-  c = cras_alsa_mixer_create("hw:0", NULL, NULL, 0, NULL);
-  EXPECT_EQ(static_cast<struct cras_alsa_mixer *>(NULL), c);
+  c = cras_alsa_mixer_create("hw:0");
+  EXPECT_NE(static_cast<struct cras_alsa_mixer *>(NULL), c);
   EXPECT_EQ(1, snd_mixer_open_called);
   EXPECT_EQ(1, snd_mixer_attach_called);
   EXPECT_EQ(0, strcmp(snd_mixer_attach_mixdev, "hw:0"));
@@ -183,8 +201,8 @@
 
   ResetStubData();
   snd_mixer_load_return_value = -1;
-  c = cras_alsa_mixer_create("hw:0", NULL, NULL, 0, NULL);
-  EXPECT_EQ(static_cast<struct cras_alsa_mixer *>(NULL), c);
+  c = cras_alsa_mixer_create("hw:0");
+  EXPECT_NE(static_cast<struct cras_alsa_mixer *>(NULL), c);
   EXPECT_EQ(1, snd_mixer_open_called);
   EXPECT_EQ(1, snd_mixer_attach_called);
   EXPECT_EQ(0, strcmp(snd_mixer_attach_mixdev, "hw:0"));
@@ -197,7 +215,8 @@
   struct cras_alsa_mixer *c;
 
   ResetStubData();
-  c = cras_alsa_mixer_create("hw:0", NULL, NULL, 0, NULL);
+  c = create_mixer_and_add_controls_by_name_matching(
+      "hw:0", NULL, NULL);
   ASSERT_NE(static_cast<struct cras_alsa_mixer *>(NULL), c);
   EXPECT_EQ(1, snd_mixer_open_called);
   EXPECT_EQ(1, snd_mixer_attach_called);
@@ -222,12 +241,14 @@
   int element_playback_volume[] = {
     0,
   };
+  int element_playback_switches[] = {
+    1,
+  };
   const char *element_names[] = {
     "Unknown",
   };
-  struct mixer_output_control output;
   struct mixer_control *mixer_output;
-  mixer_output = reinterpret_cast<mixer_control *>(&output);
+  int rc;
 
   ResetStubData();
   snd_mixer_first_elem_return_value = reinterpret_cast<snd_mixer_elem_t *>(1);
@@ -236,7 +257,8 @@
       ARRAY_SIZE(element_playback_volume);
   snd_mixer_selem_get_name_return_values = element_names;
   snd_mixer_selem_get_name_return_values_length = ARRAY_SIZE(element_names);
-  c = cras_alsa_mixer_create("hw:0", NULL, NULL, 0, NULL);
+  c = create_mixer_and_add_controls_by_name_matching(
+      "hw:0", NULL, NULL);
   ASSERT_NE(static_cast<struct cras_alsa_mixer *>(NULL), c);
   EXPECT_EQ(1, snd_mixer_open_called);
   EXPECT_EQ(1, snd_mixer_attach_called);
@@ -251,9 +273,23 @@
   /* set mute shouldn't call anything. */
   cras_alsa_mixer_set_mute(c, 0, NULL);
   EXPECT_EQ(0, snd_mixer_selem_set_playback_switch_all_called);
+
+  ResetStubData();
+  snd_mixer_selem_has_playback_switch_return_values = element_playback_switches;
+  snd_mixer_selem_has_playback_switch_return_values_length =
+      ARRAY_SIZE(element_playback_switches);
+  snd_mixer_selem_get_name_return_values = element_names;
+  snd_mixer_selem_get_name_return_values_length = ARRAY_SIZE(element_names);
+  rc = mixer_control_create(&mixer_output, NULL,
+                            reinterpret_cast<snd_mixer_elem_t *>(1),
+                            CRAS_STREAM_OUTPUT);
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(1, snd_mixer_selem_get_name_called);
+  EXPECT_EQ(1, snd_mixer_selem_has_playback_volume_called);
+  EXPECT_EQ(1, snd_mixer_selem_has_playback_switch_called);
+  EXPECT_EQ(1, snd_mixer_selem_get_playback_dB_range_called);
+
   /* if passed a mixer output then it should mute that. */
-  mixer_output->elem = reinterpret_cast<snd_mixer_elem_t *>(0x454);
-  mixer_output->has_mute = 1;
   cras_alsa_mixer_set_mute(c, 0, mixer_output);
   EXPECT_EQ(1, snd_mixer_selem_set_playback_switch_all_called);
   /* set volume shouldn't call anything. */
@@ -262,6 +298,7 @@
 
   cras_alsa_mixer_destroy(c);
   EXPECT_EQ(1, snd_mixer_close_called);
+  mixer_control_destroy(mixer_output);
 }
 
 TEST(AlsaMixer, CreateOneUnknownElementWithVolume) {
@@ -270,13 +307,18 @@
   static const long max_volumes[] = {40};
   int element_playback_volume[] = {
     1,
+    0,
+  };
+  int element_playback_switches[] = {
+    0,
+    1,
   };
   const char *element_names[] = {
     "Unknown",
+    "Playback",
   };
-  struct mixer_output_control output;
   struct mixer_control *mixer_output;
-  mixer_output = reinterpret_cast<mixer_control *>(&output);
+  int rc;
 
   ResetStubData();
   snd_mixer_first_elem_return_value = reinterpret_cast<snd_mixer_elem_t *>(1);
@@ -288,7 +330,8 @@
   snd_mixer_selem_get_playback_dB_range_min_values = min_volumes;
   snd_mixer_selem_get_playback_dB_range_max_values = max_volumes;
   snd_mixer_selem_get_playback_dB_range_values_length = ARRAY_SIZE(min_volumes);
-  c = cras_alsa_mixer_create("hw:0", NULL, NULL, 0, NULL);
+  c = create_mixer_and_add_controls_by_name_matching(
+      "hw:0", NULL, NULL);
   ASSERT_NE(static_cast<struct cras_alsa_mixer *>(NULL), c);
   EXPECT_EQ(1, snd_mixer_open_called);
   EXPECT_EQ(1, snd_mixer_attach_called);
@@ -296,16 +339,36 @@
   EXPECT_EQ(1, snd_mixer_selem_register_called);
   EXPECT_EQ(1, snd_mixer_load_called);
   EXPECT_EQ(0, snd_mixer_close_called);
-  EXPECT_EQ(2, snd_mixer_selem_has_playback_volume_called);
+  EXPECT_EQ(3, snd_mixer_selem_has_playback_volume_called);
   EXPECT_EQ(2, snd_mixer_selem_get_playback_dB_range_called);
-  EXPECT_EQ(1, snd_mixer_selem_get_name_called);
+  EXPECT_EQ(3, snd_mixer_selem_get_name_called);
 
-  /* should use the unknown element as a fallback */
+  /* Should use "Playback" since it has playback switch. */
   cras_alsa_mixer_set_mute(c, 0, NULL);
   EXPECT_EQ(1, snd_mixer_selem_set_playback_switch_all_called);
-  /* if passed a mixer output then it should mute that. */
-  mixer_output->elem = reinterpret_cast<snd_mixer_elem_t *>(0x454);
-  mixer_output->has_mute = 1;
+
+  ResetStubData();
+  snd_mixer_selem_has_playback_volume_return_values = element_playback_volume;
+  snd_mixer_selem_has_playback_volume_return_values_length =
+      ARRAY_SIZE(element_playback_volume);
+  snd_mixer_selem_has_playback_switch_return_values = element_playback_switches;
+  snd_mixer_selem_has_playback_switch_return_values_length =
+      ARRAY_SIZE(element_playback_switches);
+  snd_mixer_selem_get_name_return_values = element_names;
+  snd_mixer_selem_get_name_return_values_length = ARRAY_SIZE(element_names);
+  rc = mixer_control_create(&mixer_output, NULL,
+                            reinterpret_cast<snd_mixer_elem_t *>(2),
+                            CRAS_STREAM_OUTPUT);
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(1, snd_mixer_selem_get_name_called);
+  EXPECT_EQ(1, snd_mixer_selem_has_playback_volume_called);
+  EXPECT_EQ(1, snd_mixer_selem_has_playback_switch_called);
+  EXPECT_EQ(0, snd_mixer_selem_get_playback_dB_range_called);
+
+  /*
+   * If passed a mixer output then it should mute both "Playback" and that
+   * mixer_output.
+   */
   cras_alsa_mixer_set_mute(c, 0, mixer_output);
   EXPECT_EQ(2, snd_mixer_selem_set_playback_switch_all_called);
   cras_alsa_mixer_set_dBFS(c, 0, NULL);
@@ -313,27 +376,28 @@
 
   cras_alsa_mixer_destroy(c);
   EXPECT_EQ(1, snd_mixer_close_called);
+  mixer_control_destroy(mixer_output);
 }
 
 TEST(AlsaMixer, CreateOneMasterElement) {
   struct cras_alsa_mixer *c;
   int element_playback_volume[] = {
     1,
+    1,
   };
   int element_playback_switches[] = {
     1,
+    1,
   };
   const char *element_names[] = {
     "Master",
+    "Playback"
   };
-  struct mixer_output_control output;
   struct mixer_control *mixer_output;
-  mixer_output = reinterpret_cast<mixer_control *>(&output);
-  mixer_output->elem = reinterpret_cast<snd_mixer_elem_t *>(2);
-  mixer_output->has_mute = 1;
-  mixer_output->has_volume = 1;
-  output.max_volume_dB = 950;
+  int rc;
   long set_dB_values[3];
+  static const long min_volumes[] = {0, 0};
+  static const long max_volumes[] = {950, 950};
 
   ResetStubData();
   snd_mixer_first_elem_return_value = reinterpret_cast<snd_mixer_elem_t *>(1);
@@ -345,7 +409,8 @@
       ARRAY_SIZE(element_playback_switches);
   snd_mixer_selem_get_name_return_values = element_names;
   snd_mixer_selem_get_name_return_values_length = ARRAY_SIZE(element_names);
-  c = cras_alsa_mixer_create("hw:0", NULL, NULL, 0, NULL);
+  c = create_mixer_and_add_controls_by_name_matching(
+      "hw:0", NULL, NULL);
   ASSERT_NE(static_cast<struct cras_alsa_mixer *>(NULL), c);
   EXPECT_EQ(1, snd_mixer_open_called);
   EXPECT_EQ(1, snd_mixer_attach_called);
@@ -353,7 +418,7 @@
   EXPECT_EQ(1, snd_mixer_selem_register_called);
   EXPECT_EQ(1, snd_mixer_load_called);
   EXPECT_EQ(0, snd_mixer_close_called);
-  EXPECT_EQ(1, snd_mixer_selem_get_name_called);
+  EXPECT_EQ(3, snd_mixer_selem_get_name_called);
   EXPECT_EQ(1, snd_mixer_elem_next_called);
 
   /* set mute should be called for Master. */
@@ -363,12 +428,28 @@
   cras_alsa_mixer_set_dBFS(c, 0, NULL);
   EXPECT_EQ(1, snd_mixer_selem_set_playback_dB_all_called);
 
-  /* if passed a mixer output then it should set the volume for that too. */
+  ResetStubData();
   snd_mixer_selem_set_playback_dB_all_values = set_dB_values;
   snd_mixer_selem_set_playback_dB_all_values_length =
       ARRAY_SIZE(set_dB_values);
-  snd_mixer_selem_set_playback_dB_all_called = 0;
-  snd_mixer_selem_get_playback_dB_called = 0;
+  snd_mixer_selem_get_playback_dB_range_min_values = min_volumes;
+  snd_mixer_selem_get_playback_dB_range_max_values = max_volumes;
+  snd_mixer_selem_get_playback_dB_range_values_length = ARRAY_SIZE(min_volumes);
+  snd_mixer_selem_has_playback_volume_return_values = element_playback_volume;
+  snd_mixer_selem_has_playback_volume_return_values_length =
+      ARRAY_SIZE(element_playback_volume);
+  snd_mixer_selem_get_name_return_values = element_names;
+  snd_mixer_selem_get_name_return_values_length = ARRAY_SIZE(element_names);
+  rc = mixer_control_create(&mixer_output, NULL,
+                            reinterpret_cast<snd_mixer_elem_t *>(2),
+                            CRAS_STREAM_OUTPUT);
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(1, snd_mixer_selem_get_name_called);
+  EXPECT_EQ(1, snd_mixer_selem_has_playback_volume_called);
+  EXPECT_EQ(1, snd_mixer_selem_has_playback_switch_called);
+  EXPECT_EQ(1, snd_mixer_selem_get_playback_dB_range_called);
+
+  /* if passed a mixer output then it should set the volume for that too. */
   cras_alsa_mixer_set_dBFS(c, 0, mixer_output);
   EXPECT_EQ(2, snd_mixer_selem_set_playback_dB_all_called);
   EXPECT_EQ(950, set_dB_values[0]);
@@ -376,6 +457,7 @@
 
   cras_alsa_mixer_destroy(c);
   EXPECT_EQ(1, snd_mixer_close_called);
+  mixer_control_destroy(mixer_output);
 }
 
 TEST(AlsaMixer, CreateTwoMainVolumeElements) {
@@ -386,21 +468,20 @@
   int element_playback_volume[] = {
     1,
     1,
+    1,
   };
   int element_playback_switches[] = {
     1,
     1,
+    1,
   };
   const char *element_names[] = {
     "Master",
     "PCM",
+    "Other",
   };
-  struct mixer_output_control output;
   struct mixer_control *mixer_output;
-  mixer_output = reinterpret_cast<mixer_control *>(&output);
-  mixer_output->elem = reinterpret_cast<snd_mixer_elem_t *>(3);
-  mixer_output->has_volume = 1;
-  output.max_volume_dB = 0;
+  int rc;
   static const long min_volumes[] = {-500, -1250, -500};
   static const long max_volumes[] = {40, 40, 0};
   long get_dB_returns[] = {0, 0, 0};
@@ -425,9 +506,10 @@
   snd_mixer_selem_set_playback_dB_all_values = set_dB_values;
   snd_mixer_selem_set_playback_dB_all_values_length =
       ARRAY_SIZE(set_dB_values);
-  c = cras_alsa_mixer_create("hw:0", NULL, NULL, 0, NULL);
+  c = create_mixer_and_add_controls_by_name_matching(
+      "hw:0", NULL, NULL);
   ASSERT_NE(static_cast<struct cras_alsa_mixer *>(NULL), c);
-  EXPECT_EQ(3, snd_mixer_selem_get_playback_dB_range_called);
+  EXPECT_EQ(2, snd_mixer_selem_get_playback_dB_range_called);
   EXPECT_EQ(1, snd_mixer_open_called);
   EXPECT_EQ(1, snd_mixer_attach_called);
   EXPECT_EQ(0, strcmp(snd_mixer_attach_mixdev, "hw:0"));
@@ -435,8 +517,8 @@
   EXPECT_EQ(1, snd_mixer_load_called);
   EXPECT_EQ(0, snd_mixer_close_called);
   EXPECT_EQ(2, snd_mixer_elem_next_called);
-  EXPECT_EQ(2, snd_mixer_selem_get_name_called);
-  EXPECT_EQ(1, snd_mixer_selem_has_playback_switch_called);
+  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. */
   cras_alsa_mixer_set_mute(c, 0, NULL);
@@ -456,11 +538,33 @@
   /* volume should be set relative to max volume (40 + 40). */
   EXPECT_EQ(30, set_dB_values[0]);
   EXPECT_EQ(30, set_dB_values[1]);
+
+  ResetStubData();
+  snd_mixer_selem_set_playback_dB_all_values = set_dB_values;
+  snd_mixer_selem_set_playback_dB_all_values_length = ARRAY_SIZE(set_dB_values);
+  snd_mixer_selem_get_playback_dB_range_min_values = min_volumes;
+  snd_mixer_selem_get_playback_dB_range_max_values = max_volumes;
+  snd_mixer_selem_get_playback_dB_range_values_length = ARRAY_SIZE(min_volumes);
+  snd_mixer_selem_has_playback_volume_return_values = element_playback_volume;
+  snd_mixer_selem_has_playback_volume_return_values_length =
+      ARRAY_SIZE(element_playback_volume);
+  snd_mixer_selem_has_playback_switch_return_values = element_playback_switches;
+  snd_mixer_selem_has_playback_switch_return_values_length =
+      ARRAY_SIZE(element_playback_switches);
+  snd_mixer_selem_get_name_return_values = element_names;
+  snd_mixer_selem_get_name_return_values_length = ARRAY_SIZE(element_names);
+  rc = mixer_control_create(&mixer_output, NULL,
+                            reinterpret_cast<snd_mixer_elem_t *>(3),
+                            CRAS_STREAM_OUTPUT);
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(1, snd_mixer_selem_get_name_called);
+  EXPECT_EQ(1, snd_mixer_selem_has_playback_volume_called);
+  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
    * should be passed to the PCM control.*/
-  snd_mixer_selem_set_playback_dB_all_called = 0;
-  snd_mixer_selem_get_playback_dB_called = 0;
   cras_alsa_mixer_set_dBFS(c, -50, mixer_output);
   EXPECT_EQ(3, snd_mixer_selem_set_playback_dB_all_called);
   EXPECT_EQ(2, snd_mixer_selem_get_playback_dB_called);
@@ -480,6 +584,8 @@
   snd_mixer_selem_set_playback_dB_all_called = 0;
   snd_mixer_selem_get_playback_dB_called = 0;
   mixer_output->has_volume = 0;
+  mixer_output->min_volume_dB = MIXER_CONTROL_VOLUME_DB_INVALID;
+  mixer_output->max_volume_dB = MIXER_CONTROL_VOLUME_DB_INVALID;
   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);
@@ -488,6 +594,7 @@
 
   cras_alsa_mixer_destroy(c);
   EXPECT_EQ(1, snd_mixer_close_called);
+  mixer_control_destroy(mixer_output);
 }
 
 TEST(AlsaMixer, CreateTwoMainCaptureElements) {
@@ -511,8 +618,7 @@
     "Mic",
   };
   struct mixer_control *mixer_input;
-  mixer_input = (struct mixer_control *)calloc(1, sizeof(*mixer_input));
-  mixer_input->elem = reinterpret_cast<snd_mixer_elem_t *>(3);
+  int rc;
 
   ResetStubData();
   snd_mixer_first_elem_return_value = reinterpret_cast<snd_mixer_elem_t *>(1);
@@ -526,7 +632,8 @@
       ARRAY_SIZE(element_capture_switches);
   snd_mixer_selem_get_name_return_values = element_names;
   snd_mixer_selem_get_name_return_values_length = ARRAY_SIZE(element_names);
-  c = cras_alsa_mixer_create("hw:0", NULL, NULL, 0, NULL);
+  c = create_mixer_and_add_controls_by_name_matching(
+      "hw:0", NULL, NULL);
   ASSERT_NE(static_cast<struct cras_alsa_mixer *>(NULL), c);
   EXPECT_EQ(1, snd_mixer_open_called);
   EXPECT_EQ(1, snd_mixer_attach_called);
@@ -535,8 +642,8 @@
   EXPECT_EQ(1, snd_mixer_load_called);
   EXPECT_EQ(0, snd_mixer_close_called);
   EXPECT_EQ(2, snd_mixer_elem_next_called);
-  EXPECT_EQ(4, snd_mixer_selem_get_name_called);
-  EXPECT_EQ(1, snd_mixer_selem_has_capture_switch_called);
+  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. */
   cras_alsa_mixer_set_capture_mute(c, 0, NULL);
@@ -586,17 +693,39 @@
   long get_dB_returns3[] = {
     0,
     0,
+    0,
   };
   long set_dB_values3[3];
-  mixer_input->has_volume = 1;
+
   snd_mixer_selem_get_capture_dB_return_values = get_dB_returns3;
   snd_mixer_selem_get_capture_dB_return_values_length =
       ARRAY_SIZE(get_dB_returns3);
+  snd_mixer_selem_get_capture_dB_called = 0;
   snd_mixer_selem_set_capture_dB_all_values = set_dB_values3;
   snd_mixer_selem_set_capture_dB_all_values_length =
       ARRAY_SIZE(set_dB_values3);
   snd_mixer_selem_set_capture_dB_all_called = 0;
-  snd_mixer_selem_get_capture_dB_called = 0;
+  snd_mixer_selem_has_capture_volume_return_values = element_capture_volume;
+  snd_mixer_selem_has_capture_volume_return_values_length =
+      ARRAY_SIZE(element_capture_volume);
+  snd_mixer_selem_has_capture_switch_return_values = element_capture_switches;
+  snd_mixer_selem_has_capture_switch_return_values_length =
+      ARRAY_SIZE(element_capture_switches);
+  snd_mixer_selem_get_name_return_values = element_names;
+  snd_mixer_selem_get_name_return_values_length = ARRAY_SIZE(element_names);
+  snd_mixer_selem_get_name_called = 0;
+  snd_mixer_selem_has_capture_volume_called = 0;
+  snd_mixer_selem_has_capture_switch_called = 0;
+  snd_mixer_selem_get_capture_dB_range_called = 0;
+  rc = mixer_control_create(&mixer_input, NULL,
+                            reinterpret_cast<snd_mixer_elem_t *>(3),
+                            CRAS_STREAM_INPUT);
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(1, snd_mixer_selem_get_name_called);
+  EXPECT_EQ(1, snd_mixer_selem_has_capture_volume_called);
+  EXPECT_EQ(1, snd_mixer_selem_has_capture_switch_called);
+  EXPECT_EQ(1, snd_mixer_selem_get_capture_dB_range_called);
+  EXPECT_EQ(1, mixer_input->has_volume);
 
   cras_alsa_mixer_set_capture_dBFS(c, 20, mixer_input);
 
@@ -608,7 +737,7 @@
 
   cras_alsa_mixer_destroy(c);
   EXPECT_EQ(1, snd_mixer_close_called);
-  free(mixer_input);
+  mixer_control_destroy(mixer_input);
 }
 
 class AlsaMixerOutputs : public testing::Test {
@@ -651,6 +780,8 @@
       1,
       1,
     };
+    static const long min_volumes[] = {0, 0, 0, 0, 0, 0, 500, -1250};
+    static const long max_volumes[] = {0, 0, 0, 0, 0, 0, 3000, 400};
     static const char *element_names[] = {
       "Master",
       "PCM",
@@ -667,6 +798,11 @@
     static char *iniparser_returns[] = {
       NULL,
     };
+    struct mixer_name *extra_controls =
+        mixer_name_add_array(NULL, output_names_extra,
+                             ARRAY_SIZE(output_names_extra),
+                             CRAS_STREAM_OUTPUT,
+                             MIXER_NAME_VOLUME);
 
     ResetStubData();
     snd_mixer_first_elem_return_value =
@@ -691,11 +827,15 @@
       ARRAY_SIZE(element_capture_switches);
     snd_mixer_selem_get_name_return_values = element_names;
     snd_mixer_selem_get_name_return_values_length = ARRAY_SIZE(element_names);
+    snd_mixer_selem_get_capture_dB_range_called = 0;
+    snd_mixer_selem_get_capture_dB_range_min_values = min_volumes;
+    snd_mixer_selem_get_capture_dB_range_max_values = max_volumes;
+    snd_mixer_selem_get_capture_dB_range_values_length =
+        ARRAY_SIZE(min_volumes);
     iniparser_getstring_returns = iniparser_returns;
     iniparser_getstring_return_length = ARRAY_SIZE(iniparser_returns);
-    cras_mixer_ = cras_alsa_mixer_create("hw:0",
-        reinterpret_cast<struct cras_card_config*>(5),
-        output_names_extra, ARRAY_SIZE(output_names_extra), NULL);
+    cras_mixer_ = create_mixer_and_add_controls_by_name_matching(
+        "hw:0", extra_controls, NULL);
     ASSERT_NE(static_cast<struct cras_alsa_mixer *>(NULL), cras_mixer_);
     EXPECT_EQ(1, snd_mixer_open_called);
     EXPECT_EQ(1, snd_mixer_attach_called);
@@ -704,11 +844,11 @@
     EXPECT_EQ(1, snd_mixer_load_called);
     EXPECT_EQ(0, snd_mixer_close_called);
     EXPECT_EQ(ARRAY_SIZE(elements) + 1, snd_mixer_elem_next_called);
-    EXPECT_EQ(6, snd_mixer_selem_has_playback_volume_called);
-    EXPECT_EQ(5, snd_mixer_selem_has_playback_switch_called);
-    EXPECT_EQ(2, snd_mixer_selem_has_capture_volume_called);
-    EXPECT_EQ(1, snd_mixer_selem_has_capture_switch_called);
-    EXPECT_EQ(5, cras_card_config_get_volume_curve_for_control_called);
+    EXPECT_EQ(8, snd_mixer_selem_has_playback_volume_called);
+    EXPECT_EQ(7, snd_mixer_selem_has_playback_switch_called);
+    EXPECT_EQ(4, snd_mixer_selem_has_capture_volume_called);
+    EXPECT_EQ(3, snd_mixer_selem_has_capture_switch_called);
+    mixer_name_free(extra_controls);
   }
 
   virtual void TearDown() {
@@ -740,36 +880,31 @@
 TEST_F(AlsaMixerOutputs, CheckFindOutputByNameNoMatch) {
   struct mixer_control *out;
 
-  snd_mixer_selem_get_name_called = 0;
   out = cras_alsa_mixer_get_output_matching_name(cras_mixer_,
                                                  "AAAAA Jack");
   EXPECT_EQ(static_cast<struct mixer_control *>(NULL), out);
-  EXPECT_EQ(4, snd_mixer_selem_get_name_called);
 }
 
 TEST_F(AlsaMixerOutputs, CheckFindOutputByName) {
   struct mixer_control *out;
 
-  snd_mixer_selem_get_name_called = 0;
   out = cras_alsa_mixer_get_output_matching_name(cras_mixer_,
                                                  "Headphone Jack");
   EXPECT_NE(static_cast<struct mixer_control *>(NULL), out);
-  EXPECT_EQ(1, snd_mixer_selem_get_name_called);
 }
 
 TEST_F(AlsaMixerOutputs, CheckFindOutputHDMIByName) {
   struct mixer_control *out;
 
-  snd_mixer_selem_get_name_called = 0;
   out = cras_alsa_mixer_get_output_matching_name(cras_mixer_,
                                                  "HDMI Jack");
   EXPECT_NE(static_cast<struct mixer_control *>(NULL), out);
-  EXPECT_EQ(3, snd_mixer_selem_get_name_called);
 }
 
-TEST_F(AlsaMixerOutputs, CheckFindInputName) {
+TEST_F(AlsaMixerOutputs, CheckFindInputNameWorkaround) {
   struct mixer_control *control;
   snd_mixer_elem_t *elements[] = {
+    reinterpret_cast<snd_mixer_elem_t *>(1),  // Speaker
     reinterpret_cast<snd_mixer_elem_t *>(2),  // Headphone
     reinterpret_cast<snd_mixer_elem_t *>(3),  // MIC
   };
@@ -778,11 +913,11 @@
     "Headphone",
     "MIC",
   };
+  size_t i;
 
-  snd_mixer_first_elem_return_value = reinterpret_cast<snd_mixer_elem_t *>(1);
-  snd_mixer_elem_next_return_values = elements;
-  snd_mixer_elem_next_return_values_index = 0;
-  snd_mixer_elem_next_return_values_length = ARRAY_SIZE(elements);
+  ResetStubData();
+  for (i = 0; i < ARRAY_SIZE(elements); i++)
+    snd_mixer_find_elem_map[element_names[i]] = elements[i];
 
   snd_mixer_selem_get_name_called = 0;
   snd_mixer_selem_get_name_return_values = element_names;
@@ -790,8 +925,12 @@
   control = cras_alsa_mixer_get_input_matching_name(cras_mixer_,
                                                     "MIC");
   EXPECT_NE(static_cast<struct mixer_control *>(NULL), control);
-  /* 3 + 1, one more for log */
-  EXPECT_EQ(4, snd_mixer_selem_get_name_called);
+  /* This exercises the 'workaround' where the control is added if it was
+   * previouly missing in cras_alsa_mixer_get_input_matching_name().
+   * snd_mixer_find_selem is called once for the missing control. */
+  EXPECT_EQ(1, snd_mixer_find_selem_called);
+  EXPECT_EQ(1, snd_mixer_selem_has_capture_volume_called);
+  EXPECT_EQ(1, snd_mixer_selem_has_capture_switch_called);
 }
 
 TEST_F(AlsaMixerOutputs, ActivateDeactivate) {
@@ -812,13 +951,6 @@
 
 TEST_F(AlsaMixerOutputs, MinMaxCaptureGain) {
   long min, max;
-  static const long min_volumes[] = {0, 0, 0, 0, 0, 0, 500, -1250};
-  static const long max_volumes[] = {0, 0, 0, 0, 0, 0, 3000, 400};
-
-  snd_mixer_selem_get_capture_dB_range_called = 0;
-  snd_mixer_selem_get_capture_dB_range_min_values = min_volumes;
-  snd_mixer_selem_get_capture_dB_range_max_values = max_volumes;
-  snd_mixer_selem_get_capture_dB_range_values_length = ARRAY_SIZE(min_volumes);
   min = cras_alsa_mixer_get_minimum_capture_gain(cras_mixer_,
 		  NULL);
   EXPECT_EQ(-750, min);
@@ -831,16 +963,10 @@
   struct mixer_control *mixer_input;
   long min, max;
 
-  static const long min_volumes[] = {0, 0, 0, 0, 0, 0, 500, -1250, 50};
-  static const long max_volumes[] = {0, 0, 0, 0, 0, 0, 3000, 400, 60};
-
-  snd_mixer_selem_get_capture_dB_range_called = 0;
-  snd_mixer_selem_get_capture_dB_range_min_values = min_volumes;
-  snd_mixer_selem_get_capture_dB_range_max_values = max_volumes;
-  snd_mixer_selem_get_capture_dB_range_values_length = ARRAY_SIZE(min_volumes);
-
   mixer_input = (struct mixer_control *)calloc(1, sizeof(*mixer_input));
-  mixer_input->elem = reinterpret_cast<snd_mixer_elem_t *>(9);
+  mixer_input->min_volume_dB = 50;
+  mixer_input->max_volume_dB = 60;
+  mixer_input->has_volume = 1;
   min = cras_alsa_mixer_get_minimum_capture_gain(cras_mixer_, mixer_input);
   max = cras_alsa_mixer_get_maximum_capture_gain(cras_mixer_, mixer_input);
   EXPECT_EQ(-700, min);
@@ -849,6 +975,556 @@
   free((void *)mixer_input);
 }
 
+TEST(AlsaMixer, CreateWithCoupledOutputControls) {
+  struct cras_alsa_mixer *c;
+  struct mixer_control *output_control;
+  struct mixer_control_element *c1, *c2, *c3, *c4;
+
+  static const long min_volumes[] = {-70, -70};
+  static const long max_volumes[] = {30, 30};
+
+  long set_dB_values[2];
+
+  const char *coupled_output_names[] = {"Left Master",
+                                        "Right Master",
+                                        "Left Speaker",
+                                        "Right Speaker"};
+  struct mixer_name *coupled_controls =
+      mixer_name_add_array(NULL, coupled_output_names,
+                           ARRAY_SIZE(coupled_output_names),
+                           CRAS_STREAM_OUTPUT,
+                           MIXER_NAME_VOLUME);
+  int element_playback_volume[] = {1, 1, 0, 0};
+  int element_playback_switches[] = {0, 0, 1, 1};
+
+  long target_dBFS = -30;
+  long expected_dB_value = target_dBFS + max_volumes[0];
+
+  ResetStubData();
+
+  snd_mixer_find_elem_map[std::string("Left Master")] =
+      reinterpret_cast<snd_mixer_elem_t *>(1);
+  snd_mixer_find_elem_map[std::string("Right Master")] =
+      reinterpret_cast<snd_mixer_elem_t *>(2);
+  snd_mixer_find_elem_map[std::string("Left Speaker")] =
+      reinterpret_cast<snd_mixer_elem_t *>(3);
+  snd_mixer_find_elem_map[std::string("Right Speaker")] =
+      reinterpret_cast<snd_mixer_elem_t *>(4);
+
+  snd_mixer_selem_has_playback_volume_return_values = element_playback_volume;
+  snd_mixer_selem_has_playback_volume_return_values_length =
+      ARRAY_SIZE(element_playback_volume);
+  snd_mixer_selem_has_playback_switch_return_values = element_playback_switches;
+  snd_mixer_selem_has_playback_switch_return_values_length =
+      ARRAY_SIZE(element_playback_switches);
+
+  snd_mixer_selem_get_playback_dB_range_min_values = min_volumes;
+  snd_mixer_selem_get_playback_dB_range_max_values = max_volumes;
+  snd_mixer_selem_get_playback_dB_range_values_length = ARRAY_SIZE(min_volumes);
+
+  c = create_mixer_and_add_controls_by_name_matching(
+      "hw:0", NULL, coupled_controls);
+
+  ASSERT_NE(static_cast<struct cras_alsa_mixer *>(NULL), c);
+  EXPECT_EQ(1, snd_mixer_open_called);
+  EXPECT_EQ(1, snd_mixer_attach_called);
+  EXPECT_EQ(0, strcmp(snd_mixer_attach_mixdev, "hw:0"));
+  EXPECT_EQ(1, snd_mixer_selem_register_called);
+  EXPECT_EQ(1, snd_mixer_load_called);
+  EXPECT_EQ(0, snd_mixer_close_called);
+
+  output_control = c->output_controls;
+  EXPECT_EQ(NULL, output_control->next);
+  c1 = output_control->elements;
+  c2 = c1->next;
+  c3 = c2->next;
+  c4 = c3->next;
+  EXPECT_EQ(c1->elem, reinterpret_cast<snd_mixer_elem_t *>(1));
+  EXPECT_EQ(c2->elem, reinterpret_cast<snd_mixer_elem_t *>(2));
+  EXPECT_EQ(c3->elem, reinterpret_cast<snd_mixer_elem_t *>(3));
+  EXPECT_EQ(c4->elem, reinterpret_cast<snd_mixer_elem_t *>(4));
+  EXPECT_EQ(c4->next, reinterpret_cast<mixer_control_element *>(NULL));
+  EXPECT_EQ(c1->has_volume, 1);
+  EXPECT_EQ(c1->has_mute, 0);
+  EXPECT_EQ(c2->has_volume, 1);
+  EXPECT_EQ(c2->has_mute, 0);
+  EXPECT_EQ(c3->has_volume, 0);
+  EXPECT_EQ(c3->has_mute, 1);
+  EXPECT_EQ(c4->has_volume, 0);
+  EXPECT_EQ(c4->has_mute, 1);
+  EXPECT_EQ(output_control->max_volume_dB, max_volumes[0]);
+  EXPECT_EQ(output_control->min_volume_dB, min_volumes[0]);
+
+  snd_mixer_selem_set_playback_dB_all_values = set_dB_values;
+  snd_mixer_selem_set_playback_dB_all_values_length =
+      ARRAY_SIZE(set_dB_values);
+
+  cras_alsa_mixer_set_dBFS(c, target_dBFS, output_control);
+
+  /* Set volume should set playback dB on two of the coupled controls. */
+  EXPECT_EQ(2, snd_mixer_selem_set_playback_dB_all_called);
+  EXPECT_EQ(set_dB_values[0], expected_dB_value);
+  EXPECT_EQ(set_dB_values[1], expected_dB_value);
+
+  /* Mute should set playback switch on two of the coupled controls. */
+  cras_alsa_mixer_set_mute(c, 1, output_control);
+  EXPECT_EQ(2, snd_mixer_selem_set_playback_switch_all_called);
+  EXPECT_EQ(0, snd_mixer_selem_set_playback_switch_all_value);
+
+  /* Unmute should set playback switch on two of the coupled controls. */
+  cras_alsa_mixer_set_mute(c, 0, output_control);
+  EXPECT_EQ(4, snd_mixer_selem_set_playback_switch_all_called);
+  EXPECT_EQ(1, snd_mixer_selem_set_playback_switch_all_value);
+
+  EXPECT_EQ(max_volumes[0] - min_volumes[0],
+            cras_alsa_mixer_get_output_dB_range(output_control));
+
+  cras_alsa_mixer_destroy(c);
+  EXPECT_EQ(1, snd_mixer_close_called);
+  mixer_name_free(coupled_controls);
+}
+
+TEST(AlsaMixer, CoupledOutputHasMuteNoVolume) {
+  struct cras_alsa_mixer *c;
+  struct mixer_control *output_control;
+  struct mixer_control_element *c1, *c2, *c3, *c4;
+
+  static const long min_volumes[] = {-70};
+  static const long max_volumes[] = {30};
+
+  const char *coupled_output_names[] = {"Left Master",
+                                        "Right Master",
+                                        "Left Speaker",
+                                        "Right Speaker"};
+  struct mixer_name *coupled_controls =
+      mixer_name_add_array(NULL, coupled_output_names,
+                           ARRAY_SIZE(coupled_output_names),
+                           CRAS_STREAM_OUTPUT,
+                           MIXER_NAME_VOLUME);
+  int element_playback_volume[] = {0, 0, 0, 0};
+  int element_playback_switches[] = {0, 0, 1, 1};
+
+  ResetStubData();
+
+  snd_mixer_find_elem_map[std::string("Left Master")] =
+      reinterpret_cast<snd_mixer_elem_t *>(1);
+  snd_mixer_find_elem_map[std::string("Right Master")] =
+      reinterpret_cast<snd_mixer_elem_t *>(2);
+  snd_mixer_find_elem_map[std::string("Left Speaker")] =
+      reinterpret_cast<snd_mixer_elem_t *>(3);
+  snd_mixer_find_elem_map[std::string("Right Speaker")] =
+      reinterpret_cast<snd_mixer_elem_t *>(4);
+
+  snd_mixer_selem_has_playback_volume_return_values = element_playback_volume;
+  snd_mixer_selem_has_playback_volume_return_values_length =
+      ARRAY_SIZE(element_playback_volume);
+  snd_mixer_selem_has_playback_switch_return_values = element_playback_switches;
+  snd_mixer_selem_has_playback_switch_return_values_length =
+      ARRAY_SIZE(element_playback_switches);
+
+  snd_mixer_selem_get_playback_dB_range_min_values = min_volumes;
+  snd_mixer_selem_get_playback_dB_range_max_values = max_volumes;
+  snd_mixer_selem_get_playback_dB_range_values_length = ARRAY_SIZE(min_volumes);
+
+  c = create_mixer_and_add_controls_by_name_matching(
+      "hw:0", NULL, coupled_controls);
+
+  ASSERT_NE(static_cast<struct cras_alsa_mixer *>(NULL), c);
+  EXPECT_EQ(1, snd_mixer_open_called);
+  EXPECT_EQ(1, snd_mixer_attach_called);
+  EXPECT_EQ(0, strcmp(snd_mixer_attach_mixdev, "hw:0"));
+  EXPECT_EQ(1, snd_mixer_selem_register_called);
+  EXPECT_EQ(1, snd_mixer_load_called);
+  EXPECT_EQ(0, snd_mixer_close_called);
+
+  output_control = c->output_controls;
+  EXPECT_EQ(NULL, output_control->next);
+  c1 = output_control->elements;
+  c2 = c1->next;
+  c3 = c2->next;
+  c4 = c3->next;
+  EXPECT_EQ(c1->elem, reinterpret_cast<snd_mixer_elem_t *>(1));
+  EXPECT_EQ(c2->elem, reinterpret_cast<snd_mixer_elem_t *>(2));
+  EXPECT_EQ(c3->elem, reinterpret_cast<snd_mixer_elem_t *>(3));
+  EXPECT_EQ(c4->elem, reinterpret_cast<snd_mixer_elem_t *>(4));
+  EXPECT_EQ(c4->next, reinterpret_cast<mixer_control_element *>(NULL));
+  EXPECT_EQ(c1->has_volume, 0);
+  EXPECT_EQ(c1->has_mute, 0);
+  EXPECT_EQ(c2->has_volume, 0);
+  EXPECT_EQ(c2->has_mute, 0);
+  EXPECT_EQ(c3->has_volume, 0);
+  EXPECT_EQ(c3->has_mute, 1);
+  EXPECT_EQ(c4->has_volume, 0);
+  EXPECT_EQ(c4->has_mute, 1);
+
+  EXPECT_EQ(0, cras_alsa_mixer_has_volume(output_control));
+  EXPECT_EQ(1, output_control->has_mute);
+
+  cras_alsa_mixer_destroy(c);
+  EXPECT_EQ(1, snd_mixer_close_called);
+  mixer_name_free(coupled_controls);
+}
+
+TEST(AlsaMixer, CoupledOutputHasVolumeNoMute) {
+  struct cras_alsa_mixer *c;
+  struct mixer_control *output_control;
+  struct mixer_control_element *c1, *c2, *c3, *c4;
+
+  static const long min_volumes[] = {-70, -70};
+  static const long max_volumes[] = {30, 30};
+
+  const char *coupled_output_names[] = {"Left Master",
+                                        "Right Master",
+                                        "Left Speaker",
+                                        "Right Speaker"};
+  struct mixer_name *coupled_controls =
+      mixer_name_add_array(NULL, coupled_output_names,
+                           ARRAY_SIZE(coupled_output_names),
+                           CRAS_STREAM_OUTPUT,
+                           MIXER_NAME_VOLUME);
+  int element_playback_volume[] = {1, 1, 0, 0};
+  int element_playback_switches[] = {0, 0, 0, 0};
+
+  ResetStubData();
+
+  snd_mixer_find_elem_map[std::string("Left Master")] =
+      reinterpret_cast<snd_mixer_elem_t *>(1);
+  snd_mixer_find_elem_map[std::string("Right Master")] =
+      reinterpret_cast<snd_mixer_elem_t *>(2);
+  snd_mixer_find_elem_map[std::string("Left Speaker")] =
+      reinterpret_cast<snd_mixer_elem_t *>(3);
+  snd_mixer_find_elem_map[std::string("Right Speaker")] =
+      reinterpret_cast<snd_mixer_elem_t *>(4);
+
+  snd_mixer_selem_has_playback_volume_return_values = element_playback_volume;
+  snd_mixer_selem_has_playback_volume_return_values_length =
+      ARRAY_SIZE(element_playback_volume);
+  snd_mixer_selem_has_playback_switch_return_values = element_playback_switches;
+  snd_mixer_selem_has_playback_switch_return_values_length =
+      ARRAY_SIZE(element_playback_switches);
+
+  snd_mixer_selem_get_playback_dB_range_min_values = min_volumes;
+  snd_mixer_selem_get_playback_dB_range_max_values = max_volumes;
+  snd_mixer_selem_get_playback_dB_range_values_length = ARRAY_SIZE(min_volumes);
+
+  c = create_mixer_and_add_controls_by_name_matching(
+      "hw:0", NULL, coupled_controls);
+
+  ASSERT_NE(static_cast<struct cras_alsa_mixer *>(NULL), c);
+  EXPECT_EQ(1, snd_mixer_open_called);
+  EXPECT_EQ(1, snd_mixer_attach_called);
+  EXPECT_EQ(0, strcmp(snd_mixer_attach_mixdev, "hw:0"));
+  EXPECT_EQ(1, snd_mixer_selem_register_called);
+  EXPECT_EQ(1, snd_mixer_load_called);
+  EXPECT_EQ(0, snd_mixer_close_called);
+
+  output_control = c->output_controls;
+  EXPECT_EQ(NULL, output_control->next);
+  c1 = output_control->elements;
+  c2 = c1->next;
+  c3 = c2->next;
+  c4 = c3->next;
+  EXPECT_EQ(c1->elem, reinterpret_cast<snd_mixer_elem_t *>(1));
+  EXPECT_EQ(c2->elem, reinterpret_cast<snd_mixer_elem_t *>(2));
+  EXPECT_EQ(c3->elem, reinterpret_cast<snd_mixer_elem_t *>(3));
+  EXPECT_EQ(c4->elem, reinterpret_cast<snd_mixer_elem_t *>(4));
+  EXPECT_EQ(c4->next, reinterpret_cast<mixer_control_element *>(NULL));
+  EXPECT_EQ(c1->has_volume, 1);
+  EXPECT_EQ(c1->has_mute, 0);
+  EXPECT_EQ(c2->has_volume, 1);
+  EXPECT_EQ(c2->has_mute, 0);
+  EXPECT_EQ(c3->has_volume, 0);
+  EXPECT_EQ(c3->has_mute, 0);
+  EXPECT_EQ(c4->has_volume, 0);
+  EXPECT_EQ(c4->has_mute, 0);
+
+  EXPECT_EQ(1, cras_alsa_mixer_has_volume(output_control));
+  EXPECT_EQ(0, output_control->has_mute);
+
+  cras_alsa_mixer_destroy(c);
+  EXPECT_EQ(1, snd_mixer_close_called);
+  mixer_name_free(coupled_controls);
+}
+
+TEST(AlsaMixer, MixerName) {
+  struct mixer_name *names;
+  struct mixer_name *control;
+  size_t mixer_name_count;
+  static const char *element_names[] = {
+    "Master",
+    "PCM",
+    "Headphone",
+    "Speaker",
+    "HDMI",
+    "IEC958",
+  };
+
+  names = mixer_name_add_array(NULL, element_names,
+                               ARRAY_SIZE(element_names),
+                               CRAS_STREAM_OUTPUT, MIXER_NAME_VOLUME);
+  names = mixer_name_add(names, "Playback",
+                         CRAS_STREAM_OUTPUT, MIXER_NAME_VOLUME);
+  names = mixer_name_add(names, "Main",
+                         CRAS_STREAM_OUTPUT, MIXER_NAME_MAIN_VOLUME);
+  names = mixer_name_add(names, "Mic",
+                         CRAS_STREAM_INPUT, MIXER_NAME_VOLUME);
+  names = mixer_name_add(names, "Capture",
+                         CRAS_STREAM_INPUT, MIXER_NAME_MAIN_VOLUME);
+
+  /* Number of items (test mixer_name_add(_array)). */
+  mixer_name_count = 0;
+  DL_FOREACH(names, control) {
+    mixer_name_count++;
+  }
+  EXPECT_EQ(10, mixer_name_count);
+
+  /* Item not in the list: mismatch direction. */
+  control = mixer_name_find(names, "Main",
+                            CRAS_STREAM_INPUT, MIXER_NAME_UNDEFINED);
+  EXPECT_EQ(1, control == NULL);
+
+  /* Item not in the list: mismatch type. */
+  control = mixer_name_find(names, "Main",
+                            CRAS_STREAM_OUTPUT, MIXER_NAME_VOLUME);
+  EXPECT_EQ(1, control == NULL);
+
+  /* Find by name and direction. */
+  control = mixer_name_find(names, "Main",
+                            CRAS_STREAM_OUTPUT, MIXER_NAME_UNDEFINED);
+  EXPECT_EQ(0, strcmp("Main", control->name));
+
+  /* Find by type and direction. */
+  control = mixer_name_find(names, NULL,
+                            CRAS_STREAM_INPUT, MIXER_NAME_VOLUME);
+  EXPECT_EQ(0, strcmp("Mic", control->name));
+
+  mixer_name_free(names);
+}
+
+class AlsaMixerFullySpeced : public testing::Test {
+ protected:
+  virtual void SetUp() {
+    callback_values_.clear();
+    callback_called_ = 0;
+    static snd_mixer_elem_t *elements[] = {
+      reinterpret_cast<snd_mixer_elem_t *>(1),  // HP-L
+      reinterpret_cast<snd_mixer_elem_t *>(2),  // HP-R
+      reinterpret_cast<snd_mixer_elem_t *>(3),  // SPK-L
+      reinterpret_cast<snd_mixer_elem_t *>(4),  // SPK-R
+      reinterpret_cast<snd_mixer_elem_t *>(5),  // HDMI
+      reinterpret_cast<snd_mixer_elem_t *>(6),  // CAPTURE
+      reinterpret_cast<snd_mixer_elem_t *>(7),  // MIC-L
+      reinterpret_cast<snd_mixer_elem_t *>(8),  // MIC-R
+      reinterpret_cast<snd_mixer_elem_t *>(0),  // Unknown
+    };
+    static int element_playback_volume[] = {
+      1,
+      1,
+      1,
+      1,
+      1,
+      0, 0, 0,
+    };
+    static int element_playback_switches[] = {
+      0,
+      0,
+      0,
+      0,
+      1,
+      0, 0, 0,
+    };
+    static int element_capture_volume[] = {0, 0, 0, 0, 0,
+      0,
+      1,
+      1,
+    };
+    static int element_capture_switches[] = {0, 0, 0, 0, 0,
+      1,
+      0,
+      0,
+    };
+    static const long min_volumes[] = {-84, -84, -84, -84, -84, 0, 0, 0};
+    static const long max_volumes[] = {0, 0, 0, 0, 0, 0, 84, 84};
+    static const char *element_names[] = {
+      "HP-L",
+      "HP-R",
+      "SPK-L",
+      "SPK-R",
+      "HDMI",
+      "CAPTURE",
+      "MIC-L",
+      "MIC-R",
+      "Unknown"
+    };
+    struct ucm_section *sections = NULL;
+    struct ucm_section *section;
+    size_t i;
+
+    ResetStubData();
+
+    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);
+    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");
+    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);
+    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,
+                                 "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);
+    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);
+    ucm_section_set_mixer_name(section, "HDMI");
+    DL_APPEND(sections, section);
+    ASSERT_NE(sections, (struct ucm_section *)NULL);
+
+    snd_mixer_selem_has_playback_volume_return_values =
+        element_playback_volume;
+    snd_mixer_selem_has_playback_volume_return_values_length =
+      ARRAY_SIZE(element_playback_volume);
+    snd_mixer_selem_has_playback_switch_return_values =
+        element_playback_switches;
+    snd_mixer_selem_has_playback_switch_return_values_length =
+      ARRAY_SIZE(element_playback_switches);
+    snd_mixer_selem_has_capture_volume_return_values =
+        element_capture_volume;
+    snd_mixer_selem_has_capture_volume_return_values_length =
+      ARRAY_SIZE(element_capture_volume);
+    snd_mixer_selem_has_capture_switch_return_values =
+        element_capture_switches;
+    snd_mixer_selem_has_capture_switch_return_values_length =
+      ARRAY_SIZE(element_capture_switches);
+    snd_mixer_selem_get_name_return_values = element_names;
+    snd_mixer_selem_get_name_return_values_length = ARRAY_SIZE(element_names);
+    snd_mixer_selem_get_capture_dB_range_min_values = min_volumes;
+    snd_mixer_selem_get_capture_dB_range_max_values = max_volumes;
+    snd_mixer_selem_get_capture_dB_range_values_length =
+        ARRAY_SIZE(min_volumes);
+
+    cras_mixer_ = cras_alsa_mixer_create("hw:0");
+    ASSERT_NE(static_cast<struct cras_alsa_mixer *>(NULL), cras_mixer_);
+    EXPECT_EQ(1, snd_mixer_open_called);
+    EXPECT_EQ(1, snd_mixer_attach_called);
+    EXPECT_EQ(0, strcmp(snd_mixer_attach_mixdev, "hw:0"));
+    EXPECT_EQ(1, snd_mixer_selem_register_called);
+    EXPECT_EQ(1, snd_mixer_load_called);
+    EXPECT_EQ(0, snd_mixer_close_called);
+
+    section = sections;
+    EXPECT_EQ(-ENOENT,\
+              cras_alsa_mixer_add_controls_in_section(cras_mixer_, section));
+    ASSERT_NE((struct ucm_section *)NULL, section->next);
+    section = section->next;
+    EXPECT_EQ(0, cras_alsa_mixer_add_controls_in_section(cras_mixer_, section));
+    ASSERT_NE((struct ucm_section *)NULL, section->next);
+    section = section->next;
+    EXPECT_EQ(0, cras_alsa_mixer_add_controls_in_section(cras_mixer_, section));
+    ASSERT_NE((struct ucm_section *)NULL, section->next);
+    section = section->next;
+    EXPECT_EQ(0, cras_alsa_mixer_add_controls_in_section(cras_mixer_, section));
+    ASSERT_NE((struct ucm_section *)NULL, section->next);
+    section = section->next;
+    EXPECT_EQ(0, cras_alsa_mixer_add_controls_in_section(cras_mixer_, section));
+    ASSERT_NE((struct ucm_section *)NULL, section->next);
+    section = section->next;
+    EXPECT_EQ(0, cras_alsa_mixer_add_controls_in_section(cras_mixer_, section));
+    EXPECT_EQ(section->next, (struct ucm_section*)NULL);
+
+    EXPECT_EQ(9, snd_mixer_find_selem_called);
+    EXPECT_EQ(5, snd_mixer_selem_has_playback_volume_called);
+    EXPECT_EQ(5, snd_mixer_selem_has_playback_switch_called);
+    EXPECT_EQ(3, snd_mixer_selem_has_capture_volume_called);
+    EXPECT_EQ(3, snd_mixer_selem_has_capture_switch_called);
+    EXPECT_EQ(5, snd_mixer_selem_get_playback_dB_range_called);
+    EXPECT_EQ(2, snd_mixer_selem_get_capture_dB_range_called);
+
+    sections_ = sections;
+  }
+
+  virtual void TearDown() {
+    ucm_section_free_list(sections_);
+    cras_alsa_mixer_destroy(cras_mixer_);
+    EXPECT_EQ(1, snd_mixer_close_called);
+  }
+
+  static void Callback(struct mixer_control *control, void *arg) {
+    callback_called_++;
+    callback_values_.push_back(control);
+  }
+
+  struct cras_alsa_mixer *cras_mixer_;
+  static size_t callback_called_;
+  static std::vector<struct mixer_control *> callback_values_;
+  struct ucm_section *sections_;
+};
+
+size_t AlsaMixerFullySpeced::callback_called_;
+std::vector<struct mixer_control *> AlsaMixerFullySpeced::callback_values_;
+
+TEST_F(AlsaMixerFullySpeced, CheckControlCounts) {
+  cras_alsa_mixer_list_outputs(cras_mixer_,
+                               AlsaMixerFullySpeced::Callback,
+                               reinterpret_cast<void*>(555));
+  EXPECT_EQ(3, callback_called_);
+  callback_called_ = 0;
+  cras_alsa_mixer_list_inputs(cras_mixer_,
+                               AlsaMixerFullySpeced::Callback,
+                               reinterpret_cast<void*>(555));
+  EXPECT_EQ(2, callback_called_);
+}
+
+TEST_F(AlsaMixerFullySpeced, CheckFindOutputByNameNoMatch) {
+  struct mixer_control *out;
+
+  out = cras_alsa_mixer_get_output_matching_name(cras_mixer_,
+                                                 "AAAAA Jack");
+  EXPECT_EQ(static_cast<struct mixer_control *>(NULL), out);
+}
+
+TEST_F(AlsaMixerFullySpeced, CheckFindOutputByName) {
+  struct mixer_control *out;
+
+  out = cras_alsa_mixer_get_output_matching_name(cras_mixer_,
+                                                 "Headphone Jack");
+  EXPECT_NE(static_cast<struct mixer_control *>(NULL), out);
+}
+
+TEST_F(AlsaMixerFullySpeced, CheckFindControlForSection) {
+  struct mixer_control *control;
+  struct ucm_section *section = sections_;
+
+  // Look for the control for the Headphone section.
+  // We've already asserted that section != NULL above.
+  // Matching the control created by CoupledMixers.
+  section = section->next;
+  control = cras_alsa_mixer_get_control_for_section(cras_mixer_, section);
+  ASSERT_NE(static_cast<struct mixer_control *>(NULL), control);
+  EXPECT_EQ(0, strcmp(control->name, "Headphone"));
+
+  // Look for the control for the Mic section.
+  // Matching the control created by MixerName.
+  section = section->next->next;
+  control = cras_alsa_mixer_get_control_for_section(cras_mixer_, section);
+  ASSERT_NE(static_cast<struct mixer_control *>(NULL), control);
+  EXPECT_EQ(0, strcmp(control->name, "CAPTURE"));
+}
+
 /* Stubs */
 
 extern "C" {
@@ -1010,6 +1686,19 @@
   return 0;
 }
 
+snd_mixer_elem_t *snd_mixer_find_selem(
+    snd_mixer_t *mixer, const snd_mixer_selem_id_t *id) {
+  std::string name(snd_mixer_selem_id_get_name(id));
+  unsigned int index = snd_mixer_selem_id_get_index(id);
+  snd_mixer_find_selem_called++;
+  if (index != 0)
+    return NULL;
+  if (snd_mixer_find_elem_map.find(name) == snd_mixer_find_elem_map.end()) {
+    return NULL;
+  }
+  return snd_mixer_find_elem_map[name];
+}
+
 //  From cras_volume_curve.
 static long get_dBFS_default(const struct cras_volume_curve *curve,
 			     size_t volume)
@@ -1017,6 +1706,15 @@
   return 100 * (volume - 100);
 }
 
+struct cras_volume_curve *cras_volume_curve_create_default()
+{
+  struct cras_volume_curve *curve;
+  curve = (struct cras_volume_curve *)calloc(1, sizeof(*curve));
+  if (curve)
+    curve->get_dBFS = get_dBFS_default;
+  return curve;
+}
+
 void cras_volume_curve_destroy(struct cras_volume_curve *curve)
 {
   cras_volume_curve_destroy_called++;
@@ -1030,7 +1728,6 @@
 {
   struct cras_volume_curve *curve;
   curve = (struct cras_volume_curve *)calloc(1, sizeof(*curve));
-  cras_card_config_get_volume_curve_for_control_called++;
   if (curve != NULL)
     curve->get_dBFS = get_dBFS_default;
   return curve;
@@ -1042,5 +1739,6 @@
 
 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/alsa_ucm_unittest.cc b/cras/src/tests/alsa_ucm_unittest.cc
index 212b31a..822ef9e 100644
--- a/cras/src/tests/alsa_ucm_unittest.cc
+++ b/cras/src/tests/alsa_ucm_unittest.cc
@@ -5,11 +5,15 @@
 #include <gtest/gtest.h>
 #include <iniparser.h>
 #include <stdio.h>
+#include <syslog.h>
 #include <map>
 
 extern "C" {
 #include "cras_alsa_ucm.h"
 #include "cras_types.h"
+#include "cras_util.h"
+#include "utlist.h"
+#include "cras_util.h"
 
 //  Include C file to test static functions.
 #include "cras_alsa_ucm.c"
@@ -23,7 +27,6 @@
 static unsigned snd_use_case_mgr_close_called;
 static unsigned snd_use_case_get_called;
 static std::vector<std::string> snd_use_case_get_id;
-static std::map<std::string, int> snd_use_case_get_ret_value;
 static int snd_use_case_set_return;
 static std::map<std::string, std::string> snd_use_case_get_value;
 static unsigned snd_use_case_set_called;
@@ -31,6 +34,10 @@
 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 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;
+static const char *avail_verbs[] = { "HiFi", "Comment for Verb1" };
 
 static void ResetStubData() {
   snd_use_case_mgr_open_called = 0;
@@ -43,9 +50,45 @@
   snd_use_case_free_list_called = 0;
   snd_use_case_get_id.clear();
   snd_use_case_get_value.clear();
-  snd_use_case_get_ret_value.clear();
   fake_list.clear();
   fake_list_size.clear();
+  fake_list["_verbs"] = avail_verbs;
+  fake_list_size["_verbs"] = 2;
+  list_devices_callback_names.clear();
+  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;
+}
+
+static void list_devices_callback(const char* section_name, void *arg) {
+  list_devices_callback_names.push_back(std::string(section_name));
+  list_devices_callback_args.push_back(arg);
+}
+
+static void SetSectionDeviceData() {
+  static const char *sections[] = { "Speaker", "Comment for Dev1",
+                                    "IntMic", "Comment for Dev2",
+                                    "Headphone", "Comment for Dev3",
+                                    "ExtMic", "Comment for Dev4",
+                                    "HDMI", "Comment for Dev5"};
+  fake_list["_devices/HiFi"] = sections;
+  fake_list_size["_devices/HiFi"] = 10;
+  std::string id_1 = "=PlaybackPCM/Speaker/HiFi";
+  std::string id_2 = "=CapturePCM/IntMic/HiFi";
+  std::string id_3 = "=PlaybackPCM/Headphone/HiFi";
+  std::string id_4 = "=CapturePCM/ExtMic/HiFi";
+  std::string id_5 = "=PlaybackPCM/HDMI/HiFi";
+  std::string value_1 = "test_card:0";
+  std::string value_2 = "test_card:0";
+  std::string value_3 = "test_card:0";
+  std::string value_4 = "test_card:0";
+  std::string value_5 = "test_card:1";
+
+  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;
+  snd_use_case_get_value[id_4] = value_4;
+  snd_use_case_get_value[id_5] = value_5;
 }
 
 TEST(AlsaUcm, CreateFailInvalidCard) {
@@ -71,13 +114,12 @@
 }
 
 TEST(AlsaUcm, CreateSuccess) {
-  snd_use_case_mgr_t* mgr;
+  struct cras_use_case_mgr *mgr;
 
   ResetStubData();
-  snd_use_case_mgr_open_mgr_ptr = reinterpret_cast<snd_use_case_mgr_t*>(0x55);
 
   mgr = ucm_create("foo");
-  EXPECT_NE(static_cast<snd_use_case_mgr_t*>(NULL), mgr);
+  EXPECT_NE(static_cast<snd_use_case_mgr_t*>(NULL), mgr->mgr);
   EXPECT_EQ(1, snd_use_case_mgr_open_called);
   EXPECT_EQ(1, snd_use_case_set_called);
   EXPECT_EQ(0, snd_use_case_mgr_close_called);
@@ -87,7 +129,7 @@
 }
 
 TEST(AlsaUcm, CheckEnabledEmptyList) {
-  snd_use_case_mgr_t* mgr = reinterpret_cast<snd_use_case_mgr_t*>(0x55);
+  struct cras_use_case_mgr *mgr = &cras_ucm_mgr;
 
   ResetStubData();
   fake_list["_enadevs"] = NULL;
@@ -103,7 +145,7 @@
 }
 
 TEST(AlsaUcm, CheckEnabledAlready) {
-  snd_use_case_mgr_t* mgr = reinterpret_cast<snd_use_case_mgr_t*>(0x55);
+  struct cras_use_case_mgr *mgr = &cras_ucm_mgr;
   const char *enabled[] = { "Dev2", "Dev1" };
 
   ResetStubData();
@@ -121,7 +163,7 @@
 }
 
 TEST(AlsaUcm, GetEdidForDev) {
-  snd_use_case_mgr_t* mgr = reinterpret_cast<snd_use_case_mgr_t*>(0x55);
+  struct cras_use_case_mgr *mgr = &cras_ucm_mgr;
   std::string id = "=EDIDFile/Dev1/HiFi";
   std::string value = "EdidFileName";
   const char *file_name;
@@ -129,7 +171,6 @@
   ResetStubData();
 
   snd_use_case_get_value[id] = value;
-  snd_use_case_get_ret_value[id] = 0;
 
   file_name = ucm_get_edid_file_for_dev(mgr, "Dev1");
   ASSERT_TRUE(file_name);
@@ -141,7 +182,7 @@
 }
 
 TEST(AlsaUcm, GetCapControlForDev) {
-  snd_use_case_mgr_t* mgr = reinterpret_cast<snd_use_case_mgr_t*>(0x55);
+  struct cras_use_case_mgr *mgr = &cras_ucm_mgr;
   char *cap_control;
   std::string id = "=CaptureControl/Dev1/HiFi";
   std::string value = "MIC";
@@ -149,7 +190,6 @@
   ResetStubData();
 
   snd_use_case_get_value[id] = value;
-  snd_use_case_get_ret_value[id] = 0;
 
   cap_control = ucm_get_cap_control(mgr, "Dev1");
   ASSERT_TRUE(cap_control);
@@ -161,7 +201,7 @@
 }
 
 TEST(AlsaUcm, GetOverrideType) {
-  snd_use_case_mgr_t* mgr = reinterpret_cast<snd_use_case_mgr_t*>(0x55);
+  struct cras_use_case_mgr *mgr = &cras_ucm_mgr;
   const char *override_type_name;
   std::string id = "=OverrideNodeType/Dev1/HiFi";
   std::string value = "HDMI";
@@ -169,7 +209,6 @@
   ResetStubData();
 
   snd_use_case_get_value[id] = value;
-  snd_use_case_get_ret_value[id] = 0;
 
   override_type_name = ucm_get_override_type_name(mgr, "Dev1");
   ASSERT_TRUE(override_type_name);
@@ -180,40 +219,49 @@
   EXPECT_EQ(snd_use_case_get_id[0], id);
 }
 
-TEST(AlsaUcm, GetSectionForVar) {
-  snd_use_case_mgr_t* mgr = reinterpret_cast<snd_use_case_mgr_t*>(0x55);
-  const char *section_name;
+TEST(AlsaUcm, GetSectionsForVar) {
+  struct cras_use_case_mgr *mgr = &cras_ucm_mgr;
+  struct section_name *section_names, *c;
 
   ResetStubData();
 
-  const char *sections[] = { "Sec1", "Comment for Sec1", "Sec2",
-                             "Comment for Sec2" };
+  const char *sections[] = { "Sec1", "Comment for Sec1",
+                             "Sec2", "Comment for Sec2",
+                             "Sec3", "Comment for Sec3"};
   fake_list["Identifier"] = sections;
-  fake_list_size["Identifier"] = 4;
+  fake_list_size["Identifier"] = 6;
   std::string id_1 = "=Var/Sec1/HiFi";
   std::string id_2 = "=Var/Sec2/HiFi";
+  std::string id_3 = "=Var/Sec3/HiFi";
   std::string value_1 = "Value1";
   std::string value_2 = "Value2";
+  std::string value_3 = "Value2";
 
-  snd_use_case_get_ret_value[id_1] = 0;
   snd_use_case_get_value[id_1] = value_1;
-  snd_use_case_get_ret_value[id_2] = 0;
   snd_use_case_get_value[id_2] = value_2;
+  snd_use_case_get_value[id_3] = value_3;
 
-  section_name = ucm_get_section_for_var(mgr, "Var", "Value2", "Identifier",
-                                         CRAS_STREAM_OUTPUT);
+  section_names = ucm_get_sections_for_var(mgr, "Var", "Value2", "Identifier",
+                                           CRAS_STREAM_OUTPUT);
 
-  ASSERT_TRUE(section_name);
-  EXPECT_EQ(0, strcmp(section_name, "Sec2"));
-  free((void*)section_name);
+  ASSERT_TRUE(section_names);
+  EXPECT_EQ(0, strcmp(section_names->name, "Sec2"));
+  EXPECT_EQ(0, strcmp(section_names->next->name, "Sec3"));
 
-  ASSERT_EQ(2, snd_use_case_get_called);
+  ASSERT_EQ(3, 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);
+  EXPECT_EQ(snd_use_case_get_id[2], id_3);
+
+  DL_FOREACH(section_names, c) {
+    DL_DELETE(section_names, c);
+    free((void*)c->name);
+    free(c);
+  }
 }
 
 TEST(AlsaUcm, GetDevForJack) {
-  snd_use_case_mgr_t* mgr = reinterpret_cast<snd_use_case_mgr_t*>(0x55);
+  struct cras_use_case_mgr *mgr = &cras_ucm_mgr;
   const char *dev_name;
   const char *devices[] = { "Dev1", "Comment for Dev1", "Dev2",
                             "Comment for Dev2" };
@@ -227,9 +275,7 @@
   std::string value_1 = "Value1";
   std::string value_2 = "Value2";
 
-  snd_use_case_get_ret_value[id_1] = 0;
   snd_use_case_get_value[id_1] = value_1;
-  snd_use_case_get_ret_value[id_2] = 0;
   snd_use_case_get_value[id_2] = value_2;
   dev_name = ucm_get_dev_for_jack(mgr, value_2.c_str(), CRAS_STREAM_OUTPUT);
   ASSERT_TRUE(dev_name);
@@ -241,8 +287,90 @@
   EXPECT_EQ(snd_use_case_get_id[1], id_2);
 }
 
+TEST(AlsaUcm, GetDevForHeadphoneJack) {
+  struct cras_use_case_mgr *mgr = &cras_ucm_mgr;
+  const char *dev_name;
+  const char *devices[] = { "Mic", "Comment for Dev1", "Headphone",
+                            "Comment for Dev2" };
+
+  ResetStubData();
+
+  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 value = "JackValue";
+
+  snd_use_case_get_value[id_1] = value;
+  snd_use_case_get_value[id_2] = value;
+
+  /* Looking for jack with matched value with output direction, Headphone will
+   * be found even though Mic section has the matched value too. */
+  dev_name = ucm_get_dev_for_jack(mgr, value.c_str(), CRAS_STREAM_OUTPUT);
+
+  ASSERT_TRUE(dev_name);
+  EXPECT_EQ(0, strcmp(dev_name, "Headphone"));
+  free((void*)dev_name);
+}
+
+TEST(AlsaUcm, GetDevForMicJack) {
+  struct cras_use_case_mgr *mgr = &cras_ucm_mgr;
+  const char *dev_name;
+  const char *devices[] = { "Headphone", "Comment for Dev1", "Mic",
+                            "Comment for Dev2" };
+
+  ResetStubData();
+
+  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 value = "JackValue";
+
+  snd_use_case_get_value[id_1] = value;
+  snd_use_case_get_value[id_2] = value;
+
+  /* Looking for jack with matched value with input direction, Mic will be found
+   * even though Headphone section has the matched value too. */
+  dev_name = ucm_get_dev_for_jack(mgr, value.c_str(), CRAS_STREAM_INPUT);
+
+  ASSERT_TRUE(dev_name);
+  EXPECT_EQ(0, strcmp(dev_name, "Mic"));
+  free((void*)dev_name);
+}
+
+TEST(AlsaUcm, GetDevForMixer) {
+  struct cras_use_case_mgr *mgr = &cras_ucm_mgr;
+  const char *dev_name_out, *dev_name_in;
+  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 = "=MixerName/Dev1/HiFi";
+  std::string id_2 = "=MixerName/Dev2/HiFi";
+  std::string value_1 = "Value1";
+  std::string value_2 = "Value2";
+
+  snd_use_case_get_value[id_1] = value_1;
+  snd_use_case_get_value[id_2] = value_2;
+  dev_name_out = ucm_get_dev_for_mixer(
+      mgr, value_1.c_str(), CRAS_STREAM_OUTPUT);
+  dev_name_in = ucm_get_dev_for_mixer(mgr, value_2.c_str(), CRAS_STREAM_INPUT);
+
+  ASSERT_TRUE(dev_name_out);
+  EXPECT_EQ(0, strcmp(dev_name_out, "Dev1"));
+  free((void*)dev_name_out);
+
+  ASSERT_TRUE(dev_name_in);
+  EXPECT_EQ(0, strcmp(dev_name_in, "Dev2"));
+  free((void*)dev_name_in);
+}
+
 TEST(AlsaUcm, GetDeviceNameForDevice) {
-  snd_use_case_mgr_t* mgr = reinterpret_cast<snd_use_case_mgr_t*>(0x55);
+  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" };
@@ -256,9 +384,7 @@
   std::string value_1 = "DeviceName1";
   std::string value_2 = "DeviceName2";
 
-  snd_use_case_get_ret_value[id_1] = 0;
   snd_use_case_get_value[id_1] = value_1;
-  snd_use_case_get_ret_value[id_2] = 0;
   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);
@@ -272,8 +398,117 @@
   EXPECT_EQ(snd_use_case_get_id[1], id_2);
 }
 
+TEST(AlsaUcm, GetDeviceRateForDevice) {
+  struct cras_use_case_mgr *mgr = &cras_ucm_mgr;
+  int input_dev_rate, output_dev_rate;
+  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 = "=CaptureRate/Dev1/HiFi";
+  std::string id_2 = "=PlaybackRate/Dev2/HiFi";
+  std::string value_1 = "44100";
+  std::string value_2 = "48000";
+
+  snd_use_case_get_value[id_1] = value_1;
+  snd_use_case_get_value[id_2] = value_2;
+  input_dev_rate = ucm_get_sample_rate_for_dev(mgr, "Dev1", CRAS_STREAM_INPUT);
+  output_dev_rate = ucm_get_sample_rate_for_dev(mgr, "Dev2",
+						CRAS_STREAM_OUTPUT);
+  EXPECT_EQ(44100, input_dev_rate);
+  EXPECT_EQ(48000, output_dev_rate);
+
+  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];
+  int rc;
+
+  ResetStubData();
+
+  std::string id_1 = "=CaptureChannelMap/Dev1/HiFi";
+  std::string value_1 = "-1 -1 0 1 -1 -1 -1 -1 -1 -1 -1";
+
+  snd_use_case_get_value[id_1] = value_1;
+  rc = ucm_get_capture_chmap_for_dev(mgr, "Dev1", channel_layout);
+
+  EXPECT_EQ(0, rc);
+
+  ASSERT_EQ(1, snd_use_case_get_called);
+  EXPECT_EQ(snd_use_case_get_id[0], id_1);
+  EXPECT_EQ(channel_layout[0], -1);
+  EXPECT_EQ(channel_layout[1], -1);
+  EXPECT_EQ(channel_layout[2], 0);
+  EXPECT_EQ(channel_layout[3], 1);
+  EXPECT_EQ(channel_layout[4], -1);
+  EXPECT_EQ(channel_layout[5], -1);
+  EXPECT_EQ(channel_layout[6], -1);
+  EXPECT_EQ(channel_layout[7], -1);
+  EXPECT_EQ(channel_layout[8], -1);
+  EXPECT_EQ(channel_layout[9], -1);
+  EXPECT_EQ(channel_layout[10], -1);
+}
+
+TEST(AlsaUcm, GetHotwordModels) {
+  struct cras_use_case_mgr *mgr = &cras_ucm_mgr;
+  const char *models;
+  const char *modifiers[] = { "Mod1",
+                            "Comment1",
+                            "Hotword Model en",
+                            "Comment2",
+                            "Hotword Model jp",
+                            "Comment3",
+                            "Mod2",
+                            "Comment4",
+                            "Hotword Model de",
+                            "Comment5" };
+  ResetStubData();
+
+  fake_list["_modifiers/HiFi"] = modifiers;
+  fake_list_size["_modifiers/HiFi"] = 10;
+
+  models = ucm_get_hotword_models(mgr);
+  ASSERT_TRUE(models);
+  EXPECT_EQ(0, strcmp(models, "en,jp,de"));
+}
+
+TEST(AlsaUcm, SetHotwordModel) {
+  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;
+
+  EXPECT_EQ(-EINVAL, ucm_set_hotword_model(mgr, "zh"));
+  EXPECT_EQ(0, snd_use_case_set_called);
+
+  fake_list["_enamods"] = enabled_mods;
+  fake_list_size["_enamods"] = 1;
+  ucm_set_hotword_model(mgr, "jp");
+
+  EXPECT_EQ(2, snd_use_case_set_called);
+  EXPECT_EQ(snd_use_case_set_param[0],
+      std::make_pair(std::string("_dismod"), std::string("Hotword Model en")));
+  EXPECT_EQ(snd_use_case_set_param[1],
+      std::make_pair(std::string("_enamod"), std::string("Hotword Model jp")));
+}
+
 TEST(AlsaUcm, SwapModeExists) {
-  snd_use_case_mgr_t* mgr = reinterpret_cast<snd_use_case_mgr_t*>(0x55);
+  struct cras_use_case_mgr *mgr = &cras_ucm_mgr;
   int rc;
   const char *modifiers_1[] = { "Speaker Swap Mode",
                                 "Comment for Speaker Swap Mode",
@@ -298,7 +533,7 @@
 }
 
 TEST(AlsaUcm, EnableSwapMode) {
-  snd_use_case_mgr_t* mgr = reinterpret_cast<snd_use_case_mgr_t*>(0x55);
+  struct cras_use_case_mgr *mgr = &cras_ucm_mgr;
   int rc;
   const char *modifiers[] = { "Speaker Swap Mode",
                               "Comment for Speaker Swap Mode",
@@ -330,7 +565,7 @@
 }
 
 TEST(AlsaUcm, DisableSwapMode) {
-  snd_use_case_mgr_t* mgr = reinterpret_cast<snd_use_case_mgr_t*>(0x55);
+  struct cras_use_case_mgr *mgr = &cras_ucm_mgr;
   int rc;
   const char *modifiers[] = { "Speaker Swap Mode",
                               "Comment for Speaker Swap Mode",
@@ -363,7 +598,7 @@
 }
 
 TEST(AlsaFlag, GetFlag) {
-  snd_use_case_mgr_t* mgr = reinterpret_cast<snd_use_case_mgr_t*>(0x55);
+  struct cras_use_case_mgr *mgr = &cras_ucm_mgr;
   char *flag_value;
 
   std::string id = "=FlagName//HiFi";
@@ -382,7 +617,7 @@
 }
 
 TEST(AlsaUcm, ModifierEnabled) {
-  snd_use_case_mgr_t* mgr = reinterpret_cast<snd_use_case_mgr_t*>(0x55);
+  struct cras_use_case_mgr *mgr = &cras_ucm_mgr;
   int enabled;
 
   ResetStubData();
@@ -400,7 +635,7 @@
 }
 
 TEST(AlsaUcm, SetModifierEnabled) {
-  snd_use_case_mgr_t* mgr = reinterpret_cast<snd_use_case_mgr_t*>(0x55);
+  struct cras_use_case_mgr *mgr = &cras_ucm_mgr;
 
   ResetStubData();
 
@@ -421,7 +656,7 @@
 }
 
 TEST(AlsaUcm, SectionExistsWithName) {
-  snd_use_case_mgr_t* mgr = reinterpret_cast<snd_use_case_mgr_t*>(0x55);
+  struct cras_use_case_mgr *mgr = &cras_ucm_mgr;
   const char *sections[] = { "Sec1", "Comment for Sec1", "Sec2",
                              "Comment for Sec2" };
 
@@ -435,7 +670,7 @@
 }
 
 TEST(AlsaUcm, SectionExistsWithSuffix) {
-  snd_use_case_mgr_t* mgr = reinterpret_cast<snd_use_case_mgr_t*>(0x55);
+  struct cras_use_case_mgr *mgr = &cras_ucm_mgr;
 
   ResetStubData();
 
@@ -449,7 +684,7 @@
 }
 
 TEST(AlsaUcm, DisableSoftwareVolume) {
-  snd_use_case_mgr_t* mgr = reinterpret_cast<snd_use_case_mgr_t*>(0x55);
+  struct cras_use_case_mgr *mgr = &cras_ucm_mgr;
   unsigned int disable_software_volume;
   std::string id = "=DisableSoftwareVolume//HiFi";
   std::string value = "1";
@@ -457,7 +692,6 @@
   ResetStubData();
 
   snd_use_case_get_value[id] = value;
-  snd_use_case_get_ret_value[id] = 0;
 
   disable_software_volume = ucm_get_disable_software_volume(mgr);
   ASSERT_TRUE(disable_software_volume);
@@ -466,6 +700,697 @@
   EXPECT_EQ(snd_use_case_get_id[0], id);
 }
 
+TEST(AlsaUcm, GetCoupledMixersForDevice) {
+  struct cras_use_case_mgr *mgr = &cras_ucm_mgr;
+  struct mixer_name *mixer_names_1, *mixer_names_2, *c;
+  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 = "=CoupledMixers/Dev1/HiFi";
+  std::string value_1 = "Mixer Name1,Mixer Name2,Mixer Name3";
+  std::string id_2 = "=CoupledMixers/Dev2/HiFi";
+  std::string value_2 = "";
+  snd_use_case_get_value[id_1] = value_1;
+  snd_use_case_get_value[id_2] = value_2;
+  mixer_names_1 = ucm_get_coupled_mixer_names(mgr, "Dev1");
+  mixer_names_2 = ucm_get_coupled_mixer_names(mgr, "Dev2");
+
+  ASSERT_TRUE(mixer_names_1);
+  EXPECT_EQ(0, strcmp(mixer_names_1->name, "Mixer Name1"));
+  EXPECT_EQ(0, strcmp(mixer_names_1->next->name, "Mixer Name2"));
+  EXPECT_EQ(0, strcmp(mixer_names_1->next->next->name, "Mixer Name3"));
+  EXPECT_EQ(NULL, mixer_names_1->next->next->next);
+
+  EXPECT_EQ(NULL, mixer_names_2);
+
+  DL_FOREACH(mixer_names_1, c) {
+    DL_DELETE(mixer_names_1, c);
+    free((void*)c->name);
+    free(c);
+  }
+}
+
+TEST(AlsaUcm, FreeMixerNames) {
+  struct cras_use_case_mgr *mgr = &cras_ucm_mgr;
+  struct mixer_name *mixer_names_1;
+  const char *devices[] = { "Dev1", "Comment for Dev1"};
+
+  ResetStubData();
+
+  fake_list["_devices/HiFi"] = devices;
+  fake_list_size["_devices/HiFi"] = 2;
+  std::string id_1 = "=CoupledMixers/Dev1/HiFi";
+  std::string value_1 = "Mixer Name1,Mixer Name2,Mixer Name3";
+  snd_use_case_get_value[id_1] = value_1;
+  mixer_names_1 = ucm_get_coupled_mixer_names(mgr, "Dev1");
+
+
+  ASSERT_TRUE(mixer_names_1);
+  EXPECT_EQ(0, strcmp(mixer_names_1->name, "Mixer Name1"));
+  EXPECT_EQ(0, strcmp(mixer_names_1->next->name, "Mixer Name2"));
+  EXPECT_EQ(0, strcmp(mixer_names_1->next->next->name, "Mixer Name3"));
+  EXPECT_EQ(NULL, mixer_names_1->next->next->next);
+
+  /* No way to actually check if memory is freed. */
+  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, DefaultNodeGain) {
+  struct cras_use_case_mgr *mgr = &cras_ucm_mgr;
+  long default_node_gain;
+  int ret;
+  std::string id = "=DefaultNodeGain/Internal Mic/HiFi";
+  std::string value = "-2000";
+
+  ResetStubData();
+
+  /* Value can be found in UCM. */
+  snd_use_case_get_value[id] = value;
+
+  ret = ucm_get_default_node_gain(mgr, "Internal Mic", &default_node_gain);
+
+  EXPECT_EQ(0, ret);
+  EXPECT_EQ(-2000, default_node_gain);
+
+  ResetStubData();
+
+  /* Value can not be found in UCM. */
+  ret = ucm_get_default_node_gain(mgr, "Internal Mic", &default_node_gain);
+
+  ASSERT_TRUE(ret);
+}
+
+TEST(AlsaUcm, UseFullySpecifiedUCMConfig) {
+  struct cras_use_case_mgr *mgr = &cras_ucm_mgr;
+  int fully_specified_flag;
+
+  std::string id = "=FullySpecifiedUCM//HiFi";
+  ResetStubData();
+
+  /* Flag is not set */
+  fully_specified_flag = ucm_has_fully_specified_ucm_flag(mgr);
+  ASSERT_FALSE(fully_specified_flag);
+
+  /* Flag is set to "1". */
+  snd_use_case_get_value[id] = std::string("1");
+  fully_specified_flag = ucm_has_fully_specified_ucm_flag(mgr);
+  ASSERT_TRUE(fully_specified_flag);
+
+  /* Flag is set to "0". */
+  snd_use_case_get_value[id] = std::string("0");
+  fully_specified_flag = ucm_has_fully_specified_ucm_flag(mgr);
+  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;
+  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 = "=MixerName/Dev1/HiFi";
+  std::string id_2 = "=MixerName/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");
+
+  EXPECT_EQ(0, strcmp(mixer_name_1, value_1.c_str()));
+  EXPECT_EQ(0, strcmp(mixer_name_2, value_2.c_str()));
+}
+
+TEST(AlsaUcm, GetMainVolumeMixerName) {
+  struct cras_use_case_mgr *mgr = &cras_ucm_mgr;
+  struct mixer_name *mixer_names_1, *mixer_names_2, *c;
+
+  ResetStubData();
+
+  std::string id = "=MainVolumeNames//HiFi";
+  std::string value_1 = "Mixer Name1,Mixer Name2,Mixer Name3";
+
+  snd_use_case_get_value[id] = value_1;
+  mixer_names_1 = ucm_get_main_volume_names(mgr);
+
+  ResetStubData();
+
+  /* Can not find MainVolumeNames */
+  mixer_names_2 = ucm_get_main_volume_names(mgr);
+
+  ASSERT_TRUE(mixer_names_1);
+  EXPECT_EQ(0, strcmp(mixer_names_1->name, "Mixer Name1"));
+  EXPECT_EQ(0, strcmp(mixer_names_1->next->name, "Mixer Name2"));
+  EXPECT_EQ(0, strcmp(mixer_names_1->next->next->name, "Mixer Name3"));
+  EXPECT_EQ(NULL, mixer_names_1->next->next->next);
+
+  DL_FOREACH(mixer_names_1, c) {
+    DL_DELETE(mixer_names_1, c);
+    free((void*)c->name);
+    free(c);
+  }
+
+  EXPECT_EQ(NULL, mixer_names_2);
+}
+
+TEST(AlsaUcm, ListSectionsByDeviceNameOutput) {
+  struct cras_use_case_mgr *mgr = &cras_ucm_mgr;
+  void* callback_arg = reinterpret_cast<void*>(0x56);
+  int listed = 0;
+
+  ResetStubData();
+  SetSectionDeviceData();
+
+  listed = ucm_list_section_devices_by_device_name(
+      mgr, CRAS_STREAM_OUTPUT, "test_card:0", list_devices_callback,
+      callback_arg);
+
+  EXPECT_EQ(2, listed);
+  EXPECT_EQ(2, list_devices_callback_names.size());
+  EXPECT_EQ(2, list_devices_callback_args.size());
+
+  EXPECT_EQ(
+      0, strcmp(list_devices_callback_names[0].c_str(), "Speaker"));
+  EXPECT_EQ(callback_arg, list_devices_callback_args[0]);
+
+  EXPECT_EQ(
+      0, strcmp(list_devices_callback_names[1].c_str(), "Headphone"));
+  EXPECT_EQ(callback_arg, list_devices_callback_args[1]);
+}
+
+TEST(AlsaUcm, ListSectionsByDeviceNameInput) {
+  struct cras_use_case_mgr *mgr = &cras_ucm_mgr;
+  void* callback_arg = reinterpret_cast<void*>(0x56);
+  int listed = 0;
+
+  ResetStubData();
+  SetSectionDeviceData();
+
+  listed = ucm_list_section_devices_by_device_name(
+      mgr, CRAS_STREAM_INPUT, "test_card:0", list_devices_callback,
+      callback_arg);
+
+  EXPECT_EQ(2, listed);
+  EXPECT_EQ(2, list_devices_callback_names.size());
+  EXPECT_EQ(2, list_devices_callback_args.size());
+
+  EXPECT_EQ(
+      0, strcmp(list_devices_callback_names[0].c_str(), "IntMic"));
+  EXPECT_EQ(callback_arg, list_devices_callback_args[0]);
+
+  EXPECT_EQ(
+      0, strcmp(list_devices_callback_names[1].c_str(), "ExtMic"));
+  EXPECT_EQ(callback_arg, list_devices_callback_args[1]);
+}
+
+TEST(AlsaUcm, GetJackNameForDevice) {
+  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",
+                            "Comment for Dev2" };
+
+  ResetStubData();
+
+  fake_list["_devices/HiFi"] = devices;
+  fake_list_size["_devices/HiFi"] = 4;
+  std::string id_1 = "=JackName/Dev1/HiFi";
+  std::string value_1 = "JackName1";
+
+  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");
+
+  EXPECT_EQ(0, strcmp(jack_name_1, value_1.c_str()));
+  EXPECT_EQ(NULL, jack_name_2);
+}
+
+TEST(AlsaUcm, GetJackTypeForDevice) {
+  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"};
+
+  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";
+
+  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_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");
+
+  /* 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);
+}
+
+TEST(AlsaUcm, GetPeriodFramesForDevice) {
+  struct cras_use_case_mgr *mgr = &cras_ucm_mgr;
+  int dma_period_1, dma_period_2, dma_period_3;
+  const char *devices[] = { "Dev1", "Comment for Dev1",
+                            "Dev2", "Comment for Dev2",
+                            "Dev3", "Comment for Dev3" };
+
+  ResetStubData();
+
+  fake_list["_devices/HiFi"] = devices;
+  fake_list_size["_devices/HiFi"] = 6;
+  std::string id_1 = "=DmaPeriodMicrosecs/Dev1/HiFi";
+  std::string value_1 = "1000";
+  std::string id_2 = "=DmaPeriodMicrosecs/Dev2/HiFi";
+  std::string value_2 = "-10";
+
+  snd_use_case_get_value[id_1] = value_1;
+  snd_use_case_get_value[id_2] = value_2;
+
+  dma_period_1 = ucm_get_dma_period_for_dev(mgr, "Dev1");
+  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);
+}
+
+TEST(AlsaUcm, UcmSection) {
+  struct ucm_section *section_list = NULL;
+  struct ucm_section *section;
+  struct mixer_name *controls = NULL;
+  struct mixer_name *m_name;
+  int dev_idx = 0;
+  size_t i;
+  enum CRAS_STREAM_DIRECTION dir = CRAS_STREAM_OUTPUT;
+  static const char *name = "Headphone";
+  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);
+  EXPECT_EQ(reinterpret_cast<struct ucm_section*>(NULL), section);
+
+  section = ucm_section_create(name, 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));
+  EXPECT_EQ(dev_idx, section->dev_idx);
+  EXPECT_EQ(dir, section->dir);
+  EXPECT_NE(jack_name, section->jack_name);
+  EXPECT_NE(jack_type, section->jack_type);
+  EXPECT_EQ(section->prev, section);
+  EXPECT_EQ(reinterpret_cast<const char *>(NULL), section->mixer_name);
+  EXPECT_EQ(reinterpret_cast<struct mixer_name*>(NULL), section->coupled);
+
+  EXPECT_EQ(-EINVAL, ucm_section_set_mixer_name(section, NULL));
+  EXPECT_EQ(-EINVAL, ucm_section_set_mixer_name(NULL, mixer_name));
+  EXPECT_EQ(0, ucm_section_set_mixer_name(section, mixer_name));
+
+  EXPECT_NE(section->mixer_name, mixer_name);
+  EXPECT_EQ(0, strcmp(section->mixer_name, mixer_name));
+
+  EXPECT_EQ(-EINVAL, ucm_section_add_coupled(
+                         section, NULL, MIXER_NAME_VOLUME));
+  EXPECT_EQ(-EINVAL, ucm_section_add_coupled(
+                         NULL, coupled_names[0], MIXER_NAME_VOLUME));
+  EXPECT_EQ(0, ucm_section_add_coupled(
+                         section, coupled_names[0], MIXER_NAME_VOLUME));
+
+  EXPECT_EQ(-EINVAL, ucm_section_concat_coupled(section, NULL));
+  EXPECT_EQ(-EINVAL, ucm_section_concat_coupled(
+                         NULL, reinterpret_cast<struct mixer_name*>(0x1111)));
+
+  controls = NULL;
+  for (i = 1; i < ARRAY_SIZE(coupled_names); i++) {
+    controls = mixer_name_add(controls, coupled_names[i],
+                              CRAS_STREAM_OUTPUT, MIXER_NAME_VOLUME);
+  }
+  /* Add controls to the list of coupled controls for this section. */
+  EXPECT_EQ(0, ucm_section_concat_coupled(section, controls));
+
+  i = 0;
+  DL_FOREACH(section->coupled, m_name) {
+    EXPECT_NE(m_name->name, coupled_names[i]);
+    EXPECT_EQ(0, strcmp(m_name->name, coupled_names[i]));
+    i++;
+  }
+  EXPECT_EQ(i, ARRAY_SIZE(coupled_names));
+
+  DL_APPEND(section_list, section);
+  ucm_section_free_list(section_list);
+}
+
+TEST(AlsaUcm, GetSections) {
+  struct cras_use_case_mgr *mgr = &cras_ucm_mgr;
+  struct ucm_section* sections;
+  struct ucm_section* section;
+  struct mixer_name* m_name;
+  int section_count = 0;
+  int i = 0;
+  const char *devices[] = { "Headphone", "The headphones jack.",
+                            "Speaker", "The speakers.",
+                            "Mic", "Microphone jack.",
+                            "Internal Mic", "Internal Microphones",
+                            "HDMI", "HDMI output" };
+  const char* ids[] = {
+    "=PlaybackPCM/Headphone/HiFi",
+    "=JackName/Headphone/HiFi",
+    "=JackType/Headphone/HiFi",
+    "=JackSwitch/Headphone/HiFi",
+    "=CoupledMixers/Headphone/HiFi",
+
+    "=PlaybackPCM/Speaker/HiFi",
+    "=CoupledMixers/Speaker/HiFi",
+
+    "=CapturePCM/Mic/HiFi",
+    "=JackName/Mic/HiFi",
+    "=JackType/Mic/HiFi",
+    "=JackSwitch/Mic/HiFi",
+    "=MixerName/Mic/HiFi",
+
+    "=CapturePCM/Internal Mic/HiFi",
+    "=CoupledMixers/Internal Mic/HiFi",
+    "=JackSwitch/Internal Mic/HiFi",
+
+    "=PlaybackPCM/HDMI/HiFi",
+    "=MixerName/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",
+    "SPK-L,SPK-R",
+
+    "hw:my-sound-card,0",
+    "my-sound-card Headset Jack",
+    "gpio",
+    "0",
+    "CAPTURE",
+
+    "hw:my-sound-card,0",
+    "MIC-L,MIC-R",
+    "-10",
+
+    "hw:my-sound-card,2",
+    "HDMI",
+  };
+
+  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);
+  ASSERT_NE(sections, (struct ucm_section*)NULL);
+  DL_FOREACH(sections, section) {
+    section_count++;
+  }
+  EXPECT_EQ(section_count, ARRAY_SIZE(devices) / 2);
+
+  // Headphone
+  section = sections;
+  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(NULL, section->mixer_name);
+  ASSERT_NE((struct mixer_name*)NULL, section->coupled);
+  m_name = section->coupled;
+  EXPECT_EQ(0, strcmp(m_name->name, "HP-L"));
+  m_name = m_name->next;
+  EXPECT_EQ(0, strcmp(m_name->name, "HP-R"));
+  EXPECT_EQ(NULL, m_name->next);
+  EXPECT_EQ(2, section->jack_switch);
+
+  // Speaker
+  section = section->next;
+  EXPECT_EQ(0, strcmp(section->name, "Speaker"));
+  EXPECT_EQ(0, 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);
+  ASSERT_NE((struct mixer_name*)NULL, section->coupled);
+  m_name = section->coupled;
+  EXPECT_EQ(0, strcmp(m_name->name, "SPK-L"));
+  m_name = m_name->next;
+  EXPECT_EQ(0, strcmp(m_name->name, "SPK-R"));
+  EXPECT_EQ(NULL, m_name->next);
+
+  // Mic
+  section = section->next;
+  EXPECT_EQ(0, strcmp(section->name, "Mic"));
+  EXPECT_EQ(0, 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, section->jack_switch);
+  ASSERT_NE((const char *)NULL, section->mixer_name);
+  EXPECT_EQ(0, strcmp(section->mixer_name, "CAPTURE"));
+  EXPECT_EQ(NULL, section->coupled);
+
+  // Internal Mic
+  section = section->next;
+  EXPECT_EQ(0, strcmp(section->name, "Internal Mic"));
+  EXPECT_EQ(0, section->dev_idx);
+  EXPECT_EQ(CRAS_STREAM_INPUT, 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);
+  ASSERT_NE((struct mixer_name*)NULL, section->coupled);
+  m_name = section->coupled;
+  EXPECT_EQ(0, strcmp(m_name->name, "MIC-L"));
+  m_name = m_name->next;
+  EXPECT_EQ(0, strcmp(m_name->name, "MIC-R"));
+
+  // HDMI
+  section = section->next;
+  EXPECT_EQ(0, strcmp(section->name, "HDMI"));
+  EXPECT_EQ(2, 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);
+  ASSERT_NE((const char *)NULL, section->mixer_name);
+  EXPECT_EQ(0, strcmp(section->mixer_name, "HDMI"));
+
+  EXPECT_EQ(NULL, section->next);
+  ucm_section_free_list(sections);
+}
+
+TEST(AlsaUcm, GetSectionsMissingPCM) {
+  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[] = {
+    "=JackName/Headphone/HiFi",
+    "=CoupledMixers/Headphone/HiFi",
+    NULL
+  };
+  const char* values[] = {
+    "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, 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;
+
+  /* Verifies the mapping between stream types and verbs are correct. */
+  mgr->use_case = CRAS_STREAM_TYPE_DEFAULT;
+  EXPECT_EQ(0, strcmp("HiFi", uc_verb(mgr)));
+  mgr->use_case = CRAS_STREAM_TYPE_MULTIMEDIA;
+  EXPECT_EQ(0, strcmp("Multimedia", uc_verb(mgr)));
+  mgr->use_case = CRAS_STREAM_TYPE_VOICE_COMMUNICATION;
+  EXPECT_EQ(0, strcmp("Voice Call", uc_verb(mgr)));
+  mgr->use_case = CRAS_STREAM_TYPE_SPEECH_RECOGNITION;
+  EXPECT_EQ(0, strcmp("Speech", uc_verb(mgr)));
+  mgr->use_case = CRAS_STREAM_TYPE_PRO_AUDIO;
+  EXPECT_EQ(0, strcmp("Pro Audio", uc_verb(mgr)));
+}
+
+TEST(AlsaUcm, GetAvailUseCases) {
+  struct cras_use_case_mgr *mgr;
+  const char *verbs[] = { "HiFi", "Comment for Verb1",
+                          "Voice Call", "Comment for Verb2",
+                          "Speech", "Comment for Verb3" };
+
+  ResetStubData();
+
+  fake_list["_verbs"] = verbs;
+  fake_list_size["_verbs"] = 6;
+
+  mgr = ucm_create("foo");
+  EXPECT_EQ(0x0D, mgr->avail_use_cases);
+  ucm_destroy(mgr);
+}
+
+TEST(AlsaUcm, SetUseCase) {
+  struct cras_use_case_mgr *mgr;
+  const char *verbs[] = { "HiFi", "Comment for Verb1",
+                          "Voice Call", "Comment for Verb2",
+                          "Speech", "Comment for Verb3" };
+  int rc;
+
+  ResetStubData();
+
+  fake_list["_verbs"] = verbs;
+  fake_list_size["_verbs"] = 6;
+
+  mgr = ucm_create("foo");
+  EXPECT_EQ(snd_use_case_set_param[0],
+      std::make_pair(std::string("_verb"), std::string("HiFi")));
+
+  rc = ucm_set_use_case(mgr, CRAS_STREAM_TYPE_VOICE_COMMUNICATION);
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(mgr->use_case, CRAS_STREAM_TYPE_VOICE_COMMUNICATION);
+  EXPECT_EQ(snd_use_case_set_param[1],
+      std::make_pair(std::string("_verb"), std::string("Voice Call")));
+
+  /* Request unavailable use case will fail. */
+  rc = ucm_set_use_case(mgr, CRAS_STREAM_TYPE_PRO_AUDIO);
+  EXPECT_EQ(-1, rc);
+  /* cras_use_case_mgr's use case should not be changed. */
+  EXPECT_EQ(mgr->use_case, CRAS_STREAM_TYPE_VOICE_COMMUNICATION);
+  /* And snd_use_case_set not being called. */
+  EXPECT_EQ(2, snd_use_case_set_param.size());
+
+  ucm_destroy(mgr);
+}
+
 /* Stubs */
 
 extern "C" {
@@ -485,9 +1410,13 @@
                      const char *identifier,
                      const char **value) {
   snd_use_case_get_called++;
-  *value = strdup(snd_use_case_get_value[identifier].c_str());
   snd_use_case_get_id.push_back(std::string(identifier));
-  return snd_use_case_get_ret_value[identifier];
+  if (snd_use_case_get_value.find(identifier) == snd_use_case_get_value.end()) {
+    *value = NULL;
+    return -1;
+  }
+  *value = strdup(snd_use_case_get_value[identifier].c_str());
+  return 0;
 }
 
 int snd_use_case_set(snd_use_case_mgr_t* uc_mgr,
@@ -496,7 +1425,7 @@
   snd_use_case_set_called++;
   snd_use_case_set_param.push_back(
       std::make_pair(std::string(identifier), std::string(value)));
-  return snd_use_case_set_return;;
+  return snd_use_case_set_return;
 }
 
 int snd_use_case_get_list(snd_use_case_mgr_t *uc_mgr,
@@ -517,5 +1446,6 @@
 
 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/audio_area_unittest.cc b/cras/src/tests/audio_area_unittest.cc
index 9acf22e..7efe3a6 100644
--- a/cras/src/tests/audio_area_unittest.cc
+++ b/cras/src/tests/audio_area_unittest.cc
@@ -48,7 +48,7 @@
   memset(buf1, 0, 32 * 2);
   for (i = 0; i < 32; i++)
     buf2[i] = rand();
-  cras_audio_area_copy(a1, 0, &fmt, a2, 0, 0);
+  cras_audio_area_copy(a1, 0, &fmt, a2, 0, 1.0);
   for (i = 0; i < 32; i++)
     EXPECT_EQ(buf1[i], buf2[i]);
 
@@ -56,6 +56,44 @@
   cras_audio_area_destroy(a2);
 }
 
+TEST(AudioArea, CopyAudioAreaWithGain) {
+  struct cras_audio_format fmt;
+  int i;
+  /* Check a gain of 10x can be applied. */
+  float gain_scaler = 10.0f;
+
+  fmt.num_channels = 2;
+  fmt.format = SND_PCM_FORMAT_S16_LE;
+  for (i = 0; i < CRAS_CH_MAX; i++)
+    fmt.channel_layout[i] = stereo[i];
+
+  a1 = cras_audio_area_create(2);
+  a2 = cras_audio_area_create(2);
+  cras_audio_area_config_channels(a1, &fmt);
+  cras_audio_area_config_channels(a2, &fmt);
+  cras_audio_area_config_buf_pointers(a1, &fmt, (uint8_t *)buf1);
+  cras_audio_area_config_buf_pointers(a2, &fmt, (uint8_t *)buf2);
+  a1->frames = 16;
+  a2->frames = 16;
+
+  memset(buf1, 0, 32 * 2);
+  /* Let src has some samples smaller than 32768/10 and some samples larger than
+   * 32768/10 to test clipping. */
+  for (i = 0; i < 16; i++)
+    buf2[i] = rand() % 3270;
+  for (i = 17; i < 32; i++)
+    buf2[i] = 3280 + rand() % 3200;
+  cras_audio_area_copy(a1, 0, &fmt, a2, 0, gain_scaler);
+  for (i = 0; i < 32; i++) {
+    int32_t expected_value = buf2[i] * gain_scaler;
+    if (expected_value > INT16_MAX)
+      expected_value = INT16_MAX;
+    EXPECT_EQ(buf1[i], expected_value);
+  }
+
+  cras_audio_area_destroy(a1);
+  cras_audio_area_destroy(a2);
+}
 TEST(AudioArea, CopyAudioAreaOffset) {
   struct cras_audio_format fmt;
   int i;
@@ -74,14 +112,14 @@
   a1->frames = 16;
   a2->frames = 14;
 
-  memset(buf1, 0x55, 32 * 2);
+  memset(buf1, 0, 32 * 2);
   for (i = 0; i < 32; i++)
     buf2[i] = rand();
-  cras_audio_area_copy(a1, 2, &fmt, a2, 0, 0);
-  EXPECT_EQ(buf1[0], 0x5555);
-  EXPECT_EQ(buf1[1], 0x5555);
-  EXPECT_EQ(buf1[2], 0x5555);
-  EXPECT_EQ(buf1[3], 0x5555);
+  cras_audio_area_copy(a1, 2, &fmt, a2, 0, 1.0);
+  EXPECT_EQ(buf1[0], 0);
+  EXPECT_EQ(buf1[1], 0);
+  EXPECT_EQ(buf1[2], 0);
+  EXPECT_EQ(buf1[3], 0);
   for (i = 4; i < 32; i++)
     EXPECT_EQ(buf1[i], buf2[i-4]);
 
@@ -107,20 +145,20 @@
   a1->frames = 14;
   a2->frames = 14;
 
-  memset(buf1, 0x55, 32 * 2);
+  memset(buf1, 0, 32 * 2);
   for (i = 0; i < 32; i++)
     buf2[i] = rand();
-  cras_audio_area_copy(a1, 2, &fmt, a2, 0, 0);
-  EXPECT_EQ(buf1[0], 0x5555);
-  EXPECT_EQ(buf1[1], 0x5555);
-  EXPECT_EQ(buf1[2], 0x5555);
-  EXPECT_EQ(buf1[3], 0x5555);
+  cras_audio_area_copy(a1, 2, &fmt, a2, 0, 1.0);
+  EXPECT_EQ(buf1[0], 0);
+  EXPECT_EQ(buf1[1], 0);
+  EXPECT_EQ(buf1[2], 0);
+  EXPECT_EQ(buf1[3], 0);
   for (i = 4; i < 28; i++)
     EXPECT_EQ(buf1[i], buf2[i-4]);
-  EXPECT_EQ(buf1[28], 0x5555);
-  EXPECT_EQ(buf1[29], 0x5555);
-  EXPECT_EQ(buf1[30], 0x5555);
-  EXPECT_EQ(buf1[31], 0x5555);
+  EXPECT_EQ(buf1[28], 0);
+  EXPECT_EQ(buf1[29], 0);
+  EXPECT_EQ(buf1[30], 0);
+  EXPECT_EQ(buf1[31], 0);
 
   cras_audio_area_destroy(a1);
   cras_audio_area_destroy(a2);
@@ -152,7 +190,7 @@
   memset(buf1, 0, 32 * 2);
   for (i = 0; i < 32; i++)
     buf2[i] = rand();
-  cras_audio_area_copy(a1, 0, &dst_fmt, a2, 0, 0);
+  cras_audio_area_copy(a1, 0, &dst_fmt, a2, 0, 1.0);
   for (i = 0; i < 16; i++) {
     EXPECT_EQ(buf1[i * 2], buf2[i]);
     EXPECT_EQ(buf1[i * 2 + 1], buf2[i]);
@@ -186,7 +224,7 @@
   memset(buf1, 0, 32 * 2);
   for (i = 0; i < 32; i++)
     buf2[i] = rand() % 10000;
-  cras_audio_area_copy(a1, 0, &fmt, a2, 0, 0);
+  cras_audio_area_copy(a1, 0, &fmt, a2, 0, 1.0);
   for (i = 0; i < 16; i++)
     EXPECT_EQ(buf1[i], buf2[i * 2] + buf2[i * 2 + 1]);
 
@@ -218,7 +256,7 @@
   memset(buf1, 0, 32 * 2);
   for (i = 0; i < 32; i++)
     buf2[i] = rand();
-  cras_audio_area_copy(a1, 0, &fmt, a2, 0, 0);
+  cras_audio_area_copy(a1, 0, &fmt, a2, 0, 1.0);
   for (i = 0; i < 10; i++) {
     EXPECT_EQ(buf1[i * 3], buf2[i * 2]);
     EXPECT_EQ(buf1[i * 3 + 1], buf2[i * 2 + 1]);
@@ -257,7 +295,7 @@
   memset(buf1, 0, 32 * 2);
   for (i = 0; i < 32; i++)
     buf2[i] = rand();
-  cras_audio_area_copy(a1, 0, &dst_fmt, a2, 0, 0);
+  cras_audio_area_copy(a1, 0, &dst_fmt, a2, 0, 1.0);
   for (i = 0; i < 10; i++) {
     EXPECT_EQ(buf1[i * 3], 0);
     EXPECT_EQ(buf1[i * 3 + 1], 0);
@@ -272,15 +310,15 @@
 
 extern "C" {
 
-void cras_mix_add_stride(snd_pcm_format_t fmt, uint8_t *dst, uint8_t *src,
+void cras_mix_add_scale_stride(snd_pcm_format_t fmt, uint8_t *dst, uint8_t *src,
 			 unsigned int count, unsigned int dst_stride,
-			 unsigned int src_stride)
+			 unsigned int src_stride, float scaler)
 {
 	unsigned int i;
 
 	for (i = 0; i < count; i++) {
 		int32_t sum;
-		sum = *(int16_t *)dst + *(int16_t *)src;
+		sum = *(int16_t *)dst + *(int16_t *)src * scaler;
 		if (sum > INT16_MAX)
 			sum = INT16_MAX;
 		else if (sum < INT16_MIN)
diff --git a/cras/src/tests/audio_test_gui.py b/cras/src/tests/audio_test_gui.py
new file mode 100644
index 0000000..6ee401b
--- /dev/null
+++ b/cras/src/tests/audio_test_gui.py
@@ -0,0 +1,266 @@
+#!/usr/bin/env python
+#
+# Copyright 2016 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.
+"""Script functions as a web app and wrapper for the cras_router program."""
+
+import re
+import subprocess
+import logging
+import cherrypy
+
+# Node Format: [Stable_Id, ID, Vol, Plugged, L/R_swapped, Time, Type, Name]
+ID_INDEX = 1
+PLUGGED_INDEX = 3
+TYPE_INDEX = 6
+NAME_INDEX = 7
+
+
+def get_plugged_nodes(plugged_nodes, lines, is_input):
+  start_str = 'Input Nodes:' if is_input else 'Output Nodes:'
+  end_str = 'Attached clients:' if is_input else 'Input Devices:'
+  for i in range(lines.index(start_str) + 2,
+                 lines.index(end_str)):
+    node = filter(None, re.split(r'\s+|\*+', lines[i]))
+    # check for nodes that are plugged nodes and loopback
+    if node[PLUGGED_INDEX] == 'yes' and node[TYPE_INDEX][:4] != 'POST':
+      key = node[TYPE_INDEX] + ' ' + node[NAME_INDEX]
+      plugged_nodes[key] = node[ID_INDEX]
+
+
+class CrasRouterTest(object):
+  """Cherrypy class that builds and runs the HTML for audio testing tool."""
+
+  @cherrypy.expose
+  def index(self):
+    """Builds up and displays the html for the audio testing tool.
+
+    Returns:
+      html that was built up based on plugged audio devices.
+    """
+
+    # Stop program if currently being run.
+    if 'process' in cherrypy.session:
+      print 'Existing process'
+      # If return code is None process is still running
+      if cherrypy.session['process'].poll() is None:
+        print 'Killing existing process'
+        cherrypy.session['process'].kill()
+      else:
+        print 'Process already finished'
+
+    html = """<html>
+              <head>
+                <title>Audio Test</title>
+              </head>
+              <body style="background-color:lightgrey;">
+                <font color="red">
+                <h1>Audio Closed Loop Test</h1>
+                <font style="color:rgb(100, 149, 237)">
+                <h3>
+                <form name="routerOptions" method="get"
+                onsubmit="return validateForm()" action="start_test">
+                  <h2>Input Type</h2>
+              """
+    dump = subprocess.check_output(['cras_test_client', '--dump_s'])
+    if not dump:
+      return 'Could not connect to server'
+    dump_lines = dump.split('\n')
+    input_plugged_nodes = {}
+    get_plugged_nodes(input_plugged_nodes, dump_lines, True)
+    for name, node_id in input_plugged_nodes.items():
+      line = '<input type ="radio" name="input_type" value="'
+      line += node_id + '">' +name + '<br>\n'
+      html += line
+
+    html += """<input type ="radio" id="i0" name="input_type"
+               value="file">File<br>
+                 <div id="input_file" style="display:none;">
+                 Filename <input type="text" name="input_file"><br><br>
+                 </div>
+               <h2>Output Type</h2>"""
+    output_plugged_nodes = {}
+    get_plugged_nodes(output_plugged_nodes, dump_lines, False)
+    for name, node_id in output_plugged_nodes.items():
+      line = '<input type ="radio" name="output_type" value="'
+      line = line + node_id + '">' +name + '<br>\n'
+      html += line
+
+    html += """<input type ="radio" name="output_type"
+               value="file">File<br>
+               <div id="output_file" style="display:none;">
+               Filename <input type="text" name="output_file">
+               </div><br>
+               <h2>Sample Rate</h2>
+               <input type="radio" name="rate" id="sample_rate1" value=48000
+               checked>48,000 Hz<br>
+               <input type="radio" name="rate" id="sample_rate0" value=44100>
+               44,100 Hz<br><br>
+               <button type="submit" onclick="onOff(this)">Test!</button>
+               </h3>
+               </form>
+               </font>
+               </body>
+               </html>
+               """
+    javascript = """
+                 <script>
+                 /* Does basic error checking to make sure user doesn't
+                  * give bad options to the router.
+                  */
+                 function validateForm(){
+                    var input_type =
+                        document.forms['routerOptions']['input_type'].value;
+                    var output_type =
+                        document.forms['routerOptions']['output_type'].value;
+                    if (input_type == '' || output_type == '') {
+                        alert('Please select an input and output type.');
+                        return false;
+                    }
+                    if (input_type == 'file' && output_type == 'file') {
+                        alert('Input and Output Types cannot both be files!');
+                        return false;
+                    }
+                    //check if filename is valid
+                    if (input_type == 'file') {
+                        var input_file =
+                          document.forms['routerOptions']['input_file'].value;
+                        if (input_file == '') {
+                            alert('Please enter a file name');
+                            return false;
+                        }
+                    }
+                    if (output_type == 'file') {
+                        var output_file =
+                          document.forms['routerOptions']['output_file'].value;
+                        if (output_file == '') {
+                            alert('Please enter a file name');
+                            return false;
+                        }
+                    }
+                }
+
+                function show_filename(radio, file_elem) {
+                    for(var i =0; i < radio.length; i++){
+                        radio[i].onclick = function(){
+                            if (this.value == 'file') {
+                                file_elem.style.display = 'block';
+                            } else {
+                                file_elem.style.display = 'none';
+                            }
+                        }
+                    }
+                }
+                /* Loops determine if filename field should be shown */
+                var input_type_rad =
+                    document.forms['routerOptions']['input_type'];
+                var input_file_elem =
+                    document.getElementById('input_file');
+                var output_type_rad =
+                    document.forms['routerOptions']['output_type'];
+                var output_file_elem =
+                    document.getElementById('output_file');
+                show_filename(input_type_rad, input_file_elem);
+                show_filename(output_type_rad, output_file_elem);
+                </script>"""
+    html += javascript
+    return html
+
+  @cherrypy.expose
+  def start_test(self, input_type, output_type, input_file='',
+                 output_file='', rate=48000):
+    """Capture audio from the input_type and plays it back to the output_type.
+
+    Args:
+      input_type: Node id for the selected input or 'file' for files
+      output_type: Node id for the selected output or 'file' for files
+      input_file: Path of the input if 'file' is input type
+      output_file: Path of the output if 'file' is output type
+      rate: Sample rate for the test.
+
+    Returns:
+      html for the tesing in progress page.
+    """
+    print 'Beginning test'
+    if input_type == 'file' or output_type == 'file':
+        command = ['cras_test_client']
+    else:
+        command = ['cras_router']
+    if input_type == 'file':
+      command.append('--playback_file')
+      command.append(str(input_file))
+    else:
+      set_input = ['cras_test_client', '--select_input', str(input_type)]
+      if subprocess.check_call(set_input):
+        print 'Error setting input'
+    if output_type == 'file':
+      command.append('--capture_file')
+      command.append(str(output_file))
+    else:
+      set_output = ['cras_test_client', '--select_output', str(output_type)]
+      if subprocess.check_call(set_output):
+        print 'Error setting output'
+    command.append('--rate')
+    command.append(str(rate))
+    print 'Running commmand: ' + str(command)
+    p = subprocess.Popen(command)
+    cherrypy.session['process'] = p
+    return """
+    <html>
+    <head>
+    <style type="text/css">
+    body {
+      background-color: #DC143C;
+    }
+    #test {
+      color: white;
+      text-align: center;
+    }
+    </style>
+      <title>Running test</title>
+    </head>
+    <body>
+      <div id="test">
+        <h1>Test in progress</h1>
+        <form action="index"><!--Go back to orginal page-->
+          <button type="submit" id="stop">Click to stop</button>
+        </form>
+        <h2>Time Elapsed<br>
+            <time id="elapsed_time">00:00</time>
+        </h2>
+        </div>
+    </body>
+    </html>
+    <script type="text/javascript">
+    var seconds = 0;
+    var minutes = 0;
+    var elapsed_time;
+    var start_time = new Date().getTime();
+    function secondPassed(){
+      var time = new Date().getTime() - start_time;
+      elapsed_time = Math.floor(time / 100) / 10;
+      minutes = Math.floor(elapsed_time / 60);
+      seconds = Math.floor(elapsed_time % 60);
+      var seconds_str = (seconds < 10 ? '0' + seconds: '' + seconds);
+      var minutes_str = (minutes < 10 ? '0' + minutes: '' + minutes);
+      var time_passed = minutes_str + ':' + seconds_str;
+      document.getElementById("elapsed_time").textContent = time_passed;
+    }
+    //have time tic every second
+    var timer = setInterval(secondPassed, 1000);
+    var stop = document.getElementById("stop");
+    stop.onclick = function(){
+      seconds = 0;
+      minutes = 0;
+      clearInterval(timer);
+    }
+    </script>"""
+
+if __name__ == '__main__':
+  conf = {
+      '/': {
+          'tools.sessions.on': True
+      }
+  }
+  cherrypy.quickstart(CrasRouterTest(), '/', conf)
diff --git a/cras/src/tests/audio_thread_unittest.cc b/cras/src/tests/audio_thread_unittest.cc
index da4db8b..e150126 100644
--- a/cras/src/tests/audio_thread_unittest.cc
+++ b/cras/src/tests/audio_thread_unittest.cc
@@ -4,11 +4,15 @@
 
 extern "C" {
 #include "audio_thread.c"
+#include "cras_audio_area.h"
 }
 
 #include <gtest/gtest.h>
+#include <map>
 
 #define MAX_CALLS 10
+#define BUFFER_SIZE 8192
+#define FIRST_CB_LEVEL 480
 
 static unsigned int cras_rstream_dev_offset_called;
 static unsigned int cras_rstream_dev_offset_ret[MAX_CALLS];
@@ -18,13 +22,65 @@
 static const struct cras_rstream *cras_rstream_dev_offset_update_rstream_val[MAX_CALLS];
 static unsigned int cras_rstream_dev_offset_update_frames_val[MAX_CALLS];
 static unsigned int cras_rstream_dev_offset_update_dev_id_val[MAX_CALLS];
+static int cras_iodev_all_streams_written_ret;
+static struct cras_audio_area *cras_iodev_get_output_buffer_area;
+static int cras_iodev_put_output_buffer_called;
+static unsigned int cras_iodev_put_output_buffer_nframes;
+static unsigned int cras_iodev_fill_odev_zeros_frames;
+static int dev_stream_playback_frames_ret;
+static unsigned int cras_iodev_prepare_output_before_write_samples_called;
+static enum CRAS_IODEV_STATE cras_iodev_prepare_output_before_write_samples_state;
+static unsigned int cras_iodev_get_output_buffer_called;
+static int cras_iodev_prepare_output_before_write_samples_ret;
+static int cras_iodev_reset_request_called;
+static struct cras_iodev *cras_iodev_reset_request_iodev;
+static int cras_iodev_output_underrun_called;
+static int cras_device_monitor_reset_device_called;
+static struct cras_iodev *cras_device_monitor_reset_device_iodev;
+static struct cras_iodev *cras_iodev_start_ramp_odev;
+static enum CRAS_IODEV_RAMP_REQUEST cras_iodev_start_ramp_request;
+static std::map<const struct dev_stream*, struct timespec> dev_stream_wake_time_val;
+
+void ResetGlobalStubData() {
+  cras_rstream_dev_offset_called = 0;
+  cras_rstream_dev_offset_update_called = 0;
+  for (int i = 0; i < MAX_CALLS; i++) {
+    cras_rstream_dev_offset_ret[i] = 0;
+    cras_rstream_dev_offset_rstream_val[i] = NULL;
+    cras_rstream_dev_offset_dev_id_val[i] = 0;
+    cras_rstream_dev_offset_update_rstream_val[i] = NULL;
+    cras_rstream_dev_offset_update_frames_val[i] = 0;
+    cras_rstream_dev_offset_update_dev_id_val[i] = 0;
+  }
+  cras_iodev_all_streams_written_ret = 0;
+  if (cras_iodev_get_output_buffer_area) {
+    free(cras_iodev_get_output_buffer_area);
+    cras_iodev_get_output_buffer_area = NULL;
+  }
+  cras_iodev_put_output_buffer_called = 0;
+  cras_iodev_put_output_buffer_nframes = 0;
+  cras_iodev_fill_odev_zeros_frames = 0;
+  dev_stream_playback_frames_ret = 0;
+  cras_iodev_prepare_output_before_write_samples_called = 0;
+  cras_iodev_prepare_output_before_write_samples_state = CRAS_IODEV_STATE_OPEN;
+  cras_iodev_get_output_buffer_called = 0;
+  cras_iodev_prepare_output_before_write_samples_ret = 0;
+  cras_iodev_reset_request_called = 0;
+  cras_iodev_reset_request_iodev = NULL;
+  cras_iodev_output_underrun_called = 0;
+  cras_device_monitor_reset_device_called = 0;
+  cras_device_monitor_reset_device_iodev = NULL;
+  cras_iodev_start_ramp_odev = NULL;
+  cras_iodev_start_ramp_request = CRAS_IODEV_RAMP_REQUEST_UP_START_PLAYBACK;
+  dev_stream_wake_time_val.clear();
+}
 
 // Test streams and devices manipulation.
 class StreamDeviceSuite : public testing::Test {
   protected:
     virtual void SetUp() {
-      device_id_ = 0;
       thread_ = audio_thread_create();
+      ResetStubData();
     }
 
     virtual void TearDown() {
@@ -37,14 +93,23 @@
       iodev->direction = direction;
       iodev->open_dev = open_dev;
       iodev->close_dev = close_dev;
-      iodev->dev_running = dev_running;
-      iodev->is_open = is_open;
       iodev->frames_queued = frames_queued;
       iodev->delay_frames = delay_frames;
       iodev->get_buffer = get_buffer;
       iodev->put_buffer = put_buffer;
       iodev->flush_buffer = flush_buffer;
       iodev->ext_format = &format_;
+      iodev->buffer_size = BUFFER_SIZE;
+      iodev->min_cb_level = FIRST_CB_LEVEL;
+    }
+
+    void ResetStubData() {
+      device_id_ = 0;
+      open_dev_called_ = 0;
+      close_dev_called_ = 0;
+      frames_queued_ = 0;
+      delay_frames_ = 0;
+      audio_buffer_size_ = 0;
     }
 
     void SetupRstream(struct cras_rstream *rstream,
@@ -52,6 +117,12 @@
       memset(rstream, 0, sizeof(*rstream));
       rstream->direction = direction;
       rstream->cb_threshold = 480;
+      rstream->shm.area = static_cast<cras_audio_shm_area*>(
+          calloc(1, sizeof(rstream->shm.area)));
+    }
+
+    void TearDownRstream(struct cras_rstream *rstream) {
+      free(rstream->shm.area);
     }
 
     void SetupPinnedStream(struct cras_rstream *rstream,
@@ -72,16 +143,8 @@
       return 0;
     }
 
-    static int dev_running(const cras_iodev* iodev) {
-      dev_running_called_++;
-      return 1;
-    }
-
-    static int is_open(const cras_iodev* iodev) {
-      return is_open_;
-    }
-
-    static int frames_queued(const cras_iodev* iodev) {
+    static int frames_queued(const cras_iodev* iodev, struct timespec* tstamp) {
+      clock_gettime(CLOCK_MONOTONIC_RAW, tstamp);
       return frames_queued_;
     }
 
@@ -125,26 +188,22 @@
 
     static int open_dev_called_;
     static int close_dev_called_;
-    static int dev_running_called_;
-    static int is_open_;
     static int frames_queued_;
     static int delay_frames_;
     static struct cras_audio_format format_;
     static struct cras_audio_area *area_;
-    static uint8_t audio_buffer_[8192];
+    static uint8_t audio_buffer_[BUFFER_SIZE];
     static unsigned int audio_buffer_size_;
 };
 
-int StreamDeviceSuite::open_dev_called_ = 0;
-int StreamDeviceSuite::close_dev_called_ = 0;
-int StreamDeviceSuite::dev_running_called_ = 0;
-int StreamDeviceSuite::is_open_ = 0;
-int StreamDeviceSuite::frames_queued_ = 0;
-int StreamDeviceSuite::delay_frames_ = 0;
+int StreamDeviceSuite::open_dev_called_;
+int StreamDeviceSuite::close_dev_called_;
+int StreamDeviceSuite::frames_queued_;
+int StreamDeviceSuite::delay_frames_;
 struct cras_audio_format StreamDeviceSuite::format_;
 struct cras_audio_area *StreamDeviceSuite::area_;
 uint8_t StreamDeviceSuite::audio_buffer_[8192];
-unsigned int StreamDeviceSuite::audio_buffer_size_ = 0;
+unsigned int StreamDeviceSuite::audio_buffer_size_;
 
 TEST_F(StreamDeviceSuite, AddRemoveOpenOutputDevice) {
   struct cras_iodev iodev;
@@ -162,6 +221,38 @@
   EXPECT_EQ(NULL, adev);
 }
 
+TEST_F(StreamDeviceSuite, StartRamp) {
+  struct cras_iodev iodev;
+  struct open_dev *adev;
+  int rc;
+  enum CRAS_IODEV_RAMP_REQUEST req;
+
+  SetupDevice(&iodev, CRAS_STREAM_OUTPUT);
+
+  // Check the newly added device is open.
+  thread_add_open_dev(thread_, &iodev);
+  adev = thread_->open_devs[CRAS_STREAM_OUTPUT];
+  EXPECT_EQ(adev->dev, &iodev);
+
+  // Ramp up for unmute.
+  req = CRAS_IODEV_RAMP_REQUEST_UP_UNMUTE;
+  rc = thread_dev_start_ramp(thread_, &iodev, req);
+
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(&iodev, cras_iodev_start_ramp_odev);
+  EXPECT_EQ(req, cras_iodev_start_ramp_request);
+
+  // Ramp down for mute.
+  ResetStubData();
+  req = CRAS_IODEV_RAMP_REQUEST_DOWN_MUTE;
+
+  rc = thread_dev_start_ramp(thread_, &iodev, req);
+
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(&iodev, cras_iodev_start_ramp_odev);
+  EXPECT_EQ(req, cras_iodev_start_ramp_request);
+}
+
 TEST_F(StreamDeviceSuite, AddRemoveOpenInputDevice) {
   struct cras_iodev iodev;
   struct open_dev *adev;
@@ -271,6 +362,51 @@
   EXPECT_EQ(30, cras_rstream_dev_offset_update_frames_val[0]);
   EXPECT_EQ(&rstream2, cras_rstream_dev_offset_update_rstream_val[1]);
   EXPECT_EQ(0, cras_rstream_dev_offset_update_frames_val[1]);
+
+  TearDownRstream(&rstream);
+  TearDownRstream(&rstream2);
+  TearDownRstream(&rstream3);
+}
+
+TEST_F(StreamDeviceSuite, InputStreamsSetInputDeviceWakeTime) {
+  struct cras_iodev iodev;
+  struct cras_iodev *iodevs[] = {&iodev};
+  struct cras_rstream rstream1, rstream2;
+  struct timespec ts_wake_1 = {.tv_sec = 1, .tv_nsec = 500};
+  struct timespec ts_wake_2 = {.tv_sec = 1, .tv_nsec = 1000};
+  struct open_dev *adev;
+
+  SetupDevice(&iodev, CRAS_STREAM_INPUT);
+  SetupRstream(&rstream1, CRAS_STREAM_INPUT);
+  SetupRstream(&rstream2, CRAS_STREAM_INPUT);
+
+  thread_add_open_dev(thread_, &iodev);
+  thread_add_stream(thread_, &rstream1, iodevs, 1);
+  thread_add_stream(thread_, &rstream2, iodevs, 1);
+  EXPECT_NE((void *)NULL, iodev.streams);
+
+  // Assume device is running.
+  iodev.state = CRAS_IODEV_STATE_NORMAL_RUN;
+
+  // Set stub data for dev_stream_wake_time.
+  dev_stream_wake_time_val[iodev.streams] = ts_wake_1;
+  dev_stream_wake_time_val[iodev.streams->next] = ts_wake_2;
+
+  // Send captured samples to client.
+  // This will also update wake time for this device based on
+  // dev_stream_wake_time of each stream of this device.
+  send_captured_samples(thread_);
+
+  // wake_ts is maintained in open_dev.
+  adev = thread_->open_devs[CRAS_STREAM_INPUT];
+
+  // The wake up time for this device is the minimum of
+  // ts_wake_1 and ts_wake_2.
+  EXPECT_EQ(ts_wake_1.tv_sec, adev->wake_ts.tv_sec);
+  EXPECT_EQ(ts_wake_1.tv_nsec, adev->wake_ts.tv_nsec);
+
+  TearDownRstream(&rstream1);
+  TearDownRstream(&rstream2);
 }
 
 TEST_F(StreamDeviceSuite, AddRemoveMultipleStreamsOnMultipleDevices) {
@@ -334,6 +470,180 @@
   thread_add_open_dev(thread_, &iodev);
   dev_stream = iodev.streams;
   EXPECT_EQ(NULL, dev_stream);
+
+  TearDownRstream(&rstream);
+  TearDownRstream(&rstream2);
+  TearDownRstream(&rstream3);
+}
+
+TEST_F(StreamDeviceSuite, WriteOutputSamplesPrepareOutputFailed) {
+  struct cras_iodev iodev;
+  struct open_dev *adev;
+
+  ResetGlobalStubData();
+
+  SetupDevice(&iodev, CRAS_STREAM_OUTPUT);
+
+  // Add the device.
+  thread_add_open_dev(thread_, &iodev);
+  adev = thread_->open_devs[CRAS_STREAM_OUTPUT];
+
+  // Assume device is started.
+  iodev.state = CRAS_IODEV_STATE_NO_STREAM_RUN;
+  // Assume device remains in no stream state;
+  cras_iodev_prepare_output_before_write_samples_state = \
+      CRAS_IODEV_STATE_NO_STREAM_RUN;
+
+  // Assume there is an error in prepare_output.
+  cras_iodev_prepare_output_before_write_samples_ret = -EINVAL;
+
+  // cras_iodev should handle no stream playback.
+  EXPECT_EQ(-EINVAL, write_output_samples(thread_, adev));
+
+  // cras_iodev_get_output_buffer in audio_thread write_output_samples is not
+  // called.
+  EXPECT_EQ(0, cras_iodev_get_output_buffer_called);
+
+  thread_rm_open_dev(thread_, &iodev);
+}
+
+TEST_F(StreamDeviceSuite, WriteOutputSamplesNoStream) {
+  struct cras_iodev iodev;
+  struct open_dev *adev;
+
+  ResetGlobalStubData();
+
+  SetupDevice(&iodev, CRAS_STREAM_OUTPUT);
+
+  // Add the device.
+  thread_add_open_dev(thread_, &iodev);
+  adev = thread_->open_devs[CRAS_STREAM_OUTPUT];
+
+  // Assume device is started.
+  iodev.state = CRAS_IODEV_STATE_NO_STREAM_RUN;
+  // Assume device remains in no stream state;
+  cras_iodev_prepare_output_before_write_samples_state = \
+      CRAS_IODEV_STATE_NO_STREAM_RUN;
+
+  // cras_iodev should handle no stream playback.
+  write_output_samples(thread_, adev);
+  EXPECT_EQ(1, cras_iodev_prepare_output_before_write_samples_called);
+  // cras_iodev_get_output_buffer in audio_thread write_output_samples is not
+  // called.
+  EXPECT_EQ(0, cras_iodev_get_output_buffer_called);
+
+  thread_rm_open_dev(thread_, &iodev);
+}
+
+TEST_F(StreamDeviceSuite, WriteOutputSamplesLeaveNoStream) {
+  struct cras_iodev iodev;
+  struct open_dev *adev;
+
+  ResetGlobalStubData();
+
+  SetupDevice(&iodev, CRAS_STREAM_OUTPUT);
+
+  // Setup the output buffer for device.
+  cras_iodev_get_output_buffer_area = cras_audio_area_create(2);
+
+  // Add the device.
+  thread_add_open_dev(thread_, &iodev);
+  adev = thread_->open_devs[CRAS_STREAM_OUTPUT];
+
+  // Assume device in no stream state.
+  iodev.state = CRAS_IODEV_STATE_NO_STREAM_RUN;
+
+  // Assume device remains in no stream state;
+  cras_iodev_prepare_output_before_write_samples_state = \
+      CRAS_IODEV_STATE_NO_STREAM_RUN;
+
+  // cras_iodev should NOT leave no stream state;
+  write_output_samples(thread_, adev);
+  EXPECT_EQ(1, cras_iodev_prepare_output_before_write_samples_called);
+  // cras_iodev_get_output_buffer in audio_thread write_output_samples is not
+  // called.
+  EXPECT_EQ(0, cras_iodev_get_output_buffer_called);
+
+  // Assume device leaves no stream state;
+  cras_iodev_prepare_output_before_write_samples_state = \
+      CRAS_IODEV_STATE_NORMAL_RUN;
+
+  // cras_iodev should write samples from streams.
+  write_output_samples(thread_, adev);
+  EXPECT_EQ(2, cras_iodev_prepare_output_before_write_samples_called);
+  EXPECT_EQ(1, cras_iodev_get_output_buffer_called);
+
+  thread_rm_open_dev(thread_, &iodev);
+}
+
+TEST_F(StreamDeviceSuite, WriteOutputSamplesUnderrun) {
+  struct cras_iodev iodev, *piodev = &iodev;
+  struct open_dev *adev;
+  struct cras_rstream rstream;
+
+  ResetGlobalStubData();
+
+  SetupDevice(&iodev, CRAS_STREAM_OUTPUT);
+  SetupRstream(&rstream, CRAS_STREAM_OUTPUT);
+
+  // Setup the output buffer for device.
+  cras_iodev_get_output_buffer_area = cras_audio_area_create(2);
+
+  // Add the device and add the stream.
+  thread_add_open_dev(thread_, &iodev);
+  adev = thread_->open_devs[CRAS_STREAM_OUTPUT];
+  thread_add_stream(thread_, &rstream, &piodev, 1);
+
+  // Assume device is running and there is an underrun. There is no frame
+  // queued and there is no sample written in this cycle.
+  // Audio thread should ask iodev to handle output underrun.
+  iodev.state = CRAS_IODEV_STATE_NORMAL_RUN;
+  frames_queued_ = 0;
+  cras_iodev_all_streams_written_ret = 0;
+
+  // Assume device in normal run stream state;
+  cras_iodev_prepare_output_before_write_samples_state = \
+      CRAS_IODEV_STATE_NORMAL_RUN;
+
+  write_output_samples(thread_, adev);
+  EXPECT_EQ(1, cras_iodev_output_underrun_called);
+
+  thread_rm_open_dev(thread_, &iodev);
+  TearDownRstream(&rstream);
+}
+
+TEST_F(StreamDeviceSuite, DoPlaybackUnderrun) {
+  struct cras_iodev iodev, *piodev = &iodev;
+  struct cras_rstream rstream;
+
+  ResetGlobalStubData();
+
+  SetupDevice(&iodev, CRAS_STREAM_OUTPUT);
+  SetupRstream(&rstream, CRAS_STREAM_OUTPUT);
+
+  // Setup the output buffer for device.
+  cras_iodev_get_output_buffer_area = cras_audio_area_create(2);
+
+  // Add the device and add the stream.
+  thread_add_open_dev(thread_, &iodev);
+  thread_add_stream(thread_, &rstream, &piodev, 1);
+
+  // Assume device is running and there is a severe underrun.
+  iodev.state = CRAS_IODEV_STATE_NORMAL_RUN;
+  frames_queued_ = -EPIPE;
+
+  // Assume device in normal run stream state;
+  cras_iodev_prepare_output_before_write_samples_state = \
+      CRAS_IODEV_STATE_NORMAL_RUN;
+
+  do_playback(thread_);
+
+  // 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);
+
+  thread_rm_open_dev(thread_, &iodev);
+  TearDownRstream(&rstream);
 }
 
 TEST(AUdioThreadStreams, DrainStream) {
@@ -376,7 +686,7 @@
 
 unsigned int cras_iodev_all_streams_written(struct cras_iodev *iodev)
 {
-  return 0;
+  return cras_iodev_all_streams_written_ret;
 }
 
 int cras_iodev_close(struct cras_iodev *iodev)
@@ -445,7 +755,8 @@
 {
 }
 
-int cras_iodev_update_rate(struct cras_iodev *iodev, unsigned int level)
+int cras_iodev_update_rate(struct cras_iodev *iodev, unsigned int level,
+                           struct timespec *level_tstamp)
 {
   return 0;
 }
@@ -458,6 +769,8 @@
 int cras_iodev_put_output_buffer(struct cras_iodev *iodev, uint8_t *frames,
 				 unsigned int nframes)
 {
+  cras_iodev_put_output_buffer_called++;
+  cras_iodev_put_output_buffer_nframes = nframes;
   return 0;
 }
 
@@ -472,6 +785,8 @@
 				 struct cras_audio_area **area,
 				 unsigned *frames)
 {
+  cras_iodev_get_output_buffer_called++;
+  *area = cras_iodev_get_output_buffer_area;
   return 0;
 }
 
@@ -480,6 +795,17 @@
   return 0;
 }
 
+void cras_fmt_conv_destroy(struct cras_fmt_conv *conv)
+{
+}
+
+struct cras_fmt_conv *cras_channel_remix_conv_create(
+    unsigned int num_channels,
+    const float *coefficient)
+{
+  return NULL;
+}
+
 void cras_rstream_dev_attach(struct cras_rstream *rstream,
                              unsigned int dev_id,
                              void *dev_ptr)
@@ -520,6 +846,11 @@
   return 0;
 }
 
+void cras_rstream_record_fetch_interval(struct cras_rstream *rstream,
+    const struct timespec *now)
+{
+}
+
 int cras_set_rt_scheduling(int rt_lim)
 {
   return 0;
@@ -537,7 +868,7 @@
 unsigned int dev_stream_capture(struct dev_stream *dev_stream,
                                 const struct cras_audio_area *area,
                                 unsigned int area_offset,
-                                unsigned int dev_index)
+                                float software_gain_scaler)
 {
   return 0;
 }
@@ -560,7 +891,7 @@
 struct dev_stream *dev_stream_create(struct cras_rstream *stream,
                                      unsigned int dev_id,
                                      const struct cras_audio_format *dev_fmt,
-                                     void *dev_ptr)
+                                     void *dev_ptr, struct timespec *cb_ts)
 {
   struct dev_stream *out = static_cast<dev_stream*>(calloc(1, sizeof(*out)));
   out->stream = stream;
@@ -582,7 +913,7 @@
 
 int dev_stream_playback_frames(const struct dev_stream *dev_stream)
 {
-  return 0;
+  return dev_stream_playback_frames_ret;
 }
 
 int dev_stream_playback_update_rstream(struct dev_stream *dev_stream)
@@ -623,14 +954,47 @@
 {
 }
 
-int cras_iodev_frames_queued(struct cras_iodev *iodev)
+int dev_stream_wake_time(struct dev_stream *dev_stream,
+                         unsigned int curr_level,
+                         struct timespec *level_tstamp,
+                         struct timespec *wake_time)
 {
-	return iodev->frames_queued(iodev);
+  if (dev_stream_wake_time_val.find(dev_stream) !=
+      dev_stream_wake_time_val.end()) {
+    wake_time->tv_sec = dev_stream_wake_time_val[dev_stream].tv_sec;
+    wake_time->tv_nsec = dev_stream_wake_time_val[dev_stream].tv_nsec;
+  }
+  return 0;
+}
+
+int cras_iodev_frames_queued(struct cras_iodev *iodev, struct timespec *tstamp)
+{
+	return iodev->frames_queued(iodev, tstamp);
 }
 
 int cras_iodev_buffer_avail(struct cras_iodev *iodev, unsigned hw_level)
 {
-  return iodev->buffer_size - iodev->frames_queued(iodev);
+  struct timespec tstamp;
+  return iodev->buffer_size - iodev->frames_queued(iodev, &tstamp);
+}
+
+int cras_iodev_fill_odev_zeros(struct cras_iodev *odev, unsigned int frames)
+{
+  cras_iodev_fill_odev_zeros_frames = frames;
+  return 0;
+}
+
+int cras_iodev_output_underrun(struct cras_iodev *odev)
+{
+  cras_iodev_output_underrun_called++;
+  return 0;
+}
+
+int cras_iodev_prepare_output_before_write_samples(struct cras_iodev *odev)
+{
+  cras_iodev_prepare_output_before_write_samples_called++;
+  odev->state = cras_iodev_prepare_output_before_write_samples_state;
+  return cras_iodev_prepare_output_before_write_samples_ret;
 }
 
 int cras_server_metrics_longest_fetch_delay(int delay_msec)
@@ -638,6 +1002,68 @@
   return 0;
 }
 
+float cras_iodev_get_software_gain_scaler(const struct cras_iodev *iodev)
+{
+  return 1.0f;
+}
+
+unsigned int cras_iodev_frames_to_play_in_sleep(struct cras_iodev *odev,
+                                                unsigned int *hw_level,
+                                                struct timespec *hw_tstamp)
+{
+  clock_gettime(CLOCK_MONOTONIC_RAW, hw_tstamp);
+  *hw_level = 0;
+  return 0;
+}
+
+int cras_iodev_odev_should_wake(const struct cras_iodev *odev)
+{
+  return 1;
+}
+
+struct cras_audio_area *cras_audio_area_create(int num_channels)
+{
+  struct cras_audio_area *area;
+  size_t sz;
+
+  sz = sizeof(*area) + num_channels * sizeof(struct cras_channel_area);
+  area = (cras_audio_area *)calloc(1, sz);
+  area->num_channels = num_channels;
+  area->channels[0].buf = (uint8_t*)calloc(1, BUFFER_SIZE * 2 * num_channels);
+
+  return area;
+}
+
+enum CRAS_IODEV_STATE cras_iodev_state(const struct cras_iodev *iodev)
+{
+  return iodev->state;
+}
+
+unsigned int cras_iodev_get_num_underruns(const struct cras_iodev *iodev)
+{
+  return 0;
+}
+
+int cras_iodev_reset_request(struct cras_iodev *iodev)
+{
+  cras_iodev_reset_request_called++;
+  cras_iodev_reset_request_iodev = iodev;
+  return 0;
+}
+
+unsigned int cras_iodev_get_num_severe_underruns(const struct cras_iodev *iodev)
+{
+  return 0;
+}
+
+int cras_iodev_start_ramp(struct cras_iodev *odev,
+                          enum CRAS_IODEV_RAMP_REQUEST request)
+{
+  cras_iodev_start_ramp_odev = odev;
+  cras_iodev_start_ramp_request = request;
+  return 0;
+}
+
 }  // extern "C"
 
 int main(int argc, char **argv) {
diff --git a/cras/src/tests/bt_device_unittest.cc b/cras/src/tests/bt_device_unittest.cc
index 9f66b32..4f7cd86 100644
--- a/cras/src/tests/bt_device_unittest.cc
+++ b/cras/src/tests/bt_device_unittest.cc
@@ -44,26 +44,20 @@
     virtual void SetUp() {
       ResetStubData();
       bt_iodev1.direction = CRAS_STREAM_OUTPUT;
-      bt_iodev1.is_open = is_open;
       bt_iodev1.update_active_node = update_active_node;
       bt_iodev2.direction = CRAS_STREAM_INPUT;
-      bt_iodev2.is_open = is_open;
       bt_iodev2.update_active_node = update_active_node;
       d1_.direction = CRAS_STREAM_OUTPUT;
-      d1_.is_open = is_open;
       d1_.update_active_node = update_active_node;
       d2_.direction = CRAS_STREAM_OUTPUT;
-      d2_.is_open = is_open;
       d2_.update_active_node = update_active_node;
       d3_.direction = CRAS_STREAM_INPUT;
-      d3_.is_open = is_open;
       d3_.update_active_node = update_active_node;
     }
-    static int is_open(const cras_iodev* iodev) {
-      return is_open_;
-    }
+
     static void update_active_node(struct cras_iodev *iodev,
-                                   unsigned node_idx) {
+                                   unsigned node_idx,
+                                   unsigned dev_enabled) {
     }
 
     struct cras_iodev bt_iodev1;
@@ -71,15 +65,12 @@
     struct cras_iodev d3_;
     struct cras_iodev d2_;
     struct cras_iodev d1_;
-    static int is_open_;
 };
 
-int BtDeviceTestSuite::is_open_;
-
 TEST(BtDeviceSuite, CreateBtDevice) {
   struct cras_bt_device *device;
 
-  device = cras_bt_device_create(FAKE_OBJ_PATH);
+  device = cras_bt_device_create(NULL, FAKE_OBJ_PATH);
   EXPECT_NE((void *)NULL, device);
 
   device = cras_bt_device_get(FAKE_OBJ_PATH);
@@ -92,7 +83,7 @@
 
 TEST_F(BtDeviceTestSuite, AppendRmIodev) {
   struct cras_bt_device *device;
-  device = cras_bt_device_create(FAKE_OBJ_PATH);
+  device = cras_bt_device_create(NULL, FAKE_OBJ_PATH);
   bt_iodev1.nodes = reinterpret_cast<struct cras_ionode*>(0x123);
   cras_bt_io_create_profile_ret = &bt_iodev1;
   cras_bt_device_append_iodev(device, &d1_,
@@ -132,7 +123,7 @@
   struct cras_bt_device *device;
 
   ResetStubData();
-  device = cras_bt_device_create(FAKE_OBJ_PATH);
+  device = cras_bt_device_create(NULL, FAKE_OBJ_PATH);
   cras_bt_io_create_profile_ret = &bt_iodev1;
   cras_bt_device_append_iodev(device, &d1_,
       CRAS_BT_DEVICE_PROFILE_A2DP_SOURCE);
@@ -141,7 +132,7 @@
       CRAS_BT_DEVICE_PROFILE_HFP_AUDIOGATEWAY);
 
   cras_bt_device_start_monitor();
-  cras_bt_device_switch_profile_on_open(device, &bt_iodev1);
+  cras_bt_device_switch_profile_enable_dev(device, &bt_iodev1);
 
   /* Two bt iodevs were all active. */
   cras_main_message_add_handler_callback(
@@ -149,13 +140,13 @@
       cras_main_message_add_handler_callback_data);
 
   /* One bt iodev was active, the other was not. */
-  cras_bt_device_switch_profile_on_open(device, &bt_iodev2);
+  cras_bt_device_switch_profile_enable_dev(device, &bt_iodev2);
   cras_main_message_add_handler_callback(
       cras_main_message_send_msg,
       cras_main_message_add_handler_callback_data);
 
   /* Output bt iodev wasn't active, close the active input iodev. */
-  cras_bt_device_switch_profile_on_close(device, &bt_iodev2);
+  cras_bt_device_switch_profile(device, &bt_iodev2);
   cras_main_message_add_handler_callback(
       cras_main_message_send_msg,
       cras_main_message_add_handler_callback_data);
@@ -240,6 +231,27 @@
   return NULL;
 }
 
+void cras_hfp_ag_suspend_connected_device(struct cras_bt_device *device)
+{
+}
+
+void cras_a2dp_suspend_connected_device(struct cras_bt_device *device)
+{
+}
+
+void cras_a2dp_start(struct cras_bt_device *device)
+{
+}
+
+int cras_hfp_ag_start(struct cras_bt_device *device)
+{
+  return 0;
+}
+
+void cras_hfp_ag_suspend()
+{
+}
+
 /* From hfp_slc */
 int hfp_event_speaker_gain(struct hfp_slc_handle *handle, int gain)
 {
@@ -256,7 +268,7 @@
   return 0;
 }
 
-int cras_iodev_list_dev_is_enabled(struct cras_iodev *dev)
+int cras_iodev_list_dev_is_enabled(const struct cras_iodev *dev)
 {
   return 0;
 }
@@ -269,6 +281,10 @@
 {
 }
 
+void cras_iodev_list_notify_node_volume(struct cras_ionode *node)
+{
+}
+
 int cras_main_message_send(struct cras_main_message *msg)
 {
   cras_main_message_send_msg = msg;
@@ -291,6 +307,15 @@
 }
 
 /* From cras_tm */
+struct cras_timer *cras_tm_create_timer(
+    struct cras_tm *tm,
+    unsigned int ms,
+    void (*cb)(struct cras_timer *t, void *data),
+    void *cb_data)
+{
+  return NULL;
+}
+
 void cras_tm_cancel_timer(struct cras_tm *tm, struct cras_timer *t)
 {
 }
diff --git a/cras/src/tests/bt_io_unittest.cc b/cras/src/tests/bt_io_unittest.cc
index ad87627..979404a 100644
--- a/cras/src/tests/bt_io_unittest.cc
+++ b/cras/src/tests/bt_io_unittest.cc
@@ -23,8 +23,8 @@
 static unsigned int cras_bt_device_set_active_profile_called;
 static unsigned int cras_bt_device_set_active_profile_val;
 static int cras_bt_device_get_active_profile_ret;
-static int cras_bt_device_switch_profile_on_open_called;
-static int cras_bt_device_switch_profile_on_close_called;
+static int cras_bt_device_switch_profile_enable_dev_called;
+static int cras_bt_device_switch_profile_called;
 static int cras_bt_device_can_switch_to_a2dp_ret;
 static int cras_bt_device_has_a2dp_ret;
 static int is_utf8_string_ret_value;
@@ -41,8 +41,8 @@
   cras_bt_device_set_active_profile_called = 0;
   cras_bt_device_set_active_profile_val = 0;
   cras_bt_device_get_active_profile_ret = 0;
-  cras_bt_device_switch_profile_on_open_called= 0;
-  cras_bt_device_switch_profile_on_close_called = 0;
+  cras_bt_device_switch_profile_enable_dev_called= 0;
+  cras_bt_device_switch_profile_called = 0;
   cras_bt_device_can_switch_to_a2dp_ret = 0;
   cras_bt_device_has_a2dp_ret = 0;
   is_utf8_string_ret_value = 1;
@@ -62,10 +62,8 @@
       delay_frames_called_ = 0;
       get_buffer_called_ = 0;
       put_buffer_called_ = 0;
-      is_open_called_ = 0;
       open_dev_called_ = 0;
       close_dev_called_ = 0;
-      dev_running_called_ = 0;
     }
 
     virtual void TearDown() {
@@ -79,10 +77,8 @@
       d->delay_frames = delay_frames;
       d->get_buffer = get_buffer;
       d->put_buffer = put_buffer;
-      d->is_open = is_open;
       d->open_dev = open_dev;
       d->close_dev = close_dev;
-      d->dev_running = dev_running;
     }
 
     // Stub functions for the iodev structure.
@@ -102,7 +98,8 @@
       update_supported_formats_called_++;
       return 0;
     }
-    static int frames_queued(const cras_iodev* iodev) {
+    static int frames_queued(const cras_iodev* iodev,
+                             struct timespec *tstamp) {
       frames_queued_called_++;
       return 0;
     }
@@ -121,10 +118,6 @@
       put_buffer_called_++;
       return 0;
     }
-    static int is_open(const cras_iodev* iodev) {
-      is_open_called_++;
-      return 0;
-    }
     static int open_dev(cras_iodev* iodev) {
       open_dev_called_++;
       return 0;
@@ -133,10 +126,6 @@
       close_dev_called_++;
       return 0;
     }
-    static int dev_running(const cras_iodev* iodev) {
-      dev_running_called_++;
-      return 1;
-    }
 
   static struct cras_iodev *bt_iodev;
   static struct cras_iodev iodev_;
@@ -146,10 +135,8 @@
   static unsigned int delay_frames_called_;
   static unsigned int get_buffer_called_;
   static unsigned int put_buffer_called_;
-  static unsigned int is_open_called_;
   static unsigned int open_dev_called_;
   static unsigned int close_dev_called_;
-  static unsigned int dev_running_called_;
 };
 
 struct cras_iodev *BtIoBasicSuite::bt_iodev;
@@ -160,14 +147,13 @@
 unsigned int BtIoBasicSuite::delay_frames_called_;
 unsigned int BtIoBasicSuite::get_buffer_called_;
 unsigned int BtIoBasicSuite::put_buffer_called_;
-unsigned int BtIoBasicSuite::is_open_called_;
 unsigned int BtIoBasicSuite::open_dev_called_;
 unsigned int BtIoBasicSuite::close_dev_called_;
-unsigned int BtIoBasicSuite::dev_running_called_;
 
 TEST_F(BtIoBasicSuite, CreateBtIo) {
   struct cras_audio_area *fake_area;
   struct cras_audio_format fake_fmt;
+  struct timespec tstamp;
   unsigned fr;
   bt_iodev = cras_bt_io_create(fake_device, &iodev_,
       CRAS_BT_DEVICE_PROFILE_A2DP_SOURCE);
@@ -180,16 +166,12 @@
 
   bt_iodev->open_dev(bt_iodev);
   EXPECT_EQ(1, open_dev_called_);
-  bt_iodev->is_open(bt_iodev);
-  EXPECT_EQ(1, is_open_called_);
-  bt_iodev->frames_queued(bt_iodev);
+  bt_iodev->frames_queued(bt_iodev, &tstamp);
   EXPECT_EQ(1, frames_queued_called_);
   bt_iodev->get_buffer(bt_iodev, &fake_area, &fr);
   EXPECT_EQ(1, get_buffer_called_);
   bt_iodev->put_buffer(bt_iodev, fr);
   EXPECT_EQ(1, put_buffer_called_);
-  bt_iodev->dev_running(bt_iodev);
-  EXPECT_EQ(1, dev_running_called_);
   bt_iodev->close_dev(bt_iodev);
   EXPECT_EQ(1, close_dev_called_);
   EXPECT_EQ(1, cras_iodev_free_format_called);
@@ -209,7 +191,7 @@
   EXPECT_EQ(CRAS_BT_DEVICE_PROFILE_HSP_AUDIOGATEWAY |
             CRAS_BT_DEVICE_PROFILE_HFP_AUDIOGATEWAY,
             cras_bt_device_set_active_profile_val);
-  EXPECT_EQ(1, cras_bt_device_switch_profile_on_open_called);
+  EXPECT_EQ(1, cras_bt_device_switch_profile_enable_dev_called);
 }
 
 TEST_F(BtIoBasicSuite, NoSwitchProfileOnUpdateFormatForInputDevAlreadyOnHfp) {
@@ -223,7 +205,7 @@
       CRAS_BT_DEVICE_PROFILE_HFP_AUDIOGATEWAY;
   bt_iodev->update_supported_formats(bt_iodev);
 
-  EXPECT_EQ(0, cras_bt_device_switch_profile_on_open_called);
+  EXPECT_EQ(0, cras_bt_device_switch_profile_enable_dev_called);
 }
 
 TEST_F(BtIoBasicSuite, SwitchProfileOnCloseInputDev) {
@@ -240,7 +222,7 @@
 
   EXPECT_EQ(CRAS_BT_DEVICE_PROFILE_A2DP_SOURCE,
             cras_bt_device_set_active_profile_val);
-  EXPECT_EQ(1, cras_bt_device_switch_profile_on_close_called);
+  EXPECT_EQ(1, cras_bt_device_switch_profile_called);
 }
 
 TEST_F(BtIoBasicSuite, NoSwitchProfileOnCloseInputDevNoSupportA2dp) {
@@ -255,7 +237,7 @@
   cras_bt_device_has_a2dp_ret = 0;
   bt_iodev->close_dev(bt_iodev);
 
-  EXPECT_EQ(0, cras_bt_device_switch_profile_on_close_called);
+  EXPECT_EQ(0, cras_bt_device_switch_profile_called);
 }
 
 TEST_F(BtIoBasicSuite, SwitchProfileOnAppendA2dpDev) {
@@ -269,7 +251,8 @@
 
   EXPECT_EQ(CRAS_BT_DEVICE_PROFILE_A2DP_SOURCE,
             cras_bt_device_set_active_profile_val);
-  EXPECT_EQ(1, cras_bt_device_switch_profile_on_open_called);
+  EXPECT_EQ(0, cras_bt_device_switch_profile_enable_dev_called);
+  EXPECT_EQ(1, cras_bt_device_switch_profile_called);
 }
 
 TEST_F(BtIoBasicSuite, NoSwitchProfileOnAppendHfpDev) {
@@ -281,7 +264,7 @@
   cras_bt_io_append(bt_iodev, &iodev2_,
       CRAS_BT_DEVICE_PROFILE_HFP_AUDIOGATEWAY);
 
-  EXPECT_EQ(0, cras_bt_device_switch_profile_on_open_called);
+  EXPECT_EQ(0, cras_bt_device_switch_profile_enable_dev_called);
 }
 
 TEST_F(BtIoBasicSuite, CreateSetDeviceActiveProfileToA2DP) {
@@ -422,22 +405,33 @@
   return cras_bt_device_can_switch_to_a2dp_ret;
 }
 
-int cras_bt_device_switch_profile_on_close(struct cras_bt_device *device,
+int cras_bt_device_switch_profile(struct cras_bt_device *device,
             struct cras_iodev *bt_iodev)
 {
-  cras_bt_device_switch_profile_on_close_called++;
+  cras_bt_device_switch_profile_called++;
   return 0;
 }
 
-int cras_bt_device_switch_profile_on_open(struct cras_bt_device *device,
+int cras_bt_device_switch_profile_enable_dev(struct cras_bt_device *device,
             struct cras_iodev *bt_iodev)
 {
-  cras_bt_device_switch_profile_on_open_called++;
+  cras_bt_device_switch_profile_enable_dev_called++;
   return 0;
 }
 
+const char *cras_bt_device_object_path(const struct cras_bt_device *device)
+{
+  return "/fake/object/path";
+}
+
 int is_utf8_string(const char* string)
 {
   return is_utf8_string_ret_value;
 }
+
+int cras_iodev_default_no_stream_playback(struct cras_iodev *odev, int enable)
+{
+  return 0;
+}
+
 } // extern "C"
diff --git a/cras/src/tests/bt_profile_unittest.cc b/cras/src/tests/bt_profile_unittest.cc
index 8598e39..103d3ce 100644
--- a/cras/src/tests/bt_profile_unittest.cc
+++ b/cras/src/tests/bt_profile_unittest.cc
@@ -29,7 +29,6 @@
 static struct cras_bt_profile *profile_cancel_arg_value;
 static int cras_bt_transport_get_called;
 static const char *cras_bt_transport_get_arg_value;
-static int cras_bt_transport_fill_properties_called;
 
 void fake_profile_release(struct cras_bt_profile *profile);
 void fake_profile_new_connection(struct cras_bt_profile *profile,
@@ -59,7 +58,6 @@
     fake_profile.cancel = fake_profile_cancel;
 
     fake_transport = reinterpret_cast<struct cras_bt_transport*>(0x321);
-    cras_bt_transport_fill_properties_called = 0;
     cras_bt_transport_get_called = 0;
   }
 };
@@ -109,7 +107,6 @@
   ASSERT_EQ(1, profile_new_connection_called);
   ASSERT_STREQ("device", cras_bt_transport_get_arg_value);
   ASSERT_EQ(1, cras_bt_transport_get_called);
-  ASSERT_EQ(1, cras_bt_transport_fill_properties_called);
   ASSERT_EQ(fake_transport, profile_new_connection_arg_value);
 
   CreateMessageCall("/fake", "org.bluez.Profile1", "RequestDisconnection")
@@ -186,13 +183,6 @@
   return fake_transport;
 }
 
-void cras_bt_transport_fill_properties(struct cras_bt_transport *transport,
-		int fd, const char *uuid)
-{
-  cras_bt_transport_fill_properties_called++;
-  /* Close the duplicated fd */
-  close(fd);
-}
 } // extern "C"
 
 int main(int argc, char **argv) {
diff --git a/cras/src/tests/card_config_unittest.cc b/cras/src/tests/card_config_unittest.cc
index 184e085..e4bed40 100644
--- a/cras/src/tests/card_config_unittest.cc
+++ b/cras/src/tests/card_config_unittest.cc
@@ -22,7 +22,7 @@
 static long cras_explicit_curve[101];
 static struct cras_volume_curve* cras_volume_curve_create_explicit_return;
 
-static const char CONFIG_PATH[] = "/tmp";
+static const char CONFIG_PATH[] = CRAS_UT_TMPDIR;
 
 void CreateConfigFile(const char* name, const char* config_text) {
   FILE* f;
@@ -63,9 +63,8 @@
   EXPECT_EQ(NULL, config);
 }
 
-// Test an empty config file, should return a config, but give back the default
-// volume curve.
-TEST_F(CardConfigTestSuite, EmptyConfigFileReturnsValidConfigDefaultCurves) {
+// Test an empty config file, should return a null volume curve.
+TEST_F(CardConfigTestSuite, EmptyConfigFileReturnsNullVolumeCurve) {
   static const char empty_config_text[] = "";
   static const char empty_config_name[] = "EmptyConfigCard";
   struct cras_card_config* config;
@@ -77,19 +76,19 @@
   EXPECT_NE(static_cast<struct cras_card_config*>(NULL), config);
 
   curve = cras_card_config_get_volume_curve_for_control(config, "asdf");
-  EXPECT_EQ(1, cras_volume_curve_create_default_called);
-  EXPECT_NE(static_cast<struct cras_volume_curve*>(NULL), curve);
+  EXPECT_EQ(0, cras_volume_curve_create_default_called);
+  EXPECT_EQ(static_cast<struct cras_volume_curve*>(NULL), curve);
 
   cras_card_config_destroy(config);
 }
 
-// Getting a curve from a null config should return a default curve.
+// Getting a curve from a null config should always return null volume curve.
 TEST_F(CardConfigTestSuite, NullConfigGivesDefaultVolumeCurve) {
   struct cras_volume_curve* curve;
 
   curve = cras_card_config_get_volume_curve_for_control(NULL, "asdf");
-  EXPECT_EQ(1, cras_volume_curve_create_default_called);
-  EXPECT_NE(static_cast<struct cras_volume_curve*>(NULL), curve);
+  EXPECT_EQ(0, cras_volume_curve_create_default_called);
+  EXPECT_EQ(static_cast<struct cras_volume_curve*>(NULL), curve);
 }
 
 // Test getting a curve from a simple_step configuration.
@@ -110,9 +109,8 @@
 
   // Unknown config should return default curve.
   curve = cras_card_config_get_volume_curve_for_control(NULL, "asdf");
-  EXPECT_EQ(1, cras_volume_curve_create_default_called);
-  EXPECT_EQ(cras_volume_curve_create_default_return, curve);
-  cras_volume_curve_create_default_called = 0;
+  EXPECT_EQ(0, cras_volume_curve_create_default_called);
+  EXPECT_EQ(static_cast<struct cras_volume_curve*>(NULL), curve);
 
   // Test a config that specifies simple_step.
   curve = cras_card_config_get_volume_curve_for_control(config, "Card1");
diff --git a/cras/src/tests/cras_client_unittest.cc b/cras/src/tests/cras_client_unittest.cc
index 437c77b..1853ae4 100644
--- a/cras/src/tests/cras_client_unittest.cc
+++ b/cras/src/tests/cras_client_unittest.cc
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 #include <stdio.h>
-#include <sys/shm.h>
 #include <gtest/gtest.h>
 
 extern "C" {
@@ -15,33 +14,36 @@
 
 static const cras_stream_id_t FIRST_STREAM_ID = 1;
 
-static int shmat_called;
-static int shmdt_called;
-static int shmget_called;
 static int pthread_create_called;
 static int pthread_join_called;
+static int pthread_cond_timedwait_called;
+static int pthread_cond_timedwait_retval;
 static int close_called;
 static int pipe_called;
 static int sendmsg_called;
 static int write_called;
+static void *mmap_return_value;
+static int samples_ready_called;
+static int samples_ready_frames_value;
+static uint8_t *samples_ready_samples_value;
 
-static void* shmat_returned_value;
 static int pthread_create_returned_value;
 
 namespace {
 
 void InitStaticVariables() {
-  shmat_called = 0;
-  shmdt_called = 0;
-  shmget_called = 0;
   pthread_create_called = 0;
   pthread_join_called = 0;
+  pthread_cond_timedwait_called = 0;
+  pthread_cond_timedwait_retval = 0;
   close_called = 0;
   pipe_called = 0;
   sendmsg_called = 0;
   write_called = 0;
-  shmat_returned_value = NULL;
   pthread_create_returned_value = 0;
+  mmap_return_value = NULL;
+  samples_ready_called = 0;
+  samples_ready_frames_value = 0;
 }
 
 class CrasClientTestSuite : public testing::Test {
@@ -67,6 +69,7 @@
       InitStaticVariables();
 
       memset(&client_, 0, sizeof(client_));
+      client_.server_fd_state = CRAS_SOCKET_STATE_CONNECTED;
       memset(&stream_, 0, sizeof(stream_));
       stream_.id = FIRST_STREAM_ID;
 
@@ -104,10 +107,68 @@
     format->channel_layout[i] = i < num_channels ? i : -1;
 }
 
+int capture_samples_ready(cras_client* client,
+                          cras_stream_id_t stream_id,
+                          uint8_t* samples,
+                          size_t frames,
+                          const timespec* sample_ts,
+                          void* arg) {
+  samples_ready_called++;
+  samples_ready_samples_value = samples;
+  samples_ready_frames_value = frames;
+  return frames;
+}
+
+TEST_F(CrasClientTestSuite, HandleCaptureDataReady) {
+  struct cras_audio_shm *shm = &stream_.capture_shm;
+
+  stream_.direction = CRAS_STREAM_INPUT;
+
+  shm_writable_frames_ = 480;
+  InitShm(shm);
+  stream_.config->buffer_frames = 480;
+  stream_.config->cb_threshold = 480;
+  stream_.config->aud_cb = capture_samples_ready;
+  stream_.config->unified_cb = 0;
+
+  shm->area->write_buf_idx = 0;
+  shm->area->read_buf_idx = 0;
+  shm->area->write_offset[0] = 480 * 4;
+  shm->area->read_offset[0] = 0;
+
+  /* Normal scenario: read buffer has full of data written,
+   * handle_capture_data_ready() should consume all 480 frames and move
+   * read_buf_idx to the next buffer. */
+  handle_capture_data_ready(&stream_, 480);
+  EXPECT_EQ(1, samples_ready_called);
+  EXPECT_EQ(480, samples_ready_frames_value);
+  EXPECT_EQ(cras_shm_buff_for_idx(shm, 0), samples_ready_samples_value);
+  EXPECT_EQ(1, shm->area->read_buf_idx);
+  EXPECT_EQ(0, shm->area->write_offset[0]);
+  EXPECT_EQ(0, shm->area->read_offset[0]);
+
+  /* At the beginning of overrun: handle_capture_data_ready() should not
+   * proceed to call audio_cb because there's no data captured. */
+  shm->area->read_buf_idx = 0;
+  shm->area->write_offset[0] = 0;
+  shm->area->read_offset[0] = 0;
+  handle_capture_data_ready(&stream_, 480);
+  EXPECT_EQ(1, samples_ready_called);
+  EXPECT_EQ(0, shm->area->read_buf_idx);
+
+  /* In the middle of overrun: partially written buffer should trigger
+   * audio_cb, feed the full-sized read buffer to client. */
+  shm->area->read_buf_idx = 0;
+  shm->area->write_offset[0] = 123;
+  shm->area->read_offset[0] = 0;
+  handle_capture_data_ready(&stream_, 480);
+  EXPECT_EQ(1, samples_ready_called);
+  EXPECT_EQ(0, shm->area->read_buf_idx);
+}
+
 void CrasClientTestSuite::StreamConnected(CRAS_STREAM_DIRECTION direction) {
   struct cras_client_stream_connected msg;
-  int input_shm_key = 0;
-  int output_shm_key = 1;
+  int shm_fds[2] = {0, 1};
   int shm_max_size = 600;
   size_t format_bytes;
   struct cras_audio_shm_area area;
@@ -124,22 +185,18 @@
   area.config.frame_bytes = format_bytes;
   area.config.used_size = shm_writable_frames_ * format_bytes;
 
-  shmat_returned_value = &area;
+  mmap_return_value = &area;
 
   cras_fill_client_stream_connected(
       &msg,
       0,
       stream_.id,
       &server_format,
-      input_shm_key,
-      output_shm_key,
       shm_max_size);
 
-  stream_connected(&stream_, &msg);
+  stream_connected(&stream_, &msg, shm_fds, 2);
 
-  EXPECT_EQ(1, shmget_called);
-  EXPECT_EQ(1, shmat_called);
-  EXPECT_NE(0, stream_.thread.running);
+  EXPECT_EQ(CRAS_THREAD_RUNNING, stream_.thread.state);
 
   if (direction == CRAS_STREAM_OUTPUT) {
     EXPECT_EQ(NULL, stream_.capture_shm.area);
@@ -162,11 +219,11 @@
     CRAS_STREAM_DIRECTION direction) {
 
   struct cras_client_stream_connected msg;
-  int input_shm_key = 0;
-  int output_shm_key = 1;
+  int shm_fds[2] = {0, 1};
   int shm_max_size = 600;
   size_t format_bytes;
   struct cras_audio_shm_area area;
+  int rc;
 
   stream_.direction = direction;
   set_audio_format(&stream_.config->format, SND_PCM_FORMAT_S16_LE, 48000, 4);
@@ -174,34 +231,31 @@
   struct cras_audio_format server_format;
   set_audio_format(&server_format, SND_PCM_FORMAT_S16_LE, 44100, 2);
 
+  // Thread setup
+  rc = pipe(stream_.wake_fds);
+  ASSERT_EQ(0, rc);
+  stream_.thread.state = CRAS_THREAD_WARMUP;
+
   // Initialize shm area
   format_bytes = cras_get_format_bytes(&server_format);
   memset(&area, 0, sizeof(area));
   area.config.frame_bytes = format_bytes;
   area.config.used_size = shm_writable_frames_ * format_bytes;
 
-  shmat_returned_value = &area;
+  mmap_return_value = &area;
 
-  // let pthread_create fail
-  pthread_create_returned_value = -1;
-
+  // Put an error in the message.
   cras_fill_client_stream_connected(
       &msg,
-      0,
+      1,
       stream_.id,
       &server_format,
-      input_shm_key,
-      output_shm_key,
       shm_max_size);
 
-  stream_connected(&stream_, &msg);
+  stream_connected(&stream_, &msg, shm_fds, 2);
 
-  EXPECT_EQ(0, stream_.thread.running);
-  EXPECT_EQ(1, shmget_called);
-  EXPECT_EQ(1, shmat_called);
-  EXPECT_EQ(1, shmdt_called);
-  EXPECT_EQ(1, pipe_called);
-  EXPECT_EQ(2, close_called); // close the pipefds
+  EXPECT_EQ(CRAS_THREAD_STOP, stream_.thread.state);
+  EXPECT_EQ(4, close_called); // close the pipefds and shm_fds
 }
 
 TEST_F(CrasClientTestSuite, InputStreamConnectedFail) {
@@ -223,14 +277,23 @@
       malloc(sizeof(*(stream_ptr->config)));
   memcpy(stream_ptr->config, stream_.config, sizeof(*(stream_.config)));
 
+  pthread_cond_timedwait_retval = ETIMEDOUT;
+  EXPECT_EQ(-ETIMEDOUT, client_thread_add_stream(
+                              &client_, stream_ptr, &stream_id, NO_DEVICE));
+  EXPECT_EQ(pthread_cond_timedwait_called, 1);
+  EXPECT_EQ(pthread_join_called, 0);
+
+  InitStaticVariables();
   EXPECT_EQ(0, client_thread_add_stream(
       &client_, stream_ptr, &stream_id, NO_DEVICE));
   EXPECT_EQ(&client_, stream_ptr->client);
   EXPECT_EQ(stream_id, stream_ptr->id);
+  EXPECT_EQ(pthread_create_called, 1);
+  EXPECT_EQ(pipe_called, 1);
   EXPECT_EQ(1, sendmsg_called); // send connect message to server
   EXPECT_EQ(stream_ptr, stream_from_id(&client_, stream_id));
 
-  stream_ptr->thread.running = 1;
+  stream_ptr->thread.state = CRAS_THREAD_RUNNING;
 
   EXPECT_EQ(0, client_thread_rm_stream(&client_, stream_id));
 
@@ -252,21 +315,6 @@
 /* stubs */
 extern "C" {
 
-int shmget(key_t key, size_t size, int shmflg) {
-  ++shmget_called;
-  return 0;
-}
-
-void* shmat(int shmid, const void* shmaddr, int shmflg) {
-  ++shmat_called;
-  return shmat_returned_value;
-}
-
-int shmdt(const void *shmaddr) {
-  ++shmdt_called;
-  return 0;
-}
-
 ssize_t write(int fd, const void *buf, size_t count) {
   ++write_called;
   return count;
@@ -302,9 +350,21 @@
   return 0;
 }
 
+int pthread_cond_timedwait(pthread_cond_t *__restrict cond,
+                           pthread_mutex_t *__restrict mutex,
+                           const struct timespec *__restrict timeout) {
+  ++pthread_cond_timedwait_called;
+  return pthread_cond_timedwait_retval;
+}
+
 int clock_gettime(clockid_t clk_id, struct timespec *tp) {
   tp->tv_sec = 0;
   tp->tv_nsec = 0;
   return 0;
 }
+
+void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset)
+{
+  return mmap_return_value;
+}
 }
diff --git a/cras/src/tests/cras_dsp_pipeline_unittest.cc b/cras/src/tests/cras_dsp_pipeline_unittest.cc
index 62d523c..ef04cb6 100644
--- a/cras/src/tests/cras_dsp_pipeline_unittest.cc
+++ b/cras/src/tests/cras_dsp_pipeline_unittest.cc
@@ -411,6 +411,7 @@
   struct cras_expr_env env = CRAS_EXPR_ENV_INIT;
   cras_expr_env_install_builtins(&env);
   cras_expr_env_set_variable_string(&env, "output_device", "HDMI");
+  cras_expr_env_set_variable_boolean(&env, "swap_lr_disabled", 1);
 
   struct ini *ini = cras_dsp_ini_create(filename);
   ASSERT_TRUE(ini);
diff --git a/cras/src/tests/cras_monitor.c b/cras/src/tests/cras_monitor.c
new file mode 100644
index 0000000..f09b9f8
--- /dev/null
+++ b/cras/src/tests/cras_monitor.c
@@ -0,0 +1,319 @@
+/* Copyright 2016 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 <errno.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <syslog.h>
+#include <sys/select.h>
+#include <unistd.h>
+
+#include "cras_client.h"
+#include "cras_types.h"
+#include "cras_util.h"
+#include "cras_version.h"
+
+static void output_volume_changed(void *context, int32_t volume)
+{
+	printf("output volume: %d/100\n", volume);
+}
+
+static void output_mute_changed(void *context, int muted,
+				int user_muted, int mute_locked)
+{
+	printf("output mute: muted: %d, user_muted: %d, mute_locked: %d\n",
+	       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, mute_locked);
+}
+
+static void nodes_changed(void *context)
+{
+	printf("nodes changed\n");
+}
+
+static const char *string_for_direction(enum CRAS_STREAM_DIRECTION dir)
+{
+	switch(dir) {
+		case CRAS_STREAM_OUTPUT:
+			return "output";
+		case CRAS_STREAM_INPUT:
+			return "input";
+		case CRAS_STREAM_POST_MIX_PRE_DSP:
+			return "post_mix_pre_dsp";
+		default:
+			break;
+	}
+
+	return "undefined";
+}
+
+size_t node_array_index_of_node_id(struct cras_ionode_info *nodes,
+				   size_t num_nodes,
+				   cras_node_id_t node_id)
+{
+	uint32_t dev_index = dev_index_of(node_id);
+	uint32_t node_index = node_index_of(node_id);
+	size_t i;
+
+	for (i = 0; i < num_nodes; i++) {
+		if (nodes[i].iodev_idx == dev_index &&
+		    nodes[i].ionode_idx == node_index)
+			return i;
+	}
+	return CRAS_MAX_IONODES;
+}
+
+const char *node_name_for_node_id(struct cras_client *client,
+				  enum CRAS_STREAM_DIRECTION dir,
+				  cras_node_id_t node_id)
+{
+	struct cras_ionode_info nodes[CRAS_MAX_IONODES];
+	struct cras_iodev_info devs[CRAS_MAX_IODEVS];
+	size_t num_devs = CRAS_MAX_IODEVS;
+	size_t num_nodes = CRAS_MAX_IONODES;
+	uint32_t iodev_idx = dev_index_of(node_id);
+	size_t node_index;
+	char buf[1024];
+	int rc;
+
+	if (node_id == 0) {
+		return strdup("none");
+	} else if (iodev_idx <= 2) {
+		return strdup("fallback");
+	} else if (dir == CRAS_STREAM_POST_MIX_PRE_DSP) {
+		snprintf(buf, sizeof(buf), "%s node: %" PRIu64 "\n",
+			 string_for_direction(dir), node_id);
+		return strdup(buf);
+	} else if (dir == CRAS_STREAM_OUTPUT) {
+		rc = cras_client_get_output_devices(client, devs, nodes,
+					       &num_devs, &num_nodes);
+	} else if (dir == CRAS_STREAM_INPUT) {
+		rc = cras_client_get_input_devices(client, devs, nodes,
+					      &num_devs, &num_nodes);
+	} else {
+		return strdup("unknown");
+	}
+
+	if (rc != 0) {
+		syslog(LOG_ERR, "Couldn't get output devices: %s\n",
+		       strerror(-rc));
+		snprintf(buf, sizeof(buf), "%u:%u",
+			 iodev_idx, node_index_of(node_id));
+		return strdup(buf);
+	}
+	node_index = node_array_index_of_node_id(nodes, num_nodes, node_id);
+	if (node_index >= num_nodes)
+		snprintf(buf, sizeof(buf),
+			 "unknown: %zu >= %zu", node_index, num_nodes);
+	else
+		snprintf(buf, sizeof(buf), "%u:%u: %s",
+			 nodes[node_index].iodev_idx,
+			 nodes[node_index].ionode_idx,
+			 nodes[node_index].name);
+	return strdup(buf);
+}
+
+static void active_node_changed(void *context,
+				enum CRAS_STREAM_DIRECTION dir,
+				cras_node_id_t node_id)
+{
+	struct cras_client *client = (struct cras_client *)context;
+	const char *node_name = node_name_for_node_id(client, dir, node_id);
+	printf("active node (%s): %s\n", string_for_direction(dir), node_name);
+	free((void *)node_name);
+}
+
+static void output_node_volume_changed(void *context,
+				       cras_node_id_t node_id, int32_t volume)
+{
+	struct cras_client *client = (struct cras_client *)context;
+	const char *node_name =
+		node_name_for_node_id(client, CRAS_STREAM_OUTPUT, node_id);
+	printf("output node '%s' volume: %d\n", node_name, volume);
+	free((void *)node_name);
+}
+
+static void node_left_right_swapped_changed(void *context,
+					    cras_node_id_t node_id, int swapped)
+{
+	struct cras_client *client = (struct cras_client *)context;
+	const char *node_name =
+		node_name_for_node_id(client, CRAS_STREAM_OUTPUT, node_id);
+	printf("output node '%s' left-right swapped: %d\n", node_name, swapped);
+	free((void *)node_name);
+}
+
+static void input_node_gain_changed(void *context,
+				    cras_node_id_t node_id, int32_t gain)
+{
+	struct cras_client *client = (struct cras_client *)context;
+	const char *node_name =
+		node_name_for_node_id(client, CRAS_STREAM_INPUT, node_id);
+	printf("input node '%s' gain: %d\n", node_name, gain);
+	free((void *)node_name);
+}
+
+static void num_active_streams_changed(void *context,
+				       enum CRAS_STREAM_DIRECTION dir,
+				       uint32_t num_active_streams)
+{
+	printf("num active %s streams: %u\n",
+	       string_for_direction(dir), num_active_streams);
+}
+
+static void server_connection_callback(struct cras_client *client,
+				       cras_connection_status_t status,
+				       void *user_arg)
+{
+	const char *status_str = "undefined";
+	switch (status) {
+		case CRAS_CONN_STATUS_FAILED:
+			status_str = "error";
+			break;
+		case CRAS_CONN_STATUS_DISCONNECTED:
+			status_str = "disconnected";
+			break;
+		case CRAS_CONN_STATUS_CONNECTED:
+			status_str = "connected";
+			break;
+	}
+	printf("server %s\n", status_str);
+}
+
+static void print_usage(const char *command) {
+	fprintf(stderr,
+		"%s [options]\n"
+		"  Where [options] are:\n"
+		"    --sync|-s  - Use the synchronous connection functions.\n"
+		"    --log-level|-l <n>  - Set the syslog level (7 == "
+			"LOG_DEBUG).\n",
+		command);
+}
+
+int main(int argc, char **argv)
+{
+	struct cras_client *client;
+	int rc;
+	int option_character;
+	bool synchronous = false;
+	int log_level = LOG_WARNING;
+	static struct option long_options[] = {
+		{"sync", no_argument, NULL, 's'},
+		{"log-level", required_argument, NULL, 'l'},
+		{NULL, 0, NULL, 0},
+	};
+
+	while(true) {
+		int option_index = 0;
+
+		option_character = getopt_long(argc, argv, "sl:",
+					       long_options, &option_index);
+		if (option_character == -1)
+			break;
+		switch (option_character) {
+		case 's':
+			synchronous = !synchronous;
+			break;
+		case 'l':
+			log_level = atoi(optarg);
+			if (log_level < 0)
+				log_level = LOG_WARNING;
+			else if (log_level > LOG_DEBUG)
+				log_level = LOG_DEBUG;
+			break;
+		default:
+			print_usage(argv[0]);
+			return 1;
+		}
+	}
+
+	if (optind < argc) {
+		fprintf(stderr, "%s: Extra arguments.\n", argv[0]);
+		print_usage(argv[0]);
+		return 1;
+	}
+
+	openlog("cras_monitor", LOG_PERROR, LOG_USER);
+	setlogmask(LOG_UPTO(log_level));
+
+	rc = cras_client_create(&client);
+	if (rc < 0) {
+		syslog(LOG_ERR, "Couldn't create client.");
+		return rc;
+	}
+
+	cras_client_set_connection_status_cb(
+			client, server_connection_callback, NULL);
+
+	if (synchronous) {
+		rc = cras_client_connect(client);
+		if (rc != 0) {
+			syslog(LOG_ERR, "Could not connect to server.");
+			return -rc;
+		}
+	}
+
+	cras_client_set_output_volume_changed_callback(
+			client, 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);
+	cras_client_set_active_node_changed_callback(
+			client, active_node_changed);
+	cras_client_set_output_node_volume_changed_callback(
+			client, output_node_volume_changed);
+	cras_client_set_node_left_right_swapped_changed_callback(
+			client, node_left_right_swapped_changed);
+	cras_client_set_input_node_gain_changed_callback(
+			client, input_node_gain_changed);
+	cras_client_set_num_active_streams_changed_callback(
+			client, num_active_streams_changed);
+	cras_client_set_state_change_callback_context(client, client);
+
+	rc = cras_client_run_thread(client);
+	if (rc != 0) {
+		syslog(LOG_ERR, "Could not start thread.");
+		return -rc;
+	}
+
+	if (!synchronous) {
+		rc = cras_client_connect_async(client);
+		if (rc) {
+			syslog(LOG_ERR, "Couldn't connect to server.\n");
+			goto destroy_exit;
+		}
+	}
+
+	while(1) {
+		int rc;
+		char c;
+		rc = read(STDIN_FILENO, &c, 1);
+		if (rc < 0 || c == 'q')
+			return 0;
+	}
+
+destroy_exit:
+	cras_client_destroy(client);
+	return 0;
+}
diff --git a/cras/src/tests/cras_router.c b/cras/src/tests/cras_router.c
new file mode 100644
index 0000000..d813bc6
--- /dev/null
+++ b/cras/src/tests/cras_router.c
@@ -0,0 +1,269 @@
+/* Copyright 2016 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 <errno.h>
+#include <getopt.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/param.h>
+#include <unistd.h>
+
+#include "cras_client.h"
+#include "cras_types.h"
+#include "cras_util.h"
+#include "cras_version.h"
+
+#define PLAYBACK_BUFFERED_TIME_IN_NS (5000000)
+
+#define BUF_SIZE 32768
+
+static int keep_looping = 1;
+static int pipefd[2];
+struct cras_audio_format *aud_format;
+
+static int terminate_stream_loop(void)
+{
+	keep_looping = 0;
+	return write(pipefd[1], "1", 1);
+}
+
+static size_t get_block_size(uint64_t buffer_time_in_ns, size_t rate)
+{
+	static struct timespec t;
+
+	t.tv_nsec = buffer_time_in_ns;
+	t.tv_sec = 0;
+	return (size_t)cras_time_to_frames(&t, rate);
+}
+
+/* Run from callback thread. */
+static int got_samples(struct cras_client *client,
+		       cras_stream_id_t stream_id,
+		       uint8_t *captured_samples,
+		       uint8_t *playback_samples,
+		       unsigned int frames,
+		       const struct timespec *captured_time,
+		       const struct timespec *playback_time,
+		       void *user_arg)
+{
+	int *fd = (int *)user_arg;
+	int ret;
+	int write_size;
+	int frame_bytes;
+
+	frame_bytes = cras_client_format_bytes_per_frame(aud_format);
+	write_size = frames * frame_bytes;
+	ret = write(*fd, captured_samples, write_size);
+	if (ret != write_size)
+		printf("Error writing file\n");
+	return frames;
+}
+
+/* Run from callback thread. */
+static int put_samples(struct cras_client *client,
+		       cras_stream_id_t stream_id,
+		       uint8_t *captured_samples,
+		       uint8_t *playback_samples,
+		       unsigned int frames,
+		       const struct timespec *captured_time,
+		       const struct timespec *playback_time,
+		       void *user_arg)
+{
+	uint32_t frame_bytes = cras_client_format_bytes_per_frame(aud_format);
+	int fd = *(int *)user_arg;
+	uint8_t buff[BUF_SIZE];
+	int nread;
+
+	nread = read(fd, buff, MIN(frames * frame_bytes, BUF_SIZE));
+	if (nread <= 0) {
+		terminate_stream_loop();
+		return nread;
+	}
+
+	memcpy(playback_samples, buff, nread);
+	return nread / frame_bytes;
+}
+
+static int stream_error(struct cras_client *client,
+			cras_stream_id_t stream_id,
+			int err,
+			void *arg)
+{
+	printf("Stream error %d\n", err);
+	terminate_stream_loop();
+	return 0;
+}
+
+static int start_stream(struct cras_client *client,
+			cras_stream_id_t *stream_id,
+			struct cras_stream_params *params,
+			float stream_volume)
+{
+	int rc;
+
+	rc = cras_client_add_stream(client, stream_id, params);
+	if (rc < 0) {
+		fprintf(stderr, "adding a stream %d\n", rc);
+		return rc;
+	}
+	return cras_client_set_stream_volume(client,
+					     *stream_id,
+					     stream_volume);
+}
+
+static int run_file_io_stream(struct cras_client *client,
+			      int fd,
+			      int loop_fd,
+			      enum CRAS_STREAM_DIRECTION direction,
+			      size_t block_size,
+			      size_t rate,
+			      size_t num_channels)
+{
+	struct cras_stream_params *params;
+	cras_stream_id_t stream_id = 0;
+	int stream_playing = 0;
+	int *pfd = malloc(sizeof(*pfd));
+	*pfd = fd;
+	float volume_scaler = 1.0;
+
+	if (pipe(pipefd) == -1) {
+		perror("failed to open pipe");
+		return -errno;
+	}
+	aud_format = cras_audio_format_create(SND_PCM_FORMAT_S16_LE, rate,
+					      num_channels);
+	if (aud_format == NULL)
+		return -ENOMEM;
+
+	params = cras_client_unified_params_create(direction,
+						   block_size,
+						   0,
+						   0,
+						   pfd,
+						   got_samples,
+						   stream_error,
+						   aud_format);
+	if (params == NULL)
+		return -ENOMEM;
+
+	cras_client_run_thread(client);
+	stream_playing =
+		start_stream(client, &stream_id, params, volume_scaler) == 0;
+	if (!stream_playing)
+		return -EINVAL;
+
+	int *pfd1 = malloc(sizeof(*pfd1));
+	*pfd1 = loop_fd;
+	struct cras_stream_params *loop_params;
+	cras_stream_id_t loop_stream_id = 0;
+
+	direction = CRAS_STREAM_OUTPUT;
+
+	loop_params = cras_client_unified_params_create(direction,
+							block_size,
+							0,
+							0,
+							pfd1,
+							put_samples,
+							stream_error,
+							aud_format);
+	stream_playing =
+		start_stream(client, &loop_stream_id,
+			     loop_params, volume_scaler) == 0;
+	if (!stream_playing)
+		return -EINVAL;
+
+	fd_set poll_set;
+
+	FD_ZERO(&poll_set);
+	FD_SET(pipefd[0], &poll_set);
+	pselect(pipefd[0] + 1, &poll_set, NULL, NULL, NULL, NULL);
+	cras_client_stop(client);
+	cras_audio_format_destroy(aud_format);
+	cras_client_stream_params_destroy(params);
+	free(pfd);
+
+	close(pipefd[0]);
+	close(pipefd[1]);
+
+	return 0;
+}
+
+static struct option long_options[] = {
+	{"help", no_argument, 0, 'h'},
+	{"rate", required_argument, 0, 'r'},
+	{0, 0, 0, 0}
+};
+
+static void show_usage(void)
+{
+	printf("--help - shows this message and exits\n");
+	printf("--rate <N> - desired sample rate\n\n");
+	printf("Running cras_router will run a loop through ");
+	printf("from the currently set input to the currently set output.\n");
+	printf("Use cras_test_client --dump_s to see all avaiable nodes and");
+	printf(" cras_test_client --set_input/output to set a node.\n");
+}
+
+int main(int argc, char **argv)
+{
+	struct cras_client *client;
+	size_t rate = 44100;
+	size_t num_channels = 2;
+	size_t block_size;
+	int rc = 0;
+	int c, option_index;
+
+	option_index = 0;
+
+	rc = cras_client_create(&client);
+	if (rc < 0) {
+		fprintf(stderr, "Couldn't create client.\n");
+		return rc;
+	}
+
+	rc = cras_client_connect(client);
+	if (rc) {
+		fprintf(stderr, "Couldn't connect to server.\n");
+		goto destroy_exit;
+	}
+
+	while (1) {
+		c = getopt_long(argc, argv, "hr:",
+				long_options, &option_index);
+		if (c == -1)
+			break;
+		switch (c) {
+		case 'h':
+			show_usage();
+			goto destroy_exit;
+		case 'r':
+			rate = atoi(optarg);
+			break;
+		default:
+		break;
+		}
+	}
+
+	block_size = get_block_size(PLAYBACK_BUFFERED_TIME_IN_NS, rate);
+
+	/* Run loopthrough */
+	int pfd[2];
+
+	rc = pipe(pfd);
+	if (rc < 0) {
+		fprintf(stderr, "Couldn't create loopthrough pipe.\n");
+		return rc;
+	}
+	run_file_io_stream(client, pfd[1], pfd[0], CRAS_STREAM_INPUT,
+			   block_size, rate, num_channels);
+
+destroy_exit:
+	cras_client_destroy(client);
+	return rc;
+}
diff --git a/cras/src/tests/cras_test_client.c b/cras/src/tests/cras_test_client.c
index 87a89f0..6db3e11 100644
--- a/cras/src/tests/cras_test_client.c
+++ b/cras/src/tests/cras_test_client.c
@@ -10,7 +10,9 @@
 #include <pthread.h>
 #include <stdio.h>
 #include <stdint.h>
+#include <stdlib.h>
 #include <string.h>
+#include <syslog.h>
 #include <sys/param.h>
 #include <sys/select.h>
 #include <sys/stat.h>
@@ -50,6 +52,10 @@
 static char *channel_layout = NULL;
 static int pin_device_id;
 
+static int play_short_sound = 0;
+static int play_short_sound_periods = 0;
+static int play_short_sound_periods_left = 0;
+
 /* Conditional so the client thread can signal that main should exit. */
 static pthread_mutex_t done_mutex = PTHREAD_MUTEX_INITIALIZER;
 static pthread_cond_t done_cond = PTHREAD_COND_INITIALIZER;
@@ -123,6 +129,9 @@
 	int write_size;
 	int frame_bytes;
 
+	while (pause_client)
+		usleep(10000);
+
 	cras_client_calc_capture_latency(captured_time, &last_latency);
 
 	frame_bytes = cras_client_format_bytes_per_frame(aud_format);
@@ -184,6 +193,18 @@
 
 	cras_client_calc_playback_latency(playback_time, &last_latency);
 
+	if (play_short_sound) {
+		if (play_short_sound_periods_left)
+			/* Play a period from file. */
+			play_short_sound_periods_left--;
+		else {
+			/* Fill zeros to play silence. */
+			memset(playback_samples, 0,
+			       MIN(frames * frame_bytes, BUF_SIZE));
+			return frames;
+		}
+	}
+
 	nread = read(fd, buff, MIN(frames * frame_bytes, BUF_SIZE));
 	if (nread <= 0) {
 		if (exit_after_done_playing)
@@ -254,11 +275,9 @@
 {
 	unsigned i;
 
-	printf("\tID\tName (Stable ID)\n");
-	for (i = 0; i < num_devs; i++) {
-		printf("\t%u\t%s (%08x)\n", devs[i].idx, devs[i].name,
-					    devs[i].stable_id);
-	}
+	printf("\tID\tName\n");
+	for (i = 0; i < num_devs; i++)
+		printf("\t%u\t%s\n", devs[i].idx, devs[i].name);
 }
 
 static void print_node_info(const struct cras_ionode_info *nodes, int num_nodes,
@@ -266,10 +285,11 @@
 {
 	unsigned i;
 
-	printf("\t ID\t%4s   Plugged\tL/R swapped\t      "
-	       "Time\tType\t\t Name\n", is_input ? "Gain" : " Vol");
+	printf("\tStable Id\t ID\t%4s   Plugged\tL/R swapped\t      "
+	       "Time Hotword\tType\t\t Name\n", is_input ? "Gain" : " Vol");
 	for (i = 0; i < num_nodes; i++)
-		printf("\t%u:%u\t%5g  %7s\t%14s\t%10ld\t%-16s%c%s\n",
+		printf("\t(%08x)\t%u:%u\t%5g  %7s\t%14s\t%10ld %-7s\t%-16s%c%s\n",
+		       nodes[i].stable_id,
 		       nodes[i].iodev_idx,
 		       nodes[i].ionode_idx,
 		       is_input ? nodes[i].capture_gain / 100.0
@@ -277,6 +297,7 @@
 		       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);
@@ -363,7 +384,7 @@
 static void show_alog_tag(const struct audio_thread_event_log *log,
 			  unsigned int tag_idx)
 {
-	unsigned int tag = (log->log[tag_idx].tag_sec >> 24) & 0x1f;
+	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;
@@ -374,127 +395,152 @@
 	if (log->log[tag_idx].tag_sec == 0 && log->log[tag_idx].nsec == 0)
 		return;
 
+	printf("%10u.%09u  ", sec, nsec);
+
 	switch (tag) {
 	case AUDIO_THREAD_WAKE:
-		printf("WAKE: %u.%09u num_fds %d\n", sec, nsec, (int)data1);
+		printf("%-30s num_fds:%d\n", "WAKE", (int)data1);
 		break;
 	case AUDIO_THREAD_SLEEP:
-		printf("SLEEP: %u.%09u %09d.%09d long:%09d\n", sec, nsec,
-		       (int)data1, (int)data2, (int)data3);
+		printf("%-30s sleep:%09d.%09d longest_wake:%09d\n",
+		       "SLEEP", (int)data1, (int)data2, (int)data3);
 		break;
 	case AUDIO_THREAD_READ_AUDIO:
-		printf("READ_AUDIO: %u.%09u dev: %x hw_level: %u read %u\n",
-		       sec, nsec, data1, data2, data3);
+		printf("%-30s dev:%u hw_level:%u read:%u\n",
+		       "READ_AUDIO", data1, data2, data3);
+		break;
+	case AUDIO_THREAD_READ_AUDIO_TSTAMP:
+		printf("%-30s dev:%u tstamp:%09d.%09d\n",
+		       "READ_AUDIO_TSTAMP", data1, (int)data2, (int)data3);
 		break;
 	case AUDIO_THREAD_READ_AUDIO_DONE:
-		printf("READ_AUDIO_DONE: %u.%09u read remainder %u\n",
-		       sec, nsec, data1);
+		printf("%-30s read_remainder:%u\n", "READ_AUDIO_DONE", data1);
+		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("FILL_AUDIO: %u.%09u dev %x hw_level %u\n",
-		       sec, nsec, data1, data2);
+		printf("%-30s dev:%u hw_level:%u\n",
+		       "FILL_AUDIO", data1, data2);
+		break;
+	case AUDIO_THREAD_FILL_AUDIO_TSTAMP:
+		printf("%-30s dev:%u tstamp:%09d.%09d\n",
+		       "FILL_AUDIO_TSTAMP", data1, (int)data2, (int)data3);
 		break;
 	case AUDIO_THREAD_FILL_AUDIO_DONE:
-		printf("FILL_AUDIO_DONE: %u.%09u total_written %u\n",
-		       sec, nsec, data1);
+		printf("%-30s hw_level:%u total_written:%u min_cb_level:%u\n",
+		       "FILL_AUDIO_DONE", data1, data2, data3);
 		break;
 	case AUDIO_THREAD_WRITE_STREAMS_WAIT:
-		printf("WRITE_STREAMS_WAIT: %u.%09u for %u.%06u\n",
-		       sec, nsec, data1, data2);
+		printf("%-30s stream:%x\n", "WRITE_STREAMS_WAIT", data1);
 		break;
 	case AUDIO_THREAD_WRITE_STREAMS_WAIT_TO:
-		printf("WRITE_STREAMS_WAIT_TO: %u.%09u\n",
-		       sec, nsec);
+		printf("%-30s\n", "WRITE_STREAMS_WAIT_TO");
 		break;
 	case AUDIO_THREAD_WRITE_STREAMS_MIX:
-		printf("WRITE_STREAMS_MIX: %u.%09u wlimit %u max_offset %u\n",
-		       sec, nsec, data1, data2);
+		printf("%-30s write_limit:%u max_offset:%u\n",
+		       "WRITE_STREAMS_MIX", data1, data2);
 		break;
 	case AUDIO_THREAD_WRITE_STREAMS_MIXED:
-		printf("WRITE_STREAMS_MIXED: %u.%09u write_limit %u\n",
-		       sec, nsec, data1);
+		printf("%-30s write_limit:%u\n", "WRITE_STREAMS_MIXED", data1);
 		break;
 	case AUDIO_THREAD_WRITE_STREAMS_STREAM:
-		printf("WRITE_STREAMS_STREAM: %u.%09u id %x "
-		       "shm_frames %u cb_pending %u\n",
-		       sec, nsec, data1, data2, data3);
+		printf("%-30s id:%x shm_frames:%u cb_pending:%u\n",
+		       "WRITE_STREAMS_STREAM", data1, data2, data3);
 		break;
 	case AUDIO_THREAD_FETCH_STREAM:
-		printf("WRITE_STREAMS_FETCH_STREAM: %u.%09u id %x cbth %u delay %u\n",
-		       sec, nsec, data1, data2, data3);
+		printf("%-30s id:%x cbth:%u delay:%u\n",
+		       "WRITE_STREAMS_FETCH_STREAM", data1, data2, data3);
 		break;
 	case AUDIO_THREAD_STREAM_ADDED:
-		printf("STREAM_ADDED: %u.%9u id %x dev_idx %u\n",
-		       sec, nsec, data1, data2);
+		printf("%-30s id:%x dev:%u\n",
+		       "STREAM_ADDED", data1, data2);
 		break;
 	case AUDIO_THREAD_STREAM_REMOVED:
-		printf("STREAM_REMOVED: %u.%9u id %x\n", sec, nsec, data1);
+		printf("%-30s id:%x\n", "STREAM_REMOVED", data1);
 		break;
 	case AUDIO_THREAD_A2DP_ENCODE:
-		printf("A2DP_ENCODE: %u.%09u proc %d queued %u readable %u\n",
-		       sec, nsec, data1, data2, data3);
+		printf("%-30s proc:%d queued:%u readable:%u\n",
+		       "A2DP_ENCODE", data1, data2, data3);
 		break;
 	case AUDIO_THREAD_A2DP_WRITE:
-		printf("A2DP_WRITE: %u.%09u written %d queued %u\n",
-		       sec, nsec, data1, data2);
+		printf("%-30s written:%d queued:%u\n",
+		       "A2DP_WRITE", data1, data2);
 		break;
 	case AUDIO_THREAD_DEV_STREAM_MIX:
-		printf("DEV_STREAM_MIX: %u.%09u written %u read %u\n",
-		       sec, nsec, data1, data2);
+		printf("%-30s written:%u read:%u\n",
+		       "DEV_STREAM_MIX", data1, data2);
 		break;
 	case AUDIO_THREAD_CAPTURE_POST:
-		printf("CAPTURE_POST: %u.%09u stream %x thresh %u rd_buf %u\n",
-		       sec, nsec, data1, data2, data3);
+		printf("%-30s stream:%x thresh:%u rd_buf:%u\n",
+		       "CAPTURE_POST", data1, data2, data3);
 		break;
 	case AUDIO_THREAD_CAPTURE_WRITE:
-		printf("CAPTURE_WRITE: %u.%09u stream %x write %u shm_fr %u\n",
-		       sec, nsec, data1, data2, data3);
+		printf("%-30s stream:%x write:%u shm_fr:%u\n",
+		       "CAPTURE_WRITE", data1, data2, data3);
 		break;
 	case AUDIO_THREAD_CONV_COPY:
-		printf("CONV_COPY: %u.%09u wr_buf %u shm_writable %u"
-		       "offset %u\n",
-		       sec, nsec, data1, data2, data3);
+		printf("%-30s wr_buf:%u shm_writable:%u offset:%u\n",
+		       "CONV_COPY", data1, data2, data3);
 		break;
 	case AUDIO_THREAD_STREAM_SLEEP_TIME:
-		printf("STREAM_SLEEP_TIME: %u.%09u id:%x wake:%09u.%09d\n",
-		       sec, nsec, data1, data2, data3);
+		printf("%-30s id:%x wake:%09u.%09d\n",
+		       "STREAM_SLEEP_TIME", data1, (int)data2, (int)data3);
 		break;
 	case AUDIO_THREAD_STREAM_SLEEP_ADJUST:
-		printf("STREAM_SLEEP_ADJUST: %u.%09u id:%x from:%09u.%09d\n",
-		       sec, nsec, data1, data2, data3);
+		printf("%-30s id:%x from:%09u.%09d\n",
+		       "STREAM_SLEEP_ADJUST", data1, data2, data3);
 		break;
 	case AUDIO_THREAD_STREAM_SKIP_CB:
-		printf("STREAM_SKIP_CB: %u.%9u id %x write offsets %u %u\n",
-		       sec, nsec, data1, data2, data3);
+		printf("%-30s id:%x write_offset_0:%u write_offset_1:%u\n",
+		       "STREAM_SKIP_CB", data1, data2, data3);
 		break;
 	case AUDIO_THREAD_DEV_SLEEP_TIME:
-		printf("DEV_SLEEP_TIME: %u.%09u devidx:%x wake:%09u.%09d\n",
-		       sec, nsec, data1, data2, data3);
+		printf("%-30s dev:%u wake:%09u.%09d\n",
+		       "DEV_SLEEP_TIME", data1, data2, data3);
 		break;
 	case AUDIO_THREAD_SET_DEV_WAKE:
-		printf("SET_DEV_WAKE: %u.%09u devidx:%x hw_level:%u "
-		       "sleep:%u\n", sec, nsec, data1, data2, data3);
+		printf("%-30s dev:%u hw_level:%u sleep:%u\n",
+		       "SET_DEV_WAKE", data1, data2, data3);
 		break;
 	case AUDIO_THREAD_DEV_ADDED:
-		printf("DEV_ADDED: %u.%09u devidx:%x\n",
-		       sec, nsec, data1);
+		printf("%-30s dev:%u\n", "DEV_ADDED", data1);
 		break;
 	case AUDIO_THREAD_DEV_REMOVED:
-		printf("DEV_REMOVED: %u.%09u devidx:%x\n",
-		       sec, nsec, data1);
+		printf("%-30s dev:%u\n", "DEV_REMOVED", data1);
 		break;
 	case AUDIO_THREAD_IODEV_CB:
-		printf("IODEV_CB: %u.%09u is_write:%u\n", sec, nsec, data1);
+		printf("%-30s is_write:%u\n", "IODEV_CB", data1);
 		break;
 	case AUDIO_THREAD_PB_MSG:
-		printf("PB_MSG: %u.%09u msg_id:%u\n", sec, nsec, data1);
+		printf("%-30s msg_id:%u\n", "PB_MSG", data1);
 		break;
 	case AUDIO_THREAD_ODEV_NO_STREAMS:
-		printf("ODEV_NO_STREAMS: %u.%09u id:%u hw_level:%u cb_lev:%u\n",
-		       sec, nsec, data1, data2, data3);
+		printf("%-30s dev:%u\n",
+		       "ODEV_NO_STREAMS", data1);
+		break;
+	case AUDIO_THREAD_ODEV_LEAVE_NO_STREAMS:
+		printf("%-30s dev:%u\n",
+		       "ODEV_LEAVE_NO_STREAMS", data1);
+		break;
+	case AUDIO_THREAD_ODEV_START:
+		printf("%-30s dev:%u min_cb_level:%u\n",
+		       "ODEV_START", data1, data2);
+		break;
+	case AUDIO_THREAD_FILL_ODEV_ZEROS:
+		printf("%-30s dev:%u write:%u\n",
+		       "FILL_ODEV_ZEROS", data1, data2);
+		break;
+	case AUDIO_THREAD_ODEV_DEFAULT_NO_STREAMS:
+		printf("%-30s dev:%u hw_level:%u target:%u\n",
+		       "DEFAULT_NO_STREAMS", data1, data2, data3);
+		break;
+	case AUDIO_THREAD_SEVERE_UNDERRUN:
+		printf("%-30s dev:%u\n", "SEVERE_UNDERRUN", data1);
 		break;
 	default:
-		printf("Unknown alog tag %u\n", tag);
+		printf("%-30s tag:%u\n","UNKNOWN", tag);
 		break;
 	}
 }
@@ -518,14 +564,25 @@
 		       (info->devs[i].direction == CRAS_STREAM_INPUT)
 				? "Input" : "Output",
 		       info->devs[i].dev_name);
-		printf("%u %u %u %u %u %u %lf\n",
+		printf("buffer_size: %u\n"
+		       "min_buffer_level: %u\n"
+		       "min_cb_level: %u\n"
+		       "max_cb_level: %u\n"
+		       "frame_rate: %u\n"
+		       "num_channels: %u\n"
+		       "est_rate_ratio: %lf\n"
+		       "num_underruns: %u\n"
+		       "num_severe_underruns: %u\n",
 		       (unsigned int)info->devs[i].buffer_size,
 		       (unsigned int)info->devs[i].min_buffer_level,
 		       (unsigned int)info->devs[i].min_cb_level,
 		       (unsigned int)info->devs[i].max_cb_level,
 		       (unsigned int)info->devs[i].frame_rate,
 		       (unsigned int)info->devs[i].num_channels,
-		       info->devs[i].est_rate_ratio);
+		       info->devs[i].est_rate_ratio,
+		       (unsigned int)info->devs[i].num_underruns,
+		       (unsigned int)info->devs[i].num_severe_underruns);
+		printf("\n");
 	}
 
 	printf("-------------stream_dump------------\n");
@@ -534,20 +591,31 @@
 
 	for (i = 0; i < info->num_streams; i++) {
 		int channel;
-		printf("stream: %llx dev: %x\n",
+		printf("stream: %llx dev: %u\n",
 		       (unsigned long long)info->streams[i].stream_id,
 		       (unsigned int)info->streams[i].dev_idx);
-		printf("%d %u %u %u %u %u.%09u\n",
-		       info->streams[i].direction,
+		printf("direction: %s\n",
+		       (info->streams[i].direction == CRAS_STREAM_INPUT)
+				? "Input" : "Output");
+		printf("stream_type: %s\n",
+		       cras_stream_type_str(info->streams[i].stream_type));
+		printf("buffer_frames: %u\n"
+		       "cb_threshold: %u\n"
+		       "frame_rate: %u\n"
+		       "num_channels: %u\n"
+		       "longest_fetch_sec: %u.%09u\n"
+		       "num_overruns: %u\n",
 		       (unsigned int)info->streams[i].buffer_frames,
 		       (unsigned int)info->streams[i].cb_threshold,
 		       (unsigned int)info->streams[i].frame_rate,
 		       (unsigned int)info->streams[i].num_channels,
 		       (unsigned int)info->streams[i].longest_fetch_sec,
-		       (unsigned int)info->streams[i].longest_fetch_nsec);
+		       (unsigned int)info->streams[i].longest_fetch_nsec,
+		       (unsigned int)info->streams[i].num_overruns);
+		printf("channel map:");
 		for (channel = 0; channel < CRAS_CH_MAX; channel++)
 			printf("%d ", info->streams[i].channel_layout[channel]);
-		printf("\n");
+		printf("\n\n");
 	}
 
 	printf("Audio Thread Event Log:\n");
@@ -605,6 +673,7 @@
 			      int fd,
 			      enum CRAS_STREAM_DIRECTION direction,
 			      size_t block_size,
+			      enum CRAS_STREAM_TYPE stream_type,
 			      size_t rate,
 			      size_t num_channels,
 			      uint32_t flags,
@@ -668,7 +737,7 @@
 
 	params = cras_client_unified_params_create(direction,
 						   block_size,
-						   0,
+						   stream_type,
 						   flags,
 						   pfd,
 						   aud_cb,
@@ -803,6 +872,9 @@
 			       cras_client_get_system_min_capture_gain(client),
 			       cras_client_get_system_max_capture_gain(client));
 			break;
+		case '\'':
+			play_short_sound_periods_left = play_short_sound_periods;
+			break;
 		case '\n':
 			break;
 		default:
@@ -829,6 +901,7 @@
 static int run_capture(struct cras_client *client,
 		       const char *file,
 		       size_t block_size,
+		       enum CRAS_STREAM_TYPE stream_type,
 		       size_t rate,
 		       size_t num_channels,
 		       int is_loopback)
@@ -839,8 +912,8 @@
 		return -errno;
 	}
 
-	run_file_io_stream(client, fd, CRAS_STREAM_INPUT, block_size, rate,
-			   num_channels, 0, is_loopback);
+	run_file_io_stream(client, fd, CRAS_STREAM_INPUT, block_size,
+			   stream_type, rate, num_channels, 0, is_loopback);
 
 	close(fd);
 	return 0;
@@ -849,6 +922,7 @@
 static int run_playback(struct cras_client *client,
 			const char *file,
 			size_t block_size,
+			enum CRAS_STREAM_TYPE stream_type,
 			size_t rate,
 			size_t num_channels)
 {
@@ -860,8 +934,8 @@
 		return -errno;
 	}
 
-	run_file_io_stream(client, fd, CRAS_STREAM_OUTPUT,
-			   block_size, rate, num_channels, 0, 0);
+	run_file_io_stream(client, fd, CRAS_STREAM_OUTPUT, block_size,
+			   stream_type, rate, num_channels, 0, 0);
 
 	close(fd);
 	return 0;
@@ -871,8 +945,9 @@
 		       size_t block_size,
 		       size_t rate)
 {
-	run_file_io_stream(client, -1, CRAS_STREAM_INPUT, block_size, rate, 1,
-			   HOTWORD_STREAM, 0);
+	run_file_io_stream(client, -1, CRAS_STREAM_INPUT, block_size,
+			   CRAS_STREAM_TYPE_DEFAULT, rate, 1, HOTWORD_STREAM,
+			   0);
 	return 0;
 }
 static void print_server_info(struct cras_client *client)
@@ -902,6 +977,30 @@
 	pthread_mutex_unlock(&done_mutex);
 }
 
+static void hotword_models_cb(struct cras_client *client,
+			      const char *hotword_models)
+{
+	printf("Hotword models: %s\n", hotword_models);
+}
+
+static void print_hotword_models(struct cras_client *client,
+				 cras_node_id_t id)
+{
+	struct timespec wait_time;
+
+	cras_client_run_thread(client);
+	cras_client_connected_wait(client);
+	cras_client_get_hotword_models(client, id,
+				       hotword_models_cb);
+
+	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 check_output_plugged(struct cras_client *client, const char *name)
 {
 	cras_client_run_thread(client);
@@ -910,6 +1009,24 @@
 	       cras_client_output_dev_plugged(client, name) ? "Yes" : "No");
 }
 
+/* Repeatedly mute and unmute the output until there is an error. */
+static void mute_loop_test(struct cras_client *client, int auto_reconnect)
+{
+	int mute = 0;
+	int rc;
+
+	if (auto_reconnect)
+		cras_client_run_thread(client);
+	while(1) {
+		rc = cras_client_set_user_mute(client, mute);
+		printf("cras_client_set_user_mute(%d): %d\n", mute, rc);
+		if (rc != 0 && !auto_reconnect)
+			return;
+		mute = !mute;
+		sleep(2);
+	}
+}
+
 static struct option long_options[] = {
 	{"show_latency",	no_argument, &show_latency, 1},
 	{"show_rms",            no_argument, &show_rms, 1},
@@ -950,6 +1067,13 @@
 	{"pin_device",		required_argument,	0, '8'},
 	{"suspend",		required_argument,	0, '9'},
 	{"set_node_gain",	required_argument,	0, ':'},
+	{"play_short_sound",	required_argument,	0, '!'},
+	{"config_global_remix", required_argument,	0, ';'},
+	{"set_hotword_model",	required_argument,	0, '<'},
+	{"get_hotword_models",	required_argument,	0, '>'},
+	{"syslog_mask",		required_argument,	0, 'L'},
+	{"mute_loop_test",	required_argument,	0, 'M'},
+	{"stream_type",		required_argument,	0, 'T'},
 	{0, 0, 0, 0}
 };
 
@@ -970,15 +1094,19 @@
 	printf("--dump_dsp - Print status of dsp to syslog.\n");
 	printf("--dump_server_info - Print status of the server.\n");
 	printf("--duration_seconds <N> - Seconds to record or playback.\n");
+	printf("--get_hotword_models <N>:<M> - Get the supported hotword models of node\n");
 	printf("--help - Print this message.\n");
 	printf("--listen_for_hotword - Listen for a hotword if supported\n");
 	printf("--loopback_file <name> - Name of file to record loopback to.\n");
 	printf("--mute <0|1> - Set system mute state.\n");
+	printf("--mute_loop_test <0|1> - Continuously loop mute/umute. Argument: 0 - stop on error.\n"
+	       "                         1 - automatically reconnect to CRAS.\n");
 	printf("--num_channels <N> - Two for stereo.\n");
 	printf("--pin_device <N> - Playback/Capture only on the given device."
 	       "\n");
 	printf("--playback_file <name> - Name of file to play, "
 	       "\"-\" to playback raw audio from stdin.\n");
+	printf("--play_short_sound <N> - Plays the content in the file for N periods when ' is pressed.\n");
 	printf("--plug <N>:<M>:<0|1> - Set the plug state (0 or 1) for the"
 	       " ionode with the given index M on the device with index N\n");
 	printf("--rate <N> - Specifies the sample rate in Hz.\n");
@@ -989,6 +1117,7 @@
 	       "id from active output device list\n");
 	printf("--select_input <N>:<M> - Select the ionode with the given id as preferred input\n");
 	printf("--select_output <N>:<M> - Select the ionode with the given id as preferred output\n");
+	printf("--set_hotword_model <N>:<M>:<model> - Set the model to node\n");
 	printf("--playback_delay_us <N> - Set the time in us to delay a reply for playback when i is pressed\n");
 	printf("--set_node_volume <N>:<M>:<0-100> - Set the volume of the ionode with the given id\n");
 	printf("--show_latency - Display latency while playing or recording.\n");
@@ -998,6 +1127,8 @@
 	printf("--swap_left_right <N>:<M>:<0|1> - Swap or unswap (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");
+	printf("--syslog_mask <n> - Set the syslog mask to the given log level.\n");
 	printf("--test_hotword_file <N>:<filename> - Use filename as a hotword buffer for device N\n");
 	printf("--user_mute <0|1> - Set user mute state.\n");
 	printf("--version - Print the git commit ID that was used to build the client.\n");
@@ -1015,9 +1146,12 @@
 	const char *capture_file = NULL;
 	const char *playback_file = NULL;
 	const char *loopback_file = NULL;
+	enum CRAS_STREAM_TYPE stream_type = CRAS_STREAM_TYPE_DEFAULT;
 	int rc = 0;
 
 	option_index = 0;
+	openlog("cras_test_client", LOG_PERROR, LOG_USER);
+	setlogmask(LOG_UPTO(LOG_INFO));
 
 	rc = cras_client_create(&client);
 	if (rc < 0) {
@@ -1025,7 +1159,7 @@
 		return rc;
 	}
 
-	rc = cras_client_connect(client);
+	rc = cras_client_connect_timeout(client, 1000);
 	if (rc) {
 		fprintf(stderr, "Couldn't connect to server.\n");
 		goto destroy_exit;
@@ -1121,9 +1255,6 @@
 			break;
 		}
 		case 'y':
-		case 'z':
-			pause_in_playback_reply = atoi(optarg);
-			break;
 		case 'a': {
 			int dev_index = atoi(strtok(optarg, ":"));
 			int node_index = atoi(strtok(NULL, ":"));
@@ -1135,6 +1266,9 @@
 			cras_client_select_node(client, direction, id);
 			break;
 		}
+		case 'z':
+			pause_in_playback_reply = atoi(optarg);
+			break;
 		case 'k':
 		case 't':
 		case '1':
@@ -1246,6 +1380,77 @@
 			cras_client_set_suspend(client, suspend);
 			break;
 		}
+		case '!': {
+			play_short_sound = 1;
+			play_short_sound_periods = atoi(optarg);
+			break;
+		}
+		case ';': {
+			char *s;
+			int nch;
+			int size = 0;
+			float *coeff;
+
+			s = strtok(optarg, ":");
+			nch = atoi(s);
+			coeff = (float *)calloc(nch * nch,
+						sizeof(*coeff));
+			for (size = 0; size < nch * nch; size++) {
+				s = strtok(NULL, ",");
+				if (NULL == s)
+					break;
+				coeff[size] = atof(s);
+			}
+			cras_client_config_global_remix(client, nch, coeff);
+			free(coeff);
+			break;
+		}
+		case '<':
+		case '>': {
+			char *s;
+			int dev_index;
+			int node_index;
+
+			s = strtok(optarg, ":");
+			if (!s) {
+				show_usage();
+				return -EINVAL;
+			}
+			dev_index = atoi(s);
+
+			s = strtok(NULL, ":");
+			if (!s) {
+				show_usage();
+				return -EINVAL;
+			}
+			node_index = atoi(s);
+
+			s = strtok(NULL, ":");
+			if (!s && c == ';') {
+				show_usage();
+				return -EINVAL;
+			}
+
+			cras_node_id_t id = cras_make_node_id(dev_index,
+							      node_index);
+			if (c == '<')
+				cras_client_set_hotword_model(client, id, s);
+			else
+				print_hotword_models(client, id);
+			break;
+		}
+		case 'L': {
+			int log_level = atoi(optarg);
+
+			setlogmask(LOG_UPTO(log_level));
+			break;
+		}
+		case 'M':
+			mute_loop_test(client, atoi(optarg));
+			break;
+		case 'T':
+			stream_type = atoi(optarg);
+			break;
 		default:
 			break;
 		}
@@ -1258,20 +1463,22 @@
 	if (capture_file != NULL) {
 		if (strcmp(capture_file, "-") == 0)
 			rc = run_file_io_stream(client, 1, CRAS_STREAM_INPUT,
-					block_size, rate, num_channels, 0, 0);
+					block_size, stream_type, rate,
+					num_channels, 0, 0);
 		else
-			rc = run_capture(client, capture_file,
-					block_size, rate, num_channels, 0);
+			rc = run_capture(client, capture_file, block_size,
+					 stream_type, rate, num_channels, 0);
 	} else if (playback_file != NULL) {
 		if (strcmp(playback_file, "-") == 0)
 			rc = run_file_io_stream(client, 0, CRAS_STREAM_OUTPUT,
-					block_size, rate, num_channels, 0, 0);
+					block_size, stream_type, rate,
+					num_channels, 0, 0);
 		else
-			rc = run_playback(client, playback_file,
-					block_size, rate, num_channels);
+			rc = run_playback(client, playback_file, block_size,
+					  stream_type, rate, num_channels);
 	} else if (loopback_file != NULL) {
-		rc = run_capture(client, loopback_file,
-				 block_size, rate, num_channels, 1);
+		rc = run_capture(client, loopback_file, block_size,
+				 stream_type, rate, num_channels, 1);
 	}
 
 destroy_exit:
diff --git a/cras/src/tests/dev_stream_unittest.cc b/cras/src/tests/dev_stream_unittest.cc
index 19ff40d..8f90ed6 100644
--- a/cras/src/tests/dev_stream_unittest.cc
+++ b/cras/src/tests/dev_stream_unittest.cc
@@ -22,6 +22,7 @@
 };
 
 static struct timespec clock_gettime_retspec;
+static struct timespec cb_ts;
 
 static const int kBufferFrames = 1024;
 static const struct cras_audio_format fmt_s16le_44_1 = {
@@ -46,7 +47,7 @@
   unsigned int dst_format_bytes;
   const struct cras_audio_area *src;
   unsigned int src_offset;
-  unsigned int src_index;
+  float software_gain_scaler;
 };
 
 struct fmt_conv_call {
@@ -92,6 +93,9 @@
 static unsigned int rstream_get_readable_num;
 static uint8_t *rstream_get_readable_ptr;
 
+static int cras_rstream_audio_ready_called;
+static int cras_rstream_audio_ready_count;
+
 class CreateSuite : public testing::Test{
   protected:
     virtual void SetUp() {
@@ -117,6 +121,9 @@
       cras_fmt_conversion_needed_val = 0;
       cras_fmt_conv_set_linear_resample_rates_called = 0;
 
+      cras_rstream_audio_ready_called = 0;
+      cras_rstream_audio_ready_count = 0;
+
       memset(&copy_area_call, 0xff, sizeof(copy_area_call));
       memset(&conv_frames_call, 0xff, sizeof(conv_frames_call));
 
@@ -153,12 +160,12 @@
   struct cras_audio_area *area;
   struct cras_audio_area *stream_area;
   int16_t cap_buf[kBufferFrames * 2];
+  float software_gain_scaler = 10;
 
   devstr.stream = &rstream_;
   devstr.conv = NULL;
   devstr.conv_buffer = NULL;
   devstr.conv_buffer_size_frames = 0;
-  devstr.skip_mix = 0;
 
   area = (struct cras_audio_area*)calloc(1, sizeof(*area) +
                                                2 * sizeof(*area->channels));
@@ -180,13 +187,13 @@
   stream_area->channels[1].step_bytes = 4;
   stream_area->channels[1].buf = (uint8_t *)(shm_samples + 1);
 
-  dev_stream_capture(&devstr, area, 0, 0);
+  dev_stream_capture(&devstr, area, 0, software_gain_scaler);
 
   EXPECT_EQ(stream_area, copy_area_call.dst);
   EXPECT_EQ(0, copy_area_call.dst_offset);
   EXPECT_EQ(4, copy_area_call.dst_format_bytes);
   EXPECT_EQ(area, copy_area_call.src);
-  EXPECT_EQ(1, copy_area_call.src_index);
+  EXPECT_EQ(software_gain_scaler, copy_area_call.software_gain_scaler);
 
   free(area);
   free(stream_area);
@@ -197,13 +204,13 @@
   struct cras_audio_area *area;
   struct cras_audio_area *stream_area;
   int16_t cap_buf[kBufferFrames * 2];
+  float software_gain_scaler = 10;
 
   devstr.stream = &rstream_;
   devstr.conv = (struct cras_fmt_conv *)0xdead;
   devstr.conv_buffer =
       (struct byte_buffer *)byte_buffer_create(kBufferFrames * 2 * 4);
   devstr.conv_buffer_size_frames = kBufferFrames * 2;
-  devstr.skip_mix = 0;
 
   area = (struct cras_audio_area*)calloc(1, sizeof(*area) +
                                                2 * sizeof(*area->channels));
@@ -239,7 +246,7 @@
   conv_frames_ret = kBufferFrames / 2;
   cras_fmt_conv_convert_frames_in_frames_val = kBufferFrames;
   cras_fmt_conversion_needed_val = 1;
-  dev_stream_capture(&devstr, area, 0, 0);
+  dev_stream_capture(&devstr, area, 0, software_gain_scaler);
 
   EXPECT_EQ((struct cras_fmt_conv *)0xdead, conv_frames_call.conv);
   EXPECT_EQ((uint8_t *)cap_buf, conv_frames_call.in_buf);
@@ -251,41 +258,14 @@
   EXPECT_EQ(0, copy_area_call.dst_offset);
   EXPECT_EQ(4, copy_area_call.dst_format_bytes);
   EXPECT_EQ(devstr.conv_area, copy_area_call.src);
-  EXPECT_EQ(1, copy_area_call.src_index);
   EXPECT_EQ(conv_frames_ret, devstr.conv_area->frames);
+  EXPECT_EQ(software_gain_scaler, copy_area_call.software_gain_scaler);
 
   free(area);
   free(stream_area);
   free(devstr.conv_area);
 }
 
-TEST_F(CreateSuite, CreateNoSRCOutput) {
-  struct dev_stream *dev_stream;
-
-  rstream_.format = fmt_s16le_44_1;
-  in_fmt.frame_rate = 44100;
-  out_fmt.frame_rate = 44100;
-  dev_stream = dev_stream_create(&rstream_, 0, &fmt_s16le_44_1, (void *)0x55);
-  EXPECT_EQ(1, config_format_converter_called);
-  EXPECT_EQ(NULL, dev_stream->conv_buffer);
-  EXPECT_EQ(0, dev_stream->conv_buffer_size_frames);
-  dev_stream_destroy(dev_stream);
-}
-
-TEST_F(CreateSuite, CreateNoSRCInput) {
-  struct dev_stream *dev_stream;
-
-  rstream_.format = fmt_s16le_44_1;
-  rstream_.direction = CRAS_STREAM_INPUT;
-  in_fmt.frame_rate = 44100;
-  out_fmt.frame_rate = 44100;
-  dev_stream = dev_stream_create(&rstream_, 0, &fmt_s16le_44_1, (void *)0x55);
-  EXPECT_EQ(1, config_format_converter_called);
-  EXPECT_EQ(NULL, dev_stream->conv_buffer);
-  EXPECT_EQ(0, dev_stream->conv_buffer_size_frames);
-  dev_stream_destroy(dev_stream);
-}
-
 TEST_F(CreateSuite, CreateSRC44to48) {
   struct dev_stream *dev_stream;
 
@@ -293,7 +273,8 @@
   in_fmt.frame_rate = 44100;
   out_fmt.frame_rate = 48000;
   config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33);
-  dev_stream = dev_stream_create(&rstream_, 0, &fmt_s16le_48, (void *)0x55);
+  dev_stream = dev_stream_create(&rstream_, 0, &fmt_s16le_48, (void *)0x55,
+                                 &cb_ts);
   EXPECT_EQ(1, config_format_converter_called);
   EXPECT_NE(static_cast<byte_buffer*>(NULL), dev_stream->conv_buffer);
   EXPECT_LE(cras_frames_at_rate(in_fmt.frame_rate, kBufferFrames,
@@ -310,7 +291,8 @@
   in_fmt.frame_rate = 48000;
   out_fmt.frame_rate = 44100;
   config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33);
-  dev_stream = dev_stream_create(&rstream_, 0, &fmt_s16le_48, (void *)0x55);
+  dev_stream = dev_stream_create(&rstream_, 0, &fmt_s16le_48, (void *)0x55,
+                                 &cb_ts);
   EXPECT_EQ(1, config_format_converter_called);
   EXPECT_NE(static_cast<byte_buffer*>(NULL), dev_stream->conv_buffer);
   EXPECT_LE(cras_frames_at_rate(in_fmt.frame_rate, kBufferFrames,
@@ -326,7 +308,8 @@
   in_fmt.frame_rate = 48000;
   out_fmt.frame_rate = 44100;
   config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33);
-  dev_stream = dev_stream_create(&rstream_, 0, &fmt_s16le_44_1, (void *)0x55);
+  dev_stream = dev_stream_create(&rstream_, 0, &fmt_s16le_44_1, (void *)0x55,
+                                 &cb_ts);
   EXPECT_EQ(1, config_format_converter_called);
   EXPECT_NE(static_cast<byte_buffer*>(NULL), dev_stream->conv_buffer);
   EXPECT_LE(cras_frames_at_rate(in_fmt.frame_rate, kBufferFrames,
@@ -343,7 +326,8 @@
   in_fmt.frame_rate = 44100;
   out_fmt.frame_rate = 48000;
   config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33);
-  dev_stream = dev_stream_create(&rstream_, 0, &fmt_s16le_44_1, (void *)0x55);
+  dev_stream = dev_stream_create(&rstream_, 0, &fmt_s16le_44_1, (void *)0x55,
+                                 &cb_ts);
   EXPECT_EQ(1, config_format_converter_called);
   EXPECT_NE(static_cast<byte_buffer*>(NULL), dev_stream->conv_buffer);
   EXPECT_LE(cras_frames_at_rate(in_fmt.frame_rate, kBufferFrames,
@@ -360,7 +344,8 @@
   in_fmt.frame_rate = 44100;
   out_fmt.frame_rate = 48000;
   config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33);
-  dev_stream = dev_stream_create(&rstream_, 0, &fmt_s16le_44_1, (void *)0x55);
+  dev_stream = dev_stream_create(&rstream_, 0, &fmt_s16le_44_1, (void *)0x55,
+                                 &cb_ts);
   EXPECT_EQ(1, config_format_converter_called);
   EXPECT_NE(static_cast<byte_buffer*>(NULL), dev_stream->conv_buffer);
   EXPECT_LE(cras_frames_at_rate(in_fmt.frame_rate, kBufferFrames,
@@ -379,7 +364,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);
+  dev_stream = dev_stream_create(&rstream_, 0, &fmt_s16le_44_1, (void *)0x55,
+                                 &cb_ts);
   EXPECT_EQ(1, config_format_converter_called);
   EXPECT_NE(static_cast<byte_buffer*>(NULL), dev_stream->conv_buffer);
   EXPECT_LE(cras_frames_at_rate(in_fmt.frame_rate, kBufferFrames,
@@ -407,7 +393,7 @@
   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);
+                                 (void *)0x55, &cb_ts);
 
   dev_stream_set_dev_rate(dev_stream, 44100, 1.01, 1.0, 0);
   EXPECT_EQ(1, cras_fmt_conv_set_linear_resample_rates_called);
@@ -437,7 +423,7 @@
   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);
+                                 (void *)0x55, &cb_ts);
 
   dev_stream_set_dev_rate(dev_stream, 44100, 1.01, 1.0, 0);
   EXPECT_EQ(1, cras_fmt_conv_set_linear_resample_rates_called);
@@ -531,7 +517,7 @@
   unsigned int dev_id = 9;
 
   dev_stream = dev_stream_create(&rstream_, dev_id, &fmt_s16le_44_1,
-                                 (void *)0x55);
+                                 (void *)0x55, &cb_ts);
 
   /* Verify stream cannot fetch when it's still pending. */
   cras_shm_set_callback_pending(&rstream_.shm, 1);
@@ -550,6 +536,277 @@
   dev_stream_destroy(dev_stream);
 }
 
+TEST_F(CreateSuite, StreamCanSend) {
+  struct dev_stream *dev_stream;
+  unsigned int dev_id = 9;
+  int written_frames;
+  int rc;
+  struct timespec expected_next_cb_ts;
+
+  rstream_.direction = CRAS_STREAM_INPUT;
+  dev_stream = dev_stream_create(&rstream_, dev_id, &fmt_s16le_44_1,
+                                 (void *)0x55, &cb_ts);
+
+  // Assume there is a next_cb_ts on rstream.
+  rstream_.next_cb_ts.tv_sec = 1;
+  rstream_.next_cb_ts.tv_nsec = 0;
+
+  // Case 1: Not enough samples. Time is not late enough.
+  //         Stream can not send data to client.
+  clock_gettime_retspec.tv_sec = 0;
+  clock_gettime_retspec.tv_nsec = 0;
+  rc = dev_stream_capture_update_rstream(dev_stream);
+  EXPECT_EQ(0, cras_rstream_audio_ready_called);
+  EXPECT_EQ(0, rc);
+
+  // Case 2: Not enough samples. Time is late enough.
+  //         Stream can not send data to client.
+
+  // Assume time is greater than next_cb_ts.
+  clock_gettime_retspec.tv_sec = 1;
+  clock_gettime_retspec.tv_nsec = 500;
+  // However, written frames is less than cb_threshold.
+  // Stream still can not send samples to client.
+  rc = dev_stream_capture_update_rstream(dev_stream);
+  EXPECT_EQ(0, cras_rstream_audio_ready_called);
+  EXPECT_EQ(0, rc);
+
+  // Case 3: Enough samples. Time is not late enough.
+  //         Stream can not send data to client.
+
+  // Assume time is less than next_cb_ts.
+  clock_gettime_retspec.tv_sec = 0;
+  clock_gettime_retspec.tv_nsec = 0;
+  // Enough samples are written.
+  written_frames = rstream_.cb_threshold + 10;
+  cras_shm_buffer_written(&rstream_.shm, written_frames);
+  // Stream still can not send samples to client.
+  rc = dev_stream_capture_update_rstream(dev_stream);
+  EXPECT_EQ(0, cras_rstream_audio_ready_called);
+  EXPECT_EQ(0, rc);
+
+  // Case 4: Enough samples. Time is late enough.
+  //         Stream should send one cb_threshold to client.
+  clock_gettime_retspec.tv_sec = 1;
+  clock_gettime_retspec.tv_nsec = 500;
+  rc = dev_stream_capture_update_rstream(dev_stream);
+  EXPECT_EQ(1, cras_rstream_audio_ready_called);
+  EXPECT_EQ(rstream_.cb_threshold, cras_rstream_audio_ready_count);
+  EXPECT_EQ(0, rc);
+
+  // Check next_cb_ts is increased by one sleep interval.
+  expected_next_cb_ts.tv_sec = 1;
+  expected_next_cb_ts.tv_nsec = 0;
+  add_timespecs(&expected_next_cb_ts,
+                &rstream_.sleep_interval_ts);
+  EXPECT_EQ(expected_next_cb_ts.tv_sec, rstream_.next_cb_ts.tv_sec);
+  EXPECT_EQ(expected_next_cb_ts.tv_nsec, rstream_.next_cb_ts.tv_nsec);
+
+  // Reset stub data of interest.
+  cras_rstream_audio_ready_called = 0;
+  cras_rstream_audio_ready_count = 0;
+  rstream_.next_cb_ts.tv_sec = 1;
+  rstream_.next_cb_ts.tv_nsec = 0;
+
+  // Case 5: Enough samples. Time is late enough and it is too late
+  //         such that a new next_cb_ts is in the past.
+  //         Stream should send one cb_threshold to client and reset schedule.
+  clock_gettime_retspec.tv_sec = 2;
+  clock_gettime_retspec.tv_nsec = 0;
+  rc = dev_stream_capture_update_rstream(dev_stream);
+  EXPECT_EQ(1, cras_rstream_audio_ready_called);
+  EXPECT_EQ(rstream_.cb_threshold, cras_rstream_audio_ready_count);
+  EXPECT_EQ(0, rc);
+
+  // Check next_cb_ts is rest to be now plus one sleep interval.
+  expected_next_cb_ts.tv_sec = 2;
+  expected_next_cb_ts.tv_nsec = 0;
+  add_timespecs(&expected_next_cb_ts,
+                &rstream_.sleep_interval_ts);
+  EXPECT_EQ(expected_next_cb_ts.tv_sec, rstream_.next_cb_ts.tv_sec);
+  EXPECT_EQ(expected_next_cb_ts.tv_nsec, rstream_.next_cb_ts.tv_nsec);
+
+  dev_stream_destroy(dev_stream);
+}
+
+TEST_F(CreateSuite, StreamCanSendBulkAudio) {
+  struct dev_stream *dev_stream;
+  unsigned int dev_id = 9;
+  int written_frames;
+  int rc;
+  struct timespec expected_next_cb_ts;
+
+  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);
+
+  // Assume there is a next_cb_ts on rstream.
+  rstream_.next_cb_ts.tv_sec = 1;
+  rstream_.next_cb_ts.tv_nsec = 0;
+
+  // Case 1: Not enough samples. Time is not late enough.
+  //         Bulk audio stream can not send data to client.
+  clock_gettime_retspec.tv_sec = 0;
+  clock_gettime_retspec.tv_nsec = 0;
+  rc = dev_stream_capture_update_rstream(dev_stream);
+  EXPECT_EQ(0, cras_rstream_audio_ready_called);
+  EXPECT_EQ(0, rc);
+
+  // Case 2: Not enough samples. Time is late enough.
+  //         Bulk audio stream can not send data to client.
+
+  // Assume time is greater than next_cb_ts.
+  clock_gettime_retspec.tv_sec = 1;
+  clock_gettime_retspec.tv_nsec = 500;
+  // However, written frames is less than cb_threshold.
+  // Stream still can not send samples to client.
+  rc = dev_stream_capture_update_rstream(dev_stream);
+  EXPECT_EQ(0, cras_rstream_audio_ready_called);
+  EXPECT_EQ(0, rc);
+
+  // Case 3: Enough samples. Time is not late enough.
+  //         Bulk audio stream CAN send data to client.
+
+  // Assume time is less than next_cb_ts.
+  clock_gettime_retspec.tv_sec = 0;
+  clock_gettime_retspec.tv_nsec = 0;
+  // Enough samples are written.
+  written_frames = rstream_.cb_threshold + 10;
+  cras_shm_buffer_written(&rstream_.shm, written_frames);
+  // Bulk audio stream can send all written samples to client.
+  rc = dev_stream_capture_update_rstream(dev_stream);
+  EXPECT_EQ(1, cras_rstream_audio_ready_called);
+  EXPECT_EQ(written_frames, cras_rstream_audio_ready_count);
+  EXPECT_EQ(0, rc);
+
+  // Case 4: Enough samples. Time is late enough.
+  //         Bulk audio stream can send all written samples to client.
+
+  // Reset stub data of interest.
+  cras_rstream_audio_ready_called = 0;
+  cras_rstream_audio_ready_count = 0;
+  rstream_.next_cb_ts.tv_sec = 1;
+  rstream_.next_cb_ts.tv_nsec = 0;
+
+  clock_gettime_retspec.tv_sec = 1;
+  clock_gettime_retspec.tv_nsec = 500;
+  rc = dev_stream_capture_update_rstream(dev_stream);
+  EXPECT_EQ(1, cras_rstream_audio_ready_called);
+  EXPECT_EQ(written_frames, cras_rstream_audio_ready_count);
+  EXPECT_EQ(0, rc);
+
+  // Check next_cb_ts is increased by one sleep interval.
+  expected_next_cb_ts.tv_sec = 1;
+  expected_next_cb_ts.tv_nsec = 0;
+  add_timespecs(&expected_next_cb_ts,
+                &rstream_.sleep_interval_ts);
+  EXPECT_EQ(expected_next_cb_ts.tv_sec, rstream_.next_cb_ts.tv_sec);
+  EXPECT_EQ(expected_next_cb_ts.tv_nsec, rstream_.next_cb_ts.tv_nsec);
+
+  dev_stream_destroy(dev_stream);
+}
+
+TEST_F(CreateSuite, InputDevStreamWakeTimeByNextCbTs) {
+  struct dev_stream *dev_stream;
+  unsigned int dev_id = 9;
+  int rc;
+  unsigned int curr_level = 0;
+  int written_frames;
+  struct timespec level_tstamp = {.tv_sec = 1, .tv_nsec = 0};
+  struct timespec wake_time_out = {.tv_sec = 0, .tv_nsec = 0};
+
+  rstream_.direction = CRAS_STREAM_INPUT;
+  dev_stream = dev_stream_create(&rstream_, dev_id, &fmt_s16le_44_1,
+                                 (void *)0x55, &cb_ts);
+
+  // Assume there is a next_cb_ts on rstream.
+  rstream_.next_cb_ts.tv_sec = 1;
+  rstream_.next_cb_ts.tv_nsec = 500000;
+
+  // Assume there are enough samples for stream.
+  written_frames = rstream_.cb_threshold + 10;
+  cras_shm_buffer_written(&rstream_.shm, written_frames);
+
+  rc = dev_stream_wake_time(dev_stream, curr_level,
+                            &level_tstamp, &wake_time_out);
+
+  // The next wake up time is determined by next_cb_ts on dev_stream.
+  EXPECT_EQ(rstream_.next_cb_ts.tv_sec, wake_time_out.tv_sec);
+  EXPECT_EQ(rstream_.next_cb_ts.tv_nsec, wake_time_out.tv_nsec);
+  EXPECT_EQ(0, rc);
+}
+
+TEST_F(CreateSuite, InputDevStreamWakeTimeByDevice) {
+  struct dev_stream *dev_stream;
+  unsigned int dev_id = 9;
+  int rc;
+  unsigned int curr_level = 100;
+  int written_frames;
+  struct timespec level_tstamp = {.tv_sec = 1, .tv_nsec = 0};
+  struct timespec wake_time_out = {.tv_sec = 0, .tv_nsec = 0};
+  struct timespec expected_tstamp = {.tv_sec = 0, .tv_nsec = 0};
+  struct timespec needed_time_for_device = {.tv_sec = 0, .tv_nsec = 0};
+  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);
+
+  // Assume there is a next_cb_ts on rstream, that is, 1.005 seconds.
+  rstream_.next_cb_ts.tv_sec = 1;
+  rstream_.next_cb_ts.tv_nsec = 5000000; // 5ms
+
+  // Assume there are not enough samples for stream.
+  written_frames = 123;
+  cras_shm_buffer_written(&rstream_.shm, written_frames);
+
+  // Compute wake up time for device level to reach enough samples
+  // for one cb_threshold:
+  // Device has 100 samples (48K rate).
+  // Stream has 123 samples (44.1K rate)
+  // cb_threshold = 512 samples.
+  // Stream needs 512 - 123 = 389 samples.
+  // Converted to device rate => 389 * 48000.0 / 44100 = 423.4 samples
+  // => 424 samples.
+  // Device needs another 424 - 100 = 324 samples.
+  // Time for 252 samples = 324 / 48000 = 0.00675 sec.
+  // So expected wake up time for samples is at level_tstamp + 0.00675 sec =
+  // 1.00675 seconds.
+  needed_frames_from_device = cras_frames_at_rate(
+       44100, rstream_.cb_threshold - written_frames, 48000);
+  needed_frames_from_device -= curr_level;
+  cras_frames_to_time(needed_frames_from_device, 48000,
+                      &needed_time_for_device);
+
+  expected_tstamp.tv_sec = level_tstamp.tv_sec;
+  expected_tstamp.tv_nsec = level_tstamp.tv_nsec;
+
+  add_timespecs(&expected_tstamp, &needed_time_for_device);
+
+  // Set the stub data for cras_fmt_conv_out_frames_to_in.
+  out_fmt.frame_rate = 44100;
+  in_fmt.frame_rate = 48000;
+
+  rc = dev_stream_wake_time(dev_stream, curr_level,
+                            &level_tstamp, &wake_time_out);
+
+  // The next wake up time is determined by needed time for device level
+  // to reach enough samples for one cb_threshold.
+  EXPECT_EQ(expected_tstamp.tv_sec, wake_time_out.tv_sec);
+  EXPECT_EQ(expected_tstamp.tv_nsec, wake_time_out.tv_nsec);
+  EXPECT_EQ(0, rc);
+
+  // Assume current level is larger than cb_threshold.
+  // The wake up time is determined by next_cb_ts.
+  curr_level += rstream_.cb_threshold;
+  rc = dev_stream_wake_time(dev_stream, curr_level,
+                            &level_tstamp, &wake_time_out);
+  EXPECT_EQ(rstream_.next_cb_ts.tv_sec, wake_time_out.tv_sec);
+  EXPECT_EQ(rstream_.next_cb_ts.tv_nsec, wake_time_out.tv_nsec);
+  EXPECT_EQ(0, rc);
+}
+
 //  Test set_playback_timestamp.
 TEST(DevStreamTimimg, SetPlaybackTimeStampSimple) {
   struct cras_timespec ts;
@@ -622,10 +879,13 @@
 extern "C" {
 
 int cras_rstream_audio_ready(struct cras_rstream *stream, size_t count) {
+  cras_rstream_audio_ready_count = count;
+  cras_rstream_audio_ready_called++;
   return 0;
 }
 
-int cras_rstream_request_audio(const struct cras_rstream *stream) {
+int cras_rstream_request_audio(struct cras_rstream *stream,
+    const struct timespec *now) {
   return 0;
 }
 
@@ -743,16 +1003,16 @@
 
 unsigned int cras_audio_area_copy(const struct cras_audio_area *dst,
                                   unsigned int dst_offset,
-				  const struct cras_audio_format *dst_fmt,
+                                  const struct cras_audio_format *dst_fmt,
                                   const struct cras_audio_area *src,
                                   unsigned int src_offset,
-                                  unsigned int src_index) {
+                                  float software_gain_scaler) {
   copy_area_call.dst = dst;
   copy_area_call.dst_offset = dst_offset;
   copy_area_call.dst_format_bytes = cras_get_format_bytes(dst_fmt);
   copy_area_call.src = src;
   copy_area_call.src_offset = src_offset;
-  copy_area_call.src_index = src_index;
+  copy_area_call.software_gain_scaler = software_gain_scaler;
   return src->frames;
 }
 
diff --git a/cras/src/tests/device_blacklist_unittest.cc b/cras/src/tests/device_blacklist_unittest.cc
index 9e93a4e..812ac4c 100644
--- a/cras/src/tests/device_blacklist_unittest.cc
+++ b/cras/src/tests/device_blacklist_unittest.cc
@@ -11,7 +11,7 @@
 
 namespace {
 
-static const char CONFIG_PATH[] = "/tmp";
+static const char CONFIG_PATH[] = CRAS_UT_TMPDIR;
 static const char CONFIG_FILENAME[] = "device_blacklist";
 
 void CreateConfigFile(const char* config_text) {
diff --git a/cras/src/tests/device_monitor_unittest.cc b/cras/src/tests/device_monitor_unittest.cc
new file mode 100644
index 0000000..31913be
--- /dev/null
+++ b/cras/src/tests/device_monitor_unittest.cc
@@ -0,0 +1,154 @@
+// Copyright 2016 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 <stdio.h>
+#include <gtest/gtest.h>
+
+extern "C" {
+#include "cras_device_monitor.c"
+#include "cras_iodev.h"
+#include "cras_main_message.h"
+}
+
+static enum CRAS_MAIN_MESSAGE_TYPE type_set;
+static struct cras_device_monitor_message *sent_msg;
+static int enable_dev_called;
+static cras_iodev *enable_dev;
+static int disable_dev_called;
+static cras_iodev *disable_dev;
+static int set_mute_called;
+static cras_iodev *mute_dev;
+
+void ResetStubData() {
+  type_set = (enum CRAS_MAIN_MESSAGE_TYPE)0;
+  enable_dev_called = 0;
+  enable_dev = NULL;
+  disable_dev_called = 0;
+  disable_dev = NULL;
+  set_mute_called = 0;
+  mute_dev = NULL;
+}
+
+namespace {
+
+TEST(DeviceMonitorTestSuite, Init) {
+  ResetStubData();
+
+  cras_device_monitor_init();
+
+  EXPECT_EQ(type_set, CRAS_MAIN_MONITOR_DEVICE);
+}
+
+TEST(DeviceMonitorTestSuite, ResetDevice) {
+  struct cras_iodev dev;
+  ResetStubData();
+  // sent_msg will be filled with message content in cras_main_message_send.
+  sent_msg = (struct cras_device_monitor_message *)calloc(1, sizeof(*sent_msg));
+
+  cras_device_monitor_reset_device(&dev);
+
+  EXPECT_EQ(sent_msg->header.type, CRAS_MAIN_MONITOR_DEVICE);
+  EXPECT_EQ(sent_msg->header.length, sizeof(*sent_msg));
+  EXPECT_EQ(sent_msg->message_type, RESET_DEVICE);
+  EXPECT_EQ(sent_msg->iodev, &dev);
+
+  free(sent_msg);
+}
+
+TEST(DeviceMonitorTestSuite, HandleResetDevice) {
+  struct cras_iodev dev;
+  struct cras_device_monitor_message msg;
+  struct cras_main_message *main_message =
+      reinterpret_cast<struct cras_main_message *>(&msg);
+
+  ResetStubData();
+
+  // Filled msg with message content for resetting device.
+  init_device_msg(&msg, RESET_DEVICE, &dev);
+  // Assume the pipe works fine and main message handler receives the same
+  // message.
+  handle_device_message(main_message, NULL);
+
+  // Verify that disable/enable functions are called with correct device.
+  EXPECT_EQ(enable_dev_called, 1);
+  EXPECT_EQ(enable_dev, &dev);
+  EXPECT_EQ(disable_dev_called, 1);
+  EXPECT_EQ(disable_dev, &dev);
+}
+
+TEST(DeviceMonitorTestSuite, MuteDevice) {
+  struct cras_iodev dev;
+  ResetStubData();
+  // sent_msg will be filled with message content in cras_main_message_send.
+  sent_msg = (struct cras_device_monitor_message *)calloc(1, sizeof(*sent_msg));
+
+  cras_device_monitor_set_device_mute_state(&dev);
+
+  EXPECT_EQ(sent_msg->header.type, CRAS_MAIN_MONITOR_DEVICE);
+  EXPECT_EQ(sent_msg->header.length, sizeof(*sent_msg));
+  EXPECT_EQ(sent_msg->message_type, SET_MUTE_STATE);
+  EXPECT_EQ(sent_msg->iodev, &dev);
+
+  free(sent_msg);
+}
+
+TEST(DeviceMonitorTestSuite, HandleMuteDevice) {
+  struct cras_iodev dev;
+  struct cras_device_monitor_message msg;
+  struct cras_main_message *main_message =
+      reinterpret_cast<struct cras_main_message *>(&msg);
+
+  ResetStubData();
+
+  // Filled msg with message content for device mute/unmute.
+  init_device_msg(&msg, SET_MUTE_STATE, &dev);
+  // Assume the pipe works fine and main message handler receives the same
+  // message.
+  handle_device_message(main_message, NULL);
+
+  // Verify that cras_iodev_set_mute is called with correct device.
+  EXPECT_EQ(set_mute_called, 1);
+  EXPECT_EQ(mute_dev, &dev);
+}
+
+extern "C" {
+
+int cras_main_message_add_handler(enum CRAS_MAIN_MESSAGE_TYPE type,
+                                  cras_message_callback callback,
+                                  void *callback_data) {
+  type_set = type;
+  return 0;
+}
+
+int cras_main_message_send(struct cras_main_message *msg) {
+  // Copy the sent message so we can examine it in the test later.
+  memcpy(sent_msg, msg, sizeof(*sent_msg));
+  return 0;
+};
+
+void cras_iodev_list_enable_dev(struct cras_iodev *dev) {
+  enable_dev_called++;
+  enable_dev = dev;
+}
+
+void cras_iodev_list_disable_dev(struct cras_iodev *dev) {
+  disable_dev_called++;
+  disable_dev = dev;
+}
+
+int cras_iodev_set_mute(struct cras_iodev *dev) {
+  set_mute_called++;
+  mute_dev = dev;
+  return 0;
+}
+
+}  // extern "C"
+}  // namespace
+
+int main(int argc, char **argv) {
+  ::testing::InitGoogleTest(&argc, argv);
+  int rc = RUN_ALL_TESTS();
+
+  return rc;
+}
diff --git a/cras/src/tests/dsp_ini_unittest.cc b/cras/src/tests/dsp_ini_unittest.cc
index d27c693..b7f488d 100644
--- a/cras/src/tests/dsp_ini_unittest.cc
+++ b/cras/src/tests/dsp_ini_unittest.cc
@@ -174,6 +174,133 @@
   cras_dsp_ini_free(ini);
 }
 
+TEST_F(DspIniTestSuite, TwoChannelWithSwap) {
+
+  /*
+   *  Stated in ini:
+   *
+   *   m0 ==(a0, a1)== m1 ==(b0, b1)== m2
+   *
+   *  After inserting swap_lr plugin:
+   *
+   *   m0 ==(a0, a1)== m1 ==(b0, b1)== m_swap_lr ==(swap_lr_0, swap_lr_1)== m2
+   *
+   */
+
+  const char *content =
+      "[M0]\n"
+      "library=builtin\n"
+      "label=source\n"
+      "purpose=playback\n"
+      "output_0={a0}\n"
+      "output_1={a1}\n"
+      "[M1]\n"
+      "library=builtin\n"
+      "label=foo\n"
+      "purpose=playback\n"
+      "input_0={a0}\n"
+      "input_1={a1}\n"
+      "output_2={b0}\n"
+      "output_3={b1}\n"
+      "[M2]\n"
+      "library=builtin\n"
+      "label=sink\n"
+      "purpose=playback\n"
+      "input_0={b0}\n"
+      "input_1={b1}\n";
+  fprintf(fp, "%s", content);
+  CloseFile();
+
+  struct ini *ini = cras_dsp_ini_create(filename);
+
+  /* 3 plugins and 1 swap_lr plugin. */
+  EXPECT_EQ(4, ARRAY_COUNT(&ini->plugins));
+
+  struct plugin *m0= ARRAY_ELEMENT(&ini->plugins, 0);
+  struct plugin *m1 = ARRAY_ELEMENT(&ini->plugins, 1);
+  struct plugin *m2 = ARRAY_ELEMENT(&ini->plugins, 2);
+  struct plugin *m_swap_lr = ARRAY_ELEMENT(&ini->plugins, 3);
+
+  EXPECT_EQ(2, ARRAY_COUNT(&m0->ports));
+  EXPECT_EQ(4, ARRAY_COUNT(&m1->ports));
+  EXPECT_EQ(4, ARRAY_COUNT(&m_swap_lr->ports));
+  EXPECT_EQ(2, ARRAY_COUNT(&m2->ports));
+
+  struct port *m0_0 = ARRAY_ELEMENT(&m0->ports, 0);
+  struct port *m0_1 = ARRAY_ELEMENT(&m0->ports, 1);
+  struct port *m1_0 = ARRAY_ELEMENT(&m1->ports, 0);
+  struct port *m1_1 = ARRAY_ELEMENT(&m1->ports, 1);
+  struct port *m1_2 = ARRAY_ELEMENT(&m1->ports, 2);
+  struct port *m1_3 = ARRAY_ELEMENT(&m1->ports, 3);
+  struct port *m_swap_lr_0 = ARRAY_ELEMENT(&m_swap_lr->ports, 0);
+  struct port *m_swap_lr_1 = ARRAY_ELEMENT(&m_swap_lr->ports, 1);
+  struct port *m_swap_lr_2 = ARRAY_ELEMENT(&m_swap_lr->ports, 2);
+  struct port *m_swap_lr_3 = ARRAY_ELEMENT(&m_swap_lr->ports, 3);
+  struct port *m2_0 = ARRAY_ELEMENT(&m2->ports, 0);
+  struct port *m2_1 = ARRAY_ELEMENT(&m2->ports, 1);
+
+  /* flow       flow_id       from port       to port
+   * ------------------------------------------------------------
+   * a0            0            m0_0           m1_0
+   * a1            1            m0_1           m1_1
+   * b0            2            m1_2           m_swap_lr_0
+   * b1            3            m1_3           m_swap_lr_1
+   * swap_lr_0     4            m_swap_lr_2    m2_0
+   * swap_lr_1     5            m_swap_lr_3    m2_1
+   */
+  EXPECT_EQ(0, m0_0->flow_id);
+  EXPECT_EQ(1, m0_1->flow_id);
+  EXPECT_EQ(0, m1_0->flow_id);
+  EXPECT_EQ(1, m1_1->flow_id);
+  EXPECT_EQ(2, m1_2->flow_id);
+  EXPECT_EQ(3, m1_3->flow_id);
+  EXPECT_EQ(2, m_swap_lr_0->flow_id);
+  EXPECT_EQ(3, m_swap_lr_1->flow_id);
+  EXPECT_EQ(4, m_swap_lr_2->flow_id);
+  EXPECT_EQ(5, m_swap_lr_3->flow_id);
+  EXPECT_EQ(4, m2_0->flow_id);
+  EXPECT_EQ(5, m2_1->flow_id);
+
+  struct flow *flow_a0 = ARRAY_ELEMENT(&ini->flows, 0);
+  struct flow *flow_a1 = ARRAY_ELEMENT(&ini->flows, 1);
+  struct flow *flow_b0 = ARRAY_ELEMENT(&ini->flows, 2);
+  struct flow *flow_b1 = ARRAY_ELEMENT(&ini->flows, 3);
+  struct flow *flow_swap_lr_0 = ARRAY_ELEMENT(&ini->flows, 4);
+  struct flow *flow_swap_lr_1 = ARRAY_ELEMENT(&ini->flows, 5);
+
+  EXPECT_EQ(flow_a0->from, m0);
+  EXPECT_EQ(flow_a0->from_port, 0);
+  EXPECT_EQ(flow_a0->to, m1);
+  EXPECT_EQ(flow_a0->to_port, 0);
+
+  EXPECT_EQ(flow_a1->from, m0);
+  EXPECT_EQ(flow_a1->from_port, 1);
+  EXPECT_EQ(flow_a1->to, m1);
+  EXPECT_EQ(flow_a1->to_port, 1);
+
+  EXPECT_EQ(flow_b0->from, m1);
+  EXPECT_EQ(flow_b0->from_port, 2);
+  EXPECT_EQ(flow_b0->to, m_swap_lr);
+  EXPECT_EQ(flow_b0->to_port, 0);
+
+  EXPECT_EQ(flow_b1->from, m1);
+  EXPECT_EQ(flow_b1->from_port, 3);
+  EXPECT_EQ(flow_b1->to, m_swap_lr);
+  EXPECT_EQ(flow_b1->to_port, 1);
+
+  EXPECT_EQ(flow_swap_lr_0->from, m_swap_lr);
+  EXPECT_EQ(flow_swap_lr_0->from_port, 2);
+  EXPECT_EQ(flow_swap_lr_0->to, m2);
+  EXPECT_EQ(flow_swap_lr_0->to_port, 0);
+
+  EXPECT_EQ(flow_swap_lr_1->from, m_swap_lr);
+  EXPECT_EQ(flow_swap_lr_1->from_port, 3);
+  EXPECT_EQ(flow_swap_lr_1->to, m2);
+  EXPECT_EQ(flow_swap_lr_1->to_port, 1);
+
+  cras_dsp_ini_free(ini);
+}
+
 }  //  namespace
 
 int main(int argc, char **argv) {
diff --git a/cras/src/tests/dsp_unittest.cc b/cras/src/tests/dsp_unittest.cc
index a8a03d7..40fd9da 100644
--- a/cras/src/tests/dsp_unittest.cc
+++ b/cras/src/tests/dsp_unittest.cc
@@ -65,9 +65,9 @@
   ctx3 = cras_dsp_context_new(44100, "capture");
   ctx4 = cras_dsp_context_new(44100, "capture");
 
-  cras_dsp_set_variable(ctx1, "variable", "foo");
-  cras_dsp_set_variable(ctx3, "variable", "bar");  /* wrong value */
-  cras_dsp_set_variable(ctx4, "variable", "foo");
+  cras_dsp_set_variable_string(ctx1, "variable", "foo");
+  cras_dsp_set_variable_string(ctx3, "variable", "bar");  /* wrong value */
+  cras_dsp_set_variable_string(ctx4, "variable", "foo");
 
   cras_dsp_load_pipeline(ctx1);
   cras_dsp_load_pipeline(ctx3);
@@ -82,12 +82,12 @@
   cras_dsp_put_pipeline(ctx4);
 
   /* change the variable to a wrong value, and we should fail to reload. */
-  cras_dsp_set_variable(ctx4, "variable", "bar");
+  cras_dsp_set_variable_string(ctx4, "variable", "bar");
   cras_dsp_load_pipeline(ctx4);
   ASSERT_EQ(NULL, cras_dsp_get_pipeline(ctx4));
 
   /* change the variable back, and we should reload successfully. */
-  cras_dsp_set_variable(ctx4, "variable", "foo");
+  cras_dsp_set_variable_string(ctx4, "variable", "foo");
   cras_dsp_reload_ini();
   ASSERT_TRUE(cras_dsp_get_pipeline(ctx4));
 
diff --git a/cras/src/tests/file_wait_unittest.cc b/cras/src/tests/file_wait_unittest.cc
new file mode 100644
index 0000000..6bd76f3
--- /dev/null
+++ b/cras/src/tests/file_wait_unittest.cc
@@ -0,0 +1,279 @@
+// Copyright 2016 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 <fcntl.h>
+#include <stdlib.h>
+#include <string>
+
+#include "cras_util.h"
+#include "cras_file_wait.h"
+
+extern "C" {
+// This function is not exported in cras_util.h.
+void cras_file_wait_mock_race_condition(struct cras_file_wait *file_wait);
+}
+
+namespace {
+
+// Executes "rm -rf <path>".
+static int RmRF(const std::string& path) {
+  std::string cmd("rm -rf \"");
+  cmd += path + "\"";
+
+  if (path == "/")
+    return -EINVAL;
+
+  int rc = system(cmd.c_str());
+  if (rc < 0)
+    return -errno;
+  return WEXITSTATUS(rc);
+}
+
+// Filled-in by the FileWaitCallback.
+struct FileWaitResult {
+  size_t called;
+  cras_file_wait_event_t event;
+};
+
+// Called by the file wait code for an event.
+static void FileWaitCallback(void *context,
+			     cras_file_wait_event_t event,
+			     const char *filename)
+{
+  FileWaitResult *result = reinterpret_cast<FileWaitResult*>(context);
+  result->called++;
+  result->event = event;
+}
+
+// Do all of the EXPECTed steps for a simple wait for one file.
+static void SimpleFileWait(const char *file_path) {
+  struct cras_file_wait *file_wait;
+  FileWaitResult file_wait_result;
+  struct pollfd poll_fd;
+  struct timespec timeout = {0, 100000000};
+  struct stat stat_buf;
+  int stat_rc;
+
+  stat_rc = stat(file_path, &stat_buf);
+  if (stat_rc < 0)
+    stat_rc = -errno;
+
+  file_wait_result.called = 0;
+  EXPECT_EQ(0, cras_file_wait_create(file_path, CRAS_FILE_WAIT_FLAG_NONE,
+                                     FileWaitCallback, &file_wait_result,
+                                     &file_wait));
+  EXPECT_NE(reinterpret_cast<struct cras_file_wait *>(NULL), file_wait);
+  if (stat_rc == 0) {
+    EXPECT_EQ(1, file_wait_result.called);
+    EXPECT_EQ(CRAS_FILE_WAIT_EVENT_CREATED, file_wait_result.event);
+  } else {
+    EXPECT_EQ(0, file_wait_result.called);
+  }
+  poll_fd.events = POLLIN;
+  poll_fd.fd = cras_file_wait_get_fd(file_wait);
+
+  file_wait_result.called = 0;
+  if (stat_rc == 0)
+    EXPECT_EQ(0, RmRF(file_path));
+  else
+    EXPECT_EQ(0, mknod(file_path, S_IFREG | 0600, 0));
+  EXPECT_EQ(1, cras_poll(&poll_fd, 1, &timeout, NULL));
+  EXPECT_EQ(0, cras_file_wait_dispatch(file_wait));
+  EXPECT_EQ(1, file_wait_result.called);
+  if (stat_rc == 0)
+    EXPECT_EQ(CRAS_FILE_WAIT_EVENT_DELETED, file_wait_result.event);
+  else
+    EXPECT_EQ(CRAS_FILE_WAIT_EVENT_CREATED, file_wait_result.event);
+  EXPECT_EQ(-EAGAIN, cras_file_wait_dispatch(file_wait));
+  cras_file_wait_destroy(file_wait);
+}
+
+// Test the cras_file_wait functions including multiple path components
+// missing and path components deleted and recreated.
+TEST(Util, FileWait) {
+  struct cras_file_wait *file_wait;
+  FileWaitResult file_wait_result;
+  pid_t pid = getpid();
+  struct pollfd poll_fd;
+  int current_dir;
+  struct timespec timeout = {0, 100000000};
+  char pid_buf[32];
+  std::string tmp_dir(CRAS_UT_TMPDIR);
+  std::string dir_path;
+  std::string subdir_path;
+  std::string file_path;
+
+  snprintf(pid_buf, sizeof(pid_buf), "%d", pid);
+  dir_path = tmp_dir + "/" + pid_buf;
+  subdir_path = dir_path + "/subdir";
+  file_path = subdir_path + "/does_not_exist";
+
+  // Test arguments.
+  // Null file path.
+  EXPECT_EQ(-EINVAL, cras_file_wait_create(
+                         NULL, CRAS_FILE_WAIT_FLAG_NONE,
+                         FileWaitCallback, &file_wait_result, &file_wait));
+  // Empty file path.
+  EXPECT_EQ(-EINVAL, cras_file_wait_create(
+                         "", CRAS_FILE_WAIT_FLAG_NONE,
+                         FileWaitCallback, &file_wait_result, &file_wait));
+  // No callback structure.
+  EXPECT_EQ(-EINVAL, cras_file_wait_create(
+                         ".", CRAS_FILE_WAIT_FLAG_NONE,
+                         NULL, NULL, &file_wait));
+  // No file wait structure.
+  EXPECT_EQ(-EINVAL, cras_file_wait_create(
+                         ".", CRAS_FILE_WAIT_FLAG_NONE,
+                         FileWaitCallback, &file_wait_result, NULL));
+  EXPECT_EQ(-EINVAL, cras_file_wait_dispatch(NULL));
+  EXPECT_EQ(-EINVAL, cras_file_wait_get_fd(NULL));
+
+  // Make sure that /tmp exists.
+  file_wait_result.called = 0;
+  EXPECT_EQ(0, cras_file_wait_create(CRAS_UT_TMPDIR, CRAS_FILE_WAIT_FLAG_NONE,
+                                     FileWaitCallback, &file_wait_result,
+                                     &file_wait));
+  EXPECT_NE(reinterpret_cast<struct cras_file_wait *>(NULL), file_wait);
+  EXPECT_EQ(file_wait_result.called, 1);
+  ASSERT_EQ(file_wait_result.event, CRAS_FILE_WAIT_EVENT_CREATED);
+  cras_file_wait_destroy(file_wait);
+
+  // Create our temporary dir.
+  ASSERT_EQ(0, RmRF(dir_path));
+  ASSERT_EQ(0, mkdir(dir_path.c_str(), 0700));
+
+  // Start looking for our file '.../does_not_exist'.
+  EXPECT_EQ(0, cras_file_wait_create(file_path.c_str(),
+                                     CRAS_FILE_WAIT_FLAG_NONE,
+                                     FileWaitCallback, &file_wait_result,
+                                     &file_wait));
+  EXPECT_NE(reinterpret_cast<struct cras_file_wait *>(NULL), file_wait);
+  poll_fd.events = POLLIN;
+  poll_fd.fd = cras_file_wait_get_fd(file_wait);
+  EXPECT_NE(0, poll_fd.fd >= 0);
+
+  // Create a sub-directory in the path.
+  file_wait_result.called = 0;
+  EXPECT_EQ(0, mkdir(subdir_path.c_str(), 0700));
+  EXPECT_EQ(1, cras_poll(&poll_fd, 1, &timeout, NULL));
+  EXPECT_EQ(0, cras_file_wait_dispatch(file_wait));
+  EXPECT_EQ(0, file_wait_result.called);
+  // Removing a watch causes generation of an IN_IGNORED event for the previous
+  // watch_id. cras_file_wait_dispatch will ignore this and return 0.
+  EXPECT_EQ(0, cras_file_wait_dispatch(file_wait));
+  EXPECT_EQ(-EAGAIN, cras_file_wait_dispatch(file_wait));
+
+  // Remove the directory that we're watching.
+  EXPECT_EQ(0, RmRF(subdir_path));
+  timeout.tv_sec = 0;
+  timeout.tv_nsec = 100000000;
+  EXPECT_EQ(1, cras_poll(&poll_fd, 1, &timeout, NULL));
+  EXPECT_EQ(0, cras_file_wait_dispatch(file_wait));
+  EXPECT_EQ(0, file_wait_result.called);
+  EXPECT_EQ(-EAGAIN, cras_file_wait_dispatch(file_wait));
+
+  // Create a sub-directory in the path (again).
+  EXPECT_EQ(0, mkdir(subdir_path.c_str(), 0700));
+  timeout.tv_sec = 0;
+  timeout.tv_nsec = 100000000;
+  EXPECT_EQ(1, cras_poll(&poll_fd, 1, &timeout, NULL));
+  EXPECT_EQ(0, cras_file_wait_dispatch(file_wait));
+  EXPECT_EQ(0, file_wait_result.called);
+  // See IN_IGNORED above.
+  EXPECT_EQ(0, cras_file_wait_dispatch(file_wait));
+  EXPECT_EQ(-EAGAIN, cras_file_wait_dispatch(file_wait));
+
+  // Create the file we're looking for.
+  EXPECT_EQ(0, mknod(file_path.c_str(), S_IFREG | 0600, 0));
+  timeout.tv_sec = 0;
+  timeout.tv_nsec = 100000000;
+  EXPECT_EQ(1, cras_poll(&poll_fd, 1, &timeout, NULL));
+  EXPECT_EQ(0, cras_file_wait_dispatch(file_wait));
+  EXPECT_EQ(1, file_wait_result.called);
+  EXPECT_EQ(CRAS_FILE_WAIT_EVENT_CREATED, file_wait_result.event);
+  EXPECT_EQ(-EAGAIN, cras_file_wait_dispatch(file_wait));
+
+  // Remove the file.
+  file_wait_result.called = 0;
+  EXPECT_EQ(0, unlink(file_path.c_str()));
+  timeout.tv_sec = 0;
+  timeout.tv_nsec = 100000000;
+  EXPECT_EQ(1, cras_poll(&poll_fd, 1, &timeout, NULL));
+  EXPECT_EQ(0, cras_file_wait_dispatch(file_wait));
+  EXPECT_EQ(1, file_wait_result.called);
+  EXPECT_EQ(CRAS_FILE_WAIT_EVENT_DELETED, file_wait_result.event);
+  EXPECT_EQ(-EAGAIN, cras_file_wait_dispatch(file_wait));
+
+  // Re-create the file.
+  file_wait_result.called = 0;
+  EXPECT_EQ(0, mknod(file_path.c_str(), S_IFREG | 0600, 0));
+  timeout.tv_sec = 0;
+  timeout.tv_nsec = 100000000;
+  EXPECT_EQ(1, cras_poll(&poll_fd, 1, &timeout, NULL));
+  EXPECT_EQ(0, cras_file_wait_dispatch(file_wait));
+  EXPECT_EQ(1, file_wait_result.called);
+  EXPECT_EQ(CRAS_FILE_WAIT_EVENT_CREATED, file_wait_result.event);
+  EXPECT_EQ(-EAGAIN, cras_file_wait_dispatch(file_wait));
+
+  // Remove the subdir.
+  file_wait_result.called = 0;
+  EXPECT_EQ(0, RmRF(subdir_path));
+  timeout.tv_sec = 0;
+  timeout.tv_nsec = 100000000;
+  EXPECT_EQ(1, cras_poll(&poll_fd, 1, &timeout, NULL));
+  EXPECT_EQ(0, cras_file_wait_dispatch(file_wait));
+  EXPECT_EQ(1, file_wait_result.called);
+  EXPECT_EQ(CRAS_FILE_WAIT_EVENT_DELETED, file_wait_result.event);
+  EXPECT_EQ(-EAGAIN, cras_file_wait_dispatch(file_wait));
+
+  // Create a sub-directory in the path (again), and this time mock a race
+  // condition for creation of the file.
+  file_wait_result.called = 0;
+  EXPECT_EQ(0, mkdir(subdir_path.c_str(), 0700));
+  timeout.tv_sec = 0;
+  timeout.tv_nsec = 100000000;
+  EXPECT_EQ(1, cras_poll(&poll_fd, 1, &timeout, NULL));
+  cras_file_wait_mock_race_condition(file_wait);
+  EXPECT_EQ(0, cras_file_wait_dispatch(file_wait));
+  EXPECT_EQ(1, file_wait_result.called);
+  EXPECT_EQ(CRAS_FILE_WAIT_EVENT_CREATED, file_wait_result.event);
+  EXPECT_EQ(0, cras_file_wait_dispatch(file_wait));
+  EXPECT_EQ(1, file_wait_result.called);
+  EXPECT_EQ(-EAGAIN, cras_file_wait_dispatch(file_wait));
+
+  // Cleanup.
+  cras_file_wait_destroy(file_wait);
+
+  // Treat consecutive '/' as one.
+  file_path = dir_path + "//does_not_exist_too";
+  SimpleFileWait(file_path.c_str());
+
+  // Stash the current directory.
+  current_dir = open(".", O_RDONLY|O_PATH|O_DIRECTORY);
+  ASSERT_NE(0, current_dir >= 0);
+
+  // Search for a file in the current directory.
+  ASSERT_EQ(0, chdir(dir_path.c_str()));
+  SimpleFileWait("does_not_exist_either");
+
+  // Test notification of deletion in the current directory.
+  SimpleFileWait("does_not_exist_either");
+
+  // Search for a file in the current directory (variation).
+  SimpleFileWait("./does_not_exist_either_too");
+
+  // Return to the start directory.
+  EXPECT_EQ(0, fchdir(current_dir));
+
+  // Clean up.
+  EXPECT_EQ(0, RmRF(dir_path));
+}
+
+}  //  namespace
+
+int main(int argc, char **argv) {
+  ::testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
diff --git a/cras/src/tests/fmt_conv_unittest.cc b/cras/src/tests/fmt_conv_unittest.cc
index 3026cc5..a0b6194 100644
--- a/cras/src/tests/fmt_conv_unittest.cc
+++ b/cras/src/tests/fmt_conv_unittest.cc
@@ -1222,6 +1222,39 @@
   EXPECT_EQ(0, cras_fmt_conversion_needed(c));
 }
 
+TEST(ChannelRemixTest, ChannelRemixAppliedOrNot) {
+  float coeff[4] = {0.5, 0.5, 0.26, 0.73};
+  struct cras_fmt_conv *conv;
+  struct cras_audio_format fmt;
+  int16_t *buf, *res;
+  unsigned i;
+
+  fmt.num_channels = 2;
+  conv = cras_channel_remix_conv_create(2, coeff);
+
+  buf = (int16_t *)ralloc(50 * 4);
+  res = (int16_t *)malloc(50 * 4);
+
+  for (i = 0; i < 100; i += 2) {
+    res[i] = coeff[0] * buf[i];
+    res[i] += coeff[1] * buf[i + 1];
+    res[i + 1] = coeff[2] * buf[i];
+    res[i + 1] += coeff[3] * buf[i + 1];
+  }
+
+  cras_channel_remix_convert(conv, &fmt, (uint8_t *)buf, 50);
+  for (i = 0; i < 100; i++)
+    EXPECT_EQ(res[i],  buf[i]);
+
+  /* If num_channels not match, remix conversion will not apply. */
+  fmt.num_channels = 6;
+  cras_channel_remix_convert(conv, &fmt, (uint8_t *)buf, 50);
+  for (i = 0; i < 100; i++)
+    EXPECT_EQ(res[i],  buf[i]);
+
+  cras_fmt_conv_destroy(conv);
+}
+
 int main(int argc, char **argv) {
   ::testing::InitGoogleTest(&argc, argv);
   return RUN_ALL_TESTS();
diff --git a/cras/src/tests/hfp_info_unittest.cc b/cras/src/tests/hfp_info_unittest.cc
index 20be29c..82b9393 100644
--- a/cras/src/tests/hfp_info_unittest.cc
+++ b/cras/src/tests/hfp_info_unittest.cc
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013 The Chromium Authors. All rights reserved.
+/* Copyright 2013 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.
  */
@@ -310,6 +310,11 @@
 
 extern "C" {
 
+struct audio_thread *cras_iodev_list_get_audio_thread()
+{
+  return NULL;
+}
+
 void audio_thread_add_callback(int fd, thread_callback cb,
                                void *data)
 {
@@ -318,11 +323,11 @@
   return;
 }
 
-void audio_thread_rm_callback(int fd)
+int audio_thread_rm_callback_sync(struct audio_thread *thread, int fd)
 {
   thread_cb = NULL;
   cb_data = NULL;
-  return;
+  return 0;
 }
 }
 
diff --git a/cras/src/tests/hfp_iodev_unittest.cc b/cras/src/tests/hfp_iodev_unittest.cc
index e6b3f77..202bb13 100644
--- a/cras/src/tests/hfp_iodev_unittest.cc
+++ b/cras/src/tests/hfp_iodev_unittest.cc
@@ -71,7 +71,8 @@
 
 namespace {
 
-TEST(HfpIodev, CreateHfpIodev) {
+TEST(HfpIodev, CreateHfpOutputIodev) {
+  ResetStubData();
   iodev = hfp_iodev_create(CRAS_STREAM_OUTPUT, fake_device, fake_slc,
                            CRAS_BT_DEVICE_PROFILE_HFP_AUDIOGATEWAY,
                 		  	   fake_info);
@@ -87,6 +88,24 @@
   ASSERT_EQ(1, cras_iodev_rm_node_called);
 }
 
+TEST(HfpIodev, CreateHfpInputIodev) {
+  ResetStubData();
+  iodev = hfp_iodev_create(CRAS_STREAM_INPUT, fake_device, fake_slc,
+                           CRAS_BT_DEVICE_PROFILE_HFP_AUDIOGATEWAY, fake_info);
+
+  ASSERT_EQ(CRAS_STREAM_INPUT, iodev->direction);
+  ASSERT_EQ(1, cras_bt_device_append_iodev_called);
+  ASSERT_EQ(1, cras_iodev_add_node_called);
+  ASSERT_EQ(1, cras_iodev_set_active_node_called);
+  /* Input device does not use software gain. */
+  ASSERT_EQ(0, iodev->software_volume_needed);
+
+  hfp_iodev_destroy(iodev);
+
+  ASSERT_EQ(1, cras_bt_device_rm_iodev_called);
+  ASSERT_EQ(1, cras_iodev_rm_node_called);
+}
+
 TEST(HfpIodev, OpenHfpIodev) {
   ResetStubData();
 
@@ -105,7 +124,6 @@
 
   /* hfp_info is running now */
   hfp_info_running_return_val = 1;
-  ASSERT_EQ(1, iodev->is_open(iodev));
 
   iodev->close_dev(iodev);
   ASSERT_EQ(1, hfp_info_rm_iodev_called);
@@ -130,8 +148,6 @@
   ASSERT_EQ(0, hfp_info_start_called);
   ASSERT_EQ(1, hfp_info_add_iodev_called);
 
-  ASSERT_EQ(1, iodev->is_open(iodev));
-
   hfp_info_has_iodev_return_val = 1;
   iodev->close_dev(iodev);
   ASSERT_EQ(1, hfp_info_rm_iodev_called);
@@ -210,11 +226,6 @@
   return "1A:2B:3C:4D:5E:6F";
 }
 
-int cras_bt_device_set_speaker_gain(struct cras_bt_device *device, int gain)
-{
-  return 0;
-}
-
 void cras_bt_device_append_iodev(struct cras_bt_device *device,
                                  struct cras_iodev *iodev,
                                  enum cras_bt_device_profile profile)
@@ -333,6 +344,11 @@
   return 0;
 }
 
+int hfp_event_speaker_gain(struct hfp_slc_handle *handle, int gain)
+{
+  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 0479f6c..32749fd 100644
--- a/cras/src/tests/hfp_slc_unittest.cc
+++ b/cras/src/tests/hfp_slc_unittest.cc
@@ -16,12 +16,15 @@
 
 static struct hfp_slc_handle *handle;
 static struct cras_telephony_handle fake_telephony;
+static int cras_bt_device_update_hardware_volume_called;
 static int slc_initialized_cb_called;
 static int slc_disconnected_cb_called;
 static int cras_system_add_select_fd_called;
 static void(*slc_cb)(void *data);
 static void *slc_cb_data;
 static int fake_errno;
+static struct cras_bt_device *device =
+    reinterpret_cast<struct cras_bt_device *>(2);
 
 int slc_initialized_cb(struct hfp_slc_handle *handle);
 int slc_disconnected_cb(struct hfp_slc_handle *handle);
@@ -29,6 +32,7 @@
 void ResetStubData() {
   slc_initialized_cb_called = 0;
   cras_system_add_select_fd_called = 0;
+  cras_bt_device_update_hardware_volume_called = 0;
   slc_cb = NULL;
   slc_cb_data = NULL;
 }
@@ -38,7 +42,7 @@
 TEST(HfpSlc, CreateSlcHandle) {
   ResetStubData();
 
-  handle = hfp_slc_create(0, 0, slc_initialized_cb,
+  handle = hfp_slc_create(0, 0, device, slc_initialized_cb,
                           slc_disconnected_cb);
   ASSERT_EQ(1, cras_system_add_select_fd_called);
   ASSERT_EQ(handle, slc_cb_data);
@@ -54,7 +58,7 @@
   ResetStubData();
 
   ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sock));
-  handle = hfp_slc_create(sock[0], 0, slc_initialized_cb,
+  handle = hfp_slc_create(sock[0], 0, device, slc_initialized_cb,
                           slc_disconnected_cb);
 
   err = write(sock[1], "AT+CIND=?\r", 10);
@@ -89,6 +93,18 @@
   ASSERT_NE((void *)NULL, (void *)chp);
   ASSERT_EQ(0, strncmp("\r\nOK", chp, 4));
 
+  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\n");
+  ASSERT_NE((void *)NULL, (void *)chp);
+  ASSERT_EQ(0, strncmp("\r\nOK", chp, 4));
+
+  ASSERT_EQ(1, cras_bt_device_update_hardware_volume_called);
+
   hfp_slc_destroy(handle);
 }
 
@@ -97,7 +113,7 @@
   ResetStubData();
 
   ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sock));
-  handle = hfp_slc_create(sock[0], 0, slc_initialized_cb,
+  handle = hfp_slc_create(sock[0], 0, device, slc_initialized_cb,
                           slc_disconnected_cb);
   /* Close socket right away to make read() get negative err code, and
    * fake the errno to ECONNRESET. */
@@ -135,6 +151,12 @@
 void cras_system_rm_select_fd(int fd) {
 }
 
+void cras_bt_device_update_hardware_volume(struct cras_bt_device *device,
+    int volume)
+{
+  cras_bt_device_update_hardware_volume_called++;
+}
+
 /* To return fake errno */
 int *__errno_location() {
   return &fake_errno;
diff --git a/cras/src/tests/iodev_list_unittest.cc b/cras/src/tests/iodev_list_unittest.cc
index c24cf09..35b515d 100644
--- a/cras/src/tests/iodev_list_unittest.cc
+++ b/cras/src/tests/iodev_list_unittest.cc
@@ -2,13 +2,17 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <algorithm>
 #include <stdio.h>
 #include <gtest/gtest.h>
+#include <map>
 
 extern "C" {
 #include "audio_thread.h"
 #include "cras_iodev.h"
 #include "cras_iodev_list.h"
+#include "cras_observer_ops.h"
+#include "cras_ramp.h"
 #include "cras_rstream.h"
 #include "cras_system_state.h"
 #include "cras_tm.h"
@@ -22,32 +26,11 @@
 struct cras_server_state *server_state_update_begin_return;
 
 /* Data for stubs. */
-static cras_alert_cb volume_changed_cb;
-static void* volume_changed_arg;
-static unsigned int register_volume_changed_cb_called;
-static unsigned int remove_volume_changed_cb_called;
-static cras_alert_cb mute_changed_cb;
-static cras_alert_cb suspend_cb;
-static void* mute_changed_arg;
-static unsigned int register_mute_changed_cb_called;
-static unsigned int remove_mute_changed_cb_called;
-static unsigned int register_suspend_cb_called;
-static unsigned int remove_suspend_cb_called;
+static struct cras_observer_ops *observer_ops;
 static unsigned int cras_system_get_suspended_val;
-static cras_alert_cb capture_gain_changed_cb;
-static void* capture_gain_changed_arg;
-static unsigned int register_capture_gain_changed_cb_called;
-static unsigned int remove_capture_gain_changed_cb_called;
-static cras_alert_cb capture_mute_changed_cb;
-static void* capture_mute_changed_arg;
-static unsigned int register_capture_mute_changed_cb_called;
-static unsigned int remove_capture_mute_changed_cb_called;
 static int add_stream_called;
 static int rm_stream_called;
 static unsigned int set_node_attr_called;
-static int cras_alert_create_called;
-static int cras_alert_destroy_called;
-static int cras_alert_pending_called;
 static cras_iodev *audio_thread_remove_streams_active_dev;
 static cras_iodev *audio_thread_set_active_dev_val;
 static int audio_thread_set_active_dev_called;
@@ -55,39 +38,57 @@
 static int audio_thread_add_open_dev_called;
 static int audio_thread_rm_open_dev_called;
 static struct audio_thread thread;
-static int node_left_right_swapped_cb_called;
 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_empty_iodev[2];
 static stream_callback *stream_add_cb;
 static stream_callback *stream_rm_cb;
-static int iodev_is_open;
-static int empty_iodev_is_open[CRAS_NUM_DIRECTIONS];
 static struct cras_rstream *stream_list_get_ret;
 static int audio_thread_drain_stream_return;
 static int audio_thread_drain_stream_called;
+static int cras_tm_create_timer_called;
+static int cras_tm_cancel_timer_called;
 static void (*cras_tm_timer_cb)(struct cras_timer *t, void *data);
+static void *cras_tm_timer_cb_data;
 static struct timespec clock_gettime_retspec;
 static struct cras_iodev *device_enabled_dev;
+static int device_enabled_count;
 static struct cras_iodev *device_disabled_dev;
+static int device_disabled_count;
 static void *device_enabled_cb_data;
 static struct cras_rstream *audio_thread_add_stream_stream;
 static struct cras_iodev *audio_thread_add_stream_dev;
 static int audio_thread_add_stream_called;
+static unsigned update_active_node_called;
+static struct cras_iodev *update_active_node_iodev_val[5];
+static unsigned update_active_node_node_idx_val[5];
+static unsigned update_active_node_dev_enabled_val[5];
+static size_t cras_observer_add_called;
+static size_t cras_observer_remove_called;
+static size_t cras_observer_notify_nodes_called;
+static size_t cras_observer_notify_active_node_called;
+static size_t cras_observer_notify_output_node_volume_called;
+static size_t cras_observer_notify_node_left_right_swapped_called;
+static size_t cras_observer_notify_input_node_gain_called;
+static int cras_iodev_open_called;
+static int cras_iodev_open_ret[8];
+static int set_mute_called;
+static std::vector<struct cras_iodev*> set_mute_dev_vector;
+static struct cras_iodev *audio_thread_dev_start_ramp_dev;
+static int audio_thread_dev_start_ramp_called;
+static enum CRAS_IODEV_RAMP_REQUEST audio_thread_dev_start_ramp_req ;
+static std::map<const struct cras_iodev*, enum CRAS_IODEV_STATE> cras_iodev_state_ret;
+static int cras_iodev_is_zero_volume_ret;
 
-/* Callback in iodev_list. */
-void node_left_right_swapped_cb(cras_node_id_t, int)
-{
-  node_left_right_swapped_cb_called++;
+void dummy_update_active_node(struct cras_iodev *iodev,
+                              unsigned node_idx,
+                              unsigned dev_enabled) {
 }
 
-/* For iodev is_open. */
-int cras_iodev_is_open_stub(const struct cras_iodev *dev) {
-  enum CRAS_STREAM_DIRECTION dir = dev->direction;
-  if (dev == &dummy_empty_iodev[dir])
-    return empty_iodev_is_open[dir];
-  return iodev_is_open;
+int device_in_vector(std::vector<struct cras_iodev*> v, struct cras_iodev *dev)
+{
+  return std::find(v.begin(), v.end(), dev) != v.end();
 }
 
 class IoDevTestSuite : public testing::Test {
@@ -99,6 +100,8 @@
       stream_list_get_ret = 0;
       audio_thread_drain_stream_return = 0;
       audio_thread_drain_stream_called = 0;
+      cras_tm_create_timer_called = 0;
+      cras_tm_cancel_timer_called = 0;
 
       sample_rates_[0] = 44100;
       sample_rates_[1] = 48000;
@@ -116,10 +119,8 @@
       memset(&node3, 0, sizeof(node3));
 
       d1_.set_volume = NULL;
-      d1_.set_mute = NULL;
       d1_.set_capture_gain = NULL;
       d1_.set_capture_mute = NULL;
-      d1_.is_open = is_open;
       d1_.update_supported_formats = NULL;
       d1_.update_active_node = update_active_node;
       d1_.format = NULL;
@@ -131,10 +132,8 @@
       d1_.supported_rates = sample_rates_;
       d1_.supported_channel_counts = channel_counts_;
       d2_.set_volume = NULL;
-      d2_.set_mute = NULL;
       d2_.set_capture_gain = NULL;
       d2_.set_capture_mute = NULL;
-      d2_.is_open = is_open;
       d2_.update_supported_formats = NULL;
       d2_.update_active_node = update_active_node;
       d2_.format = NULL;
@@ -146,10 +145,8 @@
       d2_.supported_rates = sample_rates_;
       d2_.supported_channel_counts = channel_counts_;
       d3_.set_volume = NULL;
-      d3_.set_mute = NULL;
       d3_.set_capture_gain = NULL;
       d3_.set_capture_mute = NULL;
-      d3_.is_open = is_open;
       d3_.update_supported_formats = NULL;
       d3_.update_active_node = update_active_node;
       d3_.format = NULL;
@@ -162,10 +159,8 @@
       d3_.supported_channel_counts = channel_counts_;
 
       loopback_input.set_volume = NULL;
-      loopback_input.set_mute = NULL;
       loopback_input.set_capture_gain = NULL;
       loopback_input.set_capture_mute = NULL;
-      loopback_input.is_open = is_open;
       loopback_input.update_supported_formats = NULL;
       loopback_input.update_active_node = update_active_node;
       loopback_input.format = NULL;
@@ -180,38 +175,36 @@
       server_state_update_begin_return = &server_state_stub;
 
       /* Reset stub data. */
-      register_volume_changed_cb_called = 0;
-      remove_volume_changed_cb_called = 0;
-      register_capture_gain_changed_cb_called = 0;
-      remove_capture_gain_changed_cb_called = 0;
-      register_mute_changed_cb_called = 0;
-      remove_mute_changed_cb_called = 0;
-      register_suspend_cb_called = 0;
-      remove_suspend_cb_called = 0;
-      register_capture_mute_changed_cb_called = 0;
-      remove_capture_mute_changed_cb_called = 0;
       add_stream_called = 0;
       rm_stream_called = 0;
       set_node_attr_called = 0;
-      cras_alert_create_called = 0;
-      cras_alert_destroy_called = 0;
-      cras_alert_pending_called = 0;
-      is_open_ = 0;
       audio_thread_rm_open_dev_called = 0;
       audio_thread_add_open_dev_called = 0;
       audio_thread_set_active_dev_called = 0;
-      node_left_right_swapped_cb_called = 0;
       audio_thread_add_stream_called = 0;
+      update_active_node_called = 0;
+      cras_observer_add_called = 0;
+      cras_observer_remove_called = 0;
+      cras_observer_notify_nodes_called = 0;
+      cras_observer_notify_active_node_called = 0;
+      cras_observer_notify_output_node_volume_called = 0;
+      cras_observer_notify_node_left_right_swapped_called = 0;
+      cras_observer_notify_input_node_gain_called = 0;
+      cras_iodev_open_called = 0;
+      memset(cras_iodev_open_ret, 0, sizeof(cras_iodev_open_ret));
+      set_mute_called = 0;
+      set_mute_dev_vector.clear();
+      audio_thread_dev_start_ramp_dev = NULL;
+      audio_thread_dev_start_ramp_called = 0;
+      audio_thread_dev_start_ramp_req =
+          CRAS_IODEV_RAMP_REQUEST_UP_START_PLAYBACK;
+      cras_iodev_is_zero_volume_ret = 0;
     }
 
     static void set_volume_1(struct cras_iodev* iodev) {
       set_volume_1_called_++;
     }
 
-    static void set_mute_1(struct cras_iodev* iodev) {
-      set_mute_1_called_++;
-    }
-
     static void set_capture_gain_1(struct cras_iodev* iodev) {
       set_capture_gain_1_called_++;
     }
@@ -221,11 +214,12 @@
     }
 
     static void update_active_node(struct cras_iodev *iodev,
-                                   unsigned node_idx) {
-    }
-
-    static int is_open(const cras_iodev* iodev) {
-      return is_open_;
+                                   unsigned node_idx,
+                                   unsigned dev_enabled) {
+      int i = update_active_node_called++ % 5;
+      update_active_node_iodev_val[i] = iodev;
+      update_active_node_node_idx_val[i] = node_idx;
+      update_active_node_dev_enabled_val[i] = dev_enabled;
     }
 
     struct cras_iodev d1_;
@@ -234,33 +228,21 @@
     size_t sample_rates_[3];
     size_t channel_counts_[2];
     static int set_volume_1_called_;
-    static int set_mute_1_called_;
     static int set_capture_gain_1_called_;
     static int set_capture_mute_1_called_;
-    static int is_open_;
     struct cras_ionode node1, node2, node3;
 };
 
 int IoDevTestSuite::set_volume_1_called_;
-int IoDevTestSuite::set_mute_1_called_;
 int IoDevTestSuite::set_capture_gain_1_called_;
 int IoDevTestSuite::set_capture_mute_1_called_;
-int IoDevTestSuite::is_open_;
 
-// Check that Init registers a volume changed callback. */
+// Check that Init registers observer client. */
 TEST_F(IoDevTestSuite, InitSetup) {
   cras_iodev_list_init();
-  EXPECT_EQ(1, register_volume_changed_cb_called);
-  EXPECT_EQ(1, register_mute_changed_cb_called);
-  EXPECT_EQ(1, register_suspend_cb_called);
-  EXPECT_EQ(1, register_capture_gain_changed_cb_called);
-  EXPECT_EQ(1, register_capture_mute_changed_cb_called);
+  EXPECT_EQ(1, cras_observer_add_called);
   cras_iodev_list_deinit();
-  EXPECT_EQ(1, remove_volume_changed_cb_called);
-  EXPECT_EQ(1, remove_mute_changed_cb_called);
-  EXPECT_EQ(1, remove_suspend_cb_called);
-  EXPECT_EQ(1, remove_capture_gain_changed_cb_called);
-  EXPECT_EQ(1, remove_capture_mute_changed_cb_called);
+  EXPECT_EQ(1, cras_observer_remove_called);
 }
 
 /* Check that the suspend alert from cras_system will trigger suspend
@@ -277,11 +259,9 @@
   cras_iodev_list_init();
 
   d1_.direction = CRAS_STREAM_OUTPUT;
-  d1_.is_open = cras_iodev_is_open_stub;
   rc = cras_iodev_list_add_output(&d1_);
   ASSERT_EQ(0, rc);
 
-  iodev_is_open = 0;
   audio_thread_add_open_dev_called = 0;
   cras_iodev_list_add_active_node(CRAS_STREAM_OUTPUT,
       cras_make_node_id(d1_.info.idx, 1));
@@ -289,7 +269,6 @@
   stream_add_cb(&rstream);
   EXPECT_EQ(1, audio_thread_add_stream_called);
   EXPECT_EQ(1, audio_thread_add_open_dev_called);
-  iodev_is_open = 1;
 
   DL_APPEND(stream_list, &rstream2);
   stream_add_cb(&rstream2);
@@ -297,9 +276,8 @@
 
   cras_system_get_suspended_val = 1;
   audio_thread_rm_open_dev_called = 0;
-  suspend_cb(NULL);
+  observer_ops->suspend_changed(NULL, 1);
   EXPECT_EQ(1, audio_thread_rm_open_dev_called);
-  iodev_is_open = 0;
 
   /* Test disable/enable dev won't cause add_stream to audio_thread. */
   audio_thread_add_stream_called = 0;
@@ -322,54 +300,357 @@
   audio_thread_add_stream_called = 0;
   cras_system_get_suspended_val = 0;
   stream_list_get_ret = stream_list;
-  suspend_cb(NULL);
+  observer_ops->suspend_changed(NULL, 0);
   EXPECT_EQ(1, audio_thread_add_open_dev_called);
   EXPECT_EQ(2, audio_thread_add_stream_called);
   EXPECT_EQ(&rstream3, audio_thread_add_stream_stream);
-  iodev_is_open = 1;
 
   cras_iodev_list_deinit();
+  EXPECT_EQ(3, cras_observer_notify_active_node_called);
 }
 
-TEST_F(IoDevTestSuite, SelectNode) {
-  struct cras_rstream rstream, rstream2, rstream3;
+TEST_F(IoDevTestSuite, InitDevFailShouldEnableFallback) {
+  int rc;
+  struct cras_rstream rstream;
+  struct cras_rstream *stream_list = NULL;
+
+  memset(&rstream, 0, sizeof(rstream));
+  cras_iodev_list_init();
+
+  d1_.direction = CRAS_STREAM_OUTPUT;
+  rc = cras_iodev_list_add_output(&d1_);
+  ASSERT_EQ(0, rc);
+
+  cras_iodev_list_select_node(CRAS_STREAM_OUTPUT,
+      cras_make_node_id(d1_.info.idx, 0));
+
+  cras_iodev_open_ret[0] = -5;
+  cras_iodev_open_ret[1] = 0;
+
+  DL_APPEND(stream_list, &rstream);
+  stream_list_get_ret = stream_list;
+  stream_add_cb(&rstream);
+  /* open dev called twice, one for fallback device. */
+  EXPECT_EQ(2, cras_iodev_open_called);
+  EXPECT_EQ(1, audio_thread_add_stream_called);
+}
+
+TEST_F(IoDevTestSuite, SelectNodeOpenFailShouldScheduleRetry) {
+  struct cras_rstream rstream;
   struct cras_rstream *stream_list = NULL;
   int rc;
 
   memset(&rstream, 0, sizeof(rstream));
-  memset(&rstream2, 0, sizeof(rstream2));
-  memset(&rstream3, 0, sizeof(rstream3));
-
   cras_iodev_list_init();
 
   d1_.direction = CRAS_STREAM_OUTPUT;
-  d1_.is_open = cras_iodev_is_open_stub;
   rc = cras_iodev_list_add_output(&d1_);
   ASSERT_EQ(0, rc);
 
   d2_.direction = CRAS_STREAM_OUTPUT;
-  d2_.is_open = cras_iodev_is_open_stub;
   rc = cras_iodev_list_add_output(&d2_);
   ASSERT_EQ(0, rc);
 
-  iodev_is_open = 0;
-  audio_thread_add_open_dev_called = 0;
-  cras_iodev_list_add_active_node(CRAS_STREAM_OUTPUT,
+  cras_iodev_list_select_node(CRAS_STREAM_OUTPUT,
       cras_make_node_id(d1_.info.idx, 1));
   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);
-  iodev_is_open = 1;
-
-  DL_APPEND(stream_list, &rstream2);
-  stream_add_cb(&rstream2);
-  EXPECT_EQ(2, audio_thread_add_stream_called);
-
   stream_list_get_ret = stream_list;
+  stream_add_cb(&rstream);
+
+  /* Select node triggers: fallback open, d1 close, d2 open, fallback close. */
+  cras_iodev_close_called = 0;
+  cras_iodev_open_called = 0;
   cras_iodev_list_select_node(CRAS_STREAM_OUTPUT,
       cras_make_node_id(d2_.info.idx, 1));
-  EXPECT_EQ(4, audio_thread_add_stream_called);
+  EXPECT_EQ(2, cras_iodev_close_called);
+  EXPECT_EQ(2, cras_iodev_open_called);
+  EXPECT_EQ(0, cras_tm_create_timer_called);
+  EXPECT_EQ(0, cras_tm_cancel_timer_called);
+
+  /* Test that if select to d1 and open d1 fail, fallback doesn't close. */
+  cras_iodev_open_called = 0;
+  cras_iodev_open_ret[0] = 0;
+  cras_iodev_open_ret[1] = -5;
+  cras_iodev_open_ret[2] = 0;
+  cras_tm_timer_cb = NULL;
+  cras_iodev_list_select_node(CRAS_STREAM_OUTPUT,
+      cras_make_node_id(d1_.info.idx, 1));
+  EXPECT_EQ(3, cras_iodev_close_called);
+  EXPECT_EQ(&d2_, cras_iodev_close_dev);
+  EXPECT_EQ(2, cras_iodev_open_called);
+  EXPECT_EQ(0, cras_tm_cancel_timer_called);
+
+  /* Assert a timer is scheduled to retry open. */
+  EXPECT_NE((void *)NULL, cras_tm_timer_cb);
+  EXPECT_EQ(1, cras_tm_create_timer_called);
+
+  audio_thread_add_stream_called = 0;
+  cras_tm_timer_cb(NULL, cras_tm_timer_cb_data);
+  EXPECT_EQ(3, cras_iodev_open_called);
+  EXPECT_EQ(1, audio_thread_add_stream_called);
+
+  /* Retry open success will close fallback dev. */
+  EXPECT_EQ(4, cras_iodev_close_called);
+  EXPECT_EQ(0, cras_tm_cancel_timer_called);
+
+  /* Select to d2 and fake an open failure. */
+  cras_iodev_close_called = 0;
+  cras_iodev_open_called = 0;
+  cras_iodev_open_ret[0] = 0;
+  cras_iodev_open_ret[1] = -5;
+  cras_iodev_open_ret[2] = 0;
+  cras_iodev_list_select_node(CRAS_STREAM_OUTPUT,
+      cras_make_node_id(d2_.info.idx, 1));
+  EXPECT_EQ(1, cras_iodev_close_called);
+  EXPECT_EQ(&d1_, cras_iodev_close_dev);
+  EXPECT_EQ(2, cras_tm_create_timer_called);
+  EXPECT_NE((void *)NULL, cras_tm_timer_cb);
+
+  /* Select to another iodev should cancel the timer. */
+  memset(cras_iodev_open_ret, 0, sizeof(cras_iodev_open_ret));
+  cras_iodev_list_select_node(CRAS_STREAM_OUTPUT,
+      cras_make_node_id(d2_.info.idx, 1));
+  EXPECT_EQ(1, cras_tm_cancel_timer_called);
+}
+
+TEST_F(IoDevTestSuite, InitDevFailShouldScheduleRetry) {
+  int rc;
+  struct cras_rstream rstream;
+  struct cras_rstream *stream_list = NULL;
+
+  memset(&rstream, 0, sizeof(rstream));
+  cras_iodev_list_init();
+
+  d1_.direction = CRAS_STREAM_OUTPUT;
+  rc = cras_iodev_list_add_output(&d1_);
+  ASSERT_EQ(0, rc);
+
+  cras_iodev_list_select_node(CRAS_STREAM_OUTPUT,
+      cras_make_node_id(d1_.info.idx, 0));
+
+  cras_iodev_open_ret[0] = -5;
+  cras_iodev_open_ret[1] = 0;
+  cras_tm_timer_cb = NULL;
+  DL_APPEND(stream_list, &rstream);
+  stream_list_get_ret = stream_list;
+  stream_add_cb(&rstream);
+  /* open dev called twice, one for fallback device. */
+  EXPECT_EQ(2, cras_iodev_open_called);
+  EXPECT_EQ(1, audio_thread_add_stream_called);
+
+  EXPECT_NE((void *)NULL, cras_tm_timer_cb);
+  EXPECT_EQ(1, cras_tm_create_timer_called);
+
+  /* If retry still fail, won't schedule more retry. */
+  cras_iodev_open_ret[2] = -5;
+  cras_tm_timer_cb(NULL, cras_tm_timer_cb_data);
+  EXPECT_EQ(1, cras_tm_create_timer_called);
+  EXPECT_EQ(1, audio_thread_add_stream_called);
+
+  cras_tm_timer_cb = NULL;
+  cras_iodev_open_ret[3] = -5;
+  stream_add_cb(&rstream);
+  EXPECT_NE((void *)NULL, cras_tm_timer_cb);
+  EXPECT_EQ(2, cras_tm_create_timer_called);
+
+  cras_iodev_list_rm_output(&d1_);
+  EXPECT_EQ(1, cras_tm_cancel_timer_called);
+}
+
+static void device_enabled_cb(struct cras_iodev *dev, int enabled,
+                              void *cb_data)
+{
+  if (enabled) {
+    device_enabled_dev = dev;
+    device_enabled_count++;
+  } else {
+    device_disabled_dev = dev;
+    device_disabled_count++;
+  }
+  device_enabled_cb_data = cb_data;
+}
+
+TEST_F(IoDevTestSuite, SelectNode) {
+  struct cras_rstream rstream, rstream2;
+  int rc;
+
+  memset(&rstream, 0, sizeof(rstream));
+  memset(&rstream2, 0, sizeof(rstream2));
+
+  cras_iodev_list_init();
+
+  d1_.direction = CRAS_STREAM_OUTPUT;
+  node1.idx = 1;
+  rc = cras_iodev_list_add_output(&d1_);
+  ASSERT_EQ(0, rc);
+
+  d2_.direction = CRAS_STREAM_OUTPUT;
+  node2.idx = 2;
+  rc = cras_iodev_list_add_output(&d2_);
+  ASSERT_EQ(0, rc);
+
+  audio_thread_add_open_dev_called = 0;
+  audio_thread_rm_open_dev_called = 0;
+
+  device_enabled_count = 0;
+  device_disabled_count = 0;
+
+  EXPECT_EQ(0, cras_iodev_list_set_device_enabled_callback(
+      device_enabled_cb, (void *)0xABCD));
+
+  cras_iodev_list_add_active_node(CRAS_STREAM_OUTPUT,
+      cras_make_node_id(d1_.info.idx, 1));
+
+  EXPECT_EQ(1, device_enabled_count);
+  EXPECT_EQ(1, cras_observer_notify_active_node_called);
+  EXPECT_EQ(&d1_, cras_iodev_list_get_first_enabled_iodev(CRAS_STREAM_OUTPUT));
+
+  // There should be a disable device call for the fallback device.
+  EXPECT_EQ(1, audio_thread_rm_open_dev_called);
+  EXPECT_EQ(1, device_disabled_count);
+  EXPECT_NE(&d1_, device_disabled_dev);
+
+  DL_APPEND(stream_list_get_ret, &rstream);
+  stream_add_cb(&rstream);
+
+  EXPECT_EQ(1, audio_thread_add_stream_called);
+  EXPECT_EQ(1, audio_thread_add_open_dev_called);
+
+  DL_APPEND(stream_list_get_ret, &rstream2);
+  stream_add_cb(&rstream2);
+
+  EXPECT_EQ(2, audio_thread_add_stream_called);
+  EXPECT_EQ(1, audio_thread_add_open_dev_called);
+
+  cras_iodev_list_select_node(CRAS_STREAM_OUTPUT,
+      cras_make_node_id(d2_.info.idx, 2));
+
+  // Additional enabled devices: fallback device, d2_.
+  EXPECT_EQ(3, device_enabled_count);
+  // Additional disabled devices: d1_, fallback device.
+  EXPECT_EQ(3, device_disabled_count);
+  EXPECT_EQ(3, audio_thread_rm_open_dev_called);
+  EXPECT_EQ(2, cras_observer_notify_active_node_called);
+  EXPECT_EQ(&d2_, cras_iodev_list_get_first_enabled_iodev(CRAS_STREAM_OUTPUT));
+
+  // For each stream, the stream is added for fallback device and d2_.
+  EXPECT_EQ(6, audio_thread_add_stream_called);
+
+  EXPECT_EQ(0, cras_iodev_list_set_device_enabled_callback(NULL, NULL));
+}
+
+TEST_F(IoDevTestSuite, SelectPreviouslyEnabledNode) {
+  struct cras_rstream rstream;
+  int rc;
+
+  memset(&rstream, 0, sizeof(rstream));
+
+  cras_iodev_list_init();
+
+  d1_.direction = CRAS_STREAM_OUTPUT;
+  node1.idx = 1;
+  rc = cras_iodev_list_add_output(&d1_);
+  ASSERT_EQ(0, rc);
+
+  d2_.direction = CRAS_STREAM_OUTPUT;
+  node2.idx = 2;
+  rc = cras_iodev_list_add_output(&d2_);
+  ASSERT_EQ(0, rc);
+
+  audio_thread_add_open_dev_called = 0;
+  audio_thread_rm_open_dev_called = 0;
+  device_enabled_count = 0;
+  device_disabled_count = 0;
+
+  EXPECT_EQ(0, cras_iodev_list_set_device_enabled_callback(
+      device_enabled_cb, (void *)0xABCD));
+
+  // Add an active node.
+  cras_iodev_list_add_active_node(CRAS_STREAM_OUTPUT,
+      cras_make_node_id(d1_.info.idx, 1));
+
+  EXPECT_EQ(1, device_enabled_count);
+  EXPECT_EQ(1, cras_observer_notify_active_node_called);
+  EXPECT_EQ(&d1_, cras_iodev_list_get_first_enabled_iodev(CRAS_STREAM_OUTPUT));
+
+  // There should be a disable device call for the fallback device.
+  EXPECT_EQ(1, device_disabled_count);
+  EXPECT_NE(&d1_, device_disabled_dev);
+  EXPECT_NE(&d2_, device_disabled_dev);
+
+  DL_APPEND(stream_list_get_ret, &rstream);
+  stream_add_cb(&rstream);
+
+  EXPECT_EQ(1, audio_thread_add_open_dev_called);
+  EXPECT_EQ(1, audio_thread_add_stream_called);
+
+  // Add a second active node.
+  cras_iodev_list_add_active_node(CRAS_STREAM_OUTPUT,
+      cras_make_node_id(d2_.info.idx, 2));
+
+  EXPECT_EQ(2, device_enabled_count);
+  EXPECT_EQ(1, device_disabled_count);
+  EXPECT_EQ(2, cras_observer_notify_active_node_called);
+  EXPECT_EQ(&d1_, cras_iodev_list_get_first_enabled_iodev(CRAS_STREAM_OUTPUT));
+
+  EXPECT_EQ(2, audio_thread_add_open_dev_called);
+  EXPECT_EQ(2, audio_thread_add_stream_called);
+  EXPECT_EQ(0, audio_thread_rm_open_dev_called);
+
+  // Select the second added active node - the initially added node should get
+  // disabled.
+  cras_iodev_list_select_node(CRAS_STREAM_OUTPUT,
+      cras_make_node_id(d2_.info.idx, 2));
+
+  EXPECT_EQ(2, device_enabled_count);
+  EXPECT_EQ(2, device_disabled_count);
+  EXPECT_EQ(3, cras_observer_notify_active_node_called);
+
+  EXPECT_EQ(&d2_, cras_iodev_list_get_first_enabled_iodev(CRAS_STREAM_OUTPUT));
+  EXPECT_EQ(&d1_, device_disabled_dev);
+
+  EXPECT_EQ(2, audio_thread_add_stream_called);
+  EXPECT_EQ(2, audio_thread_add_open_dev_called);
+  EXPECT_EQ(1, audio_thread_rm_open_dev_called);
+
+  EXPECT_EQ(0, cras_iodev_list_set_device_enabled_callback(NULL, NULL));
+}
+
+TEST_F(IoDevTestSuite, UpdateActiveNode) {
+  int rc;
+
+  cras_iodev_list_init();
+
+  d1_.direction = CRAS_STREAM_OUTPUT;
+  rc = cras_iodev_list_add_output(&d1_);
+  ASSERT_EQ(0, rc);
+
+  d2_.direction = CRAS_STREAM_OUTPUT;
+  rc = cras_iodev_list_add_output(&d2_);
+  ASSERT_EQ(0, rc);
+
+  cras_iodev_list_select_node(CRAS_STREAM_OUTPUT,
+      cras_make_node_id(d2_.info.idx, 1));
+
+  EXPECT_EQ(1, update_active_node_called);
+  EXPECT_EQ(&d2_, update_active_node_iodev_val[0]);
+  EXPECT_EQ(1, update_active_node_node_idx_val[0]);
+  EXPECT_EQ(1, update_active_node_dev_enabled_val[0]);
+
+  /* Fake the active node idx on d2_, and later assert this node is
+   * called for update_active_node when d2_ disabled. */
+  d2_.active_node->idx = 2;
+  cras_iodev_list_select_node(CRAS_STREAM_OUTPUT,
+      cras_make_node_id(d1_.info.idx, 0));
+
+  EXPECT_EQ(3, update_active_node_called);
+  EXPECT_EQ(&d2_, update_active_node_iodev_val[1]);
+  EXPECT_EQ(&d1_, update_active_node_iodev_val[2]);
+  EXPECT_EQ(2, update_active_node_node_idx_val[1]);
+  EXPECT_EQ(0, update_active_node_node_idx_val[2]);
+  EXPECT_EQ(0, update_active_node_dev_enabled_val[1]);
+  EXPECT_EQ(1, update_active_node_dev_enabled_val[2]);
+  EXPECT_EQ(2, cras_observer_notify_active_node_called);
 }
 
 TEST_F(IoDevTestSuite, SelectNonExistingNode) {
@@ -377,7 +658,6 @@
   cras_iodev_list_init();
 
   d1_.direction = CRAS_STREAM_OUTPUT;
-  d1_.is_open = cras_iodev_is_open_stub;
   rc = cras_iodev_list_add_output(&d1_);
   ASSERT_EQ(0, rc);
 
@@ -389,6 +669,7 @@
   cras_iodev_list_select_node(CRAS_STREAM_OUTPUT,
       cras_make_node_id(2, 1));
   EXPECT_EQ(0, d1_.is_enabled);
+  EXPECT_EQ(2, cras_observer_notify_active_node_called);
 }
 
 // Devices with the wrong direction should be rejected.
@@ -436,20 +717,181 @@
   rc = cras_iodev_list_get_outputs(&dev_info);
   EXPECT_EQ(0, rc);
   free(dev_info);
+  EXPECT_EQ(0, cras_observer_notify_active_node_called);
 }
 
-static void device_enabled_cb(struct cras_iodev *dev, int enabled,
-                              void *cb_data)
-{
-  if (enabled)
-    device_enabled_dev = dev;
-  else
-    device_disabled_dev = dev;
-  device_enabled_cb_data = cb_data;
+// Test output_mute_changed callback.
+TEST_F(IoDevTestSuite, OutputMuteChangedToMute) {
+  cras_iodev_list_init();
+
+  // d1_ and d3_ have ramp while d2_ does not have ramp.
+  d1_.ramp = reinterpret_cast<cras_ramp*>(0x123);
+  d2_.ramp = NULL;
+  d3_.ramp = reinterpret_cast<cras_ramp*>(0x124);
+
+  cras_iodev_list_add_output(&d1_);
+  cras_iodev_list_add_output(&d2_);
+  cras_iodev_list_add_output(&d3_);
+
+  // d1_ and d2_ are enabled.
+  cras_iodev_list_enable_dev(&d1_);
+  cras_iodev_list_enable_dev(&d2_);
+
+  // Assume d1 and d2 devices are in normal run.
+  cras_iodev_state_ret[&d1_] = CRAS_IODEV_STATE_NORMAL_RUN;
+  cras_iodev_state_ret[&d2_] = CRAS_IODEV_STATE_NORMAL_RUN;
+  cras_iodev_state_ret[&d3_] = CRAS_IODEV_STATE_CLOSE;
+
+  // Execute the callback.
+  observer_ops->output_mute_changed(NULL, 0, 1, 0);
+
+  // d1_ should set mute state through audio_thread_dev_start_ramp.
+  EXPECT_EQ(&d1_, audio_thread_dev_start_ramp_dev);
+  EXPECT_EQ(1, audio_thread_dev_start_ramp_called);
+  EXPECT_EQ(CRAS_IODEV_RAMP_REQUEST_DOWN_MUTE, audio_thread_dev_start_ramp_req);
+
+  // d2_ should set mute state right away.
+  // d3_ should set mute state right away without calling ramp
+  // because it is not enabled.
+  EXPECT_EQ(2, set_mute_called);
+  EXPECT_EQ(2, set_mute_dev_vector.size());
+  ASSERT_TRUE(device_in_vector(set_mute_dev_vector, &d2_));
+  ASSERT_TRUE(device_in_vector(set_mute_dev_vector, &d3_));
+
+  // Assume d1_ should mute for volume.
+  // It should not use ramp.
+  cras_iodev_is_zero_volume_ret = 1;
+
+  // Clear stub data of interest.
+  audio_thread_dev_start_ramp_dev = NULL;
+  audio_thread_dev_start_ramp_called = 0;
+  set_mute_called = 0;
+  set_mute_dev_vector.clear();
+
+  // Execute the callback.
+  observer_ops->output_mute_changed(NULL, 0, 1, 0);
+
+  // Verify three devices all set mute state right away.
+  EXPECT_EQ(NULL, audio_thread_dev_start_ramp_dev);
+  EXPECT_EQ(0, audio_thread_dev_start_ramp_called);
+  EXPECT_EQ(3, set_mute_called);
+  EXPECT_EQ(3, set_mute_dev_vector.size());
+  ASSERT_TRUE(device_in_vector(set_mute_dev_vector, &d1_));
+  ASSERT_TRUE(device_in_vector(set_mute_dev_vector, &d2_));
+  ASSERT_TRUE(device_in_vector(set_mute_dev_vector, &d3_));
+
+  // Assume d1_ is changed to no_stream run state
+  // It should not use ramp.
+  cras_iodev_state_ret[&d1_] = CRAS_IODEV_STATE_NO_STREAM_RUN;
+
+  // Clear stub data of interest.
+  audio_thread_dev_start_ramp_dev = NULL;
+  audio_thread_dev_start_ramp_called = 0;
+  set_mute_called = 0;
+  set_mute_dev_vector.clear();
+
+  // Execute the callback.
+  observer_ops->output_mute_changed(NULL, 0, 1, 0);
+
+  // Verify three devices all set mute state right away.
+  EXPECT_EQ(NULL, audio_thread_dev_start_ramp_dev);
+  EXPECT_EQ(0, audio_thread_dev_start_ramp_called);
+  EXPECT_EQ(3, set_mute_called);
+  EXPECT_EQ(3, set_mute_dev_vector.size());
+  ASSERT_TRUE(device_in_vector(set_mute_dev_vector, &d1_));
+  ASSERT_TRUE(device_in_vector(set_mute_dev_vector, &d2_));
+  ASSERT_TRUE(device_in_vector(set_mute_dev_vector, &d3_));
+}
+
+// Test output_mute_changed callback.
+TEST_F(IoDevTestSuite, OutputMuteChangedToUnmute) {
+  cras_iodev_list_init();
+
+  // d1_ and d3_ have ramp while d2_ does not have ramp.
+  d1_.ramp = reinterpret_cast<cras_ramp*>(0x123);
+  d2_.ramp = NULL;
+  d3_.ramp = reinterpret_cast<cras_ramp*>(0x124);
+
+  cras_iodev_list_add_output(&d1_);
+  cras_iodev_list_add_output(&d2_);
+  cras_iodev_list_add_output(&d3_);
+
+  // d1_ and d2_ are enabled.
+  cras_iodev_list_enable_dev(&d1_);
+  cras_iodev_list_enable_dev(&d2_);
+
+  // Assume d1 and d2 devices are in normal run.
+  cras_iodev_state_ret[&d1_] = CRAS_IODEV_STATE_NORMAL_RUN;
+  cras_iodev_state_ret[&d2_] = CRAS_IODEV_STATE_NORMAL_RUN;
+  cras_iodev_state_ret[&d3_] = CRAS_IODEV_STATE_CLOSE;
+
+  // Execute the callback.
+  observer_ops->output_mute_changed(NULL, 0, 0, 0);
+
+  // d1_ should set mute state through audio_thread_dev_start_ramp.
+  EXPECT_EQ(&d1_, audio_thread_dev_start_ramp_dev);
+  EXPECT_EQ(1, audio_thread_dev_start_ramp_called);
+  EXPECT_EQ(CRAS_IODEV_RAMP_REQUEST_UP_UNMUTE,
+            audio_thread_dev_start_ramp_req);
+
+  // d2_ should set mute state right away.
+  // d3_ should set mute state right away without calling ramp
+  // because it is not enabled.
+  EXPECT_EQ(2, set_mute_called);
+  EXPECT_EQ(2, set_mute_dev_vector.size());
+  ASSERT_TRUE(device_in_vector(set_mute_dev_vector, &d2_));
+  ASSERT_TRUE(device_in_vector(set_mute_dev_vector, &d3_));
+
+  // Assume d1_ should mute for volume.
+  // It should not use ramp.
+  cras_iodev_is_zero_volume_ret = 1;
+
+  // Clear stub data of interest.
+  audio_thread_dev_start_ramp_dev = NULL;
+  audio_thread_dev_start_ramp_called = 0;
+  set_mute_called = 0;
+  set_mute_dev_vector.clear();
+
+  // Execute the callback.
+  observer_ops->output_mute_changed(NULL, 0, 1, 0);
+
+  // Verify three devices all set mute state right away.
+  EXPECT_EQ(NULL, audio_thread_dev_start_ramp_dev);
+  EXPECT_EQ(0, audio_thread_dev_start_ramp_called);
+  EXPECT_EQ(3, set_mute_called);
+  EXPECT_EQ(3, set_mute_dev_vector.size());
+  ASSERT_TRUE(device_in_vector(set_mute_dev_vector, &d1_));
+  ASSERT_TRUE(device_in_vector(set_mute_dev_vector, &d2_));
+  ASSERT_TRUE(device_in_vector(set_mute_dev_vector, &d3_));
+
+  // Assume d1_ is changed to no_stream run state
+  // It should not use ramp.
+  cras_iodev_state_ret[&d1_] = CRAS_IODEV_STATE_NO_STREAM_RUN;
+
+  // Clear stub data of interest.
+  audio_thread_dev_start_ramp_dev = NULL;
+  audio_thread_dev_start_ramp_called = 0;
+  set_mute_called = 0;
+  set_mute_dev_vector.clear();
+
+  // Execute the callback.
+  observer_ops->output_mute_changed(NULL, 0, 1, 0);
+
+  // Verify three devices all set mute state right away.
+  EXPECT_EQ(NULL, audio_thread_dev_start_ramp_dev);
+  EXPECT_EQ(0, audio_thread_dev_start_ramp_called);
+  EXPECT_EQ(3, set_mute_called);
+  EXPECT_EQ(3, set_mute_dev_vector.size());
+  ASSERT_TRUE(device_in_vector(set_mute_dev_vector, &d1_));
+  ASSERT_TRUE(device_in_vector(set_mute_dev_vector, &d2_));
+  ASSERT_TRUE(device_in_vector(set_mute_dev_vector, &d3_));
 }
 
 // Test enable/disable an iodev.
 TEST_F(IoDevTestSuite, EnableDisableDevice) {
+  device_enabled_count = 0;
+  device_disabled_count = 0;
+
   EXPECT_EQ(0, cras_iodev_list_add_output(&d1_));
 
   EXPECT_EQ(0, cras_iodev_list_set_device_enabled_callback(
@@ -459,15 +901,20 @@
   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(&d1_, cras_iodev_list_get_first_enabled_iodev(CRAS_STREAM_OUTPUT));
 
   // Disable a device.
   cras_iodev_list_disable_dev(&d1_);
   EXPECT_EQ(&d1_, device_disabled_dev);
+  EXPECT_EQ(1, device_disabled_count);
   EXPECT_EQ((void *)0xABCD, device_enabled_cb_data);
 
   EXPECT_EQ(-EEXIST, cras_iodev_list_set_device_enabled_callback(
       device_enabled_cb, (void *)0xABCD));
+  EXPECT_EQ(2, cras_observer_notify_active_node_called);
+
+  EXPECT_EQ(0, cras_iodev_list_set_device_enabled_callback(NULL, NULL));
 }
 
 // Test adding/removing an input dev to the list.
@@ -603,18 +1050,14 @@
 
 // Test nodes changed notification is sent.
 TEST_F(IoDevTestSuite, NodesChangedNotification) {
-  EXPECT_EQ(0, cras_alert_create_called);
   cras_iodev_list_init();
-  /* One for nodes changed and one for active node changed */
-  EXPECT_EQ(2, cras_alert_create_called);
+  EXPECT_EQ(1, cras_observer_add_called);
 
-  EXPECT_EQ(0, cras_alert_pending_called);
   cras_iodev_list_notify_nodes_changed();
-  EXPECT_EQ(1, cras_alert_pending_called);
+  EXPECT_EQ(1, cras_observer_notify_nodes_called);
 
-  EXPECT_EQ(0, cras_alert_destroy_called);
   cras_iodev_list_deinit();
-  EXPECT_EQ(2, cras_alert_destroy_called);
+  EXPECT_EQ(1, cras_observer_remove_called);
 }
 
 // Test callback function for left right swap mode is set and called.
@@ -625,10 +1068,22 @@
   memset(&iodev, 0, sizeof(iodev));
   memset(&ionode, 0, sizeof(ionode));
   ionode.dev = &iodev;
-  cras_iodev_list_set_node_left_right_swapped_callbacks(
-      node_left_right_swapped_cb);
   cras_iodev_list_notify_node_left_right_swapped(&ionode);
-  EXPECT_EQ(1, node_left_right_swapped_cb_called);
+  EXPECT_EQ(1, cras_observer_notify_node_left_right_swapped_called);
+}
+
+// Test callback function for volume and gain are set and called.
+TEST_F(IoDevTestSuite, VolumeGainCallback) {
+
+  struct cras_iodev iodev;
+  struct cras_ionode ionode;
+  memset(&iodev, 0, sizeof(iodev));
+  memset(&ionode, 0, sizeof(ionode));
+  ionode.dev = &iodev;
+  cras_iodev_list_notify_node_volume(&ionode);
+  cras_iodev_list_notify_node_capture_gain(&ionode);
+  EXPECT_EQ(1, cras_observer_notify_output_node_volume_called);
+  EXPECT_EQ(1, cras_observer_notify_input_node_gain_called);
 }
 
 TEST_F(IoDevTestSuite, IodevListSetNodeAttr) {
@@ -679,7 +1134,6 @@
   d1_.direction = CRAS_STREAM_OUTPUT;
   d2_.direction = CRAS_STREAM_OUTPUT;
   d3_.direction = CRAS_STREAM_OUTPUT;
-  d3_.is_open = cras_iodev_is_open_stub;
   rc = cras_iodev_list_add_output(&d1_);
   ASSERT_EQ(0, rc);
   rc = cras_iodev_list_add_output(&d2_);
@@ -687,7 +1141,6 @@
   rc = cras_iodev_list_add_output(&d3_);
   ASSERT_EQ(0, rc);
 
-  iodev_is_open = 0;
   audio_thread_add_open_dev_called = 0;
   cras_iodev_list_add_active_node(CRAS_STREAM_OUTPUT,
       cras_make_node_id(d3_.info.idx, 1));
@@ -697,7 +1150,6 @@
   // If a stream is added, the device should be opened.
   stream_add_cb(&rstream);
   ASSERT_EQ(audio_thread_add_open_dev_called, 1);
-  iodev_is_open = 1;
   audio_thread_rm_open_dev_called = 0;
   audio_thread_drain_stream_return = 10;
   stream_rm_cb(&rstream);
@@ -714,7 +1166,6 @@
   clock_gettime_retspec.tv_sec += 30;
   cras_tm_timer_cb(NULL, NULL);
   ASSERT_EQ(1, audio_thread_rm_open_dev_called);
-  iodev_is_open = 0;
 
   audio_thread_rm_open_dev_called = 0;
   cras_iodev_list_rm_output(&d3_);
@@ -734,11 +1185,9 @@
   cras_iodev_list_init();
 
   d1_.direction = CRAS_STREAM_OUTPUT;
-  d1_.is_open = cras_iodev_is_open_stub;
   rc = cras_iodev_list_add_output(&d1_);
   EXPECT_EQ(0, rc);
 
-  iodev_is_open = 0;
   audio_thread_add_open_dev_called = 0;
   cras_iodev_list_add_active_node(CRAS_STREAM_OUTPUT,
       cras_make_node_id(d1_.info.idx, 1));
@@ -748,7 +1197,6 @@
   // If a stream is added, the device should be opened.
   stream_add_cb(&rstream);
   EXPECT_EQ(1, audio_thread_add_open_dev_called);
-  iodev_is_open = 1;
 
   audio_thread_rm_open_dev_called = 0;
   audio_thread_drain_stream_return = 0;
@@ -809,7 +1257,6 @@
 
   // Add 2 output devices.
   d1_.direction = CRAS_STREAM_OUTPUT;
-  d1_.is_open = cras_iodev_is_open_stub;
   EXPECT_EQ(0, cras_iodev_list_add_output(&d1_));
   d2_.direction = CRAS_STREAM_OUTPUT;
   EXPECT_EQ(0, cras_iodev_list_add_output(&d2_));
@@ -824,16 +1271,22 @@
   EXPECT_EQ(1, audio_thread_add_stream_called);
   EXPECT_EQ(&d1_, audio_thread_add_stream_dev);
   EXPECT_EQ(&rstream, audio_thread_add_stream_stream);
-  iodev_is_open = 1;
+  EXPECT_EQ(1, update_active_node_called);
+  EXPECT_EQ(&d1_, update_active_node_iodev_val[0]);
 
-  // Enable d2, check pinned stream is not added to d2.
-  cras_iodev_list_enable_dev(&d2_);
+  // Select d2, check pinned stream is not added to d2.
+  cras_iodev_list_select_node(CRAS_STREAM_OUTPUT,
+      cras_make_node_id(d2_.info.idx, 0));
   EXPECT_EQ(1, audio_thread_add_stream_called);
+  EXPECT_EQ(2, update_active_node_called);
+  EXPECT_EQ(&d2_, update_active_node_iodev_val[1]);
 
   // Remove pinned stream from d1, check d1 is closed after stream removed.
   EXPECT_EQ(0, stream_rm_cb(&rstream));
   EXPECT_EQ(1, cras_iodev_close_called);
   EXPECT_EQ(&d1_, cras_iodev_close_dev);
+  EXPECT_EQ(3, update_active_node_called);
+  EXPECT_EQ(&d1_, update_active_node_iodev_val[2]);
 }
 
 }  //  namespace
@@ -854,95 +1307,11 @@
 void cras_system_state_update_complete() {
 }
 
-int cras_system_register_volume_changed_cb(cras_alert_cb cb, void *arg) {
-  volume_changed_cb = cb;
-  volume_changed_arg = arg;
-  register_volume_changed_cb_called++;
-  return 0;
-}
-
-int cras_system_remove_volume_changed_cb(cras_alert_cb cb, void *arg) {
-  remove_volume_changed_cb_called++;
-  return 0;
-}
-
-int cras_system_register_mute_changed_cb(cras_alert_cb cb, void *arg) {
-  mute_changed_cb = cb;
-  mute_changed_arg = arg;
-  register_mute_changed_cb_called++;
-  return 0;
-}
-
-int cras_system_remove_mute_changed_cb(cras_alert_cb cb, void *arg) {
-  remove_mute_changed_cb_called++;
-  return 0;
-}
-
-int cras_system_register_suspend_cb(cras_alert_cb cb, void *arg)
-{
-  suspend_cb = cb;
-  register_suspend_cb_called++;
-  return 0;
-}
-
-int cras_system_remove_suspend_cb(cras_alert_cb cb, void *arg)
-{
-  remove_suspend_cb_called++;
-  return 0;
-}
-
 int cras_system_get_suspended()
 {
   return cras_system_get_suspended_val;
 }
 
-int cras_system_register_capture_gain_changed_cb(cras_alert_cb cb, void *arg) {
-  capture_gain_changed_cb = cb;
-  capture_gain_changed_arg = arg;
-  register_capture_gain_changed_cb_called++;
-  return 0;
-}
-
-int cras_system_remove_capture_gain_changed_cb(cras_alert_cb cb, void *arg) {
-  remove_capture_gain_changed_cb_called++;
-  return 0;
-}
-
-int cras_system_register_capture_mute_changed_cb(cras_alert_cb cb, void *arg) {
-  capture_mute_changed_cb = cb;
-  capture_mute_changed_arg = arg;
-  register_capture_mute_changed_cb_called++;
-  return 0;
-}
-
-int cras_system_remove_capture_mute_changed_cb(cras_alert_cb cb, void *arg) {
-  remove_capture_mute_changed_cb_called++;
-  return 0;
-}
-
-struct cras_alert *cras_alert_create(cras_alert_prepare prepare) {
-  cras_alert_create_called++;
-  return NULL;
-}
-
-int cras_alert_add_callback(struct cras_alert *alert, cras_alert_cb cb,
-                            void *arg) {
-  return 0;
-}
-
-int cras_alert_rm_callback(struct cras_alert *alert, cras_alert_cb cb,
-                           void *arg) {
-  return 0;
-}
-
-void cras_alert_pending(struct cras_alert *alert) {
-  cras_alert_pending_called++;
-}
-
-void cras_alert_destroy(struct cras_alert *alert) {
-  cras_alert_destroy_called++;
-}
-
 struct audio_thread *audio_thread_create() {
   return &thread;
 }
@@ -1046,7 +1415,11 @@
 
 struct cras_iodev *empty_iodev_create(enum CRAS_STREAM_DIRECTION direction) {
   dummy_empty_iodev[direction].direction = direction;
-  dummy_empty_iodev[direction].is_open = cras_iodev_is_open_stub;
+  dummy_empty_iodev[direction].update_active_node = dummy_update_active_node;
+  if (dummy_empty_iodev[direction].active_node == NULL) {
+    struct cras_ionode *node = (struct cras_ionode *)calloc(1, sizeof(*node));
+    dummy_empty_iodev[direction].active_node = node;
+  }
   return &dummy_empty_iodev[direction];
 }
 
@@ -1070,17 +1443,13 @@
 
 int cras_iodev_open(struct cras_iodev *iodev, unsigned int cb_level)
 {
-  enum CRAS_STREAM_DIRECTION dir = iodev->direction;
-  if (iodev == &dummy_empty_iodev[dir])
-    empty_iodev_is_open[dir] = 1;
-  iodev_is_open = 1;
-  return 0;
+  if (cras_iodev_open_ret[cras_iodev_open_called] == 0)
+    iodev->state = CRAS_IODEV_STATE_OPEN;
+  return cras_iodev_open_ret[cras_iodev_open_called++];
 }
 
 int cras_iodev_close(struct cras_iodev *iodev) {
-  enum CRAS_STREAM_DIRECTION dir = iodev->direction;
-  if (iodev == &dummy_empty_iodev[dir])
-    empty_iodev_is_open[dir] = 0;
+  iodev->state = CRAS_IODEV_STATE_CLOSE;
   cras_iodev_close_called++;
   cras_iodev_close_dev = iodev;
   return 0;
@@ -1091,6 +1460,22 @@
   return 0;
 }
 
+int cras_iodev_set_mute(struct cras_iodev* iodev) {
+  set_mute_called++;
+  set_mute_dev_vector.push_back(iodev);
+  return 0;
+}
+
+int cras_iodev_is_zero_volume(const struct cras_iodev *iodev)
+{
+  return cras_iodev_is_zero_volume_ret;
+}
+
+enum CRAS_IODEV_STATE cras_iodev_state(const struct cras_iodev *iodev)
+{
+	return cras_iodev_state_ret[iodev];
+}
+
 struct stream_list *stream_list_create(stream_callback *add_cb,
                                        stream_callback *rm_cb,
                                        stream_create_func *create_cb,
@@ -1126,10 +1511,83 @@
                 void (*cb)(struct cras_timer *t, void *data),
                 void *cb_data) {
   cras_tm_timer_cb = cb;
+  cras_tm_timer_cb_data = cb_data;
+  cras_tm_create_timer_called++;
   return reinterpret_cast<struct cras_timer *>(0x404);
 }
 
 void cras_tm_cancel_timer(struct cras_tm *tm, struct cras_timer *t) {
+  cras_tm_cancel_timer_called++;
+}
+
+void cras_fmt_conv_destroy(struct cras_fmt_conv *conv)
+{
+}
+
+struct cras_fmt_conv *cras_channel_remix_conv_create(
+    unsigned int num_channels, const float *coefficient)
+{
+  return NULL;
+}
+
+void cras_channel_remix_convert(struct cras_fmt_conv *conv,
+    uint8_t *in_buf, size_t frames)
+{
+}
+
+struct cras_observer_client *cras_observer_add(
+      const struct cras_observer_ops *ops,
+      void *context)
+{
+  observer_ops = (struct cras_observer_ops *)calloc(1, sizeof(*ops));
+  memcpy(observer_ops, ops, sizeof(*ops));
+  cras_observer_add_called++;
+  return reinterpret_cast<struct cras_observer_client *>(0x55);
+}
+
+void cras_observer_remove(struct cras_observer_client *client)
+{
+  if (observer_ops)
+    free(observer_ops);
+  cras_observer_remove_called++;
+}
+
+void cras_observer_notify_nodes(void) {
+  cras_observer_notify_nodes_called++;
+}
+
+void cras_observer_notify_active_node(enum CRAS_STREAM_DIRECTION direction,
+				      cras_node_id_t node_id)
+{
+  cras_observer_notify_active_node_called++;
+}
+
+void cras_observer_notify_output_node_volume(cras_node_id_t node_id,
+					     int32_t volume)
+{
+  cras_observer_notify_output_node_volume_called++;
+}
+
+void cras_observer_notify_node_left_right_swapped(cras_node_id_t node_id,
+						  int swapped)
+{
+  cras_observer_notify_node_left_right_swapped_called++;
+}
+
+void cras_observer_notify_input_node_gain(cras_node_id_t node_id,
+					  int32_t gain)
+{
+  cras_observer_notify_input_node_gain_called++;
+}
+
+int audio_thread_dev_start_ramp(struct audio_thread *thread,
+                                struct cras_iodev *dev,
+                                enum CRAS_IODEV_RAMP_REQUEST request)
+{
+  audio_thread_dev_start_ramp_called++;
+  audio_thread_dev_start_ramp_dev = dev;
+  audio_thread_dev_start_ramp_req = request;
+  return 0;
 }
 
 //  From librt.
diff --git a/cras/src/tests/iodev_unittest.cc b/cras/src/tests/iodev_unittest.cc
index 8f8e0ad..29e6e22 100644
--- a/cras/src/tests/iodev_unittest.cc
+++ b/cras/src/tests/iodev_unittest.cc
@@ -7,14 +7,23 @@
 
 extern "C" {
 #include "cras_iodev.h"
+#include "cras_ramp.h"
 #include "cras_rstream.h"
 #include "dev_stream.h"
 #include "utlist.h"
+#include "cras_audio_area.h"
+#include "audio_thread_log.h"
 
 // Mock software volume scalers.
 float softvol_scalers[101];
 }
 
+#define BUFFER_SIZE 8192
+
+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 int cras_iodev_list_disable_dev_called;
 static int select_node_called;
 static enum CRAS_STREAM_DIRECTION select_node_direction;
@@ -53,13 +62,47 @@
 static int cras_system_get_mute_return;
 static snd_pcm_format_t cras_scale_buffer_fmt;
 static float cras_scale_buffer_scaler;
+static int cras_scale_buffer_called;
 static unsigned int pre_dsp_hook_called;
 static const uint8_t *pre_dsp_hook_frames;
+
 static void *pre_dsp_hook_cb_data;
 static unsigned int post_dsp_hook_called;
 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;
+static int output_should_wake_ret;
+static int no_stream_called;
+static int no_stream_enable;
+// This will be used extensively in cras_iodev.
+struct audio_thread_event_log *atlog;
+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;
+static int cras_ramp_start_is_up;
+static int cras_ramp_start_duration_frames;
+static int cras_ramp_start_is_called;
+static int cras_ramp_reset_is_called;
+static struct cras_ramp_action cras_ramp_get_current_action_ret;
+static int cras_ramp_update_ramped_frames_num_frames;
+static cras_ramp_cb cras_ramp_start_cb;
+static void* cras_ramp_start_cb_data;
+static int cras_device_monitor_set_device_mute_state_called;
+static struct cras_iodev* cras_device_monitor_set_device_mute_state_dev;
+static snd_pcm_format_t cras_scale_buffer_increment_fmt;
+static uint8_t *cras_scale_buffer_increment_buff;
+static unsigned int cras_scale_buffer_increment_frame;
+static float cras_scale_buffer_increment_scaler;
+static float cras_scale_buffer_increment_increment;
+static int cras_scale_buffer_increment_channel;
 
 // Iodev callback
 int update_channel_layout(struct cras_iodev *iodev) {
@@ -108,12 +151,49 @@
   rate_estimator_add_frames_num_frames = 0;
   rate_estimator_add_frames_called = 0;
   cras_system_get_mute_return = 0;
+  cras_system_get_volume_return = 100;
   cras_mix_mute_count = 0;
   pre_dsp_hook_called = 0;
   pre_dsp_hook_frames = NULL;
   post_dsp_hook_called = 0;
-  post_dsp_hook_frames = NULL;
-  iodev_buffer_size = 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) {
+    free(audio_area);
+    audio_area = NULL;
+  }
+  put_buffer_nframes = 0;
+  output_should_wake_ret= 0;
+  no_stream_called = 0;
+  no_stream_enable = 0;
+  simple_no_stream_called = 0;
+  simple_no_stream_enable = 0;
+  dev_stream_playback_frames_ret = 0;
+  if (!atlog)
+    atlog = audio_thread_event_log_init();
+  get_num_underruns_ret = 0;
+  device_monitor_reset_device_called = 0;
+  output_underrun_called = 0;
+  set_mute_called = 0;
+  cras_ramp_start_is_up = 0;
+  cras_ramp_start_duration_frames = 0;
+  cras_ramp_start_cb = NULL;
+  cras_ramp_start_cb_data = NULL;
+  cras_ramp_start_is_called = 0;
+  cras_ramp_reset_is_called = 0;
+  cras_ramp_get_current_action_ret.type = CRAS_RAMP_ACTION_NONE;
+  cras_ramp_update_ramped_frames_num_frames = 0;
+  cras_device_monitor_set_device_mute_state_called = 0;
+  cras_device_monitor_set_device_mute_state_dev = NULL;
+  cras_scale_buffer_called = 0;
+  cras_scale_buffer_increment_fmt = SND_PCM_FORMAT_UNKNOWN;
+  cras_scale_buffer_increment_buff = NULL;
+  cras_scale_buffer_increment_frame = 0;
+  cras_scale_buffer_increment_scaler = 0;
+  cras_scale_buffer_increment_increment = 0;
+  cras_scale_buffer_increment_channel = 0;
 }
 
 namespace {
@@ -434,14 +514,47 @@
 
 // Put buffer tests
 
-static unsigned int put_buffer_nframes;
+static int get_buffer(cras_iodev* iodev, struct cras_audio_area** area,
+               unsigned int* num) {
+  size_t sz = sizeof(*audio_area) + sizeof(struct cras_channel_area) * 2;
+
+  audio_area = (cras_audio_area*)calloc(1, sz);
+  audio_area->frames = *num;
+  audio_area->num_channels = 2;
+  audio_area->channels[0].buf = audio_buffer;
+  channel_area_set_channel(&audio_area->channels[0], CRAS_CH_FL);
+  audio_area->channels[0].step_bytes = 4;
+  audio_area->channels[1].buf = audio_buffer + 2;
+  channel_area_set_channel(&audio_area->channels[1], CRAS_CH_FR);
+  audio_area->channels[1].step_bytes = 4;
+
+  *area = audio_area;
+  return 0;
+}
 
 static int put_buffer(struct cras_iodev *iodev, unsigned int nframes)
 {
   put_buffer_nframes = nframes;
+  if (audio_area) {
+    free(audio_area);
+    audio_area = NULL;
+  }
   return 0;
 }
 
+static int no_stream(struct cras_iodev *odev, int enable)
+{
+  no_stream_called++;
+  no_stream_enable = enable;
+  // Use default no stream playback to test default behavior.
+  return cras_iodev_default_no_stream_playback(odev, enable);
+}
+
+static int output_should_wake(const struct cras_iodev *odev)
+{
+  return output_should_wake_ret;
+}
+
 static int pre_dsp_hook(const uint8_t *frames, unsigned int nframes,
 			const struct cras_audio_format *fmt, void *cb_data)
 {
@@ -483,7 +596,69 @@
   EXPECT_EQ(20, rate_estimator_add_frames_num_frames);
 }
 
-TEST(IoDevPutOutputBuffer, NoDSP) {
+TEST(IoDevPutOutputBuffer, MuteForVolume) {
+  struct cras_iodev iodev;
+  struct cras_ionode ionode;
+
+  ResetStubData();
+  memset(&iodev, 0, sizeof(iodev));
+  memset(&ionode, 0, sizeof(ionode));
+
+  iodev.nodes = &ionode;
+  iodev.active_node = &ionode;
+  iodev.active_node->dev = &iodev;
+
+  // Case: System volume 100; Node volume 0. => Mute
+  cras_system_get_volume_return = 100;
+  iodev.active_node->volume = 0;
+  EXPECT_EQ(1, cras_iodev_is_zero_volume(&iodev));
+
+  // Case: System volume 100; Node volume 50. => Not mute
+  cras_system_get_volume_return = 100;
+  iodev.active_node->volume = 50;
+  EXPECT_EQ(0, cras_iodev_is_zero_volume(&iodev));
+
+  // Case: System volume 0; Node volume 50. => Mute
+  cras_system_get_volume_return = 0;
+  iodev.active_node->volume = 50;
+  EXPECT_EQ(1, cras_iodev_is_zero_volume(&iodev));
+
+  // Case: System volume 50; Node volume 50. => Mute
+  cras_system_get_volume_return = 50;
+  iodev.active_node->volume = 50;
+  EXPECT_EQ(1, cras_iodev_is_zero_volume(&iodev));
+}
+
+TEST(IoDevPutOutputBuffer, NodeVolumeZeroShouldMute) {
+  struct cras_audio_format fmt;
+  struct cras_iodev iodev;
+  struct cras_ionode ionode;
+  uint8_t *frames = reinterpret_cast<uint8_t*>(0x44);
+  int rc;
+
+  ResetStubData();
+  memset(&iodev, 0, sizeof(iodev));
+  memset(&ionode, 0, sizeof(ionode));
+
+  iodev.nodes = &ionode;
+  iodev.active_node = &ionode;
+  iodev.active_node->dev = &iodev;
+  iodev.active_node->volume = 0;
+
+  fmt.format = SND_PCM_FORMAT_S16_LE;
+  fmt.frame_rate = 48000;
+  fmt.num_channels = 2;
+  iodev.format = &fmt;
+  iodev.put_buffer = put_buffer;
+
+  rc = cras_iodev_put_output_buffer(&iodev, frames, 20);
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(20, cras_mix_mute_count);
+  EXPECT_EQ(20, put_buffer_nframes);
+  EXPECT_EQ(20, rate_estimator_add_frames_num_frames);
+}
+
+TEST(IoDevPutOutputBuffer, SystemMutedWithRamp) {
   struct cras_audio_format fmt;
   struct cras_iodev iodev;
   uint8_t *frames = reinterpret_cast<uint8_t*>(0x44);
@@ -491,6 +666,103 @@
 
   ResetStubData();
   memset(&iodev, 0, sizeof(iodev));
+  cras_system_get_mute_return = 1;
+
+  fmt.format = SND_PCM_FORMAT_S16_LE;
+  fmt.frame_rate = 48000;
+  fmt.num_channels = 2;
+  iodev.format = &fmt;
+  iodev.put_buffer = put_buffer;
+
+  // Assume device has ramp member.
+  iodev.ramp = reinterpret_cast<struct cras_ramp*>(0x1);
+
+  // Assume ramping is done.
+  cras_ramp_get_current_action_ret.type = CRAS_RAMP_ACTION_NONE;
+
+  rc = cras_iodev_put_output_buffer(&iodev, frames, 20);
+  // Output should be muted.
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(20, cras_mix_mute_count);
+  EXPECT_EQ(20, put_buffer_nframes);
+  EXPECT_EQ(20, rate_estimator_add_frames_num_frames);
+
+  // Test for the case where ramping is not done yet.
+  ResetStubData();
+  cras_ramp_get_current_action_ret.type = CRAS_RAMP_ACTION_PARTIAL;
+  rc = cras_iodev_put_output_buffer(&iodev, frames, 20);
+
+  // Output should not be muted.
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(0, cras_mix_mute_count);
+  // Ramped frames should be increased by 20.
+  EXPECT_EQ(20, cras_ramp_update_ramped_frames_num_frames);
+  EXPECT_EQ(20, put_buffer_nframes);
+  EXPECT_EQ(20, rate_estimator_add_frames_num_frames);
+}
+
+TEST(IoDevPutOutputBuffer, NodeVolumeZeroShouldMuteWithRamp) {
+  struct cras_audio_format fmt;
+  struct cras_iodev iodev;
+  struct cras_ionode ionode;
+  uint8_t *frames = reinterpret_cast<uint8_t*>(0x44);
+  int rc;
+
+  ResetStubData();
+  memset(&iodev, 0, sizeof(iodev));
+  memset(&ionode, 0, sizeof(ionode));
+
+  iodev.nodes = &ionode;
+  iodev.active_node = &ionode;
+  iodev.active_node->dev = &iodev;
+  iodev.active_node->volume = 0;
+
+  fmt.format = SND_PCM_FORMAT_S16_LE;
+  fmt.frame_rate = 48000;
+  fmt.num_channels = 2;
+  iodev.format = &fmt;
+  iodev.put_buffer = put_buffer;
+
+  // Assume device has ramp member.
+  iodev.ramp = reinterpret_cast<struct cras_ramp*>(0x1);
+
+  // Assume ramping is done.
+  cras_ramp_get_current_action_ret.type = CRAS_RAMP_ACTION_NONE;
+
+  rc = cras_iodev_put_output_buffer(&iodev, frames, 20);
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(20, cras_mix_mute_count);
+  EXPECT_EQ(20, put_buffer_nframes);
+  EXPECT_EQ(20, rate_estimator_add_frames_num_frames);
+
+  // Test for the case where ramping is not done yet.
+  ResetStubData();
+  cras_ramp_get_current_action_ret.type = CRAS_RAMP_ACTION_PARTIAL;
+  rc = cras_iodev_put_output_buffer(&iodev, frames, 20);
+
+  // Output should not be muted.
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(0, cras_mix_mute_count);
+  // Ramped frames should be increased by 20.
+  EXPECT_EQ(20, cras_ramp_update_ramped_frames_num_frames);
+  EXPECT_EQ(20, put_buffer_nframes);
+  EXPECT_EQ(20, rate_estimator_add_frames_num_frames);
+}
+TEST(IoDevPutOutputBuffer, NoDSP) {
+  struct cras_audio_format fmt;
+  struct cras_iodev iodev;
+  struct cras_ionode ionode;
+  uint8_t *frames = reinterpret_cast<uint8_t*>(0x44);
+  int rc;
+
+  ResetStubData();
+  memset(&iodev, 0, sizeof(iodev));
+  memset(&ionode, 0, sizeof(ionode));
+
+  iodev.nodes = &ionode;
+  iodev.active_node = &ionode;
+  iodev.active_node->dev = &iodev;
+  iodev.active_node->volume = 100;
 
   fmt.format = SND_PCM_FORMAT_S16_LE;
   fmt.frame_rate = 48000;
@@ -566,6 +838,132 @@
   EXPECT_EQ(SND_PCM_FORMAT_S16_LE, cras_scale_buffer_fmt);
 }
 
+TEST(IoDevPutOutputBuffer, SoftVolWithRamp) {
+  struct cras_audio_format fmt;
+  struct cras_iodev iodev;
+  uint8_t *frames = reinterpret_cast<uint8_t*>(0x44);
+  int rc;
+  int n_frames = 53;
+  float ramp_scaler = 0.2;
+  float increment = 0.001;
+  int volume = 13;
+  float volume_scaler = 0.435;
+
+  ResetStubData();
+  memset(&iodev, 0, sizeof(iodev));
+  iodev.software_volume_needed = 1;
+
+  fmt.format = SND_PCM_FORMAT_S16_LE;
+  fmt.frame_rate = 48000;
+  fmt.num_channels = 2;
+  iodev.format = &fmt;
+  iodev.put_buffer = put_buffer;
+  // Assume device has ramp member.
+  iodev.ramp = reinterpret_cast<struct cras_ramp*>(0x1);
+
+  // Assume ramping is done.
+  cras_ramp_get_current_action_ret.type = CRAS_RAMP_ACTION_NONE;
+
+  cras_system_get_volume_return = volume;
+  softvol_scalers[volume] = volume_scaler;
+
+  rc = cras_iodev_put_output_buffer(&iodev, frames, n_frames);
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(0, cras_mix_mute_count);
+  EXPECT_EQ(n_frames, put_buffer_nframes);
+  EXPECT_EQ(n_frames, rate_estimator_add_frames_num_frames);
+  EXPECT_EQ(softvol_scalers[volume], cras_scale_buffer_scaler);
+  EXPECT_EQ(SND_PCM_FORMAT_S16_LE, cras_scale_buffer_fmt);
+
+  ResetStubData();
+  // Assume ramping is not done.
+  cras_ramp_get_current_action_ret.type = CRAS_RAMP_ACTION_PARTIAL;
+  cras_ramp_get_current_action_ret.scaler = ramp_scaler;
+  cras_ramp_get_current_action_ret.increment = increment;
+
+  cras_system_get_volume_return = volume;
+  softvol_scalers[volume] = volume_scaler;
+
+  rc = cras_iodev_put_output_buffer(&iodev, frames, n_frames);
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(0, cras_mix_mute_count);
+  // cras_scale_buffer is not called.
+  EXPECT_EQ(0, cras_scale_buffer_called);
+
+  // Verify the arguments passed to cras_scale_buffer_increment.
+  EXPECT_EQ(fmt.format, cras_scale_buffer_increment_fmt);
+  EXPECT_EQ(frames, cras_scale_buffer_increment_buff);
+  EXPECT_EQ(n_frames, cras_scale_buffer_increment_frame);
+  // Initial scaler will be product of software volume scaler and
+  // ramp scaler.
+  EXPECT_FLOAT_EQ(softvol_scalers[volume] * ramp_scaler,
+                  cras_scale_buffer_increment_scaler);
+  // Increment scaler will be product of software volume scaler and
+  // ramp increment.
+  EXPECT_FLOAT_EQ(softvol_scalers[volume] * increment,
+                  cras_scale_buffer_increment_increment);
+  EXPECT_EQ(fmt.num_channels, cras_scale_buffer_increment_channel);
+
+  EXPECT_EQ(n_frames, put_buffer_nframes);
+  EXPECT_EQ(n_frames, rate_estimator_add_frames_num_frames);
+}
+
+TEST(IoDevPutOutputBuffer, NoSoftVolWithRamp) {
+  struct cras_audio_format fmt;
+  struct cras_iodev iodev;
+  uint8_t *frames = reinterpret_cast<uint8_t*>(0x44);
+  int rc;
+  int n_frames = 53;
+  float ramp_scaler = 0.2;
+  float increment = 0.001;
+
+  ResetStubData();
+  memset(&iodev, 0, sizeof(iodev));
+  iodev.software_volume_needed = 0;
+
+  fmt.format = SND_PCM_FORMAT_S16_LE;
+  fmt.frame_rate = 48000;
+  fmt.num_channels = 2;
+  iodev.format = &fmt;
+  iodev.put_buffer = put_buffer;
+  // Assume device has ramp member.
+  iodev.ramp = reinterpret_cast<struct cras_ramp*>(0x1);
+
+  // Assume ramping is done.
+  cras_ramp_get_current_action_ret.type = CRAS_RAMP_ACTION_NONE;
+
+  rc = cras_iodev_put_output_buffer(&iodev, frames, n_frames);
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(0, cras_mix_mute_count);
+  // cras_scale_buffer is not called.
+  EXPECT_EQ(0, cras_scale_buffer_called);
+  EXPECT_EQ(n_frames, put_buffer_nframes);
+  EXPECT_EQ(n_frames, rate_estimator_add_frames_num_frames);
+
+  ResetStubData();
+  // Assume ramping is not done.
+  cras_ramp_get_current_action_ret.type = CRAS_RAMP_ACTION_PARTIAL;
+  cras_ramp_get_current_action_ret.scaler = ramp_scaler;
+  cras_ramp_get_current_action_ret.increment = increment;
+
+  rc = cras_iodev_put_output_buffer(&iodev, frames, n_frames);
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(0, cras_mix_mute_count);
+  // cras_scale_buffer is not called.
+  EXPECT_EQ(0, cras_scale_buffer_called);
+
+  // Verify the arguments passed to cras_scale_buffer_increment.
+  EXPECT_EQ(fmt.format, cras_scale_buffer_increment_fmt);
+  EXPECT_EQ(frames, cras_scale_buffer_increment_buff);
+  EXPECT_EQ(n_frames, cras_scale_buffer_increment_frame);
+  EXPECT_FLOAT_EQ(ramp_scaler, cras_scale_buffer_increment_scaler);
+  EXPECT_FLOAT_EQ(increment, cras_scale_buffer_increment_increment);
+  EXPECT_EQ(fmt.num_channels, cras_scale_buffer_increment_channel);
+
+  EXPECT_EQ(n_frames, put_buffer_nframes);
+  EXPECT_EQ(n_frames, rate_estimator_add_frames_num_frames);
+}
+
 TEST(IoDevPutOutputBuffer, Scale32Bit) {
   struct cras_audio_format fmt;
   struct cras_iodev iodev;
@@ -597,13 +995,16 @@
 
 static unsigned fr_queued = 0;
 
-static int frames_queued(const struct cras_iodev *iodev)
+static int frames_queued(const struct cras_iodev *iodev,
+                         struct timespec *tstamp)
 {
+  clock_gettime(CLOCK_MONOTONIC_RAW, tstamp);
   return fr_queued;
 }
 
 TEST(IoDevQueuedBuffer, ZeroMinBufferLevel) {
   struct cras_iodev iodev;
+  struct timespec tstamp;
   int rc;
 
   ResetStubData();
@@ -614,7 +1015,7 @@
   iodev.buffer_size = 200;
   fr_queued = 50;
 
-  rc = cras_iodev_frames_queued(&iodev);
+  rc = cras_iodev_frames_queued(&iodev, &tstamp);
   EXPECT_EQ(50, rc);
   rc = cras_iodev_buffer_avail(&iodev, rc);
   EXPECT_EQ(150, rc);
@@ -622,6 +1023,7 @@
 
 TEST(IoDevQueuedBuffer, NonZeroMinBufferLevel) {
   struct cras_iodev iodev;
+  struct timespec hw_tstamp;
   int rc;
 
   ResetStubData();
@@ -632,21 +1034,22 @@
   iodev.buffer_size = 200;
   fr_queued = 180;
 
-  rc = cras_iodev_frames_queued(&iodev);
+  rc = cras_iodev_frames_queued(&iodev, &hw_tstamp);
   EXPECT_EQ(80, rc);
   rc = cras_iodev_buffer_avail(&iodev, rc);
   EXPECT_EQ(20, rc);
 
   /* When fr_queued < min_buffer_level*/
   fr_queued = 80;
-  rc = cras_iodev_frames_queued(&iodev);
+  rc = cras_iodev_frames_queued(&iodev, &hw_tstamp);
   EXPECT_EQ(0, rc);
   rc = cras_iodev_buffer_avail(&iodev, rc);
   EXPECT_EQ(100, rc);
 }
 
 static void update_active_node(struct cras_iodev *iodev,
-                               unsigned node_idx)
+                               unsigned node_idx,
+                               unsigned dev_enabled)
 {
 }
 
@@ -658,20 +1061,36 @@
 {
 }
 
+static void dev_set_mute(struct cras_iodev *iodev)
+{
+  set_mute_called++;
+}
+
 TEST(IoNodePlug, PlugUnplugNode) {
   struct cras_iodev iodev;
-  struct cras_ionode ionode;
+  struct cras_ionode ionode, ionode2;
 
   memset(&iodev, 0, sizeof(iodev));
   memset(&ionode, 0, sizeof(ionode));
-  ionode.dev = &iodev;
+  memset(&ionode2, 0, sizeof(ionode2));
   iodev.direction = CRAS_STREAM_INPUT;
   iodev.update_active_node = update_active_node;
+  ionode.dev = &iodev;
+  cras_iodev_add_node(&iodev, &ionode);
+  ionode2.dev = &iodev;
+  cras_iodev_add_node(&iodev, &ionode2);
+  cras_iodev_set_active_node(&iodev, &ionode);
   ResetStubData();
   cras_iodev_set_node_attr(&ionode, IONODE_ATTR_PLUGGED, 1);
   EXPECT_EQ(0, cras_iodev_list_disable_dev_called);
   cras_iodev_set_node_attr(&ionode, IONODE_ATTR_PLUGGED, 0);
   EXPECT_EQ(1, cras_iodev_list_disable_dev_called);
+
+  /* Unplug non-active node shouldn't disable iodev. */
+  cras_iodev_set_node_attr(&ionode2, IONODE_ATTR_PLUGGED, 1);
+  EXPECT_EQ(1, cras_iodev_list_disable_dev_called);
+  cras_iodev_set_node_attr(&ionode2, IONODE_ATTR_PLUGGED, 0);
+  EXPECT_EQ(1, cras_iodev_list_disable_dev_called);
 }
 
 TEST(IoDev, AddRemoveNode) {
@@ -738,6 +1157,24 @@
   EXPECT_EQ(2, notify_node_left_right_swapped_called);
 }
 
+TEST(IoDev, SetMute) {
+  struct cras_iodev iodev;
+  int rc;
+
+  memset(&iodev, 0, sizeof(iodev));
+  iodev.set_mute = dev_set_mute;
+  iodev.state = CRAS_IODEV_STATE_CLOSE;
+
+  ResetStubData();
+  rc = cras_iodev_set_mute(&iodev);
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(0, set_mute_called);
+
+  iodev.state = CRAS_IODEV_STATE_OPEN;
+  rc = cras_iodev_set_mute(&iodev);
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(1, set_mute_called);
+}
 
 // Test software volume changes for default output.
 TEST(IoDev, SoftwareVolume) {
@@ -770,11 +1207,165 @@
   EXPECT_FLOAT_EQ(0.3, cras_iodev_get_software_volume_scaler(&iodev));
 }
 
+// Test software gain scaler.
+TEST(IoDev, SoftwareGain) {
+  struct cras_iodev iodev;
+  struct cras_ionode ionode;
+
+  memset(&iodev, 0, sizeof(iodev));
+  memset(&ionode, 0, sizeof(ionode));
+  ResetStubData();
+
+  iodev.nodes = &ionode;
+  iodev.active_node = &ionode;
+  iodev.active_node->dev = &iodev;
+
+  ionode.capture_gain= 400;
+  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 dBm 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
+// frames.
+static int bad_get_buffer(struct cras_iodev *iodev,
+                          struct cras_audio_area **area,
+                          unsigned *frames)
+{
+  *frames = *frames + 1;
+  return 0;
+}
+
+// Check that if get_buffer implementation returns invalid frames,
+// cras_iodev_get_output_buffer and cras_iodev_get_input_buffer can return
+// error.
+TEST(IoDev, GetBufferInvalidFrames) {
+  struct cras_iodev iodev;
+  struct cras_audio_area **area = NULL;
+  unsigned int frames = 512;
+  struct cras_audio_format fmt;
+
+  // Format is used in cras_iodev_get_input_buffer;
+  fmt.format = SND_PCM_FORMAT_S16_LE;
+  fmt.frame_rate = 48000;
+  fmt.num_channels = 2;
+
+  memset(&iodev, 0, sizeof(iodev));
+
+  ResetStubData();
+
+  iodev.format = &fmt;
+  iodev.get_buffer = bad_get_buffer;
+
+  EXPECT_EQ(-EINVAL, cras_iodev_get_output_buffer(&iodev, area, &frames));
+  EXPECT_EQ(-EINVAL, cras_iodev_get_input_buffer(&iodev, area, &frames));
+}
+
 static int open_dev(struct cras_iodev *iodev) {
   iodev->buffer_size = iodev_buffer_size;
   return 0;
 }
 
+TEST(IoDev, OpenOutputDeviceNoStart) {
+  struct cras_iodev iodev;
+
+  memset(&iodev, 0, sizeof(iodev));
+  iodev.open_dev = open_dev;
+  iodev.direction = CRAS_STREAM_OUTPUT;
+  ResetStubData();
+
+  iodev.state = CRAS_IODEV_STATE_CLOSE;
+
+  iodev_buffer_size = 1024;
+  cras_iodev_open(&iodev, 240);
+  EXPECT_EQ(0, iodev.max_cb_level);
+  EXPECT_EQ(240, iodev.min_cb_level);
+
+  // Test that state is no stream run when there is no start ops.
+  EXPECT_EQ(CRAS_IODEV_STATE_NO_STREAM_RUN, iodev.state);
+}
+
+int fake_start(const struct cras_iodev *iodev) {
+  return 0;
+}
+
+TEST(IoDev, OpenOutputDeviceWithStart) {
+  struct cras_iodev iodev;
+
+  memset(&iodev, 0, sizeof(iodev));
+  iodev.open_dev = open_dev;
+  iodev.direction = CRAS_STREAM_OUTPUT;
+  ResetStubData();
+
+  iodev.state = CRAS_IODEV_STATE_CLOSE;
+  iodev.start = fake_start;
+
+  iodev_buffer_size = 1024;
+  cras_iodev_open(&iodev, 240);
+  EXPECT_EQ(0, iodev.max_cb_level);
+  EXPECT_EQ(240, iodev.min_cb_level);
+
+  // Test that state is no stream run when there is start ops.
+  EXPECT_EQ(CRAS_IODEV_STATE_OPEN, iodev.state);
+}
+
+TEST(IoDev, OpenInputDeviceNoStart) {
+  struct cras_iodev iodev;
+
+  memset(&iodev, 0, sizeof(iodev));
+  iodev.open_dev = open_dev;
+  iodev.direction = CRAS_STREAM_INPUT;
+  ResetStubData();
+
+  iodev.state = CRAS_IODEV_STATE_CLOSE;
+
+  iodev_buffer_size = 1024;
+  cras_iodev_open(&iodev, 240);
+  EXPECT_EQ(0, iodev.max_cb_level);
+  EXPECT_EQ(240, iodev.min_cb_level);
+
+  // Test that state is normal run when there is start ops.
+  EXPECT_EQ(CRAS_IODEV_STATE_NORMAL_RUN, iodev.state);
+}
+
+TEST(IoDev, OpenInputDeviceWithStart) {
+  struct cras_iodev iodev;
+
+  memset(&iodev, 0, sizeof(iodev));
+  iodev.open_dev = open_dev;
+  iodev.direction = CRAS_STREAM_INPUT;
+  ResetStubData();
+
+  iodev.state = CRAS_IODEV_STATE_CLOSE;
+  iodev.start = fake_start;
+
+  iodev_buffer_size = 1024;
+  cras_iodev_open(&iodev, 240);
+  EXPECT_EQ(0, iodev.max_cb_level);
+  EXPECT_EQ(240, iodev.min_cb_level);
+
+  // Test that state is normal run even if there is start ops.
+  EXPECT_EQ(CRAS_IODEV_STATE_NORMAL_RUN, iodev.state);
+}
+
+static int simple_no_stream(struct cras_iodev *dev, int enable)
+{
+  simple_no_stream_enable = enable;
+  simple_no_stream_called++;
+  return 0;
+}
+
 TEST(IoDev, AddRmStream) {
   struct cras_iodev iodev;
   struct cras_rstream rstream1, rstream2;
@@ -782,6 +1373,8 @@
 
   memset(&iodev, 0, sizeof(iodev));
   iodev.open_dev = open_dev;
+  iodev.no_stream = simple_no_stream;
+  iodev.state = CRAS_IODEV_STATE_NORMAL_RUN;
   rstream1.cb_threshold = 800;
   stream1.stream = &rstream1;
   rstream2.cb_threshold = 400;
@@ -805,6 +1398,7 @@
   cras_iodev_rm_stream(&iodev, &rstream1);
   EXPECT_EQ(400, iodev.max_cb_level);
   EXPECT_EQ(400, iodev.min_cb_level);
+  EXPECT_EQ(0, simple_no_stream_called);
 
   /* When all streams are removed, keep the last min_cb_level for draining. */
   cras_iodev_rm_stream(&iodev, &rstream2);
@@ -812,6 +1406,557 @@
   EXPECT_EQ(400, iodev.min_cb_level);
 }
 
+TEST(IoDev, FillZeros) {
+  struct cras_iodev iodev;
+  struct cras_audio_format fmt;
+  unsigned int frames = 50;
+  int16_t *zeros;
+  int rc;
+
+  ResetStubData();
+
+  memset(&iodev, 0, sizeof(iodev));
+  fmt.format = SND_PCM_FORMAT_S16_LE;
+  fmt.frame_rate = 48000;
+  fmt.num_channels = 2;
+  iodev.ext_format = &fmt;
+  iodev.get_buffer = get_buffer;
+  iodev.put_buffer = put_buffer;
+
+  iodev.direction = CRAS_STREAM_INPUT;
+  rc = cras_iodev_fill_odev_zeros(&iodev, frames);
+  EXPECT_EQ(-EINVAL, rc);
+
+  iodev.direction = CRAS_STREAM_OUTPUT;
+  rc = cras_iodev_fill_odev_zeros(&iodev, frames);
+
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(frames, put_buffer_nframes);
+  zeros = (int16_t *)calloc(frames * 2, sizeof(*zeros));
+  rc = memcmp(audio_buffer, zeros, frames * 2 * 2);
+  free(zeros);
+  EXPECT_EQ(0, rc);
+}
+
+TEST(IoDev, DefaultNoStreamPlaybackRunning) {
+  struct cras_iodev iodev;
+  struct cras_audio_format fmt;
+  unsigned int hw_level = 50;
+  unsigned int min_cb_level = 240;
+  unsigned int zeros_to_fill;
+  int16_t *zeros;
+  int rc;
+
+  memset(&iodev, 0, sizeof(iodev));
+
+  fmt.format = SND_PCM_FORMAT_S16_LE;
+  fmt.frame_rate = 48000;
+  fmt.num_channels = 2;
+  iodev.ext_format = &fmt;
+  iodev.min_cb_level = min_cb_level;
+  iodev.get_buffer = get_buffer;
+  iodev.put_buffer = put_buffer;
+  iodev.frames_queued = frames_queued;
+  iodev.min_buffer_level = 0;
+  iodev.direction = CRAS_STREAM_OUTPUT;
+  iodev.buffer_size = BUFFER_SIZE;
+  iodev.no_stream = no_stream;
+
+  ResetStubData();
+
+  // Device is running. hw_level is less than target.
+  // Need to fill to callback level * 2;
+  iodev.state = CRAS_IODEV_STATE_NO_STREAM_RUN;
+  fr_queued = hw_level;
+  zeros_to_fill = min_cb_level * 2 - hw_level;
+
+  rc = cras_iodev_default_no_stream_playback(&iodev, 1);
+
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(CRAS_IODEV_STATE_NO_STREAM_RUN, iodev.state);
+  EXPECT_EQ(zeros_to_fill, put_buffer_nframes);
+  zeros = (int16_t *)calloc(zeros_to_fill * 2, sizeof(*zeros));
+  EXPECT_EQ(0, memcmp(audio_buffer, zeros, zeros_to_fill * 2 * 2));
+  free(zeros);
+
+  ResetStubData();
+
+  // Device is running. hw_level is not less than target.
+  // No need to fill zeros.
+  iodev.state = CRAS_IODEV_STATE_NO_STREAM_RUN;
+  hw_level = min_cb_level * 2;
+  fr_queued = hw_level;
+  zeros_to_fill = 0;
+
+  rc = cras_iodev_default_no_stream_playback(&iodev, 1);
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(CRAS_IODEV_STATE_NO_STREAM_RUN, iodev.state);
+  EXPECT_EQ(zeros_to_fill, put_buffer_nframes);
+}
+
+TEST(IoDev, PrepareOutputBeforeWriteSamples) {
+  struct cras_iodev iodev;
+  struct cras_audio_format fmt;
+  unsigned int min_cb_level = 240;
+  int rc;
+  struct cras_rstream rstream1;
+  struct dev_stream stream1;
+  struct cras_iodev_info info;
+
+  ResetStubData();
+
+  rstream1.cb_threshold = min_cb_level;
+  stream1.stream = &rstream1;
+
+  memset(&iodev, 0, sizeof(iodev));
+
+  fmt.format = SND_PCM_FORMAT_S16_LE;
+  fmt.frame_rate = 48000;
+  fmt.num_channels = 2;
+  iodev.ext_format = &fmt;
+  iodev.format = &fmt;
+  iodev.min_cb_level = min_cb_level;
+  iodev.get_buffer = get_buffer;
+  iodev.put_buffer = put_buffer;
+  iodev.frames_queued = frames_queued;
+  iodev.min_buffer_level = 0;
+  iodev.direction = CRAS_STREAM_OUTPUT;
+  iodev.buffer_size = BUFFER_SIZE;
+  iodev.no_stream = no_stream;
+  iodev.open_dev = open_dev;
+  iodev.start = fake_start;
+  iodev.info = info;
+  iodev_buffer_size = BUFFER_SIZE;
+
+  // Open device.
+  cras_iodev_open(&iodev, rstream1.cb_threshold);
+
+  // Add one stream to device.
+  cras_iodev_add_stream(&iodev, &stream1);
+
+  // Case 1: Assume device is not started yet.
+  iodev.state = CRAS_IODEV_STATE_OPEN;
+  // Assume sample is not ready yet.
+  dev_stream_playback_frames_ret = 0;
+
+  rc = cras_iodev_prepare_output_before_write_samples(&iodev);
+
+  EXPECT_EQ(0, rc);
+  // Device should remain in open state.
+  EXPECT_EQ(CRAS_IODEV_STATE_OPEN, iodev.state);
+  EXPECT_EQ(0, no_stream_called);
+
+  // Assume now sample is ready.
+  dev_stream_playback_frames_ret = 100;
+
+  rc = cras_iodev_prepare_output_before_write_samples(&iodev);
+
+  EXPECT_EQ(0, rc);
+  // Device should enter normal run state.
+  EXPECT_EQ(CRAS_IODEV_STATE_NORMAL_RUN, iodev.state);
+  EXPECT_EQ(0, no_stream_called);
+  // Need to fill 1 callback level of zeros;
+  EXPECT_EQ(min_cb_level, put_buffer_nframes);
+
+  ResetStubData();
+
+  // Case 2: Assume device is started and is in no stream state.
+  iodev.state = CRAS_IODEV_STATE_NO_STREAM_RUN;
+  // Sample is not ready yet.
+  dev_stream_playback_frames_ret = 0;
+
+  rc = cras_iodev_prepare_output_before_write_samples(&iodev);
+
+  EXPECT_EQ(0, rc);
+  // Device should remain in no_stream state.
+  EXPECT_EQ(CRAS_IODEV_STATE_NO_STREAM_RUN, iodev.state);
+  // Device in no_stream state should call no_stream ops once.
+  EXPECT_EQ(1, no_stream_called);
+  EXPECT_EQ(1, no_stream_enable);
+
+  // Assume now sample is ready.
+  dev_stream_playback_frames_ret = 100;
+
+  rc = cras_iodev_prepare_output_before_write_samples(&iodev);
+
+  EXPECT_EQ(0, rc);
+  // Device should enter normal run state.
+  EXPECT_EQ(CRAS_IODEV_STATE_NORMAL_RUN, iodev.state);
+  // Device should call no_stream ops with enable=0 to leave no stream state.
+  EXPECT_EQ(2, no_stream_called);
+  EXPECT_EQ(0, no_stream_enable);
+
+  ResetStubData();
+
+  // Case 3: Assume device is started and is in normal run state.
+  iodev.state = CRAS_IODEV_STATE_NORMAL_RUN;
+
+  rc = cras_iodev_prepare_output_before_write_samples(&iodev);
+
+  EXPECT_EQ(0, rc);
+  // Device should remain in normal run state.
+  EXPECT_EQ(CRAS_IODEV_STATE_NORMAL_RUN, iodev.state);
+  // Device in no_stream state should call no_stream ops once.
+  EXPECT_EQ(0, no_stream_called);
+
+  ResetStubData();
+
+  // Test for device with ramp. Device should start ramping
+  // when sample is ready.
+
+  // Assume device has ramp member.
+  iodev.ramp = reinterpret_cast<struct cras_ramp*>(0x1);
+
+  // 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.
+  dev_stream_playback_frames_ret = 100;
+
+  rc = cras_iodev_prepare_output_before_write_samples(&iodev);
+
+  // Device should start ramping up without setting mute callback.
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(1, cras_ramp_start_is_called);
+  EXPECT_EQ(1, cras_ramp_start_is_up);
+  EXPECT_EQ(fmt.frame_rate * RAMP_NEW_STREAM_DURATION_SECS,
+            cras_ramp_start_duration_frames);
+  EXPECT_EQ(NULL, cras_ramp_start_cb);
+  EXPECT_EQ(NULL, cras_ramp_start_cb_data);
+
+  ResetStubData();
+
+  // Case 4.2: Assume device with ramp is started and is in no stream state.
+  //           But system is muted.
+  iodev.state = CRAS_IODEV_STATE_NO_STREAM_RUN;
+  // Assume system is muted.
+  cras_system_get_mute_return = 1;
+  // Assume sample is ready.
+  dev_stream_playback_frames_ret = 100;
+
+  rc = cras_iodev_prepare_output_before_write_samples(&iodev);
+
+  // Device should not start ramping up because system is muted.
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(0, cras_ramp_start_is_called);
+
+  ResetStubData();
+
+  // Case 5.1: Assume device with ramp is in open state.
+  iodev.state = CRAS_IODEV_STATE_OPEN;
+  // Assume sample is ready.
+  dev_stream_playback_frames_ret = 100;
+
+  rc = cras_iodev_prepare_output_before_write_samples(&iodev);
+
+  // Device should start ramping up without setting mute callback.
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(1, cras_ramp_start_is_called);
+  EXPECT_EQ(1, cras_ramp_start_is_up);
+  EXPECT_EQ(fmt.frame_rate * RAMP_NEW_STREAM_DURATION_SECS,
+            cras_ramp_start_duration_frames);
+  EXPECT_EQ(NULL, cras_ramp_start_cb);
+  EXPECT_EQ(NULL, cras_ramp_start_cb_data);
+
+  ResetStubData();
+
+  // Case 5.2: Assume device with ramp is in open state. But system is muted.
+  iodev.state = CRAS_IODEV_STATE_OPEN;
+  // Assume system is muted.
+  cras_system_get_mute_return = 1;
+  // Assume sample is ready.
+  dev_stream_playback_frames_ret = 100;
+
+  rc = cras_iodev_prepare_output_before_write_samples(&iodev);
+
+  // Device should not start ramping up because system is muted.
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(0, cras_ramp_start_is_called);
+}
+
+TEST(IoDev, StartRampUp) {
+  struct cras_iodev iodev;
+  int rc;
+  struct cras_audio_format fmt;
+  enum CRAS_IODEV_RAMP_REQUEST req;
+  memset(&iodev, 0, sizeof(iodev));
+
+  // Format will be used in cras_iodev_start_ramp to determine ramp duration.
+  fmt.format = SND_PCM_FORMAT_S16_LE;
+  fmt.frame_rate = 48000;
+  fmt.num_channels = 2;
+  iodev.format = &fmt;
+
+  // Assume device has ramp member.
+  iodev.ramp = reinterpret_cast<struct cras_ramp*>(0x1);
+
+  // Case 1: Device is not opened yet.
+  ResetStubData();
+  iodev.state = CRAS_IODEV_STATE_CLOSE;
+  req = CRAS_IODEV_RAMP_REQUEST_UP_UNMUTE;
+
+  rc = cras_iodev_start_ramp(&iodev, req);
+
+  // Ramp request is ignored.
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(0, cras_ramp_start_is_called);
+
+  // Case 2: Ramp up without mute.
+  ResetStubData();
+  iodev.state = CRAS_IODEV_STATE_OPEN;
+  req = CRAS_IODEV_RAMP_REQUEST_UP_START_PLAYBACK;
+
+  rc = cras_iodev_start_ramp(&iodev, req);
+
+  // Device should start ramping up without setting mute callback.
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(1, cras_ramp_start_is_called);
+  EXPECT_EQ(1, cras_ramp_start_is_up);
+  EXPECT_EQ(fmt.frame_rate * RAMP_NEW_STREAM_DURATION_SECS,
+            cras_ramp_start_duration_frames);
+  EXPECT_EQ(NULL, cras_ramp_start_cb);
+  EXPECT_EQ(NULL, cras_ramp_start_cb_data);
+
+  // Case 3: Ramp up for unmute.
+  ResetStubData();
+  iodev.state = CRAS_IODEV_STATE_OPEN;
+  req = CRAS_IODEV_RAMP_REQUEST_UP_UNMUTE;
+
+  rc = cras_iodev_start_ramp(&iodev, req);
+
+  // Device should start ramping up.
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(1, cras_ramp_start_is_called);
+  EXPECT_EQ(1, cras_ramp_start_is_up);
+  EXPECT_EQ(fmt.frame_rate * RAMP_UNMUTE_DURATION_SECS,
+            cras_ramp_start_duration_frames);
+  // Callback for unmute is not used.
+  EXPECT_EQ(NULL, cras_ramp_start_cb);
+  // Device mute state is set after ramping starts.
+  EXPECT_EQ(1, cras_device_monitor_set_device_mute_state_called);
+  EXPECT_EQ(&iodev, cras_device_monitor_set_device_mute_state_dev);
+}
+
+TEST(IoDev, StartRampDown) {
+  struct cras_iodev iodev;
+  int rc;
+  struct cras_audio_format fmt;
+  enum CRAS_IODEV_RAMP_REQUEST req;
+  memset(&iodev, 0, sizeof(iodev));
+
+  // Format will be used in cras_iodev_start_ramp to determine ramp duration.
+  fmt.format = SND_PCM_FORMAT_S16_LE;
+  fmt.frame_rate = 48000;
+  fmt.num_channels = 2;
+  iodev.format = &fmt;
+
+  // Assume device has ramp member.
+  iodev.ramp = reinterpret_cast<struct cras_ramp*>(0x1);
+
+  // Case 1: Device is not opened yet.
+  ResetStubData();
+  iodev.state = CRAS_IODEV_STATE_CLOSE;
+  req = CRAS_IODEV_RAMP_REQUEST_DOWN_MUTE;
+
+  rc = cras_iodev_start_ramp(&iodev, req);
+
+  // Ramp request is ignored.
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(0, cras_ramp_start_is_called);
+
+  // Case 2: Ramp down for mute.
+  ResetStubData();
+  iodev.state = CRAS_IODEV_STATE_OPEN;
+  req = CRAS_IODEV_RAMP_REQUEST_DOWN_MUTE;
+
+  rc = cras_iodev_start_ramp(&iodev, req);
+
+  // Device should start ramping down with mute callback.
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(1, cras_ramp_start_is_called);
+  EXPECT_EQ(0, cras_ramp_start_is_up);
+  EXPECT_EQ(fmt.frame_rate * RAMP_MUTE_DURATION_SECS,
+            cras_ramp_start_duration_frames);
+
+  // Device mute state is not set yet. It should wait for ramp to finish.
+  EXPECT_EQ(0, cras_device_monitor_set_device_mute_state_called);
+  EXPECT_EQ(NULL, cras_device_monitor_set_device_mute_state_dev);
+
+  // Assume the callback is set, and it is later called after ramp is done.
+  // It should trigger cras_device_monitor_set_device_mute_state.
+  cras_ramp_start_cb(cras_ramp_start_cb_data);
+  EXPECT_EQ(1, cras_device_monitor_set_device_mute_state_called);
+  EXPECT_EQ(&iodev, cras_device_monitor_set_device_mute_state_dev);
+}
+
+TEST(IoDev, OutputDeviceShouldWake) {
+  struct cras_iodev iodev;
+  int rc;
+
+  memset(&iodev, 0, sizeof(iodev));
+
+  ResetStubData();
+
+  // Device is not running. No need to wake for this device.
+  iodev.state = CRAS_IODEV_STATE_OPEN;
+  rc = cras_iodev_odev_should_wake(&iodev);
+  EXPECT_EQ(0, rc);
+
+  // Device is running. Need to wake for this device.
+  iodev.state = CRAS_IODEV_STATE_NORMAL_RUN;
+  rc = cras_iodev_odev_should_wake(&iodev);
+  EXPECT_EQ(1, rc);
+
+  // Device is running. Device has output_should_wake ops.
+  iodev.output_should_wake = output_should_wake;
+  output_should_wake_ret = 0;
+  rc = cras_iodev_odev_should_wake(&iodev);
+  EXPECT_EQ(0, rc);
+
+  // Device is running. Device has output_should_wake ops.
+  output_should_wake_ret = 1;
+  rc = cras_iodev_odev_should_wake(&iodev);
+  EXPECT_EQ(1, rc);
+
+  // Ignore input device.
+  iodev.direction = CRAS_STREAM_INPUT;
+  rc = cras_iodev_odev_should_wake(&iodev);
+  EXPECT_EQ(0, rc);
+}
+
+TEST(IoDev, FramesToPlayInSleep) {
+  struct cras_iodev iodev;
+  unsigned int min_cb_level = 240, hw_level;
+  unsigned int got_hw_level, got_frames;
+  struct timespec hw_tstamp;
+
+  memset(&iodev, 0, sizeof(iodev));
+  iodev.frames_queued = frames_queued;
+  iodev.min_buffer_level = 0;
+  iodev.direction = CRAS_STREAM_OUTPUT;
+  iodev.buffer_size = BUFFER_SIZE;
+  iodev.min_cb_level = min_cb_level;
+
+  ResetStubData();
+
+  // Device is running. There is at least one stream for this device.
+  // hw_level is greater than min_cb_level.
+  iodev.state = CRAS_IODEV_STATE_NORMAL_RUN;
+  hw_level = min_cb_level + 50;
+  fr_queued = hw_level;
+  iodev.streams = reinterpret_cast<struct dev_stream *>(0x1);
+
+  got_frames = cras_iodev_frames_to_play_in_sleep(
+                   &iodev, &got_hw_level, &hw_tstamp);
+  EXPECT_EQ(hw_level, got_hw_level);
+  EXPECT_EQ(hw_level, got_frames);
+
+  // Device is running. There is no stream for this device.
+  // hw_level is greater than min_cb_level.
+  iodev.streams = NULL;
+
+  got_frames = cras_iodev_frames_to_play_in_sleep(
+                   &iodev, &got_hw_level, &hw_tstamp);
+  EXPECT_EQ(hw_level, got_hw_level);
+  EXPECT_EQ(hw_level - min_cb_level, got_frames);
+
+  // Device is running. There is no stream for this device.
+  // hw_level is less than min_cb_level.
+  iodev.streams = NULL;
+  hw_level = min_cb_level - 50;
+  fr_queued = hw_level;
+
+  got_frames = cras_iodev_frames_to_play_in_sleep(
+                   &iodev, &got_hw_level, &hw_tstamp);
+  EXPECT_EQ(hw_level, got_hw_level);
+  EXPECT_EQ(0, got_frames);
+}
+
+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;
+  EXPECT_EQ(10, cras_iodev_get_num_underruns(&iodev));
+}
+
+TEST(IoDev, RequestReset) {
+  struct cras_iodev iodev;
+  memset(&iodev, 0, sizeof(iodev));
+
+  ResetStubData();
+
+  iodev.open_dev = open_dev;
+  iodev.direction = CRAS_STREAM_OUTPUT;
+
+  iodev.state = CRAS_IODEV_STATE_CLOSE;
+  iodev_buffer_size = 1024;
+
+  // Open device.
+  cras_iodev_open(&iodev, 240);
+
+  // The first reset request works.
+  EXPECT_EQ(0, cras_iodev_reset_request(&iodev));
+  EXPECT_EQ(1, device_monitor_reset_device_called);
+
+  // The second reset request will do nothing.
+  EXPECT_EQ(0, cras_iodev_reset_request(&iodev));
+  EXPECT_EQ(1, device_monitor_reset_device_called);
+
+  // Assume device is opened again.
+  cras_iodev_open(&iodev, 240);
+
+  // The reset request works.
+  EXPECT_EQ(0, cras_iodev_reset_request(&iodev));
+  EXPECT_EQ(2, device_monitor_reset_device_called);
+}
+
+static int output_underrun(struct cras_iodev *iodev) {
+  output_underrun_called++;
+  return 0;
+}
+
+
+TEST(IoDev, HandleOutputUnderrun) {
+  struct cras_iodev iodev;
+  struct cras_audio_format fmt;
+  unsigned int frames = 240;
+  int16_t *zeros;
+  int rc;
+
+  ResetStubData();
+
+  memset(&iodev, 0, sizeof(iodev));
+  fmt.format = SND_PCM_FORMAT_S16_LE;
+  fmt.frame_rate = 48000;
+  fmt.num_channels = 2;
+  iodev.ext_format = &fmt;
+  iodev.get_buffer = get_buffer;
+  iodev.put_buffer = put_buffer;
+  iodev.direction = CRAS_STREAM_OUTPUT;
+  iodev.min_cb_level = frames;
+
+  // Default case, fill one block of zeros.
+  EXPECT_EQ(0, cras_iodev_output_underrun(&iodev));
+
+  EXPECT_EQ(frames, put_buffer_nframes);
+  zeros = (int16_t *)calloc(frames * 2, sizeof(*zeros));
+  rc = memcmp(audio_buffer, zeros, frames * 2 * 2);
+  free(zeros);
+  EXPECT_EQ(0, rc);
+
+  // Test iodev has output_underrun ops.
+  iodev.output_underrun = output_underrun;
+  EXPECT_EQ(0, cras_iodev_output_underrun(&iodev));
+  EXPECT_EQ(1, output_underrun_called);
+}
+
 extern "C" {
 
 //  From libpthread.
@@ -824,6 +1969,19 @@
   return 0;
 }
 
+// From audio_thread
+struct cras_fmt_conv *audio_thread_get_global_remix_converter()
+{
+  return NULL;
+}
+
+// Fromt fmt_conv
+void cras_channel_remix_convert(struct cras_fmt_conv *conv,
+    uint8_t *in_buf,
+    size_t frames)
+{
+}
+
 // From buffer_share
 struct buffer_share *buffer_share_create(unsigned int buf_sz) {
   return NULL;
@@ -881,8 +2039,14 @@
 {
 }
 
-void cras_dsp_set_variable(struct cras_dsp_context *ctx, const char *key,
-                           const char *value)
+void cras_dsp_set_variable_string(struct cras_dsp_context *ctx, const char *key,
+                                  const char *value)
+{
+}
+
+void cras_dsp_set_variable_boolean(struct cras_dsp_context *ctx,
+                                   const char *key,
+                                   char value)
 {
 }
 
@@ -968,7 +2132,8 @@
   notify_nodes_changed_called++;
 }
 
-void cras_iodev_list_notify_active_node_changed()
+void cras_iodev_list_notify_active_node_changed(
+				enum CRAS_STREAM_DIRECTION direction)
 {
   notify_active_node_changed_called++;
 }
@@ -1018,6 +2183,10 @@
   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;
 }
@@ -1028,10 +2197,23 @@
 
 void cras_scale_buffer(snd_pcm_format_t fmt, uint8_t *buffer,
                        unsigned int count, float scaler) {
+  cras_scale_buffer_called++;
   cras_scale_buffer_fmt = fmt;
   cras_scale_buffer_scaler = scaler;
 }
 
+void cras_scale_buffer_increment(snd_pcm_format_t fmt, uint8_t *buff,
+                                 unsigned int frame, float scaler,
+                                 float increment, int channel)
+{
+  cras_scale_buffer_increment_fmt = fmt;
+  cras_scale_buffer_increment_buff = buff;
+  cras_scale_buffer_increment_frame = frame;
+  cras_scale_buffer_increment_scaler = scaler;
+  cras_scale_buffer_increment_increment = increment;
+  cras_scale_buffer_increment_channel = channel;
+}
+
 size_t cras_mix_mute_buffer(uint8_t *dst,
                             size_t frame_bytes,
                             size_t count) {
@@ -1071,10 +2253,67 @@
   return 0;
 }
 
+int dev_stream_attached_devs(const struct dev_stream *dev_stream) {
+  return 1;
+}
+
+void dev_stream_update_frames(const struct dev_stream *dev_stream) {
+}
+
+int dev_stream_playback_frames(const struct dev_stream *dev_stream) {
+  return dev_stream_playback_frames_ret;
+}
+
+int cras_device_monitor_reset_device(struct cras_iodev *iodev) {
+  device_monitor_reset_device_called++;
+  return 0;
+}
+
+void cras_ramp_destroy(struct cras_ramp* ramp) {
+  return;
+}
+
+int cras_ramp_start(struct cras_ramp *ramp, int is_up, int duration_frames,
+                    cras_ramp_cb cb, void *cb_data)
+{
+  cras_ramp_start_is_called++;
+  cras_ramp_start_is_up = is_up;
+  cras_ramp_start_duration_frames = duration_frames;
+  cras_ramp_start_cb = cb;
+  cras_ramp_start_cb_data = cb_data;
+  return 0;
+}
+
+int cras_ramp_reset(struct cras_ramp *ramp) {
+  cras_ramp_reset_is_called++;
+  return 0;
+}
+
+struct cras_ramp_action cras_ramp_get_current_action(
+    const struct cras_ramp *ramp) {
+  return cras_ramp_get_current_action_ret;
+}
+
+int cras_ramp_update_ramped_frames(
+    struct cras_ramp *ramp, int num_frames) {
+  cras_ramp_update_ramped_frames_num_frames = num_frames;
+  return 0;
+}
+
+int cras_device_monitor_set_device_mute_state(struct cras_iodev *iodev)
+{
+  cras_device_monitor_set_device_mute_state_called++;
+  cras_device_monitor_set_device_mute_state_dev = iodev;
+  return 0;
+}
+
 }  // extern "C"
 }  //  namespace
 
 int main(int argc, char **argv) {
   ::testing::InitGoogleTest(&argc, argv);
-  return RUN_ALL_TESTS();
+  int rc = RUN_ALL_TESTS();
+
+  audio_thread_event_log_deinit(atlog);
+  return rc;
 }
diff --git a/cras/src/tests/linear_resampler_unittest.cc b/cras/src/tests/linear_resampler_unittest.cc
index 6fb724e..e299648 100644
--- a/cras/src/tests/linear_resampler_unittest.cc
+++ b/cras/src/tests/linear_resampler_unittest.cc
@@ -192,15 +192,15 @@
 
 extern "C" {
 
-void cras_mix_add_stride(int fmt, uint8_t *dst, uint8_t *src,
+void cras_mix_add_scale_stride(int fmt, uint8_t *dst, uint8_t *src,
 			 unsigned int count, unsigned int dst_stride,
-			 unsigned int src_stride)
+			 unsigned int src_stride, float scaler)
 {
 	unsigned int i;
 
 	for (i = 0; i < count; i++) {
 		int32_t sum;
-		sum = *(int16_t *)dst + *(int16_t *)src;
+		sum = *(int16_t *)dst + *(int16_t *)src * scaler;
 		if (sum > INT16_MAX)
 			sum = INT16_MAX;
 		else if (sum < INT16_MIN)
diff --git a/cras/src/tests/loopback_iodev_unittest.cc b/cras/src/tests/loopback_iodev_unittest.cc
index 02463a3..b0387bf 100644
--- a/cras/src/tests/loopback_iodev_unittest.cc
+++ b/cras/src/tests/loopback_iodev_unittest.cc
@@ -68,6 +68,7 @@
 
 TEST_F(LoopBackTestSuite, InstallLoopHook) {
   struct cras_iodev iodev;
+  struct timespec tstamp;
 
   iodev.direction = CRAS_STREAM_OUTPUT;
   iodev.format = &fmt_;
@@ -85,24 +86,19 @@
   // Expect that a hook was added to the iodev
   ASSERT_NE(reinterpret_cast<loopback_hook_t>(NULL), loop_hook);
 
-  // Check device open status.
-  EXPECT_EQ(1, loop_in_->is_open(loop_in_));
-
   // Check zero frames queued.
-  EXPECT_EQ(0, loop_in_->frames_queued(loop_in_));
+  EXPECT_EQ(0, loop_in_->frames_queued(loop_in_, &tstamp));
 
   // Close loopback devices.
   EXPECT_EQ(0, loop_in_->close_dev(loop_in_));
   EXPECT_EQ(reinterpret_cast<loopback_hook_t>(NULL), loop_hook);
-
-  // Check device open status.
-  EXPECT_EQ(0, loop_in_->is_open(loop_in_));
 }
 
 // Test how loopback works if there isn't any output devices open.
 TEST_F(LoopBackTestSuite, OpenIdleSystem) {
   cras_audio_area *area;
   unsigned int nread = 1024;
+  struct timespec tstamp;
   int rc;
 
   // No active output device.
@@ -115,7 +111,7 @@
 
   // Should be 480 samples after 480/frame rate seconds
   time_now.tv_nsec += 480 * 1e9 / 48000;
-  EXPECT_EQ(480, loop_in_->frames_queued(loop_in_));
+  EXPECT_EQ(480, loop_in_->frames_queued(loop_in_, &tstamp));
 
   // Verify frames from loopback record.
   loop_in_->get_buffer(loop_in_, &area, &nread);
@@ -126,7 +122,7 @@
   loop_in_->put_buffer(loop_in_, nread);
 
   // Check zero frames queued.
-  EXPECT_EQ(0, loop_in_->frames_queued(loop_in_));
+  EXPECT_EQ(0, loop_in_->frames_queued(loop_in_, &tstamp));
 
   EXPECT_EQ(0, loop_in_->close_dev(loop_in_));
 }
@@ -138,6 +134,7 @@
   int rc;
   struct cras_iodev iodev;
   struct dev_stream stream;
+  struct timespec tstamp;
 
   iodev.streams = &stream;
   enabled_dev = &iodev;
@@ -156,7 +153,7 @@
   loop_in_->put_buffer(loop_in_, nread);
 
   // Check zero frames queued.
-  EXPECT_EQ(0, loop_in_->frames_queued(loop_in_));
+  EXPECT_EQ(0, loop_in_->frames_queued(loop_in_, &tstamp));
 
   EXPECT_EQ(0, loop_in_->close_dev(loop_in_));
 }
diff --git a/cras/src/tests/mix_unittest.cc b/cras/src/tests/mix_unittest.cc
index 401619a..df4c5b4 100644
--- a/cras/src/tests/mix_unittest.cc
+++ b/cras/src/tests/mix_unittest.cc
@@ -18,6 +18,11 @@
 static const size_t kNumChannels = 2;
 static const size_t kNumSamples = kBufferFrames * kNumChannels;
 
+
+static inline int need_to_scale(float scaler) {
+	return (scaler < 0.99 || scaler > 1.01);
+}
+
 class MixTestSuiteS16_LE : public testing::Test{
   protected:
     virtual void SetUp() {
@@ -40,6 +45,55 @@
       free(src_buffer_);
     }
 
+    void _SetupBuffer() {
+      for (size_t i = 0; i < kBufferFrames; i++) {
+        src_buffer_[i] = i + (INT16_MAX >> 2);
+        mix_buffer_[i] = i + (INT16_MAX >> 2);
+        compare_buffer_[i] = mix_buffer_[i];
+      }
+      for (size_t i = kBufferFrames; i < kBufferFrames * 2; i++) {
+        src_buffer_[i] = i - (INT16_MAX >> 2);
+        mix_buffer_[i] = i - (INT16_MAX >> 2);
+        compare_buffer_[i] = mix_buffer_[i];
+      }
+    }
+
+    void TestScaleStride(float scaler) {
+      _SetupBuffer();
+      for (size_t i = 0; i < kBufferFrames * 2; i += 2) {
+        int32_t tmp;
+        if (need_to_scale(scaler))
+          tmp = mix_buffer_[i] + src_buffer_[i/2] * scaler;
+        else
+          tmp = mix_buffer_[i] + src_buffer_[i/2];
+        if (tmp > INT16_MAX)
+          tmp = INT16_MAX;
+        else if (tmp < INT16_MIN)
+          tmp = INT16_MIN;
+        compare_buffer_[i] = tmp;
+      }
+
+      cras_mix_add_scale_stride(
+          fmt_, (uint8_t *)mix_buffer_, (uint8_t *)src_buffer_,
+          kBufferFrames, 4, 2, scaler);
+
+      EXPECT_EQ(0, memcmp(compare_buffer_, mix_buffer_, kBufferFrames * 4));
+    }
+
+    void ScaleIncrement(float start_scaler, float increment) {
+      float scaler = start_scaler;
+      for (size_t i = 0; i < kBufferFrames * 2; i++) {
+        if (scaler > 0.9999999) {
+        } else if (scaler < 0.0000001) {
+          compare_buffer_[i] = 0;
+        } else {
+          compare_buffer_[i] = mix_buffer_[i] * scaler;
+        }
+        if (i % 2 == 1)
+          scaler += increment;
+      }
+    }
+
   int16_t *mix_buffer_;
   int16_t *src_buffer_;
   int16_t *compare_buffer_;
@@ -114,6 +168,89 @@
   EXPECT_EQ(0, memcmp(mix_buffer_, compare_buffer_, kBufferFrames*4));
 }
 
+TEST_F(MixTestSuiteS16_LE, ScaleFullVolumeIncrement) {
+  float increment = 0.01;
+  int step = 2;
+  float start_scaler = 0.999999999;
+
+  _SetupBuffer();
+  // Scale full volume with positive increment will not change buffer.
+  memcpy(compare_buffer_, src_buffer_, kBufferFrames * 4);
+  cras_scale_buffer_increment(
+      fmt_, (uint8_t *)mix_buffer_, kBufferFrames, start_scaler, increment, step);
+
+  EXPECT_EQ(0, memcmp(compare_buffer_, mix_buffer_, kBufferFrames * 4));
+}
+
+TEST_F(MixTestSuiteS16_LE, ScaleMinVolumeIncrement) {
+  float increment = -0.01;
+  int step = 2;
+  float start_scaler = 0.000000001;
+
+  _SetupBuffer();
+  // Scale min volume with negative increment will change buffer to zeros.
+  memset(compare_buffer_, 0, kBufferFrames * 4);
+  cras_scale_buffer_increment(
+      fmt_, (uint8_t *)mix_buffer_, kBufferFrames, start_scaler, increment, step);
+
+  EXPECT_EQ(0, memcmp(compare_buffer_, mix_buffer_, kBufferFrames * 4));
+}
+
+TEST_F(MixTestSuiteS16_LE, ScaleVolumePositiveIncrement) {
+  float increment = 0.0001;
+  int step = 2;
+  float start_scaler = 0.1;
+
+  _SetupBuffer();
+  ScaleIncrement(start_scaler, increment);
+
+  cras_scale_buffer_increment(
+      fmt_, (uint8_t *)mix_buffer_, kBufferFrames, start_scaler, increment, step);
+  EXPECT_EQ(0, memcmp(compare_buffer_, mix_buffer_, kBufferFrames * 4));
+}
+
+TEST_F(MixTestSuiteS16_LE, ScaleVolumeNegativeIncrement) {
+  float increment = -0.0001;
+  int step = 2;
+  float start_scaler = 0.8;
+
+  _SetupBuffer();
+  ScaleIncrement(start_scaler, increment);
+
+  cras_scale_buffer_increment(
+      fmt_, (uint8_t *)mix_buffer_, kBufferFrames, start_scaler, increment, step);
+
+  EXPECT_EQ(0, memcmp(compare_buffer_, mix_buffer_, kBufferFrames * 4));
+}
+
+TEST_F(MixTestSuiteS16_LE, ScaleVolumeStartFullNegativeIncrement) {
+  float increment = -0.0001;
+  int step = 2;
+  float start_scaler = 1.0;
+
+  _SetupBuffer();
+  ScaleIncrement(start_scaler, increment);
+
+  cras_scale_buffer_increment(
+      fmt_, (uint8_t *)mix_buffer_, kBufferFrames, start_scaler, increment, step);
+
+  EXPECT_EQ(0, memcmp(compare_buffer_, mix_buffer_, kBufferFrames * 4));
+}
+
+TEST_F(MixTestSuiteS16_LE, ScaleVolumeStartZeroPositiveIncrement) {
+  float increment = 0.0001;
+  int step = 2;
+  float start_scaler = 0.0;
+
+  _SetupBuffer();
+  ScaleIncrement(start_scaler, increment);
+
+  cras_scale_buffer_increment(
+      fmt_, (uint8_t *)mix_buffer_, kBufferFrames, start_scaler, increment, step);
+
+  EXPECT_EQ(0, memcmp(compare_buffer_, mix_buffer_, kBufferFrames * 4));
+}
+
 TEST_F(MixTestSuiteS16_LE, ScaleFullVolume) {
   memcpy(compare_buffer_, src_buffer_, kBufferFrames * 4);
   cras_scale_buffer(fmt_, (uint8_t *)mix_buffer_, kNumSamples, 0.999999999);
@@ -136,16 +273,12 @@
   EXPECT_EQ(0, memcmp(compare_buffer_, src_buffer_, kBufferFrames * 4));
 }
 
-TEST_F(MixTestSuiteS16_LE, StrideCopy) {
-  for (size_t i = 0; i < kBufferFrames * 2; i += 2)
-    compare_buffer_[i] = src_buffer_[i/2];
-  for (size_t i = 1; i < kBufferFrames * 2; i += 2)
-    compare_buffer_[i] = 0;
-  memset(mix_buffer_, 0, kBufferFrames * 4);
-  cras_mix_add_stride(fmt_, (uint8_t *)mix_buffer_, (uint8_t *)src_buffer_,
-                      kBufferFrames, 4, 2);
 
-  EXPECT_EQ(0, memcmp(compare_buffer_, mix_buffer_, kBufferFrames * 4));
+
+TEST_F(MixTestSuiteS16_LE, StrideCopy) {
+  TestScaleStride(1.0);
+  TestScaleStride(100);
+  TestScaleStride(0.5);
 }
 
 class MixTestSuiteS24_LE : public testing::Test{
@@ -171,6 +304,55 @@
       free(src_buffer_);
     }
 
+    void _SetupBuffer() {
+      for (size_t i = 0; i < kBufferFrames; i++) {
+        src_buffer_[i] = i + (0x007fffff >> 2);
+        mix_buffer_[i] = i + (0x007fffff  >> 2);
+        compare_buffer_[i] = mix_buffer_[i];
+      }
+      for (size_t i = kBufferFrames; i < kBufferFrames * 2; i++) {
+        src_buffer_[i] = i - (0x007fffff >> 2);
+        mix_buffer_[i] = i - (0x007fffff >> 2);
+        compare_buffer_[i] = mix_buffer_[i];
+      }
+    }
+
+    void TestScaleStride(float scaler) {
+      _SetupBuffer();
+      for (size_t i = 0; i < kBufferFrames * 2; i += 2) {
+        int32_t tmp;
+        if (need_to_scale(scaler))
+          tmp = mix_buffer_[i] + src_buffer_[i/2] * scaler;
+        else
+          tmp = mix_buffer_[i] + src_buffer_[i/2];
+        if (tmp > 0x007fffff)
+          tmp = 0x007fffff;
+        else if (tmp < (int32_t)0xff800000)
+          tmp = (int32_t)0xff800000;
+        compare_buffer_[i] = tmp;
+      }
+
+      cras_mix_add_scale_stride(
+          fmt_, (uint8_t *)mix_buffer_, (uint8_t *)src_buffer_,
+          kBufferFrames, 8, 4, scaler);
+
+      EXPECT_EQ(0, memcmp(compare_buffer_, mix_buffer_, kBufferFrames * 8));
+    }
+
+    void ScaleIncrement(float start_scaler, float increment) {
+      float scaler = start_scaler;
+      for (size_t i = 0; i < kBufferFrames * 2; i++) {
+        if (scaler > 0.9999999) {
+        } else if (scaler < 0.0000001) {
+          compare_buffer_[i] = 0;
+        } else {
+          compare_buffer_[i] = mix_buffer_[i] * scaler;
+        }
+        if (i % 2 == 1)
+          scaler += increment;
+      }
+    }
+
   int32_t *mix_buffer_;
   int32_t *src_buffer_;
   int32_t *compare_buffer_;
@@ -246,6 +428,89 @@
   EXPECT_EQ(0, memcmp(mix_buffer_, compare_buffer_, kBufferFrames * fr_bytes_));
 }
 
+TEST_F(MixTestSuiteS24_LE, ScaleFullVolumeIncrement) {
+  float increment = 0.01;
+  int step = 2;
+  float start_scaler = 0.999999999;
+
+  _SetupBuffer();
+  // Scale full volume with positive increment will not change buffer.
+  memcpy(compare_buffer_, src_buffer_, kBufferFrames * fr_bytes_);
+  cras_scale_buffer_increment(
+      fmt_, (uint8_t *)mix_buffer_, kBufferFrames, start_scaler, increment, step);
+
+  EXPECT_EQ(0, memcmp(compare_buffer_, mix_buffer_, kBufferFrames * fr_bytes_));
+}
+
+TEST_F(MixTestSuiteS24_LE, ScaleMinVolumeIncrement) {
+  float increment = -0.01;
+  int step = 2;
+  float start_scaler = 0.000000001;
+
+  _SetupBuffer();
+  // Scale min volume with negative increment will change buffer to zeros.
+  memset(compare_buffer_, 0, kBufferFrames * fr_bytes_);
+  cras_scale_buffer_increment(
+      fmt_, (uint8_t *)mix_buffer_, kBufferFrames, start_scaler, increment, step);
+
+  EXPECT_EQ(0, memcmp(compare_buffer_, mix_buffer_, kBufferFrames * fr_bytes_));
+}
+
+TEST_F(MixTestSuiteS24_LE, ScaleVolumePositiveIncrement) {
+  float increment = 0.0001;
+  int step = 2;
+  float start_scaler = 0.1;
+
+  _SetupBuffer();
+  ScaleIncrement(start_scaler, increment);
+
+  cras_scale_buffer_increment(
+      fmt_, (uint8_t *)mix_buffer_, kBufferFrames, start_scaler, increment, step);
+  EXPECT_EQ(0, memcmp(compare_buffer_, mix_buffer_, kBufferFrames * fr_bytes_));
+}
+
+TEST_F(MixTestSuiteS24_LE, ScaleVolumeNegativeIncrement) {
+  float increment = -0.0001;
+  int step = 2;
+  float start_scaler = 0.8;
+
+  _SetupBuffer();
+  ScaleIncrement(start_scaler, increment);
+
+  cras_scale_buffer_increment(
+      fmt_, (uint8_t *)mix_buffer_, kBufferFrames, start_scaler, increment, step);
+
+  EXPECT_EQ(0, memcmp(compare_buffer_, mix_buffer_, kBufferFrames * fr_bytes_));
+}
+
+TEST_F(MixTestSuiteS24_LE, ScaleVolumeStartFullNegativeIncrement) {
+  float increment = -0.0001;
+  int step = 2;
+  float start_scaler = 1.0;
+
+  _SetupBuffer();
+  ScaleIncrement(start_scaler, increment);
+
+  cras_scale_buffer_increment(
+      fmt_, (uint8_t *)mix_buffer_, kBufferFrames, start_scaler, increment, step);
+
+  EXPECT_EQ(0, memcmp(compare_buffer_, mix_buffer_, kBufferFrames * 4));
+}
+
+TEST_F(MixTestSuiteS24_LE, ScaleVolumeStartZeroPositiveIncrement) {
+  float increment = 0.0001;
+  int step = 2;
+  float start_scaler = 0.0;
+
+  _SetupBuffer();
+  ScaleIncrement(start_scaler, increment);
+
+  cras_scale_buffer_increment(
+      fmt_, (uint8_t *)mix_buffer_, kBufferFrames, start_scaler, increment, step);
+
+  EXPECT_EQ(0, memcmp(compare_buffer_, mix_buffer_, kBufferFrames * 4));
+}
+
 TEST_F(MixTestSuiteS24_LE, ScaleFullVolume) {
   memcpy(compare_buffer_, src_buffer_, kBufferFrames  * fr_bytes_);
   cras_scale_buffer(fmt_, (uint8_t *)mix_buffer_, kNumSamples, 0.999999999);
@@ -269,15 +534,9 @@
 }
 
 TEST_F(MixTestSuiteS24_LE, StrideCopy) {
-  for (size_t i = 0; i < kBufferFrames * 2; i += 2)
-    compare_buffer_[i] = src_buffer_[i/2];
-  for (size_t i = 1; i < kBufferFrames * 2; i += 2)
-    compare_buffer_[i] = 0;
-  memset(mix_buffer_, 0, kBufferFrames * 8);
-  cras_mix_add_stride(fmt_, (uint8_t *)mix_buffer_, (uint8_t *)src_buffer_,
-                      kBufferFrames, 8, 4);
-
-  EXPECT_EQ(0, memcmp(compare_buffer_, mix_buffer_, kBufferFrames * 8));
+  TestScaleStride(1.0);
+  TestScaleStride(100);
+  TestScaleStride(0.1);
 }
 
 class MixTestSuiteS32_LE : public testing::Test{
@@ -303,6 +562,55 @@
       free(src_buffer_);
     }
 
+    void _SetupBuffer() {
+      for (size_t i = 0; i < kBufferFrames; i++) {
+        src_buffer_[i] = i + (INT32_MAX >> 2);
+        mix_buffer_[i] = i + (INT32_MAX >> 2);
+        compare_buffer_[i] = mix_buffer_[i];
+      }
+      for (size_t i = kBufferFrames; i < kBufferFrames * 2; i++) {
+        src_buffer_[i] = i - (INT32_MAX >> 2);
+        mix_buffer_[i] = i - (INT32_MAX >> 2);
+        compare_buffer_[i] = mix_buffer_[i];
+      }
+    }
+
+    void TestScaleStride(float scaler) {
+      _SetupBuffer();
+      for (size_t i = 0; i < kBufferFrames * 2; i += 2) {
+        int64_t tmp;
+        if (need_to_scale(scaler))
+          tmp = mix_buffer_[i] + src_buffer_[i/2] * scaler;
+        else
+          tmp = mix_buffer_[i] + src_buffer_[i/2];
+        if (tmp > INT32_MAX)
+          tmp = INT32_MAX;
+        else if (tmp < INT32_MIN)
+          tmp = INT32_MIN;
+        compare_buffer_[i] = tmp;
+      }
+
+      cras_mix_add_scale_stride(
+          fmt_, (uint8_t *)mix_buffer_, (uint8_t *)src_buffer_,
+          kBufferFrames, 8, 4, scaler);
+
+      EXPECT_EQ(0, memcmp(compare_buffer_, mix_buffer_, kBufferFrames * 8));
+    }
+
+    void ScaleIncrement(float start_scaler, float increment) {
+      float scaler = start_scaler;
+      for (size_t i = 0; i < kBufferFrames * 2; i++) {
+        if (scaler > 0.9999999) {
+        } else if (scaler < 0.0000001) {
+          compare_buffer_[i] = 0;
+        } else {
+          compare_buffer_[i] = mix_buffer_[i] * scaler;
+        }
+        if (i % 2 == 1)
+          scaler += increment;
+      }
+    }
+
   int32_t *mix_buffer_;
   int32_t *src_buffer_;
   int32_t *compare_buffer_;
@@ -378,6 +686,89 @@
   EXPECT_EQ(0, memcmp(mix_buffer_, compare_buffer_, kBufferFrames * fr_bytes_));
 }
 
+TEST_F(MixTestSuiteS32_LE, ScaleFullVolumeIncrement) {
+  float increment = 0.01;
+  int step = 2;
+  float start_scaler = 0.999999999;
+
+  _SetupBuffer();
+  // Scale full volume with positive increment will not change buffer.
+  memcpy(compare_buffer_, src_buffer_, kBufferFrames * fr_bytes_);
+  cras_scale_buffer_increment(
+      fmt_, (uint8_t *)mix_buffer_, kBufferFrames, start_scaler, increment, step);
+
+  EXPECT_EQ(0, memcmp(compare_buffer_, mix_buffer_, kBufferFrames * fr_bytes_));
+}
+
+TEST_F(MixTestSuiteS32_LE, ScaleMinVolumeIncrement) {
+  float increment = -0.01;
+  int step = 2;
+  float start_scaler = 0.000000001;
+
+  _SetupBuffer();
+  // Scale min volume with negative increment will change buffer to zeros.
+  memset(compare_buffer_, 0, kBufferFrames * fr_bytes_);
+  cras_scale_buffer_increment(
+      fmt_, (uint8_t *)mix_buffer_, kBufferFrames, start_scaler, increment, step);
+
+  EXPECT_EQ(0, memcmp(compare_buffer_, mix_buffer_, kBufferFrames * fr_bytes_));
+}
+
+TEST_F(MixTestSuiteS32_LE, ScaleVolumePositiveIncrement) {
+  float increment = 0.0001;
+  int step = 2;
+  float start_scaler = 0.1;
+
+  _SetupBuffer();
+  ScaleIncrement(start_scaler, increment);
+
+  cras_scale_buffer_increment(
+      fmt_, (uint8_t *)mix_buffer_, kBufferFrames, start_scaler, increment, step);
+  EXPECT_EQ(0, memcmp(compare_buffer_, mix_buffer_, kBufferFrames * fr_bytes_));
+}
+
+TEST_F(MixTestSuiteS32_LE, ScaleVolumeNegativeIncrement) {
+  float increment = -0.0001;
+  int step = 2;
+  float start_scaler = 0.8;
+
+  _SetupBuffer();
+  ScaleIncrement(start_scaler, increment);
+
+  cras_scale_buffer_increment(
+      fmt_, (uint8_t *)mix_buffer_, kBufferFrames, start_scaler, increment, step);
+
+  EXPECT_EQ(0, memcmp(compare_buffer_, mix_buffer_, kBufferFrames * fr_bytes_));
+}
+
+TEST_F(MixTestSuiteS32_LE, ScaleVolumeStartFullNegativeIncrement) {
+  float increment = -0.0001;
+  int step = 2;
+  float start_scaler = 1.0;
+
+  _SetupBuffer();
+  ScaleIncrement(start_scaler, increment);
+
+  cras_scale_buffer_increment(
+      fmt_, (uint8_t *)mix_buffer_, kBufferFrames, start_scaler, increment, step);
+
+  EXPECT_EQ(0, memcmp(compare_buffer_, mix_buffer_, kBufferFrames * 4));
+}
+
+TEST_F(MixTestSuiteS32_LE, ScaleVolumeStartZeroPositiveIncrement) {
+  float increment = 0.0001;
+  int step = 2;
+  float start_scaler = 0.0;
+
+  _SetupBuffer();
+  ScaleIncrement(start_scaler, increment);
+
+  cras_scale_buffer_increment(
+      fmt_, (uint8_t *)mix_buffer_, kBufferFrames, start_scaler, increment, step);
+
+  EXPECT_EQ(0, memcmp(compare_buffer_, mix_buffer_, kBufferFrames * 4));
+}
+
 TEST_F(MixTestSuiteS32_LE, ScaleFullVolume) {
   memcpy(compare_buffer_, src_buffer_, kBufferFrames  * fr_bytes_);
   cras_scale_buffer(fmt_, (uint8_t *)mix_buffer_, kNumSamples, 0.999999999);
@@ -401,15 +792,9 @@
 }
 
 TEST_F(MixTestSuiteS32_LE, StrideCopy) {
-  for (size_t i = 0; i < kBufferFrames * 2; i += 2)
-    compare_buffer_[i] = src_buffer_[i/2];
-  for (size_t i = 1; i < kBufferFrames * 2; i += 2)
-    compare_buffer_[i] = 0;
-  memset(mix_buffer_, 0, kBufferFrames * 8);
-  cras_mix_add_stride(fmt_, (uint8_t *)mix_buffer_, (uint8_t *)src_buffer_,
-                      kBufferFrames, 8, 4);
-
-  EXPECT_EQ(0, memcmp(compare_buffer_, mix_buffer_, kBufferFrames * 8));
+  TestScaleStride(1.0);
+  TestScaleStride(100);
+  TestScaleStride(0.1);
 }
 
 class MixTestSuiteS24_3LE : public testing::Test{
@@ -436,6 +821,69 @@
       free(src_buffer_);
     }
 
+    void _SetupBuffer() {
+      memset(compare_buffer_, 0, kBufferFrames * fr_bytes_);
+      for (size_t i = 0; i < kBufferFrames; i++) {
+        int32_t tmp = (i << 8) + (INT32_MAX >> 2);
+        memcpy(src_buffer_ + 3*i, (uint8_t *)&tmp + 1, 3);
+        memcpy(mix_buffer_ + 3*i, (uint8_t *)&tmp + 1, 3);
+        memcpy(compare_buffer_ + 3*i, (uint8_t *)&tmp + 1, 3);
+      }
+      for (size_t i = kBufferFrames; i < kBufferFrames * 2; i++) {
+        int32_t tmp = (i << 8) - (INT32_MAX >> 2);
+        memcpy(src_buffer_ + 3*i, (uint8_t *)&tmp + 1, 3);
+        memcpy(mix_buffer_ + 3*i, (uint8_t *)&tmp + 1, 3);
+        memcpy(compare_buffer_ + 3*i, (uint8_t *)&tmp + 1, 3);
+      }
+    }
+
+    void TestScaleStride(float scaler) {
+      _SetupBuffer();
+      for (size_t i = 0; i < kBufferFrames * kNumChannels; i += 2) {
+        int64_t tmp;
+        int32_t src_frame = 0;
+        int32_t dst_frame = 0;
+        memcpy((uint8_t *)&src_frame + 1, src_buffer_ + 3*i/2, 3);
+        memcpy((uint8_t *)&dst_frame + 1, mix_buffer_ + 3*i, 3);
+        if (need_to_scale(scaler))
+          tmp = (int64_t)dst_frame + (int64_t)src_frame * scaler;
+        else
+          tmp = (int64_t)dst_frame + (int64_t)src_frame;
+        if (tmp > INT32_MAX)
+          tmp = INT32_MAX;
+        else if (tmp < INT32_MIN)
+          tmp = INT32_MIN;
+        dst_frame = (int32_t)tmp;
+        memcpy(compare_buffer_ + 3*i, (uint8_t *)&dst_frame + 1, 3);
+      }
+
+      cras_mix_add_scale_stride(
+          fmt_, (uint8_t *)mix_buffer_, (uint8_t *)src_buffer_,
+          kBufferFrames, 6, 3, scaler);
+
+      EXPECT_EQ(0, memcmp(compare_buffer_, mix_buffer_, kBufferFrames * 6));
+    }
+
+    void ScaleIncrement(float start_scaler, float increment) {
+      float scaler = start_scaler;
+      for (size_t i = 0; i < kBufferFrames * kNumChannels; i++) {
+        int32_t tmp = 0;
+        memcpy((uint8_t *)&tmp + 1, src_buffer_ + 3*i, 3);
+
+        if (scaler > 0.9999999) {
+	} else if (scaler < 0.0000001) {
+	  tmp = 0;
+        } else {
+          tmp *= scaler;
+	}
+
+        memcpy(compare_buffer_ + 3*i, (uint8_t *)&tmp + 1, 3);
+
+        if (i % 2 == 1)
+          scaler += increment;
+      }
+    }
+
   uint8_t *mix_buffer_;
   uint8_t *src_buffer_;
   uint8_t *compare_buffer_;
@@ -528,6 +976,89 @@
   EXPECT_EQ(0, memcmp(mix_buffer_, compare_buffer_, kBufferFrames * fr_bytes_));
 }
 
+TEST_F(MixTestSuiteS24_3LE, ScaleFullVolumeIncrement) {
+  float increment = 0.01;
+  int step = 2;
+  float start_scaler = 0.999999999;
+
+  _SetupBuffer();
+  // Scale full volume with positive increment will not change buffer.
+  memcpy(compare_buffer_, src_buffer_, kBufferFrames * fr_bytes_);
+  cras_scale_buffer_increment(
+      fmt_, (uint8_t *)mix_buffer_, kBufferFrames, start_scaler, increment, step);
+
+  EXPECT_EQ(0, memcmp(compare_buffer_, mix_buffer_, kBufferFrames * fr_bytes_));
+}
+
+TEST_F(MixTestSuiteS24_3LE, ScaleMinVolumeIncrement) {
+  float increment = -0.01;
+  int step = 2;
+  float start_scaler = 0.000000001;
+
+  _SetupBuffer();
+  // Scale min volume with negative increment will change buffer to zeros.
+  memset(compare_buffer_, 0, kBufferFrames * fr_bytes_);
+  cras_scale_buffer_increment(
+      fmt_, (uint8_t *)mix_buffer_, kBufferFrames, start_scaler, increment, step);
+
+  EXPECT_EQ(0, memcmp(compare_buffer_, mix_buffer_, kBufferFrames * fr_bytes_));
+}
+
+TEST_F(MixTestSuiteS24_3LE, ScaleVolumePositiveIncrement) {
+  float increment = 0.0001;
+  int step = 2;
+  float start_scaler = 0.1;
+
+  _SetupBuffer();
+  ScaleIncrement(start_scaler, increment);
+
+  cras_scale_buffer_increment(
+      fmt_, (uint8_t *)mix_buffer_, kBufferFrames, start_scaler, increment, step);
+  EXPECT_EQ(0, memcmp(compare_buffer_, mix_buffer_, kBufferFrames * fr_bytes_));
+}
+
+TEST_F(MixTestSuiteS24_3LE, ScaleVolumeNegativeIncrement) {
+  float increment = -0.0001;
+  int step = 2;
+  float start_scaler = 0.8;
+
+  _SetupBuffer();
+  ScaleIncrement(start_scaler, increment);
+
+  cras_scale_buffer_increment(
+      fmt_, (uint8_t *)mix_buffer_, kBufferFrames, start_scaler, increment, step);
+
+  EXPECT_EQ(0, memcmp(compare_buffer_, mix_buffer_, kBufferFrames * fr_bytes_));
+}
+
+TEST_F(MixTestSuiteS24_3LE, ScaleVolumeStartFullNegativeIncrement) {
+  float increment = -0.0001;
+  int step = 2;
+  float start_scaler = 1.0;
+
+  _SetupBuffer();
+  ScaleIncrement(start_scaler, increment);
+
+  cras_scale_buffer_increment(
+      fmt_, (uint8_t *)mix_buffer_, kBufferFrames, start_scaler, increment, step);
+
+  EXPECT_EQ(0, memcmp(compare_buffer_, mix_buffer_, kBufferFrames * 4));
+}
+
+TEST_F(MixTestSuiteS24_3LE, ScaleVolumeStartZeroPositiveIncrement) {
+  float increment = 0.0001;
+  int step = 2;
+  float start_scaler = 0.0;
+
+  _SetupBuffer();
+  ScaleIncrement(start_scaler, increment);
+
+  cras_scale_buffer_increment(
+      fmt_, (uint8_t *)mix_buffer_, kBufferFrames, start_scaler, increment, step);
+
+  EXPECT_EQ(0, memcmp(compare_buffer_, mix_buffer_, kBufferFrames * 4));
+}
+
 TEST_F(MixTestSuiteS24_3LE, ScaleFullVolume) {
   memcpy(compare_buffer_, src_buffer_, kBufferFrames  * fr_bytes_);
   cras_scale_buffer(fmt_, (uint8_t *)mix_buffer_, kNumSamples, 0.999999999);
@@ -555,15 +1086,9 @@
 }
 
 TEST_F(MixTestSuiteS24_3LE, StrideCopy) {
-  for (size_t i = 0; i < kBufferFrames * kNumChannels; i += 2)
-    memcpy(compare_buffer_ + 3*i, src_buffer_ + 3*i/2, 3);
-  for (size_t i = 1; i < kBufferFrames * kNumChannels; i += 2)
-    memset(compare_buffer_ + 3*i, 0, 3);
-  memset(mix_buffer_, 0, kBufferFrames * 6);
-  cras_mix_add_stride(fmt_, (uint8_t *)mix_buffer_, (uint8_t *)src_buffer_,
-                      kBufferFrames, 6, 3);
-
-  EXPECT_EQ(0, memcmp(compare_buffer_, mix_buffer_, kBufferFrames * 6));
+  TestScaleStride(1.0);
+  TestScaleStride(100);
+  TestScaleStride(0.1);
 }
 
 /* Stubs */
diff --git a/cras/src/tests/observer_unittest.cc b/cras/src/tests/observer_unittest.cc
new file mode 100644
index 0000000..d45a6a4
--- /dev/null
+++ b/cras/src/tests/observer_unittest.cc
@@ -0,0 +1,625 @@
+// Copyright 2016 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>
+#include <syslog.h>
+
+#include <vector>
+#include <map>
+
+extern "C" {
+#include "cras_observer.c"
+}
+
+namespace {
+
+static size_t cras_alert_destroy_called;
+static size_t cras_alert_create_called;
+static std::vector<struct cras_alert *> cras_alert_create_return_values;
+typedef std::map<struct cras_alert *, void *> alert_callback_map;
+static alert_callback_map cras_alert_create_prepare_map;
+static alert_callback_map cras_alert_add_callback_map;
+typedef std::map<struct cras_alert *, unsigned int> alert_flags_map;
+static alert_flags_map cras_alert_create_flags_map;
+static struct cras_alert *cras_alert_pending_alert_value;
+static void *cras_alert_pending_data_value = NULL;
+static size_t cras_alert_pending_data_size_value;
+static size_t cras_iodev_list_update_device_list_called;
+static std::vector<void *> cb_context;
+static size_t cb_output_volume_changed_called;
+static std::vector<int32_t> cb_output_volume_changed_volume;
+static size_t cb_output_mute_changed_called;
+static std::vector<int> cb_output_mute_changed_muted;
+static std::vector<int> cb_output_mute_changed_user_muted;
+static std::vector<int> cb_output_mute_changed_mute_locked;
+static size_t cb_capture_gain_changed_called;
+static std::vector<int32_t> cb_capture_gain_changed_gain;
+static size_t cb_capture_mute_changed_called;
+static std::vector<int> cb_capture_mute_changed_muted;
+static std::vector<int> cb_capture_mute_changed_mute_locked;
+static size_t cb_nodes_changed_called;
+static size_t cb_active_node_changed_called;
+static std::vector<enum CRAS_STREAM_DIRECTION> cb_active_node_changed_dir;
+static std::vector<cras_node_id_t> cb_active_node_changed_node_id;
+static size_t cb_output_node_volume_changed_called;
+static std::vector<cras_node_id_t> cb_output_node_volume_changed_node_id;
+static std::vector<int32_t> cb_output_node_volume_changed_volume;
+static size_t cb_node_left_right_swapped_changed_called;
+static std::vector<cras_node_id_t> cb_node_left_right_swapped_changed_node_id;
+static std::vector<int> cb_node_left_right_swapped_changed_swapped;
+static size_t cb_input_node_gain_changed_called;
+static std::vector<cras_node_id_t> cb_input_node_gain_changed_node_id;
+static std::vector<int32_t> cb_input_node_gain_changed_gain;
+static size_t cb_num_active_streams_changed_called;
+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 void ResetStubData() {
+  cras_alert_destroy_called = 0;
+  cras_alert_create_called = 0;
+  cras_alert_create_return_values.clear();
+  cras_alert_create_prepare_map.clear();
+  cras_alert_create_flags_map.clear();
+  cras_alert_add_callback_map.clear();
+  cras_alert_pending_alert_value = NULL;
+  cras_alert_pending_data_size_value = 0;
+  if (cras_alert_pending_data_value) {
+    free(cras_alert_pending_data_value);
+    cras_alert_pending_data_value = NULL;
+  }
+  cras_iodev_list_update_device_list_called = 0;
+  cb_context.clear();
+  cb_output_volume_changed_called = 0;
+  cb_output_volume_changed_volume.clear();
+  cb_output_mute_changed_called = 0;
+  cb_output_mute_changed_muted.clear();
+  cb_output_mute_changed_user_muted.clear();
+  cb_output_mute_changed_mute_locked.clear();
+  cb_capture_gain_changed_called = 0;
+  cb_capture_gain_changed_gain.clear();
+  cb_capture_mute_changed_called = 0;
+  cb_capture_mute_changed_muted.clear();
+  cb_capture_mute_changed_mute_locked.clear();
+  cb_nodes_changed_called = 0;
+  cb_active_node_changed_called = 0;
+  cb_active_node_changed_dir.clear();
+  cb_active_node_changed_node_id.clear();
+  cb_output_node_volume_changed_called = 0;
+  cb_output_node_volume_changed_node_id.clear();
+  cb_output_node_volume_changed_volume.clear();
+  cb_node_left_right_swapped_changed_called = 0;
+  cb_node_left_right_swapped_changed_node_id.clear();
+  cb_node_left_right_swapped_changed_swapped.clear();
+  cb_input_node_gain_changed_called = 0;
+  cb_input_node_gain_changed_node_id.clear();
+  cb_input_node_gain_changed_gain.clear();
+  cb_num_active_streams_changed_called = 0;
+  cb_num_active_streams_changed_dir.clear();
+  cb_num_active_streams_changed_num.clear();
+}
+
+/* System output volume changed. */
+void cb_output_volume_changed(void *context, int32_t volume) {
+  cb_output_volume_changed_called++;
+  cb_context.push_back(context);
+  cb_output_volume_changed_volume.push_back(volume);
+}
+/* System output mute changed. */
+void cb_output_mute_changed(void *context,
+                            int muted, int user_muted, int mute_locked) {
+  cb_output_mute_changed_called++;
+  cb_context.push_back(context);
+  cb_output_mute_changed_muted.push_back(muted);
+  cb_output_mute_changed_user_muted.push_back(user_muted);
+  cb_output_mute_changed_mute_locked.push_back(mute_locked);
+}
+/* System input/capture gain changed. */
+void cb_capture_gain_changed(void *context, int32_t gain) {
+  cb_capture_gain_changed_called++;
+  cb_context.push_back(context);
+  cb_capture_gain_changed_gain.push_back(gain);
+}
+
+/* System input/capture mute changed. */
+void cb_capture_mute_changed(void *context, int muted, int mute_locked) {
+  cb_capture_mute_changed_called++;
+  cb_context.push_back(context);
+  cb_capture_mute_changed_muted.push_back(muted);
+  cb_capture_mute_changed_mute_locked.push_back(mute_locked);
+}
+
+/* Device or node topology changed. */
+void cb_nodes_changed(void *context) {
+  cb_nodes_changed_called++;
+  cb_context.push_back(context);
+}
+
+/* Active node changed. A notification is sent for every change.
+ * When there is no active node, node_id is 0. */
+void cb_active_node_changed(void *context,
+                            enum CRAS_STREAM_DIRECTION dir,
+                            cras_node_id_t node_id) {
+  cb_active_node_changed_called++;
+  cb_context.push_back(context);
+  cb_active_node_changed_dir.push_back(dir);
+  cb_active_node_changed_node_id.push_back(node_id);
+}
+
+/* Output node volume changed. */
+void cb_output_node_volume_changed(void *context,
+                                   cras_node_id_t node_id,
+                                   int32_t volume) {
+  cb_output_node_volume_changed_called++;
+  cb_context.push_back(context);
+  cb_output_node_volume_changed_node_id.push_back(node_id);
+  cb_output_node_volume_changed_volume.push_back(volume);
+}
+
+/* Node left/right swapped state change. */
+void cb_node_left_right_swapped_changed(void *context,
+                                        cras_node_id_t node_id,
+                                        int swapped) {
+  cb_node_left_right_swapped_changed_called++;
+  cb_context.push_back(context);
+  cb_node_left_right_swapped_changed_node_id.push_back(node_id);
+  cb_node_left_right_swapped_changed_swapped.push_back(swapped);
+}
+
+/* Input gain changed. */
+void cb_input_node_gain_changed(void *context,
+                                cras_node_id_t node_id,
+                                int32_t gain) {
+  cb_input_node_gain_changed_called++;
+  cb_context.push_back(context);
+  cb_input_node_gain_changed_node_id.push_back(node_id);
+  cb_input_node_gain_changed_gain.push_back(gain);
+}
+
+/* Number of active streams changed. */
+void cb_num_active_streams_changed(void *context,
+                                         enum CRAS_STREAM_DIRECTION dir,
+                                         uint32_t num_active_streams) {
+  cb_num_active_streams_changed_called++;
+  cb_context.push_back(context);
+  cb_num_active_streams_changed_dir.push_back(dir);
+  cb_num_active_streams_changed_num.push_back(num_active_streams);
+}
+
+class ObserverTest : public testing::Test {
+ protected:
+  virtual void SetUp() {
+    int rc;
+
+    ResetStubData();
+    rc = cras_observer_server_init();
+    ASSERT_EQ(0, rc);
+    EXPECT_EQ(13, 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),
+              cras_alert_add_callback_map[g_observer->alerts.output_mute]);
+    EXPECT_EQ(reinterpret_cast<void *>(capture_gain_alert),
+              cras_alert_add_callback_map[g_observer->alerts.capture_gain]);
+    EXPECT_EQ(reinterpret_cast<void *>(capture_mute_alert),
+              cras_alert_add_callback_map[g_observer->alerts.capture_mute]);
+    EXPECT_EQ(reinterpret_cast<void *>(nodes_alert),
+              cras_alert_add_callback_map[g_observer->alerts.nodes]);
+    EXPECT_EQ(reinterpret_cast<void *>(nodes_prepare),
+              cras_alert_create_prepare_map[g_observer->alerts.nodes]);
+    EXPECT_EQ(reinterpret_cast<void *>(active_node_alert),
+              cras_alert_add_callback_map[g_observer->alerts.active_node]);
+    EXPECT_EQ(CRAS_ALERT_FLAG_KEEP_ALL_DATA,
+              cras_alert_create_flags_map[g_observer->alerts.active_node]);
+    EXPECT_EQ(reinterpret_cast<void *>(output_node_volume_alert),
+            cras_alert_add_callback_map[g_observer->alerts.output_node_volume]);
+    EXPECT_EQ(reinterpret_cast<void *>(node_left_right_swapped_alert),
+       cras_alert_add_callback_map[g_observer->alerts.node_left_right_swapped]);
+    EXPECT_EQ(reinterpret_cast<void *>(input_node_gain_alert),
+            cras_alert_add_callback_map[g_observer->alerts.input_node_gain]);
+    EXPECT_EQ(reinterpret_cast<void *>(num_active_streams_alert),
+       cras_alert_add_callback_map[g_observer->alerts.num_active_streams[
+                                               CRAS_STREAM_OUTPUT]]);
+    EXPECT_EQ(reinterpret_cast<void *>(num_active_streams_alert),
+       cras_alert_add_callback_map[g_observer->alerts.num_active_streams[
+                                               CRAS_STREAM_INPUT]]);
+    EXPECT_EQ(reinterpret_cast<void *>(num_active_streams_alert),
+       cras_alert_add_callback_map[g_observer->alerts.num_active_streams[
+                                               CRAS_STREAM_POST_MIX_PRE_DSP]]);
+    EXPECT_EQ(reinterpret_cast<void *>(suspend_changed_alert),
+       cras_alert_add_callback_map[g_observer->alerts.suspend_changed]);
+
+    cras_observer_get_ops(NULL, &ops1_);
+    EXPECT_NE(0, cras_observer_ops_are_empty(&ops1_));
+
+    cras_observer_get_ops(NULL, &ops2_);
+    EXPECT_NE(0, cras_observer_ops_are_empty(&ops2_));
+
+    context1_ = reinterpret_cast<void *>(1);
+    context2_ = reinterpret_cast<void *>(2);
+  }
+
+  virtual void TearDown() {
+    cras_observer_server_free();
+    EXPECT_EQ(13, cras_alert_destroy_called);
+    ResetStubData();
+  }
+
+  void DoObserverAlert(cras_alert_cb alert, void *data) {
+    client1_ = cras_observer_add(&ops1_, context1_);
+    client2_ = cras_observer_add(&ops2_, context2_);
+    ASSERT_NE(client1_, reinterpret_cast<struct cras_observer_client *>(NULL));
+    ASSERT_NE(client2_, reinterpret_cast<struct cras_observer_client *>(NULL));
+
+    ASSERT_NE(alert, reinterpret_cast<cras_alert_cb>(NULL));
+    alert(NULL, data);
+
+    EXPECT_EQ(cb_context[0], context1_);
+    EXPECT_EQ(cb_context[1], context2_);
+  }
+
+  void DoObserverRemoveClear(cras_alert_cb alert, void *data) {
+    ASSERT_NE(alert, reinterpret_cast<cras_alert_cb>(NULL));
+    ASSERT_NE(client1_, reinterpret_cast<struct cras_observer_client *>(NULL));
+    ASSERT_NE(client2_, reinterpret_cast<struct cras_observer_client *>(NULL));
+
+    // Test observer removal.
+    cras_observer_remove(client1_);
+    cb_context.clear();
+    alert(NULL, data);
+    EXPECT_EQ(cb_context[0], context2_);
+    EXPECT_EQ(cb_context.size(), 1);
+
+    // Clear out ops1_.
+    cras_observer_get_ops(NULL, &ops1_);
+    EXPECT_NE(0, cras_observer_ops_are_empty(&ops1_));
+
+    // Get the current value of ops2_ into ops1_.
+    cras_observer_get_ops(client2_, &ops1_);
+    EXPECT_EQ(0, memcmp((void *)&ops1_, (void *)&ops2_, sizeof(ops1_)));
+
+    // Clear out opts for client2.
+    cras_observer_get_ops(NULL, &ops2_);
+    EXPECT_NE(0, cras_observer_ops_are_empty(&ops2_));
+    cras_observer_set_ops(client2_, &ops2_);
+
+    cb_context.clear();
+    alert(NULL, data);
+    // No callbacks executed.
+    EXPECT_EQ(cb_context.size(), 0);
+  }
+
+  struct cras_observer_client *client1_;
+  struct cras_observer_client *client2_;
+  struct cras_observer_ops ops1_;
+  struct cras_observer_ops ops2_;
+  void *context1_;
+  void *context2_;
+};
+
+TEST_F(ObserverTest, NotifyOutputVolume) {
+  struct cras_observer_alert_data_volume *data;
+  const int32_t volume = 100;
+
+  cras_observer_notify_output_volume(volume);
+  EXPECT_EQ(cras_alert_pending_alert_value, g_observer->alerts.output_volume);
+  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_volume *>(
+             cras_alert_pending_data_value);
+  EXPECT_EQ(data->volume, volume);
+
+  ops1_.output_volume_changed = cb_output_volume_changed;
+  ops2_.output_volume_changed = cb_output_volume_changed;
+  DoObserverAlert(output_volume_alert, data);
+  ASSERT_EQ(2, cb_output_volume_changed_called);
+  EXPECT_EQ(cb_output_volume_changed_volume[0], volume);
+  EXPECT_EQ(cb_output_volume_changed_volume[1], volume);
+
+  DoObserverRemoveClear(output_volume_alert, data);
+};
+
+TEST_F(ObserverTest, NotifyOutputMute) {
+  struct cras_observer_alert_data_mute *data;
+  const int muted = 1;
+  const int user_muted = 0;
+  const int mute_locked = 0;
+
+  cras_observer_notify_output_mute(muted, user_muted, mute_locked);
+  EXPECT_EQ(cras_alert_pending_alert_value, g_observer->alerts.output_mute);
+  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_mute *>(
+             cras_alert_pending_data_value);
+  EXPECT_EQ(data->muted, muted);
+  EXPECT_EQ(data->user_muted, user_muted);
+  EXPECT_EQ(data->mute_locked, mute_locked);
+
+  ops1_.output_mute_changed = cb_output_mute_changed;
+  ops2_.output_mute_changed = cb_output_mute_changed;
+  DoObserverAlert(output_mute_alert, data);
+  ASSERT_EQ(2, cb_output_mute_changed_called);
+  EXPECT_EQ(cb_output_mute_changed_muted[0], muted);
+  EXPECT_EQ(cb_output_mute_changed_muted[1], muted);
+  EXPECT_EQ(cb_output_mute_changed_user_muted[0], user_muted);
+  EXPECT_EQ(cb_output_mute_changed_user_muted[1], user_muted);
+  EXPECT_EQ(cb_output_mute_changed_mute_locked[0], mute_locked);
+  EXPECT_EQ(cb_output_mute_changed_mute_locked[1], mute_locked);
+
+  DoObserverRemoveClear(output_mute_alert, data);
+};
+
+TEST_F(ObserverTest, NotifyCaptureGain) {
+  struct cras_observer_alert_data_volume *data;
+  const int32_t gain = -20;
+
+  cras_observer_notify_capture_gain(gain);
+  EXPECT_EQ(cras_alert_pending_alert_value, g_observer->alerts.capture_gain);
+  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_volume *>(
+             cras_alert_pending_data_value);
+  EXPECT_EQ(data->volume, gain);
+
+  ops1_.capture_gain_changed = cb_capture_gain_changed;
+  ops2_.capture_gain_changed = cb_capture_gain_changed;
+  DoObserverAlert(capture_gain_alert, data);
+  ASSERT_EQ(2, cb_capture_gain_changed_called);
+  EXPECT_EQ(cb_capture_gain_changed_gain[0], gain);
+  EXPECT_EQ(cb_capture_gain_changed_gain[1], gain);
+
+  DoObserverRemoveClear(capture_gain_alert, data);
+};
+
+TEST_F(ObserverTest, NotifyCaptureMute) {
+  struct cras_observer_alert_data_mute *data;
+  const int muted = 1;
+  const int mute_locked = 0;
+
+  cras_observer_notify_capture_mute(muted, mute_locked);
+  EXPECT_EQ(cras_alert_pending_alert_value, g_observer->alerts.capture_mute);
+  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_mute *>(
+             cras_alert_pending_data_value);
+  EXPECT_EQ(data->muted, muted);
+  EXPECT_EQ(data->mute_locked, mute_locked);
+
+  ops1_.capture_mute_changed = cb_capture_mute_changed;
+  ops2_.capture_mute_changed = cb_capture_mute_changed;
+  DoObserverAlert(capture_mute_alert, data);
+  ASSERT_EQ(2, cb_capture_mute_changed_called);
+  EXPECT_EQ(cb_capture_mute_changed_muted[0], muted);
+  EXPECT_EQ(cb_capture_mute_changed_muted[1], muted);
+  EXPECT_EQ(cb_capture_mute_changed_mute_locked[0], mute_locked);
+  EXPECT_EQ(cb_capture_mute_changed_mute_locked[1], mute_locked);
+
+  DoObserverRemoveClear(capture_mute_alert, data);
+};
+
+TEST_F(ObserverTest, NotifyNodes) {
+  cras_observer_notify_nodes();
+  EXPECT_EQ(cras_alert_pending_alert_value, g_observer->alerts.nodes);
+
+  ops1_.nodes_changed = cb_nodes_changed;
+  ops2_.nodes_changed = cb_nodes_changed;
+  DoObserverAlert(nodes_alert, NULL);
+  ASSERT_EQ(2, cb_nodes_changed_called);
+
+  DoObserverRemoveClear(nodes_alert, NULL);
+};
+
+TEST_F(ObserverTest, NotifyActiveNode) {
+  struct cras_observer_alert_data_active_node *data;
+  const enum CRAS_STREAM_DIRECTION dir = CRAS_STREAM_INPUT;
+  const cras_node_id_t node_id = 0x0001000100020002;
+
+  cras_observer_notify_active_node(dir, node_id);
+  EXPECT_EQ(cras_alert_pending_alert_value, g_observer->alerts.active_node);
+  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_active_node *>(
+             cras_alert_pending_data_value);
+  EXPECT_EQ(data->node_id, node_id);
+  EXPECT_EQ(data->direction, dir);
+
+  ops1_.active_node_changed = cb_active_node_changed;
+  ops2_.active_node_changed = cb_active_node_changed;
+  DoObserverAlert(active_node_alert, data);
+  ASSERT_EQ(2, cb_active_node_changed_called);
+  EXPECT_EQ(cb_active_node_changed_dir[0], dir);
+  EXPECT_EQ(cb_active_node_changed_dir[1], dir);
+  EXPECT_EQ(cb_active_node_changed_node_id[0], node_id);
+  EXPECT_EQ(cb_active_node_changed_node_id[1], node_id);
+
+  DoObserverRemoveClear(active_node_alert, data);
+};
+
+TEST_F(ObserverTest, NotifyOutputNodeVolume) {
+  struct cras_observer_alert_data_node_volume *data;
+  const cras_node_id_t node_id = 0x0001000100020002;
+  const int32_t volume = 100;
+
+  cras_observer_notify_output_node_volume(node_id, volume);
+  EXPECT_EQ(cras_alert_pending_alert_value,
+            g_observer->alerts.output_node_volume);
+  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_node_volume *>(
+             cras_alert_pending_data_value);
+  EXPECT_EQ(data->node_id, node_id);
+  EXPECT_EQ(data->volume, volume);
+
+  ops1_.output_node_volume_changed = cb_output_node_volume_changed;
+  ops2_.output_node_volume_changed = cb_output_node_volume_changed;
+  DoObserverAlert(output_node_volume_alert, data);
+  ASSERT_EQ(2, cb_output_node_volume_changed_called);
+  EXPECT_EQ(cb_output_node_volume_changed_volume[0], volume);
+  EXPECT_EQ(cb_output_node_volume_changed_volume[1], volume);
+  EXPECT_EQ(cb_output_node_volume_changed_node_id[0], node_id);
+  EXPECT_EQ(cb_output_node_volume_changed_node_id[1], node_id);
+
+  DoObserverRemoveClear(output_node_volume_alert, data);
+};
+
+TEST_F(ObserverTest, NotifyNodeLeftRightSwapped) {
+  struct cras_observer_alert_data_node_lr_swapped *data;
+  const cras_node_id_t node_id = 0x0001000100020002;
+  const int swapped = 1;
+
+  cras_observer_notify_node_left_right_swapped(node_id, swapped);
+  EXPECT_EQ(cras_alert_pending_alert_value,
+            g_observer->alerts.node_left_right_swapped);
+  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_node_lr_swapped *>(
+             cras_alert_pending_data_value);
+  EXPECT_EQ(data->node_id, node_id);
+  EXPECT_EQ(data->swapped, swapped);
+
+  ops1_.node_left_right_swapped_changed = cb_node_left_right_swapped_changed;
+  ops2_.node_left_right_swapped_changed = cb_node_left_right_swapped_changed;
+  DoObserverAlert(node_left_right_swapped_alert, data);
+  ASSERT_EQ(2, cb_node_left_right_swapped_changed_called);
+  EXPECT_EQ(cb_node_left_right_swapped_changed_swapped[0], swapped);
+  EXPECT_EQ(cb_node_left_right_swapped_changed_swapped[1], swapped);
+  EXPECT_EQ(cb_node_left_right_swapped_changed_node_id[0], node_id);
+  EXPECT_EQ(cb_node_left_right_swapped_changed_node_id[1], node_id);
+
+  DoObserverRemoveClear(node_left_right_swapped_alert, data);
+};
+
+TEST_F(ObserverTest, NotifyInputNodeGain) {
+  struct cras_observer_alert_data_node_volume *data;
+  const cras_node_id_t node_id = 0x0001000100020002;
+  const int32_t gain = -20;
+
+  cras_observer_notify_input_node_gain(node_id, gain);
+  EXPECT_EQ(cras_alert_pending_alert_value,
+            g_observer->alerts.input_node_gain);
+  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_node_volume *>(
+             cras_alert_pending_data_value);
+  EXPECT_EQ(data->node_id, node_id);
+  EXPECT_EQ(data->volume, gain);
+
+  ops1_.input_node_gain_changed = cb_input_node_gain_changed;
+  ops2_.input_node_gain_changed = cb_input_node_gain_changed;
+  DoObserverAlert(input_node_gain_alert, data);
+  ASSERT_EQ(2, cb_input_node_gain_changed_called);
+  EXPECT_EQ(cb_input_node_gain_changed_gain[0], gain);
+  EXPECT_EQ(cb_input_node_gain_changed_gain[1], gain);
+  EXPECT_EQ(cb_input_node_gain_changed_node_id[0], node_id);
+  EXPECT_EQ(cb_input_node_gain_changed_node_id[1], node_id);
+
+  DoObserverRemoveClear(input_node_gain_alert, data);
+};
+
+TEST_F(ObserverTest, NotifySuspendChanged) {
+  struct cras_observer_alert_data_suspend *data;
+
+  cras_observer_notify_suspend_changed(1);
+  EXPECT_EQ(cras_alert_pending_alert_value,
+            g_observer->alerts.suspend_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_suspend *>(
+      cras_alert_pending_data_value);
+  EXPECT_EQ(data->suspended, 1);
+
+  cras_observer_notify_suspend_changed(0);
+  EXPECT_EQ(cras_alert_pending_alert_value,
+            g_observer->alerts.suspend_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_suspend *>(
+      cras_alert_pending_data_value);
+  EXPECT_EQ(data->suspended, 0);
+}
+
+TEST_F(ObserverTest, NotifyNumActiveStreams) {
+  struct cras_observer_alert_data_streams *data;
+  const enum CRAS_STREAM_DIRECTION dir = CRAS_STREAM_INPUT;
+  const uint32_t active_streams = 10;
+
+  cras_observer_notify_num_active_streams(dir, active_streams);
+  EXPECT_EQ(cras_alert_pending_alert_value,
+            g_observer->alerts.num_active_streams[CRAS_STREAM_INPUT]);
+  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_streams *>(
+             cras_alert_pending_data_value);
+  EXPECT_EQ(data->num_active_streams, active_streams);
+  EXPECT_EQ(data->direction, dir);
+
+  ops1_.num_active_streams_changed = cb_num_active_streams_changed;
+  ops2_.num_active_streams_changed = cb_num_active_streams_changed;
+  DoObserverAlert(num_active_streams_alert, data);
+  ASSERT_EQ(2, cb_num_active_streams_changed_called);
+  EXPECT_EQ(cb_num_active_streams_changed_dir[0], dir);
+  EXPECT_EQ(cb_num_active_streams_changed_dir[1], dir);
+  EXPECT_EQ(cb_num_active_streams_changed_num[0], active_streams);
+  EXPECT_EQ(cb_num_active_streams_changed_num[1], active_streams);
+
+  DoObserverRemoveClear(num_active_streams_alert, data);
+};
+
+// Stubs
+extern "C" {
+
+void cras_alert_destroy(struct cras_alert *alert) {
+  cras_alert_destroy_called++;
+}
+
+struct cras_alert *cras_alert_create(cras_alert_prepare prepare,
+                                     unsigned int flags) {
+  struct cras_alert *alert = NULL;
+
+  cras_alert_create_called++;
+  alert = reinterpret_cast<struct cras_alert*>(cras_alert_create_called);
+  cras_alert_create_return_values.push_back(alert);
+  cras_alert_create_flags_map[alert] = flags;
+  cras_alert_create_prepare_map[alert] = reinterpret_cast<void *>(prepare);
+  return alert;
+}
+
+int cras_alert_add_callback(struct cras_alert *alert, cras_alert_cb cb,
+			    void *arg) {
+  cras_alert_add_callback_map[alert] = reinterpret_cast<void *>(cb);
+  return 0;
+}
+
+void cras_alert_pending(struct cras_alert *alert) {
+  cras_alert_pending_alert_value = alert;
+}
+
+void cras_alert_pending_data(struct cras_alert *alert,
+			     void *data, size_t data_size) {
+  cras_alert_pending_alert_value = alert;
+  cras_alert_pending_data_size_value = data_size;
+  if (cras_alert_pending_data_value)
+    free(cras_alert_pending_data_value);
+  if (data) {
+    cras_alert_pending_data_value = malloc(data_size);
+    memcpy(cras_alert_pending_data_value, data, data_size);
+  }
+  else
+    cras_alert_pending_data_value = NULL;
+}
+
+void cras_iodev_list_update_device_list() {
+  cras_iodev_list_update_device_list_called++;
+}
+
+}  // extern "C"
+
+}  // namespace
+
+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/ramp_unittest.cc b/cras/src/tests/ramp_unittest.cc
new file mode 100644
index 0000000..7bd8fac
--- /dev/null
+++ b/cras/src/tests/ramp_unittest.cc
@@ -0,0 +1,406 @@
+// Copyright 2016 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 <stdio.h>
+#include <gtest/gtest.h>
+
+extern "C" {
+#include "cras_ramp.c"
+}
+
+static int callback_called;
+static void *callback_arg;
+
+void ResetStubData() {
+  callback_called = 0;
+  callback_arg = NULL;
+}
+
+namespace {
+
+TEST(RampTestSuite, Init) {
+  struct cras_ramp *ramp;
+  struct cras_ramp_action action;
+
+  ResetStubData();
+
+  ramp = cras_ramp_create();
+  action = cras_ramp_get_current_action(ramp);
+
+  EXPECT_EQ(action.type, CRAS_RAMP_ACTION_NONE);
+  EXPECT_FLOAT_EQ(1.0, action.scaler);
+  EXPECT_FLOAT_EQ(0.0, action.increment);
+
+  cras_ramp_destroy(ramp);
+}
+
+TEST(RampTestSuite, RampUpInitialIncrement) {
+  int ramp_up = 1;
+  int duration_frames = 48000;
+  float increment = 1.0 / 48000;
+  cras_ramp *ramp;
+  cras_ramp_action action;
+
+  ResetStubData();
+
+  ramp = cras_ramp_create();
+  cras_ramp_start(ramp, ramp_up, 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(increment, action.increment);
+
+  cras_ramp_destroy(ramp);
+}
+
+TEST(RampTestSuite, RampUpUpdateRampedFrames) {
+  int ramp_up = 1;
+  int duration_frames = 48000;
+  float increment = 1.0 / 48000;
+  int rc;
+  int ramped_frames = 512;
+  struct cras_ramp *ramp;
+  struct cras_ramp_action action;
+  float scaler = increment * ramped_frames;
+
+  ResetStubData();
+
+  ramp = cras_ramp_create();
+  cras_ramp_start(ramp, ramp_up, duration_frames, NULL, NULL);
+
+  rc = cras_ramp_update_ramped_frames(ramp, ramped_frames);
+
+  action = cras_ramp_get_current_action(ramp);
+
+  EXPECT_EQ(rc, 0);
+  EXPECT_EQ(CRAS_RAMP_ACTION_PARTIAL, action.type);
+  EXPECT_FLOAT_EQ(scaler, action.scaler);
+  EXPECT_FLOAT_EQ(increment, action.increment);
+
+  cras_ramp_destroy(ramp);
+}
+
+TEST(RampTestSuite, RampUpPassedRamp) {
+  int ramp_up = 1;
+  int duration_frames = 48000;
+  int rc;
+  int ramped_frames = 48000;
+  struct cras_ramp *ramp;
+  struct cras_ramp_action action;
+
+  ResetStubData();
+
+  ramp = cras_ramp_create();
+  cras_ramp_start(ramp, ramp_up, duration_frames, NULL, NULL);
+
+  rc = cras_ramp_update_ramped_frames(ramp, ramped_frames);
+
+  action = cras_ramp_get_current_action(ramp);
+
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(CRAS_RAMP_ACTION_NONE, action.type);
+  EXPECT_FLOAT_EQ(1.0, action.scaler);
+  EXPECT_FLOAT_EQ(0.0, action.increment);
+
+  cras_ramp_destroy(ramp);
+}
+
+TEST(RampTestSuite, RampUpWhileHalfWayRampDown) {
+  int ramp_up;
+  int duration_frames = 48000;
+  int rc;
+  int ramped_frames = 24000;
+  struct cras_ramp *ramp;
+  struct cras_ramp_action action;
+  float down_increment = -1.0 / 48000;
+  float up_increment;
+  float scaler;
+
+  ResetStubData();
+
+  ramp = cras_ramp_create();
+  ramp_up = 0;
+  cras_ramp_start(ramp, ramp_up, duration_frames, NULL, NULL);
+
+  rc = cras_ramp_update_ramped_frames(ramp, ramped_frames);
+
+  // Get expected current scaler.
+  scaler = 1 + down_increment * ramped_frames;
+  // The increment will be calculated by ramping to 1 starting from scaler.
+  up_increment = (1 - scaler) / 48000;
+
+  ramp_up = 1;
+  cras_ramp_start(ramp, ramp_up, duration_frames, NULL, NULL);
+
+  action = cras_ramp_get_current_action(ramp);
+
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(CRAS_RAMP_ACTION_PARTIAL, action.type);
+  EXPECT_FLOAT_EQ(scaler, action.scaler);
+  EXPECT_FLOAT_EQ(up_increment, action.increment);
+
+  cras_ramp_destroy(ramp);
+}
+
+TEST(RampTestSuite, RampUpWhileHalfWayRampUp) {
+  int ramp_up;
+  int duration_frames = 48000;
+  int rc;
+  int ramped_frames = 24000;
+  struct cras_ramp *ramp;
+  struct cras_ramp_action action;
+  float first_increment = 1.0 / 48000;
+  float second_increment;
+  float scaler;
+
+  ResetStubData();
+
+  ramp = cras_ramp_create();
+  ramp_up = 1;
+  cras_ramp_start(ramp, ramp_up, duration_frames, NULL, NULL);
+
+  rc = cras_ramp_update_ramped_frames(ramp, ramped_frames);
+
+  // Get expected current scaler.
+  scaler = first_increment * ramped_frames;
+  // The increment will be calculated by ramping to 1 starting from scaler.
+  second_increment = (1 - scaler) / 48000;
+
+  ramp_up = 1;
+  cras_ramp_start(ramp, ramp_up, duration_frames, NULL, NULL);
+
+  action = cras_ramp_get_current_action(ramp);
+
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(CRAS_RAMP_ACTION_PARTIAL, action.type);
+  EXPECT_FLOAT_EQ(scaler, action.scaler);
+  EXPECT_FLOAT_EQ(second_increment, action.increment);
+
+  cras_ramp_destroy(ramp);
+}
+
+TEST(RampTestSuite, RampDownInitialIncrement) {
+  int ramp_up = 0;
+  int duration_frames = 48000;
+  float increment = -1.0 / 48000;
+  cras_ramp *ramp;
+  cras_ramp_action action;
+
+  ResetStubData();
+
+  ramp = cras_ramp_create();
+  cras_ramp_start(ramp, ramp_up, duration_frames, NULL, NULL);
+
+  action = cras_ramp_get_current_action(ramp);
+
+  EXPECT_EQ(CRAS_RAMP_ACTION_PARTIAL, action.type);
+  EXPECT_FLOAT_EQ(1.0, action.scaler);
+  EXPECT_FLOAT_EQ(increment, action.increment);
+
+  cras_ramp_destroy(ramp);
+}
+
+TEST(RampTestSuite, RampDownUpdateRampedFrames) {
+  int ramp_up = 0;
+  int duration_frames = 48000;
+  float increment = -1.0 / 48000;
+  int rc;
+  int ramped_frames = 512;
+  struct cras_ramp *ramp;
+  struct cras_ramp_action action;
+  float scaler = 1 + increment * ramped_frames;
+
+  ResetStubData();
+
+  ramp = cras_ramp_create();
+  cras_ramp_start(ramp, ramp_up, duration_frames, NULL, NULL);
+
+  rc = cras_ramp_update_ramped_frames(ramp, ramped_frames);
+
+  action = cras_ramp_get_current_action(ramp);
+
+  EXPECT_EQ(rc, 0);
+  EXPECT_EQ(CRAS_RAMP_ACTION_PARTIAL, action.type);
+  EXPECT_FLOAT_EQ(scaler, action.scaler);
+  EXPECT_FLOAT_EQ(increment, action.increment);
+
+  cras_ramp_destroy(ramp);
+}
+
+TEST(RampTestSuite, RampDownPassedRamp) {
+  int ramp_up = 0;
+  int duration_frames = 48000;
+  int rc;
+  int ramped_frames = 48000;
+  struct cras_ramp *ramp;
+  struct cras_ramp_action action;
+
+  ResetStubData();
+
+  ramp = cras_ramp_create();
+  cras_ramp_start(ramp, ramp_up, duration_frames, NULL, NULL);
+
+  rc = cras_ramp_update_ramped_frames(ramp, ramped_frames);
+
+  action = cras_ramp_get_current_action(ramp);
+
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(CRAS_RAMP_ACTION_NONE, action.type);
+  EXPECT_FLOAT_EQ(1.0, action.scaler);
+  EXPECT_FLOAT_EQ(0.0, action.increment);
+
+  cras_ramp_destroy(ramp);
+}
+
+TEST(RampTestSuite, RampDownWhileHalfWayRampUp) {
+  int ramp_up;
+  int duration_frames = 48000;
+  int rc;
+  int ramped_frames = 24000;
+  struct cras_ramp *ramp;
+  struct cras_ramp_action action;
+  float up_increment = 1.0 / 48000;
+  float down_increment;
+  float scaler;
+
+  ResetStubData();
+
+  ramp = cras_ramp_create();
+  // Ramp up first.
+  ramp_up = 1;
+  cras_ramp_start(ramp, ramp_up, duration_frames, NULL, NULL);
+
+  rc = cras_ramp_update_ramped_frames(ramp, ramped_frames);
+
+  // Get expected current scaler.
+  scaler = up_increment * ramped_frames;
+  // The increment will be calculated by ramping to 0 starting from scaler.
+  down_increment = -scaler / duration_frames;
+
+
+  // Ramp down will start from current scaler.
+  ramp_up = 0;
+  cras_ramp_start(ramp, ramp_up, duration_frames, NULL, NULL);
+
+  action = cras_ramp_get_current_action(ramp);
+
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(CRAS_RAMP_ACTION_PARTIAL, action.type);
+  EXPECT_FLOAT_EQ(scaler, action.scaler);
+  EXPECT_FLOAT_EQ(down_increment, action.increment);
+
+  cras_ramp_destroy(ramp);
+}
+
+TEST(RampTestSuite, RampDownWhileHalfWayRampDown) {
+  int ramp_up;
+  int duration_frames = 48000;
+  int rc;
+  int ramped_frames = 24000;
+  struct cras_ramp *ramp;
+  struct cras_ramp_action action;
+  float down_increment = -1.0 / 48000;
+  float second_down_increment;
+  float scaler;
+
+  ResetStubData();
+
+  ramp = cras_ramp_create();
+  // Ramp down.
+  ramp_up = 0;
+  cras_ramp_start(ramp, ramp_up, duration_frames, NULL, NULL);
+
+  rc = cras_ramp_update_ramped_frames(ramp, ramped_frames);
+
+  // Get expected current scaler.
+  scaler = 1 + down_increment * ramped_frames;
+  // The increment will be calculated by ramping to 0 starting from scaler.
+  second_down_increment = -scaler / duration_frames;
+
+
+  // Ramp down starting from current scaler.
+  ramp_up = 0;
+  cras_ramp_start(ramp, ramp_up, duration_frames, NULL, NULL);
+
+  action = cras_ramp_get_current_action(ramp);
+
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(CRAS_RAMP_ACTION_PARTIAL, action.type);
+  EXPECT_FLOAT_EQ(scaler, action.scaler);
+  EXPECT_FLOAT_EQ(second_down_increment, action.increment);
+
+  cras_ramp_destroy(ramp);
+}
+
+void ramp_callback(void *arg) {
+  callback_called++;
+  callback_arg = arg;
+}
+
+TEST(RampTestSuite, RampUpPassedRampCallback) {
+  int ramp_up = 1;
+  int duration_frames = 48000;
+  int rc;
+  int ramped_frames = 48000;
+  struct cras_ramp *ramp;
+  struct cras_ramp_action action;
+  void *cb_data = reinterpret_cast<void*>(0x123);
+
+  ResetStubData();
+
+  ramp = cras_ramp_create();
+  cras_ramp_start(ramp, ramp_up, duration_frames, ramp_callback, cb_data);
+
+  rc = cras_ramp_update_ramped_frames(ramp, ramped_frames);
+
+  action = cras_ramp_get_current_action(ramp);
+
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(CRAS_RAMP_ACTION_NONE, action.type);
+  EXPECT_FLOAT_EQ(1.0, action.scaler);
+  EXPECT_FLOAT_EQ(0.0, action.increment);
+  EXPECT_EQ(1, callback_called);
+  EXPECT_EQ(cb_data, callback_arg);
+
+  cras_ramp_destroy(ramp);
+}
+
+TEST(RampTestSuite, RampDownPassedRampCallback) {
+  int ramp_up = 0;
+  int duration_frames = 48000;
+  int rc;
+  int ramped_frames = 48000;
+  struct cras_ramp *ramp;
+  struct cras_ramp_action action;
+  void *cb_data = reinterpret_cast<void*>(0x123);
+
+  ResetStubData();
+
+  ramp = cras_ramp_create();
+  cras_ramp_start(ramp, ramp_up, duration_frames, ramp_callback, cb_data);
+
+  rc = cras_ramp_update_ramped_frames(ramp, ramped_frames);
+
+  action = cras_ramp_get_current_action(ramp);
+
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(CRAS_RAMP_ACTION_NONE, action.type);
+  EXPECT_FLOAT_EQ(1.0, action.scaler);
+  EXPECT_FLOAT_EQ(0.0, action.increment);
+  EXPECT_EQ(1, callback_called);
+  EXPECT_EQ(cb_data, callback_arg);
+
+  cras_ramp_destroy(ramp);
+}
+
+}  // namespace
+
+int main(int argc, char **argv) {
+  ::testing::InitGoogleTest(&argc, argv);
+  int rc = RUN_ALL_TESTS();
+
+  return rc;
+}
diff --git a/cras/src/tests/rclient_unittest.cc b/cras/src/tests/rclient_unittest.cc
index c16dc52..e699c7e 100644
--- a/cras/src/tests/rclient_unittest.cc
+++ b/cras/src/tests/rclient_unittest.cc
@@ -12,6 +12,9 @@
 #include "cras_rclient.h"
 #include "cras_rstream.h"
 #include "cras_system_state.h"
+
+// Access to data structures and static functions.
+#include "cras_rclient.c"
 }
 
 //  Stub data.
@@ -40,6 +43,17 @@
 static unsigned int cras_iodev_list_rm_input_called;
 static unsigned int cras_iodev_list_rm_output_called;
 static struct cras_rstream dummy_rstream;
+static size_t cras_observer_num_ops_registered;
+static size_t cras_observer_register_notify_called;
+static size_t cras_observer_add_called;
+static void *cras_observer_add_context_value;
+static struct cras_observer_client *cras_observer_add_return_value;
+static size_t cras_observer_get_ops_called;
+static struct cras_observer_ops cras_observer_ops_value;
+static size_t cras_observer_set_ops_called;
+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;
 
 void ResetStubData() {
   cras_rstream_create_return = 0;
@@ -66,6 +80,19 @@
   stream_list_disconnect_stream_called = 0;
   cras_iodev_list_rm_output_called = 0;
   cras_iodev_list_rm_input_called = 0;
+  cras_observer_num_ops_registered = 0;
+  cras_observer_register_notify_called = 0;
+  cras_observer_add_called = 0;
+  cras_observer_add_return_value =
+      reinterpret_cast<struct cras_observer_client *>(1);
+  cras_observer_add_context_value = NULL;
+  cras_observer_get_ops_called = 0;
+  memset(&cras_observer_ops_value, 0, sizeof(cras_observer_ops_value));
+  cras_observer_set_ops_called = 0;
+  cras_observer_ops_are_empty_called = 0;
+  memset(&cras_observer_ops_are_empty_empty_ops, 0,
+         sizeof(cras_observer_ops_are_empty_empty_ops));
+  cras_observer_remove_called = 0;
 }
 
 namespace {
@@ -87,7 +114,6 @@
   rc = read(pipe_fds[0], &msg, sizeof(msg));
   EXPECT_EQ(sizeof(msg), rc);
   EXPECT_EQ(CRAS_CLIENT_CONNECTED, msg.header.id);
-  EXPECT_EQ(CRAS_CLIENT_CONNECTED, msg.header.id);
 
   cras_rclient_destroy(rclient);
   close(pipe_fds[0]);
@@ -134,6 +160,9 @@
       close(pipe_fds_[1]);
     }
 
+    void RegisterNotification(enum CRAS_CLIENT_MESSAGE_ID msg_id,
+                              void *callback, void **ops_address);
+
     struct cras_connect_message connect_msg_;
     struct cras_rclient *rclient_;
     struct cras_rstream *rstream_;
@@ -295,6 +324,301 @@
   EXPECT_EQ(1, cras_system_set_capture_mute_locked_value);
 }
 
+void RClientMessagesSuite::RegisterNotification(
+    enum CRAS_CLIENT_MESSAGE_ID msg_id,
+    void *callback, void **ops_address) {
+  struct cras_register_notification msg;
+  int do_register = callback != NULL ? 1 : 0;
+  int rc;
+
+  cras_observer_register_notify_called++;
+
+  cras_fill_register_notification_message(&msg, msg_id, do_register);
+  EXPECT_EQ(msg.header.length, sizeof(msg));
+  EXPECT_EQ(msg.header.id, CRAS_SERVER_REGISTER_NOTIFICATION);
+  EXPECT_EQ(msg.do_register, do_register);
+  EXPECT_EQ(msg.msg_id, msg_id);
+
+  rc = cras_rclient_message_from_client(rclient_, &msg.header, -1);
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(cras_observer_register_notify_called, cras_observer_get_ops_called);
+  EXPECT_EQ(cras_observer_register_notify_called,\
+            cras_observer_ops_are_empty_called);
+  if (msg.do_register)
+    cras_observer_num_ops_registered++;
+  if (cras_observer_num_ops_registered == 1) {
+    if (msg.do_register) {
+      EXPECT_EQ(1, cras_observer_add_called);
+      EXPECT_EQ(rclient_, cras_observer_add_context_value);
+      EXPECT_EQ(rclient_->observer, cras_observer_add_return_value);
+    } else {
+      EXPECT_EQ(1, cras_observer_remove_called);
+      EXPECT_EQ(rclient_->observer, (struct cras_observer_client *)NULL);
+    }
+  } else {
+    EXPECT_EQ(cras_observer_register_notify_called - 1,\
+              cras_observer_set_ops_called);
+  }
+  if (!msg.do_register)
+    cras_observer_num_ops_registered--;
+  if (cras_observer_num_ops_registered)
+    EXPECT_EQ(callback, *ops_address);
+}
+
+TEST_F(RClientMessagesSuite, RegisterStatusNotification) {
+  /* First registration for this client. */
+  RegisterNotification(
+      CRAS_CLIENT_OUTPUT_VOLUME_CHANGED,
+      (void *)send_output_volume_changed,
+      (void **)&cras_observer_ops_value.output_volume_changed);
+
+  /* Second registration for this client. */
+  RegisterNotification(
+      CRAS_CLIENT_CAPTURE_GAIN_CHANGED,
+      (void *)send_capture_gain_changed,
+      (void **)&cras_observer_ops_value.capture_gain_changed);
+
+  /* Deregister output_volume. */
+  RegisterNotification(
+      CRAS_CLIENT_OUTPUT_VOLUME_CHANGED, NULL,
+      (void **)&cras_observer_ops_value.output_volume_changed);
+
+  /* Register/deregister all msg_ids. */
+
+  RegisterNotification(
+      CRAS_CLIENT_OUTPUT_MUTE_CHANGED,
+      (void *)send_output_mute_changed,
+      (void **)&cras_observer_ops_value.output_mute_changed);
+  RegisterNotification(
+      CRAS_CLIENT_OUTPUT_MUTE_CHANGED, NULL,
+      (void **)&cras_observer_ops_value.output_mute_changed);
+
+  RegisterNotification(
+      CRAS_CLIENT_CAPTURE_MUTE_CHANGED,
+      (void *)send_capture_mute_changed,
+      (void **)&cras_observer_ops_value.capture_mute_changed);
+  RegisterNotification(
+      CRAS_CLIENT_CAPTURE_MUTE_CHANGED, NULL,
+      (void **)&cras_observer_ops_value.capture_mute_changed);
+
+  RegisterNotification(
+      CRAS_CLIENT_NODES_CHANGED,
+      (void *)send_nodes_changed,
+      (void **)&cras_observer_ops_value.nodes_changed);
+  RegisterNotification(
+      CRAS_CLIENT_NODES_CHANGED, NULL,
+      (void **)&cras_observer_ops_value.nodes_changed);
+
+  RegisterNotification(
+      CRAS_CLIENT_ACTIVE_NODE_CHANGED,
+      (void *)send_active_node_changed,
+      (void **)&cras_observer_ops_value.active_node_changed);
+  RegisterNotification(
+      CRAS_CLIENT_ACTIVE_NODE_CHANGED, NULL,
+      (void **)&cras_observer_ops_value.active_node_changed);
+
+  RegisterNotification(
+      CRAS_CLIENT_OUTPUT_NODE_VOLUME_CHANGED,
+      (void *)send_output_node_volume_changed,
+      (void **)&cras_observer_ops_value.output_node_volume_changed);
+  RegisterNotification(
+      CRAS_CLIENT_OUTPUT_NODE_VOLUME_CHANGED, NULL,
+      (void **)&cras_observer_ops_value.output_node_volume_changed);
+
+  RegisterNotification(
+      CRAS_CLIENT_NODE_LEFT_RIGHT_SWAPPED_CHANGED,
+      (void *)send_node_left_right_swapped_changed,
+      (void **)&cras_observer_ops_value.node_left_right_swapped_changed);
+  RegisterNotification(
+      CRAS_CLIENT_NODE_LEFT_RIGHT_SWAPPED_CHANGED, NULL,
+      (void **)&cras_observer_ops_value.node_left_right_swapped_changed);
+
+  RegisterNotification(
+      CRAS_CLIENT_INPUT_NODE_GAIN_CHANGED,
+      (void *)send_input_node_gain_changed,
+      (void **)&cras_observer_ops_value.input_node_gain_changed);
+  RegisterNotification(
+      CRAS_CLIENT_INPUT_NODE_GAIN_CHANGED, NULL,
+      (void **)&cras_observer_ops_value.input_node_gain_changed);
+
+  RegisterNotification(
+      CRAS_CLIENT_NUM_ACTIVE_STREAMS_CHANGED,
+      (void *)send_num_active_streams_changed,
+      (void **)&cras_observer_ops_value.num_active_streams_changed);
+  RegisterNotification(
+      CRAS_CLIENT_NUM_ACTIVE_STREAMS_CHANGED, NULL,
+      (void **)&cras_observer_ops_value.num_active_streams_changed);
+
+  /* Deregister last. */
+  RegisterNotification(
+      CRAS_CLIENT_CAPTURE_GAIN_CHANGED, NULL,
+      (void **)&cras_observer_ops_value.capture_gain_changed);
+}
+
+TEST_F(RClientMessagesSuite, SendOutputVolumeChanged) {
+  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 volume = 90;
+
+  send_output_volume_changed(void_client, volume);
+  rc = read(pipe_fds_[0], buf, sizeof(buf));
+  ASSERT_EQ(rc, (ssize_t)sizeof(*msg));
+  EXPECT_EQ(msg->header.id, CRAS_CLIENT_OUTPUT_VOLUME_CHANGED);
+  EXPECT_EQ(msg->volume, volume);
+}
+
+TEST_F(RClientMessagesSuite, SendOutputMuteChanged) {
+  void *void_client = reinterpret_cast<void *>(rclient_);
+  char buf[1024];
+  ssize_t rc;
+  struct cras_client_mute_changed *msg =
+      (struct cras_client_mute_changed *)buf;
+  const int muted = 1;
+  const int user_muted = 0;
+  const int mute_locked = 1;
+
+  send_output_mute_changed(void_client, muted, user_muted, mute_locked);
+  rc = read(pipe_fds_[0], buf, sizeof(buf));
+  ASSERT_EQ(rc, (ssize_t)sizeof(*msg));
+  EXPECT_EQ(msg->header.id, CRAS_CLIENT_OUTPUT_MUTE_CHANGED);
+  EXPECT_EQ(msg->muted, muted);
+  EXPECT_EQ(msg->user_muted, user_muted);
+  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];
+  ssize_t rc;
+  struct cras_client_mute_changed *msg =
+      (struct cras_client_mute_changed *)buf;
+  const int muted = 1;
+  const int mute_locked = 0;
+
+  send_capture_mute_changed(void_client, muted, mute_locked);
+  rc = read(pipe_fds_[0], buf, sizeof(buf));
+  ASSERT_EQ(rc, (ssize_t)sizeof(*msg));
+  EXPECT_EQ(msg->header.id, CRAS_CLIENT_CAPTURE_MUTE_CHANGED);
+  EXPECT_EQ(msg->muted, muted);
+  EXPECT_EQ(msg->mute_locked, mute_locked);
+}
+
+TEST_F(RClientMessagesSuite, SendNodesChanged) {
+  void *void_client = reinterpret_cast<void *>(rclient_);
+  char buf[1024];
+  ssize_t rc;
+  struct cras_client_nodes_changed *msg =
+      (struct cras_client_nodes_changed *)buf;
+
+  send_nodes_changed(void_client);
+  rc = read(pipe_fds_[0], buf, sizeof(buf));
+  ASSERT_EQ(rc, (ssize_t)sizeof(*msg));
+  EXPECT_EQ(msg->header.id, CRAS_CLIENT_NODES_CHANGED);
+}
+
+TEST_F(RClientMessagesSuite, SendActiveNodeChanged) {
+  void *void_client = reinterpret_cast<void *>(rclient_);
+  char buf[1024];
+  ssize_t rc;
+  struct cras_client_active_node_changed *msg =
+      (struct cras_client_active_node_changed *)buf;
+  const enum CRAS_STREAM_DIRECTION dir = CRAS_STREAM_INPUT;
+  const cras_node_id_t node_id = 0x0001000200030004;
+
+  send_active_node_changed(void_client, dir, node_id);
+  rc = read(pipe_fds_[0], buf, sizeof(buf));
+  ASSERT_EQ(rc, (ssize_t)sizeof(*msg));
+  EXPECT_EQ(msg->header.id, CRAS_CLIENT_ACTIVE_NODE_CHANGED);
+  EXPECT_EQ(msg->direction, (int32_t)dir);
+  EXPECT_EQ(msg->node_id, node_id);
+}
+
+TEST_F(RClientMessagesSuite, SendOutputNodeVolumeChanged) {
+  void *void_client = reinterpret_cast<void *>(rclient_);
+  char buf[1024];
+  ssize_t rc;
+  struct cras_client_node_value_changed *msg =
+      (struct cras_client_node_value_changed *)buf;
+  const cras_node_id_t node_id = 0x0001000200030004;
+  const int32_t value = 90;
+
+  send_output_node_volume_changed(void_client, node_id, value);
+  rc = read(pipe_fds_[0], buf, sizeof(buf));
+  ASSERT_EQ(rc, (ssize_t)sizeof(*msg));
+  EXPECT_EQ(msg->header.id, CRAS_CLIENT_OUTPUT_NODE_VOLUME_CHANGED);
+  EXPECT_EQ(msg->node_id, node_id);
+  EXPECT_EQ(msg->value, value);
+}
+
+TEST_F(RClientMessagesSuite, SendNodeLeftRightSwappedChanged) {
+  void *void_client = reinterpret_cast<void *>(rclient_);
+  char buf[1024];
+  ssize_t rc;
+  struct cras_client_node_value_changed *msg =
+      (struct cras_client_node_value_changed *)buf;
+  const cras_node_id_t node_id = 0x0001000200030004;
+  const int32_t value = 0;
+
+  send_node_left_right_swapped_changed(void_client, node_id, value);
+  rc = read(pipe_fds_[0], buf, sizeof(buf));
+  ASSERT_EQ(rc, (ssize_t)sizeof(*msg));
+  EXPECT_EQ(msg->header.id, CRAS_CLIENT_NODE_LEFT_RIGHT_SWAPPED_CHANGED);
+  EXPECT_EQ(msg->node_id, node_id);
+  EXPECT_EQ(msg->value, value);
+}
+
+TEST_F(RClientMessagesSuite, SendNodeInputNodeGainChanged) {
+  void *void_client = reinterpret_cast<void *>(rclient_);
+  char buf[1024];
+  ssize_t rc;
+  struct cras_client_node_value_changed *msg =
+      (struct cras_client_node_value_changed *)buf;
+  const cras_node_id_t node_id = 0x0001000200030004;
+  const int32_t value = -19;
+
+  send_input_node_gain_changed(void_client, node_id, value);
+  rc = read(pipe_fds_[0], buf, sizeof(buf));
+  ASSERT_EQ(rc, (ssize_t)sizeof(*msg));
+  EXPECT_EQ(msg->header.id, CRAS_CLIENT_INPUT_NODE_GAIN_CHANGED);
+  EXPECT_EQ(msg->node_id, node_id);
+  EXPECT_EQ(msg->value, value);
+}
+
+TEST_F(RClientMessagesSuite, SendNumActiveStreamsChanged) {
+  void *void_client = reinterpret_cast<void *>(rclient_);
+  char buf[1024];
+  ssize_t rc;
+  struct cras_client_num_active_streams_changed *msg =
+      (struct cras_client_num_active_streams_changed *)buf;
+  const enum CRAS_STREAM_DIRECTION dir = CRAS_STREAM_INPUT;
+  const uint32_t num_active_streams = 3;
+
+  send_num_active_streams_changed(void_client, dir, num_active_streams);
+  rc = read(pipe_fds_[0], buf, sizeof(buf));
+  ASSERT_EQ(rc, (ssize_t)sizeof(*msg));
+  EXPECT_EQ(msg->header.id, CRAS_CLIENT_NUM_ACTIVE_STREAMS_CHANGED);
+  EXPECT_EQ(msg->direction, (int32_t)dir);
+  EXPECT_EQ(msg->num_active_streams, num_active_streams);
+}
+
 }  //  namespace
 
 int main(int argc, char **argv) {
@@ -345,9 +669,16 @@
   return 0;
 }
 
+int audio_thread_config_global_remix(struct audio_thread *thread,
+    unsigned int num_channels,
+    const float *coefficient)
+{
+  return 0;
+}
+
 const char *cras_config_get_socket_file_dir()
 {
-  return "/tmp";
+  return CRAS_UT_TMPDIR;
 }
 
 int cras_rstream_create(struct cras_rstream_config *stream_config,
@@ -434,7 +765,7 @@
   return NULL;
 }
 
-key_t cras_sys_state_shm_key()
+key_t cras_sys_state_shm_fd()
 {
   return 1;
 }
@@ -447,8 +778,8 @@
 {
 }
 
-int cras_iodev_list_set_node_attr(int dev_index, int node_index,
-                                  enum ionode_attr attr, int value)
+int cras_iodev_list_set_node_attr(cras_node_id_t id,
+				  enum ionode_attr attr, int value)
 {
   return 0;
 }
@@ -473,6 +804,12 @@
                                       const uint8_t *data) {
 }
 
+void cras_iodev_list_configure_global_remix_converter(
+    unsigned int num_channels,
+    const float *coefficient)
+{
+}
+
 int stream_list_add(struct stream_list *list,
                     struct cras_rstream_config *config,
 		    struct cras_rstream **stream)
@@ -492,17 +829,69 @@
   return ret;
 }
 
-struct cras_rstream *stream_list_rm(struct stream_list *list,
-                                    cras_stream_id_t id)
+int stream_list_rm(struct stream_list *list, cras_stream_id_t id)
 {
   stream_list_disconnect_stream_called++;
+  return 0;
+}
+
+int stream_list_rm_all_client_streams(struct stream_list *list,
+				      struct cras_rclient *rclient)
+{
+  return 0;
+}
+
+int cras_send_with_fds(int sockfd, const void *buf, size_t len, int *fd,
+                       unsigned int num_fds)
+{
+  return write(sockfd, buf, len);
+}
+
+char *cras_iodev_list_get_hotword_models(cras_node_id_t node_id)
+{
   return NULL;
 }
 
-struct cras_rstream *stream_list_rm_all_client_streams(
-                struct stream_list *list, struct cras_rclient *rclient)
+int cras_iodev_list_set_hotword_model(cras_node_id_t id,
+              const char *model_name)
 {
-  return NULL;
+  return 0;
+}
+
+struct cras_observer_client *cras_observer_add(
+			const struct cras_observer_ops *ops,
+			void *context)
+{
+  cras_observer_add_called++;
+  cras_observer_add_context_value = context;
+  memcpy(&cras_observer_ops_value, ops, sizeof(cras_observer_ops_value));
+  return cras_observer_add_return_value;
+}
+
+void cras_observer_get_ops(const struct cras_observer_client *client,
+			   struct cras_observer_ops *ops)
+{
+  cras_observer_get_ops_called++;
+  memcpy(ops, &cras_observer_ops_value, sizeof(*ops));
+}
+
+void cras_observer_set_ops(struct cras_observer_client *client,
+			   const struct cras_observer_ops *ops)
+{
+  cras_observer_set_ops_called++;
+  memcpy(&cras_observer_ops_value, ops, sizeof(cras_observer_ops_value));
+}
+
+int cras_observer_ops_are_empty(const struct cras_observer_ops *ops)
+{
+  cras_observer_ops_are_empty_called++;
+  return memcmp(&cras_observer_ops_are_empty_empty_ops, ops,
+                sizeof(cras_observer_ops_are_empty_empty_ops)) == 0;
+}
+
+void cras_observer_remove(struct cras_observer_client *client)
+{
+  cras_observer_remove_called++;
 }
 
 }  // extern "C"
diff --git a/cras/src/tests/rstream_unittest.cc b/cras/src/tests/rstream_unittest.cc
index 4e9496f..8034bfb 100644
--- a/cras/src/tests/rstream_unittest.cc
+++ b/cras/src/tests/rstream_unittest.cc
@@ -2,8 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <fcntl.h>
 #include <stdio.h>
-#include <sys/shm.h>
+#include <sys/mman.h>
+#include <sys/types.h>
 #include <gtest/gtest.h>
 
 extern "C" {
@@ -53,6 +55,15 @@
   EXPECT_NE(0, rc);
 }
 
+TEST_F(RstreamTestSuite, InvalidStreamType) {
+  struct cras_rstream *s;
+  int rc;
+
+  config_.stream_type = (enum CRAS_STREAM_TYPE)7;
+  rc = cras_rstream_create(&config_, &s);
+  EXPECT_NE(0, rc);
+}
+
 TEST_F(RstreamTestSuite, InvalidBufferSize) {
   struct cras_rstream *s;
   int rc;
@@ -83,7 +94,7 @@
   struct cras_audio_format fmt_ret;
   struct cras_audio_shm *shm_ret;
   struct cras_audio_shm shm_mapped;
-  int rc, key_ret, shmid;
+  int rc, fd_ret;
   size_t shm_size;
 
   rc = cras_rstream_create(&config_, &s);
@@ -101,16 +112,15 @@
   // Check if shm is really set up.
   shm_ret = cras_rstream_output_shm(s);
   ASSERT_NE((void *)NULL, shm_ret);
-  key_ret = cras_rstream_output_shm_key(s);
+  fd_ret = cras_rstream_output_shm_fd(s);
   shm_size = cras_rstream_get_total_shm_size(s);
   EXPECT_GT(shm_size, 4096);
-  shmid = shmget(key_ret, shm_size, 0600);
-  EXPECT_GE(shmid, 0);
-  shm_mapped.area = (struct cras_audio_shm_area *)shmat(shmid, NULL, 0);
+  shm_mapped.area = (struct cras_audio_shm_area *)mmap(
+      NULL, shm_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_ret, 0);
   EXPECT_NE((void *)NULL, shm_mapped.area);
   cras_shm_copy_shared_config(&shm_mapped);
   EXPECT_EQ(cras_shm_used_size(&shm_mapped), cras_shm_used_size(shm_ret));
-  shmdt(shm_mapped.area);
+  munmap(shm_mapped.area, shm_size);
 
   cras_rstream_destroy(s);
 }
@@ -120,7 +130,7 @@
   struct cras_audio_format fmt_ret;
   struct cras_audio_shm *shm_ret;
   struct cras_audio_shm shm_mapped;
-  int rc, key_ret, shmid;
+  int rc, fd_ret;
   size_t shm_size;
 
   config_.direction = CRAS_STREAM_INPUT;
@@ -139,20 +149,50 @@
   // Check if shm is really set up.
   shm_ret = cras_rstream_input_shm(s);
   ASSERT_NE((void *)NULL, shm_ret);
-  key_ret = cras_rstream_input_shm_key(s);
+  fd_ret = cras_rstream_input_shm_fd(s);
   shm_size = cras_rstream_get_total_shm_size(s);
   EXPECT_GT(shm_size, 4096);
-  shmid = shmget(key_ret, shm_size, 0600);
-  EXPECT_GE(shmid, 0);
-  shm_mapped.area = (struct cras_audio_shm_area *)shmat(shmid, NULL, 0);
+  shm_mapped.area = (struct cras_audio_shm_area *)mmap(
+      NULL, shm_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_ret, 0);
   EXPECT_NE((void *)NULL, shm_mapped.area);
   cras_shm_copy_shared_config(&shm_mapped);
   EXPECT_EQ(cras_shm_used_size(&shm_mapped), cras_shm_used_size(shm_ret));
-  shmdt(shm_mapped.area);
+  munmap(shm_mapped.area, shm_size);
 
   cras_rstream_destroy(s);
 }
 
+TEST_F(RstreamTestSuite, VerifyStreamTypes) {
+  struct cras_rstream *s;
+  int rc;
+
+  config_.stream_type = CRAS_STREAM_TYPE_DEFAULT;
+  rc = cras_rstream_create(&config_, &s);
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(CRAS_STREAM_TYPE_DEFAULT, cras_rstream_get_type(s));
+  EXPECT_NE(CRAS_STREAM_TYPE_MULTIMEDIA, cras_rstream_get_type(s));
+  cras_rstream_destroy(s);
+
+  config_.stream_type = CRAS_STREAM_TYPE_VOICE_COMMUNICATION;
+  rc = cras_rstream_create(&config_, &s);
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(CRAS_STREAM_TYPE_VOICE_COMMUNICATION, cras_rstream_get_type(s));
+  cras_rstream_destroy(s);
+
+  config_.direction = CRAS_STREAM_INPUT;
+  config_.stream_type = CRAS_STREAM_TYPE_SPEECH_RECOGNITION;
+  rc = cras_rstream_create(&config_, &s);
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(CRAS_STREAM_TYPE_SPEECH_RECOGNITION, cras_rstream_get_type(s));
+  cras_rstream_destroy(s);
+
+  config_.stream_type = CRAS_STREAM_TYPE_PRO_AUDIO;
+  rc = cras_rstream_create(&config_, &s);
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ(CRAS_STREAM_TYPE_PRO_AUDIO, cras_rstream_get_type(s));
+  cras_rstream_destroy(s);
+}
+
 }  //  namespace
 
 int main(int argc, char **argv) {
@@ -163,12 +203,6 @@
 /* stubs */
 extern "C" {
 
-int cras_rclient_send_message(const struct cras_rclient *client,
-            const struct cras_message *msg)
-{
-  return 0;
-}
-
 struct cras_audio_area *cras_audio_area_create(int num_channels) {
   return NULL;
 }
diff --git a/cras/src/tests/server_metrics_unittest.cc b/cras/src/tests/server_metrics_unittest.cc
new file mode 100644
index 0000000..db1b69f
--- /dev/null
+++ b/cras/src/tests/server_metrics_unittest.cc
@@ -0,0 +1,72 @@
+// Copyright 2016 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 <stdio.h>
+#include <gtest/gtest.h>
+
+extern "C" {
+#include "cras_server_metrics.c"
+#include "cras_main_message.h"
+}
+
+static enum CRAS_MAIN_MESSAGE_TYPE type_set;
+static struct cras_server_metrics_message *sent_msg;
+
+void ResetStubData() {
+  type_set = (enum CRAS_MAIN_MESSAGE_TYPE)0;
+}
+
+namespace {
+
+TEST(ServerMetricsTestSuite, Init) {
+  ResetStubData();
+
+  cras_server_metrics_init();
+
+  EXPECT_EQ(type_set, CRAS_MAIN_METRICS);
+}
+
+TEST(ServerMetricsTestSuite, SetMetrics) {
+  ResetStubData();
+  unsigned int delay = 100;
+  sent_msg = (struct cras_server_metrics_message *)calloc(1, sizeof(*sent_msg));
+
+  cras_server_metrics_longest_fetch_delay(delay);
+
+  EXPECT_EQ(sent_msg->header.type, CRAS_MAIN_METRICS);
+  EXPECT_EQ(sent_msg->header.length, sizeof(*sent_msg));
+  EXPECT_EQ(sent_msg->metrics_type, LONGEST_FETCH_DELAY);
+  EXPECT_EQ(sent_msg->data, delay);
+
+  free(sent_msg);
+}
+
+extern "C" {
+
+int cras_main_message_add_handler(enum CRAS_MAIN_MESSAGE_TYPE type,
+                                  cras_message_callback callback,
+                                  void *callback_data) {
+  type_set = type;
+  return 0;
+}
+
+void cras_metrics_log_histogram(const char *name, int sample, int min,
+                                int max, int nbuckets) {
+}
+
+int cras_main_message_send(struct cras_main_message *msg) {
+  // Copy the sent message so we can examine it in the test later.
+  memcpy(sent_msg, msg, sizeof(*sent_msg));
+  return 0;
+};
+
+}  // extern "C"
+}  // namespace
+
+int main(int argc, char **argv) {
+  ::testing::InitGoogleTest(&argc, argv);
+  int rc = RUN_ALL_TESTS();
+
+  return rc;
+}
diff --git a/cras/src/tests/shm_unittest.cc b/cras/src/tests/shm_unittest.cc
index e1bdc7d..c0bec89 100644
--- a/cras/src/tests/shm_unittest.cc
+++ b/cras/src/tests/shm_unittest.cc
@@ -17,7 +17,8 @@
     virtual void SetUp() {
       memset(&shm_, 0, sizeof(shm_));
       shm_.area =
-          static_cast<cras_audio_shm_area *>(calloc(1, sizeof(*shm_.area)));
+          static_cast<cras_audio_shm_area *>(
+              calloc(1, sizeof(*shm_.area) + 2048));
       cras_shm_set_frame_bytes(&shm_, 4);
       cras_shm_set_used_size(&shm_, 1024);
       memcpy(&shm_.area->config, &shm_.config, sizeof(shm_.config));
@@ -223,6 +224,33 @@
   EXPECT_EQ(shm_.config.used_size / 4, frames_);
 }
 
+TEST_F(ShmTestSuite, InputBufferOverrun) {
+  int rc;
+  shm_.area->write_offset[0] = 0;
+  shm_.area->read_offset[0] = 0;
+  shm_.area->write_offset[1] = 0;
+  shm_.area->read_offset[1] = 0;
+
+  shm_.area->write_buf_idx = 0;
+  shm_.area->read_buf_idx = 0;
+
+  EXPECT_EQ(0, cras_shm_num_overruns(&shm_));
+  rc = cras_shm_check_write_overrun(&shm_);
+  EXPECT_EQ(0, rc);
+  cras_shm_buffer_written(&shm_, 100);
+  cras_shm_buffer_write_complete(&shm_);
+
+  rc = cras_shm_check_write_overrun(&shm_);
+  EXPECT_EQ(0, rc);
+  cras_shm_buffer_written(&shm_, 100);
+  cras_shm_buffer_write_complete(&shm_);
+
+  // Assert two consecutive writes causes overrun.
+  rc = cras_shm_check_write_overrun(&shm_);
+  EXPECT_EQ(1, rc);
+  EXPECT_EQ(1, cras_shm_num_overruns(&shm_));
+}
+
 }  //  namespace
 
 int main(int argc, char **argv) {
diff --git a/cras/src/tests/stream_list_unittest.cc b/cras/src/tests/stream_list_unittest.cc
index 64ec0ab..f2e8fbb 100644
--- a/cras/src/tests/stream_list_unittest.cc
+++ b/cras/src/tests/stream_list_unittest.cc
@@ -1,9 +1,8 @@
-// Copyright (c) 2015 The Chromium OS Authors. All rights reserved.
+// Copyright 2015 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 <stdio.h>
-#include <sys/shm.h>
 #include <gtest/gtest.h>
 
 extern "C" {
diff --git a/cras/src/tests/system_state_unittest.cc b/cras/src/tests/system_state_unittest.cc
index ea3e57d..5816b77 100644
--- a/cras/src/tests/system_state_unittest.cc
+++ b/cras/src/tests/system_state_unittest.cc
@@ -6,6 +6,7 @@
 #include <gtest/gtest.h>
 
 extern "C" {
+#include "cras_alert.h"
 #include "cras_system_state.h"
 #include "cras_types.h"
 }
@@ -27,6 +28,12 @@
 static size_t alert_pending_called;
 static char* device_config_dir;
 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 void ResetStubData() {
   cras_alsa_card_create_called = 0;
@@ -40,27 +47,12 @@
   alert_pending_called = 0;
   device_config_dir = reinterpret_cast<char *>(3);
   cras_alsa_card_config_dir = NULL;
-}
-
-static void volume_changed(void *arg) {
-}
-
-static void volume_limits_changed(void *arg) {
-}
-
-static void volume_limits_changed_2(void *arg) {
-}
-
-static void capture_gain_changed(void *arg) {
-}
-
-static void mute_changed(void *arg) {
-}
-
-static void capture_mute_changed(void *arg) {
-}
-
-static void capture_mute_changed_2(void *arg) {
+  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;
 }
 
 static int add_stub(int fd, void (*cb)(void *data),
@@ -99,6 +91,7 @@
   cras_system_set_volume(CRAS_MAX_SYSTEM_VOLUME + 1);
   EXPECT_EQ(CRAS_MAX_SYSTEM_VOLUME, cras_system_get_volume());
   cras_system_state_deinit();
+  EXPECT_EQ(4, cras_observer_notify_output_volume_called);
 }
 
 TEST(SystemStateSuite, SetMinMaxVolume) {
@@ -119,241 +112,132 @@
   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, VolumeChangedCallback) {
-  void * const fake_user_arg = (void *)1;
-  const size_t fake_volume = 55;
-  const size_t fake_volume_2 = 44;
-  int rc;
-
+TEST(SystemStateSuite, SetCaptureVolumeStoreTarget) {
   cras_system_state_init(device_config_dir);
-  ResetStubData();
-  cras_system_register_volume_changed_cb(volume_changed, fake_user_arg);
-  EXPECT_EQ(1, add_callback_called);
-  EXPECT_EQ((void *)volume_changed, (void *)add_callback_cb);
-  EXPECT_EQ(fake_user_arg, add_callback_arg);
+  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());
 
-  cras_system_set_volume(fake_volume);
-  EXPECT_EQ(fake_volume, cras_system_get_volume());
-  EXPECT_EQ(1, alert_pending_called);
+  // Assume the range is changed.
+  cras_system_set_capture_gain_limits(-4000, 4000);
 
-  rc = cras_system_remove_volume_changed_cb(volume_changed, fake_user_arg);
-  EXPECT_EQ(0, rc);
-  EXPECT_EQ(1, rm_callback_called);
-  EXPECT_EQ((void *)volume_changed, (void *)rm_callback_cb);
-  EXPECT_EQ(fake_user_arg, rm_callback_arg);
+  // Gain is also changed because target gain is re-applied.
+  EXPECT_EQ(3000, cras_system_get_capture_gain());
 
-  cras_system_set_volume(fake_volume_2);
-  EXPECT_EQ(fake_volume_2, cras_system_get_volume());
-  EXPECT_EQ(2, alert_pending_called);
   cras_system_state_deinit();
 }
 
-TEST(SystemStateSuite, VolumeLimitChangedCallbackMultiple) {
-  void * const fake_user_arg = (void *)1;
-  void * const fake_user_arg_2 = (void *)2;
-  const size_t fake_min = -10000;
-  const size_t fake_max = 800;
-  const size_t fake_min_2 = -4500;
-  const size_t fake_max_2 = -600;
-  int rc;
-
+TEST(SystemStateSuite, SetMinMaxCaptureGain) {
   cras_system_state_init(device_config_dir);
-  ResetStubData();
-  rc = cras_system_register_volume_limits_changed_cb(volume_limits_changed,
-                                                     fake_user_arg);
-  EXPECT_EQ(0, rc);
-  EXPECT_EQ(1, add_callback_called);
-  EXPECT_EQ((void *)volume_limits_changed, (void *)add_callback_cb);
-  EXPECT_EQ(fake_user_arg, add_callback_arg);
-
-  cras_system_register_volume_limits_changed_cb(volume_limits_changed_2,
-                                                fake_user_arg_2);
-  EXPECT_EQ(2, add_callback_called);
-  EXPECT_EQ((void *)volume_limits_changed_2, (void *)add_callback_cb);
-  EXPECT_EQ(fake_user_arg_2, add_callback_arg);
-
-  cras_system_set_volume_limits(fake_min, fake_max);
-  cras_system_set_capture_gain_limits(fake_min_2, fake_max_2);
-  EXPECT_EQ(fake_min, cras_system_get_min_volume());
-  EXPECT_EQ(fake_max, cras_system_get_max_volume());
-  EXPECT_EQ(2, alert_pending_called);
-
-  cras_system_remove_volume_limits_changed_cb(volume_limits_changed,
-                                              fake_user_arg);
-  EXPECT_EQ(1, rm_callback_called);
-  EXPECT_EQ((void *)volume_limits_changed, (void *)rm_callback_cb);
-  EXPECT_EQ(fake_user_arg, rm_callback_arg);
-
-  cras_system_set_volume_limits(fake_min_2, fake_max_2);
-  EXPECT_EQ(fake_min_2, cras_system_get_min_volume());
-  EXPECT_EQ(fake_max_2, cras_system_get_max_volume());
-  EXPECT_EQ(3, alert_pending_called);
-
-  cras_system_remove_volume_limits_changed_cb(volume_limits_changed_2,
-                                              fake_user_arg_2);
-
-  cras_system_set_volume_limits(fake_min, fake_max);
-  EXPECT_EQ(fake_min, cras_system_get_min_volume());
-  EXPECT_EQ(fake_max, cras_system_get_max_volume());
-  EXPECT_EQ(4, alert_pending_called);
+  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, CaptureVolumeChangedCallback) {
-  void * const fake_user_arg = (void *)1;
-  const long fake_capture_gain = 2200;
-  const long fake_capture_gain_2 = -1600;
-  int rc;
-
-  cras_system_state_init(device_config_dir);
+TEST(SystemStateSuite, SetUserMute) {
   ResetStubData();
-  cras_system_register_capture_gain_changed_cb(capture_gain_changed,
-                                               fake_user_arg);
-  EXPECT_EQ(1, add_callback_called);
-  EXPECT_EQ((void *)capture_gain_changed, (void *)add_callback_cb);
-  EXPECT_EQ(fake_user_arg, add_callback_arg);
+  cras_system_state_init(device_config_dir);
 
-  cras_system_set_capture_gain(fake_capture_gain);
-  EXPECT_EQ(fake_capture_gain, cras_system_get_capture_gain());
-  EXPECT_EQ(1, alert_pending_called);
+  EXPECT_EQ(0, cras_system_get_mute());
 
-  rc = cras_system_remove_capture_gain_changed_cb(capture_gain_changed,
-                                                  fake_user_arg);
-  EXPECT_EQ(0, rc);
-  EXPECT_EQ(1, rm_callback_called);
-  EXPECT_EQ((void *)capture_gain_changed, (void *)rm_callback_cb);
-  EXPECT_EQ(fake_user_arg, rm_callback_arg);
+  cras_system_set_user_mute(0);
+  EXPECT_EQ(0, cras_system_get_mute());
+  EXPECT_EQ(0, cras_observer_notify_output_mute_called);
 
-  cras_system_set_capture_gain(fake_capture_gain_2);
-  EXPECT_EQ(fake_capture_gain_2, cras_system_get_capture_gain());
-  EXPECT_EQ(2, alert_pending_called);
+  cras_system_set_user_mute(1);
+  EXPECT_EQ(1, cras_system_get_mute());
+  EXPECT_EQ(1, cras_observer_notify_output_mute_called);
+
+  cras_system_set_user_mute(22);
+  EXPECT_EQ(1, cras_system_get_mute());
+  EXPECT_EQ(1, cras_observer_notify_output_mute_called);
+
   cras_system_state_deinit();
 }
 
 TEST(SystemStateSuite, SetMute) {
+  ResetStubData();
   cras_system_state_init(device_config_dir);
+
   EXPECT_EQ(0, cras_system_get_mute());
+
   cras_system_set_mute(0);
   EXPECT_EQ(0, cras_system_get_mute());
+  EXPECT_EQ(0, cras_observer_notify_output_mute_called);
+
   cras_system_set_mute(1);
   EXPECT_EQ(1, cras_system_get_mute());
+  EXPECT_EQ(1, cras_observer_notify_output_mute_called);
+
   cras_system_set_mute(22);
   EXPECT_EQ(1, cras_system_get_mute());
-  cras_system_state_deinit();
-}
+  EXPECT_EQ(1, cras_observer_notify_output_mute_called);
 
-TEST(SystemStateSuite, MuteChangedCallback) {
-  void * const fake_user_arg = (void *)1;
-  int rc;
-
-  cras_system_state_init(device_config_dir);
-  ResetStubData();
-  cras_system_register_mute_changed_cb(mute_changed, fake_user_arg);
-  EXPECT_EQ(1, add_callback_called);
-  EXPECT_EQ((void *)mute_changed, (void *)add_callback_cb);
-  EXPECT_EQ(fake_user_arg, add_callback_arg);
-
-  cras_system_set_mute(1);
-  EXPECT_EQ(1, cras_system_get_mute());
-  EXPECT_EQ(1, alert_pending_called);
-
-  rc = cras_system_remove_mute_changed_cb(mute_changed, fake_user_arg);
-  EXPECT_EQ(0, rc);
-  EXPECT_EQ(1, rm_callback_called);
-  EXPECT_EQ((void *)mute_changed, (void *)rm_callback_cb);
-  EXPECT_EQ(fake_user_arg, rm_callback_arg);
-
-  cras_system_set_mute(0);
-  EXPECT_EQ(0, cras_system_get_mute());
-  EXPECT_EQ(2, alert_pending_called);
   cras_system_state_deinit();
 }
 
 TEST(SystemStateSuite, CaptureMuteChangedCallbackMultiple) {
-  void * const fake_arg = (void *)1;
-  void * const fake_arg_2 = (void *)2;
-  int rc;
-
   cras_system_state_init(device_config_dir);
   ResetStubData();
-  rc = cras_system_register_capture_mute_changed_cb(capture_mute_changed,
-                                                    fake_arg);
-  EXPECT_EQ(0, rc);
-  EXPECT_EQ(1, add_callback_called);
-  EXPECT_EQ((void *)capture_mute_changed, (void *)add_callback_cb);
-  EXPECT_EQ(fake_arg, add_callback_arg);
-
-  rc = cras_system_register_capture_mute_changed_cb(capture_mute_changed_2,
-                                                    fake_arg_2);
-  EXPECT_EQ(0, rc);
-  EXPECT_EQ(2, add_callback_called);
-  EXPECT_EQ((void *)capture_mute_changed_2, (void *)add_callback_cb);
-  EXPECT_EQ(fake_arg_2, add_callback_arg);
 
   cras_system_set_capture_mute(1);
   EXPECT_EQ(1, cras_system_get_capture_mute());
-  EXPECT_EQ(1, alert_pending_called);
-
-  rc = cras_system_remove_capture_mute_changed_cb(capture_mute_changed,
-                                                  fake_arg);
-  EXPECT_EQ(0, rc);
-  EXPECT_EQ(1, rm_callback_called);
-  EXPECT_EQ((void *)capture_mute_changed, (void *)rm_callback_cb);
-  EXPECT_EQ(fake_arg, rm_callback_arg);
-
+  EXPECT_EQ(1, cras_observer_notify_capture_mute_called);
   cras_system_set_capture_mute(0);
   EXPECT_EQ(0, cras_system_get_capture_mute());
-  EXPECT_EQ(2, alert_pending_called);
+  EXPECT_EQ(2, cras_observer_notify_capture_mute_called);
 
-  rc = cras_system_remove_capture_mute_changed_cb(capture_mute_changed_2,
-                                                  fake_arg_2);
-  EXPECT_EQ(0, rc);
   cras_system_state_deinit();
 }
 
 TEST(SystemStateSuite, MuteLocked) {
-  void * const fake_user_arg = (void *)1;
-  int rc;
-
   cras_system_state_init(device_config_dir);
   ResetStubData();
 
-  cras_system_register_mute_changed_cb(mute_changed, fake_user_arg);
-  EXPECT_EQ(1, add_callback_called);
-  EXPECT_EQ((void *)mute_changed, (void *)add_callback_cb);
-  EXPECT_EQ(fake_user_arg, add_callback_arg);
-
   cras_system_set_mute(1);
   EXPECT_EQ(1, cras_system_get_mute());
   EXPECT_EQ(0, cras_system_get_mute_locked());
-  EXPECT_EQ(1, alert_pending_called);
+  EXPECT_EQ(1, cras_observer_notify_output_mute_called);
 
   cras_system_set_mute_locked(1);
   cras_system_set_mute(0);
   EXPECT_EQ(1, cras_system_get_mute());
   EXPECT_EQ(1, cras_system_get_mute_locked());
-  EXPECT_EQ(1, alert_pending_called);
+  EXPECT_EQ(2, cras_observer_notify_output_mute_called);
 
-  rc = cras_system_remove_mute_changed_cb(mute_changed, fake_user_arg);
-  EXPECT_EQ(0, rc);
-  EXPECT_EQ(1, rm_callback_called);
-  EXPECT_EQ((void *)mute_changed, (void *)rm_callback_cb);
-  EXPECT_EQ(fake_user_arg, rm_callback_arg);
-
-  cras_system_register_capture_mute_changed_cb(capture_mute_changed,
-                                               fake_user_arg);
   cras_system_set_capture_mute(1);
   EXPECT_EQ(1, cras_system_get_capture_mute());
   EXPECT_EQ(0, cras_system_get_capture_mute_locked());
-  EXPECT_EQ(2, alert_pending_called);
+  EXPECT_EQ(1, cras_observer_notify_capture_mute_called);
 
   cras_system_set_capture_mute_locked(1);
   cras_system_set_capture_mute(0);
   EXPECT_EQ(1, cras_system_get_capture_mute());
   EXPECT_EQ(1, cras_system_get_capture_mute_locked());
-  EXPECT_EQ(2, alert_pending_called);
+  cras_system_state_deinit();
+  EXPECT_EQ(2, cras_observer_notify_capture_mute_called);
+}
+
+TEST(SystemStateSuite, Suspend) {
+  cras_system_state_init(device_config_dir);
+  ResetStubData();
+
+  cras_system_set_suspended(1);
+  EXPECT_EQ(1, cras_observer_notify_suspend_changed_called);
+  EXPECT_EQ(1, cras_system_get_suspended());
+
+  cras_system_set_suspended(0);
+  EXPECT_EQ(2, cras_observer_notify_suspend_changed_called);
+  EXPECT_EQ(0, cras_system_get_suspended());
+
   cras_system_state_deinit();
 }
 
@@ -368,6 +252,7 @@
   EXPECT_EQ(-ENOMEM, cras_system_add_alsa_card(&info));
   EXPECT_EQ(1, cras_alsa_card_create_called);
   EXPECT_EQ(cras_alsa_card_config_dir, device_config_dir);
+  cras_system_state_deinit();
 }
 
 TEST(SystemStateSuite, AddCard) {
@@ -387,6 +272,7 @@
   // Removing card should destroy it.
   cras_system_remove_alsa_card(0);
   EXPECT_EQ(1, cras_alsa_card_destroy_called);
+  cras_system_state_deinit();
 }
 
 TEST(SystemSettingsRegisterSelectDescriptor, AddSelectFd) {
@@ -454,6 +340,7 @@
 	cras_system_state_get_active_streams_by_direction(
 		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);
@@ -467,6 +354,7 @@
 	cras_system_state_get_active_streams_by_direction(
 		CRAS_STREAM_POST_MIX_PRE_DSP));
   EXPECT_EQ(0, cras_system_state_get_active_streams());
+  EXPECT_EQ(6, cras_observer_notify_num_active_streams_called);
 
   cras_system_state_deinit();
 }
@@ -500,7 +388,8 @@
 {
 }
 
-struct cras_alert *cras_alert_create(cras_alert_prepare prepare)
+struct cras_alert *cras_alert_create(cras_alert_prepare prepare,
+                                     unsigned int flags)
 {
   return NULL;
 }
@@ -540,6 +429,38 @@
   free(tm);
 }
 
+void cras_observer_notify_output_volume(int32_t volume)
+{
+  cras_observer_notify_output_volume_called++;
+}
+
+void cras_observer_notify_output_mute(int muted, int user_muted,
+				      int mute_locked)
+{
+  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++;
+}
+
+void cras_observer_notify_suspend_changed(int suspended)
+{
+  cras_observer_notify_suspend_changed_called++;
+}
+
+void cras_observer_notify_num_active_streams(enum CRAS_STREAM_DIRECTION dir,
+					     uint32_t num_active_streams)
+{
+  cras_observer_notify_num_active_streams_called++;
+}
+
 }  // extern "C"
 }  // namespace
 
diff --git a/cras/src/tests/utf8_unittest.cc b/cras/src/tests/utf8_unittest.cc
new file mode 100644
index 0000000..34360c3
--- /dev/null
+++ b/cras/src/tests/utf8_unittest.cc
@@ -0,0 +1,154 @@
+// 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.
+
+// Some UTF character seqeuences in this file were taken from
+// https://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt
+
+#include <gtest/gtest.h>
+#include <stdio.h>
+
+extern "C" {
+#include "cras_utf8.h"
+}
+
+namespace {
+
+TEST(UTF8, ValidStress) {
+  size_t pos;
+
+  EXPECT_EQ(1, valid_utf8_string("The greek word 'kosme': "
+                                 "\xce\xba\xe1\xbd\xb9\xcf\x83\xce"
+                                 "\xbc\xce\xb5", &pos));
+  EXPECT_EQ(35, pos);
+
+  EXPECT_EQ(1, valid_utf8_string("Playback", &pos));
+  EXPECT_EQ(8, pos);
+
+  EXPECT_EQ(1, valid_utf8_string("The Euro sign: \xe2\x82\xac", &pos));
+  EXPECT_EQ(18, pos);
+
+  /* First possible sequence of a certain length. */
+  EXPECT_EQ(1, valid_utf8_string("\x01", &pos));
+  EXPECT_EQ(1, pos);
+  EXPECT_EQ(1, valid_utf8_string("\xc2\x80", &pos));
+  EXPECT_EQ(2, pos);
+  EXPECT_EQ(1, valid_utf8_string("\xe0\xa0\x80", &pos));
+  EXPECT_EQ(3, pos);
+  EXPECT_EQ(1, valid_utf8_string("\xe1\x80\x80", &pos));
+  EXPECT_EQ(3, pos);
+  EXPECT_EQ(1, valid_utf8_string("\xf0\x90\x80\x80", &pos));
+  EXPECT_EQ(4, pos);
+  EXPECT_EQ(1, valid_utf8_string("\xf1\x80\x80\x80", &pos));
+  EXPECT_EQ(4, pos);
+
+  /* Last possible sequence of a certain length. */
+  EXPECT_EQ(1, valid_utf8_string("\x7f", &pos));
+  EXPECT_EQ(1, pos);
+  EXPECT_EQ(1, valid_utf8_string("\xdf\xbf", &pos));
+  EXPECT_EQ(2, pos);
+  EXPECT_EQ(1, valid_utf8_string("\xef\xbf\xbf", &pos));
+  EXPECT_EQ(3, pos);
+  EXPECT_EQ(1, valid_utf8_string("\xf4\x8f\xbf\xbf", &pos));
+  EXPECT_EQ(4, pos);
+
+  /* Other boundary conditions. */
+  EXPECT_EQ(1, valid_utf8_string("\xed\x9f\xbf", &pos));
+  EXPECT_EQ(3, pos);
+  EXPECT_EQ(1, valid_utf8_string("\xee\x80\x80", &pos));
+  EXPECT_EQ(3, pos);
+  EXPECT_EQ(1, valid_utf8_string("\xef\xbf\xbd", &pos));
+  EXPECT_EQ(3, pos);
+  EXPECT_EQ(1, valid_utf8_string("\xf0\xbf\xbf\xbf", &pos));
+  EXPECT_EQ(4, pos);
+
+  /* BOM sequence. */
+  EXPECT_EQ(1, valid_utf8_string("\xef\xbb\xbf", &pos));
+  EXPECT_EQ(3, pos);
+
+  /* Valid UTF-8 that shouldn't appear in text; chose to allow
+   * these characters anyway. */
+  EXPECT_EQ(1, valid_utf8_string("U+FFFE: \xef\xbf\xbe", &pos));
+  EXPECT_EQ(11, pos);
+  EXPECT_EQ(1, valid_utf8_string("U+FDD0: \xef\xb7\x90", &pos));
+  EXPECT_EQ(11, pos);
+  EXPECT_EQ(1, valid_utf8_string("\xf0\x9f\xbf\xbe", &pos));
+  EXPECT_EQ(4, pos);
+}
+
+TEST(UTF8, InvalidStress) {
+  size_t pos;
+
+  /* Malformed continuation bytes. */
+  EXPECT_EQ(0, valid_utf8_string("\x80", &pos));
+  EXPECT_EQ(0, pos);
+  EXPECT_EQ(0, valid_utf8_string("\xbf", &pos));
+  EXPECT_EQ(0, pos);
+  EXPECT_EQ(0, valid_utf8_string("\x80\xbf", &pos));
+  EXPECT_EQ(0, pos);
+  EXPECT_EQ(0, valid_utf8_string("\xc2\x80\xbf", &pos));
+  EXPECT_EQ(2, pos);
+
+  /* Lonely start characters. */
+  EXPECT_EQ(0, valid_utf8_string("\xc2 \xc3 \xc4 ", &pos));
+  EXPECT_EQ(1, pos);
+
+  /* Out of range cases. */
+  EXPECT_EQ(0, valid_utf8_string("\xf4\x90\xbf\xbf", &pos));
+  EXPECT_EQ(1, pos);
+  EXPECT_EQ(0, valid_utf8_string(" \xf5\x80", &pos));
+  EXPECT_EQ(1, pos);
+  EXPECT_EQ(0, valid_utf8_string(" \xe0\x80\x80", &pos));
+  EXPECT_EQ(2, pos);
+  EXPECT_EQ(0, valid_utf8_string("\xf4\x80\x80\xcf", &pos));
+  EXPECT_EQ(3, pos);
+
+  /* Stop in mid-sequence. */
+  EXPECT_EQ(0, valid_utf8_string("\xf4\x80", &pos));
+  EXPECT_EQ(2, pos);
+
+  /* Bad characters. */
+  EXPECT_EQ(0, valid_utf8_string("\xff", &pos));
+  EXPECT_EQ(0, pos);
+  EXPECT_EQ(0, valid_utf8_string("\xfe", &pos));
+  EXPECT_EQ(0, pos);
+
+  /* Overlong representations of ASCII characters. */
+  EXPECT_EQ(0, valid_utf8_string("This represents the / character with too"
+                                 "many bytes: \xe0\x80\xaf", &pos));
+  EXPECT_EQ(53, pos);
+  EXPECT_EQ(0, valid_utf8_string("This represents the / character with too"
+                                 "many bytes: \xf0\x80\x80\xaf", &pos));
+  EXPECT_EQ(53, pos);
+
+  /* Should not be interpreted as the ASCII NUL character. */
+  EXPECT_EQ(0, valid_utf8_string("This represents the NUL character with too"
+                                 "many bytes: \xe0\x80\x80", &pos));
+  EXPECT_EQ(55, pos);
+  EXPECT_EQ(0, valid_utf8_string("This represents the NUL character with too"
+                                 "many bytes: \xf0\x80\x80\x80", &pos));
+  EXPECT_EQ(55, pos);
+
+  /* Single UTF-16 surrogates. */
+  EXPECT_EQ(0, valid_utf8_string("\xed\xa0\x80", &pos));
+  EXPECT_EQ(1, pos);
+  EXPECT_EQ(0, valid_utf8_string("\xed\xad\xbf", &pos));
+  EXPECT_EQ(1, pos);
+  EXPECT_EQ(0, valid_utf8_string("\xed\xae\x80", &pos));
+  EXPECT_EQ(1, pos);
+  EXPECT_EQ(0, valid_utf8_string("\xed\xaf\xbf", &pos));
+  EXPECT_EQ(1, pos);
+  EXPECT_EQ(0, valid_utf8_string("\xed\xb0\x80", &pos));
+  EXPECT_EQ(1, pos);
+  EXPECT_EQ(0, valid_utf8_string("\xed\xbe\x80", &pos));
+  EXPECT_EQ(1, pos);
+  EXPECT_EQ(0, valid_utf8_string("\xed\xbf\xbf", &pos));
+  EXPECT_EQ(1, pos);
+}
+
+}  //  namespace
+
+int main(int argc, char **argv) {
+  ::testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
diff --git a/cras/src/tests/util_unittest.cc b/cras/src/tests/util_unittest.cc
index 0c85dec..3dbc026 100644
--- a/cras/src/tests/util_unittest.cc
+++ b/cras/src/tests/util_unittest.cc
@@ -3,21 +3,73 @@
 // found in the LICENSE file.
 
 #include <gtest/gtest.h>
+#include <string>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <unistd.h>
+#include <vector>
 
 #include "cras_util.h"
 
 namespace {
 
-static struct timespec time_now;
+static std::vector<struct timespec> time_now;
 
-TEST(Util, SendRecvFileDescriptor) {
+TEST(Util, SendRecvTwoFileDescriptors) {
+  int fd[2];
+  int fd2[2];
+  int send_fds[2];
+  int sock[2];
+  char buf[256] = {0};
+  int new_fds[2];
+  char msg[] = "multi-fd";
+  unsigned int num_fds = 2;
+
+  /* Create a pipe and a pair of sockets. Then send the write end of
+   * the pipe (fd[1]) through the socket, and receive it as
+   * new_fd */
+  ASSERT_EQ(0, pipe(fd));
+  ASSERT_EQ(0, pipe(fd2));
+  ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sock));
+
+  send_fds[0] = fd[1];
+  send_fds[1] = fd2[1];
+  ASSERT_GE(cras_send_with_fds(sock[0], msg, strlen(msg), send_fds, num_fds),
+            0);
+  ASSERT_GE(cras_recv_with_fds(sock[1], buf, strlen(msg), new_fds, &num_fds),
+            0);
+  ASSERT_STREQ(msg, buf);
+  ASSERT_EQ(2, num_fds);
+  ASSERT_NE(-1, new_fds[0]);
+  ASSERT_NE(-1, new_fds[1]);
+
+  close(sock[0]);
+  close(sock[1]);
+  close(fd[1]);
+  close(fd2[1]);
+
+  /* Send a character to the new_fd, and receive it from the read end
+   * of the pipe (fd[0]) */
+  ASSERT_EQ(1, write(new_fds[0], "a", 1));
+  ASSERT_EQ(1, read(fd[0], buf, 1));
+  ASSERT_EQ('a', buf[0]);
+  ASSERT_EQ(1, write(new_fds[1], "b", 1));
+  ASSERT_EQ(1, read(fd2[0], buf, 1));
+  ASSERT_EQ('b', buf[0]);
+
+  close(fd[0]);
+  close(fd2[0]);
+  close(new_fds[0]);
+  close(new_fds[1]);
+}
+
+TEST(Util, SendOneRecvTwoFileDescriptors) {
   int fd[2];
   int sock[2];
-  char buf[6] = {0};
-  int new_fd;
+  char buf[256] = {0};
+  int new_fds[2];
+  char msg[] = "multi-fd";
+  unsigned int num_fds = 2;
 
   /* Create a pipe and a pair of sockets. Then send the write end of
    * the pipe (fd[1]) through the socket, and receive it as
@@ -25,9 +77,48 @@
   ASSERT_EQ(0, pipe(fd));
   ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sock));
 
-  ASSERT_EQ(5, cras_send_with_fd(sock[0], "hello", 5, fd[1]));
-  ASSERT_EQ(5, cras_recv_with_fd(sock[1], buf, 5, &new_fd));
-  ASSERT_STREQ("hello", buf);
+  ASSERT_GE(cras_send_with_fds(sock[0], msg, strlen(msg), &fd[1], 1), 0);
+  ASSERT_GE(cras_recv_with_fds(sock[1], buf, strlen(msg), new_fds, &num_fds),
+            0);
+  ASSERT_STREQ(msg, buf);
+  ASSERT_EQ(1, num_fds);
+  ASSERT_NE(-1, new_fds[0]);
+  ASSERT_EQ(-1, new_fds[1]);
+
+  close(sock[0]);
+  close(sock[1]);
+  close(fd[1]);
+
+  /* Send a character to the new_fd, and receive it from the read end
+   * of the pipe (fd[0]) */
+  ASSERT_EQ(1, write(new_fds[0], "a", 1));
+  ASSERT_EQ(1, read(fd[0], buf, 1));
+  ASSERT_EQ('a', buf[0]);
+
+  close(fd[0]);
+  close(new_fds[0]);
+  close(new_fds[1]);
+}
+
+TEST(Util, SendRecvFileDescriptor) {
+  int fd[2];
+  int sock[2];
+  char buf[256] = {0};
+  int new_fd;
+  char msg[] = "hello";
+  unsigned int num_fds = 1;
+
+  /* Create a pipe and a pair of sockets. Then send the write end of
+   * the pipe (fd[1]) through the socket, and receive it as
+   * new_fd */
+  ASSERT_EQ(0, pipe(fd));
+  ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sock));
+
+  ASSERT_EQ(5, cras_send_with_fds(sock[0], msg, strlen(msg), &fd[1], num_fds));
+  ASSERT_EQ(5,
+            cras_recv_with_fds(sock[1], buf, strlen(msg), &new_fd, &num_fds));
+  ASSERT_STREQ(msg, buf);
+  ASSERT_EQ(1, num_fds);
 
   close(sock[0]);
   close(sock[1]);
@@ -43,6 +134,23 @@
   close(new_fd);
 }
 
+TEST(Util, SendRecvNoDescriptors) {
+  char buf[256] = {0};
+  char msg[] = "no descriptors";
+  unsigned int num_fds = 0;
+  int sock[2];
+
+  ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sock));
+
+  ASSERT_GE(cras_send_with_fds(sock[0], msg, strlen(msg), NULL, num_fds), 0);
+  ASSERT_GE(cras_recv_with_fds(sock[1], buf, strlen(msg), NULL, &num_fds), 0);
+  ASSERT_STREQ(msg, buf);
+  ASSERT_EQ(0, num_fds);
+
+  close(sock[0]);
+  close(sock[1]);
+}
+
 TEST(Util, TimevalAfter) {
   struct timeval t0, t1;
   t0.tv_sec = 0;
@@ -137,28 +245,79 @@
 }
 
 TEST(Util, FramesSinceTime) {
-  struct timespec t;
+  struct timespec t, tn;
   unsigned int frames;
 
   t.tv_sec = 0;
   t.tv_nsec = 500000000;
 
-  time_now.tv_sec = 2;
-  time_now.tv_nsec = 0;
+  tn.tv_sec = 2;
+  tn.tv_nsec = 0;
+  time_now.push_back(tn);
   frames = cras_frames_since_time(&t, 48000);
   EXPECT_EQ(72000, frames);
 
-  time_now.tv_sec = 0;
-  time_now.tv_nsec = 0;
+  tn.tv_sec = 0;
+  time_now.push_back(tn);
   frames = cras_frames_since_time(&t, 48000);
   EXPECT_EQ(0, frames);
 }
 
+// Test cras_poll().
+TEST(Util, CrasPoll) {
+  int pipe_fds[2];
+  struct pollfd poll_fd;
+  std::string output;
+  struct timespec timeout;
+  char buf[256];
+
+  ASSERT_EQ(0, pipe(pipe_fds));
+  poll_fd.fd = pipe_fds[0];
+  poll_fd.events = POLLIN;
+  ASSERT_NE(0, poll_fd.fd >= 0);
+
+  // Simple poll.
+  output = "Hello";
+  EXPECT_EQ(output.size() + 1,
+            write(pipe_fds[1], output.c_str(), output.size() + 1));
+  EXPECT_EQ(1, cras_poll(&poll_fd, 1, NULL, NULL));
+  ASSERT_EQ(static_cast<ssize_t>(output.size() + 1),
+            read(pipe_fds[0], buf, sizeof(buf)));
+  EXPECT_EQ(0, strcmp(output.c_str(), buf));
+
+  // Negative time.
+  timeout.tv_sec = 0;
+  timeout.tv_nsec = -10000000;
+  EXPECT_EQ(-ETIMEDOUT, cras_poll(&poll_fd, 1, &timeout, NULL));
+  timeout.tv_sec = -1;
+  timeout.tv_nsec = 10000000;
+  EXPECT_EQ(-ETIMEDOUT, cras_poll(&poll_fd, 1, &timeout, NULL));
+
+  // Timeout.
+  timeout.tv_sec = 0;
+  timeout.tv_nsec = 0;
+  time_now.push_back(timeout);
+  timeout.tv_nsec = 1100000;
+  time_now.push_back(timeout);
+  timeout.tv_nsec = 1000000;
+  EXPECT_EQ(-ETIMEDOUT, cras_poll(&poll_fd, 1, &timeout, NULL));
+  EXPECT_EQ(timeout.tv_nsec, -100000);
+
+  EXPECT_EQ(0, close(pipe_fds[0]));
+  EXPECT_EQ(0, close(pipe_fds[1]));
+}
+
 /* Stubs */
 extern "C" {
 
 int clock_gettime(clockid_t clk_id, struct timespec *tp) {
-  *tp = time_now;
+  std::vector<struct timespec>::iterator i = time_now.begin();
+  if (i != time_now.end()) {
+    *tp = *i;
+    time_now.erase(i);
+  }
+  else
+    memset(tp, 0, sizeof(*tp));
   return 0;
 }
 
diff --git a/defs/utilities.mk b/defs/utilities.mk
index 230eae5..8bc4c04 100644
--- a/defs/utilities.mk
+++ b/defs/utilities.mk
@@ -15,3 +15,4 @@
 export ECHO	= /bin/echo
 export MESSAGE	= $(ECHO) "$(foreach v,$(shell seq $(MAKELEVEL))," ") [$(MAKELEVEL)] "
 export INSTALL	= /usr/bin/install
+export LINK	= /bin/ln
diff --git a/init/cras-directories.conf b/init/cras-directories.conf
new file mode 100644
index 0000000..4a79a18
--- /dev/null
+++ b/init/cras-directories.conf
@@ -0,0 +1 @@
+d /run/cras 1770 cras cras -
diff --git a/init/cras.conf b/init/cras.conf
new file mode 100644
index 0000000..eb1e62c
--- /dev/null
+++ b/init/cras.conf
@@ -0,0 +1,25 @@
+# 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.
+
+# Installed by ADHD package.
+# cras upstart job.
+
+description     "ChromeOS audio server"
+author          "chromium-os-dev@chromium.org"
+
+env CRAS_SOCKET_DIR=/run/cras
+
+start on starting system-services
+stop on stopping system-services
+respawn
+
+# Allow the audio server real time priority.
+limit rtprio 12 12
+
+pre-start script
+  mkdir -p -m 1770 "${CRAS_SOCKET_DIR}"
+  chown -R cras:cras "${CRAS_SOCKET_DIR}"
+end script
+
+exec /bin/sh /usr/share/cros/init/cras.sh
diff --git a/init/cras.service b/init/cras.service
new file mode 100644
index 0000000..bda61c1
--- /dev/null
+++ b/init/cras.service
@@ -0,0 +1,14 @@
+# Copyright 2016 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.
+
+[Unit]
+Description=ChromeOS audio server
+PartOf=system-services.target
+After=system-services.target
+
+[Service]
+Restart=on-failure
+LimitRTPRIO=12
+TimeoutStopSec=20
+ExecStart=/bin/sh /usr/share/cros/init/cras.sh
diff --git a/init/cras.sh b/init/cras.sh
new file mode 100644
index 0000000..2284243
--- /dev/null
+++ b/init/cras.sh
@@ -0,0 +1,25 @@
+# Copyright 2016 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.
+
+# Disable HSP/HFP on Google WiFi (Gale) with UART-HCI Bluetooth
+# which is incapable to handle SCO audio.
+platform_name="$(mosys platform name)"
+if [ "$platform_name" = "Gale" ]; then
+     DISABLE_PROFILE="--disable_profile=hfp,hsp"
+fi
+# For board needs different device configs, check which config
+# directory to use. Use that directory for both volume curves
+# and dsp config.
+if [ -f /etc/cras/get_device_config_dir ]; then
+  device_config_dir="$(sh /etc/cras/get_device_config_dir)"
+  DEVICE_CONFIG_DIR="--device_config_dir=${device_config_dir}"
+  DSP_CONFIG="--dsp_config=${device_config_dir}/dsp.ini"
+fi
+if [ -f /etc/cras/get_internal_ucm_suffix ]; then
+  internal_ucm_suffix="$(sh /etc/cras/get_internal_ucm_suffix)"
+  INTERNAL_UCM_SUFFIX="--internal_ucm_suffix=${internal_ucm_suffix}"
+fi
+exec minijail0 -u cras -g cras -G -- /usr/bin/cras \
+    ${DSP_CONFIG} ${DEVICE_CONFIG_DIR} ${DISABLE_PROFILE} \
+    ${INTERNAL_UCM_SUFFIX}
diff --git a/scripts/audio_diagnostics b/scripts/audio_diagnostics
index 45f99b1..5ec96a9 100755
--- a/scripts/audio_diagnostics
+++ b/scripts/audio_diagnostics
@@ -6,6 +6,16 @@
 #
 # Collect information about the audio system from top to bottom.
 
+dump_cards() {
+    for card in ${@}
+    do
+        echo '=== amixer -c' $card scontents '==='
+        amixer -c $card scontents
+        echo '=== amixer -c' $card contents '==='
+        amixer -c $card contents
+    done
+}
+
 echo '=== cras_test_client --dump_server_info ==='
 cras_test_client --dump_server_info
 
@@ -17,14 +27,11 @@
 echo '=== arecord -l ==='
 arecord -l
 
-cards=$(aplay -l | egrep ^card | sed 's/card \([0-9]\+\).*/\1/' | sort -u)
-for card in $cards
-do
-    echo '=== amixer -c' $card scontents '==='
-    amixer -c $card scontents
-    echo '=== amixer -c' $card contents '==='
-    amixer -c $card contents
-done
+output_cards=$(aplay -l | egrep ^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
 
 # HDA codec for codecs on x86.
 codecs=$(find /proc/asound -mindepth 2 -maxdepth 2 -path '*card*/codec#*')
diff --git a/scripts/audio_thread_log_viewer/log.test b/scripts/audio_thread_log_viewer/log.test
new file mode 100644
index 0000000..8206761
--- /dev/null
+++ b/scripts/audio_thread_log_viewer/log.test
@@ -0,0 +1,6151 @@
+Audio Debug Stats:
+-------------devices------------
+Output dev: bdw-rt5677: :1,0
+65536 0 1024 0 48000 2 1.000000
+-------------stream_dump------------
+Audio Thread Event Log:
+start at 736
+    496098.524565708  DEV_SLEEP_TIME                 dev:8 wake:000496098.545892346
+    496098.524567126  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496098.545959543  WAKE                           num_fds:0
+    496098.545994178  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496098.546000808  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496098.546001355  DEV_SLEEP_TIME                 dev:8 wake:000496098.567328258
+    496098.546002783  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496098.567581739  WAKE                           num_fds:0
+    496098.567615773  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496098.567622518  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496098.567623059  DEV_SLEEP_TIME                 dev:8 wake:000496098.588949912
+    496098.567624543  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496098.589207274  WAKE                           num_fds:0
+    496098.589240896  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496098.589247582  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496098.589248133  DEV_SLEEP_TIME                 dev:8 wake:000496098.610574976
+    496098.589250062  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496098.610821201  WAKE                           num_fds:0
+    496098.610854808  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496098.610861519  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496098.610862050  DEV_SLEEP_TIME                 dev:8 wake:000496098.632188948
+    496098.610863463  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496098.632443051  WAKE                           num_fds:0
+    496098.632476082  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496098.632482753  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496098.632483289  DEV_SLEEP_TIME                 dev:8 wake:000496098.653810157
+    496098.632484727  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496098.654059981  WAKE                           num_fds:0
+    496098.654093774  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496098.654100469  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496098.654101015  DEV_SLEEP_TIME                 dev:8 wake:000496098.675427884
+    496098.654102514  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496098.675676644  WAKE                           num_fds:0
+    496098.675709505  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496098.675715664  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496098.675716210  DEV_SLEEP_TIME                 dev:8 wake:000496098.697043620
+    496098.675717624  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496098.697292000  WAKE                           num_fds:0
+    496098.697325252  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496098.697331912  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496098.697332439  DEV_SLEEP_TIME                 dev:8 wake:000496098.718659357
+    496098.697333932  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496098.718759444  WAKE                           num_fds:0
+    496098.718791864  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496098.718798584  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496098.718799116  DEV_SLEEP_TIME                 dev:8 wake:000496098.740125964
+    496098.718800569  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496098.740374739  WAKE                           num_fds:0
+    496098.740407625  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496098.740414446  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496098.740414982  DEV_SLEEP_TIME                 dev:8 wake:000496098.761741745
+    496098.740416420  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496098.761990271  WAKE                           num_fds:0
+    496098.762023593  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496098.762030293  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496098.762030839  DEV_SLEEP_TIME                 dev:8 wake:000496098.783357712
+    496098.762032303  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496098.783607275  WAKE                           num_fds:0
+    496098.783640005  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496098.783646686  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496098.783647222  DEV_SLEEP_TIME                 dev:8 wake:000496098.804974110
+    496098.783648665  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496098.805225207  WAKE                           num_fds:0
+    496098.805258890  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496098.805265655  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496098.805266202  DEV_SLEEP_TIME                 dev:8 wake:000496098.826592999
+    496098.805267675  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496098.826841354  WAKE                           num_fds:0
+    496098.826874200  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496098.826880900  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496098.826881567  DEV_SLEEP_TIME                 dev:8 wake:000496098.848208295
+    496098.826883075  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496098.848306238  WAKE                           num_fds:0
+    496098.848338928  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496098.848345624  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496098.848346165  DEV_SLEEP_TIME                 dev:8 wake:000496098.869673018
+    496098.848367619  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496098.869943940  WAKE                           num_fds:0
+    496098.869977317  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496098.869983927  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496098.869984473  DEV_SLEEP_TIME                 dev:8 wake:000496098.891311411
+    496098.869985886  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496098.891556864  WAKE                           num_fds:0
+    496098.891589740  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496098.891596290  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496098.891596837  DEV_SLEEP_TIME                 dev:8 wake:000496098.912923825
+    496098.891598310  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496098.913173874  WAKE                           num_fds:0
+    496098.913207412  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496098.913214077  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496098.913214734  DEV_SLEEP_TIME                 dev:8 wake:000496098.934541466
+    496098.913216312  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496098.934788182  WAKE                           num_fds:0
+    496098.934820552  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496098.934827267  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496098.934827799  DEV_SLEEP_TIME                 dev:8 wake:000496098.956154637
+    496098.934829237  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496098.956406414  WAKE                           num_fds:0
+    496098.956439565  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496098.956446341  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496098.956446872  DEV_SLEEP_TIME                 dev:8 wake:000496098.977773730
+    496098.956448321  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496098.978020778  WAKE                           num_fds:0
+    496098.978053749  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496098.978060374  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496098.978060916  DEV_SLEEP_TIME                 dev:8 wake:000496098.999387834
+    496098.978062359  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496098.999634870  WAKE                           num_fds:0
+    496098.999667952  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496098.999674592  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496098.999675198  DEV_SLEEP_TIME                 dev:8 wake:000496099.021002041
+    496098.999676632  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.021101388  WAKE                           num_fds:0
+    496099.021133918  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496099.021140623  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.021141155  DEV_SLEEP_TIME                 dev:8 wake:000496099.042468008
+    496099.021142573  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.042718602  WAKE                           num_fds:0
+    496099.042751734  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496099.042758414  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.042758961  DEV_SLEEP_TIME                 dev:8 wake:000496099.064085824
+    496099.042760384  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.064330956  WAKE                           num_fds:0
+    496099.064382264  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496099.064388955  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.064389501  DEV_SLEEP_TIME                 dev:8 wake:000496099.085716384
+    496099.064390929  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.085964449  WAKE                           num_fds:0
+    496099.085997670  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496099.086004286  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.086004822  DEV_SLEEP_TIME                 dev:8 wake:000496099.107331775
+    496099.086006386  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.107586550  WAKE                           num_fds:0
+    496099.107619801  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496099.107626397  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.107626938  DEV_SLEEP_TIME                 dev:8 wake:000496099.128953866
+    496099.107628366  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.129204762  WAKE                           num_fds:0
+    496099.129237403  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496099.129244023  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.129244569  DEV_SLEEP_TIME                 dev:8 wake:000496099.150571487
+    496099.129246008  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.150641335  WAKE                           num_fds:0
+    496099.150673374  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496099.150680004  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.150680656  DEV_SLEEP_TIME                 dev:8 wake:000496099.172007509
+    496099.150682074  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.172256786  WAKE                           num_fds:0
+    496099.172289878  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496099.172296493  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.172297034  DEV_SLEEP_TIME                 dev:8 wake:000496099.193623983
+    496099.172298478  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.193874512  WAKE                           num_fds:0
+    496099.193907859  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496099.193914469  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.193915006  DEV_SLEEP_TIME                 dev:8 wake:000496099.215241949
+    496099.193916414  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.215492809  WAKE                           num_fds:0
+    496099.215525304  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496099.215531864  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.215532421  DEV_SLEEP_TIME                 dev:8 wake:000496099.236859384
+    496099.215533834  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.237111493  WAKE                           num_fds:0
+    496099.237143973  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496099.237150678  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.237151210  DEV_SLEEP_TIME                 dev:8 wake:000496099.258478073
+    496099.237152628  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.258726588  WAKE                           num_fds:0
+    496099.258759554  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496099.258766124  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.258766665  DEV_SLEEP_TIME                 dev:8 wake:000496099.280093668
+    496099.258768219  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.280340434  WAKE                           num_fds:0
+    496099.280393006  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496099.280399631  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.280400182  DEV_SLEEP_TIME                 dev:8 wake:000496099.301727090
+    496099.280401641  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.301975591  WAKE                           num_fds:0
+    496099.302008723  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496099.302015493  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.302016025  DEV_SLEEP_TIME                 dev:8 wake:000496099.323342868
+    496099.302017463  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.323440980  WAKE                           num_fds:0
+    496099.323477615  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496099.323484245  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.323484791  DEV_SLEEP_TIME                 dev:8 wake:000496099.344811715
+    496099.323486230  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.345086125  WAKE                           num_fds:0
+    496099.345126263  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496099.345131465  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.345131996  DEV_SLEEP_TIME                 dev:8 wake:000496099.366460378
+    496099.345133429  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.366708151  WAKE                           num_fds:0
+    496099.366739964  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496099.366746760  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.366747432  DEV_SLEEP_TIME                 dev:8 wake:000496099.388074069
+    496099.366748880  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.388144614  WAKE                           num_fds:0
+    496099.388176969  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496099.388183740  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.388184281  DEV_SLEEP_TIME                 dev:8 wake:000496099.409511159
+    496099.388185724  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.409545707  WAKE                           num_fds:0
+    496099.409566242  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496099.409571665  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.409571919  DEV_SLEEP_TIME                 dev:8 wake:000496099.430899910
+    496099.409572582  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.430927759  WAKE                           num_fds:0
+    496099.430955957  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496099.430962098  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.430962539  DEV_SLEEP_TIME                 dev:8 wake:000496099.452289928
+    496099.430963662  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.452379104  WAKE                           num_fds:0
+    496099.452413514  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496099.452418641  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.452419177  DEV_SLEEP_TIME                 dev:8 wake:000496099.473747588
+    496099.452420710  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.473884983  WAKE                           num_fds:0
+    496099.473917934  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496099.473924589  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.473925130  DEV_SLEEP_TIME                 dev:8 wake:000496099.495252069
+    496099.473926564  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.495367456  WAKE                           num_fds:0
+    496099.495399996  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496099.495406612  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.495407158  DEV_SLEEP_TIME                 dev:8 wake:000496099.516734061
+    496099.495408727  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.516763201  WAKE                           num_fds:0
+    496099.516795615  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496099.516802181  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.516802732  DEV_SLEEP_TIME                 dev:8 wake:000496099.538129745
+    496099.516804095  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.538207146  WAKE                           num_fds:0
+    496099.538238564  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496099.538245094  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.538245665  DEV_SLEEP_TIME                 dev:8 wake:000496099.559572673
+    496099.538247123  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.559628849  WAKE                           num_fds:0
+    496099.559659896  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496099.559665359  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.559665890  DEV_SLEEP_TIME                 dev:8 wake:000496099.580994046
+    496099.559667333  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.581019938  WAKE                           num_fds:0
+    496099.581050679  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496099.581057460  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.581057996  DEV_SLEEP_TIME                 dev:8 wake:000496099.602384964
+    496099.581059284  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.602418768  WAKE                           num_fds:0
+    496099.602454421  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496099.602462449  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.602463206  DEV_SLEEP_TIME                 dev:8 wake:000496099.623788801
+    496099.602465055  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.623858429  WAKE                           num_fds:0
+    496099.623890809  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496099.623897589  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.623898131  DEV_SLEEP_TIME                 dev:8 wake:000496099.645224914
+    496099.623899609  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.645296641  WAKE                           num_fds:0
+    496099.645334724  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496099.645340272  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.645340808  DEV_SLEEP_TIME                 dev:8 wake:000496099.666668954
+    496099.645342192  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.666692849  WAKE                           num_fds:0
+    496099.666719109  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496099.666725256  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.666725704  DEV_SLEEP_TIME                 dev:8 wake:000496099.688053104
+    496099.666726894  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.688074387  WAKE                           num_fds:0
+    496099.688096225  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496099.688101841  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.688102175  DEV_SLEEP_TIME                 dev:8 wake:000496099.709430072
+    496099.688102887  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.709443343  WAKE                           num_fds:0
+    496099.709455328  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496099.709459005  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.709459229  DEV_SLEEP_TIME                 dev:8 wake:000496099.730788957
+    496099.709459736  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.730805574  WAKE                           num_fds:0
+    496099.730824543  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496099.730829830  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.730830098  DEV_SLEEP_TIME                 dev:8 wake:000496099.752158351
+    496099.730830581  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.752172469  WAKE                           num_fds:0
+    496099.752187265  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496099.752190979  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.752191240  DEV_SLEEP_TIME                 dev:8 wake:000496099.773520970
+    496099.752191743  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.773533690  WAKE                           num_fds:0
+    496099.773549159  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496099.773552607  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.773552834  DEV_SLEEP_TIME                 dev:8 wake:000496099.794882783
+    496099.773553393  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.794893261  WAKE                           num_fds:0
+    496099.794905112  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496099.794908388  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.794908531  DEV_SLEEP_TIME                 dev:8 wake:000496099.816238663
+    496099.794908990  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.816248944  WAKE                           num_fds:0
+    496099.816260791  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496099.816263965  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.816264138  DEV_SLEEP_TIME                 dev:8 wake:000496099.837594355
+    496099.816264504  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.837604072  WAKE                           num_fds:0
+    496099.837616853  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496099.837619986  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.837620168  DEV_SLEEP_TIME                 dev:8 wake:000496099.858950398
+    496099.837620511  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.858961345  WAKE                           num_fds:0
+    496099.858976724  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496099.858979930  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.858980107  DEV_SLEEP_TIME                 dev:8 wake:000496099.880310281
+    496099.858980424  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.880319596  WAKE                           num_fds:0
+    496099.880330501  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496099.880333657  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.880333835  DEV_SLEEP_TIME                 dev:8 wake:000496099.901664064
+    496099.880334293  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.901673173  WAKE                           num_fds:0
+    496099.901683309  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496099.901686399  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.901686518  DEV_SLEEP_TIME                 dev:8 wake:000496099.923016808
+    496099.901686986  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.923026422  WAKE                           num_fds:0
+    496099.923039650  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496099.923042794  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.923042971  DEV_SLEEP_TIME                 dev:8 wake:000496099.944373193
+    496099.923043433  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.944382798  WAKE                           num_fds:0
+    496099.944393969  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496099.944397102  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.944397285  DEV_SLEEP_TIME                 dev:8 wake:000496099.965727428
+    496099.944397681  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.965737960  WAKE                           num_fds:0
+    496099.965751311  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496099.965754524  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.965754702  DEV_SLEEP_TIME                 dev:8 wake:000496099.987084869
+    496099.965755111  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496099.987093719  WAKE                           num_fds:0
+    496099.987104561  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496099.987107677  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496099.987107905  DEV_SLEEP_TIME                 dev:8 wake:000496100.008438069
+    496099.987108340  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.008446847  WAKE                           num_fds:0
+    496100.008457382  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496100.008460682  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.008460869  DEV_SLEEP_TIME                 dev:8 wake:000496100.029790961
+    496100.008461246  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.029818678  WAKE                           num_fds:0
+    496100.029837149  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496100.029843078  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.029843305  DEV_SLEEP_TIME                 dev:8 wake:000496100.051171009
+    496100.029843952  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.051190229  WAKE                           num_fds:0
+    496100.051209390  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496100.051214799  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.051215052  DEV_SLEEP_TIME                 dev:8 wake:000496100.072543113
+    496100.051215796  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.072561196  WAKE                           num_fds:0
+    496100.072576819  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496100.072580750  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.072581115  DEV_SLEEP_TIME                 dev:8 wake:000496100.093910689
+    496100.072581922  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.093973259  WAKE                           num_fds:0
+    496100.093991762  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496100.093995366  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.093995594  DEV_SLEEP_TIME                 dev:8 wake:000496100.115325510
+    496100.093996174  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.115537814  WAKE                           num_fds:0
+    496100.115557401  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496100.115560841  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.115561080  DEV_SLEEP_TIME                 dev:8 wake:000496100.136891036
+    496100.115561681  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.137102895  WAKE                           num_fds:0
+    496100.137121895  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496100.137125352  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.137125589  DEV_SLEEP_TIME                 dev:8 wake:000496100.158455544
+    496100.137126194  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.158690778  WAKE                           num_fds:0
+    496100.158718867  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496100.158724914  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.158725362  DEV_SLEEP_TIME                 dev:8 wake:000496100.180052858
+    496100.158726597  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.180291907  WAKE                           num_fds:0
+    496100.180334939  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496100.180341137  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.180341694  DEV_SLEEP_TIME                 dev:8 wake:000496100.201668888
+    496100.180342901  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.201904895  WAKE                           num_fds:0
+    496100.201935065  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496100.201941215  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.201941653  DEV_SLEEP_TIME                 dev:8 wake:000496100.223269028
+    496100.201942826  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.223392063  WAKE                           num_fds:0
+    496100.223421828  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496100.223427926  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.223428484  DEV_SLEEP_TIME                 dev:8 wake:000496100.244755795
+    496100.223429666  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.245001270  WAKE                           num_fds:0
+    496100.245034126  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496100.245041097  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.245041638  DEV_SLEEP_TIME                 dev:8 wake:000496100.266368371
+    496100.245043102  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.266620745  WAKE                           num_fds:0
+    496100.266654027  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496100.266660717  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.266661253  DEV_SLEEP_TIME                 dev:8 wake:000496100.287988172
+    496100.266662682  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.288238777  WAKE                           num_fds:0
+    496100.288272199  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496100.288278840  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.288279496  DEV_SLEEP_TIME                 dev:8 wake:000496100.309606269
+    496100.288280920  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.309680702  WAKE                           num_fds:0
+    496100.309714560  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496100.309721221  SET_DEV_WAKE                   dev:8 hw_level:2000 sleep:976
+    496100.309721767  DEV_SLEEP_TIME                 dev:8 wake:000496100.330048685
+    496100.309723215  SLEEP                          sleep:000000000.020333333 longest_wake:000158140
+    496100.330297374  WAKE                           num_fds:0
+    496100.330330320  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496100.330336875  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.330337422  DEV_SLEEP_TIME                 dev:8 wake:000496100.351664410
+    496100.330338870  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.351764312  WAKE                           num_fds:0
+    496100.351796516  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496100.351803227  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.351803773  DEV_SLEEP_TIME                 dev:8 wake:000496100.373130636
+    496100.351805196  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.373381346  WAKE                           num_fds:0
+    496100.373414302  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496100.373421028  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.373421579  DEV_SLEEP_TIME                 dev:8 wake:000496100.394748397
+    496100.373422997  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.394996512  WAKE                           num_fds:0
+    496100.395030009  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496100.395036710  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.395037256  DEV_SLEEP_TIME                 dev:8 wake:000496100.416364124
+    496100.395038699  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.416614232  WAKE                           num_fds:0
+    496100.416646743  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496100.416653363  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.416653904  DEV_SLEEP_TIME                 dev:8 wake:000496100.437980857
+    496100.416655448  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.438214409  WAKE                           num_fds:0
+    496100.438247169  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496100.438253865  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.438254401  DEV_SLEEP_TIME                 dev:8 wake:000496100.459581389
+    496100.438255839  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.459827454  WAKE                           num_fds:0
+    496100.459861848  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496100.459868483  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.459869060  DEV_SLEEP_TIME                 dev:8 wake:000496100.481195973
+    496100.459870483  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.481448141  WAKE                           num_fds:0
+    496100.481481212  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496100.481487983  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.481488559  DEV_SLEEP_TIME                 dev:8 wake:000496100.502815503
+    496100.481489988  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.503061022  WAKE                           num_fds:0
+    496100.503093867  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496100.503100483  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.503101054  DEV_SLEEP_TIME                 dev:8 wake:000496100.524427977
+    496100.503102497  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.524533081  WAKE                           num_fds:0
+    496100.524566132  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496100.524572718  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.524573259  DEV_SLEEP_TIME                 dev:8 wake:000496100.545900277
+    496100.524574737  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.546150402  WAKE                           num_fds:0
+    496100.546183914  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496100.546190600  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.546191141  DEV_SLEEP_TIME                 dev:8 wake:000496100.567517989
+    496100.546192584  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.567766865  WAKE                           num_fds:0
+    496100.567800633  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496100.567807248  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.567807789  DEV_SLEEP_TIME                 dev:8 wake:000496100.589134742
+    496100.567809222  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.589386409  WAKE                           num_fds:0
+    496100.589419551  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496100.589426096  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.589426642  DEV_SLEEP_TIME                 dev:8 wake:000496100.610753641
+    496100.589428091  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.611000528  WAKE                           num_fds:0
+    496100.611033584  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496100.611040440  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.611040991  DEV_SLEEP_TIME                 dev:8 wake:000496100.632367749
+    496100.611042425  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.632618399  WAKE                           num_fds:0
+    496100.632651826  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496100.632658577  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.632659118  DEV_SLEEP_TIME                 dev:8 wake:000496100.653985931
+    496100.632660526  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.654089236  WAKE                           num_fds:0
+    496100.654122483  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496100.654129063  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.654129610  DEV_SLEEP_TIME                 dev:8 wake:000496100.675456613
+    496100.654131033  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.675707809  WAKE                           num_fds:0
+    496100.675739668  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496100.675746323  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.675746884  DEV_SLEEP_TIME                 dev:8 wake:000496100.697073767
+    496100.675748318  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.697320964  WAKE                           num_fds:0
+    496100.697371546  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496100.697378417  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.697378968  DEV_SLEEP_TIME                 dev:8 wake:000496100.718705691
+    496100.697380422  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.718805258  WAKE                           num_fds:0
+    496100.718838966  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496100.718845586  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.718846132  DEV_SLEEP_TIME                 dev:8 wake:000496100.740173041
+    496100.718847591  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.740425615  WAKE                           num_fds:0
+    496100.740458811  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496100.740465657  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.740466203  DEV_SLEEP_TIME                 dev:8 wake:000496100.761793051
+    496100.740467632  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.761892959  WAKE                           num_fds:0
+    496100.761925459  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496100.761932105  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.761932776  DEV_SLEEP_TIME                 dev:8 wake:000496100.783259524
+    496100.761934220  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.783381637  WAKE                           num_fds:0
+    496100.783414944  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496100.783421680  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.783422226  DEV_SLEEP_TIME                 dev:8 wake:000496100.804749039
+    496100.783423649  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.804853272  WAKE                           num_fds:0
+    496100.804885807  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496100.804892417  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.804892963  DEV_SLEEP_TIME                 dev:8 wake:000496100.826219907
+    496100.804894402  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.826322110  WAKE                           num_fds:0
+    496100.826373047  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496100.826380063  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.826380610  DEV_SLEEP_TIME                 dev:8 wake:000496100.847707192
+    496100.826382018  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.847959501  WAKE                           num_fds:0
+    496100.847992322  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496100.847999067  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.847999618  DEV_SLEEP_TIME                 dev:8 wake:000496100.869326431
+    496100.848001317  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.869578930  WAKE                           num_fds:0
+    496100.869612292  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496100.869619023  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.869619574  DEV_SLEEP_TIME                 dev:8 wake:000496100.890946412
+    496100.869620997  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.891197770  WAKE                           num_fds:0
+    496100.891230550  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496100.891237110  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.891237787  DEV_SLEEP_TIME                 dev:8 wake:000496100.912564655
+    496100.891239220  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.912816197  WAKE                           num_fds:0
+    496100.912849323  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496100.912855848  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.912856420  DEV_SLEEP_TIME                 dev:8 wake:000496100.934183418
+    496100.912857878  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.934430826  WAKE                           num_fds:0
+    496100.934464102  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496100.934470708  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.934471254  DEV_SLEEP_TIME                 dev:8 wake:000496100.955798272
+    496100.934472697  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.955901337  WAKE                           num_fds:0
+    496100.955933948  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496100.955940678  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.955941229  DEV_SLEEP_TIME                 dev:8 wake:000496100.977268047
+    496100.955942683  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.977519955  WAKE                           num_fds:0
+    496100.977552550  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496100.977559256  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.977559822  DEV_SLEEP_TIME                 dev:8 wake:000496100.998886650
+    496100.977561271  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496100.999137892  WAKE                           num_fds:0
+    496100.999169961  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496100.999176612  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496100.999177088  DEV_SLEEP_TIME                 dev:8 wake:000496101.020504061
+    496100.999178456  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.020757502  WAKE                           num_fds:0
+    496101.020790759  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496101.020797399  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.020797941  DEV_SLEEP_TIME                 dev:8 wake:000496101.042124944
+    496101.020799364  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.042376561  WAKE                           num_fds:0
+    496101.042409227  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496101.042415827  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.042416388  DEV_SLEEP_TIME                 dev:8 wake:000496101.063743316
+    496101.042417987  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.063993676  WAKE                           num_fds:0
+    496101.064026808  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496101.064033453  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.064033994  DEV_SLEEP_TIME                 dev:8 wake:000496101.085360928
+    496101.064035438  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.085608450  WAKE                           num_fds:0
+    496101.085642659  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496101.085649365  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.085649901  DEV_SLEEP_TIME                 dev:8 wake:000496101.106976764
+    496101.085651324  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.107228552  WAKE                           num_fds:0
+    496101.107261478  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496101.107268179  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.107268720  DEV_SLEEP_TIME                 dev:8 wake:000496101.128595704
+    496101.107270164  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.128696698  WAKE                           num_fds:0
+    496101.128726381  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496101.128733227  SET_DEV_WAKE                   dev:8 hw_level:2000 sleep:976
+    496101.128733768  DEV_SLEEP_TIME                 dev:8 wake:000496101.149060501
+    496101.128735212  SLEEP                          sleep:000000000.020333333 longest_wake:000158140
+    496101.149310393  WAKE                           num_fds:0
+    496101.149343359  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496101.149381326  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.149381917  DEV_SLEEP_TIME                 dev:8 wake:000496101.170677469
+    496101.149383436  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.170961115  WAKE                           num_fds:0
+    496101.170994692  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496101.171001378  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.171001924  DEV_SLEEP_TIME                 dev:8 wake:000496101.192328802
+    496101.171003367  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.192580429  WAKE                           num_fds:0
+    496101.192613290  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496101.192619910  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.192620462  DEV_SLEEP_TIME                 dev:8 wake:000496101.213947420
+    496101.192621920  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.214192247  WAKE                           num_fds:0
+    496101.214224998  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496101.214231573  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.214232119  DEV_SLEEP_TIME                 dev:8 wake:000496101.235559088
+    496101.214233553  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.235808094  WAKE                           num_fds:0
+    496101.235840980  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496101.235847595  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.235848146  DEV_SLEEP_TIME                 dev:8 wake:000496101.257175089
+    496101.235849590  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.257275849  WAKE                           num_fds:0
+    496101.257308500  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496101.257315135  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.257315676  DEV_SLEEP_TIME                 dev:8 wake:000496101.278642594
+    496101.257317104  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.278887125  WAKE                           num_fds:0
+    496101.278919525  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496101.278926135  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.278926676  DEV_SLEEP_TIME                 dev:8 wake:000496101.300253620
+    496101.278928120  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.300391730  WAKE                           num_fds:0
+    496101.300424490  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496101.300431176  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.300431722  DEV_SLEEP_TIME                 dev:8 wake:000496101.321758585
+    496101.300433160  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.322005658  WAKE                           num_fds:0
+    496101.322038759  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496101.322045274  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.322045820  DEV_SLEEP_TIME                 dev:8 wake:000496101.343372864
+    496101.322047254  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.343623649  WAKE                           num_fds:0
+    496101.343656334  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496101.343662930  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.343663471  DEV_SLEEP_TIME                 dev:8 wake:000496101.364990429
+    496101.343664909  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.365239140  WAKE                           num_fds:0
+    496101.365272096  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496101.365278837  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.365279378  DEV_SLEEP_TIME                 dev:8 wake:000496101.386606216
+    496101.365280832  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.386856460  WAKE                           num_fds:0
+    496101.386890138  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496101.386896783  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.386897330  DEV_SLEEP_TIME                 dev:8 wake:000496101.408224208
+    496101.386898763  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.408472672  WAKE                           num_fds:0
+    496101.408505724  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496101.408512364  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.408512905  DEV_SLEEP_TIME                 dev:8 wake:000496101.429839848
+    496101.408514329  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.429906550  WAKE                           num_fds:0
+    496101.429938428  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496101.429944993  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.429945535  DEV_SLEEP_TIME                 dev:8 wake:000496101.451272568
+    496101.429946993  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.451524275  WAKE                           num_fds:0
+    496101.451557176  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496101.451563777  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.451564328  DEV_SLEEP_TIME                 dev:8 wake:000496101.472891271
+    496101.451565906  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.473137346  WAKE                           num_fds:0
+    496101.473170368  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496101.473177068  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.473177609  DEV_SLEEP_TIME                 dev:8 wake:000496101.494504467
+    496101.473179058  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.494599058  WAKE                           num_fds:0
+    496101.494630274  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496101.494636524  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.494637017  DEV_SLEEP_TIME                 dev:8 wake:000496101.515964292
+    496101.494638308  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.516182970  WAKE                           num_fds:0
+    496101.516214183  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496101.516220585  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.516221074  DEV_SLEEP_TIME                 dev:8 wake:000496101.537548225
+    496101.516222409  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.537791076  WAKE                           num_fds:0
+    496101.537822243  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496101.537828687  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.537829164  DEV_SLEEP_TIME                 dev:8 wake:000496101.559156302
+    496101.537830627  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.559252688  WAKE                           num_fds:0
+    496101.559283407  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496101.559289745  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.559290243  DEV_SLEEP_TIME                 dev:8 wake:000496101.580617438
+    496101.559291526  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.580861870  WAKE                           num_fds:0
+    496101.580893182  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496101.580899553  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.580900034  DEV_SLEEP_TIME                 dev:8 wake:000496101.602227209
+    496101.580901321  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.602471830  WAKE                           num_fds:0
+    496101.602503226  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496101.602509601  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.602510086  DEV_SLEEP_TIME                 dev:8 wake:000496101.623837257
+    496101.602511361  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.624078044  WAKE                           num_fds:0
+    496101.624109220  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496101.624115607  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.624116088  DEV_SLEEP_TIME                 dev:8 wake:000496101.645443234
+    496101.624117382  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.645685689  WAKE                           num_fds:0
+    496101.645715144  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496101.645721455  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.645721940  DEV_SLEEP_TIME                 dev:8 wake:000496101.667049171
+    496101.645723231  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.667144488  WAKE                           num_fds:0
+    496101.667175468  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496101.667182259  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.667182761  DEV_SLEEP_TIME                 dev:8 wake:000496101.688509494
+    496101.667184067  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.688754196  WAKE                           num_fds:0
+    496101.688785797  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496101.688792043  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.688792544  DEV_SLEEP_TIME                 dev:8 wake:000496101.710119799
+    496101.688793835  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.710368568  WAKE                           num_fds:0
+    496101.710399467  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496101.710405854  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.710406339  DEV_SLEEP_TIME                 dev:8 wake:000496101.731733498
+    496101.710407618  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.731827954  WAKE                           num_fds:0
+    496101.731858565  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496101.731864803  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.731865301  DEV_SLEEP_TIME                 dev:8 wake:000496101.753192595
+    496101.731866592  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.753434542  WAKE                           num_fds:0
+    496101.753465201  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496101.753471516  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.753472073  DEV_SLEEP_TIME                 dev:8 wake:000496101.774799220
+    496101.753473400  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.775034482  WAKE                           num_fds:0
+    496101.775064488  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496101.775070890  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.775071391  DEV_SLEEP_TIME                 dev:8 wake:000496101.796398646
+    496101.775072682  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.796638895  WAKE                           num_fds:0
+    496101.796669879  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496101.796676257  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.796676734  DEV_SLEEP_TIME                 dev:8 wake:000496101.818003893
+    496101.796678026  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.818247105  WAKE                           num_fds:0
+    496101.818277917  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496101.818284203  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.818284684  DEV_SLEEP_TIME                 dev:8 wake:000496101.839611936
+    496101.818285967  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.839856667  WAKE                           num_fds:0
+    496101.839887703  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496101.839893985  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.839894482  DEV_SLEEP_TIME                 dev:8 wake:000496101.861221745
+    496101.839895877  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.861461557  WAKE                           num_fds:0
+    496101.861492472  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496101.861498774  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.861499264  DEV_SLEEP_TIME                 dev:8 wake:000496101.882826487
+    496101.861500567  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.883067472  WAKE                           num_fds:0
+    496101.883097802  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496101.883104085  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.883104586  DEV_SLEEP_TIME                 dev:8 wake:000496101.904431816
+    496101.883105897  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.904675842  WAKE                           num_fds:0
+    496101.904707142  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496101.904712154  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.904712656  DEV_SLEEP_TIME                 dev:8 wake:000496101.926041285
+    496101.904713943  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.926286481  WAKE                           num_fds:0
+    496101.926317412  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496101.926323746  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.926324244  DEV_SLEEP_TIME                 dev:8 wake:000496101.947651462
+    496101.926325542  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.947894255  WAKE                           num_fds:0
+    496101.947925656  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496101.947932034  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.947932528  DEV_SLEEP_TIME                 dev:8 wake:000496101.969259655
+    496101.947933819  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.969503271  WAKE                           num_fds:0
+    496101.969533801  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496101.969540176  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.969540650  DEV_SLEEP_TIME                 dev:8 wake:000496101.990867828
+    496101.969541940  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496101.991112235  WAKE                           num_fds:0
+    496101.991142850  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496101.991149285  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496101.991149702  DEV_SLEEP_TIME                 dev:8 wake:000496102.012476969
+    496101.991151093  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.012720280  WAKE                           num_fds:0
+    496102.012751156  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496102.012757551  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.012758032  DEV_SLEEP_TIME                 dev:8 wake:000496102.034085183
+    496102.012759347  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.034179223  WAKE                           num_fds:0
+    496102.034209573  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496102.034215959  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.034216445  DEV_SLEEP_TIME                 dev:8 wake:000496102.055543612
+    496102.034217748  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.055786354  WAKE                           num_fds:0
+    496102.055817274  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496102.055823596  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.055824098  DEV_SLEEP_TIME                 dev:8 wake:000496102.077151308
+    496102.055825385  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.077396578  WAKE                           num_fds:0
+    496102.077427578  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496102.077433828  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.077434386  DEV_SLEEP_TIME                 dev:8 wake:000496102.098761576
+    496102.077435665  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.099004172  WAKE                           num_fds:0
+    496102.099035175  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496102.099041530  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.099042024  DEV_SLEEP_TIME                 dev:8 wake:000496102.120369198
+    496102.099043326  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.120613023  WAKE                           num_fds:0
+    496102.120644090  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496102.120650614  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.120651091  DEV_SLEEP_TIME                 dev:8 wake:000496102.141978225
+    496102.120652494  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.142225706  WAKE                           num_fds:0
+    496102.142256890  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496102.142263153  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.142263645  DEV_SLEEP_TIME                 dev:8 wake:000496102.163590920
+    496102.142265037  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.163836347  WAKE                           num_fds:0
+    496102.163867587  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496102.163873914  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.163874460  DEV_SLEEP_TIME                 dev:8 wake:000496102.185201638
+    496102.163875798  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.185447500  WAKE                           num_fds:0
+    496102.185478163  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496102.185484509  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.185484991  DEV_SLEEP_TIME                 dev:8 wake:000496102.206812162
+    496102.185486293  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.207058359  WAKE                           num_fds:0
+    496102.207089563  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496102.207095821  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.207096391  DEV_SLEEP_TIME                 dev:8 wake:000496102.228423577
+    496102.207097693  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.228668781  WAKE                           num_fds:0
+    496102.228701857  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496102.228707109  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.228707636  DEV_SLEEP_TIME                 dev:8 wake:000496102.250035927
+    496102.228709310  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.250285696  WAKE                           num_fds:0
+    496102.250317905  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496102.250324535  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.250325072  DEV_SLEEP_TIME                 dev:8 wake:000496102.271651990
+    496102.250326550  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.271905241  WAKE                           num_fds:0
+    496102.271938628  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496102.271945223  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.271945764  DEV_SLEEP_TIME                 dev:8 wake:000496102.293272728
+    496102.271947358  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.293521749  WAKE                           num_fds:0
+    496102.293554584  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496102.293561180  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.293561721  DEV_SLEEP_TIME                 dev:8 wake:000496102.314888689
+    496102.293563174  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.315141390  WAKE                           num_fds:0
+    496102.315173950  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496102.315180640  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.315181177  DEV_SLEEP_TIME                 dev:8 wake:000496102.336508085
+    496102.315182615  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.336608964  WAKE                           num_fds:0
+    496102.336641554  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496102.336648134  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.336648786  DEV_SLEEP_TIME                 dev:8 wake:000496102.357975634
+    496102.336650354  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.358060502  WAKE                           num_fds:0
+    496102.358092746  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496102.358099572  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.358100118  DEV_SLEEP_TIME                 dev:8 wake:000496102.379426886
+    496102.358101582  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.379679310  WAKE                           num_fds:0
+    496102.379711860  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496102.379717002  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.379717538  DEV_SLEEP_TIME                 dev:8 wake:000496102.401045965
+    496102.379718967  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.401289935  WAKE                           num_fds:0
+    496102.401322290  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496102.401328976  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.401329512  DEV_SLEEP_TIME                 dev:8 wake:000496102.422656405
+    496102.401330940  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.422906689  WAKE                           num_fds:0
+    496102.422940056  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496102.422946696  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.422947232  DEV_SLEEP_TIME                 dev:8 wake:000496102.444274171
+    496102.422948681  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.444528128  WAKE                           num_fds:0
+    496102.444561089  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496102.444567699  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.444568241  DEV_SLEEP_TIME                 dev:8 wake:000496102.465895209
+    496102.444569724  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.466143570  WAKE                           num_fds:0
+    496102.466176561  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496102.466183417  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.466183943  DEV_SLEEP_TIME                 dev:8 wake:000496102.487510801
+    496102.466185391  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.487759281  WAKE                           num_fds:0
+    496102.487792688  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496102.487799288  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.487799839  DEV_SLEEP_TIME                 dev:8 wake:000496102.509126787
+    496102.487801283  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.509382158  WAKE                           num_fds:0
+    496102.509414728  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496102.509421519  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.509422050  DEV_SLEEP_TIME                 dev:8 wake:000496102.530748953
+    496102.509423484  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.530997274  WAKE                           num_fds:0
+    496102.531029418  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496102.531036073  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.531036610  DEV_SLEEP_TIME                 dev:8 wake:000496102.552363538
+    496102.531038068  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.552613621  WAKE                           num_fds:0
+    496102.552646277  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496102.552652837  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.552653398  DEV_SLEEP_TIME                 dev:8 wake:000496102.573980382
+    496102.552654847  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.574235683  WAKE                           num_fds:0
+    496102.574268759  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496102.574275550  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.574276086  DEV_SLEEP_TIME                 dev:8 wake:000496102.595602849
+    496102.574277530  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.595703738  WAKE                           num_fds:0
+    496102.595737742  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496102.595744357  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.595744903  DEV_SLEEP_TIME                 dev:8 wake:000496102.617071842
+    496102.595746362  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.617323143  WAKE                           num_fds:0
+    496102.617380144  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496102.617386815  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.617387476  DEV_SLEEP_TIME                 dev:8 wake:000496102.638714259
+    496102.617388925  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.638973490  WAKE                           num_fds:0
+    496102.639006691  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496102.639013432  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.639013983  DEV_SLEEP_TIME                 dev:8 wake:000496102.660340786
+    496102.639015447  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.660417244  WAKE                           num_fds:0
+    496102.660456845  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496102.660465896  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.660466528  DEV_SLEEP_TIME                 dev:8 wake:000496102.681791421
+    496102.660468447  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.682044914  WAKE                           num_fds:0
+    496102.682077384  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496102.682084109  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.682084761  DEV_SLEEP_TIME                 dev:8 wake:000496102.703411513
+    496102.682086214  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.703658395  WAKE                           num_fds:0
+    496102.703692107  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496102.703698723  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.703699274  DEV_SLEEP_TIME                 dev:8 wake:000496102.725026202
+    496102.703700717  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.725275906  WAKE                           num_fds:0
+    496102.725309578  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496102.725316209  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.725316870  DEV_SLEEP_TIME                 dev:8 wake:000496102.746643673
+    496102.725318439  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.746892644  WAKE                           num_fds:0
+    496102.746925380  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496102.746932065  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.746932596  DEV_SLEEP_TIME                 dev:8 wake:000496102.768259499
+    496102.746934020  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.768510786  WAKE                           num_fds:0
+    496102.768543812  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496102.768550613  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.768551154  DEV_SLEEP_TIME                 dev:8 wake:000496102.789877957
+    496102.768552672  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.790129755  WAKE                           num_fds:0
+    496102.790162812  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496102.790169432  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.790169988  DEV_SLEEP_TIME                 dev:8 wake:000496102.811496916
+    496102.790171416  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.811745406  WAKE                           num_fds:0
+    496102.811779034  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496102.811785589  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.811786140  DEV_SLEEP_TIME                 dev:8 wake:000496102.833113134
+    496102.811787564  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.833368700  WAKE                           num_fds:0
+    496102.833408141  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496102.833416480  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.833417112  DEV_SLEEP_TIME                 dev:8 wake:000496102.854742566
+    496102.833418956  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.854847040  WAKE                           num_fds:0
+    496102.854879640  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496102.854886280  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.854886832  DEV_SLEEP_TIME                 dev:8 wake:000496102.876213770
+    496102.854888265  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.876314619  WAKE                           num_fds:0
+    496102.876375595  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496102.876382566  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.876383112  DEV_SLEEP_TIME                 dev:8 wake:000496102.897709815
+    496102.876384550  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.897811827  WAKE                           num_fds:0
+    496102.897845044  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496102.897851795  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.897852431  DEV_SLEEP_TIME                 dev:8 wake:000496102.919179159
+    496102.897853869  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.919280565  WAKE                           num_fds:0
+    496102.919313952  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496102.919320728  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.919321274  DEV_SLEEP_TIME                 dev:8 wake:000496102.940648037
+    496102.919322707  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.940901293  WAKE                           num_fds:0
+    496102.940934504  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496102.940941105  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.940941661  DEV_SLEEP_TIME                 dev:8 wake:000496102.962268614
+    496102.940943109  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.962374715  WAKE                           num_fds:0
+    496102.962408604  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496102.962415304  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.962415840  DEV_SLEEP_TIME                 dev:8 wake:000496102.983742834
+    496102.962417289  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496102.983989801  WAKE                           num_fds:0
+    496102.984022416  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496102.984029127  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496102.984029598  DEV_SLEEP_TIME                 dev:8 wake:000496103.005356531
+    496102.984031001  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.005604715  WAKE                           num_fds:0
+    496103.005637877  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496103.005644412  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.005644948  DEV_SLEEP_TIME                 dev:8 wake:000496103.026971961
+    496103.005646371  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.027219901  WAKE                           num_fds:0
+    496103.027252371  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496103.027259051  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.027259603  DEV_SLEEP_TIME                 dev:8 wake:000496103.048586471
+    496103.027261016  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.048836534  WAKE                           num_fds:0
+    496103.048869601  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496103.048876291  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.048876837  DEV_SLEEP_TIME                 dev:8 wake:000496103.070203690
+    496103.048878406  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.070456866  WAKE                           num_fds:0
+    496103.070490058  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496103.070496793  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.070497324  DEV_SLEEP_TIME                 dev:8 wake:000496103.091824152
+    496103.070498778  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.092072047  WAKE                           num_fds:0
+    496103.092104782  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496103.092111468  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.092112014  DEV_SLEEP_TIME                 dev:8 wake:000496103.113438897
+    496103.092113457  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.113689061  WAKE                           num_fds:0
+    496103.113722453  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496103.113729133  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.113729675  DEV_SLEEP_TIME                 dev:8 wake:000496103.135056578
+    496103.113731128  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.135140323  WAKE                           num_fds:0
+    496103.135172087  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496103.135178677  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.135179223  DEV_SLEEP_TIME                 dev:8 wake:000496103.156506176
+    496103.135180651  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.156755693  WAKE                           num_fds:0
+    496103.156789141  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496103.156795941  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.156796473  DEV_SLEEP_TIME                 dev:8 wake:000496103.178123281
+    496103.156797911  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.178369600  WAKE                           num_fds:0
+    496103.178402637  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496103.178409347  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.178409878  DEV_SLEEP_TIME                 dev:8 wake:000496103.199736726
+    496103.178411312  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.199985012  WAKE                           num_fds:0
+    496103.200018454  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496103.200025154  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.200025706  DEV_SLEEP_TIME                 dev:8 wake:000496103.221352569
+    496103.200027164  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.221411887  WAKE                           num_fds:0
+    496103.221449599  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496103.221457367  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.221458149  DEV_SLEEP_TIME                 dev:8 wake:000496103.242784100
+    496103.221460163  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.243030786  WAKE                           num_fds:0
+    496103.243063321  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496103.243069992  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.243070543  DEV_SLEEP_TIME                 dev:8 wake:000496103.264397441
+    496103.243071991  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.264449458  WAKE                           num_fds:0
+    496103.264482559  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496103.264489289  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.264489826  DEV_SLEEP_TIME                 dev:8 wake:000496103.285816679
+    496103.264491259  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.285917784  WAKE                           num_fds:0
+    496103.285950585  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496103.285957336  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.285957867  DEV_SLEEP_TIME                 dev:8 wake:000496103.307284700
+    496103.285959300  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.307384411  WAKE                           num_fds:0
+    496103.307417092  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496103.307423752  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.307424298  DEV_SLEEP_TIME                 dev:8 wake:000496103.328751181
+    496103.307425727  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.328852327  WAKE                           num_fds:0
+    496103.328885203  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496103.328891733  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.328892279  DEV_SLEEP_TIME                 dev:8 wake:000496103.350219293
+    496103.328893698  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.350469121  WAKE                           num_fds:0
+    496103.350501911  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496103.350508482  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.350509028  DEV_SLEEP_TIME                 dev:8 wake:000496103.371836026
+    496103.350510602  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.372086512  WAKE                           num_fds:0
+    496103.372119994  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496103.372126719  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.372127266  DEV_SLEEP_TIME                 dev:8 wake:000496103.393454084
+    496103.372128694  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.393705741  WAKE                           num_fds:0
+    496103.393738817  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496103.393745367  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.393745909  DEV_SLEEP_TIME                 dev:8 wake:000496103.415072907
+    496103.393747357  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.415333750  WAKE                           num_fds:0
+    496103.415396976  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496103.415404193  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.415404729  DEV_SLEEP_TIME                 dev:8 wake:000496103.436731547
+    496103.415406162  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.436814764  WAKE                           num_fds:0
+    496103.436838871  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496103.436844444  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.436844804  DEV_SLEEP_TIME                 dev:8 wake:000496103.458172708
+    496103.436845753  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.458400798  WAKE                           num_fds:0
+    496103.458425703  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496103.458431340  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.458431693  DEV_SLEEP_TIME                 dev:8 wake:000496103.479759544
+    496103.458432626  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.479984935  WAKE                           num_fds:0
+    496103.480009455  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496103.480014980  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.480015343  DEV_SLEEP_TIME                 dev:8 wake:000496103.501343283
+    496103.480016274  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.501571552  WAKE                           num_fds:0
+    496103.501596146  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496103.501601748  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.501602114  DEV_SLEEP_TIME                 dev:8 wake:000496103.522930023
+    496103.501603047  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.523156936  WAKE                           num_fds:0
+    496103.523181532  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496103.523187086  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.523187437  DEV_SLEEP_TIME                 dev:8 wake:000496103.544515344
+    496103.523188369  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.544740125  WAKE                           num_fds:0
+    496103.544764472  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496103.544770082  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.544770446  DEV_SLEEP_TIME                 dev:8 wake:000496103.566098335
+    496103.544771384  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.566324453  WAKE                           num_fds:0
+    496103.566371205  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496103.566377992  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.566378422  DEV_SLEEP_TIME                 dev:8 wake:000496103.587705271
+    496103.566379814  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.587786308  WAKE                           num_fds:0
+    496103.587811809  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496103.587817500  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.587817863  DEV_SLEEP_TIME                 dev:8 wake:000496103.609145680
+    496103.587818973  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.609376370  WAKE                           num_fds:0
+    496103.609400933  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496103.609406578  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.609406944  DEV_SLEEP_TIME                 dev:8 wake:000496103.630734830
+    496103.609407885  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.630960573  WAKE                           num_fds:0
+    496103.630985847  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496103.630991468  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.630991821  DEV_SLEEP_TIME                 dev:8 wake:000496103.652319678
+    496103.630992762  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.652564901  WAKE                           num_fds:0
+    496103.652597787  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496103.652604413  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.652604944  DEV_SLEEP_TIME                 dev:8 wake:000496103.673931877
+    496103.652606367  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.674031369  WAKE                           num_fds:0
+    496103.674064982  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496103.674071517  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.674072063  DEV_SLEEP_TIME                 dev:8 wake:000496103.695399066
+    496103.674073511  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.695648238  WAKE                           num_fds:0
+    496103.695681735  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496103.695688531  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.695689082  DEV_SLEEP_TIME                 dev:8 wake:000496103.717015820
+    496103.695690631  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.717266806  WAKE                           num_fds:0
+    496103.717300549  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496103.717307220  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.717307761  DEV_SLEEP_TIME                 dev:8 wake:000496103.738634664
+    496103.717309194  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.738734857  WAKE                           num_fds:0
+    496103.738766890  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496103.738773646  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.738774192  DEV_SLEEP_TIME                 dev:8 wake:000496103.760101015
+    496103.738775641  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.760377525  WAKE                           num_fds:0
+    496103.760411473  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496103.760418114  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.760418660  DEV_SLEEP_TIME                 dev:8 wake:000496103.781745613
+    496103.760420098  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.781991819  WAKE                           num_fds:0
+    496103.782024419  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496103.782031049  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.782031595  DEV_SLEEP_TIME                 dev:8 wake:000496103.803358509
+    496103.782033034  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.803607936  WAKE                           num_fds:0
+    496103.803640907  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496103.803647532  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.803648068  DEV_SLEEP_TIME                 dev:8 wake:000496103.824975016
+    496103.803649532  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.825225166  WAKE                           num_fds:0
+    496103.825257711  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496103.825264397  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.825264928  DEV_SLEEP_TIME                 dev:8 wake:000496103.846591806
+    496103.825266386  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.846843268  WAKE                           num_fds:0
+    496103.846875868  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496103.846882468  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.846883004  DEV_SLEEP_TIME                 dev:8 wake:000496103.868210043
+    496103.846884453  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.868311589  WAKE                           num_fds:0
+    496103.868369121  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496103.868377471  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.868378172  DEV_SLEEP_TIME                 dev:8 wake:000496103.889703602
+    496103.868379906  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.889953992  WAKE                           num_fds:0
+    496103.889987179  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496103.889993729  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.889994275  DEV_SLEEP_TIME                 dev:8 wake:000496103.911321284
+    496103.889995693  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.911575091  WAKE                           num_fds:0
+    496103.911608678  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496103.911615268  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.911615805  DEV_SLEEP_TIME                 dev:8 wake:000496103.932942778
+    496103.911617248  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.933189961  WAKE                           num_fds:0
+    496103.933222922  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496103.933229587  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.933230138  DEV_SLEEP_TIME                 dev:8 wake:000496103.954557037
+    496103.933231577  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.954805246  WAKE                           num_fds:0
+    496103.954837916  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496103.954844572  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.954845108  DEV_SLEEP_TIME                 dev:8 wake:000496103.976172026
+    496103.954846546  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.976423272  WAKE                           num_fds:0
+    496103.976456529  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496103.976463194  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.976463851  DEV_SLEEP_TIME                 dev:8 wake:000496103.997790639
+    496103.976465369  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496103.998041575  WAKE                           num_fds:0
+    496103.998074456  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496103.998081157  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496103.998081628  DEV_SLEEP_TIME                 dev:8 wake:000496104.019408596
+    496103.998083011  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.019658098  WAKE                           num_fds:0
+    496104.019691300  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496104.019697830  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.019698386  DEV_SLEEP_TIME                 dev:8 wake:000496104.041025390
+    496104.019699855  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.041127397  WAKE                           num_fds:0
+    496104.041159612  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496104.041166252  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.041166798  DEV_SLEEP_TIME                 dev:8 wake:000496104.062493731
+    496104.041168216  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.062742647  WAKE                           num_fds:0
+    496104.062776280  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496104.062782875  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.062783416  DEV_SLEEP_TIME                 dev:8 wake:000496104.084110370
+    496104.062784850  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.084368467  WAKE                           num_fds:0
+    496104.084401583  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496104.084408108  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.084408649  DEV_SLEEP_TIME                 dev:8 wake:000496104.105735673
+    496104.084410068  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.105982034  WAKE                           num_fds:0
+    496104.106015741  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496104.106022367  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.106022913  DEV_SLEEP_TIME                 dev:8 wake:000496104.127349861
+    496104.106024341  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.127600586  WAKE                           num_fds:0
+    496104.127633903  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496104.127640669  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.127641320  DEV_SLEEP_TIME                 dev:8 wake:000496104.148968038
+    496104.127642744  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.149220703  WAKE                           num_fds:0
+    496104.149253800  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496104.149260330  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.149260871  DEV_SLEEP_TIME                 dev:8 wake:000496104.170587935
+    496104.149262269  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.170721179  WAKE                           num_fds:0
+    496104.170754495  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496104.170761211  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.170761747  DEV_SLEEP_TIME                 dev:8 wake:000496104.192088610
+    496104.170763170  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.192334148  WAKE                           num_fds:0
+    496104.192385321  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496104.192391932  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.192392483  DEV_SLEEP_TIME                 dev:8 wake:000496104.213719466
+    496104.192393901  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.213969626  WAKE                           num_fds:0
+    496104.214003173  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496104.214009784  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.214010325  DEV_SLEEP_TIME                 dev:8 wake:000496104.235337263
+    496104.214011758  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.235589010  WAKE                           num_fds:0
+    496104.235622558  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496104.235629233  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.235629765  DEV_SLEEP_TIME                 dev:8 wake:000496104.256956668
+    496104.235631193  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.257204853  WAKE                           num_fds:0
+    496104.257238110  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496104.257244850  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.257245391  DEV_SLEEP_TIME                 dev:8 wake:000496104.278572214
+    496104.257246820  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.278826247  WAKE                           num_fds:0
+    496104.278861418  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496104.278868550  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.278869111  DEV_SLEEP_TIME                 dev:8 wake:000496104.300195693
+    496104.278870524  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.300444238  WAKE                           num_fds:0
+    496104.300477420  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496104.300484135  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.300484672  DEV_SLEEP_TIME                 dev:8 wake:000496104.321811585
+    496104.300486110  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.322062401  WAKE                           num_fds:0
+    496104.322095693  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496104.322102308  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.322102975  DEV_SLEEP_TIME                 dev:8 wake:000496104.343429772
+    496104.322104433  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.343531524  WAKE                           num_fds:0
+    496104.343564685  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496104.343571270  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.343571817  DEV_SLEEP_TIME                 dev:8 wake:000496104.364898785
+    496104.343573265  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.365146048  WAKE                           num_fds:0
+    496104.365179570  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496104.365186266  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.365186807  DEV_SLEEP_TIME                 dev:8 wake:000496104.386513665
+    496104.365188245  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.386763683  WAKE                           num_fds:0
+    496104.386797366  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496104.386803886  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.386804427  DEV_SLEEP_TIME                 dev:8 wake:000496104.408131476
+    496104.386805876  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.408381509  WAKE                           num_fds:0
+    496104.408415418  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496104.408422178  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.408422729  DEV_SLEEP_TIME                 dev:8 wake:000496104.429749482
+    496104.408424293  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.430002674  WAKE                           num_fds:0
+    496104.430036221  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496104.430042706  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.430043283  DEV_SLEEP_TIME                 dev:8 wake:000496104.451370316
+    496104.430044801  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.451618891  WAKE                           num_fds:0
+    496104.451651647  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496104.451658357  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.451658904  DEV_SLEEP_TIME                 dev:8 wake:000496104.472985797
+    496104.451660332  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.473234513  WAKE                           num_fds:0
+    496104.473268396  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496104.473274946  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.473275518  DEV_SLEEP_TIME                 dev:8 wake:000496104.494602496
+    496104.473276971  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.494850299  WAKE                           num_fds:0
+    496104.494883872  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496104.494890522  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.494891058  DEV_SLEEP_TIME                 dev:8 wake:000496104.516217982
+    496104.494892497  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.516465429  WAKE                           num_fds:0
+    496104.516497849  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496104.516504484  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.516505025  DEV_SLEEP_TIME                 dev:8 wake:000496104.537831949
+    496104.516506474  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.538084509  WAKE                           num_fds:0
+    496104.538116688  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496104.538123419  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.538123950  DEV_SLEEP_TIME                 dev:8 wake:000496104.559450813
+    496104.538125398  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.559701493  WAKE                           num_fds:0
+    496104.559734554  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496104.559741089  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.559741635  DEV_SLEEP_TIME                 dev:8 wake:000496104.581068669
+    496104.559743104  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.581317008  WAKE                           num_fds:0
+    496104.581373920  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496104.581381016  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.581381557  DEV_SLEEP_TIME                 dev:8 wake:000496104.602708135
+    496104.581383061  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.602955819  WAKE                           num_fds:0
+    496104.602989075  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496104.602995756  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.602996417  DEV_SLEEP_TIME                 dev:8 wake:000496104.624323190
+    496104.602997951  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.624574071  WAKE                           num_fds:0
+    496104.624606786  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496104.624613336  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.624613887  DEV_SLEEP_TIME                 dev:8 wake:000496104.645940886
+    496104.624615321  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.646039125  WAKE                           num_fds:0
+    496104.646072131  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496104.646078791  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.646079338  DEV_SLEEP_TIME                 dev:8 wake:000496104.667406256
+    496104.646080766  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.667623494  WAKE                           num_fds:0
+    496104.667664082  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496104.667672797  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.667673469  DEV_SLEEP_TIME                 dev:8 wake:000496104.688999335
+    496104.667675403  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.689252361  WAKE                           num_fds:0
+    496104.689285583  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496104.689292348  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.689292895  DEV_SLEEP_TIME                 dev:8 wake:000496104.710619672
+    496104.689294348  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.710870232  WAKE                           num_fds:0
+    496104.710903940  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496104.710910650  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.710911312  DEV_SLEEP_TIME                 dev:8 wake:000496104.732238050
+    496104.710912735  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.732487847  WAKE                           num_fds:0
+    496104.732520778  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496104.732527314  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.732527860  DEV_SLEEP_TIME                 dev:8 wake:000496104.753854878
+    496104.732529308  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.754105003  WAKE                           num_fds:0
+    496104.754137408  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496104.754144128  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.754144664  DEV_SLEEP_TIME                 dev:8 wake:000496104.775471517
+    496104.754146103  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.775719892  WAKE                           num_fds:0
+    496104.775752763  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496104.775759303  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.775759849  DEV_SLEEP_TIME                 dev:8 wake:000496104.797086893
+    496104.775761277  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.797331022  WAKE                           num_fds:0
+    496104.797383769  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496104.797390394  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.797390941  DEV_SLEEP_TIME                 dev:8 wake:000496104.818717909
+    496104.797392374  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.818964300  WAKE                           num_fds:0
+    496104.818996575  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496104.819003275  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.819003816  DEV_SLEEP_TIME                 dev:8 wake:000496104.840330689
+    496104.819005234  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.840580843  WAKE                           num_fds:0
+    496104.840613864  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496104.840620555  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.840621086  DEV_SLEEP_TIME                 dev:8 wake:000496104.861947939
+    496104.840622509  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.862198068  WAKE                           num_fds:0
+    496104.862231100  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496104.862237675  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.862238216  DEV_SLEEP_TIME                 dev:8 wake:000496104.883565189
+    496104.862239805  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.883815458  WAKE                           num_fds:0
+    496104.883848640  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496104.883855210  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.883855756  DEV_SLEEP_TIME                 dev:8 wake:000496104.905182735
+    496104.883857185  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.905430593  WAKE                           num_fds:0
+    496104.905463559  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496104.905470210  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.905470756  DEV_SLEEP_TIME                 dev:8 wake:000496104.926797684
+    496104.905472199  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.927045448  WAKE                           num_fds:0
+    496104.927078660  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496104.927085360  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.927085897  DEV_SLEEP_TIME                 dev:8 wake:000496104.948412810
+    496104.927087325  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.948489252  WAKE                           num_fds:0
+    496104.948522459  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496104.948529140  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.948529691  DEV_SLEEP_TIME                 dev:8 wake:000496104.969856534
+    496104.948531134  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.969958296  WAKE                           num_fds:0
+    496104.969991277  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496104.969997942  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.969998494  DEV_SLEEP_TIME                 dev:8 wake:000496104.991325397
+    496104.969999942  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496104.991576864  WAKE                           num_fds:0
+    496104.991610752  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496104.991617362  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496104.991617838  DEV_SLEEP_TIME                 dev:8 wake:000496105.012944852
+    496104.991619206  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.013197317  WAKE                           num_fds:0
+    496105.013229992  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496105.013236557  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.013237093  DEV_SLEEP_TIME                 dev:8 wake:000496105.034564082
+    496105.013238527  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.034815719  WAKE                           num_fds:0
+    496105.034849256  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496105.034855866  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.034856413  DEV_SLEEP_TIME                 dev:8 wake:000496105.056183386
+    496105.034857836  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.056432743  WAKE                           num_fds:0
+    496105.056465699  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496105.056472284  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.056472830  DEV_SLEEP_TIME                 dev:8 wake:000496105.077799809
+    496105.056474259  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.078050495  WAKE                           num_fds:0
+    496105.078083877  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496105.078090507  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.078091058  DEV_SLEEP_TIME                 dev:8 wake:000496105.099417966
+    496105.078092492  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.099669604  WAKE                           num_fds:0
+    496105.099702540  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496105.099707727  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.099708263  DEV_SLEEP_TIME                 dev:8 wake:000496105.121036634
+    496105.099709746  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.121285461  WAKE                           num_fds:0
+    496105.121317951  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496105.121324611  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.121325268  DEV_SLEEP_TIME                 dev:8 wake:000496105.142652076
+    496105.121326736  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.142903778  WAKE                           num_fds:0
+    496105.142936749  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496105.142943460  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.142943991  DEV_SLEEP_TIME                 dev:8 wake:000496105.164270849
+    496105.142945419  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.164519885  WAKE                           num_fds:0
+    496105.164552300  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496105.164558900  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.164559441  DEV_SLEEP_TIME                 dev:8 wake:000496105.185886405
+    496105.164560985  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.186137030  WAKE                           num_fds:0
+    496105.186170513  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496105.186177243  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.186177769  DEV_SLEEP_TIME                 dev:8 wake:000496105.207504597
+    496105.186179198  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.207754355  WAKE                           num_fds:0
+    496105.207786910  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496105.207793495  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.207794042  DEV_SLEEP_TIME                 dev:8 wake:000496105.229121015
+    496105.207795495  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.229371835  WAKE                           num_fds:0
+    496105.229404380  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496105.229411061  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.229411587  DEV_SLEEP_TIME                 dev:8 wake:000496105.250738520
+    496105.229413065  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.250989191  WAKE                           num_fds:0
+    496105.251022533  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496105.251029098  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.251029649  DEV_SLEEP_TIME                 dev:8 wake:000496105.272356643
+    496105.251031078  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.272465525  WAKE                           num_fds:0
+    496105.272498271  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496105.272505137  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.272505678  DEV_SLEEP_TIME                 dev:8 wake:000496105.293832411
+    496105.272507131  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.294080130  WAKE                           num_fds:0
+    496105.294113276  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496105.294119922  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.294120458  DEV_SLEEP_TIME                 dev:8 wake:000496105.315447346
+    496105.294121896  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.315695525  WAKE                           num_fds:0
+    496105.315727564  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496105.315734204  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.315734751  DEV_SLEEP_TIME                 dev:8 wake:000496105.337061674
+    496105.315736194  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.337311722  WAKE                           num_fds:0
+    496105.337363963  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496105.337370999  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.337371535  DEV_SLEEP_TIME                 dev:8 wake:000496105.358698148
+    496105.337373004  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.358950763  WAKE                           num_fds:0
+    496105.358983855  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496105.358990410  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.358990956  DEV_SLEEP_TIME                 dev:8 wake:000496105.380317989
+    496105.358992429  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.380568549  WAKE                           num_fds:0
+    496105.380601365  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496105.380607900  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.380608451  DEV_SLEEP_TIME                 dev:8 wake:000496105.401935460
+    496105.380609894  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.402180688  WAKE                           num_fds:0
+    496105.402212987  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496105.402219633  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.402220169  DEV_SLEEP_TIME                 dev:8 wake:000496105.423547082
+    496105.402221627  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.423793407  WAKE                           num_fds:0
+    496105.423826664  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496105.423833274  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.423833815  DEV_SLEEP_TIME                 dev:8 wake:000496105.445160758
+    496105.423835239  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.445409013  WAKE                           num_fds:0
+    496105.445441924  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496105.445448494  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.445449045  DEV_SLEEP_TIME                 dev:8 wake:000496105.466776074
+    496105.445450639  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.467024434  WAKE                           num_fds:0
+    496105.467057591  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496105.467064336  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.467064882  DEV_SLEEP_TIME                 dev:8 wake:000496105.488391700
+    496105.467066331  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.488641904  WAKE                           num_fds:0
+    496105.488674479  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496105.488681345  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.488681882  DEV_SLEEP_TIME                 dev:8 wake:000496105.510008579
+    496105.488683310  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.510259461  WAKE                           num_fds:0
+    496105.510292828  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496105.510299403  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.510299944  DEV_SLEEP_TIME                 dev:8 wake:000496105.531626892
+    496105.510301372  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.531877692  WAKE                           num_fds:0
+    496105.531910704  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496105.531917324  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.531917855  DEV_SLEEP_TIME                 dev:8 wake:000496105.553244793
+    496105.531919283  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.553365934  WAKE                           num_fds:0
+    496105.553398950  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496105.553405576  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.553406122  DEV_SLEEP_TIME                 dev:8 wake:000496105.574733040
+    496105.553407535  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.574834893  WAKE                           num_fds:0
+    496105.574867167  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496105.574873822  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.574874354  DEV_SLEEP_TIME                 dev:8 wake:000496105.596201262
+    496105.574875787  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.596446710  WAKE                           num_fds:0
+    496105.596479731  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496105.596486276  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.596486812  DEV_SLEEP_TIME                 dev:8 wake:000496105.617813856
+    496105.596488281  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.618065669  WAKE                           num_fds:0
+    496105.618099372  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496105.618106092  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.618106619  DEV_SLEEP_TIME                 dev:8 wake:000496105.639433497
+    496105.618108077  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.639683655  WAKE                           num_fds:0
+    496105.639714487  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496105.639721508  SET_DEV_WAKE                   dev:8 hw_level:2000 sleep:976
+    496105.639722054  DEV_SLEEP_TIME                 dev:8 wake:000496105.660048626
+    496105.639723477  SLEEP                          sleep:000000000.020333333 longest_wake:000158140
+    496105.660301504  WAKE                           num_fds:0
+    496105.660334535  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496105.660367015  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.660367572  DEV_SLEEP_TIME                 dev:8 wake:000496105.681668625
+    496105.660369040  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.681942991  WAKE                           num_fds:0
+    496105.681976147  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496105.681982883  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.681983419  DEV_SLEEP_TIME                 dev:8 wake:000496105.703310237
+    496105.681984857  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.703558436  WAKE                           num_fds:0
+    496105.703591708  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496105.703598413  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.703598950  DEV_SLEEP_TIME                 dev:8 wake:000496105.724925813
+    496105.703600388  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.725176313  WAKE                           num_fds:0
+    496105.725208658  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496105.725215549  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.725216080  DEV_SLEEP_TIME                 dev:8 wake:000496105.746542803
+    496105.725217568  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.746792781  WAKE                           num_fds:0
+    496105.746825331  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496105.746831876  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.746832453  DEV_SLEEP_TIME                 dev:8 wake:000496105.768159431
+    496105.746833886  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.768409820  WAKE                           num_fds:0
+    496105.768442711  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496105.768449406  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.768449953  DEV_SLEEP_TIME                 dev:8 wake:000496105.789776891
+    496105.768451421  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.790025698  WAKE                           num_fds:0
+    496105.790058914  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496105.790065650  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.790066186  DEV_SLEEP_TIME                 dev:8 wake:000496105.811393034
+    496105.790067619  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.811643934  WAKE                           num_fds:0
+    496105.811676870  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496105.811683431  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.811683977  DEV_SLEEP_TIME                 dev:8 wake:000496105.833010970
+    496105.811685410  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.833259676  WAKE                           num_fds:0
+    496105.833293284  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496105.833299784  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.833300325  DEV_SLEEP_TIME                 dev:8 wake:000496105.854627374
+    496105.833301869  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.854876535  WAKE                           num_fds:0
+    496105.854909596  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496105.854916192  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.854916733  DEV_SLEEP_TIME                 dev:8 wake:000496105.876243686
+    496105.854918171  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.876365955  WAKE                           num_fds:0
+    496105.876403862  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496105.876410763  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.876411304  DEV_SLEEP_TIME                 dev:8 wake:000496105.897738112
+    496105.876412748  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.897985014  WAKE                           num_fds:0
+    496105.898017996  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496105.898024566  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.898025112  DEV_SLEEP_TIME                 dev:8 wake:000496105.919352085
+    496105.898026530  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.919602019  WAKE                           num_fds:0
+    496105.919635095  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496105.919641750  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.919642291  DEV_SLEEP_TIME                 dev:8 wake:000496105.940969270
+    496105.919643785  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.941221058  WAKE                           num_fds:0
+    496105.941253709  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496105.941260544  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.941261091  DEV_SLEEP_TIME                 dev:8 wake:000496105.962587808
+    496105.941262514  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.962838809  WAKE                           num_fds:0
+    496105.962871800  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496105.962878330  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.962878866  DEV_SLEEP_TIME                 dev:8 wake:000496105.984205910
+    496105.962880375  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496105.984453327  WAKE                           num_fds:0
+    496105.984486108  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496105.984492768  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496105.984493239  DEV_SLEEP_TIME                 dev:8 wake:000496106.005820173
+    496105.984494638  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496106.006072327  WAKE                           num_fds:0
+    496106.006105654  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496106.006112179  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496106.006112715  DEV_SLEEP_TIME                 dev:8 wake:000496106.027439754
+    496106.006114163  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496106.027691130  WAKE                           num_fds:0
+    496106.027723174  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496106.027729900  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496106.027730446  DEV_SLEEP_TIME                 dev:8 wake:000496106.049057314
+    496106.027731899  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496106.049118332  WAKE                           num_fds:0
+    496106.049149760  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496106.049156335  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496106.049156886  DEV_SLEEP_TIME                 dev:8 wake:000496106.070483854
+    496106.049158304  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496106.070587359  WAKE                           num_fds:0
+    496106.070620506  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496106.070627071  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496106.070627617  DEV_SLEEP_TIME                 dev:8 wake:000496106.091954571
+    496106.070629041  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496106.092200591  WAKE                           num_fds:0
+    496106.092233722  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496106.092240292  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496106.092240839  DEV_SLEEP_TIME                 dev:8 wake:000496106.113567797
+    496106.092242292  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496106.113817610  WAKE                           num_fds:0
+    496106.113850350  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496106.113857006  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496106.113857657  DEV_SLEEP_TIME                 dev:8 wake:000496106.135184485
+    496106.113859181  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496106.135434824  WAKE                           num_fds:0
+    496106.135468362  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496106.135474907  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496106.135475448  DEV_SLEEP_TIME                 dev:8 wake:000496106.156802487
+    496106.135476881  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496106.157054420  WAKE                           num_fds:0
+    496106.157087412  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496106.157094092  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496106.157094628  DEV_SLEEP_TIME                 dev:8 wake:000496106.178421511
+    496106.157096102  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496106.178468120  WAKE                           num_fds:0
+    496106.178501477  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496106.178508318  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496106.178508849  DEV_SLEEP_TIME                 dev:8 wake:000496106.199835752
+    496106.178510348  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496106.200086092  WAKE                           num_fds:0
+    496106.200118818  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496106.200125523  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496106.200126065  DEV_SLEEP_TIME                 dev:8 wake:000496106.221452913
+    496106.200127518  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496106.221581095  WAKE                           num_fds:0
+    496106.221614773  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496106.221621473  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496106.221621999  DEV_SLEEP_TIME                 dev:8 wake:000496106.242948877
+    496106.221623513  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496106.243200696  WAKE                           num_fds:0
+    496106.243234359  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496106.243241069  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496106.243241610  DEV_SLEEP_TIME                 dev:8 wake:000496106.264568453
+    496106.243243039  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496106.264635264  WAKE                           num_fds:0
+    496106.264667323  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496106.264673953  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496106.264674489  DEV_SLEEP_TIME                 dev:8 wake:000496106.286001423
+    496106.264675948  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496106.286249247  WAKE                           num_fds:0
+    496106.286282017  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496106.286288618  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496106.286289159  DEV_SLEEP_TIME                 dev:8 wake:000496106.307616107
+    496106.286290592  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496106.307868020  WAKE                           num_fds:0
+    496106.307901131  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496106.307907807  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496106.307908353  DEV_SLEEP_TIME                 dev:8 wake:000496106.329235196
+    496106.307909776  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496106.329485164  WAKE                           num_fds:0
+    496106.329518181  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496106.329525026  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496106.329525563  DEV_SLEEP_TIME                 dev:8 wake:000496106.350852295
+    496106.329527071  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496106.350909770  WAKE                           num_fds:0
+    496106.350941839  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496106.350948820  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496106.350949357  DEV_SLEEP_TIME                 dev:8 wake:000496106.372276054
+    496106.350950795  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496106.372524735  WAKE                           num_fds:0
+    496106.372558297  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496106.372564937  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496106.372565469  DEV_SLEEP_TIME                 dev:8 wake:000496106.393892407
+    496106.372566922  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496106.394139539  WAKE                           num_fds:0
+    496106.394172305  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496106.394178965  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496106.394179517  DEV_SLEEP_TIME                 dev:8 wake:000496106.415506450
+    496106.394180940  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496106.415761560  WAKE                           num_fds:0
+    496106.415794401  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496106.415801106  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496106.415801663  DEV_SLEEP_TIME                 dev:8 wake:000496106.437128511
+    496106.415803201  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496106.437377767  WAKE                           num_fds:0
+    496106.437410964  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496106.437417664  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496106.437418206  DEV_SLEEP_TIME                 dev:8 wake:000496106.458745059
+    496106.437419634  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496106.458813434  WAKE                           num_fds:0
+    496106.458845648  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496106.458852308  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496106.458852850  DEV_SLEEP_TIME                 dev:8 wake:000496106.480179718
+    496106.458854293  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496106.480280397  WAKE                           num_fds:0
+    496106.480312832  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496106.480319598  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496106.480320129  DEV_SLEEP_TIME                 dev:8 wake:000496106.501646992
+    496106.480321598  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496106.501898760  WAKE                           num_fds:0
+    496106.501932046  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496106.501938717  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496106.501939258  DEV_SLEEP_TIME                 dev:8 wake:000496106.523266191
+    496106.501940681  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496106.523515268  WAKE                           num_fds:0
+    496106.523547652  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496106.523554283  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496106.523554824  DEV_SLEEP_TIME                 dev:8 wake:000496106.544881747
+    496106.523556262  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496106.545129010  WAKE                           num_fds:0
+    496106.545161906  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496106.545168631  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496106.545169168  DEV_SLEEP_TIME                 dev:8 wake:000496106.566495986
+    496106.545170621  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496106.566746781  WAKE                           num_fds:0
+    496106.566779802  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496106.566786387  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496106.566786938  DEV_SLEEP_TIME                 dev:8 wake:000496106.588113892
+    496106.566788472  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496106.588381170  WAKE                           num_fds:0
+    496106.588413956  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496106.588420536  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496106.588421082  DEV_SLEEP_TIME                 dev:8 wake:000496106.609748050
+    496106.588422505  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496106.609997674  WAKE                           num_fds:0
+    496106.610030184  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496106.610036814  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496106.610037365  DEV_SLEEP_TIME                 dev:8 wake:000496106.631364289
+    496106.610038794  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496106.631612047  WAKE                           num_fds:0
+    496106.631644993  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496106.631651568  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496106.631652109  DEV_SLEEP_TIME                 dev:8 wake:000496106.652979113
+    496106.631653543  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496106.653079141  WAKE                           num_fds:0
+    496106.653111641  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496106.653118306  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496106.653118862  DEV_SLEEP_TIME                 dev:8 wake:000496106.674445796
+    496106.653120301  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496106.674531169  WAKE                           num_fds:0
+    496106.674566811  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496106.674573477  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496106.674574018  DEV_SLEEP_TIME                 dev:8 wake:000496106.695900961
+    496106.674575446  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496106.696158187  WAKE                           num_fds:0
+    496106.696191840  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496106.696198555  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496106.696199092  DEV_SLEEP_TIME                 dev:8 wake:000496106.717525935
+    496106.696201021  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496106.717774134  WAKE                           num_fds:0
+    496106.717807591  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496106.717814186  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496106.717814727  DEV_SLEEP_TIME                 dev:8 wake:000496106.739141681
+    496106.717816196  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496106.739392065  WAKE                           num_fds:0
+    496106.739426254  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496106.739432934  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496106.739433591  DEV_SLEEP_TIME                 dev:8 wake:000496106.760760369
+    496106.739435179  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496106.760007023  WAKE                           num_fds:1
+    496106.760016440  PB_MSG                         msg_id:1
+    496106.760018008  DEV_REMOVED                    dev:8
+    496106.760032377  SLEEP                          sleep:000000000.000000000 longest_wake:000158140
+    496118.354002034  WAKE                           num_fds:1
+    496118.354013360  PB_MSG                         msg_id:0
+    496118.354015856  DEV_ADDED                      dev:8
+    496118.354055002  ODEV_NO_STREAMS                dev:8 hw_level:0 write:2048
+    496118.354057131  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496118.354057663  DEV_SLEEP_TIME                 dev:8 wake:000496118.375389076
+    496118.354058795  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496118.354063185  WAKE                           num_fds:1
+    496118.354066032  PB_MSG                         msg_id:2
+    496118.354066598  WRITE_STREAMS_WAIT             stream:140000
+    496118.354078641  STREAM_ADDED                   id:140000 dev:8
+    496118.354085291  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2048
+    496118.354096267  FILL_AUDIO                     dev:8 hw_level:2048
+    496118.354097760  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496118.354098121  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496118.354098893  WRITE_STREAMS_MIXED            write_limit:0
+    496118.354111888  FILL_AUDIO_DONE                hw_level:2048 total_written:0 min_cb_level:1024
+    496118.354120337  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:2048
+    496118.354120738  DEV_SLEEP_TIME                 dev:8 wake:000496118.396779331
+    496118.354121530  SLEEP                          sleep:000000000.042666666 longest_wake:000158140
+    496118.354648175  WAKE                           num_fds:1
+    496118.354678825  FILL_AUDIO                     dev:8 hw_level:2048
+    496118.354682173  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496118.354690728  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496118.354702069  DEV_STREAM_MIX                 written:1024 read:1024
+    496118.354703026  WRITE_STREAMS_MIXED            write_limit:1024
+    496118.354705141  FILL_AUDIO_DONE                hw_level:2048 total_written:1024 min_cb_level:1024
+    496118.354706975  STREAM_SLEEP_TIME              id:140000 wake:000496118.375409268
+    496118.354714473  SET_DEV_WAKE                   dev:8 hw_level:3072 sleep:3072
+    496118.354714989  DEV_SLEEP_TIME                 dev:8 wake:000496118.418706179
+    496118.354716537  SLEEP                          sleep:000000000.020703089 longest_wake:000158140
+    496118.375445608  WAKE                           num_fds:0
+    496118.375470776  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496118.375493288  FILL_AUDIO                     dev:8 hw_level:2208
+    496118.375498029  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496118.375498460  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496118.375499492  WRITE_STREAMS_MIXED            write_limit:0
+    496118.375502529  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496118.375511756  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496118.375512242  DEV_SLEEP_TIME                 dev:8 wake:000496118.421503923
+    496118.375514156  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496118.375625007  WAKE                           num_fds:1
+    496118.375650521  FILL_AUDIO                     dev:8 hw_level:2208
+    496118.375652766  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496118.375665305  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496118.375680355  DEV_STREAM_MIX                 written:1024 read:1024
+    496118.375681277  WRITE_STREAMS_MIXED            write_limit:1024
+    496118.375683131  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496118.375684780  STREAM_SLEEP_TIME              id:140000 wake:000496118.396742601
+    496118.375692087  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496118.375692633  DEV_SLEEP_TIME                 dev:8 wake:000496118.443017306
+    496118.375694217  SLEEP                          sleep:000000000.021058628 longest_wake:000158140
+    496118.396778232  WAKE                           num_fds:0
+    496118.396804492  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2224
+    496118.396830803  FILL_AUDIO                     dev:8 hw_level:2176
+    496118.396835373  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496118.396835890  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496118.396837007  WRITE_STREAMS_MIXED            write_limit:0
+    496118.396839979  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496118.396848248  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496118.396848825  DEV_SLEEP_TIME                 dev:8 wake:000496118.442174490
+    496118.396850488  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496118.396934603  WAKE                           num_fds:1
+    496118.396958914  FILL_AUDIO                     dev:8 hw_level:2176
+    496118.396961299  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496118.396978694  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496118.396986317  DEV_STREAM_MIX                 written:1024 read:1024
+    496118.396987114  WRITE_STREAMS_MIXED            write_limit:1024
+    496118.396989123  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496118.396990582  STREAM_SLEEP_TIME              id:140000 wake:000496118.418075934
+    496118.396997628  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496118.396998149  DEV_SLEEP_TIME                 dev:8 wake:000496118.463656556
+    496118.396999758  SLEEP                          sleep:000000000.021086044 longest_wake:000158140
+    496118.418106179  WAKE                           num_fds:0
+    496118.418127885  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496118.418148733  FILL_AUDIO                     dev:8 hw_level:2192
+    496118.418152962  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496118.418153584  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496118.418154666  WRITE_STREAMS_MIXED            write_limit:0
+    496118.418157608  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496118.418166053  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496118.418166554  DEV_SLEEP_TIME                 dev:8 wake:000496118.463825722
+    496118.418168558  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496118.418266114  WAKE                           num_fds:1
+    496118.418294374  FILL_AUDIO                     dev:8 hw_level:2192
+    496118.418296063  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496118.418306216  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496118.418312010  DEV_STREAM_MIX                 written:1024 read:1024
+    496118.418312576  WRITE_STREAMS_MIXED            write_limit:1024
+    496118.418313588  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496118.418314871  STREAM_SLEEP_TIME              id:140000 wake:000496118.439409267
+    496118.418320720  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496118.418321136  DEV_SLEEP_TIME                 dev:8 wake:000496118.485314360
+    496118.418322293  SLEEP                          sleep:000000000.021094907 longest_wake:000158140
+    496118.439496761  WAKE                           num_fds:0
+    496118.439530519  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496118.439586553  FILL_AUDIO                     dev:8 hw_level:2208
+    496118.439591966  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496118.439592507  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496118.439593695  WRITE_STREAMS_MIXED            write_limit:0
+    496118.439597258  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496118.439605853  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496118.439606354  DEV_SLEEP_TIME                 dev:8 wake:000496118.485598576
+    496118.439608218  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496118.439716288  WAKE                           num_fds:1
+    496118.439750747  FILL_AUDIO                     dev:8 hw_level:2208
+    496118.439752842  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496118.439767887  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496118.439772748  DEV_STREAM_MIX                 written:1024 read:1024
+    496118.439773409  WRITE_STREAMS_MIXED            write_limit:1024
+    496118.439774372  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496118.439775434  STREAM_SLEEP_TIME              id:140000 wake:000496118.460742600
+    496118.439781538  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496118.439781894  DEV_SLEEP_TIME                 dev:8 wake:000496118.507108286
+    496118.439782906  SLEEP                          sleep:000000000.020967647 longest_wake:000158140
+    496118.460789142  WAKE                           num_fds:0
+    496118.460810982  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2224
+    496118.460836150  FILL_AUDIO                     dev:8 hw_level:2176
+    496118.460840610  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496118.460841127  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496118.460842124  WRITE_STREAMS_MIXED            write_limit:0
+    496118.460844995  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496118.460853270  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496118.460853771  DEV_SLEEP_TIME                 dev:8 wake:000496118.506179516
+    496118.460855876  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496118.460930107  WAKE                           num_fds:1
+    496118.460954508  FILL_AUDIO                     dev:8 hw_level:2176
+    496118.460955716  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496118.460966080  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496118.460971678  DEV_STREAM_MIX                 written:1024 read:1024
+    496118.460972239  WRITE_STREAMS_MIXED            write_limit:1024
+    496118.460973136  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496118.460974234  STREAM_SLEEP_TIME              id:140000 wake:000496118.482075933
+    496118.460979857  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496118.460980238  DEV_SLEEP_TIME                 dev:8 wake:000496118.527640454
+    496118.460981235  SLEEP                          sleep:000000000.021102145 longest_wake:000158140
+    496118.482113920  WAKE                           num_fds:0
+    496118.482135974  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496118.482159569  FILL_AUDIO                     dev:8 hw_level:2192
+    496118.482163130  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496118.482163491  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496118.482164624  WRITE_STREAMS_MIXED            write_limit:0
+    496118.482167216  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496118.482175522  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496118.482175939  DEV_SLEEP_TIME                 dev:8 wake:000496118.527835011
+    496118.482177634  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496118.482244261  WAKE                           num_fds:1
+    496118.482270354  FILL_AUDIO                     dev:8 hw_level:2192
+    496118.482271744  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496118.482283118  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496118.482287734  DEV_STREAM_MIX                 written:1024 read:1024
+    496118.482288242  WRITE_STREAMS_MIXED            write_limit:1024
+    496118.482289425  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496118.482290547  STREAM_SLEEP_TIME              id:140000 wake:000496118.503409266
+    496118.482297115  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496118.482297491  DEV_SLEEP_TIME                 dev:8 wake:000496118.549289966
+    496118.482298539  SLEEP                          sleep:000000000.021119300 longest_wake:000158140
+    496118.503438723  WAKE                           num_fds:0
+    496118.503461769  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496118.503484060  FILL_AUDIO                     dev:8 hw_level:2208
+    496118.503488733  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496118.503489426  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496118.503490351  WRITE_STREAMS_MIXED            write_limit:0
+    496118.503493748  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496118.503503115  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496118.503503465  DEV_SLEEP_TIME                 dev:8 wake:000496118.549495009
+    496118.503505371  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496118.503555888  WAKE                           num_fds:1
+    496118.503581022  FILL_AUDIO                     dev:8 hw_level:2208
+    496118.503582739  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496118.503592868  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496118.503597479  DEV_STREAM_MIX                 written:1024 read:1024
+    496118.503598142  WRITE_STREAMS_MIXED            write_limit:1024
+    496118.503599527  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496118.503600467  STREAM_SLEEP_TIME              id:140000 wake:000496118.524742599
+    496118.503605592  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496118.503605971  DEV_SLEEP_TIME                 dev:8 wake:000496118.570933428
+    496118.503607064  SLEEP                          sleep:000000000.021142504 longest_wake:000158140
+    496118.524771187  WAKE                           num_fds:0
+    496118.524792444  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2224
+    496118.524811297  FILL_AUDIO                     dev:8 hw_level:2224
+    496118.524814347  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496118.524814633  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496118.524815430  WRITE_STREAMS_MIXED            write_limit:0
+    496118.524817932  FILL_AUDIO_DONE                hw_level:2224 total_written:0 min_cb_level:1024
+    496118.524825913  SET_DEV_WAKE                   dev:8 hw_level:2224 sleep:2224
+    496118.524826242  DEV_SLEEP_TIME                 dev:8 wake:000496118.571152173
+    496118.524827565  SLEEP                          sleep:000000000.046333333 longest_wake:000158140
+    496118.524879030  WAKE                           num_fds:1
+    496118.524900851  FILL_AUDIO                     dev:8 hw_level:2176
+    496118.524902685  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496118.524911230  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496118.524915787  DEV_STREAM_MIX                 written:1024 read:1024
+    496118.524916319  WRITE_STREAMS_MIXED            write_limit:1024
+    496118.524917543  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496118.524918588  STREAM_SLEEP_TIME              id:140000 wake:000496118.546075932
+    496118.524924426  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496118.524924899  DEV_SLEEP_TIME                 dev:8 wake:000496118.591584778
+    496118.524925885  SLEEP                          sleep:000000000.021157820 longest_wake:000158140
+    496118.546097655  WAKE                           num_fds:0
+    496118.546112541  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496118.546126703  FILL_AUDIO                     dev:8 hw_level:2192
+    496118.546128925  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496118.546129092  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496118.546129613  WRITE_STREAMS_MIXED            write_limit:0
+    496118.546130952  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496118.546136209  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496118.546136370  DEV_SLEEP_TIME                 dev:8 wake:000496118.591798078
+    496118.546137203  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496118.546161324  WAKE                           num_fds:1
+    496118.546173430  FILL_AUDIO                     dev:8 hw_level:2192
+    496118.546174217  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496118.546179310  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496118.546183028  DEV_STREAM_MIX                 written:1024 read:1024
+    496118.546183368  WRITE_STREAMS_MIXED            write_limit:1024
+    496118.546183858  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496118.546184331  STREAM_SLEEP_TIME              id:140000 wake:000496118.567409265
+    496118.546189248  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496118.546189390  DEV_SLEEP_TIME                 dev:8 wake:000496118.613184163
+    496118.546189920  SLEEP                          sleep:000000000.021225102 longest_wake:000158140
+    496118.567424239  WAKE                           num_fds:0
+    496118.567439411  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496118.567452286  FILL_AUDIO                     dev:8 hw_level:2208
+    496118.567454872  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496118.567455174  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496118.567455825  WRITE_STREAMS_MIXED            write_limit:0
+    496118.567457476  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496118.567464131  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496118.567464314  DEV_SLEEP_TIME                 dev:8 wake:000496118.613458159
+    496118.567465126  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496118.567490875  WAKE                           num_fds:1
+    496118.567505725  FILL_AUDIO                     dev:8 hw_level:2208
+    496118.567506502  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496118.567511232  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496118.567512996  DEV_STREAM_MIX                 written:1024 read:1024
+    496118.567513203  WRITE_STREAMS_MIXED            write_limit:1024
+    496118.567513727  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496118.567514129  STREAM_SLEEP_TIME              id:140000 wake:000496118.588742598
+    496118.567518840  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496118.567518992  DEV_SLEEP_TIME                 dev:8 wake:000496118.634847296
+    496118.567519449  SLEEP                          sleep:000000000.021228635 longest_wake:000158140
+    496118.588780218  WAKE                           num_fds:0
+    496118.588794235  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2224
+    496118.588810187  FILL_AUDIO                     dev:8 hw_level:2224
+    496118.588812252  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496118.588812393  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496118.588812942  WRITE_STREAMS_MIXED            write_limit:0
+    496118.588814561  FILL_AUDIO_DONE                hw_level:2224 total_written:0 min_cb_level:1024
+    496118.588819039  SET_DEV_WAKE                   dev:8 hw_level:2224 sleep:2224
+    496118.588819202  DEV_SLEEP_TIME                 dev:8 wake:000496118.635148443
+    496118.588820036  SLEEP                          sleep:000000000.046333333 longest_wake:000158140
+    496118.588854210  WAKE                           num_fds:1
+    496118.588870487  FILL_AUDIO                     dev:8 hw_level:2176
+    496118.588871082  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496118.588877727  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496118.588880046  DEV_STREAM_MIX                 written:1024 read:1024
+    496118.588880375  WRITE_STREAMS_MIXED            write_limit:1024
+    496118.588881027  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496118.588881474  STREAM_SLEEP_TIME              id:140000 wake:000496118.610075931
+    496118.588886516  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496118.588886664  DEV_SLEEP_TIME                 dev:8 wake:000496118.655547937
+    496118.588887092  SLEEP                          sleep:000000000.021194660 longest_wake:000158140
+    496118.610097814  WAKE                           num_fds:0
+    496118.610123399  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496118.610137913  FILL_AUDIO                     dev:8 hw_level:2192
+    496118.610140374  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496118.610140519  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496118.610141505  WRITE_STREAMS_MIXED            write_limit:0
+    496118.610143585  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496118.610149415  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496118.610149652  DEV_SLEEP_TIME                 dev:8 wake:000496118.655810785
+    496118.610150372  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496118.610181468  WAKE                           num_fds:1
+    496118.610197004  FILL_AUDIO                     dev:8 hw_level:2192
+    496118.610198230  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496118.610203984  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496118.610206515  DEV_STREAM_MIX                 written:1024 read:1024
+    496118.610206788  WRITE_STREAMS_MIXED            write_limit:1024
+    496118.610207314  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496118.610207787  STREAM_SLEEP_TIME              id:140000 wake:000496118.631409264
+    496118.610212963  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496118.610213165  DEV_SLEEP_TIME                 dev:8 wake:000496118.677207596
+    496118.610213722  SLEEP                          sleep:000000000.021201668 longest_wake:000158140
+    496118.631437687  WAKE                           num_fds:0
+    496118.631451138  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496118.631464477  FILL_AUDIO                     dev:8 hw_level:2208
+    496118.631466576  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496118.631466740  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496118.631467215  WRITE_STREAMS_MIXED            write_limit:0
+    496118.631468559  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496118.631474164  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496118.631474394  DEV_SLEEP_TIME                 dev:8 wake:000496118.677468970
+    496118.631475094  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496118.631557823  WAKE                           num_fds:1
+    496118.631572529  FILL_AUDIO                     dev:8 hw_level:2208
+    496118.631573346  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496118.631578696  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496118.631580226  DEV_STREAM_MIX                 written:1024 read:1024
+    496118.631580423  WRITE_STREAMS_MIXED            write_limit:1024
+    496118.631581039  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496118.631581413  STREAM_SLEEP_TIME              id:140000 wake:000496118.652742597
+    496118.631585882  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496118.631585996  DEV_SLEEP_TIME                 dev:8 wake:000496118.698914565
+    496118.631586399  SLEEP                          sleep:000000000.021161365 longest_wake:000158140
+    496118.652804884  WAKE                           num_fds:0
+    496118.652853602  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2176
+    496118.652906300  FILL_AUDIO                     dev:8 hw_level:2176
+    496118.652916970  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496118.652917116  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496118.652918057  WRITE_STREAMS_MIXED            write_limit:0
+    496118.652923991  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496118.652949242  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496118.652950075  DEV_SLEEP_TIME                 dev:8 wake:000496118.698258600
+    496118.652951721  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496118.653034042  WAKE                           num_fds:1
+    496118.653082023  FILL_AUDIO                     dev:8 hw_level:2176
+    496118.653087608  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496118.653115701  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496118.653126099  DEV_STREAM_MIX                 written:1024 read:1024
+    496118.653126366  WRITE_STREAMS_MIXED            write_limit:1024
+    496118.653128297  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496118.653129600  STREAM_SLEEP_TIME              id:140000 wake:000496118.674075930
+    496118.653141084  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496118.653141227  DEV_SLEEP_TIME                 dev:8 wake:000496118.719796011
+    496118.653142617  SLEEP                          sleep:000000000.020946585 longest_wake:000158140
+    496118.674106400  WAKE                           num_fds:0
+    496118.674120878  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496118.674135054  FILL_AUDIO                     dev:8 hw_level:2192
+    496118.674137010  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496118.674137173  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496118.674137648  WRITE_STREAMS_MIXED            write_limit:0
+    496118.674139240  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496118.674144657  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496118.674144812  DEV_SLEEP_TIME                 dev:8 wake:000496118.719806407
+    496118.674145446  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496118.674178767  WAKE                           num_fds:1
+    496118.674190201  FILL_AUDIO                     dev:8 hw_level:2192
+    496118.674190857  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496118.674195897  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496118.674197446  DEV_STREAM_MIX                 written:1024 read:1024
+    496118.674197623  WRITE_STREAMS_MIXED            write_limit:1024
+    496118.674197903  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496118.674198238  STREAM_SLEEP_TIME              id:140000 wake:000496118.695409263
+    496118.674202644  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496118.674202766  DEV_SLEEP_TIME                 dev:8 wake:000496118.741198093
+    496118.674203141  SLEEP                          sleep:000000000.021211170 longest_wake:000158140
+    496118.695425973  WAKE                           num_fds:0
+    496118.695442160  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496118.695456413  FILL_AUDIO                     dev:8 hw_level:2208
+    496118.695458598  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496118.695458801  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496118.695459398  WRITE_STREAMS_MIXED            write_limit:0
+    496118.695461090  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496118.695466757  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496118.695466949  DEV_SLEEP_TIME                 dev:8 wake:000496118.741461697
+    496118.695467723  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496118.695503662  WAKE                           num_fds:1
+    496118.695518956  FILL_AUDIO                     dev:8 hw_level:2208
+    496118.695519793  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496118.695526046  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496118.695528211  DEV_STREAM_MIX                 written:1024 read:1024
+    496118.695528463  WRITE_STREAMS_MIXED            write_limit:1024
+    496118.695528914  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496118.695529424  STREAM_SLEEP_TIME              id:140000 wake:000496118.716742596
+    496118.695534156  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496118.695534347  DEV_SLEEP_TIME                 dev:8 wake:000496118.762862556
+    496118.695534780  SLEEP                          sleep:000000000.021213373 longest_wake:000158140
+    496118.716796026  WAKE                           num_fds:0
+    496118.716844186  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2176
+    496118.716881094  FILL_AUDIO                     dev:8 hw_level:2176
+    496118.716892655  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496118.716892910  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496118.716894646  WRITE_STREAMS_MIXED            write_limit:0
+    496118.716899983  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496118.716910139  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496118.716910410  DEV_SLEEP_TIME                 dev:8 wake:000496118.762235198
+    496118.716914465  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496118.717037504  WAKE                           num_fds:1
+    496118.717069219  FILL_AUDIO                     dev:8 hw_level:2176
+    496118.717073251  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496118.717093009  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496118.717099155  DEV_STREAM_MIX                 written:1024 read:1024
+    496118.717099507  WRITE_STREAMS_MIXED            write_limit:1024
+    496118.717100701  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496118.717101460  STREAM_SLEEP_TIME              id:140000 wake:000496118.738075929
+    496118.717107009  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496118.717107210  DEV_SLEEP_TIME                 dev:8 wake:000496118.783767828
+    496118.717109169  SLEEP                          sleep:000000000.020974767 longest_wake:000158140
+    496118.738099922  WAKE                           num_fds:0
+    496118.738113821  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496118.738128082  FILL_AUDIO                     dev:8 hw_level:2192
+    496118.738130192  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496118.738130387  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496118.738130972  WRITE_STREAMS_MIXED            write_limit:0
+    496118.738132446  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496118.738137824  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496118.738138054  DEV_SLEEP_TIME                 dev:8 wake:000496118.783799661
+    496118.738138719  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496118.738174290  WAKE                           num_fds:1
+    496118.738189554  FILL_AUDIO                     dev:8 hw_level:2192
+    496118.738190236  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496118.738195073  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496118.738198050  DEV_STREAM_MIX                 written:1024 read:1024
+    496118.738198380  WRITE_STREAMS_MIXED            write_limit:1024
+    496118.738198938  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496118.738199357  STREAM_SLEEP_TIME              id:140000 wake:000496118.759409262
+    496118.738203929  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496118.738204062  DEV_SLEEP_TIME                 dev:8 wake:000496118.805199158
+    496118.738204447  SLEEP                          sleep:000000000.021210104 longest_wake:000158140
+    496118.759429248  WAKE                           num_fds:0
+    496118.759443563  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496118.759460244  FILL_AUDIO                     dev:8 hw_level:2208
+    496118.759462267  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496118.759462398  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496118.759462818  WRITE_STREAMS_MIXED            write_limit:0
+    496118.759464345  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496118.759469501  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496118.759469655  DEV_SLEEP_TIME                 dev:8 wake:000496118.805464831
+    496118.759470367  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496118.759517748  WAKE                           num_fds:1
+    496118.759531678  FILL_AUDIO                     dev:8 hw_level:2208
+    496118.759532357  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496118.759537800  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496118.759540617  DEV_STREAM_MIX                 written:1024 read:1024
+    496118.759540905  WRITE_STREAMS_MIXED            write_limit:1024
+    496118.759541444  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496118.759541852  STREAM_SLEEP_TIME              id:140000 wake:000496118.780742595
+    496118.759546251  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496118.759546357  DEV_SLEEP_TIME                 dev:8 wake:000496118.826875025
+    496118.759546696  SLEEP                          sleep:000000000.021200903 longest_wake:000158140
+    496118.780759760  WAKE                           num_fds:0
+    496118.780775008  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2224
+    496118.780790131  FILL_AUDIO                     dev:8 hw_level:2224
+    496118.780791975  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496118.780792123  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496118.780792803  WRITE_STREAMS_MIXED            write_limit:0
+    496118.780793974  FILL_AUDIO_DONE                hw_level:2224 total_written:0 min_cb_level:1024
+    496118.780800286  SET_DEV_WAKE                   dev:8 hw_level:2224 sleep:2224
+    496118.780800421  DEV_SLEEP_TIME                 dev:8 wake:000496118.827128100
+    496118.780801289  SLEEP                          sleep:000000000.046333333 longest_wake:000158140
+    496118.780847586  WAKE                           num_fds:1
+    496118.780862518  FILL_AUDIO                     dev:8 hw_level:2176
+    496118.780863379  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496118.780868464  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496118.780870041  DEV_STREAM_MIX                 written:1024 read:1024
+    496118.780870230  WRITE_STREAMS_MIXED            write_limit:1024
+    496118.780870593  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496118.780870958  STREAM_SLEEP_TIME              id:140000 wake:000496118.802075928
+    496118.780875355  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496118.780875478  DEV_SLEEP_TIME                 dev:8 wake:000496118.847537449
+    496118.780875847  SLEEP                          sleep:000000000.021205145 longest_wake:000158140
+    496118.802102780  WAKE                           num_fds:0
+    496118.802120472  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496118.802137210  FILL_AUDIO                     dev:8 hw_level:2192
+    496118.802139917  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496118.802140064  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496118.802140609  WRITE_STREAMS_MIXED            write_limit:0
+    496118.802142432  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496118.802147701  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496118.802147852  DEV_SLEEP_TIME                 dev:8 wake:000496118.847809557
+    496118.802148516  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496118.802195380  WAKE                           num_fds:1
+    496118.802209967  FILL_AUDIO                     dev:8 hw_level:2192
+    496118.802210703  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496118.802217788  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496118.802219789  DEV_STREAM_MIX                 written:1024 read:1024
+    496118.802219989  WRITE_STREAMS_MIXED            write_limit:1024
+    496118.802220449  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496118.802220872  STREAM_SLEEP_TIME              id:140000 wake:000496118.823409261
+    496118.802225303  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496118.802225418  DEV_SLEEP_TIME                 dev:8 wake:000496118.869220669
+    496118.802225820  SLEEP                          sleep:000000000.021188592 longest_wake:000158140
+    496118.823426698  WAKE                           num_fds:0
+    496118.823441151  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496118.823457708  FILL_AUDIO                     dev:8 hw_level:2208
+    496118.823460089  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496118.823460330  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496118.823460828  WRITE_STREAMS_MIXED            write_limit:0
+    496118.823462430  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496118.823467790  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496118.823467949  DEV_SLEEP_TIME                 dev:8 wake:000496118.869462918
+    496118.823468618  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496118.823507440  WAKE                           num_fds:1
+    496118.823521661  FILL_AUDIO                     dev:8 hw_level:2208
+    496118.823522559  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496118.823529596  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496118.823531920  DEV_STREAM_MIX                 written:1024 read:1024
+    496118.823532244  WRITE_STREAMS_MIXED            write_limit:1024
+    496118.823533007  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496118.823533383  STREAM_SLEEP_TIME              id:140000 wake:000496118.844742594
+    496118.823538141  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496118.823538313  DEV_SLEEP_TIME                 dev:8 wake:000496118.890866568
+    496118.823538782  SLEEP                          sleep:000000000.021209359 longest_wake:000158140
+    496118.844793611  WAKE                           num_fds:0
+    496118.844842455  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2176
+    496118.844888762  FILL_AUDIO                     dev:8 hw_level:2176
+    496118.844899355  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496118.844899489  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496118.844901921  WRITE_STREAMS_MIXED            write_limit:0
+    496118.844909466  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496118.844922258  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496118.844923289  DEV_SLEEP_TIME                 dev:8 wake:000496118.890245017
+    496118.844926573  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496118.845050151  WAKE                           num_fds:1
+    496118.845096292  FILL_AUDIO                     dev:8 hw_level:2176
+    496118.845101172  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496118.845127890  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496118.845137332  DEV_STREAM_MIX                 written:1024 read:1024
+    496118.845138226  WRITE_STREAMS_MIXED            write_limit:1024
+    496118.845140015  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496118.845143090  STREAM_SLEEP_TIME              id:140000 wake:000496118.866075927
+    496118.845154493  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496118.845155261  DEV_SLEEP_TIME                 dev:8 wake:000496118.911809504
+    496118.845156233  SLEEP                          sleep:000000000.020933089 longest_wake:000158140
+    496118.866103566  WAKE                           num_fds:0
+    496118.866119526  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496118.866134595  FILL_AUDIO                     dev:8 hw_level:2192
+    496118.866137519  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496118.866137677  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496118.866138149  WRITE_STREAMS_MIXED            write_limit:0
+    496118.866140088  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496118.866146294  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496118.866146511  DEV_SLEEP_TIME                 dev:8 wake:000496118.911807282
+    496118.866147537  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496118.866177435  WAKE                           num_fds:1
+    496118.866192557  FILL_AUDIO                     dev:8 hw_level:2192
+    496118.866193498  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496118.866198506  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496118.866200264  DEV_STREAM_MIX                 written:1024 read:1024
+    496118.866200426  WRITE_STREAMS_MIXED            write_limit:1024
+    496118.866200819  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496118.866201185  STREAM_SLEEP_TIME              id:140000 wake:000496118.887409260
+    496118.866205681  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496118.866205793  DEV_SLEEP_TIME                 dev:8 wake:000496118.933201012
+    496118.866206313  SLEEP                          sleep:000000000.021208248 longest_wake:000158140
+    496118.887425173  WAKE                           num_fds:0
+    496118.887438794  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496118.887455874  FILL_AUDIO                     dev:8 hw_level:2208
+    496118.887457788  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496118.887457958  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496118.887458388  WRITE_STREAMS_MIXED            write_limit:0
+    496118.887460020  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496118.887465184  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496118.887465331  DEV_SLEEP_TIME                 dev:8 wake:000496118.933460516
+    496118.887465992  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496118.887502924  WAKE                           num_fds:1
+    496118.887517026  FILL_AUDIO                     dev:8 hw_level:2208
+    496118.887517553  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496118.887522198  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496118.887524704  DEV_STREAM_MIX                 written:1024 read:1024
+    496118.887524986  WRITE_STREAMS_MIXED            write_limit:1024
+    496118.887525564  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496118.887525976  STREAM_SLEEP_TIME              id:140000 wake:000496118.908742593
+    496118.887530536  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496118.887530658  DEV_SLEEP_TIME                 dev:8 wake:000496118.954859148
+    496118.887531007  SLEEP                          sleep:000000000.021216778 longest_wake:000158140
+    496118.908761014  WAKE                           num_fds:0
+    496118.908774255  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2224
+    496118.908788078  FILL_AUDIO                     dev:8 hw_level:2224
+    496118.908791095  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496118.908791227  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496118.908791643  WRITE_STREAMS_MIXED            write_limit:0
+    496118.908792879  FILL_AUDIO_DONE                hw_level:2224 total_written:0 min_cb_level:1024
+    496118.908798303  SET_DEV_WAKE                   dev:8 hw_level:2224 sleep:2224
+    496118.908798448  DEV_SLEEP_TIME                 dev:8 wake:000496118.955126660
+    496118.908799066  SLEEP                          sleep:000000000.046333333 longest_wake:000158140
+    496118.908840065  WAKE                           num_fds:1
+    496118.908854501  FILL_AUDIO                     dev:8 hw_level:2176
+    496118.908855141  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496118.908859990  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496118.908861559  DEV_STREAM_MIX                 written:1024 read:1024
+    496118.908861758  WRITE_STREAMS_MIXED            write_limit:1024
+    496118.908862178  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496118.908862536  STREAM_SLEEP_TIME              id:140000 wake:000496118.930075926
+    496118.908866946  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496118.908867060  DEV_SLEEP_TIME                 dev:8 wake:000496118.975529057
+    496118.908867432  SLEEP                          sleep:000000000.021213535 longest_wake:000158140
+    496118.930094024  WAKE                           num_fds:0
+    496118.930107968  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496118.930124375  FILL_AUDIO                     dev:8 hw_level:2192
+    496118.930126805  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496118.930127218  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496118.930127938  WRITE_STREAMS_MIXED            write_limit:0
+    496118.930129478  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496118.930136050  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496118.930136345  DEV_SLEEP_TIME                 dev:8 wake:000496118.975796868
+    496118.930137375  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496118.930180425  WAKE                           num_fds:1
+    496118.930195856  FILL_AUDIO                     dev:8 hw_level:2192
+    496118.930197015  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496118.930202916  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496118.930204953  DEV_STREAM_MIX                 written:1024 read:1024
+    496118.930205305  WRITE_STREAMS_MIXED            write_limit:1024
+    496118.930205985  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496118.930206375  STREAM_SLEEP_TIME              id:140000 wake:000496118.951409259
+    496118.930211062  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496118.930211211  DEV_SLEEP_TIME                 dev:8 wake:000496118.997206196
+    496118.930211579  SLEEP                          sleep:000000000.021203063 longest_wake:000158140
+    496118.951427153  WAKE                           num_fds:0
+    496118.951440867  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496118.951457640  FILL_AUDIO                     dev:8 hw_level:2208
+    496118.951459818  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496118.951459979  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496118.951460418  WRITE_STREAMS_MIXED            write_limit:0
+    496118.951462188  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496118.951468096  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496118.951468253  DEV_SLEEP_TIME                 dev:8 wake:000496118.997462723
+    496118.951469025  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496118.951511365  WAKE                           num_fds:1
+    496118.951527108  FILL_AUDIO                     dev:8 hw_level:2208
+    496118.951527922  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496118.951533170  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496118.951534775  DEV_STREAM_MIX                 written:1024 read:1024
+    496118.951534973  WRITE_STREAMS_MIXED            write_limit:1024
+    496118.951536328  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496118.951536704  STREAM_SLEEP_TIME              id:140000 wake:000496118.972742592
+    496118.951541447  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496118.951541573  DEV_SLEEP_TIME                 dev:8 wake:000496119.018869867
+    496118.951542050  SLEEP                          sleep:000000000.021206058 longest_wake:000158140
+    496118.972761770  WAKE                           num_fds:0
+    496118.972776276  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2224
+    496118.972797345  FILL_AUDIO                     dev:8 hw_level:2224
+    496118.972803518  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496118.972803884  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496118.972804436  WRITE_STREAMS_MIXED            write_limit:0
+    496118.972806221  FILL_AUDIO_DONE                hw_level:2224 total_written:0 min_cb_level:1024
+    496118.972822039  SET_DEV_WAKE                   dev:8 hw_level:2224 sleep:2224
+    496118.972822283  DEV_SLEEP_TIME                 dev:8 wake:000496119.019140179
+    496118.972823594  SLEEP                          sleep:000000000.046333333 longest_wake:000158140
+    496118.972882579  WAKE                           num_fds:1
+    496118.972898523  FILL_AUDIO                     dev:8 hw_level:2176
+    496118.972899309  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496118.972905056  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496118.972906877  DEV_STREAM_MIX                 written:1024 read:1024
+    496118.972907047  WRITE_STREAMS_MIXED            write_limit:1024
+    496118.972907575  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496118.972907888  STREAM_SLEEP_TIME              id:140000 wake:000496118.994075925
+    496118.972912755  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496118.972912878  DEV_SLEEP_TIME                 dev:8 wake:000496119.039574389
+    496118.972913362  SLEEP                          sleep:000000000.021168202 longest_wake:000158140
+    496118.994114414  WAKE                           num_fds:0
+    496118.994140794  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496118.994156916  FILL_AUDIO                     dev:8 hw_level:2192
+    496118.994159491  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496118.994159650  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496118.994160169  WRITE_STREAMS_MIXED            write_limit:0
+    496118.994164191  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496118.994171200  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496118.994171359  DEV_SLEEP_TIME                 dev:8 wake:000496119.039831814
+    496118.994172168  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496118.994205388  WAKE                           num_fds:1
+    496118.994222444  FILL_AUDIO                     dev:8 hw_level:2192
+    496118.994223295  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496118.994229362  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496118.994232914  DEV_STREAM_MIX                 written:1024 read:1024
+    496118.994233254  WRITE_STREAMS_MIXED            write_limit:1024
+    496118.994233932  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496118.994234486  STREAM_SLEEP_TIME              id:140000 wake:000496119.015409258
+    496118.994239352  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496118.994239573  DEV_SLEEP_TIME                 dev:8 wake:000496119.061234321
+    496118.994239966  SLEEP                          sleep:000000000.021174937 longest_wake:000158140
+    496119.015494273  WAKE                           num_fds:0
+    496119.015517241  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496119.015543933  FILL_AUDIO                     dev:8 hw_level:2208
+    496119.015548213  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.015548684  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.015549922  WRITE_STREAMS_MIXED            write_limit:0
+    496119.015552888  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496119.015562300  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496119.015563077  DEV_SLEEP_TIME                 dev:8 wake:000496119.061554262
+    496119.015565252  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496119.015647747  WAKE                           num_fds:1
+    496119.015677927  FILL_AUDIO                     dev:8 hw_level:2208
+    496119.015679811  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.015695257  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.015700629  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.015701276  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.015702835  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496119.015704443  STREAM_SLEEP_TIME              id:140000 wake:000496119.036742591
+    496119.015711715  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496119.015712136  DEV_SLEEP_TIME                 dev:8 wake:000496119.083037125
+    496119.015713529  SLEEP                          sleep:000000000.021038799 longest_wake:000158140
+    496119.036811692  WAKE                           num_fds:0
+    496119.036832364  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2176
+    496119.036857117  FILL_AUDIO                     dev:8 hw_level:2176
+    496119.036861557  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.036862023  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.036863201  WRITE_STREAMS_MIXED            write_limit:0
+    496119.036866368  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496119.036875123  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496119.036875629  DEV_SLEEP_TIME                 dev:8 wake:000496119.082201545
+    496119.036877418  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496119.036947164  WAKE                           num_fds:1
+    496119.036973731  FILL_AUDIO                     dev:8 hw_level:2176
+    496119.036975349  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.036987853  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.036993702  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.036994293  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.036995441  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496119.036996538  STREAM_SLEEP_TIME              id:140000 wake:000496119.058075924
+    496119.037002352  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496119.037002703  DEV_SLEEP_TIME                 dev:8 wake:000496119.103662693
+    496119.037003665  SLEEP                          sleep:000000000.021079897 longest_wake:000158140
+    496119.058114096  WAKE                           num_fds:0
+    496119.058141173  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496119.058170250  FILL_AUDIO                     dev:8 hw_level:2192
+    496119.058174380  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.058174856  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.058176079  WRITE_STREAMS_MIXED            write_limit:0
+    496119.058179060  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496119.058186803  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496119.058187274  DEV_SLEEP_TIME                 dev:8 wake:000496119.103846909
+    496119.058189705  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496119.058271589  WAKE                           num_fds:1
+    496119.058298411  FILL_AUDIO                     dev:8 hw_level:2192
+    496119.058300596  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.058314814  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.058320702  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.058321610  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.058323068  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496119.058324672  STREAM_SLEEP_TIME              id:140000 wake:000496119.079409257
+    496119.058343765  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496119.058344301  DEV_SLEEP_TIME                 dev:8 wake:000496119.125323950
+    496119.058345935  SLEEP                          sleep:000000000.021085307 longest_wake:000158140
+    496119.079480188  WAKE                           num_fds:0
+    496119.079495256  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496119.079509743  FILL_AUDIO                     dev:8 hw_level:2208
+    496119.079512120  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.079512360  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.079512974  WRITE_STREAMS_MIXED            write_limit:0
+    496119.079514674  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496119.079521296  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496119.079521544  DEV_SLEEP_TIME                 dev:8 wake:000496119.125515340
+    496119.079522548  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496119.079554700  WAKE                           num_fds:1
+    496119.079570214  FILL_AUDIO                     dev:8 hw_level:2208
+    496119.079570828  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.079578731  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.079580760  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.079581043  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.079581535  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496119.079582051  STREAM_SLEEP_TIME              id:140000 wake:000496119.100742590
+    496119.079586774  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496119.079586940  DEV_SLEEP_TIME                 dev:8 wake:000496119.146915148
+    496119.079587422  SLEEP                          sleep:000000000.021160775 longest_wake:000158140
+    496119.100983708  WAKE                           num_fds:0
+    496119.101006952  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2176
+    496119.101039086  FILL_AUDIO                     dev:8 hw_level:2176
+    496119.101043727  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.101044840  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.101045897  WRITE_STREAMS_MIXED            write_limit:0
+    496119.101049455  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496119.101057769  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496119.101058316  DEV_SLEEP_TIME                 dev:8 wake:000496119.146384086
+    496119.101060616  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496119.101152679  WAKE                           num_fds:1
+    496119.101184647  FILL_AUDIO                     dev:8 hw_level:2176
+    496119.101186787  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.101201316  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.101205696  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.101206237  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.101207300  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496119.101208487  STREAM_SLEEP_TIME              id:140000 wake:000496119.122075923
+    496119.101214476  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496119.101214857  DEV_SLEEP_TIME                 dev:8 wake:000496119.167874687
+    496119.101215909  SLEEP                          sleep:000000000.020867902 longest_wake:000158140
+    496119.122173783  WAKE                           num_fds:0
+    496119.122197212  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496119.122228920  FILL_AUDIO                     dev:8 hw_level:2192
+    496119.122234067  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.122234538  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.122235896  WRITE_STREAMS_MIXED            write_limit:0
+    496119.122238828  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496119.122248681  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496119.122249172  DEV_SLEEP_TIME                 dev:8 wake:000496119.167907429
+    496119.122251026  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496119.122367113  WAKE                           num_fds:1
+    496119.122402269  FILL_AUDIO                     dev:8 hw_level:2192
+    496119.122404324  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.122419379  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.122424220  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.122424812  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.122425909  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496119.122427007  STREAM_SLEEP_TIME              id:140000 wake:000496119.143409256
+    496119.122433276  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496119.122433662  DEV_SLEEP_TIME                 dev:8 wake:000496119.189426520
+    496119.122434704  SLEEP                          sleep:000000000.020982736 longest_wake:000158140
+    496119.143482275  WAKE                           num_fds:0
+    496119.143518679  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496119.143547377  FILL_AUDIO                     dev:8 hw_level:2208
+    496119.143553840  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.143554353  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.143555696  WRITE_STREAMS_MIXED            write_limit:0
+    496119.143561538  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496119.143570165  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496119.143570707  DEV_SLEEP_TIME                 dev:8 wake:000496119.189563077
+    496119.143572956  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496119.143667142  WAKE                           num_fds:1
+    496119.143695684  FILL_AUDIO                     dev:8 hw_level:2208
+    496119.143698005  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.143716969  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.143722870  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.143723459  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.143724850  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496119.143725889  STREAM_SLEEP_TIME              id:140000 wake:000496119.164742589
+    496119.143732011  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496119.143732364  DEV_SLEEP_TIME                 dev:8 wake:000496119.211058825
+    496119.143734365  SLEEP                          sleep:000000000.021017097 longest_wake:000158140
+    496119.164992925  WAKE                           num_fds:0
+    496119.165016424  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2176
+    496119.165048107  FILL_AUDIO                     dev:8 hw_level:2176
+    496119.165052342  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.165053244  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.165054607  WRITE_STREAMS_MIXED            write_limit:0
+    496119.165057874  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496119.165065933  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496119.165066519  DEV_SLEEP_TIME                 dev:8 wake:000496119.210392460
+    496119.165068399  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496119.165163333  WAKE                           num_fds:1
+    496119.165194605  FILL_AUDIO                     dev:8 hw_level:2176
+    496119.165196530  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.165211549  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.165216014  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.165216566  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.165217433  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496119.165218575  STREAM_SLEEP_TIME              id:140000 wake:000496119.186075922
+    496119.165224735  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496119.165225080  DEV_SLEEP_TIME                 dev:8 wake:000496119.231884610
+    496119.165226073  SLEEP                          sleep:000000000.020857978 longest_wake:000158140
+    496119.186110447  WAKE                           num_fds:0
+    496119.186133796  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496119.186157525  FILL_AUDIO                     dev:8 hw_level:2192
+    496119.186161725  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.186162151  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.186163629  WRITE_STREAMS_MIXED            write_limit:0
+    496119.186166561  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496119.186175261  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496119.186175828  DEV_SLEEP_TIME                 dev:8 wake:000496119.231834500
+    496119.186177707  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496119.186268025  WAKE                           num_fds:1
+    496119.186295584  FILL_AUDIO                     dev:8 hw_level:2192
+    496119.186297734  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.186308379  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.186312207  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.186312824  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.186313921  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496119.186315004  STREAM_SLEEP_TIME              id:140000 wake:000496119.207409255
+    496119.186320902  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496119.186321253  DEV_SLEEP_TIME                 dev:8 wake:000496119.253314533
+    496119.186322296  SLEEP                          sleep:000000000.021094722 longest_wake:000158140
+    496119.207471590  WAKE                           num_fds:0
+    496119.207496974  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496119.207525795  FILL_AUDIO                     dev:8 hw_level:2208
+    496119.207530271  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.207530767  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.207532561  WRITE_STREAMS_MIXED            write_limit:0
+    496119.207535513  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496119.207543070  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496119.207543581  DEV_SLEEP_TIME                 dev:8 wake:000496119.253536665
+    496119.207545335  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496119.207627590  WAKE                           num_fds:1
+    496119.207655034  FILL_AUDIO                     dev:8 hw_level:2208
+    496119.207657244  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.207673451  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.207679410  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.207680162  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.207681600  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496119.207683068  STREAM_SLEEP_TIME              id:140000 wake:000496119.228742588
+    496119.207690606  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496119.207691172  DEV_SLEEP_TIME                 dev:8 wake:000496119.275015700
+    496119.207692600  SLEEP                          sleep:000000000.021060221 longest_wake:000158140
+    496119.228853062  WAKE                           num_fds:0
+    496119.228876095  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2176
+    496119.228907021  FILL_AUDIO                     dev:8 hw_level:2176
+    496119.228912308  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.228913446  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.228914463  WRITE_STREAMS_MIXED            write_limit:0
+    496119.228917315  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496119.228925719  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496119.228926215  DEV_SLEEP_TIME                 dev:8 wake:000496119.274252016
+    496119.228928676  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496119.229018133  WAKE                           num_fds:1
+    496119.229047681  FILL_AUDIO                     dev:8 hw_level:2176
+    496119.229049234  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.229064119  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.229070388  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.229070959  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.229071967  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496119.229072949  STREAM_SLEEP_TIME              id:140000 wake:000496119.250075921
+    496119.229079509  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496119.229079870  DEV_SLEEP_TIME                 dev:8 wake:000496119.295739144
+    496119.229080907  SLEEP                          sleep:000000000.021003443 longest_wake:000158140
+    496119.250343383  WAKE                           num_fds:0
+    496119.250367173  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496119.250399042  FILL_AUDIO                     dev:8 hw_level:2192
+    496119.250403953  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.250404414  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.250406243  WRITE_STREAMS_MIXED            write_limit:0
+    496119.250409240  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496119.250418672  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496119.250419183  DEV_SLEEP_TIME                 dev:8 wake:000496119.296078016
+    496119.250421268  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496119.250517861  WAKE                           num_fds:1
+    496119.250549253  FILL_AUDIO                     dev:8 hw_level:2192
+    496119.250551022  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.250566493  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.250571199  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.250571770  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.250572933  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496119.250573955  STREAM_SLEEP_TIME              id:140000 wake:000496119.271409254
+    496119.250580571  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496119.250580951  DEV_SLEEP_TIME                 dev:8 wake:000496119.317573489
+    496119.250582019  SLEEP                          sleep:000000000.020835765 longest_wake:000158140
+    496119.271663162  WAKE                           num_fds:0
+    496119.271686306  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496119.271718400  FILL_AUDIO                     dev:8 hw_level:2208
+    496119.271722684  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.271723126  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.271725130  WRITE_STREAMS_MIXED            write_limit:0
+    496119.271728042  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496119.271736516  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496119.271737058  DEV_SLEEP_TIME                 dev:8 wake:000496119.317729726
+    496119.271738827  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496119.271834628  WAKE                           num_fds:1
+    496119.271865213  FILL_AUDIO                     dev:8 hw_level:2160
+    496119.271867238  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.271881546  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.271887019  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.271887685  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.271888612  FILL_AUDIO_DONE                hw_level:2160 total_written:1024 min_cb_level:1024
+    496119.271889700  STREAM_SLEEP_TIME              id:140000 wake:000496119.292742587
+    496119.271895884  SET_DEV_WAKE                   dev:8 hw_level:3184 sleep:3184
+    496119.271896255  DEV_SLEEP_TIME                 dev:8 wake:000496119.338222557
+    496119.271897242  SLEEP                          sleep:000000000.020853363 longest_wake:000158140
+    496119.292999314  WAKE                           num_fds:0
+    496119.293023229  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2176
+    496119.293055559  FILL_AUDIO                     dev:8 hw_level:2176
+    496119.293060059  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.293060520  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.293062169  WRITE_STREAMS_MIXED            write_limit:0
+    496119.293065406  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496119.293073836  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496119.293074387  DEV_SLEEP_TIME                 dev:8 wake:000496119.338400093
+    496119.293076161  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496119.293169717  WAKE                           num_fds:1
+    496119.293197421  FILL_AUDIO                     dev:8 hw_level:2176
+    496119.293199205  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.293213278  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.293217447  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.293218024  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.293219562  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496119.293220524  STREAM_SLEEP_TIME              id:140000 wake:000496119.314075920
+    496119.293226634  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496119.293227014  DEV_SLEEP_TIME                 dev:8 wake:000496119.359886734
+    496119.293228157  SLEEP                          sleep:000000000.020855852 longest_wake:000158140
+    496119.314326940  WAKE                           num_fds:0
+    496119.314367935  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496119.314398355  FILL_AUDIO                     dev:8 hw_level:2192
+    496119.314402439  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.314402945  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.314404148  WRITE_STREAMS_MIXED            write_limit:0
+    496119.314406874  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496119.314415835  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496119.314416366  DEV_SLEEP_TIME                 dev:8 wake:000496119.360075290
+    496119.314418211  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496119.314513646  WAKE                           num_fds:1
+    496119.314545354  FILL_AUDIO                     dev:8 hw_level:2192
+    496119.314547228  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.314562363  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.314566809  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.314567295  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.314568227  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496119.314569314  STREAM_SLEEP_TIME              id:140000 wake:000496119.335409253
+    496119.314575188  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496119.314575539  DEV_SLEEP_TIME                 dev:8 wake:000496119.381568818
+    496119.314576531  SLEEP                          sleep:000000000.020840435 longest_wake:000158140
+    496119.335516443  WAKE                           num_fds:0
+    496119.335539261  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496119.335569801  FILL_AUDIO                     dev:8 hw_level:2208
+    496119.335574597  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.335575099  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.335576286  WRITE_STREAMS_MIXED            write_limit:0
+    496119.335579890  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496119.335588279  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496119.335588815  DEV_SLEEP_TIME                 dev:8 wake:000496119.381581313
+    496119.335590930  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496119.335683594  WAKE                           num_fds:1
+    496119.335716806  FILL_AUDIO                     dev:8 hw_level:2208
+    496119.335718695  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.335733775  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.335738676  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.335739258  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.335740340  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496119.335741733  STREAM_SLEEP_TIME              id:140000 wake:000496119.356742586
+    496119.335747988  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496119.335748384  DEV_SLEEP_TIME                 dev:8 wake:000496119.403074355
+    496119.335749381  SLEEP                          sleep:000000000.021001564 longest_wake:000158140
+    496119.356772896  WAKE                           num_fds:0
+    496119.356796024  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2224
+    496119.356818947  FILL_AUDIO                     dev:8 hw_level:2224
+    496119.356823142  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.356823573  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.356824741  WRITE_STREAMS_MIXED            write_limit:0
+    496119.356827893  FILL_AUDIO_DONE                hw_level:2224 total_written:0 min_cb_level:1024
+    496119.356836297  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496119.356836784  DEV_SLEEP_TIME                 dev:8 wake:000496119.402162539
+    496119.356838492  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496119.356924451  WAKE                           num_fds:1
+    496119.356950401  FILL_AUDIO                     dev:8 hw_level:2176
+    496119.356952285  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.356965009  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.356969244  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.356969806  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.356971003  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496119.356972096  STREAM_SLEEP_TIME              id:140000 wake:000496119.378075919
+    496119.356977999  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496119.356978415  DEV_SLEEP_TIME                 dev:8 wake:000496119.423638251
+    496119.356979448  SLEEP                          sleep:000000000.021104334 longest_wake:000158140
+    496119.378141728  WAKE                           num_fds:0
+    496119.378163549  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496119.378184522  FILL_AUDIO                     dev:8 hw_level:2192
+    496119.378189328  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.378189804  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.378190987  WRITE_STREAMS_MIXED            write_limit:0
+    496119.378193969  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496119.378202564  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496119.378203050  DEV_SLEEP_TIME                 dev:8 wake:000496119.423862198
+    496119.378204924  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496119.378286091  WAKE                           num_fds:1
+    496119.378312282  FILL_AUDIO                     dev:8 hw_level:2192
+    496119.378313760  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.378325999  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.378342576  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.378343172  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.378344185  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496119.378345337  STREAM_SLEEP_TIME              id:140000 wake:000496119.399409252
+    496119.378351411  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496119.378351822  DEV_SLEEP_TIME                 dev:8 wake:000496119.445344856
+    496119.378352865  SLEEP                          sleep:000000000.021064396 longest_wake:000158140
+    496119.399442392  WAKE                           num_fds:0
+    496119.399463837  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496119.399490884  FILL_AUDIO                     dev:8 hw_level:2208
+    496119.399495094  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.399495550  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.399496602  WRITE_STREAMS_MIXED            write_limit:0
+    496119.399499955  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496119.399507778  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496119.399508294  DEV_SLEEP_TIME                 dev:8 wake:000496119.445501148
+    496119.399510033  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496119.399592408  WAKE                           num_fds:1
+    496119.399619716  FILL_AUDIO                     dev:8 hw_level:2208
+    496119.399621180  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.399633964  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.399637964  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.399638485  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.399639652  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496119.399640655  STREAM_SLEEP_TIME              id:140000 wake:000496119.420742585
+    496119.399646398  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496119.399646749  DEV_SLEEP_TIME                 dev:8 wake:000496119.466973537
+    496119.399647736  SLEEP                          sleep:000000000.021102381 longest_wake:000158140
+    496119.420990443  WAKE                           num_fds:0
+    496119.421014493  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2176
+    496119.421045224  FILL_AUDIO                     dev:8 hw_level:2176
+    496119.421050482  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.421050928  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.421052376  WRITE_STREAMS_MIXED            write_limit:0
+    496119.421055273  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496119.421063802  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496119.421064384  DEV_SLEEP_TIME                 dev:8 wake:000496119.466390335
+    496119.421066238  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496119.421162485  WAKE                           num_fds:1
+    496119.421190410  FILL_AUDIO                     dev:8 hw_level:2176
+    496119.421192264  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.421208496  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.421213172  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.421213728  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.421214706  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496119.421215848  STREAM_SLEEP_TIME              id:140000 wake:000496119.442075918
+    496119.421222303  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496119.421222684  DEV_SLEEP_TIME                 dev:8 wake:000496119.487882013
+    496119.421223726  SLEEP                          sleep:000000000.020860571 longest_wake:000158140
+    496119.442117753  WAKE                           num_fds:0
+    496119.442143662  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496119.442172815  FILL_AUDIO                     dev:8 hw_level:2192
+    496119.442177621  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.442178072  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.442178974  WRITE_STREAMS_MIXED            write_limit:0
+    496119.442181911  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496119.442190205  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496119.442190776  DEV_SLEEP_TIME                 dev:8 wake:000496119.487849729
+    496119.442192661  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496119.442277682  WAKE                           num_fds:1
+    496119.442304804  FILL_AUDIO                     dev:8 hw_level:2192
+    496119.442307466  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.442319594  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.442325988  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.442326830  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.442342225  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496119.442343733  STREAM_SLEEP_TIME              id:140000 wake:000496119.463409251
+    496119.442351025  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496119.442351657  DEV_SLEEP_TIME                 dev:8 wake:000496119.509343087
+    496119.442353215  SLEEP                          sleep:000000000.021066164 longest_wake:000158140
+    496119.463441223  WAKE                           num_fds:0
+    496119.463463406  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496119.463494930  FILL_AUDIO                     dev:8 hw_level:2208
+    496119.463499425  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.463499838  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.463501037  WRITE_STREAMS_MIXED            write_limit:0
+    496119.463504016  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496119.463513001  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496119.463513429  DEV_SLEEP_TIME                 dev:8 wake:000496119.509505367
+    496119.463515069  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496119.463624173  WAKE                           num_fds:1
+    496119.463650301  FILL_AUDIO                     dev:8 hw_level:2208
+    496119.463653032  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.463670801  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.463678575  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.463679252  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.463680808  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496119.463682111  STREAM_SLEEP_TIME              id:140000 wake:000496119.484742584
+    496119.463688870  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496119.463689315  DEV_SLEEP_TIME                 dev:8 wake:000496119.531014955
+    496119.463690759  SLEEP                          sleep:000000000.021060962 longest_wake:000158140
+    496119.484761936  WAKE                           num_fds:0
+    496119.484774842  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2224
+    496119.484786984  FILL_AUDIO                     dev:8 hw_level:2224
+    496119.484789594  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.484789778  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.484790606  WRITE_STREAMS_MIXED            write_limit:0
+    496119.484791910  FILL_AUDIO_DONE                hw_level:2224 total_written:0 min_cb_level:1024
+    496119.484797359  SET_DEV_WAKE                   dev:8 hw_level:2224 sleep:2224
+    496119.484797534  DEV_SLEEP_TIME                 dev:8 wake:000496119.531125750
+    496119.484798276  SLEEP                          sleep:000000000.046333333 longest_wake:000158140
+    496119.484828393  WAKE                           num_fds:1
+    496119.484842892  FILL_AUDIO                     dev:8 hw_level:2176
+    496119.484843484  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.484849786  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.484852445  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.484852760  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.484853392  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496119.484853881  STREAM_SLEEP_TIME              id:140000 wake:000496119.506075917
+    496119.484858408  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496119.484858539  DEV_SLEEP_TIME                 dev:8 wake:000496119.551520364
+    496119.484858931  SLEEP                          sleep:000000000.021222219 longest_wake:000158140
+    496119.506277376  WAKE                           num_fds:0
+    496119.506291073  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496119.506307673  FILL_AUDIO                     dev:8 hw_level:2192
+    496119.506310095  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.506310261  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.506310695  WRITE_STREAMS_MIXED            write_limit:0
+    496119.506312020  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496119.506317760  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496119.506317950  DEV_SLEEP_TIME                 dev:8 wake:000496119.551979289
+    496119.506318606  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496119.506364111  WAKE                           num_fds:1
+    496119.506379211  FILL_AUDIO                     dev:8 hw_level:2192
+    496119.506380031  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.506386971  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.506388635  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.506388839  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.506389202  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496119.506389581  STREAM_SLEEP_TIME              id:140000 wake:000496119.527409250
+    496119.506394062  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496119.506394200  DEV_SLEEP_TIME                 dev:8 wake:000496119.573389409
+    496119.506394609  SLEEP                          sleep:000000000.021019841 longest_wake:000158140
+    496119.527434443  WAKE                           num_fds:0
+    496119.527460343  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496119.527484093  FILL_AUDIO                     dev:8 hw_level:2208
+    496119.527489130  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.527489626  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.527490718  WRITE_STREAMS_MIXED            write_limit:0
+    496119.527495374  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496119.527504154  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496119.527504685  DEV_SLEEP_TIME                 dev:8 wake:000496119.573496602
+    496119.527508008  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496119.527588594  WAKE                           num_fds:1
+    496119.527616604  FILL_AUDIO                     dev:8 hw_level:2208
+    496119.527619475  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.527633914  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.527640614  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.527641321  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.527643035  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496119.527644568  STREAM_SLEEP_TIME              id:140000 wake:000496119.548742583
+    496119.527651885  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496119.527653088  DEV_SLEEP_TIME                 dev:8 wake:000496119.594977240
+    496119.527654657  SLEEP                          sleep:000000000.021098676 longest_wake:000158140
+    496119.548999664  WAKE                           num_fds:0
+    496119.549022737  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2176
+    496119.549054295  FILL_AUDIO                     dev:8 hw_level:2176
+    496119.549059261  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.549059843  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.549061186  WRITE_STREAMS_MIXED            write_limit:0
+    496119.549064483  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496119.549072813  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496119.549073359  DEV_SLEEP_TIME                 dev:8 wake:000496119.594399250
+    496119.549075689  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496119.549176036  WAKE                           num_fds:1
+    496119.549203574  FILL_AUDIO                     dev:8 hw_level:2176
+    496119.549205544  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.549220529  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.549224718  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.549225270  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.549226192  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496119.549227244  STREAM_SLEEP_TIME              id:140000 wake:000496119.570075916
+    496119.549234205  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496119.549234591  DEV_SLEEP_TIME                 dev:8 wake:000496119.615893439
+    496119.549235593  SLEEP                          sleep:000000000.020849143 longest_wake:000158140
+    496119.570344229  WAKE                           num_fds:0
+    496119.570367668  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496119.570397667  FILL_AUDIO                     dev:8 hw_level:2192
+    496119.570402293  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.570402749  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.570403942  WRITE_STREAMS_MIXED            write_limit:0
+    496119.570406964  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496119.570415654  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496119.570416160  DEV_SLEEP_TIME                 dev:8 wake:000496119.616075023
+    496119.570417854  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496119.570517549  WAKE                           num_fds:1
+    496119.570548375  FILL_AUDIO                     dev:8 hw_level:2192
+    496119.570550235  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.570565405  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.570570125  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.570570832  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.570571614  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496119.570572651  STREAM_SLEEP_TIME              id:140000 wake:000496119.591409249
+    496119.570578309  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496119.570578660  DEV_SLEEP_TIME                 dev:8 wake:000496119.637572175
+    496119.570579662  SLEEP                          sleep:000000000.020837074 longest_wake:000158140
+    496119.591447839  WAKE                           num_fds:0
+    496119.591471083  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496119.591498381  FILL_AUDIO                     dev:8 hw_level:2208
+    496119.591502641  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.591503082  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.591504470  WRITE_STREAMS_MIXED            write_limit:0
+    496119.591507331  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496119.591515575  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496119.591516077  DEV_SLEEP_TIME                 dev:8 wake:000496119.637508639
+    496119.591517911  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496119.591605508  WAKE                           num_fds:1
+    496119.591633272  FILL_AUDIO                     dev:8 hw_level:2208
+    496119.591634976  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.591649530  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.591655148  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.591655869  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.591657373  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496119.591658806  STREAM_SLEEP_TIME              id:140000 wake:000496119.612742582
+    496119.591665451  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496119.591665907  DEV_SLEEP_TIME                 dev:8 wake:000496119.658991447
+    496119.591667235  SLEEP                          sleep:000000000.021084468 longest_wake:000158140
+    496119.612826659  WAKE                           num_fds:0
+    496119.612848891  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2176
+    496119.612873648  FILL_AUDIO                     dev:8 hw_level:2176
+    496119.612878048  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.612878489  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.612879697  WRITE_STREAMS_MIXED            write_limit:0
+    496119.612882694  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496119.612890742  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496119.612891319  DEV_SLEEP_TIME                 dev:8 wake:000496119.658217229
+    496119.612893073  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496119.612975583  WAKE                           num_fds:1
+    496119.613003011  FILL_AUDIO                     dev:8 hw_level:2176
+    496119.613004364  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.613018011  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.613022115  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.613022672  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.613023754  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496119.613024812  STREAM_SLEEP_TIME              id:140000 wake:000496119.634075915
+    496119.613030515  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496119.613030896  DEV_SLEEP_TIME                 dev:8 wake:000496119.679690987
+    496119.613031868  SLEEP                          sleep:000000000.021051594 longest_wake:000158140
+    496119.634323952  WAKE                           num_fds:0
+    496119.634365252  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496119.634397427  FILL_AUDIO                     dev:8 hw_level:2192
+    496119.634401792  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.634402208  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.634403330  WRITE_STREAMS_MIXED            write_limit:0
+    496119.634406733  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496119.634415082  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496119.634415644  DEV_SLEEP_TIME                 dev:8 wake:000496119.680074867
+    496119.634417673  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496119.634513685  WAKE                           num_fds:1
+    496119.634542587  FILL_AUDIO                     dev:8 hw_level:2192
+    496119.634544110  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.634559245  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.634563565  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.634564222  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.634565304  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496119.634566527  STREAM_SLEEP_TIME              id:140000 wake:000496119.655409248
+    496119.634572972  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496119.634573353  DEV_SLEEP_TIME                 dev:8 wake:000496119.701566016
+    496119.634574335  SLEEP                          sleep:000000000.020843232 longest_wake:000158140
+    496119.655663146  WAKE                           num_fds:0
+    496119.655686319  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496119.655718418  FILL_AUDIO                     dev:8 hw_level:2208
+    496119.655723791  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.655724302  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.655725710  WRITE_STREAMS_MIXED            write_limit:0
+    496119.655728542  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496119.655736746  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496119.655737322  DEV_SLEEP_TIME                 dev:8 wake:000496119.701729780
+    496119.655739201  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496119.655836651  WAKE                           num_fds:1
+    496119.655867467  FILL_AUDIO                     dev:8 hw_level:2160
+    496119.655869332  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.655883900  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.655888255  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.655888807  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.655890465  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:0
+    496119.655891047  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.655891503  WRITE_STREAMS_MIXED            write_limit:0
+    496119.655892059  FILL_AUDIO_DONE                hw_level:2160 total_written:1024 min_cb_level:1024
+    496119.655893267  STREAM_SLEEP_TIME              id:140000 wake:000496119.676742581
+    496119.655899216  SET_DEV_WAKE                   dev:8 hw_level:3184 sleep:3184
+    496119.655899561  DEV_SLEEP_TIME                 dev:8 wake:000496119.722225999
+    496119.655900579  SLEEP                          sleep:000000000.020849915 longest_wake:000158140
+    496119.676782360  WAKE                           num_fds:0
+    496119.676802491  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2224
+    496119.676828715  FILL_AUDIO                     dev:8 hw_level:2176
+    496119.676831930  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.676832205  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.676832933  WRITE_STREAMS_MIXED            write_limit:0
+    496119.676835095  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496119.676841542  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496119.676841833  DEV_SLEEP_TIME                 dev:8 wake:000496119.722169273
+    496119.676843191  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496119.676901138  WAKE                           num_fds:1
+    496119.676923101  FILL_AUDIO                     dev:8 hw_level:2176
+    496119.676924667  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.676927011  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.676930681  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.676931111  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.676932162  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496119.676933078  STREAM_SLEEP_TIME              id:140000 wake:000496119.698075914
+    496119.676939028  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496119.676939378  DEV_SLEEP_TIME                 dev:8 wake:000496119.743599341
+    496119.676940404  SLEEP                          sleep:000000000.021143239 longest_wake:000158140
+    496119.698136425  WAKE                           num_fds:0
+    496119.698158456  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496119.698186947  FILL_AUDIO                     dev:8 hw_level:2192
+    496119.698191006  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.698191492  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.698192615  WRITE_STREAMS_MIXED            write_limit:0
+    496119.698195446  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496119.698203269  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496119.698203800  DEV_SLEEP_TIME                 dev:8 wake:000496119.743863350
+    496119.698205620  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496119.698283585  WAKE                           num_fds:1
+    496119.698308688  FILL_AUDIO                     dev:8 hw_level:2192
+    496119.698310517  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.698312035  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.698315914  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.698316430  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.698317528  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496119.698318540  STREAM_SLEEP_TIME              id:140000 wake:000496119.719409247
+    496119.698324143  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496119.698324524  DEV_SLEEP_TIME                 dev:8 wake:000496119.765318074
+    496119.698325461  SLEEP                          sleep:000000000.021091173 longest_wake:000158140
+    496119.719658229  WAKE                           num_fds:0
+    496119.719682220  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496119.719712981  FILL_AUDIO                     dev:8 hw_level:2208
+    496119.719717286  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.719717742  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.719718839  WRITE_STREAMS_MIXED            write_limit:0
+    496119.719721741  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496119.719730717  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496119.719731308  DEV_SLEEP_TIME                 dev:8 wake:000496119.765723665
+    496119.719733202  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496119.719836902  WAKE                           num_fds:1
+    496119.719868354  FILL_AUDIO                     dev:8 hw_level:2160
+    496119.719869998  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.719871687  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.719876724  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.719877250  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.719878513  FILL_AUDIO_DONE                hw_level:2160 total_written:1024 min_cb_level:1024
+    496119.719880016  STREAM_SLEEP_TIME              id:140000 wake:000496119.740742580
+    496119.719885724  SET_DEV_WAKE                   dev:8 hw_level:3184 sleep:3184
+    496119.719886100  DEV_SLEEP_TIME                 dev:8 wake:000496119.786212602
+    496119.719887067  SLEEP                          sleep:000000000.020863311 longest_wake:000158140
+    496119.740777485  WAKE                           num_fds:0
+    496119.740799787  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2224
+    496119.740819222  FILL_AUDIO                     dev:8 hw_level:2224
+    496119.740824434  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.740824920  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.740826088  WRITE_STREAMS_MIXED            write_limit:0
+    496119.740828989  FILL_AUDIO_DONE                hw_level:2224 total_written:0 min_cb_level:1024
+    496119.740837805  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496119.740838286  DEV_SLEEP_TIME                 dev:8 wake:000496119.786163525
+    496119.740840075  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496119.740937109  WAKE                           num_fds:1
+    496119.740963259  FILL_AUDIO                     dev:8 hw_level:2176
+    496119.740965564  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.740968697  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.740973979  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.740974670  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.740975984  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496119.740977372  STREAM_SLEEP_TIME              id:140000 wake:000496119.762075913
+    496119.740983656  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496119.740984132  DEV_SLEEP_TIME                 dev:8 wake:000496119.807643386
+    496119.740985510  SLEEP                          sleep:000000000.021099193 longest_wake:000158140
+    496119.762184771  WAKE                           num_fds:0
+    496119.762207604  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496119.762239753  FILL_AUDIO                     dev:8 hw_level:2192
+    496119.762244123  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.762245146  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.762246414  WRITE_STREAMS_MIXED            write_limit:0
+    496119.762249320  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496119.762257569  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496119.762258131  DEV_SLEEP_TIME                 dev:8 wake:000496119.807917415
+    496119.762261579  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496119.762376087  WAKE                           num_fds:1
+    496119.762404828  FILL_AUDIO                     dev:8 hw_level:2192
+    496119.762407088  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.762408577  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.762412676  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.762413358  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.762414671  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496119.762416124  STREAM_SLEEP_TIME              id:140000 wake:000496119.783409246
+    496119.762422289  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496119.762422689  DEV_SLEEP_TIME                 dev:8 wake:000496119.829415488
+    496119.762423667  SLEEP                          sleep:000000000.020993758 longest_wake:000158140
+    496119.783498916  WAKE                           num_fds:0
+    496119.783520847  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496119.783546040  FILL_AUDIO                     dev:8 hw_level:2208
+    496119.783550180  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.783550656  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.783551718  WRITE_STREAMS_MIXED            write_limit:0
+    496119.783554464  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496119.783562839  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496119.783563390  DEV_SLEEP_TIME                 dev:8 wake:000496119.829555798
+    496119.783565164  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496119.783648807  WAKE                           num_fds:1
+    496119.783676742  FILL_AUDIO                     dev:8 hw_level:2208
+    496119.783678280  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.783680801  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.783684720  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.783685236  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.783686750  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496119.783687847  STREAM_SLEEP_TIME              id:140000 wake:000496119.804742579
+    496119.783694097  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496119.783694618  DEV_SLEEP_TIME                 dev:8 wake:000496119.851020649
+    496119.783695881  SLEEP                          sleep:000000000.021055263 longest_wake:000158140
+    496119.804846154  WAKE                           num_fds:0
+    496119.804869563  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2176
+    496119.804900975  FILL_AUDIO                     dev:8 hw_level:2176
+    496119.804905260  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.804906282  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.804907335  WRITE_STREAMS_MIXED            write_limit:0
+    496119.804911094  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496119.804919623  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496119.804920190  DEV_SLEEP_TIME                 dev:8 wake:000496119.850245890
+    496119.804922224  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496119.805017163  WAKE                           num_fds:1
+    496119.805048155  FILL_AUDIO                     dev:8 hw_level:2176
+    496119.805049984  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.805051578  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.805055662  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.805056218  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.805057331  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496119.805058439  STREAM_SLEEP_TIME              id:140000 wake:000496119.826075912
+    496119.805064147  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496119.805064523  DEV_SLEEP_TIME                 dev:8 wake:000496119.871724664
+    496119.805065520  SLEEP                          sleep:000000000.021017914 longest_wake:000158140
+    496119.826181499  WAKE                           num_fds:0
+    496119.826204557  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496119.826235859  FILL_AUDIO                     dev:8 hw_level:2192
+    496119.826240500  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.826240931  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.826242559  WRITE_STREAMS_MIXED            write_limit:0
+    496119.826245521  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496119.826253670  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496119.826254236  DEV_SLEEP_TIME                 dev:8 wake:000496119.871913500
+    496119.826256056  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496119.826373791  WAKE                           num_fds:1
+    496119.826402197  FILL_AUDIO                     dev:8 hw_level:2192
+    496119.826404943  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.826406793  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.826410982  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.826411508  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.826412541  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496119.826413729  STREAM_SLEEP_TIME              id:140000 wake:000496119.847409245
+    496119.826419271  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496119.826419938  DEV_SLEEP_TIME                 dev:8 wake:000496119.893413262
+    496119.826421141  SLEEP                          sleep:000000000.020995983 longest_wake:000158140
+    496119.847573032  WAKE                           num_fds:0
+    496119.847597188  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496119.847631537  FILL_AUDIO                     dev:8 hw_level:2208
+    496119.847636143  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.847636594  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.847638057  WRITE_STREAMS_MIXED            write_limit:0
+    496119.847642778  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496119.847652751  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496119.847653332  DEV_SLEEP_TIME                 dev:8 wake:000496119.893644703
+    496119.847655563  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496119.847751424  WAKE                           num_fds:1
+    496119.847777820  FILL_AUDIO                     dev:8 hw_level:2208
+    496119.847779419  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.847780937  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.847786791  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.847787337  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.847788499  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496119.847789517  STREAM_SLEEP_TIME              id:140000 wake:000496119.868742578
+    496119.847794999  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496119.847795365  DEV_SLEEP_TIME                 dev:8 wake:000496119.915122379
+    496119.847796523  SLEEP                          sleep:000000000.020953532 longest_wake:000158140
+    496119.868841929  WAKE                           num_fds:0
+    496119.868866084  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2176
+    496119.868897878  FILL_AUDIO                     dev:8 hw_level:2176
+    496119.868902263  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.868903210  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.868904428  WRITE_STREAMS_MIXED            write_limit:0
+    496119.868907149  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496119.868915248  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496119.868915794  DEV_SLEEP_TIME                 dev:8 wake:000496119.914241845
+    496119.868917784  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496119.869015113  WAKE                           num_fds:1
+    496119.869048987  FILL_AUDIO                     dev:8 hw_level:2176
+    496119.869050751  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.869053281  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.869058158  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.869058894  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.869059566  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496119.869060543  STREAM_SLEEP_TIME              id:140000 wake:000496119.890075911
+    496119.869066547  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496119.869066923  DEV_SLEEP_TIME                 dev:8 wake:000496119.935726768
+    496119.869068086  SLEEP                          sleep:000000000.021015809 longest_wake:000158140
+    496119.890110720  WAKE                           num_fds:0
+    496119.890133212  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496119.890157759  FILL_AUDIO                     dev:8 hw_level:2192
+    496119.890162880  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.890163321  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.890164504  WRITE_STREAMS_MIXED            write_limit:0
+    496119.890167491  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496119.890175499  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496119.890176021  DEV_SLEEP_TIME                 dev:8 wake:000496119.935835435
+    496119.890177800  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496119.890260912  WAKE                           num_fds:1
+    496119.890286265  FILL_AUDIO                     dev:8 hw_level:2192
+    496119.890288195  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.890290149  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.890295557  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.890296268  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.890297671  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496119.890298914  STREAM_SLEEP_TIME              id:140000 wake:000496119.911409244
+    496119.890305374  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496119.890305805  DEV_SLEEP_TIME                 dev:8 wake:000496119.957298358
+    496119.890307223  SLEEP                          sleep:000000000.021110886 longest_wake:000158140
+    496119.911457981  WAKE                           num_fds:0
+    496119.911482252  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496119.911511630  FILL_AUDIO                     dev:8 hw_level:2208
+    496119.911516807  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.911517404  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.911518641  WRITE_STREAMS_MIXED            write_limit:0
+    496119.911521668  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496119.911529396  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496119.911529912  DEV_SLEEP_TIME                 dev:8 wake:000496119.957522851
+    496119.911531636  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496119.911612914  WAKE                           num_fds:1
+    496119.911641044  FILL_AUDIO                     dev:8 hw_level:2208
+    496119.911643039  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.911645043  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.911652420  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.911653147  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.911654886  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496119.911656239  STREAM_SLEEP_TIME              id:140000 wake:000496119.932742577
+    496119.911663180  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496119.911663601  DEV_SLEEP_TIME                 dev:8 wake:000496119.978989021
+    496119.911665014  SLEEP                          sleep:000000000.021086889 longest_wake:000158140
+    496119.932781594  WAKE                           num_fds:0
+    496119.932810225  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2224
+    496119.932837659  FILL_AUDIO                     dev:8 hw_level:2176
+    496119.932842344  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.932842941  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.932844214  WRITE_STREAMS_MIXED            write_limit:0
+    496119.932847311  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496119.932856257  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496119.932856723  DEV_SLEEP_TIME                 dev:8 wake:000496119.978182037
+    496119.932858742  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496119.932950314  WAKE                           num_fds:1
+    496119.932981345  FILL_AUDIO                     dev:8 hw_level:2176
+    496119.932983395  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.932985836  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.932990792  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.932991338  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.932992311  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496119.932993358  STREAM_SLEEP_TIME              id:140000 wake:000496119.954075910
+    496119.932999216  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496119.932999838  DEV_SLEEP_TIME                 dev:8 wake:000496119.999659568
+    496119.933001116  SLEEP                          sleep:000000000.021083008 longest_wake:000158140
+    496119.954146565  WAKE                           num_fds:0
+    496119.954162536  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496119.954180693  FILL_AUDIO                     dev:8 hw_level:2192
+    496119.954183341  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.954183605  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.954184349  WRITE_STREAMS_MIXED            write_limit:0
+    496119.954186514  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496119.954192771  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496119.954193049  DEV_SLEEP_TIME                 dev:8 wake:000496119.999853959
+    496119.954194267  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496119.954240476  WAKE                           num_fds:1
+    496119.954258950  FILL_AUDIO                     dev:8 hw_level:2192
+    496119.954259846  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.954260998  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.954266147  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.954266591  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.954267353  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496119.954268157  STREAM_SLEEP_TIME              id:140000 wake:000496119.975409243
+    496119.954273532  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496119.954273850  DEV_SLEEP_TIME                 dev:8 wake:000496120.021267834
+    496119.954274529  SLEEP                          sleep:000000000.021141409 longest_wake:000158140
+    496119.975463624  WAKE                           num_fds:0
+    496119.975512667  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496119.975552860  FILL_AUDIO                     dev:8 hw_level:2208
+    496119.975562763  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.975563259  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.975564547  WRITE_STREAMS_MIXED            write_limit:0
+    496119.975572255  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496119.975582799  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496119.975583275  DEV_SLEEP_TIME                 dev:8 wake:000496120.021574064
+    496119.975585826  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496119.975725950  WAKE                           num_fds:1
+    496119.975762304  FILL_AUDIO                     dev:8 hw_level:2208
+    496119.975765351  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.975769365  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.975777188  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.975777829  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.975779543  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496119.975781348  STREAM_SLEEP_TIME              id:140000 wake:000496119.996742576
+    496119.975787873  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496119.975788269  DEV_SLEEP_TIME                 dev:8 wake:000496120.043114219
+    496119.975789411  SLEEP                          sleep:000000000.020961690 longest_wake:000158140
+    496119.996809488  WAKE                           num_fds:0
+    496119.996830326  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2176
+    496119.996856422  FILL_AUDIO                     dev:8 hw_level:2176
+    496119.996860426  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496119.996860867  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496119.996861919  WRITE_STREAMS_MIXED            write_limit:0
+    496119.996864570  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496119.996872348  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496119.996872845  DEV_SLEEP_TIME                 dev:8 wake:000496120.042199096
+    496119.996874814  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496119.996972104  WAKE                           num_fds:1
+    496119.996998314  FILL_AUDIO                     dev:8 hw_level:2176
+    496119.996999908  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496119.997001361  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496119.997005220  DEV_STREAM_MIX                 written:1024 read:1024
+    496119.997005761  WRITE_STREAMS_MIXED            write_limit:1024
+    496119.997006899  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496119.997007997  STREAM_SLEEP_TIME              id:140000 wake:000496120.018075909
+    496119.997013484  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496119.997013880  DEV_SLEEP_TIME                 dev:8 wake:000496120.063674186
+    496119.997014872  SLEEP                          sleep:000000000.021068389 longest_wake:000158140
+    496120.018176962  WAKE                           num_fds:0
+    496120.018200617  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496120.018232290  FILL_AUDIO                     dev:8 hw_level:2192
+    496120.018237242  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.018237748  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.018238900  WRITE_STREAMS_MIXED            write_limit:0
+    496120.018243346  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496120.018251489  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496120.018252036  DEV_SLEEP_TIME                 dev:8 wake:000496120.063911239
+    496120.018254025  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496120.018371866  WAKE                           num_fds:1
+    496120.018402292  FILL_AUDIO                     dev:8 hw_level:2192
+    496120.018404587  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.018406206  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.018410290  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.018410856  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.018412074  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496120.018413157  STREAM_SLEEP_TIME              id:140000 wake:000496120.039409242
+    496120.018419020  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496120.018419371  DEV_SLEEP_TIME                 dev:8 wake:000496120.085412691
+    496120.018420468  SLEEP                          sleep:000000000.020996551 longest_wake:000158140
+    496120.039592015  WAKE                           num_fds:0
+    496120.039614197  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496120.039645043  FILL_AUDIO                     dev:8 hw_level:2208
+    496120.039649328  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.039649764  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.039651312  WRITE_STREAMS_MIXED            write_limit:0
+    496120.039654023  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496120.039662277  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496120.039662864  DEV_SLEEP_TIME                 dev:8 wake:000496120.085655417
+    496120.039664573  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496120.039757006  WAKE                           num_fds:1
+    496120.039786254  FILL_AUDIO                     dev:8 hw_level:2208
+    496120.039788358  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.039789902  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.039794478  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.039795144  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.039796482  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496120.039797560  STREAM_SLEEP_TIME              id:140000 wake:000496120.060742575
+    496120.039803468  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496120.039803824  DEV_SLEEP_TIME                 dev:8 wake:000496120.107130392
+    496120.039804832  SLEEP                          sleep:000000000.020945516 longest_wake:000158140
+    496120.060797771  WAKE                           num_fds:0
+    496120.060820273  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2224
+    496120.060854302  FILL_AUDIO                     dev:8 hw_level:2176
+    496120.060859644  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.060860160  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.060861328  WRITE_STREAMS_MIXED            write_limit:0
+    496120.060865207  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496120.060874082  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496120.060874618  DEV_SLEEP_TIME                 dev:8 wake:000496120.106199748
+    496120.060876327  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496120.060978919  WAKE                           num_fds:1
+    496120.061008252  FILL_AUDIO                     dev:8 hw_level:2176
+    496120.061010392  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.061012447  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.061017007  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.061017714  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.061018736  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496120.061019864  STREAM_SLEEP_TIME              id:140000 wake:000496120.082075908
+    496120.061025281  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496120.061025662  DEV_SLEEP_TIME                 dev:8 wake:000496120.127686029
+    496120.061026895  SLEEP                          sleep:000000000.021056545 longest_wake:000158140
+    496120.082178195  WAKE                           num_fds:0
+    496120.082200907  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496120.082232125  FILL_AUDIO                     dev:8 hw_level:2192
+    496120.082237612  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.082238705  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.082240629  WRITE_STREAMS_MIXED            write_limit:0
+    496120.082243696  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496120.082252522  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496120.082253519  DEV_SLEEP_TIME                 dev:8 wake:000496120.127911745
+    496120.082255629  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496120.082367060  WAKE                           num_fds:1
+    496120.082395866  FILL_AUDIO                     dev:8 hw_level:2192
+    496120.082397595  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.082399144  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.082405188  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.082405950  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.082407373  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496120.082408405  STREAM_SLEEP_TIME              id:140000 wake:000496120.103409241
+    496120.082413963  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496120.082414670  DEV_SLEEP_TIME                 dev:8 wake:000496120.149407924
+    496120.082415858  SLEEP                          sleep:000000000.021001317 longest_wake:000158140
+    496120.103453866  WAKE                           num_fds:0
+    496120.103476263  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496120.103506513  FILL_AUDIO                     dev:8 hw_level:2208
+    496120.103511449  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.103512000  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.103513504  WRITE_STREAMS_MIXED            write_limit:0
+    496120.103516381  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496120.103524870  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496120.103525436  DEV_SLEEP_TIME                 dev:8 wake:000496120.149517653
+    496120.103527722  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496120.103621638  WAKE                           num_fds:1
+    496120.103650109  FILL_AUDIO                     dev:8 hw_level:2208
+    496120.103651953  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.103653607  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.103658398  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.103658960  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.103659942  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496120.103660949  STREAM_SLEEP_TIME              id:140000 wake:000496120.124742574
+    496120.103666838  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496120.103667234  DEV_SLEEP_TIME                 dev:8 wake:000496120.170993811
+    496120.103668261  SLEEP                          sleep:000000000.021082096 longest_wake:000158140
+    496120.124785237  WAKE                           num_fds:0
+    496120.124806356  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2224
+    496120.124831689  FILL_AUDIO                     dev:8 hw_level:2176
+    496120.124835618  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.124836190  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.124837197  WRITE_STREAMS_MIXED            write_limit:0
+    496120.124840109  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496120.124848789  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496120.124849265  DEV_SLEEP_TIME                 dev:8 wake:000496120.170174865
+    496120.124851064  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496120.124937794  WAKE                           num_fds:1
+    496120.124963223  FILL_AUDIO                     dev:8 hw_level:2176
+    496120.124964751  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.124966350  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.124970339  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.124970971  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.124972018  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496120.124973111  STREAM_SLEEP_TIME              id:140000 wake:000496120.146075907
+    496120.124978849  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496120.124979235  DEV_SLEEP_TIME                 dev:8 wake:000496120.191639361
+    496120.124980252  SLEEP                          sleep:000000000.021103212 longest_wake:000158140
+    496120.146169219  WAKE                           num_fds:0
+    496120.146191606  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496120.146222547  FILL_AUDIO                     dev:8 hw_level:2192
+    496120.146228346  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.146228812  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.146230265  WRITE_STREAMS_MIXED            write_limit:0
+    496120.146233297  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496120.146243125  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496120.146243676  DEV_SLEEP_TIME                 dev:8 wake:000496120.191901662
+    496120.146245530  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496120.146362163  WAKE                           num_fds:1
+    496120.146392519  FILL_AUDIO                     dev:8 hw_level:2192
+    496120.146394378  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.146396062  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.146400091  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.146400963  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.146402020  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496120.146403308  STREAM_SLEEP_TIME              id:140000 wake:000496120.167409240
+    496120.146409392  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496120.146409743  DEV_SLEEP_TIME                 dev:8 wake:000496120.213402737
+    496120.146410746  SLEEP                          sleep:000000000.021006503 longest_wake:000158140
+    496120.167670100  WAKE                           num_fds:0
+    496120.167693524  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496120.167724260  FILL_AUDIO                     dev:8 hw_level:2208
+    496120.167728821  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.167729272  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.167730389  WRITE_STREAMS_MIXED            write_limit:0
+    496120.167733466  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496120.167742041  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496120.167742547  DEV_SLEEP_TIME                 dev:8 wake:000496120.213734845
+    496120.167744286  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496120.167843866  WAKE                           num_fds:1
+    496120.167874216  FILL_AUDIO                     dev:8 hw_level:2160
+    496120.167876241  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.167878035  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.167882305  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.167882866  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.167883713  FILL_AUDIO_DONE                hw_level:2160 total_written:1024 min_cb_level:1024
+    496120.167884645  STREAM_SLEEP_TIME              id:140000 wake:000496120.188742573
+    496120.167890554  SET_DEV_WAKE                   dev:8 hw_level:3184 sleep:3184
+    496120.167890915  DEV_SLEEP_TIME                 dev:8 wake:000496120.234217522
+    496120.167892268  SLEEP                          sleep:000000000.020858384 longest_wake:000158140
+    496120.188990921  WAKE                           num_fds:0
+    496120.189014721  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2176
+    496120.189046640  FILL_AUDIO                     dev:8 hw_level:2176
+    496120.189050845  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.189051361  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.189052930  WRITE_STREAMS_MIXED            write_limit:0
+    496120.189055856  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496120.189064396  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496120.189064967  DEV_SLEEP_TIME                 dev:8 wake:000496120.234390863
+    496120.189067603  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496120.189162332  WAKE                           num_fds:1
+    496120.189191139  FILL_AUDIO                     dev:8 hw_level:2176
+    496120.189193003  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.189194577  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.189199142  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.189199708  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.189200570  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496120.189201663  STREAM_SLEEP_TIME              id:140000 wake:000496120.210075906
+    496120.189207286  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496120.189207657  DEV_SLEEP_TIME                 dev:8 wake:000496120.255867863
+    496120.189208654  SLEEP                          sleep:000000000.020874709 longest_wake:000158140
+    496120.210165596  WAKE                           num_fds:0
+    496120.210210895  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496120.210251669  FILL_AUDIO                     dev:8 hw_level:2192
+    496120.210260850  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.210261337  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.210262830  WRITE_STREAMS_MIXED            write_limit:0
+    496120.210268693  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496120.210279498  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496120.210280035  DEV_SLEEP_TIME                 dev:8 wake:000496120.255938211
+    496120.210282661  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496120.210445466  WAKE                           num_fds:1
+    496120.210479965  FILL_AUDIO                     dev:8 hw_level:2192
+    496120.210481915  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.210487217  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.210495035  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.210495737  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.210496979  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496120.210497957  STREAM_SLEEP_TIME              id:140000 wake:000496120.231409239
+    496120.210503740  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496120.210504116  DEV_SLEEP_TIME                 dev:8 wake:000496120.277497491
+    496120.210505173  SLEEP                          sleep:000000000.020911748 longest_wake:000158140
+    496120.231663695  WAKE                           num_fds:0
+    496120.231687425  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496120.231718522  FILL_AUDIO                     dev:8 hw_level:2208
+    496120.231723092  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.231723518  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.231725257  WRITE_STREAMS_MIXED            write_limit:0
+    496120.231728700  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496120.231737390  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496120.231737977  DEV_SLEEP_TIME                 dev:8 wake:000496120.277729958
+    496120.231740066  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496120.231836699  WAKE                           num_fds:1
+    496120.231867290  FILL_AUDIO                     dev:8 hw_level:2160
+    496120.231869004  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.231870808  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.231874722  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.231875253  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.231876421  FILL_AUDIO_DONE                hw_level:2160 total_written:1024 min_cb_level:1024
+    496120.231877634  STREAM_SLEEP_TIME              id:140000 wake:000496120.252742572
+    496120.231883943  SET_DEV_WAKE                   dev:8 hw_level:3184 sleep:3184
+    496120.231884314  DEV_SLEEP_TIME                 dev:8 wake:000496120.298210511
+    496120.231885317  SLEEP                          sleep:000000000.020865394 longest_wake:000158140
+    496120.252825725  WAKE                           num_fds:0
+    496120.252847370  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2176
+    496120.252873480  FILL_AUDIO                     dev:8 hw_level:2176
+    496120.252877595  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.252878031  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.252879183  WRITE_STREAMS_MIXED            write_limit:0
+    496120.252882060  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496120.252889973  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496120.252890449  DEV_SLEEP_TIME                 dev:8 wake:000496120.298216616
+    496120.252892264  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496120.252976333  WAKE                           num_fds:1
+    496120.253004262  FILL_AUDIO                     dev:8 hw_level:2176
+    496120.253005560  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.253007124  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.253011143  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.253011674  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.253012526  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496120.253013624  STREAM_SLEEP_TIME              id:140000 wake:000496120.274075905
+    496120.253019227  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496120.253019572  DEV_SLEEP_TIME                 dev:8 wake:000496120.319679829
+    496120.253020595  SLEEP                          sleep:000000000.021062742 longest_wake:000158140
+    496120.274106404  WAKE                           num_fds:0
+    496120.274128715  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496120.274152144  FILL_AUDIO                     dev:8 hw_level:2192
+    496120.274156384  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.274156855  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.274157943  WRITE_STREAMS_MIXED            write_limit:0
+    496120.274161110  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496120.274169534  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496120.274170056  DEV_SLEEP_TIME                 dev:8 wake:000496120.319829134
+    496120.274171905  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496120.274253398  WAKE                           num_fds:1
+    496120.274278085  FILL_AUDIO                     dev:8 hw_level:2192
+    496120.274279458  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.274281037  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.274285166  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.274285718  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.274286800  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496120.274287888  STREAM_SLEEP_TIME              id:140000 wake:000496120.295409238
+    496120.274293445  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496120.274293796  DEV_SLEEP_TIME                 dev:8 wake:000496120.341287447
+    496120.274295009  SLEEP                          sleep:000000000.021121791 longest_wake:000158140
+    496120.295659600  WAKE                           num_fds:0
+    496120.295682664  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496120.295713329  FILL_AUDIO                     dev:8 hw_level:2208
+    496120.295717534  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.295718386  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.295720250  WRITE_STREAMS_MIXED            write_limit:0
+    496120.295723067  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496120.295731967  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496120.295732504  DEV_SLEEP_TIME                 dev:8 wake:000496120.341724505
+    496120.295734333  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496120.295829954  WAKE                           num_fds:1
+    496120.295858961  FILL_AUDIO                     dev:8 hw_level:2160
+    496120.295860870  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.295862925  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.295867099  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.295867606  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.295868347  FILL_AUDIO_DONE                hw_level:2160 total_written:1024 min_cb_level:1024
+    496120.295869871  STREAM_SLEEP_TIME              id:140000 wake:000496120.316742571
+    496120.295876075  SET_DEV_WAKE                   dev:8 hw_level:3184 sleep:3184
+    496120.295876441  DEV_SLEEP_TIME                 dev:8 wake:000496120.362202743
+    496120.295877448  SLEEP                          sleep:000000000.020873161 longest_wake:000158140
+    496120.317001495  WAKE                           num_fds:0
+    496120.317024874  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2176
+    496120.317055114  FILL_AUDIO                     dev:8 hw_level:2176
+    496120.317059414  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.317060487  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.317062541  WRITE_STREAMS_MIXED            write_limit:0
+    496120.317065453  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496120.317073852  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496120.317074344  DEV_SLEEP_TIME                 dev:8 wake:000496120.362400199
+    496120.317076313  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496120.317171929  WAKE                           num_fds:1
+    496120.317202003  FILL_AUDIO                     dev:8 hw_level:2176
+    496120.317203762  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.317205486  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.317209596  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.317210167  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.317211310  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496120.317212497  STREAM_SLEEP_TIME              id:140000 wake:000496120.338075904
+    496120.317218276  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496120.317218647  DEV_SLEEP_TIME                 dev:8 wake:000496120.383878722
+    496120.317219604  SLEEP                          sleep:000000000.020863848 longest_wake:000158140
+    496120.338325157  WAKE                           num_fds:0
+    496120.338373825  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496120.338405919  FILL_AUDIO                     dev:8 hw_level:2192
+    496120.338411276  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.338411737  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.338413556  WRITE_STREAMS_MIXED            write_limit:0
+    496120.338416749  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496120.338424973  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496120.338425564  DEV_SLEEP_TIME                 dev:8 wake:000496120.384084738
+    496120.338427378  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496120.338521641  WAKE                           num_fds:1
+    496120.338549681  FILL_AUDIO                     dev:8 hw_level:2192
+    496120.338551405  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.338552868  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.338558997  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.338559533  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.338560415  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496120.338561518  STREAM_SLEEP_TIME              id:140000 wake:000496120.359409237
+    496120.338567281  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496120.338567632  DEV_SLEEP_TIME                 dev:8 wake:000496120.405561052
+    496120.338568614  SLEEP                          sleep:000000000.020848185 longest_wake:000158140
+    496120.359662607  WAKE                           num_fds:0
+    496120.359686227  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496120.359718331  FILL_AUDIO                     dev:8 hw_level:2208
+    496120.359722616  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.359723513  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.359724700  WRITE_STREAMS_MIXED            write_limit:0
+    496120.359727878  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496120.359736167  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496120.359736738  DEV_SLEEP_TIME                 dev:8 wake:000496120.405729211
+    496120.359738628  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496120.359837621  WAKE                           num_fds:1
+    496120.359869219  FILL_AUDIO                     dev:8 hw_level:2160
+    496120.359871189  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.359873679  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.359877814  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.359878395  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.359879197  FILL_AUDIO_DONE                hw_level:2160 total_written:1024 min_cb_level:1024
+    496120.359880245  STREAM_SLEEP_TIME              id:140000 wake:000496120.380742570
+    496120.359885867  SET_DEV_WAKE                   dev:8 hw_level:3184 sleep:3184
+    496120.359886213  DEV_SLEEP_TIME                 dev:8 wake:000496120.426213101
+    496120.359887266  SLEEP                          sleep:000000000.020862802 longest_wake:000158140
+    496120.380845520  WAKE                           num_fds:0
+    496120.380869776  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2176
+    496120.380902542  FILL_AUDIO                     dev:8 hw_level:2176
+    496120.380906721  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.380907649  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.380909438  WRITE_STREAMS_MIXED            write_limit:0
+    496120.380912520  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496120.380921636  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496120.380922177  DEV_SLEEP_TIME                 dev:8 wake:000496120.426247958
+    496120.380924212  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496120.381017252  WAKE                           num_fds:1
+    496120.381045913  FILL_AUDIO                     dev:8 hw_level:2176
+    496120.381048008  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.381049777  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.381053916  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.381054462  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.381055660  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496120.381057169  STREAM_SLEEP_TIME              id:140000 wake:000496120.402075903
+    496120.381063062  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496120.381063408  DEV_SLEEP_TIME                 dev:8 wake:000496120.447722903
+    496120.381064390  SLEEP                          sleep:000000000.021019666 longest_wake:000158140
+    496120.402140933  WAKE                           num_fds:0
+    496120.402162097  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496120.402189415  FILL_AUDIO                     dev:8 hw_level:2192
+    496120.402193519  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.402193985  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.402195153  WRITE_STREAMS_MIXED            write_limit:0
+    496120.402198355  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496120.402206043  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496120.402206589  DEV_SLEEP_TIME                 dev:8 wake:000496120.447866189
+    496120.402208318  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496120.402282184  WAKE                           num_fds:1
+    496120.402306199  FILL_AUDIO                     dev:8 hw_level:2192
+    496120.402307868  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.402309497  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.402313747  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.402314258  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.402315240  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496120.402316273  STREAM_SLEEP_TIME              id:140000 wake:000496120.423409236
+    496120.402321866  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496120.402322251  DEV_SLEEP_TIME                 dev:8 wake:000496120.469315797
+    496120.402323179  SLEEP                          sleep:000000000.021093439 longest_wake:000158140
+    496120.423449244  WAKE                           num_fds:0
+    496120.423476422  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496120.423503059  FILL_AUDIO                     dev:8 hw_level:2208
+    496120.423507604  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.423508090  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.423509418  WRITE_STREAMS_MIXED            write_limit:0
+    496120.423512510  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496120.423520454  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496120.423520975  DEV_SLEEP_TIME                 dev:8 wake:000496120.469513698
+    496120.423522774  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496120.423600017  WAKE                           num_fds:1
+    496120.423628152  FILL_AUDIO                     dev:8 hw_level:2208
+    496120.423630277  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.423632317  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.423639759  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.423640521  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.423642105  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496120.423643353  STREAM_SLEEP_TIME              id:140000 wake:000496120.444742569
+    496120.423650028  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496120.423650594  DEV_SLEEP_TIME                 dev:8 wake:000496120.490976119
+    496120.423651992  SLEEP                          sleep:000000000.021099783 longest_wake:000158140
+    496120.444785897  WAKE                           num_fds:0
+    496120.444805768  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2224
+    496120.444826351  FILL_AUDIO                     dev:8 hw_level:2176
+    496120.444830240  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.444830731  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.444831889  WRITE_STREAMS_MIXED            write_limit:0
+    496120.444835256  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496120.444842919  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496120.444843495  DEV_SLEEP_TIME                 dev:8 wake:000496120.490169752
+    496120.444845274  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496120.444910946  WAKE                           num_fds:1
+    496120.444933318  FILL_AUDIO                     dev:8 hw_level:2176
+    496120.444934841  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.444936269  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.444940083  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.444940655  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.444941451  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496120.444942519  STREAM_SLEEP_TIME              id:140000 wake:000496120.466075902
+    496120.444947921  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496120.444948317  DEV_SLEEP_TIME                 dev:8 wake:000496120.511608689
+    496120.444949335  SLEEP                          sleep:000000000.021133879 longest_wake:000158140
+    496120.466115369  WAKE                           num_fds:0
+    496120.466135761  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496120.466155045  FILL_AUDIO                     dev:8 hw_level:2192
+    496120.466159004  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.466159506  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.466160548  WRITE_STREAMS_MIXED            write_limit:0
+    496120.466163319  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496120.466170927  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496120.466171518  DEV_SLEEP_TIME                 dev:8 wake:000496120.511831198
+    496120.466173272  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496120.466238388  WAKE                           num_fds:1
+    496120.466260313  FILL_AUDIO                     dev:8 hw_level:2192
+    496120.466261681  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.466263085  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.466268868  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.466269444  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.466270301  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496120.466271249  STREAM_SLEEP_TIME              id:140000 wake:000496120.487409235
+    496120.466276636  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496120.466276992  DEV_SLEEP_TIME                 dev:8 wake:000496120.533270767
+    496120.466278014  SLEEP                          sleep:000000000.021138468 longest_wake:000158140
+    496120.487490685  WAKE                           num_fds:0
+    496120.487512195  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496120.487536877  FILL_AUDIO                     dev:8 hw_level:2208
+    496120.487540956  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.487541387  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.487542424  WRITE_STREAMS_MIXED            write_limit:0
+    496120.487545241  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496120.487553319  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496120.487553801  DEV_SLEEP_TIME                 dev:8 wake:000496120.533546479
+    496120.487555795  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496120.487637278  WAKE                           num_fds:1
+    496120.487664200  FILL_AUDIO                     dev:8 hw_level:2208
+    496120.487665549  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.487667388  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.487671317  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.487671903  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.487672976  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496120.487673953  STREAM_SLEEP_TIME              id:140000 wake:000496120.508742568
+    496120.487679601  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496120.487679967  DEV_SLEEP_TIME                 dev:8 wake:000496120.555006830
+    496120.487680884  SLEEP                          sleep:000000000.021069071 longest_wake:000158140
+    496120.508772677  WAKE                           num_fds:0
+    496120.508795374  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2224
+    496120.508815400  FILL_AUDIO                     dev:8 hw_level:2224
+    496120.508819660  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.508820161  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.508821374  WRITE_STREAMS_MIXED            write_limit:0
+    496120.508824196  FILL_AUDIO_DONE                hw_level:2224 total_written:0 min_cb_level:1024
+    496120.508832600  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496120.508833091  DEV_SLEEP_TIME                 dev:8 wake:000496120.554158651
+    496120.508834880  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496120.508902311  WAKE                           num_fds:1
+    496120.508923881  FILL_AUDIO                     dev:8 hw_level:2176
+    496120.508925309  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.508926873  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.508931017  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.508931624  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.508932751  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496120.508933844  STREAM_SLEEP_TIME              id:140000 wake:000496120.530075901
+    496120.508939532  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496120.508939878  DEV_SLEEP_TIME                 dev:8 wake:000496120.575600019
+    496120.508940905  SLEEP                          sleep:000000000.021142548 longest_wake:000158140
+    496120.530106994  WAKE                           num_fds:0
+    496120.530126138  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496120.530149222  FILL_AUDIO                     dev:8 hw_level:2192
+    496120.530153271  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.530153772  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.530154915  WRITE_STREAMS_MIXED            write_limit:0
+    496120.530157902  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496120.530166151  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496120.530166632  DEV_SLEEP_TIME                 dev:8 wake:000496120.575825851
+    496120.530168476  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496120.530263270  WAKE                           num_fds:1
+    496120.530287591  FILL_AUDIO                     dev:8 hw_level:2192
+    496120.530289390  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.530291224  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.530297243  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.530298100  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.530299073  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496120.530300030  STREAM_SLEEP_TIME              id:140000 wake:000496120.551409234
+    496120.530305537  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496120.530305923  DEV_SLEEP_TIME                 dev:8 wake:000496120.597299604
+    496120.530306931  SLEEP                          sleep:000000000.021109630 longest_wake:000158140
+    496120.551515216  WAKE                           num_fds:0
+    496120.551539643  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496120.551571982  FILL_AUDIO                     dev:8 hw_level:2208
+    496120.551576849  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.551577670  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.551578848  WRITE_STREAMS_MIXED            write_limit:0
+    496120.551581605  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496120.551589884  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496120.551590535  DEV_SLEEP_TIME                 dev:8 wake:000496120.597582882
+    496120.551592309  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496120.551687780  WAKE                           num_fds:1
+    496120.551717463  FILL_AUDIO                     dev:8 hw_level:2208
+    496120.551719322  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.551720916  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.551725036  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.551725582  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.551726554  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496120.551727752  STREAM_SLEEP_TIME              id:140000 wake:000496120.572742567
+    496120.551733861  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496120.551734207  DEV_SLEEP_TIME                 dev:8 wake:000496120.619060629
+    496120.551735425  SLEEP                          sleep:000000000.021015271 longest_wake:000158140
+    496120.572824676  WAKE                           num_fds:0
+    496120.572845770  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2176
+    496120.572870968  FILL_AUDIO                     dev:8 hw_level:2176
+    496120.572875679  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.572876100  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.572877102  WRITE_STREAMS_MIXED            write_limit:0
+    496120.572880210  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496120.572888138  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496120.572888634  DEV_SLEEP_TIME                 dev:8 wake:000496120.618214821
+    496120.572890538  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496120.572971871  WAKE                           num_fds:1
+    496120.572998568  FILL_AUDIO                     dev:8 hw_level:2176
+    496120.573000197  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.573001775  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.573005724  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.573006361  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.573007188  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496120.573008170  STREAM_SLEEP_TIME              id:140000 wake:000496120.594075900
+    496120.573013723  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496120.573014129  DEV_SLEEP_TIME                 dev:8 wake:000496120.639674380
+    496120.573015131  SLEEP                          sleep:000000000.021068186 longest_wake:000158140
+    496120.594106428  WAKE                           num_fds:0
+    496120.594130167  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496120.594152995  FILL_AUDIO                     dev:8 hw_level:2192
+    496120.594157060  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.594157621  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.594158964  WRITE_STREAMS_MIXED            write_limit:0
+    496120.594162472  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496120.594171398  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496120.594171974  DEV_SLEEP_TIME                 dev:8 wake:000496120.639830446
+    496120.594173888  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496120.594300360  WAKE                           num_fds:1
+    496120.594345318  FILL_AUDIO                     dev:8 hw_level:2192
+    496120.594349362  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.594351888  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.594357862  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.594359015  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.594360694  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496120.594362272  STREAM_SLEEP_TIME              id:140000 wake:000496120.615409233
+    496120.594372245  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496120.594372631  DEV_SLEEP_TIME                 dev:8 wake:000496120.661361646
+    496120.594373869  SLEEP                          sleep:000000000.021047587 longest_wake:000158140
+    496120.615497480  WAKE                           num_fds:0
+    496120.615518910  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496120.615544870  FILL_AUDIO                     dev:8 hw_level:2208
+    496120.615549009  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.615549485  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.615550703  WRITE_STREAMS_MIXED            write_limit:0
+    496120.615553650  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496120.615561789  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496120.615562275  DEV_SLEEP_TIME                 dev:8 wake:000496120.661554928
+    496120.615564194  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496120.615648213  WAKE                           num_fds:1
+    496120.615675972  FILL_AUDIO                     dev:8 hw_level:2208
+    496120.615677651  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.615679205  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.615685173  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.615685690  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.615686592  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496120.615687614  STREAM_SLEEP_TIME              id:140000 wake:000496120.636742566
+    496120.615693242  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496120.615693638  DEV_SLEEP_TIME                 dev:8 wake:000496120.683020481
+    496120.615694630  SLEEP                          sleep:000000000.021055418 longest_wake:000158140
+    496120.636988830  WAKE                           num_fds:0
+    496120.637012740  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2176
+    496120.637044844  FILL_AUDIO                     dev:8 hw_level:2176
+    496120.637049500  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.637050006  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.637051670  WRITE_STREAMS_MIXED            write_limit:0
+    496120.637055198  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496120.637063793  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496120.637064369  DEV_SLEEP_TIME                 dev:8 wake:000496120.682389804
+    496120.637066249  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496120.637165513  WAKE                           num_fds:1
+    496120.637197221  FILL_AUDIO                     dev:8 hw_level:2176
+    496120.637199186  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.637200739  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.637204809  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.637205505  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.637206292  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496120.637207460  STREAM_SLEEP_TIME              id:140000 wake:000496120.658075899
+    496120.637213203  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496120.637213549  DEV_SLEEP_TIME                 dev:8 wake:000496120.703873655
+    496120.637214581  SLEEP                          sleep:000000000.020868910 longest_wake:000158140
+    496120.658107059  WAKE                           num_fds:0
+    496120.658131099  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496120.658156964  FILL_AUDIO                     dev:8 hw_level:2192
+    496120.658161169  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.658161655  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.658162793  WRITE_STREAMS_MIXED            write_limit:0
+    496120.658165679  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496120.658173658  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496120.658174189  DEV_SLEEP_TIME                 dev:8 wake:000496120.703833628
+    496120.658176143  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496120.658282023  WAKE                           num_fds:1
+    496120.658308744  FILL_AUDIO                     dev:8 hw_level:2192
+    496120.658310418  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.658312052  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.658315991  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.658316542  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.658317565  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496120.658318632  STREAM_SLEEP_TIME              id:140000 wake:000496120.679409232
+    496120.658324140  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496120.658324521  DEV_SLEEP_TIME                 dev:8 wake:000496120.725318191
+    496120.658325598  SLEEP                          sleep:000000000.021091041 longest_wake:000158140
+    496120.679472577  WAKE                           num_fds:0
+    496120.679499710  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496120.679534876  FILL_AUDIO                     dev:8 hw_level:2208
+    496120.679539322  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.679539768  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.679540820  WRITE_STREAMS_MIXED            write_limit:0
+    496120.679543962  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496120.679552066  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496120.679552552  DEV_SLEEP_TIME                 dev:8 wake:000496120.725545526
+    496120.679554717  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496120.679658206  WAKE                           num_fds:1
+    496120.679687063  FILL_AUDIO                     dev:8 hw_level:2208
+    496120.679689233  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.679691282  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.679696504  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.679697211  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.679698694  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496120.679700002  STREAM_SLEEP_TIME              id:140000 wake:000496120.700742565
+    496120.679706392  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496120.679706863  DEV_SLEEP_TIME                 dev:8 wake:000496120.747032699
+    496120.679708442  SLEEP                          sleep:000000000.021043199 longest_wake:000158140
+    496120.701001735  WAKE                           num_fds:0
+    496120.701028687  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2176
+    496120.701068313  FILL_AUDIO                     dev:8 hw_level:2176
+    496120.701073981  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.701074483  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.701075831  WRITE_STREAMS_MIXED            write_limit:0
+    496120.701078873  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496120.701087443  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496120.701088089  DEV_SLEEP_TIME                 dev:8 wake:000496120.746413599
+    496120.701089998  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496120.701178107  WAKE                           num_fds:1
+    496120.701210877  FILL_AUDIO                     dev:8 hw_level:2176
+    496120.701214907  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.701217107  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.701224354  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.701225426  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.701226919  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496120.701228268  STREAM_SLEEP_TIME              id:140000 wake:000496120.722075898
+    496120.701235750  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496120.701236226  DEV_SLEEP_TIME                 dev:8 wake:000496120.767894327
+    496120.701237489  SLEEP                          sleep:000000000.020848237 longest_wake:000158140
+    496120.722129751  WAKE                           num_fds:0
+    496120.722152443  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496120.722180463  FILL_AUDIO                     dev:8 hw_level:2192
+    496120.722184517  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.722185986  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.722187118  WRITE_STREAMS_MIXED            write_limit:0
+    496120.722190125  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496120.722198114  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496120.722198590  DEV_SLEEP_TIME                 dev:8 wake:000496120.767858014
+    496120.722200319  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496120.722290758  WAKE                           num_fds:1
+    496120.722319033  FILL_AUDIO                     dev:8 hw_level:2192
+    496120.722320817  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.722322326  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.722343188  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.722344035  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.722345638  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496120.722347122  STREAM_SLEEP_TIME              id:140000 wake:000496120.743409231
+    496120.722354043  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496120.722354504  DEV_SLEEP_TIME                 dev:8 wake:000496120.789346455
+    496120.722355882  SLEEP                          sleep:000000000.021062776 longest_wake:000158140
+    496120.743467496  WAKE                           num_fds:0
+    496120.743516138  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496120.743566865  FILL_AUDIO                     dev:8 hw_level:2208
+    496120.743580211  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.743581248  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.743582621  WRITE_STREAMS_MIXED            write_limit:0
+    496120.743591356  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496120.743609794  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496120.743611438  DEV_SLEEP_TIME                 dev:8 wake:000496120.789595295
+    496120.743614821  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496120.743749752  WAKE                           num_fds:1
+    496120.743794781  FILL_AUDIO                     dev:8 hw_level:2208
+    496120.743800940  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.743806643  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.743815474  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.743816401  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.743820355  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496120.743821543  STREAM_SLEEP_TIME              id:140000 wake:000496120.764742564
+    496120.743832989  SET_DEV_WAKE                   dev:8 hw_level:3184 sleep:3184
+    496120.743833335  DEV_SLEEP_TIME                 dev:8 wake:000496120.810154400
+    496120.743834934  SLEEP                          sleep:000000000.020921497 longest_wake:000158140
+    496120.764995124  WAKE                           num_fds:0
+    496120.765018142  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2176
+    496120.765049405  FILL_AUDIO                     dev:8 hw_level:2176
+    496120.765054130  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.765054607  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.765055724  WRITE_STREAMS_MIXED            write_limit:0
+    496120.765059077  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496120.765067501  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496120.765068063  DEV_SLEEP_TIME                 dev:8 wake:000496120.810393783
+    496120.765069787  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496120.765170805  WAKE                           num_fds:1
+    496120.765200934  FILL_AUDIO                     dev:8 hw_level:2176
+    496120.765203149  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.765205049  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.765209123  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.765209710  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.765210597  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496120.765211584  STREAM_SLEEP_TIME              id:140000 wake:000496120.786075897
+    496120.765217232  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496120.765217583  DEV_SLEEP_TIME                 dev:8 wake:000496120.831877784
+    496120.765218565  SLEEP                          sleep:000000000.020864779 longest_wake:000158140
+    496120.786359235  WAKE                           num_fds:0
+    496120.786386748  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496120.786424054  FILL_AUDIO                     dev:8 hw_level:2192
+    496120.786429051  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.786429712  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.786430840  WRITE_STREAMS_MIXED            write_limit:0
+    496120.786434593  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496120.786442146  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496120.786442732  DEV_SLEEP_TIME                 dev:8 wake:000496120.832102517
+    496120.786444822  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496120.786626827  WAKE                           num_fds:1
+    496120.786663677  FILL_AUDIO                     dev:8 hw_level:2192
+    496120.786666764  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.786669656  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.786676146  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.786676853  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.786678446  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496120.786679774  STREAM_SLEEP_TIME              id:140000 wake:000496120.807409230
+    496120.786686410  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496120.786686836  DEV_SLEEP_TIME                 dev:8 wake:000496120.853679218
+    496120.786688139  SLEEP                          sleep:000000000.020730012 longest_wake:000158140
+    496120.807445429  WAKE                           num_fds:0
+    496120.807468548  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496120.807488058  FILL_AUDIO                     dev:8 hw_level:2208
+    496120.807491601  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.807491967  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.807492949  WRITE_STREAMS_MIXED            write_limit:0
+    496120.807495610  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496120.807502712  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496120.807503133  DEV_SLEEP_TIME                 dev:8 wake:000496120.853496708
+    496120.807504536  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496120.807594052  WAKE                           num_fds:1
+    496120.807615998  FILL_AUDIO                     dev:8 hw_level:2208
+    496120.807617141  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.807618714  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.807623114  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.807623711  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.807624498  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496120.807625550  STREAM_SLEEP_TIME              id:140000 wake:000496120.828742563
+    496120.807631148  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496120.807631539  DEV_SLEEP_TIME                 dev:8 wake:000496120.874958417
+    496120.807632491  SLEEP                          sleep:000000000.021117479 longest_wake:000158140
+    496120.828792742  WAKE                           num_fds:0
+    496120.828820235  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2224
+    496120.828856364  FILL_AUDIO                     dev:8 hw_level:2176
+    496120.828863094  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.828863861  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.828865430  WRITE_STREAMS_MIXED            write_limit:0
+    496120.828869083  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496120.828879402  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496120.828880214  DEV_SLEEP_TIME                 dev:8 wake:000496120.874204110
+    496120.828883105  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496120.829016984  WAKE                           num_fds:1
+    496120.829054035  FILL_AUDIO                     dev:8 hw_level:2176
+    496120.829056892  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.829058781  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.829064614  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.829065461  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.829067341  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496120.829069421  STREAM_SLEEP_TIME              id:140000 wake:000496120.850075896
+    496120.829076051  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496120.829076938  DEV_SLEEP_TIME                 dev:8 wake:000496120.895735420
+    496120.829078632  SLEEP                          sleep:000000000.021007142 longest_wake:000158140
+    496120.850120456  WAKE                           num_fds:0
+    496120.850146552  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496120.850169236  FILL_AUDIO                     dev:8 hw_level:2192
+    496120.850174064  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.850174484  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.850175535  WRITE_STREAMS_MIXED            write_limit:0
+    496120.850178863  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496120.850186829  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496120.850187290  DEV_SLEEP_TIME                 dev:8 wake:000496120.895846740
+    496120.850189190  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496120.850269175  WAKE                           num_fds:1
+    496120.850295709  FILL_AUDIO                     dev:8 hw_level:2192
+    496120.850297505  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.850299301  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.850306373  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.850307146  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.850308429  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496120.850309942  STREAM_SLEEP_TIME              id:140000 wake:000496120.871409229
+    496120.850316384  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496120.850316865  DEV_SLEEP_TIME                 dev:8 wake:000496120.917309275
+    496120.850318385  SLEEP                          sleep:000000000.021099954 longest_wake:000158140
+    496120.871515551  WAKE                           num_fds:0
+    496120.871539366  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496120.871569746  FILL_AUDIO                     dev:8 hw_level:2208
+    496120.871573680  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.871574046  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.871574918  WRITE_STREAMS_MIXED            write_limit:0
+    496120.871578080  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496120.871584976  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496120.871585402  DEV_SLEEP_TIME                 dev:8 wake:000496120.917579043
+    496120.871586780  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496120.871750112  WAKE                           num_fds:1
+    496120.871778648  FILL_AUDIO                     dev:8 hw_level:2208
+    496120.871780162  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.871781846  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.871788481  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.871789102  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.871790060  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496120.871791287  STREAM_SLEEP_TIME              id:140000 wake:000496120.892742562
+    496120.871797371  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496120.871797833  DEV_SLEEP_TIME                 dev:8 wake:000496120.939124029
+    496120.871799045  SLEEP                          sleep:000000000.020951866 longest_wake:000158140
+    496120.892801211  WAKE                           num_fds:0
+    496120.892847132  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2176
+    496120.892888528  FILL_AUDIO                     dev:8 hw_level:2176
+    496120.892896396  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.892897123  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.892898987  WRITE_STREAMS_MIXED            write_limit:0
+    496120.892904364  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496120.892913505  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496120.892914112  DEV_SLEEP_TIME                 dev:8 wake:000496120.938239276
+    496120.892917434  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496120.893084380  WAKE                           num_fds:1
+    496120.893127394  FILL_AUDIO                     dev:8 hw_level:2176
+    496120.893129760  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.893131584  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.893139863  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.893140660  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.893154903  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496120.893156266  STREAM_SLEEP_TIME              id:140000 wake:000496120.914075895
+    496120.893162600  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496120.893163157  DEV_SLEEP_TIME                 dev:8 wake:000496120.959822335
+    496120.893164620  SLEEP                          sleep:000000000.020920226 longest_wake:000158140
+    496120.914118665  WAKE                           num_fds:0
+    496120.914145071  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496120.914177055  FILL_AUDIO                     dev:8 hw_level:2192
+    496120.914181194  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.914181555  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.914182592  WRITE_STREAMS_MIXED            write_limit:0
+    496120.914185198  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496120.914192555  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496120.914192976  DEV_SLEEP_TIME                 dev:8 wake:000496120.959852962
+    496120.914194495  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496120.914342014  WAKE                           num_fds:1
+    496120.914374870  FILL_AUDIO                     dev:8 hw_level:2192
+    496120.914377005  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.914378849  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.914384623  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.914385279  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.914386522  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496120.914387710  STREAM_SLEEP_TIME              id:140000 wake:000496120.935409228
+    496120.914393338  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496120.914393764  DEV_SLEEP_TIME                 dev:8 wake:000496120.981387174
+    496120.914395007  SLEEP                          sleep:000000000.021022054 longest_wake:000158140
+    496120.935443284  WAKE                           num_fds:0
+    496120.935466292  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496120.935494723  FILL_AUDIO                     dev:8 hw_level:2208
+    496120.935498757  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.935499143  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.935500250  WRITE_STREAMS_MIXED            write_limit:0
+    496120.935502811  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496120.935509858  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496120.935510274  DEV_SLEEP_TIME                 dev:8 wake:000496120.981503844
+    496120.935512088  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496120.935671401  WAKE                           num_fds:1
+    496120.935705795  FILL_AUDIO                     dev:8 hw_level:2208
+    496120.935708366  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.935709944  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.935715983  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.935716700  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.935718204  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496120.935719502  STREAM_SLEEP_TIME              id:140000 wake:000496120.956742561
+    496120.935725365  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496120.935725731  DEV_SLEEP_TIME                 dev:8 wake:000496121.003052283
+    496120.935726974  SLEEP                          sleep:000000000.021023611 longest_wake:000158140
+    496120.956811014  WAKE                           num_fds:0
+    496120.956833380  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2176
+    496120.956857200  FILL_AUDIO                     dev:8 hw_level:2176
+    496120.956860568  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.956860934  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.956861791  WRITE_STREAMS_MIXED            write_limit:0
+    496120.956864337  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496120.956871167  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496120.956871538  DEV_SLEEP_TIME                 dev:8 wake:000496121.002198642
+    496120.956872942  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496120.957023259  WAKE                           num_fds:1
+    496120.957050822  FILL_AUDIO                     dev:8 hw_level:2176
+    496120.957052085  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.957053584  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.957059472  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.957060068  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.957060795  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496120.957061727  STREAM_SLEEP_TIME              id:140000 wake:000496120.978075894
+    496120.957067446  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496120.957067826  DEV_SLEEP_TIME                 dev:8 wake:000496121.023727977
+    496120.957068839  SLEEP                          sleep:000000000.021014583 longest_wake:000158140
+    496120.978110225  WAKE                           num_fds:0
+    496120.978133193  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496120.978157349  FILL_AUDIO                     dev:8 hw_level:2192
+    496120.978161774  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.978162401  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.978163603  WRITE_STREAMS_MIXED            write_limit:0
+    496120.978166410  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496120.978174498  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496120.978175055  DEV_SLEEP_TIME                 dev:8 wake:000496121.023834404
+    496120.978176864  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496120.978294115  WAKE                           num_fds:1
+    496120.978341573  FILL_AUDIO                     dev:8 hw_level:2192
+    496120.978344284  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.978345853  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.978350043  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.978350724  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.978352358  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496120.978353656  STREAM_SLEEP_TIME              id:140000 wake:000496120.999409227
+    496120.978359555  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496120.978359931  DEV_SLEEP_TIME                 dev:8 wake:000496121.045353110
+    496120.978361198  SLEEP                          sleep:000000000.021056117 longest_wake:000158140
+    496120.999438377  WAKE                           num_fds:0
+    496120.999457772  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496120.999481026  FILL_AUDIO                     dev:8 hw_level:2208
+    496120.999484323  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496120.999484689  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496120.999485541  WRITE_STREAMS_MIXED            write_limit:0
+    496120.999487892  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496120.999494517  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496120.999494883  DEV_SLEEP_TIME                 dev:8 wake:000496121.045488844
+    496120.999496361  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496120.999657453  WAKE                           num_fds:1
+    496120.999688670  FILL_AUDIO                     dev:8 hw_level:2208
+    496120.999691136  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496120.999692664  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496120.999698443  DEV_STREAM_MIX                 written:1024 read:1024
+    496120.999699154  WRITE_STREAMS_MIXED            write_limit:1024
+    496120.999700618  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496120.999701906  STREAM_SLEEP_TIME              id:140000 wake:000496121.020742560
+    496120.999707408  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496120.999707779  DEV_SLEEP_TIME                 dev:8 wake:000496121.067034697
+    496120.999709032  SLEEP                          sleep:000000000.021041196 longest_wake:000158140
+    496121.020803596  WAKE                           num_fds:0
+    496121.020825632  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2176
+    496121.020845974  FILL_AUDIO                     dev:8 hw_level:2176
+    496121.020849552  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.020849963  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.020850860  WRITE_STREAMS_MIXED            write_limit:0
+    496121.020853421  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496121.020860498  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496121.020860919  DEV_SLEEP_TIME                 dev:8 wake:000496121.066187902
+    496121.020862302  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496121.021012834  WAKE                           num_fds:1
+    496121.021038699  FILL_AUDIO                     dev:8 hw_level:2176
+    496121.021040107  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.021041651  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.021047835  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.021048461  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.021049830  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:0
+    496121.021050226  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.021050727  WRITE_STREAMS_MIXED            write_limit:0
+    496121.021051388  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496121.021052396  STREAM_SLEEP_TIME              id:140000 wake:000496121.042075893
+    496121.021057968  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496121.021058349  DEV_SLEEP_TIME                 dev:8 wake:000496121.087718621
+    496121.021059432  SLEEP                          sleep:000000000.021023938 longest_wake:000158140
+    496121.042115492  WAKE                           num_fds:0
+    496121.042141532  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496121.042168459  FILL_AUDIO                     dev:8 hw_level:2192
+    496121.042173215  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.042173726  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.042174779  WRITE_STREAMS_MIXED            write_limit:0
+    496121.042177791  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496121.042186050  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496121.042186571  DEV_SLEEP_TIME                 dev:8 wake:000496121.087845660
+    496121.042188430  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496121.042299602  WAKE                           num_fds:1
+    496121.042374133  FILL_AUDIO                     dev:8 hw_level:2192
+    496121.042379581  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.042383896  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.042396134  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.042396845  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.042399356  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496121.042401336  STREAM_SLEEP_TIME              id:140000 wake:000496121.063409226
+    496121.042408733  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496121.042409194  DEV_SLEEP_TIME                 dev:8 wake:000496121.109400118
+    496121.042411098  SLEEP                          sleep:000000000.021009108 longest_wake:000158140
+    496121.063442196  WAKE                           num_fds:0
+    496121.063463530  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496121.063487531  FILL_AUDIO                     dev:8 hw_level:2208
+    496121.063490733  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.063491094  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.063491956  WRITE_STREAMS_MIXED            write_limit:0
+    496121.063494311  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496121.063501097  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496121.063501518  DEV_SLEEP_TIME                 dev:8 wake:000496121.109495334
+    496121.063502906  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496121.063638629  WAKE                           num_fds:1
+    496121.063664449  FILL_AUDIO                     dev:8 hw_level:2208
+    496121.063665983  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.063667807  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.063672392  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.063672979  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.063673881  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496121.063674803  STREAM_SLEEP_TIME              id:140000 wake:000496121.084742559
+    496121.063680601  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496121.063680947  DEV_SLEEP_TIME                 dev:8 wake:000496121.131007700
+    496121.063681985  SLEEP                          sleep:000000000.021068192 longest_wake:000158140
+    496121.084972982  WAKE                           num_fds:0
+    496121.084998972  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2176
+    496121.085036704  FILL_AUDIO                     dev:8 hw_level:2176
+    496121.085042467  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.085043004  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.085044231  WRITE_STREAMS_MIXED            write_limit:0
+    496121.085047218  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496121.085055317  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496121.085055828  DEV_SLEEP_TIME                 dev:8 wake:000496121.130381869
+    496121.085057853  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496121.085230672  WAKE                           num_fds:1
+    496121.085267442  FILL_AUDIO                     dev:8 hw_level:2176
+    496121.085269406  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.085270950  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.085275450  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.085276072  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.085277325  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496121.085278387  STREAM_SLEEP_TIME              id:140000 wake:000496121.106075892
+    496121.085284005  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496121.085284386  DEV_SLEEP_TIME                 dev:8 wake:000496121.151944572
+    496121.085285343  SLEEP                          sleep:000000000.020797986 longest_wake:000158140
+    496121.106153725  WAKE                           num_fds:0
+    496121.106176578  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496121.106205204  FILL_AUDIO                     dev:8 hw_level:2192
+    496121.106209083  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.106209449  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.106210311  WRITE_STREAMS_MIXED            write_limit:0
+    496121.106212756  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496121.106219677  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496121.106220093  DEV_SLEEP_TIME                 dev:8 wake:000496121.151880430
+    496121.106221497  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496121.106405836  WAKE                           num_fds:1
+    496121.106434848  FILL_AUDIO                     dev:8 hw_level:2192
+    496121.106436587  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.106438196  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.106442836  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.106443453  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.106444270  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496121.106445267  STREAM_SLEEP_TIME              id:140000 wake:000496121.127409225
+    496121.106450840  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496121.106451211  DEV_SLEEP_TIME                 dev:8 wake:000496121.173444846
+    496121.106452173  SLEEP                          sleep:000000000.020964379 longest_wake:000158140
+    496121.127456103  WAKE                           num_fds:0
+    496121.127480189  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496121.127509316  FILL_AUDIO                     dev:8 hw_level:2208
+    496121.127514788  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.127515350  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.127516447  WRITE_STREAMS_MIXED            write_limit:0
+    496121.127519494  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496121.127527327  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496121.127527949  DEV_SLEEP_TIME                 dev:8 wake:000496121.173520722
+    496121.127529863  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496121.127623489  WAKE                           num_fds:1
+    496121.127651173  FILL_AUDIO                     dev:8 hw_level:2208
+    496121.127653223  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.127655077  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.127661482  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.127662299  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.127663462  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496121.127664910  STREAM_SLEEP_TIME              id:140000 wake:000496121.148742558
+    496121.127671079  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496121.127671600  DEV_SLEEP_TIME                 dev:8 wake:000496121.194997566
+    496121.127673084  SLEEP                          sleep:000000000.021078325 longest_wake:000158140
+    496121.148794283  WAKE                           num_fds:0
+    496121.148808400  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2224
+    496121.148823722  FILL_AUDIO                     dev:8 hw_level:2176
+    496121.148826017  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.148826191  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.148826634  WRITE_STREAMS_MIXED            write_limit:0
+    496121.148828005  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496121.148833984  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496121.148834180  DEV_SLEEP_TIME                 dev:8 wake:000496121.194161902
+    496121.148834991  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496121.148928624  WAKE                           num_fds:1
+    496121.148951279  FILL_AUDIO                     dev:8 hw_level:2176
+    496121.148952642  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.148953524  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.148957366  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.148957752  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.148958498  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496121.148959151  STREAM_SLEEP_TIME              id:140000 wake:000496121.170075891
+    496121.148964100  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496121.148964295  DEV_SLEEP_TIME                 dev:8 wake:000496121.215625558
+    496121.148965082  SLEEP                          sleep:000000000.021116999 longest_wake:000158140
+    496121.170295036  WAKE                           num_fds:0
+    496121.170310195  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496121.170338189  FILL_AUDIO                     dev:8 hw_level:2192
+    496121.170340356  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.170340526  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.170341064  WRITE_STREAMS_MIXED            write_limit:0
+    496121.170342450  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496121.170348111  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496121.170348306  DEV_SLEEP_TIME                 dev:8 wake:000496121.216009708
+    496121.170349021  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496121.170436685  WAKE                           num_fds:1
+    496121.170457636  FILL_AUDIO                     dev:8 hw_level:2192
+    496121.170458990  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.170459889  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.170463374  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.170463768  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.170464518  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496121.170465104  STREAM_SLEEP_TIME              id:140000 wake:000496121.191409224
+    496121.170469940  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496121.170470140  DEV_SLEEP_TIME                 dev:8 wake:000496121.237464872
+    496121.170470759  SLEEP                          sleep:000000000.020944352 longest_wake:000158140
+    496121.191442767  WAKE                           num_fds:0
+    496121.191468065  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496121.191492321  FILL_AUDIO                     dev:8 hw_level:2208
+    496121.191497173  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.191497739  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.191498982  WRITE_STREAMS_MIXED            write_limit:0
+    496121.191502044  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496121.191510178  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496121.191510804  DEV_SLEEP_TIME                 dev:8 wake:000496121.237503387
+    496121.191512688  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496121.191656004  WAKE                           num_fds:1
+    496121.191683488  FILL_AUDIO                     dev:8 hw_level:2208
+    496121.191685171  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.191687051  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.191693110  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.191693912  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.191694869  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496121.191696172  STREAM_SLEEP_TIME              id:140000 wake:000496121.212742557
+    496121.191702466  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496121.191703003  DEV_SLEEP_TIME                 dev:8 wake:000496121.259028934
+    496121.191704526  SLEEP                          sleep:000000000.021046956 longest_wake:000158140
+    496121.212804603  WAKE                           num_fds:0
+    496121.212826428  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2176
+    496121.212845046  FILL_AUDIO                     dev:8 hw_level:2176
+    496121.212848564  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.212848930  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.212849963  WRITE_STREAMS_MIXED            write_limit:0
+    496121.212852378  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496121.212859840  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496121.212860281  DEV_SLEEP_TIME                 dev:8 wake:000496121.258187200
+    496121.212861805  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496121.213036739  WAKE                           num_fds:1
+    496121.213070923  FILL_AUDIO                     dev:8 hw_level:2176
+    496121.213073554  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.213075102  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.213081507  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.213082214  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.213083777  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496121.213085085  STREAM_SLEEP_TIME              id:140000 wake:000496121.234075890
+    496121.213090939  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496121.213091360  DEV_SLEEP_TIME                 dev:8 wake:000496121.279751190
+    496121.213092643  SLEEP                          sleep:000000000.020991366 longest_wake:000158140
+    496121.234340660  WAKE                           num_fds:0
+    496121.234364265  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496121.234393978  FILL_AUDIO                     dev:8 hw_level:2192
+    496121.234398053  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.234398418  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.234399290  WRITE_STREAMS_MIXED            write_limit:0
+    496121.234401912  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496121.234408692  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496121.234409113  DEV_SLEEP_TIME                 dev:8 wake:000496121.280069555
+    496121.234410491  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496121.234595734  WAKE                           num_fds:1
+    496121.234631867  FILL_AUDIO                     dev:8 hw_level:2192
+    496121.234634709  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.234636328  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.234640613  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.234641319  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.234643008  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496121.234644276  STREAM_SLEEP_TIME              id:140000 wake:000496121.255409223
+    496121.234650119  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496121.234650540  DEV_SLEEP_TIME                 dev:8 wake:000496121.301643780
+    496121.234651838  SLEEP                          sleep:000000000.020765443 longest_wake:000158140
+    496121.255666578  WAKE                           num_fds:0
+    496121.255690188  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496121.255720508  FILL_AUDIO                     dev:8 hw_level:2208
+    496121.255724261  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.255724632  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.255725509  WRITE_STREAMS_MIXED            write_limit:0
+    496121.255728055  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496121.255734826  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496121.255735242  DEV_SLEEP_TIME                 dev:8 wake:000496121.301729027
+    496121.255736630  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496121.255919587  WAKE                           num_fds:1
+    496121.255961218  FILL_AUDIO                     dev:8 hw_level:2160
+    496121.255964646  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.255966716  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.255972560  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.255973502  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.255975742  FILL_AUDIO_DONE                hw_level:2160 total_written:1024 min_cb_level:1024
+    496121.255977441  STREAM_SLEEP_TIME              id:140000 wake:000496121.276742556
+    496121.256005751  SET_DEV_WAKE                   dev:8 hw_level:3184 sleep:3184
+    496121.256006408  DEV_SLEEP_TIME                 dev:8 wake:000496121.322310077
+    496121.256008177  SLEEP                          sleep:000000000.020765812 longest_wake:000158140
+    496121.276833494  WAKE                           num_fds:0
+    496121.276877902  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2176
+    496121.276909971  FILL_AUDIO                     dev:8 hw_level:2176
+    496121.276917979  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.276918350  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.276919974  WRITE_STREAMS_MIXED            write_limit:0
+    496121.276924930  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496121.276932894  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496121.276933325  DEV_SLEEP_TIME                 dev:8 wake:000496121.322259882
+    496121.276934914  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496121.277102220  WAKE                           num_fds:1
+    496121.277141049  FILL_AUDIO                     dev:8 hw_level:2176
+    496121.277146141  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.277147930  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.277154756  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.277155468  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.277157577  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496121.277159051  STREAM_SLEEP_TIME              id:140000 wake:000496121.298075889
+    496121.277165325  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496121.277165756  DEV_SLEEP_TIME                 dev:8 wake:000496121.343825146
+    496121.277167140  SLEEP                          sleep:000000000.020917409 longest_wake:000158140
+    496121.298348914  WAKE                           num_fds:0
+    496121.298371636  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496121.298402057  FILL_AUDIO                     dev:8 hw_level:2192
+    496121.298407078  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.298407444  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.298408321  WRITE_STREAMS_MIXED            write_limit:0
+    496121.298410827  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496121.298417593  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496121.298418013  DEV_SLEEP_TIME                 dev:8 wake:000496121.344078475
+    496121.298419487  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496121.298605947  WAKE                           num_fds:1
+    496121.298642421  FILL_AUDIO                     dev:8 hw_level:2192
+    496121.298645453  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.298646977  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.298651342  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.298652024  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.298653682  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496121.298654975  STREAM_SLEEP_TIME              id:140000 wake:000496121.319409222
+    496121.298660674  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496121.298661120  DEV_SLEEP_TIME                 dev:8 wake:000496121.365654424
+    496121.298662398  SLEEP                          sleep:000000000.020754798 longest_wake:000158140
+    496121.319517158  WAKE                           num_fds:0
+    496121.319540282  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496121.319569664  FILL_AUDIO                     dev:8 hw_level:2208
+    496121.319573483  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.319573844  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.319574716  WRITE_STREAMS_MIXED            write_limit:0
+    496121.319577397  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496121.319584288  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496121.319584709  DEV_SLEEP_TIME                 dev:8 wake:000496121.365578450
+    496121.319586107  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496121.319772252  WAKE                           num_fds:1
+    496121.319806747  FILL_AUDIO                     dev:8 hw_level:2208
+    496121.319809608  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.319811162  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.319817070  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.319817782  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.319819521  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496121.319820794  STREAM_SLEEP_TIME              id:140000 wake:000496121.340742555
+    496121.319826878  SET_DEV_WAKE                   dev:8 hw_level:3184 sleep:3184
+    496121.319827319  DEV_SLEEP_TIME                 dev:8 wake:000496121.386153636
+    496121.319828577  SLEEP                          sleep:000000000.020922252 longest_wake:000158140
+    496121.340779013  WAKE                           num_fds:0
+    496121.340801190  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2224
+    496121.340820529  FILL_AUDIO                     dev:8 hw_level:2224
+    496121.340824208  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.340824574  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.340825431  WRITE_STREAMS_MIXED            write_limit:0
+    496121.340827766  FILL_AUDIO_DONE                hw_level:2224 total_written:0 min_cb_level:1024
+    496121.340835128  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496121.340835564  DEV_SLEEP_TIME                 dev:8 wake:000496121.386162156
+    496121.340837022  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496121.340934888  WAKE                           num_fds:1
+    496121.340962677  FILL_AUDIO                     dev:8 hw_level:2176
+    496121.340965148  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.340967083  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.340972921  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.340973838  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.340975302  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496121.340976885  STREAM_SLEEP_TIME              id:140000 wake:000496121.362075888
+    496121.340983536  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496121.340984062  DEV_SLEEP_TIME                 dev:8 wake:000496121.407642910
+    496121.340985375  SLEEP                          sleep:000000000.021099644 longest_wake:000158140
+    496121.362354958  WAKE                           num_fds:0
+    496121.362378873  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496121.362408697  FILL_AUDIO                     dev:8 hw_level:2192
+    496121.362412716  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.362413087  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.362413959  WRITE_STREAMS_MIXED            write_limit:0
+    496121.362416470  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496121.362423406  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496121.362423822  DEV_SLEEP_TIME                 dev:8 wake:000496121.408084143
+    496121.362425310  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496121.362609405  WAKE                           num_fds:1
+    496121.362644947  FILL_AUDIO                     dev:8 hw_level:2192
+    496121.362647794  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.362649352  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.362655296  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.362656003  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.362657697  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496121.362658959  STREAM_SLEEP_TIME              id:140000 wake:000496121.383409221
+    496121.362664853  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496121.362665284  DEV_SLEEP_TIME                 dev:8 wake:000496121.429658468
+    496121.362666582  SLEEP                          sleep:000000000.020750753 longest_wake:000158140
+    496121.383661311  WAKE                           num_fds:0
+    496121.383685507  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496121.383715957  FILL_AUDIO                     dev:8 hw_level:2208
+    496121.383719771  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.383720152  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.383721169  WRITE_STREAMS_MIXED            write_limit:0
+    496121.383723635  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496121.383730520  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496121.383730941  DEV_SLEEP_TIME                 dev:8 wake:000496121.429724627
+    496121.383732590  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496121.383917362  WAKE                           num_fds:1
+    496121.383953876  FILL_AUDIO                     dev:8 hw_level:2160
+    496121.383956778  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.383958356  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.383964300  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.383965012  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.383966590  FILL_AUDIO_DONE                hw_level:2160 total_written:1024 min_cb_level:1024
+    496121.383967858  STREAM_SLEEP_TIME              id:140000 wake:000496121.404742554
+    496121.383973662  SET_DEV_WAKE                   dev:8 hw_level:3184 sleep:3184
+    496121.383974088  DEV_SLEEP_TIME                 dev:8 wake:000496121.450300690
+    496121.383975396  SLEEP                          sleep:000000000.020775197 longest_wake:000158140
+    496121.404849376  WAKE                           num_fds:0
+    496121.404872895  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2176
+    496121.404901997  FILL_AUDIO                     dev:8 hw_level:2176
+    496121.404905926  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.404906297  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.404907169  WRITE_STREAMS_MIXED            write_limit:0
+    496121.404909630  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496121.404916471  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496121.404916897  DEV_SLEEP_TIME                 dev:8 wake:000496121.450243925
+    496121.404918325  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496121.405102806  WAKE                           num_fds:1
+    496121.405139886  FILL_AUDIO                     dev:8 hw_level:2176
+    496121.405142743  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.405144241  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.405150476  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.405151182  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.405152826  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496121.405154069  STREAM_SLEEP_TIME              id:140000 wake:000496121.426075887
+    496121.405159912  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496121.405160338  DEV_SLEEP_TIME                 dev:8 wake:000496121.471820239
+    496121.405161661  SLEEP                          sleep:000000000.020922314 longest_wake:000158140
+    496121.426187061  WAKE                           num_fds:0
+    496121.426210430  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496121.426240775  FILL_AUDIO                     dev:8 hw_level:2192
+    496121.426244468  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.426244844  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.426245716  WRITE_STREAMS_MIXED            write_limit:0
+    496121.426248337  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496121.426255243  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496121.426255674  DEV_SLEEP_TIME                 dev:8 wake:000496121.471916076
+    496121.426257068  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496121.426460306  WAKE                           num_fds:1
+    496121.426497361  FILL_AUDIO                     dev:8 hw_level:2192
+    496121.426500183  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.426501801  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.426507575  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.426508286  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.426509890  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496121.426511163  STREAM_SLEEP_TIME              id:140000 wake:000496121.447409220
+    496121.426516901  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496121.426517322  DEV_SLEEP_TIME                 dev:8 wake:000496121.493510657
+    496121.426518570  SLEEP                          sleep:000000000.020898563 longest_wake:000158140
+    496121.447437098  WAKE                           num_fds:0
+    496121.447459314  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496121.447478694  FILL_AUDIO                     dev:8 hw_level:2208
+    496121.447482052  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.447482413  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.447483420  WRITE_STREAMS_MIXED            write_limit:0
+    496121.447485916  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496121.447492992  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496121.447493413  DEV_SLEEP_TIME                 dev:8 wake:000496121.493486988
+    496121.447494891  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496121.447638448  WAKE                           num_fds:1
+    496121.447662433  FILL_AUDIO                     dev:8 hw_level:2208
+    496121.447663871  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.447665395  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.447669329  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.447669940  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.447670717  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496121.447671765  STREAM_SLEEP_TIME              id:140000 wake:000496121.468742553
+    496121.447677317  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496121.447677698  DEV_SLEEP_TIME                 dev:8 wake:000496121.515004652
+    496121.447678721  SLEEP                          sleep:000000000.021071234 longest_wake:000158140
+    496121.468810020  WAKE                           num_fds:0
+    496121.468833494  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2176
+    496121.468862756  FILL_AUDIO                     dev:8 hw_level:2176
+    496121.468866450  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.468866821  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.468867683  WRITE_STREAMS_MIXED            write_limit:0
+    496121.468870224  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496121.468877119  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496121.468877535  DEV_SLEEP_TIME                 dev:8 wake:000496121.514204544
+    496121.468878934  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496121.469065043  WAKE                           num_fds:1
+    496121.469101332  FILL_AUDIO                     dev:8 hw_level:2176
+    496121.469104214  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.469105782  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.469110273  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.469110984  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.469112643  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496121.469113906  STREAM_SLEEP_TIME              id:140000 wake:000496121.490075886
+    496121.469119609  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496121.469120035  DEV_SLEEP_TIME                 dev:8 wake:000496121.535780076
+    496121.469121338  SLEEP                          sleep:000000000.020962476 longest_wake:000158140
+    496121.490134710  WAKE                           num_fds:0
+    496121.490157758  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496121.490176612  FILL_AUDIO                     dev:8 hw_level:2192
+    496121.490180090  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.490180455  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.490181328  WRITE_STREAMS_MIXED            write_limit:0
+    496121.490183939  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496121.490191015  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496121.490191441  DEV_SLEEP_TIME                 dev:8 wake:000496121.535851602
+    496121.490192819  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496121.490344027  WAKE                           num_fds:1
+    496121.490368824  FILL_AUDIO                     dev:8 hw_level:2192
+    496121.490369947  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.490371526  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.490375760  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.490376372  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.490377103  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496121.490378071  STREAM_SLEEP_TIME              id:140000 wake:000496121.511409219
+    496121.490383679  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496121.490384044  DEV_SLEEP_TIME                 dev:8 wake:000496121.557377630
+    496121.490385037  SLEEP                          sleep:000000000.021031589 longest_wake:000158140
+    496121.511477967  WAKE                           num_fds:0
+    496121.511498976  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496121.511518240  FILL_AUDIO                     dev:8 hw_level:2208
+    496121.511521848  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.511522214  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.511523071  WRITE_STREAMS_MIXED            write_limit:0
+    496121.511525662  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496121.511532739  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496121.511533159  DEV_SLEEP_TIME                 dev:8 wake:000496121.557526780
+    496121.511534738  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496121.511709978  WAKE                           num_fds:1
+    496121.511744327  FILL_AUDIO                     dev:8 hw_level:2208
+    496121.511746938  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.511748527  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.511752872  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.511753583  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.511755062  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496121.511756370  STREAM_SLEEP_TIME              id:140000 wake:000496121.532742552
+    496121.511762093  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496121.511762519  DEV_SLEEP_TIME                 dev:8 wake:000496121.579089151
+    496121.511763777  SLEEP                          sleep:000000000.020986734 longest_wake:000158140
+    496121.532787267  WAKE                           num_fds:0
+    496121.532806276  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2224
+    496121.532828853  FILL_AUDIO                     dev:8 hw_level:2176
+    496121.532832171  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.532832546  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.532833393  WRITE_STREAMS_MIXED            write_limit:0
+    496121.532835618  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496121.532842209  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496121.532842640  DEV_SLEEP_TIME                 dev:8 wake:000496121.578169939
+    496121.532844013  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496121.533004248  WAKE                           num_fds:1
+    496121.533031992  FILL_AUDIO                     dev:8 hw_level:2176
+    496121.533033139  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.533034768  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.533039339  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.533039885  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.533040546  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496121.533041479  STREAM_SLEEP_TIME              id:140000 wake:000496121.554075885
+    496121.533046991  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496121.533047332  DEV_SLEEP_TIME                 dev:8 wake:000496121.599707689
+    496121.533048249  SLEEP                          sleep:000000000.021034862 longest_wake:000158140
+    496121.554149766  WAKE                           num_fds:0
+    496121.554166364  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496121.554188087  FILL_AUDIO                     dev:8 hw_level:2192
+    496121.554190863  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.554191054  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.554191551  WRITE_STREAMS_MIXED            write_limit:0
+    496121.554193238  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496121.554198768  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496121.554198995  DEV_SLEEP_TIME                 dev:8 wake:000496121.599860491
+    496121.554199801  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496121.554306442  WAKE                           num_fds:1
+    496121.554337642  FILL_AUDIO                     dev:8 hw_level:2192
+    496121.554339165  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.554340148  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.554342551  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.554342938  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.554343832  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496121.554344526  STREAM_SLEEP_TIME              id:140000 wake:000496121.575409218
+    496121.554349427  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496121.554349648  DEV_SLEEP_TIME                 dev:8 wake:000496121.621344250
+    496121.554350349  SLEEP                          sleep:000000000.021064968 longest_wake:000158140
+    496121.575454337  WAKE                           num_fds:0
+    496121.575494936  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496121.575524409  FILL_AUDIO                     dev:8 hw_level:2208
+    496121.575533510  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.575533881  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.575535084  WRITE_STREAMS_MIXED            write_limit:0
+    496121.575542005  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496121.575550880  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496121.575551301  DEV_SLEEP_TIME                 dev:8 wake:000496121.621544054
+    496121.575553642  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496121.575716452  WAKE                           num_fds:1
+    496121.575751844  FILL_AUDIO                     dev:8 hw_level:2208
+    496121.575754250  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.575758309  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.575767365  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.575768162  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.575770342  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496121.575771555  STREAM_SLEEP_TIME              id:140000 wake:000496121.596742551
+    496121.575778536  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496121.575778881  DEV_SLEEP_TIME                 dev:8 wake:000496121.643104391
+    496121.575779819  SLEEP                          sleep:000000000.020971493 longest_wake:000158140
+    496121.596772698  WAKE                           num_fds:0
+    496121.596791942  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2224
+    496121.596812365  FILL_AUDIO                     dev:8 hw_level:2224
+    496121.596816053  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.596816449  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.596817366  WRITE_STREAMS_MIXED            write_limit:0
+    496121.596819742  FILL_AUDIO_DONE                hw_level:2224 total_written:0 min_cb_level:1024
+    496121.596827289  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496121.596827710  DEV_SLEEP_TIME                 dev:8 wake:000496121.642154277
+    496121.596829168  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496121.596956217  WAKE                           num_fds:1
+    496121.596983470  FILL_AUDIO                     dev:8 hw_level:2176
+    496121.596984933  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.596986747  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.596992150  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.596992816  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.596993984  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496121.596995237  STREAM_SLEEP_TIME              id:140000 wake:000496121.618075884
+    496121.597001296  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496121.597001767  DEV_SLEEP_TIME                 dev:8 wake:000496121.663661412
+    496121.597003526  SLEEP                          sleep:000000000.021081138 longest_wake:000158140
+    496121.618124193  WAKE                           num_fds:0
+    496121.618142226  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496121.618162091  FILL_AUDIO                     dev:8 hw_level:2192
+    496121.618164259  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.618164430  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.618164887  WRITE_STREAMS_MIXED            write_limit:0
+    496121.618166527  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496121.618172515  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496121.618172712  DEV_SLEEP_TIME                 dev:8 wake:000496121.663833716
+    496121.618173602  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496121.618250086  WAKE                           num_fds:1
+    496121.618275409  FILL_AUDIO                     dev:8 hw_level:2192
+    496121.618277345  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.618278382  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.618282712  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.618283080  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.618284169  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496121.618284939  STREAM_SLEEP_TIME              id:140000 wake:000496121.639409217
+    496121.618290559  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496121.618290797  DEV_SLEEP_TIME                 dev:8 wake:000496121.685284641
+    496121.618291691  SLEEP                          sleep:000000000.021124576 longest_wake:000158140
+    496121.639440385  WAKE                           num_fds:0
+    496121.639455997  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496121.639474693  FILL_AUDIO                     dev:8 hw_level:2208
+    496121.639476921  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.639477163  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.639477713  WRITE_STREAMS_MIXED            write_limit:0
+    496121.639479088  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496121.639484385  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496121.639484585  DEV_SLEEP_TIME                 dev:8 wake:000496121.685479618
+    496121.639485314  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496121.639571021  WAKE                           num_fds:1
+    496121.639588187  FILL_AUDIO                     dev:8 hw_level:2208
+    496121.639588853  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.639589796  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.639591916  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.639592176  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.639592524  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496121.639592994  STREAM_SLEEP_TIME              id:140000 wake:000496121.660742550
+    496121.639597798  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496121.639598010  DEV_SLEEP_TIME                 dev:8 wake:000496121.706926113
+    496121.639598591  SLEEP                          sleep:000000000.021149770 longest_wake:000158140
+    496121.660777354  WAKE                           num_fds:0
+    496121.660803529  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2224
+    496121.660831248  FILL_AUDIO                     dev:8 hw_level:2176
+    496121.660837232  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.660837603  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.660838725  WRITE_STREAMS_MIXED            write_limit:0
+    496121.660842188  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496121.660849174  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496121.660849605  DEV_SLEEP_TIME                 dev:8 wake:000496121.706176328
+    496121.660851059  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496121.660972434  WAKE                           num_fds:1
+    496121.660998349  FILL_AUDIO                     dev:8 hw_level:2176
+    496121.661000228  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.661001912  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.661007961  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.661008622  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.661009860  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496121.661011399  STREAM_SLEEP_TIME              id:140000 wake:000496121.682075883
+    496121.661017543  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496121.661017944  DEV_SLEEP_TIME                 dev:8 wake:000496121.727677223
+    496121.661018966  SLEEP                          sleep:000000000.021065326 longest_wake:000158140
+    496121.682107792  WAKE                           num_fds:0
+    496121.682128911  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496121.682148682  FILL_AUDIO                     dev:8 hw_level:2192
+    496121.682152465  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.682152846  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.682153863  WRITE_STREAMS_MIXED            write_limit:0
+    496121.682156525  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496121.682164117  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496121.682164533  DEV_SLEEP_TIME                 dev:8 wake:000496121.727824328
+    496121.682166262  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496121.682264925  WAKE                           num_fds:1
+    496121.682287552  FILL_AUDIO                     dev:8 hw_level:2192
+    496121.682289416  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.682291381  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.682297630  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.682298352  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.682299835  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496121.682301093  STREAM_SLEEP_TIME              id:140000 wake:000496121.703409216
+    496121.682306160  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496121.682306676  DEV_SLEEP_TIME                 dev:8 wake:000496121.749300442
+    496121.682308029  SLEEP                          sleep:000000000.021108774 longest_wake:000158140
+    496121.703497071  WAKE                           num_fds:0
+    496121.703520750  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496121.703547256  FILL_AUDIO                     dev:8 hw_level:2208
+    496121.703551261  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.703551837  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.703552945  WRITE_STREAMS_MIXED            write_limit:0
+    496121.703555796  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496121.703563900  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496121.703564391  DEV_SLEEP_TIME                 dev:8 wake:000496121.749557079
+    496121.703566491  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496121.703744086  WAKE                           num_fds:1
+    496121.703780810  FILL_AUDIO                     dev:8 hw_level:2208
+    496121.703783762  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.703785371  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.703791515  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.703792207  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.703793886  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496121.703795229  STREAM_SLEEP_TIME              id:140000 wake:000496121.724742549
+    496121.703801448  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496121.703801869  DEV_SLEEP_TIME                 dev:8 wake:000496121.771128005
+    496121.703803137  SLEEP                          sleep:000000000.020947877 longest_wake:000158140
+    496121.724787767  WAKE                           num_fds:0
+    496121.724808766  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2224
+    496121.724832015  FILL_AUDIO                     dev:8 hw_level:2176
+    496121.724835688  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.724836099  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.724837081  WRITE_STREAMS_MIXED            write_limit:0
+    496121.724839888  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496121.724847761  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496121.724848197  DEV_SLEEP_TIME                 dev:8 wake:000496121.770174283
+    496121.724849776  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496121.724991743  WAKE                           num_fds:1
+    496121.725028934  FILL_AUDIO                     dev:8 hw_level:2176
+    496121.725031786  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.725033399  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.725039383  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.725040095  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.725041663  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496121.725042986  STREAM_SLEEP_TIME              id:140000 wake:000496121.746075882
+    496121.725049030  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496121.725049456  DEV_SLEEP_TIME                 dev:8 wake:000496121.791709096
+    496121.725050830  SLEEP                          sleep:000000000.021033452 longest_wake:000158140
+    496121.746183507  WAKE                           num_fds:0
+    496121.746206440  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496121.746236283  FILL_AUDIO                     dev:8 hw_level:2192
+    496121.746239977  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.746240348  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.746241220  WRITE_STREAMS_MIXED            write_limit:0
+    496121.746243841  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496121.746250787  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496121.746251208  DEV_SLEEP_TIME                 dev:8 wake:000496121.791911514
+    496121.746252752  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496121.746440178  WAKE                           num_fds:1
+    496121.746474552  FILL_AUDIO                     dev:8 hw_level:2192
+    496121.746477209  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.746478887  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.746484646  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.746485362  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.746487167  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496121.746488465  STREAM_SLEEP_TIME              id:140000 wake:000496121.767409215
+    496121.746494258  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496121.746494684  DEV_SLEEP_TIME                 dev:8 wake:000496121.813487968
+    496121.746495987  SLEEP                          sleep:000000000.020921247 longest_wake:000158140
+    496121.767517668  WAKE                           num_fds:0
+    496121.767540631  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496121.767570465  FILL_AUDIO                     dev:8 hw_level:2208
+    496121.767574359  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.767574730  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.767575607  WRITE_STREAMS_MIXED            write_limit:0
+    496121.767578077  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496121.767584953  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496121.767585374  DEV_SLEEP_TIME                 dev:8 wake:000496121.813579080
+    496121.767586833  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496121.767774586  WAKE                           num_fds:1
+    496121.767808720  FILL_AUDIO                     dev:8 hw_level:2208
+    496121.767811737  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.767813270  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.767819550  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.767820261  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.767821965  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496121.767823248  STREAM_SLEEP_TIME              id:140000 wake:000496121.788742548
+    496121.767829498  SET_DEV_WAKE                   dev:8 hw_level:3184 sleep:3184
+    496121.767829924  DEV_SLEEP_TIME                 dev:8 wake:000496121.834156085
+    496121.767831212  SLEEP                          sleep:000000000.020919796 longest_wake:000158140
+    496121.788852201  WAKE                           num_fds:0
+    496121.788875299  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2176
+    496121.788905694  FILL_AUDIO                     dev:8 hw_level:2176
+    496121.788909388  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.788909764  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.788910641  WRITE_STREAMS_MIXED            write_limit:0
+    496121.788913157  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496121.788919962  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496121.788920378  DEV_SLEEP_TIME                 dev:8 wake:000496121.834247487
+    496121.788921847  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496121.789105962  WAKE                           num_fds:1
+    496121.789141990  FILL_AUDIO                     dev:8 hw_level:2176
+    496121.789144821  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.789146320  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.789152188  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.789152905  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.789154589  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496121.789155837  STREAM_SLEEP_TIME              id:140000 wake:000496121.810075881
+    496121.789161580  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496121.789162031  DEV_SLEEP_TIME                 dev:8 wake:000496121.855822012
+    496121.789163364  SLEEP                          sleep:000000000.020920535 longest_wake:000158140
+    496121.810105924  WAKE                           num_fds:0
+    496121.810127257  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496121.810146862  FILL_AUDIO                     dev:8 hw_level:2192
+    496121.810150015  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.810150306  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.810151004  WRITE_STREAMS_MIXED            write_limit:0
+    496121.810153450  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496121.810160382  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496121.810160763  DEV_SLEEP_TIME                 dev:8 wake:000496121.855821025
+    496121.810162046  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496121.810236321  WAKE                           num_fds:1
+    496121.810255739  FILL_AUDIO                     dev:8 hw_level:2192
+    496121.810256712  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.810257947  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.810261439  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.810261907  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.810262882  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496121.810263721  STREAM_SLEEP_TIME              id:140000 wake:000496121.831409214
+    496121.810269822  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496121.810270232  DEV_SLEEP_TIME                 dev:8 wake:000496121.877263347
+    496121.810271515  SLEEP                          sleep:000000000.021145867 longest_wake:000158140
+    496121.831434429  WAKE                           num_fds:0
+    496121.831453119  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496121.831472022  FILL_AUDIO                     dev:8 hw_level:2208
+    496121.831475096  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.831475410  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.831476122  WRITE_STREAMS_MIXED            write_limit:0
+    496121.831478086  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496121.831484565  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496121.831484852  DEV_SLEEP_TIME                 dev:8 wake:000496121.877478858
+    496121.831486095  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496121.831591475  WAKE                           num_fds:1
+    496121.831622506  FILL_AUDIO                     dev:8 hw_level:2208
+    496121.831624705  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.831626162  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.831630221  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.831630832  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.831632757  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496121.831633906  STREAM_SLEEP_TIME              id:140000 wake:000496121.852742547
+    496121.831640127  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496121.831640498  DEV_SLEEP_TIME                 dev:8 wake:000496121.898966785
+    496121.831641684  SLEEP                          sleep:000000000.021109095 longest_wake:000158140
+    496121.852996684  WAKE                           num_fds:0
+    496121.853020685  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2176
+    496121.853051536  FILL_AUDIO                     dev:8 hw_level:2176
+    496121.853055385  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.853055745  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.853056617  WRITE_STREAMS_MIXED            write_limit:0
+    496121.853059665  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496121.853066580  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496121.853067001  DEV_SLEEP_TIME                 dev:8 wake:000496121.898393980
+    496121.853068390  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496121.853258724  WAKE                           num_fds:1
+    496121.853296812  FILL_AUDIO                     dev:8 hw_level:2176
+    496121.853300455  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.853302259  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.853307968  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.853308880  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.853311250  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496121.853312864  STREAM_SLEEP_TIME              id:140000 wake:000496121.874075880
+    496121.853319424  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496121.853319925  DEV_SLEEP_TIME                 dev:8 wake:000496121.919978883
+    496121.853321699  SLEEP                          sleep:000000000.020763663 longest_wake:000158140
+    496121.874121581  WAKE                           num_fds:0
+    496121.874137484  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496121.874154658  FILL_AUDIO                     dev:8 hw_level:2192
+    496121.874157144  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.874157263  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.874157596  WRITE_STREAMS_MIXED            write_limit:0
+    496121.874159259  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496121.874164831  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496121.874164963  DEV_SLEEP_TIME                 dev:8 wake:000496121.919826370
+    496121.874165593  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496121.874230568  WAKE                           num_fds:1
+    496121.874249311  FILL_AUDIO                     dev:8 hw_level:2192
+    496121.874250329  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.874251172  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.874253846  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.874254168  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.874254879  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496121.874255437  STREAM_SLEEP_TIME              id:140000 wake:000496121.895409213
+    496121.874260087  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496121.874260219  DEV_SLEEP_TIME                 dev:8 wake:000496121.941255230
+    496121.874260704  SLEEP                          sleep:000000000.021153983 longest_wake:000158140
+    496121.895621418  WAKE                           num_fds:0
+    496121.895637180  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496121.895653809  FILL_AUDIO                     dev:8 hw_level:2208
+    496121.895655732  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.895655866  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.895656225  WRITE_STREAMS_MIXED            write_limit:0
+    496121.895657665  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496121.895662759  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496121.895662914  DEV_SLEEP_TIME                 dev:8 wake:000496121.941658210
+    496121.895663513  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496121.895745235  WAKE                           num_fds:1
+    496121.895762578  FILL_AUDIO                     dev:8 hw_level:2208
+    496121.895763511  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.895764286  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.895767203  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.895767568  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.895768193  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496121.895768676  STREAM_SLEEP_TIME              id:140000 wake:000496121.916742546
+    496121.895773163  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496121.895773318  DEV_SLEEP_TIME                 dev:8 wake:000496121.963101805
+    496121.895773786  SLEEP                          sleep:000000000.020974074 longest_wake:000158140
+    496121.916816246  WAKE                           num_fds:0
+    496121.916829844  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2176
+    496121.916847665  FILL_AUDIO                     dev:8 hw_level:2176
+    496121.916849561  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.916849697  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.916850045  WRITE_STREAMS_MIXED            write_limit:0
+    496121.916851345  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496121.916856369  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496121.916856526  DEV_SLEEP_TIME                 dev:8 wake:000496121.962185175
+    496121.916857127  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496121.916936358  WAKE                           num_fds:1
+    496121.916953777  FILL_AUDIO                     dev:8 hw_level:2176
+    496121.916954713  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.916955421  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.916957165  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.916957412  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.916957966  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496121.916958446  STREAM_SLEEP_TIME              id:140000 wake:000496121.938075879
+    496121.916962996  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496121.916963151  DEV_SLEEP_TIME                 dev:8 wake:000496121.983624913
+    496121.916963634  SLEEP                          sleep:000000000.021117632 longest_wake:000158140
+    496121.938161250  WAKE                           num_fds:0
+    496121.938185050  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496121.938214142  FILL_AUDIO                     dev:8 hw_level:2192
+    496121.938217785  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.938218156  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.938219033  WRITE_STREAMS_MIXED            write_limit:0
+    496121.938221504  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496121.938228400  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496121.938228816  DEV_SLEEP_TIME                 dev:8 wake:000496121.983889147
+    496121.938230174  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496121.938435592  WAKE                           num_fds:1
+    496121.938472612  FILL_AUDIO                     dev:8 hw_level:2192
+    496121.938476607  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.938478346  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.938484390  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.938485106  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.938486865  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496121.938488118  STREAM_SLEEP_TIME              id:140000 wake:000496121.959409212
+    496121.938493766  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496121.938494132  DEV_SLEEP_TIME                 dev:8 wake:000496122.005487627
+    496121.938495390  SLEEP                          sleep:000000000.020921585 longest_wake:000158140
+    496121.959493040  WAKE                           num_fds:0
+    496121.959515848  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496121.959537678  FILL_AUDIO                     dev:8 hw_level:2208
+    496121.959541187  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.959541547  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.959542444  WRITE_STREAMS_MIXED            write_limit:0
+    496121.959544775  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496121.959551766  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496121.959552132  DEV_SLEEP_TIME                 dev:8 wake:000496122.005545807
+    496121.959553570  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496121.959722871  WAKE                           num_fds:1
+    496121.959757015  FILL_AUDIO                     dev:8 hw_level:2208
+    496121.959759616  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.959761390  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.959765625  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.959766331  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.959767920  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496121.959769213  STREAM_SLEEP_TIME              id:140000 wake:000496121.980742545
+    496121.959775262  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496121.959775628  DEV_SLEEP_TIME                 dev:8 wake:000496122.027101990
+    496121.959776906  SLEEP                          sleep:000000000.020973888 longest_wake:000158140
+    496121.980846407  WAKE                           num_fds:0
+    496121.980870472  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2176
+    496121.980900422  FILL_AUDIO                     dev:8 hw_level:2176
+    496121.980904105  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496121.980904476  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496121.980905353  WRITE_STREAMS_MIXED            write_limit:0
+    496121.980907799  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496121.980914860  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496121.980915226  DEV_SLEEP_TIME                 dev:8 wake:000496122.026242354
+    496121.980916564  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496121.981102759  WAKE                           num_fds:1
+    496121.981138190  FILL_AUDIO                     dev:8 hw_level:2176
+    496121.981141027  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496121.981142591  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496121.981148414  DEV_STREAM_MIX                 written:1024 read:1024
+    496121.981149101  WRITE_STREAMS_MIXED            write_limit:1024
+    496121.981150754  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496121.981152037  STREAM_SLEEP_TIME              id:140000 wake:000496122.002075878
+    496121.981157881  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496121.981158252  DEV_SLEEP_TIME                 dev:8 wake:000496122.047818162
+    496121.981159515  SLEEP                          sleep:000000000.020924382 longest_wake:000158140
+    496122.002337641  WAKE                           num_fds:0
+    496122.002361576  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496122.002391655  FILL_AUDIO                     dev:8 hw_level:2192
+    496122.002395404  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496122.002395775  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496122.002396652  WRITE_STREAMS_MIXED            write_limit:0
+    496122.002399092  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496122.002405908  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496122.002406324  DEV_SLEEP_TIME                 dev:8 wake:000496122.048066736
+    496122.002407727  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496122.002594263  WAKE                           num_fds:1
+    496122.002630126  FILL_AUDIO                     dev:8 hw_level:2192
+    496122.002632972  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496122.002634551  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496122.002640490  DEV_STREAM_MIX                 written:1024 read:1024
+    496122.002641201  WRITE_STREAMS_MIXED            write_limit:1024
+    496122.002642775  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496122.002644058  STREAM_SLEEP_TIME              id:140000 wake:000496122.023409211
+    496122.002649826  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496122.002650252  DEV_SLEEP_TIME                 dev:8 wake:000496122.069643567
+    496122.002651565  SLEEP                          sleep:000000000.020765644 longest_wake:000158140
+    496122.023443827  WAKE                           num_fds:0
+    496122.023465787  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496122.023484866  FILL_AUDIO                     dev:8 hw_level:2208
+    496122.023488024  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496122.023488409  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496122.023489432  WRITE_STREAMS_MIXED            write_limit:0
+    496122.023491757  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496122.023499440  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496122.023499856  DEV_SLEEP_TIME                 dev:8 wake:000496122.069493326
+    496122.023501389  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496122.023673351  WAKE                           num_fds:1
+    496122.023707245  FILL_AUDIO                     dev:8 hw_level:2208
+    496122.023709846  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496122.023711369  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496122.023715639  DEV_STREAM_MIX                 written:1024 read:1024
+    496122.023716356  WRITE_STREAMS_MIXED            write_limit:1024
+    496122.023717934  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496122.023719237  STREAM_SLEEP_TIME              id:140000 wake:000496122.044742544
+    496122.023724981  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496122.023725401  DEV_SLEEP_TIME                 dev:8 wake:000496122.091052019
+    496122.023726710  SLEEP                          sleep:000000000.021023858 longest_wake:000158140
+    496122.044805628  WAKE                           num_fds:0
+    496122.044828160  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2176
+    496122.044852285  FILL_AUDIO                     dev:8 hw_level:2176
+    496122.044855738  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496122.044856109  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496122.044857096  WRITE_STREAMS_MIXED            write_limit:0
+    496122.044859537  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496122.044866443  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496122.044866884  DEV_SLEEP_TIME                 dev:8 wake:000496122.090193902
+    496122.044868277  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496122.045010320  WAKE                           num_fds:1
+    496122.045043877  FILL_AUDIO                     dev:8 hw_level:2176
+    496122.045046408  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496122.045048012  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496122.045054056  DEV_STREAM_MIX                 written:1024 read:1024
+    496122.045054773  WRITE_STREAMS_MIXED            write_limit:1024
+    496122.045056376  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496122.045057684  STREAM_SLEEP_TIME              id:140000 wake:000496122.066075877
+    496122.045063523  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496122.045063944  DEV_SLEEP_TIME                 dev:8 wake:000496122.111723799
+    496122.045065247  SLEEP                          sleep:000000000.021018744 longest_wake:000158140
+    496122.066136582  WAKE                           num_fds:0
+    496122.066157034  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496122.066179426  FILL_AUDIO                     dev:8 hw_level:2192
+    496122.066183501  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496122.066183871  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496122.066184819  WRITE_STREAMS_MIXED            write_limit:0
+    496122.066187029  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496122.066193809  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496122.066194230  DEV_SLEEP_TIME                 dev:8 wake:000496122.111854797
+    496122.066195614  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496122.066335856  WAKE                           num_fds:1
+    496122.066358799  FILL_AUDIO                     dev:8 hw_level:2192
+    496122.066359997  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496122.066361581  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496122.066365510  DEV_STREAM_MIX                 written:1024 read:1024
+    496122.066366071  WRITE_STREAMS_MIXED            write_limit:1024
+    496122.066366697  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496122.066367615  STREAM_SLEEP_TIME              id:140000 wake:000496122.087409210
+    496122.066372967  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496122.066373363  DEV_SLEEP_TIME                 dev:8 wake:000496122.133367189
+    496122.066374330  SLEEP                          sleep:000000000.021042021 longest_wake:000158140
+    496122.087471671  WAKE                           num_fds:0
+    496122.087493561  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496122.087517421  FILL_AUDIO                     dev:8 hw_level:2208
+    496122.087520754  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496122.087521140  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496122.087521987  WRITE_STREAMS_MIXED            write_limit:0
+    496122.087524713  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496122.087531669  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496122.087532090  DEV_SLEEP_TIME                 dev:8 wake:000496122.133525760
+    496122.087533503  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496122.087705320  WAKE                           num_fds:1
+    496122.087739699  FILL_AUDIO                     dev:8 hw_level:2208
+    496122.087742245  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496122.087743899  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496122.087748465  DEV_STREAM_MIX                 written:1024 read:1024
+    496122.087749151  WRITE_STREAMS_MIXED            write_limit:1024
+    496122.087750710  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496122.087752033  STREAM_SLEEP_TIME              id:140000 wake:000496122.108742543
+    496122.087757781  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496122.087758202  DEV_SLEEP_TIME                 dev:8 wake:000496122.155084819
+    496122.087759490  SLEEP                          sleep:000000000.020991057 longest_wake:000158140
+    496122.108799368  WAKE                           num_fds:0
+    496122.108822551  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2224
+    496122.108869730  FILL_AUDIO                     dev:8 hw_level:2176
+    496122.108877628  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496122.108877999  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496122.108881858  WRITE_STREAMS_MIXED            write_limit:0
+    496122.108886479  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496122.108895164  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496122.108895585  DEV_SLEEP_TIME                 dev:8 wake:000496122.154221646
+    496122.108897805  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496122.109059453  WAKE                           num_fds:1
+    496122.109096789  FILL_AUDIO                     dev:8 hw_level:2176
+    496122.109100518  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496122.109105585  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496122.109113262  DEV_STREAM_MIX                 written:1024 read:1024
+    496122.109114079  WRITE_STREAMS_MIXED            write_limit:1024
+    496122.109118204  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496122.109119286  STREAM_SLEEP_TIME              id:140000 wake:000496122.130075876
+    496122.109126252  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496122.109126653  DEV_SLEEP_TIME                 dev:8 wake:000496122.175785456
+    496122.109127856  SLEEP                          sleep:000000000.020957086 longest_wake:000158140
+    496122.130119813  WAKE                           num_fds:0
+    496122.130140075  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496122.130163088  FILL_AUDIO                     dev:8 hw_level:2192
+    496122.130166421  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496122.130166792  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496122.130167639  WRITE_STREAMS_MIXED            write_limit:0
+    496122.130170480  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496122.130177431  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496122.130177852  DEV_SLEEP_TIME                 dev:8 wake:000496122.175838254
+    496122.130179326  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496122.130282258  WAKE                           num_fds:1
+    496122.130303026  FILL_AUDIO                     dev:8 hw_level:2192
+    496122.130304109  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496122.130305512  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496122.130311125  DEV_STREAM_MIX                 written:1024 read:1024
+    496122.130311711  WRITE_STREAMS_MIXED            write_limit:1024
+    496122.130312353  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496122.130313300  STREAM_SLEEP_TIME              id:140000 wake:000496122.151409209
+    496122.130318647  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496122.130319053  DEV_SLEEP_TIME                 dev:8 wake:000496122.197312804
+    496122.130320116  SLEEP                          sleep:000000000.021096405 longest_wake:000158140
+    496122.151491150  WAKE                           num_fds:0
+    496122.151513712  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496122.151539091  FILL_AUDIO                     dev:8 hw_level:2208
+    496122.151542780  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496122.151543150  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496122.151544022  WRITE_STREAMS_MIXED            write_limit:0
+    496122.151546684  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496122.151553564  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496122.151553985  DEV_SLEEP_TIME                 dev:8 wake:000496122.197547651
+    496122.151555409  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496122.151727611  WAKE                           num_fds:1
+    496122.151761519  FILL_AUDIO                     dev:8 hw_level:2208
+    496122.151764040  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496122.151765614  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496122.151770169  DEV_STREAM_MIX                 written:1024 read:1024
+    496122.151770876  WRITE_STREAMS_MIXED            write_limit:1024
+    496122.151772395  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496122.151773642  STREAM_SLEEP_TIME              id:140000 wake:000496122.172742542
+    496122.151779361  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496122.151779782  DEV_SLEEP_TIME                 dev:8 wake:000496122.219106479
+    496122.151781085  SLEEP                          sleep:000000000.020969396 longest_wake:000158140
+    496122.172792031  WAKE                           num_fds:0
+    496122.172815340  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2224
+    496122.172845539  FILL_AUDIO                     dev:8 hw_level:2176
+    496122.172849508  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496122.172849889  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496122.172850761  WRITE_STREAMS_MIXED            write_limit:0
+    496122.172853217  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496122.172860223  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496122.172860644  DEV_SLEEP_TIME                 dev:8 wake:000496122.218187577
+    496122.172862143  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496122.173005714  WAKE                           num_fds:1
+    496122.173028998  FILL_AUDIO                     dev:8 hw_level:2176
+    496122.173029985  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496122.173031554  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496122.173037182  DEV_STREAM_MIX                 written:1024 read:1024
+    496122.173037808  WRITE_STREAMS_MIXED            write_limit:1024
+    496122.173038429  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496122.173039507  STREAM_SLEEP_TIME              id:140000 wake:000496122.194075875
+    496122.173045130  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496122.173045476  DEV_SLEEP_TIME                 dev:8 wake:000496122.239705692
+    496122.173046378  SLEEP                          sleep:000000000.021036849 longest_wake:000158140
+    496122.194116034  WAKE                           num_fds:0
+    496122.194140887  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496122.194162928  FILL_AUDIO                     dev:8 hw_level:2192
+    496122.194167077  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496122.194167443  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496122.194168390  WRITE_STREAMS_MIXED            write_limit:0
+    496122.194171227  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496122.194178529  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496122.194178980  DEV_SLEEP_TIME                 dev:8 wake:000496122.239839035
+    496122.194181155  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496122.194266111  WAKE                           num_fds:1
+    496122.194291179  FILL_AUDIO                     dev:8 hw_level:2192
+    496122.194292136  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496122.194294035  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496122.194300290  DEV_STREAM_MIX                 written:1024 read:1024
+    496122.194300896  WRITE_STREAMS_MIXED            write_limit:1024
+    496122.194301873  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496122.194302780  STREAM_SLEEP_TIME              id:140000 wake:000496122.215409208
+    496122.194309255  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496122.194310062  DEV_SLEEP_TIME                 dev:8 wake:000496122.261302294
+    496122.194311440  SLEEP                          sleep:000000000.021106914 longest_wake:000158140
+    496122.215448777  WAKE                           num_fds:0
+    496122.215463923  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496122.215476895  FILL_AUDIO                     dev:8 hw_level:2208
+    496122.215478751  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496122.215478927  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496122.215479448  WRITE_STREAMS_MIXED            write_limit:0
+    496122.215480831  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496122.215486357  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496122.215486562  DEV_SLEEP_TIME                 dev:8 wake:000496122.261481539
+    496122.215487384  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496122.215549002  WAKE                           num_fds:1
+    496122.215563098  FILL_AUDIO                     dev:8 hw_level:2208
+    496122.215563611  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496122.215565341  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496122.215567658  DEV_STREAM_MIX                 written:1024 read:1024
+    496122.215567938  WRITE_STREAMS_MIXED            write_limit:1024
+    496122.215568239  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496122.215568747  STREAM_SLEEP_TIME              id:140000 wake:000496122.236742541
+    496122.215573511  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496122.215573716  DEV_SLEEP_TIME                 dev:8 wake:000496122.282901820
+    496122.215574324  SLEEP                          sleep:000000000.021174054 longest_wake:000158140
+    496122.236813718  WAKE                           num_fds:0
+    496122.236830443  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2176
+    496122.236848304  FILL_AUDIO                     dev:8 hw_level:2176
+    496122.236850592  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496122.236850766  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496122.236851211  WRITE_STREAMS_MIXED            write_limit:0
+    496122.236853866  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496122.236859339  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496122.236859539  DEV_SLEEP_TIME                 dev:8 wake:000496122.282187883
+    496122.236860303  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496122.236957758  WAKE                           num_fds:1
+    496122.236979930  FILL_AUDIO                     dev:8 hw_level:2176
+    496122.236981687  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496122.236982827  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496122.236987199  DEV_STREAM_MIX                 written:1024 read:1024
+    496122.236987605  WRITE_STREAMS_MIXED            write_limit:1024
+    496122.236988630  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496122.236989373  STREAM_SLEEP_TIME              id:140000 wake:000496122.258075874
+    496122.236994623  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496122.236994884  DEV_SLEEP_TIME                 dev:8 wake:000496122.303655730
+    496122.236995656  SLEEP                          sleep:000000000.021086810 longest_wake:000158140
+    496122.258105114  WAKE                           num_fds:0
+    496122.258126969  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496122.258149697  FILL_AUDIO                     dev:8 hw_level:2192
+    496122.258153501  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496122.258153867  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496122.258154713  WRITE_STREAMS_MIXED            write_limit:0
+    496122.258157274  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496122.258164351  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496122.258164817  DEV_SLEEP_TIME                 dev:8 wake:000496122.303825028
+    496122.258166230  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496122.258305507  WAKE                           num_fds:1
+    496122.258341639  FILL_AUDIO                     dev:8 hw_level:2192
+    496122.258343749  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496122.258345653  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496122.258351321  DEV_STREAM_MIX                 written:1024 read:1024
+    496122.258352123  WRITE_STREAMS_MIXED            write_limit:1024
+    496122.258353306  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496122.258354649  STREAM_SLEEP_TIME              id:140000 wake:000496122.279409207
+    496122.258361405  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496122.258361921  DEV_SLEEP_TIME                 dev:8 wake:000496122.325354028
+    496122.258363414  SLEEP                          sleep:000000000.021055179 longest_wake:000158140
+    496122.279475995  WAKE                           num_fds:0
+    496122.279498647  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496122.279522287  FILL_AUDIO                     dev:8 hw_level:2208
+    496122.279525795  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496122.279526166  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496122.279527153  WRITE_STREAMS_MIXED            write_limit:0
+    496122.279529684  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496122.279536585  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496122.279537021  DEV_SLEEP_TIME                 dev:8 wake:000496122.325530666
+    496122.279538424  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496122.279679094  WAKE                           num_fds:1
+    496122.279712721  FILL_AUDIO                     dev:8 hw_level:2208
+    496122.279715312  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496122.279717558  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496122.279723471  DEV_STREAM_MIX                 written:1024 read:1024
+    496122.279724183  WRITE_STREAMS_MIXED            write_limit:1024
+    496122.279725726  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496122.279727069  STREAM_SLEEP_TIME              id:140000 wake:000496122.300742540
+    496122.279732808  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496122.279733229  DEV_SLEEP_TIME                 dev:8 wake:000496122.347059836
+    496122.279734562  SLEEP                          sleep:000000000.021016037 longest_wake:000158140
+    496122.300998522  WAKE                           num_fds:0
+    496122.301022512  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2176
+    496122.301051805  FILL_AUDIO                     dev:8 hw_level:2176
+    496122.301055604  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496122.301055969  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496122.301056836  WRITE_STREAMS_MIXED            write_limit:0
+    496122.301059287  FILL_AUDIO_DONE                hw_level:2176 total_written:0 min_cb_level:1024
+    496122.301066183  SET_DEV_WAKE                   dev:8 hw_level:2176 sleep:2176
+    496122.301066619  DEV_SLEEP_TIME                 dev:8 wake:000496122.346393648
+    496122.301068007  SLEEP                          sleep:000000000.045333333 longest_wake:000158140
+    496122.301254372  WAKE                           num_fds:1
+    496122.301290025  FILL_AUDIO                     dev:8 hw_level:2176
+    496122.301292946  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496122.301294495  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496122.301300178  DEV_STREAM_MIX                 written:1024 read:1024
+    496122.301300885  WRITE_STREAMS_MIXED            write_limit:1024
+    496122.301302534  FILL_AUDIO_DONE                hw_level:2176 total_written:1024 min_cb_level:1024
+    496122.301303781  STREAM_SLEEP_TIME              id:140000 wake:000496122.322075873
+    496122.301309565  SET_DEV_WAKE                   dev:8 hw_level:3200 sleep:3200
+    496122.301309981  DEV_SLEEP_TIME                 dev:8 wake:000496122.367969956
+    496122.301311284  SLEEP                          sleep:000000000.020772583 longest_wake:000158140
+    496122.322353591  WAKE                           num_fds:0
+    496122.322376259  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2192
+    496122.322417509  FILL_AUDIO                     dev:8 hw_level:2192
+    496122.322421443  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496122.322421804  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496122.322422681  WRITE_STREAMS_MIXED            write_limit:0
+    496122.322424981  FILL_AUDIO_DONE                hw_level:2192 total_written:0 min_cb_level:1024
+    496122.322431852  SET_DEV_WAKE                   dev:8 hw_level:2192 sleep:2192
+    496122.322432273  DEV_SLEEP_TIME                 dev:8 wake:000496122.368092619
+    496122.322433771  SLEEP                          sleep:000000000.045666666 longest_wake:000158140
+    496122.322600105  WAKE                           num_fds:1
+    496122.322628852  FILL_AUDIO                     dev:8 hw_level:2192
+    496122.322631252  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496122.322633001  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496122.322638734  DEV_STREAM_MIX                 written:1024 read:1024
+    496122.322639617  WRITE_STREAMS_MIXED            write_limit:1024
+    496122.322641145  FILL_AUDIO_DONE                hw_level:2192 total_written:1024 min_cb_level:1024
+    496122.322642659  STREAM_SLEEP_TIME              id:140000 wake:000496122.343409206
+    496122.322648953  SET_DEV_WAKE                   dev:8 hw_level:3216 sleep:3216
+    496122.322649474  DEV_SLEEP_TIME                 dev:8 wake:000496122.389642037
+    496122.322651183  SLEEP                          sleep:000000000.020767169 longest_wake:000158140
+    496122.343460464  WAKE                           num_fds:0
+    496122.343483111  WRITE_STREAMS_FETCH_STREAM     id:140000 cbth:1024 delay:2208
+    496122.343504801  FILL_AUDIO                     dev:8 hw_level:2208
+    496122.343508630  WRITE_STREAMS_STREAM           id:140000 shm_frames:0 cb_pending:1
+    496122.343508996  WRITE_STREAMS_MIX              write_limit:0 max_offset:0
+    496122.343510093  WRITE_STREAMS_MIXED            write_limit:0
+    496122.343512629  FILL_AUDIO_DONE                hw_level:2208 total_written:0 min_cb_level:1024
+    496122.343519871  SET_DEV_WAKE                   dev:8 hw_level:2208 sleep:2208
+    496122.343520302  DEV_SLEEP_TIME                 dev:8 wake:000496122.389513687
+    496122.343521810  SLEEP                          sleep:000000000.046000000 longest_wake:000158140
+    496122.343659067  WAKE                           num_fds:1
+    496122.343692660  FILL_AUDIO                     dev:8 hw_level:2208
+    496122.343695401  WRITE_STREAMS_STREAM           id:140000 shm_frames:1024 cb_pending:0
+    496122.343697170  WRITE_STREAMS_MIX              write_limit:1024 max_offset:0
+    496122.343701300  DEV_STREAM_MIX                 written:1024 read:1024
+    496122.343702001  WRITE_STREAMS_MIXED            write_limit:1024
+    496122.343703570  FILL_AUDIO_DONE                hw_level:2208 total_written:1024 min_cb_level:1024
+    496122.343704858  STREAM_SLEEP_TIME              id:140000 wake:000496122.364742539
+    496122.343710616  SET_DEV_WAKE                   dev:8 hw_level:3232 sleep:3232
+    496122.343711027  DEV_SLEEP_TIME                 dev:8 wake:000496122.411037640
+    496122.343712340  SLEEP                          sleep:000000000.021038232 longest_wake:000158140
+    496122.359904900  WAKE                           num_fds:1
+    496122.359908867  PB_MSG                         msg_id:6
+    496122.359909941  STREAM_REMOVED                 id:140000
+    496122.359939533  ODEV_NO_STREAMS                dev:8 hw_level:2416 write:0
+    496122.359944076  SET_DEV_WAKE                   dev:8 hw_level:2416 sleep:1392
+    496122.359944442  DEV_SLEEP_TIME                 dev:8 wake:000496122.388940010
+    496122.359945062  SLEEP                          sleep:000000000.029000000 longest_wake:000158140
+    496122.388959912  WAKE                           num_fds:0
+    496122.388982707  ODEV_NO_STREAMS                dev:8 hw_level:1024 write:1024
+    496122.388988021  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496122.388988288  DEV_SLEEP_TIME                 dev:8 wake:000496122.410316487
+    496122.388988949  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496122.410345296  WAKE                           num_fds:0
+    496122.410370857  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496122.410376456  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496122.410376727  DEV_SLEEP_TIME                 dev:8 wake:000496122.431704661
+    496122.410377434  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496122.431764840  WAKE                           num_fds:0
+    496122.431833959  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496122.431843690  SET_DEV_WAKE                   dev:8 hw_level:2000 sleep:976
+    496122.431844254  DEV_SLEEP_TIME                 dev:8 wake:000496122.452168375
+    496122.431845811  SLEEP                          sleep:000000000.020333333 longest_wake:000158140
+    496122.452219198  WAKE                           num_fds:0
+    496122.452281422  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496122.452292006  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496122.452292589  DEV_SLEEP_TIME                 dev:8 wake:000496122.473616205
+    496122.452294378  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496122.473669621  WAKE                           num_fds:0
+    496122.473700008  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496122.473706996  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496122.473707244  DEV_SLEEP_TIME                 dev:8 wake:000496122.495033989
+    496122.473708271  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496122.495074480  WAKE                           num_fds:0
+    496122.495131374  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496122.495146355  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496122.495147373  DEV_SLEEP_TIME                 dev:8 wake:000496122.516468874
+    496122.495150668  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496122.516543702  WAKE                           num_fds:0
+    496122.516620145  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496122.516634195  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496122.516634731  DEV_SLEEP_TIME                 dev:8 wake:000496122.537955861
+    496122.516638331  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496122.537993577  WAKE                           num_fds:0
+    496122.538015496  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496122.538021060  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496122.538021339  DEV_SLEEP_TIME                 dev:8 wake:000496122.559349272
+    496122.538022084  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496122.559371175  WAKE                           num_fds:0
+    496122.559406369  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496122.559412084  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496122.559412308  DEV_SLEEP_TIME                 dev:8 wake:000496122.580740216
+    496122.559413309  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496122.580803483  WAKE                           num_fds:0
+    496122.580876491  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496122.580892291  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496122.580892889  DEV_SLEEP_TIME                 dev:8 wake:000496122.602212271
+    496122.580896126  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496122.602249850  WAKE                           num_fds:0
+    496122.602273699  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496122.602279632  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496122.602279889  DEV_SLEEP_TIME                 dev:8 wake:000496122.623607597
+    496122.602281013  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496122.623630275  WAKE                           num_fds:0
+    496122.623652277  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496122.623657745  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496122.623657981  DEV_SLEEP_TIME                 dev:8 wake:000496122.644985979
+    496122.623658807  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496122.645029671  WAKE                           num_fds:0
+    496122.645048901  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496122.645052376  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496122.645052602  DEV_SLEEP_TIME                 dev:8 wake:000496122.666382558
+    496122.645053154  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496122.666591508  WAKE                           num_fds:0
+    496122.666610205  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496122.666613617  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496122.666613845  DEV_SLEEP_TIME                 dev:8 wake:000496122.687943859
+    496122.666614421  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496122.688006670  WAKE                           num_fds:0
+    496122.688026198  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496122.688029632  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496122.688029858  DEV_SLEEP_TIME                 dev:8 wake:000496122.709359852
+    496122.688030443  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496122.709414448  WAKE                           num_fds:0
+    496122.709440781  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496122.709446781  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496122.709447196  DEV_SLEEP_TIME                 dev:8 wake:000496122.730774701
+    496122.709448436  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496122.730857004  WAKE                           num_fds:0
+    496122.730888497  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496122.730895228  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496122.730895769  DEV_SLEEP_TIME                 dev:8 wake:000496122.752222632
+    496122.730897217  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496122.752337493  WAKE                           num_fds:0
+    496122.752370820  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496122.752377486  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496122.752378032  DEV_SLEEP_TIME                 dev:8 wake:000496122.773704945
+    496122.752379530  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496122.773777745  WAKE                           num_fds:0
+    496122.773809844  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496122.773816680  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496122.773817282  DEV_SLEEP_TIME                 dev:8 wake:000496122.795143924
+    496122.773818700  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496122.795180466  WAKE                           num_fds:0
+    496122.795213337  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496122.795220052  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496122.795220588  DEV_SLEEP_TIME                 dev:8 wake:000496122.816547467
+    496122.795222002  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496122.816655372  WAKE                           num_fds:0
+    496122.816688037  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496122.816694728  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496122.816695264  DEV_SLEEP_TIME                 dev:8 wake:000496122.838022122
+    496122.816696782  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496122.838272918  WAKE                           num_fds:0
+    496122.838308290  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496122.838314910  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496122.838315456  DEV_SLEEP_TIME                 dev:8 wake:000496122.859642410
+    496122.838316890  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496122.859692416  WAKE                           num_fds:0
+    496122.859724926  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496122.859731943  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496122.859732479  DEV_SLEEP_TIME                 dev:8 wake:000496122.881059061
+    496122.859733932  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496122.881167273  WAKE                           num_fds:0
+    496122.881200094  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496122.881206664  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496122.881207215  DEV_SLEEP_TIME                 dev:8 wake:000496122.902534184
+    496122.881208654  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496122.902785139  WAKE                           num_fds:0
+    496122.902814893  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496122.902821764  SET_DEV_WAKE                   dev:8 hw_level:2000 sleep:976
+    496122.902822315  DEV_SLEEP_TIME                 dev:8 wake:000496122.923148978
+    496122.902823743  SLEEP                          sleep:000000000.020333333 longest_wake:000158140
+    496122.923196282  WAKE                           num_fds:0
+    496122.923229584  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496122.923236269  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496122.923236810  DEV_SLEEP_TIME                 dev:8 wake:000496122.944563663
+    496122.923238234  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496122.944615625  WAKE                           num_fds:0
+    496122.944650330  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496122.944657045  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496122.944657587  DEV_SLEEP_TIME                 dev:8 wake:000496122.965984470
+    496122.944659050  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496122.966082719  WAKE                           num_fds:0
+    496122.966116547  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496122.966123342  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496122.966123884  DEV_SLEEP_TIME                 dev:8 wake:000496122.987450657
+    496122.966125332  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496122.987704348  WAKE                           num_fds:0
+    496122.987737645  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496122.987744150  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496122.987744631  DEV_SLEEP_TIME                 dev:8 wake:000496123.009071715
+    496122.987746020  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496123.009107139  WAKE                           num_fds:0
+    496123.009145457  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496123.009153220  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496123.009153892  DEV_SLEEP_TIME                 dev:8 wake:000496123.030479863
+    496123.009155841  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496123.030584265  WAKE                           num_fds:0
+    496123.030618043  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496123.030624739  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496123.030625275  DEV_SLEEP_TIME                 dev:8 wake:000496123.051952138
+    496123.030626703  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496123.052094028  WAKE                           num_fds:0
+    496123.052127385  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496123.052134070  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496123.052134606  DEV_SLEEP_TIME                 dev:8 wake:000496123.073461509
+    496123.052136040  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496123.073511516  WAKE                           num_fds:0
+    496123.073544643  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496123.073551248  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496123.073551789  DEV_SLEEP_TIME                 dev:8 wake:000496123.094878732
+    496123.073553217  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496123.094926450  WAKE                           num_fds:0
+    496123.094961846  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496123.094968617  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496123.094969168  DEV_SLEEP_TIME                 dev:8 wake:000496123.116295946
+    496123.094970737  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496123.116406874  WAKE                           num_fds:0
+    496123.116439890  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496123.116446710  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496123.116447252  DEV_SLEEP_TIME                 dev:8 wake:000496123.137774020
+    496123.116448730  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496123.137883404  WAKE                           num_fds:0
+    496123.137916390  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496123.137923126  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496123.137923672  DEV_SLEEP_TIME                 dev:8 wake:000496123.159250490
+    496123.137925111  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496123.159287924  WAKE                           num_fds:0
+    496123.159335483  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496123.159343406  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496123.159344138  DEV_SLEEP_TIME                 dev:8 wake:000496123.180669833
+    496123.159346042  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496123.180704931  WAKE                           num_fds:0
+    496123.180736895  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496123.180743866  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496123.180744402  DEV_SLEEP_TIME                 dev:8 wake:000496123.202071140
+    496123.180745821  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496123.202319259  WAKE                           num_fds:0
+    496123.202364484  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496123.202371294  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496123.202371830  DEV_SLEEP_TIME                 dev:8 wake:000496123.223698598
+    496123.202373279  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496123.223730915  WAKE                           num_fds:0
+    496123.223763370  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496123.223770086  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496123.223770622  DEV_SLEEP_TIME                 dev:8 wake:000496123.245097470
+    496123.223772065  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496123.245148129  WAKE                           num_fds:0
+    496123.245184318  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496123.245190958  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496123.245191494  DEV_SLEEP_TIME                 dev:8 wake:000496123.266518437
+    496123.245192887  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496123.266776098  WAKE                           num_fds:0
+    496123.266809636  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496123.266817093  SET_DEV_WAKE                   dev:8 hw_level:2000 sleep:976
+    496123.266817634  DEV_SLEEP_TIME                 dev:8 wake:000496123.287143786
+    496123.266819078  SLEEP                          sleep:000000000.020333333 longest_wake:000158140
+    496123.287397671  WAKE                           num_fds:0
+    496123.287432130  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496123.287438836  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496123.287439367  DEV_SLEEP_TIME                 dev:8 wake:000496123.308766250
+    496123.287440785  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496123.308870132  WAKE                           num_fds:0
+    496123.308903564  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496123.308910340  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496123.308910881  DEV_SLEEP_TIME                 dev:8 wake:000496123.330237814
+    496123.308912360  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496123.330308465  WAKE                           num_fds:0
+    496123.330350260  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496123.330356966  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496123.330357512  DEV_SLEEP_TIME                 dev:8 wake:000496123.351684385
+    496123.330358955  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496123.351937251  WAKE                           num_fds:0
+    496123.351970653  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496123.351977293  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496123.351977825  DEV_SLEEP_TIME                 dev:8 wake:000496123.373304743
+    496123.351979268  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496123.373411526  WAKE                           num_fds:0
+    496123.373445334  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496123.373452009  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496123.373452550  DEV_SLEEP_TIME                 dev:8 wake:000496123.394779408
+    496123.373453984  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496123.395033698  WAKE                           num_fds:0
+    496123.395067641  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496123.395074336  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496123.395074883  DEV_SLEEP_TIME                 dev:8 wake:000496123.416401736
+    496123.395076306  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496123.416466100  WAKE                           num_fds:0
+    496123.416499903  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496123.416506809  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496123.416507341  DEV_SLEEP_TIME                 dev:8 wake:000496123.437833978
+    496123.416508784  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496123.437920547  WAKE                           num_fds:0
+    496123.437948816  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496123.437954655  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496123.437955070  DEV_SLEEP_TIME                 dev:8 wake:000496123.459282732
+    496123.437956175  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496123.459515811  WAKE                           num_fds:0
+    496123.459544842  ODEV_NO_STREAMS                dev:8 hw_level:1040 write:1008
+    496123.459550812  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496123.459551220  DEV_SLEEP_TIME                 dev:8 wake:000496123.480878732
+    496123.459552302  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496123.481115112  WAKE                           num_fds:0
+    496123.481142011  ODEV_NO_STREAMS                dev:8 hw_level:992 write:1056
+    496123.481147923  SET_DEV_WAKE                   dev:8 hw_level:2048 sleep:1024
+    496123.481148342  DEV_SLEEP_TIME                 dev:8 wake:000496123.502475931
+    496123.481149428  SLEEP                          sleep:000000000.021333333 longest_wake:000158140
+    496123.488698368  WAKE                           num_fds:1
+    496123.488701940  PB_MSG                         msg_id:5
diff --git a/scripts/audio_thread_log_viewer/viewer_c3.py b/scripts/audio_thread_log_viewer/viewer_c3.py
new file mode 100755
index 0000000..a6b11be
--- /dev/null
+++ b/scripts/audio_thread_log_viewer/viewer_c3.py
@@ -0,0 +1,567 @@
+#!/usr/bin/python
+#
+# Copyright 2016 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.
+#
+
+"""Generates an HTML file with plot of buffer level in the audio thread log."""
+
+import argparse
+import collections
+import logging
+import string
+
+page_content = string.Template("""
+<html meta charset="UTF8">
+<head>
+  <!-- Load c3.css -->
+  <link href="https://rawgit.com/masayuki0812/c3/master/c3.css" rel="stylesheet" type="text/css">
+  <!-- Load d3.js and c3.js -->
+  <script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
+  <script src="https://rawgit.com/masayuki0812/c3/master/c3.js" charset="utf-8"></script>
+  <style type="text/css">
+    .c3-grid text {
+        fill: grey;
+    }
+    .event_log_box {
+      font-family: 'Courier New', Courier, 'Lucida Sans Typewriter', 'Lucida Typewriter', monospace;
+      font-size: 20px;
+      font-style: normal;
+      font-variant: normal;
+      font-weight: 300;
+      line-height: 26.4px;
+      white-space: pre;
+      height:50%;
+      width:48%;
+      border:1px solid #ccc;
+      overflow:auto;
+    }
+    .checkbox {
+      font-size: 30px;
+      border: 2px;
+    }
+    .device {
+      font-size: 15px;
+    }
+    .stream{
+      font-size: 15px;
+    }
+    .fetch{
+    }
+    .wake{
+    }
+  </style>
+  <script type="text/javascript">
+    draw_chart = function() {
+      var chart = c3.generate({
+        data: {
+          x: 'time',
+          columns: [
+              ['time',                   $times],
+              ['buffer_level',           $buffer_levels],
+          ],
+          type: 'bar',
+          types: {
+              buffer_level: 'line',
+          },
+        },
+        zoom: {
+          enabled: true,
+        },
+
+        grid: {
+          x: {
+            lines: [
+              $grids,
+            ],
+          },
+        },
+
+        axis: {
+          y: {min: 0, max: $max_y},
+        },
+      });
+    };
+
+    logs = `$logs`;
+    put_logs = function () {
+      document.getElementById('logs').innerHTML = logs;
+    };
+
+    set_initial_checkbox_value = function () {
+      document.getElementById('device').checked = true;
+      document.getElementById('stream').checked = true;
+      document.getElementById('fetch').checked = true;
+      document.getElementById('wake').checked = true;
+    }
+
+    window.onload = function() {
+      draw_chart();
+      put_logs();
+      set_initial_checkbox_value();
+    };
+
+    function handleClick(checkbox) {
+      var class_name = checkbox.id;
+      var elements = document.getElementsByClassName(class_name);
+      var i;
+
+      if (checkbox.checked) {
+        display_value = "block";
+      } else {
+        display_value = "none"
+      }
+
+      console.log("change " + class_name + " to " + display_value);
+      for (i = 0; i < elements.length; i++) {
+        elements[i].style.display = display_value;
+      }
+    }
+
+  </script>
+</head>
+
+<body>
+  <div id="chart" style="height:50%; width:100%" ></div>
+  <div style="margin:0 auto"; class="checkbox">
+      <label><input type="checkbox" onclick="handleClick(this);" id="device">Show device removed/added event</label>
+      <label><input type="checkbox" onclick="handleClick(this);" id="stream">Show stream removed/added event</label>
+      <label><input type="checkbox" onclick="handleClick(this);" id="fetch">Show fetch event</label>
+      <label><input type="checkbox" onclick="handleClick(this);" id="wake">Show wake by num_fds=1 event</label>
+  </div>
+  <div class="event_log_box", id="logs", style="float:left;"></div>
+  <textarea class="event_log_box", id="text", style="float:right;"></textarea>
+</body>
+</html>
+""")
+
+
+Tag = collections.namedtuple('Tag', {'time', 'text', 'position', 'class_name'})
+"""
+The tuple for tags shown on the plot on certain time.
+text is the tag to show, position is the tag position, which is one of
+'start', 'middle', 'end', class_name is one of 'device', 'stream', 'fetch',
+and 'wake' which will be their CSS class name.
+"""
+
+class EventData(object):
+    """The base class of an event."""
+    def __init__(self, time, name):
+        """Initializes an EventData.
+
+        @param time: A string for event time.
+        @param name: A string for event name.
+
+        """
+        self.time = time
+        self.name = name
+        self._text = None
+        self._position = None
+        self._class_name = None
+
+    def GetTag(self):
+        """Gets the tag for this event.
+
+        @returns: A Tag object. Returns None if no need to show tag.
+
+        """
+        if self._text:
+            return Tag(
+                    time=self.time, text=self._text, position=self._position,
+                    class_name=self._class_name)
+        return None
+
+
+class DeviceEvent(EventData):
+    """Class for device event."""
+    def __init__(self, time, name, device):
+        """Initializes a DeviceEvent.
+
+        @param time: A string for event time.
+        @param name: A string for event name.
+        @param device: A string for device index.
+
+        """
+        super(DeviceEvent, self).__init__(time, name)
+        self.device = device
+        self._position = 'start'
+        self._class_name = 'device'
+
+
+class DeviceRemovedEvent(DeviceEvent):
+    """Class for device removed event."""
+    def __init__(self, time, name, device):
+        """Initializes a DeviceRemovedEvent.
+
+        @param time: A string for event time.
+        @param name: A string for event name.
+        @param device: A string for device index.
+
+        """
+        super(DeviceRemovedEvent, self).__init__(time, name, device)
+        self._text = 'Removed Device %s' % self.device
+
+
+class DeviceAddedEvent(DeviceEvent):
+    """Class for device added event."""
+    def __init__(self, time, name, device):
+        """Initializes a DeviceAddedEvent.
+
+        @param time: A string for event time.
+        @param name: A string for event name.
+        @param device: A string for device index.
+
+        """
+        super(DeviceAddedEvent, self).__init__(time, name, device)
+        self._text = 'Added Device %s' % self.device
+
+
+class LevelEvent(DeviceEvent):
+    """Class for device event with buffer level."""
+    def __init__(self, time, name, device, level):
+        """Initializes a LevelEvent.
+
+        @param time: A string for event time.
+        @param name: A string for event name.
+        @param device: A string for device index.
+        @param level: An int for buffer level.
+
+        """
+        super(LevelEvent, self).__init__(time, name, device)
+        self.level = level
+
+
+class StreamEvent(EventData):
+    """Class for event with stream."""
+    def __init__(self, time, name, stream):
+        """Initializes a StreamEvent.
+
+        @param time: A string for event time.
+        @param name: A string for event name.
+        @param stream: A string for stream id.
+
+        """
+        super(StreamEvent, self).__init__(time, name)
+        self.stream = stream
+        self._class_name = 'stream'
+
+
+class FetchStreamEvent(StreamEvent):
+    """Class for stream fetch event."""
+    def __init__(self, time, name, stream):
+        """Initializes a FetchStreamEvent.
+
+        @param time: A string for event time.
+        @param name: A string for event name.
+        @param stream: A string for stream id.
+
+        """
+        super(FetchStreamEvent, self).__init__(time, name, stream)
+        self._text = 'Fetch %s' % self.stream
+        self._position = 'end'
+        self._class_name = 'fetch'
+
+
+class StreamAddedEvent(StreamEvent):
+    """Class for stream added event."""
+    def __init__(self, time, name, stream):
+        """Initializes a StreamAddedEvent.
+
+        @param time: A string for event time.
+        @param name: A string for event name.
+        @param stream: A string for stream id.
+
+        """
+        super(StreamAddedEvent, self).__init__(time, name, stream)
+        self._text = 'Add stream %s' % self.stream
+        self._position = 'middle'
+
+
+class StreamRemovedEvent(StreamEvent):
+    """Class for stream removed event."""
+    def __init__(self, time, name, stream):
+        """Initializes a StreamRemovedEvent.
+
+        @param time: A string for event time.
+        @param name: A string for event name.
+        @param stream: A string for stream id.
+
+        """
+        super(StreamRemovedEvent, self).__init__(time, name, stream)
+        self._text = 'Remove stream %s' % self.stream
+        self._position = 'middle'
+
+
+class WakeEvent(EventData):
+    """Class for wake event."""
+    def __init__(self, time, name, num_fds):
+        """Initializes a WakeEvent.
+
+        @param time: A string for event time.
+        @param name: A string for event name.
+        @param num_fds: A string for number of fd that wakes audio thread up.
+
+        """
+        super(WakeEvent, self).__init__(time, name)
+        self._position = 'middle'
+        self._class_name = 'wake'
+        if num_fds != '0':
+            self._text = 'num_fds %s' % num_fds
+
+
+class C3LogWriter(object):
+    """Class to handle event data and fill an HTML page using c3.js library"""
+    def __init__(self):
+        """Initializes a C3LogWriter."""
+        self.times = []
+        self.buffer_levels = []
+        self.tags = []
+        self.max_y = 0
+
+    def AddEvent(self, event):
+        """Digests an event.
+
+        Add a tag if this event needs to be shown on grid.
+        Add a buffer level data into buffer_levels if this event has buffer
+        level.
+
+        @param event: An EventData object.
+
+        """
+        tag = event.GetTag()
+        if tag:
+            self.tags.append(tag)
+
+        if isinstance(event, LevelEvent):
+            self.times.append(event.time)
+            self.buffer_levels.append(str(event.level))
+            if event.level > self.max_y:
+                self.max_y = event.level
+            logging.debug('add data for a level event %s: %s',
+                          event.time, event.level)
+
+        if (isinstance(event, DeviceAddedEvent) or
+            isinstance(event, DeviceRemovedEvent)):
+            self.times.append(event.time)
+            self.buffer_levels.append('null')
+
+    def _GetGrids(self):
+        """Gets the content to be filled for grids.
+
+        @returns: A str for grid with format:
+           '{value: time1, text: "tag1", position: "position1"},
+            {value: time1, text: "tag1"},...'
+
+        """
+        grids = []
+        for tag in self.tags:
+            content = ('{value: %s, text: "%s", position: "%s", '
+                       'class: "%s"}') % (
+                              tag.time, tag.text, tag.position, tag.class_name)
+            grids.append(content)
+        grids_joined = ', '.join(grids)
+        return grids_joined
+
+    def FillPage(self, page_template):
+        """Fills in the page template with content.
+
+        @param page_template: A string for HTML page content with variables
+                              to be filled.
+
+        @returns: A string for filled page.
+
+        """
+        times = ', '.join(self.times)
+        buffer_levels = ', '.join(self.buffer_levels)
+        grids = self._GetGrids()
+        filled = page_template.safe_substitute(
+                times=times,
+                buffer_levels=buffer_levels,
+                grids=grids,
+                max_y=str(self.max_y))
+        return filled
+
+
+class EventLogParser(object):
+    """Class for event log parser."""
+    def __init__(self):
+        """Initializes an EventLogParse."""
+        self.parsed_events = []
+
+    def AddEventLog(self, event_log):
+        """Digests a line of event log.
+
+        @param event_log: A line for event log.
+
+        """
+        event = self._ParseOneLine(event_log)
+        if event:
+            self.parsed_events.append(event)
+
+    def GetParsedEvents(self):
+        """Gets the list of parsed events.
+
+        @returns: A list of parsed EventData.
+
+        """
+        return self.parsed_events
+
+    def _ParseOneLine(self, line):
+        """Parses one line of event log.
+
+        Split a line like
+        169536.504763588  WRITE_STREAMS_FETCH_STREAM     id:0 cbth:512 delay:1136
+        into time, name, and props where
+        time = '169536.504763588'
+        name = 'WRITE_STREAMS_FETCH_STREAM'
+        props = {
+            'id': 0,
+            'cb_th': 512,
+            'delay': 1136
+        }
+
+        @param line: A line of event log.
+
+        @returns: A EventData object.
+
+        """
+        line_split = line.split()
+        time, name = line_split[0], line_split[1]
+        logging.debug('time: %s, name: %s', time, name)
+        props = {}
+        for index in xrange(2, len(line_split)):
+            key, value = line_split[index].split(':')
+            props[key] = value
+        logging.debug('props: %s', props)
+        return self._CreateEventData(time, name, props)
+
+    def _CreateEventData(self, time, name, props):
+        """Creates an EventData based on event name.
+
+        @param time: A string for event time.
+        @param name: A string for event name.
+        @param props: A dict for event properties.
+
+        @returns: A EventData object.
+
+        """
+        if name == 'WRITE_STREAMS_FETCH_STREAM':
+            return FetchStreamEvent(time, name, stream=props['id'])
+        if name == 'STREAM_ADDED':
+            return StreamAddedEvent(time, name, stream=props['id'])
+        if name == 'STREAM_REMOVED':
+            return StreamRemovedEvent(time, name, stream=props['id'])
+        if name in ['FILL_AUDIO', 'SET_DEV_WAKE']:
+            return LevelEvent(
+                    time, name, device=props['dev'],
+                    level=int(props['hw_level']))
+        if name == 'DEV_ADDED':
+            return DeviceAddedEvent(time, name, device=props['dev'])
+        if name == 'DEV_REMOVED':
+            return DeviceRemovedEvent(time, name, device=props['dev'])
+        if name == 'WAKE':
+            return WakeEvent(time, name, num_fds=props['num_fds'])
+        return None
+
+
+class AudioThreadLogParser(object):
+    """Class for audio thread log parser."""
+    def __init__(self, path):
+        """Initializes an AudioThreadLogParser.
+
+        @param path: The audio thread log file path.
+
+        """
+        self.path = path
+        self.content = None
+
+    def Parse(self):
+        """Prases the audio thread logs.
+
+        @returns: A list of event log lines.
+
+        """
+        logging.debug('Using file: %s', self.path)
+        with open(self.path, 'r') as f:
+            self.content = f.read().splitlines()
+
+        # Event logs starting at two lines after 'Audio Thread Event Log'.
+        index_start = self.content.index('Audio Thread Event Log:') + 2
+        # If input is from audio_diagnostic result, use aplay -l line to find
+        # the end of audio thread event logs.
+        try:
+            index_end = self.content.index('=== aplay -l ===')
+        except ValueError:
+            logging.debug(
+                    'Can not find aplay line. This is not from diagnostic')
+            index_end = len(self.content)
+        event_logs = self.content[index_start:index_end]
+        logging.info('Parsed %s log events', len(event_logs))
+        return event_logs
+
+    def FillLogs(self, page_template):
+        """Fills the HTML page template with contents for audio thread logs.
+
+        @param page_template: A string for HTML page content with log variable
+                              to be filled.
+
+        @returns: A string for filled page.
+
+        """
+        logs = '\n<br>'.join(self.content)
+        return page_template.substitute(logs=logs)
+
+
+def ParseArgs():
+    """Parses the arguments.
+
+    @returns: The namespace containing parsed arguments.
+
+    """
+    parser = argparse.ArgumentParser(
+            description='Draw time chart from audio thread log',
+            formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+    parser.add_argument('FILE', type=str, help='The audio thread log file')
+    parser.add_argument('-o', type=str, dest='output',
+                        default='view.html', help='The output HTML file')
+    parser.add_argument('-d', dest='debug', action='store_true',
+                        default=False, help='Show debug message')
+    return parser.parse_args()
+
+
+def Main():
+    """The Main program."""
+    options = ParseArgs()
+    logging.basicConfig(
+            format='%(asctime)s:%(levelname)s:%(message)s',
+            level=logging.DEBUG if options.debug else logging.INFO)
+
+    # Gets lines of event logs.
+    audio_thread_log_parser = AudioThreadLogParser(options.FILE)
+    event_logs = audio_thread_log_parser.Parse()
+
+    # Parses event logs into events.
+    event_log_parser = EventLogParser()
+    for event_log in event_logs:
+        event_log_parser.AddEventLog(event_log)
+    events = event_log_parser.GetParsedEvents()
+
+    # Reads in events in preparation of filling HTML template.
+    c3_writer = C3LogWriter()
+    for event in events:
+        c3_writer.AddEvent(event)
+
+    # Fills in buffer level chart.
+    page_content_with_chart = c3_writer.FillPage(page_content)
+
+    # Fills in audio thread log into text box.
+    page_content_with_chart_and_logs = audio_thread_log_parser.FillLogs(
+            string.Template(page_content_with_chart))
+
+    with open(options.output, 'w') as f:
+        f.write(page_content_with_chart_and_logs)
+
+
+if __name__ == '__main__':
+    Main()
diff --git a/scripts/audio_tuning/frontend/audio.js b/scripts/audio_tuning/frontend/audio.js
index c8f393a..2476f1a 100644
--- a/scripts/audio_tuning/frontend/audio.js
+++ b/scripts/audio_tuning/frontend/audio.js
@@ -117,6 +117,58 @@
  * The detection result will be stored in this value. When user saves config,
  * This value is stored in drc.emphasis_disabled in the config. */
 var browser_emphasis_disabled_detection_result;
+/* check_biquad_filter_q detects if the browser implements the lowpass and
+ * highpass biquad filters with the original formula or the new formula from
+ * Audio EQ Cookbook. Chrome changed the filter implementation in R53, see:
+ * https://github.com/GoogleChrome/web-audio-samples/wiki/Detection-of-lowpass-BiquadFilter-implementation
+ * The detection result is saved in this value before the page is initialized.
+ * make_biquad_q() uses this value to compute Q to ensure consistent behavior
+ * on different browser versions.
+ */
+var browser_biquad_filter_uses_audio_cookbook_formula;
+
+/* Check the lowpass implementation and return a promise. */
+function check_biquad_filter_q() {
+  'use strict';
+  var context = new OfflineAudioContext(1, 128, 48000);
+  var osc = context.createOscillator();
+  var filter1 = context.createBiquadFilter();
+  var filter2 = context.createBiquadFilter();
+  var inverter = context.createGain();
+
+  osc.type = 'sawtooth';
+  osc.frequency.value = 8 * 440;
+  inverter.gain.value = -1;
+  /* each filter should get a different Q value */
+  filter1.Q.value = -1;
+  filter2.Q.value = -20;
+  osc.connect(filter1);
+  osc.connect(filter2);
+  filter1.connect(context.destination);
+  filter2.connect(inverter);
+  inverter.connect(context.destination);
+  osc.start();
+
+  return context.startRendering().then(function (buffer) {
+    return browser_biquad_filter_uses_audio_cookbook_formula =
+      Math.max(...buffer.getChannelData(0)) !== 0;
+  });
+}
+
+/* Return the Q value to be used with the lowpass and highpass biquad filters,
+ * given Q in dB for the original filter formula. If the browser uses the new
+ * formula, conversion is made to simulate the original frequency response
+ * with the new formula.
+ */
+function make_biquad_q(q_db) {
+  if (!browser_biquad_filter_uses_audio_cookbook_formula)
+    return q_db;
+
+  var q_lin = dBToLinear(q_db);
+  var q_new = 1 / Math.sqrt((4 - Math.sqrt(16 - 16 / (q_lin * q_lin))) / 2);
+  q_new = linearToDb(q_new);
+  return q_new;
+}
 
 /* The supported audio element names are different on browsers with different
  * versions.*/
@@ -506,7 +558,10 @@
       filter.frequency.value = parseFloat(value);
       break;
     case 'q':
-      filter.Q.value = parseFloat(value);
+      value = parseFloat(value);
+      if (filter.type == 'lowpass' || filter.type == 'highpass')
+        value = make_biquad_q(value);
+      filter.Q.value = value;
       break;
     case 'gain':
       filter.gain.value = parseFloat(value);
@@ -837,7 +892,7 @@
   var lp = audioContext.createBiquadFilter();
   lp.type = 'lowpass';
   lp.frequency.value = freq;
-  lp.Q.value = 0;
+  lp.Q.value = make_biquad_q(0);
   return lp;
 }
 
@@ -846,7 +901,7 @@
   var hp = audioContext.createBiquadFilter();
   hp.type = 'highpass';
   hp.frequency.value = freq;
-  hp.Q.value = 0;
+  hp.Q.value = make_biquad_q(0);
   return hp;
 }
 
@@ -1438,12 +1493,17 @@
 
 window.onload = function() {
   fix_audio_elements();
-  /* Detects if emphasis is disabled and sets
-   * browser_emphasis_disabled_detection_result. */
-  get_emphasis_disabled();
-  init_config();
-  init_audio();
-  init_ui();
+  check_biquad_filter_q().then(function (flag) {
+    console.log('Browser biquad filter uses Audio Cookbook formula:', flag);
+    /* Detects if emphasis is disabled and sets
+     * browser_emphasis_disabled_detection_result. */
+    get_emphasis_disabled();
+    init_config();
+    init_audio();
+    init_ui();
+  }).catch(function (reason) {
+    alert('Cannot detect browser biquad filter implementation:', reason);
+  });
 };
 
 function init_ui() {
@@ -1760,7 +1820,10 @@
       }
       filter.type = get_config('eq', channel, i, 'type');
       filter.frequency.value = centerFreq[i];
-      filter.Q.value = q[i];
+      if (filter.type == 'lowpass' || filter.type == 'highpass')
+        filter.Q.value = make_biquad_q(q[i]);
+      else
+        filter.Q.value = q[i];
       filter.gain.value = gain[i];
       filter.getFrequencyResponse(frequencyHz, magResponse,
                                   phaseResponse);
diff --git a/ucm-config/banjo/byt-max98090/byt-max98090.conf b/ucm-config/banjo/byt-max98090/byt-max98090.conf
deleted file mode 100644
index 95cdd0d..0000000
--- a/ucm-config/banjo/byt-max98090/byt-max98090.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Banjo internal card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/candy/byt-max98090/HiFi.conf b/ucm-config/candy/byt-max98090/HiFi.conf
deleted file mode 100644
index 75577c0..0000000
--- a/ucm-config/candy/byt-max98090/HiFi.conf
+++ /dev/null
@@ -1,97 +0,0 @@
-SectionVerb {
-	Value {
-		OutputDspName "speaker_eq"
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-
-		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='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='Headphone Left Switch' on"
-		cset "name='Headphone Right Switch' on"
-		cset "name='Headphone Switch' off"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-
-		cset "name='Speaker Left Switch' on"
-		cset "name='Speaker Right Switch' on"
-		cset "name='Ext Spk Switch' on"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-		JackName "byt-max98090 Headphone Jack"
-		OutputDspName ""
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Speaker Left Switch' off"
-		cset "name='Speaker Right Switch' off"
-		cset "name='Headphone Switch' on"
-		cset "name='HP Left Out Switch' on"
-		cset "name='HP Right Out Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-		cset "name='Headphone Switch' off"
-		cset "name='Speaker Left Switch' on"
-		cset "name='Speaker Right Switch' on"
-	]
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		JackName "byt-max98090 Mic Jack"
-		CaptureControl "MIC2"
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Int Mic Switch' off"
-		cset "name='Headset Mic Switch' on"
-		cset "name='DMIC Mux' ADC"
-		cset "name='Record Path DC Blocking' on"
-	]
-
-	DisableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Headset Mic Switch' off"
-		cset "name='Int Mic Switch' on"
-		cset "name='DMIC Mux' DMIC"
-		cset "name='Record Path DC Blocking' off"
-	]
-}
diff --git a/ucm-config/candy/byt-max98090/byt-max98090.conf b/ucm-config/candy/byt-max98090/byt-max98090.conf
deleted file mode 100644
index 43b155f..0000000
--- a/ucm-config/candy/byt-max98090/byt-max98090.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Candy internal card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/chell b/ucm-config/chell
new file mode 120000
index 0000000..1ec1f47
--- /dev/null
+++ b/ucm-config/chell
@@ -0,0 +1 @@
+glados/
\ No newline at end of file
diff --git a/ucm-config/chell-cheets b/ucm-config/chell-cheets
new file mode 120000
index 0000000..1ec1f47
--- /dev/null
+++ b/ucm-config/chell-cheets
@@ -0,0 +1 @@
+glados/
\ No newline at end of file
diff --git a/ucm-config/clapper/byt-max98090/HiFi.conf b/ucm-config/clapper/byt-max98090/HiFi.conf
deleted file mode 100644
index e425050..0000000
--- a/ucm-config/clapper/byt-max98090/HiFi.conf
+++ /dev/null
@@ -1,119 +0,0 @@
-SectionVerb {
-	Value {
-		OutputDspName "speaker_eq"
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-
-		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='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='Headphone Left Switch' on"
-		cset "name='Headphone Right Switch' on"
-		cset "name='Headphone Switch' off"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-
-		cset "name='Speaker Left Switch' on"
-		cset "name='Speaker Right Switch' on"
-		cset "name='Ext Spk Switch' on"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-		JackName "byt-max98090 Headphone Jack"
-		OutputDspName ""
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Speaker Left Switch' off"
-		cset "name='Speaker Right Switch' off"
-		cset "name='Headphone Switch' on"
-		cset "name='HP Left Out Switch' on"
-		cset "name='HP Right Out Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-		cset "name='Headphone Switch' off"
-		cset "name='Speaker Left Switch' on"
-		cset "name='Speaker Right Switch' on"
-	]
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		JackName "byt-max98090 Mic Jack"
-		CaptureControl "MIC2"
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Int Mic Switch' off"
-		cset "name='Headset Mic Switch' on"
-		cset "name='DMIC Mux' ADC"
-		cset "name='Record Path DC Blocking' on"
-	]
-
-	DisableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Headset Mic Switch' off"
-		cset "name='Int Mic Switch' on"
-		cset "name='DMIC Mux' DMIC"
-		cset "name='Record Path DC Blocking' off"
-	]
-}
-
-SectionModifier."Speaker Swap Mode".0 {
-	Comment "Swap the left and right channels of speaker."
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-
-		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 [
-		cdev "hw:bytmax98090"
-
-		cset "name='Left Speaker Mixer Right DAC Switch' off"
-		cset "name='Right Speaker Mixer Left DAC Switch' off"
-		cset "name='Left Speaker Mixer Left DAC Switch' on"
-		cset "name='Right Speaker Mixer Right DAC Switch' on"
-	]
-}
diff --git a/ucm-config/clapper/byt-max98090/byt-max98090.conf b/ucm-config/clapper/byt-max98090/byt-max98090.conf
deleted file mode 100644
index 6c8f18c..0000000
--- a/ucm-config/clapper/byt-max98090/byt-max98090.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Clapper internal card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/cranky/byt-max98090/HiFi.conf b/ucm-config/cranky/byt-max98090/HiFi.conf
deleted file mode 100644
index 75577c0..0000000
--- a/ucm-config/cranky/byt-max98090/HiFi.conf
+++ /dev/null
@@ -1,97 +0,0 @@
-SectionVerb {
-	Value {
-		OutputDspName "speaker_eq"
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-
-		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='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='Headphone Left Switch' on"
-		cset "name='Headphone Right Switch' on"
-		cset "name='Headphone Switch' off"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-
-		cset "name='Speaker Left Switch' on"
-		cset "name='Speaker Right Switch' on"
-		cset "name='Ext Spk Switch' on"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-		JackName "byt-max98090 Headphone Jack"
-		OutputDspName ""
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Speaker Left Switch' off"
-		cset "name='Speaker Right Switch' off"
-		cset "name='Headphone Switch' on"
-		cset "name='HP Left Out Switch' on"
-		cset "name='HP Right Out Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-		cset "name='Headphone Switch' off"
-		cset "name='Speaker Left Switch' on"
-		cset "name='Speaker Right Switch' on"
-	]
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		JackName "byt-max98090 Mic Jack"
-		CaptureControl "MIC2"
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Int Mic Switch' off"
-		cset "name='Headset Mic Switch' on"
-		cset "name='DMIC Mux' ADC"
-		cset "name='Record Path DC Blocking' on"
-	]
-
-	DisableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Headset Mic Switch' off"
-		cset "name='Int Mic Switch' on"
-		cset "name='DMIC Mux' DMIC"
-		cset "name='Record Path DC Blocking' off"
-	]
-}
diff --git a/ucm-config/cranky/byt-max98090/byt-max98090.conf b/ucm-config/cranky/byt-max98090/byt-max98090.conf
deleted file mode 100644
index 5fd8d42..0000000
--- a/ucm-config/cranky/byt-max98090/byt-max98090.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Cranky internal card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/cyan-cheets b/ucm-config/cyan-cheets
new file mode 120000
index 0000000..d8fcd90
--- /dev/null
+++ b/ucm-config/cyan-cheets
@@ -0,0 +1 @@
+cyan
\ No newline at end of file
diff --git a/ucm-config/cyan/chtmax98090/HiFi.conf b/ucm-config/cyan/chtmax98090/HiFi.conf
index a4e5308..4cf5204 100644
--- a/ucm-config/cyan/chtmax98090/HiFi.conf
+++ b/ucm-config/cyan/chtmax98090/HiFi.conf
@@ -111,3 +111,25 @@
 		cset "name='Record Path DC Blocking' off"
 	]
 }
+
+SectionModifier."Speaker Swap Mode".0 {
+	Comment "Swap the left and right channels of speaker."
+
+	EnableSequence [
+		cdev "hw:chtmax98090"
+
+		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 [
+		cdev "hw:chtmax98090"
+
+		cset "name='Left Speaker Mixer Right DAC Switch' off"
+		cset "name='Right Speaker Mixer Left DAC Switch' off"
+		cset "name='Left Speaker Mixer Left DAC Switch' on"
+		cset "name='Right Speaker Mixer Right DAC Switch' on"
+	]
+}
diff --git a/ucm-config/daisy/DAISY-I2S/HiFi.conf b/ucm-config/daisy/DAISY-I2S/HiFi.conf
index 6539d87..54d08fc 100644
--- a/ucm-config/daisy/DAISY-I2S/HiFi.conf
+++ b/ucm-config/daisy/DAISY-I2S/HiFi.conf
@@ -102,6 +102,7 @@
 	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"
 	]
diff --git a/ucm-config/daisy_skate/DAISY-I2S/HiFi.conf b/ucm-config/daisy_skate/DAISY-I2S/HiFi.conf
index c0de090..2516a96 100644
--- a/ucm-config/daisy_skate/DAISY-I2S/HiFi.conf
+++ b/ucm-config/daisy_skate/DAISY-I2S/HiFi.conf
@@ -36,15 +36,11 @@
 	EnableSequence [
 		cdev "hw:DAISYI2S"
 		cset "name='EQ1 Switch' off"
-		cset "name='Left HP Mixer Left DAC1 Switch' on"
-		cset "name='Right HP Mixer Right DAC1 Switch' on"
 	]
 	DisableSequence [
 		cdev "hw:DAISYI2S"
 		cset "name='EQ1 Mode' Default"
 		cset "name='EQ1 Switch' on"
-		cset "name='Left SPK Mixer Left DAC1 Switch' on"
-		cset "name='Right SPK Mixer Right DAC1 Switch' on"
 	]
 }
 
diff --git a/ucm-config/daisy_spring/DAISY-I2S/HiFi.conf b/ucm-config/daisy_spring/DAISY-I2S/HiFi.conf
index 079af4f..85fbc56 100644
--- a/ucm-config/daisy_spring/DAISY-I2S/HiFi.conf
+++ b/ucm-config/daisy_spring/DAISY-I2S/HiFi.conf
@@ -59,14 +59,8 @@
 	}
 
 	EnableSequence [
-		cdev "hw:DAISYI2S"
-		cset "name='Speaker Switch' off"
-		cset "name='Headphone Switch' on"
 	]
 	DisableSequence [
-		cdev "hw:DAISYI2S"
-		cset "name='Headphone Switch' off"
-		cset "name='Speaker Switch' on"
 	]
 }
 
diff --git a/ucm-config/enguarde/byt-max98090/HiFi.conf b/ucm-config/enguarde/byt-max98090/HiFi.conf
deleted file mode 100644
index 75577c0..0000000
--- a/ucm-config/enguarde/byt-max98090/HiFi.conf
+++ /dev/null
@@ -1,97 +0,0 @@
-SectionVerb {
-	Value {
-		OutputDspName "speaker_eq"
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-
-		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='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='Headphone Left Switch' on"
-		cset "name='Headphone Right Switch' on"
-		cset "name='Headphone Switch' off"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-
-		cset "name='Speaker Left Switch' on"
-		cset "name='Speaker Right Switch' on"
-		cset "name='Ext Spk Switch' on"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-		JackName "byt-max98090 Headphone Jack"
-		OutputDspName ""
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Speaker Left Switch' off"
-		cset "name='Speaker Right Switch' off"
-		cset "name='Headphone Switch' on"
-		cset "name='HP Left Out Switch' on"
-		cset "name='HP Right Out Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-		cset "name='Headphone Switch' off"
-		cset "name='Speaker Left Switch' on"
-		cset "name='Speaker Right Switch' on"
-	]
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		JackName "byt-max98090 Mic Jack"
-		CaptureControl "MIC2"
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Int Mic Switch' off"
-		cset "name='Headset Mic Switch' on"
-		cset "name='DMIC Mux' ADC"
-		cset "name='Record Path DC Blocking' on"
-	]
-
-	DisableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Headset Mic Switch' off"
-		cset "name='Int Mic Switch' on"
-		cset "name='DMIC Mux' DMIC"
-		cset "name='Record Path DC Blocking' off"
-	]
-}
diff --git a/ucm-config/enguarde/byt-max98090/byt-max98090.conf b/ucm-config/enguarde/byt-max98090/byt-max98090.conf
deleted file mode 100644
index 068e405..0000000
--- a/ucm-config/enguarde/byt-max98090/byt-max98090.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Enguarde internal card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/expresso/byt-max98090/HiFi.conf b/ucm-config/expresso/byt-max98090/HiFi.conf
deleted file mode 100644
index 75577c0..0000000
--- a/ucm-config/expresso/byt-max98090/HiFi.conf
+++ /dev/null
@@ -1,97 +0,0 @@
-SectionVerb {
-	Value {
-		OutputDspName "speaker_eq"
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-
-		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='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='Headphone Left Switch' on"
-		cset "name='Headphone Right Switch' on"
-		cset "name='Headphone Switch' off"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-
-		cset "name='Speaker Left Switch' on"
-		cset "name='Speaker Right Switch' on"
-		cset "name='Ext Spk Switch' on"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-		JackName "byt-max98090 Headphone Jack"
-		OutputDspName ""
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Speaker Left Switch' off"
-		cset "name='Speaker Right Switch' off"
-		cset "name='Headphone Switch' on"
-		cset "name='HP Left Out Switch' on"
-		cset "name='HP Right Out Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-		cset "name='Headphone Switch' off"
-		cset "name='Speaker Left Switch' on"
-		cset "name='Speaker Right Switch' on"
-	]
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		JackName "byt-max98090 Mic Jack"
-		CaptureControl "MIC2"
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Int Mic Switch' off"
-		cset "name='Headset Mic Switch' on"
-		cset "name='DMIC Mux' ADC"
-		cset "name='Record Path DC Blocking' on"
-	]
-
-	DisableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Headset Mic Switch' off"
-		cset "name='Int Mic Switch' on"
-		cset "name='DMIC Mux' DMIC"
-		cset "name='Record Path DC Blocking' off"
-	]
-}
diff --git a/ucm-config/expresso/byt-max98090/byt-max98090.conf b/ucm-config/expresso/byt-max98090/byt-max98090.conf
deleted file mode 100644
index 23bf8df..0000000
--- a/ucm-config/expresso/byt-max98090/byt-max98090.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Expresso internal card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/glados/sklnau8825adi/HiFi.conf b/ucm-config/glados/sklnau8825adi/HiFi.conf
new file mode 100644
index 0000000..376e704
--- /dev/null
+++ b/ucm-config/glados/sklnau8825adi/HiFi.conf
@@ -0,0 +1,507 @@
+SectionVerb {
+	Value {
+		OutputDspName "speaker_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' 128"
+		cset "name='Headset Mic Switch' off"
+		cset "name='codec0_iv_in Switch' 1"
+		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 "2000"
+	}
+
+	EnableSequence [
+	]
+
+	DisableSequence [
+	]
+}
+
+SectionDevice."Speaker".0 {
+	Value {
+		CoupledMixers "Left Master,Right Master"
+	}
+
+	EnableSequence [
+	]
+
+	DisableSequence [
+	]
+}
+
+SectionDevice."HDMI1".0 {
+	Value {
+		JackName "HDMI/DP, pcm=4 Jack"
+		OutputDspName ""
+	}
+
+	EnableSequence [
+	]
+
+	DisableSequence [
+	]
+}
+
+SectionDevice."HDMI2".0 {
+	Value {
+		JackName "HDMI/DP, pcm=5 Jack"
+		OutputDspName ""
+	}
+
+	EnableSequence [
+	]
+
+	DisableSequence [
+	]
+}
+
+SectionDevice."Headphone".0 {
+	Value {
+		JackName "sklnau8825adi Headset Jack"
+		OutputDspName ""
+	}
+
+	EnableSequence [
+		cdev "hw:sklnau8825adi"
+		cset "name='codec0_iv_in Switch' 0"
+		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"
+		cset "name='codec0_iv_in Switch' 1"
+	]
+}
+
+SectionDevice."Mic".0 {
+	Value {
+		JackName "sklnau8825adi Headset Jack"
+		CaptureControl "Mic"
+	}
+
+	EnableSequence [
+		cdev "hw:sklnau8825adi"
+		cset "name='Headset Mic Switch' on"
+		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='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/quawks/byt-max98090/byt-max98090.conf b/ucm-config/glados/sklnau8825adi/sklnau8825adi.conf
similarity index 66%
rename from ucm-config/quawks/byt-max98090/byt-max98090.conf
rename to ucm-config/glados/sklnau8825adi/sklnau8825adi.conf
index 1856a8d..e1b2a27 100644
--- a/ucm-config/quawks/byt-max98090/byt-max98090.conf
+++ b/ucm-config/glados/sklnau8825adi/sklnau8825adi.conf
@@ -1,4 +1,4 @@
-Comment "Quawks internal card"
+Comment "Glados internal card"
 
 SectionUseCase."HiFi" {
 	File "HiFi.conf"
diff --git a/ucm-config/glimmer/byt-max98090/HiFi.conf b/ucm-config/glimmer-cheets/byt-max98090/HiFi.conf
similarity index 100%
rename from ucm-config/glimmer/byt-max98090/HiFi.conf
rename to ucm-config/glimmer-cheets/byt-max98090/HiFi.conf
diff --git a/ucm-config/glimmer/byt-max98090/byt-max98090.conf b/ucm-config/glimmer-cheets/byt-max98090/byt-max98090.conf
similarity index 100%
rename from ucm-config/glimmer/byt-max98090/byt-max98090.conf
rename to ucm-config/glimmer-cheets/byt-max98090/byt-max98090.conf
diff --git a/ucm-config/gnawty/byt-max98090/HiFi.conf b/ucm-config/gnawty/byt-max98090/HiFi.conf
deleted file mode 100644
index 75577c0..0000000
--- a/ucm-config/gnawty/byt-max98090/HiFi.conf
+++ /dev/null
@@ -1,97 +0,0 @@
-SectionVerb {
-	Value {
-		OutputDspName "speaker_eq"
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-
-		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='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='Headphone Left Switch' on"
-		cset "name='Headphone Right Switch' on"
-		cset "name='Headphone Switch' off"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-
-		cset "name='Speaker Left Switch' on"
-		cset "name='Speaker Right Switch' on"
-		cset "name='Ext Spk Switch' on"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-		JackName "byt-max98090 Headphone Jack"
-		OutputDspName ""
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Speaker Left Switch' off"
-		cset "name='Speaker Right Switch' off"
-		cset "name='Headphone Switch' on"
-		cset "name='HP Left Out Switch' on"
-		cset "name='HP Right Out Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-		cset "name='Headphone Switch' off"
-		cset "name='Speaker Left Switch' on"
-		cset "name='Speaker Right Switch' on"
-	]
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		JackName "byt-max98090 Mic Jack"
-		CaptureControl "MIC2"
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Int Mic Switch' off"
-		cset "name='Headset Mic Switch' on"
-		cset "name='DMIC Mux' ADC"
-		cset "name='Record Path DC Blocking' on"
-	]
-
-	DisableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Headset Mic Switch' off"
-		cset "name='Int Mic Switch' on"
-		cset "name='DMIC Mux' DMIC"
-		cset "name='Record Path DC Blocking' off"
-	]
-}
diff --git a/ucm-config/gnawty/byt-max98090/byt-max98090.conf b/ucm-config/gnawty/byt-max98090/byt-max98090.conf
deleted file mode 100644
index bd9087e..0000000
--- a/ucm-config/gnawty/byt-max98090/byt-max98090.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Gnawty internal card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/heli/byt-max98090/HiFi.conf b/ucm-config/heli/byt-max98090/HiFi.conf
deleted file mode 100644
index 75577c0..0000000
--- a/ucm-config/heli/byt-max98090/HiFi.conf
+++ /dev/null
@@ -1,97 +0,0 @@
-SectionVerb {
-	Value {
-		OutputDspName "speaker_eq"
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-
-		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='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='Headphone Left Switch' on"
-		cset "name='Headphone Right Switch' on"
-		cset "name='Headphone Switch' off"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-
-		cset "name='Speaker Left Switch' on"
-		cset "name='Speaker Right Switch' on"
-		cset "name='Ext Spk Switch' on"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-		JackName "byt-max98090 Headphone Jack"
-		OutputDspName ""
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Speaker Left Switch' off"
-		cset "name='Speaker Right Switch' off"
-		cset "name='Headphone Switch' on"
-		cset "name='HP Left Out Switch' on"
-		cset "name='HP Right Out Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-		cset "name='Headphone Switch' off"
-		cset "name='Speaker Left Switch' on"
-		cset "name='Speaker Right Switch' on"
-	]
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		JackName "byt-max98090 Mic Jack"
-		CaptureControl "MIC2"
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Int Mic Switch' off"
-		cset "name='Headset Mic Switch' on"
-		cset "name='DMIC Mux' ADC"
-		cset "name='Record Path DC Blocking' on"
-	]
-
-	DisableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Headset Mic Switch' off"
-		cset "name='Int Mic Switch' on"
-		cset "name='DMIC Mux' DMIC"
-		cset "name='Record Path DC Blocking' off"
-	]
-}
diff --git a/ucm-config/heli/byt-max98090/byt-max98090.conf b/ucm-config/heli/byt-max98090/byt-max98090.conf
deleted file mode 100644
index 83f2a82..0000000
--- a/ucm-config/heli/byt-max98090/byt-max98090.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Heli internal card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/kip/byt-max98090/HiFi.conf b/ucm-config/kip/byt-max98090/HiFi.conf
deleted file mode 100644
index 75577c0..0000000
--- a/ucm-config/kip/byt-max98090/HiFi.conf
+++ /dev/null
@@ -1,97 +0,0 @@
-SectionVerb {
-	Value {
-		OutputDspName "speaker_eq"
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-
-		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='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='Headphone Left Switch' on"
-		cset "name='Headphone Right Switch' on"
-		cset "name='Headphone Switch' off"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-
-		cset "name='Speaker Left Switch' on"
-		cset "name='Speaker Right Switch' on"
-		cset "name='Ext Spk Switch' on"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-		JackName "byt-max98090 Headphone Jack"
-		OutputDspName ""
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Speaker Left Switch' off"
-		cset "name='Speaker Right Switch' off"
-		cset "name='Headphone Switch' on"
-		cset "name='HP Left Out Switch' on"
-		cset "name='HP Right Out Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-		cset "name='Headphone Switch' off"
-		cset "name='Speaker Left Switch' on"
-		cset "name='Speaker Right Switch' on"
-	]
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		JackName "byt-max98090 Mic Jack"
-		CaptureControl "MIC2"
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Int Mic Switch' off"
-		cset "name='Headset Mic Switch' on"
-		cset "name='DMIC Mux' ADC"
-		cset "name='Record Path DC Blocking' on"
-	]
-
-	DisableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Headset Mic Switch' off"
-		cset "name='Int Mic Switch' on"
-		cset "name='DMIC Mux' DMIC"
-		cset "name='Record Path DC Blocking' off"
-	]
-}
diff --git a/ucm-config/ninja/byt-max98090/HiFi.conf b/ucm-config/ninja/byt-max98090/HiFi.conf
deleted file mode 100644
index 628b696..0000000
--- a/ucm-config/ninja/byt-max98090/HiFi.conf
+++ /dev/null
@@ -1,81 +0,0 @@
-SectionVerb {
-	Value {
-		NoCreateDefaultInputNode "1"
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-
-		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' ADC"
-		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='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='Record Path DC Blocking' on"
-		cset "name='Playback Path DC Blocking' on"
-
-		cset "name='Headphone Left Switch' on"
-		cset "name='Headphone Right Switch' on"
-		cset "name='Headphone Switch' off"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-		JackName "byt-max98090 Headphone Jack"
-		OutputDspName ""
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Headphone Switch' on"
-		cset "name='HP Left Out Switch' on"
-		cset "name='HP Right Out Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-		cset "name='Headphone Switch' off"
-	]
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		JackName "byt-max98090 Mic Jack"
-		CaptureControl "MIC2"
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Int Mic Switch' off"
-		cset "name='Headset Mic Switch' on"
-		cset "name='DMIC Mux' ADC"
-		cset "name='Record Path DC Blocking' on"
-	]
-
-	DisableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Headset Mic Switch' off"
-		cset "name='Record Path DC Blocking' off"
-	]
-}
diff --git a/ucm-config/ninja/byt-max98090/byt-max98090.conf b/ucm-config/ninja/byt-max98090/byt-max98090.conf
deleted file mode 100644
index 255e87e..0000000
--- a/ucm-config/ninja/byt-max98090/byt-max98090.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Ninja internal card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/orco/byt-max98090/HiFi.conf b/ucm-config/orco/byt-max98090/HiFi.conf
deleted file mode 100644
index 75577c0..0000000
--- a/ucm-config/orco/byt-max98090/HiFi.conf
+++ /dev/null
@@ -1,97 +0,0 @@
-SectionVerb {
-	Value {
-		OutputDspName "speaker_eq"
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-
-		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='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='Headphone Left Switch' on"
-		cset "name='Headphone Right Switch' on"
-		cset "name='Headphone Switch' off"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-
-		cset "name='Speaker Left Switch' on"
-		cset "name='Speaker Right Switch' on"
-		cset "name='Ext Spk Switch' on"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-		JackName "byt-max98090 Headphone Jack"
-		OutputDspName ""
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Speaker Left Switch' off"
-		cset "name='Speaker Right Switch' off"
-		cset "name='Headphone Switch' on"
-		cset "name='HP Left Out Switch' on"
-		cset "name='HP Right Out Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-		cset "name='Headphone Switch' off"
-		cset "name='Speaker Left Switch' on"
-		cset "name='Speaker Right Switch' on"
-	]
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		JackName "byt-max98090 Mic Jack"
-		CaptureControl "MIC2"
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Int Mic Switch' off"
-		cset "name='Headset Mic Switch' on"
-		cset "name='DMIC Mux' ADC"
-		cset "name='Record Path DC Blocking' on"
-	]
-
-	DisableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Headset Mic Switch' off"
-		cset "name='Int Mic Switch' on"
-		cset "name='DMIC Mux' DMIC"
-		cset "name='Record Path DC Blocking' off"
-	]
-}
diff --git a/ucm-config/orco/byt-max98090/byt-max98090.conf b/ucm-config/orco/byt-max98090/byt-max98090.conf
deleted file mode 100644
index 6d3deec..0000000
--- a/ucm-config/orco/byt-max98090/byt-max98090.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Orco internal card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/parry/byt-max98090/HiFi.conf b/ucm-config/parry/byt-max98090/HiFi.conf
deleted file mode 100644
index 75577c0..0000000
--- a/ucm-config/parry/byt-max98090/HiFi.conf
+++ /dev/null
@@ -1,97 +0,0 @@
-SectionVerb {
-	Value {
-		OutputDspName "speaker_eq"
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-
-		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='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='Headphone Left Switch' on"
-		cset "name='Headphone Right Switch' on"
-		cset "name='Headphone Switch' off"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-
-		cset "name='Speaker Left Switch' on"
-		cset "name='Speaker Right Switch' on"
-		cset "name='Ext Spk Switch' on"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-		JackName "byt-max98090 Headphone Jack"
-		OutputDspName ""
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Speaker Left Switch' off"
-		cset "name='Speaker Right Switch' off"
-		cset "name='Headphone Switch' on"
-		cset "name='HP Left Out Switch' on"
-		cset "name='HP Right Out Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-		cset "name='Headphone Switch' off"
-		cset "name='Speaker Left Switch' on"
-		cset "name='Speaker Right Switch' on"
-	]
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		JackName "byt-max98090 Mic Jack"
-		CaptureControl "MIC2"
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Int Mic Switch' off"
-		cset "name='Headset Mic Switch' on"
-		cset "name='DMIC Mux' ADC"
-		cset "name='Record Path DC Blocking' on"
-	]
-
-	DisableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Headset Mic Switch' off"
-		cset "name='Int Mic Switch' on"
-		cset "name='DMIC Mux' DMIC"
-		cset "name='Record Path DC Blocking' off"
-	]
-}
diff --git a/ucm-config/parry/byt-max98090/byt-max98090.conf b/ucm-config/parry/byt-max98090/byt-max98090.conf
deleted file mode 100644
index a14a6c4..0000000
--- a/ucm-config/parry/byt-max98090/byt-max98090.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Parry internal card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/quawks/byt-max98090/HiFi.conf b/ucm-config/quawks/byt-max98090/HiFi.conf
deleted file mode 100644
index 75577c0..0000000
--- a/ucm-config/quawks/byt-max98090/HiFi.conf
+++ /dev/null
@@ -1,97 +0,0 @@
-SectionVerb {
-	Value {
-		OutputDspName "speaker_eq"
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-
-		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='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='Headphone Left Switch' on"
-		cset "name='Headphone Right Switch' on"
-		cset "name='Headphone Switch' off"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-
-		cset "name='Speaker Left Switch' on"
-		cset "name='Speaker Right Switch' on"
-		cset "name='Ext Spk Switch' on"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-		JackName "byt-max98090 Headphone Jack"
-		OutputDspName ""
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Speaker Left Switch' off"
-		cset "name='Speaker Right Switch' off"
-		cset "name='Headphone Switch' on"
-		cset "name='HP Left Out Switch' on"
-		cset "name='HP Right Out Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-		cset "name='Headphone Switch' off"
-		cset "name='Speaker Left Switch' on"
-		cset "name='Speaker Right Switch' on"
-	]
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		JackName "byt-max98090 Mic Jack"
-		CaptureControl "MIC2"
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Int Mic Switch' off"
-		cset "name='Headset Mic Switch' on"
-		cset "name='DMIC Mux' ADC"
-		cset "name='Record Path DC Blocking' on"
-	]
-
-	DisableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Headset Mic Switch' off"
-		cset "name='Int Mic Switch' on"
-		cset "name='DMIC Mux' DMIC"
-		cset "name='Record Path DC Blocking' off"
-	]
-}
diff --git a/ucm-config/rambi/byt-max98090/HiFi.conf b/ucm-config/rambi/byt-max98090/HiFi.conf
deleted file mode 100644
index 75577c0..0000000
--- a/ucm-config/rambi/byt-max98090/HiFi.conf
+++ /dev/null
@@ -1,97 +0,0 @@
-SectionVerb {
-	Value {
-		OutputDspName "speaker_eq"
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-
-		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='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='Headphone Left Switch' on"
-		cset "name='Headphone Right Switch' on"
-		cset "name='Headphone Switch' off"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-
-		cset "name='Speaker Left Switch' on"
-		cset "name='Speaker Right Switch' on"
-		cset "name='Ext Spk Switch' on"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-		JackName "byt-max98090 Headphone Jack"
-		OutputDspName ""
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Speaker Left Switch' off"
-		cset "name='Speaker Right Switch' off"
-		cset "name='Headphone Switch' on"
-		cset "name='HP Left Out Switch' on"
-		cset "name='HP Right Out Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-		cset "name='Headphone Switch' off"
-		cset "name='Speaker Left Switch' on"
-		cset "name='Speaker Right Switch' on"
-	]
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		JackName "byt-max98090 Mic Jack"
-		CaptureControl "MIC2"
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Int Mic Switch' off"
-		cset "name='Headset Mic Switch' on"
-		cset "name='DMIC Mux' ADC"
-		cset "name='Record Path DC Blocking' on"
-	]
-
-	DisableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Headset Mic Switch' off"
-		cset "name='Int Mic Switch' on"
-		cset "name='DMIC Mux' DMIC"
-		cset "name='Record Path DC Blocking' off"
-	]
-}
diff --git a/ucm-config/rambi/byt-max98090/byt-max98090.conf b/ucm-config/rambi/byt-max98090/byt-max98090.conf
deleted file mode 100644
index 915d155..0000000
--- a/ucm-config/rambi/byt-max98090/byt-max98090.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Rambi internal card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/squawks/byt-max98090/HiFi.conf b/ucm-config/squawks/byt-max98090/HiFi.conf
deleted file mode 100644
index 75577c0..0000000
--- a/ucm-config/squawks/byt-max98090/HiFi.conf
+++ /dev/null
@@ -1,97 +0,0 @@
-SectionVerb {
-	Value {
-		OutputDspName "speaker_eq"
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-
-		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='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='Headphone Left Switch' on"
-		cset "name='Headphone Right Switch' on"
-		cset "name='Headphone Switch' off"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-
-		cset "name='Speaker Left Switch' on"
-		cset "name='Speaker Right Switch' on"
-		cset "name='Ext Spk Switch' on"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-		JackName "byt-max98090 Headphone Jack"
-		OutputDspName ""
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Speaker Left Switch' off"
-		cset "name='Speaker Right Switch' off"
-		cset "name='Headphone Switch' on"
-		cset "name='HP Left Out Switch' on"
-		cset "name='HP Right Out Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-		cset "name='Headphone Switch' off"
-		cset "name='Speaker Left Switch' on"
-		cset "name='Speaker Right Switch' on"
-	]
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		JackName "byt-max98090 Mic Jack"
-		CaptureControl "MIC2"
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Int Mic Switch' off"
-		cset "name='Headset Mic Switch' on"
-		cset "name='DMIC Mux' ADC"
-		cset "name='Record Path DC Blocking' on"
-	]
-
-	DisableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Headset Mic Switch' off"
-		cset "name='Int Mic Switch' on"
-		cset "name='DMIC Mux' DMIC"
-		cset "name='Record Path DC Blocking' off"
-	]
-}
diff --git a/ucm-config/squawks/byt-max98090/byt-max98090.conf b/ucm-config/squawks/byt-max98090/byt-max98090.conf
deleted file mode 100644
index 6a8972c..0000000
--- a/ucm-config/squawks/byt-max98090/byt-max98090.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Squawks internal card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/sumo/byt-max98090/HiFi.conf b/ucm-config/sumo/byt-max98090/HiFi.conf
deleted file mode 100644
index 75577c0..0000000
--- a/ucm-config/sumo/byt-max98090/HiFi.conf
+++ /dev/null
@@ -1,97 +0,0 @@
-SectionVerb {
-	Value {
-		OutputDspName "speaker_eq"
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-
-		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='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='Headphone Left Switch' on"
-		cset "name='Headphone Right Switch' on"
-		cset "name='Headphone Switch' off"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-
-		cset "name='Speaker Left Switch' on"
-		cset "name='Speaker Right Switch' on"
-		cset "name='Ext Spk Switch' on"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-		JackName "byt-max98090 Headphone Jack"
-		OutputDspName ""
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Speaker Left Switch' off"
-		cset "name='Speaker Right Switch' off"
-		cset "name='Headphone Switch' on"
-		cset "name='HP Left Out Switch' on"
-		cset "name='HP Right Out Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-		cset "name='Headphone Switch' off"
-		cset "name='Speaker Left Switch' on"
-		cset "name='Speaker Right Switch' on"
-	]
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		JackName "byt-max98090 Mic Jack"
-		CaptureControl "MIC2"
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Int Mic Switch' off"
-		cset "name='Headset Mic Switch' on"
-		cset "name='DMIC Mux' ADC"
-		cset "name='Record Path DC Blocking' on"
-	]
-
-	DisableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Headset Mic Switch' off"
-		cset "name='Int Mic Switch' on"
-		cset "name='DMIC Mux' DMIC"
-		cset "name='Record Path DC Blocking' off"
-	]
-}
diff --git a/ucm-config/sumo/byt-max98090/byt-max98090.conf b/ucm-config/sumo/byt-max98090/byt-max98090.conf
deleted file mode 100644
index 47fee7a..0000000
--- a/ucm-config/sumo/byt-max98090/byt-max98090.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Sumo internal card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/ucm-config/veyron/ROCKCHIP-I2S/HiFi.conf b/ucm-config/veyron/ROCKCHIP-I2S/HiFi.conf
index 7bfe995..f01190c 100644
--- a/ucm-config/veyron/ROCKCHIP-I2S/HiFi.conf
+++ b/ucm-config/veyron/ROCKCHIP-I2S/HiFi.conf
@@ -2,6 +2,7 @@
 	Value {
 		OutputDspName "speaker_eq"
 		MinBufferLevel "512"
+		FullySpecifiedUCM "1"
 	}
 
 	EnableSequence [
@@ -47,8 +48,45 @@
 	]
 }
 
+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"
 		OutputDspName ""
 	}
@@ -57,6 +95,7 @@
 		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"
 	]
@@ -65,12 +104,16 @@
 
 		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"
 	}
 
diff --git a/ucm-config/veyron/RockchipHDMI/HiFi.conf b/ucm-config/veyron/RockchipHDMI/HiFi.conf
index fc2eaaa..890c97c 100644
--- a/ucm-config/veyron/RockchipHDMI/HiFi.conf
+++ b/ucm-config/veyron/RockchipHDMI/HiFi.conf
@@ -1,6 +1,7 @@
 SectionVerb {
 	Value {
 		OutputDspName ""
+		MinBufferLevel "512"
 	}
 }
 
diff --git a/ucm-config/banjo/byt-max98090/HiFi.conf b/ucm-config/veyron_fievel/ROCKCHIP-I2S/HiFi.conf
similarity index 71%
rename from ucm-config/banjo/byt-max98090/HiFi.conf
rename to ucm-config/veyron_fievel/ROCKCHIP-I2S/HiFi.conf
index 75577c0..bf52f73 100644
--- a/ucm-config/banjo/byt-max98090/HiFi.conf
+++ b/ucm-config/veyron_fievel/ROCKCHIP-I2S/HiFi.conf
@@ -1,13 +1,17 @@
 SectionVerb {
 	Value {
 		OutputDspName "speaker_eq"
+		MinBufferLevel "512"
+		FullySpecifiedUCM "1"
 	}
 
 	EnableSequence [
-		cdev "hw:bytmax98090"
+		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"
@@ -20,6 +24,7 @@
 		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"
@@ -34,15 +39,9 @@
 		cset "name='Record Path DC Blocking' on"
 		cset "name='Playback Path DC Blocking' on"
 
-		cset "name='Headphone Left Switch' on"
-		cset "name='Headphone Right Switch' on"
-		cset "name='Headphone Switch' off"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-
 		cset "name='Speaker Left Switch' on"
 		cset "name='Speaker Right Switch' on"
-		cset "name='Ext Spk Switch' on"
+		cset "name='Speaker Switch' on"
 	]
 
 	DisableSequence [
@@ -51,47 +50,54 @@
 
 SectionDevice."Headphone".0 {
 	Value {
-		JackName "byt-max98090 Headphone Jack"
+		PlaybackPCM "hw:ROCKCHIPI2S,0"
+		MixerName "Headphone"
+		JackType "gpio"
+		JackName "ROCKCHIP-I2S Headset Jack"
 		OutputDspName ""
 	}
 
 	EnableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Speaker Left Switch' off"
-		cset "name='Speaker Right Switch' off"
+		cdev "hw:ROCKCHIPI2S"
+
+		cset "name='Speaker Switch' off"
 		cset "name='Headphone Switch' on"
-		cset "name='HP Left Out Switch' on"
-		cset "name='HP Right Out Switch' on"
+		cset "name='Headphone Left Switch' on"
+		cset "name='Headphone Right Switch' on"
 	]
 	DisableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
+		cdev "hw:ROCKCHIPI2S"
+
+		cset "name='Headphone Left Switch' off"
+		cset "name='Headphone Right Switch' off"
 		cset "name='Headphone Switch' off"
-		cset "name='Speaker Left Switch' on"
-		cset "name='Speaker Right Switch' on"
+		cset "name='Speaker Switch' on"
 	]
 }
 
 SectionDevice."Mic".0 {
 	Value {
-		JackName "byt-max98090 Mic Jack"
-		CaptureControl "MIC2"
+		CapturePCM "hw:ROCKCHIPI2S,0"
+		MixerName "Headset Mic"
+		JackType "gpio"
+		JackName "ROCKCHIP-I2S Headset Jack"
 	}
 
 	EnableSequence [
-		cdev "hw:bytmax98090"
+		cdev "hw:ROCKCHIPI2S"
+
 		cset "name='Int Mic Switch' off"
-		cset "name='Headset Mic Switch' on"
 		cset "name='DMIC Mux' ADC"
+		cset "name='Headset Mic Switch' on"
 		cset "name='Record Path DC Blocking' on"
 	]
 
 	DisableSequence [
-		cdev "hw:bytmax98090"
+		cdev "hw:ROCKCHIPI2S"
+
 		cset "name='Headset Mic Switch' off"
-		cset "name='Int Mic Switch' on"
 		cset "name='DMIC Mux' DMIC"
+		cset "name='Int Mic Switch' on"
 		cset "name='Record Path DC Blocking' off"
 	]
 }
diff --git a/ucm-config/kip/byt-max98090/byt-max98090.conf b/ucm-config/veyron_fievel/ROCKCHIP-I2S/ROCKCHIP-I2S.conf
similarity index 69%
rename from ucm-config/kip/byt-max98090/byt-max98090.conf
rename to ucm-config/veyron_fievel/ROCKCHIP-I2S/ROCKCHIP-I2S.conf
index 0d51934..0c399b0 100644
--- a/ucm-config/kip/byt-max98090/byt-max98090.conf
+++ b/ucm-config/veyron_fievel/ROCKCHIP-I2S/ROCKCHIP-I2S.conf
@@ -1,4 +1,4 @@
-Comment "Kip internal card"
+Comment "Rockchip card"
 
 SectionUseCase."HiFi" {
 	File "HiFi.conf"
diff --git a/ucm-config/veyron_fievel/RockchipHDMI/HiFi.conf b/ucm-config/veyron_fievel/RockchipHDMI/HiFi.conf
new file mode 100644
index 0000000..890c97c
--- /dev/null
+++ b/ucm-config/veyron_fievel/RockchipHDMI/HiFi.conf
@@ -0,0 +1,14 @@
+SectionVerb {
+	Value {
+		OutputDspName ""
+		MinBufferLevel "512"
+	}
+}
+
+SectionDevice."HDMI".0 {
+	Value {
+		JackName "RockchipHDMI HDMI Jack"
+		OutputDspName ""
+		EDIDFile "/sys/class/drm/card1-HDMI-A-1/edid"
+	}
+}
diff --git a/ucm-config/kip/byt-max98090/byt-max98090.conf b/ucm-config/veyron_fievel/RockchipHDMI/RockchipHDMI.conf
similarity index 68%
copy from ucm-config/kip/byt-max98090/byt-max98090.conf
copy to ucm-config/veyron_fievel/RockchipHDMI/RockchipHDMI.conf
index 0d51934..a167d74 100644
--- a/ucm-config/kip/byt-max98090/byt-max98090.conf
+++ b/ucm-config/veyron_fievel/RockchipHDMI/RockchipHDMI.conf
@@ -1,4 +1,4 @@
-Comment "Kip internal card"
+Comment "Rockchip HDMI card"
 
 SectionUseCase."HiFi" {
 	File "HiFi.conf"
diff --git a/ucm-config/veyron_jaq/ROCKCHIP-I2S/HiFi.conf b/ucm-config/veyron_jaq/ROCKCHIP-I2S/HiFi.conf
index f30fa3d..4701262 100644
--- a/ucm-config/veyron_jaq/ROCKCHIP-I2S/HiFi.conf
+++ b/ucm-config/veyron_jaq/ROCKCHIP-I2S/HiFi.conf
@@ -2,6 +2,7 @@
 	Value {
 		OutputDspName "speaker_eq"
 		MinBufferLevel "512"
+		FullySpecifiedUCM "1"
 	}
 
 	EnableSequence [
@@ -52,8 +53,45 @@
 	]
 }
 
+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"
 		OutputDspName ""
 	}
@@ -62,6 +100,7 @@
 		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"
 	]
@@ -70,12 +109,16 @@
 
 		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"
 	}
 
diff --git a/ucm-config/veyron_jaq/RockchipHDMI/HiFi.conf b/ucm-config/veyron_jaq/RockchipHDMI/HiFi.conf
index fc2eaaa..890c97c 100644
--- a/ucm-config/veyron_jaq/RockchipHDMI/HiFi.conf
+++ b/ucm-config/veyron_jaq/RockchipHDMI/HiFi.conf
@@ -1,6 +1,7 @@
 SectionVerb {
 	Value {
 		OutputDspName ""
+		MinBufferLevel "512"
 	}
 }
 
diff --git a/ucm-config/veyron_minnie/ROCKCHIP-I2S/HiFi.conf b/ucm-config/veyron_minnie/ROCKCHIP-I2S/HiFi.conf
index 4032e60..ce29a3f 100644
--- a/ucm-config/veyron_minnie/ROCKCHIP-I2S/HiFi.conf
+++ b/ucm-config/veyron_minnie/ROCKCHIP-I2S/HiFi.conf
@@ -2,6 +2,7 @@
 	Value {
 		OutputDspName "speaker_eq"
 		MinBufferLevel "512"
+		FullySpecifiedUCM "1"
 	}
 
 	EnableSequence [
@@ -47,8 +48,45 @@
 	]
 }
 
+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"
 		OutputDspName ""
 	}
@@ -57,6 +95,7 @@
 		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"
 	]
@@ -65,12 +104,16 @@
 
 		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"
 	}
 
@@ -92,25 +135,3 @@
 		cset "name='Record Path DC Blocking' off"
 	]
 }
-
-SectionModifier."Speaker Swap Mode".0 {
-	Comment "Swap the left and right channels of speaker."
-
-	EnableSequence [
-		cdev "hw:ROCKCHIPI2S"
-
-		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 [
-		cdev "hw:ROCKCHIPI2S"
-
-		cset "name='Left Speaker Mixer Right DAC Switch' off"
-		cset "name='Right Speaker Mixer Left DAC Switch' off"
-		cset "name='Left Speaker Mixer Left DAC Switch' on"
-		cset "name='Right Speaker Mixer Right DAC Switch' on"
-	]
-}
diff --git a/ucm-config/veyron_minnie/RockchipHDMI/HiFi.conf b/ucm-config/veyron_minnie/RockchipHDMI/HiFi.conf
index fc2eaaa..890c97c 100644
--- a/ucm-config/veyron_minnie/RockchipHDMI/HiFi.conf
+++ b/ucm-config/veyron_minnie/RockchipHDMI/HiFi.conf
@@ -1,6 +1,7 @@
 SectionVerb {
 	Value {
 		OutputDspName ""
+		MinBufferLevel "512"
 	}
 }
 
diff --git a/ucm-config/veyron_speedy/ROCKCHIP-I2S/HiFi.conf b/ucm-config/veyron_speedy/ROCKCHIP-I2S/HiFi.conf
index 0177feb..ce29a3f 100644
--- a/ucm-config/veyron_speedy/ROCKCHIP-I2S/HiFi.conf
+++ b/ucm-config/veyron_speedy/ROCKCHIP-I2S/HiFi.conf
@@ -2,6 +2,7 @@
 	Value {
 		OutputDspName "speaker_eq"
 		MinBufferLevel "512"
+		FullySpecifiedUCM "1"
 	}
 
 	EnableSequence [
@@ -47,8 +48,45 @@
 	]
 }
 
+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"
 		OutputDspName ""
 	}
@@ -57,6 +95,7 @@
 		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"
 	]
@@ -65,12 +104,16 @@
 
 		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"
 	}
 
diff --git a/ucm-config/veyron_speedy/RockchipHDMI/HiFi.conf b/ucm-config/veyron_speedy/RockchipHDMI/HiFi.conf
index fc2eaaa..890c97c 100644
--- a/ucm-config/veyron_speedy/RockchipHDMI/HiFi.conf
+++ b/ucm-config/veyron_speedy/RockchipHDMI/HiFi.conf
@@ -1,6 +1,7 @@
 SectionVerb {
 	Value {
 		OutputDspName ""
+		MinBufferLevel "512"
 	}
 }
 
diff --git a/ucm-config/winky/byt-max98090/HiFi.conf b/ucm-config/winky/byt-max98090/HiFi.conf
deleted file mode 100644
index 75577c0..0000000
--- a/ucm-config/winky/byt-max98090/HiFi.conf
+++ /dev/null
@@ -1,97 +0,0 @@
-SectionVerb {
-	Value {
-		OutputDspName "speaker_eq"
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-
-		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='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='Headphone Left Switch' on"
-		cset "name='Headphone Right Switch' on"
-		cset "name='Headphone Switch' off"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-
-		cset "name='Speaker Left Switch' on"
-		cset "name='Speaker Right Switch' on"
-		cset "name='Ext Spk Switch' on"
-	]
-
-	DisableSequence [
-	]
-}
-
-SectionDevice."Headphone".0 {
-	Value {
-		JackName "byt-max98090 Headphone Jack"
-		OutputDspName ""
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Speaker Left Switch' off"
-		cset "name='Speaker Right Switch' off"
-		cset "name='Headphone Switch' on"
-		cset "name='HP Left Out Switch' on"
-		cset "name='HP Right Out Switch' on"
-	]
-	DisableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='HP Left Out Switch' off"
-		cset "name='HP Right Out Switch' off"
-		cset "name='Headphone Switch' off"
-		cset "name='Speaker Left Switch' on"
-		cset "name='Speaker Right Switch' on"
-	]
-}
-
-SectionDevice."Mic".0 {
-	Value {
-		JackName "byt-max98090 Mic Jack"
-		CaptureControl "MIC2"
-	}
-
-	EnableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Int Mic Switch' off"
-		cset "name='Headset Mic Switch' on"
-		cset "name='DMIC Mux' ADC"
-		cset "name='Record Path DC Blocking' on"
-	]
-
-	DisableSequence [
-		cdev "hw:bytmax98090"
-		cset "name='Headset Mic Switch' off"
-		cset "name='Int Mic Switch' on"
-		cset "name='DMIC Mux' DMIC"
-		cset "name='Record Path DC Blocking' off"
-	]
-}
diff --git a/ucm-config/winky/byt-max98090/byt-max98090.conf b/ucm-config/winky/byt-max98090/byt-max98090.conf
deleted file mode 100644
index 915d155..0000000
--- a/ucm-config/winky/byt-max98090/byt-max98090.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-Comment "Rambi internal card"
-
-SectionUseCase."HiFi" {
-	File "HiFi.conf"
-	Comment "Default"
-}
diff --git a/upstart/cras.conf b/upstart/cras.conf
deleted file mode 100644
index 28c13e2..0000000
--- a/upstart/cras.conf
+++ /dev/null
@@ -1,57 +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.
-
-# Installed by ADHD package.
-# cras upstart job.
-
-description     "ChromeOS audio server"
-author          "chromium-os-dev@chromium.org"
-
-env CRAS_SOCKET_DIR=/var/run/cras
-
-start on starting system-services
-stop on stopping system-services
-respawn
-
-# Allow the audio server real time priority.
-limit rtprio 12 12
-
-pre-start script
-  mkdir -p -m 1770 "${CRAS_SOCKET_DIR}"
-  chown -R cras:cras "${CRAS_SOCKET_DIR}"
-end script
-
-script
-  # Use /etc/cras/enable_hfp as a flag to enable HFP/HSP
-  # support for testing purpose. In test image, touch or
-  # rm this file to toggle this feature.
-  # TODO(hychao): remove this temporary  flag when we pass
-  # qualification and ready to launch this feature.
-  if [ -f /etc/cras/enable_hfp ]; then
-    ENABLE_HFP="--enable_hfp"
-  else
-    ENABLE_HFP=""
-  fi
-
-  # For Samus only, check which dsp.ini to load.
-  if [ "$(mosys platform name)" = "Samus" ]; then
-    local hw_version="$(mosys platform version)"
-    if [ "$hw_version" = "MP.A" ] ||
-       [ "$hw_version" = "EVT" ] ||
-       [ "$hw_version" = "DVT" ] ||
-       [ "$hw_version" = "PVT" ]; then
-         DSP_CONFIG="--dsp_config=/etc/cras/dsp.samus.orig.ini"
-    fi
-  fi
-  # For board needs different device configs, check which config
-  # directory to use. Use that directory for both volume curves
-  # and dsp config.
-  if [ -f /etc/cras/get_device_config_dir ]; then
-    local device_config_dir="$(sh /etc/cras/get_device_config_dir)"
-    DEVICE_CONFIG_DIR="--device_config_dir=${device_config_dir}"
-    DSP_CONFIG="--dsp_config=${device_config_dir}/dsp.ini"
-  fi
-  exec minijail0 -u cras -g cras -G -- /usr/bin/cras ${ENABLE_HFP} \
-      ${DSP_CONFIG} ${DEVICE_CONFIG_DIR}
-end script