[automerger skipped] DO NOT MERGE - Merge pi-dev@5234907 into stage-aosp-master
am: 1782629867 -s ours
am skip reason: subject contains skip directive
Change-Id: Ifec292c1fba4aa39e9acae496723bd3273bbb60f
diff --git a/.gitignore b/.gitignore
index 2854f05..98c5c08 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,2 @@
build
cscope.*
-PRESUBMIT.cfg
diff --git a/OWNERS b/OWNERS
index 85070ac..79cff9d 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,6 +1,18 @@
-dgreid@chromium.org
-chinyue@chromium.org
cychiang@chromium.org
-
-# For bluetooth support.
+dgreid@chromium.org
hychao@chromium.org
+louiscollard@chromium.org
+paulhsia@chromium.org
+tzungbi@chromium.org
+wuchengli@chromium.org
+yuhsuan@chromium.org
+
+# For AOSP
+cychiang@google.com
+dgreid@google.com
+hychao@google.com
+louiscollard@google.com
+paulhsia@google.com
+tzungbi@google.com
+wuchengli@google.com
+yuhsuan@google.com
diff --git a/PRESUBMIT.cfg b/PRESUBMIT.cfg
new file mode 100644
index 0000000..d4d5410
--- /dev/null
+++ b/PRESUBMIT.cfg
@@ -0,0 +1,2 @@
+[Hook Overrides]
+tab_check: false
diff --git a/cras-config/cyan/cht-bsw-max98090 b/cras-config/cyan/cht-bsw-max98090
new file mode 100644
index 0000000..fba7d1b
--- /dev/null
+++ b/cras-config/cyan/cht-bsw-max98090
@@ -0,0 +1,107 @@
+[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/.gitignore b/cras/.gitignore
index 40d4c80..dcbb180 100644
--- a/cras/.gitignore
+++ b/cras/.gitignore
@@ -1,23 +1,23 @@
*.o
+*.la
*.lo
+*.a
+*.so*
+*.log
*.d
-src/cras
-src/libcras.a
-src/libcras.so*
-tags
-libcras.pc
-src/mix_unittest
-src/cras_monitor
-src/cras_test_client
-src/*_unittest
+*.swp
+*.trs
+.deps
+.dirstamp
+.libs
+
/.__autoconf_trace_data
/.elibtoolized
-/Makefile
-/Makefile.in
/aclocal.m4
/ar-lib
-autom4te.cache/
+/autom4te.cache/
/compile
+/config.cache
/config.guess
/config.log
/config.status
@@ -29,37 +29,17 @@
/ltmain.sh
/missing
/test-driver
-src/*.la
-src/*.log
-src/*.trs
-src/.libs/
-src/Makefile
-src/Makefile.in
-src/common/.deps/
-src/common/.dirstamp
-src/common/cras_version.h
-src/.deps/
-src/libcras.la
-src/libcras/.deps/
-src/libcras/.dirstamp
-src/server/.deps/
-src/server/.dirstamp
-src/server/config/.deps/
-src/server/config/.dirstamp
-src/tests/.deps/
-src/tests/.dirstamp
-src/alsa_plugin/.deps/
-src/alsa_plugin/.dirstamp
-src/libasound_module_ctl_cras.la
-src/libasound_module_pcm_cras.la
-src/cmpraw
-src/crossover_test
-src/crossover2_test
-src/drc_test
-src/dsp/.deps/
-src/dsp/.dirstamp
-src/dsp/tests/.deps/
-src/dsp/tests/.dirstamp
-src/dsp_util_test
-src/eq2_test
-src/eq_test
+Makefile.in
+
+tags
+/Makefile
+/libcras.pc
+/src/Makefile
+/src/*_unittest
+/src/*_test
+/src/cmpraw
+/src/cras
+/src/cras_monitor
+/src/cras_router
+/src/cras_test_client
+/src/common/cras_version.h
diff --git a/cras/README.dbus-api b/cras/README.dbus-api
index 819f068..653bc5d 100644
--- a/cras/README.dbus-api
+++ b/cras/README.dbus-api
@@ -58,6 +58,10 @@
boolean input_mute
boolean output_user_mute
+ void GetDefaultOutputBufferSize()
+
+ Returns the default output buffer size in frames.
+
{dict},{dict},... GetNodes()
Returns information about nodes. A node can be either
@@ -118,6 +122,11 @@
The string is empty if the node type is
not HOTWORD.
+ void GetSystemAecSupported();
+
+ Returns 1 if system echo cancellation is supported,
+ otherwise return 0.
+
void SetActiveOutputNode(uint64 node_id);
Requests the specified node to be used for
@@ -145,6 +154,13 @@
Returns the number of streams currently using output hardware.
+ int32 IsAudioOutputActive()
+
+ Returns 1 if there are currently any active output streams,
+ excluding 'dummy' streams that are not actually outputting any
+ audio. Returns 0 if there are no active streams, or all active
+ streams are 'dummy' streams.
+
void SetGlobalOutputChannelRemix(int32 num_channels,
array:double coefficient)
@@ -217,3 +233,12 @@
NumberOfActiveStreamsChanged(int32 num_active_streams)
Indicates the number of active streams has changed.
+
+ AudioOutputActiveStateChanged(boolean active)
+
+ Indicates active output state has changed.
+ See IsAudioOutputActive for details.
+
+ HotwordTriggered(int64 tv_sec, int64 tv_nsec)
+
+ Indicates that hotword was triggered at the given timestamp.
diff --git a/cras/configure.ac b/cras/configure.ac
index 97d0849..f00011c 100644
--- a/cras/configure.ac
+++ b/cras/configure.ac
@@ -5,7 +5,7 @@
[cras], [http://www.chromium.org/])
AC_PREREQ([2.59])
-AC_CANONICAL_TARGET
+AC_CANONICAL_HOST
AM_INIT_AUTOMAKE([1.10 -Wall no-define])
#AC_CONFIG_HEADERS([config.h])
@@ -18,23 +18,45 @@
AC_LANG_C
AM_PROG_CC_C_O
PKG_PROG_PKG_CONFIG
-#AC_CONFIG_FILES([Makefile src/Makefile libcras.pc])
+AC_CONFIG_FILES([Makefile src/Makefile libcras.pc])
PKG_CHECK_MODULES([LIBSPEEX], [ speexdsp >= 1.2 ])
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)
+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)
+ AS_VAR_APPEND([DBUS_CFLAGS], [" -DCRAS_DBUS"])
else
DBUS_CFLAGS=
- AC_SUBST(DBUS_CFLAGS)
DBUS_LIBS=
- AC_SUBST(DBUS_LIBS)
fi
+AC_SUBST(DBUS_LIBS)
+AC_SUBST(DBUS_CFLAGS)
+
+AC_ARG_ENABLE([selinux], AS_HELP_STRING([--enable-selinux], [Enable SELinux calls]), have_selinux=$enableval, have_selinux=no)
+AM_CONDITIONAL(HAVE_SELINUX, test "$have_selinux" = "yes")
+if test "$have_selinux" = "yes"; then
+ PKG_CHECK_MODULES([SELINUX], [ libselinux ])
+ AS_VAR_APPEND([SELINUX_CFLAGS], [" -DCRAS_SELINUX"])
+else
+ SELINUX_CFLAGS=
+ SELINUX_LIBS=
+fi
+AC_SUBST(SELINUX_CFLAGS)
+AC_SUBST(SELINUX_LIBS)
+
+# WEBRTC APM support
+AC_ARG_ENABLE([webrtc-apm], AS_HELP_STRING([--disable-webrtc-apm], [Disable webrtc-apm uses]), have_webrtc_apm=$enableval, have_webrtc_apm=yes)
+AM_CONDITIONAL(HAVE_WEBRTC_APM, test "$have_webrtc_apm" = "yes")
+if test "$have_webrtc_apm" = "yes"; then
+ PKG_CHECK_MODULES([WEBRTC_APM], [ libwebrtc_apm ])
+ AC_DEFINE(HAVE_WEBRTC_APM, 1, [Define to use webrtc-apm.])
+else
+ WEBRTC_APM_LIBS=
+fi
+AC_SUBST(WEBRTC_APM_LIBS)
PKG_CHECK_MODULES([SBC], [ sbc >= 1.0 ])
AC_CHECK_LIB(asound, snd_pcm_ioplug_create,,
@@ -43,6 +65,15 @@
AC_ARG_ENABLE([alsa-plugin], AS_HELP_STRING([--disable-alsa-plugin],
[Disable building of ALSA plugin]))
+AC_ARG_ENABLE([metrics], AS_HELP_STRING([--enable-metrics], [Enable metrics uses]), have_metrics=$enableval, have_metrics=no)
+if test "$have_metrics" = "yes"; then
+ AC_DEFINE(HAVE_LIB_METRICS, 1, [Define to use libmetrics])
+ METRICS_LIBS=-lmetrics-${BASE_VER}
+else
+ METRICS_LIBS=
+fi
+AC_SUBST(METRICS_LIBS)
+
# Determine ALSA plugin directory.
test "x$prefix" = xNONE && prefix=$ac_default_prefix
test "x$exec_prefix" = xNONE && exec_prefix=$prefix
@@ -78,24 +109,9 @@
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],
- [location of the iniparser headers, defaults to /usr/include/])],
- [INIPARSER_CFLAGS="-I$withval"],
- [INIPARSER_CFLAGS='-I/usr/include'])
-AC_SUBST([INIPARSER_CFLAGS])
-
-AC_ARG_WITH([iniparser-lib-path],
- [AS_HELP_STRING([--with-iniparser-lib-path],
- [location of the iniparser libraries])],
- [INIPARSER_LIBS="-L$withval -liniparser"],
- [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
+if test "x$host_cpu" != xx86_64; then
have_sse42=no
fi
if test "$have_sse42" = "yes"; then
@@ -107,7 +123,7 @@
# 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
+if test "x$host_cpu" != xx86_64; then
have_avx=no
fi
if test "$have_avx" = "yes"; then
@@ -119,7 +135,7 @@
# 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
+if test "x$host_cpu" != xx86_64; then
have_avx2=no
fi
if test "$have_avx2" = "yes"; then
@@ -131,7 +147,7 @@
# 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
+if test "x$host_cpu" != xx86_64; then
have_fma=no
fi
if test "$have_fma" = "yes"; then
@@ -141,20 +157,12 @@
AM_CONDITIONAL(HAVE_FMA, test "$have_fma" = "yes")
AC_SUBST(FMA_CFLAGS)
-AC_OUTPUT([
- Makefile
- src/Makefile
- libcras.pc
+AC_OUTPUT
+
+AC_MSG_NOTICE([
+
+Enable SSE42: ${have_sse42}
+Enable AVX: ${have_avx}
+Enable AVX2: ${have_avx2}
+Enable FMA: ${have_fma}
])
-
-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/Makefile.am b/cras/src/Makefile.am
index e83e948..ddb53fa 100644
--- a/cras/src/Makefile.am
+++ b/cras/src/Makefile.am
@@ -29,6 +29,15 @@
CRAS_FMA =
endif
+if HAVE_WEBRTC_APM
+CRAS_WEBRTC_APM_SOURCES = \
+ server/cras_apm_list.c \
+ server/config/aec_config.c \
+ server/config/apm_config.c
+else
+CRAS_WEBRTC_APM_SOURCES =
+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
@@ -56,13 +65,24 @@
server/cras_a2dp_endpoint.c \
server/cras_a2dp_info.c \
server/cras_a2dp_iodev.c \
- server/cras_telephony.c
+ server/cras_telephony.c \
+ server/cras_utf8.c
else
CRAS_DBUS_SOURCES =
endif
-cras_SOURCES = \
+if HAVE_SELINUX
+CRAS_SELINUX_SOURCES = common/cras_selinux_helper.c
+CRAS_SELINUX_UNITTEST_SOURCES = tests/cras_selinux_helper_unittest.c
+else
+CRAS_SELINUX_SOURCES =
+CRAS_SELINUX_UNITTEST_SOURCES =
+endif
+
+cras_server_SOURCES = \
$(CRAS_DBUS_SOURCES) \
+ $(CRAS_SELINUX_SOURCES) \
+ $(CRAS_WEBRTC_APM_SOURCES) \
common/cras_audio_format.c \
common/cras_checksum.c \
common/cras_config.c \
@@ -75,6 +95,7 @@
dsp/biquad.c \
dsp/crossover.c \
dsp/crossover2.c \
+ dsp/dcblock.c \
dsp/drc.c \
dsp/drc_kernel.c \
dsp/drc_math.c \
@@ -83,9 +104,9 @@
dsp/eq2.c \
server/audio_thread.c \
server/buffer_share.c \
+ server/config/cras_board_config.c \
server/config/cras_card_config.c \
server/config/cras_device_blacklist.c \
- server/cras.c \
server/cras_alert.c \
server/cras_alsa_card.c \
server/cras_alsa_helpers.c \
@@ -96,6 +117,7 @@
server/cras_alsa_ucm.c \
server/cras_alsa_ucm_section.c \
server/cras_audio_area.c \
+ server/cras_audio_thread_monitor.c \
server/cras_device_monitor.c \
server/cras_dsp.c \
server/cras_dsp_ini.c \
@@ -106,29 +128,54 @@
server/cras_expr.c \
server/cras_fmt_conv.c \
server/cras_gpio_jack.c \
+ server/cras_hotword_handler.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_non_empty_audio_handler.c \
server/cras_observer.c \
server/cras_ramp.c \
server/cras_rclient.c \
server/cras_rstream.c \
- server/cras_utf8.c \
- server/cras_server.c \
server/cras_server_metrics.c \
server/cras_system_state.c \
server/cras_tm.c \
server/cras_udev.c \
server/cras_volume_curve.c \
+ server/dev_io.c \
server/dev_stream.c \
+ server/input_data.c \
server/linear_resampler.c \
+ server/polled_interval_checker.c \
+ server/server_stream.c \
server/stream_list.c \
server/test_iodev.c \
server/rate_estimator.c \
server/softvol_curve.c
+libcrasserver_la_SOURCES = \
+ $(cras_server_SOURCES)
+libcrasserver_la_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \
+ -I$(top_srcdir)/src/dsp -I$(top_srcdir)/src/server \
+ -I$(top_srcdir)/src/server/config \
+ $(DBUS_CFLAGS) $(SBC_CFLAGS) $(SELINUX_CFLAGS)
+libcrasserver_la_LIBADD = \
+ libcrasmix.la \
+ $(CRAS_SSE4_2) \
+ $(CRAS_AVX) \
+ $(CRAS_AVX2) \
+ $(CRAS_FMA) \
+ -lpthread -lasound -lrt -liniparser -ludev -ldl -lm -lspeexdsp \
+ $(SBC_LIBS) \
+ $(DBUS_LIBS) \
+ $(SELINUX_LIBS)
+
+cras_SOURCES = \
+ server/cras.c \
+ server/cras_server.c
+
cras_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \
-I$(top_srcdir)/src/dsp -I$(top_srcdir)/src/server \
-I$(top_srcdir)/src/server/config \
@@ -136,20 +183,24 @@
cras_LDADD = \
libcrasmix.la \
+ libcrasserver.la \
$(CRAS_SSE4_2) \
$(CRAS_AVX) \
$(CRAS_AVX2) \
$(CRAS_FMA) \
-lpthread -lasound -lrt -liniparser -ludev -ldl -lm -lspeexdsp \
+ $(METRICS_LIBS) \
$(SBC_LIBS) \
- $(DBUS_LIBS)
+ $(DBUS_LIBS) \
+ $(WEBRTC_APM_LIBS)
noinst_LTLIBRARIES = \
$(CRAS_SSE4_2) \
$(CRAS_AVX) \
$(CRAS_AVX2) \
$(CRAS_FMA) \
- libcrasmix.la
+ libcrasmix.la \
+ libcrasserver.la
libcrasmix_la_SOURCES = \
server/cras_mix_ops.c
@@ -265,11 +316,20 @@
DBUS_TESTS =
endif
+if HAVE_WEBRTC_APM
+CRAS_WEBRTC_APM_TESTS = \
+ apm_list_unittest
+else
+CRAS_WEBRTC_APM_TESTS =
+endif
+
TESTS = \
$(DBUS_TESTS) \
+ $(CRAS_WEBRTC_APM_TESTS) \
audio_area_unittest \
audio_format_unittest \
audio_thread_unittest \
+ audio_thread_monitor_unittest \
alert_unittest \
alsa_card_unittest \
alsa_helpers_unittest \
@@ -283,6 +343,7 @@
cras_client_unittest \
cras_tm_unittest \
device_monitor_unittest \
+ dev_io_unittest \
dev_stream_unittest \
device_blacklist_unittest \
dsp_core_unittest \
@@ -293,6 +354,7 @@
edid_utils_unittest \
expr_unittest \
file_wait_unittest \
+ float_buffer_unittest \
fmt_conv_unittest \
hfp_info_unittest \
buffer_share_unittest \
@@ -302,6 +364,7 @@
mix_unittest \
linear_resampler_unittest \
observer_unittest \
+ polled_interval_checker_unittest \
ramp_unittest \
rate_estimator_unittest \
rclient_unittest \
@@ -311,6 +374,7 @@
softvol_curve_unittest \
stream_list_unittest \
system_state_unittest \
+ timing_unittest \
utf8_unittest \
util_unittest \
volume_curve_unittest
@@ -357,45 +421,53 @@
check_PROGRAMS += \
crossover_test \
crossover2_test \
+ dcblock_test \
drc_test \
dsp_util_test \
eq_test \
eq2_test \
cmpraw
+DSP_INCLUDE_PATHS = -I$(top_srcdir)/src/dsp -I$(top_srcdir)/src/common
+
crossover_test_SOURCES = dsp/crossover.c dsp/biquad.c dsp/dsp_util.c \
dsp/tests/crossover_test.c dsp/tests/dsp_test_util.c dsp/tests/raw.c
crossover_test_LDADD = -lrt -lm
-crossover_test_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/dsp
+crossover_test_CPPFLAGS = $(COMMON_CPPFLAGS) $(DSP_INCLUDE_PATHS)
crossover2_test_SOURCES = dsp/crossover2.c dsp/biquad.c dsp/dsp_util.c \
dsp/tests/crossover2_test.c dsp/tests/dsp_test_util.c dsp/tests/raw.c
crossover2_test_LDADD = -lrt -lm
-crossover2_test_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/dsp
+crossover2_test_CPPFLAGS = $(COMMON_CPPFLAGS) $(DSP_INCLUDE_PATHS)
+
+dcblock_test_SOURCES = dsp/dcblock.c dsp/dsp_util.c dsp/tests/dcblock_test.c \
+ dsp/tests/dsp_test_util.c dsp/tests/raw.c
+dcblock_test_LDADD = -lrt -lm
+dcblock_test_CPPFLAGS = $(COMMON_CPPFLAGS) $(DSP_INCLUDE_PATHS)
drc_test_SOURCES = dsp/drc.c dsp/drc_kernel.c dsp/drc_math.c \
dsp/crossover2.c dsp/eq2.c dsp/biquad.c dsp/dsp_util.c \
dsp/tests/drc_test.c dsp/tests/dsp_test_util.c dsp/tests/raw.c
drc_test_LDADD = -lrt -lm
-drc_test_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/dsp
+drc_test_CPPFLAGS = $(COMMON_CPPFLAGS) $(DSP_INCLUDE_PATHS)
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
+dsp_util_test_CPPFLAGS = $(COMMON_CPPFLAGS) $(DSP_INCLUDE_PATHS) -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
-eq_test_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/dsp
+eq_test_CPPFLAGS = $(COMMON_CPPFLAGS) $(DSP_INCLUDE_PATHS)
eq2_test_SOURCES = dsp/biquad.c dsp/eq2.c dsp/dsp_util.c dsp/tests/eq2_test.c \
dsp/tests/dsp_test_util.c dsp/tests/raw.c
eq2_test_LDADD = -lrt -lm
-eq2_test_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/dsp
+eq2_test_CPPFLAGS = $(COMMON_CPPFLAGS) $(DSP_INCLUDE_PATHS)
cmpraw_SOURCES = dsp/tests/cmpraw.c dsp/tests/raw.c
cmpraw_LDADD = -lm
-cmpraw_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/dsp
+cmpraw_CPPFLAGS = $(COMMON_CPPFLAGS) $(DSP_INCLUDE_PATHS)
# unit tests
alert_unittest_SOURCES = tests/alert_unittest.cc \
@@ -450,7 +522,8 @@
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 \
+alsa_io_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) $(DBUS_CFLAGS) \
+ -I$(top_srcdir)/src/server \
-I$(top_srcdir)/src/common \
-I$(top_srcdir)/src/server/config
alsa_io_unittest_LDADD = -lgtest -lpthread
@@ -481,15 +554,32 @@
-I$(top_srcdir)/src/server/config
alsa_ucm_unittest_LDADD = -lgtest -lpthread
+if HAVE_WEBRTC_APM
+apm_list_unittest_SOURCES = tests/apm_list_unittest.cc \
+ server/cras_apm_list.c
+apm_list_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) \
+ $(DSP_INCLUDE_PATHS) \
+ -I$(top_srcdir)/src/server \
+ -I$(top_srcdir)/src/server/config \
+ $(WEBRTC_APM_CFLAGS)
+apm_list_unittest_LDADD = -lgtest
+endif
+
array_unittest_SOURCES = tests/array_unittest.cc
array_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common
array_unittest_LDADD = -lgtest -lpthread
-audio_thread_unittest_SOURCES = tests/audio_thread_unittest.cc
+audio_thread_unittest_SOURCES = tests/audio_thread_unittest.cc \
+ server/dev_io.c tests/empty_audio_stub.cc
audio_thread_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) \
-I$(top_srcdir)/src/common -I$(top_srcdir)/src/server
audio_thread_unittest_LDADD = -lgtest -lpthread -lrt
+audio_thread_monitor_unittest_SOURCES = tests/audio_thread_monitor_unittest.cc
+audio_thread_monitor_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) \
+ -I$(top_srcdir)/src/common -I$(top_srcdir)/src/server
+audio_thread_monitor_unittest_LDADD = -lgtest -lpthread -lrt
+
if HAVE_DBUS
bt_device_unittest_SOURCES = tests/bt_device_unittest.cc \
server/cras_bt_device.c
@@ -529,6 +619,39 @@
-I$(top_srcdir)/src/server
cras_tm_unittest_LDADD = -lgtest -lpthread
+dev_io_unittest_SOURCES = \
+ $(CRAS_SELINUX_UNITTEST_SOURCES) \
+ common/cras_audio_format.c \
+ common/cras_shm.c \
+ server/cras_audio_area.c \
+ server/cras_fmt_conv.c \
+ server/cras_mix.c \
+ server/cras_mix_ops.c \
+ server/dev_io.c \
+ server/dev_stream.c \
+ server/linear_resampler.c \
+ tests/dev_io_stubs.cc \
+ tests/iodev_stub.cc \
+ tests/empty_audio_stub.cc \
+ tests/rstream_stub.cc \
+ tests/dev_io_unittest.cc
+dev_io_unittest_CXXFLAGS = \
+ -std=c++11 -Wno-noexcept-type
+dev_io_unittest_CPPFLAGS = \
+ $(COMMON_CPPFLAGS) \
+ -I$(top_srcdir)/src/common \
+ -I$(top_srcdir)/src/server \
+ -I$(top_srcdir)/src/server/config \
+ $(SELINUX_CFLAGS)
+dev_io_unittest_LDADD = \
+ libcrasmix.la \
+ $(CRAS_SSE4_2) \
+ $(CRAS_AVX) \
+ $(CRAS_AVX2) \
+ $(CRAS_FMA) \
+ $(SELINUX_LIBS) \
+ -lgtest -lrt -lpthread -ldl -lm -lspeexdsp
+
dev_stream_unittest_SOURCES = tests/dev_stream_unittest.cc \
server/dev_stream.c
dev_stream_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) \
@@ -550,7 +673,7 @@
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
-dsp_core_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/dsp
+dsp_core_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) $(DSP_INCLUDE_PATHS)
dsp_core_unittest_LDADD = -lgtest -lpthread
dsp_ini_unittest_SOURCES = tests/dsp_ini_unittest.cc \
@@ -562,16 +685,16 @@
dsp_pipeline_unittest_SOURCES = tests/cras_dsp_pipeline_unittest.cc \
server/cras_dsp_ini.c server/cras_expr.c server/cras_dsp_pipeline.c \
common/dumper.c dsp/dsp_util.c
-dsp_pipeline_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \
- -I$(top_srcdir)/src/server -I$(top_srcdir)/src/dsp
+dsp_pipeline_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) \
+ -I$(top_srcdir)/src/server $(DSP_INCLUDE_PATHS)
dsp_pipeline_unittest_LDADD = -lgtest -lrt -liniparser -lpthread
dsp_unittest_SOURCES = tests/dsp_unittest.cc \
server/cras_dsp.c server/cras_dsp_ini.c server/cras_dsp_pipeline.c \
server/cras_expr.c common/dumper.c dsp/dsp_util.c \
dsp/tests/dsp_test_util.c
-dsp_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \
- -I$(top_srcdir)/src/server -I$(top_srcdir)/src/dsp
+dsp_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) \
+ -I$(top_srcdir)/src/server $(DSP_INCLUDE_PATHS)
dsp_unittest_LDADD = -lgtest -lrt -liniparser -lpthread
dumper_unittest_SOURCES = tests/dumper_unittest.cc common/dumper.c
@@ -593,6 +716,12 @@
$(CRAS_UT_TMPDIR_CFLAGS)
file_wait_unittest_LDADD = -lgtest -lpthread
+
+float_buffer_unittest_SOURCES = tests/float_buffer_unittest.cc
+float_buffer_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \
+ -I$(top_srcdir)/src/server
+float_buffer_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
@@ -664,6 +793,12 @@
-I$(top_srcdir)/src/server
observer_unittest_LDADD = -lgtest -lpthread
+polled_interval_checker_unittest_SOURCES = tests/polled_interval_checker_unittest.cc \
+ server/polled_interval_checker.c
+polled_interval_checker_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \
+ -I$(top_srcdir)/src/server
+polled_interval_checker_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
@@ -680,10 +815,11 @@
rclient_unittest_LDADD = -lgtest -lpthread
rstream_unittest_SOURCES = tests/rstream_unittest.cc server/cras_rstream.c \
- common/cras_shm.c
+ common/cras_shm.c $(CRAS_SELINUX_UNITTEST_SOURCES)
rstream_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \
- -I$(top_srcdir)/src/server
-rstream_unittest_LDADD = -lasound -lgtest -lpthread -lrt
+ -I$(top_srcdir)/src/server $(SELINUX_CFLAGS)
+rstream_unittest_LDADD = $(SELINUX_LIBS) \
+ -lasound -lgtest -lpthread -lrt
server_metrics_unittest_SOURCES = tests/server_metrics_unittest.cc
server_metrics_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \
@@ -706,11 +842,46 @@
stream_list_unittest_LDADD = -lgtest -lpthread
system_state_unittest_SOURCES = tests/system_state_unittest.cc \
- server/cras_system_state.c common/cras_shm.c
+ server/cras_system_state.c common/cras_shm.c \
+ server/config/cras_board_config.c $(CRAS_SELINUX_UNITTEST_SOURCES)
system_state_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) \
-I$(top_srcdir)/src/common -I$(top_srcdir)/src/server \
- -I$(top_srcdir)/src/server/config
-system_state_unittest_LDADD = -lgtest -lrt -lpthread
+ -I$(top_srcdir)/src/server/config $(SELINUX_CFLAGS)
+system_state_unittest_LDADD = $(SELINUX_LIBS) \
+ -lgtest -liniparser -lpthread -lrt
+
+timing_unittest_SOURCES = \
+ $(CRAS_SELINUX_UNITTEST_SOURCES) \
+ common/cras_audio_format.c \
+ common/cras_shm.c \
+ server/cras_audio_area.c \
+ server/cras_fmt_conv.c \
+ server/cras_mix.c \
+ server/cras_mix_ops.c \
+ server/dev_io.c \
+ server/dev_stream.c \
+ server/linear_resampler.c \
+ tests/dev_io_stubs.cc \
+ tests/iodev_stub.cc \
+ tests/empty_audio_stub.cc \
+ tests/rstream_stub.cc \
+ tests/timing_unittest.cc
+timing_unittest_CXXFLAGS = \
+ -std=c++11 -Wno-noexcept-type
+timing_unittest_CPPFLAGS = \
+ $(COMMON_CPPFLAGS) \
+ -I$(top_srcdir)/src/common \
+ -I$(top_srcdir)/src/server \
+ -I$(top_srcdir)/src/server/config \
+ $(SELINUX_CFLAGS)
+timing_unittest_LDADD = \
+ libcrasmix.la \
+ $(CRAS_SSE4_2) \
+ $(CRAS_AVX) \
+ $(CRAS_AVX2) \
+ $(CRAS_FMA) \
+ $(SELINUX_LIBS) \
+ -lgtest -lrt -lpthread -ldl -lm -lspeexdsp
utf8_unittest_SOURCES = tests/utf8_unittest.cc server/cras_utf8.c
utf8_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \
diff --git a/cras/src/alsa_plugin/pcm_cras.c b/cras/src/alsa_plugin/pcm_cras.c
index 93b61a3..f30c7cd 100644
--- a/cras/src/alsa_plugin/pcm_cras.c
+++ b/cras/src/alsa_plugin/pcm_cras.c
@@ -143,7 +143,8 @@
sample_bytes = snd_pcm_format_physical_width(io->format) / 8;
if (io->stream == SND_PCM_STREAM_PLAYBACK) {
- if (io->state != SND_PCM_STATE_RUNNING) {
+ if (io->state != SND_PCM_STATE_RUNNING &&
+ io->state != SND_PCM_STATE_DRAINING) {
memset(samples, 0, nframes * frame_bytes);
return nframes;
}
@@ -228,29 +229,6 @@
return cras_client_connect(pcm_cras->client);
}
-/* Get the pcm boundary value. */
-static int get_boundary(snd_pcm_t *pcm, snd_pcm_uframes_t *boundary)
-{
- snd_pcm_sw_params_t *sw_params;
- int rc;
-
- snd_pcm_sw_params_alloca(&sw_params);
-
- rc = snd_pcm_sw_params_current(pcm, sw_params);
- if (rc < 0) {
- fprintf(stderr, "sw_params_current: %s\n", snd_strerror(rc));
- return rc;
- }
-
- rc = snd_pcm_sw_params_get_boundary(sw_params, boundary);
- if (rc < 0) {
- fprintf(stderr, "get_boundary: %s\n", snd_strerror(rc));
- return rc;
- }
-
- return 0;
-}
-
/* Called when an ALSA stream is started. */
static int snd_pcm_cras_start(snd_pcm_ioplug_t *io)
{
@@ -300,71 +278,8 @@
return rc;
}
-static int snd_pcm_cras_delay(snd_pcm_ioplug_t *io, snd_pcm_sframes_t *delayp)
-{
- snd_pcm_uframes_t limit;
- int rc;
- struct snd_pcm_cras *pcm_cras;
- struct timespec latency;
-
- pcm_cras = (struct snd_pcm_cras *)io->private_data;
-
- rc = get_boundary(io->pcm, &limit);
- if ((rc < 0) || (limit == 0)) {
- *delayp = 0;
- return -EINVAL;
- }
-
- /* To get the delay, first calculate the latency between now and the
- * playback/capture sample time for the old hw_ptr we tracked, note that
- * for playback path this latency could be negative. Then add it up with
- * the difference between appl_ptr and the old hw_ptr.
- */
- if (io->stream == SND_PCM_STREAM_PLAYBACK) {
- /* Do not compute latency if playback_sample_time is not set */
- if (pcm_cras->playback_sample_time.tv_sec == 0 &&
- pcm_cras->playback_sample_time.tv_nsec == 0) {
- latency.tv_sec = 0;
- latency.tv_nsec = 0;
- } else {
- cras_client_calc_playback_latency(
- &pcm_cras->playback_sample_time,
- &latency);
- }
-
- *delayp = limit +
- io->appl_ptr -
- pcm_cras->playback_sample_index +
- latency.tv_sec * io->rate +
- latency.tv_nsec / (1000000000L / (long)io->rate);
- } else {
- /* Do not compute latency if capture_sample_time is not set */
- if (pcm_cras->capture_sample_time.tv_sec == 0 &&
- pcm_cras->capture_sample_time.tv_nsec == 0) {
- latency.tv_sec = 0;
- latency.tv_nsec = 0;
- } else {
- cras_client_calc_capture_latency(
- &pcm_cras->capture_sample_time,
- &latency);
- }
-
- *delayp = limit +
- pcm_cras->capture_sample_index -
- io->appl_ptr +
- latency.tv_sec * io->rate +
- latency.tv_nsec / (1000000000L / (long)io->rate);
- }
-
- /* Both appl and hw pointers wrap at the pcm boundary. */
- *delayp %= limit;
-
- return 0;
-}
-
static snd_pcm_ioplug_callback_t cras_pcm_callback = {
.close = snd_pcm_cras_close,
- .delay = snd_pcm_cras_delay,
.start = snd_pcm_cras_start,
.stop = snd_pcm_cras_stop,
.pointer = snd_pcm_cras_pointer,
diff --git a/cras/src/common/byte_buffer.h b/cras/src/common/byte_buffer.h
index c949b71..2470d26 100644
--- a/cras/src/common/byte_buffer.h
+++ b/cras/src/common/byte_buffer.h
@@ -3,6 +3,9 @@
* found in the LICENSE file.
*/
+#ifndef BYTE_BUFFER_H_
+#define BYTE_BUFFER_H_
+
#include <stdint.h>
#include <stdlib.h>
#include <sys/param.h>
@@ -36,12 +39,13 @@
}
/* Destory a byte_buffer created with byte_buffer_create. */
-static inline void byte_buffer_destroy(struct byte_buffer *buf)
+static inline void byte_buffer_destroy(struct byte_buffer **buf)
{
- free(buf);
+ free(*buf);
+ *buf = NULL;
}
-static inline unsigned int buf_writable_bytes(struct byte_buffer *buf)
+static inline unsigned int buf_writable(struct byte_buffer *buf)
{
if (buf->level >= buf->used_size)
return 0;
@@ -51,7 +55,7 @@
return buf->used_size - buf->write_idx;
}
-static inline unsigned int buf_readable_bytes(struct byte_buffer *buf)
+static inline unsigned int buf_readable(struct byte_buffer *buf)
{
if (buf->level == 0)
return 0;
@@ -62,12 +66,12 @@
return buf->used_size - buf->read_idx;
}
-static inline unsigned int buf_queued_bytes(struct byte_buffer *buf)
+static inline unsigned int buf_queued(struct byte_buffer *buf)
{
return buf->level;
}
-static inline unsigned int buf_available_bytes(const struct byte_buffer *buf)
+static inline unsigned int buf_available(const struct byte_buffer *buf)
{
return buf->used_size - buf->level;
}
@@ -80,7 +84,7 @@
static inline uint8_t *buf_read_pointer_size(struct byte_buffer *buf,
unsigned int *readable)
{
- *readable = buf_readable_bytes(buf);
+ *readable = buf_readable(buf);
return buf_read_pointer(buf);
}
@@ -100,7 +104,7 @@
static inline uint8_t *buf_write_pointer_size(struct byte_buffer *buf,
unsigned int *writeable)
{
- *writeable = buf_writable_bytes(buf);
+ *writeable = buf_writable(buf);
return buf_write_pointer(buf);
}
@@ -120,3 +124,5 @@
buf->read_idx = 0;
buf->level = 0;
}
+
+#endif /* BYTE_BUFFER_H_ */
diff --git a/cras/src/common/cras_audio_format.c b/cras/src/common/cras_audio_format.c
index 3ff3ccb..e25c20c 100644
--- a/cras/src/common/cras_audio_format.c
+++ b/cras/src/common/cras_audio_format.c
@@ -19,8 +19,8 @@
/* FL FR RL RR FC LFE SL SR RC FLC FRC */
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* FL */
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* FR */
- { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* RL */
- { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* RR */
+ { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 }, /* RL */
+ { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 }, /* RR */
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* FC */
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* LFE */
{ 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, /* SL */
diff --git a/cras/src/common/cras_audio_format.h b/cras/src/common/cras_audio_format.h
index 516a18d..acef686 100644
--- a/cras/src/common/cras_audio_format.h
+++ b/cras/src/common/cras_audio_format.h
@@ -124,6 +124,16 @@
return (size_t)bytes * fmt->num_channels;
}
+/* Sets channel layout to a default value where channels [0, num_channels] are
+ * placed to the same position of its channel index, otherwise set to -1. */
+static inline void cras_audio_format_set_default_channel_layout(
+ struct cras_audio_format *format)
+{
+ unsigned int i;
+ for (i = 0; i < CRAS_CH_MAX; i++)
+ format->channel_layout[i] = i < format->num_channels ? i : -1;
+}
+
/* Create an audio format structure. */
struct cras_audio_format *cras_audio_format_create(snd_pcm_format_t format,
size_t frame_rate,
diff --git a/cras/src/common/cras_file_wait.c b/cras/src/common/cras_file_wait.c
index 809bc68..5304f40 100644
--- a/cras/src/common/cras_file_wait.c
+++ b/cras/src/common/cras_file_wait.c
@@ -18,7 +18,7 @@
#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)
+#define CRAS_FILE_WAIT_FLAG_MOCK_RACE (1u << 31)
struct cras_file_wait {
cras_file_wait_callback_t callback;
diff --git a/cras/src/common/cras_iodev_info.h b/cras/src/common/cras_iodev_info.h
index 9b1dbb5..bc3c18c 100644
--- a/cras/src/common/cras_iodev_info.h
+++ b/cras/src/common/cras_iodev_info.h
@@ -7,7 +7,7 @@
#define CRAS_IODEV_INFO_H_
#include <stddef.h>
-#include <sys/time.h>
+#include <stdint.h>
#define CRAS_IODEV_NAME_BUFFER_SIZE 64
#define CRAS_NODE_TYPE_BUFFER_SIZE 32
diff --git a/cras/src/common/cras_messages.h b/cras/src/common/cras_messages.h
index ab962df..c08d2bb 100644
--- a/cras/src/common/cras_messages.h
+++ b/cras/src/common/cras_messages.h
@@ -16,10 +16,14 @@
/* Rev when message format changes. If new messages are added, or message ID
* values change. */
-#define CRAS_PROTO_VER 1
+#define CRAS_PROTO_VER 2
#define CRAS_SERV_MAX_MSG_SIZE 256
#define CRAS_CLIENT_MAX_MSG_SIZE 256
#define CRAS_HOTWORD_NAME_MAX_SIZE 8
+#define CRAS_MAX_HOTWORD_MODELS 244
+#define CRAS_MAX_REMIX_CHANNELS 32
+#define CRAS_MAX_TEST_DATA_LEN 224
+#define CRAS_AEC_DUMP_FILE_NAME_LEN 128
/* Message IDs. */
enum CRAS_SERVER_MESSAGE_ID {
@@ -39,6 +43,7 @@
CRAS_SERVER_RELOAD_DSP,
CRAS_SERVER_DUMP_DSP_INFO,
CRAS_SERVER_DUMP_AUDIO_THREAD,
+ CRAS_SERVER_DUMP_SNAPSHOTS,
CRAS_SERVER_ADD_ACTIVE_NODE,
CRAS_SERVER_RM_ACTIVE_NODE,
CRAS_SERVER_ADD_TEST_DEV,
@@ -49,6 +54,8 @@
CRAS_SERVER_GET_HOTWORD_MODELS,
CRAS_SERVER_SET_HOTWORD_MODEL,
CRAS_SERVER_REGISTER_NOTIFICATION,
+ CRAS_SERVER_SET_AEC_DUMP,
+ CRAS_SERVER_RELOAD_AEC_CONFIG,
};
enum CRAS_CLIENT_MESSAGE_ID {
@@ -100,7 +107,28 @@
uint32_t flags;
struct cras_audio_format_packed format; /* rate, channel, sample size */
uint32_t dev_idx; /* device to attach stream, 0 if none */
+ uint64_t effects; /* Bit map of requested effects. */
};
+
+/*
+ * Old version of connect message without 'effects' member defined.
+ * Used to check against when receiving invalid size of connect message.
+ * Expected to have proto_version set to 1.
+ * TODO(hychao): remove when all clients migrate to latest libcras.
+ */
+struct __attribute__ ((__packed__)) cras_connect_message_old {
+ struct cras_server_message header;
+ uint32_t proto_version;
+ enum CRAS_STREAM_DIRECTION direction; /* input/output/loopback */
+ cras_stream_id_t stream_id; /* unique id for this stream */
+ enum CRAS_STREAM_TYPE stream_type; /* media, or call, etc. */
+ uint32_t buffer_frames; /* Buffer size in frames. */
+ uint32_t cb_threshold; /* callback client when this much is left */
+ uint32_t flags;
+ struct cras_audio_format_packed format; /* rate, channel, sample size */
+ uint32_t dev_idx; /* device to attach stream, 0 if none */
+};
+
static inline void cras_fill_connect_message(struct cras_connect_message *m,
enum CRAS_STREAM_DIRECTION direction,
cras_stream_id_t stream_id,
@@ -108,6 +136,7 @@
size_t buffer_frames,
size_t cb_threshold,
uint32_t flags,
+ uint64_t effects,
struct cras_audio_format format,
uint32_t dev_idx)
{
@@ -118,6 +147,7 @@
m->buffer_frames = buffer_frames;
m->cb_threshold = cb_threshold;
m->flags = flags;
+ m->effects = effects;
pack_cras_audio_format(&m->format, &format);
m->dev_idx = dev_idx;
m->header.id = CRAS_SERVER_CONNECT_STREAM;
@@ -325,6 +355,18 @@
m->header.length = sizeof(*m);
}
+/* Dump current audio thread snapshots to shard memory with the client. */
+struct __attribute__ ((__packed__)) cras_dump_snapshots {
+ struct cras_server_message header;
+};
+
+static inline void cras_fill_dump_snapshots(
+ struct cras_dump_snapshots *m)
+{
+ m->header.id = CRAS_SERVER_DUMP_SNAPSHOTS;
+ m->header.length = sizeof(*m);
+}
+
/* Add a test device. */
struct __attribute__ ((__packed__)) cras_add_test_dev {
struct cras_server_message header;
@@ -345,7 +387,7 @@
unsigned int command;
unsigned int iodev_idx;
unsigned int data_len;
- uint8_t data[];
+ uint8_t data[CRAS_MAX_TEST_DATA_LEN];
};
static inline void cras_fill_test_dev_command(struct cras_test_dev_command *m,
@@ -369,11 +411,14 @@
m->length = sizeof(*m);
}
-/* Configures the global remix converter. */
+/*
+ * Configures the global remix converter.
+ * `num_channels` must be less than `CRAS_MAX_REMIX_CHANNELS`.
+ */
struct __attribute__ ((__packed__)) cras_config_global_remix {
struct cras_server_message header;
unsigned int num_channels;
- float coefficient[];
+ float coefficient[CRAS_MAX_REMIX_CHANNELS];
};
static inline void cras_fill_config_global_remix_command(
@@ -421,6 +466,35 @@
memcpy(m->model_name, model_name, CRAS_HOTWORD_NAME_MAX_SIZE);
}
+/* Set aec dump to start or stop. */
+struct __attribute__ ((__packed__)) cras_set_aec_dump {
+ struct cras_server_message header;
+ cras_stream_id_t stream_id;
+ unsigned int start;
+};
+
+static inline void cras_fill_set_aec_dump_message(
+ struct cras_set_aec_dump *m,
+ cras_stream_id_t stream_id,
+ unsigned int start)
+{
+ m->header.id = CRAS_SERVER_SET_AEC_DUMP;
+ m->header.length = sizeof(*m);
+ m->stream_id = stream_id;
+ m->start = start;
+}
+
+/* Reload the aec configuration. */
+struct __attribute__ ((__packed__)) cras_reload_aec_config {
+ struct cras_server_message header;
+};
+static inline void cras_fill_reload_aec_config(
+ struct cras_reload_aec_config *m)
+{
+ m->header.id = CRAS_SERVER_RELOAD_AEC_CONFIG;
+ m->header.length = sizeof(*m);
+}
+
struct __attribute__ ((__packed__)) cras_register_notification {
struct cras_server_message header;
uint32_t msg_id;
@@ -465,12 +539,40 @@
cras_stream_id_t stream_id;
struct cras_audio_format_packed format;
uint32_t shm_max_size;
+ uint64_t effects;
+};
+/*
+ * Old version of stream connected message without effects defined.
+ * TODO(hychao): remove when all clients migrate to latest libcras.
+ */
+struct __attribute__ ((__packed__)) cras_client_stream_connected_old {
+ struct cras_client_message header;
+ int32_t err;
+ cras_stream_id_t stream_id;
+ struct cras_audio_format_packed format;
+ uint32_t shm_max_size;
};
static inline void cras_fill_client_stream_connected(
struct cras_client_stream_connected *m,
int err,
cras_stream_id_t stream_id,
struct cras_audio_format *format,
+ size_t shm_max_size,
+ uint64_t effects)
+{
+ m->err = err;
+ m->stream_id = stream_id;
+ pack_cras_audio_format(&m->format, format);
+ m->shm_max_size = shm_max_size;
+ m->effects = effects;
+ m->header.id = CRAS_CLIENT_STREAM_CONNECTED;
+ m->header.length = sizeof(struct cras_client_stream_connected);
+}
+static inline void cras_fill_client_stream_connected_old(
+ struct cras_client_stream_connected_old *m,
+ int err,
+ cras_stream_id_t stream_id,
+ struct cras_audio_format *format,
size_t shm_max_size)
{
m->err = err;
@@ -478,7 +580,7 @@
pack_cras_audio_format(&m->format, format);
m->shm_max_size = shm_max_size;
m->header.id = CRAS_CLIENT_STREAM_CONNECTED;
- m->header.length = sizeof(struct cras_client_stream_connected);
+ m->header.length = sizeof(struct cras_client_stream_connected_old);
}
/* Sent from server to client when audio debug information is requested. */
@@ -496,7 +598,7 @@
struct cras_client_get_hotword_models_ready {
struct cras_client_message header;
int32_t hotword_models_size;
- uint8_t hotword_models[0];
+ uint8_t hotword_models[CRAS_MAX_HOTWORD_MODELS];
};
static inline void cras_fill_client_get_hotword_models_ready(
struct cras_client_get_hotword_models_ready *m,
@@ -640,6 +742,7 @@
enum CRAS_AUDIO_MESSAGE_ID {
AUDIO_MESSAGE_REQUEST_DATA,
AUDIO_MESSAGE_DATA_READY,
+ AUDIO_MESSAGE_DATA_CAPTURED,
NUM_AUDIO_MESSAGES
};
diff --git a/cras/src/common/cras_metrics.c b/cras/src/common/cras_metrics.c
index ac005ff..dc5957a 100644
--- a/cras/src/common/cras_metrics.c
+++ b/cras/src/common/cras_metrics.c
@@ -7,30 +7,55 @@
#include <syslog.h>
#include <unistd.h>
-#define METRICS_CLIENT "metrics_client"
+#ifdef HAVE_LIB_METRICS
+#include <metrics/c_metrics_library.h>
void cras_metrics_log_event(const char *event)
{
- syslog(LOG_DEBUG, "Log event: %s", event);
- if (!fork()) {
- const char *argv[] = {METRICS_CLIENT, "-v", event, NULL} ;
- execvp(argv[0], (char * const *)argv);
- _exit(1);
- }
+ CMetricsLibrary handle;
+
+ syslog(LOG_DEBUG, "UMA event: %s", event);
+ handle = CMetricsLibraryNew();
+ CMetricsLibraryInit(handle);
+ CMetricsLibrarySendCrosEventToUMA(handle, event);
+ CMetricsLibraryDelete(handle);
}
void cras_metrics_log_histogram(const char *name, int sample, int min,
int max, int nbuckets)
{
- if (!fork()) {
- char tmp[4][16];
- snprintf(tmp[0], 16, "%d", sample);
- snprintf(tmp[1], 16, "%d", min);
- snprintf(tmp[2], 16, "%d", max);
- snprintf(tmp[3], 16, "%d", nbuckets);
- const char *argv[] = {METRICS_CLIENT, name, tmp[0], tmp[1],
- tmp[2], tmp[3], NULL};
- execvp(argv[0], (char * const *)argv);
- _exit(1);
- }
+ CMetricsLibrary handle;
+
+ syslog(LOG_DEBUG, "UMA name: %s", name);
+ handle = CMetricsLibraryNew();
+ CMetricsLibraryInit(handle);
+ CMetricsLibrarySendToUMA(handle, name, sample, min, max, nbuckets);
+ CMetricsLibraryDelete(handle);
}
+
+void cras_metrics_log_sparse_histogram(const char *name, int sample)
+{
+ CMetricsLibrary handle;
+
+ syslog(LOG_DEBUG, "UMA name: %s", name);
+ handle = CMetricsLibraryNew();
+ CMetricsLibraryInit(handle);
+ CMetricsLibrarySendSparseToUMA(handle, name, sample);
+ CMetricsLibraryDelete(handle);
+}
+
+#else
+void cras_metrics_log_event(const char *event)
+{
+}
+void cras_metrics_log_histogram(const char *name, int sample, int min,
+ int max, int nbuckets)
+{
+}
+void cras_metrics_log_enum_histogram(const char *name, int sample, int max)
+{
+}
+void cras_metrics_log_sparse_histogram(const char *name, int sample)
+{
+}
+#endif
diff --git a/cras/src/common/cras_metrics.h b/cras/src/common/cras_metrics.h
index 2d482eb..82c1d21 100644
--- a/cras/src/common/cras_metrics.h
+++ b/cras/src/common/cras_metrics.h
@@ -13,4 +13,7 @@
void cras_metrics_log_histogram(const char *name, int sample, int min,
int max, int nbuckets);
+/* Sends sparse histogram data. */
+void cras_metrics_log_sparse_histogram(const char *name, int sample);
+
#endif /* CRAS_METRICS_H_ */
diff --git a/cras/src/common/cras_observer_ops.h b/cras/src/common/cras_observer_ops.h
index 7265d98..108f166 100644
--- a/cras/src/common/cras_observer_ops.h
+++ b/cras/src/common/cras_observer_ops.h
@@ -49,6 +49,12 @@
void (*num_active_streams_changed)(void *context,
enum CRAS_STREAM_DIRECTION dir,
uint32_t num_active_streams);
+ /* Hotword triggered. */
+ void (*hotword_triggered)(void *context,
+ int64_t tv_sec, int64_t tv_nsec);
+ /* State regarding whether non-empty audio is being played/captured has
+ * changed. */
+ void (*non_empty_audio_state_changed)(void *context, int non_empty);
};
#endif /* CRAS_OBSERVER_OPS_H */
diff --git a/cras/src/common/cras_selinux_helper.c b/cras/src/common/cras_selinux_helper.c
new file mode 100644
index 0000000..59717bb
--- /dev/null
+++ b/cras/src/common/cras_selinux_helper.c
@@ -0,0 +1,11 @@
+// Copyright 2018 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 <selinux/restorecon.h>
+
+#include "cras_shm.h"
+
+int cras_selinux_restorecon(const char *pathname) {
+ return selinux_restorecon(pathname, 0);
+}
diff --git a/cras/src/common/cras_shm.c b/cras/src/common/cras_shm.c
index 6b4085a..bf42347 100644
--- a/cras/src/common/cras_shm.c
+++ b/cras/src/common/cras_shm.c
@@ -12,10 +12,40 @@
#endif
#include <errno.h>
#include <syslog.h>
+#include <stdio.h>
#include <string.h>
#include "cras_shm.h"
+/* Set the correct SELinux label for SHM fds. */
+static void cras_shm_restorecon(int fd)
+{
+#ifdef CRAS_SELINUX
+ char fd_proc_path[64];
+
+ if (snprintf(fd_proc_path, sizeof(fd_proc_path), "/proc/self/fd/%d", fd) < 0) {
+ syslog(LOG_WARNING,
+ "Couldn't construct proc symlink path of fd: %d", fd);
+ return;
+ }
+
+ /* Get the actual file-path for this fd. */
+ char *path = realpath(fd_proc_path, NULL);
+ if (path == NULL) {
+ syslog(LOG_WARNING, "Couldn't run realpath() for %s: %s",
+ fd_proc_path, strerror(errno));
+ return;
+ }
+
+ if (cras_selinux_restorecon(path) < 0) {
+ syslog(LOG_WARNING, "Restorecon on %s failed: %s",
+ fd_proc_path, strerror(errno));
+ }
+
+ free(path);
+#endif
+}
+
#ifdef __BIONIC__
int cras_shm_open_rw (const char *name, size_t size)
@@ -73,6 +103,9 @@
name, strerror(-rc));
return rc;
}
+
+ cras_shm_restorecon(fd);
+
return fd;
}
@@ -96,3 +129,30 @@
}
#endif
+
+void *cras_shm_setup(const char *name,
+ size_t mmap_size,
+ int *rw_fd_out,
+ int *ro_fd_out)
+{
+ int rw_shm_fd = cras_shm_open_rw(name, mmap_size);
+ if (rw_shm_fd < 0)
+ return NULL;
+
+ /* mmap shm. */
+ void *exp_state = mmap(NULL, mmap_size,
+ PROT_READ | PROT_WRITE, MAP_SHARED,
+ rw_shm_fd, 0);
+ if (exp_state == (void *)-1)
+ return NULL;
+
+ /* Open a read-only copy to dup and pass to clients. */
+ int ro_shm_fd = cras_shm_reopen_ro(name, rw_shm_fd);
+ if (ro_shm_fd < 0)
+ return NULL;
+
+ *rw_fd_out = rw_shm_fd;
+ *ro_fd_out = ro_shm_fd;
+
+ return exp_state;
+}
diff --git a/cras/src/common/cras_shm.h b/cras/src/common/cras_shm.h
index 9a54132..df5dfd8 100644
--- a/cras/src/common/cras_shm.h
+++ b/cras/src/common/cras_shm.h
@@ -150,11 +150,17 @@
unsigned i = shm->area->write_buf_idx & CRAS_SHM_BUFFERS_MASK;
unsigned write_offset;
const unsigned frame_bytes = shm->config.frame_bytes;
+ unsigned written;
write_offset = cras_shm_check_write_offset(shm,
shm->area->write_offset[i]);
- if (frames)
- *frames = limit_frames - (write_offset / frame_bytes);
+ written = write_offset / frame_bytes;
+ if (frames) {
+ if (limit_frames >= written)
+ *frames = limit_frames - written;
+ else
+ *frames = 0;
+ }
return cras_shm_buff_for_idx(shm, i) + write_offset;
}
@@ -353,13 +359,10 @@
buf_idx = (buf_idx + 1) & CRAS_SHM_BUFFERS_MASK;
if (remainder < area->write_offset[buf_idx]) {
area->read_offset[buf_idx] = remainder;
- } else {
- area->read_offset[buf_idx] = 0;
+ } else if (remainder) {
+ /* Read all of this buffer too. */
area->write_offset[buf_idx] = 0;
- if (remainder) {
- /* Read all of this buffer too. */
- buf_idx = (buf_idx + 1) & CRAS_SHM_BUFFERS_MASK;
- }
+ buf_idx = (buf_idx + 1) & CRAS_SHM_BUFFERS_MASK;
}
area->read_buf_idx = buf_idx;
}
@@ -510,4 +513,31 @@
*/
void cras_shm_close_unlink (const char *name, int fd);
+/*
+ * Configure shared memory for the system state.
+ * Args:
+ * name - Name of the shared-memory area.
+ * mmap_size - Amount of shared memor to map.
+ * rw_fd_out - Filled with the RW fd for the shm region.
+ * ro_fd_out - Filled with the RO fd for the shm region.
+ * Returns a pointer to the new shared memory region. Or NULL on error.
+ */
+void *cras_shm_setup(const char *name,
+ size_t mmap_size,
+ int *rw_fd_out,
+ int *ro_fd_out);
+
+#ifdef CRAS_SELINUX
+/*
+ * Wrapper around selinux_restorecon(). This is helpful in unit tests because
+ * we can mock out the selinux_restorecon() behaviour there. That is required
+ * because selinux_restorecon() would fail in the unit tests, since there
+ * is no file_contexts file.
+ * Args:
+ * pathname - Name of the file on which to run restorecon
+ * Returns 0 on success, otherwise -1 and errno is set appropriately.
+ */
+int cras_selinux_restorecon(const char *pathname);
+#endif
+
#endif /* CRAS_SHM_H_ */
diff --git a/cras/src/common/cras_types.h b/cras/src/common/cras_types.h
index 6c0ce0f..d0463fa 100644
--- a/cras/src/common/cras_types.h
+++ b/cras/src/common/cras_types.h
@@ -26,6 +26,7 @@
NO_DEVICE,
SILENT_RECORD_DEVICE,
SILENT_PLAYBACK_DEVICE,
+ SILENT_HOTWORD_DEVICE,
MAX_SPECIAL_DEVICE_IDX
};
@@ -66,11 +67,17 @@
* device is ready. Input streams only.
* HOTWORD_STREAM - This stream is used only to listen for hotwords such as "OK
* Google". Hardware will wake the device when this phrase is heard.
+ * TRIGGER_ONLY - This stream only wants to receive when the data is available
+ * and does not want to receive data. Used with HOTWORD_STREAM.
+ * SERVER_ONLY - This stream doesn't associate to a client. It's used mainly
+ * for audio data to flow from hardware through iodev's dsp pipeline.
*/
enum CRAS_INPUT_STREAM_FLAG {
BULK_AUDIO_OK = 0x01,
USE_DEV_TIMING = 0x02,
HOTWORD_STREAM = BULK_AUDIO_OK | USE_DEV_TIMING,
+ TRIGGER_ONLY = 0x04,
+ SERVER_ONLY = 0x08,
};
/*
@@ -109,6 +116,7 @@
CRAS_STREAM_TYPE_VOICE_COMMUNICATION,
CRAS_STREAM_TYPE_SPEECH_RECOGNITION,
CRAS_STREAM_TYPE_PRO_AUDIO,
+ CRAS_STREAM_TYPE_ACCESSIBILITY,
CRAS_STREAM_NUM_TYPES,
};
@@ -123,11 +131,20 @@
ENUM_STR(CRAS_STREAM_TYPE_VOICE_COMMUNICATION)
ENUM_STR(CRAS_STREAM_TYPE_SPEECH_RECOGNITION)
ENUM_STR(CRAS_STREAM_TYPE_PRO_AUDIO)
+ ENUM_STR(CRAS_STREAM_TYPE_ACCESSIBILITY)
default:
return "INVALID_STREAM_TYPE";
}
}
+/* Effects that can be enabled for a CRAS stream. */
+enum CRAS_STREAM_EFFECT {
+ APM_ECHO_CANCELLATION = (1 << 0),
+ APM_NOISE_SUPRESSION = (1 << 1),
+ APM_GAIN_CONTROL = (1 << 2),
+ APM_VOICE_DETECTION = (1 << 3),
+};
+
/* Information about a client attached to the server. */
struct __attribute__ ((__packed__)) cras_attached_client_info {
uint32_t id;
@@ -160,6 +177,7 @@
#define CRAS_MAX_IODEVS 20
#define CRAS_MAX_IONODES 20
#define CRAS_MAX_ATTACHED_CLIENTS 20
+#define CRAS_MAX_AUDIO_THREAD_SNAPSHOTS 10
#define CRAS_HOTWORD_STRING_SIZE 256
#define MAX_DEBUG_DEVS 4
#define MAX_DEBUG_STREAMS 8
@@ -204,6 +222,7 @@
AUDIO_THREAD_ODEV_LEAVE_NO_STREAMS,
AUDIO_THREAD_ODEV_DEFAULT_NO_STREAMS,
AUDIO_THREAD_FILL_ODEV_ZEROS,
+ AUDIO_THREAD_UNDERRUN,
AUDIO_THREAD_SEVERE_UNDERRUN,
};
@@ -234,6 +253,7 @@
uint8_t direction;
uint32_t num_underruns;
uint32_t num_severe_underruns;
+ uint32_t highest_hw_level;
};
struct __attribute__ ((__packed__)) audio_stream_debug_info {
@@ -243,6 +263,7 @@
uint32_t stream_type;
uint32_t buffer_frames;
uint32_t cb_threshold;
+ uint64_t effects;
uint32_t flags;
uint32_t frame_rate;
uint32_t num_channels;
@@ -261,6 +282,35 @@
struct audio_thread_event_log log;
};
+/*
+ * All event enums should be less then AUDIO_THREAD_EVENT_TYPE_COUNT,
+ * or they will be ignored by the handler.
+ */
+enum CRAS_AUDIO_THREAD_EVENT_TYPE {
+ AUDIO_THREAD_EVENT_BUSYLOOP,
+ AUDIO_THREAD_EVENT_DEBUG,
+ AUDIO_THREAD_EVENT_SEVERE_UNDERRUN,
+ AUDIO_THREAD_EVENT_UNDERRUN,
+ AUDIO_THREAD_EVENT_TYPE_COUNT,
+};
+
+/*
+ * Structure of snapshot for audio thread.
+ */
+struct __attribute__ ((__packed__)) cras_audio_thread_snapshot {
+ struct timespec timestamp;
+ enum CRAS_AUDIO_THREAD_EVENT_TYPE event_type;
+ struct audio_debug_info audio_debug_info;
+};
+
+/*
+ * Ring buffer for storing snapshots.
+ */
+struct __attribute__ ((__packed__)) cras_audio_thread_snapshot_buffer{
+ struct cras_audio_thread_snapshot snapshots[
+ CRAS_MAX_AUDIO_THREAD_SNAPSHOTS];
+ int pos;
+};
/* The server state that is shared with clients.
* state_version - Version of this structure.
@@ -302,6 +352,11 @@
* audio_debug_info - Debug data filled in when a client requests it. This
* isn't protected against concurrent updating, only one client should
* use it.
+ * default_output_buffer_size - Default output buffer size in frames.
+ * non_empty_status - Whether any non-empty audio is being
+ * played/captured.
+ * aec_supported - Flag to indicate if system aec is supported.
+ * snapshot_buffer - ring buffer for storing audio thread snapshots.
*/
#define CRAS_SERVER_STATE_VERSION 2
struct __attribute__ ((packed, aligned(4))) cras_server_state {
@@ -334,6 +389,10 @@
uint32_t num_active_streams[CRAS_NUM_DIRECTIONS];
struct cras_timespec last_active_stream_time;
struct audio_debug_info audio_debug_info;
+ int32_t default_output_buffer_size;
+ int32_t non_empty_status;
+ int32_t aec_supported;
+ struct cras_audio_thread_snapshot_buffer snapshot_buffer;
};
/* Actions for card add/remove/change. */
diff --git a/cras/src/common/edid_utils.c b/cras/src/common/edid_utils.c
index 4a1a0a6..2707b55 100644
--- a/cras/src/common/edid_utils.c
+++ b/cras/src/common/edid_utils.c
@@ -684,11 +684,11 @@
"Input: %s, vid level %d, %s, %s %s %s %s sync, %dx%dcm, Gamma %f\n",
(edid_data[EDID_VIDEO_IN] & 0x80) ? "digital" : "analog",
(edid_data[EDID_VIDEO_IN]>>5) & 3,
- (edid_data[EDID_VIDEO_IN] * 0x10) ? "Blank to black" : "",
- (edid_data[EDID_VIDEO_IN] * 0x08) ? "Separate" : "",
- (edid_data[EDID_VIDEO_IN] * 0x04) ? "Composite" : "",
- (edid_data[EDID_VIDEO_IN] * 0x02) ? "On-green" : "",
- (edid_data[EDID_VIDEO_IN] * 0x01) ? "Serration V" : "",
+ (edid_data[EDID_VIDEO_IN] & 0x10) ? "Blank to black" : "",
+ (edid_data[EDID_VIDEO_IN] & 0x08) ? "Separate" : "",
+ (edid_data[EDID_VIDEO_IN] & 0x04) ? "Composite" : "",
+ (edid_data[EDID_VIDEO_IN] & 0x02) ? "On-green" : "",
+ (edid_data[EDID_VIDEO_IN] & 0x01) ? "Serration V" : "",
edid_data[EDID_MAX_HSIZE], edid_data[EDID_MAX_VSIZE],
1.0+((float)edid_data[EDID_GAMMA]/100.0));
diff --git a/cras/src/dsp/dcblock.c b/cras/src/dsp/dcblock.c
new file mode 100644
index 0000000..87379e4
--- /dev/null
+++ b/cras/src/dsp/dcblock.c
@@ -0,0 +1,42 @@
+/* Copyright 2017 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <stdlib.h>
+#include "dcblock.h"
+
+struct dcblock {
+ float R;
+ float x_prev;
+ float y_prev;
+};
+
+struct dcblock *dcblock_new(float R)
+{
+ struct dcblock *dcblock = (struct dcblock *)calloc(1, sizeof(*dcblock));
+ dcblock->R = R;
+ return dcblock;
+}
+
+void dcblock_free(struct dcblock *dcblock)
+{
+ free(dcblock);
+}
+
+/* This is the prototype of the processing loop. */
+void dcblock_process(struct dcblock *dcblock, float *data, int count)
+{
+ int n;
+ float x_prev = dcblock->x_prev;
+ float y_prev = dcblock->y_prev;
+ float R = dcblock->R;
+ for (n = 0; n < count; n++) {
+ float x = data[n];
+ data[n] = x - x_prev + R * y_prev;
+ y_prev = data[n];
+ x_prev = x;
+ }
+ dcblock->x_prev = x_prev;
+ dcblock->y_prev = y_prev;
+}
diff --git a/cras/src/dsp/dcblock.h b/cras/src/dsp/dcblock.h
new file mode 100644
index 0000000..b723470
--- /dev/null
+++ b/cras/src/dsp/dcblock.h
@@ -0,0 +1,39 @@
+/* Copyright (c) 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.
+ */
+
+#ifndef DCBLOCK_H_
+#define DCBLOCK_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* A DC blocking filter. */
+
+struct dcblock;
+
+/*
+ * Create a DC blocking filter.
+ *
+ * Transfer fn: (1 - z^-1) / (1 - R * z^-1)
+ */
+struct dcblock *dcblock_new(float R);
+
+/* Free a DC blocking filter. */
+void dcblock_free(struct dcblock *dcblock);
+
+/* Process a buffer of audio data through the filter.
+ * Args:
+ * dcblock - The filter we want to use.
+ * data - The array of audio samples.
+ * count - The number of elements in the data array to process.
+ */
+void dcblock_process(struct dcblock *dcblock, float *data, int count);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* DCBLOCK_H_ */
diff --git a/cras/src/dsp/dsp_util.c b/cras/src/dsp/dsp_util.c
index 6de9a4e..c95f0c5 100644
--- a/cras/src/dsp/dsp_util.c
+++ b/cras/src/dsp/dsp_util.c
@@ -3,6 +3,9 @@
* found in the LICENSE file.
*/
+#include <limits.h>
+#include <syslog.h>
+
#include "dsp_util.h"
#ifndef max
@@ -373,8 +376,8 @@
#define interleave_stereo interleave_stereo
#endif
-void dsp_util_deinterleave(int16_t *input, float *const *output, int channels,
- int frames)
+static void dsp_util_deinterleave_s16le(int16_t *input, float *const *output,
+ int channels, int frames)
{
float *output_ptr[channels];
int i, j;
@@ -394,8 +397,83 @@
*(output_ptr[j]++) = *input++ / 32768.0f;
}
-void dsp_util_interleave(float *const *input, int16_t *output, int channels,
- int frames)
+
+static void dsp_util_deinterleave_s24le(int32_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++, input++)
+ *(output_ptr[j]++) =
+ (*input << 8) / 2147483648.0f;
+}
+
+static void dsp_util_deinterleave_s243le(uint8_t *input, float *const *output,
+ int channels, int frames)
+{
+ float *output_ptr[channels];
+ int32_t sample;
+ 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++, input += 3) {
+ sample = 0;
+ memcpy((uint8_t *)&sample + 1, input, 3);
+ *(output_ptr[j]++) = sample / 2147483648.0f;
+ }
+}
+
+static void dsp_util_deinterleave_s32le(int32_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++, input++)
+ *(output_ptr[j]++) = *input / 2147483648.0f;
+}
+
+int dsp_util_deinterleave(uint8_t *input, float *const *output, int channels,
+ snd_pcm_format_t format, int frames)
+{
+ switch (format) {
+ case SND_PCM_FORMAT_S16_LE:
+ dsp_util_deinterleave_s16le((int16_t *)input, output,
+ channels, frames);
+ break;
+ case SND_PCM_FORMAT_S24_LE:
+ dsp_util_deinterleave_s24le((int32_t *)input, output,
+ channels, frames);
+ break;
+ case SND_PCM_FORMAT_S24_3LE:
+ dsp_util_deinterleave_s243le(input, output,
+ channels, frames);
+ break;
+ case SND_PCM_FORMAT_S32_LE:
+ dsp_util_deinterleave_s32le((int32_t *)input, output,
+ channels, frames);
+ break;
+ default:
+ syslog(LOG_ERR, "Invalid format to deinterleave");
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static void dsp_util_interleave_s16le(float *const *input, int16_t *output,
+ int channels, int frames)
{
float *input_ptr[channels];
int i, j;
@@ -418,6 +496,87 @@
}
}
+static void dsp_util_interleave_s24le(float *const *input, int32_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++, output++) {
+ float f = *(input_ptr[j]++) * 2147483648.0f;
+ f += (f >= 0) ? 0.5f : -0.5f;
+ *output = max((float)INT_MIN, min((float)INT_MAX, f));
+ *output >>= 8;
+ }
+}
+
+static void dsp_util_interleave_s243le(float *const *input, uint8_t *output,
+ int channels, int frames)
+{
+ float *input_ptr[channels];
+ int i, j;
+ int32_t tmp;
+
+ for (i = 0; i < channels; i++)
+ input_ptr[i] = input[i];
+
+ for (i = 0; i < frames; i++)
+ for (j = 0; j < channels; j++, output += 3) {
+ float f = *(input_ptr[j]++) * 2147483648.0f;
+ f += (f >= 0) ? 0.5f : -0.5f;
+ tmp = max((float)INT_MIN, min((float)INT_MAX, f));
+ tmp >>= 8;
+ memcpy(output, &tmp, 3);
+ }
+}
+
+static void dsp_util_interleave_s32le(float *const *input, int32_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++, output++) {
+ float f = *(input_ptr[j]++) * 2147483648.0f;
+ f += (f >= 0) ? 0.5f : -0.5f;
+ *output = max((float)INT_MIN, min((float)INT_MAX, f));
+ }
+}
+
+int dsp_util_interleave(float *const *input, uint8_t *output, int channels,
+ snd_pcm_format_t format, int frames)
+{
+ switch (format) {
+ case SND_PCM_FORMAT_S16_LE:
+ dsp_util_interleave_s16le(input, (int16_t *)output,
+ channels, frames);
+ break;
+ case SND_PCM_FORMAT_S24_LE:
+ dsp_util_interleave_s24le(input, (int32_t *)output,
+ channels, frames);
+ break;
+ case SND_PCM_FORMAT_S24_3LE:
+ dsp_util_interleave_s243le(input, output, channels, frames);
+ break;
+ case SND_PCM_FORMAT_S32_LE:
+ dsp_util_interleave_s32le(input, (int32_t *)output,
+ channels, frames);
+ break;
+ default:
+ syslog(LOG_ERR, "Invalid format to interleave");
+ return -EINVAL;
+ }
+ return 0;
+}
+
void dsp_enable_flush_denormal_to_zero()
{
#if defined(__i386__) || defined(__x86_64__)
diff --git a/cras/src/dsp/dsp_util.h b/cras/src/dsp/dsp_util.h
index 19fc35a..7ca227a 100644
--- a/cras/src/dsp/dsp_util.h
+++ b/cras/src/dsp/dsp_util.h
@@ -12,6 +12,8 @@
#include <stdint.h>
+#include "cras_audio_format.h"
+
/* Converts from interleaved int16_t samples to non-interleaved float samples.
* The int16_t samples have range [-32768, 32767], and the float samples have
* range [-1.0, 1.0].
@@ -20,9 +22,11 @@
* output - Pointers to output buffers. There are "channels" output buffers.
* channels - The number of samples per frame.
* frames - The number of frames to convert.
+ * Returns:
+ * Negative error if format isn't supported, otherwise 0.
*/
-void dsp_util_deinterleave(int16_t *input, float *const *output, int channels,
- int frames);
+int dsp_util_deinterleave(uint8_t *input, float *const *output, int channels,
+ snd_pcm_format_t format, int frames);
/* Converts from non-interleaved float samples to interleaved int16_t samples.
* The int16_t samples have range [-32768, 32767], and the float samples have
@@ -33,9 +37,11 @@
* frame.
* channels - The number of samples per frame.
* frames - The number of frames to convert.
+ * Returns:
+ * Negative error if format isn't supported, otherwise 0.
*/
-void dsp_util_interleave(float *const *input, int16_t *output, int channels,
- int frames);
+int dsp_util_interleave(float *const *input, uint8_t *output, int channels,
+ snd_pcm_format_t format, int frames);
/* Disables denormal numbers in floating point calculation. Denormal numbers
* happens often in IIR filters, and it can be very slow.
diff --git a/cras/src/dsp/tests/dcblock_test.c b/cras/src/dsp/tests/dcblock_test.c
new file mode 100644
index 0000000..2093a5d
--- /dev/null
+++ b/cras/src/dsp/tests/dcblock_test.c
@@ -0,0 +1,78 @@
+/* Copyright (c) 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 <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include "dsp_test_util.h"
+#include "dsp_util.h"
+#include "dcblock.h"
+#include "raw.h"
+
+#ifndef min
+#define min(a, b) ({ __typeof__(a) _a = (a); \
+ __typeof__(b) _b = (b); \
+ _a < _b ? _a : _b; })
+#endif
+
+static double tp_diff(struct timespec *tp2, struct timespec *tp1)
+{
+ return (tp2->tv_sec - tp1->tv_sec)
+ + (tp2->tv_nsec - tp1->tv_nsec) * 1e-9;
+}
+
+/* Processes a buffer of data chunk by chunk using the filter */
+static void process(struct dcblock *dcblock, float *data, int count)
+{
+ int start;
+ for (start = 0; start < count; start += 128)
+ dcblock_process(dcblock,
+ data + start,
+ min(128, count - start));
+}
+
+/* Runs the filters on an input file */
+static void test_file(const char *input_filename, const char *output_filename)
+{
+ size_t frames;
+ struct timespec tp1, tp2;
+ struct dcblock *dcblockl;
+ struct dcblock *dcblockr;
+
+ float *data = read_raw(input_filename, &frames);
+
+ dcblockl = dcblock_new(0.995);
+ dcblockr = dcblock_new(0.995);
+ clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tp1);
+ process(dcblockl, data, frames);
+ process(dcblockr, data+frames, frames);
+ clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tp2);
+ printf("processing takes %g seconds for %zu samples\n",
+ tp_diff(&tp2, &tp1), frames);
+ dcblock_free(dcblockl);
+ dcblock_free(dcblockr);
+
+ write_raw(output_filename, data, frames);
+ free(data);
+}
+
+int main(int argc, char **argv)
+{
+ dsp_enable_flush_denormal_to_zero();
+ if (dsp_util_has_denormal())
+ printf("denormal still supported?\n");
+ else
+ printf("denormal disabled\n");
+ dsp_util_clear_fp_exceptions();
+
+ if (argc == 3)
+ test_file(argv[1], argv[2]);
+ else
+ printf("Usage: dcblock_test input.raw output.raw\n");
+
+ dsp_util_print_fp_exceptions();
+ return 0;
+}
diff --git a/cras/src/dsp/tests/dsp_util_test.c b/cras/src/dsp/tests/dsp_util_test.c
index 3a22db3..f545fc8 100644
--- a/cras/src/dsp/tests/dsp_util_test.c
+++ b/cras/src/dsp/tests/dsp_util_test.c
@@ -113,8 +113,8 @@
/* measure optimized interleave */
for (i = 0; i < ITERATIONS; ++i) {
- dsp_util_interleave(out_floats_ptr_c, out_shorts_opt, 2,
- samples);
+ dsp_util_interleave(out_floats_ptr_c, (uint8_t *)out_shorts_opt,
+ 2, SND_PCM_FORMAT_S16_LE, samples);
}
max_diff = 0;
@@ -135,7 +135,8 @@
samples);
/* measure optimized deinterleave */
- dsp_util_deinterleave(in_shorts, out_floats_ptr_opt, 2, samples);
+ dsp_util_deinterleave((uint8_t *)in_shorts, out_floats_ptr_opt, 2,
+ SND_PCM_FORMAT_S16_LE, 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],
@@ -307,8 +308,9 @@
/* 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);
+ dsp_util_interleave(out_floats_ptr_c,
+ (uint8_t *)out_shorts_opt, 2,
+ SND_PCM_FORMAT_S16_LE, samples);
}
clock_gettime(CLOCK_MONOTONIC, &end); /* mark the end time */
diff = (BILLION * (end.tv_sec - start.tv_sec) +
@@ -344,8 +346,9 @@
/* 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);
+ dsp_util_deinterleave((uint8_t *)in_shorts,
+ out_floats_ptr_opt, 2,
+ SND_PCM_FORMAT_S16_LE, samples);
}
clock_gettime(CLOCK_MONOTONIC, &end); /* mark the end time */
diff = (BILLION * (end.tv_sec - start.tv_sec) +
@@ -373,4 +376,4 @@
free(out_shorts_opt);
return 0;
-}
\ No newline at end of file
+}
diff --git a/cras/src/fuzz/Dockerfile b/cras/src/fuzz/Dockerfile
new file mode 100644
index 0000000..cf08a6e
--- /dev/null
+++ b/cras/src/fuzz/Dockerfile
@@ -0,0 +1,57 @@
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# Defines a docker image that can build cras fuzzers.
+#
+FROM gcr.io/oss-fuzz-base/base-builder
+
+RUN apt-get -y update && \
+ apt-get install -y \
+ automake \
+ build-essential \
+ cmake \
+ ctags \
+ g++ \
+ gdb \
+ git \
+ ladspa-sdk \
+ libasound-dev \
+ libdbus-1-dev \
+ libgtest-dev \
+ libncurses5-dev \
+ libsbc-dev \
+ libsndfile-dev \
+ libspeexdsp-dev \
+ libtool \
+ libudev-dev \
+ wget
+RUN apt-get clean
+RUN cd /tmp && git clone https://github.com/ndevilla/iniparser.git && \
+ cd iniparser && \
+ make && \
+ cp libiniparser.* /usr/local/lib && \
+ cp src/dictionary.h src/iniparser.h /usr/local/include && \
+ chmod 644 /usr/local/include/dictionary.h /usr/local/include/iniparser.h && \
+ chmod 644 /usr/local/lib/libiniparser.a && \
+ chmod 755 /usr/local/lib/libiniparser.so.*
+RUN cd /usr/src/gtest && \
+ cmake . && \
+ make && \
+ chmod 644 *.a && \
+ cp *.a /usr/local/lib
+
+# Need to build and install alsa so there is a static lib.
+RUN mkdir -p /tmp/alsa-build && cd /tmp/alsa-build && \
+ wget ftp://ftp.alsa-project.org/pub/lib/alsa-lib-1.1.4.1.tar.bz2 && \
+ bzip2 -f -d alsa-lib-* && \
+ tar xf alsa-lib-* && \
+ cd alsa-lib-* && \
+ ./configure --enable-static --disable-shared && \
+ make clean && \
+ make -j$(nproc) all && \
+ make install
+
+
+COPY . /src/cras/
+COPY src/fuzz/build.sh /src/
diff --git a/cras/src/fuzz/README b/cras/src/fuzz/README
new file mode 100644
index 0000000..96e9076
--- /dev/null
+++ b/cras/src/fuzz/README
@@ -0,0 +1,23 @@
+This directory contains source code and build scripts for coverage-guided
+fuzzers.
+
+Detailed instructions are available at:
+
+ https://github.com/google/oss-fuzz/blob/master/docs/
+
+Quick start:
+
+ Build a container from the cras directory
+
+ $ sudo docker build -t ossfuzz/cras -f src/fuzz/Dockerfile .
+
+ Build fuzzers
+
+ $ sudo docker run -ti --rm -v $(pwd):/src/cras -v /tmp/fuzzers:/out \
+ ossfuzz/cras
+
+ Look in /tmp/fuzzers to see the executables. Run them like so:
+
+ $ sudo docker run -ti -v $(pwd)/src/fuzz/corpus:/corpus \
+ -v /tmp/fuzzers:/out ossfuzz/base-runner /out/rclient_message \
+ /corpus -runs=100
diff --git a/cras/src/fuzz/build.sh b/cras/src/fuzz/build.sh
new file mode 100755
index 0000000..ac4ad2f
--- /dev/null
+++ b/cras/src/fuzz/build.sh
@@ -0,0 +1,22 @@
+#!/bin/bash -eux
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# Builds fuzzers from within a container into /out/ directory.
+# Expects /src/cras to contain a cras checkout.
+
+mkdir $WORK/cras
+cd $SRC/cras
+./git_prepare.sh
+./configure --disable-dbus --disable-webrtc-apm
+make -j$(nproc)
+
+$CXX $CXXFLAGS $FUZZER_LDFLAGS \
+ $SRC/cras/src/fuzz/rclient_message.cc -o $OUT/rclient_message \
+ -I $SRC/cras/src/server \
+ -I $SRC/cras/src/common \
+ $SRC/cras/src/.libs/libcrasserver.a \
+ -lpthread -lrt -ludev -ldl -lm \
+ -lFuzzingEngine \
+ -Wl,-Bstatic -liniparser -lasound -lspeexdsp -Wl,-Bdynamic
diff --git a/cras/src/fuzz/corpus/06051efd871be7eb2b4baed61c3ba53fe0b27833 b/cras/src/fuzz/corpus/06051efd871be7eb2b4baed61c3ba53fe0b27833
new file mode 100644
index 0000000..09605ec
--- /dev/null
+++ b/cras/src/fuzz/corpus/06051efd871be7eb2b4baed61c3ba53fe0b27833
Binary files differ
diff --git a/cras/src/fuzz/corpus/06e2b48cb549540ab62c0a62a1480f3de7807896 b/cras/src/fuzz/corpus/06e2b48cb549540ab62c0a62a1480f3de7807896
new file mode 100644
index 0000000..fd0149b
--- /dev/null
+++ b/cras/src/fuzz/corpus/06e2b48cb549540ab62c0a62a1480f3de7807896
Binary files differ
diff --git a/cras/src/fuzz/corpus/1cea528aa30177cb48d9b279b7d1f115b5a60c01 b/cras/src/fuzz/corpus/1cea528aa30177cb48d9b279b7d1f115b5a60c01
new file mode 100644
index 0000000..ffd27be
--- /dev/null
+++ b/cras/src/fuzz/corpus/1cea528aa30177cb48d9b279b7d1f115b5a60c01
Binary files differ
diff --git a/cras/src/fuzz/corpus/348b671615268f526f174ed2f89d887a26ee8b7a b/cras/src/fuzz/corpus/348b671615268f526f174ed2f89d887a26ee8b7a
new file mode 100644
index 0000000..0b7a92f
--- /dev/null
+++ b/cras/src/fuzz/corpus/348b671615268f526f174ed2f89d887a26ee8b7a
Binary files differ
diff --git a/cras/src/fuzz/corpus/756692a278ad08a8f3a9e198b1436620c53247e9 b/cras/src/fuzz/corpus/756692a278ad08a8f3a9e198b1436620c53247e9
new file mode 100644
index 0000000..c768e08
--- /dev/null
+++ b/cras/src/fuzz/corpus/756692a278ad08a8f3a9e198b1436620c53247e9
Binary files differ
diff --git a/cras/src/fuzz/corpus/90c740f0e2e83b050ba7ccc2b963f2cb6bf23bc1 b/cras/src/fuzz/corpus/90c740f0e2e83b050ba7ccc2b963f2cb6bf23bc1
new file mode 100644
index 0000000..05718e1
--- /dev/null
+++ b/cras/src/fuzz/corpus/90c740f0e2e83b050ba7ccc2b963f2cb6bf23bc1
Binary files differ
diff --git a/cras/src/fuzz/corpus/9448261a4711bdb5e62490e57f78a9a8d99cfd90 b/cras/src/fuzz/corpus/9448261a4711bdb5e62490e57f78a9a8d99cfd90
new file mode 100644
index 0000000..6eaf65c
--- /dev/null
+++ b/cras/src/fuzz/corpus/9448261a4711bdb5e62490e57f78a9a8d99cfd90
Binary files differ
diff --git a/cras/src/fuzz/corpus/b148b3ca1a089964a122b42170eb39e41461e8de b/cras/src/fuzz/corpus/b148b3ca1a089964a122b42170eb39e41461e8de
new file mode 100644
index 0000000..12f9ff6
--- /dev/null
+++ b/cras/src/fuzz/corpus/b148b3ca1a089964a122b42170eb39e41461e8de
Binary files differ
diff --git a/cras/src/fuzz/corpus/c66fae687ac462af608f9f22ea1b9493b66fe844 b/cras/src/fuzz/corpus/c66fae687ac462af608f9f22ea1b9493b66fe844
new file mode 100644
index 0000000..74c5e05
--- /dev/null
+++ b/cras/src/fuzz/corpus/c66fae687ac462af608f9f22ea1b9493b66fe844
Binary files differ
diff --git a/cras/src/fuzz/corpus/da39a3ee5e6b4b0d3255bfef95601890afd80709 b/cras/src/fuzz/corpus/da39a3ee5e6b4b0d3255bfef95601890afd80709
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/cras/src/fuzz/corpus/da39a3ee5e6b4b0d3255bfef95601890afd80709
diff --git a/cras/src/fuzz/corpus/da6b17d11511d4c4eb14933aa404715e753e2c02 b/cras/src/fuzz/corpus/da6b17d11511d4c4eb14933aa404715e753e2c02
new file mode 100644
index 0000000..99ceca0
--- /dev/null
+++ b/cras/src/fuzz/corpus/da6b17d11511d4c4eb14933aa404715e753e2c02
Binary files differ
diff --git a/cras/src/fuzz/corpus/e578774a32e52e17461c30848f554f78c3ffda66 b/cras/src/fuzz/corpus/e578774a32e52e17461c30848f554f78c3ffda66
new file mode 100644
index 0000000..6bfd3e0
--- /dev/null
+++ b/cras/src/fuzz/corpus/e578774a32e52e17461c30848f554f78c3ffda66
Binary files differ
diff --git a/cras/src/fuzz/corpus/e85fb46a837d5a798d371573b41fd60e1e1486ed b/cras/src/fuzz/corpus/e85fb46a837d5a798d371573b41fd60e1e1486ed
new file mode 100644
index 0000000..ed467b1
--- /dev/null
+++ b/cras/src/fuzz/corpus/e85fb46a837d5a798d371573b41fd60e1e1486ed
Binary files differ
diff --git a/cras/src/fuzz/rclient_message.cc b/cras/src/fuzz/rclient_message.cc
new file mode 100644
index 0000000..9ae5fb1
--- /dev/null
+++ b/cras/src/fuzz/rclient_message.cc
@@ -0,0 +1,50 @@
+/* Copyright 2017 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+
+extern "C" {
+#include "cras_iodev_list.h"
+#include "cras_mix.h"
+#include "cras_observer.h"
+#include "cras_rclient.h"
+#include "cras_shm.h"
+#include "cras_system_state.h"
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ cras_rclient *client = cras_rclient_create(0, 0);
+ cras_rclient_buffer_from_client(client, data, size, -1);
+ cras_rclient_destroy(client);
+
+ return 0;
+}
+
+extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv) {
+ char *shm_name;
+ if (asprintf(&shm_name, "/cras-%d", getpid()) < 0)
+ exit(-ENOMEM);
+ struct cras_server_state *exp_state = (struct cras_server_state *)
+ calloc(1, sizeof(*exp_state));
+ if (!exp_state)
+ exit(-1);
+ int rw_shm_fd = open("/dev/null", O_RDWR);
+ int ro_shm_fd = open("/dev/null", O_RDONLY);
+ cras_system_state_init("/tmp",
+ shm_name,
+ rw_shm_fd,
+ ro_shm_fd,
+ exp_state,
+ sizeof(*exp_state));
+ free(shm_name);
+
+ cras_observer_server_init();
+ cras_mix_init(0);
+ cras_iodev_list_init();
+
+ return 0;
+}
diff --git a/cras/src/libcras/cras_client.c b/cras/src/libcras/cras_client.c
index 98acde1..4f9f131 100644
--- a/cras/src/libcras/cras_client.c
+++ b/cras/src/libcras/cras_client.c
@@ -57,6 +57,8 @@
static const size_t MAX_CMD_MSG_LEN = 256;
static const size_t SERVER_SHUTDOWN_TIMEOUT_US = 500000;
static const size_t SERVER_CONNECT_TIMEOUT_MS = 1000;
+static const size_t HOTWORD_FRAME_RATE = 16000;
+static const size_t HOTWORD_BLOCK_SIZE = 320;
/* Commands sent from the user to the running client. */
enum {
@@ -123,6 +125,7 @@
size_t cb_threshold;
enum CRAS_STREAM_TYPE stream_type;
uint32_t flags;
+ uint64_t effects;
void *user_data;
cras_playback_cb_t aud_cb;
cras_unified_cb_t unified_cb;
@@ -256,6 +259,20 @@
((struct client_int *)((char *)cptr - offsetof(struct client_int, client)))
/*
+ * Holds the hotword stream format, params, and ID used when waiting for a
+ * hotword. The structure is created by cras_client_enable_hotword_callback and
+ * destroyed by cras_client_disable_hotword_callback.
+ */
+struct cras_hotword_handle {
+ struct cras_audio_format *format;
+ struct cras_stream_params *params;
+ cras_stream_id_t stream_id;
+ cras_hotword_trigger_cb_t trigger_cb;
+ cras_hotword_error_cb_t err_cb;
+ void *user_data;
+};
+
+/*
* Local Helpers
*/
@@ -1042,6 +1059,27 @@
cras_shm_buffer_read_current(&stream->capture_shm, num_frames);
}
+static int send_capture_reply(struct client_stream *stream,
+ unsigned int frames,
+ int err)
+{
+ struct audio_message aud_msg;
+ int rc;
+
+ if (!cras_stream_uses_input_hw(stream->direction))
+ return 0;
+
+ aud_msg.id = AUDIO_MESSAGE_DATA_CAPTURED;
+ aud_msg.frames = frames;
+ aud_msg.error = err;
+
+ rc = write(stream->aud_fd, &aud_msg, sizeof(aud_msg));
+ if (rc != sizeof(aud_msg))
+ return -EPIPE;
+
+ return 0;
+}
+
/* For capture streams this handles the message signalling that data is ready to
* be passed to the user of this stream. Calls the audio callback with the new
* samples, and mark them as read.
@@ -1058,6 +1096,7 @@
struct cras_stream_params *config;
uint8_t *captured_frames;
struct timespec ts;
+ int rc = 0;
config = stream->config;
/* If this message is for an output stream, log error and drop it. */
@@ -1088,15 +1127,17 @@
num_frames,
&ts,
config->user_data);
- if (frames == EOF) {
+ if (frames < 0) {
send_stream_message(stream, CLIENT_STREAM_EOF);
- return EOF;
+ rc = frames;
+ goto reply_captured;
}
if (frames == 0)
return 0;
complete_capture_read_current(stream, frames);
- return 0;
+reply_captured:
+ return send_capture_reply(stream, frames, rc);
}
/* Notifies the server that "frames" samples have been written. */
@@ -1313,7 +1354,7 @@
return -rc;
}
- clock_gettime(CLOCK_MONOTONIC, &future);
+ clock_gettime(CLOCK_REALTIME, &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);
@@ -1472,6 +1513,7 @@
stream->config->buffer_frames,
stream->config->cb_threshold,
stream->flags,
+ stream->config->effects,
stream->config->format,
dev_idx);
rc = cras_send_with_fds(client->server_fd, &serv_msg, sizeof(serv_msg),
@@ -1689,8 +1731,23 @@
(struct cras_client_stream_connected *)msg;
struct client_stream *stream =
stream_from_id(client, cmsg->stream_id);
- if (stream == NULL)
+ if (stream == NULL) {
+ if (num_fds != 2) {
+ syslog(LOG_ERR, "cras_client: Error receiving "
+ "stream 0x%x connected message",
+ cmsg->stream_id);
+ return -EINVAL;
+ }
+
+ /*
+ * Usually, the fds should be closed in stream_connected
+ * callback. However, sometimes a stream is removed
+ * before it is connected.
+ */
+ close(server_fds[0]);
+ close(server_fds[1]);
break;
+ }
rc = stream_connected(stream, cmsg, server_fds, num_fds);
if (rc < 0)
stream->config->err_cb(stream->client,
@@ -1702,6 +1759,7 @@
case CRAS_CLIENT_AUDIO_DEBUG_INFO_READY:
if (client->debug_info_callback)
client->debug_info_callback(client);
+ client->debug_info_callback = NULL;
break;
case CRAS_CLIENT_GET_HOTWORD_MODELS_READY: {
struct cras_client_get_hotword_models_ready *cmsg =
@@ -2172,6 +2230,7 @@
client->server_err_cb = NULL;
cras_client_stop(client);
server_disconnect(client);
+ close(client->server_event_fd);
close(client->command_fds[0]);
close(client->command_fds[1]);
close(client->stream_fds[0]);
@@ -2224,7 +2283,7 @@
params->direction = direction;
params->buffer_frames = buffer_frames;
params->cb_threshold = cb_threshold;
-
+ params->effects = 0;
params->stream_type = stream_type;
params->flags = flags;
params->user_data = user_data;
@@ -2235,6 +2294,46 @@
return params;
}
+void cras_client_stream_params_enable_aec(struct cras_stream_params *params)
+{
+ params->effects |= APM_ECHO_CANCELLATION;
+}
+
+void cras_client_stream_params_disable_aec(struct cras_stream_params *params)
+{
+ params->effects &= ~APM_ECHO_CANCELLATION;
+}
+
+void cras_client_stream_params_enable_ns(struct cras_stream_params *params)
+{
+ params->effects |= APM_NOISE_SUPRESSION;
+}
+
+void cras_client_stream_params_disable_ns(struct cras_stream_params *params)
+{
+ params->effects &= ~APM_NOISE_SUPRESSION;
+}
+
+void cras_client_stream_params_enable_agc(struct cras_stream_params *params)
+{
+ params->effects |= APM_GAIN_CONTROL;
+}
+
+void cras_client_stream_params_disable_agc(struct cras_stream_params *params)
+{
+ params->effects &= ~APM_GAIN_CONTROL;
+}
+
+void cras_client_stream_params_enable_vad(struct cras_stream_params *params)
+{
+ params->effects |= APM_VOICE_DETECTION;
+}
+
+void cras_client_stream_params_disable_vad(struct cras_stream_params *params)
+{
+ params->effects &= ~APM_VOICE_DETECTION;
+}
+
struct cras_stream_params *cras_client_unified_params_create(
enum CRAS_STREAM_DIRECTION direction,
unsigned int block_size,
@@ -2256,6 +2355,7 @@
params->cb_threshold = block_size;
params->stream_type = stream_type;
params->flags = flags;
+ params->effects = 0;
params->user_data = user_data;
params->aud_cb = 0;
params->unified_cb = unified_cb;
@@ -2593,6 +2693,22 @@
return debug_info;
}
+const struct cras_audio_thread_snapshot_buffer*
+ cras_client_get_audio_thread_snapshot_buffer(
+ const struct cras_client *client)
+{
+ const struct cras_audio_thread_snapshot_buffer *snapshot_buffer;
+ int lock_rc;
+
+ lock_rc = server_state_rdlock(client);
+ if (lock_rc)
+ return 0;
+
+ snapshot_buffer = &client->server_state->snapshot_buffer;
+ server_state_unlock(client, lock_rc);
+ return snapshot_buffer;
+}
+
unsigned cras_client_get_num_active_streams(const struct cras_client *client,
struct timespec *ts)
{
@@ -3029,12 +3145,31 @@
if (client == NULL)
return -EINVAL;
+ if (client->debug_info_callback != NULL)
+ return -EINVAL;
client->debug_info_callback = debug_info_cb;
cras_fill_dump_audio_thread(&msg);
return write_message_to_server(client, &msg.header);
}
+int cras_client_update_audio_thread_snapshots(
+ struct cras_client *client,
+ void (*debug_info_cb)(struct cras_client *))
+{
+ struct cras_dump_snapshots msg;
+
+ if (client == NULL)
+ return -EINVAL;
+
+ if (client->debug_info_callback != NULL)
+ return -EINVAL;
+ client->debug_info_callback = debug_info_cb;
+
+ cras_fill_dump_snapshots(&msg);
+ return write_message_to_server(client, &msg.header);
+}
+
int cras_client_set_node_volume(struct cras_client *client,
cras_node_id_t node_id,
uint8_t volume)
@@ -3118,10 +3253,10 @@
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,
- enum CRAS_STREAM_DIRECTION direction)
+int cras_client_get_first_node_type_idx(const struct cras_client *client,
+ enum CRAS_NODE_TYPE type,
+ enum CRAS_STREAM_DIRECTION direction,
+ cras_node_id_t *node_id)
{
const struct cras_server_state *state;
unsigned int version;
@@ -3146,9 +3281,10 @@
}
for (i = 0; i < num_nodes; i++) {
if ((enum CRAS_NODE_TYPE)node_list[i].type_enum == type) {
- int ret_idx = node_list[i].iodev_idx;
+ *node_id = cras_make_node_id(node_list[i].iodev_idx,
+ node_list[i].ionode_idx);
server_state_unlock(client, lock_rc);
- return ret_idx;
+ return 0;
}
}
if (end_server_state_read(state, version))
@@ -3158,6 +3294,21 @@
return -ENODEV;
}
+int cras_client_get_first_dev_type_idx(const struct cras_client *client,
+ enum CRAS_NODE_TYPE type,
+ enum CRAS_STREAM_DIRECTION direction)
+{
+ cras_node_id_t node_id;
+ int rc;
+
+ rc = cras_client_get_first_node_type_idx(client, type, direction,
+ &node_id);
+ if (rc)
+ return rc;
+
+ return dev_index_of(node_id);
+}
+
int cras_client_set_suspend(struct cras_client *client, int suspend)
{
struct cras_server_message msg;
@@ -3190,6 +3341,44 @@
return write_message_to_server(client, &msg.header);
}
+int cras_client_set_aec_dump(struct cras_client *client,
+ cras_stream_id_t stream_id,
+ int start,
+ int fd)
+{
+ struct cras_set_aec_dump msg;
+
+ cras_fill_set_aec_dump_message(&msg, stream_id, start);
+
+ if (fd != -1)
+ return cras_send_with_fds(client->server_fd, &msg,
+ sizeof(msg), &fd, 1);
+ else
+ return write_message_to_server(client, &msg.header);
+}
+
+int cras_client_reload_aec_config(struct cras_client *client)
+{
+ struct cras_reload_aec_config msg;
+
+ cras_fill_reload_aec_config(&msg);
+ return write_message_to_server(client, &msg.header);
+}
+
+int cras_client_get_aec_supported(struct cras_client *client)
+{
+ int aec_supported;
+ int lock_rc;
+
+ lock_rc = server_state_rdlock(client);
+ if (lock_rc)
+ return 0;
+
+ aec_supported = client->server_state->aec_supported;
+ server_state_unlock(client, lock_rc);
+ return aec_supported;
+}
+
void cras_client_set_state_change_callback_context(
struct cras_client *client, void *context)
{
@@ -3400,3 +3589,106 @@
}
return 0;
}
+
+static int hotword_read_cb(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)
+{
+ struct cras_hotword_handle *handle;
+
+ handle = (struct cras_hotword_handle *)user_arg;
+ if (handle->trigger_cb)
+ handle->trigger_cb(client, handle, handle->user_data);
+
+ return 0;
+}
+
+static int hotword_err_cb(struct cras_client *client,
+ cras_stream_id_t stream_id,
+ int error,
+ void *user_arg)
+{
+ struct cras_hotword_handle *handle;
+
+ handle = (struct cras_hotword_handle *)user_arg;
+ if (handle->err_cb)
+ handle->err_cb(client, handle, error, handle->user_data);
+
+ return 0;
+}
+
+int cras_client_enable_hotword_callback(struct cras_client *client,
+ void *user_data,
+ cras_hotword_trigger_cb_t trigger_cb,
+ cras_hotword_error_cb_t err_cb,
+ struct cras_hotword_handle **handle_out)
+{
+ struct cras_hotword_handle *handle;
+ int ret = 0;
+
+ if (!client)
+ return -EINVAL;
+
+ handle = (struct cras_hotword_handle *)calloc(1, sizeof(*handle));
+ if (!handle)
+ return -ENOMEM;
+
+ handle->format = cras_audio_format_create(SND_PCM_FORMAT_S16_LE,
+ HOTWORD_FRAME_RATE, 1);
+ if (!handle->format) {
+ ret = -ENOMEM;
+ goto cleanup;
+ }
+
+ handle->params = cras_client_unified_params_create(
+ CRAS_STREAM_INPUT,
+ HOTWORD_BLOCK_SIZE,
+ CRAS_STREAM_TYPE_DEFAULT,
+ HOTWORD_STREAM | TRIGGER_ONLY,
+ (void *)handle,
+ hotword_read_cb,
+ hotword_err_cb,
+ handle->format);
+ if (!handle->params) {
+ ret = -ENOMEM;
+ goto cleanup_format;
+ }
+
+ handle->trigger_cb = trigger_cb;
+ handle->err_cb = err_cb;
+ handle->user_data = user_data;
+
+ ret = cras_client_add_stream(client, &handle->stream_id,
+ handle->params);
+ if (ret)
+ goto cleanup_params;
+
+ *handle_out = handle;
+ return 0;
+
+cleanup_params:
+ cras_client_stream_params_destroy(handle->params);
+cleanup_format:
+ cras_audio_format_destroy(handle->format);
+cleanup:
+ free(handle);
+ return ret;
+}
+
+int cras_client_disable_hotword_callback(struct cras_client *client,
+ struct cras_hotword_handle *handle)
+{
+ if (!client || !handle)
+ return -EINVAL;
+
+ cras_client_rm_stream(client, handle->stream_id);
+ cras_audio_format_destroy(handle->format);
+ cras_client_stream_params_destroy(handle->params);
+ free(handle);
+ return 0;
+}
diff --git a/cras/src/libcras/cras_client.h b/cras/src/libcras/cras_client.h
index d17b650..1817a73 100644
--- a/cras/src/libcras/cras_client.h
+++ b/cras/src/libcras/cras_client.h
@@ -59,6 +59,7 @@
#include "cras_util.h"
struct cras_client;
+struct cras_hotword_handle;
struct cras_stream_params;
/* Callback for audio received or transmitted.
@@ -177,6 +178,17 @@
typedef void (*get_hotword_models_cb_t)(struct cras_client *client,
const char *hotword_models);
+/* Callback to wait for a hotword trigger. */
+typedef void (*cras_hotword_trigger_cb_t)(struct cras_client *client,
+ struct cras_hotword_handle *handle,
+ void *user_data);
+
+/* Callback for handling hotword errors. */
+typedef int (*cras_hotword_error_cb_t)(struct cras_client *client,
+ struct cras_hotword_handle *handle,
+ int error,
+ void *user_data);
+
/*
* Client handling.
*/
@@ -492,6 +504,17 @@
int cras_client_update_audio_debug_info(
struct cras_client *client, void (*cb)(struct cras_client *));
+/* Asks the server to dump current audio thread snapshots.
+ *
+ * Args:
+ * client - The client from cras_client_create.
+ * cb - A function to call when the data is received.
+ * Returns:
+ * 0 on success, -EINVAL if the client isn't valid or isn't running.
+ */
+int cras_client_update_audio_thread_snapshots(
+ struct cras_client *client, void (*cb)(struct cras_client *));
+
/*
* Stream handling.
*/
@@ -525,6 +548,19 @@
cras_error_cb_t err_cb,
struct cras_audio_format *format);
+/* Functions to enable or disable specific effect on given stream parameter.
+ * Args:
+ * params - Stream configuration parameters.
+ */
+void cras_client_stream_params_enable_aec(struct cras_stream_params *params);
+void cras_client_stream_params_disable_aec(struct cras_stream_params *params);
+void cras_client_stream_params_enable_ns(struct cras_stream_params *params);
+void cras_client_stream_params_disable_ns(struct cras_stream_params *params);
+void cras_client_stream_params_enable_agc(struct cras_stream_params *params);
+void cras_client_stream_params_disable_agc(struct cras_stream_params *params);
+void cras_client_stream_params_enable_vad(struct cras_stream_params *params);
+void cras_client_stream_params_disable_vad(struct cras_stream_params *params);
+
/* Setup stream configuration parameters.
* Args:
* direction - playback(CRAS_STREAM_OUTPUT) or capture(CRAS_STREAM_INPUT) or
@@ -821,6 +857,21 @@
const struct audio_debug_info *cras_client_get_audio_debug_info(
const struct cras_client *client);
+/* Gets audio thread snapshot buffer.
+ *
+ * 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:
+ * A pointer to the snapshot buffer. This info is only updated when
+ * requested by calling cras_client_update_audio_thread_snapshots.
+ */
+const struct cras_audio_thread_snapshot_buffer *
+ cras_client_get_audio_thread_snapshot_buffer(
+ 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
@@ -934,6 +985,25 @@
unsigned int data_len,
const uint8_t *data);
+/* Finds the first node of the given type.
+ *
+ * This is used for finding a special hotword node.
+ *
+ * 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.
+ * direction - Search input or output devices.
+ * node_id - The found node on success.
+ * Returns:
+ * 0 on success, a negative error on failure.
+ */
+int cras_client_get_first_node_type_idx(const struct cras_client *client,
+ enum CRAS_NODE_TYPE type,
+ enum CRAS_STREAM_DIRECTION direction,
+ cras_node_id_t *node_id);
+
/* Finds the first device that contains a node of the given type.
*
* This is used for finding a special hotword device.
@@ -1003,6 +1073,58 @@
cras_node_id_t node_id,
const char *model_name);
+/*
+ * Creates a hotword stream and waits for the hotword to trigger.
+ *
+ * Args:
+ * client - The client to add the stream to (from cras_client_create).
+ * user_data - Pointer that will be passed to the callback.
+ * trigger_cb - Called when a hotword is triggered.
+ * err_cb - Called when there is an error with the stream.
+ * handle_out - On success will be filled with a cras_hotword_handle.
+ * Returns:
+ * 0 on success, negative error code on failure (from errno.h).
+ */
+int cras_client_enable_hotword_callback(
+ struct cras_client *client,
+ void *user_data,
+ cras_hotword_trigger_cb_t trigger_cb,
+ cras_hotword_error_cb_t err_cb,
+ struct cras_hotword_handle **handle_out);
+
+/*
+ * Closes a hotword stream that was created by cras_client_wait_for_hotword.
+ *
+ * Args:
+ * client - Client to remove the stream (returned from cras_client_create).
+ * handle - cras_hotword_handle returned from cras_client_wait_for_hotword.
+ * Returns:
+ * 0 on success negative error code on failure (from errno.h).
+ */
+int cras_client_disable_hotword_callback(struct cras_client *client,
+ struct cras_hotword_handle *handle);
+
+/* Starts or stops the aec dump task on server side.
+ * Args:
+ * client - The client from cras_client_create.
+ * stream_id - The id of the input stream running with aec effect.
+ * start - True to start APM debugging, otherwise to stop it.
+ * fd - File descriptor of the file to store aec dump result.
+ */
+int cras_client_set_aec_dump(struct cras_client *client,
+ cras_stream_id_t stream_id,
+ int start,
+ int fd);
+/*
+ * Reloads the aec.ini config file on server side.
+ */
+int cras_client_reload_aec_config(struct cras_client *client);
+
+/*
+ * Returns if AEC is supported.
+ */
+int cras_client_get_aec_supported(struct cras_client *client);
+
/* Set the context pointer for system state change callbacks.
* Args:
* client - The client from cras_client_create.
diff --git a/cras/src/server/audio_thread.c b/cras/src/server/audio_thread.c
index 8b34914..7d208d8 100644
--- a/cras/src/server/audio_thread.c
+++ b/cras/src/server/audio_thread.c
@@ -9,16 +9,16 @@
#include <pthread.h>
#include <poll.h>
+#include <stdbool.h>
#include <sys/param.h>
#include <syslog.h>
-#include "cras_audio_area.h"
#include "audio_thread_log.h"
+#include "cras_audio_thread_monitor.h"
#include "cras_config.h"
#include "cras_fmt_conv.h"
#include "cras_iodev.h"
#include "cras_rstream.h"
-#include "cras_server_metrics.h"
#include "cras_system_state.h"
#include "cras_types.h"
#include "cras_util.h"
@@ -29,14 +29,16 @@
#define MIN_PROCESS_TIME_US 500 /* 0.5ms - min amount of time to mix/src. */
#define SLEEP_FUZZ_FRAMES 10 /* # to consider "close enough" to sleep frames. */
#define MIN_READ_WAIT_US 2000 /* 2ms */
-static const struct timespec playback_wake_fuzz_ts = {
- 0, 500 * 1000 /* 500 usec. */
-};
+/*
+ * # to check whether a busyloop event happens
+ */
+#define MAX_CONTINUOUS_ZERO_SLEEP_COUNT 2
/* Messages that can be sent from the main context to the audio thread. */
enum AUDIO_THREAD_COMMAND {
AUDIO_THREAD_ADD_OPEN_DEV,
AUDIO_THREAD_RM_OPEN_DEV,
+ AUDIO_THREAD_IS_DEV_OPEN,
AUDIO_THREAD_ADD_STREAM,
AUDIO_THREAD_DISCONNECT_STREAM,
AUDIO_THREAD_STOP,
@@ -45,6 +47,7 @@
AUDIO_THREAD_CONFIG_GLOBAL_REMIX,
AUDIO_THREAD_DEV_START_RAMP,
AUDIO_THREAD_REMOVE_CALLBACK,
+ AUDIO_THREAD_AEC_DUMP,
};
struct audio_thread_msg {
@@ -85,10 +88,15 @@
enum CRAS_IODEV_RAMP_REQUEST request;
};
+struct audio_thread_aec_dump_msg {
+ struct audio_thread_msg header;
+ cras_stream_id_t stream_id;
+ unsigned int start; /* */
+ int fd;
+};
+
/* 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;
@@ -174,6 +182,35 @@
return write(thread->to_main_fds[1], &rc, sizeof(rc));
}
+/* Reads from a file descriptor until all bytes are read.
+ *
+ * Args:
+ * fd - file descriptor to read
+ * buf - the buffer to be written.
+ * count - the number of bytes to read from fd
+ * Returns:
+ * |count| on success, negative error code on failure.
+ */
+static int read_until_finished(int fd, void *buf, size_t count) {
+ int nread, count_left = count;
+
+ while (count_left > 0) {
+ nread = read(fd, (uint8_t *)buf + count - count_left,
+ count_left);
+ if (nread < 0) {
+ if (errno == EINTR)
+ continue;
+ else
+ return nread;
+ } else if (nread == 0) {
+ syslog(LOG_ERR, "Pipe has been closed.");
+ return -EPIPE;
+ }
+ count_left -= nread;
+ }
+ return count;
+}
+
/* Reads a command from the main thread. Called from the playback/capture
* thread. This will read the next available command from the main thread and
* put it in buf.
@@ -192,14 +229,17 @@
struct audio_thread_msg *msg = (struct audio_thread_msg *)buf;
/* Get the length of the message first */
- nread = read(thread->to_thread_fds[0], buf, sizeof(msg->length));
+ nread = read_until_finished(
+ thread->to_thread_fds[0], buf, sizeof(msg->length));
if (nread < 0)
return nread;
+
if (msg->length > max_len)
return -ENOMEM;
- to_read = msg->length - nread;
- rc = read(thread->to_thread_fds[0], &buf[0] + nread, to_read);
+ to_read = msg->length - sizeof(msg->length);
+ rc = read_until_finished(thread->to_thread_fds[0],
+ &buf[0] + sizeof(msg->length), to_read);
if (rc < 0)
return rc;
return 0;
@@ -211,19 +251,6 @@
cras_iodev_fill_odev_zeros(odev, odev->min_buffer_level);
}
-static void thread_rm_open_adev(struct audio_thread *thread,
- struct open_dev *adev);
-
-static void delete_stream_from_dev(struct cras_iodev *dev,
- struct cras_rstream *stream)
-{
- struct dev_stream *out;
-
- out = cras_iodev_rm_stream(dev, stream);
- if (out)
- dev_stream_destroy(out);
-}
-
/* Append a new stream to a specified set of iodevs. */
static int append_stream(struct audio_thread *thread,
struct cras_rstream *stream,
@@ -236,6 +263,7 @@
struct timespec init_cb_ts;
const struct timespec *stream_ts;
unsigned int i;
+ bool cb_ts_set = false;
int rc = 0;
for (i = 0; i < num_iodevs; i++) {
@@ -249,15 +277,26 @@
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.
+ /* For output, if open device already has stream, get the earliest next
+ * callback time from these streams to align with. Otherwise, use the
+ * timestamp now as the initial callback time for new stream so dev_stream
+ * can set its own schedule.
+ * If next callback time is too far from now, it will block writing and
+ * lower hardware level. Else if we fetch the new stream immediately, it
+ * may cause device buffer level stack up.
*/
- if (dev->streams &&
- (stream_ts = dev_stream_next_cb_ts(dev->streams)))
- init_cb_ts = *stream_ts;
- else
+ if (stream->direction == CRAS_STREAM_OUTPUT && dev->streams) {
+ DL_FOREACH(dev->streams, out) {
+ stream_ts = dev_stream_next_cb_ts(out);
+ if (stream_ts &&
+ (!cb_ts_set || timespec_after(&init_cb_ts, stream_ts))) {
+ init_cb_ts = *stream_ts;
+ cb_ts_set = true;
+ }
+ }
+ }
+
+ if (!cb_ts_set)
clock_gettime(CLOCK_MONOTONIC_RAW, &init_cb_ts);
out = dev_stream_create(stream, dev->info.idx,
@@ -339,74 +378,46 @@
*/
if (iodev->direction == CRAS_STREAM_OUTPUT)
fill_odevs_zeros_min_level(iodev);
- else
- adev->input_streaming = 0;
- ATLOG(atlog,
- AUDIO_THREAD_DEV_ADDED,
- iodev->info.idx, 0, 0);
+ ATLOG(atlog, AUDIO_THREAD_DEV_ADDED, iodev->info.idx, 0, 0);
DL_APPEND(thread->open_devs[iodev->direction], adev);
return 0;
}
-static struct open_dev *find_adev(struct open_dev *adev_list,
- struct cras_iodev *dev)
-{
- struct open_dev *adev;
- DL_FOREACH(adev_list, adev)
- if (adev->dev == dev)
- return adev;
- return NULL;
-}
-
-static void thread_rm_open_adev(struct audio_thread *thread,
- struct open_dev *dev_to_rm)
-{
- enum CRAS_STREAM_DIRECTION dir = dev_to_rm->dev->direction;
- struct open_dev *adev;
- struct dev_stream *dev_stream;
-
- /* Do nothing if dev_to_rm wasn't already in the active dev list. */
- adev = find_adev(thread->open_devs[dir], dev_to_rm->dev);
- if (!adev)
- return;
-
- DL_DELETE(thread->open_devs[dir], dev_to_rm);
-
- ATLOG(atlog,
- AUDIO_THREAD_DEV_REMOVED,
- dev_to_rm->dev->info.idx, 0, 0);
-
- DL_FOREACH(dev_to_rm->dev->streams, dev_stream) {
- cras_iodev_rm_stream(dev_to_rm->dev, dev_stream->stream);
- dev_stream_destroy(dev_stream);
- }
-
- free(dev_to_rm);
-}
-
/* Handles messages from the main thread to remove an active device. */
static int thread_rm_open_dev(struct audio_thread *thread,
struct cras_iodev *iodev)
{
- struct open_dev *adev = find_adev(
+ struct open_dev *adev = dev_io_find_open_dev(
thread->open_devs[iodev->direction], iodev);
if (!adev)
return -EINVAL;
- thread_rm_open_adev(thread, adev);
+ dev_io_rm_open_dev(&thread->open_devs[iodev->direction], adev);
return 0;
}
+/*
+ * Handles message from the main thread to check if an iodev is in the
+ * open dev list.
+ */
+static int thread_is_dev_open(struct audio_thread *thread,
+ struct cras_iodev *iodev)
+{
+ struct open_dev *adev = dev_io_find_open_dev(
+ thread->open_devs[iodev->direction], iodev);
+ return !!adev;
+}
+
/* 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(
+ struct open_dev *adev = dev_io_find_open_dev(
thread->open_devs[iodev->direction], iodev);
if (!adev)
return -EINVAL;
@@ -430,45 +441,6 @@
return 0;
}
-/* Remove stream from the audio thread. If this is the last stream to be
- * removed close the device.
- */
-static int thread_remove_stream(struct audio_thread *thread,
- struct cras_rstream *stream,
- struct cras_iodev *dev)
-{
- struct open_dev *open_dev;
- struct timespec delay;
- unsigned fetch_delay_msec;
-
- /* Metrics log the longest fetch delay of this stream. */
- if (timespec_after(&stream->longest_fetch_interval,
- &stream->sleep_interval_ts)) {
- subtract_timespecs(&stream->longest_fetch_interval,
- &stream->sleep_interval_ts,
- &delay);
- fetch_delay_msec = delay.tv_sec * 1000 +
- delay.tv_nsec / 1000000;
- if (fetch_delay_msec)
- cras_server_metrics_longest_fetch_delay(
- fetch_delay_msec);
- }
-
- ATLOG(atlog,
- AUDIO_THREAD_STREAM_REMOVED,
- stream->stream_id, 0, 0);
-
- if (dev == NULL) {
- DL_FOREACH(thread->open_devs[stream->direction], open_dev) {
- delete_stream_from_dev(open_dev->dev, stream);
- }
- } else {
- delete_stream_from_dev(dev, stream);
- }
-
- return 0;
-}
-
/* Handles the disconnect_stream message from the main thread. */
static int thread_disconnect_stream(struct audio_thread* thread,
struct cras_rstream* stream,
@@ -479,7 +451,8 @@
if (!thread_find_stream(thread, stream))
return 0;
- rc = thread_remove_stream(thread, stream, dev);
+ rc = dev_io_remove_stream(&thread->open_devs[stream->direction],
+ stream, dev);
return rc;
}
@@ -522,7 +495,8 @@
ms_left = thread_drain_stream_ms_remaining(thread, rstream);
if (ms_left == 0)
- thread_remove_stream(thread, rstream, NULL);
+ dev_io_remove_stream(&thread->open_devs[rstream->direction],
+ rstream, NULL);
return ms_left;
}
@@ -539,224 +513,35 @@
if (rc < 0)
return rc;
- ATLOG(atlog,
- AUDIO_THREAD_STREAM_ADDED,
- stream->stream_id,
- num_iodevs ? iodevs[0]->info.idx : 0,
- num_iodevs);
+ ATLOG(atlog, AUDIO_THREAD_STREAM_ADDED, stream->stream_id,
+ num_iodevs ? iodevs[0]->info.idx : 0, num_iodevs);
return 0;
}
-/* Reads any pending audio message from the socket. */
-static void flush_old_aud_messages(struct cras_audio_shm *shm, int fd)
+/* Starts or stops aec dump task. */
+static int thread_set_aec_dump(struct audio_thread *thread,
+ cras_stream_id_t stream_id,
+ unsigned int start,
+ int fd)
{
- struct audio_message msg;
- struct pollfd pollfd;
- int err;
-
- pollfd.fd = fd;
- pollfd.events = POLLIN;
-
- do {
- err = poll(&pollfd, 1, 0);
- if (pollfd.revents & POLLIN) {
- err = read(fd, &msg, sizeof(msg));
- cras_shm_set_callback_pending(shm, 0);
- }
- } while (err > 0);
-}
-
-/* Asks any stream with room for more data. Sets the time stamp for all streams.
- * Args:
- * thread - The thread to fetch samples for.
- * adev - The output device streams are attached to.
- * Returns:
- * 0 on success, negative error on failure. If failed, can assume that all
- * streams have been removed from the device.
- */
-static int fetch_streams(struct audio_thread *thread,
- struct open_dev *adev)
-{
- struct dev_stream *dev_stream;
- struct cras_iodev *odev = adev->dev;
- int rc;
- int delay;
-
- delay = cras_iodev_delay_frames(odev);
- if (delay < 0)
- return delay;
-
- DL_FOREACH(adev->dev->streams, dev_stream) {
- struct cras_rstream *rstream = dev_stream->stream;
- struct cras_audio_shm *shm =
- cras_rstream_output_shm(rstream);
- int fd = cras_rstream_get_audio_fd(rstream);
- const struct timespec *next_cb_ts;
- struct timespec now;
-
- 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);
-
- if (cras_rstream_get_is_draining(dev_stream->stream))
- continue;
-
- next_cb_ts = dev_stream_next_cb_ts(dev_stream);
- if (!next_cb_ts)
- continue;
-
- /* Check if it's time to get more data from this stream.
- * Allowing for waking up half a little early. */
- add_timespecs(&now, &playback_wake_fuzz_ts);
- if (!timespec_after(&now, next_cb_ts))
- continue;
-
- if (!dev_stream_can_fetch(dev_stream)) {
- ATLOG(
- atlog, AUDIO_THREAD_STREAM_SKIP_CB,
- rstream->stream_id,
- shm->area->write_offset[0],
- shm->area->write_offset[1]);
- continue;
- }
-
- dev_stream_set_delay(dev_stream, delay);
-
- ATLOG(
- atlog,
- AUDIO_THREAD_FETCH_STREAM,
- rstream->stream_id,
- cras_rstream_get_cb_threshold(rstream), delay);
-
- rc = dev_stream_request_playback_samples(dev_stream, &now);
- if (rc < 0) {
- syslog(LOG_ERR, "fetch err: %d for %x",
- rc, rstream->stream_id);
- cras_rstream_set_is_draining(rstream, 1);
- }
- }
-
- return 0;
-}
-
-/* Fill the buffer with samples from the attached streams.
- * Args:
- * thread - The thread object the device is attached to.
- * adev - The device to write to.
- * dst - The buffer to put the samples in (returned from snd_pcm_mmap_begin)
- * write_limit - The maximum number of frames to write to dst.
- *
- * Returns:
- * The number of frames rendered on success, a negative error code otherwise.
- * This number of frames is the minimum of the amount of frames each stream
- * could provide which is the maximum that can currently be rendered.
- */
-static int write_streams(struct audio_thread *thread,
- struct open_dev *adev,
- uint8_t *dst,
- size_t write_limit)
-{
- struct cras_iodev *odev = adev->dev;
- struct dev_stream *curr;
- unsigned int max_offset = 0;
- unsigned int frame_bytes = cras_get_format_bytes(odev->ext_format);
- unsigned int num_playing = 0;
- unsigned int drain_limit = write_limit;
-
- /* Mix as much as we can, the minimum fill level of any stream. */
- max_offset = cras_iodev_max_stream_offset(odev);
-
- /* Mix as much as we can, the minimum fill level of any stream. */
- DL_FOREACH(adev->dev->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);
- if (dev_frames < 0) {
- thread_remove_stream(thread, curr->stream, NULL);
- continue;
- }
- ATLOG(atlog,
- AUDIO_THREAD_WRITE_STREAMS_STREAM,
- curr->stream->stream_id,
- dev_frames,
- cras_shm_callback_pending(cras_rstream_output_shm(curr->stream)));
- if (cras_rstream_get_is_draining(curr->stream)) {
- drain_limit = MIN((size_t)dev_frames, drain_limit);
- if (!dev_frames)
- thread_remove_stream(thread, curr->stream,
- NULL);
- } else {
- write_limit = MIN((size_t)dev_frames, write_limit);
- num_playing++;
- }
- }
-
- if (!num_playing)
- write_limit = drain_limit;
-
- if (write_limit > max_offset)
- memset(dst + max_offset * frame_bytes, 0,
- (write_limit - max_offset) * frame_bytes);
-
- ATLOG(atlog, AUDIO_THREAD_WRITE_STREAMS_MIX,
- write_limit, max_offset, 0);
-
- DL_FOREACH(adev->dev->streams, curr) {
- unsigned int offset;
- int nwritten;
-
- offset = cras_iodev_stream_offset(odev, curr);
- if (offset >= write_limit)
- continue;
- nwritten = dev_stream_mix(curr, odev->ext_format,
- dst + frame_bytes * offset,
- write_limit - offset);
-
- if (nwritten < 0) {
- thread_remove_stream(thread, curr->stream, NULL);
- continue;
- }
-
- cras_iodev_stream_written(odev, curr, nwritten);
- }
-
- write_limit = cras_iodev_all_streams_written(odev);
-
- ATLOG(atlog, AUDIO_THREAD_WRITE_STREAMS_MIXED,
- write_limit, 0, 0);
-
- return write_limit;
-}
-
-/* Gets the max delay frames of open input devices. */
-static int input_delay_frames(struct open_dev *adevs)
-{
+ struct open_dev *idev_list = thread->open_devs[CRAS_STREAM_INPUT];
struct open_dev *adev;
- int delay;
- int max_delay = 0;
+ struct dev_stream *stream;
- DL_FOREACH(adevs, adev) {
+ DL_FOREACH(idev_list, adev) {
if (!cras_iodev_is_open(adev->dev))
continue;
- delay = cras_iodev_delay_frames(adev->dev);
- if (delay < 0)
- return delay;
- if (delay > max_delay)
- max_delay = delay;
+
+ DL_FOREACH(adev->dev->streams, stream) {
+ if ((stream->stream->apm_list == NULL) ||
+ (stream->stream->stream_id != stream_id))
+ continue;
+
+ cras_apm_list_set_aec_dump(stream->stream->apm_list,
+ adev->dev, start, fd);
+ }
}
- return max_delay;
+ return 0;
}
/* Stop the playback thread */
@@ -778,6 +563,7 @@
di->num_underruns = cras_iodev_get_num_underruns(adev->dev);
di->num_severe_underruns = cras_iodev_get_num_severe_underruns(
adev->dev);
+ di->highest_hw_level = adev->dev->highest_hw_level;
if (fmt) {
di->frame_rate = fmt->frame_rate;
di->num_channels = fmt->num_channels;
@@ -812,6 +598,7 @@
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);
+ si->effects = cras_apm_list_get_effects(stream->stream->apm_list);
longest_wake.tv_sec = 0;
longest_wake.tv_nsec = 0;
@@ -835,10 +622,8 @@
case AUDIO_THREAD_ADD_STREAM: {
struct audio_thread_add_rm_stream_msg *amsg;
amsg = (struct audio_thread_add_rm_stream_msg *)msg;
- ATLOG(
- atlog,
- AUDIO_THREAD_WRITE_STREAMS_WAIT,
- amsg->stream->stream_id, 0, 0);
+ ATLOG(atlog, AUDIO_THREAD_WRITE_STREAMS_WAIT,
+ amsg->stream->stream_id, 0, 0);
ret = thread_add_stream(thread, amsg->stream, amsg->devs,
amsg->num_devs);
break;
@@ -866,6 +651,13 @@
ret = thread_rm_open_dev(thread, rmsg->dev);
break;
}
+ case AUDIO_THREAD_IS_DEV_OPEN: {
+ struct audio_thread_open_device_msg *rmsg;
+
+ rmsg = (struct audio_thread_open_device_msg *)msg;
+ ret = thread_is_dev_open(thread, rmsg->dev);
+ break;
+ }
case AUDIO_THREAD_STOP:
ret = 0;
err = audio_thread_send_response(thread, ret);
@@ -938,10 +730,10 @@
/* Respond the pointer to the old remix converter, so it can be
* freed later in main thread. */
- rsp = (void *)remix_converter;
+ rsp = (void *)thread->remix_converter;
rmsg = (struct audio_thread_config_global_remix *)msg;
- remix_converter = rmsg->fmt_conv;
+ thread->remix_converter = rmsg->fmt_conv;
return write(thread->to_main_fds[1], &rsp, sizeof(rsp));
}
@@ -952,6 +744,13 @@
ret = thread_dev_start_ramp(thread, rmsg->dev, rmsg->request);
break;
}
+ case AUDIO_THREAD_AEC_DUMP: {
+ struct audio_thread_aec_dump_msg *rmsg;
+ rmsg = (struct audio_thread_aec_dump_msg *)msg;
+ ret = thread_set_aec_dump(thread, rmsg->stream_id,
+ rmsg->start, rmsg->fd);
+ break;
+ }
default:
ret = -EINVAL;
break;
@@ -983,11 +782,9 @@
if (!next_cb_ts)
continue;
- ATLOG(atlog,
- AUDIO_THREAD_STREAM_SLEEP_TIME,
- dev_stream->stream->stream_id,
- next_cb_ts->tv_sec,
- next_cb_ts->tv_nsec);
+ ATLOG(atlog, AUDIO_THREAD_STREAM_SLEEP_TIME,
+ dev_stream->stream->stream_id, next_cb_ts->tv_sec,
+ next_cb_ts->tv_nsec);
if (timespec_after(min_ts, next_cb_ts))
*min_ts = *next_cb_ts;
ret++;
@@ -996,53 +793,23 @@
return ret;
}
-static int get_next_output_wake(struct audio_thread *thread,
- struct timespec *min_ts,
- const struct timespec *now)
+static int get_next_output_wake(struct open_dev **odevs,
+ struct timespec *min_ts,
+ const struct timespec *now)
{
struct open_dev *adev;
- struct timespec sleep_time;
- double est_rate;
int ret = 0;
- unsigned int frames_to_play_in_sleep;
- unsigned int hw_level = 0;
- DL_FOREACH(thread->open_devs[CRAS_STREAM_OUTPUT], adev)
+ DL_FOREACH(*odevs, adev)
ret += get_next_stream_wake_from_list(
adev->dev->streams,
min_ts);
- DL_FOREACH(thread->open_devs[CRAS_STREAM_OUTPUT], adev) {
+ DL_FOREACH(*odevs, adev) {
if (!cras_iodev_odev_should_wake(adev->dev))
continue;
- 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);
-
- ATLOG(atlog,
- AUDIO_THREAD_SET_DEV_WAKE,
- adev->dev->info.idx,
- hw_level,
- frames_to_play_in_sleep);
-
- cras_frames_to_time_precise(
- frames_to_play_in_sleep,
- est_rate,
- &sleep_time);
-
- add_timespecs(&adev->wake_ts, &sleep_time);
-
ret++;
- ATLOG(atlog,
- AUDIO_THREAD_DEV_SLEEP_TIME,
- adev->dev->info.idx,
- adev->wake_ts.tv_sec,
- adev->wake_ts.tv_nsec);
if (timespec_after(min_ts, &adev->wake_ts))
*min_ts = adev->wake_ts;
}
@@ -1050,450 +817,13 @@
return ret;
}
-static int input_adev_ignore_wake(const struct open_dev *adev)
-{
- if (!cras_iodev_is_open(adev->dev))
- return 1;
-
- if (!adev->dev->active_node)
- return 1;
-
- if (adev->dev->active_node->type == CRAS_NODE_TYPE_HOTWORD &&
- !adev->input_streaming)
- return 1;
-
- return 0;
-}
-
-static int get_next_input_wake(struct audio_thread *thread,
- struct timespec *min_ts,
- const struct timespec *now)
-{
- struct open_dev *adev;
- int ret = 0; /* The total number of devices to wait on. */
-
- DL_FOREACH(thread->open_devs[CRAS_STREAM_INPUT], adev) {
- if (input_adev_ignore_wake(adev))
- continue;
- ret++;
- ATLOG(atlog,
- AUDIO_THREAD_DEV_SLEEP_TIME,
- adev->dev->info.idx,
- adev->wake_ts.tv_sec,
- adev->wake_ts.tv_nsec);
- if (timespec_after(min_ts, &adev->wake_ts))
- *min_ts = adev->wake_ts;
- }
-
- return ret;
-}
-
-static int output_stream_fetch(struct audio_thread *thread)
-{
- struct open_dev *odev_list = thread->open_devs[CRAS_STREAM_OUTPUT];
- struct open_dev *adev;
-
- DL_FOREACH(odev_list, adev) {
- if (!cras_iodev_is_open(adev->dev))
- continue;
- fetch_streams(thread, adev);
- }
-
- return 0;
-}
-
-static int wait_pending_output_streams(struct audio_thread *thread)
-{
- /* TODO(dgreid) - is this needed? */
- return 0;
-}
-
-/* Gets the master device which the stream is attached to. */
-static inline
-struct cras_iodev *get_master_dev(const struct dev_stream *stream)
-{
- return (struct cras_iodev *)stream->stream->master_dev.dev_ptr;
-}
-
-/* Updates the estimated sample rate of open device to all attached
- * streams.
- */
-static void update_estimated_rate(struct audio_thread *thread,
- struct open_dev *adev)
-{
- struct cras_iodev *master_dev;
- struct cras_iodev *dev = adev->dev;
- struct dev_stream *dev_stream;
-
- DL_FOREACH(dev->streams, dev_stream) {
- master_dev = get_master_dev(dev_stream);
- if (master_dev == NULL) {
- syslog(LOG_ERR, "Fail to find master open dev.");
- continue;
- }
-
- dev_stream_set_dev_rate(dev_stream,
- dev->ext_format->frame_rate,
- cras_iodev_get_est_rate_ratio(dev),
- cras_iodev_get_est_rate_ratio(master_dev),
- adev->coarse_rate_adjust);
- }
-}
-
-/* Returns 0 on success negative error on device failure. */
-static int write_output_samples(struct audio_thread *thread,
- struct open_dev *adev)
-{
- 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;
- int rc;
- uint8_t *dst = NULL;
- struct cras_audio_area *area = NULL;
-
- /* 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;
- }
-
- 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;
-
- 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);
-
- /* Don't request more than hardware can hold. Note that min_buffer_level
- * has been subtracted from the actual hw_level so we need to take it
- * into account here. */
- fr_to_req = cras_iodev_buffer_avail(odev, hw_level);
-
- /* Have to loop writing to the device, will be at most 2 loops, this
- * only happens when the circular buffer is at the end and returns us a
- * partial area to write to from mmap_begin */
- while (total_written < fr_to_req) {
- frames = fr_to_req - total_written;
- rc = cras_iodev_get_output_buffer(odev, &area, &frames);
- if (rc < 0)
- return rc;
-
- /* TODO(dgreid) - This assumes interleaved audio. */
- dst = area->channels[0].buf;
- written = write_streams(thread, adev, dst, frames);
- if (written < 0) /* pcm has been closed */
- return (int)written;
-
- if (written < (snd_pcm_sframes_t)frames)
- /* Got all the samples from client that we can, but it
- * won't fill the request. */
- fr_to_req = 0; /* break out after committing samples */
-
- rc = cras_iodev_put_output_buffer(odev, dst, written);
- if (rc < 0)
- return rc;
- total_written += written;
- }
-
- /* 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,
- hw_level, total_written, odev->min_cb_level);
- return 0;
-}
-
-static int do_playback(struct audio_thread *thread)
-{
- struct open_dev *adev;
- struct dev_stream *curr;
- int rc;
-
- /* For multiple output case, update the number of queued frames in shm
- * of all streams before starting write output samples. */
- adev = thread->open_devs[CRAS_STREAM_OUTPUT];
- if (adev && adev->next) {
- DL_FOREACH(thread->open_devs[CRAS_STREAM_OUTPUT], adev) {
- DL_FOREACH(adev->dev->streams, curr)
- dev_stream_update_frames(curr);
- }
- }
-
- DL_FOREACH(thread->open_devs[CRAS_STREAM_OUTPUT], adev) {
- if (!cras_iodev_is_open(adev->dev))
- continue;
-
- rc = write_output_samples(thread, adev);
- if (rc < 0) {
- 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);
- }
- }
- }
-
- /* TODO(dgreid) - once per rstream, not once per dev_stream. */
- DL_FOREACH(thread->open_devs[CRAS_STREAM_OUTPUT], adev) {
- struct dev_stream *stream;
- if (!cras_iodev_is_open(adev->dev))
- continue;
- DL_FOREACH(adev->dev->streams, stream) {
- dev_stream_playback_update_rstream(stream);
- }
- }
-
- return 0;
-}
-
-/* Gets the minimum amount of space available for writing across all streams.
- * Args:
- * adev - The device to capture from.
- * write_limit - Initial limit to number of frames to capture.
- */
-static unsigned int get_stream_limit_set_delay(struct open_dev *adev,
- unsigned int write_limit)
-{
- struct cras_rstream *rstream;
- 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);
-
- DL_FOREACH(adev->dev->streams, stream) {
- rstream = stream->stream;
-
- shm = cras_rstream_input_shm(rstream);
- 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);
- avail = dev_stream_capture_avail(stream);
- write_limit = MIN(write_limit, avail);
- }
-
- return write_limit;
-}
-
-/* Read samples from an input device to the specified stream.
- * Args:
- * adev - The device to capture samples from.
- * Returns 0 on success.
- */
-static int capture_to_streams(struct audio_thread *thread,
- struct open_dev *adev)
-{
- struct cras_iodev *idev = adev->dev;
- snd_pcm_uframes_t remainder, hw_level, cap_limit;
- struct timespec hw_tstamp;
- int rc;
-
- rc = cras_iodev_frames_queued(idev, &hw_tstamp);
- if (rc < 0)
- return rc;
- hw_level = rc;
-
- 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 (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);
- }
-
- 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 (cras_iodev_state(idev) != CRAS_IODEV_STATE_NORMAL_RUN)
- return 0;
-
- while (remainder > 0) {
- struct cras_audio_area *area = NULL;
- struct dev_stream *stream;
- unsigned int nread, total_read;
-
- nread = remainder;
-
- rc = cras_iodev_get_input_buffer(idev, &area, &nread);
- if (rc < 0 || nread == 0)
- return rc;
-
- DL_FOREACH(adev->dev->streams, stream) {
- unsigned int this_read;
- unsigned int area_offset;
-
- area_offset = cras_iodev_stream_offset(idev, stream);
- 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)
- total_read = cras_iodev_all_streams_written(idev);
- else
- total_read = nread; /* No streams, drop. */
-
- rc = cras_iodev_put_input_buffer(idev, total_read);
- if (rc < 0)
- return rc;
- remainder -= nread;
-
- if (total_read < nread)
- break;
- }
-
- ATLOG(atlog, AUDIO_THREAD_READ_AUDIO_DONE,
- remainder, 0, 0);
-
- return 0;
-}
-
-static int do_capture(struct audio_thread *thread)
-{
- struct open_dev *idev_list = thread->open_devs[CRAS_STREAM_INPUT];
- struct open_dev *adev;
-
- DL_FOREACH(idev_list, adev) {
- if (!cras_iodev_is_open(adev->dev))
- continue;
- if (capture_to_streams(thread, adev) < 0)
- thread_rm_open_adev(thread, adev);
- }
-
- 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;
- int rc;
-
- // TODO(dgreid) - once per rstream, not once per dev_stream.
- DL_FOREACH(idev_list, adev) {
- struct dev_stream *stream;
-
- if (!cras_iodev_is_open(adev->dev))
- continue;
-
- /* Post samples to rstream if there are enough samples. */
- DL_FOREACH(adev->dev->streams, stream) {
- dev_stream_capture_update_rstream(stream);
- }
-
- /* Set wake_ts for this device. */
- rc = set_input_dev_wake_ts(adev);
- if (rc < 0)
- return rc;
- }
-
- return 0;
-}
-
-/* Reads and/or writes audio sampels from/to the devices. */
-static int stream_dev_io(struct audio_thread *thread)
-{
- output_stream_fetch(thread);
- do_capture(thread);
- send_captured_samples(thread);
- wait_pending_output_streams(thread);
- do_playback(thread);
-
- return 0;
-}
-
-int fill_next_sleep_interval(struct audio_thread *thread, struct timespec *ts)
+/* Returns the number of active streams plus the number of active devices. */
+static int fill_next_sleep_interval(struct audio_thread *thread,
+ struct timespec *ts)
{
struct timespec min_ts;
struct timespec now;
- int ret; /* The sum of active streams and devices. */
+ int ret;
ts->tv_sec = 0;
ts->tv_nsec = 0;
@@ -1502,14 +832,53 @@
min_ts.tv_nsec = 0;
clock_gettime(CLOCK_MONOTONIC_RAW, &now);
add_timespecs(&min_ts, &now);
- ret = get_next_output_wake(thread, &min_ts, &now);
- ret += get_next_input_wake(thread, &min_ts, &now);
+ ret = get_next_output_wake(&thread->open_devs[CRAS_STREAM_OUTPUT],
+ &min_ts, &now);
+ ret += dev_io_next_input_wake(&thread->open_devs[CRAS_STREAM_INPUT],
+ &min_ts);
if (timespec_after(&min_ts, &now))
subtract_timespecs(&min_ts, &now, ts);
return ret;
}
+static struct pollfd *add_pollfd(struct audio_thread *thread,
+ int fd, int is_write)
+{
+ thread->pollfds[thread->num_pollfds].fd = fd;
+ if (is_write)
+ thread->pollfds[thread->num_pollfds].events = POLLOUT;
+ else
+ thread->pollfds[thread->num_pollfds].events = POLLIN;
+ thread->num_pollfds++;
+ if (thread->num_pollfds >= thread->pollfds_size) {
+ thread->pollfds_size *= 2;
+ thread->pollfds =
+ (struct pollfd *)realloc(thread->pollfds,
+ sizeof(*thread->pollfds) *
+ thread->pollfds_size);
+ return NULL;
+ }
+
+ return &thread->pollfds[thread->num_pollfds - 1];
+}
+
+static int continuous_zero_sleep_count = 0;
+static void check_busyloop(struct timespec* wait_ts)
+{
+ if(wait_ts->tv_sec == 0 && wait_ts->tv_nsec == 0)
+ {
+ continuous_zero_sleep_count ++;
+ if(continuous_zero_sleep_count ==
+ MAX_CONTINUOUS_ZERO_SLEEP_COUNT)
+ cras_audio_thread_busyloop();
+ }
+ else
+ {
+ continuous_zero_sleep_count = 0;
+ }
+}
+
/* For playback, fill the audio buffer when needed, for capture, pull out
* samples when they are ready.
* This thread will attempt to run at a high priority to allow for low latency
@@ -1523,9 +892,6 @@
struct open_dev *adev;
struct dev_stream *curr;
struct timespec ts, now, last_wake;
- struct pollfd *pollfds;
- unsigned int num_pollfds;
- unsigned int pollfds_size = 32;
int msg_fd;
int rc;
@@ -1539,44 +905,34 @@
longest_wake.tv_sec = 0;
longest_wake.tv_nsec = 0;
- pollfds = (struct pollfd *)malloc(sizeof(*pollfds) * pollfds_size);
- pollfds[0].fd = msg_fd;
- pollfds[0].events = POLLIN;
+ thread->pollfds[0].fd = msg_fd;
+ thread->pollfds[0].events = POLLIN;
while (1) {
struct timespec *wait_ts;
struct iodev_callback_list *iodev_cb;
wait_ts = NULL;
- num_pollfds = 1;
+ thread->num_pollfds = 1;
/* device opened */
- rc = stream_dev_io(thread);
- if (rc < 0)
- syslog(LOG_ERR, "audio cb error %d", rc);
+ dev_io_run(&thread->open_devs[CRAS_STREAM_OUTPUT],
+ &thread->open_devs[CRAS_STREAM_INPUT],
+ thread->remix_converter);
if (fill_next_sleep_interval(thread, &ts))
wait_ts = &ts;
restart_poll_loop:
- num_pollfds = 1;
+ thread->num_pollfds = 1;
DL_FOREACH(iodev_callbacks, iodev_cb) {
if (!iodev_cb->enabled)
continue;
- pollfds[num_pollfds].fd = iodev_cb->fd;
- iodev_cb->pollfd = &pollfds[num_pollfds];
- if (iodev_cb->is_write)
- pollfds[num_pollfds].events = POLLOUT;
- else
- pollfds[num_pollfds].events = POLLIN;
- num_pollfds++;
- if (num_pollfds >= pollfds_size) {
- pollfds_size *= 2;
- pollfds = (struct pollfd *)realloc(pollfds,
- sizeof(*pollfds) * pollfds_size);
- goto restart_poll_loop;
- }
+ iodev_cb->pollfd = add_pollfd(thread, iodev_cb->fd,
+ iodev_cb->is_write);
+ if (!iodev_cb->pollfd)
+ goto restart_poll_loop;
}
/* TODO(dgreid) - once per rstream not per dev_stream */
@@ -1585,17 +941,17 @@
int fd = dev_stream_poll_stream_fd(curr);
if (fd < 0)
continue;
- pollfds[num_pollfds].fd = fd;
- pollfds[num_pollfds].events = POLLIN;
- num_pollfds++;
- if (num_pollfds >= pollfds_size) {
- pollfds_size *= 2;
- pollfds = (struct pollfd *)realloc(
- pollfds,
- sizeof(*pollfds) *
- pollfds_size);
+ if (!add_pollfd(thread, fd, 0))
goto restart_poll_loop;
- }
+ }
+ }
+ DL_FOREACH(thread->open_devs[CRAS_STREAM_INPUT], adev) {
+ DL_FOREACH(adev->dev->streams, curr) {
+ int fd = dev_stream_poll_stream_fd(curr);
+ if (fd < 0)
+ continue;
+ if (!add_pollfd(thread, fd, 0))
+ goto restart_poll_loop;
}
}
@@ -1607,17 +963,17 @@
longest_wake = this_wake;
}
- ATLOG(atlog, AUDIO_THREAD_SLEEP,
- wait_ts ? wait_ts->tv_sec : 0,
- wait_ts ? wait_ts->tv_nsec : 0,
- longest_wake.tv_nsec);
- rc = ppoll(pollfds, num_pollfds, wait_ts, NULL);
+ ATLOG(atlog, AUDIO_THREAD_SLEEP, wait_ts ? wait_ts->tv_sec : 0,
+ wait_ts ? wait_ts->tv_nsec : 0, longest_wake.tv_nsec);
+ if(wait_ts)
+ check_busyloop(wait_ts);
+ rc = ppoll(thread->pollfds, thread->num_pollfds, wait_ts, NULL);
clock_gettime(CLOCK_MONOTONIC_RAW, &last_wake);
ATLOG(atlog, AUDIO_THREAD_WAKE, rc, 0, 0);
if (rc <= 0)
continue;
- if (pollfds[0].revents & POLLIN) {
+ if (thread->pollfds[0].revents & POLLIN) {
rc = handle_playback_thread_message(thread);
if (rc < 0)
syslog(LOG_INFO, "handle message %d", rc);
@@ -1626,9 +982,8 @@
DL_FOREACH(iodev_callbacks, iodev_cb) {
if (iodev_cb->pollfd &&
iodev_cb->pollfd->revents & (POLLIN | POLLOUT)) {
- ATLOG(
- atlog, AUDIO_THREAD_IODEV_CB,
- iodev_cb->is_write, 0, 0);
+ ATLOG(atlog, AUDIO_THREAD_IODEV_CB,
+ iodev_cb->is_write, 0, 0);
iodev_cb->cb(iodev_cb->cb_data);
}
}
@@ -1651,8 +1006,7 @@
static int audio_thread_post_message(struct audio_thread *thread,
struct audio_thread_msg *msg)
{
- int err;
- void *rsp;
+ int err, rsp;
err = write(thread->to_thread_fds[1], msg, msg->length);
if (err < 0) {
@@ -1660,13 +1014,13 @@
return err;
}
/* Synchronous action, wait for response. */
- err = read(thread->to_main_fds[0], &rsp, sizeof(rsp));
+ err = read_until_finished(thread->to_main_fds[0], &rsp, sizeof(rsp));
if (err < 0) {
syslog(LOG_ERR, "Failed to read reply from thread.");
return err;
}
- return (intptr_t)rsp;
+ return rsp;
}
static void init_open_device_msg(struct audio_thread_open_device_msg *msg,
@@ -1777,6 +1131,22 @@
return audio_thread_post_message(thread, &msg.header);
}
+int audio_thread_set_aec_dump(struct audio_thread *thread,
+ cras_stream_id_t stream_id,
+ unsigned int start,
+ int fd)
+{
+ struct audio_thread_aec_dump_msg msg;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.header.id = AUDIO_THREAD_AEC_DUMP;
+ msg.header.length = sizeof(msg);
+ msg.stream_id = stream_id;
+ msg.start = start;
+ msg.fd = fd;
+ 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;
@@ -1810,9 +1180,10 @@
}
for (j = i + 1; j < num_channels; j++) {
if (coefficient[i * num_channels + j] != 0 ||
- coefficient[j * num_channels + i] != 0)
+ coefficient[j * num_channels + i] != 0) {
identity_remix = 0;
break;
+ }
}
}
@@ -1829,22 +1200,17 @@
return err;
}
/* Synchronous action, wait for response. */
- err = read(thread->to_main_fds[0], &rsp, sizeof(rsp));
+ err = read_until_finished(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);
+ 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;
@@ -1875,6 +1241,11 @@
atlog = audio_thread_event_log_init();
+ thread->pollfds_size = 32;
+ thread->pollfds =
+ (struct pollfd *)malloc(sizeof(*thread->pollfds)
+ * thread->pollfds_size);
+
return thread;
}
@@ -1905,6 +1276,18 @@
return audio_thread_post_message(thread, &msg.header);
}
+int audio_thread_is_dev_open(struct audio_thread *thread,
+ struct cras_iodev *dev)
+{
+ struct audio_thread_open_device_msg msg;
+
+ if (!dev)
+ return 0;
+
+ init_open_device_msg(&msg, AUDIO_THREAD_IS_DEV_OPEN, 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)
@@ -1938,8 +1321,6 @@
void audio_thread_destroy(struct audio_thread *thread)
{
- audio_thread_event_log_deinit(atlog);
-
if (thread->started) {
struct audio_thread_msg msg;
@@ -1949,6 +1330,10 @@
pthread_join(thread->tid, NULL);
}
+ free(thread->pollfds);
+
+ audio_thread_event_log_deinit(atlog);
+
if (thread->to_thread_fds[0] != -1) {
close(thread->to_thread_fds[0]);
close(thread->to_thread_fds[1]);
@@ -1958,8 +1343,8 @@
close(thread->to_main_fds[1]);
}
- if (remix_converter)
- cras_fmt_conv_destroy(remix_converter);
+ if (thread->remix_converter)
+ cras_fmt_conv_destroy(&thread->remix_converter);
free(thread);
}
diff --git a/cras/src/server/audio_thread.h b/cras/src/server/audio_thread.h
index 1131e0c..e2c0d72 100644
--- a/cras/src/server/audio_thread.h
+++ b/cras/src/server/audio_thread.h
@@ -11,26 +11,14 @@
#include "cras_iodev.h"
#include "cras_types.h"
+#include "dev_io.h"
struct buffer_share;
+struct cras_fmt_conv;
struct cras_iodev;
struct cras_rstream;
struct dev_stream;
-/* Open input/output devices.
- * dev - The device.
- * wake_ts - When callback is needed to avoid xrun.
- * coarse_rate_adjust - Hack for when the sample rate needs heavy correction.
- * input_streaming - For capture, has the input started?
- */
-struct open_dev {
- struct cras_iodev *dev;
- struct timespec wake_ts;
- int coarse_rate_adjust;
- int input_streaming;
- struct open_dev *prev, *next;
-};
-
/* Hold communication pipes and pthread info for the thread used to play or
* record audio.
* to_thread_fds - Send a message from main to running thread.
@@ -39,6 +27,10 @@
* started - Non-zero if the thread has started successfully.
* suspended - Non-zero if the thread is suspended.
* open_devs - Lists of open input and output devices.
+ * pollfds - What FDs wake up this thread.
+ * pollfds_size - Number of available poll fds.
+ * num_pollfds - Number of currently registered poll fds.
+ * remix_converter - Format converter used to remix output channels.
*/
struct audio_thread {
int to_thread_fds[2];
@@ -47,7 +39,10 @@
int started;
int suspended;
struct open_dev *open_devs[CRAS_NUM_DIRECTIONS];
-
+ struct pollfd *pollfds;
+ size_t pollfds_size;
+ size_t num_pollfds;
+ struct cras_fmt_conv *remix_converter;
};
/* Callback function to be handled in main loop in audio thread.
@@ -58,7 +53,7 @@
/* Creates an audio thread.
* Returns:
- * A pointer to the newly create audio thread. It must be freed by calling
+ * A pointer to the newly created audio thread. It must be freed by calling
* audio_thread_destroy(). Returns NULL on error.
*/
struct audio_thread *audio_thread_create();
@@ -79,6 +74,14 @@
int audio_thread_rm_open_dev(struct audio_thread *thread,
struct cras_iodev *dev);
+/* Checks if dev is open and used by audio thread.
+ * Args:
+ * thread - The thread accessing open devs.
+ * dev - The device to check if it has already been open.
+ */
+int audio_thread_is_dev_open(struct audio_thread *thread,
+ struct cras_iodev *dev);
+
/* Adds an thread_callback to audio thread.
* Args:
* fd - The file descriptor to be polled for the callback.
@@ -172,16 +175,24 @@
int audio_thread_dump_thread_info(struct audio_thread *thread,
struct audio_debug_info *info);
+/* Starts or stops the aec dump task.
+ * Args:
+ * thread - pointer to the audio thread.
+ * stream_id - id of the target stream for aec dump.
+ * start - True to start the aec dump, false to stop.
+ * fd - File to store aec dump result.
+ */
+int audio_thread_set_aec_dump(struct audio_thread *thread,
+ cras_stream_id_t stream_id,
+ unsigned int start,
+ int fd);
+
/* 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
diff --git a/cras/src/server/config/aec_config.c b/cras/src/server/config/aec_config.c
new file mode 100644
index 0000000..d51d4b8
--- /dev/null
+++ b/cras/src/server/config/aec_config.c
@@ -0,0 +1,479 @@
+/* Copyright 2018 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 "aec_config.h"
+#include "iniparser_wrapper.h"
+
+static const unsigned int MAX_INI_NAME_LEN = 63;
+
+#define AEC_CONFIG_NAME "aec.ini"
+
+#define AEC_GET_INT(ini, category, key) \
+ iniparser_getint( \
+ ini, AEC_ ## category ## _ ## key, \
+ AEC_ ## category ## _ ## key ## _VALUE)
+
+#define AEC_GET_FLOAT(ini, category, key) \
+ iniparser_getdouble( \
+ ini, AEC_ ## category ## _ ## key, \
+ AEC_ ## category ## _ ## key ## _VALUE)
+
+struct aec_config *aec_config_get(const char *device_config_dir)
+{
+ struct aec_config *config;
+ char ini_name[MAX_INI_NAME_LEN + 1];
+ dictionary *ini;
+
+ snprintf(ini_name, MAX_INI_NAME_LEN, "%s/%s",
+ device_config_dir, AEC_CONFIG_NAME);
+ ini_name[MAX_INI_NAME_LEN] = '\0';
+
+ ini = iniparser_load_wrapper(ini_name);
+ if (ini == NULL) {
+ syslog(LOG_DEBUG, "No ini file %s", ini_name);
+ return NULL;
+ }
+
+ config = (struct aec_config *)calloc(1, sizeof(*config));
+
+ config->delay.default_delay =
+ AEC_GET_INT(ini, DELAY, DEFAULT_DELAY);
+ config->delay.down_sampling_factor =
+ AEC_GET_INT(ini, DELAY, DOWN_SAMPLING_FACTOR);
+ config->delay.num_filters =
+ AEC_GET_INT(ini, DELAY, NUM_FILTERS);
+ config->delay.api_call_jitter_blocks =
+ AEC_GET_INT(ini, DELAY, API_CALL_JITTER_BLOCKS);
+ config->delay.min_echo_path_delay_blocks =
+ AEC_GET_INT(ini, DELAY, MIN_ECHO_PATH_DELAY_BLOCKS);
+ config->delay.delay_headroom_blocks =
+ AEC_GET_INT(ini, DELAY, DELAY_HEADROOM_BLOCKS);
+ config->delay.hysteresis_limit_1_blocks =
+ AEC_GET_INT(ini, DELAY, HYSTERESIS_LIMIT_1_BLOCKS);
+ config->delay.hysteresis_limit_2_blocks =
+ AEC_GET_INT(ini, DELAY, HYSTERESIS_LIMIT_2_BLOCKS);
+ config->delay.skew_hysteresis_blocks =
+ AEC_GET_INT(ini, DELAY, SKEW_HYSTERESIS_BLOCKS);
+ config->delay.fixed_capture_delay_samples =
+ AEC_GET_INT(ini, DELAY, FIXED_CAPTURE_DELAY_SAMPLES);
+
+ config->filter.main.length_blocks =
+ AEC_GET_INT(ini, FILTER_MAIN, LENGTH_BLOCKS);
+ config->filter.main.leakage_converged =
+ AEC_GET_FLOAT(ini, FILTER_MAIN, LEAKAGE_CONVERGED);
+ config->filter.main.leakage_diverged =
+ AEC_GET_FLOAT(ini, FILTER_MAIN, LEAKAGE_DIVERGED);
+ config->filter.main.error_floor =
+ AEC_GET_FLOAT(ini, FILTER_MAIN, ERROR_FLOOR);
+ config->filter.main.noise_gate =
+ AEC_GET_FLOAT(ini, FILTER_MAIN, NOISE_GATE);
+
+ config->filter.shadow.length_blocks =
+ AEC_GET_INT(ini, FILTER_SHADOW, LENGTH_BLOCKS);
+ config->filter.shadow.rate =
+ AEC_GET_FLOAT(ini, FILTER_SHADOW, RATE);
+ config->filter.shadow.noise_gate =
+ AEC_GET_FLOAT(ini, FILTER_SHADOW, NOISE_GATE);
+
+ config->filter.main_initial.length_blocks =
+ AEC_GET_INT(ini, FILTER_MAIN_INIT, LENGTH_BLOCKS);
+ config->filter.main_initial.leakage_converged =
+ AEC_GET_FLOAT(ini, FILTER_MAIN_INIT, LEAKAGE_CONVERGED);
+ config->filter.main_initial.leakage_diverged =
+ AEC_GET_FLOAT(ini, FILTER_MAIN_INIT, LEAKAGE_DIVERGED);
+ config->filter.main_initial.error_floor =
+ AEC_GET_FLOAT(ini, FILTER_MAIN_INIT, ERROR_FLOOR);
+ config->filter.main_initial.noise_gate =
+ AEC_GET_FLOAT(ini, FILTER_MAIN_INIT, NOISE_GATE);
+
+ config->filter.shadow_initial.length_blocks =
+ AEC_GET_INT(ini, FILTER_SHADOW_INIT, LENGTH_BLOCKS);
+ config->filter.shadow_initial.rate =
+ AEC_GET_FLOAT(ini, FILTER_SHADOW_INIT, RATE);
+ config->filter.shadow_initial.noise_gate =
+ AEC_GET_FLOAT(ini, FILTER_SHADOW_INIT, NOISE_GATE);
+
+ config->filter.config_change_duration_blocks =
+ AEC_GET_INT(ini, FILTER, CONFIG_CHANGE_DURATION_BLOCKS);
+ config->filter.initial_state_seconds =
+ AEC_GET_FLOAT(ini, FILTER, INITIAL_STATE_SECONDS);
+ config->filter.conservative_initial_phase =
+ AEC_GET_INT(ini, FILTER, CONSERVATIVE_INITIAL_PHASE);
+ config->filter.enable_shadow_filter_output_usage =
+ AEC_GET_INT(ini, FILTER, ENABLE_SHADOW_FILTER_OUTPUT_USAGE);
+
+ config->erle.min =
+ AEC_GET_FLOAT(ini, ERLE, MIN);
+ config->erle.max_l =
+ AEC_GET_FLOAT(ini, ERLE, MAX_L);
+ config->erle.max_h =
+ AEC_GET_FLOAT(ini, ERLE, MAX_H);
+ config->erle.onset_detection =
+ AEC_GET_INT(ini, ERLE, ONSET_DETECTION);
+
+ config->ep_strength.lf =
+ AEC_GET_FLOAT(ini, EP_STRENGTH, LF);
+ config->ep_strength.mf =
+ AEC_GET_FLOAT(ini, EP_STRENGTH, MF);
+ config->ep_strength.hf =
+ AEC_GET_FLOAT(ini, EP_STRENGTH, HF);
+ config->ep_strength.default_len =
+ AEC_GET_FLOAT(ini, EP_STRENGTH, DEFAULT_LEN);
+ config->ep_strength.reverb_based_on_render =
+ AEC_GET_INT(ini, EP_STRENGTH, REVERB_BASED_ON_RENDER);
+ config->ep_strength.bounded_erl =
+ AEC_GET_INT(ini, EP_STRENGTH, BOUNDED_ERL);
+ config->ep_strength.echo_can_saturate =
+ AEC_GET_INT(ini, EP_STRENGTH, ECHO_CAN_SATURATE);
+
+ config->gain_mask.m0 =
+ AEC_GET_FLOAT(ini, GAIN_MASK, M0);
+ config->gain_mask.m1 =
+ AEC_GET_FLOAT(ini, GAIN_MASK, M1);
+ config->gain_mask.m2 =
+ AEC_GET_FLOAT(ini, GAIN_MASK, M2);
+ config->gain_mask.m3 =
+ AEC_GET_FLOAT(ini, GAIN_MASK, M3);
+ config->gain_mask.m5 =
+ AEC_GET_FLOAT(ini, GAIN_MASK, M5);
+ config->gain_mask.m6 =
+ AEC_GET_FLOAT(ini, GAIN_MASK, M6);
+ config->gain_mask.m7 =
+ AEC_GET_FLOAT(ini, GAIN_MASK, M7);
+ config->gain_mask.m8 =
+ AEC_GET_FLOAT(ini, GAIN_MASK, M8);
+ config->gain_mask.m9 =
+ AEC_GET_FLOAT(ini, GAIN_MASK, M9);
+ config->gain_mask.gain_curve_offset =
+ AEC_GET_FLOAT(ini, GAIN_MASK, GAIN_CURVE_OFFSET);
+ config->gain_mask.gain_curve_slope =
+ AEC_GET_FLOAT(ini, GAIN_MASK, GAIN_CURVE_SLOPE);
+ config->gain_mask.temporal_masking_lf =
+ AEC_GET_FLOAT(ini, GAIN_MASK, TEMPORAL_MASKING_LF);
+ config->gain_mask.temporal_masking_hf =
+ AEC_GET_FLOAT(ini, GAIN_MASK, TEMPORAL_MASKING_HF);
+ config->gain_mask.temporal_masking_lf_bands =
+ AEC_GET_INT(ini, GAIN_MASK, TEMPORAL_MASKING_LF_BANDS);
+
+ config->echo_audibility.low_render_limit =
+ AEC_GET_FLOAT(ini, ECHO_AUDIBILITY, LOW_RENDER_LIMIT);
+ config->echo_audibility.normal_render_limit =
+ AEC_GET_FLOAT(ini, ECHO_AUDIBILITY, NORMAL_RENDER_LIMIT);
+ config->echo_audibility.floor_power =
+ AEC_GET_FLOAT(ini, ECHO_AUDIBILITY, FLOOR_POWER);
+ config->echo_audibility.audibility_threshold_lf =
+ AEC_GET_FLOAT(ini, ECHO_AUDIBILITY, AUDIBILITY_THRESHOLD_LF);
+ config->echo_audibility.audibility_threshold_mf =
+ AEC_GET_FLOAT(ini, ECHO_AUDIBILITY, AUDIBILITY_THRESHOLD_MF);
+ config->echo_audibility.audibility_threshold_hf =
+ AEC_GET_FLOAT(ini, ECHO_AUDIBILITY, AUDIBILITY_THRESHOLD_HF);
+ config->echo_audibility.use_stationary_properties =
+ AEC_GET_INT(ini, ECHO_AUDIBILITY, USE_STATIONARY_PROPERTIES);
+
+ config->render_levels.active_render_limit =
+ AEC_GET_FLOAT(ini, RENDER_LEVELS, ACTIVE_RENDER_LIMIT);
+ config->render_levels.poor_excitation_render_limit =
+ AEC_GET_FLOAT(ini, RENDER_LEVELS, POOR_EXCITATION_RENDER_LIMIT);
+ config->render_levels.poor_excitation_render_limit_ds8 =
+ AEC_GET_FLOAT(ini, RENDER_LEVELS, POOR_EXCITATION_RENDER_LIMIT_DS8);
+
+ config->echo_removal_control.gain_rampup.initial_gain =
+ AEC_GET_FLOAT(ini, ECHO_REMOVAL_CTL, INITIAL_GAIN);
+ config->echo_removal_control.gain_rampup.first_non_zero_gain =
+ AEC_GET_FLOAT(ini, ECHO_REMOVAL_CTL, FIRST_NON_ZERO_GAIN);
+ config->echo_removal_control.gain_rampup.non_zero_gain_blocks =
+ AEC_GET_INT(ini, ECHO_REMOVAL_CTL, NON_ZERO_GAIN_BLOCKS);
+ config->echo_removal_control.gain_rampup.full_gain_blocks =
+ AEC_GET_INT(ini, ECHO_REMOVAL_CTL, FULL_GAIN_BLOCKS);
+ config->echo_removal_control.has_clock_drift =
+ AEC_GET_INT(ini, ECHO_REMOVAL_CTL, HAS_CLOCK_DRIFT);
+ config->echo_removal_control.linear_and_stable_echo_path =
+ AEC_GET_INT(ini, ECHO_REMOVAL_CTL, LINEAR_AND_STABLE_ECHO_PATH);
+
+ config->echo_model.noise_floor_hold =
+ AEC_GET_INT(ini, ECHO_MODEL, NOISE_FLOOR_HOLD);
+ config->echo_model.min_noise_floor_power =
+ AEC_GET_FLOAT(ini, ECHO_MODEL, MIN_NOISE_FLOOR_POWER);
+ config->echo_model.stationary_gate_slope =
+ AEC_GET_FLOAT(ini, ECHO_MODEL, STATIONARY_GATE_SLOPE);
+ config->echo_model.noise_gate_power =
+ AEC_GET_FLOAT(ini, ECHO_MODEL, NOISE_GATE_POWER);
+ config->echo_model.noise_gate_slope =
+ AEC_GET_FLOAT(ini, ECHO_MODEL, NOISE_GATE_SLOPE);
+ config->echo_model.render_pre_window_size =
+ AEC_GET_INT(ini, ECHO_MODEL, RENDER_PRE_WINDOW_SIZE);
+ config->echo_model.render_post_window_size =
+ AEC_GET_INT(ini, ECHO_MODEL, RENDER_POST_WINDOW_SIZE);
+ config->echo_model.render_pre_window_size_init =
+ AEC_GET_INT(ini, ECHO_MODEL, RENDER_PRE_WINDOW_SIZE_INIT);
+ config->echo_model.render_post_window_size_init =
+ AEC_GET_INT(ini, ECHO_MODEL, RENDER_POST_WINDOW_SIZE_INIT);
+ config->echo_model.nonlinear_hold =
+ AEC_GET_FLOAT(ini, ECHO_MODEL, NONLINEAR_HOLD);
+ config->echo_model.nonlinear_release =
+ AEC_GET_FLOAT(ini, ECHO_MODEL, NONLINEAR_RELEASE);
+
+ config->suppressor.nearend_average_blocks =
+ AEC_GET_INT(ini, SUPPRESSOR, NEAREND_AVERAGE_BLOCKS);
+
+ config->suppressor.normal_tuning.mask_lf.enr_transparent =
+ AEC_GET_FLOAT(ini, SUPPRESSOR_NORMAL_TUNING,
+ MASK_LF_ENR_TRANSPARENT);
+ config->suppressor.normal_tuning.mask_lf.enr_suppress =
+ AEC_GET_FLOAT(ini, SUPPRESSOR_NORMAL_TUNING,
+ MASK_LF_ENR_SUPPRESS);
+ config->suppressor.normal_tuning.mask_lf.emr_transparent =
+ AEC_GET_FLOAT(ini, SUPPRESSOR_NORMAL_TUNING,
+ MASK_LF_EMR_TRANSPARENT);
+ config->suppressor.normal_tuning.mask_hf.enr_transparent =
+ AEC_GET_FLOAT(ini, SUPPRESSOR_NORMAL_TUNING,
+ MASK_HF_ENR_TRANSPARENT);
+ config->suppressor.normal_tuning.mask_hf.enr_suppress =
+ AEC_GET_FLOAT(ini, SUPPRESSOR_NORMAL_TUNING,
+ MASK_HF_ENR_SUPPRESS);
+ config->suppressor.normal_tuning.mask_hf.emr_transparent =
+ AEC_GET_FLOAT(ini, SUPPRESSOR_NORMAL_TUNING,
+ MASK_HF_EMR_TRANSPARENT);
+ config->suppressor.normal_tuning.max_inc_factor =
+ AEC_GET_FLOAT(ini, SUPPRESSOR_NORMAL_TUNING, MAX_INC_FACTOR);
+ config->suppressor.normal_tuning.max_dec_factor_lf =
+ AEC_GET_FLOAT(ini, SUPPRESSOR_NORMAL_TUNING, MAX_DEC_FACTOR_LF);
+
+ config->suppressor.nearend_tuning.mask_lf.enr_transparent =
+ AEC_GET_FLOAT(ini, SUPPRESSOR_NEAREND_TUNING,
+ MASK_LF_ENR_TRANSPARENT);
+ config->suppressor.nearend_tuning.mask_lf.enr_suppress =
+ AEC_GET_FLOAT(ini, SUPPRESSOR_NEAREND_TUNING,
+ MASK_LF_ENR_SUPPRESS);
+ config->suppressor.nearend_tuning.mask_lf.emr_transparent =
+ AEC_GET_FLOAT(ini, SUPPRESSOR_NEAREND_TUNING,
+ MASK_LF_EMR_TRANSPARENT);
+ config->suppressor.nearend_tuning.mask_hf.enr_transparent =
+ AEC_GET_FLOAT(ini, SUPPRESSOR_NEAREND_TUNING,
+ MASK_HF_ENR_TRANSPARENT);
+ config->suppressor.nearend_tuning.mask_hf.enr_suppress =
+ AEC_GET_FLOAT(ini, SUPPRESSOR_NEAREND_TUNING,
+ MASK_HF_ENR_SUPPRESS);
+ config->suppressor.nearend_tuning.mask_hf.emr_transparent =
+ AEC_GET_FLOAT(ini, SUPPRESSOR_NEAREND_TUNING,
+ MASK_HF_EMR_TRANSPARENT);
+ config->suppressor.nearend_tuning.max_inc_factor =
+ AEC_GET_FLOAT(ini, SUPPRESSOR_NEAREND_TUNING, MAX_INC_FACTOR);
+ config->suppressor.nearend_tuning.max_dec_factor_lf =
+ AEC_GET_FLOAT(ini, SUPPRESSOR_NEAREND_TUNING, MAX_DEC_FACTOR_LF);
+
+ config->suppressor.dominant_nearend_detection.enr_threshold =
+ AEC_GET_FLOAT(ini, SUPPRESSOR_DOMINANT_NEAREND_DETECTION,
+ ENR_THRESHOLD);
+ config->suppressor.dominant_nearend_detection.snr_threshold =
+ AEC_GET_FLOAT(ini, SUPPRESSOR_DOMINANT_NEAREND_DETECTION,
+ SNR_THRESHOLD);
+ config->suppressor.dominant_nearend_detection.hold_duration =
+ AEC_GET_INT(ini, SUPPRESSOR_DOMINANT_NEAREND_DETECTION,
+ HOLD_DURATION);
+ config->suppressor.dominant_nearend_detection.trigger_threshold =
+ AEC_GET_INT(ini, SUPPRESSOR_DOMINANT_NEAREND_DETECTION,
+ TRIGGER_THRESHOLD);
+
+ config->suppressor.high_bands_suppression.enr_threshold =
+ AEC_GET_FLOAT(ini, SUPPRESSOR_HIGH_BANDS_SUPPRESSION,
+ ENR_THRESHOLD);
+ config->suppressor.high_bands_suppression.max_gain_during_echo =
+ AEC_GET_FLOAT(ini, SUPPRESSOR_HIGH_BANDS_SUPPRESSION,
+ MAX_GAIN_DURING_ECHO);
+
+ config->suppressor.floor_first_increase =
+ AEC_GET_FLOAT(ini, SUPPRESSOR, FLOOR_FIRST_INCREASE);
+ config->suppressor.enforce_transparent =
+ AEC_GET_INT(ini, SUPPRESSOR, ENFORCE_TRANSPARENT);
+ config->suppressor.enforce_empty_higher_bands =
+ AEC_GET_INT(ini, SUPPRESSOR, ENFORCE_EMPTY_HIGHER_BANDS);
+
+ return config;
+}
+
+void aec_config_dump(struct aec_config *config)
+{
+ syslog(LOG_ERR, "---- aec config dump ----");
+ syslog(LOG_ERR, "Delay:");
+ syslog(LOG_ERR, " default_delay %zu sampling_factor %zu num_filters %zu",
+ config->delay.default_delay,
+ config->delay.down_sampling_factor,
+ config->delay.num_filters);
+ syslog(LOG_ERR, " api_call_jitter_blocks %zu, min_echo_path_delay_blocks %zu",
+ config->delay.api_call_jitter_blocks,
+ config->delay.min_echo_path_delay_blocks);
+ syslog(LOG_ERR, " delay_headroom_blocks %zu, hysteresis_limit_1_blocks %zu",
+ config->delay.delay_headroom_blocks,
+ config->delay.hysteresis_limit_1_blocks);
+ syslog(LOG_ERR, " hysteresis_limit_2_blocks %zu, skew_hysteresis_blocks %zu",
+ config->delay.hysteresis_limit_2_blocks,
+ config->delay.skew_hysteresis_blocks);
+ syslog(LOG_ERR, " fixed_capture_delay_samples %zu",
+ config->delay.fixed_capture_delay_samples);
+
+ syslog(LOG_ERR, "Filter main configuration:");
+ syslog(LOG_ERR, " length_blocks %zu, leakage_converged %f, leakage_diverged %f",
+ config->filter.main.length_blocks,
+ config->filter.main.leakage_converged,
+ config->filter.main.leakage_diverged);
+ syslog(LOG_ERR, " error_floor %f, noise_gate %f",
+ config->filter.main.error_floor,
+ config->filter.main.noise_gate);
+ syslog(LOG_ERR, "Filter shadow configuration:");
+ syslog(LOG_ERR, " length_blocks %zu, rate %f, noise_gate %f",
+ config->filter.shadow.length_blocks,
+ config->filter.shadow.rate,
+ config->filter.shadow.noise_gate);
+ syslog(LOG_ERR, "Filter main initial configuration:");
+ syslog(LOG_ERR, " length_blocks %zu, leakage_converged %f",
+ config->filter.main_initial.length_blocks,
+ config->filter.main_initial.leakage_converged);
+ syslog(LOG_ERR, " leakage_diverged %f, error_floor %f, noise_gate %f",
+ config->filter.main_initial.leakage_diverged,
+ config->filter.main_initial.error_floor,
+ config->filter.main_initial.noise_gate);
+ syslog(LOG_ERR, "Filter shadow initial configuration:");
+ syslog(LOG_ERR, " length_blocks %zu, rate %f, noise_gate %f",
+ config->filter.shadow_initial.length_blocks,
+ config->filter.shadow_initial.rate,
+ config->filter.shadow_initial.noise_gate);
+ syslog(LOG_ERR, "Filter: config_change_duration_blocks %d",
+ config->filter.config_change_duration_blocks);
+ syslog(LOG_ERR, " initial_state_seconds %f",
+ config->filter.initial_state_seconds);
+ syslog(LOG_ERR, " conservative_initial_phase %d",
+ config->filter.conservative_initial_phase);
+ syslog(LOG_ERR, " enable_shadow_filter_output_usage %d",
+ config->filter.enable_shadow_filter_output_usage);
+ syslog(LOG_ERR, "Erle: min %f max_l %f max_h %f onset_detection %d",
+ config->erle.min, config->erle.max_l,
+ config->erle.max_h, config->erle.onset_detection);
+ syslog(LOG_ERR, "Ep strength: lf %f mf %f hf %f default_len %f",
+ config->ep_strength.lf,
+ config->ep_strength.mf,
+ config->ep_strength.hf,
+ config->ep_strength.default_len);
+ syslog(LOG_ERR, " echo_can_saturate %d, bounded_erl %d,"
+ " ep_strength.reverb_based_on_render %d",
+ config->ep_strength.echo_can_saturate,
+ config->ep_strength.bounded_erl,
+ config->ep_strength.reverb_based_on_render);
+ syslog(LOG_ERR, "Gain mask: m0 %f m1 %f m2 %f m3 %f m5 %f",
+ config->gain_mask.m0,
+ config->gain_mask.m1,
+ config->gain_mask.m2,
+ config->gain_mask.m3,
+ config->gain_mask.m5);
+ syslog(LOG_ERR, " m6 %f m7 %f m8 %f m9 %f",
+ config->gain_mask.m6,
+ config->gain_mask.m7,
+ config->gain_mask.m8,
+ config->gain_mask.m9);
+ syslog(LOG_ERR, " gain_curve offset %f, gain_curve_slope %f",
+ config->gain_mask.gain_curve_offset,
+ config->gain_mask.gain_curve_slope);
+ syslog(LOG_ERR, " temporal_masking_lf %f, temporal_masking_hf %f",
+ config->gain_mask.temporal_masking_lf,
+ config->gain_mask.temporal_masking_hf);
+ syslog(LOG_ERR, " temporal_masking_lf_bands %zu",
+ config->gain_mask.temporal_masking_lf_bands);
+ syslog(LOG_ERR, "Echo audibility:");
+ syslog(LOG_ERR, " low_render_limit %f, normal_render_limit %f",
+ config->echo_audibility.low_render_limit,
+ config->echo_audibility.normal_render_limit);
+ syslog(LOG_ERR, " floor_power %f, audibility_threshold_lf %f",
+ config->echo_audibility.floor_power,
+ config->echo_audibility.audibility_threshold_lf);
+ syslog(LOG_ERR, " audibility_threshold_mf %f",
+ config->echo_audibility.audibility_threshold_mf);
+ syslog(LOG_ERR, " audibility_threshold_hf %f",
+ config->echo_audibility.audibility_threshold_hf);
+ syslog(LOG_ERR, " use_stationary_properties %d",
+ config->echo_audibility.use_stationary_properties);
+ syslog(LOG_ERR, "Render levels:");
+ syslog(LOG_ERR, " active_render_limit %f",
+ config->render_levels.active_render_limit);
+ syslog(LOG_ERR, " poor_excitation_render_limit %f",
+ config->render_levels.poor_excitation_render_limit);
+ syslog(LOG_ERR, " poor_excitation_render_limit_ds8 %f",
+ config->render_levels.poor_excitation_render_limit_ds8);
+ syslog(LOG_ERR, "Echo removal control:");
+ syslog(LOG_ERR, " gain rampup:");
+ syslog(LOG_ERR, " initial_gain %f, first_non_zero_gain %f",
+ config->echo_removal_control.gain_rampup.initial_gain,
+ config->echo_removal_control.gain_rampup.first_non_zero_gain);
+ syslog(LOG_ERR, " non_zero_gain_blocks %d, full_gain_blocks %d",
+ config->echo_removal_control.gain_rampup.non_zero_gain_blocks,
+ config->echo_removal_control.gain_rampup.full_gain_blocks);
+ syslog(LOG_ERR, " has_clock_drift %d",
+ config->echo_removal_control.has_clock_drift);
+ syslog(LOG_ERR, " linear_and_stable_echo_path %d",
+ config->echo_removal_control.linear_and_stable_echo_path);
+ syslog(LOG_ERR, "Echo model:");
+ syslog(LOG_ERR, " noise_floor_hold %zu, min_noise_floor_power %f",
+ config->echo_model.noise_floor_hold,
+ config->echo_model.min_noise_floor_power);
+ syslog(LOG_ERR, " stationary_gate_slope %f, noise_gate_power %f",
+ config->echo_model.stationary_gate_slope,
+ config->echo_model.noise_gate_power);
+ syslog(LOG_ERR, " noise_gate_slope %f, render_pre_window_size %zu",
+ config->echo_model.noise_gate_slope,
+ config->echo_model.render_pre_window_size);
+ syslog(LOG_ERR, " render_post_window_size %zu nonlinear_hold %f",
+ config->echo_model.render_post_window_size,
+ config->echo_model.nonlinear_hold);
+ syslog(LOG_ERR, " render_pre_window_size_init %u, "
+ "render_post_window_size_init %u",
+ config->echo_model.render_pre_window_size_init,
+ config->echo_model.render_post_window_size_init);
+ syslog(LOG_ERR, " nonlinear_release %f",
+ config->echo_model.nonlinear_release);
+ syslog(LOG_ERR, "Suppressor:");
+ syslog(LOG_ERR, " nearend_average_blocks %u",
+ config->suppressor.nearend_average_blocks);
+ syslog(LOG_ERR, " Normal tuning, mask_lf %f %f %f",
+ config->suppressor.normal_tuning.mask_lf.enr_transparent,
+ config->suppressor.normal_tuning.mask_lf.enr_suppress,
+ config->suppressor.normal_tuning.mask_lf.emr_transparent);
+ syslog(LOG_ERR, " mask_hf %f %f %f",
+ config->suppressor.normal_tuning.mask_hf.enr_transparent,
+ config->suppressor.normal_tuning.mask_hf.enr_suppress,
+ config->suppressor.normal_tuning.mask_hf.emr_transparent);
+ syslog(LOG_ERR, " max_inc_factor %f max_dec_factor_lf %f",
+ config->suppressor.normal_tuning.max_inc_factor,
+ config->suppressor.normal_tuning.max_dec_factor_lf);
+ syslog(LOG_ERR, " Nearend tuning, mask_lf %f %f %f",
+ config->suppressor.nearend_tuning.mask_lf.enr_transparent,
+ config->suppressor.nearend_tuning.mask_lf.enr_suppress,
+ config->suppressor.nearend_tuning.mask_lf.emr_transparent);
+ syslog(LOG_ERR, " mask_hf %f %f %f",
+ config->suppressor.nearend_tuning.mask_hf.enr_transparent,
+ config->suppressor.nearend_tuning.mask_hf.enr_suppress,
+ config->suppressor.nearend_tuning.mask_hf.emr_transparent);
+ syslog(LOG_ERR, " max_inc_factor %f max_dec_factor_lf %f",
+ config->suppressor.nearend_tuning.max_inc_factor,
+ config->suppressor.nearend_tuning.max_dec_factor_lf);
+ syslog(LOG_ERR, " Dominant nearend detection:");
+ syslog(LOG_ERR, " enr_threshold %f",
+ config->suppressor.dominant_nearend_detection.enr_threshold);
+ syslog(LOG_ERR, " snr_threshold %f",
+ config->suppressor.dominant_nearend_detection.snr_threshold);
+ syslog(LOG_ERR, " hold_duration %d",
+ config->suppressor.dominant_nearend_detection.hold_duration);
+ syslog(LOG_ERR, " trigger_threshold %d",
+ config->suppressor.dominant_nearend_detection.trigger_threshold);
+ syslog(LOG_ERR, " High bands suppression:");
+ syslog(LOG_ERR, " enr_threshold %f max_gain_during_echo %f",
+ config->suppressor.high_bands_suppression.enr_threshold,
+ config->suppressor.high_bands_suppression.max_gain_during_echo);
+ syslog(LOG_ERR, " floor_first_increase %f, enforce_transparent %d",
+ config->suppressor.floor_first_increase,
+ config->suppressor.enforce_transparent);
+ syslog(LOG_ERR, " enforce_empty_higher_bands %f",
+ config->suppressor.enforce_empty_higher_bands);
+}
diff --git a/cras/src/server/config/aec_config.h b/cras/src/server/config/aec_config.h
new file mode 100644
index 0000000..015ca76
--- /dev/null
+++ b/cras/src/server/config/aec_config.h
@@ -0,0 +1,318 @@
+/* Copyright 2018 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 AEC_CONFIG_H_
+#define AEC_CONFIG_H_
+
+#include <webrtc-apm/webrtc_apm.h>
+
+#define AEC_DELAY_DEFAULT_DELAY "delay:default_delay"
+#define AEC_DELAY_DEFAULT_DELAY_VALUE 5
+#define AEC_DELAY_DOWN_SAMPLING_FACTOR "delay:down_sampling_factor"
+#define AEC_DELAY_DOWN_SAMPLING_FACTOR_VALUE 4
+#define AEC_DELAY_NUM_FILTERS "delay:num_filters"
+#define AEC_DELAY_NUM_FILTERS_VALUE 6
+#define AEC_DELAY_API_CALL_JITTER_BLOCKS "delay:api_call_jitter_blocks"
+#define AEC_DELAY_API_CALL_JITTER_BLOCKS_VALUE 26
+#define AEC_DELAY_MIN_ECHO_PATH_DELAY_BLOCKS "delay:min_echo_path_delay_blocks"
+#define AEC_DELAY_MIN_ECHO_PATH_DELAY_BLOCKS_VALUE 0
+#define AEC_DELAY_DELAY_HEADROOM_BLOCKS "delay:delay_headroom_blocks"
+#define AEC_DELAY_DELAY_HEADROOM_BLOCKS_VALUE 2
+#define AEC_DELAY_HYSTERESIS_LIMIT_1_BLOCKS "delay:hysteresis_limit_1_blocks"
+#define AEC_DELAY_HYSTERESIS_LIMIT_1_BLOCKS_VALUE 1
+#define AEC_DELAY_HYSTERESIS_LIMIT_2_BLOCKS "delay:hysteresis_limit_2_blocks"
+#define AEC_DELAY_HYSTERESIS_LIMIT_2_BLOCKS_VALUE 1
+#define AEC_DELAY_SKEW_HYSTERESIS_BLOCKS "delay:skew_hysteresis_blocks"
+#define AEC_DELAY_SKEW_HYSTERESIS_BLOCKS_VALUE 3
+#define AEC_DELAY_FIXED_CAPTURE_DELAY_SAMPLES \
+ "delay:fixed_capture_delay_samples"
+#define AEC_DELAY_FIXED_CAPTURE_DELAY_SAMPLES_VALUE 0
+
+// Filter Main configuration
+#define AEC_FILTER_MAIN_LENGTH_BLOCKS "filter.main:length_blocks"
+#define AEC_FILTER_MAIN_LENGTH_BLOCKS_VALUE 13
+#define AEC_FILTER_MAIN_LEAKAGE_CONVERGED "filter.main:leakage_converged"
+#define AEC_FILTER_MAIN_LEAKAGE_CONVERGED_VALUE 0.00005f
+#define AEC_FILTER_MAIN_LEAKAGE_DIVERGED "filter.main:leakage_diverged"
+#define AEC_FILTER_MAIN_LEAKAGE_DIVERGED_VALUE 0.01f
+#define AEC_FILTER_MAIN_ERROR_FLOOR "filter.main:error_floor"
+#define AEC_FILTER_MAIN_ERROR_FLOOR_VALUE 0.1f
+#define AEC_FILTER_MAIN_NOISE_GATE "filter.main:noise_gate"
+#define AEC_FILTER_MAIN_NOISE_GATE_VALUE 20075344.f
+
+// Filter Shadow configuration
+#define AEC_FILTER_SHADOW_LENGTH_BLOCKS "filter.shadow:length_blocks"
+#define AEC_FILTER_SHADOW_LENGTH_BLOCKS_VALUE 13
+#define AEC_FILTER_SHADOW_RATE "filter.shadow:rate"
+#define AEC_FILTER_SHADOW_RATE_VALUE 0.7f
+#define AEC_FILTER_SHADOW_NOISE_GATE "filter.shadow:noise_gate"
+#define AEC_FILTER_SHADOW_NOISE_GATE_VALUE 20075344.f
+
+// Filter Main initial configuration
+#define AEC_FILTER_MAIN_INIT_LENGTH_BLOCKS "filter.main_initial:length_blocks"
+#define AEC_FILTER_MAIN_INIT_LENGTH_BLOCKS_VALUE 12
+#define AEC_FILTER_MAIN_INIT_LEAKAGE_CONVERGED \
+ "filter.main_initial:leakage_converged"
+#define AEC_FILTER_MAIN_INIT_LEAKAGE_CONVERGED_VALUE 0.005f
+#define AEC_FILTER_MAIN_INIT_LEAKAGE_DIVERGED \
+ "filter.main_initial:leakage_diverged"
+#define AEC_FILTER_MAIN_INIT_LEAKAGE_DIVERGED_VALUE 0.5f
+#define AEC_FILTER_MAIN_INIT_ERROR_FLOOR "filter.main_initial:error_floor"
+#define AEC_FILTER_MAIN_INIT_ERROR_FLOOR_VALUE 0.001f
+#define AEC_FILTER_MAIN_INIT_NOISE_GATE "filter.main_initial:noise_gate"
+#define AEC_FILTER_MAIN_INIT_NOISE_GATE_VALUE 20075344.f
+
+// Filter Shadow initial configuration
+#define AEC_FILTER_SHADOW_INIT_LENGTH_BLOCKS \
+ "filter.shadow_initial:length_blocks"
+#define AEC_FILTER_SHADOW_INIT_LENGTH_BLOCKS_VALUE 12
+#define AEC_FILTER_SHADOW_INIT_RATE "filter.shadow_initial:rate"
+#define AEC_FILTER_SHADOW_INIT_RATE_VALUE 0.9f
+#define AEC_FILTER_SHADOW_INIT_NOISE_GATE "filter.shadow_initial:noise_gate"
+#define AEC_FILTER_SHADOW_INIT_NOISE_GATE_VALUE 20075344.f
+
+#define AEC_FILTER_CONFIG_CHANGE_DURATION_BLOCKS \
+ "filter:config_change_duration_blocks"
+#define AEC_FILTER_CONFIG_CHANGE_DURATION_BLOCKS_VALUE 250
+#define AEC_FILTER_INITIAL_STATE_SECONDS "filter:initial_state_seconds"
+#define AEC_FILTER_INITIAL_STATE_SECONDS_VALUE 2.5f
+#define AEC_FILTER_CONSERVATIVE_INITIAL_PHASE \
+ "filter:conservative_initial_phase"
+#define AEC_FILTER_CONSERVATIVE_INITIAL_PHASE_VALUE 0
+#define AEC_FILTER_ENABLE_SHADOW_FILTER_OUTPUT_USAGE \
+ "filter:enable_shadow_filter_output_usage"
+#define AEC_FILTER_ENABLE_SHADOW_FILTER_OUTPUT_USAGE_VALUE 1
+
+// Erle
+#define AEC_ERLE_MIN "erle:min"
+#define AEC_ERLE_MIN_VALUE 1.f
+#define AEC_ERLE_MAX_L "erle:max_l"
+#define AEC_ERLE_MAX_L_VALUE 4.f
+#define AEC_ERLE_MAX_H "erle:max_h"
+#define AEC_ERLE_MAX_H_VALUE 1.5f
+#define AEC_ERLE_ONSET_DETECTION "erle:onset_detection"
+#define AEC_ERLE_ONSET_DETECTION_VALUE 1
+
+// EpStrength
+#define AEC_EP_STRENGTH_LF "ep_strength:lf"
+#define AEC_EP_STRENGTH_LF_VALUE 1.f
+#define AEC_EP_STRENGTH_MF "ep_strength:mf"
+#define AEC_EP_STRENGTH_MF_VALUE 1.f
+#define AEC_EP_STRENGTH_HF "ep_strength:hf"
+#define AEC_EP_STRENGTH_HF_VALUE 1.f
+#define AEC_EP_STRENGTH_DEFAULT_LEN "ep_strength:default_len"
+#define AEC_EP_STRENGTH_DEFAULT_LEN_VALUE 0.88f
+#define AEC_EP_STRENGTH_REVERB_BASED_ON_RENDER \
+ "ep_strength:reverb_based_on_render"
+#define AEC_EP_STRENGTH_REVERB_BASED_ON_RENDER_VALUE 1
+#define AEC_EP_STRENGTH_ECHO_CAN_SATURATE "ep_strength:echo_can_saturate"
+#define AEC_EP_STRENGTH_ECHO_CAN_SATURATE_VALUE 1
+#define AEC_EP_STRENGTH_BOUNDED_ERL "ep_strength:bounded_erl"
+#define AEC_EP_STRENGTH_BOUNDED_ERL_VALUE 0
+
+// Gain mask
+#define AEC_GAIN_MASK_M0 "gain_mask:m0"
+#define AEC_GAIN_MASK_M0_VALUE 0.1f
+#define AEC_GAIN_MASK_M1 "gain_mask:m1"
+#define AEC_GAIN_MASK_M1_VALUE 0.01f
+#define AEC_GAIN_MASK_M2 "gain_mask:m2"
+#define AEC_GAIN_MASK_M2_VALUE 0.0001f
+#define AEC_GAIN_MASK_M3 "gain_mask:m3"
+#define AEC_GAIN_MASK_M3_VALUE 0.01f
+// m4 was removed intentionally.
+// https://webrtc-review.googlesource.com/c/src/+/70421
+#define AEC_GAIN_MASK_M5 "gain_mask:m5"
+#define AEC_GAIN_MASK_M5_VALUE 0.01f
+#define AEC_GAIN_MASK_M6 "gain_mask:m6"
+#define AEC_GAIN_MASK_M6_VALUE 0.0001f
+#define AEC_GAIN_MASK_M7 "gain_mask:m7"
+#define AEC_GAIN_MASK_M7_VALUE 0.01f
+#define AEC_GAIN_MASK_M8 "gain_mask:m8"
+#define AEC_GAIN_MASK_M8_VALUE 0.0001f
+#define AEC_GAIN_MASK_M9 "gain_mask:m9"
+#define AEC_GAIN_MASK_M9_VALUE 0.1f
+#define AEC_GAIN_MASK_GAIN_CURVE_OFFSET "gain_mask:gain_curve_offset"
+#define AEC_GAIN_MASK_GAIN_CURVE_OFFSET_VALUE 1.45f
+#define AEC_GAIN_MASK_GAIN_CURVE_SLOPE "gain_mask:gain_curve_slope"
+#define AEC_GAIN_MASK_GAIN_CURVE_SLOPE_VALUE 5.f
+#define AEC_GAIN_MASK_TEMPORAL_MASKING_LF "gain_mask:temporal_masking_lf"
+#define AEC_GAIN_MASK_TEMPORAL_MASKING_LF_VALUE 0.9f
+#define AEC_GAIN_MASK_TEMPORAL_MASKING_HF "gain_mask:temporal_masking_hf"
+#define AEC_GAIN_MASK_TEMPORAL_MASKING_HF_VALUE 0.6f
+#define AEC_GAIN_MASK_TEMPORAL_MASKING_LF_BANDS \
+ "gain_mask:temporal_masking_lf_bands"
+#define AEC_GAIN_MASK_TEMPORAL_MASKING_LF_BANDS_VALUE 3
+
+#define AEC_ECHO_AUDIBILITY_LOW_RENDER_LIMIT "echo_audibility:low_render_limit"
+#define AEC_ECHO_AUDIBILITY_LOW_RENDER_LIMIT_VALUE 4 * 64.f
+#define AEC_ECHO_AUDIBILITY_NORMAL_RENDER_LIMIT \
+ "echo_audibility:normal_render_limit"
+#define AEC_ECHO_AUDIBILITY_NORMAL_RENDER_LIMIT_VALUE 64.f
+#define AEC_ECHO_AUDIBILITY_FLOOR_POWER "echo_audibility:floor_power"
+#define AEC_ECHO_AUDIBILITY_FLOOR_POWER_VALUE 2 * 64.f
+#define AEC_ECHO_AUDIBILITY_AUDIBILITY_THRESHOLD_LF \
+ "echo_audibility:audibility_threshold_lf"
+#define AEC_ECHO_AUDIBILITY_AUDIBILITY_THRESHOLD_LF_VALUE 10
+#define AEC_ECHO_AUDIBILITY_AUDIBILITY_THRESHOLD_MF \
+ "echo_audibility:audibility_threshold_mf"
+#define AEC_ECHO_AUDIBILITY_AUDIBILITY_THRESHOLD_MF_VALUE 10
+#define AEC_ECHO_AUDIBILITY_AUDIBILITY_THRESHOLD_HF \
+ "echo_audibility:audibility_threshold_hf"
+#define AEC_ECHO_AUDIBILITY_AUDIBILITY_THRESHOLD_HF_VALUE 10
+#define AEC_ECHO_AUDIBILITY_USE_STATIONARY_PROPERTIES \
+ "echo_audibility:use_stationary_properties"
+#define AEC_ECHO_AUDIBILITY_USE_STATIONARY_PROPERTIES_VALUE 1
+
+// Rendering levels
+#define AEC_RENDER_LEVELS_ACTIVE_RENDER_LIMIT \
+ "render_levels:active_render_limit"
+#define AEC_RENDER_LEVELS_ACTIVE_RENDER_LIMIT_VALUE 100.f
+#define AEC_RENDER_LEVELS_POOR_EXCITATION_RENDER_LIMIT \
+ "render_levels:poor_excitation_render_limit"
+#define AEC_RENDER_LEVELS_POOR_EXCITATION_RENDER_LIMIT_VALUE 150.f
+#define AEC_RENDER_LEVELS_POOR_EXCITATION_RENDER_LIMIT_DS8 \
+ "render_levels:poor_excitation_render_limit_ds8"
+#define AEC_RENDER_LEVELS_POOR_EXCITATION_RENDER_LIMIT_DS8_VALUE 20.f
+
+// Echo removal controls
+#define AEC_ECHO_REMOVAL_CTL_INITIAL_GAIN "echo_removal_control:initial_gain"
+#define AEC_ECHO_REMOVAL_CTL_INITIAL_GAIN_VALUE 0.0f
+#define AEC_ECHO_REMOVAL_CTL_FIRST_NON_ZERO_GAIN \
+ "echo_removal_control:first_non_zero_gain"
+#define AEC_ECHO_REMOVAL_CTL_FIRST_NON_ZERO_GAIN_VALUE 0.001f
+#define AEC_ECHO_REMOVAL_CTL_NON_ZERO_GAIN_BLOCKS \
+ "echo_removal_control:non_zero_gain_blocks"
+#define AEC_ECHO_REMOVAL_CTL_NON_ZERO_GAIN_BLOCKS_VALUE 187
+#define AEC_ECHO_REMOVAL_CTL_FULL_GAIN_BLOCKS \
+ "echo_removal_control:full_gain_blocks"
+#define AEC_ECHO_REMOVAL_CTL_FULL_GAIN_BLOCKS_VALUE 312
+#define AEC_ECHO_REMOVAL_CTL_HAS_CLOCK_DRIFT \
+ "echo_removal_control:has_clock_drift"
+#define AEC_ECHO_REMOVAL_CTL_HAS_CLOCK_DRIFT_VALUE 0
+#define AEC_ECHO_REMOVAL_CTL_LINEAR_AND_STABLE_ECHO_PATH \
+ "echo_removal_control:linear_and_stable_echo_path"
+#define AEC_ECHO_REMOVAL_CTL_LINEAR_AND_STABLE_ECHO_PATH_VALUE 0
+
+// EchoModel
+#define AEC_ECHO_MODEL_NOISE_FLOOR_HOLD "echo_model:noise_floor_hold"
+#define AEC_ECHO_MODEL_NOISE_FLOOR_HOLD_VALUE 50
+#define AEC_ECHO_MODEL_MIN_NOISE_FLOOR_POWER "echo_model:min_noise_floor_power"
+#define AEC_ECHO_MODEL_MIN_NOISE_FLOOR_POWER_VALUE 1638400.f
+#define AEC_ECHO_MODEL_STATIONARY_GATE_SLOPE "echo_model:stationary_gate_slope"
+#define AEC_ECHO_MODEL_STATIONARY_GATE_SLOPE_VALUE 10.f
+#define AEC_ECHO_MODEL_NOISE_GATE_POWER "echo_model:noise_gate_power"
+#define AEC_ECHO_MODEL_NOISE_GATE_POWER_VALUE 27509.42f
+#define AEC_ECHO_MODEL_NOISE_GATE_SLOPE "echo_model:noise_gate_slope"
+#define AEC_ECHO_MODEL_NOISE_GATE_SLOPE_VALUE 0.3f
+#define AEC_ECHO_MODEL_RENDER_PRE_WINDOW_SIZE \
+ "echo_model:render_pre_window_size"
+#define AEC_ECHO_MODEL_RENDER_PRE_WINDOW_SIZE_VALUE 1
+#define AEC_ECHO_MODEL_RENDER_POST_WINDOW_SIZE \
+ "echo_model:render_post_window_size"
+#define AEC_ECHO_MODEL_RENDER_POST_WINDOW_SIZE_VALUE 1
+#define AEC_ECHO_MODEL_RENDER_PRE_WINDOW_SIZE_INIT \
+ "echo_model:render_pre_window_size_init"
+#define AEC_ECHO_MODEL_RENDER_PRE_WINDOW_SIZE_INIT_VALUE 10
+#define AEC_ECHO_MODEL_RENDER_POST_WINDOW_SIZE_INIT \
+ "echo_model:render_post_window_size_init"
+#define AEC_ECHO_MODEL_RENDER_POST_WINDOW_SIZE_INIT_VALUE 10
+#define AEC_ECHO_MODEL_NONLINEAR_HOLD "echo_model:nonlinear_hold"
+#define AEC_ECHO_MODEL_NONLINEAR_HOLD_VALUE 1
+#define AEC_ECHO_MODEL_NONLINEAR_RELEASE "echo_model:nonlinear_release"
+#define AEC_ECHO_MODEL_NONLINEAR_RELEASE_VALUE 0.001f
+
+#define AEC_SUPPRESSOR_NEAREND_AVERAGE_BLOCKS \
+ "suppressor:nearend_average_blocks"
+#define AEC_SUPPRESSOR_NEAREND_AVERAGE_BLOCKS_VALUE 4
+
+#define AEC_SUPPRESSOR_NORMAL_TUNING_MASK_LF_ENR_TRANSPARENT \
+ "suppressor.normal_tuning:mask_lf_enr_transparent"
+#define AEC_SUPPRESSOR_NORMAL_TUNING_MASK_LF_ENR_TRANSPARENT_VALUE .2f
+#define AEC_SUPPRESSOR_NORMAL_TUNING_MASK_LF_ENR_SUPPRESS \
+ "suppressor.normal_tuning:mask_lf_enr_suppress"
+#define AEC_SUPPRESSOR_NORMAL_TUNING_MASK_LF_ENR_SUPPRESS_VALUE .3f
+#define AEC_SUPPRESSOR_NORMAL_TUNING_MASK_LF_EMR_TRANSPARENT \
+ "suppressor.normal_tuning:mask_lf_emr_transparent"
+#define AEC_SUPPRESSOR_NORMAL_TUNING_MASK_LF_EMR_TRANSPARENT_VALUE .3f
+
+#define AEC_SUPPRESSOR_NORMAL_TUNING_MASK_HF_ENR_TRANSPARENT \
+ "suppressor.normal_tuning:mask_hf_enr_transparent"
+#define AEC_SUPPRESSOR_NORMAL_TUNING_MASK_HF_ENR_TRANSPARENT_VALUE .07f
+#define AEC_SUPPRESSOR_NORMAL_TUNING_MASK_HF_ENR_SUPPRESS \
+ "suppressor.normal_tuning:mask_hf_enr_suppress"
+#define AEC_SUPPRESSOR_NORMAL_TUNING_MASK_HF_ENR_SUPPRESS_VALUE .1f
+#define AEC_SUPPRESSOR_NORMAL_TUNING_MASK_HF_EMR_TRANSPARENT \
+ "suppressor.normal_tuning:mask_hf_emr_transparent"
+#define AEC_SUPPRESSOR_NORMAL_TUNING_MASK_HF_EMR_TRANSPARENT_VALUE .3f
+
+#define AEC_SUPPRESSOR_NORMAL_TUNING_MAX_INC_FACTOR \
+ "suppressor.normal_tuning:max_inc_factor"
+#define AEC_SUPPRESSOR_NORMAL_TUNING_MAX_INC_FACTOR_VALUE 2.0f
+#define AEC_SUPPRESSOR_NORMAL_TUNING_MAX_DEC_FACTOR_LF \
+ "suppressor.normal_tuning:max_dec_factor_lf"
+#define AEC_SUPPRESSOR_NORMAL_TUNING_MAX_DEC_FACTOR_LF_VALUE 0.25f
+
+#define AEC_SUPPRESSOR_NEAREND_TUNING_MASK_LF_ENR_TRANSPARENT \
+ "suppressor.nearend_tuning:mask_lf_enr_transparent"
+#define AEC_SUPPRESSOR_NEAREND_TUNING_MASK_LF_ENR_TRANSPARENT_VALUE .2f
+#define AEC_SUPPRESSOR_NEAREND_TUNING_MASK_LF_ENR_SUPPRESS \
+ "suppressor.nearend_tuning:mask_lf_enr_suppress"
+#define AEC_SUPPRESSOR_NEAREND_TUNING_MASK_LF_ENR_SUPPRESS_VALUE .3f
+#define AEC_SUPPRESSOR_NEAREND_TUNING_MASK_LF_EMR_TRANSPARENT \
+ "suppressor.nearend_tuning:mask_lf_emr_transparent"
+#define AEC_SUPPRESSOR_NEAREND_TUNING_MASK_LF_EMR_TRANSPARENT_VALUE .3f
+
+#define AEC_SUPPRESSOR_NEAREND_TUNING_MASK_HF_ENR_TRANSPARENT \
+ "suppressor.nearend_tuning:mask_hf_enr_transparent"
+#define AEC_SUPPRESSOR_NEAREND_TUNING_MASK_HF_ENR_TRANSPARENT_VALUE .07f
+#define AEC_SUPPRESSOR_NEAREND_TUNING_MASK_HF_ENR_SUPPRESS \
+ "suppressor.nearend_tuning:mask_hf_enr_suppress"
+#define AEC_SUPPRESSOR_NEAREND_TUNING_MASK_HF_ENR_SUPPRESS_VALUE .1f
+#define AEC_SUPPRESSOR_NEAREND_TUNING_MASK_HF_EMR_TRANSPARENT \
+ "suppressor.nearend_tuning:mask_hf_emr_transparent"
+#define AEC_SUPPRESSOR_NEAREND_TUNING_MASK_HF_EMR_TRANSPARENT_VALUE .3f
+
+#define AEC_SUPPRESSOR_NEAREND_TUNING_MAX_INC_FACTOR \
+ "suppressor.nearend_tuning:max_inc_factor"
+#define AEC_SUPPRESSOR_NEAREND_TUNING_MAX_INC_FACTOR_VALUE 2.0f
+#define AEC_SUPPRESSOR_NEAREND_TUNING_MAX_DEC_FACTOR_LF \
+ "suppressor.nearend_tuning:max_dec_factor_lf"
+#define AEC_SUPPRESSOR_NEAREND_TUNING_MAX_DEC_FACTOR_LF_VALUE 0.25f
+
+#define AEC_SUPPRESSOR_DOMINANT_NEAREND_DETECTION_ENR_THRESHOLD \
+ "suppressor.dominant_nearend_detection:enr_threshold"
+#define AEC_SUPPRESSOR_DOMINANT_NEAREND_DETECTION_ENR_THRESHOLD_VALUE 10.f
+#define AEC_SUPPRESSOR_DOMINANT_NEAREND_DETECTION_SNR_THRESHOLD \
+ "suppressor.dominant_nearend_detection:snr_threshold"
+#define AEC_SUPPRESSOR_DOMINANT_NEAREND_DETECTION_SNR_THRESHOLD_VALUE 10.f
+#define AEC_SUPPRESSOR_DOMINANT_NEAREND_DETECTION_HOLD_DURATION \
+ "suppressor.dominant_nearend_detection:hold_duration"
+#define AEC_SUPPRESSOR_DOMINANT_NEAREND_DETECTION_HOLD_DURATION_VALUE 25
+#define AEC_SUPPRESSOR_DOMINANT_NEAREND_DETECTION_TRIGGER_THRESHOLD \
+ "suppressor.dominant_nearend_detection:trigger_threshold"
+#define AEC_SUPPRESSOR_DOMINANT_NEAREND_DETECTION_TRIGGER_THRESHOLD_VALUE 15
+
+#define AEC_SUPPRESSOR_HIGH_BANDS_SUPPRESSION_ENR_THRESHOLD \
+ "suppressor.high_bands_suppression:enr_threshold"
+#define AEC_SUPPRESSOR_HIGH_BANDS_SUPPRESSION_ENR_THRESHOLD_VALUE 1.f
+#define AEC_SUPPRESSOR_HIGH_BANDS_SUPPRESSION_MAX_GAIN_DURING_ECHO \
+ "suppressor.high_bands_suppression:max_gain_during_echo"
+#define AEC_SUPPRESSOR_HIGH_BANDS_SUPPRESSION_MAX_GAIN_DURING_ECHO_VALUE 1.f
+
+#define AEC_SUPPRESSOR_FLOOR_FIRST_INCREASE "suppressor:floor_first_increase"
+#define AEC_SUPPRESSOR_FLOOR_FIRST_INCREASE_VALUE 0.00001f
+#define AEC_SUPPRESSOR_ENFORCE_TRANSPARENT "suppressor:enforce_transparent"
+#define AEC_SUPPRESSOR_ENFORCE_TRANSPARENT_VALUE 0
+#define AEC_SUPPRESSOR_ENFORCE_EMPTY_HIGHER_BANDS \
+ "suppressor:enforce_empty_higher_bands"
+#define AEC_SUPPRESSOR_ENFORCE_EMPTY_HIGHER_BANDS_VALUE 0
+
+/* Gets the aec config from given config directory. */
+struct aec_config *aec_config_get(const char *device_config_dir);
+
+/* Prints the config content to syslog. */
+void aec_config_dump(struct aec_config *config);
+
+#endif /* AEC_CONFIG_H_ */
diff --git a/cras/src/server/config/apm_config.c b/cras/src/server/config/apm_config.c
new file mode 100644
index 0000000..ae5df69
--- /dev/null
+++ b/cras/src/server/config/apm_config.c
@@ -0,0 +1,94 @@
+/* Copyright 2018 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 "apm_config.h"
+#include "iniparser_wrapper.h"
+
+static const unsigned int MAX_INI_NAME_LEN = 63;
+
+#define APM_CONFIG_NAME "apm.ini"
+
+#define APM_GET_INT(ini, key) \
+ iniparser_getint( \
+ ini, key, \
+ key ## _VALUE)
+#define APM_GET_FLOAT(ini, key) \
+ iniparser_getdouble( \
+ ini, key, \
+ key ## _VALUE)
+
+struct apm_config *apm_config_get(const char *device_config_dir)
+{
+ struct apm_config *config;
+ char ini_name[MAX_INI_NAME_LEN + 1];
+ dictionary *ini;
+
+ snprintf(ini_name, MAX_INI_NAME_LEN, "%s/%s",
+ device_config_dir, APM_CONFIG_NAME);
+ ini_name[MAX_INI_NAME_LEN] = '\0';
+
+ ini = iniparser_load_wrapper(ini_name);
+ if (ini == NULL) {
+ syslog(LOG_DEBUG, "No ini file %s", ini_name);
+ return NULL;
+ }
+
+ config = (struct apm_config *)calloc(1, sizeof(*config));
+ config->residual_echo_detector_enabled =
+ APM_GET_INT(ini, APM_RESIDUAL_ECHO_DETECTOR_ENABLED);
+ config->high_pass_filter_enabled =
+ APM_GET_INT(ini, APM_HIGH_PASS_FILTER_ENABLED);
+ config->pre_amplifier_enabled =
+ APM_GET_INT(ini, APM_PRE_AMPLIFIER_ENABLED);
+ config->pre_amplifier_fixed_gain_factor =
+ APM_GET_FLOAT(ini, APM_PRE_AMPLIFIER_FIXED_GAIN_FACTOR);
+ config->gain_controller2_enabled =
+ APM_GET_INT(ini, APM_GAIN_CONTROLLER2_ENABLED);
+ config->gain_controller2_fixed_gain_db =
+ APM_GET_FLOAT(ini, APM_GAIN_CONTROLLER2_FIXED_GAIN_DB);
+ config->gain_controller2_adaptive_digital_mode =
+ APM_GET_INT(ini, APM_GAIN_CONTROLLER2_ADAPTIVE_DIGITAL_MODE);
+ config->gain_control_compression_gain_db =
+ APM_GET_INT(ini, APM_GAIN_CONTROL_COMPRESSION_GAIN_DB);
+ config->agc_mode = (enum gain_control_mode)
+ APM_GET_INT(ini, APM_GAIN_CONTROL_MODE);
+ config->gain_control_enabled =
+ APM_GET_INT(ini, APM_GAIN_CONTROL_ENABLED);
+ config->ns_level = (enum noise_suppression_level)
+ APM_GET_INT(ini, APM_NOISE_SUPPRESSION_LEVEL);
+ config->noise_suppression_enabled =
+ APM_GET_INT(ini, APM_NOISE_SUPPRESSION_ENABLED);
+
+ return config;
+}
+
+void apm_config_dump(struct apm_config *config)
+{
+ syslog(LOG_ERR, "---- apm config dump ----");
+ syslog(LOG_ERR, "residual_echo_detector_enabled %u",
+ config->residual_echo_detector_enabled);
+ syslog(LOG_ERR, "high_pass_filter_enabled %u",
+ config->high_pass_filter_enabled);
+ syslog(LOG_ERR, "pre_amplifier_enabled %u",
+ config->pre_amplifier_enabled);
+ syslog(LOG_ERR, "pre_amplifier_fixed_gain_factor %f",
+ config->pre_amplifier_fixed_gain_factor);
+ syslog(LOG_ERR, "gain_controller2_enabled %u",
+ config->gain_controller2_enabled);
+ syslog(LOG_ERR, "gain_controller2_fixed_gain_db %f",
+ config->gain_controller2_fixed_gain_db);
+ syslog(LOG_ERR, "gain_controller2_adaptive_digital_mode %d",
+ config->gain_controller2_adaptive_digital_mode);
+ syslog(LOG_ERR, "gain_control_compression_gain_db %u",
+ config->gain_control_compression_gain_db);
+ syslog(LOG_ERR, "gain_control_mode %u", config->agc_mode);
+ syslog(LOG_ERR, "gain_control_enabled %u",
+ config->gain_control_enabled);
+ syslog(LOG_ERR, "noise_suppression_level %u", config->ns_level);
+ syslog(LOG_ERR, "noise_suppression_enabled %u",
+ config->noise_suppression_enabled);
+}
diff --git a/cras/src/server/config/apm_config.h b/cras/src/server/config/apm_config.h
new file mode 100644
index 0000000..b0e39be
--- /dev/null
+++ b/cras/src/server/config/apm_config.h
@@ -0,0 +1,45 @@
+/* Copyright 2018 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 APM_CONFIG_H_
+#define APM_CONFIG_H_
+
+#include <webrtc-apm/webrtc_apm.h>
+#include <webrtc-apm/webrtc_apm.h>
+
+#define APM_RESIDUAL_ECHO_DETECTOR_ENABLED "apm:residual_echo_detector_enabled"
+#define APM_RESIDUAL_ECHO_DETECTOR_ENABLED_VALUE 1
+#define APM_HIGH_PASS_FILTER_ENABLED "apm:high_pass_filter_enabled"
+#define APM_HIGH_PASS_FILTER_ENABLED_VALUE 0
+#define APM_PRE_AMPLIFIER_ENABLED "apm:pre_amplifier_enabled"
+#define APM_PRE_AMPLIFIER_ENABLED_VALUE 0
+#define APM_PRE_AMPLIFIER_FIXED_GAIN_FACTOR "apm:pre_amplifier_fixed_gain_factor"
+#define APM_PRE_AMPLIFIER_FIXED_GAIN_FACTOR_VALUE 1.f
+#define APM_GAIN_CONTROLLER2_ENABLED "apm:gain_controller2_enabled"
+#define APM_GAIN_CONTROLLER2_ENABLED_VALUE 0
+#define APM_GAIN_CONTROLLER2_FIXED_GAIN_DB "apm:gain_controller2_fixed_gain_db"
+#define APM_GAIN_CONTROLLER2_FIXED_GAIN_DB_VALUE 0.f
+#define APM_GAIN_CONTROLLER2_ADAPTIVE_DIGITAL_MODE "apm:gain_controller2_adaptive_digital_mode"
+#define APM_GAIN_CONTROLLER2_ADAPTIVE_DIGITAL_MODE_VALUE 1
+#define APM_GAIN_CONTROL_COMPRESSION_GAIN_DB "apm:gain_control_compression_gain_db"
+#define APM_GAIN_CONTROL_COMPRESSION_GAIN_DB_VALUE 9
+/* 0: adaptive analog, 1: adaptive digital, 2: fixed digital */
+#define APM_GAIN_CONTROL_MODE "apm:gain_control_mode"
+#define APM_GAIN_CONTROL_MODE_VALUE 0
+#define APM_GAIN_CONTROL_ENABLED "apm:gain_control_enabled"
+#define APM_GAIN_CONTROL_ENABLED_VALUE 0
+/* 0: low, 1: moderate, 2: high, 3: very high*/
+#define APM_NOISE_SUPPRESSION_LEVEL "apm:noise_suppression_level"
+#define APM_NOISE_SUPPRESSION_LEVEL_VALUE 0
+#define APM_NOISE_SUPPRESSION_ENABLED "apm:noise_suppression_enabled"
+#define APM_NOISE_SUPPRESSION_ENABLED_VALUE 0
+
+/* Gets apm config from given config directory. */
+struct apm_config *apm_config_get(const char *device_config_dir);
+
+/* Prints config content to syslog. */
+void apm_config_dump(struct apm_config *config);
+
+#endif /* APM_CONFIG_H_ */
diff --git a/cras/src/server/config/cras_board_config.c b/cras/src/server/config/cras_board_config.c
new file mode 100644
index 0000000..a920b63
--- /dev/null
+++ b/cras/src/server/config/cras_board_config.c
@@ -0,0 +1,56 @@
+/* Copyright 2017 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <syslog.h>
+
+#include "cras_board_config.h"
+#include "iniparser_wrapper.h"
+
+/* Allocate 63 chars + 1 for null where declared. */
+static const unsigned int MAX_INI_NAME_LEN = 63;
+static const unsigned int MAX_KEY_LEN = 63;
+static const int32_t DEFAULT_OUTPUT_BUFFER_SIZE = 512;
+static const int32_t AEC_SUPPORTED_DEFAULT = 0;
+
+#define CONFIG_NAME "board.ini"
+#define DEFAULT_OUTPUT_BUF_SIZE_INI_KEY "output:default_output_buffer_size"
+#define AEC_SUPPORTED_INI_KEY "processing:aec_supported"
+
+
+void cras_board_config_get(const char *config_path,
+ struct cras_board_config *board_config)
+{
+ char ini_name[MAX_INI_NAME_LEN + 1];
+ char ini_key[MAX_KEY_LEN + 1];
+ dictionary *ini;
+
+ board_config->default_output_buffer_size = DEFAULT_OUTPUT_BUFFER_SIZE;
+ board_config->aec_supported = AEC_SUPPORTED_DEFAULT;
+ if (config_path == NULL)
+ return;
+
+ snprintf(ini_name, MAX_INI_NAME_LEN, "%s/%s", config_path,
+ CONFIG_NAME);
+ ini_name[MAX_INI_NAME_LEN] = '\0';
+ ini = iniparser_load_wrapper(ini_name);
+ if (ini == NULL) {
+ syslog(LOG_DEBUG, "No ini file %s", ini_name);
+ return;
+ }
+
+ snprintf(ini_key, MAX_KEY_LEN, DEFAULT_OUTPUT_BUF_SIZE_INI_KEY);
+ ini_key[MAX_KEY_LEN] = 0;
+ board_config->default_output_buffer_size =
+ iniparser_getint(ini, ini_key, DEFAULT_OUTPUT_BUFFER_SIZE);
+
+ snprintf(ini_key, MAX_KEY_LEN, AEC_SUPPORTED_INI_KEY);
+ ini_key[MAX_KEY_LEN] = 0;
+ board_config->aec_supported =
+ iniparser_getint(ini, ini_key, AEC_SUPPORTED_DEFAULT);
+
+ iniparser_freedict(ini);
+ syslog(LOG_DEBUG, "Loaded ini file %s", ini_name);
+}
+
diff --git a/cras/src/server/config/cras_board_config.h b/cras/src/server/config/cras_board_config.h
new file mode 100644
index 0000000..f06f92b
--- /dev/null
+++ b/cras/src/server/config/cras_board_config.h
@@ -0,0 +1,24 @@
+/* Copyright 2017 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef CRAS_BOARD_CONFIG_H_
+#define CRAS_BOARD_CONFIG_H_
+
+#include <stdint.h>
+
+struct cras_board_config {
+ int32_t default_output_buffer_size;
+ int32_t aec_supported;
+};
+
+/* Gets a configuration based on the config file specified.
+ * Args:
+ * config_path - Path containing the config files.
+ * board_config - The returned configs.
+ */
+void cras_board_config_get(const char *config_path,
+ struct cras_board_config *board_config);
+
+#endif /* CRAS_BOARD_CONFIG_H_ */
diff --git a/cras/src/server/config/cras_card_config.c b/cras/src/server/config/cras_card_config.c
index ba39ce9..c6f5edd 100644
--- a/cras/src/server/config/cras_card_config.c
+++ b/cras/src/server/config/cras_card_config.c
@@ -3,11 +3,11 @@
* found in the LICENSE file.
*/
-#include <iniparser.h>
#include <syslog.h>
#include "cras_util.h"
#include "cras_volume_curve.h"
+#include "iniparser_wrapper.h"
#include "utlist.h"
/* Allocate 63 chars + 1 for null where declared. */
@@ -66,7 +66,7 @@
snprintf(ini_name, MAX_INI_NAME_LEN, "%s/%s", config_path, card_name);
ini_name[MAX_INI_NAME_LEN] = '\0';
- ini = iniparser_load(ini_name);
+ ini = iniparser_load_wrapper(ini_name);
if (ini == NULL) {
syslog(LOG_DEBUG, "No ini file %s", ini_name);
return NULL;
diff --git a/cras/src/server/config/cras_device_blacklist.c b/cras/src/server/config/cras_device_blacklist.c
index 5706ea8..f3000a8 100644
--- a/cras/src/server/config/cras_device_blacklist.c
+++ b/cras/src/server/config/cras_device_blacklist.c
@@ -3,9 +3,8 @@
* found in the LICENSE file.
*/
-#include <iniparser.h>
-
#include "cras_device_blacklist.h"
+#include "iniparser_wrapper.h"
#include "utlist.h"
/* Allocate 63 chars + 1 for null where declared. */
@@ -33,7 +32,7 @@
snprintf(ini_name, MAX_INI_NAME_LEN, "%s/%s",
config_path, "device_blacklist");
ini_name[MAX_INI_NAME_LEN] = '\0';
- blacklist->ini = iniparser_load(ini_name);
+ blacklist->ini = iniparser_load_wrapper(ini_name);
return blacklist;
}
diff --git a/cras/src/server/cras.c b/cras/src/server/cras.c
index 29806a8..cc213ab 100644
--- a/cras/src/server/cras.c
+++ b/cras/src/server/cras.c
@@ -3,13 +3,17 @@
* found in the LICENSE file.
*/
+#define _GNU_SOURCE /* for asprintf */
#include <getopt.h>
#include <signal.h>
+#include <stdio.h>
#include <syslog.h>
+#include "cras_apm_list.h"
#include "cras_config.h"
#include "cras_iodev_list.h"
#include "cras_server.h"
+#include "cras_shm.h"
#include "cras_system_state.h"
#include "cras_dsp.h"
@@ -108,10 +112,29 @@
/* Initialize system. */
cras_server_init();
- cras_system_state_init(device_config_dir);
+ char *shm_name;
+ if (asprintf(&shm_name, "/cras-%d", getpid()) < 0)
+ exit(-1);
+ int rw_shm_fd;
+ int ro_shm_fd;
+ struct cras_server_state *exp_state = (struct cras_server_state *)
+ cras_shm_setup(shm_name,
+ sizeof(*exp_state),
+ &rw_shm_fd,
+ &ro_shm_fd);
+ if (!exp_state)
+ exit(-1);
+ cras_system_state_init(device_config_dir,
+ shm_name,
+ rw_shm_fd,
+ ro_shm_fd,
+ exp_state,
+ sizeof(*exp_state));
+ free(shm_name);
if (internal_ucm_suffix)
cras_system_state_set_internal_ucm_suffix(internal_ucm_suffix);
cras_dsp_init(dsp_config);
+ cras_apm_list_init(device_config_dir);
cras_iodev_list_init();
/* Start the server. */
diff --git a/cras/src/server/cras_a2dp_iodev.c b/cras/src/server/cras_a2dp_iodev.c
index a9551a1..ec8c345 100644
--- a/cras/src/server/cras_a2dp_iodev.c
+++ b/cras/src/server/cras_a2dp_iodev.c
@@ -8,6 +8,7 @@
#include <sys/param.h>
#include <linux/sockios.h>
#include <sys/socket.h>
+#include <sys/time.h>
#include <syslog.h>
#include <time.h>
@@ -128,14 +129,14 @@
int estimate_queued_frames = bt_queued_frames(iodev, 0);
int local_queued_frames =
a2dp_queued_frames(&a2dpio->a2dp) +
- buf_queued_bytes(a2dpio->pcm_buf) /
+ buf_queued(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));
}
-static int open_dev(struct cras_iodev *iodev)
+static int configure_dev(struct cras_iodev *iodev)
{
struct a2dp_io *a2dpio = (struct a2dp_io *)iodev;
int sock_depth;
@@ -214,7 +215,7 @@
if (device)
cras_bt_device_cancel_suspend(device);
a2dp_drain(&a2dpio->a2dp);
- byte_buffer_destroy(a2dpio->pcm_buf);
+ byte_buffer_destroy(&a2dpio->pcm_buf);
cras_iodev_free_format(iodev);
cras_iodev_free_audio_area(iodev);
return 0;
@@ -279,17 +280,17 @@
return -EINVAL;
encode_more:
- while (buf_queued_bytes(a2dpio->pcm_buf)) {
+ while (buf_queued(a2dpio->pcm_buf)) {
processed = a2dp_encode(
&a2dpio->a2dp,
buf_read_pointer(a2dpio->pcm_buf),
- buf_readable_bytes(a2dpio->pcm_buf),
+ buf_readable(a2dpio->pcm_buf),
format_bytes,
cras_bt_transport_write_mtu(a2dpio->transport));
ATLOG(atlog, AUDIO_THREAD_A2DP_ENCODE,
processed,
- buf_queued_bytes(a2dpio->pcm_buf),
- buf_readable_bytes(a2dpio->pcm_buf)
+ buf_queued(a2dpio->pcm_buf),
+ buf_readable(a2dpio->pcm_buf)
);
if (processed == -ENOSPC || processed == 0)
break;
@@ -328,7 +329,7 @@
* 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;
+ queued_frames = buf_queued(a2dpio->pcm_buf) / format_bytes;
if (written && (iodev->min_buffer_level + written < queued_frames))
goto encode_more;
@@ -362,7 +363,7 @@
if (iodev->direction != CRAS_STREAM_OUTPUT)
return 0;
- *frames = MIN(*frames, buf_writable_bytes(a2dpio->pcm_buf) /
+ *frames = MIN(*frames, buf_writable(a2dpio->pcm_buf) /
format_bytes);
iodev->area->frames = *frames;
cras_audio_area_config_buf_pointers(
@@ -381,7 +382,7 @@
format_bytes = cras_get_format_bytes(iodev->format);
written_bytes = nwritten * format_bytes;
- if (written_bytes > buf_writable_bytes(a2dpio->pcm_buf))
+ if (written_bytes > buf_writable(a2dpio->pcm_buf))
return -EINVAL;
buf_increment_write(a2dpio->pcm_buf, written_bytes);
@@ -437,6 +438,7 @@
}
free(a2dpio->base.supported_channel_counts);
free(a2dpio->base.supported_rates);
+ free(a2dpio->base.supported_formats);
destroy_a2dp(&a2dpio->a2dp);
}
@@ -484,7 +486,7 @@
strlen(cras_bt_device_object_path(device)));
iodev->info.stable_id_new = iodev->info.stable_id;
- iodev->open_dev = open_dev;
+ iodev->configure_dev = configure_dev;
iodev->frames_queued = frames_queued;
iodev->delay_frames = delay_frames;
iodev->get_buffer = get_buffer;
diff --git a/cras/src/server/cras_alsa_card.c b/cras/src/server/cras_alsa_card.c
index 16a6ef5..7f7a620 100644
--- a/cras/src/server/cras_alsa_card.c
+++ b/cras/src/server/cras_alsa_card.c
@@ -180,6 +180,8 @@
CRAS_STREAM_OUTPUT);
if (!dev)
DL_DELETE(controls, control);
+ else
+ free(dev);
}
return controls;
}
@@ -410,6 +412,41 @@
return rc;
}
+static void configure_echo_reference_dev(struct cras_alsa_card *alsa_card)
+{
+ struct iodev_list_node *dev_node, *echo_ref_node;
+ const char *echo_ref_name;
+
+ if (!alsa_card->ucm)
+ return;
+
+ DL_FOREACH(alsa_card->iodevs, dev_node) {
+ if (!dev_node->iodev->nodes)
+ continue;
+
+ echo_ref_name = ucm_get_echo_reference_dev_name_for_dev(
+ alsa_card->ucm,
+ dev_node->iodev->nodes->name);
+ if (!echo_ref_name)
+ continue;
+ DL_FOREACH(alsa_card->iodevs, echo_ref_node) {
+ if (echo_ref_node->iodev->nodes == NULL)
+ continue;
+ if (!strcmp(echo_ref_name,
+ echo_ref_node->iodev->nodes->name))
+ break;
+ }
+ if (echo_ref_node)
+ dev_node->iodev->echo_reference_dev =
+ echo_ref_node->iodev;
+ else
+ syslog(LOG_ERR,
+ "Echo ref dev %s doesn't exist on card %s",
+ echo_ref_name, alsa_card->name);
+ free((void *)echo_ref_name);
+ }
+}
+
/*
* Exported Interface.
*/
@@ -528,6 +565,8 @@
if (rc)
goto error_bail;
+ configure_echo_reference_dev(alsa_card);
+
n = alsa_card->hctl ?
snd_hctl_poll_descriptors_count(alsa_card->hctl) : 0;
if (n != 0 && card_has_hctl_jack(alsa_card)) {
diff --git a/cras/src/server/cras_alsa_helpers.c b/cras/src/server/cras_alsa_helpers.c
index a8719c2..d2520e7 100644
--- a/cras/src/server/cras_alsa_helpers.c
+++ b/cras/src/server/cras_alsa_helpers.c
@@ -53,6 +53,7 @@
4,
2,
1,
+ 8,
0
};
@@ -304,7 +305,6 @@
int cras_alsa_set_channel_map(snd_pcm_t *handle,
struct cras_audio_format *fmt)
{
- int rc = 0;
size_t i, ch;
snd_pcm_chmap_query_t **chmaps;
snd_pcm_chmap_query_t *match;
@@ -315,14 +315,12 @@
chmaps = snd_pcm_query_chmaps(handle);
if (chmaps == NULL) {
syslog(LOG_WARNING, "No chmap queried! Skip chmap set");
- rc = 0;
goto done;
}
match = cras_chmap_caps_best(handle, chmaps, fmt);
if (!match) {
syslog(LOG_ERR, "Unable to find the best channel map");
- rc = -1;
goto done;
}
@@ -337,11 +335,12 @@
if (ch != CRAS_CH_MAX)
match->map.pos[i] = CH_TO_ALSA(ch);
}
- rc = snd_pcm_set_chmap(handle, &match->map);
+ if (snd_pcm_set_chmap(handle, &match->map) != 0)
+ syslog(LOG_ERR, "Unable to set channel map");
done:
snd_pcm_free_chmaps(chmaps);
- return rc;
+ return 0;
}
int cras_alsa_get_channel_map(snd_pcm_t *handle,
@@ -354,7 +353,6 @@
chmaps = snd_pcm_query_chmaps(handle);
if (chmaps == NULL) {
- syslog(LOG_ERR, "No chmap queried!");
rc = -EINVAL;
goto done;
}
@@ -385,48 +383,34 @@
return rc;
}
-int cras_alsa_fill_properties(const char *dev, snd_pcm_stream_t stream,
+int cras_alsa_fill_properties(snd_pcm_t *handle,
size_t **rates, size_t **channel_counts,
snd_pcm_format_t **formats)
{
int rc;
- snd_pcm_t *handle;
size_t i, num_found;
snd_pcm_hw_params_t *params;
snd_pcm_hw_params_alloca(¶ms);
- rc = cras_alsa_pcm_open(&handle,
- dev,
- stream);
- if (rc < 0) {
- syslog(LOG_ERR, "snd_pcm_open_failed: %s", snd_strerror(rc));
- return rc;
- }
-
rc = snd_pcm_hw_params_any(handle, params);
if (rc < 0) {
- snd_pcm_close(handle);
syslog(LOG_ERR, "snd_pcm_hw_params_any: %s", snd_strerror(rc));
return rc;
}
*rates = (size_t *)malloc(sizeof(test_sample_rates));
- if (*rates == NULL) {
- snd_pcm_close(handle);
+ if (*rates == NULL)
return -ENOMEM;
- }
*channel_counts = (size_t *)malloc(sizeof(test_channel_counts));
if (*channel_counts == NULL) {
free(*rates);
- snd_pcm_close(handle);
return -ENOMEM;
}
*formats = (snd_pcm_format_t *)malloc(sizeof(test_formats));
if (*formats == NULL) {
free(*channel_counts);
free(*rates);
- snd_pcm_close(handle);
return -ENOMEM;
}
@@ -438,6 +422,10 @@
(*rates)[num_found++] = test_sample_rates[i];
}
(*rates)[num_found] = 0;
+ if (num_found == 0) {
+ syslog(LOG_WARNING, "No valid sample rates.");
+ return -EINVAL;
+ }
num_found = 0;
for (i = 0; test_channel_counts[i] != 0; i++) {
@@ -447,6 +435,10 @@
(*channel_counts)[num_found++] = test_channel_counts[i];
}
(*channel_counts)[num_found] = 0;
+ if (num_found == 0) {
+ syslog(LOG_WARNING, "No valid channel counts found.");
+ return -EINVAL;
+ }
num_found = 0;
for (i = 0; test_formats[i] != 0; i++) {
@@ -456,8 +448,10 @@
(*formats)[num_found++] = test_formats[i];
}
(*formats)[num_found] = (snd_pcm_format_t)0;
-
- snd_pcm_close(handle);
+ if (num_found == 0) {
+ syslog(LOG_WARNING, "No valid sample formats.");
+ return -EINVAL;
+ }
return 0;
}
@@ -669,8 +663,7 @@
snd_pcm_uframes_t severe_underrun_frames,
const char *dev_name,
snd_pcm_uframes_t *avail,
- struct timespec *tstamp,
- unsigned int *underruns)
+ struct timespec *tstamp)
{
snd_pcm_sframes_t frames;
int rc = 0;
@@ -694,17 +687,16 @@
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) {
+ } 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);
+ "%s: %ld > %lu\n",
+ dev_name, frames, buf_size);
tstamp_last_underrun_log.tv_sec = tstamp_now.tv_sec;
tstamp_last_underrun_log.tv_nsec = tstamp_now.tv_nsec;
}
@@ -741,6 +733,13 @@
return 0;
}
+/*
+ * Attempts to resume a PCM.
+ * Note that this path does not get executed for default playback/capture
+ * stream. Default playback/capture stream are removed from the device
+ * upon suspend, and re-attached to the device after resume.
+ * The only stream that lives across suspend resume is hotword stream.
+ */
int cras_alsa_attempt_resume(snd_pcm_t *handle)
{
int rc;
@@ -749,18 +748,31 @@
while ((rc = snd_pcm_resume(handle)) == -EAGAIN)
usleep(ALSA_SUSPENDED_SLEEP_TIME_US);
if (rc < 0) {
+ /*
+ * Some devices do not support snd_pcm_resume, that is
+ * acceptable.
+ */
syslog(LOG_INFO, "System suspended, failed to resume %s.",
snd_strerror(rc));
rc = snd_pcm_prepare(handle);
- if (rc < 0)
- syslog(LOG_INFO, "Suspended, failed to prepare: %s.",
+ if (rc < 0) {
+ syslog(LOG_ERR, "Suspended, failed to prepare: %s.",
snd_strerror(rc));
+ }
+ /*
+ * CRAS does not use auto-start (start_threshold = 0), so start
+ * PCM after it is prepared. This is only for hotword stream.
+ */
+ rc = snd_pcm_start(handle);
+ if (rc < 0) {
+ syslog(LOG_ERR, "Suspended, failed to start: %s.",
+ snd_strerror(rc));
+ }
}
return rc;
}
-int cras_alsa_mmap_get_whole_buffer(snd_pcm_t *handle, uint8_t **dst,
- unsigned int *underruns)
+int cras_alsa_mmap_get_whole_buffer(snd_pcm_t *handle, uint8_t **dst)
{
snd_pcm_uframes_t offset;
/* The purpose of calling cras_alsa_mmap_begin is to get the base
@@ -773,13 +785,12 @@
*/
snd_pcm_uframes_t frames = 1;
- return cras_alsa_mmap_begin(
- handle, 0, dst, &offset, &frames, underruns);
+ return cras_alsa_mmap_begin(handle, 0, dst, &offset, &frames);
}
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)
+ snd_pcm_uframes_t *frames)
{
int rc;
unsigned int attempts = 0;
@@ -794,7 +805,6 @@
return rc;
continue; /* Recovered from suspend, try again. */
} else if (rc < 0) {
- *underruns = *underruns + 1;
/* If we can recover, continue and try again. */
if (snd_pcm_recover(handle, rc, 0) == 0)
continue;
@@ -819,7 +829,7 @@
}
int cras_alsa_mmap_commit(snd_pcm_t *handle, snd_pcm_uframes_t offset,
- snd_pcm_uframes_t frames, unsigned int *underruns)
+ snd_pcm_uframes_t frames)
{
int rc;
snd_pcm_sframes_t res;
@@ -833,7 +843,6 @@
if (rc < 0)
return rc;
} else {
- *underruns = *underruns + 1;
/* If we can recover, continue and try again. */
rc = snd_pcm_recover(handle, res, 0);
if (rc < 0) {
diff --git a/cras/src/server/cras_alsa_helpers.h b/cras/src/server/cras_alsa_helpers.h
index 33c3390..81ed645 100644
--- a/cras/src/server/cras_alsa_helpers.h
+++ b/cras/src/server/cras_alsa_helpers.h
@@ -105,8 +105,7 @@
/* Probes properties of the alsa device.
* Args:
- * dev - Path to the alsa device to test.
- * stream - Alsa stream type, input or output.
+ * handle - The open PCM to configure.
* rates - Pointer that will be set to the arrary of valid samples rates.
* Must be freed by the caller.
* channel_counts - Pointer that will be set to the array of valid channel
@@ -116,7 +115,7 @@
* Returns:
* 0 on success. On failure an error code from alsa or -ENOMEM.
*/
-int cras_alsa_fill_properties(const char *dev, snd_pcm_stream_t stream,
+int cras_alsa_fill_properties(snd_pcm_t *handle,
size_t **rates, size_t **channel_counts,
snd_pcm_format_t **formats);
@@ -162,8 +161,6 @@
* 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. -EPIPE if severe underrun
* happens.
@@ -172,8 +169,7 @@
snd_pcm_uframes_t severe_underrun_frames,
const char *dev_name,
snd_pcm_uframes_t *avail,
- struct timespec *tstamp,
- unsigned int *underruns);
+ struct timespec *tstamp);
/* Get the current alsa delay, make sure it's no bigger than the buffer size.
* Args:
@@ -191,12 +187,10 @@
* 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);
+int cras_alsa_mmap_get_whole_buffer(snd_pcm_t *handle, uint8_t **dst);
/* Wrapper for snd_pcm_mmap_begin
* Args:
@@ -206,27 +200,25 @@
* offset - Filled with the offset to pass back to commit.
* frames - Passed with the max number of frames to request. Filled with the
* max number to use.
- * underruns - counter to increment if an under-run occurs.
* Returns:
* zero on success, negative error code for fatal
* errors.
*/
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);
+ snd_pcm_uframes_t *frames);
/* Wrapper for snd_pcm_mmap_commit
* Args:
* handle - The open PCM to configure.
* offset - offset from call to mmap_begin.
* frames - # of frames written/read.
- * underruns - counter to increment if an under-run occurs.
* Returns:
* zero on success, negative error code for fatal
* errors.
*/
int cras_alsa_mmap_commit(snd_pcm_t *handle, snd_pcm_uframes_t offset,
- snd_pcm_uframes_t frames, unsigned int *underruns);
+ snd_pcm_uframes_t frames);
/* When the stream is suspended, due to a system suspend, loop until we can
* resume it. Won't actually loop very much because the system will be
diff --git a/cras/src/server/cras_alsa_io.c b/cras/src/server/cras_alsa_io.c
index 80a8c3b..d9169f1 100644
--- a/cras/src/server/cras_alsa_io.c
+++ b/cras/src/server/cras_alsa_io.c
@@ -23,6 +23,7 @@
#include "cras_audio_area.h"
#include "cras_config.h"
#include "cras_utf8.h"
+#include "cras_hotword_handler.h"
#include "cras_iodev.h"
#include "cras_iodev_list.h"
#include "cras_messages.h"
@@ -44,6 +45,8 @@
#define INTERNAL_MICROPHONE "Internal Mic"
#define INTERNAL_SPEAKER "Speaker"
#define KEYBOARD_MIC "Keyboard Mic"
+#define HEADPHONE "Headphone"
+#define MIC "Mic"
#define USB "USB"
/*
@@ -151,6 +154,7 @@
unsigned int filled_zeros_for_draining;
snd_pcm_uframes_t severe_underrun_frames;
struct cras_volume_curve *default_volume_curve;
+ int hwparams_set;
};
static void init_device_settings(struct alsa_io *aio);
@@ -244,6 +248,31 @@
},
};
+static int set_hwparams(struct cras_iodev *iodev)
+{
+ struct alsa_io *aio = (struct alsa_io *)iodev;
+ int period_wakeup;
+ int rc;
+
+ /* Only need to set hardware params once. */
+ if (aio->hwparams_set)
+ return 0;
+
+ /* If it's a wake on voice device, period_wakeups are required. */
+ period_wakeup = (iodev->active_node->type == CRAS_NODE_TYPE_HOTWORD);
+
+ /* Sets frame rate and channel count to alsa device before
+ * we test channel mapping. */
+ rc = cras_alsa_set_hwparams(aio->handle, iodev->format,
+ &iodev->buffer_size, period_wakeup,
+ aio->dma_period_set_microsecs);
+ if (rc < 0)
+ return rc;
+
+ aio->hwparams_set = 1;
+ return 0;
+}
+
/*
* iodev callbacks.
*/
@@ -259,8 +288,7 @@
aio->base.buffer_size,
aio->severe_underrun_frames,
iodev->info.name,
- &frames, tstamp,
- &aio->num_underruns);
+ &frames, tstamp);
if (rc < 0) {
if (rc == -EPIPE)
aio->num_severe_underruns++;
@@ -305,6 +333,7 @@
aio->handle = NULL;
aio->is_free_running = 0;
aio->filled_zeros_for_draining = 0;
+ aio->hwparams_set = 0;
cras_iodev_free_format(&aio->base);
cras_iodev_free_audio_area(&aio->base);
return 0;
@@ -316,6 +345,11 @@
struct alsa_io *aio = (struct alsa_io *)arg;
audio_thread_rm_callback(aio->poll_fd);
aio->poll_fd = -1;
+ aio->base.input_streaming = 1;
+
+ /* Send hotword triggered signal. */
+ cras_hotword_send_triggered_msg();
+
return 0;
}
@@ -323,7 +357,20 @@
{
struct alsa_io *aio = (struct alsa_io *)iodev;
snd_pcm_t *handle;
- int period_wakeup;
+ int rc;
+
+ rc = cras_alsa_pcm_open(&handle, aio->dev, aio->alsa_stream);
+ if (rc < 0)
+ return rc;
+
+ aio->handle = handle;
+
+ return 0;
+}
+
+static int configure_dev(struct cras_iodev *iodev)
+{
+ struct alsa_io *aio = (struct alsa_io *)iodev;
int rc;
/* This is called after the first stream added so configure for it.
@@ -342,39 +389,23 @@
syslog(LOG_DEBUG, "Configure alsa device %s rate %zuHz, %zu channels",
aio->dev, iodev->format->frame_rate,
iodev->format->num_channels);
- handle = 0; /* Avoid unused warning. */
- rc = cras_alsa_pcm_open(&handle, aio->dev, aio->alsa_stream);
+
+ rc = set_hwparams(iodev);
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, period_wakeup,
- aio->dma_period_set_microsecs);
- if (rc < 0) {
- cras_alsa_pcm_close(handle);
- return rc;
- }
-
/* Set channel map to device */
- rc = cras_alsa_set_channel_map(handle,
+ rc = cras_alsa_set_channel_map(aio->handle,
iodev->format);
- if (rc < 0) {
- cras_alsa_pcm_close(handle);
+ if (rc < 0)
return rc;
- }
/* Configure software params. */
- rc = cras_alsa_set_swparams(handle, &aio->enable_htimestamp);
- if (rc < 0) {
- cras_alsa_pcm_close(handle);
+ rc = cras_alsa_set_swparams(aio->handle, &aio->enable_htimestamp);
+ if (rc < 0)
return rc;
- }
- /* Assign pcm handle then initialize device settings. */
- aio->handle = handle;
+ /* Initialize device settings. */
init_device_settings(aio);
aio->poll_fd = -1;
@@ -382,7 +413,7 @@
struct pollfd *ufds;
int count, i;
- count = snd_pcm_poll_descriptors_count(handle);
+ count = snd_pcm_poll_descriptors_count(aio->handle);
if (count <= 0) {
syslog(LOG_ERR, "Invalid poll descriptors count\n");
return count;
@@ -392,7 +423,7 @@
if (ufds == NULL)
return -ENOMEM;
- rc = snd_pcm_poll_descriptors(handle, ufds, count);
+ rc = snd_pcm_poll_descriptors(aio->handle, ufds, count);
if (rc < 0) {
syslog(LOG_ERR,
"Getting hotword poll descriptors: %s\n",
@@ -480,8 +511,7 @@
format_bytes,
&dst,
&aio->mmap_offset,
- &nframes,
- &aio->num_underruns);
+ &nframes);
iodev->area->frames = nframes;
cras_audio_area_config_buf_pointers(iodev->area, iodev->format, dst);
@@ -498,8 +528,7 @@
return cras_alsa_mmap_commit(aio->handle,
aio->mmap_offset,
- nwritten,
- &aio->num_underruns);
+ nwritten);
}
static int flush_buffer(struct cras_iodev *iodev)
@@ -508,6 +537,7 @@
snd_pcm_uframes_t nframes;
if (iodev->direction == CRAS_STREAM_INPUT) {
+ nframes = snd_pcm_avail(aio->handle);
nframes = snd_pcm_forwardable(aio->handle);
return snd_pcm_forward(aio->handle, nframes);
}
@@ -552,8 +582,6 @@
static int update_channel_layout(struct cras_iodev *iodev)
{
struct alsa_io *aio = (struct alsa_io *)iodev;
- snd_pcm_t *handle = NULL;
- snd_pcm_uframes_t buf_size = 0;
int err = 0;
/* If the capture channel map is specified in UCM, prefer it over
@@ -570,25 +598,11 @@
}
}
- 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));
+ err = set_hwparams(iodev);
+ if (err < 0)
return err;
- }
- /* Sets frame rate and channel count to alsa device before
- * we test channel mapping. */
- 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;
- }
-
- err = cras_alsa_get_channel_map(handle, iodev->format);
-
- cras_alsa_pcm_close(handle);
- return err;
+ return cras_alsa_get_channel_map(aio->handle, iodev->format);
}
static int set_hotword_model(struct cras_iodev *iodev, const char *model_name)
@@ -795,7 +809,8 @@
mixer_input = ain->mixer_input;
if (cras_iodev_software_volume_needed(&aio->base)) {
- min_capture_gain = DEFAULT_MIN_CAPTURE_GAIN;
+ min_capture_gain = cras_iodev_minimum_software_gain(
+ &aio->base);
max_capture_gain = cras_iodev_maximum_software_gain(
&aio->base);
} else {
@@ -881,6 +896,7 @@
return n >= m && !strcmp(s + (n - m), suffix);
}
+#ifdef CRAS_DBUS
/*
* Drop the node name and replace it with node type.
*/
@@ -897,6 +913,7 @@
strcpy(node->name, DEFAULT);
}
}
+#endif
/*
* Sets the initial plugged state and type of a node based on its
@@ -923,18 +940,27 @@
break;
}
- /* If we didn't find a matching name above, but the node is a jack node,
- * set its type to headphone/mic. This matches node names like "DAISY-I2S Mic
- * Jack".
+ /*
+ * If we didn't find a matching name above, but the node is a jack node,
+ * and there is no "HDMI" in the node name, then this must be a 3.5mm
+ * headphone/mic.
+ * Set its type and name to headphone/mic. The name is important because
+ * it associates the UCM section to the node so the properties like
+ * default node gain can be obtained.
+ * This matches node names like "DAISY-I2S Mic Jack".
* If HDMI is in the node name, set its type to HDMI. This matches node names
* like "Rockchip HDMI Jack".
*/
if (i == ARRAY_SIZE(node_defaults)) {
- if (endswith(node->name, "Jack")) {
- if (node->dev->direction == CRAS_STREAM_OUTPUT)
+ if (endswith(node->name, "Jack") && !strstr(node->name, HDMI)) {
+ if (node->dev->direction == CRAS_STREAM_OUTPUT) {
node->type = CRAS_NODE_TYPE_HEADPHONE;
- else
+ strncpy(node->name, HEADPHONE, sizeof(node->name) - 1);
+ }
+ else {
node->type = CRAS_NODE_TYPE_MIC;
+ strncpy(node->name, MIC, sizeof(node->name) - 1);
+ }
}
if (strstr(node->name, HDMI) &&
node->dev->direction == CRAS_STREAM_OUTPUT)
@@ -949,8 +975,10 @@
node->position = NODE_POSITION_EXTERNAL;
}
+#ifdef CRAS_DBUS
if (!is_utf8_string(node->name))
drop_node_name(node);
+#endif
}
static int get_ucm_flag_integer(struct alsa_io *aio,
@@ -1043,18 +1071,22 @@
static void set_input_node_software_volume_needed(
struct alsa_input_node *input, struct alsa_io *aio)
{
+ long min_software_gain;
long max_software_gain;
int rc;
input->base.software_volume_needed = 0;
+ input->base.min_software_gain = DEFAULT_MIN_CAPTURE_GAIN;
input->base.max_software_gain = 0;
- /* Enable software gain only if max software gain is specified in UCM.*/
+ /* Enable software gain only if max software gain is specified in UCM. */
if (!aio->ucm)
return;
rc = ucm_get_max_software_gain(aio->ucm, input->base.name,
&max_software_gain);
+
+ /* If max software gain doesn't exist, skip min software gain setting. */
if (rc)
return;
@@ -1063,6 +1095,24 @@
syslog(LOG_INFO,
"Use software gain for %s with max %ld because it is specified"
" in UCM", input->base.name, max_software_gain);
+
+ /* Enable min software gain if it is specified in UCM. */
+ rc = ucm_get_min_software_gain(aio->ucm, input->base.name,
+ &min_software_gain);
+ if (rc)
+ return;
+
+ if (min_software_gain > max_software_gain) {
+ syslog(LOG_ERR,
+ "Ignore MinSoftwareGain %ld because it is larger than "
+ "MaxSoftwareGain %ld", min_software_gain, max_software_gain);
+ return;
+ }
+
+ syslog(LOG_INFO,
+ "Use software gain for %s with min %ld because it is specified"
+ " in UCM", input->base.name, min_software_gain);
+ input->base.min_software_gain = min_software_gain;
}
static void set_input_default_node_gain(struct alsa_input_node *input,
@@ -1167,9 +1217,10 @@
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);
+ if (snprintf(node_name, sizeof(node_name), "%s: %s",
+ aio->base.info.name, ctl_name) > 0) {
+ new_output(aio, cras_output, node_name);
+ }
} else {
new_output(aio, cras_output, ctl_name);
}
@@ -1203,6 +1254,7 @@
static struct alsa_input_node *new_input(struct alsa_io *aio,
struct mixer_control *cras_input, const char *name)
{
+ struct cras_iodev *iodev = &aio->base;
struct alsa_input_node *input;
char *mic_positions;
int err;
@@ -1248,6 +1300,12 @@
free(input->channel_layout);
input->channel_layout = 0;
}
+ if (ucm_get_preempt_hotword(aio->ucm, name)) {
+ iodev->pre_open_iodev_hook =
+ cras_iodev_list_suspend_hotword_streams;
+ iodev->post_close_iodev_hook =
+ cras_iodev_list_resume_hotword_stream;
+ }
}
cras_iodev_add_node(&aio->base, &input->base);
@@ -1264,8 +1322,11 @@
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);
+ int ret = snprintf(node_name , sizeof(node_name), "%s: %s",
+ aio->base.info.name, ctl_name);
+ // Truncation is OK, but add a check to make the compiler happy.
+ if (ret == sizeof(node_name))
+ node_name[sizeof(node_name) - 1] = '\0';
new_input(aio, cras_input, node_name);
} else {
new_input(aio, cras_input, ctl_name);
@@ -1317,6 +1378,21 @@
return ain;
}
+static const struct cras_alsa_jack *get_jack_from_node(struct cras_ionode *node)
+{
+ const struct cras_alsa_jack *jack = NULL;
+
+ if (node == NULL)
+ return NULL;
+
+ if (node->dev->direction == CRAS_STREAM_OUTPUT)
+ jack = ((struct alsa_output_node *)node)->jack;
+ else if (node->dev->direction == CRAS_STREAM_INPUT)
+ jack = ((struct alsa_input_node *)node)->jack;
+
+ return jack;
+}
+
/*
* 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
@@ -1417,9 +1493,12 @@
cras_alsa_jack_update_monitor_name(jack, node->base.name,
sizeof(node->base.name));
+
+#ifdef CRAS_DBUS
/* The name got from jack might be an invalid UTF8 string. */
if (!is_utf8_string(node->base.name))
drop_node_name(&node->base);
+#endif
cras_iodev_set_node_attr(&node->base, IONODE_ATTR_PLUGGED, plugged);
@@ -1569,7 +1648,7 @@
free(iodev->supported_formats);
iodev->supported_formats = NULL;
- err = cras_alsa_fill_properties(aio->dev, aio->alsa_stream,
+ err = cras_alsa_fill_properties(aio->handle,
&iodev->supported_rates,
&iodev->supported_channel_counts,
&iodev->supported_formats);
@@ -1641,8 +1720,7 @@
size_t format_bytes;
/* Fill whole buffer with zeros. */
- rc = cras_alsa_mmap_get_whole_buffer(
- aio->handle, &dst, &aio->num_underruns);
+ rc = cras_alsa_mmap_get_whole_buffer(aio->handle, &dst);
if (rc < 0) {
syslog(LOG_ERR, "Failed to get whole buffer: %s",
@@ -1668,9 +1746,55 @@
odev->min_buffer_level + odev->min_cb_level);
}
+/* This function is for leaving no-stream state but still not in free run yet.
+ * The device may have valid samples remaining. We need to adjust appl_ptr to
+ * the correct position, which is MAX(min_cb_level + min_buffer_level,
+ * valid_sample) */
+static int adjust_appl_ptr_samples_remaining(struct cras_iodev *odev)
+{
+ struct alsa_io *aio = (struct alsa_io *)odev;
+ int rc;
+ unsigned int real_hw_level, valid_sample, offset;
+ struct timespec hw_tstamp;
+
+ /* Get the amount of valid samples which haven't been played yet.
+ * The real_hw_level is the real hw_level in device buffer. It doesn't
+ * subtract min_buffer_level. */
+ valid_sample = 0;
+ rc = odev->frames_queued(odev, &hw_tstamp);
+ if(rc < 0)
+ return rc;
+ real_hw_level = rc;
+
+ /*
+ * If underrun happened, handle it. Because alsa_output_underrun function
+ * has already called adjust_appl_ptr, we don't need to call it again.
+ */
+ if (real_hw_level < odev->min_buffer_level)
+ return odev->output_underrun(odev);
+
+ if (real_hw_level > aio->filled_zeros_for_draining)
+ valid_sample = real_hw_level - aio->filled_zeros_for_draining;
+
+ offset = MAX(odev->min_buffer_level + odev->min_cb_level, valid_sample);
+
+ /* Fill zeros to make sure there are enough zero samples in device buffer.*/
+ if (offset > real_hw_level) {
+ rc = cras_iodev_fill_odev_zeros(odev, offset - real_hw_level);
+ if (rc)
+ return rc;
+ }
+ return cras_alsa_resume_appl_ptr(aio->handle, offset);
+}
+
static int alsa_output_underrun(struct cras_iodev *odev)
{
+ struct alsa_io *aio = (struct alsa_io *)odev;
int rc;
+
+ /* Update number of underruns we got. */
+ aio->num_underruns++;
+
/* Fill whole buffer with zeros. This avoids samples left in buffer causing
* noise when device plays them. */
rc = fill_whole_buffer_with_zeros(odev);
@@ -1684,21 +1808,31 @@
{
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;
+ unsigned int real_hw_level, fr_to_write;
+ unsigned int target_hw_level = odev->min_cb_level * 2 + odev->min_buffer_level;
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)
+ /* Check if all valid samples are played. If all valid samples are played,
+ * fill whole buffer with zeros. The real_hw_level is the real hw_level in
+ * device buffer. It doesn't subtract min_buffer_level.*/
+ rc = odev->frames_queued(odev, &hw_tstamp);
+ if(rc < 0)
return rc;
- hw_level = rc;
+ real_hw_level = rc;
- if (hw_level < aio->filled_zeros_for_draining || hw_level == 0) {
+ /* If underrun happened, handle it and enter free run state. */
+ if (real_hw_level < odev->min_buffer_level) {
+ rc = odev->output_underrun(odev);
+ if (rc < 0)
+ return rc;
+ aio->is_free_running = 1;
+ return 0;
+ }
+
+ if (real_hw_level <= aio->filled_zeros_for_draining || real_hw_level == 0) {
rc = fill_whole_buffer_with_zeros(odev);
if (rc < 0)
return rc;
@@ -1707,10 +1841,9 @@
}
/* 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);
+ fr_to_write = odev->buffer_size - real_hw_level;
+ if (real_hw_level < target_hw_level) {
+ fr_to_write = MIN(target_hw_level - real_hw_level, fr_to_write);
rc = cras_iodev_fill_odev_zeros(odev, fr_to_write);
if (rc)
return rc;
@@ -1725,10 +1858,10 @@
struct alsa_io *aio = (struct alsa_io *)odev;
int rc;
- if (!aio->is_free_running)
- return 0;
-
- rc = adjust_appl_ptr(odev);
+ if (aio->is_free_running)
+ rc = adjust_appl_ptr(odev);
+ else
+ rc = adjust_appl_ptr_samples_remaining(odev);
if (rc) {
syslog(LOG_ERR, "device %s failed to leave free run, rc = %d",
odev->info.name, rc);
@@ -1861,6 +1994,7 @@
aio->base.output_underrun = alsa_output_underrun;
}
iodev->open_dev = open_dev;
+ iodev->configure_dev = configure_dev;
iodev->close_dev = close_dev;
iodev->update_supported_formats = update_supported_formats;
iodev->frames_queued = frames_queued;
@@ -1899,6 +2033,7 @@
aio->ucm = ucm;
if (ucm) {
unsigned int level;
+ int rc;
aio->dsp_name_default = ucm_get_dsp_name_default(ucm,
direction);
@@ -1908,8 +2043,8 @@
aio->base.set_swap_mode_for_node =
set_alsa_node_swapped;
- level = ucm_get_min_buffer_level(ucm);
- if (level && direction == CRAS_STREAM_OUTPUT)
+ rc = ucm_get_min_buffer_level(ucm, &level);
+ if (!rc && direction == CRAS_STREAM_OUTPUT)
iodev->min_buffer_level = level;
aio->enable_htimestamp =
@@ -2029,8 +2164,10 @@
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 && is_first)
+ /* Set plugged for the first USB device per card when it appears if
+ * there is no jack reporting plug status. */
+ if (aio->card_type == ALSA_CARD_TYPE_USB && is_first &&
+ !get_jack_from_node(iodev->active_node))
cras_iodev_set_node_attr(iodev->active_node,
IONODE_ATTR_PLUGGED, 1);
@@ -2116,8 +2253,10 @@
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)
+ /* Set plugged for the first USB device per card when it appears if
+ * there is no jack reporting plug status. */
+ if (aio->card_type == ALSA_CARD_TYPE_USB && aio->is_first &&
+ !get_jack_from_node(iodev->active_node))
cras_iodev_set_node_attr(iodev->active_node,
IONODE_ATTR_PLUGGED, 1);
diff --git a/cras/src/server/cras_alsa_jack.c b/cras/src/server/cras_alsa_jack.c
index 2f69e3b..b74af25 100644
--- a/cras/src/server/cras_alsa_jack.c
+++ b/cras/src/server/cras_alsa_jack.c
@@ -74,6 +74,12 @@
* edid_file - File to read the EDID from (if available, HDMI only).
* display_info_timer - Timer used to poll display info for HDMI jacks.
* display_info_retries - Number of times to retry reading display info.
+ *
+ * mixer_output/mixer_input fields are only used to find the node for this
+ * jack. These are not used for setting volume or mute. There should be a
+ * 1:1 map between node and jack. node->jack follows the pointer; jack->node
+ * is done by either searching node->jack pointers or searching the node that
+ * has the same mixer_control as the jack.
*/
struct cras_alsa_jack {
unsigned is_gpio; /* !0 -> 'gpio' valid
@@ -863,6 +869,15 @@
return 0;
}
+/* Check if the given name is a jack created for the connector control of a
+ * input/output terminal entity on a USB Audio Class 2.0 device. */
+static int is_jack_uac2(const char *jack_name,
+ enum CRAS_STREAM_DIRECTION direction)
+{
+ return jack_matches_regex(jack_name, direction == CRAS_STREAM_OUTPUT ?
+ "^.* - Output Jack$" : "^.* - Input Jack$");
+}
+
/* 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)
@@ -882,7 +897,6 @@
static const char eld_control_name[] = "ELD";
const char * const *jack_names;
unsigned int num_jack_names;
- char device_name[6];
if (!jack_list->hctl) {
syslog(LOG_WARNING, "Can't search hctl for jacks.");
@@ -905,7 +919,8 @@
if (iface != SND_CTL_ELEM_IFACE_CARD)
continue;
name = snd_hctl_elem_get_name(elem);
- if (!is_jack_control_in_list(jack_names, num_jack_names, name))
+ if (!is_jack_control_in_list(jack_names, num_jack_names, name) &&
+ !is_jack_uac2(name, jack_list->direction))
continue;
if (hctl_jack_device_index(name) != jack_list->device_index)
continue;
@@ -917,7 +932,6 @@
jack->jack_list = jack_list;
DL_APPEND(jack_list->jacks, jack);
- syslog(LOG_DEBUG, "Found Jack: %s for %s", name, device_name);
snd_hctl_elem_set_callback(elem, hctl_jack_cb);
snd_hctl_elem_set_callback_private(elem, jack);
diff --git a/cras/src/server/cras_alsa_mixer.c b/cras/src/server/cras_alsa_mixer.c
index 4cb665a..27ea4a6 100644
--- a/cras/src/server/cras_alsa_mixer.c
+++ b/cras/src/server/cras_alsa_mixer.c
@@ -770,7 +770,7 @@
"Failed to add mixer control '%s'"
" with type '%d'",
control->name, control->type);
- return rc;
+ goto out;
}
found = 1;
}
@@ -806,7 +806,7 @@
"Failed to add mixer control '%s'"
" with type '%d'",
control->name, control->type);
- return rc;
+ goto out;
}
found = 1;
}
@@ -836,7 +836,7 @@
"Speaker", coupled_controls);
if (rc) {
syslog(LOG_ERR, "Could not add coupled output");
- return rc;
+ goto out;
}
}
@@ -848,10 +848,12 @@
rc = add_main_volume_control(cmix, other_elem);
if (rc) {
syslog(LOG_ERR, "Could not add other volume control");
- return rc;
+ goto out;
}
}
+out:
+ mixer_name_free(default_controls);
return rc;
}
diff --git a/cras/src/server/cras_alsa_ucm.c b/cras/src/server/cras_alsa_ucm.c
index 8aa4891..2508b8b 100644
--- a/cras/src/server/cras_alsa_ucm.c
+++ b/cras/src/server/cras_alsa_ucm.c
@@ -10,6 +10,7 @@
#include <syslog.h>
#include "cras_alsa_ucm.h"
+#include "cras_util.h"
#include "utlist.h"
static const char jack_var[] = "JackName";
@@ -32,10 +33,23 @@
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 preempt_hotword_var[] = "PreemptHotword";
+static const char echo_reference_dev_name_var[] = "EchoReferenceDev";
+/*
+ * Set this value in a SectionDevice to specify the minimum software gain in
+ * 0.01 dB and enable software gain on this node. It must be used with
+ * MaxSoftwareGain. If not, the value will be ignored.
+ */
+static const char min_software_gain[] = "MinSoftwareGain";
+/*
+ * Set this value in a SectionDevice to specify the maximum software gain in
+ * 0.01 dB and enable software gain on this node.
+ */
static const char max_software_gain[] = "MaxSoftwareGain";
-/* Set this value in a SectionDevice to specify the default node gain in dBm. */
+/*
+ * Set this value in a SectionDevice to specify the default node gain in
+ * 0.01 dB.
+ */
static const char default_node_gain[] = "DefaultNodeGain";
static const char hotword_model_prefix[] = "Hotword Model";
static const char fully_specified_ucm_var[] = "FullySpecifiedUCM";
@@ -49,6 +63,7 @@
"Voice Call",
"Speech",
"Pro Audio",
+ "Accessibility",
};
/* Represents a list of section names found in UCM. */
@@ -334,7 +349,8 @@
{
const char *names_in_string = NULL;
int rc;
- char *tokens, *name, *laststr;
+ char *tokens, *name;
+ char *laststr = NULL;
struct mixer_name *names = NULL;
rc = get_var(mgr, var, dev, uc_verb(mgr), &names_in_string);
@@ -361,6 +377,8 @@
const char **list;
int num_verbs, i, j;
+ assert_on_compile(ARRAY_SIZE(use_case_verbs) == CRAS_STREAM_NUM_TYPES);
+
if (!name)
return NULL;
@@ -614,16 +632,18 @@
return ucm_get_dsp_name(mgr, "", direction);
}
-unsigned int ucm_get_min_buffer_level(struct cras_use_case_mgr *mgr)
+int ucm_get_min_buffer_level(struct cras_use_case_mgr *mgr,
+ unsigned int *level)
{
int value;
int rc;
rc = get_int(mgr, min_buffer_level_var, "", uc_verb(mgr), &value);
if (rc)
- return 0;
+ return -ENOENT;
+ *level = value;
- return value;
+ return 0;
}
unsigned int ucm_get_disable_software_volume(struct cras_use_case_mgr *mgr)
@@ -638,6 +658,19 @@
return value;
}
+int ucm_get_min_software_gain(struct cras_use_case_mgr *mgr, const char *dev,
+ long *gain)
+{
+ int value;
+ int rc;
+
+ rc = get_int(mgr, min_software_gain, dev, uc_verb(mgr), &value);
+ if (rc)
+ return rc;
+ *gain = value;
+ return 0;
+}
+
int ucm_get_max_software_gain(struct cras_use_case_mgr *mgr, const char *dev,
long *gain)
{
@@ -664,6 +697,17 @@
return 0;
}
+int ucm_get_preempt_hotword(struct cras_use_case_mgr *mgr, const char *dev)
+{
+ int value;
+ int rc;
+
+ rc = get_int(mgr, preempt_hotword_var, dev, uc_verb(mgr), &value);
+ if (rc)
+ return 0;
+ return value;
+}
+
const char *ucm_get_device_name_for_dev(
struct cras_use_case_mgr *mgr, const char *dev,
enum CRAS_STREAM_DIRECTION direction)
@@ -675,6 +719,19 @@
return NULL;
}
+const char *ucm_get_echo_reference_dev_name_for_dev(
+ struct cras_use_case_mgr *mgr, const char *dev)
+{
+ const char *name = NULL;
+ int rc;
+
+ rc = get_var(mgr, echo_reference_dev_name_var, dev,
+ uc_verb(mgr), &name);
+ if (rc)
+ return NULL;
+ return name;
+}
+
int ucm_get_sample_rate_for_dev(struct cras_use_case_mgr *mgr, const char *dev,
enum CRAS_STREAM_DIRECTION direction)
{
@@ -760,10 +817,10 @@
/* snd_use_case_get_list fills list with pairs of device name and
* comment, so device names are in even-indexed elements. */
+ const char *dev_name;
for (i = 0; i < num_devs; i += 2) {
enum CRAS_STREAM_DIRECTION dir = CRAS_STREAM_UNDEFINED;
int dev_idx = -1;
- const char *dev_name = strdup(list[i]);
const char *jack_name;
const char *jack_type;
const char *mixer_name;
@@ -771,6 +828,7 @@
int rc;
const char *target_device_name;
+ dev_name = strdup(list[i]);
if (!dev_name)
continue;
@@ -840,6 +898,7 @@
DL_APPEND(sections, dev_sec);
ucm_section_dump(dev_sec);
+ free((void *)dev_name);
}
if (num_devs > 0)
@@ -850,6 +909,7 @@
if (num_devs > 0)
snd_use_case_free_list(list, num_devs);
ucm_section_free_list(sections);
+ free((void *)dev_name);
return NULL;
}
@@ -920,7 +980,7 @@
enable_mod:
ucm_set_modifier_enabled(mgr, model_mod, 1);
-
+ free((void *)model_mod);
return 0;
}
@@ -1018,6 +1078,8 @@
if (strcmp(name, "hctl") && strcmp(name, "gpio")) {
syslog(LOG_ERR, "Unknown jack type: %s", name);
+ if(name)
+ free((void *)name);
return NULL;
}
return name;
diff --git a/cras/src/server/cras_alsa_ucm.h b/cras/src/server/cras_alsa_ucm.h
index 622bbf8..38377a0 100644
--- a/cras/src/server/cras_alsa_ucm.h
+++ b/cras/src/server/cras_alsa_ucm.h
@@ -184,15 +184,31 @@
* unreliable dma residue.
* Args:
* mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
+ * level - The pointer to the returned value.
+ *
*/
-unsigned int ucm_get_min_buffer_level(struct cras_use_case_mgr *mgr);
+int ucm_get_min_buffer_level(struct cras_use_case_mgr *mgr,
+ unsigned int *level);
/* Gets the flag for disabling software volume.
* Args:
* mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
+ * Returns:
+ * 0 on success, -ENOENT on failure.
*/
unsigned int ucm_get_disable_software_volume(struct cras_use_case_mgr *mgr);
+/* Gets the value for minimum software gain.
+ * Args:
+ * mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
+ * dev - The device to check for minimum software gain.
+ * gain - The pointer to the returned value;
+ * Returns:
+ * 0 on success, other error codes on failure.
+ */
+int ucm_get_min_software_gain(struct cras_use_case_mgr *mgr, const char *dev,
+ long *gain);
+
/* Gets the value for maximum software gain.
* Args:
* mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
@@ -215,6 +231,16 @@
int ucm_get_default_node_gain(struct cras_use_case_mgr *mgr, const char *dev,
long *gain);
+/* Gets the flag if an input device can preempt hotword recording.
+ * Args:
+ * mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
+ * dev - The device to check for preempt hotword flag.
+ * Returns:
+ * Non-zero value means input can preempt hotword recording, otherwise
+ * return zero.
+ */
+int ucm_get_preempt_hotword(struct cras_use_case_mgr *mgr, const char *dev);
+
/* Gets the device name of this device on the card..
*
* Args:
@@ -230,6 +256,18 @@
struct cras_use_case_mgr *mgr, const char *dev,
enum CRAS_STREAM_DIRECTION direction);
+/* Gets the node name of the echo reference device on the card.
+ * Args:
+ * mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create.
+ * dev - The device to check echo reference for.
+ * Returns:
+ * String containing the node name of the echo reference to this
+ * dev, caller is responsible to free it later. NULL if echo reference
+ * doesn't exist.
+ */
+const char *ucm_get_echo_reference_dev_name_for_dev(
+ struct cras_use_case_mgr *mgr, const char *dev);
+
/* Gets the sample rate at which to run this device.
*
* Args:
diff --git a/cras/src/server/cras_apm_list.c b/cras/src/server/cras_apm_list.c
new file mode 100644
index 0000000..ac0935e
--- /dev/null
+++ b/cras/src/server/cras_apm_list.c
@@ -0,0 +1,643 @@
+/* Copyright 2018 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <string.h>
+#include <syslog.h>
+
+#include <webrtc-apm/webrtc_apm.h>
+
+#include "aec_config.h"
+#include "apm_config.h"
+#include "byte_buffer.h"
+#include "cras_apm_list.h"
+#include "cras_audio_area.h"
+#include "cras_audio_format.h"
+#include "cras_dsp_pipeline.h"
+#include "cras_iodev.h"
+#include "cras_iodev_list.h"
+#include "dsp_util.h"
+#include "dumper.h"
+#include "float_buffer.h"
+#include "utlist.h"
+
+
+/*
+ * Structure holding a WebRTC audio processing module and necessary
+ * info to process and transfer input buffer from device to stream.
+ *
+ * Below chart describes the buffer structure inside APM and how an input buffer
+ * flows from a device through the APM to stream. APM processes audio buffers in
+ * fixed 10ms width, and that's the main reason we need two copies of the
+ * buffer:
+ * (1) to cache input buffer from device until 10ms size is filled.
+ * (2) to store the interleaved buffer, of 10ms size also, after APM processing.
+ *
+ * ________ _______ _______________________________
+ * | | | | |_____________APM ____________|
+ * |input |-> | DSP |---> || | | || -> stream 1
+ * |device| | | | || float buf | -> | byte buf ||
+ * |______| |_____| | ||___________| |__________||
+ * | |_____________________________|
+ * | _______________________________
+ * |-> | APM 2 | -> stream 2
+ * | |_____________________________|
+ * | ...
+ * |
+ * |------------------------------------> stream N
+ *
+ * Members:
+ * apm_ptr - An APM instance from libwebrtc_audio_processing
+ * dev_ptr - Pointer to the device this APM is associated with.
+ * buffer - Stores the processed/interleaved data ready for stream to read.
+ * fbuffer - Stores the floating pointer buffer from input device waiting
+ * for APM to process.
+ * dev_fmt - The format used by the iodev this APM attaches to.
+ * fmt - The audio data format configured for this APM.
+ * area - The cras_audio_area used for copying processed data to client
+ * stream.
+ * work_queue - A task queue instance created and destroyed by
+ * libwebrtc_apm.
+ */
+struct cras_apm {
+ webrtc_apm apm_ptr;
+ void *dev_ptr;
+ struct byte_buffer *buffer;
+ struct float_buffer *fbuffer;
+ struct cras_audio_format dev_fmt;
+ struct cras_audio_format fmt;
+ struct cras_audio_area *area;
+ void *work_queue;
+ struct cras_apm *prev, *next;
+};
+
+/*
+ * Lists of cras_apm instances created for a stream. A stream may
+ * have more than one cras_apm when multiple input devices are
+ * enabled. The most common scenario is the silent input iodev be
+ * enabled when CRAS switches active input device.
+ */
+struct cras_apm_list {
+ void *stream_ptr;
+ uint64_t effects;
+ struct cras_apm *apms;
+ struct cras_apm_list *prev, *next;
+};
+
+/*
+ * Object used to analyze playback audio from output iodev. It is responsible
+ * to get buffer containing latest output data and provide it to the APM
+ * instances which want to analyze reverse stream.
+ * Member:
+ * ext - The interface implemented to process reverse(output) stream
+ * data in various formats.
+ * fbuf - Middle buffer holding reverse data for APMs to analyze.
+ * odev - Pointer to the output iodev playing audio as the reverse
+ * stream. NULL if there's no playback stream.
+ * dev_rate - The sample rate odev is opened for.
+ * process_reverse - Flag to indicate if there's APM has effect that
+ * needs to process reverse stream.
+ */
+struct cras_apm_reverse_module {
+ struct ext_dsp_module ext;
+ struct float_buffer *fbuf;
+ struct cras_iodev *odev;
+ unsigned int dev_rate;
+ unsigned process_reverse;
+};
+
+static struct cras_apm_reverse_module *rmodule = NULL;
+static struct cras_apm_list *apm_list = NULL;
+static struct aec_config *aec_config = NULL;
+static struct apm_config *apm_config = NULL;
+static const char *aec_config_dir = NULL;
+
+/* Update the global process reverse flag. Should be called when apms are added
+ * or removed. */
+static void update_process_reverse_flag()
+{
+ struct cras_apm_list *list;
+
+ if (!rmodule)
+ return;
+ rmodule->process_reverse = 0;
+ DL_FOREACH(apm_list, list) {
+ rmodule->process_reverse |=
+ !!(list->effects & APM_ECHO_CANCELLATION);
+ }
+}
+
+static void apm_destroy(struct cras_apm **apm)
+{
+ if (*apm == NULL)
+ return;
+ byte_buffer_destroy(&(*apm)->buffer);
+ float_buffer_destroy(&(*apm)->fbuffer);
+ cras_audio_area_destroy((*apm)->area);
+
+ /* Any unfinished AEC dump handle will be closed. */
+ webrtc_apm_destroy((*apm)->apm_ptr);
+ free(*apm);
+ *apm = NULL;
+}
+
+struct cras_apm_list *cras_apm_list_create(void *stream_ptr,
+ uint64_t effects)
+{
+ struct cras_apm_list *list;
+
+ if (effects == 0)
+ return NULL;
+
+ DL_SEARCH_SCALAR(apm_list, list, stream_ptr, stream_ptr);
+ if (list)
+ return list;
+
+ list = (struct cras_apm_list *)calloc(1, sizeof(*list));
+ list->stream_ptr = stream_ptr;
+ list->effects = effects;
+ list->apms = NULL;
+ DL_APPEND(apm_list, list);
+
+ return list;
+}
+
+struct cras_apm *cras_apm_list_get(struct cras_apm_list *list, void *dev_ptr)
+{
+ struct cras_apm *apm;
+
+ if (list == NULL)
+ return NULL;
+
+ DL_FOREACH(list->apms, apm) {
+ if (apm->dev_ptr == dev_ptr)
+ return apm;
+ }
+ return NULL;
+}
+
+uint64_t cras_apm_list_get_effects(struct cras_apm_list *list)
+{
+ if (list == NULL)
+ return 0;
+ else
+ return list->effects;
+}
+
+void cras_apm_list_remove(struct cras_apm_list *list, void *dev_ptr)
+{
+ struct cras_apm *apm;
+
+ DL_FOREACH(list->apms, apm) {
+ if (apm->dev_ptr == dev_ptr ) {
+ DL_DELETE(list->apms, apm);
+ apm_destroy(&apm);
+ }
+ }
+}
+
+/*
+ * WebRTC APM handles no more than stereo + keyboard mic channels.
+ * Ignore keyboard mic feature for now because that requires processing on
+ * mixed buffer from two input devices. Based on that we should modify the best
+ * channel layout for APM use.
+ * Args:
+ * apm_fmt - Pointer to a format struct already filled with the value of
+ * the open device format. Its content may be modified for APM use.
+ */
+static void get_best_channels(struct cras_audio_format *apm_fmt)
+{
+ int ch;
+ int8_t layout[CRAS_CH_MAX];
+
+ /* Assume device format has correct channel layout populated. */
+ if (apm_fmt->num_channels <= 2)
+ return;
+
+ /* If the device provides recording from more channels than we care
+ * about, construct a new channel layout containing subset of original
+ * channels that matches either FL, FR, or FC.
+ * TODO(hychao): extend the logic when we have a stream that wants
+ * to record channels like RR(rear right).
+ */
+ for (ch = 0 ; ch < CRAS_CH_MAX; ch++)
+ layout[ch] = -1;
+
+ apm_fmt->num_channels = 0;
+ if (apm_fmt->channel_layout[CRAS_CH_FL] != -1)
+ layout[CRAS_CH_FL] = apm_fmt->num_channels++;
+ if (apm_fmt->channel_layout[CRAS_CH_FR] != -1)
+ layout[CRAS_CH_FR] = apm_fmt->num_channels++;
+ if (apm_fmt->channel_layout[CRAS_CH_FC] != -1)
+ layout[CRAS_CH_FC] = apm_fmt->num_channels++;
+
+ for (ch = 0 ; ch < CRAS_CH_MAX; ch++)
+ apm_fmt->channel_layout[ch] = layout[ch];
+}
+
+struct cras_apm *cras_apm_list_add(struct cras_apm_list *list,
+ void *dev_ptr,
+ const struct cras_audio_format *dev_fmt)
+{
+ struct cras_apm *apm;
+
+ DL_FOREACH(list->apms, apm) {
+ if (apm->dev_ptr == dev_ptr) {
+ DL_DELETE(list->apms, apm);
+ apm_destroy(&apm);
+ }
+ }
+
+ // TODO(hychao): Remove the check when we enable more effects.
+ if (!(list->effects & APM_ECHO_CANCELLATION))
+ return NULL;
+
+ apm = (struct cras_apm *)calloc(1, sizeof(*apm));
+
+ /* Configures APM to the format used by input device. If the channel
+ * count is larger than stereo, use the standard channel count/layout
+ * in APM. */
+ apm->dev_fmt = *dev_fmt;
+ apm->fmt = *dev_fmt;
+ get_best_channels(&apm->fmt);
+
+ apm->apm_ptr = webrtc_apm_create(
+ apm->fmt.num_channels,
+ apm->fmt.frame_rate,
+ aec_config,
+ apm_config);
+ if (apm->apm_ptr == NULL) {
+ syslog(LOG_ERR, "Fail to create webrtc apm for ch %zu"
+ " rate %zu effect %lu",
+ dev_fmt->num_channels,
+ dev_fmt->frame_rate,
+ list->effects);
+ free(apm);
+ return NULL;
+ }
+
+ apm->dev_ptr = dev_ptr;
+ apm->work_queue = NULL;
+
+ /* WebRTC APM wants 10 ms equivalence of data to process. */
+ apm->buffer = byte_buffer_create(10 * apm->fmt.frame_rate / 1000 *
+ cras_get_format_bytes(&apm->fmt));
+ apm->fbuffer = float_buffer_create(10 * apm->fmt.frame_rate / 1000,
+ apm->fmt.num_channels);
+ apm->area = cras_audio_area_create(apm->fmt.num_channels);
+ cras_audio_area_config_channels(apm->area, &apm->fmt);
+
+ DL_APPEND(list->apms, apm);
+ update_process_reverse_flag();
+
+ return apm;
+}
+
+int cras_apm_list_destroy(struct cras_apm_list *list)
+{
+ struct cras_apm_list *tmp;
+ struct cras_apm *apm;
+
+ DL_FOREACH(apm_list, tmp) {
+ if (tmp == list) {
+ DL_DELETE(apm_list, tmp);
+ break;
+ }
+ }
+
+ if (tmp == NULL)
+ return 0;
+
+ DL_FOREACH(list->apms, apm) {
+ DL_DELETE(list->apms, apm);
+ apm_destroy(&apm);
+ }
+ free(list);
+
+ update_process_reverse_flag();
+
+ return 0;
+}
+
+/*
+ * Determines the iodev to be used as the echo reference for APM reverse
+ * analysis. If there exists the special purpose "echo reference dev" then
+ * use it. Otherwise just use this output iodev.
+ */
+static struct cras_iodev *get_echo_reference_target(struct cras_iodev *iodev)
+{
+ return iodev->echo_reference_dev
+ ? iodev->echo_reference_dev
+ : iodev;
+}
+
+/*
+ * Updates the first enabled output iodev in the list, determine the echo
+ * reference target base on this output iodev, and register rmodule as ext dsp
+ * module to this echo reference target.
+ * When this echo reference iodev is opened and audio data flows through its
+ * dsp pipeline, APMs will anaylize the reverse stream. This is expected to be
+ * called in main thread when output devices enable/dsiable state changes.
+ */
+static void update_first_output_dev_to_process()
+{
+ struct cras_iodev *echo_ref;
+ struct cras_iodev *iodev =
+ cras_iodev_list_get_first_enabled_iodev(
+ CRAS_STREAM_OUTPUT);
+
+ if (iodev == NULL)
+ return;
+
+ echo_ref = get_echo_reference_target(iodev);
+ rmodule->odev = echo_ref;
+ cras_iodev_set_ext_dsp_module(echo_ref, &rmodule->ext);
+}
+
+static void handle_device_enabled(struct cras_iodev *iodev, void *cb_data)
+{
+ if (iodev->direction != CRAS_STREAM_OUTPUT)
+ return;
+
+ /* Register to the first enabled output device. */
+ update_first_output_dev_to_process();
+}
+
+static void handle_device_disabled(struct cras_iodev *iodev, void *cb_data)
+{
+ struct cras_iodev *echo_ref;
+
+ if (iodev->direction != CRAS_STREAM_OUTPUT)
+ return;
+
+ echo_ref = get_echo_reference_target(iodev);
+
+ if (rmodule->odev == echo_ref) {
+ cras_iodev_set_ext_dsp_module(echo_ref, NULL);
+ rmodule->odev = NULL;
+ }
+
+ /* Register to the first enabled output device. */
+ update_first_output_dev_to_process();
+}
+
+static int process_reverse(struct float_buffer *fbuf, unsigned int frame_rate)
+{
+ struct cras_apm_list *list;
+ struct cras_apm *apm;
+ int ret;
+ float *const *wp;
+
+ if (float_buffer_writable(fbuf))
+ return 0;
+
+ wp = float_buffer_write_pointer(fbuf);
+
+ DL_FOREACH(apm_list, list) {
+ if (!(list->effects & APM_ECHO_CANCELLATION))
+ continue;
+
+ DL_FOREACH(list->apms, apm) {
+ ret = webrtc_apm_process_reverse_stream_f(
+ apm->apm_ptr,
+ fbuf->num_channels,
+ frame_rate,
+ wp);
+ if (ret) {
+ syslog(LOG_ERR,
+ "APM process reverse err");
+ return ret;
+ }
+ }
+ }
+ float_buffer_reset(fbuf);
+ return 0;
+}
+
+void reverse_data_run(struct ext_dsp_module *ext,
+ unsigned int nframes)
+{
+ struct cras_apm_reverse_module *rmod =
+ (struct cras_apm_reverse_module *)ext;
+ unsigned int writable;
+ int i, offset = 0;
+ float *const *wp;
+
+ if (!rmod->process_reverse)
+ return;
+
+ while (nframes) {
+ process_reverse(rmod->fbuf, rmod->dev_rate);
+ writable = float_buffer_writable(rmod->fbuf);
+ writable = MIN(nframes, writable);
+ wp = float_buffer_write_pointer(rmod->fbuf);
+ for (i = 0; i < rmod->fbuf->num_channels; i++)
+ memcpy(wp[i], ext->ports[i] + offset,
+ writable * sizeof(float));
+
+ offset += writable;
+ float_buffer_written(rmod->fbuf, writable);
+ nframes -= writable;
+ }
+}
+
+void reverse_data_configure(struct ext_dsp_module *ext,
+ unsigned int buffer_size,
+ unsigned int num_channels,
+ unsigned int rate)
+{
+ struct cras_apm_reverse_module *rmod =
+ (struct cras_apm_reverse_module *)ext;
+ if (rmod->fbuf)
+ float_buffer_destroy(&rmod->fbuf);
+ rmod->fbuf = float_buffer_create(rate / 100,
+ num_channels);
+ rmod->dev_rate = rate;
+}
+
+int cras_apm_list_init(const char *device_config_dir)
+{
+ if (rmodule == NULL) {
+ rmodule = (struct cras_apm_reverse_module *)
+ calloc(1, sizeof(*rmodule));
+ rmodule->ext.run = reverse_data_run;
+ rmodule->ext.configure = reverse_data_configure;
+ }
+
+ aec_config_dir = device_config_dir;
+ if (aec_config)
+ free(aec_config);
+ aec_config = aec_config_get(device_config_dir);
+ if (apm_config)
+ free(apm_config);
+ apm_config = apm_config_get(device_config_dir);
+
+ update_first_output_dev_to_process();
+ cras_iodev_list_set_device_enabled_callback(
+ handle_device_enabled,
+ handle_device_disabled,
+ rmodule);
+
+ return 0;
+}
+
+void cras_apm_list_reload_aec_config()
+{
+ if (NULL == aec_config_dir)
+ return;
+
+ if (aec_config)
+ free(aec_config);
+ aec_config = aec_config_get(aec_config_dir);
+
+ /* Dump the config content at reload only, for debug. */
+ if (aec_config)
+ aec_config_dump(aec_config);
+
+ if (apm_config)
+ free(apm_config);
+ apm_config = apm_config_get(aec_config_dir);
+
+ /* Dump the config content at reload only, for debug. */
+ if (apm_config)
+ apm_config_dump(apm_config);
+}
+
+int cras_apm_list_deinit()
+{
+ if (rmodule) {
+ if (rmodule->fbuf)
+ float_buffer_destroy(&rmodule->fbuf);
+ free(rmodule);
+ }
+ return 0;
+}
+
+int cras_apm_list_process(struct cras_apm *apm,
+ struct float_buffer *input,
+ unsigned int offset)
+{
+ unsigned int writable, nframes, nread;
+ int ch, i, j, ret;
+ float *const *wp;
+ float *const *rp;
+
+ nread = float_buffer_level(input);
+ if (nread < offset) {
+ syslog(LOG_ERR, "Process offset exceeds read level");
+ return -EINVAL;
+ }
+
+ writable = float_buffer_writable(apm->fbuffer);
+ writable = MIN(nread - offset, writable);
+
+ nframes = writable;
+ while (nframes) {
+ nread = nframes;
+ wp = float_buffer_write_pointer(apm->fbuffer);
+ rp = float_buffer_read_pointer(input, offset, &nread);
+
+ for (i = 0; i < apm->fbuffer->num_channels; i++) {
+ /* Look up the channel position and copy from
+ * the correct index of |input| buffer.
+ */
+ for (ch = 0; ch < CRAS_CH_MAX; ch++)
+ if (apm->fmt.channel_layout[ch] == i)
+ break;
+ if (ch == CRAS_CH_MAX)
+ continue;
+
+ j = apm->dev_fmt.channel_layout[ch];
+ if (j == -1)
+ continue;
+
+ memcpy(wp[i], rp[j], nread * sizeof(float));
+ }
+
+ nframes -= nread;
+ offset += nread;
+
+ float_buffer_written(apm->fbuffer, nread);
+ }
+
+ /* process and move to int buffer */
+ if ((float_buffer_writable(apm->fbuffer) == 0) &&
+ (buf_queued(apm->buffer) == 0)) {
+ nread = float_buffer_level(apm->fbuffer);
+ rp = float_buffer_read_pointer(apm->fbuffer, 0, &nread);
+ ret = webrtc_apm_process_stream_f(apm->apm_ptr,
+ apm->fmt.num_channels,
+ apm->fmt.frame_rate,
+ rp);
+ if (ret) {
+ syslog(LOG_ERR, "APM process stream f err");
+ return ret;
+ }
+
+ dsp_util_interleave(rp,
+ buf_write_pointer(apm->buffer),
+ apm->fbuffer->num_channels,
+ apm->fmt.format,
+ nread);
+ buf_increment_write(apm->buffer,
+ nread * cras_get_format_bytes(&apm->fmt));
+ float_buffer_reset(apm->fbuffer);
+ }
+
+ return writable;
+}
+
+struct cras_audio_area *cras_apm_list_get_processed(struct cras_apm *apm)
+{
+ uint8_t *buf_ptr;
+
+ buf_ptr = buf_read_pointer_size(apm->buffer, &apm->area->frames);
+ apm->area->frames /= cras_get_format_bytes(&apm->fmt);
+ cras_audio_area_config_buf_pointers(apm->area, &apm->fmt, buf_ptr);
+ return apm->area;
+}
+
+void cras_apm_list_put_processed(struct cras_apm *apm, unsigned int frames)
+{
+ buf_increment_read(apm->buffer,
+ frames * cras_get_format_bytes(&apm->fmt));
+}
+
+struct cras_audio_format *cras_apm_list_get_format(struct cras_apm *apm)
+{
+ return &apm->fmt;
+}
+
+void cras_apm_list_set_aec_dump(struct cras_apm_list *list, void *dev_ptr,
+ int start, int fd)
+{
+ struct cras_apm *apm;
+ char file_name[256];
+ int rc;
+ FILE *handle;
+
+ DL_SEARCH_SCALAR(list->apms, apm, dev_ptr, dev_ptr);
+ if (apm == NULL)
+ return;
+
+ if (start) {
+ handle = fdopen(fd, "w");
+ if (handle == NULL) {
+ syslog(LOG_ERR, "Create dump handle fail, errno %d",
+ errno);
+ return ;
+ }
+ /* webrtc apm will own the FILE handle and close it. */
+ rc = webrtc_apm_aec_dump(apm->apm_ptr, &apm->work_queue, start,
+ handle);
+ if (rc)
+ syslog(LOG_ERR, "Fail to dump debug file %s, rc %d",
+ file_name, rc);
+ } else {
+ rc = webrtc_apm_aec_dump(apm->apm_ptr, &apm->work_queue, 0,
+ NULL);
+ if (rc)
+ syslog(LOG_ERR, "Failed to stop apm debug, rc %d", rc);
+ }
+}
diff --git a/cras/src/server/cras_apm_list.h b/cras/src/server/cras_apm_list.h
new file mode 100644
index 0000000..c7450d3
--- /dev/null
+++ b/cras/src/server/cras_apm_list.h
@@ -0,0 +1,201 @@
+/* Copyright 2018 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_APM_LIST_H_
+#define CRAS_APM_LIST_H_
+
+#include "cras_types.h"
+
+struct cras_audio_area;
+struct cras_audio_format;
+struct cras_apm;
+struct cras_apm_list;
+struct float_buffer;
+
+#ifdef HAVE_WEBRTC_APM
+
+/* Initialize the apm list for analyzing output data. */
+int cras_apm_list_init(const char *device_config_dir);
+
+/* Reloads the aec config. Used for debug and tuning. */
+void cras_apm_list_reload_aec_config();
+
+/* Deinitialize apm list to free all allocated resources. */
+int cras_apm_list_deinit();
+
+/*
+ * Creates an list to hold all APM instances created when a stream
+ * attaches to an iodev.
+ * Args:
+ * stream_ptr - Pointer to the stream.
+ * effects - Bit map specifying the enabled effects on this stream.
+ */
+struct cras_apm_list *cras_apm_list_create(void *stream_ptr,
+ uint64_t effects);
+
+/*
+ * Creates a cras_apm and adds it to the list.
+ * Args:
+ * list - The list holding APM instances.
+ * dev_ptr - Pointer to the iodev to add new APM for.
+ * fmt - Format of the audio data used for this cras_apm.
+ */
+struct cras_apm *cras_apm_list_add(struct cras_apm_list *list,
+ void *dev_ptr,
+ const struct cras_audio_format *fmt);
+
+/*
+ * Gets the cras_apm instance in the list that associates with given dev.
+ * Args:
+ * list - The list holding APM instances.
+ * dev_ptr - The iodev as key to look up associated APM.
+ */
+struct cras_apm *cras_apm_list_get(struct cras_apm_list *list,
+ void *dev_ptr);
+
+/*
+ * Gets the effects bit map of the APM list.
+ * Args:
+ * list - The list holding APM instances.
+ */
+uint64_t cras_apm_list_get_effects(struct cras_apm_list *list);
+
+/* Removes a cras_apm from list and destroys it. */
+int cras_apm_list_destroy(struct cras_apm_list *list);
+
+/*
+ * Removes an APM from the list, expected to be used when an iodev is no
+ * longer open for the client stream holding the APM list.
+ * Args:
+ * list - The list holding APM instances.
+ * dev_ptr - Device pointer used to look up which apm to remove.
+ */
+void cras_apm_list_remove(struct cras_apm_list *list, void *dev_ptr);
+
+/* Passes audio data from hardware for cras_apm to process.
+ * Args:
+ * apm - The cras_apm instance.
+ * input - Float buffer from device for apm to process.
+ * offset - Offset in |input| to note the data position to start
+ * reading.
+ */
+int cras_apm_list_process(struct cras_apm *apm,
+ struct float_buffer *input,
+ unsigned int offset);
+
+/* Gets the APM processed data in the form of audio area.
+ * Args:
+ * apm - The cras_apm instance that owns the audio area pointer and
+ * processed data.
+ * Returns:
+ * The audio area used to read processed data. No need to free
+ * by caller.
+ */
+struct cras_audio_area *cras_apm_list_get_processed(struct cras_apm *apm);
+
+/* Tells |apm| that |frames| of processed data has been used, so |apm|
+ * can allocate space to read more from input device.
+ * Args:
+ * apm - The cras_apm instance owns the processed data.
+ * frames - The number in frames of processed data to mark as used.
+ */
+void cras_apm_list_put_processed(struct cras_apm *apm, unsigned int frames);
+
+/* Gets the format of the actual data processed by webrtc-apm library.
+ * Args:
+ * apm - The cras_apm instance holding audio data and format info.
+ */
+struct cras_audio_format *cras_apm_list_get_format(struct cras_apm *apm);
+
+/* Sets debug recording to start or stop.
+ * Args:
+ * list - List contains the apm instance to start/stop debug recording.
+ * dev_ptr - Use as key to look up specific apm to do aec dump.
+ * start - True to set debug recording start, otherwise stop.
+ * fd - File descriptor to aec dump destination.
+ */
+void cras_apm_list_set_aec_dump(struct cras_apm_list *list,
+ void *dev_ptr,
+ int start,
+ int fd);
+
+#else
+
+/*
+ * If webrtc audio processing library is not available then define all
+ * cras_apm_list functions as dummy. As long as cras_apm_list_add returns
+ * NULL, non of the other functions should be called.
+ */
+static inline int cras_apm_list_init(const char *device_config_dir)
+{
+ return 0;
+}
+static inline void cras_apm_list_reload_aec_config()
+{
+}
+static inline struct cras_apm_list *cras_apm_list_create(void *stream_ptr,
+ unsigned int effects)
+{
+ return NULL;
+}
+static inline struct cras_apm *cras_apm_list_add(
+ struct cras_apm_list *list,
+ void *dev_ptr,
+ const struct cras_audio_format *fmt)
+{
+ return NULL;
+}
+static inline struct cras_apm *cras_apm_list_get(struct cras_apm_list *list,
+ void *dev_ptr)
+{
+ return NULL;
+}
+static inline uint64_t cras_apm_list_get_effects(struct cras_apm_list *list)
+{
+ return 0;
+}
+static inline int cras_apm_list_destroy(struct cras_apm_list *list)
+{
+ return 0;
+}
+static inline void cras_apm_list_remove(struct cras_apm_list *list,
+ void *dev_ptr)
+{
+}
+
+static inline int cras_apm_list_process(struct cras_apm *apm,
+ struct float_buffer *input,
+ unsigned int offset)
+{
+ return 0;
+}
+
+static inline struct cras_audio_area *cras_apm_list_get_processed(
+ struct cras_apm *apm)
+{
+ return NULL;
+}
+
+static inline void cras_apm_list_put_processed(struct cras_apm *apm,
+ unsigned int frames)
+{
+}
+
+static inline struct cras_audio_format *cras_apm_list_get_format(
+ struct cras_apm *apm)
+{
+ return NULL;
+}
+
+static inline void cras_apm_list_set_aec_dump(struct cras_apm_list *list,
+ void *dev_ptr,
+ int start,
+ int fd)
+{
+}
+
+#endif /* HAVE_WEBRTC_APM */
+
+#endif /* CRAS_APM_LIST_H_ */
diff --git a/cras/src/server/cras_audio_thread_monitor.c b/cras/src/server/cras_audio_thread_monitor.c
new file mode 100644
index 0000000..9a23b00
--- /dev/null
+++ b/cras/src/server/cras_audio_thread_monitor.c
@@ -0,0 +1,128 @@
+/* Copyright 2018 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 <stdbool.h>
+#include <syslog.h>
+#include "audio_thread.h"
+#include "cras_iodev_list.h"
+#include "cras_main_message.h"
+#include "cras_system_state.h"
+#include "cras_types.h"
+#include "cras_util.h"
+
+#define MIN_WAIT_SECOND 30
+
+struct cras_audio_thread_event_message {
+ struct cras_main_message header;
+ enum CRAS_AUDIO_THREAD_EVENT_TYPE event_type;
+};
+
+static void take_snapshot(enum CRAS_AUDIO_THREAD_EVENT_TYPE event_type)
+{
+ struct cras_audio_thread_snapshot *snapshot;
+
+ snapshot = (struct cras_audio_thread_snapshot*)
+ calloc(1, sizeof(struct cras_audio_thread_snapshot));
+ struct timespec now_time;
+ clock_gettime(CLOCK_MONOTONIC_RAW, &now_time);
+ snapshot->timestamp = now_time;
+ snapshot->event_type = event_type;
+ audio_thread_dump_thread_info(cras_iodev_list_get_audio_thread(),
+ &snapshot->audio_debug_info);
+ cras_system_state_add_snapshot(snapshot);
+}
+
+static void cras_audio_thread_event_message_init(
+ struct cras_audio_thread_event_message *msg,
+ enum CRAS_AUDIO_THREAD_EVENT_TYPE event_type)
+{
+ msg->header.type = CRAS_MAIN_AUDIO_THREAD_EVENT;
+ msg->header.length = sizeof(*msg);
+ msg->event_type = event_type;
+}
+
+int cras_audio_thread_event_send(enum CRAS_AUDIO_THREAD_EVENT_TYPE event_type)
+{
+ struct cras_audio_thread_event_message msg;
+ int err;
+ cras_audio_thread_event_message_init(&msg, event_type);
+ err = cras_main_message_send(&msg.header);
+ if (err < 0) {
+ syslog(LOG_ERR, "Failed to send audio thread event message %d",
+ event_type);
+ return err;
+ }
+ return 0;
+}
+
+int cras_audio_thread_debug()
+{
+ return cras_audio_thread_event_send(AUDIO_THREAD_EVENT_DEBUG);
+}
+
+int cras_audio_thread_busyloop()
+{
+ return cras_audio_thread_event_send(AUDIO_THREAD_EVENT_BUSYLOOP);
+}
+
+int cras_audio_thread_underrun()
+{
+ return cras_audio_thread_event_send(AUDIO_THREAD_EVENT_UNDERRUN);
+}
+
+int cras_audio_thread_severe_underrun()
+{
+ return cras_audio_thread_event_send(
+ AUDIO_THREAD_EVENT_SEVERE_UNDERRUN);
+}
+
+static struct timespec last_event_snapshot_time[AUDIO_THREAD_EVENT_TYPE_COUNT];
+
+/*
+ * Callback function for handling audio thread events in main thread,
+ * which takes a snapshot of the audio thread and waits at least 30 seconds
+ * for the same event type. Events with the same event type within 30 seconds
+ * will be ignored by the handle function.
+ */
+static void handle_audio_thread_event_message(
+ struct cras_main_message *msg,
+ void *arg) {
+ struct cras_audio_thread_event_message *audio_thread_msg =
+ (struct cras_audio_thread_event_message *)msg;
+ struct timespec now_time;
+
+ /*
+ * Skip invalid event types
+ */
+ if(audio_thread_msg->event_type >= AUDIO_THREAD_EVENT_TYPE_COUNT)
+ return;
+
+ struct timespec *last_snapshot_time =
+ &last_event_snapshot_time[audio_thread_msg->event_type];
+
+ clock_gettime(CLOCK_REALTIME, &now_time);
+
+ /*
+ * Wait at least 30 seconds for the same event type
+ */
+ struct timespec diff_time;
+ subtract_timespecs(&now_time, last_snapshot_time, &diff_time);
+ if((last_snapshot_time->tv_sec == 0 &&
+ last_snapshot_time->tv_nsec == 0) ||
+ diff_time.tv_sec >= MIN_WAIT_SECOND)
+ {
+ take_snapshot(audio_thread_msg->event_type);
+ *last_snapshot_time = now_time;
+ }
+}
+
+int cras_audio_thread_monitor_init()
+{
+ memset(last_event_snapshot_time,
+ 0, sizeof(struct timespec) * AUDIO_THREAD_EVENT_TYPE_COUNT);
+ cras_main_message_add_handler(CRAS_MAIN_AUDIO_THREAD_EVENT,
+ handle_audio_thread_event_message, NULL);
+ return 0;
+}
diff --git a/cras/src/server/cras_audio_thread_monitor.h b/cras/src/server/cras_audio_thread_monitor.h
new file mode 100644
index 0000000..f2044bf
--- /dev/null
+++ b/cras/src/server/cras_audio_thread_monitor.h
@@ -0,0 +1,34 @@
+/* Copyright 2018 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_AUDIO_THREAD_MONITOR_H_
+#define CRAS_AUDIO_THREAD_MONITOR_H_
+
+/*
+ * Sends a debug event to the audio thread for debugging.
+ */
+int cras_audio_thread_debug();
+
+/*
+ * Notifies the main thread when a busyloop event happens.
+ */
+int cras_audio_thread_busyloop();
+
+/*
+ * Notifies the main thread when a underrun event happens.
+ */
+int cras_audio_thread_underrun();
+
+/*
+ * Notifies the main thread when a severe underrun event happens.
+ */
+int cras_audio_thread_severe_underrun();
+
+/*
+ * Initializes audio thread monitor and sets main thread callback.
+ */
+int cras_audio_thread_monitor_init();
+
+#endif /* CRAS_AUDIO_THREAD_MONITOR_H_ */
diff --git a/cras/src/server/cras_bt_device.c b/cras/src/server/cras_bt_device.c
index 682edba..f993626 100644
--- a/cras/src/server/cras_bt_device.c
+++ b/cras/src/server/cras_bt_device.c
@@ -11,6 +11,7 @@
#include <errno.h>
#include <poll.h>
+#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
@@ -1015,6 +1016,18 @@
iodev = device->bt_iodevs[CRAS_STREAM_OUTPUT];
if (!iodev)
return;
+
+ /*
+ * During the |PROFILE_SWITCH_DELAY_MS| time interval, BT iodev could
+ * have been enabled by others, and its active profile may have changed.
+ * If iodev has been enabled, that means it has already picked up a
+ * reasonable profile to use and audio thread is accessing iodev now.
+ * We should NOT call into update_active_node from main thread
+ * because that may mess up the active node content.
+ */
+ if (cras_iodev_list_dev_is_enabled(iodev))
+ return;
+
iodev->update_active_node(iodev, 0, 1);
cras_iodev_list_enable_dev(iodev);
}
@@ -1053,7 +1066,7 @@
if (!iodev)
continue;
was_enabled[dir] = cras_iodev_list_dev_is_enabled(iodev);
- cras_iodev_list_disable_dev(iodev);
+ cras_iodev_list_disable_dev(iodev, false);
}
for (dir = 0; dir < CRAS_NUM_DIRECTIONS; dir++) {
@@ -1116,6 +1129,16 @@
static void bt_device_process_msg(struct cras_main_message *msg, void *arg)
{
struct bt_device_msg *bt_msg = (struct bt_device_msg *)msg;
+ struct cras_bt_device *device = NULL;
+
+ DL_FOREACH(devices, device) {
+ if (device == bt_msg->device)
+ break;
+ }
+
+ /* Do nothing if target device no longer exists. */
+ if (device == NULL)
+ return;
switch (bt_msg->cmd) {
case BT_DEVICE_SWITCH_PROFILE:
diff --git a/cras/src/server/cras_bt_io.c b/cras/src/server/cras_bt_io.c
index 1363d90..84ac5ca 100644
--- a/cras/src/server/cras_bt_io.c
+++ b/cras/src/server/cras_bt_io.c
@@ -3,6 +3,7 @@
* found in the LICENSE file.
*/
+#include <sys/time.h>
#include <syslog.h>
#include "cras_bt_io.h"
@@ -112,20 +113,17 @@
return cras_bt_device_get_active_profile(device) & profile;
}
-/* Checks if the condition is met to switch to a different profile
- * when trying to set the format to btio before open it. Base on two
- * rules:
+/* Checks if the condition is met to switch to a different profile based
+ * on two rules:
* (1) Prefer to use A2DP for output since the audio quality is better.
* (2) Must use HFP/HSP for input since A2DP doesn't support audio input.
*
* If the profile switch happens, return non-zero error code, otherwise
* return zero.
*/
-static int update_supported_formats(struct cras_iodev *iodev)
+static int open_dev(struct cras_iodev *iodev)
{
struct bt_io *btio = (struct bt_io *)iodev;
- struct cras_iodev *dev = active_profile_dev(iodev);
- int rc, length, i;
/* Force to use HFP if opening input dev. */
if (device_using_profile(btio->device,
@@ -137,6 +135,14 @@
return -EAGAIN;
}
+ return 0;
+}
+
+static int update_supported_formats(struct cras_iodev *iodev)
+{
+ struct cras_iodev *dev = active_profile_dev(iodev);
+ int rc, length, i;
+
if (dev->format == NULL) {
dev->format = (struct cras_audio_format *)
malloc(sizeof(*dev->format));
@@ -173,7 +179,7 @@
return 0;
}
-static int open_dev(struct cras_iodev *iodev)
+static int configure_dev(struct cras_iodev *iodev)
{
int rc;
struct cras_iodev *dev = active_profile_dev(iodev);
@@ -183,7 +189,7 @@
/* Fill back the format iodev is using. */
*dev->format = *iodev->format;
- rc = dev->open_dev(dev);
+ rc = dev->configure_dev(dev);
if (rc) {
/* Free format here to assure the update_supported_format
* callback will be called before any future open_dev call. */
@@ -332,6 +338,7 @@
iodev->info.stable_id_new = dev->info.stable_id_new;
iodev->open_dev = open_dev;
+ iodev->configure_dev = configure_dev;
iodev->frames_queued = frames_queued;
iodev->delay_frames = delay_frames;
iodev->get_buffer = get_buffer;
@@ -340,10 +347,19 @@
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_bt_volume;
iodev->no_stream = cras_iodev_default_no_stream_playback;
+ /* Input also checks |software_volume_needed| flag for using software
+ * gain. Keep it as false for BT input.
+ * TODO(hychao): after wide band speech mode is supported, consider
+ * enable software gain.
+ */
+ if (dev->direction == CRAS_STREAM_OUTPUT) {
+ iodev->software_volume_needed =
+ !cras_bt_device_get_use_hardware_volume(device);
+ iodev->set_volume = set_bt_volume;
+ }
+
/* Create the dummy node set to plugged so it's the only node exposed
* to UI, and point it to the first profile dev. */
active = (struct bt_node *)calloc(1, sizeof(*active));
@@ -394,12 +410,28 @@
return NULL;
}
+void cras_bt_io_free_resources(struct cras_iodev *bt_iodev)
+{
+ struct cras_ionode *node;
+ struct bt_node *n;
+
+ free(bt_iodev->supported_rates);
+ free(bt_iodev->supported_channel_counts);
+ free(bt_iodev->supported_formats);
+
+ DL_FOREACH(bt_iodev->nodes, node) {
+ n = (struct bt_node *)node;
+ cras_iodev_rm_node(bt_iodev, node);
+ free(n);
+ }
+
+ cras_iodev_free_resources(bt_iodev);
+}
+
void cras_bt_io_destroy(struct cras_iodev *bt_iodev)
{
int rc;
struct bt_io *btio = (struct bt_io *)bt_iodev;
- struct cras_ionode *node;
- struct bt_node *n;
if (bt_iodev->direction == CRAS_STREAM_OUTPUT)
rc = cras_iodev_list_rm_output(bt_iodev);
@@ -408,11 +440,7 @@
if (rc == -EBUSY)
return;
- DL_FOREACH(bt_iodev->nodes, node) {
- n = (struct bt_node *)node;
- cras_iodev_rm_node(bt_iodev, node);
- free(n);
- }
+ cras_bt_io_free_resources(bt_iodev);
free(btio);
}
diff --git a/cras/src/server/cras_dbus_control.c b/cras/src/server/cras_dbus_control.c
index 2100224..56effce 100644
--- a/cras/src/server/cras_dbus_control.c
+++ b/cras/src/server/cras_dbus_control.c
@@ -63,9 +63,15 @@
" <arg name=\"input_mute\" type=\"b\" direction=\"out\"/>\n" \
" <arg name=\"output_user_mute\" type=\"b\" direction=\"out\"/>\n"\
" </method>\n" \
+ " <method name=\"GetDefaultOutputBufferSize\">\n" \
+ " <arg name=\"buffer_size\" type=\"i\" direction=\"out\"/>\n" \
+ " </method>\n" \
" <method name=\"GetNodes\">\n" \
" <arg name=\"nodes\" type=\"a{sv}\" direction=\"out\"/>\n" \
" </method>\n" \
+ " <method name=\"GetSystemAecSupported\">\n" \
+ " <arg name=\"supported\" type=\"b\" direction=\"out\"/>\n" \
+ " </method>\n" \
" <method name=\"SetActiveOutputNode\">\n" \
" <arg name=\"node_id\" type=\"t\" direction=\"in\"/>\n" \
" </method>\n" \
@@ -101,6 +107,9 @@
" <arg name=\"node_id\" type=\"t\" direction=\"in\"/>\n" \
" <arg name=\"model_name\" type=\"s\" direction=\"in\"/>\n" \
" </method>\n" \
+ " <method name=\"IsAudioOutputActive\">\n" \
+ " <arg name=\"active\" type=\"b\" direction=\"out\"/>\n" \
+ " </method>\n" \
" </interface>\n" \
" <interface name=\"" DBUS_INTERFACE_INTROSPECTABLE "\">\n" \
" <method name=\"Introspect\">\n" \
@@ -408,6 +417,29 @@
return DBUS_HANDLER_RESULT_HANDLED;
}
+static DBusHandlerResult handle_get_default_output_buffer_size(
+ DBusConnection *conn,
+ DBusMessage *message,
+ void *arg)
+{
+ DBusMessage *reply;
+ dbus_uint32_t serial = 0;
+ dbus_int32_t buffer_size;
+
+ reply = dbus_message_new_method_return(message);
+
+ buffer_size = cras_system_get_default_output_buffer_size();
+ dbus_message_append_args(reply,
+ DBUS_TYPE_INT32, &buffer_size,
+ DBUS_TYPE_INVALID);
+
+ dbus_connection_send(conn, reply, &serial);
+
+ dbus_message_unref(reply);
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+}
+
/* Appends the information about a node to the dbus message. Returns
* false if not enough memory. */
static dbus_bool_t append_node_dict(DBusMessageIter *iter,
@@ -547,6 +579,29 @@
return DBUS_HANDLER_RESULT_HANDLED;
}
+static DBusHandlerResult handle_get_system_aec_supported(
+ DBusConnection *conn,
+ DBusMessage *message,
+ void *arg)
+{
+ DBusMessage *reply;
+ dbus_uint32_t serial = 0;
+ dbus_bool_t system_aec_supported;
+
+ reply = dbus_message_new_method_return(message);
+
+ system_aec_supported = cras_system_get_aec_supported();
+ dbus_message_append_args(reply,
+ DBUS_TYPE_BOOLEAN, &system_aec_supported,
+ DBUS_TYPE_INVALID);
+
+ dbus_connection_send(conn, reply, &serial);
+
+ dbus_message_unref(reply);
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+}
+
static DBusHandlerResult
handle_set_active_node(DBusConnection *conn,
DBusMessage *message,
@@ -721,6 +776,18 @@
return DBUS_HANDLER_RESULT_HANDLED;
}
+static DBusHandlerResult handle_is_audio_active(
+ DBusConnection *conn,
+ DBusMessage *message,
+ void* arg)
+{
+ dbus_int32_t active = cras_system_state_get_non_empty_status();
+
+ send_int32_reply(conn, message, active);
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+}
+
/* Handle incoming messages. */
static DBusHandlerResult handle_control_message(DBusConnection *conn,
DBusMessage *message,
@@ -792,10 +859,18 @@
return handle_get_volume_state(conn, message, arg);
} else if (dbus_message_is_method_call(message,
CRAS_CONTROL_INTERFACE,
+ "GetDefaultOutputBufferSize")) {
+ return handle_get_default_output_buffer_size(conn, message, arg);
+ } else if (dbus_message_is_method_call(message,
+ CRAS_CONTROL_INTERFACE,
"GetNodes")) {
return handle_get_nodes(conn, message, arg);
} else if (dbus_message_is_method_call(message,
CRAS_CONTROL_INTERFACE,
+ "GetSystemAecSupported")) {
+ return handle_get_system_aec_supported(conn, message, arg);
+ } else if (dbus_message_is_method_call(message,
+ CRAS_CONTROL_INTERFACE,
"SetActiveOutputNode")) {
return handle_set_active_node(conn, message, arg,
CRAS_STREAM_OUTPUT);
@@ -847,9 +922,12 @@
CRAS_CONTROL_INTERFACE,
"SetHotwordModel")) {
return handle_set_hotword_model(conn, message, arg);
+ } else if (dbus_message_is_method_call(message,
+ CRAS_CONTROL_INTERFACE,
+ "IsAudioOutputActive")) {
+ return handle_is_audio_active(conn, message, arg);
}
-
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
@@ -1057,6 +1135,45 @@
dbus_message_unref(msg);
}
+static void signal_hotword_triggered(void *context,
+ int64_t tv_sec,
+ int64_t tv_nsec)
+{
+ struct cras_dbus_control *control = (struct cras_dbus_control *)context;
+ dbus_uint32_t serial = 0;
+ DBusMessage *msg;
+
+ msg = create_dbus_message("HotwordTriggered");
+ if (!msg)
+ return;
+
+ dbus_message_append_args(msg,
+ DBUS_TYPE_INT64, &tv_sec,
+ DBUS_TYPE_INT64, &tv_nsec,
+ DBUS_TYPE_INVALID);
+ dbus_connection_send(control->conn, msg, &serial);
+ dbus_message_unref(msg);
+}
+
+static void signal_non_empty_audio_state_changed(void *context, int non_empty)
+{
+ struct cras_dbus_control *control = (struct cras_dbus_control *)context;
+
+ dbus_uint32_t serial = 0;
+ DBusMessage *msg;
+
+ msg = create_dbus_message("AudioOutputActiveStateChanged");
+ if (!msg)
+ return;
+
+ dbus_message_append_args(msg,
+ DBUS_TYPE_BOOLEAN, &non_empty,
+ DBUS_TYPE_INVALID);
+
+ dbus_connection_send(control->conn, msg, &serial);
+ dbus_message_unref(msg);
+}
+
/* Exported Interface */
void cras_dbus_control_start(DBusConnection *conn)
@@ -1095,6 +1212,9 @@
observer_ops.output_node_volume_changed = signal_node_volume_changed;
observer_ops.node_left_right_swapped_changed =
signal_node_left_right_swapped_changed;
+ observer_ops.hotword_triggered = signal_hotword_triggered;
+ observer_ops.non_empty_audio_state_changed =
+ signal_non_empty_audio_state_changed;
dbus_control.observer = cras_observer_add(&observer_ops, &dbus_control);
}
diff --git a/cras/src/server/cras_device_monitor.c b/cras/src/server/cras_device_monitor.c
index be9d086..7dd8f27 100644
--- a/cras/src/server/cras_device_monitor.c
+++ b/cras/src/server/cras_device_monitor.c
@@ -3,6 +3,7 @@
* found in the LICENSE file.
*/
+#include <stdbool.h>
#include <syslog.h>
#include "cras_device_monitor.h"
@@ -80,7 +81,7 @@
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_disable_dev(iodev, true);
cras_iodev_list_enable_dev(iodev);
break;
case SET_MUTE_STATE:
diff --git a/cras/src/server/cras_dsp.c b/cras/src/server/cras_dsp.c
index 8052a04..97e3a85 100644
--- a/cras/src/server/cras_dsp.c
+++ b/cras/src/server/cras_dsp.c
@@ -48,15 +48,14 @@
cras_expr_env_set_variable_boolean(env, "swap_lr_disabled", 1);
}
-static struct pipeline *prepare_pipeline(struct cras_dsp_context *ctx)
+static struct pipeline *prepare_pipeline(
+ struct cras_dsp_context *ctx,
+ struct ini *target_ini)
{
struct pipeline *pipeline;
const char *purpose = ctx->purpose;
- if (!ini)
- return NULL;
-
- pipeline = cras_dsp_pipeline_create(ini, &ctx->env, purpose);
+ pipeline = cras_dsp_pipeline_create(target_ini, &ctx->env, purpose);
if (pipeline) {
syslog(LOG_DEBUG, "pipeline created");
@@ -90,11 +89,12 @@
return NULL;
}
-static void cmd_load_pipeline(struct cras_dsp_context *ctx)
+static void cmd_load_pipeline(struct cras_dsp_context *ctx,
+ struct ini *target_ini)
{
struct pipeline *pipeline, *old_pipeline;
- pipeline = prepare_pipeline(ctx);
+ pipeline = target_ini ? prepare_pipeline(ctx, target_ini) : NULL;
/* This locking is short to avoild blocking audio thread. */
pthread_mutex_lock(&ctx->mutex);
@@ -112,11 +112,14 @@
struct cras_dsp_context *ctx;
ini = cras_dsp_ini_create(ini_filename);
- if (!ini)
+ if (!ini) {
syslog(LOG_ERR, "cannot create dsp ini");
+ return;
+ }
+
DL_FOREACH(context_list, ctx) {
- cmd_load_pipeline(ctx);
+ cmd_load_pipeline(ctx, ini);
}
if (old_ini)
@@ -186,7 +189,16 @@
void cras_dsp_load_pipeline(struct cras_dsp_context *ctx)
{
- cmd_load_pipeline(ctx);
+ cmd_load_pipeline(ctx, ini);
+}
+
+void cras_dsp_load_dummy_pipeline(struct cras_dsp_context *ctx,
+ unsigned int num_channels)
+{
+ struct ini *dummy_ini;
+ dummy_ini = create_dummy_ini(ctx->purpose, num_channels);
+ cmd_load_pipeline(ctx, dummy_ini);
+ cras_dsp_ini_free((dummy_ini));
}
struct pipeline *cras_dsp_get_pipeline(struct cras_dsp_context *ctx)
diff --git a/cras/src/server/cras_dsp.h b/cras/src/server/cras_dsp.h
index 18c45f7..6c7af18 100644
--- a/cras/src/server/cras_dsp.h
+++ b/cras/src/server/cras_dsp.h
@@ -54,6 +54,12 @@
* blocking the audio thread. */
void cras_dsp_load_pipeline(struct cras_dsp_context *ctx);
+/* Loads a dummy pipeline of source directly connects to sink, of given
+ * number of channels.
+ */
+void cras_dsp_load_dummy_pipeline(struct cras_dsp_context *ctx,
+ unsigned int num_channels);
+
/* Locks the pipeline in the context for access. Returns NULL if the
* pipeline is still being loaded or cannot be loaded. */
struct pipeline *cras_dsp_get_pipeline(struct cras_dsp_context *ctx);
diff --git a/cras/src/server/cras_dsp_ini.c b/cras/src/server/cras_dsp_ini.c
index a4014c8..a8b3a9c 100644
--- a/cras/src/server/cras_dsp_ini.c
+++ b/cras/src/server/cras_dsp_ini.c
@@ -7,10 +7,12 @@
#include <stdlib.h>
#include <syslog.h>
#include "cras_dsp_ini.h"
+#include "iniparser_wrapper.h"
#define MAX_INI_KEY_LENGTH 64 /* names like "output_source:output_0" */
#define MAX_NR_PORT 128 /* the max number of ports for a plugin */
#define MAX_PORT_NAME_LENGTH 20 /* names like "output_32" */
+#define MAX_DUMMY_INI_CH 8 /* Max number of channels to create dummy ini */
/* Format of the ini file (See dsp.ini.sample for an example).
@@ -312,6 +314,55 @@
return 0;
}
+struct ini *create_dummy_ini(const char *purpose, unsigned int num_channels)
+{
+ static char dummy_flow_names[MAX_DUMMY_INI_CH][8] = {
+ "{tmp:0}", "{tmp:1}", "{tmp:2}", "{tmp:3}",
+ "{tmp:4}", "{tmp:5}", "{tmp:6}", "{tmp:7}",
+ };
+ struct ini *ini;
+ struct plugin *source, *sink;
+ int tmp_flow_ids[MAX_DUMMY_INI_CH];
+ int i;
+
+ if (num_channels > MAX_DUMMY_INI_CH) {
+ syslog(LOG_ERR, "Unable to create %u channels of dummy ini",
+ num_channels);
+ return NULL;
+ }
+
+ ini = calloc(1, sizeof(struct ini));
+ if (!ini) {
+ syslog(LOG_ERR, "no memory for ini struct");
+ return NULL;
+ }
+
+ for (i = 0; i < num_channels; i++)
+ tmp_flow_ids[i] = add_new_flow(ini, dummy_flow_names[i]);
+
+ source = ARRAY_APPEND_ZERO(&ini->plugins);
+ source->title = "source";
+ source->library = "builtin";
+ source->label = "source";
+ source->purpose = purpose;
+
+ for (i = 0; i < num_channels; i++)
+ add_audio_port(ini, source, tmp_flow_ids[i], PORT_OUTPUT);
+
+ sink = ARRAY_APPEND_ZERO(&ini->plugins);
+ sink->title = "sink";
+ sink->library = "builtin";
+ sink->label = "sink";
+ sink->purpose = purpose;
+
+ for (i = 0; i < num_channels; i++)
+ add_audio_port(ini, sink, tmp_flow_ids[i], PORT_INPUT);
+
+ fill_flow_info(ini);
+
+ return ini;
+}
+
struct ini *cras_dsp_ini_create(const char *ini_filename)
{
struct ini *ini;
@@ -327,7 +378,7 @@
return NULL;
}
- dict = iniparser_load((char *)ini_filename);
+ dict = iniparser_load_wrapper((char *)ini_filename);
if (!dict) {
syslog(LOG_ERR, "no ini file %s", ini_filename);
goto bail;
diff --git a/cras/src/server/cras_dsp_ini.h b/cras/src/server/cras_dsp_ini.h
index d90a87c..0fdd69f 100644
--- a/cras/src/server/cras_dsp_ini.h
+++ b/cras/src/server/cras_dsp_ini.h
@@ -70,6 +70,24 @@
flow_array flows;
};
+/*
+ * Creates a dummy ini structure equivalent to:
+ *
+ * [src]
+ * out0={tmp:0}
+ * out1={tmp:1}
+ * ...
+ *
+ * [sink]
+ * in0={tmp:0}
+ * in1={tmp:1}
+ * ...
+ *
+ * The caller of this function is responsible to free the returned
+ * ini by calling cras_dsp_ini_free().
+ */
+struct ini *create_dummy_ini(const char *purpose, unsigned int num_channels);
+
/* Reads the ini file into the ini structure */
struct ini *cras_dsp_ini_create(const char *ini_filename);
/* Frees the dsp structure. */
diff --git a/cras/src/server/cras_dsp_mod_builtin.c b/cras/src/server/cras_dsp_mod_builtin.c
index c694879..25106de 100644
--- a/cras/src/server/cras_dsp_mod_builtin.c
+++ b/cras/src/server/cras_dsp_mod_builtin.c
@@ -7,6 +7,7 @@
#include "cras_dsp_module.h"
#include "drc.h"
#include "dsp_util.h"
+#include "dcblock.h"
#include "eq.h"
#include "eq2.h"
@@ -201,6 +202,71 @@
}
/*
+ * dcblock module functions
+ */
+struct dcblock_data {
+ struct dcblock *dcblockl;
+ struct dcblock *dcblockr;
+
+ /* One port for input, one for output, and 1 parameter */
+ float *ports[5];
+};
+
+static int dcblock_instantiate(struct dsp_module *module,
+ unsigned long sample_rate)
+{
+ module->data = calloc(1, sizeof(struct dcblock_data));
+ return 0;
+}
+
+static void dcblock_connect_port(struct dsp_module *module,
+ unsigned long port, float *data_location)
+{
+ struct dcblock_data *data = (struct dcblock_data *) module->data;
+ data->ports[port] = data_location;
+}
+
+static void dcblock_run(struct dsp_module *module, unsigned long sample_count)
+{
+ struct dcblock_data *data = (struct dcblock_data *) module->data;
+ if (!data->dcblockl)
+ data->dcblockl = dcblock_new(*data->ports[4]);
+ if (!data->dcblockr)
+ data->dcblockr = dcblock_new(*data->ports[4]);
+ if (data->ports[0] != data->ports[2])
+ memcpy(data->ports[2], data->ports[0],
+ sizeof(float) * sample_count);
+ if (data->ports[1] != data->ports[3])
+ memcpy(data->ports[3], data->ports[1],
+ sizeof(float) * sample_count);
+
+ dcblock_process(data->dcblockl, data->ports[2], (int) sample_count);
+ dcblock_process(data->dcblockr, data->ports[3], (int) sample_count);
+}
+
+static void dcblock_deinstantiate(struct dsp_module *module)
+{
+ struct dcblock_data *data = (struct dcblock_data *) module->data;
+ if (data->dcblockl)
+ dcblock_free(data->dcblockl);
+ if (data->dcblockr)
+ dcblock_free(data->dcblockr);
+ free(data);
+}
+
+static void dcblock_init_module(struct dsp_module *module)
+{
+ module->instantiate = &dcblock_instantiate;
+ module->connect_port = &dcblock_connect_port;
+ module->get_delay = &empty_get_delay;
+ module->run = &dcblock_run;
+ module->deinstantiate = &dcblock_deinstantiate;
+ module->free_module = &empty_free_module;
+ module->get_properties = &empty_get_properties;
+ module->dump = &empty_dump;
+}
+
+/*
* eq module functions
*/
struct eq_data {
@@ -454,6 +520,66 @@
}
/*
+ * sink module functions
+ */
+struct sink_data {
+ struct ext_dsp_module *ext_module;
+ float *ports[MAX_EXT_DSP_PORTS];
+};
+
+static int sink_instantiate(struct dsp_module *module,
+ unsigned long sample_rate)
+{
+ module->data = (struct sink_data *)calloc(1, sizeof(struct sink_data));
+ return 0;
+}
+
+static void sink_deinstantiate(struct dsp_module *module)
+{
+ struct sink_data *data = (struct sink_data *)module->data;
+ free(data);
+}
+
+static void sink_connect_port(struct dsp_module *module, unsigned long port,
+ float *data_location)
+{
+ struct sink_data *data = (struct sink_data *)module->data;
+ data->ports[port] = data_location;
+}
+
+static void sink_run(struct dsp_module *module, unsigned long sample_count)
+{
+ struct sink_data *data = (struct sink_data *)module->data;
+
+ if (!data->ext_module)
+ return;
+ data->ext_module->run(data->ext_module, sample_count);
+}
+
+static void sink_init_module(struct dsp_module *module)
+{
+ module->instantiate = &sink_instantiate;
+ module->connect_port = &sink_connect_port;
+ module->get_delay = &empty_get_delay;
+ module->run = &sink_run;
+ module->deinstantiate = &sink_deinstantiate;
+ module->free_module = &empty_free_module;
+ module->get_properties = &empty_get_properties;
+ module->dump = &empty_dump;
+}
+
+void cras_dsp_module_set_sink_ext_module(struct dsp_module *module,
+ struct ext_dsp_module *ext_module)
+{
+ struct sink_data *data = (struct sink_data *)module->data;
+ int i;
+ data->ext_module = ext_module;
+
+ for (i = 0; i < MAX_EXT_DSP_PORTS; i++)
+ ext_module->ports[i] = data->ports[i];
+}
+
+/*
* builtin module dispatcher
*/
struct dsp_module *cras_dsp_module_load_builtin(struct plugin *plugin)
@@ -468,6 +594,8 @@
mix_stereo_init_module(module);
} else if (strcmp(plugin->label, "invert_lr") == 0) {
invert_lr_init_module(module);
+ } else if (strcmp(plugin->label, "dcblock") == 0) {
+ dcblock_init_module(module);
} else if (strcmp(plugin->label, "eq") == 0) {
eq_init_module(module);
} else if (strcmp(plugin->label, "eq2") == 0) {
@@ -476,6 +604,8 @@
drc_init_module(module);
} else if (strcmp(plugin->label, "swap_lr") == 0) {
swap_lr_init_module(module);
+ } else if (strcmp(plugin->label, "sink") == 0) {
+ sink_init_module(module);
} else {
empty_init_module(module);
}
diff --git a/cras/src/server/cras_dsp_module.h b/cras/src/server/cras_dsp_module.h
index 59e6fbe..728418c 100644
--- a/cras/src/server/cras_dsp_module.h
+++ b/cras/src/server/cras_dsp_module.h
@@ -12,6 +12,8 @@
#include "cras_dsp_ini.h"
+#define MAX_EXT_DSP_PORTS 8
+
/* Holds the functions we can use on a dsp module. */
struct dsp_module {
/* Opaque data used by the implementation of this module */
@@ -70,10 +72,49 @@
void (*dump)(struct dsp_module *mod, struct dumper *d);
};
+
+/* An external module interface working with existing dsp pipeline.
+ * __________ ___________ ____________ __________
+ * | | | | | | | |
+ * | |->| dsp mod |-> ...->| dsp mod | ---> | |
+ * | device | |_________| |__________| | stream |
+ * | | | ___________ | |
+ * | | | | ext | | |
+ * | | ->| dsp mod | -> | |
+ * |________| |_________| |________|
+ *
+ * According to above diagram, an ext_dsp_module works by appending to
+ * the sink of existing dsp pipeline. For audio input, this creates a
+ * multiple output pipeline that stream can read processed buffer from.
+ * This is useful for a stream to apply special processing effects while
+ * sharing the common dsp with the other streams.
+ *
+ * Members:
+ * ports - A list of ports can connect to existing dsp ports in a pipeline.
+ * run - Processes |nframes| of data.
+ * configure - Configures given external dsp module by the device buffer
+ * size, rate, and number of channels of the format of the device that
+ * the associated pipeline runs for.
+ */
+struct ext_dsp_module {
+ float *ports[MAX_EXT_DSP_PORTS];
+ void (*run)(struct ext_dsp_module *ext,
+ unsigned int nframes);
+ void (*configure)(struct ext_dsp_module *ext,
+ unsigned int buffer_size,
+ unsigned int num_channels,
+ unsigned int rate);
+};
+
+
enum {
MODULE_INPLACE_BROKEN = 1 /* See ladspa.h for explanation */
};
+/* Connects an external dsp module to a builtin sink module. */
+void cras_dsp_module_set_sink_ext_module(struct dsp_module *module,
+ struct ext_dsp_module *ext_module);
+
struct dsp_module *cras_dsp_module_load_ladspa(struct plugin *plugin);
struct dsp_module *cras_dsp_module_load_builtin(struct plugin *plugin);
diff --git a/cras/src/server/cras_dsp_pipeline.c b/cras/src/server/cras_dsp_pipeline.c
index 332ed1c..699c86b 100644
--- a/cras/src/server/cras_dsp_pipeline.c
+++ b/cras/src/server/cras_dsp_pipeline.c
@@ -780,6 +780,14 @@
index);
}
+void cras_dsp_pipeline_set_sink_ext_module(struct pipeline *pipeline,
+ struct ext_dsp_module *ext_module)
+{
+ cras_dsp_module_set_sink_ext_module(
+ pipeline->sink_instance->module,
+ ext_module);
+}
+
void cras_dsp_pipeline_run(struct pipeline *pipeline, int sample_count)
{
int i;
@@ -814,26 +822,24 @@
pipeline->total_time += t;
}
-void cras_dsp_pipeline_apply(struct pipeline *pipeline,
- uint8_t *buf, unsigned int frames)
+int cras_dsp_pipeline_apply(struct pipeline *pipeline, uint8_t *buf,
+ snd_pcm_format_t format, unsigned int frames)
{
size_t remaining;
size_t chunk;
size_t i;
- int16_t *target;
unsigned int input_channels = pipeline->input_channels;
unsigned int output_channels = pipeline->output_channels;
float *source[input_channels];
float *sink[output_channels];
struct timespec begin, end, delta;
+ int rc;
if (!pipeline || frames == 0)
- return;
+ return 0;
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &begin);
- target = (int16_t *)buf;
-
/* get pointers to source and sink buffers */
for (i = 0; i < input_channels; i++)
source[i] = cras_dsp_pipeline_get_source_buffer(pipeline, i);
@@ -847,21 +853,28 @@
chunk = MIN(remaining, (size_t)DSP_BUFFER_SIZE);
/* deinterleave and convert to float */
- dsp_util_deinterleave(target, source, input_channels, chunk);
+ rc = dsp_util_deinterleave(buf, source, input_channels,
+ format, chunk);
+ if (rc)
+ return rc;
/* Run the pipeline */
cras_dsp_pipeline_run(pipeline, chunk);
/* interleave and convert back to int16_t */
- dsp_util_interleave(sink, target, output_channels, chunk);
+ rc = dsp_util_interleave(sink, buf, output_channels,
+ format, chunk);
+ if (rc)
+ return rc;
- target += chunk * output_channels;
+ buf += chunk * output_channels * PCM_FORMAT_WIDTH(format) / 8;
remaining -= chunk;
}
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
subtract_timespecs(&end, &begin, &delta);
cras_dsp_pipeline_add_statistic(pipeline, &delta, frames);
+ return 0;
}
void cras_dsp_pipeline_free(struct pipeline *pipeline)
diff --git a/cras/src/server/cras_dsp_pipeline.h b/cras/src/server/cras_dsp_pipeline.h
index 4803c40..c299f3a 100644
--- a/cras/src/server/cras_dsp_pipeline.h
+++ b/cras/src/server/cras_dsp_pipeline.h
@@ -13,7 +13,9 @@
#include <stdint.h>
#include "dumper.h"
+#include "cras_audio_format.h"
#include "cras_dsp_ini.h"
+#include "cras_dsp_module.h"
/* These are the functions to create and use dsp pipelines. A dsp
* pipeline is a collection of dsp plugins that process audio
@@ -100,6 +102,15 @@
*/
float *cras_dsp_pipeline_get_sink_buffer(struct pipeline *pipeline, int index);
+/*
+ * Connects |ext_module| to the sink of given dsp pipeline.
+ * Args:
+ * pipeline - The pipeline whose sink should connect to ext_module.
+ * ext_module - The external dsp module to connect to pipeline sink.
+ */
+void cras_dsp_pipeline_set_sink_ext_module(struct pipeline *pipeline,
+ struct ext_dsp_module *ext_module);
+
/* Returns the number of internal audio buffers allocated by the
* pipeline. This is used by the unit test only */
int cras_dsp_pipeline_get_peak_audio_buffers(struct pipeline *pipeline);
@@ -127,10 +138,13 @@
* Args:
* pipeline - The pipeline to run.
* buf - The samples to be processed, interleaved.
+ * format - Sample format of the buffer.
* frames - the numver of samples in the buffer.
+ * Returns:
+ * Negative code if error, otherwise 0.
*/
-void cras_dsp_pipeline_apply(struct pipeline *pipeline,
- uint8_t *buf, unsigned int frames);
+int cras_dsp_pipeline_apply(struct pipeline *pipeline, uint8_t *buf,
+ snd_pcm_format_t format, unsigned int frames);
/* Dumps the current state of the pipeline. For debugging only */
void cras_dsp_pipeline_dump(struct dumper *d, struct pipeline *pipeline);
diff --git a/cras/src/server/cras_empty_iodev.c b/cras/src/server/cras_empty_iodev.c
index 4616cd3..09defa0 100644
--- a/cras/src/server/cras_empty_iodev.c
+++ b/cras/src/server/cras_empty_iodev.c
@@ -52,6 +52,9 @@
struct empty_iodev *empty_iodev = (struct empty_iodev *)iodev;
unsigned int frames, frames_since_last;
+ if (iodev->active_node->type == CRAS_NODE_TYPE_HOTWORD)
+ return 0;
+
frames = empty_iodev->buffer_level;
frames_since_last = cras_frames_since_time(
&empty_iodev->last_buffer_access,
@@ -93,7 +96,7 @@
return 0;
}
-static int open_dev(struct cras_iodev *iodev)
+static int configure_dev(struct cras_iodev *iodev)
{
struct empty_iodev *empty_iodev = (struct empty_iodev *)iodev;
@@ -176,7 +179,8 @@
* Exported Interface.
*/
-struct cras_iodev *empty_iodev_create(enum CRAS_STREAM_DIRECTION direction)
+struct cras_iodev *empty_iodev_create(enum CRAS_STREAM_DIRECTION direction,
+ enum CRAS_NODE_TYPE node_type)
{
struct empty_iodev *empty_iodev;
struct cras_iodev *iodev;
@@ -196,7 +200,7 @@
iodev->supported_formats = empty_supported_formats;
iodev->buffer_size = EMPTY_FRAMES;
- iodev->open_dev = open_dev;
+ iodev->configure_dev = configure_dev;
iodev->close_dev = close_dev;
iodev->frames_queued = frames_queued;
iodev->delay_frames = delay_frames;
@@ -209,7 +213,7 @@
/* Create a dummy ionode */
node = (struct cras_ionode *)calloc(1, sizeof(*node));
node->dev = iodev;
- node->type = CRAS_NODE_TYPE_UNKNOWN;
+ node->type = node_type;
node->volume = 100;
strcpy(node->name, "(default)");
cras_iodev_add_node(iodev, node);
@@ -217,11 +221,21 @@
/* Finally add it to the appropriate iodev list. */
if (direction == CRAS_STREAM_INPUT) {
- snprintf(iodev->info.name,
- ARRAY_SIZE(iodev->info.name),
- "Silent record device.");
- iodev->info.name[ARRAY_SIZE(iodev->info.name) - 1] = '\0';
- iodev->info.idx = SILENT_RECORD_DEVICE;
+ if (node->type == CRAS_NODE_TYPE_HOTWORD) {
+ snprintf(iodev->info.name,
+ ARRAY_SIZE(iodev->info.name),
+ "Silent hotword device.");
+ iodev->info.name[ARRAY_SIZE(iodev->info.name) - 1] =
+ '\0';
+ iodev->info.idx = SILENT_HOTWORD_DEVICE;
+ } else {
+ snprintf(iodev->info.name,
+ ARRAY_SIZE(iodev->info.name),
+ "Silent record device.");
+ iodev->info.name[ARRAY_SIZE(iodev->info.name) - 1] =
+ '\0';
+ iodev->info.idx = SILENT_RECORD_DEVICE;
+ }
} else {
snprintf(iodev->info.name,
ARRAY_SIZE(iodev->info.name),
diff --git a/cras/src/server/cras_empty_iodev.h b/cras/src/server/cras_empty_iodev.h
index 0d28a5d..d85b9e0 100644
--- a/cras/src/server/cras_empty_iodev.h
+++ b/cras/src/server/cras_empty_iodev.h
@@ -15,10 +15,12 @@
* until a new iodev becomes available.
* Args:
* direciton - input or output.
+ * node_type - the default node type.
* Returns:
* A pointer to the newly created iodev if successful, NULL otherwise.
*/
-struct cras_iodev *empty_iodev_create(enum CRAS_STREAM_DIRECTION direction);
+struct cras_iodev *empty_iodev_create(enum CRAS_STREAM_DIRECTION direction,
+ enum CRAS_NODE_TYPE node_type);
/* Destroys an empty_iodev created with empty_iodev_create. */
void empty_iodev_destroy(struct cras_iodev *iodev);
diff --git a/cras/src/server/cras_fmt_conv.c b/cras/src/server/cras_fmt_conv.c
index 729b347..5b4bbeb 100644
--- a/cras/src/server/cras_fmt_conv.c
+++ b/cras/src/server/cras_fmt_conv.c
@@ -69,7 +69,7 @@
uint16_t *_out = (uint16_t *)out;
for (i = 0; i < in_samples; i++, in++, _out++)
- *_out = ((int16_t)*in - 0x80) << 8;
+ *_out = (uint16_t)((int16_t)*in - 0x80) << 8;
}
/* Converts from S24 to S16. */
@@ -131,7 +131,7 @@
uint32_t *_out = (uint32_t *)out;
for (i = 0; i < in_samples; i++, _in++, _out++)
- *_out = ((int32_t)*_in << 8);
+ *_out = ((uint32_t)(int32_t)*_in << 8);
}
/* Converts from S16 to S32. */
@@ -143,7 +143,7 @@
uint32_t *_out = (uint32_t *)out;
for (i = 0; i < in_samples; i++, _in++, _out++)
- *_out = ((int32_t)*_in << 16);
+ *_out = ((uint32_t)(int32_t)*_in << 16);
}
/* Converts from S16 to S24_3LE. */
@@ -289,6 +289,79 @@
return in_frames;
}
+/* Converts S16 stereo to quad (front L/R, rear L/R). Fit left/right of input
+ * to the front left/right of output respectively and fill others
+ * with zero.
+ */
+static size_t s16_stereo_to_quad(struct cras_fmt_conv *conv,
+ const int16_t *in, size_t in_frames,
+ int16_t *out)
+{
+ size_t i, front_left, front_right, rear_left, rear_right;
+
+ front_left = conv->out_fmt.channel_layout[CRAS_CH_FL];
+ front_right = conv->out_fmt.channel_layout[CRAS_CH_FR];
+ rear_left = conv->out_fmt.channel_layout[CRAS_CH_RL];
+ rear_right = conv->out_fmt.channel_layout[CRAS_CH_RR];
+
+ if (front_left != -1 && front_right != -1 &&
+ rear_left != -1 && rear_right != -1)
+ for (i = 0; i < in_frames; i++) {
+ out[4 * i + front_left] = in[2 * i];
+ out[4 * i + front_right] = in[2 * i + 1];
+ out[4 * i + rear_left] = in[2 * i];
+ out[4 * i + rear_right] = in[2 * i + 1];
+ }
+ else
+ /* Select the first four channels to convert to as the
+ * default behavior.
+ */
+ for (i = 0; i < in_frames; i++) {
+ out[4 * i] = in[2 * i];
+ out[4 * i + 1] = in[2 * i + 1];
+ out[4 * i + 2] = in[2 * i];
+ out[4 * i + 3] = in[2 * i + 1];
+ }
+
+ return in_frames;
+}
+
+/* Converts S16 quad (front L/R, rear L/R) to S16 stereo. The out buffer
+ * can have room for just stereo samples.
+ */
+static size_t s16_quad_to_stereo(struct cras_fmt_conv *conv,
+ const int16_t *in, size_t in_frames,
+ int16_t *out)
+{
+ size_t i;
+ unsigned int left_idx =
+ conv->in_fmt.channel_layout[CRAS_CH_FL];
+ unsigned int right_idx =
+ conv->in_fmt.channel_layout[CRAS_CH_FR];
+ unsigned int left_rear_idx =
+ conv->in_fmt.channel_layout[CRAS_CH_RL];
+ unsigned int right_rear_idx =
+ conv->in_fmt.channel_layout[CRAS_CH_RR];
+
+ if (left_idx == -1 || right_idx == -1 ||
+ left_rear_idx == -1 || right_rear_idx == -1) {
+ left_idx = 0;
+ right_idx = 1;
+ left_rear_idx = 2;
+ right_rear_idx = 3;
+ }
+
+ for (i = 0; i < in_frames; i++) {
+ out[2 * i] = s16_add_and_clip(
+ in[4 * i + left_idx],
+ in[4 * i + left_rear_idx] / 4);
+ out[2 * i + 1] = s16_add_and_clip(
+ in[4 * i + right_idx],
+ in[4 * i + right_rear_idx] / 4);
+ }
+ return in_frames;
+}
+
/* Converts S16 N channels to S16 M channels. The out buffer must have room for
* M channel. This convert function is used as the default behavior when channel
* layout is not set from the client side. */
@@ -300,8 +373,7 @@
unsigned int num_out_ch = conv->out_fmt.num_channels;
unsigned int in_ch, out_ch, i;
- memset(out, 0, num_out_ch * in_frames *
- cras_get_format_bytes(&conv->out_fmt));
+ memset(out, 0, in_frames * cras_get_format_bytes(&conv->out_fmt));
for (out_ch = 0; out_ch < num_out_ch; out_ch++) {
for (in_ch = 0; in_ch < num_in_ch; in_ch++) {
for (i = 0; i < in_frames; i++) {
@@ -456,7 +528,7 @@
break;
default:
syslog(LOG_WARNING, "Invalid format %d", in->format);
- cras_fmt_conv_destroy(conv);
+ cras_fmt_conv_destroy(&conv);
return NULL;
}
}
@@ -479,7 +551,7 @@
break;
default:
syslog(LOG_WARNING, "Invalid format %d", out->format);
- cras_fmt_conv_destroy(conv);
+ cras_fmt_conv_destroy(&conv);
return NULL;
}
}
@@ -498,6 +570,10 @@
conv->channel_converter = s16_mono_to_51;
} else if (in->num_channels == 2 && out->num_channels == 1) {
conv->channel_converter = s16_stereo_to_mono;
+ } else if (in->num_channels == 2 && out->num_channels == 4) {
+ conv->channel_converter = s16_stereo_to_quad;
+ } else if (in->num_channels == 4 && out->num_channels == 2) {
+ conv->channel_converter = s16_quad_to_stereo;
} else if (in->num_channels == 2 && out->num_channels == 6) {
conv->channel_converter = s16_stereo_to_51;
} else if (in->num_channels == 6 && out->num_channels == 2) {
@@ -516,7 +592,7 @@
in->num_channels,
out->num_channels);
if (conv->ch_conv_mtx == NULL) {
- cras_fmt_conv_destroy(conv);
+ cras_fmt_conv_destroy(&conv);
return NULL;
}
conv->channel_converter = convert_channels;
@@ -538,7 +614,7 @@
conv->ch_conv_mtx = cras_channel_conv_matrix_create(in, out);
if (conv->ch_conv_mtx == NULL) {
syslog(LOG_ERR, "Failed to create channel conversion matrix");
- cras_fmt_conv_destroy(conv);
+ cras_fmt_conv_destroy(&conv);
return NULL;
}
conv->channel_converter = convert_channels;
@@ -559,7 +635,7 @@
in->frame_rate,
out->frame_rate,
rc);
- cras_fmt_conv_destroy(conv);
+ cras_fmt_conv_destroy(&conv);
return NULL;
}
}
@@ -573,7 +649,7 @@
out->frame_rate);
if (conv->resampler == NULL) {
syslog(LOG_ERR, "Fail to create linear resampler");
- cras_fmt_conv_destroy(conv);
+ cras_fmt_conv_destroy(&conv);
return NULL;
}
@@ -585,7 +661,7 @@
4 * /* width in bytes largest format. */
MAX(in->num_channels, out->num_channels));
if (conv->tmp_bufs[i] == NULL) {
- cras_fmt_conv_destroy(conv);
+ cras_fmt_conv_destroy(&conv);
return NULL;
}
}
@@ -595,9 +671,11 @@
return conv;
}
-void cras_fmt_conv_destroy(struct cras_fmt_conv *conv)
+void cras_fmt_conv_destroy(struct cras_fmt_conv **convp)
{
unsigned i;
+ struct cras_fmt_conv *conv = *convp;
+
if (conv->ch_conv_mtx)
cras_channel_conv_matrix_destroy(conv->ch_conv_mtx,
conv->out_fmt.num_channels);
@@ -608,6 +686,7 @@
for (i = 0; i < MAX_NUM_CONVERTERS - 1; i++)
free(conv->tmp_bufs[i]);
free(conv);
+ *convp = NULL;
}
struct cras_fmt_conv *cras_channel_remix_conv_create(
@@ -736,6 +815,7 @@
unsigned int linear_resample_fr = 0;
assert(conv);
+ assert(*in_frames <= conv->tmp_buf_frames);
if (linear_resampler_needed(conv->resampler)) {
post_linear_resample = !conv->pre_linear_resample;
@@ -778,9 +858,19 @@
* resample limit and round it to the lower bound in order
* not to convert too many frames in the pre linear resampler.
*/
- if (conv->speex_state != NULL)
- resample_limit = resample_limit * conv->in_fmt.frame_rate /
+ if (conv->speex_state != NULL) {
+ resample_limit = resample_limit *
+ conv->in_fmt.frame_rate /
conv->out_fmt.frame_rate;
+ /*
+ * However if the limit frames count is less than
+ * |out_rate / in_rate|, the final limit value could be
+ * rounded to zero so it confuses linear resampler to
+ * do nothing. Make sure it's non-zero in that case.
+ */
+ if (resample_limit == 0)
+ resample_limit = 1;
+ }
resample_limit = MIN(resample_limit, conv->tmp_buf_frames);
fr_in = linear_resampler_resample(
@@ -857,10 +947,23 @@
buf_idx++;
}
- if (pre_linear_resample)
+ if (pre_linear_resample) {
*in_frames = linear_resample_fr;
- else
+
+ /* When buffer sizes are small, there's a corner case that
+ * speex library resamples 0 frame to N-1 frames, where N
+ * is the integer ratio of output and input rate. For example,
+ * 16KHz to 48KHz. In this case fmt_conv should claim zero
+ * frames processed, instead of using the linear resampler
+ * processed frames count. Otherwise there will be a frame
+ * leak and, if accumulated, causes delay in multiple devices
+ * use case.
+ */
+ if (conv->speex_state && (fr_in == 0))
+ *in_frames = 0;
+ } else {
*in_frames = fr_in;
+ }
return fr_out;
}
diff --git a/cras/src/server/cras_fmt_conv.h b/cras/src/server/cras_fmt_conv.h
index 732a517..0f6042a 100644
--- a/cras/src/server/cras_fmt_conv.h
+++ b/cras/src/server/cras_fmt_conv.h
@@ -23,7 +23,7 @@
const struct cras_audio_format *out,
size_t max_frames,
size_t pre_linear_resample);
-void cras_fmt_conv_destroy(struct cras_fmt_conv *conv);
+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.
diff --git a/cras/src/server/cras_hfp_info.c b/cras/src/server/cras_hfp_info.c
index d85d285..4127a00 100644
--- a/cras/src/server/cras_hfp_info.c
+++ b/cras/src/server/cras_hfp_info.c
@@ -136,9 +136,9 @@
format_bytes = cras_get_format_bytes(dev->format);
if (dev->direction == CRAS_STREAM_OUTPUT)
- return buf_queued_bytes(info->playback_buf) / format_bytes;
+ return buf_queued(info->playback_buf) / format_bytes;
else
- return buf_queued_bytes(info->capture_buf) / format_bytes;
+ return buf_queued(info->capture_buf) / format_bytes;
}
int hfp_write(struct hfp_info *info)
@@ -318,9 +318,9 @@
error:
if (info) {
if (info->capture_buf)
- byte_buffer_destroy(info->capture_buf);
+ byte_buffer_destroy(&info->capture_buf);
if (info->playback_buf)
- byte_buffer_destroy(info->playback_buf);
+ byte_buffer_destroy(&info->playback_buf);
free(info);
}
return NULL;
@@ -368,10 +368,10 @@
void hfp_info_destroy(struct hfp_info *info)
{
if (info->capture_buf)
- byte_buffer_destroy(info->capture_buf);
+ byte_buffer_destroy(&info->capture_buf);
if (info->playback_buf)
- byte_buffer_destroy(info->playback_buf);
+ byte_buffer_destroy(&info->playback_buf);
free(info);
}
diff --git a/cras/src/server/cras_hfp_iodev.c b/cras/src/server/cras_hfp_iodev.c
index 10314e6..dd8296e 100644
--- a/cras/src/server/cras_hfp_iodev.c
+++ b/cras/src/server/cras_hfp_iodev.c
@@ -4,6 +4,7 @@
*/
#include <sys/socket.h>
+#include <sys/time.h>
#include <syslog.h>
#include "cras_audio_area.h"
@@ -74,7 +75,7 @@
cras_bt_device_iodev_buffer_size_changed(hfpio->device);
}
-static int open_dev(struct cras_iodev *iodev)
+static int configure_dev(struct cras_iodev *iodev)
{
struct hfp_io *hfpio = (struct hfp_io *)iodev;
int sk, err, mtu;
@@ -205,6 +206,8 @@
}
free(hfpio->base.supported_channel_counts);
free(hfpio->base.supported_rates);
+ free(hfpio->base.supported_formats);
+ cras_iodev_free_resources(&hfpio->base);
}
struct cras_iodev *hfp_iodev_create(
@@ -242,7 +245,7 @@
strlen(cras_bt_device_object_path(device)));
iodev->info.stable_id_new = iodev->info.stable_id;
- iodev->open_dev= open_dev;
+ iodev->configure_dev= configure_dev;
iodev->frames_queued = frames_queued;
iodev->delay_frames = delay_frames;
iodev->get_buffer = get_buffer;
diff --git a/cras/src/server/cras_hotword_handler.c b/cras/src/server/cras_hotword_handler.c
new file mode 100644
index 0000000..055294a
--- /dev/null
+++ b/cras/src/server/cras_hotword_handler.c
@@ -0,0 +1,64 @@
+/* Copyright (c) 2017 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <stdint.h>
+#include <string.h>
+#include <syslog.h>
+
+#include "cras_main_message.h"
+#include "cras_observer.h"
+
+struct hotword_triggered_msg {
+ struct cras_main_message header;
+ int64_t tv_sec;
+ int64_t tv_nsec;
+};
+
+/* The following functions are called from audio thread. */
+
+static void init_hotword_triggered_msg(struct hotword_triggered_msg *msg)
+{
+ struct timespec now;
+
+ clock_gettime(CLOCK_MONOTONIC, &now);
+
+ memset(msg, 0, sizeof(*msg));
+ msg->header.type = CRAS_MAIN_HOTWORD_TRIGGERED;
+ msg->header.length = sizeof(*msg);
+ msg->tv_sec = now.tv_sec;
+ msg->tv_nsec = now.tv_nsec;
+}
+
+int cras_hotword_send_triggered_msg()
+{
+ struct hotword_triggered_msg msg;
+ int rc;
+
+ init_hotword_triggered_msg(&msg);
+
+ rc = cras_main_message_send((struct cras_main_message *)&msg);
+ if (rc < 0)
+ syslog(LOG_ERR, "Failed to send hotword triggered message!");
+
+ return rc;
+}
+
+/* The following functions are called from main thread. */
+
+static void handle_hotword_message(struct cras_main_message *msg, void *arg)
+{
+ struct hotword_triggered_msg *hotword_msg =
+ (struct hotword_triggered_msg *)msg;
+
+ cras_observer_notify_hotword_triggered(hotword_msg->tv_sec,
+ hotword_msg->tv_nsec);
+}
+
+int cras_hotword_handler_init()
+{
+ cras_main_message_add_handler(CRAS_MAIN_HOTWORD_TRIGGERED,
+ handle_hotword_message, NULL);
+ return 0;
+}
diff --git a/cras/src/server/cras_hotword_handler.h b/cras/src/server/cras_hotword_handler.h
new file mode 100644
index 0000000..48fc405
--- /dev/null
+++ b/cras/src/server/cras_hotword_handler.h
@@ -0,0 +1,24 @@
+/* Copyright (c) 2017 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * The hotword handler is used to send a DBus signal when a hotword device is
+ * triggered.
+ *
+ * cras_hotword_send_triggered_msg() is called from audio thread to send a
+ * hotword message to main thread which in turn sends the DBus signal.
+ *
+ * cras_hotword_handler_init() is used to setup message handler in main thread
+ * to handle the hotword message from audio thread.
+ */
+
+#ifndef CRAS_HOTWORD_HANDLER_H_
+#define CRAS_HOTWORD_HANDLER_H_
+
+/* Send hotword triggered message. */
+int cras_hotword_send_triggered_msg();
+
+/* Initialize hotword handler. */
+int cras_hotword_handler_init();
+
+#endif /* CRAS_HOTWORD_HANDLER_H_ */
diff --git a/cras/src/server/cras_iodev.c b/cras/src/server/cras_iodev.c
index 69e65db..3130b30 100644
--- a/cras/src/server/cras_iodev.c
+++ b/cras/src/server/cras_iodev.c
@@ -4,6 +4,7 @@
*/
#include <pthread.h>
+#include <stdbool.h>
#include <stdlib.h>
#include <sys/param.h>
#include <sys/time.h>
@@ -14,6 +15,7 @@
#include "audio_thread_log.h"
#include "buffer_share.h"
#include "cras_audio_area.h"
+#include "cras_audio_thread_monitor.h"
#include "cras_device_monitor.h"
#include "cras_dsp.h"
#include "cras_dsp_pipeline.h"
@@ -26,6 +28,7 @@
#include "cras_system_state.h"
#include "cras_util.h"
#include "dev_stream.h"
+#include "input_data.h"
#include "utlist.h"
#include "rate_estimator.h"
#include "softvol_curve.h"
@@ -34,10 +37,14 @@
static const float RAMP_NEW_STREAM_DURATION_SECS = 0.01;
static const float RAMP_MUTE_DURATION_SECS = 0.1;
+/*
+ * Check issu b/72496547 and commit message for the history of
+ * rate estimator tuning.
+ */
static const struct timespec rate_estimation_window_sz = {
- 20, 0 /* 20 sec. */
+ 5, 0 /* 5 sec. */
};
-static const double rate_estimation_smooth_factor = 0.9f;
+static const double rate_estimation_smooth_factor = 0.3f;
static void cras_iodev_alloc_dsp(struct cras_iodev *iodev);
@@ -54,6 +61,18 @@
return rc;
hw_level = rc;
+ /* If underrun happened, handle underrun and get hw_level again. */
+ if (hw_level == 0) {
+ rc = cras_iodev_output_underrun(odev);
+ if (rc < 0)
+ return rc;
+
+ 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);
@@ -324,38 +343,28 @@
return iodev->supported_formats[0];
}
-/* 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;
-
- for (i = 0; i < CRAS_CH_MAX; i++)
- default_layout[i] = i < iodev->format->num_channels ? i : -1;
-
- cras_audio_format_set_channel_layout(iodev->format, default_layout);
- cras_audio_format_set_channel_layout(iodev->ext_format, default_layout);
-}
-
/* Applies the DSP to the samples for the iodev if applicable. */
-static void apply_dsp(struct cras_iodev *iodev, uint8_t *buf, size_t frames)
+static int apply_dsp(struct cras_iodev *iodev, uint8_t *buf, size_t frames)
{
struct cras_dsp_context *ctx;
struct pipeline *pipeline;
+ int rc;
ctx = iodev->dsp_context;
if (!ctx)
- return;
+ return 0;
pipeline = cras_dsp_get_pipeline(ctx);
if (!pipeline)
- return;
+ return 0;
- cras_dsp_pipeline_apply(pipeline,
- buf,
- frames);
+ rc = cras_dsp_pipeline_apply(pipeline,
+ buf,
+ iodev->format->format,
+ frames);
cras_dsp_put_pipeline(ctx);
+ return rc;
}
static void cras_iodev_free_dsp(struct cras_iodev *iodev)
@@ -399,24 +408,13 @@
{
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_layout(iodev);
+ cras_audio_format_set_default_channel_layout(iodev->format);
+ cras_audio_format_set_default_channel_layout(iodev->ext_format);
} else {
cras_audio_format_set_channel_layout(
iodev->ext_format,
@@ -424,6 +422,18 @@
}
}
+/*
+ * For the specified format, removes any channels from the channel layout that
+ * are higher than the supported number of channels. Should be used when the
+ * number of channels of the format been reduced.
+ */
+static void trim_channel_layout(struct cras_audio_format *fmt) {
+ int i;
+ for (i = 0; i < CRAS_CH_MAX; i++)
+ if (fmt->channel_layout[i] >= fmt->num_channels)
+ fmt->channel_layout[i] = -1;
+}
+
int cras_iodev_set_format(struct cras_iodev *iodev,
const struct cras_audio_format *fmt)
{
@@ -457,6 +467,7 @@
iodev->ext_format->frame_rate = actual_rate;
cras_iodev_alloc_dsp(iodev);
+ cras_iodev_update_dsp(iodev);
if (iodev->dsp_context)
adjust_dev_channel_for_dsp(iodev);
@@ -474,7 +485,9 @@
if (iodev->format->num_channels != actual_num_channels) {
/* If the DSP for this device doesn't match, drop it. */
iodev->format->num_channels = actual_num_channels;
+ trim_channel_layout(iodev->format);
iodev->ext_format->num_channels = actual_num_channels;
+ trim_channel_layout(iodev->ext_format);
cras_iodev_free_dsp(iodev);
}
@@ -499,6 +512,51 @@
return rc;
}
+/*
+ * Configures the external dsp module and adds it to the existing dsp pipeline.
+ */
+static void add_ext_dsp_module_to_pipeline(struct cras_iodev *iodev)
+{
+ struct pipeline *pipeline;
+
+ if (!iodev->ext_dsp_module)
+ return;
+
+ iodev->ext_dsp_module->configure(
+ iodev->ext_dsp_module,
+ iodev->buffer_size,
+ iodev->ext_format->num_channels,
+ iodev->ext_format->frame_rate);
+
+ pipeline = iodev->dsp_context
+ ? cras_dsp_get_pipeline(iodev->dsp_context)
+ : NULL;
+
+ if (!pipeline) {
+ cras_iodev_alloc_dsp(iodev);
+ cras_dsp_load_dummy_pipeline(
+ iodev->dsp_context,
+ iodev->format->num_channels);
+ pipeline = cras_dsp_get_pipeline(iodev->dsp_context);
+ }
+
+ cras_dsp_pipeline_set_sink_ext_module(
+ pipeline,
+ iodev->ext_dsp_module);
+
+ cras_dsp_put_pipeline(iodev->dsp_context);
+}
+
+void cras_iodev_set_ext_dsp_module(struct cras_iodev *iodev,
+ struct ext_dsp_module *ext)
+{
+ iodev->ext_dsp_module = ext;
+
+ if (!ext || !cras_iodev_is_open(iodev))
+ return;
+ add_ext_dsp_module_to_pipeline(iodev);
+}
+
void cras_iodev_update_dsp(struct cras_iodev *iodev)
{
char swap_lr_disabled = 1;
@@ -585,7 +643,6 @@
cras_iodev_free_dsp(iodev);
iodev->dsp_context = cras_dsp_context_new(iodev->format->frame_rate,
purpose);
- cras_iodev_update_dsp(iodev);
}
void cras_iodev_fill_time_from_frames(size_t frames,
@@ -614,7 +671,7 @@
if (plugged) {
gettimeofday(&node->plugged_time, NULL);
} else if (node == node->dev->active_node) {
- cras_iodev_list_disable_dev(node->dev);
+ cras_iodev_list_disable_dev(node->dev, false);
}
cras_iodev_list_notify_nodes_changed();
}
@@ -740,7 +797,13 @@
if (!iodev->buf_state)
iodev->buf_state = buffer_share_create(iodev->buffer_size);
- buffer_share_add_id(iodev->buf_state, stream->stream->stream_id, NULL);
+ /*
+ * TRIGGER_ONLY streams do not want to receive data, so do not add them
+ * to buffer_share, otherwise they'll affect other streams to receive.
+ */
+ if (!(stream->stream->flags & TRIGGER_ONLY))
+ buffer_share_add_id(iodev->buf_state, stream->stream->stream_id,
+ NULL);
iodev->min_cb_level = MIN(iodev->min_cb_level, cb_threshold);
iodev->max_cb_level = MAX(iodev->max_cb_level, cb_threshold);
@@ -753,7 +816,6 @@
struct dev_stream *out;
struct dev_stream *ret = NULL;
unsigned int cb_threshold;
- unsigned int old_min_cb_level = iodev->min_cb_level;
iodev->min_cb_level = iodev->buffer_size / 2;
iodev->max_cb_level = 0;
@@ -773,7 +835,7 @@
if (!iodev->streams) {
buffer_share_destroy(iodev->buf_state);
iodev->buf_state = NULL;
- iodev->min_cb_level = old_min_cb_level;
+ iodev->min_cb_level = iodev->buffer_size / 2;
/* Let output device transit into no stream state if it's
* in normal run state now. Leave input device in normal
* run state. */
@@ -820,20 +882,47 @@
return max;
}
-int cras_iodev_open(struct cras_iodev *iodev, unsigned int cb_level)
+int cras_iodev_open(struct cras_iodev *iodev, unsigned int cb_level,
+ const struct cras_audio_format *fmt)
{
int rc;
- rc = iodev->open_dev(iodev);
- if (rc < 0)
- return rc;
+ if (iodev->pre_open_iodev_hook)
+ iodev->pre_open_iodev_hook();
+ if (iodev->open_dev) {
+ rc = iodev->open_dev(iodev);
+ if (rc)
+ return rc;
+ }
+
+ if (iodev->ext_format == NULL) {
+ rc = cras_iodev_set_format(iodev, fmt);
+ if (rc) {
+ iodev->close_dev(iodev);
+ return rc;
+ }
+ }
+
+ rc = iodev->configure_dev(iodev);
+ if (rc < 0) {
+ iodev->close_dev(iodev);
+ return rc;
+ }
+
+ /*
+ * Convert cb_level from input format to device format
+ */
+ cb_level = cras_frames_at_rate(fmt->frame_rate,
+ cb_level,
+ iodev->ext_format->frame_rate);
/* Make sure the min_cb_level doesn't get too large. */
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;
+ iodev->highest_hw_level = 0;
if (iodev->direction == CRAS_STREAM_OUTPUT) {
/* If device supports start ops, device can be in open state.
@@ -843,12 +932,23 @@
else
iodev->state = CRAS_IODEV_STATE_NO_STREAM_RUN;
} else {
+ iodev->input_data = input_data_create(iodev);
+ /* If this is the echo reference dev, its ext_dsp_module will
+ * be set to APM reverse module. Do not override it to its
+ * input data. */
+ if (iodev->ext_dsp_module == NULL)
+ iodev->ext_dsp_module = &iodev->input_data->ext;
+
/* 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;
+ /* Initialize the input_streaming flag to zero.*/
+ iodev->input_streaming = 0;
}
+ add_ext_dsp_module_to_pipeline(iodev);
+
return 0;
}
@@ -860,37 +960,61 @@
int cras_iodev_close(struct cras_iodev *iodev)
{
int rc;
+
if (!cras_iodev_is_open(iodev))
return 0;
+ if (iodev->input_data) {
+ if (iodev->ext_dsp_module == &iodev->input_data->ext)
+ iodev->ext_dsp_module = NULL;
+ input_data_destroy(&iodev->input_data);
+ }
+
rc = iodev->close_dev(iodev);
if (rc)
return rc;
iodev->state = CRAS_IODEV_STATE_CLOSE;
if (iodev->ramp)
cras_ramp_reset(iodev->ramp);
+
+ if (iodev->post_close_iodev_hook)
+ iodev->post_close_iodev_hook();
return 0;
}
-int cras_iodev_put_input_buffer(struct cras_iodev *iodev, unsigned int nframes)
+int cras_iodev_put_input_buffer(struct cras_iodev *iodev)
{
- rate_estimator_add_frames(iodev->rate_est, -nframes);
- return iodev->put_buffer(iodev, nframes);
+ unsigned int min_frames;
+ unsigned int dsp_frames;
+ struct input_data *data = iodev->input_data;
+
+ if (iodev->streams)
+ min_frames = buffer_share_get_new_write_point(iodev->buf_state);
+ else
+ min_frames = data->area->frames;
+
+ // Update the max number of frames has applied input dsp.
+ dsp_frames = MAX(iodev->input_frames_read, iodev->input_dsp_offset);
+ iodev->input_dsp_offset = dsp_frames - min_frames;
+
+ input_data_set_all_streams_read(data, min_frames);
+ rate_estimator_add_frames(iodev->rate_est, -min_frames);
+ return iodev->put_buffer(iodev, min_frames);
}
int cras_iodev_put_output_buffer(struct cras_iodev *iodev, uint8_t *frames,
- unsigned int nframes)
+ unsigned int nframes, int *is_non_empty,
+ struct cras_fmt_conv *remix_converter)
{
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;
+ float software_volume_scaler = 1.0;
int software_volume_needed = cras_iodev_software_volume_needed(iodev);
+ int rc;
if (iodev->pre_dsp_hook)
iodev->pre_dsp_hook(frames, nframes, iodev->ext_format,
@@ -900,48 +1024,53 @@
ramp_action = cras_ramp_get_current_action(iodev->ramp);
}
+ rc = apply_dsp(iodev, frames, nframes);
+ if (rc)
+ return rc;
+
+ if (iodev->post_dsp_hook)
+ iodev->post_dsp_hook(frames, nframes, fmt,
+ iodev->post_dsp_hook_cb_data);
+
/* 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 {
- apply_dsp(iodev, frames, nframes);
- if (iodev->post_dsp_hook)
- iodev->post_dsp_hook(frames, nframes, fmt,
- iodev->post_dsp_hook_cb_data);
+ // Skip non-empty check, since we know it's empty.
+ is_non_empty = NULL;
+ }
- /* Compute scaler for software volume if needed. */
+ /* 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) {
- software_volume_scaler =
- cras_iodev_get_software_volume_scaler(iodev);
+ starting_scaler *= software_volume_scaler;
+ increment *= software_volume_scaler;
}
- 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, 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 (!output_should_mute(iodev) && 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, software_volume_scaler);
}
if (remix_converter)
@@ -950,22 +1079,33 @@
frames,
nframes);
rate_estimator_add_frames(iodev->rate_est, nframes);
+
+ // Calculate whether the final output was non-empty, if requested.
+ if (is_non_empty) {
+ unsigned int i;
+ for (i = 0; i < nframes * cras_get_format_bytes(fmt); i++) {
+ if (frames[i]) {
+ *is_non_empty = 1;
+ break;
+ }
+ }
+ }
+
return iodev->put_buffer(iodev, nframes);
}
-int cras_iodev_get_input_buffer(struct cras_iodev *iodev,
- struct cras_audio_area **area,
- unsigned *frames)
+int cras_iodev_get_input_buffer(struct cras_iodev *iodev, unsigned int *frames)
{
- const struct cras_audio_format *fmt = iodev->format;
- const unsigned int frame_bytes = cras_get_format_bytes(fmt);
- uint8_t *hw_buffer;
+ const unsigned int frame_bytes = cras_get_format_bytes(iodev->format);
+ struct input_data *data = iodev->input_data;
int rc;
+ uint8_t *hw_buffer;
unsigned frame_requested = *frames;
- rc = iodev->get_buffer(iodev, area, frames);
+ rc = iodev->get_buffer(iodev, &data->area, frames);
if (rc < 0 || *frames == 0)
return rc;
+
if (*frames > frame_requested) {
syslog(LOG_ERR,
"frames returned from get_buffer is greater than "
@@ -973,13 +1113,31 @@
return -EINVAL;
}
- /* TODO(dgreid) - This assumes interleaved audio. */
- hw_buffer = (*area)->channels[0].buf;
+ iodev->input_frames_read = *frames;
+
+ /* TODO(hychao) - This assumes interleaved audio. */
+ hw_buffer = data->area->channels[0].buf;
+
+
+ /*
+ * input_dsp_offset records the position where input dsp has applied to
+ * last time. It's possible the requested |frames| count is smaller
+ * than the tracked offset. That could happen when client stream uses
+ * small buffer size and runs APM processing (which requires 10 ms
+ * equivalent of data to process).
+ * Only apply input dsp to the part of read buffer beyond where we've
+ * already applied dsp.
+ */
+ if (*frames > iodev->input_dsp_offset) {
+ rc = apply_dsp(iodev, hw_buffer +
+ iodev->input_dsp_offset * frame_bytes,
+ *frames - iodev->input_dsp_offset);
+ if (rc)
+ return rc;
+ }
if (cras_system_get_capture_mute())
cras_mix_mute_buffer(hw_buffer, frame_bytes, *frames);
- else
- apply_dsp(iodev, hw_buffer, *frames); /* TODO-applied 2x */
return rc;
}
@@ -1004,6 +1162,11 @@
int cras_iodev_update_rate(struct cras_iodev *iodev, unsigned int level,
struct timespec *level_tstamp)
{
+ /* If output underruns, reset to avoid incorrect estimated rate. */
+ if ((iodev->direction == CRAS_STREAM_OUTPUT) && !level)
+ rate_estimator_reset_rate(iodev->rate_est,
+ iodev->ext_format->frame_rate);
+
return rate_estimator_check(iodev->rate_est, level, level_tstamp);
}
@@ -1046,9 +1209,18 @@
int rc;
rc = iodev->frames_queued(iodev, hw_tstamp);
- if (rc < 0 || iodev->direction == CRAS_STREAM_INPUT)
+ if(rc == -EPIPE)
+ cras_audio_thread_severe_underrun();
+
+ if (rc < 0)
return rc;
+ if (iodev->direction == CRAS_STREAM_INPUT) {
+ if (rc > 0)
+ iodev->input_streaming = 1;
+ return rc;
+ }
+
if (rc < iodev->min_buffer_level)
return 0;
@@ -1106,7 +1278,8 @@
/* 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);
+ cras_iodev_put_output_buffer(odev, buf, frames_written,
+ NULL, NULL);
frames -= frames_written;
}
@@ -1114,6 +1287,7 @@
}
int cras_iodev_output_underrun(struct cras_iodev *odev) {
+ cras_audio_thread_underrun();
if (odev->output_underrun)
return odev->output_underrun(odev);
else
@@ -1137,18 +1311,22 @@
unsigned int *hw_level,
struct timespec *hw_tstamp)
{
- int rc;
-
- rc = cras_iodev_frames_queued(odev, hw_tstamp);
- *hw_level = (rc < 0) ? 0 : rc;
+ int rc = cras_iodev_frames_queued(odev, hw_tstamp);
+ unsigned int level = (rc < 0) ? 0 : rc;
+ *hw_level = level;
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;
+ /* Schedule that audio thread will wake up when hw_level drops to
+ * 1ms. Normally, this isn't hit because the client will wake us
+ * up before then. This helps with cases where the hardware
+ * buffer is smaller than the client stream buffer. */
+ unsigned int one_ms_frames = odev->format->frame_rate / 1000;
+ if (level > one_ms_frames)
+ return level - one_ms_frames;
+ else
+ return 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. */
@@ -1286,3 +1464,20 @@
iodev->set_mute(iodev);
return 0;
}
+
+int cras_iodev_has_pinned_stream(const struct cras_iodev *dev)
+{
+ const struct dev_stream *out;
+ DL_FOREACH(dev->streams, out) {
+ if (out->stream->is_pinned)
+ return 1;
+ }
+ return 0;
+}
+
+void cras_iodev_update_highest_hw_level(struct cras_iodev *iodev,
+ unsigned int hw_level)
+{
+ iodev->highest_hw_level = MAX(iodev->highest_hw_level, hw_level);
+}
+
diff --git a/cras/src/server/cras_iodev.h b/cras/src/server/cras_iodev.h
index 51a0003..d40af91 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_fmt_conv;
struct cras_ramp;
struct cras_rstream;
struct cras_audio_area;
@@ -34,6 +35,9 @@
const struct cras_audio_format *fmt,
void *cb_data);
+/* Callback type for an iodev event. */
+typedef int (*iodev_hook_t)();
+
/* State of an iodev.
* no_stream state is only supported on output device.
* Open state is only supported for device supporting start ops.
@@ -66,7 +70,8 @@
* software_volume_needed - For output: True if the volume range of the node
* is smaller than desired. For input: True if this node needs software
* gain.
- * max_software_gain - The maximum software gain in dBm if needed.
+ * min_software_gain - The minimum software gain in 0.01 dB if needed.
+ * max_software_gain - The maximum software gain in 0.01 dB if needed.
* 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.
@@ -86,6 +91,7 @@
char active_hotword_model[CRAS_NODE_HOTWORD_MODEL_BUFFER_SIZE];
float *softvol_scalers;
int software_volume_needed;
+ long min_software_gain;
long max_software_gain;
unsigned int stable_id;
unsigned int stable_id_new;
@@ -99,6 +105,7 @@
* set_capture_mute - Function to call if the system capture mute state changes.
* set_swap_mode_for_node - Function to call to set swap mode for the node.
* open_dev - Opens the device.
+ * configure_dev - Configures the device.
* close_dev - Closes the device if it is open.
* update_supported_formats - Refresh supported frame rates and channel counts.
* frames_queued - The number of frames in the audio buffer, and fills tstamp
@@ -150,6 +157,9 @@
* min_buffer_level - Extra frames to keep queued in addition to requested.
* dsp_context - The context used for dsp processing on the audio data.
* dsp_name - The "dsp_name" dsp variable specified in the ucm config.
+ * echo_reference_dev - Used only for playback iodev. Pointer to the input
+ * iodev, which can be used to record what is playing out from this
+ * iodev. This will be used as the echo reference for echo cancellation.
* 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.
@@ -166,9 +176,20 @@
* 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.
+ * pre_open_iodev_hook - Optional callback to call before iodev open.
+ * post_close_iodev_hook - Optional callback to call after iodev close.
+ * ext_dsp_module - External dsp module to process audio data in stream level
+ * after dsp_context.
* 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.
+ * input_streaming - For capture only. Indicate if input has started.
+ * input_frames_read - The number of frames read from the device, but that
+ * haven't been "put" yet.
+ * input_dsp_offset - The number of frames in the HW buffer that have already
+ * been processed by the input DSP.
+ * input_data - Used to pass audio input data to streams with or without
+ * stream side processing.
*/
struct cras_iodev {
void (*set_volume)(struct cras_iodev *iodev);
@@ -179,6 +200,7 @@
struct cras_ionode *node,
int enable);
int (*open_dev)(struct cras_iodev *iodev);
+ int (*configure_dev)(struct cras_iodev *iodev);
int (*close_dev)(struct cras_iodev *iodev);
int (*update_supported_formats)(struct cras_iodev *iodev);
int (*frames_queued)(const struct cras_iodev *iodev,
@@ -216,6 +238,7 @@
unsigned int min_buffer_level;
struct cras_dsp_context *dsp_context;
const char *dsp_name;
+ struct cras_iodev *echo_reference_dev;
int is_enabled;
int software_volume_needed;
struct dev_stream *streams;
@@ -228,8 +251,16 @@
loopback_hook_t post_dsp_hook;
void *pre_dsp_hook_cb_data;
void *post_dsp_hook_cb_data;
+ iodev_hook_t pre_open_iodev_hook;
+ iodev_hook_t post_close_iodev_hook;
+ struct ext_dsp_module *ext_dsp_module;
int reset_request_pending;
struct cras_ramp* ramp;
+ int input_streaming;
+ unsigned int input_frames_read;
+ unsigned int input_dsp_offset;
+ unsigned int highest_hw_level;
+ struct input_data *input_data;
struct cras_iodev *prev, *next;
};
@@ -318,27 +349,6 @@
size_t frame_rate,
struct timespec *ts);
-/* Sets the timestamp for when the next sample will be rendered. Determined by
- * combining the current time with the playback latency specified in frames.
- * Args:
- * frame_rate - in Hz.
- * frames - Delay specified in frames.
- * ts - Filled with the time that the next sample will be played.
- */
-void cras_iodev_set_playback_timestamp(size_t frame_rate,
- size_t frames,
- struct cras_timespec *ts);
-
-/* Sets the time that the first sample in the buffer was captured at the ADC.
- * Args:
- * frame_rate - in Hz.
- * frames - Delay specified in frames.
- * ts - Filled with the time that the next sample was captured.
- */
-void cras_iodev_set_capture_timestamp(size_t frame_rate,
- size_t frames,
- struct cras_timespec *ts);
-
/* Update the "dsp_name" dsp variable. This may cause the dsp pipeline to be
* reloaded.
* Args:
@@ -437,6 +447,22 @@
return iodev->active_node->software_volume_needed;
}
+/* Returns minimum software gain for the iodev.
+ * Args:
+ * iodev - The device.
+ * Returs:
+ * 0 if software gain is not needed, or if there is no active node.
+ * Returns min_software_gain on active node if there is one. */
+static inline long cras_iodev_minimum_software_gain(
+ const struct cras_iodev *iodev)
+{
+ if (!cras_iodev_software_volume_needed(iodev))
+ return 0;
+ if (!iodev->active_node)
+ return 0;
+ return iodev->active_node->min_software_gain;
+}
+
/* Returns maximum software gain for the iodev.
* Args:
* iodev - The device.
@@ -457,7 +483,7 @@
* Args:
* iodev - The device.
* Returns:
- * A scaler translated from system gain and active node gain dBm value.
+ * A scaler translated from system gain and active node gain.
* Returns 1.0 if software gain is not needed. */
float cras_iodev_get_software_gain_scaler(const struct cras_iodev *iodev);
@@ -495,7 +521,8 @@
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);
+int cras_iodev_open(struct cras_iodev *iodev, unsigned int cb_level,
+ const struct cras_audio_format *fmt);
/* Open an iodev, does teardown and invokes the close_dev callback. */
int cras_iodev_close(struct cras_iodev *iodev);
@@ -504,21 +531,19 @@
int cras_iodev_buffer_avail(struct cras_iodev *iodev, unsigned hw_level);
/* Marks a buffer from get_buffer as read. */
-int cras_iodev_put_input_buffer(struct cras_iodev *iodev, unsigned int nframes);
+int cras_iodev_put_input_buffer(struct cras_iodev *iodev);
/* Marks a buffer from get_buffer as written. */
int cras_iodev_put_output_buffer(struct cras_iodev *iodev, uint8_t *frames,
- unsigned int nframes);
+ unsigned int nframes, int *is_non_empty,
+ struct cras_fmt_conv *remix_converter);
/* Returns a buffer to read from.
* Args:
* iodev - The device.
- * area - Filled with a pointer to the audio to read/write.
* frames - Filled with the number of frames that can be read/written.
*/
-int cras_iodev_get_input_buffer(struct cras_iodev *iodev,
- struct cras_audio_area **area,
- unsigned *frames);
+int cras_iodev_get_input_buffer(struct cras_iodev *iodev, unsigned *frames);
/* Returns a buffer to read from.
* Args:
@@ -561,6 +586,12 @@
return iodev->delay_frames(iodev) + cras_iodev_get_dsp_delay(iodev);
}
+/* Returns if input iodev has started streaming. */
+static inline int cras_iodev_input_streaming(const struct cras_iodev *iodev)
+{
+ return iodev->input_streaming;
+}
+
/* Returns true if the device is open. */
static inline int cras_iodev_is_open(const struct cras_iodev *iodev)
{
@@ -579,6 +610,17 @@
loopback_hook_t loop_cb,
void *cb_data);
+/*
+ * Sets the external dsp module for |iodev| and configures the module
+ * accordingly if iodev is already open. This function should be called
+ * in main thread.
+ * Args:
+ * iodev - The iodev to hold the dsp module.
+ * ext - External dsp module to set to iodev.
+ */
+void cras_iodev_set_ext_dsp_module(struct cras_iodev *iodev,
+ struct ext_dsp_module *ext);
+
/* Put 'frames' worth of zero samples into odev. */
int cras_iodev_fill_odev_zeros(struct cras_iodev *odev, unsigned int frames);
@@ -701,4 +743,21 @@
*/
int cras_iodev_is_zero_volume(const struct cras_iodev *odev);
+/*
+ * Checks if a iodev has pinned stream attached to it.
+ * Args:
+ * dev[in] - The device.
+ * Returns:
+ * 1 if device has pinned stream. 0 otherwise.
+ */
+int cras_iodev_has_pinned_stream(const struct cras_iodev *dev);
+
+/*
+ * Updates the highest hardware level of the device.
+ * Args:
+ * iodev - The device.
+ */
+void cras_iodev_update_highest_hw_level(struct cras_iodev *iodev,
+ unsigned int hw_level);
+
#endif /* CRAS_IODEV_H_ */
diff --git a/cras/src/server/cras_iodev_list.c b/cras/src/server/cras_iodev_list.c
index b89767e..5328af4 100644
--- a/cras/src/server/cras_iodev_list.c
+++ b/cras/src/server/cras_iodev_list.c
@@ -17,6 +17,7 @@
#include "cras_tm.h"
#include "cras_types.h"
#include "cras_system_state.h"
+#include "server_stream.h"
#include "stream_list.h"
#include "test_iodev.h"
#include "utlist.h"
@@ -38,10 +39,22 @@
*/
struct enabled_dev {
struct cras_iodev *dev;
- struct cras_timer *init_timer;
struct enabled_dev *prev, *next;
};
+struct dev_init_retry {
+ int dev_idx;
+ struct cras_timer *init_timer;
+ struct dev_init_retry *next, *prev;
+};
+
+struct device_enabled_cb {
+ device_enabled_callback_t enabled_cb;
+ device_disabled_callback_t disabled_cb;
+ void *cb_data;
+ struct device_enabled_cb *next, *prev;
+};
+
/* 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. */
@@ -50,13 +63,21 @@
static struct enabled_dev *enabled_devs[CRAS_NUM_DIRECTIONS];
/* Keep an empty device per direction. */
static struct cras_iodev *fallback_devs[CRAS_NUM_DIRECTIONS];
+/* Special empty device for hotword streams. */
+static struct cras_iodev *empty_hotword_dev;
+/* Loopback devices. */
+static struct cras_iodev *loopdev_post_mix;
+static struct cras_iodev *loopdev_post_dsp;
+/* List of pending device init retries. */
+static struct dev_init_retry *init_retries;
+
/* 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;
/* Call when a device is enabled or disabled. */
-static device_enabled_callback_t device_enabled_callback;
-static void *device_enabled_cb_data;
+struct device_enabled_cb *device_enable_cbs;
+
/* Thread that handles audio input and output. */
static struct audio_thread *audio_thread;
/* List of all streams. */
@@ -67,6 +88,8 @@
static int stream_list_suspended = 0;
/* If init device failed, retry after 1 second. */
static const unsigned int INIT_DEV_DELAY_MS = 1000;
+/* Flag to indicate that hotword streams are suspended. */
+static int hotword_suspended = 0;
static void idle_dev_check(struct cras_timer *timer, void *data);
@@ -350,25 +373,73 @@
}
}
-static int dev_has_pinned_stream(unsigned int dev_idx)
+static void remove_all_streams_from_dev(struct cras_iodev *dev)
{
- const struct cras_rstream *rstream;
+ struct cras_rstream *rstream;
+
+ audio_thread_rm_open_dev(audio_thread, dev);
DL_FOREACH(stream_list_get(stream_list), rstream) {
- if (rstream->is_pinned && (rstream->pinned_dev_idx == dev_idx))
- return 1;
+ if (rstream->apm_list == NULL)
+ continue;
+ cras_apm_list_remove(rstream->apm_list, dev);
}
+}
+
+/*
+ * If output dev has an echo reference dev associated, add a server
+ * stream to read audio data from it so APM can analyze.
+ */
+static void possibly_enable_echo_reference(struct cras_iodev *dev)
+{
+ if (dev->direction != CRAS_STREAM_OUTPUT)
+ return;
+
+ if (dev->echo_reference_dev == NULL)
+ return;
+
+ server_stream_create(stream_list, dev->echo_reference_dev->info.idx);
+}
+
+/*
+ * If output dev has an echo reference dev associated, check if there
+ * is server stream opened for it and remove it.
+ */
+static void possibly_disable_echo_reference(struct cras_iodev *dev)
+{
+ if (dev->echo_reference_dev == NULL)
+ return;
+
+ server_stream_destroy(stream_list, dev->echo_reference_dev->info.idx);
+}
+
+/*
+ * Close dev if it's opened, without the extra call to idle_dev_check.
+ * This is useful for closing a dev inside idle_dev_check function to
+ * avoid infinite recursive call.
+ *
+ * Returns:
+ * -EINVAL if device was not opened, otherwise return 0.
+ */
+static int close_dev_without_idle_check(struct cras_iodev *dev)
+{
+ if (!cras_iodev_is_open(dev))
+ return -EINVAL;
+ if (cras_iodev_has_pinned_stream(dev))
+ syslog(LOG_ERR, "Closing device with pinned streams.");
+
+ remove_all_streams_from_dev(dev);
+ dev->idle_timeout.tv_sec = 0;
+ cras_iodev_close(dev);
+ possibly_disable_echo_reference(dev);
return 0;
}
static void close_dev(struct cras_iodev *dev)
{
- if (!cras_iodev_is_open(dev) ||
- dev_has_pinned_stream(dev->info.idx))
+ if (close_dev_without_idle_check(dev))
return;
- audio_thread_rm_open_dev(audio_thread, dev);
- dev->idle_timeout.tv_sec = 0;
- cras_iodev_close(dev);
+
if (idle_timer)
cras_tm_cancel_timer(cras_system_state_get_tm(), idle_timer);
idle_dev_check(NULL, NULL);
@@ -390,9 +461,7 @@
if (edev->dev->idle_timeout.tv_sec == 0)
continue;
if (timespec_after(&now, &edev->dev->idle_timeout)) {
- audio_thread_rm_open_dev(audio_thread, edev->dev);
- edev->dev->idle_timeout.tv_sec = 0;
- cras_iodev_close(edev->dev);
+ close_dev_without_idle_check(edev->dev);
continue;
}
num_idle_devs++;
@@ -419,6 +488,24 @@
idle_dev_check, NULL);
}
+/*
+ * Cancel pending init tries. Called at device initialization or when device
+ * is disabled.
+ */
+static void cancel_pending_init_retries(unsigned int dev_idx)
+{
+ struct dev_init_retry *retry;
+
+ DL_FOREACH(init_retries, retry) {
+ if (retry->dev_idx != dev_idx)
+ continue;
+ cras_tm_cancel_timer(cras_system_state_get_tm(),
+ retry->init_timer);
+ DL_DELETE(init_retries, retry);
+ free(retry);
+ }
+}
+
/* Open the device potentially filling the output with a pre buffer. */
static int init_device(struct cras_iodev *dev,
struct cras_rstream *rstream)
@@ -429,20 +516,18 @@
if (cras_iodev_is_open(dev))
return 0;
+ cancel_pending_init_retries(dev->info.idx);
- if (dev->ext_format == NULL) {
- rc = cras_iodev_set_format(dev, &rstream->format);
- if (rc)
- return rc;
- }
-
- rc = cras_iodev_open(dev, rstream->cb_threshold);
+ rc = cras_iodev_open(dev, rstream->cb_threshold, &rstream->format);
if (rc)
return rc;
rc = audio_thread_add_open_dev(audio_thread, dev);
if (rc)
cras_iodev_close(dev);
+
+ possibly_enable_echo_reference(dev);
+
return rc;
}
@@ -455,6 +540,9 @@
if (rstream->is_pinned) {
struct cras_iodev *dev;
+ if ((rstream->flags & HOTWORD_STREAM) == HOTWORD_STREAM)
+ continue;
+
dev = find_dev(rstream->pinned_dev_idx);
if (dev) {
audio_thread_disconnect_stream(audio_thread,
@@ -484,8 +572,11 @@
struct cras_rstream *rstream;
stream_list_suspended = 0;
- DL_FOREACH(stream_list_get(stream_list), rstream)
+ DL_FOREACH(stream_list_get(stream_list), rstream) {
+ if ((rstream->flags & HOTWORD_STREAM) == HOTWORD_STREAM)
+ continue;
stream_added_cb(rstream);
+ }
}
/* Called when the system audio is suspended or resumed. */
@@ -521,7 +612,7 @@
}
}
-static int disable_device(struct enabled_dev *edev);
+static int disable_device(struct enabled_dev *edev, bool force);
static int enable_device(struct cras_iodev *dev);
static void possibly_disable_fallback(enum CRAS_STREAM_DIRECTION dir)
@@ -530,39 +621,74 @@
DL_FOREACH(enabled_devs[dir], edev) {
if (edev->dev == fallback_devs[dir])
- disable_device(edev);
+ disable_device(edev, false);
}
}
static void possibly_enable_fallback(enum CRAS_STREAM_DIRECTION dir)
{
+ if (fallback_devs[dir] == NULL)
+ return;
if (!cras_iodev_list_dev_is_enabled(fallback_devs[dir]))
enable_device(fallback_devs[dir]);
}
+/*
+ * Adds stream to one or more open iodevs. If the stream has processing effect
+ * turned on, create new APM instance and add to the list. This makes sure the
+ * time consuming APM creation happens in main thread.
+ */
+static int add_stream_to_open_devs(struct cras_rstream *stream,
+ struct cras_iodev **iodevs,
+ unsigned int num_iodevs)
+{
+ int i;
+ if (stream->apm_list) {
+ for (i = 0; i < num_iodevs; i++)
+ cras_apm_list_add(stream->apm_list,
+ iodevs[i],
+ iodevs[i]->ext_format);
+ }
+ return audio_thread_add_stream(audio_thread,
+ stream, iodevs, num_iodevs);
+}
+
static int init_and_attach_streams(struct cras_iodev *dev)
{
int rc;
enum CRAS_STREAM_DIRECTION dir = dev->direction;
struct cras_rstream *stream;
+ int dev_enabled = cras_iodev_list_dev_is_enabled(dev);
/* 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);
+ if (stream_list_suspended)
+ return 0;
+
+ /* If there are active streams to attach to this device,
+ * open it. */
+ DL_FOREACH(stream_list_get(stream_list), stream) {
+ if (stream->direction != dir)
+ continue;
+ /*
+ * Don't attach this stream if (1) this stream pins to a
+ * different device, or (2) this is a normal stream, but
+ * device is not enabled.
+ */
+ if(stream->is_pinned) {
+ if (stream->pinned_dev_idx != dev->info.idx)
+ continue;
+ } else if (!dev_enabled) {
+ continue;
}
+
+ rc = init_device(dev, stream);
+ if (rc) {
+ syslog(LOG_ERR, "Enable %s failed, rc = %d",
+ dev->info.name, rc);
+ return rc;
+ }
+ add_stream_to_open_devs(stream, &dev, 1);
}
return 0;
}
@@ -570,10 +696,16 @@
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;
+ struct dev_init_retry *retry = (struct dev_init_retry *)arg;
+ struct cras_iodev *dev = find_dev(retry->dev_idx);
- edev->init_timer = NULL;
+ /*
+ * First of all, remove retry record to avoid confusion to the
+ * actual device init work.
+ */
+ DL_DELETE(init_retries, retry);
+ free(retry);
+
if (cras_iodev_is_open(dev))
return;
@@ -584,24 +716,29 @@
possibly_disable_fallback(dev->direction);
}
-static void schedule_init_device_retry(struct enabled_dev *edev)
+static int schedule_init_device_retry(struct cras_iodev *dev)
{
+ struct dev_init_retry *retry;
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);
+ retry = (struct dev_init_retry *)calloc(1, sizeof(*retry));
+ if (!retry)
+ return -ENOMEM;
+
+ retry->dev_idx = dev->info.idx;
+ retry->init_timer = cras_tm_create_timer(
+ tm, INIT_DEV_DELAY_MS, init_device_cb, retry);
+ DL_APPEND(init_retries, retry);
+ return 0;
}
-static int pinned_stream_added(struct cras_rstream *rstream)
+static int init_pinned_device(struct cras_iodev *dev,
+ 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;
+ if (audio_thread_is_dev_open(audio_thread, dev))
+ return 0;
/* Make sure the active node is configured properly, it could be
* disabled when last normal stream removed. */
@@ -611,8 +748,54 @@
rc = init_device(dev, rstream);
if (rc && (rc != -EAGAIN))
return rc;
+ return 0;
+}
- return audio_thread_add_stream(audio_thread, rstream, &dev, 1);
+static int close_pinned_device(struct cras_iodev *dev)
+{
+ close_dev(dev);
+ dev->update_active_node(dev, dev->active_node->idx, 0);
+ return 0;
+}
+
+static struct cras_iodev *find_pinned_device(struct cras_rstream *rstream)
+{
+ struct cras_iodev *dev;
+ if (!rstream->is_pinned)
+ return NULL;
+
+ dev = find_dev(rstream->pinned_dev_idx);
+
+ if ((rstream->flags & HOTWORD_STREAM) != HOTWORD_STREAM)
+ return dev;
+
+ /* Double check node type for hotword stream */
+ if (dev && dev->active_node->type != CRAS_NODE_TYPE_HOTWORD) {
+ syslog(LOG_ERR, "Hotword stream pinned to invalid dev %u",
+ dev->info.idx);
+ return NULL;
+ }
+
+ return hotword_suspended ? empty_hotword_dev : dev;
+}
+
+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_pinned_device(rstream);
+ if (!dev)
+ return -EINVAL;
+
+ rc = init_pinned_device(dev, rstream);
+ if (rc) {
+ syslog(LOG_INFO, "init_pinned_device failed, rc %d", rc);
+ return schedule_init_device_retry(dev);
+ }
+
+ return add_stream_to_open_devs(rstream, &dev, 1);
}
static int stream_added_cb(struct cras_rstream *rstream)
@@ -644,15 +827,14 @@
*/
syslog(LOG_ERR, "Init %s failed, rc = %d",
edev->dev->info.name, rc);
- schedule_init_device_retry(edev);
+ schedule_init_device_retry(edev->dev);
continue;
}
iodevs[num_iodevs++] = edev->dev;
}
if (num_iodevs) {
- rc = audio_thread_add_stream(audio_thread, rstream,
- iodevs, num_iodevs);
+ rc = add_stream_to_open_devs(rstream, iodevs, num_iodevs);
if (rc) {
syslog(LOG_ERR, "adding stream to thread fail");
return rc;
@@ -687,7 +869,7 @@
/* No more default streams, close any device that doesn't have a stream
* pinned to it. */
DL_FOREACH(enabled_devs[dir], edev) {
- if (dev_has_pinned_stream(edev->dev->info.idx))
+ if (cras_iodev_has_pinned_stream(edev->dev))
continue;
if (dir == CRAS_STREAM_INPUT) {
close_dev(edev->dev);
@@ -706,11 +888,12 @@
{
struct cras_iodev *dev;
- dev = find_dev(rstream->pinned_dev_idx);
- if (!cras_iodev_list_dev_is_enabled(dev)) {
- close_dev(dev);
- dev->update_active_node(dev, dev->active_node->idx, 0);
- }
+ dev = find_pinned_device(rstream);
+ if (!dev)
+ return;
+ if (!cras_iodev_list_dev_is_enabled(dev) &&
+ !cras_iodev_has_pinned_stream(dev))
+ close_pinned_device(dev);
}
/* Returns the number of milliseconds left to drain this stream. This is passed
@@ -737,6 +920,7 @@
int rc;
struct enabled_dev *edev;
enum CRAS_STREAM_DIRECTION dir = dev->direction;
+ struct device_enabled_cb *callback;
DL_FOREACH(enabled_devs[dir], edev) {
if (edev->dev == dev)
@@ -745,45 +929,55 @@
edev = calloc(1, sizeof(*edev));
edev->dev = dev;
- edev->init_timer = NULL;
DL_APPEND(enabled_devs[dir], edev);
dev->is_enabled = 1;
rc = init_and_attach_streams(dev);
if (rc < 0) {
- schedule_init_device_retry(edev);
+ syslog(LOG_INFO, "Enable device fail, rc %d", rc);
+ schedule_init_device_retry(dev);
return rc;
}
- if (device_enabled_callback)
- device_enabled_callback(dev, 1, device_enabled_cb_data);
+ DL_FOREACH(device_enable_cbs, callback)
+ callback->enabled_cb(dev, callback->cb_data);
return 0;
}
-static int disable_device(struct enabled_dev *edev)
+/* Set `force to true to flush any pinned streams before closing the device. */
+static int disable_device(struct enabled_dev *edev, bool force)
{
struct cras_iodev *dev = edev->dev;
enum CRAS_STREAM_DIRECTION dir = dev->direction;
struct cras_rstream *stream;
+ struct device_enabled_cb *callback;
+ /*
+ * Remove from enabled dev list. However this dev could have a stream
+ * pinned to it, only cancel pending init timers when force flag is set.
+ */
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;
+ if (force)
+ cancel_pending_init_retries(dev->info.idx);
- /* Pull all default streams off this device. */
+ /*
+ * Pull all default streams off this device.
+ * Pull all pinned streams off as well if force is true.
+ */
DL_FOREACH(stream_list_get(stream_list), stream) {
- if (stream->direction != dev->direction || stream->is_pinned)
+ if (stream->direction != dev->direction)
+ continue;
+ if (stream->is_pinned && !force)
continue;
audio_thread_disconnect_stream(audio_thread, stream, dev);
}
- if (device_enabled_callback)
- device_enabled_callback(dev, 0, device_enabled_cb_data);
+ if (cras_iodev_has_pinned_stream(dev))
+ return 0;
+ DL_FOREACH(device_enable_cbs, callback)
+ callback->disabled_cb(dev, callback->cb_data);
close_dev(dev);
dev->update_active_node(dev, dev->active_node->idx, 0);
@@ -791,6 +985,34 @@
}
/*
+ * Assume the device is not in enabled_devs list.
+ * Assume there is no default stream on the device.
+ * An example is that this device is unplugged while it is playing
+ * a pinned stream. The device and stream may have been removed in
+ * audio thread due to I/O error handling.
+ */
+static int force_close_pinned_only_device(struct cras_iodev *dev)
+{
+ struct cras_rstream *rstream;
+
+ /* Pull pinned streams off this device. */
+ DL_FOREACH(stream_list_get(stream_list), rstream) {
+ if (rstream->direction != dev->direction)
+ continue;
+ if (!rstream->is_pinned)
+ continue;
+ if (dev->info.idx != rstream->pinned_dev_idx)
+ continue;
+ audio_thread_disconnect_stream(audio_thread, rstream, dev);
+ }
+ if (cras_iodev_has_pinned_stream(dev))
+ return -EEXIST;
+ close_dev(dev);
+ dev->update_active_node(dev, dev->active_node->idx, 0);
+ return 0;
+}
+
+/*
* Exported Interface.
*/
@@ -815,16 +1037,22 @@
/* Add an empty device so there is always something to play to or
* capture from. */
- fallback_devs[CRAS_STREAM_OUTPUT] =
- empty_iodev_create(CRAS_STREAM_OUTPUT);
- fallback_devs[CRAS_STREAM_INPUT] =
- empty_iodev_create(CRAS_STREAM_INPUT);
+ fallback_devs[CRAS_STREAM_OUTPUT] = empty_iodev_create(
+ CRAS_STREAM_OUTPUT,
+ CRAS_NODE_TYPE_UNKNOWN);
+ fallback_devs[CRAS_STREAM_INPUT] = empty_iodev_create(
+ CRAS_STREAM_INPUT,
+ CRAS_NODE_TYPE_UNKNOWN);
enable_device(fallback_devs[CRAS_STREAM_OUTPUT]);
enable_device(fallback_devs[CRAS_STREAM_INPUT]);
+ empty_hotword_dev = empty_iodev_create(
+ CRAS_STREAM_INPUT,
+ CRAS_NODE_TYPE_HOTWORD);
+
/* Create loopback devices. */
- loopback_iodev_create(LOOPBACK_POST_MIX_PRE_DSP);
- loopback_iodev_create(LOOPBACK_POST_DSP);
+ loopdev_post_mix = loopback_iodev_create(LOOPBACK_POST_MIX_PRE_DSP);
+ loopdev_post_dsp = loopback_iodev_create(LOOPBACK_POST_DSP);
audio_thread = audio_thread_create();
if (!audio_thread) {
@@ -838,12 +1066,17 @@
void cras_iodev_list_deinit()
{
+ audio_thread_destroy(audio_thread);
+ loopback_iodev_destroy(loopdev_post_dsp);
+ loopback_iodev_destroy(loopdev_post_mix);
+ empty_iodev_destroy(empty_hotword_dev);
+ empty_iodev_destroy(fallback_devs[CRAS_STREAM_INPUT]);
+ empty_iodev_destroy(fallback_devs[CRAS_STREAM_OUTPUT]);
+ stream_list_destroy(stream_list);
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(const struct cras_iodev *dev)
@@ -861,6 +1094,8 @@
void cras_iodev_list_enable_dev(struct cras_iodev *dev)
{
possibly_disable_fallback(dev->direction);
+ /* Enable ucm setting of active node. */
+ dev->update_active_node(dev, dev->active_node->idx, 1);
enable_device(dev);
cras_iodev_list_notify_active_node_changed(dev->direction);
}
@@ -873,11 +1108,25 @@
if (!new_dev || new_dev->direction != dir)
return;
+ /* If the new dev is already enabled but its active node needs to be
+ * changed. Disable new dev first, update active node, and then
+ * re-enable it again.
+ */
+ if (cras_iodev_list_dev_is_enabled(new_dev)) {
+ if (node_index_of(node_id) == new_dev->active_node->idx)
+ return;
+ else
+ cras_iodev_list_disable_dev(new_dev, true);
+ }
+
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)
+/*
+ * Disables device which may or may not be in enabled_devs list.
+ */
+void cras_iodev_list_disable_dev(struct cras_iodev *dev, bool force_close)
{
struct enabled_dev *edev, *edev_to_disable = NULL;
@@ -890,16 +1139,25 @@
is_the_only_enabled_device = 0;
}
- if (!edev_to_disable)
+ /*
+ * Disables the device for these two cases:
+ * 1. Disable a device in the enabled_devs list.
+ * 2. Force close a device that is not in the enabled_devs list,
+ * but it is running a pinned stream.
+ */
+ if (!edev_to_disable) {
+ if (force_close)
+ force_close_pinned_only_device(dev);
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)
+ if (is_the_only_enabled_device && fallback_devs[dev->direction])
enable_device(fallback_devs[dev->direction]);
- disable_device(edev_to_disable);
+ disable_device(edev_to_disable, force_close);
cras_iodev_list_notify_active_node_changed(dev->direction);
return;
@@ -914,7 +1172,7 @@
if (!dev)
return;
- cras_iodev_list_disable_dev(dev);
+ cras_iodev_list_disable_dev(dev, false);
}
int cras_iodev_list_add_output(struct cras_iodev *output)
@@ -952,7 +1210,7 @@
/* Retire the current active output device before removing it from
* list, otherwise it could be busy and remain in the list.
*/
- cras_iodev_list_disable_dev(dev);
+ cras_iodev_list_disable_dev(dev, true);
res = rm_dev_from_list(dev);
if (res == 0)
cras_iodev_list_update_device_list();
@@ -966,7 +1224,7 @@
/* Retire the current active input device before removing it from
* list, otherwise it could be busy and remain in the list.
*/
- cras_iodev_list_disable_dev(dev);
+ cras_iodev_list_disable_dev(dev, true);
res = rm_dev_from_list(dev);
if (res == 0)
cras_iodev_list_update_device_list();
@@ -1028,6 +1286,97 @@
cras_system_state_update_complete();
}
+/* Look up the first hotword stream and the device it pins to. */
+int find_hotword_stream_dev(struct cras_iodev **dev,
+ struct cras_rstream **stream)
+{
+ DL_FOREACH(stream_list_get(stream_list), *stream) {
+ if (((*stream)->flags & HOTWORD_STREAM) != HOTWORD_STREAM)
+ continue;
+
+ *dev = find_dev((*stream)->pinned_dev_idx);
+ if (*dev == NULL)
+ return -ENOENT;
+ break;
+ }
+ return 0;
+}
+
+/* Suspend/resume hotword streams functions are used to provide seamless
+ * experience to cras clients when there's hardware limitation about concurrent
+ * DSP and normal recording. The empty hotword iodev is used to hold all
+ * hotword streams during suspend, so client side will not know about the
+ * transition, and can still remove or add streams. At resume, the real hotword
+ * device will be initialized and opened again to re-arm the DSP.
+ */
+int cras_iodev_list_suspend_hotword_streams()
+{
+ struct cras_iodev *hotword_dev;
+ struct cras_rstream *stream = NULL;
+ int rc;
+
+ rc = find_hotword_stream_dev(&hotword_dev, &stream);
+ if (rc)
+ return rc;
+
+ if (stream == NULL) {
+ hotword_suspended = 1;
+ return 0;
+ }
+ /* Move all existing hotword streams to the empty hotword iodev. */
+ init_pinned_device(empty_hotword_dev, stream);
+ DL_FOREACH(stream_list_get(stream_list), stream) {
+ if ((stream->flags & HOTWORD_STREAM) != HOTWORD_STREAM)
+ continue;
+ if (stream->pinned_dev_idx != hotword_dev->info.idx) {
+ syslog(LOG_ERR,
+ "Failed to suspend hotword stream on dev %u",
+ stream->pinned_dev_idx);
+ continue;
+ }
+
+ audio_thread_disconnect_stream(audio_thread, stream, hotword_dev);
+ audio_thread_add_stream(audio_thread, stream, &empty_hotword_dev, 1);
+ }
+ close_pinned_device(hotword_dev);
+ hotword_suspended = 1;
+ return 0;
+}
+
+int cras_iodev_list_resume_hotword_stream()
+{
+ struct cras_iodev *hotword_dev;
+ struct cras_rstream *stream = NULL;
+ int rc;
+
+ rc = find_hotword_stream_dev(&hotword_dev, &stream);
+ if (rc)
+ return rc;
+
+ if (stream == NULL) {
+ hotword_suspended = 0;
+ return 0;
+ }
+ /* Move all existing hotword streams to the real hotword iodev. */
+ init_pinned_device(hotword_dev, stream);
+ DL_FOREACH(stream_list_get(stream_list), stream) {
+ if ((stream->flags & HOTWORD_STREAM) != HOTWORD_STREAM)
+ continue;
+ if (stream->pinned_dev_idx != hotword_dev->info.idx) {
+ syslog(LOG_ERR,
+ "Fail to resume hotword stream on dev %u",
+ stream->pinned_dev_idx);
+ continue;
+ }
+
+ audio_thread_disconnect_stream(audio_thread, stream, empty_hotword_dev);
+ audio_thread_add_stream(audio_thread, stream, &hotword_dev, 1);
+ }
+ close_pinned_device(empty_hotword_dev);
+ hotword_suspended = 0;
+ return 0;
+}
+
char *cras_iodev_list_get_hotword_models(cras_node_id_t node_id)
{
struct cras_iodev *dev = NULL;
@@ -1112,7 +1461,7 @@
DL_FOREACH(enabled_devs[direction], edev) {
if (edev->dev != fallback_devs[direction] &&
!(new_node_already_enabled && edev->dev == new_dev)) {
- disable_device(edev);
+ disable_device(edev, false);
}
}
@@ -1197,22 +1546,29 @@
}
int cras_iodev_list_set_device_enabled_callback(
- device_enabled_callback_t device_enabled_cb, void *cb_data)
+ device_enabled_callback_t enabled_cb,
+ device_disabled_callback_t disabled_cb,
+ void *cb_data)
{
- if (!device_enabled_cb) {
- device_enabled_callback = NULL;
- device_enabled_cb_data = NULL;
- return 0;
+ struct device_enabled_cb *callback;
+
+ DL_FOREACH(device_enable_cbs, callback) {
+ if (callback->cb_data != cb_data)
+ continue;
+
+ DL_DELETE(device_enable_cbs, callback);
+ free(callback);
}
- /* TODO(chinyue): Allow multiple callbacks to be registered. */
- if (device_enabled_callback) {
- syslog(LOG_ERR, "Device enabled callback already registered.");
- return -EEXIST;
+ if (enabled_cb && disabled_cb) {
+ callback = (struct device_enabled_cb *)
+ calloc(1, sizeof(*callback));
+ callback->enabled_cb = enabled_cb;
+ callback->disabled_cb = disabled_cb;
+ callback->cb_data = cb_data;
+ DL_APPEND(device_enable_cbs, callback);
}
- device_enabled_callback = device_enabled_cb;
- device_enabled_cb_data = cb_data;
return 0;
}
diff --git a/cras/src/server/cras_iodev_list.h b/cras/src/server/cras_iodev_list.h
index 3599f1b..96c7306 100644
--- a/cras/src/server/cras_iodev_list.h
+++ b/cras/src/server/cras_iodev_list.h
@@ -9,6 +9,7 @@
#ifndef CRAS_IODEV_LIST_H_
#define CRAS_IODEV_LIST_H_
+#include <stdbool.h>
#include <stdint.h>
#include "cras_types.h"
@@ -21,11 +22,9 @@
struct cras_audio_format;
struct stream_list;
-/* Device enabled/disabled callback.
- * enabled=1 when a device is enabled, enabled=0 when a device is disabled.
- */
-typedef void (*device_enabled_callback_t)(struct cras_iodev *dev, int enabled,
- void *cb_data);
+/* Device enabled/disabled callback. */
+typedef void (*device_enabled_callback_t)(struct cras_iodev *dev, void *cb_data);
+typedef void (*device_disabled_callback_t)(struct cras_iodev *dev, void *cb_data);
/* Initialize the list of iodevs. */
void cras_iodev_list_init();
@@ -157,9 +156,13 @@
* call will disable it. */
void cras_iodev_list_enable_dev(struct cras_iodev *dev);
-/* Disables an iodev. If this is the last device to disable, the
- * fallback devices will be enabled accordingly. */
-void cras_iodev_list_disable_dev(struct cras_iodev *dev);
+/*
+ * Disables an iodev. If this is the last device to disable, the
+ * fallback devices will be enabled accordingly.
+ * Set `foce_close` to true if the device must be closed regardless of having
+ * pinned streams attached.
+ */
+void cras_iodev_list_disable_dev(struct cras_iodev *dev, bool force_close);
/* Adds a node to the active devices list.
* Args:
@@ -206,7 +209,15 @@
/* Sets the function to call when a device is enabled or disabled. */
int cras_iodev_list_set_device_enabled_callback(
- device_enabled_callback_t device_enabled_cb, void *cb_data);
+ device_enabled_callback_t enabled_cb,
+ device_disabled_callback_t disabled_cb,
+ void *cb_data);
+
+/* Suspends all hotwording streams. */
+int cras_iodev_list_suspend_hotword_streams();
+
+/* Resumes all hotwording streams. */
+int cras_iodev_list_resume_hotword_stream();
/* For unit test only. */
void cras_iodev_list_reset();
diff --git a/cras/src/server/cras_loopback_iodev.c b/cras/src/server/cras_loopback_iodev.c
index ff33aca..2598089 100644
--- a/cras/src/server/cras_loopback_iodev.c
+++ b/cras/src/server/cras_loopback_iodev.c
@@ -64,7 +64,7 @@
if (!edev || !edev->streams)
return 0;
- frames_to_copy = MIN(buf_writable_bytes(sbuf) / frame_bytes, nframes);
+ frames_to_copy = MIN(buf_writable(sbuf) / frame_bytes, nframes);
if (!frames_to_copy)
return 0;
@@ -91,8 +91,7 @@
cras_iodev_register_post_dsp_hook(iodev, hook, cb_data);
}
-static void device_enabled_hook(struct cras_iodev *iodev, int enabled,
- void *cb_data)
+static void device_enabled_hook(struct cras_iodev *iodev, void *cb_data)
{
struct loopback_iodev *loopdev = (struct loopback_iodev *)cb_data;
struct cras_iodev *edev;
@@ -100,17 +99,23 @@
if (iodev->direction != CRAS_STREAM_OUTPUT)
return;
- if (!enabled) {
- /* Unregister loopback hook from disabled iodev. */
- register_loopback_hook(loopdev->loopback_type, iodev, NULL,
- NULL);
- } else {
- /* Register loopback hook onto first enabled iodev. */
- edev = cras_iodev_list_get_first_enabled_iodev(
- CRAS_STREAM_OUTPUT);
- register_loopback_hook(loopdev->loopback_type, edev,
- sample_hook, cb_data);
- }
+ /* Register loopback hook onto first enabled iodev. */
+ edev = cras_iodev_list_get_first_enabled_iodev(
+ CRAS_STREAM_OUTPUT);
+ register_loopback_hook(loopdev->loopback_type, edev,
+ sample_hook, cb_data);
+}
+
+static void device_disabled_hook(struct cras_iodev *iodev, void *cb_data)
+{
+ struct loopback_iodev *loopdev = (struct loopback_iodev *)cb_data;
+
+ if (iodev->direction != CRAS_STREAM_OUTPUT)
+ return;
+
+ /* Unregister loopback hook from disabled iodev. */
+ register_loopback_hook(loopdev->loopback_type, iodev, NULL,
+ NULL);
}
/*
@@ -132,7 +137,7 @@
frames_since_last = cras_frames_since_time(
&loopdev->last_filled,
iodev->format->frame_rate);
- frames_to_fill = MIN(buf_writable_bytes(sbuf) / frame_bytes,
+ frames_to_fill = MIN(buf_writable(sbuf) / frame_bytes,
frames_since_last);
if (frames_to_fill > 0) {
bytes_to_fill = frames_to_fill * frame_bytes;
@@ -143,7 +148,7 @@
}
}
*hw_tstamp = loopdev->last_filled;
- return buf_queued_bytes(sbuf) / frame_bytes;
+ return buf_queued(sbuf) / frame_bytes;
}
static int delay_frames(const struct cras_iodev *iodev)
@@ -165,12 +170,12 @@
edev = cras_iodev_list_get_first_enabled_iodev(CRAS_STREAM_OUTPUT);
register_loopback_hook(loopdev->loopback_type, edev, NULL, NULL);
- cras_iodev_list_set_device_enabled_callback(NULL, NULL);
+ cras_iodev_list_set_device_enabled_callback(NULL, NULL, (void *)iodev);
return 0;
}
-static int open_record_dev(struct cras_iodev *iodev)
+static int configure_record_dev(struct cras_iodev *iodev)
{
struct loopback_iodev *loopdev = (struct loopback_iodev *)iodev;
struct cras_iodev *edev;
@@ -182,6 +187,7 @@
register_loopback_hook(loopdev->loopback_type, edev, sample_hook,
(void *)iodev);
cras_iodev_list_set_device_enabled_callback(device_enabled_hook,
+ device_disabled_hook,
(void *)iodev);
return 0;
@@ -194,7 +200,7 @@
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;
+ unsigned int avail_frames = buf_readable(sbuf) / frame_bytes;
*frames = MIN(avail_frames, *frames);
iodev->area->frames = *frames;
@@ -220,7 +226,7 @@
{
struct loopback_iodev *loopdev = (struct loopback_iodev *)iodev;
struct byte_buffer *sbuf = loopdev->sample_buffer;
- unsigned int queued_bytes = buf_queued_bytes(sbuf);
+ unsigned int queued_bytes = buf_queued(sbuf);
buf_increment_read(sbuf, queued_bytes);
return 0;
}
@@ -265,7 +271,7 @@
iodev->frames_queued = frames_queued;
iodev->delay_frames = delay_frames;
iodev->update_active_node = update_active_node;
- iodev->open_dev = open_record_dev;
+ iodev->configure_dev = configure_record_dev;
iodev->close_dev = close_record_dev;
iodev->get_buffer = get_record_buffer;
iodev->put_buffer = put_record_buffer;
@@ -324,7 +330,8 @@
struct byte_buffer *sbuf = loopdev->sample_buffer;
cras_iodev_list_rm_input(iodev);
+ free(iodev->nodes);
- byte_buffer_destroy(sbuf);
+ byte_buffer_destroy(&sbuf);
free(loopdev);
}
diff --git a/cras/src/server/cras_main_message.h b/cras/src/server/cras_main_message.h
index ba6b02a..5a2bb28 100644
--- a/cras/src/server/cras_main_message.h
+++ b/cras/src/server/cras_main_message.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.
*/
@@ -14,9 +14,12 @@
enum CRAS_MAIN_MESSAGE_TYPE {
/* Audio thread -> main thread */
CRAS_MAIN_A2DP,
+ CRAS_MAIN_AUDIO_THREAD_EVENT,
CRAS_MAIN_BT,
CRAS_MAIN_METRICS,
CRAS_MAIN_MONITOR_DEVICE,
+ CRAS_MAIN_HOTWORD_TRIGGERED,
+ CRAS_MAIN_NON_EMPTY_AUDIO_STATE,
};
/* Structure of the header of the message handled by main thread.
diff --git a/cras/src/server/cras_non_empty_audio_handler.c b/cras/src/server/cras_non_empty_audio_handler.c
new file mode 100644
index 0000000..9482453
--- /dev/null
+++ b/cras/src/server/cras_non_empty_audio_handler.c
@@ -0,0 +1,60 @@
+/* Copyright 2017 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <stdint.h>
+#include <string.h>
+#include <syslog.h>
+
+#include "cras_main_message.h"
+#include "cras_observer.h"
+#include "cras_system_state.h"
+
+struct non_empty_audio_msg {
+ struct cras_main_message header;
+ int32_t non_empty;
+};
+
+/* The following functions are called from audio thread. */
+
+static void init_non_empty_audio_msg(struct non_empty_audio_msg *msg)
+{
+ memset(msg, 0, sizeof(*msg));
+ msg->header.type = CRAS_MAIN_NON_EMPTY_AUDIO_STATE;
+ msg->header.length = sizeof(*msg);
+}
+
+int cras_non_empty_audio_send_msg(int32_t non_empty)
+{
+ struct non_empty_audio_msg msg;
+ int rc;
+
+ init_non_empty_audio_msg(&msg);
+ msg.non_empty = non_empty;
+
+ rc = cras_main_message_send((struct cras_main_message *)&msg);
+ if (rc < 0)
+ syslog(LOG_ERR, "Failed to send non-empty audio message!");
+
+ return rc;
+}
+
+/* The following functions are called from main thread. */
+
+static void handle_non_empty_audio_message(struct cras_main_message *msg,
+ void *arg)
+{
+ struct non_empty_audio_msg *audio_msg =
+ (struct non_empty_audio_msg *)msg;
+
+ cras_system_state_set_non_empty_status(audio_msg->non_empty);
+ cras_observer_notify_non_empty_audio_state_changed(audio_msg->non_empty);
+}
+
+int cras_non_empty_audio_handler_init()
+{
+ cras_main_message_add_handler(CRAS_MAIN_NON_EMPTY_AUDIO_STATE,
+ handle_non_empty_audio_message, NULL);
+ return 0;
+}
\ No newline at end of file
diff --git a/cras/src/server/cras_non_empty_audio_handler.h b/cras/src/server/cras_non_empty_audio_handler.h
new file mode 100644
index 0000000..dac6b22
--- /dev/null
+++ b/cras/src/server/cras_non_empty_audio_handler.h
@@ -0,0 +1,25 @@
+/* Copyright 2018 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.
+ *
+ * The non-empty audio state handler is used to send a DBus signal when the
+ * system-level non-empty audio state changes.
+ *
+ * cras_non_empty_audio_msg() is called from audio thread to update the
+ * non-empty audio state in the main thread, which in turn sends the DBus
+ * signal.
+ *
+ * cras_non_empty_audio_handler_init() is used to setup the message handler
+ * in the main thread to handle the non-empty audiomessage from audio thread.
+ */
+
+#ifndef CRAS_NON_EMPTY_AUDIO_HANDLER_H_
+#define CRAS_NON_EMPTY_AUDIO_HANDLER_H_
+
+/* Send non-empty audio state message. */
+int cras_non_empty_audio_send_msg(int32_t non_empty);
+
+/* Initialize non-empty audio handler. */
+int cras_non_empty_audio_handler_init();
+
+#endif /* CRAS_NON_EMPTY_AUDIO_HANDLER_H_ */
diff --git a/cras/src/server/cras_observer.c b/cras/src/server/cras_observer.c
index d0366d5..caff00d 100644
--- a/cras/src/server/cras_observer.c
+++ b/cras/src/server/cras_observer.c
@@ -26,12 +26,14 @@
struct cras_alert *node_left_right_swapped;
struct cras_alert *input_node_gain;
struct cras_alert *suspend_changed;
+ struct cras_alert *hotword_triggered;
/* 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_alert *non_empty_audio_state_changed;
};
struct cras_observer_server {
@@ -73,6 +75,15 @@
uint32_t num_active_streams;
};
+struct cras_observer_alert_data_hotword_triggered {
+ int64_t tv_sec;
+ int64_t tv_nsec;
+};
+
+struct cras_observer_non_empty_audio_state {
+ int non_empty;
+};
+
/* Global observer instance. */
static struct cras_observer_server *g_observer;
@@ -246,6 +257,36 @@
}
}
+static void hotword_triggered_alert(void *arg, void *data)
+{
+ struct cras_observer_client *client;
+ struct cras_observer_alert_data_hotword_triggered *triggered_data =
+ (struct cras_observer_alert_data_hotword_triggered *)data;
+
+ DL_FOREACH(g_observer->clients, client) {
+ if (client->ops.hotword_triggered)
+ client->ops.hotword_triggered(
+ client->context,
+ triggered_data->tv_sec,
+ triggered_data->tv_nsec);
+ }
+}
+
+static void non_empty_audio_state_changed_alert(void *arg, void *data)
+{
+ struct cras_observer_client *client;
+ struct cras_observer_non_empty_audio_state *non_empty_audio_data =
+ (struct cras_observer_non_empty_audio_state *)data;
+
+ DL_FOREACH(g_observer->clients, client) {
+ if (client->ops.non_empty_audio_state_changed) {
+ client->ops.non_empty_audio_state_changed(
+ client->context,
+ non_empty_audio_data->non_empty);
+ }
+ }
+}
+
static int cras_observer_server_set_alert(struct cras_alert **alert,
cras_alert_cb cb,
cras_alert_prepare prepare,
@@ -300,6 +341,8 @@
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(hotword_triggered, NULL, 0);
+ CRAS_OBSERVER_SET_ALERT(non_empty_audio_state_changed, NULL, 0);
CRAS_OBSERVER_SET_ALERT_WITH_DIRECTION(
num_active_streams, CRAS_STREAM_OUTPUT);
@@ -307,6 +350,7 @@
num_active_streams, CRAS_STREAM_INPUT);
CRAS_OBSERVER_SET_ALERT_WITH_DIRECTION(
num_active_streams, CRAS_STREAM_POST_MIX_PRE_DSP);
+
return 0;
error:
@@ -328,6 +372,8 @@
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.hotword_triggered);
+ cras_alert_destroy(g_observer->alerts.non_empty_audio_state_changed);
cras_alert_destroy(g_observer->alerts.num_active_streams[
CRAS_STREAM_OUTPUT]);
cras_alert_destroy(g_observer->alerts.num_active_streams[
@@ -505,3 +551,23 @@
cras_alert_pending_data(alert, &data, sizeof(data));
}
+
+void cras_observer_notify_hotword_triggered(int64_t tv_sec, int64_t tv_nsec)
+{
+ struct cras_observer_alert_data_hotword_triggered data;
+
+ data.tv_sec = tv_sec;
+ data.tv_nsec = tv_nsec;
+ cras_alert_pending_data(g_observer->alerts.hotword_triggered,
+ &data, sizeof(data));
+}
+
+void cras_observer_notify_non_empty_audio_state_changed(int non_empty)
+{
+ struct cras_observer_non_empty_audio_state data;
+
+ data.non_empty = non_empty;
+
+ cras_alert_pending_data(g_observer->alerts.non_empty_audio_state_changed,
+ &data, sizeof(data));
+}
\ No newline at end of file
diff --git a/cras/src/server/cras_observer.h b/cras/src/server/cras_observer.h
index bbbaf89..440a8ce 100644
--- a/cras/src/server/cras_observer.h
+++ b/cras/src/server/cras_observer.h
@@ -94,4 +94,10 @@
void cras_observer_notify_num_active_streams(enum CRAS_STREAM_DIRECTION dir,
uint32_t num_active_streams);
+/* Notify observers of the timestamp when hotword triggered. */
+void cras_observer_notify_hotword_triggered(int64_t tv_sec, int64_t tv_nsec);
+
+/* Notify observers the non-empty audio state changed. */
+void cras_observer_notify_non_empty_audio_state_changed(int active);
+
#endif /* CRAS_OBSERVER_H */
diff --git a/cras/src/server/cras_rclient.c b/cras/src/server/cras_rclient.c
index 6a68351..3131725 100644
--- a/cras/src/server/cras_rclient.c
+++ b/cras/src/server/cras_rclient.c
@@ -8,6 +8,7 @@
#include <syslog.h>
#include "audio_thread.h"
+#include "cras_apm_list.h"
#include "cras_config.h"
#include "cras_dsp.h"
#include "cras_iodev.h"
@@ -16,6 +17,7 @@
#include "cras_observer.h"
#include "cras_rclient.h"
#include "cras_rstream.h"
+#include "cras_server_metrics.h"
#include "cras_system_state.h"
#include "cras_types.h"
#include "cras_util.h"
@@ -38,7 +40,9 @@
int aud_fd)
{
struct cras_rstream *stream;
- struct cras_client_stream_connected reply;
+ struct cras_client_stream_connected stream_connected;
+ struct cras_client_stream_connected_old stream_connected_old;
+ struct cras_client_message *reply;
struct cras_audio_format remote_fmt;
struct cras_rstream_config stream_config;
int rc;
@@ -61,6 +65,7 @@
stream_config.direction = msg->direction;
stream_config.dev_idx = msg->dev_idx;
stream_config.flags = msg->flags;
+ stream_config.effects = msg->effects;
stream_config.format = &remote_fmt;
stream_config.buffer_frames = msg->buffer_frames;
stream_config.cb_threshold = msg->cb_threshold;
@@ -75,15 +80,27 @@
/* Tell client about the stream setup. */
syslog(LOG_DEBUG, "Send connected for stream %x\n", msg->stream_id);
- cras_fill_client_stream_connected(
- &reply,
- 0, /* No error. */
- msg->stream_id,
- &remote_fmt,
- cras_rstream_get_total_shm_size(stream));
+ if (msg->proto_version == CRAS_PROTO_VER) {
+ cras_fill_client_stream_connected(
+ &stream_connected,
+ 0, /* No error. */
+ msg->stream_id,
+ &remote_fmt,
+ cras_rstream_get_total_shm_size(stream),
+ cras_rstream_get_effects(stream));
+ reply = &stream_connected.header;
+ } else {
+ cras_fill_client_stream_connected_old(
+ &stream_connected_old,
+ 0, /* No error. */
+ msg->stream_id,
+ &remote_fmt,
+ cras_rstream_get_total_shm_size(stream));
+ reply = &stream_connected_old.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);
+ rc = cras_rclient_send_message(client, reply, stream_fds, 2);
if (rc < 0) {
syslog(LOG_ERR, "Failed to send connected messaged\n");
stream_list_rm(cras_iodev_list_get_stream_list(),
@@ -91,13 +108,25 @@
goto reply_err;
}
+ /* Metrics logs the stream configurations. */
+ cras_server_metrics_stream_config(&stream_config);
+
return 0;
reply_err:
/* Send the error code to the client. */
- cras_fill_client_stream_connected(&reply, rc, msg->stream_id,
- &remote_fmt, 0);
- cras_rclient_send_message(client, &reply.header, NULL, 0);
+ if (msg->proto_version == CRAS_PROTO_VER) {
+ cras_fill_client_stream_connected(
+ &stream_connected, rc, msg->stream_id,
+ &remote_fmt, 0, msg->effects);
+ reply = &stream_connected.header;
+ } else {
+ cras_fill_client_stream_connected_old(
+ &stream_connected_old, rc, msg->stream_id,
+ &remote_fmt, 0);
+ reply = &stream_connected_old.header;
+ }
+ cras_rclient_send_message(client, reply, NULL, 0);
if (aud_fd >= 0)
close(aud_fd);
@@ -128,6 +157,16 @@
cras_rclient_send_message(client, &msg.header, NULL, 0);
}
+/* Handles dumping audio snapshots to shared memory for the client. */
+static void dump_audio_thread_snapshots(struct cras_rclient *client)
+{
+ struct cras_client_audio_debug_info_ready msg;
+
+ cras_fill_client_audio_debug_info_ready(&msg);
+ cras_system_state_dump_snapshots();
+ 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)
{
@@ -369,15 +408,68 @@
/* Entry point for handling a message from the client. Called from the main
* server context. */
+int cras_rclient_buffer_from_client(struct cras_rclient *client,
+ const uint8_t *buf,
+ size_t buf_len,
+ int fd) {
+ struct cras_server_message *msg = (struct cras_server_message *)buf;
+
+ if (buf_len < sizeof(*msg))
+ return -EINVAL;
+ if (msg->length != buf_len)
+ return -EINVAL;
+ cras_rclient_message_from_client(client, msg, fd);
+ return 0;
+}
+
+static int direction_valid(enum CRAS_STREAM_DIRECTION direction)
+{
+ return direction < CRAS_NUM_DIRECTIONS &&
+ direction != CRAS_STREAM_UNDEFINED;
+}
+
+#define MSG_LEN_VALID(msg, type) ((msg)->length >= sizeof(type))
+
+/*
+ * Check if client is sending an old version of connect message
+ * and converts it to the correct cras_connect_message.
+ * Note that this is special check only for libcras transition in
+ * clients, from CRAS_PROTO_VER = 1 to 2.
+ * TODO(hychao): clean up the check once clients transition is done.
+ */
+static int is_connect_msg_old(const struct cras_server_message *msg,
+ struct cras_connect_message *cmsg)
+{
+ struct cras_connect_message_old *old;
+
+ if (!MSG_LEN_VALID(msg, struct cras_connect_message_old))
+ return 0;
+
+ old = (struct cras_connect_message_old *)msg;
+ if (old->proto_version + 1 != CRAS_PROTO_VER)
+ return 0;
+
+ memcpy(cmsg, old, sizeof(*old));
+ cmsg->effects = 0;
+ return 1;
+}
+
+/* Entry point for handling a message from the client. Called from the main
+ * server context. */
int cras_rclient_message_from_client(struct cras_rclient *client,
const struct cras_server_message *msg,
int fd) {
+ struct cras_connect_message cmsg;
+
assert(client && msg);
/* Most messages should not have a file descriptor. */
switch (msg->id) {
case CRAS_SERVER_CONNECT_STREAM:
break;
+ case CRAS_SERVER_SET_AEC_DUMP:
+ syslog(LOG_ERR, "client msg for APM debug, fd %d", fd);
+ break;
default:
if (fd != -1) {
syslog(LOG_ERR,
@@ -391,64 +483,97 @@
switch (msg->id) {
case CRAS_SERVER_CONNECT_STREAM:
- handle_client_stream_connect(client,
- (const struct cras_connect_message *)msg, fd);
+ if (MSG_LEN_VALID(msg, struct cras_connect_message)) {
+ handle_client_stream_connect(client,
+ (const struct cras_connect_message *)msg, fd);
+ } else if (is_connect_msg_old(msg, &cmsg)) {
+ handle_client_stream_connect(client, &cmsg, fd);
+ } else {
+ return -EINVAL;
+ }
break;
case CRAS_SERVER_DISCONNECT_STREAM:
+ if (!MSG_LEN_VALID(msg, struct cras_disconnect_stream_message))
+ return -EINVAL;
handle_client_stream_disconnect(client,
(const struct cras_disconnect_stream_message *)msg);
break;
case CRAS_SERVER_SET_SYSTEM_VOLUME:
+ if (!MSG_LEN_VALID(msg, struct cras_set_system_volume))
+ return -EINVAL;
cras_system_set_volume(
((const struct cras_set_system_volume *)msg)->volume);
break;
case CRAS_SERVER_SET_SYSTEM_MUTE:
+ if (!MSG_LEN_VALID(msg, struct cras_set_system_mute))
+ return -EINVAL;
cras_system_set_mute(
((const struct cras_set_system_mute *)msg)->mute);
break;
case CRAS_SERVER_SET_USER_MUTE:
+ if (!MSG_LEN_VALID(msg, struct cras_set_system_mute))
+ return -EINVAL;
cras_system_set_user_mute(
((const struct cras_set_system_mute *)msg)->mute);
break;
case CRAS_SERVER_SET_SYSTEM_MUTE_LOCKED:
+ if (!MSG_LEN_VALID(msg, struct cras_set_system_mute))
+ return -EINVAL;
cras_system_set_mute_locked(
((const struct cras_set_system_mute *)msg)->mute);
break;
case CRAS_SERVER_SET_SYSTEM_CAPTURE_GAIN: {
const struct cras_set_system_capture_gain *m =
(const struct cras_set_system_capture_gain *)msg;
+ if (!MSG_LEN_VALID(msg, struct cras_set_system_capture_gain))
+ return -EINVAL;
cras_system_set_capture_gain(m->gain);
break;
}
case CRAS_SERVER_SET_SYSTEM_CAPTURE_MUTE:
+ if (!MSG_LEN_VALID(msg, struct cras_set_system_mute))
+ return -EINVAL;
cras_system_set_capture_mute(
((const struct cras_set_system_mute *)msg)->mute);
break;
case CRAS_SERVER_SET_SYSTEM_CAPTURE_MUTE_LOCKED:
+ if (!MSG_LEN_VALID(msg, struct cras_set_system_mute))
+ return -EINVAL;
cras_system_set_capture_mute_locked(
((const struct cras_set_system_mute *)msg)->mute);
break;
case CRAS_SERVER_SET_NODE_ATTR: {
const struct cras_set_node_attr *m =
(const struct cras_set_node_attr *)msg;
+ if (!MSG_LEN_VALID(msg, struct cras_set_node_attr))
+ return -EINVAL;
cras_iodev_list_set_node_attr(m->node_id, m->attr, m->value);
break;
}
case CRAS_SERVER_SELECT_NODE: {
const struct cras_select_node *m =
(const struct cras_select_node *)msg;
+ if (!MSG_LEN_VALID(msg, struct cras_select_node) ||
+ !direction_valid(m->direction))
+ return -EINVAL;
cras_iodev_list_select_node(m->direction, m->node_id);
break;
}
case CRAS_SERVER_ADD_ACTIVE_NODE: {
const struct cras_add_active_node *m =
(const struct cras_add_active_node *)msg;
+ if (!MSG_LEN_VALID(msg, struct cras_add_active_node) ||
+ !direction_valid(m->direction))
+ return -EINVAL;
cras_iodev_list_add_active_node(m->direction, m->node_id);
break;
}
case CRAS_SERVER_RM_ACTIVE_NODE: {
const struct cras_rm_active_node *m =
(const struct cras_rm_active_node *)msg;
+ if (!MSG_LEN_VALID(msg, struct cras_rm_active_node) ||
+ !direction_valid(m->direction))
+ return -EINVAL;
cras_iodev_list_rm_active_node(m->direction, m->node_id);
break;
}
@@ -461,15 +586,22 @@
case CRAS_SERVER_DUMP_AUDIO_THREAD:
dump_audio_thread_info(client);
break;
+ case CRAS_SERVER_DUMP_SNAPSHOTS:
+ dump_audio_thread_snapshots(client);
+ break;
case CRAS_SERVER_ADD_TEST_DEV: {
const struct cras_add_test_dev *m =
(const struct cras_add_test_dev *)msg;
+ if (!MSG_LEN_VALID(msg, struct cras_add_test_dev))
+ return -EINVAL;
cras_iodev_list_add_test_dev(m->type);
break;
}
case CRAS_SERVER_TEST_DEV_COMMAND: {
const struct cras_test_dev_command *m =
(const struct cras_test_dev_command *)msg;
+ if (!MSG_LEN_VALID(msg, struct cras_test_dev_command))
+ return -EINVAL;
cras_iodev_list_test_dev_command(
m->iodev_idx, (enum CRAS_TEST_IODEV_CMD)m->command,
m->data_len, m->data);
@@ -484,6 +616,14 @@
case CRAS_CONFIG_GLOBAL_REMIX: {
const struct cras_config_global_remix *m =
(const struct cras_config_global_remix *)msg;
+ if (!MSG_LEN_VALID(msg, struct cras_config_global_remix) ||
+ m->num_channels > CRAS_MAX_REMIX_CHANNELS)
+ return -EINVAL;
+ size_t size_with_coefficients = sizeof(*m) +
+ m->num_channels * m->num_channels *
+ sizeof(m->coefficient[0]);
+ if (size_with_coefficients != msg->length)
+ return -EINVAL;
audio_thread_config_global_remix(
cras_iodev_list_get_audio_thread(),
m->num_channels,
@@ -491,6 +631,8 @@
break;
}
case CRAS_SERVER_GET_HOTWORD_MODELS: {
+ if (!MSG_LEN_VALID(msg, struct cras_get_hotword_models))
+ return -EINVAL;
handle_get_hotword_models(client,
((const struct cras_get_hotword_models *)msg)->node_id);
break;
@@ -498,6 +640,8 @@
case CRAS_SERVER_SET_HOTWORD_MODEL: {
const struct cras_set_hotword_model *m =
(const struct cras_set_hotword_model *)msg;
+ if (!MSG_LEN_VALID(msg, struct cras_set_hotword_model))
+ return -EINVAL;
cras_iodev_list_set_hotword_model(m->node_id,
m->model_name);
break;
@@ -505,11 +649,27 @@
case CRAS_SERVER_REGISTER_NOTIFICATION: {
const struct cras_register_notification *m =
(struct cras_register_notification *)msg;
+ if (!MSG_LEN_VALID(msg, struct cras_register_notification))
+ return -EINVAL;
register_for_notification(
client, (enum CRAS_CLIENT_MESSAGE_ID)m->msg_id,
m->do_register);
break;
}
+ case CRAS_SERVER_SET_AEC_DUMP: {
+ const struct cras_set_aec_dump *m =
+ (const struct cras_set_aec_dump *)msg;
+ if (!MSG_LEN_VALID(msg, struct cras_set_aec_dump))
+ return -EINVAL;
+ audio_thread_set_aec_dump(
+ cras_iodev_list_get_audio_thread(),
+ m->stream_id,
+ m->start, fd);
+ break;
+ }
+ case CRAS_SERVER_RELOAD_AEC_CONFIG:
+ cras_apm_list_reload_aec_config();
+ break;
default:
break;
}
diff --git a/cras/src/server/cras_rclient.h b/cras/src/server/cras_rclient.h
index 26e4f3b..af0457f 100644
--- a/cras/src/server/cras_rclient.h
+++ b/cras/src/server/cras_rclient.h
@@ -9,8 +9,10 @@
#ifndef CRAS_RCLIENT_H_
#define CRAS_RCLIENT_H_
+struct cras_client_message;
struct cras_message;
struct cras_rclient;
+struct cras_server_message;
/* Creates an rclient structure.
* Args:
@@ -40,6 +42,22 @@
const struct cras_server_message *msg,
int fd);
+/* Handles a received buffer from the client.
+ * Args:
+ * client - The client that received this message.
+ * buf - The raw byte buffer the client sent. It should contain a valid
+ * cras_server_message.
+ * buf_len - The length of |buf|.
+ * fd - The file descriptor that was sent by the remote client (or -1 if no
+ * file descriptor was sent).
+ * Returns:
+ * 0 on success, otherwise a negative error code.
+ */
+int cras_rclient_buffer_from_client(struct cras_rclient *client,
+ const uint8_t *buf,
+ size_t buf_len,
+ int fd);
+
/* Sends a message to the client.
* Args:
* client - The client to send the message to.
diff --git a/cras/src/server/cras_rstream.c b/cras/src/server/cras_rstream.c
index 8b9b046..d527dd7 100644
--- a/cras/src/server/cras_rstream.c
+++ b/cras/src/server/cras_rstream.c
@@ -128,6 +128,85 @@
return 0;
}
+/*
+ * Setting pending reply is only needed inside this module.
+ */
+static void set_pending_reply(struct cras_rstream *stream)
+{
+ cras_shm_set_callback_pending(&stream->shm, 1);
+}
+
+/*
+ * Clearing pending reply is only needed inside this module.
+ */
+static void clear_pending_reply(struct cras_rstream *stream)
+{
+ cras_shm_set_callback_pending(&stream->shm, 0);
+}
+
+/*
+ * Reads one response of audio request from client.
+ * Args:
+ * stream[in]: A pointer to cras_rstream.
+ * msg[out]: A pointer to audio_message to hold the message.
+ * Returns:
+ * Number of bytes read from the socket.
+ * A negative error code if read fails or the message from client
+ * has errors.
+ */
+static int get_audio_request_reply(
+ const struct cras_rstream *stream, struct audio_message *msg)
+{
+ int rc;
+
+ rc = read(stream->fd, msg, sizeof(*msg));
+ if (rc < 0)
+ return -errno;
+ if (rc == 0)
+ return rc;
+ if (msg->error < 0)
+ return msg->error;
+ return rc;
+}
+
+/*
+ * Reads and handles one audio message from client.
+ * Returns:
+ * Number of bytes read from the socket.
+ * A negative error code if read fails or the message from client
+ * has errors.
+ */
+static int read_and_handle_client_message(struct cras_rstream *stream) {
+
+ struct audio_message msg;
+ int rc;
+
+ rc = get_audio_request_reply(stream, &msg);
+ if (rc <= 0) {
+ syslog(LOG_ERR, "Got error from client: rc: %d", rc);
+ clear_pending_reply(stream);
+ return rc;
+ }
+
+ /*
+ * Got client reply that data in the input stream is captured.
+ */
+ if (stream->direction == CRAS_STREAM_INPUT &&
+ msg.id == AUDIO_MESSAGE_DATA_CAPTURED) {
+ clear_pending_reply(stream);
+ }
+
+ /*
+ * Got client reply that data for output stream is ready in shm.
+ */
+ if (stream->direction == CRAS_STREAM_OUTPUT &&
+ msg.id == AUDIO_MESSAGE_DATA_READY) {
+ clear_pending_reply(stream);
+ }
+
+ return rc;
+}
+
/* Exported functions */
int cras_rstream_create(struct cras_rstream_config *config,
@@ -171,6 +250,9 @@
}
stream->buf_state = buffer_share_create(stream->buffer_frames);
+ stream->apm_list = (stream->direction == CRAS_STREAM_INPUT)
+ ? cras_apm_list_create(stream, config->effects)
+ : NULL;
syslog(LOG_DEBUG, "stream %x frames %zu, cb_thresh %zu",
config->stream_id, config->buffer_frames, config->cb_threshold);
@@ -192,9 +274,32 @@
cras_audio_area_destroy(stream->audio_area);
}
buffer_share_destroy(stream->buf_state);
+ if (stream->apm_list)
+ cras_apm_list_destroy(stream->apm_list);
free(stream);
}
+unsigned int cras_rstream_get_effects(const struct cras_rstream *stream)
+{
+ return stream->apm_list
+ ? cras_apm_list_get_effects(stream->apm_list)
+ : 0;
+}
+
+struct cras_audio_format *cras_rstream_post_processing_format(
+ const struct cras_rstream *stream, void *dev_ptr)
+{
+ struct cras_apm *apm;
+
+ if (NULL == stream->apm_list)
+ return NULL;
+
+ apm = cras_apm_list_get(stream->apm_list, dev_ptr);
+ if (NULL == apm)
+ return NULL;
+ return cras_apm_list_get_format(apm);
+}
+
void cras_rstream_record_fetch_interval(struct cras_rstream *rstream,
const struct timespec *now)
{
@@ -233,6 +338,9 @@
rc = write(stream->fd, &msg, sizeof(msg));
if (rc < 0)
return -errno;
+
+ set_pending_reply(stream);
+
return rc;
}
@@ -243,26 +351,22 @@
cras_shm_buffer_write_complete(&stream->shm);
+ /* Mark shm as used. */
+ if (stream_is_server_only(stream)) {
+ cras_shm_buffer_read_current(&stream->shm, count);
+ return 0;
+ }
+
init_audio_message(&msg, AUDIO_MESSAGE_DATA_READY, count);
rc = write(stream->fd, &msg, sizeof(msg));
if (rc < 0)
return -errno;
+
+ set_pending_reply(stream);
+
return rc;
}
-int cras_rstream_get_audio_request_reply(const struct cras_rstream *stream)
-{
- struct audio_message msg;
- int rc;
-
- rc = read(stream->fd, &msg, sizeof(msg));
- if (rc < 0)
- return -errno;
- if (msg.error < 0)
- return msg.error;
- return 0;
-}
-
void cras_rstream_dev_attach(struct cras_rstream *rstream,
unsigned int dev_id,
void *dev_ptr)
@@ -365,3 +469,32 @@
{
return cras_shm_get_mute(&rstream->shm);
}
+
+int cras_rstream_is_pending_reply(const struct cras_rstream *stream)
+{
+ return cras_shm_callback_pending(&stream->shm);
+}
+
+int cras_rstream_flush_old_audio_messages(struct cras_rstream *stream)
+{
+ struct pollfd pollfd;
+ int err;
+
+ if (!stream->fd)
+ return 0;
+
+ if (stream_is_server_only(stream))
+ return 0;
+
+ pollfd.fd = stream->fd;
+ pollfd.events = POLLIN;
+
+ do {
+ err = poll(&pollfd, 1, 0);
+ if (pollfd.revents & POLLIN) {
+ err = read_and_handle_client_message(stream);
+ }
+ } while (err > 0);
+
+ return 0;
+}
diff --git a/cras/src/server/cras_rstream.h b/cras/src/server/cras_rstream.h
index 1a22717..d19fc47 100644
--- a/cras/src/server/cras_rstream.h
+++ b/cras/src/server/cras_rstream.h
@@ -9,6 +9,7 @@
#ifndef CRAS_RSTREAM_H_
#define CRAS_RSTREAM_H_
+#include "cras_apm_list.h"
#include "cras_shm.h"
#include "cras_types.h"
@@ -59,10 +60,12 @@
* last_fetch_ts - The time of the last stream fetch.
* longest_fetch_interval_ts - Longest interval between two fetches.
* buf_state - State of the buffer from all devices for this stream.
+ * apm_list - List of audio processing module instances.
* num_attached_devs - Number of iodevs this stream has attached to.
* queued_frames - Cached value of the number of queued frames in shm.
* is_pinned - True if the stream is a pinned stream, false otherwise.
* pinned_dev_idx - device the stream is pinned, 0 if none.
+ * triggered - True if already notified TRIGGER_ONLY stream, false otherwise.
*/
struct cras_rstream {
cras_stream_id_t stream_id;
@@ -84,10 +87,12 @@
struct timespec last_fetch_ts;
struct timespec longest_fetch_interval;
struct buffer_share *buf_state;
+ struct cras_apm_list *apm_list;
int num_attached_devs;
int queued_frames;
int is_pinned;
uint32_t pinned_dev_idx;
+ int triggered;
struct cras_rstream *prev, *next;
};
@@ -96,6 +101,7 @@
* direction - CRAS_STREAM_OUTPUT or CRAS_STREAM_INPUT.
* dev_idx - Pin to this device if != NO_DEVICE.
* flags - Any special handling for this stream.
+ * effects - Bit map of effects to be enabled on this stream.
* format - The audio format the stream wishes to use.
* buffer_frames - Total number of audio frames to buffer.
* cb_threshold - # of frames when to request more from the client.
@@ -108,6 +114,7 @@
enum CRAS_STREAM_DIRECTION direction;
uint32_t dev_idx;
uint32_t flags;
+ uint32_t effects;
const struct cras_audio_format *format;
size_t buffer_frames;
size_t cb_threshold;
@@ -129,6 +136,13 @@
/* Destroys an rstream. */
void cras_rstream_destroy(struct cras_rstream *stream);
+/* Gets the id of the stream */
+static inline cras_stream_id_t cras_rstream_id(
+ const struct cras_rstream *stream)
+{
+ return stream->stream_id;
+}
+
/* Gets the total buffer size in frames for the given client stream. */
static inline size_t cras_rstream_get_buffer_frames(
const struct cras_rstream *stream)
@@ -250,6 +264,18 @@
return cras_stream_uses_input_hw(s->direction);
}
+static inline int stream_is_server_only(const struct cras_rstream *s)
+{
+ return s->flags & SERVER_ONLY;
+}
+
+/* Gets the enabled effects of this stream. */
+unsigned int cras_rstream_get_effects(const struct cras_rstream *stream);
+
+/* Gets the format of data after stream specific processing. */
+struct cras_audio_format *cras_rstream_post_processing_format(
+ const struct cras_rstream *stream, void *dev_ptr);
+
/* Checks how much time has passed since last stream fetch and records
* the longest fetch interval. */
void cras_rstream_record_fetch_interval(struct cras_rstream *rstream,
@@ -261,8 +287,6 @@
/* Tells a capture client that count frames are ready. */
int cras_rstream_audio_ready(struct cras_rstream *stream, size_t count);
-/* Waits for the response to a request for audio. */
-int cras_rstream_get_audio_request_reply(const struct cras_rstream *stream);
/* Let the rstream know when a device is added or removed. */
void cras_rstream_dev_attach(struct cras_rstream *rstream,
@@ -314,4 +338,18 @@
/* Returns non-zero if the stream is muted. */
int cras_rstream_get_mute(const struct cras_rstream *rstream);
+/*
+ * Returns non-zero if the stream is pending a reply from client.
+ * - For playback, stream is waiting for AUDIO_MESSAGE_DATA_READY message from
+ * client.
+ * - For capture, stream is waiting for AUDIO_MESSAGE_DATA_CAPTURED message
+ * from client.
+ */
+int cras_rstream_is_pending_reply(const struct cras_rstream *stream);
+
+/*
+ * Reads any pending audio message from the socket.
+ */
+int cras_rstream_flush_old_audio_messages(struct cras_rstream *stream);
+
#endif /* CRAS_RSTREAM_H_ */
diff --git a/cras/src/server/cras_server.c b/cras/src/server/cras_server.c
index ff5e601..51f3507 100644
--- a/cras/src/server/cras_server.c
+++ b/cras/src/server/cras_server.c
@@ -34,12 +34,15 @@
#include "cras_telephony.h"
#endif
#include "cras_alert.h"
+#include "cras_audio_thread_monitor.h"
#include "cras_config.h"
#include "cras_device_monitor.h"
+#include "cras_hotword_handler.h"
#include "cras_iodev_list.h"
#include "cras_main_message.h"
#include "cras_messages.h"
#include "cras_metrics.h"
+#include "cras_non_empty_audio_handler.h"
#include "cras_observer.h"
#include "cras_rclient.h"
#include "cras_server.h"
@@ -88,11 +91,19 @@
struct client_callback *prev, *next;
};
+/* Stores callback function and argument data to be executed later. */
+struct system_task {
+ void (*callback)(void *);
+ void *callback_data;
+ struct system_task *next, *prev;
+};
+
/* Local server data. */
struct server_data {
struct attached_client *clients_head;
size_t num_clients;
struct client_callback *client_callbacks;
+ struct system_task *system_tasks;
size_t num_client_callbacks;
size_t next_client_id;
} server_instance;
@@ -114,18 +125,15 @@
static void handle_message_from_client(struct attached_client *client)
{
uint8_t buf[CRAS_SERV_MAX_MSG_SIZE];
- struct cras_server_message *msg;
int nread;
int fd;
unsigned int num_fds = 1;
- msg = (struct cras_server_message *)buf;
nread = cras_recv_with_fds(client->fd, buf, sizeof(buf), &fd, &num_fds);
- if (nread < sizeof(msg->length))
+ if (nread < 0)
+ goto read_error;
+ if (cras_rclient_buffer_from_client(client->client, buf, nread, fd) < 0)
goto read_error;
- if (msg->length != nread)
- goto read_error;
- cras_rclient_message_from_client(client->client, msg, fd);
return;
read_error:
@@ -291,6 +299,31 @@
client_cb->deleted = 1;
}
+/* Creates a new task entry and append to system_tasks list, which will be
+ * executed in main loop later without wait time.
+ */
+static int add_task(void (*cb)(void *data),
+ void *callback_data,
+ void *server_data)
+{
+ struct server_data *serv;
+ struct system_task *new_task;
+
+ serv = (struct server_data *)server_data;
+ if (serv == NULL)
+ return -EINVAL;
+
+ new_task = (struct system_task *)calloc(1, sizeof(*new_task));
+ if (new_task == NULL)
+ return -ENOMEM;
+
+ new_task->callback = cb;
+ new_task->callback_data = callback_data;
+
+ DL_APPEND(serv->system_tasks, new_task);
+ return 0;
+}
+
/* Cleans up the file descriptor list removing items deleted during the main
* loop iteration. */
static void cleanup_select_fds(void *server_data)
@@ -381,6 +414,8 @@
/* Log to syslog. */
openlog("cras_server", LOG_PID, LOG_USER);
+ server_instance.next_client_id = RESERVED_CLIENT_IDS;
+
/* Initialize global observer. */
cras_observer_server_init();
@@ -392,6 +427,7 @@
* from the list that are passed to select in the main loop below. */
cras_system_set_select_handler(add_select_fd, rm_select_fd,
&server_instance);
+ cras_system_set_add_task_handler(add_task, &server_instance);
cras_main_message_init();
return 0;
@@ -409,8 +445,10 @@
struct sockaddr_un addr;
struct attached_client *elm;
struct client_callback *client_cb;
+ struct system_task *tasks;
+ struct system_task *system_task;
struct cras_tm *tm;
- struct timespec ts;
+ struct timespec ts, *poll_timeout;
int timers_active;
struct pollfd *pollfds;
unsigned int pollfds_size = 32;
@@ -427,6 +465,12 @@
cras_device_monitor_init();
+ cras_hotword_handler_init();
+
+ cras_non_empty_audio_handler_init();
+
+ cras_audio_thread_monitor_init();
+
#ifdef CRAS_DBUS
dbus_threads_init_default();
dbus_conn = cras_dbus_connect_system_bus();
@@ -527,10 +571,26 @@
num_pollfds++;
}
+ tasks = server_instance.system_tasks;
+ server_instance.system_tasks = NULL;
+ DL_FOREACH(tasks, system_task) {
+ system_task->callback(system_task->callback_data);
+ DL_DELETE(tasks, system_task);
+ free(system_task);
+ }
+
timers_active = cras_tm_get_next_timeout(tm, &ts);
- rc = ppoll(pollfds, num_pollfds,
- timers_active ? &ts : NULL, NULL);
+ /*
+ * If new client task has been scheduled, no need to wait
+ * for timeout, just do another loop to execute them.
+ */
+ if (server_instance.system_tasks)
+ poll_timeout = NULL;
+ else
+ poll_timeout = timers_active ? &ts : NULL;
+
+ rc = ppoll(pollfds, num_pollfds, poll_timeout, NULL);
if (rc < 0)
continue;
diff --git a/cras/src/server/cras_server.h b/cras/src/server/cras_server.h
index aff9b7a..d17314b 100644
--- a/cras/src/server/cras_server.h
+++ b/cras/src/server/cras_server.h
@@ -16,6 +16,10 @@
#define CRAS_SERVER_PROFILE_MASK_HSP (1 << 1)
#define CRAS_SERVER_PROFILE_MASK_A2DP (1 << 2)
+/* Reserver client id 0-15 for internal server usage. */
+#define RESERVED_CLIENT_IDS 16
+#define SERVER_STREAM_CLIENT_ID 0
+
struct cras_client_message;
/* Initialize some server setup. Mainly to add the select handler first
diff --git a/cras/src/server/cras_server_metrics.c b/cras/src/server/cras_server_metrics.c
index c824407..c78ad64 100644
--- a/cras/src/server/cras_server_metrics.c
+++ b/cras/src/server/cras_server_metrics.c
@@ -10,25 +10,49 @@
#include "cras_metrics.h"
#include "cras_main_message.h"
+#include "cras_rstream.h"
+const char kHighestInputHardwareLevel[] = "Cras.HighestInputHardwareLevel";
+const char kHighestOutputHardwareLevel[] = "Cras.HighestOutputHardwareLevel";
const char kNoCodecsFoundMetric[] = "Cras.NoCodecsFoundAtBoot";
const char kStreamTimeoutMilliSeconds[] = "Cras.StreamTimeoutMilliSeconds";
+const char kStreamCallbackThreshold[] = "Cras.StreamCallbackThreshold";
+const char kStreamFlags[] = "Cras.StreamFlags";
+const char kStreamSamplingFormat[] = "Cras.StreamSamplingFormat";
+const char kStreamSamplingRate[] = "Cras.StreamSamplingRate";
+const char kUnderrunsPerDevice[] = "Cras.UnderrunsPerDevice";
/* Type of metrics to log. */
enum CRAS_SERVER_METRICS_TYPE {
+ HIGHEST_INPUT_HW_LEVEL,
+ HIGHEST_OUTPUT_HW_LEVEL,
LONGEST_FETCH_DELAY,
+ NUM_UNDERRUNS,
+ STREAM_CONFIG
+};
+
+struct cras_server_metrics_stream_config {
+ unsigned cb_threshold;
+ unsigned flags;
+ int format;
+ unsigned rate;
+};
+
+union cras_server_metrics_data {
+ unsigned value;
+ struct cras_server_metrics_stream_config stream_config;
};
struct cras_server_metrics_message {
struct cras_main_message header;
enum CRAS_SERVER_METRICS_TYPE metrics_type;
- unsigned data;
+ union cras_server_metrics_data data;
};
-static void init_longest_fetch_delay_msg(
+static void init_server_metrics_msg(
struct cras_server_metrics_message *msg,
enum CRAS_SERVER_METRICS_TYPE type,
- unsigned data)
+ union cras_server_metrics_data data)
{
memset(msg, 0, sizeof(*msg));
msg->header.type = CRAS_MAIN_METRICS;
@@ -37,32 +61,112 @@
msg->data = data;
}
-int cras_server_metrics_longest_fetch_delay(unsigned delay_msec)
+int cras_server_metrics_highest_hw_level(unsigned hw_level,
+ enum CRAS_STREAM_DIRECTION direction)
{
struct cras_server_metrics_message msg;
+ union cras_server_metrics_data data;
int err;
- init_longest_fetch_delay_msg(&msg, LONGEST_FETCH_DELAY, delay_msec);
+ data.value = hw_level;
+
+ switch (direction) {
+ case CRAS_STREAM_INPUT:
+ init_server_metrics_msg(&msg, HIGHEST_INPUT_HW_LEVEL, data);
+ break;
+ case CRAS_STREAM_OUTPUT:
+ init_server_metrics_msg(&msg, HIGHEST_OUTPUT_HW_LEVEL, data);
+ break;
+ default:
+ return 0;
+ }
+
err = cras_main_message_send((struct cras_main_message *)&msg);
if (err < 0) {
- syslog(LOG_ERR, "Failed to send metrics message");
+ syslog(LOG_ERR,
+ "Failed to send metrics message: HIGHEST_HW_LEVEL");
return err;
}
return 0;
}
-static void metrics_longest_fetch_delay(unsigned delay_msec)
+int cras_server_metrics_longest_fetch_delay(unsigned delay_msec)
{
- static const int fetch_delay_min_msec = 1;
- static const int fetch_delay_max_msec = 10000;
- static const int fetch_delay_nbuckets = 10;
+ struct cras_server_metrics_message msg;
+ union cras_server_metrics_data data;
+ int err;
- cras_metrics_log_histogram(kStreamTimeoutMilliSeconds,
- delay_msec,
- fetch_delay_min_msec,
- fetch_delay_max_msec,
- fetch_delay_nbuckets);
+ data.value = delay_msec;
+ init_server_metrics_msg(&msg, LONGEST_FETCH_DELAY, data);
+ err = cras_main_message_send((struct cras_main_message *)&msg);
+ if (err < 0) {
+ syslog(LOG_ERR,
+ "Failed to send metrics message: LONGEST_FETCH_DELAY");
+ return err;
+ }
+
+ return 0;
+}
+
+int cras_server_metrics_num_underruns(unsigned num_underruns)
+{
+ struct cras_server_metrics_message msg;
+ union cras_server_metrics_data data;
+ int err;
+
+ data.value = num_underruns;
+ init_server_metrics_msg(&msg, NUM_UNDERRUNS, data);
+ err = cras_main_message_send((struct cras_main_message *)&msg);
+ if (err < 0) {
+ syslog(LOG_ERR,
+ "Failed to send metrics message: NUM_UNDERRUNS");
+ return err;
+ }
+
+ return 0;
+}
+
+int cras_server_metrics_stream_config(struct cras_rstream_config *config)
+{
+ struct cras_server_metrics_message msg;
+ union cras_server_metrics_data data;
+ int err;
+
+ data.stream_config.cb_threshold = (unsigned)config->cb_threshold;
+ data.stream_config.flags = (unsigned)config->flags;
+ data.stream_config.format = (int)config->format->format;
+ data.stream_config.rate = (unsigned)config->format->frame_rate;
+
+ init_server_metrics_msg(&msg, STREAM_CONFIG, data);
+ err = cras_main_message_send((struct cras_main_message *)&msg);
+ if (err < 0) {
+ syslog(LOG_ERR,
+ "Failed to send metrics message: STREAM_CONFIG");
+ return err;
+ }
+
+ return 0;
+}
+
+static void metrics_stream_config(
+ struct cras_server_metrics_stream_config config)
+{
+ /* Logs stream callback threshold. */
+ cras_metrics_log_sparse_histogram(kStreamCallbackThreshold,
+ config.cb_threshold);
+
+ /* Logs stream flags. */
+ cras_metrics_log_sparse_histogram(kStreamFlags,
+ config.flags);
+
+ /* Logs stream sampling format. */
+ cras_metrics_log_sparse_histogram(kStreamSamplingFormat,
+ config.format);
+
+ /* Logs stream sampling rate. */
+ cras_metrics_log_sparse_histogram(kStreamSamplingRate,
+ config.rate);
}
static void handle_metrics_message(struct cras_main_message *msg, void *arg)
@@ -70,8 +174,24 @@
struct cras_server_metrics_message *metrics_msg =
(struct cras_server_metrics_message *)msg;
switch (metrics_msg->metrics_type) {
+ case HIGHEST_INPUT_HW_LEVEL:
+ cras_metrics_log_histogram(kHighestInputHardwareLevel,
+ metrics_msg->data.value, 1, 10000, 20);
+ break;
+ case HIGHEST_OUTPUT_HW_LEVEL:
+ cras_metrics_log_histogram(kHighestOutputHardwareLevel,
+ metrics_msg->data.value, 1, 10000, 20);
+ break;
case LONGEST_FETCH_DELAY:
- metrics_longest_fetch_delay(metrics_msg->data);
+ cras_metrics_log_histogram(kStreamTimeoutMilliSeconds,
+ metrics_msg->data.value, 1, 20000, 10);
+ break;
+ case NUM_UNDERRUNS:
+ cras_metrics_log_histogram(kUnderrunsPerDevice,
+ metrics_msg->data.value, 0, 1000, 10);
+ break;
+ case STREAM_CONFIG:
+ metrics_stream_config(metrics_msg->data.stream_config);
break;
default:
syslog(LOG_ERR, "Unknown metrics type %u",
diff --git a/cras/src/server/cras_server_metrics.h b/cras/src/server/cras_server_metrics.h
index b64614f..f2b206a 100644
--- a/cras/src/server/cras_server_metrics.h
+++ b/cras/src/server/cras_server_metrics.h
@@ -6,12 +6,31 @@
#ifndef CRAS_SERVER_METRICS_H_
#define CRAS_SERVER_METRICS_H_
+#include "cras_rstream.h"
+
extern const char kNoCodecsFoundMetric[];
+extern const char kHighestInputHardwareLevel[];
+extern const char kHighestOutputHardwareLevel[];
extern const char kStreamTimeoutMilliSeconds[];
+extern const char kStreamCallbackThreshold[];
+extern const char kStreamFlags[];
+extern const char kStreamSamplingFormat[];
+extern const char kStreamSamplingRate[];
+extern const char kUnderrunsPerDevice[];
+
+/* Logs the highest hardware level of a device. */
+int cras_server_metrics_highest_hw_level(unsigned hw_level,
+ enum CRAS_STREAM_DIRECTION direction);
/* Logs the longest fetch delay of a stream in millisecond. */
int cras_server_metrics_longest_fetch_delay(int delay_msec);
+/* Logs the number of underruns of a device. */
+int cras_server_metrics_num_underruns(unsigned num_underruns);
+
+/* Logs the stream configurations from clients. */
+int cras_server_metrics_stream_config(struct cras_rstream_config *config);
+
/* Initialize metrics logging stuff. */
int cras_server_metrics_init();
diff --git a/cras/src/server/cras_system_state.c b/cras/src/server/cras_system_state.c
index 3483ebc..1eaa5ec 100644
--- a/cras/src/server/cras_system_state.c
+++ b/cras/src/server/cras_system_state.c
@@ -13,6 +13,7 @@
#include <syslog.h>
#include "cras_alsa_card.h"
+#include "cras_board_config.h"
#include "cras_config.h"
#include "cras_device_blacklist.h"
#include "cras_observer.h"
@@ -34,6 +35,7 @@
* 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.
+ * This copy is to dup and pass to clients.
* 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
@@ -43,6 +45,8 @@
* update_lock - Protects the update_count, as audio threads can update the
* stream count.
* tm - The system-wide timer manager.
+ * add_task - Function to handle adding a task for main thread to execute.
+ * task_data - Data to be passed to add_task handler function.
*/
static struct {
struct cras_server_state *exp_state;
@@ -61,35 +65,38 @@
void *cb_data, void *select_data);
void (*fd_rm)(int fd, void *select_data);
void *select_data;
+ int (*add_task)(void (*callback)(void *data),
+ void *callback_data,
+ void *task_data);
+ void *task_data;
+ struct cras_audio_thread_snapshot_buffer snapshot_buffer;
} state;
/*
* Exported Interface.
*/
-void cras_system_state_init(const char *device_config_dir)
+void cras_system_state_init(const char *device_config_dir,
+ const char *shm_name,
+ int rw_shm_fd,
+ int ro_shm_fd,
+ struct cras_server_state *exp_state,
+ size_t exp_state_size)
{
- struct cras_server_state *exp_state;
+ struct cras_board_config board_config;
int rc;
+ assert(sizeof(*exp_state) == exp_state_size);
state.shm_size = sizeof(*exp_state);
- 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);
+ strncpy(state.shm_name, shm_name, sizeof(state.shm_name));
+ state.shm_name[sizeof(state.shm_name) - 1] = '\0';
+ state.shm_fd = rw_shm_fd;
+ state.shm_fd_ro = ro_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);
+ /* Read board config. */
+ memset(&board_config, 0, sizeof(board_config));
+ cras_board_config_get(device_config_dir, &board_config);
/* Initial system state. */
exp_state->state_version = CRAS_SERVER_STATE_VERSION;
@@ -106,6 +113,10 @@
exp_state->min_capture_gain = DEFAULT_MIN_CAPTURE_GAIN;
exp_state->max_capture_gain = DEFAULT_MAX_CAPTURE_GAIN;
exp_state->num_streams_attached = 0;
+ exp_state->default_output_buffer_size =
+ board_config.default_output_buffer_size;
+ exp_state->aec_supported =
+ board_config.aec_supported;
if ((rc = pthread_mutex_init(&state.update_lock, 0) != 0)) {
syslog(LOG_ERR, "Fatal: system state mutex init");
@@ -130,6 +141,10 @@
/* Read config file for blacklisted devices. */
state.device_blacklist =
cras_device_blacklist_create(CRAS_CONFIG_FILE_DIR);
+
+ /* Initialize snapshot buffer memory */
+ memset(&state.snapshot_buffer, 0,
+ sizeof(struct cras_audio_thread_snapshot_buffer));
}
void cras_system_state_set_internal_ucm_suffix(const char *internal_ucm_suffix)
@@ -193,15 +208,23 @@
void cras_system_set_user_mute(int mute)
{
+ int current_mute = cras_system_get_mute();
+
if (state.exp_state->user_mute == !!mute)
return;
state.exp_state->user_mute = !!mute;
+
+ if (current_mute == (mute || state.exp_state->mute))
+ return;
+
cras_system_notify_mute();
}
void cras_system_set_mute(int mute)
{
+ int current_mute = cras_system_get_mute();
+
if (state.exp_state->mute_locked)
return;
@@ -209,6 +232,10 @@
return;
state.exp_state->mute = !!mute;
+
+ if (current_mute == (mute || state.exp_state->user_mute))
+ return;
+
cras_system_notify_mute();
}
@@ -218,7 +245,6 @@
return;
state.exp_state->mute_locked = !!locked;
- cras_system_notify_mute();
}
int cras_system_get_mute()
@@ -317,6 +343,16 @@
return state.exp_state->max_capture_gain;
}
+int cras_system_get_default_output_buffer_size()
+{
+ return state.exp_state->default_output_buffer_size;
+}
+
+int cras_system_get_aec_supported()
+{
+ return state.exp_state->aec_supported;
+}
+
int cras_system_add_alsa_card(struct cras_alsa_card_info *alsa_card_info)
{
struct card_list *card;
@@ -330,7 +366,7 @@
DL_FOREACH(state.cards, card) {
if (card_index == cras_alsa_card_get_index(card->card))
- return -EINVAL;
+ return -EEXIST;
}
alsa_card = cras_alsa_card_create(
alsa_card_info,
@@ -400,6 +436,27 @@
state.select_data);
}
+int cras_system_set_add_task_handler(int (*add_task)(void (*cb)(void *data),
+ void *callback_data,
+ void *task_data),
+ void *task_data)
+{
+ if (state.add_task != NULL)
+ return -EEXIST;
+
+ state.add_task = add_task;
+ state.task_data = task_data;
+ return 0;
+}
+
+int cras_system_add_task(void (*callback)(void *data), void *callback_data)
+{
+ if (state.add_task == NULL)
+ return -EINVAL;
+
+ return state.add_task(callback, callback_data, state.task_data);
+}
+
void cras_system_rm_select_fd(int fd)
{
if (state.fd_rm != NULL)
@@ -491,6 +548,16 @@
return state.exp_state->num_input_nodes;
}
+void cras_system_state_set_non_empty_status(int non_empty)
+{
+ state.exp_state->non_empty_status = non_empty;
+}
+
+int cras_system_state_get_non_empty_status()
+{
+ return state.exp_state->non_empty_status;
+}
+
struct cras_server_state *cras_system_state_update_begin()
{
if (pthread_mutex_lock(&state.update_lock)) {
@@ -522,3 +589,19 @@
{
return state.tm;
}
+
+
+void cras_system_state_dump_snapshots()
+{
+ memcpy(&state.exp_state->snapshot_buffer, &state.snapshot_buffer,
+ sizeof(struct cras_audio_thread_snapshot_buffer));
+}
+
+void cras_system_state_add_snapshot(
+ struct cras_audio_thread_snapshot *snapshot)
+{
+ state.snapshot_buffer.snapshots[state.snapshot_buffer.pos++] =
+ (*snapshot);
+ state.snapshot_buffer.pos %=
+ CRAS_MAX_AUDIO_THREAD_SNAPSHOTS;
+}
diff --git a/cras/src/server/cras_system_state.h b/cras/src/server/cras_system_state.h
index f0bb98a..e5c3980 100644
--- a/cras/src/server/cras_system_state.h
+++ b/cras/src/server/cras_system_state.h
@@ -31,8 +31,18 @@
*
* Args:
* device_config_dir - Directory for device configs where volume curves live.
+ * shm_name - Name of the shared memory region used to store the state.
+ * rw_shm_fd - FD of the shm region.
+ * ro_shm_fd - FD of the shm region opened RO for sharing with clients.
+ * exp_state - Shared memory region for storing state.
+ * exp_state_size - Size of |exp_state|.
*/
-void cras_system_state_init(const char *device_config_dir);
+void cras_system_state_init(const char *device_config_dir,
+ const char *shm_name,
+ int rw_shm_fd,
+ int ro_shm_fd,
+ struct cras_server_state *exp_state,
+ size_t exp_state_size);
void cras_system_state_deinit();
/* Sets the suffix string to control which UCM config fo load. */
@@ -107,6 +117,12 @@
/* Returns the min value allowed for capture gain in dB * 100. */
long cras_system_get_max_capture_gain();
+/* Returns the default value of output buffer size in frames. */
+int cras_system_get_default_output_buffer_size();
+
+/* Returns if system aec is supported. */
+int cras_system_get_aec_supported();
+
/* Adds a card at the given index to the system. When a new card is found
* (through a udev event notification) this will add the card to the system,
* causing its devices to become available for playback/capture.
@@ -175,6 +191,30 @@
*/
void cras_system_rm_select_fd(int fd);
+/*
+ * Register the function to use to add a task for main thread to execute.
+ * Args:
+ * add_task - The function to call when new task is added.
+ * task_data - Additional data to pass back to add_task.
+ * Returns:
+ * 0 on success, or -EEXIST if there's already a registered handler.
+ */
+int cras_system_set_add_task_handler(int (*add_task)(void (*cb)(void *data),
+ void *callback_data,
+ void *task_data),
+ void *task_data);
+
+/*
+ * Adds a task callback and data pair, to be executed in the next main thread
+ * loop without additional wait time.
+ * Args:
+ * callback - The function to execute.
+ * callback_data - The data to be passed to callback when executed.
+ * Returns:
+ * 0 on success, or -EINVAL if there's no handler for adding task.
+ */
+int cras_system_add_task(void (*callback)(void *data), void *callback_data);
+
/* Signals that an audio input or output stream has been added to the system.
* This allows the count of active streams can be used to notice when the audio
* subsystem is idle.
@@ -238,6 +278,16 @@
*/
int cras_system_state_get_input_nodes(const struct cras_ionode_info **nodes);
+/*
+ * Sets the non-empty audio status.
+ */
+void cras_system_state_set_non_empty_status(int non_empty);
+
+/*
+ * Returns the non-empty audio status.
+ */
+int cras_system_state_get_non_empty_status();
+
/* Returns a pointer to the current system state that is shared with clients.
* This also 'locks' the structure by incrementing the update count to an odd
* value.
@@ -259,4 +309,14 @@
/* Returns the timer manager. */
struct cras_tm *cras_system_state_get_tm();
+/*
+ * Add snapshot to snapshot buffer in system state
+ */
+void cras_system_state_add_snapshot(struct cras_audio_thread_snapshot *);
+
+/*
+ * Dump snapshots from system state to shared memory with client
+ */
+void cras_system_state_dump_snapshots();
+
#endif /* CRAS_SYSTEM_STATE_H_ */
diff --git a/cras/src/server/dev_io.c b/cras/src/server/dev_io.c
new file mode 100644
index 0000000..f51e58a
--- /dev/null
+++ b/cras/src/server/dev_io.c
@@ -0,0 +1,1013 @@
+/* Copyright 2017 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <poll.h>
+#include <syslog.h>
+
+#include "audio_thread_log.h"
+#include "cras_audio_area.h"
+#include "cras_iodev.h"
+#include "cras_non_empty_audio_handler.h"
+#include "cras_rstream.h"
+#include "cras_server_metrics.h"
+#include "dev_stream.h"
+#include "input_data.h"
+#include "polled_interval_checker.h"
+#include "utlist.h"
+
+#include "dev_io.h"
+
+static const struct timespec playback_wake_fuzz_ts = {
+ 0, 500 * 1000 /* 500 usec. */
+};
+
+/* The maximum time to wait before checking the device's non-empty status. */
+static const int NON_EMPTY_UPDATE_INTERVAL_SEC = 5;
+
+/*
+ * The minimum number of consecutive seconds of empty audio that must be
+ * played before a device is considered to be playing empty audio.
+ */
+static const int MIN_EMPTY_PERIOD_SEC = 30;
+
+/* The number of devices playing/capturing non-empty stream(s). */
+static int non_empty_device_count = 0;
+
+/* Gets the master device which the stream is attached to. */
+static inline
+struct cras_iodev *get_master_dev(const struct dev_stream *stream)
+{
+ return (struct cras_iodev *)stream->stream->master_dev.dev_ptr;
+}
+
+/* Updates the estimated sample rate of open device to all attached
+ * streams.
+ */
+static void update_estimated_rate(struct open_dev *adev)
+{
+ struct cras_iodev *master_dev;
+ struct cras_iodev *dev = adev->dev;
+ struct dev_stream *dev_stream;
+
+ DL_FOREACH(dev->streams, dev_stream) {
+ master_dev = get_master_dev(dev_stream);
+ if (master_dev == NULL) {
+ syslog(LOG_ERR, "Fail to find master open dev.");
+ continue;
+ }
+
+ dev_stream_set_dev_rate(dev_stream,
+ dev->ext_format->frame_rate,
+ cras_iodev_get_est_rate_ratio(dev),
+ cras_iodev_get_est_rate_ratio(master_dev),
+ adev->coarse_rate_adjust);
+ }
+}
+
+/*
+ * Counts the number of devices which are currently playing/capturing non-empty
+ * audio.
+ */
+static inline int count_non_empty_dev(struct open_dev *adevs) {
+ int count = 0;
+ struct open_dev *adev;
+ DL_FOREACH(adevs, adev) {
+ if (!adev->empty_pi || !pic_interval_elapsed(adev->empty_pi))
+ count++;
+ }
+ return count;
+}
+
+static void check_non_empty_state_transition(struct open_dev *adevs) {
+ int new_non_empty_dev_count = count_non_empty_dev(adevs);
+
+ // If we have transitioned to or from a state with 0 non-empty devices,
+ // notify the main thread to update system state.
+ if ((non_empty_device_count == 0) != (new_non_empty_dev_count == 0))
+ cras_non_empty_audio_send_msg(
+ new_non_empty_dev_count > 0 ? 1 : 0);
+
+ non_empty_device_count = new_non_empty_dev_count;
+}
+
+/* Asks any stream with room for more data. Sets the time stamp for all streams.
+ * Args:
+ * adev - The output device streams are attached to.
+ * Returns:
+ * 0 on success, negative error on failure. If failed, can assume that all
+ * streams have been removed from the device.
+ */
+static int fetch_streams(struct open_dev *adev)
+{
+ struct dev_stream *dev_stream;
+ struct cras_iodev *odev = adev->dev;
+ int rc;
+ int delay;
+
+ delay = cras_iodev_delay_frames(odev);
+ if (delay < 0)
+ return delay;
+
+ DL_FOREACH(adev->dev->streams, dev_stream) {
+ struct cras_rstream *rstream = dev_stream->stream;
+ struct cras_audio_shm *shm =
+ cras_rstream_output_shm(rstream);
+ const struct timespec *next_cb_ts;
+ struct timespec now;
+
+ clock_gettime(CLOCK_MONOTONIC_RAW, &now);
+
+ if (dev_stream_is_pending_reply(dev_stream)) {
+ dev_stream_flush_old_audio_messages(dev_stream);
+ cras_rstream_record_fetch_interval(dev_stream->stream,
+ &now);
+ }
+
+ if (cras_shm_get_frames(shm) < 0)
+ cras_rstream_set_is_draining(rstream, 1);
+
+ if (cras_rstream_get_is_draining(dev_stream->stream))
+ continue;
+
+ next_cb_ts = dev_stream_next_cb_ts(dev_stream);
+ if (!next_cb_ts)
+ continue;
+
+ /* Check if it's time to get more data from this stream.
+ * Allow for waking up a little early. */
+ add_timespecs(&now, &playback_wake_fuzz_ts);
+ if (!timespec_after(&now, next_cb_ts))
+ continue;
+
+ if (!dev_stream_can_fetch(dev_stream)) {
+ ATLOG(atlog, AUDIO_THREAD_STREAM_SKIP_CB,
+ cras_rstream_id(rstream),
+ shm->area->write_offset[0],
+ shm->area->write_offset[1]);
+ continue;
+ }
+
+ dev_stream_set_delay(dev_stream, delay);
+
+ ATLOG(atlog, AUDIO_THREAD_FETCH_STREAM, rstream->stream_id,
+ cras_rstream_get_cb_threshold(rstream), delay);
+
+ rc = dev_stream_request_playback_samples(dev_stream, &now);
+ if (rc < 0) {
+ syslog(LOG_ERR, "fetch err: %d for %x",
+ rc, cras_rstream_id(rstream));
+ cras_rstream_set_is_draining(rstream, 1);
+ }
+ }
+
+ return 0;
+}
+
+/* Gets the max delay frames of open input devices. */
+static int input_delay_frames(struct open_dev *adevs)
+{
+ struct open_dev *adev;
+ int delay;
+ int max_delay = 0;
+
+ DL_FOREACH(adevs, adev) {
+ if (!cras_iodev_is_open(adev->dev))
+ continue;
+ delay = cras_iodev_delay_frames(adev->dev);
+ if (delay < 0)
+ return delay;
+ if (delay > max_delay)
+ max_delay = delay;
+ }
+ return max_delay;
+}
+
+/* Sets the stream delay.
+ * Args:
+ * adev[in] - The device to capture from.
+ */
+static unsigned int set_stream_delay(struct open_dev *adev)
+{
+ struct dev_stream *stream;
+ int delay;
+
+ /* TODO(dgreid) - Setting delay from last dev only. */
+ delay = input_delay_frames(adev);
+
+ DL_FOREACH(adev->dev->streams, stream) {
+ if (stream->stream->flags & TRIGGER_ONLY)
+ continue;
+
+ dev_stream_set_delay(stream, delay);
+ }
+
+ return 0;
+}
+
+/* Gets the minimum amount of space available for writing across all streams.
+ * Args:
+ * adev[in] - The device to capture from.
+ * write_limit[in] - Initial limit to number of frames to capture.
+ * limit_stream[out] - The pointer to the pointer of stream which
+ * causes capture limit.
+ * Output NULL if there is no stream that causes
+ * capture limit less than the initial limit.
+ */
+static unsigned int get_stream_limit(
+ struct open_dev *adev,
+ unsigned int write_limit,
+ struct dev_stream **limit_stream)
+{
+ struct cras_rstream *rstream;
+ struct cras_audio_shm *shm;
+ struct dev_stream *stream;
+ unsigned int avail;
+
+ *limit_stream = NULL;
+
+ DL_FOREACH(adev->dev->streams, stream) {
+ rstream = stream->stream;
+ if (rstream->flags & TRIGGER_ONLY)
+ continue;
+
+ shm = cras_rstream_input_shm(rstream);
+ if (cras_shm_check_write_overrun(shm))
+ ATLOG(atlog, AUDIO_THREAD_READ_OVERRUN,
+ adev->dev->info.idx, rstream->stream_id,
+ shm->area->num_overruns);
+ avail = dev_stream_capture_avail(stream);
+ if (avail < write_limit) {
+ write_limit = avail;
+ *limit_stream = stream;
+ }
+ }
+
+ return write_limit;
+}
+
+/*
+ * The minimum wake time for a input device, which is 5ms. It's only used by
+ * function get_input_dev_max_wake_ts.
+ */
+static const struct timespec min_input_dev_wake_ts = {
+ 0, 5 * 1000 * 1000 /* 5 ms. */
+};
+
+/*
+ * Get input device maximum sleep time, which is the approximate time that the
+ * device will have hw_level = buffer_size / 2 samples. Some devices have
+ * capture period = 2 so the audio_thread should wake up and consume some
+ * samples from hardware at that time. To prevent busy loop occurs, the returned
+ * sleep time should be >= 5ms.
+ *
+ * Returns: 0 on success negative error on device failure.
+ */
+static int get_input_dev_max_wake_ts(
+ struct open_dev *adev,
+ unsigned int curr_level,
+ struct timespec *res_ts)
+{
+ struct timespec dev_wake_ts, now;
+ unsigned int dev_rate, half_buffer_size, target_frames;
+
+ if(!adev || !adev->dev || !adev->dev->format ||
+ !adev->dev->format->frame_rate || !adev->dev->buffer_size)
+ return -EINVAL;
+
+ *res_ts = min_input_dev_wake_ts;
+
+ dev_rate = adev->dev->format->frame_rate;
+ half_buffer_size = adev->dev->buffer_size / 2;
+ if(curr_level < half_buffer_size)
+ target_frames = half_buffer_size - curr_level;
+ else
+ target_frames = 0;
+
+ cras_frames_to_time(target_frames, dev_rate, &dev_wake_ts);
+
+ if (timespec_after(&dev_wake_ts, res_ts)) {
+ *res_ts = dev_wake_ts;
+ }
+
+ clock_gettime(CLOCK_MONOTONIC_RAW, &now);
+ add_timespecs(res_ts, &now);
+ 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, dev_wake_ts;
+ unsigned int curr_level, cap_limit;
+ struct dev_stream *stream;
+ struct dev_stream *cap_limit_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);
+
+ rc = cras_iodev_frames_queued(adev->dev, &level_tstamp);
+ if (rc < 0)
+ return rc;
+ curr_level = rc;
+ if (!timespec_is_nonzero(&level_tstamp))
+ clock_gettime(CLOCK_MONOTONIC_RAW, &level_tstamp);
+
+
+ cap_limit = get_stream_limit(adev, UINT_MAX, &cap_limit_stream);
+
+ /*
+ * Loop through streams to find the earliest time audio thread
+ * should wake up.
+ */
+ DL_FOREACH(adev->dev->streams, stream) {
+ wake_time_out = min_ts;
+ rc = dev_stream_wake_time(
+ stream,
+ curr_level,
+ &level_tstamp,
+ cap_limit,
+ cap_limit_stream == stream,
+ &wake_time_out);
+
+ /*
+ * rc > 0 means there is no need to set wake up time for this
+ * stream.
+ */
+ if (rc > 0)
+ continue;
+
+ if (rc < 0)
+ return rc;
+
+ if (timespec_after(&min_ts, &wake_time_out)) {
+ min_ts = wake_time_out;
+ }
+ }
+
+ if(adev->dev->active_node &&
+ adev->dev->active_node->type != CRAS_NODE_TYPE_HOTWORD) {
+ rc = get_input_dev_max_wake_ts(adev, curr_level, &dev_wake_ts);
+ if(rc < 0) {
+ syslog(LOG_ERR,
+ "Failed to call get_input_dev_max_wake_ts."
+ "rc = %d", rc);
+ } else if(timespec_after(&min_ts, &dev_wake_ts)) {
+ min_ts = dev_wake_ts;
+ }
+ }
+
+ adev->wake_ts = min_ts;
+ return rc;
+}
+
+/* Read samples from an input device to the specified stream.
+ * Args:
+ * adev - The device to capture samples from.
+ * Returns 0 on success.
+ */
+static int capture_to_streams(struct open_dev *adev)
+{
+ struct cras_iodev *idev = adev->dev;
+ snd_pcm_uframes_t remainder, hw_level, cap_limit;
+ struct timespec hw_tstamp;
+ int rc;
+ struct dev_stream *cap_limit_stream;
+ struct dev_stream *stream;
+
+ DL_FOREACH(adev->dev->streams, stream)
+ dev_stream_flush_old_audio_messages(stream);
+
+ rc = cras_iodev_frames_queued(idev, &hw_tstamp);
+ if (rc < 0)
+ return rc;
+ hw_level = rc;
+
+ cras_iodev_update_highest_hw_level(idev, hw_level);
+
+ 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 < idev->min_cb_level / 2)
+ adev->coarse_rate_adjust = 1;
+ else if (hw_level > idev->max_cb_level * 2)
+ adev->coarse_rate_adjust = -1;
+ else
+ adev->coarse_rate_adjust = 0;
+ if (cras_iodev_update_rate(idev, hw_level, &hw_tstamp))
+ update_estimated_rate(adev);
+ }
+
+ cap_limit = get_stream_limit(adev, hw_level, &cap_limit_stream);
+ set_stream_delay(adev);
+
+ remainder = MIN(hw_level, cap_limit);
+
+ ATLOG(atlog, AUDIO_THREAD_READ_AUDIO, idev->info.idx,
+ hw_level, remainder);
+
+ if (cras_iodev_state(idev) != CRAS_IODEV_STATE_NORMAL_RUN)
+ return 0;
+
+ while (remainder > 0) {
+ struct cras_audio_area *area = NULL;
+ unsigned int nread, total_read;
+
+ nread = remainder;
+
+ rc = cras_iodev_get_input_buffer(idev, &nread);
+ if (rc < 0 || nread == 0)
+ return rc;
+
+ DL_FOREACH(adev->dev->streams, stream) {
+ unsigned int this_read;
+ unsigned int area_offset;
+ float software_gain_scaler;
+
+ if ((stream->stream->flags & TRIGGER_ONLY) &&
+ stream->stream->triggered)
+ continue;
+
+ input_data_get_for_stream(idev->input_data, stream->stream,
+ idev->buf_state,
+ &area, &area_offset);
+ /*
+ * APM has more advanced gain control mechanism, so
+ * don't apply the CRAS software gain to this stream
+ * if APM is used.
+ */
+ software_gain_scaler = stream->stream->apm_list
+ ? 1.0f
+ : cras_iodev_get_software_gain_scaler(idev);
+
+ this_read = dev_stream_capture(
+ stream, area, area_offset,
+ software_gain_scaler);
+
+ input_data_put_for_stream(idev->input_data, stream->stream,
+ idev->buf_state, this_read);
+ }
+
+ rc = cras_iodev_put_input_buffer(idev);
+ if (rc < 0)
+ return rc;
+
+ total_read = rc;
+ remainder -= nread;
+
+ if (total_read < nread)
+ break;
+ }
+
+ ATLOG(atlog, AUDIO_THREAD_READ_AUDIO_DONE, remainder, 0, 0);
+
+ return 0;
+}
+
+/* Fill the buffer with samples from the attached streams.
+ * Args:
+ * odevs - The list of open output devices, provided so streams can be
+ * removed from all devices on error.
+ * adev - The device to write to.
+ * dst - The buffer to put the samples in (returned from snd_pcm_mmap_begin)
+ * write_limit - The maximum number of frames to write to dst.
+ *
+ * Returns:
+ * The number of frames rendered on success, a negative error code otherwise.
+ * This number of frames is the minimum of the amount of frames each stream
+ * could provide which is the maximum that can currently be rendered.
+ */
+static int write_streams(struct open_dev **odevs,
+ struct open_dev *adev,
+ uint8_t *dst,
+ size_t write_limit)
+{
+ struct cras_iodev *odev = adev->dev;
+ struct dev_stream *curr;
+ unsigned int max_offset = 0;
+ unsigned int frame_bytes = cras_get_format_bytes(odev->ext_format);
+ unsigned int num_playing = 0;
+ unsigned int drain_limit = write_limit;
+
+ /* Mix as much as we can, the minimum fill level of any stream. */
+ max_offset = cras_iodev_max_stream_offset(odev);
+
+ /* Mix as much as we can, the minimum fill level of any stream. */
+ DL_FOREACH(adev->dev->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);
+ if (dev_frames < 0) {
+ dev_io_remove_stream(
+ odevs,
+ curr->stream, NULL);
+ continue;
+ }
+ ATLOG(atlog, AUDIO_THREAD_WRITE_STREAMS_STREAM,
+ curr->stream->stream_id, dev_frames,
+ dev_stream_is_pending_reply(curr));
+ if (cras_rstream_get_is_draining(curr->stream)) {
+ drain_limit = MIN((size_t)dev_frames, drain_limit);
+ if (!dev_frames)
+ dev_io_remove_stream(
+ odevs,
+ curr->stream, NULL);
+ } else {
+ write_limit = MIN((size_t)dev_frames, write_limit);
+ num_playing++;
+ }
+ }
+
+ if (!num_playing)
+ write_limit = drain_limit;
+
+ if (write_limit > max_offset)
+ memset(dst + max_offset * frame_bytes, 0,
+ (write_limit - max_offset) * frame_bytes);
+
+ ATLOG(atlog, AUDIO_THREAD_WRITE_STREAMS_MIX,
+ write_limit, max_offset, 0);
+
+ DL_FOREACH(adev->dev->streams, curr) {
+ unsigned int offset;
+ int nwritten;
+
+ offset = cras_iodev_stream_offset(odev, curr);
+ if (offset >= write_limit)
+ continue;
+ nwritten = dev_stream_mix(curr, odev->ext_format,
+ dst + frame_bytes * offset,
+ write_limit - offset);
+
+ if (nwritten < 0) {
+ dev_io_remove_stream(odevs, curr->stream, NULL);
+ continue;
+ }
+
+ cras_iodev_stream_written(odev, curr, nwritten);
+ }
+
+ write_limit = cras_iodev_all_streams_written(odev);
+
+ ATLOG(atlog, AUDIO_THREAD_WRITE_STREAMS_MIXED, write_limit, 0, 0);
+
+ return write_limit;
+}
+
+/* Update next wake up time of the device.
+ * Args:
+ * adev[in] - The device to update to.
+ * hw_level[out] - Pointer to number of frames in hardware.
+ */
+void update_dev_wakeup_time(struct open_dev *adev, unsigned int *hw_level)
+{
+ struct timespec now;
+ struct timespec sleep_time;
+ double est_rate;
+ unsigned int frames_to_play_in_sleep;
+
+ clock_gettime(CLOCK_MONOTONIC_RAW, &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;
+
+ if (cras_iodev_state(adev->dev) == CRAS_IODEV_STATE_NORMAL_RUN)
+ cras_iodev_update_highest_hw_level(adev->dev, *hw_level);
+
+ est_rate = adev->dev->ext_format->frame_rate *
+ cras_iodev_get_est_rate_ratio(adev->dev);
+
+ ATLOG(atlog, AUDIO_THREAD_SET_DEV_WAKE, adev->dev->info.idx,
+ *hw_level, frames_to_play_in_sleep);
+
+ cras_frames_to_time_precise(
+ frames_to_play_in_sleep,
+ est_rate,
+ &sleep_time);
+
+ add_timespecs(&adev->wake_ts, &sleep_time);
+
+ ATLOG(atlog, AUDIO_THREAD_DEV_SLEEP_TIME, adev->dev->info.idx,
+ adev->wake_ts.tv_sec, adev->wake_ts.tv_nsec);
+}
+
+/* Returns 0 on success negative error on device failure. */
+int write_output_samples(struct open_dev **odevs,
+ struct open_dev *adev,
+ struct cras_fmt_conv *output_converter)
+{
+ 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;
+ int rc;
+ int non_empty = 0;
+ int *non_empty_ptr = NULL;
+ uint8_t *dst = NULL;
+ struct cras_audio_area *area = NULL;
+
+ /* 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;
+ }
+
+ 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;
+
+ 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(adev);
+ }
+ ATLOG(atlog, AUDIO_THREAD_FILL_AUDIO, adev->dev->info.idx, hw_level, 0);
+
+ /* Don't request more than hardware can hold. Note that min_buffer_level
+ * has been subtracted from the actual hw_level so we need to take it
+ * into account here. */
+ fr_to_req = cras_iodev_buffer_avail(odev, hw_level);
+
+ /* Have to loop writing to the device, will be at most 2 loops, this
+ * only happens when the circular buffer is at the end and returns us a
+ * partial area to write to from mmap_begin */
+ while (total_written < fr_to_req) {
+ frames = fr_to_req - total_written;
+ rc = cras_iodev_get_output_buffer(odev, &area, &frames);
+ if (rc < 0)
+ return rc;
+
+ /* TODO(dgreid) - This assumes interleaved audio. */
+ dst = area->channels[0].buf;
+ written = write_streams(odevs, adev, dst, frames);
+ if (written < 0) /* pcm has been closed */
+ return (int)written;
+
+ if (written < (snd_pcm_sframes_t)frames)
+ /* Got all the samples from client that we can, but it
+ * won't fill the request. */
+ fr_to_req = 0; /* break out after committing samples */
+
+ // This interval is lazily initialized once per device.
+ // Note that newly opened devices are considered non-empty
+ // (until their status is updated through the normal flow).
+ if (!adev->non_empty_check_pi) {
+ adev->non_empty_check_pi = pic_polled_interval_create(
+ NON_EMPTY_UPDATE_INTERVAL_SEC);
+ }
+
+ // If we were empty last iteration, or the sampling interval
+ // has elapsed, check for emptiness.
+ if (adev->empty_pi ||
+ pic_interval_elapsed(adev->non_empty_check_pi)) {
+ non_empty_ptr = &non_empty;
+ pic_interval_reset(adev->non_empty_check_pi);
+ }
+
+ rc = cras_iodev_put_output_buffer(odev, dst, written,
+ non_empty_ptr,
+ output_converter);
+
+ if (rc < 0)
+ return rc;
+ total_written += written;
+
+ if (non_empty && adev->empty_pi) {
+ // We're not empty, but we were previously.
+ // Reset the empty period.
+ pic_polled_interval_destroy(&adev->empty_pi);
+ }
+
+ if (non_empty_ptr && !non_empty && !adev->empty_pi)
+ // We checked for emptiness, we were empty, and we
+ // previously weren't. Start the empty period.
+ adev->empty_pi = pic_polled_interval_create(
+ MIN_EMPTY_PERIOD_SEC);
+ }
+
+ ATLOG(atlog, AUDIO_THREAD_FILL_AUDIO_DONE, hw_level,
+ total_written, odev->min_cb_level);
+
+ return total_written;
+}
+
+/*
+ * Public funcitons.
+ */
+
+int dev_io_send_captured_samples(struct open_dev *idev_list)
+{
+ struct open_dev *adev;
+ int rc;
+
+ // TODO(dgreid) - once per rstream, not once per dev_stream.
+ DL_FOREACH(idev_list, adev) {
+ struct dev_stream *stream;
+
+ if (!cras_iodev_is_open(adev->dev))
+ continue;
+
+ /* Post samples to rstream if there are enough samples. */
+ DL_FOREACH(adev->dev->streams, stream) {
+ dev_stream_capture_update_rstream(stream);
+ }
+
+ /* Set wake_ts for this device. */
+ rc = set_input_dev_wake_ts(adev);
+ if (rc < 0)
+ return rc;
+ }
+
+ return 0;
+}
+
+static void handle_dev_err(
+ int err_rc,
+ struct open_dev **odevs,
+ struct open_dev *adev)
+{
+ if (err_rc == -EPIPE) {
+ /* Handle severe underrun. */
+ ATLOG(atlog, AUDIO_THREAD_SEVERE_UNDERRUN,
+ adev->dev->info.idx, 0, 0);
+ cras_iodev_reset_request(adev->dev);
+ } else {
+ /* Device error, close it. */
+ dev_io_rm_open_dev(odevs, adev);
+ }
+}
+
+int dev_io_capture(struct open_dev **list)
+{
+ struct open_dev *idev_list = *list;
+ struct open_dev *adev;
+ int rc;
+
+ DL_FOREACH(idev_list, adev) {
+ if (!cras_iodev_is_open(adev->dev))
+ continue;
+ rc = capture_to_streams(adev);
+ if (rc < 0)
+ handle_dev_err(rc, list, adev);
+ }
+
+ return 0;
+}
+
+void dev_io_playback_fetch(struct open_dev *odev_list)
+{
+ struct open_dev *adev;
+
+ DL_FOREACH(odev_list, adev) {
+ if (!cras_iodev_is_open(adev->dev))
+ continue;
+ fetch_streams(adev);
+ }
+}
+
+int dev_io_playback_write(struct open_dev **odevs,
+ struct cras_fmt_conv *output_converter)
+{
+ struct open_dev *adev;
+ struct dev_stream *curr;
+ int rc;
+ unsigned int hw_level, total_written;
+
+ /* For multiple output case, update the number of queued frames in shm
+ * of all streams before starting write output samples. */
+ adev = *odevs;
+ if (adev && adev->next) {
+ DL_FOREACH(*odevs, adev) {
+ DL_FOREACH(adev->dev->streams, curr)
+ dev_stream_update_frames(curr);
+ }
+ }
+
+ DL_FOREACH(*odevs, adev) {
+ if (!cras_iodev_is_open(adev->dev))
+ continue;
+
+ rc = write_output_samples(odevs, adev, output_converter);
+ if (rc < 0) {
+ handle_dev_err(rc, odevs, adev);
+ } else {
+ total_written = rc;
+
+ /*
+ * Skip the underrun check and device wake up time update if
+ * device should not wake up.
+ */
+ if (!cras_iodev_odev_should_wake(adev->dev))
+ continue;
+
+ /*
+ * Update device wake up time and get the new hardware
+ * level.
+ */
+ update_dev_wakeup_time(adev, &hw_level);
+
+ /*
+ * If new hardware level is less than or equal to the
+ * written frames, we can suppose underrun happened. But
+ * keep in mind there may have a false positive. If
+ * hardware level changed just after frames being
+ * written, we may get hw_level <= total_written here
+ * without underrun happened. However, we can still
+ * treat it as underrun because it is an abnormal state
+ * we should handle it.
+ */
+ if (hw_level <= total_written) {
+ ATLOG(atlog, AUDIO_THREAD_UNDERRUN,
+ adev->dev->info.idx,
+ hw_level, total_written);
+ rc = cras_iodev_output_underrun(adev->dev);
+ if(rc < 0) {
+ handle_dev_err(rc, odevs, adev);
+ } else {
+ update_dev_wakeup_time(adev, &hw_level);
+ }
+ }
+ }
+ }
+
+ /* TODO(dgreid) - once per rstream, not once per dev_stream. */
+ DL_FOREACH(*odevs, adev) {
+ struct dev_stream *stream;
+ if (!cras_iodev_is_open(adev->dev))
+ continue;
+ DL_FOREACH(adev->dev->streams, stream) {
+ dev_stream_playback_update_rstream(stream);
+ }
+ }
+
+ return 0;
+}
+
+void dev_io_run(struct open_dev **odevs, struct open_dev **idevs,
+ struct cras_fmt_conv *output_converter)
+{
+ pic_update_current_time();
+
+ dev_io_playback_fetch(*odevs);
+ dev_io_capture(idevs);
+ dev_io_send_captured_samples(*idevs);
+ dev_io_playback_write(odevs, output_converter);
+
+ check_non_empty_state_transition(*odevs);
+}
+
+static int input_adev_ignore_wake(const struct open_dev *adev)
+{
+ if (!cras_iodev_is_open(adev->dev))
+ return 1;
+
+ if (!adev->dev->active_node)
+ return 1;
+
+ if (adev->dev->active_node->type == CRAS_NODE_TYPE_HOTWORD &&
+ !cras_iodev_input_streaming(adev->dev))
+ return 1;
+
+ return 0;
+}
+
+int dev_io_next_input_wake(struct open_dev **idevs, struct timespec *min_ts)
+{
+ struct open_dev *adev;
+ int ret = 0; /* The total number of devices to wait on. */
+
+ DL_FOREACH(*idevs, adev) {
+ if (input_adev_ignore_wake(adev))
+ continue;
+ ret++;
+ ATLOG(atlog, AUDIO_THREAD_DEV_SLEEP_TIME, adev->dev->info.idx,
+ adev->wake_ts.tv_sec, adev->wake_ts.tv_nsec);
+ if (timespec_after(min_ts, &adev->wake_ts))
+ *min_ts = adev->wake_ts;
+ }
+
+ return ret;
+}
+
+struct open_dev *dev_io_find_open_dev(struct open_dev *odev_list,
+ const struct cras_iodev *dev)
+{
+ struct open_dev *odev;
+ DL_FOREACH(odev_list, odev)
+ if (odev->dev == dev)
+ return odev;
+ return NULL;
+}
+
+void dev_io_rm_open_dev(struct open_dev **odev_list,
+ struct open_dev *dev_to_rm)
+{
+ struct open_dev *odev;
+ struct dev_stream *dev_stream;
+
+ /* Do nothing if dev_to_rm wasn't already in the active dev list. */
+ odev = dev_io_find_open_dev(*odev_list, dev_to_rm->dev);
+ if (!odev)
+ return;
+
+
+ DL_DELETE(*odev_list, dev_to_rm);
+
+ /* Metrics logs the number of underruns of this device. */
+ cras_server_metrics_num_underruns(
+ cras_iodev_get_num_underruns(dev_to_rm->dev));
+
+ /* Metrics logs the highest_hw_level of this device. */
+ cras_server_metrics_highest_hw_level(
+ dev_to_rm->dev->highest_hw_level, dev_to_rm->dev->direction);
+
+ check_non_empty_state_transition(*odev_list);
+
+ ATLOG(atlog, AUDIO_THREAD_DEV_REMOVED, dev_to_rm->dev->info.idx, 0, 0);
+
+ DL_FOREACH(dev_to_rm->dev->streams, dev_stream) {
+ cras_iodev_rm_stream(dev_to_rm->dev, dev_stream->stream);
+ dev_stream_destroy(dev_stream);
+ }
+
+ if (dev_to_rm->empty_pi)
+ pic_polled_interval_destroy(&dev_to_rm->empty_pi);
+ if (dev_to_rm->non_empty_check_pi)
+ pic_polled_interval_destroy(&dev_to_rm->non_empty_check_pi);
+ free(dev_to_rm);
+}
+
+static void delete_stream_from_dev(struct cras_iodev *dev,
+ struct cras_rstream *stream)
+{
+ struct dev_stream *out;
+
+ out = cras_iodev_rm_stream(dev, stream);
+ if (out)
+ dev_stream_destroy(out);
+}
+
+int dev_io_remove_stream(struct open_dev **dev_list,
+ struct cras_rstream *stream,
+ struct cras_iodev *dev)
+{
+ struct open_dev *open_dev;
+ struct timespec delay;
+ unsigned fetch_delay_msec;
+
+ /* Metrics log the longest fetch delay of this stream. */
+ if (timespec_after(&stream->longest_fetch_interval,
+ &stream->sleep_interval_ts)) {
+ subtract_timespecs(&stream->longest_fetch_interval,
+ &stream->sleep_interval_ts,
+ &delay);
+ fetch_delay_msec = delay.tv_sec * 1000 +
+ delay.tv_nsec / 1000000;
+ if (fetch_delay_msec)
+ cras_server_metrics_longest_fetch_delay(
+ fetch_delay_msec);
+ }
+
+ ATLOG(atlog, AUDIO_THREAD_STREAM_REMOVED, stream->stream_id, 0, 0);
+
+ if (dev == NULL) {
+ DL_FOREACH(*dev_list, open_dev) {
+ delete_stream_from_dev(open_dev->dev, stream);
+ }
+ } else {
+ delete_stream_from_dev(dev, stream);
+ }
+
+ return 0;
+}
diff --git a/cras/src/server/dev_io.h b/cras/src/server/dev_io.h
new file mode 100644
index 0000000..83e16e0
--- /dev/null
+++ b/cras/src/server/dev_io.h
@@ -0,0 +1,91 @@
+/* Copyright 2017 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * `dev_io` Handles playback to and capture from open devices. It runs only on
+ * the audio thread.
+ */
+
+#ifndef DEV_IO_H_
+#define DEV_IO_H_
+
+#include "cras_iodev.h"
+#include "cras_types.h"
+#include "polled_interval_checker.h"
+
+/*
+ * Open input/output devices.
+ * dev - The device.
+ * wake_ts - When callback is needed to avoid xrun.
+ * last_non_empty_ts - The last time we know the device played/captured
+ * non-empty (zero) audio.
+ * coarse_rate_adjust - Hack for when the sample rate needs heavy correction.
+ */
+struct open_dev {
+ struct cras_iodev *dev;
+ struct timespec wake_ts;
+ struct polled_interval *non_empty_check_pi;
+ struct polled_interval *empty_pi;
+ int coarse_rate_adjust;
+ struct open_dev *prev, *next;
+};
+
+/*
+ * Fetches streams from each device in `odev_list`.
+ * odev_list - The list of open devices.
+ */
+void dev_io_playback_fetch(struct open_dev *odev_list);
+
+/*
+ * Writes the samples fetched from the streams to the playback devices.
+ * odev_list - The list of open devices. Devices will be removed when
+ * writing returns an error.
+ */
+int dev_io_playback_write(struct open_dev **odevs,
+ struct cras_fmt_conv *output_converter);
+
+/* Only public for testing. */
+int write_output_samples(struct open_dev **odevs,
+ struct open_dev *adev,
+ struct cras_fmt_conv *output_converter);
+
+/*
+ * Captures samples from each device in the list.
+ * list - Pointer to the list of input devices. Devices that fail to read
+ * will be removed from the list.
+ */
+int dev_io_capture(struct open_dev **list);
+
+/*
+ * Send samples that have been captured to their streams.
+ */
+int dev_io_send_captured_samples(struct open_dev *idev_list);
+
+/* Reads and/or writes audio samples from/to the devices. */
+void dev_io_run(struct open_dev **odevs, struct open_dev **idevs,
+ struct cras_fmt_conv *output_converter);
+
+/*
+ * Fills min_ts with the next time the system should wake to service input.
+ * Returns the number of devices waiting.
+ */
+int dev_io_next_input_wake(struct open_dev **idevs, struct timespec *min_ts);
+
+/*
+ * Removes a device from a list of devices.
+ * odev_list - A pointer to the list to modify.
+ * dev_to_rm - Find this device in the list and remove it.
+ */
+void dev_io_rm_open_dev(struct open_dev **odev_list,
+ struct open_dev *dev_to_rm);
+
+/* Returns a pointer to an open_dev if it is in the list, otherwise NULL. */
+struct open_dev *dev_io_find_open_dev(struct open_dev *odev_list,
+ const struct cras_iodev *dev);
+
+/* Remove a stream from the provided list of devices. */
+int dev_io_remove_stream(struct open_dev **dev_list,
+ struct cras_rstream *stream,
+ struct cras_iodev *dev);
+
+#endif /* DEV_IO_H_ */
diff --git a/cras/src/server/dev_stream.c b/cras/src/server/dev_stream.c
index 4d893cc..a625d58 100644
--- a/cras/src/server/dev_stream.c
+++ b/cras/src/server/dev_stream.c
@@ -32,6 +32,38 @@
.tv_nsec = 1000000, /* 1 ms. */
};
+/*
+ * Returns the size in frames that a format converter must allocate for its
+ * temporary buffers to be able to convert the specified number of stream
+ * frames to or from the corresponding number of device frames, at the
+ * specified device rate.
+ */
+unsigned int max_frames_for_conversion(unsigned int stream_frames,
+ unsigned int stream_rate,
+ unsigned int device_rate) {
+ /*
+ * There are multiple temp buffers in the format converter,
+ * which are all the same size. Some of these contain audio
+ * in the source sample rate, and others in the converted
+ * sample rate. We need to make sure the converter is large
+ * enough to hold either.
+ */
+ return MAX(
+ // Number of stream frames does not require conversion.
+ stream_frames,
+ // Calculate corresponding number of frames at device rate.
+ cras_frames_at_rate(stream_rate,
+ stream_frames,
+ device_rate))
+ /*
+ * Add 1 because the linear resampler's frame rate
+ * conversion does this, and is used to calculate
+ * how many frames to read from the device.
+ * See linear_resampler_{in,out}_frames_to_{out,in}(..)
+ */
+ + 1;
+}
+
struct dev_stream *dev_stream_create(struct cras_rstream *stream,
unsigned int dev_id,
const struct cras_audio_format *dev_fmt,
@@ -49,24 +81,27 @@
out->stream = stream;
out->dev_rate = dev_fmt->frame_rate;
+ max_frames = max_frames_for_conversion(stream->buffer_frames,
+ stream_fmt->frame_rate,
+ dev_fmt->frame_rate);
+
if (stream->direction == CRAS_STREAM_OUTPUT) {
- max_frames = MAX(stream->buffer_frames,
- cras_frames_at_rate(stream_fmt->frame_rate,
- stream->buffer_frames,
- dev_fmt->frame_rate));
rc = config_format_converter(&out->conv,
stream->direction,
stream_fmt,
dev_fmt,
max_frames);
} else {
- max_frames = MAX(stream->buffer_frames,
- cras_frames_at_rate(dev_fmt->frame_rate,
- stream->buffer_frames,
- stream_fmt->frame_rate));
+ /*
+ * For input, take into account the stream specific processing
+ * like AEC. Use the post processing format to configure format
+ * converter.
+ */
+ ofmt = cras_rstream_post_processing_format(
+ stream, dev_ptr) ? : dev_fmt,
rc = config_format_converter(&out->conv,
stream->direction,
- dev_fmt,
+ ofmt,
stream_fmt,
max_frames);
}
@@ -117,8 +152,8 @@
cras_rstream_dev_detach(dev_stream->stream, dev_stream->dev_id);
if (dev_stream->conv) {
cras_audio_area_destroy(dev_stream->conv_area);
- cras_fmt_conv_destroy(dev_stream->conv);
- byte_buffer_destroy(dev_stream->conv_buffer);
+ cras_fmt_conv_destroy(&dev_stream->conv);
+ byte_buffer_destroy(&dev_stream->conv_buffer);
}
free(dev_stream);
}
@@ -286,7 +321,7 @@
cras_rstream_get_cb_threshold(rstream),
&rstream->audio_area->frames);
num_frames = MIN(rstream->audio_area->frames - offset,
- buf_queued_bytes(dev_stream->conv_buffer) /
+ buf_queued(dev_stream->conv_buffer) /
frame_bytes);
ATLOG(atlog, AUDIO_THREAD_CONV_COPY,
@@ -343,14 +378,18 @@
/* Check if format conversion is needed. */
if (cras_fmt_conversion_needed(dev_stream->conv)) {
- unsigned int format_bytes;
+ unsigned int format_bytes, fr_to_capture;
+
+ fr_to_capture = dev_stream_capture_avail(dev_stream);
+ fr_to_capture = MIN(fr_to_capture, area->frames - area_offset);
format_bytes = cras_get_format_bytes(
cras_fmt_conv_in_format(dev_stream->conv));
nread = capture_with_fmt_conv(
dev_stream,
area->channels[0].buf + area_offset * format_bytes,
- area->frames - area_offset);
+ fr_to_capture);
+
capture_copy_converted_to_stream(dev_stream, rstream,
software_gain_scaler);
} else {
@@ -446,15 +485,15 @@
/* Sample rate conversion may cause some sample left in conv_buffer
* take this buffer into account. */
- conv_buf_level = buf_queued_bytes(dev_stream->conv_buffer) /
+ conv_buf_level = buf_queued(dev_stream->conv_buffer) /
format_bytes;
- if (frames_avail < conv_buf_level)
+ if (frames_avail <= conv_buf_level)
return 0;
else
frames_avail -= conv_buf_level;
frames_avail = MIN(frames_avail,
- buf_available_bytes(dev_stream->conv_buffer) /
+ buf_available(dev_stream->conv_buffer) /
format_bytes);
return cras_fmt_conv_out_frames_to_in(dev_stream->conv, frames_avail);
@@ -495,6 +534,9 @@
unsigned int frames_ready = cras_rstream_get_cb_threshold(rstream);
int rc;
+ if ((rstream->flags & TRIGGER_ONLY) && rstream->triggered)
+ return 0;
+
cras_rstream_update_input_write_pointer(rstream);
/*
@@ -523,6 +565,9 @@
if (rc < 0)
return rc;
+ if (rstream->flags & TRIGGER_ONLY)
+ rstream->triggered = 1;
+
/* Update next callback time according to perfect schedule. */
add_timespecs(&rstream->next_cb_ts,
&rstream->sleep_interval_ts);
@@ -608,7 +653,7 @@
shm = cras_rstream_output_shm(rstream);
/* Don't fetch if the previous request hasn't got response. */
- return !cras_shm_callback_pending(shm) &&
+ return !cras_rstream_is_pending_reply(rstream) &&
cras_shm_is_buffer_available(shm);
}
@@ -625,7 +670,6 @@
add_timespecs(&rstream->next_cb_ts,
&rstream->sleep_interval_ts);
check_next_wake_time(dev_stream);
- cras_shm_set_callback_pending(cras_rstream_output_shm(rstream), 1);
return 0;
}
@@ -634,8 +678,14 @@
{
const struct cras_rstream *stream = dev_stream->stream;
+ /* For streams which rely on dev level timing, we should
+ * let client response wake audio thread up. */
+ if (stream_uses_input(stream) && (stream->flags & USE_DEV_TIMING) &&
+ cras_rstream_is_pending_reply(stream))
+ return stream->fd;
+
if (!stream_uses_output(stream) ||
- !cras_shm_callback_pending(&stream->shm) ||
+ !cras_rstream_is_pending_reply(stream) ||
cras_rstream_get_is_draining(stream))
return -1;
@@ -643,54 +693,69 @@
}
/*
- * 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.
+ * Returns:
+ * 0 on success; negavite error code on failure. A positive value if
+ * there is no need to set wake up time for this stream.
*/
static int get_input_wake_time(struct dev_stream *dev_stream,
unsigned int curr_level,
struct timespec *level_tstamp,
+ unsigned int cap_limit,
+ int is_cap_limit_stream,
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);
+ needed_frames_from_device = dev_stream_capture_avail(dev_stream);
+
+ /*
+ * If this stream is not cap_limit stream, and it needs more
+ * frames than the capture limit from audio thread, don't bother
+ * re-calculating the wake time for it because
+ * |needed_frames_from_device| cannot be all copied to shm until
+ * the cap_limit stream get its samples in shm read by client
+ * and relieve the cap_limit.
+ *
+ * Note that we need to know whether this stream is cap_limit
+ * stream here because the client of cap_limit stream may read
+ * the data from shm during this time window, and cause
+ * needed_frames_from_device to be greater than cap_limit which
+ * was calculated before.
+ */
+ if (!is_cap_limit_stream && needed_frames_from_device > cap_limit)
+ return 1;
+
+ /*
+ * For capture stream using device timing, the flow would be:
+ * 1. Device has less than one cb_threshold of data.
+ * 2. Device has a large chunk of data that client needs to consume
+ * in multiple cycles.
+ * 3. Audio thread sends one block to client and goes to sleep.
+ * 4. Client sends reply to wake up audio thread.
+ * 5. Repeat 3 and 4 until there is less than one cb_threshold of data.
+ * 6. Goes to 1.
+ *
+ * In 1, we schedule the next wake up time based on the needed frames.
+ * This is needed to poll the samples from device.
+ *
+ * In 3, we do not schedule a wake up time for this stream.
+ * We let reply from client wakes up audio thread to send next
+ * cb_threshold of data.
+ *
+ * TODO(cychiang) Do we want to actually block sending data to client
+ * until client replies ? Or control the scheduling of wake up time
+ * is enough ?
+ *
+ */
+ if ((rstream->flags & USE_DEV_TIMING) &&
+ cras_rstream_is_pending_reply(rstream))
+ return 1;
+
+ *wake_time_out = rstream->next_cb_ts;
/*
* If current frames in the device can provide needed amount for stream,
@@ -698,7 +763,6 @@
*/
if (curr_level >= needed_frames_from_device)
needed_frames_from_device = 0;
-
else
needed_frames_from_device -= curr_level;
@@ -710,11 +774,11 @@
/* Select the time that is later so both sample and time conditions
* are met. */
- if (timespec_after(&time_for_sample, &rstream->next_cb_ts)) {
+ if (timespec_after(&time_for_sample, &rstream->next_cb_ts))
*wake_time_out = time_for_sample;
- } else {
- *wake_time_out = rstream->next_cb_ts;
- }
+ /* Using device timing means the stream neglects next callback time. */
+ if (rstream->flags & USE_DEV_TIMING)
+ *wake_time_out = time_for_sample;
return 0;
}
@@ -722,6 +786,8 @@
int dev_stream_wake_time(struct dev_stream *dev_stream,
unsigned int curr_level,
struct timespec *level_tstamp,
+ unsigned int cap_limit,
+ int is_cap_limit_stream,
struct timespec *wake_time_out)
{
if (dev_stream->stream->direction == CRAS_STREAM_OUTPUT) {
@@ -734,5 +800,16 @@
}
return get_input_wake_time(dev_stream, curr_level, level_tstamp,
+ cap_limit, is_cap_limit_stream,
wake_time_out);
}
+
+int dev_stream_is_pending_reply(const struct dev_stream *dev_stream)
+{
+ return cras_rstream_is_pending_reply(dev_stream->stream);
+}
+
+int dev_stream_flush_old_audio_messages(struct dev_stream *dev_stream)
+{
+ return cras_rstream_flush_old_audio_messages(dev_stream->stream);
+}
diff --git a/cras/src/server/dev_stream.h b/cras/src/server/dev_stream.h
index c4af825..52838f0 100644
--- a/cras/src/server/dev_stream.h
+++ b/cras/src/server/dev_stream.h
@@ -162,13 +162,20 @@
* Args:
* dev_stream[in]: The dev_stream to check wake up time.
* curr_level[in]: The current level of device.
+ * level_tstamp[in]: The time stamp when getting current level of device.
+ * cap_limit[in]: The number of frames that can be captured across all
+ * streams.
+ * is_cap_limit_stream[in]: 1 if this stream is causing cap_limit.
* wake_time_out[out]: A timespec for wake up time.
* Returns:
* 0 on success; negative error code on failure.
+ * A positive value if there is no need to set wake up time for this stream.
*/
int dev_stream_wake_time(struct dev_stream *dev_stream,
unsigned int curr_level,
struct timespec *level_tstamp,
+ unsigned int cap_limit,
+ int is_cap_limit_stream,
struct timespec *wake_time_out);
/*
@@ -192,4 +199,11 @@
return &dev_stream->stream->sleep_interval_ts;
}
+int dev_stream_is_pending_reply(const struct dev_stream *dev_stream);
+
+/*
+ * Reads any pending audio message from the socket.
+ */
+int dev_stream_flush_old_audio_messages(struct dev_stream *dev_stream);
+
#endif /* DEV_STREAM_H_ */
diff --git a/cras/src/server/float_buffer.h b/cras/src/server/float_buffer.h
new file mode 100644
index 0000000..55e176e
--- /dev/null
+++ b/cras/src/server/float_buffer.h
@@ -0,0 +1,128 @@
+/* Copyright 2018 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 FLOAT_BUFFER_H_
+#define FLOAT_BUFFER_H_
+
+#include "byte_buffer.h"
+
+/*
+ * Circular buffer storing deinterleaved floating point data.
+ * Members:
+ * fp - Pointer to be filled wtih read/write position of the buffer.
+ * num_channels - Number of channels.
+ */
+struct float_buffer {
+ struct byte_buffer *buf;
+ float **fp;
+ unsigned int num_channels;
+};
+
+/*
+ * Creates an float_buffer.
+ * Args:
+ * max_size - The max number of frames this buffer may store.
+ * num_channels - Number of channels of the deinterleaved data.
+ */
+static inline struct float_buffer *float_buffer_create(
+ unsigned int max_size,
+ unsigned int num_channels)
+{
+ struct float_buffer *b;
+
+ b = (struct float_buffer *)calloc(1, sizeof(*b));
+
+ b->num_channels = num_channels;
+ b->fp = (float **)malloc(num_channels * sizeof(float *));
+ b->buf = (struct byte_buffer *)
+ calloc(1, sizeof(struct byte_buffer) +
+ max_size * num_channels * sizeof(float));
+ b->buf->max_size = max_size;
+ b->buf->used_size = max_size;
+ return b;
+}
+
+/* Destroys the float buffer. */
+static inline void float_buffer_destroy(struct float_buffer **b)
+{
+ if (*b == NULL)
+ return;
+
+ byte_buffer_destroy(&(*b)->buf);
+ free((*b)->fp);
+ free(*b);
+ *b = NULL;
+}
+
+/* Gets the write pointer of given float_buffer. */
+static inline float *const *float_buffer_write_pointer(struct float_buffer *b)
+{
+ unsigned int i;
+ float *data = (float *)b->buf->bytes;
+
+ for (i = 0; i < b->num_channels; i++, data += b->buf->max_size)
+ b->fp[i] = data + b->buf->write_idx;
+ return b->fp;
+}
+
+/* Gets the number of frames can write to the float_buffer. */
+static inline unsigned int float_buffer_writable(struct float_buffer *b)
+{
+ return buf_writable(b->buf);
+}
+
+/* Marks |nwritten| of frames as written to float_buffer. */
+static inline void float_buffer_written(struct float_buffer *b,
+ unsigned int nwritten)
+{
+ buf_increment_write(b->buf, nwritten);
+}
+
+/* Gets the read pointer of given float_buffer. */
+static inline float *const *float_buffer_read_pointer(struct float_buffer *b,
+ unsigned int offset,
+ unsigned int *readable)
+{
+ unsigned int i;
+ float *data = (float *)b->buf->bytes;
+ unsigned int nread = buf_readable(b->buf);
+
+ if (offset >= buf_queued(b->buf)) {
+ *readable = 0;
+ offset = 0;
+ } else if (offset >= nread) {
+ /* wraps */
+ offset = offset + b->buf->read_idx - b->buf->max_size;
+ *readable = MIN(*readable, b->buf->write_idx - offset);
+ } else {
+ *readable = MIN(*readable, nread - offset);
+ offset += b->buf->read_idx;
+ }
+
+ for (i = 0; i < b->num_channels; i++, data += b->buf->max_size)
+ b->fp[i] = data + offset;
+ return b->fp;
+}
+
+/* Gets the buffer level in frames queued in float_buffer. */
+static inline unsigned int float_buffer_level(struct float_buffer *b)
+{
+ return buf_queued(b->buf);
+}
+
+/* Resets float_buffer to initial state. */
+static inline void float_buffer_reset(struct float_buffer *b)
+{
+ buf_reset(b->buf);
+}
+
+/* Marks |nread| frames as read in float_buffer. */
+static inline void float_buffer_read(struct float_buffer *b,
+ unsigned int nread)
+{
+ buf_increment_read(b->buf, nread);
+}
+
+#endif /* FLOAT_BUFFER_H_ */
diff --git a/cras/src/server/iniparser_wrapper.h b/cras/src/server/iniparser_wrapper.h
new file mode 100644
index 0000000..b6d32f9
--- /dev/null
+++ b/cras/src/server/iniparser_wrapper.h
@@ -0,0 +1,22 @@
+/* Copyright 2017 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#ifndef INIPARSER_WRAPPER_H_
+#define INIPARSER_WRAPPER_H_
+
+#include <iniparser.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+static inline dictionary *iniparser_load_wrapper(const char *ini_name)
+{
+ struct stat st;
+ int rc = stat(ini_name, &st);
+ if (rc < 0)
+ return NULL;
+ return iniparser_load(ini_name);
+}
+
+#endif /* INIPARSER_WRAPPER_H_ */
diff --git a/cras/src/server/input_data.c b/cras/src/server/input_data.c
new file mode 100644
index 0000000..a790e11
--- /dev/null
+++ b/cras/src/server/input_data.c
@@ -0,0 +1,139 @@
+/* Copyright 2018 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 "buffer_share.h"
+#include "cras_audio_area.h"
+#include "cras_dsp_pipeline.h"
+#include "cras_mix.h"
+#include "cras_rstream.h"
+#include "cras_system_state.h"
+#include "dsp_util.h"
+#include "input_data.h"
+#include "utlist.h"
+
+void input_data_run(struct ext_dsp_module *ext,
+ unsigned int nframes)
+{
+ struct input_data *data = (struct input_data *)ext;
+ float *const *wp;
+ int i;
+ unsigned int writable;
+ unsigned int offset = 0;
+
+ while (nframes) {
+ writable = float_buffer_writable(data->fbuffer);
+ writable = MIN(nframes, writable);
+ if (!writable) {
+ syslog(LOG_ERR, "Not enough space to process input data");
+ break;
+ }
+ wp = float_buffer_write_pointer(data->fbuffer);
+ for (i = 0; i < data->fbuffer->num_channels; i++)
+ memcpy(wp[i], ext->ports[i] + offset, writable * sizeof(float));
+
+ float_buffer_written(data->fbuffer, writable);
+ nframes -= writable;
+ offset += writable;
+ }
+}
+
+void input_data_configure(struct ext_dsp_module *ext, unsigned int buffer_size,
+ unsigned int num_channels, unsigned int rate)
+{
+ struct input_data *data = (struct input_data *)ext;
+
+ if (data->fbuffer)
+ float_buffer_destroy(&data->fbuffer);
+ data->fbuffer = float_buffer_create(buffer_size, num_channels);
+}
+
+struct input_data *input_data_create(void *dev_ptr)
+{
+ struct input_data *data = (struct input_data *)calloc(1, sizeof(*data));
+
+ data->dev_ptr = dev_ptr;
+
+ data->ext.run = input_data_run;
+ data->ext.configure = input_data_configure;
+
+ return data;
+}
+
+void input_data_destroy(struct input_data **data)
+{
+ if ((*data)->fbuffer)
+ float_buffer_destroy(&(*data)->fbuffer);
+ free(*data);
+ *data = NULL;
+}
+
+void input_data_set_all_streams_read(struct input_data *data,
+ unsigned int nframes)
+{
+ if (!data->fbuffer)
+ return;
+
+ if (float_buffer_level(data->fbuffer) < nframes) {
+ syslog(LOG_ERR, "All streams read %u frames exceeds %u"
+ " in input_data's buffer",
+ nframes, float_buffer_level(data->fbuffer));
+ float_buffer_reset(data->fbuffer);
+ return;
+ }
+ float_buffer_read(data->fbuffer, nframes);
+}
+
+int input_data_get_for_stream(struct input_data *data,
+ struct cras_rstream *stream,
+ struct buffer_share *offsets,
+ struct cras_audio_area **area,
+ unsigned int *offset)
+{
+ unsigned int apm_processed;
+ struct cras_apm *apm;
+
+ /*
+ * It is possible that area buffer frames is smaller than the
+ * offset of stream. In this case, just reset the offset value
+ * to area->frames to prevent caller using these information get
+ * bad access to data.
+ */
+ *area = data->area;
+ *offset = MIN(buffer_share_id_offset(offsets, stream->stream_id),
+ data->area->frames);
+
+ apm = cras_apm_list_get(stream->apm_list, data->dev_ptr);
+ if (apm == NULL)
+ return 0;
+
+ apm_processed = cras_apm_list_process(apm, data->fbuffer, *offset);
+ if (apm_processed < 0) {
+ cras_apm_list_remove(stream->apm_list, apm);
+ return 0;
+ }
+ buffer_share_offset_update(offsets, stream->stream_id, apm_processed);
+ *area = cras_apm_list_get_processed(apm);
+ *offset = 0;
+
+ return 0;
+}
+
+int input_data_put_for_stream(struct input_data *data,
+ struct cras_rstream *stream,
+ struct buffer_share *offsets,
+ unsigned int frames)
+{
+ struct cras_apm *apm = cras_apm_list_get(
+ stream->apm_list, data->dev_ptr);
+
+ if (apm)
+ cras_apm_list_put_processed(apm, frames);
+ else
+ buffer_share_offset_update(offsets, stream->stream_id, frames);
+
+ return 0;
+}
diff --git a/cras/src/server/input_data.h b/cras/src/server/input_data.h
new file mode 100644
index 0000000..71ec1a6
--- /dev/null
+++ b/cras/src/server/input_data.h
@@ -0,0 +1,79 @@
+/* Copyright 2018 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 INPUT_DATA_H_
+#define INPUT_DATA_H_
+
+#include "cras_dsp_pipeline.h"
+#include "float_buffer.h"
+
+/*
+ * Structure holding the information used when a chunk of input buffer
+ * is accessed by multiple streams with different properties and
+ * processing requirements.
+ * Member:
+ * ext - Provides interface to read and process buffer in dsp pipeline.
+ * dev_ptr - Pointer to the associated input iodev.
+ * area - The audio area used for deinterleaved data copy.
+ * fbuffer - Floating point buffer from input device.
+ */
+struct input_data {
+ struct ext_dsp_module ext;
+ void *dev_ptr;
+ struct cras_audio_area *area;
+ struct float_buffer *fbuffer;
+};
+
+/*
+ * Creates an input_data instance for input iodev.
+ * Args:
+ * dev_ptr - Pointer to the associated input device.
+ */
+struct input_data *input_data_create(void *dev_ptr);
+
+/* Destroys an input_data instance. */
+void input_data_destroy(struct input_data **data);
+
+/* Sets how many frames in buffer has been read by all input streams. */
+void input_data_set_all_streams_read(struct input_data *data,
+ unsigned int nframes);
+
+/*
+ * Gets an audio area for |stream| to read data from. An input_data may be
+ * accessed by multiple streams while some requires processing, the
+ * |offsets| arguments helps track the offset value each stream has read
+ * into |data|.
+ * Args:
+ * data - The input data to get audio area from.
+ * stream - The stream that reads data.
+ * offsets - Structure holding the mapping from stream to the offset value
+ * of how many frames each stream has read into input buffer.
+ * area - To be filled with a pointer to an audio area struct for stream to
+ * read data.
+ * offset - To be filled with the samples offset in |area| that |stream|
+ * should start reading.
+ */
+int input_data_get_for_stream(
+ struct input_data *data,
+ struct cras_rstream *stream,
+ struct buffer_share *offsets,
+ struct cras_audio_area **area,
+ unsigned int *offset);
+
+/*
+ * Marks |frames| of audio data as read by |stream|.
+ * Args:
+ * data - The input_data to mark frames has been read by |stream|.
+ * stream - The stream that has read audio data.
+ * offsets - Structure holding the mapping from stream to the offset value
+ * of how many frames each stream has read into input buffer.
+ * frames - Number of frames |stream| has read.
+ */
+int input_data_put_for_stream(struct input_data *data,
+ struct cras_rstream *stream,
+ struct buffer_share *offsets,
+ unsigned int frames);
+
+#endif /* INPUT_DATA_H_ */
diff --git a/cras/src/server/polled_interval_checker.c b/cras/src/server/polled_interval_checker.c
new file mode 100644
index 0000000..f647455
--- /dev/null
+++ b/cras/src/server/polled_interval_checker.c
@@ -0,0 +1,47 @@
+/* Copyright 2018 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_util.h"
+#include "polled_interval_checker.h"
+
+struct polled_interval {
+ struct timespec last_interval_start_ts;
+ int interval_sec;
+};
+
+static struct timespec now;
+
+static inline int get_sec_since_last_active(
+ const struct timespec *last_active_ts) {
+ struct timespec diff;
+ subtract_timespecs(&now, last_active_ts, &diff);
+ return diff.tv_sec;
+}
+
+void pic_update_current_time() {
+ clock_gettime(CLOCK_MONOTONIC_RAW, &now);
+}
+
+struct polled_interval *pic_polled_interval_create(int interval_sec) {
+ struct polled_interval *pi;
+ pi = malloc(sizeof(*pi));
+ pi->last_interval_start_ts = now;
+ pi->interval_sec = interval_sec;
+ return pi;
+}
+
+void pic_polled_interval_destroy(struct polled_interval **interval) {
+ free(*interval);
+ *interval = NULL;
+}
+
+int pic_interval_elapsed(const struct polled_interval *pi) {
+ return get_sec_since_last_active(&pi->last_interval_start_ts) >=
+ pi->interval_sec;
+}
+
+void pic_interval_reset(struct polled_interval *pi) {
+ pi->last_interval_start_ts = now;
+}
\ No newline at end of file
diff --git a/cras/src/server/polled_interval_checker.h b/cras/src/server/polled_interval_checker.h
new file mode 100644
index 0000000..fc2fec5
--- /dev/null
+++ b/cras/src/server/polled_interval_checker.h
@@ -0,0 +1,50 @@
+/* Copyright 2018 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 POLLED_ACTIVITY_CHECKER_H_
+#define POLLED_ACTIVITY_CHECKER_H_
+
+#include <time.h>
+
+/* Represents a time interval, in seconds, which can be checked periodically. */
+struct polled_interval;
+
+/*
+ * Creates a new polled_interval, of the specified duration. The interval will
+ * first elapse interval_sec after it was created.
+ *
+ * Call pic_update_current_time() shortly before this function.
+ */
+struct polled_interval *pic_polled_interval_create(int interval_sec);
+
+/*
+ * Destroys the specified polled_interval, and set's the pointer to it to NULL.
+ */
+void pic_polled_interval_destroy(struct polled_interval **interval);
+
+/*
+ * Whether the interval's duration has elapsed (since the interval was created
+ * or reset).
+ *
+ * Call pic_update_current_time() shortly before this function.
+ */
+int pic_interval_elapsed(const struct polled_interval *interval);
+
+/*
+ * Resets the interval; it will elapse it's specified duration from now.
+ *
+ * Call pic_update_current_time() shortly before this function.
+ */
+void pic_interval_reset(struct polled_interval *pi);
+
+/*
+ * Updates the current time, which is used in all other pic_* functions (which
+ * will never update the current time). This update is pulled out separately to
+ * allow the caller to control when and how often the time is updated.
+ */
+void pic_update_current_time();
+
+
+#endif /* POLLED_ACTIVITY_CHECKER_H_ */
\ No newline at end of file
diff --git a/cras/src/server/rate_estimator.c b/cras/src/server/rate_estimator.c
index b6b76ac..e12c0b7 100644
--- a/cras/src/server/rate_estimator.c
+++ b/cras/src/server/rate_estimator.c
@@ -81,12 +81,8 @@
{
struct timespec td;
- /* TODO(hychao) - is this the right thing to do if level is 0? */
- if ((re->window_start_ts.tv_sec == 0) || (level == 0)) {
+ if (re->window_start_ts.tv_sec == 0) {
re->window_start_ts = *now;
- re->window_frames = 0;
- re->level_diff = 0;
- re->last_level = level;
return 0;
}
diff --git a/cras/src/server/server_stream.c b/cras/src/server/server_stream.c
new file mode 100644
index 0000000..242a34c
--- /dev/null
+++ b/cras/src/server/server_stream.c
@@ -0,0 +1,99 @@
+/* Copyright 2018 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_rstream.h"
+#include "cras_server.h"
+#include "cras_system_state.h"
+#include "cras_types.h"
+#include "server_stream.h"
+#include "stream_list.h"
+
+
+/* Parameters used for server stream. */
+static unsigned int server_stream_block_size = 480;
+
+/*
+ * Server stream doesn't care what format is used, because no
+ * client is reading data from stream. The main point is to
+ * make pinned device open and let data flow through its dsp
+ * pipeline.
+ */
+static struct cras_audio_format format =
+{
+ SND_PCM_FORMAT_S16_LE,
+ 48000,
+ 2,
+};
+
+/*
+ * Information of a stream created by server. Currently only
+ * one server stream is allowed, for echo reference use.
+ */
+static struct cras_rstream_config *stream_config;
+
+/* Actually create the server stream and add to stream list. */
+static void server_stream_add_cb(void *data)
+{
+ struct stream_list *stream_list = (struct stream_list *)data;
+ struct cras_rstream *stream;
+
+ if (!stream_config)
+ return;
+
+ stream_list_add(stream_list, stream_config, &stream);
+}
+
+void server_stream_create(struct stream_list *stream_list, unsigned int dev_idx)
+{
+ if (stream_config) {
+ syslog(LOG_ERR, "server stream already exists, dev %u",
+ stream_config->dev_idx);
+ return;
+ }
+
+ stream_config = (struct cras_rstream_config *)
+ calloc(1, sizeof(*stream_config));
+ stream_config->format = &format;
+ stream_config->stream_id =
+ cras_get_stream_id(SERVER_STREAM_CLIENT_ID, 0);
+ stream_config->stream_type = CRAS_STREAM_TYPE_DEFAULT;
+ stream_config->direction = CRAS_STREAM_INPUT;
+ stream_config->flags = SERVER_ONLY;
+ stream_config->buffer_frames = server_stream_block_size;
+ stream_config->cb_threshold = server_stream_block_size;
+ stream_config->dev_idx = dev_idx;
+ stream_config->audio_fd = -1;
+
+ /* Schedule add stream in next main thread loop. */
+ cras_system_add_task(server_stream_add_cb, stream_list);
+}
+
+static void server_stream_rm_cb(void *data)
+{
+ struct stream_list *stream_list = (struct stream_list *)data;
+
+ if (stream_config == NULL)
+ return;
+
+ if (stream_list_rm(stream_list, stream_config->stream_id))
+ syslog(LOG_ERR, "Server stream %x no longer exist",
+ stream_config->stream_id);
+
+ free(stream_config);
+ stream_config = NULL;
+}
+
+void server_stream_destroy(struct stream_list *stream_list,
+ unsigned int dev_idx)
+{
+ if (!stream_config || stream_config->dev_idx != dev_idx) {
+ syslog(LOG_ERR, "No server stream to destroy");
+ return;
+ }
+ /* Schedule remove stream in next main thread loop. */
+ cras_system_add_task(server_stream_rm_cb, stream_list);
+}
diff --git a/cras/src/server/server_stream.h b/cras/src/server/server_stream.h
new file mode 100644
index 0000000..595987c
--- /dev/null
+++ b/cras/src/server/server_stream.h
@@ -0,0 +1,29 @@
+/* Copyright 2018 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 SERVER_STREAM_H_
+#define SERVER_STREAM_H_
+
+struct stream_list;
+
+/*
+ * Asynchronously creates a server stream pinned to device of given idx.
+ * Args:
+ * stream_list - List of stream to add new server stream to.
+ * dev_idx - The id of the device that new server stream will pin to.
+ */
+void server_stream_create(struct stream_list *stream_list,
+ unsigned int dev_idx);
+
+/*
+ * Asynchronously destroys existing server stream pinned to device of given idx.
+ * Args:
+ * stream_list - List of stream to look up server stream.
+ * dev_idx - The device id that target server stream is pinned to.
+ **/
+void server_stream_destroy(struct stream_list *stream_list,
+ unsigned int dev_idx);
+
+#endif /* SERVER_STREAM_H_ */
diff --git a/cras/src/server/test_iodev.c b/cras/src/server/test_iodev.c
index 4955726..37730d3 100644
--- a/cras/src/server/test_iodev.c
+++ b/cras/src/server/test_iodev.c
@@ -67,13 +67,13 @@
{
struct test_iodev *testio = (struct test_iodev *)iodev;
- byte_buffer_destroy(testio->audbuff);
+ byte_buffer_destroy(&testio->audbuff);
testio->audbuff = NULL;
cras_iodev_free_audio_area(iodev);
return 0;
}
-static int open_dev(struct cras_iodev *iodev)
+static int configure_dev(struct cras_iodev *iodev)
{
struct test_iodev *testio = (struct test_iodev *)iodev;
@@ -193,7 +193,7 @@
iodev->supported_formats = test_supported_formats;
iodev->buffer_size = TEST_BUFFER_SIZE;
- iodev->open_dev = open_dev;
+ iodev->configure_dev = configure_dev;
iodev->close_dev = close_dev;
iodev->frames_queued = frames_queued;
iodev->delay_frames = delay_frames;
diff --git a/cras/src/tests/a2dp_iodev_unittest.cc b/cras/src/tests/a2dp_iodev_unittest.cc
index 6e8e3c7..02d5068 100644
--- a/cras/src/tests/a2dp_iodev_unittest.cc
+++ b/cras/src/tests/a2dp_iodev_unittest.cc
@@ -99,14 +99,25 @@
namespace {
static struct timespec time_now;
+class A2dpIodev: public testing::Test {
+ protected:
+ virtual void SetUp() {
+ ResetStubData();
+ atlog = (audio_thread_event_log *)calloc(
+ 1,
+ sizeof(audio_thread_event_log));
+ }
-TEST(A2dpIoInit, InitializeA2dpIodev) {
+ virtual void TearDown() {
+ free(dummy_audio_area);
+ dummy_audio_area = NULL;
+ free(atlog);
+ }
+};
+
+TEST_F(A2dpIodev, InitializeA2dpIodev) {
struct cras_iodev *iodev;
- atlog = (audio_thread_event_log *)calloc(1, sizeof(audio_thread_event_log));
-
- ResetStubData();
-
cras_bt_device_name_ret = NULL;
iodev = a2dp_iodev_create(fake_transport);
@@ -137,11 +148,9 @@
a2dp_iodev_destroy(iodev);
}
-TEST(A2dpIoInit, InitializeFail) {
+TEST_F(A2dpIodev, InitializeFail) {
struct cras_iodev *iodev;
- ResetStubData();
-
init_a2dp_return_val = -1;
iodev = a2dp_iodev_create(fake_transport);
@@ -154,14 +163,13 @@
ASSERT_EQ(0, cras_iodev_rm_node_called);
}
-TEST(A2dpIoInit, OpenIodev) {
+TEST_F(A2dpIodev, OpenIodev) {
struct cras_iodev *iodev;
- ResetStubData();
iodev = a2dp_iodev_create(fake_transport);
iodev_set_format(iodev, &format);
- iodev->open_dev(iodev);
+ iodev->configure_dev(iodev);
ASSERT_EQ(1, cras_bt_transport_acquire_called);
@@ -173,17 +181,16 @@
a2dp_iodev_destroy(iodev);
}
-TEST(A2dpIoInit, GetPutBuffer) {
+TEST_F(A2dpIodev, GetPutBuffer) {
struct cras_iodev *iodev;
struct cras_audio_area *area1, *area2, *area3;
uint8_t *area1_buf;
unsigned frames;
- ResetStubData();
iodev = a2dp_iodev_create(fake_transport);
iodev_set_format(iodev, &format);
- iodev->open_dev(iodev);
+ iodev->configure_dev(iodev);
ASSERT_NE(write_callback, (void *)NULL);
frames = 256;
@@ -233,22 +240,22 @@
ASSERT_EQ(256, frames);
EXPECT_EQ(800, area3->channels[0].buf - area1_buf);
+ iodev->close_dev(iodev);
a2dp_iodev_destroy(iodev);
}
-TEST(A2dpIoInif, FramesQueued) {
+TEST_F(A2dpIodev, FramesQueued) {
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);
+ iodev->configure_dev(iodev);
ASSERT_NE(write_callback, (void *)NULL);
frames = 256;
@@ -303,21 +310,22 @@
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);
+ iodev->close_dev(iodev);
+ a2dp_iodev_destroy(iodev);
}
-TEST(A2dpIo, FlushAtLowBufferLevel) {
+TEST_F(A2dpIodev, 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);
+ iodev->configure_dev(iodev);
ASSERT_NE(write_callback, (void *)NULL);
ASSERT_EQ(iodev->min_buffer_level, 400);
@@ -351,6 +359,8 @@
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);
+ iodev->close_dev(iodev);
+ a2dp_iodev_destroy(iodev);
}
} // namespace
diff --git a/cras/src/tests/alert_unittest.cc b/cras/src/tests/alert_unittest.cc
index f013c1b..5f547f5 100644
--- a/cras/src/tests/alert_unittest.cc
+++ b/cras/src/tests/alert_unittest.cc
@@ -15,8 +15,12 @@
void callback2(void *arg, void *data);
void prepare(struct cras_alert *alert);
+struct cb_data_struct {
+ int data;
+};
+
static int cb1_called = 0;
-static void *cb1_data;
+static cb_data_struct cb1_data;
static int cb2_called = 0;
static int cb2_set_pending = 0;
static int prepare_called = 0;
@@ -26,9 +30,20 @@
cb2_called = 0;
cb2_set_pending = 0;
prepare_called = 0;
+ cb1_data.data = 0;
}
-TEST(Alert, OneCallback) {
+class Alert: public testing::Test {
+ protected:
+ virtual void SetUp() {
+ cb1_data.data = 0;
+ }
+
+ virtual void TearDown() {
+ }
+};
+
+TEST_F(Alert, OneCallback) {
struct cras_alert *alert = cras_alert_create(NULL, 0);
cras_alert_add_callback(alert, &callback1, NULL);
ResetStub();
@@ -39,7 +54,7 @@
cras_alert_destroy(alert);
}
-TEST(Alert, OneCallbackPost2Call1) {
+TEST_F(Alert, OneCallbackPost2Call1) {
struct cras_alert *alert = cras_alert_create(NULL, 0);
cras_alert_add_callback(alert, &callback1, NULL);
ResetStub();
@@ -52,53 +67,55 @@
cras_alert_destroy(alert);
}
-TEST(Alert, OneCallbackWithData) {
+TEST_F(Alert, OneCallbackWithData) {
struct cras_alert *alert = cras_alert_create(NULL, 0);
- const char *data = "ThisIsMyData";
+ struct cb_data_struct data = {1};
cras_alert_add_callback(alert, &callback1, NULL);
ResetStub();
- cras_alert_pending_data(alert, (void *)data, strlen(data) + 1);
+ cras_alert_pending_data(alert, (void *)&data, sizeof(struct cb_data_struct));
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));
+ EXPECT_EQ(1, cb1_data.data);
cras_alert_destroy(alert);
}
-TEST(Alert, OneCallbackTwoDataCalledOnce) {
+TEST_F(Alert, OneCallbackTwoDataCalledOnce) {
struct cras_alert *alert = cras_alert_create(NULL, 0);
- const char *data = "ThisIsMyData";
- const char *data2 = "ThisIsMyData2";
+ struct cb_data_struct data = {1};
+ struct cb_data_struct data2 = {2};
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);
+ cras_alert_pending_data(
+ alert, (void *)&data, sizeof(struct cb_data_struct));
+ cras_alert_pending_data(
+ alert, (void *)&data2, sizeof(struct cb_data_struct));
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));
+ EXPECT_EQ(2, cb1_data.data);
cras_alert_destroy(alert);
}
-TEST(Alert, OneCallbackTwoDataKeepAll) {
+TEST_F(Alert, OneCallbackTwoDataKeepAll) {
struct cras_alert *alert = cras_alert_create(
NULL, CRAS_ALERT_FLAG_KEEP_ALL_DATA);
- const char *data = "ThisIsMyData";
- const char *data2 = "ThisIsMyData2";
+ struct cb_data_struct data = {1};
+ struct cb_data_struct data2 = {2};
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);
+ cras_alert_pending_data(alert, (void *)&data, sizeof(cb_data_struct));
+ cras_alert_pending_data(alert, (void *)&data2, sizeof(cb_data_struct));
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));
+ EXPECT_EQ(2, cb1_data.data);
cras_alert_destroy(alert);
}
-TEST(Alert, TwoCallbacks) {
+TEST_F(Alert, TwoCallbacks) {
struct cras_alert *alert = cras_alert_create(NULL, 0);
cras_alert_add_callback(alert, &callback1, NULL);
cras_alert_add_callback(alert, &callback2, NULL);
@@ -112,7 +129,7 @@
cras_alert_destroy(alert);
}
-TEST(Alert, NoPending) {
+TEST_F(Alert, NoPending) {
struct cras_alert *alert = cras_alert_create(NULL, 0);
cras_alert_add_callback(alert, &callback1, NULL);
ResetStub();
@@ -122,7 +139,7 @@
cras_alert_destroy(alert);
}
-TEST(Alert, PendingInCallback) {
+TEST_F(Alert, PendingInCallback) {
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);
@@ -139,7 +156,7 @@
cras_alert_destroy(alert2);
}
-TEST(Alert, Prepare) {
+TEST_F(Alert, Prepare) {
struct cras_alert *alert = cras_alert_create(prepare, 0);
cras_alert_add_callback(alert, &callback1, NULL);
ResetStub();
@@ -151,7 +168,7 @@
cras_alert_destroy(alert);
}
-TEST(Alert, TwoAlerts) {
+TEST_F(Alert, TwoAlerts) {
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);
@@ -191,7 +208,8 @@
void callback1(void *arg, void *data)
{
cb1_called++;
- cb1_data = data;
+ if(data)
+ cb1_data.data = ((struct cb_data_struct *)data)->data;
}
void callback2(void *arg, void *data)
diff --git a/cras/src/tests/alsa_card_unittest.cc b/cras/src/tests/alsa_card_unittest.cc
index 741c6b4..29c315b 100644
--- a/cras/src/tests/alsa_card_unittest.cc
+++ b/cras/src/tests/alsa_card_unittest.cc
@@ -14,6 +14,7 @@
#include "cras_alsa_io.h"
#include "cras_alsa_mixer.h"
#include "cras_alsa_ucm.h"
+#include "cras_iodev.h"
#include "cras_types.h"
#include "cras_util.h"
#include "utlist.h"
@@ -26,11 +27,12 @@
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 fake_dev1, fake_dev2, fake_dev3, fake_dev4;
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),
+ &fake_dev1,
+ &fake_dev2,
+ &fake_dev3,
+ &fake_dev4,
};
static size_t cras_alsa_iodev_create_return_size;
static size_t cras_alsa_iodev_legacy_complete_init_called;
@@ -89,6 +91,8 @@
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 int ucm_get_echo_reference_dev_name_for_dev_called;
+static const char *ucm_get_echo_reference_dev_name_for_dev_return_value[4];
static void ResetStubData() {
cras_alsa_mixer_create_called = 0;
@@ -154,6 +158,7 @@
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;
+ ucm_get_echo_reference_dev_name_for_dev_called = 0;
}
TEST(AlsaCard, CreateFailInvalidCard) {
@@ -223,6 +228,7 @@
EXPECT_EQ(0, snd_hctl_nonblock_called);
EXPECT_EQ(0, snd_hctl_load_called);
EXPECT_EQ(1, cras_alsa_mixer_create_called);
+ cras_alsa_card_destroy(c);
}
TEST(AlsaCard, CreateFailHctlLoad) {
@@ -632,6 +638,11 @@
mixer_name_2 = (struct mixer_name*)malloc(sizeof(*mixer_name_2));
mixer_name_1->name = name1;
mixer_name_2->name = name2;
+ mixer_name_1->dir = CRAS_STREAM_OUTPUT;
+ mixer_name_2->dir = CRAS_STREAM_OUTPUT;
+ mixer_name_1->type = MIXER_NAME_VOLUME;
+ mixer_name_2->type = MIXER_NAME_VOLUME;
+
DL_APPEND(ucm_get_coupled_mixer_names_return_value, mixer_name_1);
DL_APPEND(ucm_get_coupled_mixer_names_return_value, mixer_name_2);
@@ -715,10 +726,6 @@
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);
@@ -810,6 +817,39 @@
EXPECT_EQ(iniparser_load_called, iniparser_freedict_called);
}
+TEST(AlsaCard, GG) {
+ struct cras_alsa_card *c;
+ cras_alsa_card_info card_info;
+ int info_rets[] = {0, 0, 0, 0, 0, -1};
+ struct cras_ionode nodes[4];
+ const char *echo_ref = "echo ref";
+
+ 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();
+
+ fake_dev1.nodes = nodes;
+ fake_dev2.nodes = nodes + 1;
+ fake_dev3.nodes = nodes + 2;
+ fake_dev4.nodes = nodes + 3;
+ snprintf(nodes[0].name, CRAS_NODE_NAME_BUFFER_SIZE, "dev1");
+ snprintf(nodes[1].name, CRAS_NODE_NAME_BUFFER_SIZE, "dev2");
+ snprintf(nodes[2].name, CRAS_NODE_NAME_BUFFER_SIZE, "dev3");
+ snprintf(nodes[3].name, CRAS_NODE_NAME_BUFFER_SIZE, "echo ref");
+
+ ucm_get_echo_reference_dev_name_for_dev_return_value[0] = strdup(echo_ref);
+
+ c = cras_alsa_card_create(&card_info, device_config_dir,
+ fake_blacklist, NULL);
+
+ EXPECT_NE(static_cast<struct cras_alsa_card *>(NULL), c);
+ EXPECT_EQ(fake_dev1.echo_reference_dev, &fake_dev4);
+ cras_alsa_card_destroy(c);
+}
/* Stubs */
@@ -929,6 +969,12 @@
}
void snd_pcm_info_set_stream(snd_pcm_info_t *obj, snd_pcm_stream_t val) {
}
+const char *snd_pcm_info_get_name(const snd_pcm_info_t *obj) {
+ return "Fake device name";
+}
+const char *snd_pcm_info_get_id(const snd_pcm_info_t *obj) {
+ return "Fake device id";
+}
int snd_ctl_pcm_info(snd_ctl_t *ctl, snd_pcm_info_t *info) {
int ret;
snd_ctl_pcm_info_called++;
@@ -1063,6 +1109,12 @@
ucm_get_sections_called++;
return ucm_get_sections_return_value;
}
+const char *ucm_get_echo_reference_dev_name_for_dev(
+ struct cras_use_case_mgr *mgr, const char *dev)
+{
+ int idx = ucm_get_echo_reference_dev_name_for_dev_called++;
+ return ucm_get_echo_reference_dev_name_for_dev_return_value[idx];
+}
int cras_alsa_mixer_add_controls_in_section(
struct cras_alsa_mixer *cmix,
diff --git a/cras/src/tests/alsa_helpers_unittest.cc b/cras/src/tests/alsa_helpers_unittest.cc
index f774a50..a613929 100644
--- a/cras/src/tests/alsa_helpers_unittest.cc
+++ b/cras/src/tests/alsa_helpers_unittest.cc
@@ -168,7 +168,6 @@
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";
@@ -201,7 +200,7 @@
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);
+ dev_name, &used, &tstamp);
EXPECT_EQ(used, snd_pcm_htimestamp_avail_ret_val);
EXPECT_EQ(tstamp.tv_sec, snd_pcm_htimestamp_tstamp_ret_val.tv_sec);
EXPECT_EQ(tstamp.tv_nsec, snd_pcm_htimestamp_tstamp_ret_val.tv_nsec);
@@ -213,7 +212,6 @@
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";
@@ -221,41 +219,28 @@
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);
+ &avail, &tstamp);
// 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);
+ &avail, &tstamp);
// 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);
+ &avail, &tstamp);
// When avail < buffer_size, there is no underrun.
EXPECT_EQ(avail, buffer_size - 1);
EXPECT_EQ(rc, 0);
- EXPECT_EQ(3, underruns);
}
} // namespace
diff --git a/cras/src/tests/alsa_io_unittest.cc b/cras/src/tests/alsa_io_unittest.cc
index dc58cd7..9a6b7f6 100644
--- a/cras/src/tests/alsa_io_unittest.cc
+++ b/cras/src/tests/alsa_io_unittest.cc
@@ -116,12 +116,15 @@
static int ucm_enable_swap_mode_ret_value;
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 const char *cras_alsa_jack_update_monitor_fake_name = 0;
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_min_software_gain_called;
+static int ucm_get_min_software_gain_ret_value;
+static long ucm_get_min_software_gain_value;
static int ucm_get_max_software_gain_called;
static int ucm_get_max_software_gain_ret_value;
static long ucm_get_max_software_gain_value;
@@ -147,6 +150,9 @@
static const struct cras_volume_curve *fake_get_dBFS_volume_curve_val;
static int cras_iodev_dsp_set_swap_mode_for_node_called;
static std::map<std::string, long> ucm_get_default_node_gain_values;
+static thread_callback audio_thread_cb;
+static void *audio_thread_cb_data;
+static int hotword_send_triggered_msg_called;
void ResetStubData() {
cras_alsa_open_called = 0;
@@ -209,6 +215,9 @@
cras_alsa_jack_get_name_called = 0;
cras_alsa_jack_get_name_ret_value = default_jack_name;
cras_alsa_jack_update_monitor_fake_name = 0;
+ ucm_get_min_software_gain_called = 0;
+ ucm_get_min_software_gain_ret_value = -1;
+ ucm_get_min_software_gain_value = 0;
ucm_get_max_software_gain_called = 0;
ucm_get_max_software_gain_ret_value = -1;
ucm_get_max_software_gain_value = 0;
@@ -393,6 +402,7 @@
EXPECT_EQ(2, cras_card_config_get_volume_curve_for_control_called);
aio = (struct alsa_io *)iodev;
format.frame_rate = 48000;
+ format.num_channels = 1;
cras_iodev_set_format(iodev, &format);
// Test that these flags are cleared after open_dev.
@@ -400,6 +410,8 @@
aio->filled_zeros_for_draining = 512;
iodev->open_dev(iodev);
EXPECT_EQ(1, cras_alsa_open_called);
+ iodev->configure_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);
@@ -482,8 +494,10 @@
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. */
+ /* MaxSoftwareGain is specified in UCM */
ResetStubData();
+ ucm_get_min_software_gain_ret_value = 1;
+ ucm_get_min_software_gain_value = 1;
ucm_get_max_software_gain_ret_value = 0;
ucm_get_max_software_gain_value = 2000;
iodev = alsa_iodev_create_with_default_parameters(0, NULL,
@@ -493,14 +507,62 @@
CRAS_STREAM_INPUT);
ASSERT_EQ(0, alsa_iodev_legacy_complete_init(iodev));
EXPECT_EQ(1, iodev->active_node->software_volume_needed);
+ EXPECT_EQ(DEFAULT_MIN_CAPTURE_GAIN, iodev->active_node->min_software_gain);
EXPECT_EQ(2000, iodev->active_node->max_software_gain);
ASSERT_EQ(1, sys_set_capture_gain_limits_called);
- /* The gain range is [DEFAULT_MIN_CAPTURE_GAIN, maximum softare gain]. */
+ /* The gain range is [DEFAULT_MIN_CAPTURE_GAIN, maximum software gain]. */
ASSERT_EQ(cras_system_set_capture_gain_limits_set_value[0],
DEFAULT_MIN_CAPTURE_GAIN);
ASSERT_EQ(cras_system_set_capture_gain_limits_set_value[1], 2000);
- /* MaxSoftwareGain is not specified in UCM */
+ alsa_iodev_destroy(iodev);
+
+ /* MaxSoftwareGain and MinSoftwareGain are specified in UCM. */
+ ResetStubData();
+ ucm_get_min_software_gain_ret_value = 0;
+ ucm_get_min_software_gain_value = 1000;
+ ucm_get_max_software_gain_ret_value = 0;
+ ucm_get_max_software_gain_value = 2000;
+ iodev = alsa_iodev_create_with_default_parameters(0, NULL,
+ ALSA_CARD_TYPE_INTERNAL, 1,
+ fake_mixer, fake_config,
+ fake_ucm,
+ CRAS_STREAM_INPUT);
+ ASSERT_EQ(0, alsa_iodev_legacy_complete_init(iodev));
+ EXPECT_EQ(1, iodev->active_node->software_volume_needed);
+ EXPECT_EQ(1000, iodev->active_node->min_software_gain);
+ EXPECT_EQ(2000, iodev->active_node->max_software_gain);
+ ASSERT_EQ(1, sys_set_capture_gain_limits_called);
+ /* The gain range is [minimum software gain, maximum software gain]. */
+ ASSERT_EQ(cras_system_set_capture_gain_limits_set_value[0], 1000);
+ ASSERT_EQ(cras_system_set_capture_gain_limits_set_value[1], 2000);
+
+ alsa_iodev_destroy(iodev);
+
+ /* MinSoftwareGain is larger than MaxSoftwareGain in UCM. */
+ ResetStubData();
+ ucm_get_min_software_gain_ret_value = 0;
+ ucm_get_min_software_gain_value = 3000;
+ ucm_get_max_software_gain_ret_value = 0;
+ ucm_get_max_software_gain_value = 2000;
+ iodev = alsa_iodev_create_with_default_parameters(0, NULL,
+ ALSA_CARD_TYPE_INTERNAL, 1,
+ fake_mixer, fake_config,
+ fake_ucm,
+ CRAS_STREAM_INPUT);
+ ASSERT_EQ(0, alsa_iodev_legacy_complete_init(iodev));
+ EXPECT_EQ(1, iodev->active_node->software_volume_needed);
+ EXPECT_EQ(DEFAULT_MIN_CAPTURE_GAIN, iodev->active_node->min_software_gain);
+ EXPECT_EQ(2000, iodev->active_node->max_software_gain);
+ ASSERT_EQ(1, sys_set_capture_gain_limits_called);
+ /* The gain range is [DEFAULT_MIN_CAPTURE_GAIN, maximum software gain]. */
+ ASSERT_EQ(cras_system_set_capture_gain_limits_set_value[0],
+ DEFAULT_MIN_CAPTURE_GAIN);
+ ASSERT_EQ(cras_system_set_capture_gain_limits_set_value[1], 2000);
+
+ alsa_iodev_destroy(iodev);
+
+ /* MaxSoftwareGain is not specified in UCM. */
ResetStubData();
ucm_get_max_software_gain_ret_value = 1;
ucm_get_max_software_gain_value = 1;
@@ -534,7 +596,7 @@
ucm_get_max_software_gain_ret_value = 0;
ucm_get_max_software_gain_value = 2000;
- // Set default node gain to -1000 dBm.
+ // Set default node gain to -1000 * 0.01 dB.
ucm_get_default_node_gain_values["Internal Mic"] = default_node_gain;
// Assume this is the first device so it gets internal mic node name.
@@ -545,7 +607,7 @@
CRAS_STREAM_INPUT);
ASSERT_EQ(0, alsa_iodev_legacy_complete_init(iodev));
- // Gain on node is 300 dBm.
+ // Gain on node is 300 * 0.01 dB.
iodev->active_node->capture_gain = default_node_gain;
// cras_iodev will call cras_iodev_adjust_active_node_gain to get gain for
@@ -647,11 +709,14 @@
aio = (struct alsa_io *)iodev;
format.frame_rate = 48000;
+ format.num_channels = 1;
cras_iodev_set_format(iodev, &format);
ResetStubData();
iodev->open_dev(iodev);
EXPECT_EQ(1, cras_alsa_open_called);
+ iodev->configure_dev(iodev);
+ EXPECT_EQ(1, cras_alsa_open_called);
EXPECT_EQ(1, cras_alsa_mixer_get_minimum_capture_gain_called);
EXPECT_EQ(1, cras_alsa_mixer_get_maximum_capture_gain_called);
EXPECT_EQ(1, sys_set_capture_gain_limits_called);
@@ -675,7 +740,7 @@
long default_node_gain = -1000;
ResetStubData();
- // Set default node gain to -1000 dBm.
+ // Set default node gain to -1000 * 0.01 dB.
ucm_get_default_node_gain_values["Internal Mic"] = default_node_gain;
// Assume this is the first device so it gets internal mic node name.
@@ -690,13 +755,14 @@
// 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.
+ // System gain is set to 2000 * 0.01 dB.
sys_get_capture_gain_return_value = system_gain;
iodev->open_dev(iodev);
+ iodev->configure_dev(iodev);
iodev->close_dev(iodev);
- // Hardware gain is set to 2000 - 1000 dBm.
+ // Hardware gain is set to (2000 - 1000) * 0.01 dB.
EXPECT_EQ(system_gain + default_node_gain, alsa_mixer_set_capture_dBFS_value);
alsa_iodev_destroy(iodev);
@@ -720,12 +786,15 @@
CRAS_STREAM_INPUT);
ASSERT_EQ(0, alsa_iodev_legacy_complete_init(iodev));
+ format.frame_rate = 48000;
+ format.num_channels = 1;
cras_iodev_set_format(iodev, &format);
- /* System gain is set to 1000dBm */
+ /* System gain is set to 1000 * 0.01 dB */
sys_get_capture_gain_return_value = 1000;
iodev->open_dev(iodev);
+ iodev->configure_dev(iodev);
iodev->close_dev(iodev);
/* Hardware gain is set to 0dB when software gain is used. */
@@ -734,9 +803,10 @@
/* Test the case where software gain is not needed. */
iodev->active_node->software_volume_needed = 0;
iodev->open_dev(iodev);
+ iodev->configure_dev(iodev);
iodev->close_dev(iodev);
- /* Hardware gain is set to 1000dBm as got from system capture gain.*/
+ /* Hardware gain is set to 1000 * 0.01 dB as got from system capture gain.*/
EXPECT_EQ(1000, alsa_mixer_set_capture_dBFS_value);
alsa_iodev_destroy(iodev);
@@ -892,7 +962,8 @@
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_ionode * const fake_node = (cras_ionode *)4;
+ struct cras_ionode * const fake_node = (cras_ionode *)calloc(
+ 1, sizeof(struct cras_ionode));
ResetStubData();
// Stub replies that swap mode does not exist.
ucm_swap_mode_exists_ret_value = 0;
@@ -908,6 +979,7 @@
// Stub replies that swap mode exists.
ucm_swap_mode_exists_ret_value = 1;
+ alsa_iodev_destroy((struct cras_iodev *)aio);
aio = (struct alsa_io *)alsa_iodev_create_with_default_parameters(
0, NULL, ALSA_CARD_TYPE_INTERNAL, 0, fake_mixer, fake_config, fake_ucm,
@@ -921,6 +993,7 @@
EXPECT_EQ(1, ucm_enable_swap_mode_called);
alsa_iodev_destroy((struct cras_iodev *)aio);
+ free(fake_node);
}
// Test that system settins aren't touched if no streams active.
@@ -1641,6 +1714,19 @@
ASSERT_EQ(0, node.plugged);
ASSERT_EQ(CRAS_NODE_TYPE_MIC, node.type);
ASSERT_EQ(NODE_POSITION_EXTERNAL, node.position);
+ // Node name is changed to "Mic".
+ ASSERT_EQ(0, strcmp(node.name, "Mic"));
+
+ memset(&node, 0, sizeof(node));
+ node.dev = &dev;
+ dev.direction = CRAS_STREAM_OUTPUT;
+ strcpy(node.name, "DAISY-I2S Headphone Jack");
+ 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);
+ // Node name is changed to "Headphone".
+ ASSERT_EQ(0, strcmp(node.name, "Headphone"));
memset(&node, 0, sizeof(node));
node.dev = &dev;
@@ -1720,8 +1806,7 @@
// HDMI jack, and thus the callback creates an HDMI node.
cras_alsa_jack_get_name_ret_value = "HDMI Jack";
// Set the jack name updated from monitor to be an invalid UTF8 string.
- cras_alsa_jack_update_monitor_fake_name = strdup("Something");
- cras_alsa_jack_update_monitor_fake_name[0] = 0xfe;
+ cras_alsa_jack_update_monitor_fake_name = "\xfeomething";
is_utf8_string_ret_value = 0;
// Add the jack node.
@@ -1785,12 +1870,13 @@
aio_output_->base.format = fmt;
aio_output_->handle = (snd_pcm_t *)0x24;
- rc = aio_output_->base.open_dev(&aio_output_->base);
+ rc = aio_output_->base.configure_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);
+ free(fmt);
}
TEST_F(AlsaVolumeMuteSuite, GetVolumeCurveFromNode)
@@ -1820,12 +1906,13 @@
node = aio_output_->base.nodes->next;
aio_output_->base.active_node = node;
- rc = aio_output_->base.open_dev(&aio_output_->base);
+ rc = aio_output_->base.configure_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);
+ free(fmt);
}
TEST_F(AlsaVolumeMuteSuite, SetVolume) {
@@ -1841,7 +1928,7 @@
aio_output_->num_underruns = 3; // Something non-zero.
sys_get_volume_return_value = fake_system_volume;
- rc = aio_output_->base.open_dev(&aio_output_->base);
+ rc = aio_output_->base.configure_dev(&aio_output_->base);
ASSERT_EQ(0, rc);
EXPECT_EQ(1, alsa_mixer_set_dBFS_called);
EXPECT_EQ(fake_system_volume_dB, alsa_mixer_set_dBFS_value);
@@ -1919,12 +2006,22 @@
fmt_.format = SND_PCM_FORMAT_S16_LE;
fmt_.frame_rate = 48000;
fmt_.num_channels = 2;
+ aio.base.frames_queued = frames_queued;
+ aio.base.direction = CRAS_STREAM_OUTPUT;
aio.base.format = &fmt_;
aio.base.buffer_size = BUFFER_SIZE;
aio.base.min_cb_level = 240;
+ aio.base.min_buffer_level = 0;
+ aio.filled_zeros_for_draining = 0;
+ 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));
}
virtual void TearDown() {
+ free(cras_alsa_mmap_begin_buffer);
}
struct alsa_io aio;
@@ -1935,11 +2032,6 @@
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);
@@ -1948,7 +2040,6 @@
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) {
@@ -1966,14 +2057,17 @@
}
TEST_F(AlsaFreeRunTestSuite, EnterFreeRunNotDrainedYetNeedToFillZeros) {
- int rc;
-
+ int rc, real_hw_level;
+ struct timespec hw_tstamp;
// 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;
+ real_hw_level = 200;
+ cras_alsa_get_avail_frames_avail = BUFFER_SIZE - real_hw_level;
+
+ rc = aio.base.frames_queued(&aio.base, &hw_tstamp);
+ EXPECT_EQ(200, rc);
rc = no_stream(&aio.base, 1);
@@ -1986,13 +2080,13 @@
}
TEST_F(AlsaFreeRunTestSuite, EnterFreeRunNotDrainedYetNoNeedToFillZeros) {
- int rc;
+ int rc, real_hw_level;
// 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;
+ real_hw_level = 500;
+ cras_alsa_get_avail_frames_avail = BUFFER_SIZE - real_hw_level;
rc = no_stream(&aio.base, 1);
@@ -2003,13 +2097,13 @@
}
TEST_F(AlsaFreeRunTestSuite, EnterFreeRunDrained) {
- int rc;
+ int rc, real_hw_level;
// 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;
+ real_hw_level = 40;
+ cras_alsa_get_avail_frames_avail = BUFFER_SIZE - real_hw_level;
aio.filled_zeros_for_draining = 100;
rc = no_stream(&aio.base, 1);
@@ -2021,12 +2115,12 @@
}
TEST_F(AlsaFreeRunTestSuite, EnterFreeRunNoSamples) {
- int rc;
+ int rc, real_hw_level;
// 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;
+ real_hw_level = 0;
+ cras_alsa_get_avail_frames_avail = BUFFER_SIZE - real_hw_level;
rc = no_stream(&aio.base, 1);
@@ -2053,13 +2147,50 @@
EXPECT_EQ(0, output_should_wake(&aio.base));
}
-TEST_F(AlsaFreeRunTestSuite, LeaveFreeRunNotInFreeRun) {
- int rc;
+TEST_F(AlsaFreeRunTestSuite, LeaveFreeRunNotInFreeRunMoreRemain) {
+ int rc, real_hw_level;
+
+ // Compare min_buffer_level + min_cb_level with valid samples left.
+ // 240 + 512 < 900 - 100, so we will get 900 - 100 in appl_ptr_ahead.
+
+ aio.is_free_running = 0;
+ aio.filled_zeros_for_draining = 100;
+ aio.base.min_buffer_level = 512;
+ real_hw_level = 900;
+ cras_alsa_get_avail_frames_avail = BUFFER_SIZE - real_hw_level;
rc = no_stream(&aio.base, 0);
EXPECT_EQ(0, rc);
- EXPECT_EQ(0, cras_alsa_resume_appl_ptr_called);
+ EXPECT_EQ(1, cras_alsa_resume_appl_ptr_called);
+ EXPECT_EQ(800, cras_alsa_resume_appl_ptr_ahead);
+ EXPECT_EQ(0, cras_iodev_fill_odev_zeros_frames);
+ EXPECT_EQ(0, aio.is_free_running);
+ EXPECT_EQ(0, aio.filled_zeros_for_draining);
+}
+
+TEST_F(AlsaFreeRunTestSuite, LeaveFreeRunNotInFreeRunLessRemain) {
+ int rc, real_hw_level;
+
+ // Compare min_buffer_level + min_cb_level with valid samples left.
+ // 240 + 256 > 400 - 500, so we will get 240 + 256 in appl_ptr_ahead.
+ // And it will fill 240 + 256 - 400 = 96 zeros frames into device.
+
+ aio.is_free_running = 0;
+ aio.filled_zeros_for_draining = 500;
+ aio.base.min_buffer_level = 256;
+ real_hw_level = 400;
+ cras_alsa_get_avail_frames_avail = BUFFER_SIZE - real_hw_level;
+
+ 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(96, cras_iodev_fill_odev_zeros_frames);
+ EXPECT_EQ(0, aio.is_free_running);
+ EXPECT_EQ(0, aio.filled_zeros_for_draining);
}
TEST_F(AlsaFreeRunTestSuite, LeaveFreeRunInFreeRun) {
@@ -2085,15 +2216,12 @@
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));
+ aio.num_underruns = 0;
// Ask alsa_io to handle output underrun.
rc = alsa_output_underrun(&aio.base);
EXPECT_EQ(0, rc);
+ EXPECT_EQ(1, aio.num_underruns);
// mmap buffer should be filled with zeros.
zeros = (int16_t *)calloc(BUFFER_SIZE * 2, sizeof(*zeros));
@@ -2106,9 +2234,40 @@
cras_alsa_resume_appl_ptr_ahead);
free(zeros);
- free(cras_alsa_mmap_begin_buffer);
}
+TEST(AlsaHotwordNode, HotwordTriggeredSendMessage) {
+ struct cras_iodev *iodev;
+ struct cras_audio_format format;
+ struct alsa_input_node alsa_node;
+ struct cras_ionode *node = &alsa_node.base;
+ int rc;
+
+ ResetStubData();
+ iodev = alsa_iodev_create_with_default_parameters(
+ 0, NULL, ALSA_CARD_TYPE_INTERNAL, 0, fake_mixer, fake_config, NULL,
+ CRAS_STREAM_INPUT);
+ format.frame_rate = 16000;
+ format.num_channels = 1;
+ cras_iodev_set_format(iodev, &format);
+
+ memset(&alsa_node, 0, sizeof(alsa_node));
+ node->dev = iodev;
+ strcpy(node->name, "Wake on Voice");
+ set_node_initial_state(node, ALSA_CARD_TYPE_INTERNAL);
+ EXPECT_EQ(CRAS_NODE_TYPE_HOTWORD, node->type);
+
+ iodev->active_node = node;
+ iodev->open_dev(iodev);
+ rc = iodev->configure_dev(iodev);
+ free(fake_format);
+ ASSERT_EQ(0, rc);
+
+ ASSERT_NE(reinterpret_cast<thread_callback>(NULL), audio_thread_cb);
+ audio_thread_cb(audio_thread_cb_data);
+ EXPECT_EQ(1, hotword_send_triggered_msg_called);
+ alsa_iodev_destroy(iodev);
+}
} // namespace
@@ -2152,6 +2311,16 @@
return 0;
}
+int cras_iodev_list_suspend_hotword_streams()
+{
+ return 0;
+}
+
+int cras_iodev_list_resume_hotword_stream()
+{
+ return 0;
+}
+
struct audio_thread *cras_iodev_list_get_audio_thread()
{
return NULL;
@@ -2188,8 +2357,7 @@
{
return 0;
}
-int cras_alsa_fill_properties(const char *dev,
- snd_pcm_stream_t stream,
+int cras_alsa_fill_properties(snd_pcm_t *handle,
size_t **rates,
size_t **channel_counts,
snd_pcm_format_t **formats)
@@ -2222,8 +2390,7 @@
snd_pcm_uframes_t severe_underrun_frames,
const char* dev_name,
snd_pcm_uframes_t *used,
- struct timespec *tstamp,
- unsigned int *num_underruns)
+ struct timespec *tstamp)
{
*used = cras_alsa_get_avail_frames_avail;
clock_gettime(CLOCK_MONOTONIC_RAW, tstamp);
@@ -2237,14 +2404,14 @@
}
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)
+ snd_pcm_uframes_t *frames)
{
*dst = cras_alsa_mmap_begin_buffer;
*frames = cras_alsa_mmap_begin_frames;
return 0;
}
int cras_alsa_mmap_commit(snd_pcm_t *handle, snd_pcm_uframes_t offset,
- snd_pcm_uframes_t frames, unsigned int *underruns)
+ snd_pcm_uframes_t frames)
{
return 0;
}
@@ -2530,11 +2697,11 @@
}
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)) {
+ char *ret = (char *)malloc(8);
snprintf(ret, 8, "%s", "1");
return ret;
}
@@ -2558,8 +2725,10 @@
return ucm_enable_swap_mode_ret_value;
}
-unsigned int ucm_get_min_buffer_level(struct cras_use_case_mgr *mgr)
+int ucm_get_min_buffer_level(struct cras_use_case_mgr *mgr,
+ unsigned int *level)
{
+ *level = 0;
return 0;
}
@@ -2573,6 +2742,14 @@
return 0;
}
+int ucm_get_min_software_gain(struct cras_use_case_mgr *mgr, const char *dev,
+ long *gain)
+{
+ ucm_get_min_software_gain_called++;
+ *gain = ucm_get_min_software_gain_value;
+ return ucm_get_min_software_gain_ret_value;
+}
+
int ucm_get_max_software_gain(struct cras_use_case_mgr *mgr, const char *dev,
long *gain)
{
@@ -2611,6 +2788,11 @@
return -EINVAL;
}
+int ucm_get_preempt_hotword(struct cras_use_case_mgr *mgr, const char *dev)
+{
+ return 0;
+}
+
struct cras_volume_curve *cras_volume_curve_create_default()
{
return &default_curve;
@@ -2637,9 +2819,11 @@
int cras_iodev_set_format(struct cras_iodev *iodev,
const struct cras_audio_format *fmt)
{
- fake_format = (struct cras_audio_format *)calloc(1, sizeof(*fake_format));
+ fake_format = (struct cras_audio_format *)calloc(
+ 1,
+ sizeof(cras_audio_format));
// Copy the content of format from fmt into format of iodev.
- memcpy(fake_format, fmt, sizeof(*fake_format));
+ memcpy(fake_format, fmt, sizeof(cras_audio_format));
iodev->format = fake_format;
return 0;
}
@@ -2761,6 +2945,8 @@
void audio_thread_add_callback(int fd, thread_callback cb, void *data)
{
+ audio_thread_cb = cb;
+ audio_thread_cb_data = data;
}
void audio_thread_rm_callback(int fd)
@@ -2771,18 +2957,38 @@
return 0;
}
+int cras_hotword_send_triggered_msg()
+{
+ hotword_send_triggered_msg_called++;
+ return 0;
+}
+
+int snd_pcm_poll_descriptors_count(snd_pcm_t *pcm)
+{
+ return 1;
+}
+
+int snd_pcm_poll_descriptors(snd_pcm_t *pcm, struct pollfd *pfds,
+ unsigned int space)
+{
+ if (space >= 1) {
+ pfds[0].events = POLLIN;
+ pfds[0].fd = 99;
+ }
+ 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)
+int cras_alsa_mmap_get_whole_buffer(snd_pcm_t *handle, uint8_t **dst)
{
snd_pcm_uframes_t offset, frames;
cras_alsa_mmap_get_whole_buffer_called++;
- return cras_alsa_mmap_begin(handle, 0, dst, &offset, &frames, underruns);
+ return cras_alsa_mmap_begin(handle, 0, dst, &offset, &frames);
}
int cras_alsa_resume_appl_ptr(snd_pcm_t *handle, snd_pcm_uframes_t ahead)
diff --git a/cras/src/tests/alsa_jack_unittest.cc b/cras/src/tests/alsa_jack_unittest.cc
index e188f87..a739c70 100644
--- a/cras/src/tests/alsa_jack_unittest.cc
+++ b/cras/src/tests/alsa_jack_unittest.cc
@@ -239,10 +239,12 @@
EXPECT_GE(snd_hctl_elem_next_called, nelems + nhdmi_jacks);
EXPECT_GE(snd_hctl_elem_get_name_called, nelems + njacks);
- if (direction == CRAS_STREAM_OUTPUT)
+ if (direction == CRAS_STREAM_OUTPUT) {
EXPECT_EQ(njacks, cras_alsa_mixer_get_output_matching_name_called);
- if (direction == CRAS_STREAM_INPUT && ucm_get_dev_for_jack_return)
+ }
+ if (direction == CRAS_STREAM_INPUT && ucm_get_dev_for_jack_return) {
EXPECT_EQ(njacks, ucm_get_cap_control_called);
+ }
return jack_list;
}
@@ -357,7 +359,6 @@
snd_hctl_first_elem_return_val = NULL;
ucm_get_cap_control_value = reinterpret_cast<char *>(0x1);
- // Freed in destroy.
cras_alsa_mixer_get_input_matching_name_return_value =
reinterpret_cast<struct mixer_control *>(malloc(1));
@@ -377,6 +378,8 @@
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);
+ // Mixer will be free by alsa_card_destroy, we should free it explicitly here
+ free(cras_alsa_mixer_get_input_matching_name_return_value);
}
TEST(AlsaJacks, CreateGPIOHdmi) {
diff --git a/cras/src/tests/alsa_mixer_unittest.cc b/cras/src/tests/alsa_mixer_unittest.cc
index c689835..2deef7f 100644
--- a/cras/src/tests/alsa_mixer_unittest.cc
+++ b/cras/src/tests/alsa_mixer_unittest.cc
@@ -167,6 +167,7 @@
c = cras_alsa_mixer_create("hw:0");
EXPECT_NE(static_cast<struct cras_alsa_mixer *>(NULL), c);
EXPECT_EQ(1, snd_mixer_open_called);
+ cras_alsa_mixer_destroy(c);
}
TEST(AlsaMixer, CreateFailAttach) {
@@ -180,6 +181,7 @@
EXPECT_EQ(1, snd_mixer_attach_called);
EXPECT_EQ(0, strcmp(snd_mixer_attach_mixdev, "hw:0"));
EXPECT_EQ(1, snd_mixer_close_called);
+ cras_alsa_mixer_destroy(c);
}
TEST(AlsaMixer, CreateFailSelemRegister) {
@@ -194,6 +196,7 @@
EXPECT_EQ(0, strcmp(snd_mixer_attach_mixdev, "hw:0"));
EXPECT_EQ(1, snd_mixer_selem_register_called);
EXPECT_EQ(1, snd_mixer_close_called);
+ cras_alsa_mixer_destroy(c);
}
TEST(AlsaMixer, CreateFailLoad) {
@@ -209,6 +212,7 @@
EXPECT_EQ(1, snd_mixer_selem_register_called);
EXPECT_EQ(1, snd_mixer_load_called);
EXPECT_EQ(1, snd_mixer_close_called);
+ cras_alsa_mixer_destroy(c);
}
TEST(AlsaMixer, CreateNoElements) {
diff --git a/cras/src/tests/alsa_ucm_unittest.cc b/cras/src/tests/alsa_ucm_unittest.cc
index 822ef9e..3199573 100644
--- a/cras/src/tests/alsa_ucm_unittest.cc
+++ b/cras/src/tests/alsa_ucm_unittest.cc
@@ -396,6 +396,8 @@
ASSERT_EQ(2, snd_use_case_get_called);
EXPECT_EQ(snd_use_case_get_id[0], id_1);
EXPECT_EQ(snd_use_case_get_id[1], id_2);
+ free((void *)input_dev_name);
+ free((void *)output_dev_name);
}
TEST(AlsaUcm, GetDeviceRateForDevice) {
@@ -456,6 +458,24 @@
EXPECT_EQ(channel_layout[10], -1);
}
+TEST(AlsaUcm, GetEchoReferenceDev) {
+ struct cras_use_case_mgr *mgr = &cras_ucm_mgr;
+ const char *echo_ref_dev;
+
+ ResetStubData();
+
+ std::string id_1 = "=EchoReferenceDev/Dev1/HiFi";
+ std::string value_1 = "Echo Ref";
+
+ snd_use_case_get_value[id_1] = value_1;
+ echo_ref_dev = ucm_get_echo_reference_dev_name_for_dev(mgr, "Dev1");
+
+ ASSERT_EQ(1, snd_use_case_get_called);
+ EXPECT_EQ(snd_use_case_get_id[0], id_1);
+ EXPECT_EQ(0, strcmp(echo_ref_dev, value_1.c_str()));
+ free((void *)echo_ref_dev);
+}
+
TEST(AlsaUcm, GetHotwordModels) {
struct cras_use_case_mgr *mgr = &cras_ucm_mgr;
const char *models;
@@ -477,6 +497,7 @@
models = ucm_get_hotword_models(mgr);
ASSERT_TRUE(models);
EXPECT_EQ(0, strcmp(models, "en,jp,de"));
+ free((void *)models);
}
TEST(AlsaUcm, SetHotwordModel) {
@@ -784,6 +805,31 @@
ASSERT_TRUE(ret);
}
+TEST(AlsaUcm, MinSoftwareGain) {
+ struct cras_use_case_mgr *mgr = &cras_ucm_mgr;
+ long min_software_gain;
+ int ret;
+ std::string id = "=MinSoftwareGain/Internal Mic/HiFi";
+ std::string value = "2000";
+
+ ResetStubData();
+
+ /* Value can be found in UCM. */
+ snd_use_case_get_value[id] = value;
+
+ ret = ucm_get_min_software_gain(mgr, "Internal Mic", &min_software_gain);
+
+ EXPECT_EQ(0, ret);
+ EXPECT_EQ(2000, min_software_gain);
+
+ ResetStubData();
+
+ /* Value can not be found in UCM. */
+ ret = ucm_get_min_software_gain(mgr, "Internal Mic", &min_software_gain);
+
+ ASSERT_TRUE(ret);
+}
+
TEST(AlsaUcm, DefaultNodeGain) {
struct cras_use_case_mgr *mgr = &cras_ucm_mgr;
long default_node_gain;
@@ -875,6 +921,8 @@
EXPECT_EQ(0, strcmp(mixer_name_1, value_1.c_str()));
EXPECT_EQ(0, strcmp(mixer_name_2, value_2.c_str()));
+ free((void *)mixer_name_1);
+ free((void *)mixer_name_2);
}
TEST(AlsaUcm, GetMainVolumeMixerName) {
@@ -978,6 +1026,9 @@
EXPECT_EQ(0, strcmp(jack_name_1, value_1.c_str()));
EXPECT_EQ(NULL, jack_name_2);
+
+ free((void *)jack_name_1);
+ free((void *)jack_name_2);
}
TEST(AlsaUcm, GetJackTypeForDevice) {
@@ -1013,6 +1064,11 @@
EXPECT_EQ(0, strcmp(jack_type_2, value_2.c_str()));
EXPECT_EQ(NULL, jack_type_3);
EXPECT_EQ(NULL, jack_type_4);
+
+ free((void *)jack_type_1);
+ free((void *)jack_type_2);
+ free((void *)jack_type_3);
+ free((void *)jack_type_4);
}
TEST(AlsaUcm, GetPeriodFramesForDevice) {
diff --git a/cras/src/tests/apm_list_unittest.cc b/cras/src/tests/apm_list_unittest.cc
new file mode 100644
index 0000000..b7d01c5
--- /dev/null
+++ b/cras/src/tests/apm_list_unittest.cc
@@ -0,0 +1,271 @@
+// Copyright 2018 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_apm_list.h"
+#include "cras_audio_area.h"
+#include "cras_dsp_pipeline.h"
+#include "cras_iodev.h"
+#include "cras_iodev_list.h"
+#include "cras_types.h"
+#include "float_buffer.h"
+#include "webrtc_apm.h"
+}
+
+namespace {
+
+static void *stream_ptr = reinterpret_cast<void *>(0x123);
+static void *dev_ptr = reinterpret_cast<void *>(0x345);
+static void *dev_ptr2 = reinterpret_cast<void *>(0x678);
+static struct cras_apm_list *list;
+static struct cras_audio_area fake_audio_area;
+static unsigned int dsp_util_interleave_frames;
+static unsigned int webrtc_apm_process_stream_f_called;
+static unsigned int webrtc_apm_process_reverse_stream_f_called;
+static device_enabled_callback_t device_enabled_callback_val;
+static struct ext_dsp_module *ext_dsp_module_value;
+static struct cras_iodev fake_iodev;
+
+
+TEST(ApmList, ApmListCreate) {
+ list = cras_apm_list_create(stream_ptr, 0);
+ EXPECT_EQ((void *)NULL, list);
+
+ list = cras_apm_list_create(stream_ptr, APM_ECHO_CANCELLATION);
+ EXPECT_NE((void *)NULL, list);
+ EXPECT_EQ(APM_ECHO_CANCELLATION, cras_apm_list_get_effects(list));
+
+ cras_apm_list_destroy(list);
+}
+
+TEST(ApmList, AddRemoveApm) {
+ struct cras_audio_format fmt;
+
+ fmt.num_channels = 2;
+ fmt.frame_rate = 48000;
+ fmt.format = SND_PCM_FORMAT_S16_LE;
+
+ list = cras_apm_list_create(stream_ptr, APM_ECHO_CANCELLATION);
+ EXPECT_NE((void *)NULL, list);
+
+ EXPECT_NE((void *)NULL, cras_apm_list_add(list, dev_ptr, &fmt));
+ EXPECT_EQ((void *)NULL, cras_apm_list_get(list, dev_ptr2));
+
+ EXPECT_NE((void *)NULL, cras_apm_list_add(list, dev_ptr2, &fmt));
+ EXPECT_NE((void *)NULL, cras_apm_list_get(list, dev_ptr));
+
+ cras_apm_list_remove(list, dev_ptr);
+ EXPECT_EQ((void *)NULL, cras_apm_list_get(list, dev_ptr));
+ EXPECT_NE((void *)NULL, cras_apm_list_get(list, dev_ptr2));
+
+ cras_apm_list_remove(list, dev_ptr2);
+ EXPECT_EQ((void *)NULL, cras_apm_list_get(list, dev_ptr2));
+
+ cras_apm_list_destroy(list);
+}
+
+TEST(ApmList, ApmProcessForwardBuffer) {
+ struct cras_apm *apm;
+ struct cras_audio_format fmt;
+ struct cras_audio_area *area;
+ struct float_buffer *buf;
+
+ fmt.num_channels = 2;
+ fmt.frame_rate = 48000;
+ fmt.format = SND_PCM_FORMAT_S16_LE;
+
+ list = cras_apm_list_create(stream_ptr, APM_ECHO_CANCELLATION);
+ EXPECT_NE((void *)NULL, list);
+
+ apm = cras_apm_list_add(list, dev_ptr, &fmt);
+
+ buf = float_buffer_create(500, 2);
+ float_buffer_written(buf, 300);
+ webrtc_apm_process_stream_f_called = 0;
+ cras_apm_list_process(apm, buf, 0);
+ EXPECT_EQ(0, webrtc_apm_process_stream_f_called);
+
+ area = cras_apm_list_get_processed(apm);
+ EXPECT_EQ(0, area->frames);
+
+ float_buffer_reset(buf);
+ float_buffer_written(buf, 200);
+ cras_apm_list_process(apm, buf, 0);
+ area = cras_apm_list_get_processed(apm);
+ EXPECT_EQ(1, webrtc_apm_process_stream_f_called);
+ EXPECT_EQ(480, dsp_util_interleave_frames);
+ EXPECT_EQ(480, area->frames);
+
+ /* Put some processed frames. Another apm_list process will not call
+ * into webrtc_apm because the processed buffer is not yet empty.
+ */
+ cras_apm_list_put_processed(apm, 200);
+ float_buffer_reset(buf);
+ float_buffer_written(buf, 500);
+ cras_apm_list_process(apm, buf, 0);
+ EXPECT_EQ(1, webrtc_apm_process_stream_f_called);
+
+ /* Put another 280 processed frames, so it's now ready for webrtc_apm
+ * to process another chunk of 480 frames (10ms) data.
+ */
+ cras_apm_list_put_processed(apm, 280);
+ cras_apm_list_process(apm, buf, 0);
+ EXPECT_EQ(2, webrtc_apm_process_stream_f_called);
+
+ float_buffer_destroy(&buf);
+ cras_apm_list_destroy(list);
+}
+
+TEST(ApmList, ApmProcessReverseData) {
+ struct cras_apm *apm;
+ struct cras_audio_format fmt;
+ struct float_buffer *buf;
+ float *const *rp;
+ unsigned int nread;
+ struct cras_iodev fake_iodev;
+
+ fmt.num_channels = 2;
+ fmt.frame_rate = 48000;
+ fmt.format = SND_PCM_FORMAT_S16_LE;
+
+ fake_iodev.direction = CRAS_STREAM_OUTPUT;
+ device_enabled_callback_val = NULL;
+ ext_dsp_module_value = NULL;
+ webrtc_apm_process_reverse_stream_f_called = 0;
+
+ cras_apm_list_init("");
+ EXPECT_NE((void *)NULL, device_enabled_callback_val);
+
+ device_enabled_callback_val(&fake_iodev, NULL);
+ EXPECT_NE((void *)NULL, ext_dsp_module_value);
+ EXPECT_NE((void *)NULL, ext_dsp_module_value->ports);
+
+ buf = float_buffer_create(500, 2);
+ float_buffer_written(buf, 500);
+ nread = 500;
+ rp = float_buffer_read_pointer(buf, 0, &nread);
+
+ for (int i = 0; i < buf->num_channels; i++)
+ ext_dsp_module_value->ports[i] = rp[i];
+
+ ext_dsp_module_value->configure(ext_dsp_module_value,
+ 800, 2, 48000);
+ ext_dsp_module_value->run(ext_dsp_module_value, 500);
+ EXPECT_EQ(0, webrtc_apm_process_reverse_stream_f_called);
+
+ list = cras_apm_list_create(stream_ptr, APM_ECHO_CANCELLATION);
+ EXPECT_NE((void *)NULL, list);
+
+ apm = cras_apm_list_add(list, dev_ptr, &fmt);
+
+ ext_dsp_module_value->run(ext_dsp_module_value, 250);
+ EXPECT_EQ(0, webrtc_apm_process_reverse_stream_f_called);
+
+ ext_dsp_module_value->run(ext_dsp_module_value, 250);
+ EXPECT_EQ(1, webrtc_apm_process_reverse_stream_f_called);
+
+ float_buffer_destroy(&buf);
+ cras_apm_list_deinit();
+}
+
+extern "C" {
+int cras_iodev_list_set_device_enabled_callback(
+ device_enabled_callback_t enabled_cb,
+ device_disabled_callback_t disabled_cb,
+ void *cb_data)
+{
+ device_enabled_callback_val = enabled_cb;
+ return 0;
+}
+struct cras_iodev *cras_iodev_list_get_first_enabled_iodev(
+ enum CRAS_STREAM_DIRECTION direction)
+{
+ return &fake_iodev;
+}
+void cras_iodev_set_ext_dsp_module(struct cras_iodev *iodev,
+ struct ext_dsp_module *ext)
+{
+ ext_dsp_module_value = ext;
+}
+struct cras_audio_area *cras_audio_area_create(int num_channels)
+{
+ return &fake_audio_area;
+}
+
+void cras_audio_area_destroy(struct cras_audio_area *area)
+{
+}
+void cras_audio_area_config_channels(struct cras_audio_area *area,
+ const struct cras_audio_format *fmt)
+{
+}
+void cras_audio_area_config_buf_pointers(struct cras_audio_area *area,
+ const struct cras_audio_format *fmt,
+ uint8_t *base_buffer)
+{
+}
+void dsp_util_interleave(float *const *input, int16_t *output, int channels,
+ snd_pcm_format_t format, int frames)
+{
+ dsp_util_interleave_frames = frames;
+}
+struct aec_config *aec_config_get(const char *device_config_dir)
+{
+ return NULL;
+}
+void aec_config_dump(struct aec_config *config)
+{
+}
+struct apm_config *apm_config_get(const char *device_config_dir)
+{
+ return NULL;
+}
+void apm_config_dump(struct apm_config *config)
+{
+}
+webrtc_apm webrtc_apm_create(unsigned int num_channels,
+ unsigned int frame_rate,
+ struct aec_config *aec_config,
+ struct apm_config *apm_config)
+{
+ return reinterpret_cast<webrtc_apm>(0x11);
+}
+void webrtc_apm_destroy(webrtc_apm apm)
+{
+ return;
+}
+int webrtc_apm_process_stream_f(webrtc_apm ptr,
+ int num_channels,
+ int rate,
+ float *const *data)
+{
+ webrtc_apm_process_stream_f_called++;
+ return 0;
+}
+
+int webrtc_apm_process_reverse_stream_f(
+ webrtc_apm ptr,
+ int num_channels, int rate,
+ float *const *data)
+{
+ webrtc_apm_process_reverse_stream_f_called++;
+ return 0;
+}
+int webrtc_apm_aec_dump(webrtc_apm ptr, void** work_queue,
+ int start, FILE *handle)
+{
+ return 0;
+}
+
+} // extern "C"
+} // namespace
+
+
+int main(int argc, char **argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/cras/src/tests/audio_thread_monitor_unittest.cc b/cras/src/tests/audio_thread_monitor_unittest.cc
new file mode 100644
index 0000000..d62c572
--- /dev/null
+++ b/cras/src/tests/audio_thread_monitor_unittest.cc
@@ -0,0 +1,129 @@
+// Copyright 2018 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_audio_thread_monitor.c"
+#include "cras_main_message.h"
+}
+
+// Function call counters
+static int cras_system_state_add_snapshot_called;
+static int audio_thread_dump_thread_info_called;
+
+// Stub data
+static enum CRAS_MAIN_MESSAGE_TYPE type_set;
+struct cras_audio_thread_event_message message;
+
+void ResetStubData() {
+ cras_system_state_add_snapshot_called = 0;
+ audio_thread_dump_thread_info_called = 0;
+ type_set = (enum CRAS_MAIN_MESSAGE_TYPE) 999;
+ message.event_type = (enum CRAS_AUDIO_THREAD_EVENT_TYPE)999;
+}
+
+namespace {
+
+class AudioThreadMonitorTestSuite: public testing::Test {
+ protected:
+ virtual void SetUp() {
+ ResetStubData();
+ }
+
+ virtual void TearDown() {
+ }
+};
+
+TEST_F(AudioThreadMonitorTestSuite, Init) {
+ cras_audio_thread_monitor_init();
+ EXPECT_EQ(type_set, CRAS_MAIN_AUDIO_THREAD_EVENT);
+}
+
+TEST_F(AudioThreadMonitorTestSuite, Busyloop) {
+ cras_audio_thread_busyloop();
+ EXPECT_EQ(message.event_type, AUDIO_THREAD_EVENT_BUSYLOOP);
+}
+
+TEST_F(AudioThreadMonitorTestSuite, Debug) {
+ cras_audio_thread_debug();
+ EXPECT_EQ(message.event_type, AUDIO_THREAD_EVENT_DEBUG);
+}
+
+TEST_F(AudioThreadMonitorTestSuite, Underrun) {
+ cras_audio_thread_underrun();
+ EXPECT_EQ(message.event_type, AUDIO_THREAD_EVENT_UNDERRUN);
+}
+
+TEST_F(AudioThreadMonitorTestSuite, SevereUnderrun) {
+ cras_audio_thread_severe_underrun();
+ EXPECT_EQ(message.event_type, AUDIO_THREAD_EVENT_SEVERE_UNDERRUN);
+}
+
+TEST_F(AudioThreadMonitorTestSuite, TakeSnapshot) {
+ take_snapshot(AUDIO_THREAD_EVENT_DEBUG);
+ EXPECT_EQ(cras_system_state_add_snapshot_called, 1);
+ EXPECT_EQ(audio_thread_dump_thread_info_called, 1);
+}
+
+TEST_F(AudioThreadMonitorTestSuite, EventHandlerDoubleCall) {
+ struct cras_audio_thread_event_message msg;
+ msg.event_type = AUDIO_THREAD_EVENT_DEBUG;
+ handle_audio_thread_event_message((struct cras_main_message *)&msg, NULL);
+ EXPECT_EQ(cras_system_state_add_snapshot_called, 1);
+ EXPECT_EQ(audio_thread_dump_thread_info_called, 1);
+
+ // take_snapshot shouldn't be called since the time interval is short
+ handle_audio_thread_event_message((struct cras_main_message *)&msg, NULL);
+ EXPECT_EQ(cras_system_state_add_snapshot_called, 1);
+ EXPECT_EQ(audio_thread_dump_thread_info_called, 1);
+}
+
+TEST_F(AudioThreadMonitorTestSuite, EventHandlerIgnoreInvalidEvent) {
+ struct cras_audio_thread_event_message msg;
+ msg.event_type = (enum CRAS_AUDIO_THREAD_EVENT_TYPE)999;
+ handle_audio_thread_event_message((struct cras_main_message *)&msg, NULL);
+ EXPECT_EQ(cras_system_state_add_snapshot_called, 0);
+ EXPECT_EQ(audio_thread_dump_thread_info_called, 0);
+}
+
+extern "C" {
+
+void cras_system_state_add_snapshot(
+ struct cras_audio_thread_snapshot *snapshot) {
+ cras_system_state_add_snapshot_called ++;
+}
+
+struct audio_thread* cras_iodev_list_get_audio_thread() {
+ return reinterpret_cast <struct audio_thread*>(0xff);
+}
+
+int audio_thread_dump_thread_info(struct audio_thread *thread,
+ struct audio_debug_info *info) {
+ audio_thread_dump_thread_info_called ++;
+ return 0;
+}
+
+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) {
+ message = *(struct cras_audio_thread_event_message*)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/audio_thread_unittest.cc b/cras/src/tests/audio_thread_unittest.cc
index e150126..083dab1 100644
--- a/cras/src/tests/audio_thread_unittest.cc
+++ b/cras/src/tests/audio_thread_unittest.cc
@@ -14,6 +14,7 @@
#define BUFFER_SIZE 8192
#define FIRST_CB_LEVEL 480
+static int cras_audio_thread_busyloop_called;
static unsigned int cras_rstream_dev_offset_called;
static unsigned int cras_rstream_dev_offset_ret[MAX_CALLS];
static const struct cras_rstream *cras_rstream_dev_offset_rstream_val[MAX_CALLS];
@@ -31,6 +32,7 @@
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 unsigned int cras_iodev_frames_to_play_in_sleep_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;
@@ -54,12 +56,14 @@
}
cras_iodev_all_streams_written_ret = 0;
if (cras_iodev_get_output_buffer_area) {
+ free(cras_iodev_get_output_buffer_area->channels[0].buf);
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;
+ cras_iodev_frames_to_play_in_sleep_called = 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;
@@ -84,6 +88,8 @@
}
virtual void TearDown() {
+ audio_thread_destroy(thread_);
+ ResetGlobalStubData();
}
virtual void SetupDevice(cras_iodev *iodev,
@@ -91,7 +97,7 @@
memset(iodev, 0, sizeof(*iodev));
iodev->info.idx = ++device_id_;
iodev->direction = direction;
- iodev->open_dev = open_dev;
+ iodev->configure_dev = configure_dev;
iodev->close_dev = close_dev;
iodev->frames_queued = frames_queued;
iodev->delay_frames = delay_frames;
@@ -118,7 +124,7 @@
rstream->direction = direction;
rstream->cb_threshold = 480;
rstream->shm.area = static_cast<cras_audio_shm_area*>(
- calloc(1, sizeof(rstream->shm.area)));
+ calloc(1, sizeof(*rstream->shm.area)));
}
void TearDownRstream(struct cras_rstream *rstream) {
@@ -133,7 +139,7 @@
rstream->pinned_dev_idx = pin_to_dev->info.idx;
}
- static int open_dev(cras_iodev* iodev) {
+ static int configure_dev(cras_iodev* iodev) {
open_dev_called_++;
return 0;
}
@@ -251,6 +257,7 @@
EXPECT_EQ(0, rc);
EXPECT_EQ(&iodev, cras_iodev_start_ramp_odev);
EXPECT_EQ(req, cras_iodev_start_ramp_request);
+ thread_rm_open_dev(thread_, &iodev);
}
TEST_F(StreamDeviceSuite, AddRemoveOpenInputDevice) {
@@ -320,6 +327,9 @@
thread_rm_open_dev(thread_, &idev3);
adev = thread_->open_devs[CRAS_STREAM_INPUT];
EXPECT_EQ(adev->dev, &idev2);
+ thread_rm_open_dev(thread_, &idev2);
+ thread_rm_open_dev(thread_, &odev2);
+ thread_rm_open_dev(thread_, &odev3);
}
TEST_F(StreamDeviceSuite, MultipleInputStreamsCopyFirstStreamOffset) {
@@ -363,6 +373,8 @@
EXPECT_EQ(&rstream2, cras_rstream_dev_offset_update_rstream_val[1]);
EXPECT_EQ(0, cras_rstream_dev_offset_update_frames_val[1]);
+ thread_rm_open_dev(thread_, &iodev);
+ thread_rm_open_dev(thread_, &iodev2);
TearDownRstream(&rstream);
TearDownRstream(&rstream2);
TearDownRstream(&rstream3);
@@ -395,7 +407,7 @@
// 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_);
+ dev_io_send_captured_samples(thread_->open_devs[CRAS_STREAM_INPUT]);
// wake_ts is maintained in open_dev.
adev = thread_->open_devs[CRAS_STREAM_INPUT];
@@ -405,6 +417,7 @@
EXPECT_EQ(ts_wake_1.tv_sec, adev->wake_ts.tv_sec);
EXPECT_EQ(ts_wake_1.tv_nsec, adev->wake_ts.tv_nsec);
+ thread_rm_open_dev(thread_, &iodev);
TearDownRstream(&rstream1);
TearDownRstream(&rstream2);
}
@@ -458,8 +471,10 @@
EXPECT_EQ(NULL, dev_stream->next);
// Remove 2 streams, check the streams are removed from both open devices.
- thread_remove_stream(thread_, &rstream, &iodev);
- thread_remove_stream(thread_, &rstream3, &iodev2);
+ dev_io_remove_stream(&thread_->open_devs[rstream.direction],
+ &rstream, &iodev);
+ dev_io_remove_stream(&thread_->open_devs[rstream3.direction],
+ &rstream3, &iodev2);
dev_stream = iodev2.streams;
EXPECT_EQ(NULL, dev_stream);
@@ -471,6 +486,7 @@
dev_stream = iodev.streams;
EXPECT_EQ(NULL, dev_stream);
+ thread_rm_open_dev(thread_, &iodev);
TearDownRstream(&rstream);
TearDownRstream(&rstream2);
TearDownRstream(&rstream3);
@@ -498,7 +514,9 @@
cras_iodev_prepare_output_before_write_samples_ret = -EINVAL;
// cras_iodev should handle no stream playback.
- EXPECT_EQ(-EINVAL, write_output_samples(thread_, adev));
+ EXPECT_EQ(-EINVAL,
+ write_output_samples(&thread_->open_devs[CRAS_STREAM_OUTPUT],
+ adev, nullptr));
// cras_iodev_get_output_buffer in audio_thread write_output_samples is not
// called.
@@ -526,7 +544,7 @@
CRAS_IODEV_STATE_NO_STREAM_RUN;
// cras_iodev should handle no stream playback.
- write_output_samples(thread_, adev);
+ write_output_samples(&thread_->open_devs[CRAS_STREAM_OUTPUT], adev, nullptr);
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.
@@ -558,7 +576,7 @@
CRAS_IODEV_STATE_NO_STREAM_RUN;
// cras_iodev should NOT leave no stream state;
- write_output_samples(thread_, adev);
+ write_output_samples(&thread_->open_devs[CRAS_STREAM_OUTPUT], adev, nullptr);
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.
@@ -569,16 +587,48 @@
CRAS_IODEV_STATE_NORMAL_RUN;
// cras_iodev should write samples from streams.
- write_output_samples(thread_, adev);
+ write_output_samples(&thread_->open_devs[CRAS_STREAM_OUTPUT], adev, nullptr);
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) {
+TEST_F(StreamDeviceSuite, DoPlaybackNoStream) {
+ struct cras_iodev iodev;
+
+ ResetGlobalStubData();
+
+ SetupDevice(&iodev, CRAS_STREAM_OUTPUT);
+
+ // Add the device.
+ thread_add_open_dev(thread_, &iodev);
+
+ // 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;
+ // Add 10 frames in queue to prevent underrun
+ frames_queued_ = 10;
+
+ // cras_iodev should handle no stream playback.
+ dev_io_playback_write(&thread_->open_devs[CRAS_STREAM_OUTPUT], nullptr);
+ 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);
+
+ EXPECT_EQ(0, cras_iodev_output_underrun_called);
+ // cras_iodev_frames_to_play_in_sleep should be called from
+ // update_dev_wakeup_time.
+ EXPECT_EQ(1, cras_iodev_frames_to_play_in_sleep_called);
+
+ thread_rm_open_dev(thread_, &iodev);
+}
+
+TEST_F(StreamDeviceSuite, DoPlaybackUnderrun) {
struct cras_iodev iodev, *piodev = &iodev;
- struct open_dev *adev;
struct cras_rstream rstream;
ResetGlobalStubData();
@@ -591,28 +641,29 @@
// 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.
+ // Assume device is running and there is an underrun.
+ // It wrote 11 frames into device but new hw_level is only 10.
+ // It means underrun may happened because 10 - 11 < 0.
// 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;
+ frames_queued_ = 10;
+ cras_iodev_all_streams_written_ret = 11;
// 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(0, cras_iodev_output_underrun_called);
+ dev_io_playback_write(&thread_->open_devs[CRAS_STREAM_OUTPUT], nullptr);
EXPECT_EQ(1, cras_iodev_output_underrun_called);
thread_rm_open_dev(thread_, &iodev);
TearDownRstream(&rstream);
}
-TEST_F(StreamDeviceSuite, DoPlaybackUnderrun) {
+TEST_F(StreamDeviceSuite, DoPlaybackSevereUnderrun) {
struct cras_iodev iodev, *piodev = &iodev;
struct cras_rstream rstream;
@@ -636,7 +687,7 @@
cras_iodev_prepare_output_before_write_samples_state = \
CRAS_IODEV_STATE_NORMAL_RUN;
- do_playback(thread_);
+ dev_io_playback_write(&thread_->open_devs[CRAS_STREAM_OUTPUT], nullptr);
// Audio thread should ask main thread to reset device.
EXPECT_EQ(1, cras_iodev_reset_request_called);
@@ -675,6 +726,28 @@
EXPECT_EQ(0, thread_drain_stream_ms_remaining(&thread, &rstream));
}
+TEST(BusyloopDetectSuite, CheckerTest) {
+ continuous_zero_sleep_count = 0;
+ cras_audio_thread_busyloop_called = 0;
+ timespec wait_ts;
+ wait_ts.tv_sec = 0;
+ wait_ts.tv_nsec = 0;
+
+ check_busyloop(&wait_ts);
+ EXPECT_EQ(continuous_zero_sleep_count, 1);
+ EXPECT_EQ(cras_audio_thread_busyloop_called, 0);
+ check_busyloop(&wait_ts);
+ EXPECT_EQ(continuous_zero_sleep_count, 2);
+ EXPECT_EQ(cras_audio_thread_busyloop_called, 1);
+ check_busyloop(&wait_ts);
+ EXPECT_EQ(continuous_zero_sleep_count, 3);
+ EXPECT_EQ(cras_audio_thread_busyloop_called, 1);
+
+ wait_ts.tv_sec = 1;
+ check_busyloop(&wait_ts);
+ EXPECT_EQ(continuous_zero_sleep_count, 0);
+ EXPECT_EQ(cras_audio_thread_busyloop_called, 1);
+}
extern "C" {
@@ -709,7 +782,8 @@
return 0;
}
-int cras_iodev_open(struct cras_iodev *iodev, unsigned int cb_level)
+int cras_iodev_open(struct cras_iodev *iodev, unsigned int cb_level,
+ const struct cras_audio_format *fmt)
{
return 0;
}
@@ -761,21 +835,20 @@
return 0;
}
-int cras_iodev_put_input_buffer(struct cras_iodev *iodev, unsigned int nframes)
+int cras_iodev_put_input_buffer(struct cras_iodev *iodev)
{
return 0;
}
int cras_iodev_put_output_buffer(struct cras_iodev *iodev, uint8_t *frames,
- unsigned int nframes)
-{
+ unsigned int nframes, int* non_empty,
+ struct cras_fmt_conv *output_converter) {
cras_iodev_put_output_buffer_called++;
cras_iodev_put_output_buffer_nframes = nframes;
return 0;
}
int cras_iodev_get_input_buffer(struct cras_iodev *iodev,
- struct cras_audio_area **area,
unsigned *frames)
{
return 0;
@@ -795,7 +868,7 @@
return 0;
}
-void cras_fmt_conv_destroy(struct cras_fmt_conv *conv)
+void cras_fmt_conv_destroy(struct cras_fmt_conv **conv)
{
}
@@ -957,6 +1030,8 @@
int dev_stream_wake_time(struct dev_stream *dev_stream,
unsigned int curr_level,
struct timespec *level_tstamp,
+ unsigned int cap_limit,
+ int is_cap_limit_stream,
struct timespec *wake_time)
{
if (dev_stream_wake_time_val.find(dev_stream) !=
@@ -967,6 +1042,16 @@
return 0;
}
+int dev_stream_is_pending_reply(const struct dev_stream *dev_stream)
+{
+ return 0;
+}
+
+int dev_stream_flush_old_audio_messages(struct dev_stream *dev_stream)
+{
+ return 0;
+}
+
int cras_iodev_frames_queued(struct cras_iodev *iodev, struct timespec *tstamp)
{
return iodev->frames_queued(iodev, tstamp);
@@ -997,11 +1082,22 @@
return cras_iodev_prepare_output_before_write_samples_ret;
}
+int cras_server_metrics_highest_hw_level(unsigned hw_level,
+ enum CRAS_STREAM_DIRECTION direction)
+{
+ return 0;
+}
+
int cras_server_metrics_longest_fetch_delay(int delay_msec)
{
return 0;
}
+int cras_server_metrics_num_underruns(unsigned num_underruns)
+{
+ return 0;
+}
+
float cras_iodev_get_software_gain_scaler(const struct cras_iodev *iodev)
{
return 1.0f;
@@ -1011,8 +1107,8 @@
unsigned int *hw_level,
struct timespec *hw_tstamp)
{
- clock_gettime(CLOCK_MONOTONIC_RAW, hw_tstamp);
- *hw_level = 0;
+ *hw_level = cras_iodev_frames_queued(odev, hw_tstamp);
+ cras_iodev_frames_to_play_in_sleep_called++;
return 0;
}
@@ -1056,6 +1152,11 @@
return 0;
}
+void cras_iodev_update_highest_hw_level(struct cras_iodev *iodev,
+ unsigned int hw_level)
+{
+}
+
int cras_iodev_start_ramp(struct cras_iodev *odev,
enum CRAS_IODEV_RAMP_REQUEST request)
{
@@ -1064,6 +1165,50 @@
return 0;
}
+int input_data_get_for_stream(
+ struct input_data *data,
+ struct cras_rstream *stream,
+ struct buffer_share *offsets,
+ struct cras_audio_area **area,
+ unsigned int *offset)
+{
+ return 0;
+}
+
+int input_data_put_for_stream(struct input_data *data,
+ struct cras_rstream *stream,
+ struct buffer_share *offsets,
+ unsigned int frames)
+{
+ return 0;
+}
+
+#ifdef HAVE_WEBRTC_APM
+
+uint64_t cras_apm_list_get_effects(struct cras_apm_list *list)
+{
+ return 0;
+}
+
+void cras_apm_list_set_debug_recording(struct cras_apm *apm,
+ unsigned int stream_id, int start, const char *file_name_base)
+{
+}
+void cras_apm_list_set_aec_dump(struct cras_apm_list *list,
+ void *dev_ptr,
+ int start,
+ int fd)
+{
+}
+
+#endif
+
+int cras_audio_thread_busyloop()
+{
+ cras_audio_thread_busyloop_called ++;
+ 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 4f7cd86..e0f340e 100644
--- a/cras/src/tests/bt_device_unittest.cc
+++ b/cras/src/tests/bt_device_unittest.cc
@@ -35,6 +35,7 @@
cras_bt_io_remove_called = 0;
cras_bt_io_destroy_called = 0;
cras_bt_io_try_remove_ret = 0;
+ cras_main_message_send_msg = NULL;
}
namespace {
@@ -55,6 +56,11 @@
d3_.update_active_node = update_active_node;
}
+ virtual void TearDown() {
+ if(cras_main_message_send_msg)
+ free(cras_main_message_send_msg);
+ }
+
static void update_active_node(struct cras_iodev *iodev,
unsigned node_idx,
unsigned dev_enabled) {
@@ -117,6 +123,7 @@
EXPECT_EQ(1, cras_bt_io_remove_called);
EXPECT_EQ(1, cras_bt_io_destroy_called);
EXPECT_EQ(0, cras_bt_device_get_active_profile(device));
+ cras_bt_device_destroy(device);
}
TEST_F(BtDeviceTestSuite, SwitchProfile) {
@@ -150,6 +157,7 @@
cras_main_message_add_handler_callback(
cras_main_message_send_msg,
cras_main_message_add_handler_callback_data);
+ cras_bt_device_destroy(device);
}
/* Stubs */
@@ -260,7 +268,9 @@
/* From iodev_list */
-int cras_iodev_open(struct cras_iodev *dev, unsigned int cb_level) {
+int cras_iodev_open(struct cras_iodev *dev, unsigned int cb_level,
+ const struct cras_audio_format *fmt)
+{
return 0;
}
@@ -287,7 +297,13 @@
int cras_main_message_send(struct cras_main_message *msg)
{
- cras_main_message_send_msg = msg;
+ // cras_main_message is a local variable from caller, we should allocate
+ // memory from heap and copy its data
+ if(cras_main_message_send_msg)
+ free(cras_main_message_send_msg);
+ cras_main_message_send_msg =
+ (struct cras_main_message *)calloc(1, msg->length);
+ memcpy((void *)cras_main_message_send_msg, (void *)msg, msg->length);
return 0;
}
diff --git a/cras/src/tests/bt_io_unittest.cc b/cras/src/tests/bt_io_unittest.cc
index 979404a..aa37cc9 100644
--- a/cras/src/tests/bt_io_unittest.cc
+++ b/cras/src/tests/bt_io_unittest.cc
@@ -8,6 +8,7 @@
// To test static functions.
#include "cras_bt_io.c"
+#include "utlist.h"
}
static struct cras_bt_device *fake_device =
@@ -15,6 +16,7 @@
static unsigned int cras_iodev_add_node_called;
static unsigned int cras_iodev_rm_node_called;
static unsigned int cras_iodev_free_format_called;
+static unsigned int cras_iodev_free_resources_called;
static unsigned int cras_iodev_set_active_node_called;
static unsigned int cras_iodev_list_add_output_called;
static unsigned int cras_iodev_list_rm_output_called;
@@ -33,6 +35,7 @@
cras_iodev_add_node_called = 0;
cras_iodev_rm_node_called = 0;
cras_iodev_free_format_called = 0;
+ cras_iodev_free_resources_called = 0;
cras_iodev_set_active_node_called = 0;
cras_iodev_list_add_output_called = 0;
cras_iodev_list_rm_output_called = 0;
@@ -62,7 +65,7 @@
delay_frames_called_ = 0;
get_buffer_called_ = 0;
put_buffer_called_ = 0;
- open_dev_called_ = 0;
+ configure_dev_called_ = 0;
close_dev_called_ = 0;
}
@@ -77,12 +80,18 @@
d->delay_frames = delay_frames;
d->get_buffer = get_buffer;
d->put_buffer = put_buffer;
- d->open_dev = open_dev;
+ d->configure_dev = configure_dev;
d->close_dev = close_dev;
+ d->supported_rates = NULL;
+ d->supported_channel_counts = NULL;
+ d->supported_formats = NULL;
}
// Stub functions for the iodev structure.
static int update_supported_formats(struct cras_iodev *iodev) {
+ free(iodev->supported_rates);
+ free(iodev->supported_channel_counts);
+ free(iodev->supported_formats);
iodev->supported_rates = (size_t *)calloc(
2, sizeof(*iodev->supported_rates));
iodev->supported_rates[0] = 48000;
@@ -118,11 +127,15 @@
put_buffer_called_++;
return 0;
}
- static int open_dev(cras_iodev* iodev) {
- open_dev_called_++;
+ static int configure_dev(cras_iodev* iodev) {
+ configure_dev_called_++;
return 0;
}
static int close_dev(cras_iodev* iodev) {
+ free(iodev->format);
+ free(iodev->ext_format);
+ iodev->format = NULL;
+ iodev->ext_format = NULL;
close_dev_called_++;
return 0;
}
@@ -135,7 +148,7 @@
static unsigned int delay_frames_called_;
static unsigned int get_buffer_called_;
static unsigned int put_buffer_called_;
- static unsigned int open_dev_called_;
+ static unsigned int configure_dev_called_;
static unsigned int close_dev_called_;
};
@@ -147,7 +160,7 @@
unsigned int BtIoBasicSuite::delay_frames_called_;
unsigned int BtIoBasicSuite::get_buffer_called_;
unsigned int BtIoBasicSuite::put_buffer_called_;
-unsigned int BtIoBasicSuite::open_dev_called_;
+unsigned int BtIoBasicSuite::configure_dev_called_;
unsigned int BtIoBasicSuite::close_dev_called_;
TEST_F(BtIoBasicSuite, CreateBtIo) {
@@ -160,12 +173,13 @@
EXPECT_NE((void *)NULL, bt_iodev);
EXPECT_EQ(&iodev_, active_profile_dev(bt_iodev));
EXPECT_EQ(1, cras_iodev_list_add_output_called);
+ bt_iodev->open_dev(bt_iodev);
bt_iodev->format = &fake_fmt;
bt_iodev->update_supported_formats(bt_iodev);
EXPECT_EQ(1, update_supported_formats_called_);
- bt_iodev->open_dev(bt_iodev);
- EXPECT_EQ(1, open_dev_called_);
+ bt_iodev->configure_dev(bt_iodev);
+ EXPECT_EQ(1, configure_dev_called_);
bt_iodev->frames_queued(bt_iodev, &tstamp);
EXPECT_EQ(1, frames_queued_called_);
bt_iodev->get_buffer(bt_iodev, &fake_area, &fr);
@@ -176,25 +190,31 @@
EXPECT_EQ(1, close_dev_called_);
EXPECT_EQ(1, cras_iodev_free_format_called);
cras_bt_io_destroy(bt_iodev);
+ EXPECT_EQ(1, cras_iodev_free_resources_called);
EXPECT_EQ(1, cras_iodev_list_rm_output_called);
+
+ free(iodev_.supported_rates);
+ free(iodev_.supported_channel_counts);
+ free(iodev_.supported_formats);
}
-TEST_F(BtIoBasicSuite, SwitchProfileOnUpdateFormatForInputDev) {
+TEST_F(BtIoBasicSuite, SwitchProfileOnOpenDevForInputDev) {
ResetStubData();
iodev_.direction = CRAS_STREAM_INPUT;
bt_iodev = cras_bt_io_create(fake_device, &iodev_,
CRAS_BT_DEVICE_PROFILE_HFP_AUDIOGATEWAY);
cras_bt_device_get_active_profile_ret = CRAS_BT_DEVICE_PROFILE_A2DP_SOURCE;
- bt_iodev->update_supported_formats(bt_iodev);
+ bt_iodev->open_dev(bt_iodev);
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_enable_dev_called);
+ cras_bt_io_destroy(bt_iodev);
}
-TEST_F(BtIoBasicSuite, NoSwitchProfileOnUpdateFormatForInputDevAlreadyOnHfp) {
+TEST_F(BtIoBasicSuite, NoSwitchProfileOnOpenDevForInputDevAlreadyOnHfp) {
ResetStubData();
iodev_.direction = CRAS_STREAM_INPUT;
bt_iodev = cras_bt_io_create(fake_device, &iodev_,
@@ -203,9 +223,10 @@
/* No need to switch profile if already on HFP. */
cras_bt_device_get_active_profile_ret =
CRAS_BT_DEVICE_PROFILE_HFP_AUDIOGATEWAY;
- bt_iodev->update_supported_formats(bt_iodev);
+ bt_iodev->open_dev(bt_iodev);
EXPECT_EQ(0, cras_bt_device_switch_profile_enable_dev_called);
+ cras_bt_io_destroy(bt_iodev);
}
TEST_F(BtIoBasicSuite, SwitchProfileOnCloseInputDev) {
@@ -223,6 +244,7 @@
EXPECT_EQ(CRAS_BT_DEVICE_PROFILE_A2DP_SOURCE,
cras_bt_device_set_active_profile_val);
EXPECT_EQ(1, cras_bt_device_switch_profile_called);
+ cras_bt_io_destroy(bt_iodev);
}
TEST_F(BtIoBasicSuite, NoSwitchProfileOnCloseInputDevNoSupportA2dp) {
@@ -238,6 +260,7 @@
bt_iodev->close_dev(bt_iodev);
EXPECT_EQ(0, cras_bt_device_switch_profile_called);
+ cras_bt_io_destroy(bt_iodev);
}
TEST_F(BtIoBasicSuite, SwitchProfileOnAppendA2dpDev) {
@@ -253,6 +276,7 @@
cras_bt_device_set_active_profile_val);
EXPECT_EQ(0, cras_bt_device_switch_profile_enable_dev_called);
EXPECT_EQ(1, cras_bt_device_switch_profile_called);
+ cras_bt_io_destroy(bt_iodev);
}
TEST_F(BtIoBasicSuite, NoSwitchProfileOnAppendHfpDev) {
@@ -265,6 +289,7 @@
CRAS_BT_DEVICE_PROFILE_HFP_AUDIOGATEWAY);
EXPECT_EQ(0, cras_bt_device_switch_profile_enable_dev_called);
+ cras_bt_io_destroy(bt_iodev);
}
TEST_F(BtIoBasicSuite, CreateSetDeviceActiveProfileToA2DP) {
@@ -315,6 +340,7 @@
CRAS_BT_DEVICE_PROFILE_A2DP_SOURCE);
ASSERT_STREQ("BLUETOOTH", bt_iodev->active_node->name);
+ cras_bt_io_destroy(bt_iodev);
}
} // namespace
@@ -330,13 +356,13 @@
void cras_iodev_add_node(struct cras_iodev *iodev, struct cras_ionode *node)
{
cras_iodev_add_node_called++;
- iodev->nodes = node;
+ DL_APPEND(iodev->nodes, node);
}
void cras_iodev_rm_node(struct cras_iodev *iodev, struct cras_ionode *node)
{
cras_iodev_rm_node_called++;
- iodev->nodes = NULL;
+ DL_DELETE(iodev->nodes, node);
}
void cras_iodev_free_format(struct cras_iodev *iodev)
@@ -357,6 +383,11 @@
return 0;
}
+void cras_iodev_free_resources(struct cras_iodev *iodev)
+{
+ cras_iodev_free_resources_called++;
+}
+
// From iodev list.
int cras_iodev_list_add_output(struct cras_iodev *output)
{
@@ -424,6 +455,11 @@
return "/fake/object/path";
}
+int cras_bt_device_get_use_hardware_volume(struct cras_bt_device *device)
+{
+ return 1;
+}
+
int is_utf8_string(const char* string)
{
return is_utf8_string_ret_value;
diff --git a/cras/src/tests/byte_buffer_unittest.cc b/cras/src/tests/byte_buffer_unittest.cc
index 5acfb1a..e0e7bbf 100644
--- a/cras/src/tests/byte_buffer_unittest.cc
+++ b/cras/src/tests/byte_buffer_unittest.cc
@@ -14,8 +14,8 @@
unsigned int data_size;
b = byte_buffer_create(100);
- EXPECT_EQ(100, buf_available_bytes(b));
- EXPECT_EQ(0, buf_queued_bytes(b));
+ EXPECT_EQ(100, buf_available(b));
+ EXPECT_EQ(0, buf_queued(b));
data = buf_read_pointer_size(b, &data_size);
EXPECT_NE((void *)NULL, data);
@@ -31,8 +31,8 @@
EXPECT_EQ(50, data_size);
buf_increment_read(b, 40);
- EXPECT_EQ(10, buf_queued_bytes(b));
- EXPECT_EQ(90, buf_available_bytes(b));
+ EXPECT_EQ(10, buf_queued(b));
+ EXPECT_EQ(90, buf_available(b));
/* Test write to the end of ring buffer. */
data = buf_write_pointer_size(b, &data_size);
@@ -44,7 +44,7 @@
EXPECT_NE((void *)NULL, data);
EXPECT_EQ(40, data_size);
- byte_buffer_destroy(b);
+ byte_buffer_destroy(&b);
}
TEST(ByteBuffer, SetUsedSizeReadWrite) {
@@ -53,12 +53,12 @@
unsigned int data_size;
b = byte_buffer_create(100);
- EXPECT_EQ(100, buf_available_bytes(b));
- EXPECT_EQ(0, buf_queued_bytes(b));
+ EXPECT_EQ(100, buf_available(b));
+ EXPECT_EQ(0, buf_queued(b));
/* Test set used_size to limit the initial allocated max size. */
byte_buffer_set_used_size(b, 90);
- EXPECT_EQ(90, buf_available_bytes(b));
+ EXPECT_EQ(90, buf_available(b));
data = buf_write_pointer_size(b, &data_size);
EXPECT_NE((void *)NULL, data);
@@ -70,8 +70,8 @@
EXPECT_EQ(90, data_size);
buf_increment_read(b, 50);
- EXPECT_EQ(50, buf_available_bytes(b));
- EXPECT_EQ(40, buf_queued_bytes(b));
+ EXPECT_EQ(50, buf_available(b));
+ EXPECT_EQ(40, buf_queued(b));
data = buf_write_pointer_size(b, &data_size);
EXPECT_NE((void *)NULL, data);
@@ -92,7 +92,7 @@
EXPECT_NE((void *)NULL, data);
EXPECT_EQ(50, data_size);
- byte_buffer_destroy(b);
+ byte_buffer_destroy(&b);
}
} // namespace
diff --git a/cras/src/tests/cras_client_unittest.cc b/cras/src/tests/cras_client_unittest.cc
index 1853ae4..4980609 100644
--- a/cras/src/tests/cras_client_unittest.cc
+++ b/cras/src/tests/cras_client_unittest.cc
@@ -164,6 +164,7 @@
handle_capture_data_ready(&stream_, 480);
EXPECT_EQ(1, samples_ready_called);
EXPECT_EQ(0, shm->area->read_buf_idx);
+ FreeShm(shm);
}
void CrasClientTestSuite::StreamConnected(CRAS_STREAM_DIRECTION direction) {
@@ -171,6 +172,7 @@
int shm_fds[2] = {0, 1};
int shm_max_size = 600;
size_t format_bytes;
+ size_t effects = 123;
struct cras_audio_shm_area area;
stream_.direction = direction;
@@ -192,7 +194,8 @@
0,
stream_.id,
&server_format,
- shm_max_size);
+ shm_max_size,
+ effects);
stream_connected(&stream_, &msg, shm_fds, 2);
@@ -222,6 +225,7 @@
int shm_fds[2] = {0, 1};
int shm_max_size = 600;
size_t format_bytes;
+ size_t effects = 123;
struct cras_audio_shm_area area;
int rc;
@@ -250,7 +254,8 @@
1,
stream_.id,
&server_format,
- shm_max_size);
+ shm_max_size,
+ effects);
stream_connected(&stream_, &msg, shm_fds, 2);
@@ -367,4 +372,16 @@
{
return mmap_return_value;
}
+
+struct cras_audio_format *cras_audio_format_create(snd_pcm_format_t format,
+ size_t frame_rate,
+ size_t num_channels)
+{
+ return reinterpret_cast<struct cras_audio_format*>(0x123);
+}
+
+void cras_audio_format_destroy(struct cras_audio_format *fmt)
+{
+}
+
}
diff --git a/cras/src/tests/cras_dsp_pipeline_unittest.cc b/cras/src/tests/cras_dsp_pipeline_unittest.cc
index ef04cb6..21cbc10 100644
--- a/cras/src/tests/cras_dsp_pipeline_unittest.cc
+++ b/cras/src/tests/cras_dsp_pipeline_unittest.cc
@@ -188,6 +188,7 @@
}
static struct dsp_module *modules[MAX_MODULES];
+static struct dsp_module *cras_dsp_module_set_sink_ext_module_val;
static int num_modules;
static struct dsp_module *find_module(const char *name)
{
@@ -210,6 +211,11 @@
modules[num_modules++] = module;
return module;
}
+void cras_dsp_module_set_sink_ext_module(struct dsp_module *module,
+ struct ext_dsp_module *ext_module)
+{
+ cras_dsp_module_set_sink_ext_module_val = module;
+}
}
namespace {
@@ -237,6 +243,7 @@
char filename[sizeof(FILENAME_TEMPLATE) + 1];
FILE *fp;
+ struct ext_dsp_module ext_mod;
};
TEST_F(DspPipelineTestSuite, Simple) {
@@ -335,6 +342,12 @@
ASSERT_EQ(3, d2->input[0]);
ASSERT_EQ(1000, d2->input[1]);
+ /* Expect the sink module "m2" is set. */
+ cras_dsp_pipeline_set_sink_ext_module(p, &ext_mod);
+ struct data *d = (struct data *)
+ cras_dsp_module_set_sink_ext_module_val->data;
+ ASSERT_STREQ("m2", d->title);
+
cras_dsp_pipeline_deinstantiate(p);
ASSERT_EQ(1, d1->deinstantiate_called);
ASSERT_EQ(1, d2->deinstantiate_called);
@@ -465,7 +478,7 @@
int16_t *samples = new int16_t[DSP_BUFFER_SIZE];
fill_test_data(samples, DSP_BUFFER_SIZE);
- cras_dsp_pipeline_apply(p, (uint8_t*)samples, 100);
+ cras_dsp_pipeline_apply(p, (uint8_t*)samples, SND_PCM_FORMAT_S16_LE, 100);
/* the data flow through 2 plugins because m4 is disabled. */
verify_processed_data(samples, 100, 2);
delete[] samples;
@@ -477,6 +490,12 @@
ASSERT_EQ(1, d5->run_called);
ASSERT_EQ(100, d5->sample_count);
+ /* Expect the sink module "m5" is set. */
+ cras_dsp_pipeline_set_sink_ext_module(p, &ext_mod);
+ struct data *d = (struct data *)
+ cras_dsp_module_set_sink_ext_module_val->data;
+ ASSERT_STREQ("m5", d->title);
+
/* re-instantiate */
ASSERT_EQ(1, d5->instantiate_called);
ASSERT_EQ(1, d5->get_delay_called);
diff --git a/cras/src/tests/cras_selinux_helper_unittest.c b/cras/src/tests/cras_selinux_helper_unittest.c
new file mode 100644
index 0000000..0e7efad
--- /dev/null
+++ b/cras/src/tests/cras_selinux_helper_unittest.c
@@ -0,0 +1,10 @@
+// Copyright 2018 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_shm.h"
+
+/* Define a stub cras_selinux_restorecon() which doesn't do anything */
+int cras_selinux_restorecon(const char* pathname) {
+ return 0;
+}
diff --git a/cras/src/tests/cras_test_client.c b/cras/src/tests/cras_test_client.c
index 6db3e11..c7c4565 100644
--- a/cras/src/tests/cras_test_client.c
+++ b/cras/src/tests/cras_test_client.c
@@ -6,6 +6,7 @@
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
+#include <inttypes.h>
#include <math.h>
#include <pthread.h>
#include <stdio.h>
@@ -56,6 +57,12 @@
static int play_short_sound_periods = 0;
static int play_short_sound_periods_left = 0;
+static int effect_aec = 0;
+static int effect_ns = 0;
+static int effect_agc = 0;
+static int effect_vad = 0;
+static char *aecdump_file = NULL;
+
/* 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;
@@ -152,21 +159,6 @@
}
/* Run from callback thread. */
-static int got_hotword(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)
-{
- printf("got hotword %u frames\n", frames);
-
- return frames;
-}
-
-/* Run from callback thread. */
static int put_samples(struct cras_client *client,
cras_stream_id_t stream_id,
uint8_t *captured_samples,
@@ -536,6 +528,10 @@
printf("%-30s dev:%u hw_level:%u target:%u\n",
"DEFAULT_NO_STREAMS", data1, data2, data3);
break;
+ case AUDIO_THREAD_UNDERRUN:
+ printf("%-30s dev:%u hw_level:%u total_written:%u\n",
+ "UNDERRUN", data1, data2, data3);
+ break;
case AUDIO_THREAD_SEVERE_UNDERRUN:
printf("%-30s dev:%u\n", "SEVERE_UNDERRUN", data1);
break;
@@ -545,15 +541,9 @@
}
}
-static void audio_debug_info(struct cras_client *client)
+static void print_audio_debug_info(const struct audio_debug_info *info)
{
- const struct audio_debug_info *info;
int i, j;
-
- info = cras_client_get_audio_debug_info(client);
- if (!info)
- return;
-
printf("Audio Debug Stats:\n");
printf("-------------devices------------\n");
if (info->num_devs > MAX_DEBUG_DEVS)
@@ -572,7 +562,8 @@
"num_channels: %u\n"
"est_rate_ratio: %lf\n"
"num_underruns: %u\n"
- "num_severe_underruns: %u\n",
+ "num_severe_underruns: %u\n"
+ "highest_hw_level: %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,
@@ -581,7 +572,8 @@
(unsigned int)info->devs[i].num_channels,
info->devs[i].est_rate_ratio,
(unsigned int)info->devs[i].num_underruns,
- (unsigned int)info->devs[i].num_severe_underruns);
+ (unsigned int)info->devs[i].num_severe_underruns,
+ (unsigned int)info->devs[i].highest_hw_level);
printf("\n");
}
@@ -591,7 +583,7 @@
for (i = 0; i < info->num_streams; i++) {
int channel;
- printf("stream: %llx dev: %u\n",
+ printf("stream: %llu dev: %u\n",
(unsigned long long)info->streams[i].stream_id,
(unsigned int)info->streams[i].dev_idx);
printf("direction: %s\n",
@@ -601,12 +593,14 @@
cras_stream_type_str(info->streams[i].stream_type));
printf("buffer_frames: %u\n"
"cb_threshold: %u\n"
+ "effects: 0x%.4x\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].effects,
(unsigned int)info->streams[i].frame_rate,
(unsigned int)info->streams[i].num_channels,
(unsigned int)info->streams[i].longest_fetch_sec,
@@ -628,6 +622,72 @@
j++;
j %= info->log.len;
}
+}
+
+static void audio_debug_info(struct cras_client *client)
+{
+ const struct audio_debug_info *info;
+ info = cras_client_get_audio_debug_info(client);
+ if (!info)
+ return;
+ print_audio_debug_info(info);
+
+ /* Signal main thread we are done after the last chunk. */
+ pthread_mutex_lock(&done_mutex);
+ pthread_cond_signal(&done_cond);
+ pthread_mutex_unlock(&done_mutex);
+}
+
+static void print_cras_audio_thread_snapshot(
+ const struct cras_audio_thread_snapshot *snapshot)
+{
+ printf("-------------snapshot------------\n");
+ printf("Event time: %" PRId64 ".%ld\n",
+ (int64_t)snapshot->timestamp.tv_sec,
+ snapshot->timestamp.tv_nsec);
+
+ printf("Event type: ");
+ switch(snapshot->event_type) {
+ case AUDIO_THREAD_EVENT_BUSYLOOP:
+ printf("busyloop\n");
+ break;
+ case AUDIO_THREAD_EVENT_UNDERRUN:
+ printf("underrun\n");
+ break;
+ case AUDIO_THREAD_EVENT_SEVERE_UNDERRUN:
+ printf("severe underrun\n");
+ break;
+ case AUDIO_THREAD_EVENT_DEBUG:
+ printf("debug\n");
+ break;
+ default:
+ printf("no such type\n");
+ }
+ print_audio_debug_info(&snapshot->audio_debug_info);
+}
+
+static void audio_thread_snapshots(struct cras_client *client)
+{
+ const struct cras_audio_thread_snapshot_buffer *snapshot_buffer;
+ uint32_t i;
+ int j;
+ int count = 0;
+
+ snapshot_buffer = cras_client_get_audio_thread_snapshot_buffer(client);
+ i = snapshot_buffer->pos;
+ for(j = 0; j < CRAS_MAX_AUDIO_THREAD_SNAPSHOTS; j++)
+ {
+ if(snapshot_buffer->snapshots[i].timestamp.tv_sec ||
+ snapshot_buffer->snapshots[i].timestamp.tv_nsec)
+ {
+ print_cras_audio_thread_snapshot(
+ &snapshot_buffer->snapshots[i]);
+ count++;
+ }
+ i++;
+ i %= CRAS_MAX_AUDIO_THREAD_SNAPSHOTS;
+ }
+ printf("There are %d, snapshots.\n", count);
/* Signal main thread we are done after the last chunk. */
pthread_mutex_lock(&done_mutex);
@@ -669,6 +729,27 @@
return 0;
}
+static void run_aecdump(struct cras_client *client, uint64_t stream_id,
+ int start)
+{
+ int aecdump_fd;
+ if (start) {
+ aecdump_fd = open(aecdump_file, O_CREAT | O_RDWR | O_TRUNC,
+ 0666);
+ if (aecdump_fd == -1) {
+ printf("Fail to open file %s", aecdump_file);
+ return;
+ }
+
+ printf("Dumping AEC info to %s, stream %" PRId64 ", fd %d\n",
+ aecdump_file, stream_id, aecdump_fd);
+ cras_client_set_aec_dump(client, stream_id, 1, aecdump_fd);
+ } else {
+ cras_client_set_aec_dump(client, stream_id, 0, -1);
+ printf("Close AEC dump file %s\n", aecdump_file);
+ }
+}
+
static int run_file_io_stream(struct cras_client *client,
int fd,
enum CRAS_STREAM_DIRECTION direction,
@@ -677,7 +758,8 @@
size_t rate,
size_t num_channels,
uint32_t flags,
- int is_loopback)
+ int is_loopback,
+ int is_post_dsp)
{
int rc, tty;
struct cras_stream_params *params;
@@ -709,14 +791,10 @@
total_rms_sqr_sum = 0;
total_rms_size = 0;
- if (direction == CRAS_STREAM_INPUT) {
- if (flags == HOTWORD_STREAM)
- aud_cb = got_hotword;
- else
- aud_cb = got_samples;
- } else {
+ if (direction == CRAS_STREAM_INPUT)
+ aud_cb = got_samples;
+ else
aud_cb = put_samples;
- }
if (fd == 0) {
if (direction != CRAS_STREAM_OUTPUT)
@@ -746,12 +824,24 @@
if (params == NULL)
return -ENOMEM;
+ if (effect_aec)
+ cras_client_stream_params_enable_aec(params);
+ if (effect_ns)
+ cras_client_stream_params_enable_ns(params);
+ if (effect_agc)
+ cras_client_stream_params_enable_agc(params);
+ if (effect_vad)
+ cras_client_stream_params_enable_vad(params);
+
cras_client_run_thread(client);
if (is_loopback) {
+ enum CRAS_NODE_TYPE type = (is_post_dsp ?
+ CRAS_NODE_TYPE_POST_DSP :
+ CRAS_NODE_TYPE_POST_MIX_PRE_DSP);
+
cras_client_connected_wait(client);
- pin_device_id = cras_client_get_first_dev_type_idx(client,
- CRAS_NODE_TYPE_POST_MIX_PRE_DSP,
- CRAS_STREAM_INPUT);
+ pin_device_id = cras_client_get_first_dev_type_idx(
+ client, type, CRAS_STREAM_INPUT);
}
stream_playing =
@@ -904,7 +994,9 @@
enum CRAS_STREAM_TYPE stream_type,
size_t rate,
size_t num_channels,
- int is_loopback)
+ uint32_t flags,
+ int is_loopback,
+ int is_post_dsp)
{
int fd = open(file, O_CREAT | O_RDWR | O_TRUNC, 0666);
if (fd == -1) {
@@ -913,7 +1005,8 @@
}
run_file_io_stream(client, fd, CRAS_STREAM_INPUT, block_size,
- stream_type, rate, num_channels, 0, is_loopback);
+ stream_type, rate, num_channels, flags, is_loopback,
+ is_post_dsp);
close(fd);
return 0;
@@ -935,21 +1028,12 @@
}
run_file_io_stream(client, fd, CRAS_STREAM_OUTPUT, block_size,
- stream_type, rate, num_channels, 0, 0);
+ stream_type, rate, num_channels, 0, 0, 0);
close(fd);
return 0;
}
-static int run_hotword(struct cras_client *client,
- size_t block_size,
- size_t rate)
-{
- 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)
{
cras_client_run_thread(client);
@@ -961,7 +1045,24 @@
print_active_stream_info(client);
}
-static void print_audio_debug_info(struct cras_client *client)
+static void show_audio_thread_snapshots(struct cras_client *client)
+{
+ struct timespec wait_time;
+
+ cras_client_run_thread(client);
+ cras_client_connected_wait(client); /* To synchronize data. */
+ cras_client_update_audio_thread_snapshots(client,
+ audio_thread_snapshots);
+
+ 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 show_audio_debug_info(struct cras_client *client)
{
struct timespec wait_time;
@@ -1035,6 +1136,7 @@
{"block_size", required_argument, 0, 'b'},
{"capture_file", required_argument, 0, 'c'},
{"duration_seconds", required_argument, 0, 'd'},
+ {"dump_events", no_argument, 0, 'e'},
{"dump_dsp", no_argument, 0, 'f'},
{"capture_gain", required_argument, 0, 'g'},
{"help", no_argument, 0, 'h'},
@@ -1063,7 +1165,7 @@
{"version", no_argument, 0, '4'},
{"add_test_dev", required_argument, 0, '5'},
{"test_hotword_file", required_argument, 0, '6'},
- {"listen_for_hotword", no_argument, 0, '7'},
+ {"listen_for_hotword", required_argument, 0, '7'},
{"pin_device", required_argument, 0, '8'},
{"suspend", required_argument, 0, '9'},
{"set_node_gain", required_argument, 0, ':'},
@@ -1071,6 +1173,12 @@
{"config_global_remix", required_argument, 0, ';'},
{"set_hotword_model", required_argument, 0, '<'},
{"get_hotword_models", required_argument, 0, '>'},
+ {"post_dsp", required_argument, 0, 'A'},
+ {"stream_id", required_argument, 0, 'B'},
+ {"aecdump", required_argument, 0, 'C'},
+ {"reload_aec_config", no_argument, 0, 'D'},
+ {"effects", required_argument, 0, 'E'},
+ {"get_aec_supported", no_argument, 0, 'F'},
{"syslog_mask", required_argument, 0, 'L'},
{"mute_loop_test", required_argument, 0, 'M'},
{"stream_type", required_argument, 0, 'T'},
@@ -1096,8 +1204,8 @@
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("--listen_for_hotword <name> - Listen and capture hotword stream if supported\n");
+ printf("--loopback_file <name> - Name of file to record from loopback device.\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");
@@ -1119,6 +1227,9 @@
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("--post_dsp <0|1> - Use this flag with --loopback_file. The default value is 0.\n"
+ " Argument: 0 - Record from post-mix, pre-DSP loopback device.\n"
+ " 1 - Record from post-DSP loopback device.\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");
printf("--show_rms - Display RMS value of loopback stream.\n");
@@ -1146,8 +1257,11 @@
const char *capture_file = NULL;
const char *playback_file = NULL;
const char *loopback_file = NULL;
+ int post_dsp = 0;
enum CRAS_STREAM_TYPE stream_type = CRAS_STREAM_TYPE_DEFAULT;
int rc = 0;
+ uint32_t stream_flags = 0;
+ cras_stream_id_t stream_id = 0;
option_index = 0;
openlog("cras_test_client", LOG_PERROR, LOG_USER);
@@ -1180,6 +1294,9 @@
case 'l':
loopback_file = optarg;
break;
+ case 'A':
+ post_dsp = atoi(optarg);
+ break;
case 'b':
block_size = atoi(optarg);
break;
@@ -1337,8 +1454,11 @@
}
break;
}
+ case 'e':
+ show_audio_thread_snapshots(client);
+ break;
case 'm':
- print_audio_debug_info(client);
+ show_audio_debug_info(client);
break;
case 'o':
channel_layout = optarg;
@@ -1369,7 +1489,8 @@
break;
}
case '7': {
- run_hotword(client, 4096, 16000);
+ stream_flags = HOTWORD_STREAM;
+ capture_file = optarg;
break;
}
case '8':
@@ -1451,6 +1572,37 @@
case 'T':
stream_type = atoi(optarg);
break;
+ case 'E': {
+ char *s;
+
+ s = strtok(optarg, ",");
+ while (s) {
+ if (strcmp("aec", s) == 0)
+ effect_aec = 1;
+ else if (strcmp("ns", s) == 0)
+ effect_ns = 1;
+ else if (strcmp("agc", s) == 0)
+ effect_agc = 1;
+ else if (strcmp("vad", s) == 0)
+ effect_vad = 1;
+ else
+ printf("Unknown effect %s\n", s);
+ s = strtok(NULL, ",");
+ }
+ break;
+ }
+ case 'B':
+ stream_id = atoi(optarg);
+ break;
+ case 'C':
+ aecdump_file = optarg;
+ break;
+ case 'D':
+ cras_client_reload_aec_config(client);
+ break;
+ case 'F':
+ printf("AEC supported %d\n",
+ !!cras_client_get_aec_supported(client));
default:
break;
}
@@ -1464,21 +1616,27 @@
if (strcmp(capture_file, "-") == 0)
rc = run_file_io_stream(client, 1, CRAS_STREAM_INPUT,
block_size, stream_type, rate,
- num_channels, 0, 0);
+ num_channels, stream_flags, 0, 0);
else
rc = run_capture(client, capture_file, block_size,
- stream_type, rate, num_channels, 0);
+ stream_type, rate, num_channels,
+ stream_flags, 0, 0);
} else if (playback_file != NULL) {
if (strcmp(playback_file, "-") == 0)
rc = run_file_io_stream(client, 0, CRAS_STREAM_OUTPUT,
block_size, stream_type, rate,
- num_channels, 0, 0);
+ num_channels, stream_flags, 0, 0);
else
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,
- stream_type, rate, num_channels, 1);
+ stream_type, rate, num_channels,
+ stream_flags, 1, post_dsp);
+ } else if (aecdump_file != NULL) {
+ run_aecdump(client, stream_id, 1);
+ sleep(duration_seconds);
+ run_aecdump(client, stream_id, 0);
}
destroy_exit:
diff --git a/cras/src/tests/dev_io_stubs.cc b/cras/src/tests/dev_io_stubs.cc
new file mode 100644
index 0000000..fc6c353
--- /dev/null
+++ b/cras/src/tests/dev_io_stubs.cc
@@ -0,0 +1,144 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <algorithm>
+#include <memory>
+#include <stdint.h>
+#include <stdio.h>
+#include <time.h>
+
+extern "C" {
+#include "dev_stream.h"
+#include "cras_rstream.h"
+#include "cras_iodev.h"
+#include "cras_shm.h"
+#include "cras_types.h"
+#include "utlist.h"
+}
+
+#include "dev_io_stubs.h"
+
+ShmPtr create_shm(size_t cb_threshold) {
+ uint32_t frame_bytes = 4;
+ uint32_t used_size = cb_threshold * 2 * frame_bytes;
+ uint32_t shm_size = sizeof(cras_audio_shm_area) + used_size * 2;
+ ShmPtr shm(reinterpret_cast<cras_audio_shm_area*>(calloc(1, shm_size)),
+ free);
+ shm->config.used_size = used_size;
+ shm->config.frame_bytes = frame_bytes;
+ shm->volume_scaler = 1.0;
+ return shm;
+}
+
+RstreamPtr create_rstream(cras_stream_id_t id,
+ CRAS_STREAM_DIRECTION direction,
+ size_t cb_threshold,
+ const cras_audio_format* format,
+ cras_audio_shm_area* shm) {
+ RstreamPtr rstream(
+ reinterpret_cast<cras_rstream*>(calloc(1, sizeof(cras_rstream))), free);
+ rstream->stream_id = id;
+ rstream->direction = direction;
+ rstream->fd = RSTREAM_FAKE_POLL_FD;
+ rstream->buffer_frames = cb_threshold * 2;
+ rstream->cb_threshold = cb_threshold;
+ rstream->shm.area = shm;
+ rstream->shm.config = shm->config;
+ rstream->format = *format;
+ cras_frames_to_time(cb_threshold,
+ rstream->format.frame_rate,
+ &rstream->sleep_interval_ts);
+ return rstream;
+}
+
+DevStreamPtr create_dev_stream(unsigned int dev_id, cras_rstream* rstream) {
+ DevStreamPtr dstream(
+ reinterpret_cast<dev_stream*>(calloc(1, sizeof(dev_stream))),
+ free);
+ dstream->dev_id = dev_id;
+ dstream->stream = rstream;
+ dstream->dev_rate = rstream->format.frame_rate;
+ return dstream;
+}
+
+StreamPtr create_stream(cras_stream_id_t id,
+ unsigned int dev_id,
+ CRAS_STREAM_DIRECTION direction,
+ size_t cb_threshold,
+ const cras_audio_format* format) {
+ ShmPtr shm = create_shm(cb_threshold);
+ RstreamPtr rstream = create_rstream(1, CRAS_STREAM_INPUT, cb_threshold,
+ format, shm.get());
+ DevStreamPtr dstream = create_dev_stream(1, rstream.get());
+ StreamPtr s(new Stream(std::move(shm),
+ std::move(rstream),
+ std::move(dstream)));
+ return s;
+}
+
+void AddFakeDataToStream(Stream* stream, unsigned int frames) {
+ cras_shm_check_write_overrun(&stream->rstream->shm);
+ cras_shm_buffer_written(&stream->rstream->shm, frames);
+}
+
+int delay_frames_stub(const struct cras_iodev* iodev) {
+ return 0;
+}
+
+IonodePtr create_ionode(CRAS_NODE_TYPE type) {
+ IonodePtr ionode(
+ reinterpret_cast<cras_ionode*>(calloc(1, sizeof(cras_ionode))), free);
+ ionode->type = type;
+ return ionode;
+}
+
+IodevPtr create_open_iodev(CRAS_STREAM_DIRECTION direction,
+ size_t cb_threshold,
+ cras_audio_format* format,
+ cras_ionode* active_node) {
+ IodevPtr iodev(reinterpret_cast<cras_iodev*>(calloc(1, sizeof(cras_iodev))),
+ free);
+ iodev->is_enabled = 1;
+ iodev->direction = direction;
+ iodev->format = format;
+ iodev->state = CRAS_IODEV_STATE_OPEN;
+ iodev->delay_frames = delay_frames_stub;
+ iodev->active_node = active_node;
+ iodev->buffer_size = cb_threshold * 2;
+ iodev->min_cb_level = UINT_MAX;
+ iodev->max_cb_level = 0;
+ return iodev;
+}
+
+DevicePtr create_device(CRAS_STREAM_DIRECTION direction,
+ size_t cb_threshold,
+ cras_audio_format* format,
+ CRAS_NODE_TYPE active_node_type) {
+ IonodePtr node = create_ionode(active_node_type);
+ IodevPtr dev = create_open_iodev(direction, cb_threshold, format, node.get());
+ OpendevPtr odev(
+ reinterpret_cast<open_dev*>(calloc(1, sizeof(open_dev))), free);
+ odev->dev = dev.get();
+
+ DevicePtr d(new Device(std::move(dev), std::move(node), std::move(odev)));
+ return d;
+}
+
+void add_stream_to_dev(IodevPtr& dev, const StreamPtr& stream) {
+ DL_APPEND(dev->streams, stream->dstream.get());
+ dev->min_cb_level = std::min(stream->rstream->cb_threshold,
+ static_cast<size_t>(dev->min_cb_level));
+ dev->max_cb_level = std::max(stream->rstream->cb_threshold,
+ static_cast<size_t>(dev->max_cb_level));
+}
+
+void fill_audio_format(cras_audio_format* format, unsigned int rate) {
+ format->format = SND_PCM_FORMAT_S16_LE;
+ format->frame_rate = rate;
+ format->num_channels = 2;
+ format->channel_layout[0] = 0;
+ format->channel_layout[1] = 1;
+ for (int i = 2; i < CRAS_CH_MAX; i++)
+ format->channel_layout[i] = -1;
+}
diff --git a/cras/src/tests/dev_io_stubs.h b/cras/src/tests/dev_io_stubs.h
new file mode 100644
index 0000000..4c54ff8
--- /dev/null
+++ b/cras/src/tests/dev_io_stubs.h
@@ -0,0 +1,82 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+#include <stdint.h>
+#include <stdio.h>
+#include <time.h>
+
+extern "C" {
+#include "dev_io.h"
+#include "dev_stream.h"
+#include "cras_rstream.h"
+#include "cras_iodev.h"
+#include "cras_shm.h"
+#include "cras_types.h"
+#include "utlist.h"
+}
+
+#include "iodev_stub.h"
+#include "rstream_stub.h"
+
+#define RSTREAM_FAKE_POLL_FD 33
+
+using DevStreamPtr = std::unique_ptr<dev_stream, decltype(free)*>;
+using IodevPtr = std::unique_ptr<cras_iodev, decltype(free)*>;
+using IonodePtr = std::unique_ptr<cras_ionode, decltype(free)*>;
+using OpendevPtr = std::unique_ptr<open_dev, decltype(free)*>;
+using RstreamPtr = std::unique_ptr<cras_rstream, decltype(free)*>;
+using ShmPtr = std::unique_ptr<cras_audio_shm_area, decltype(free)*>;
+
+// Holds the rstream and devstream pointers for an attached stream.
+struct Stream {
+ Stream(ShmPtr shm, RstreamPtr rstream, DevStreamPtr dstream) :
+ shm(std::move(shm)),
+ rstream(std::move(rstream)),
+ dstream(std::move(dstream)) {
+ }
+ ShmPtr shm;
+ RstreamPtr rstream;
+ DevStreamPtr dstream;
+};
+using StreamPtr = std::unique_ptr<Stream>;
+
+// Holds the iodev and ionode pointers for an attached device.
+struct Device {
+ Device(IodevPtr dev, IonodePtr node, OpendevPtr odev) :
+ dev(std::move(dev)),
+ node(std::move(node)),
+ odev(std::move(odev)) {
+ }
+ IodevPtr dev;
+ IonodePtr node;
+ OpendevPtr odev;
+};
+using DevicePtr = std::unique_ptr<Device>;
+
+ShmPtr create_shm(size_t cb_threshold);
+RstreamPtr create_rstream(cras_stream_id_t id,
+ CRAS_STREAM_DIRECTION direction,
+ size_t cb_threshold,
+ const cras_audio_format* format,
+ cras_audio_shm_area* shm);
+DevStreamPtr create_dev_stream(unsigned int dev_id, cras_rstream* rstream);
+StreamPtr create_stream(cras_stream_id_t id,
+ unsigned int dev_id,
+ CRAS_STREAM_DIRECTION direction,
+ size_t cb_threshold,
+ const cras_audio_format* format);
+void AddFakeDataToStream(Stream* stream, unsigned int frames);
+int delay_frames_stub(const struct cras_iodev* iodev);
+IonodePtr create_ionode(CRAS_NODE_TYPE type);
+IodevPtr create_open_iodev(CRAS_STREAM_DIRECTION direction,
+ size_t cb_threshold,
+ cras_audio_format* format,
+ cras_ionode* active_node);
+DevicePtr create_device(CRAS_STREAM_DIRECTION direction,
+ size_t cb_threshold,
+ cras_audio_format* format,
+ CRAS_NODE_TYPE active_node_type);
+void add_stream_to_dev(IodevPtr& dev, const StreamPtr& stream);
+void fill_audio_format(cras_audio_format* format, unsigned int rate);
diff --git a/cras/src/tests/dev_io_unittest.cc b/cras/src/tests/dev_io_unittest.cc
new file mode 100644
index 0000000..c49fb45
--- /dev/null
+++ b/cras/src/tests/dev_io_unittest.cc
@@ -0,0 +1,118 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+#include <stdint.h>
+#include <stdio.h>
+#include <time.h>
+
+#include <gtest/gtest.h>
+
+extern "C" {
+#include "dev_io.h" // tested
+#include "dev_stream.h" // tested
+#include "cras_rstream.h" // stubbed
+#include "cras_iodev.h" // stubbed
+#include "cras_shm.h"
+#include "cras_types.h"
+#include "utlist.h"
+
+struct audio_thread_event_log* atlog;
+}
+
+#include "dev_io_stubs.h"
+#include "iodev_stub.h"
+#include "rstream_stub.h"
+
+namespace {
+
+
+class DevIoSuite : public testing::Test{
+ protected:
+ virtual void SetUp() {
+ atlog = static_cast<audio_thread_event_log*>(calloc(1, sizeof(*atlog)));
+ iodev_stub_reset();
+ rstream_stub_reset();
+ }
+
+ virtual void TearDown() {
+ free(atlog);
+ }
+};
+
+TEST_F(DevIoSuite, SendCapturedFails) {
+ const size_t cb_threshold = 480;
+
+ cras_audio_format format;
+ fill_audio_format(&format, 48000);
+
+ StreamPtr stream =
+ create_stream(1, 1, CRAS_STREAM_INPUT, cb_threshold, &format);
+ // rstream's next callback is now and there is enough data to fill.
+ struct timespec start;
+ clock_gettime(CLOCK_MONOTONIC_RAW, &start);
+ stream->rstream->next_cb_ts = start;
+ AddFakeDataToStream(stream.get(), 480);
+
+ struct open_dev* dev_list = NULL;
+ DevicePtr dev = create_device(CRAS_STREAM_INPUT, cb_threshold,
+ &format, CRAS_NODE_TYPE_MIC);
+ DL_APPEND(dev_list, dev->odev.get());
+ add_stream_to_dev(dev->dev, stream);
+
+ // Set failure response from frames_queued.
+ iodev_stub_frames_queued(dev->dev.get(), -3, start);
+
+ EXPECT_EQ(-3, dev_io_send_captured_samples(dev_list));
+}
+
+/* Stubs */
+extern "C" {
+
+int cras_server_metrics_highest_hw_level(unsigned hw_level,
+ enum CRAS_STREAM_DIRECTION direction)
+{
+ return 0;
+}
+
+int cras_server_metrics_longest_fetch_delay(unsigned delay_msec)
+{
+ return 0;
+}
+
+int cras_server_metrics_num_underruns(unsigned num_underruns)
+{
+ return 0;
+}
+
+int input_data_get_for_stream(
+ struct input_data *data,
+ struct cras_rstream *stream,
+ struct buffer_share *offsets,
+ struct cras_audio_area **area,
+ unsigned int *offset)
+{
+ return 0;
+}
+
+int input_data_put_for_stream(struct input_data *data,
+ struct cras_rstream *stream,
+ struct buffer_share *offsets,
+ unsigned int frames)
+{
+ return 0;
+}
+struct cras_audio_format *cras_rstream_post_processing_format(
+ const struct cras_rstream *stream, void *dev_ptr)
+{
+ return NULL;
+}
+} // extern "C"
+
+} // namespace
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/cras/src/tests/dev_stream_unittest.cc b/cras/src/tests/dev_stream_unittest.cc
index 8f90ed6..450f5c7 100644
--- a/cras/src/tests/dev_stream_unittest.cc
+++ b/cras/src/tests/dev_stream_unittest.cc
@@ -19,6 +19,10 @@
extern "C" {
struct audio_thread_event_log *atlog;
+unsigned int max_frames_for_conversion(
+ unsigned int stream_frames,
+ unsigned int stream_rate,
+ unsigned int device_rate);
};
static struct timespec clock_gettime_retspec;
@@ -40,6 +44,11 @@
48000,
1,
};
+static const struct cras_audio_format fmt_s16le_8 = {
+ SND_PCM_FORMAT_S16_LE,
+ 8000,
+ 2,
+};
struct cras_audio_area_copy_call {
const struct cras_audio_area *dst;
@@ -74,14 +83,14 @@
};
static int config_format_converter_called;
+static const struct cras_audio_format *config_format_converter_from_fmt;
+static int config_format_converter_frames;
static struct cras_fmt_conv *config_format_converter_conv;
static struct cras_audio_format in_fmt;
static struct cras_audio_format out_fmt;
static struct cras_audio_area_copy_call copy_area_call;
static struct fmt_conv_call conv_frames_call;
-static size_t conv_frames_ret;
static int cras_audio_area_create_num_channels_val;
-static int cras_fmt_conv_convert_frames_in_frames_val;
static int cras_fmt_conversion_needed_val;
static int cras_fmt_conv_set_linear_resample_rates_called;
static float cras_fmt_conv_set_linear_resample_rates_from;
@@ -93,8 +102,11 @@
static unsigned int rstream_get_readable_num;
static uint8_t *rstream_get_readable_ptr;
+static struct cras_audio_format *cras_rstream_post_processing_format_val;
static int cras_rstream_audio_ready_called;
static int cras_rstream_audio_ready_count;
+static int cras_rstream_is_pending_reply_ret;
+static int cras_rstream_flush_old_audio_messages_called;
class CreateSuite : public testing::Test{
protected:
@@ -117,20 +129,51 @@
rstream_.format = fmt_s16le_44_1;
rstream_.flags = 0;
+ config_format_converter_from_fmt = NULL;
config_format_converter_called = 0;
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;
+ cras_rstream_is_pending_reply_ret = 0;
+ cras_rstream_flush_old_audio_messages_called = 0;
memset(©_area_call, 0xff, sizeof(copy_area_call));
memset(&conv_frames_call, 0xff, sizeof(conv_frames_call));
atlog = audio_thread_event_log_init();
+
+ devstr.stream = &rstream_;
+ devstr.conv = NULL;
+ devstr.conv_buffer = NULL;
+ devstr.conv_buffer_size_frames = 0;
+
+ area = (struct cras_audio_area*)calloc(1,
+ sizeof(*area) + 2 * sizeof(struct cras_channel_area));
+ area->num_channels = 2;
+ channel_area_set_channel(&area->channels[0], CRAS_CH_FL);
+ channel_area_set_channel(&area->channels[1], CRAS_CH_FR);
+ area->channels[0].step_bytes = 4;
+ area->channels[0].buf = (uint8_t *)(cap_buf);
+ area->channels[1].step_bytes = 4;
+ area->channels[1].buf = (uint8_t *)(cap_buf + 1);
+ area->frames = kBufferFrames;
+
+ stream_area = (struct cras_audio_area*)calloc(1,
+ sizeof(*area) + 2 * sizeof(struct cras_channel_area));
+ stream_area->num_channels = 2;
+ rstream_.audio_area = stream_area;
+ int16_t *shm_samples = (int16_t *)rstream_.shm.area->samples;
+ stream_area->channels[0].step_bytes = 4;
+ stream_area->channels[0].buf = (uint8_t *)(shm_samples);
+ stream_area->channels[1].step_bytes = 4;
+ stream_area->channels[1].buf = (uint8_t *)(shm_samples + 1);
}
virtual void TearDown() {
+ free(area);
+ free(stream_area);
free(rstream_.shm.area);
audio_thread_event_log_deinit(atlog);
}
@@ -151,42 +194,37 @@
cras_shm_set_volume_scaler(shm, 1.0);
}
- int16_t *compare_buffer_;
- struct cras_rstream rstream_;
-};
+ void SetUpFmtConv(unsigned int in_rate, unsigned int out_rate,
+ unsigned int conv_buf_size) {
+ in_fmt.frame_rate = in_rate;
+ out_fmt.frame_rate = out_rate;
+ cras_fmt_conversion_needed_val = 1;
-TEST_F(CreateSuite, CaptureNoSRC) {
+ devstr.conv = (struct cras_fmt_conv *)0xdead;
+ devstr.conv_buffer =
+ (struct byte_buffer *)byte_buffer_create(conv_buf_size * 4);
+ devstr.conv_buffer_size_frames = kBufferFrames * 2;
+
+ devstr.conv_area = (struct cras_audio_area*)calloc(1,
+ sizeof(*area) + 2 * sizeof(*area->channels));
+ devstr.conv_area->num_channels = 2;
+ devstr.conv_area->channels[0].step_bytes = 4;
+ devstr.conv_area->channels[0].buf = (uint8_t *)(devstr.conv_buffer->bytes);
+ devstr.conv_area->channels[1].step_bytes = 4;
+ devstr.conv_area->channels[1].buf =
+ (uint8_t *)(devstr.conv_buffer->bytes + 1);
+ }
+
struct dev_stream devstr;
struct cras_audio_area *area;
struct cras_audio_area *stream_area;
int16_t cap_buf[kBufferFrames * 2];
+ struct cras_rstream rstream_;
+};
+
+TEST_F(CreateSuite, CaptureNoSRC) {
float software_gain_scaler = 10;
- devstr.stream = &rstream_;
- devstr.conv = NULL;
- devstr.conv_buffer = NULL;
- devstr.conv_buffer_size_frames = 0;
-
- area = (struct cras_audio_area*)calloc(1, sizeof(*area) +
- 2 * sizeof(*area->channels));
- area->num_channels = 2;
- channel_area_set_channel(&area->channels[0], CRAS_CH_FL);
- channel_area_set_channel(&area->channels[1], CRAS_CH_FR);
- area->channels[0].step_bytes = 4;
- area->channels[0].buf = (uint8_t *)(cap_buf);
- area->channels[1].step_bytes = 4;
- area->channels[1].buf = (uint8_t *)(cap_buf + 1);
-
- stream_area = (struct cras_audio_area*)calloc(1, sizeof(*area) +
- 2 * sizeof(*area->channels));
- stream_area->num_channels = 2;
- rstream_.audio_area = stream_area;
- int16_t *shm_samples = (int16_t *)rstream_.shm.area->samples;
- stream_area->channels[0].step_bytes = 4;
- stream_area->channels[0].buf = (uint8_t *)(shm_samples);
- stream_area->channels[1].step_bytes = 4;
- stream_area->channels[1].buf = (uint8_t *)(shm_samples + 1);
-
dev_stream_capture(&devstr, area, 0, software_gain_scaler);
EXPECT_EQ(stream_area, copy_area_call.dst);
@@ -195,109 +233,123 @@
EXPECT_EQ(area, copy_area_call.src);
EXPECT_EQ(software_gain_scaler, copy_area_call.software_gain_scaler);
- free(area);
- free(stream_area);
}
-TEST_F(CreateSuite, CaptureSRC) {
- struct dev_stream devstr;
- struct cras_audio_area *area;
- struct cras_audio_area *stream_area;
- int16_t cap_buf[kBufferFrames * 2];
+TEST_F(CreateSuite, CaptureSRCSmallConverterBuffer) {
float software_gain_scaler = 10;
+ unsigned int conv_buf_avail_at_input_rate;
+ int nread;
- 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;
+ SetUpFmtConv(44100, 32000, kBufferFrames / 4);
+ nread = dev_stream_capture(&devstr, area, 0, software_gain_scaler);
- area = (struct cras_audio_area*)calloc(1, sizeof(*area) +
- 2 * sizeof(*area->channels));
- area->num_channels = 2;
- channel_area_set_channel(&area->channels[0], CRAS_CH_FL);
- channel_area_set_channel(&area->channels[1], CRAS_CH_FR);
- area->channels[0].step_bytes = 4;
- area->channels[0].buf = (uint8_t *)(cap_buf);
- area->channels[1].step_bytes = 4;
- area->channels[1].buf = (uint8_t *)(cap_buf + 1);
- area->frames = kBufferFrames;
+ // |nread| is bound by small converter buffer size (kBufferFrames / 4)
+ conv_buf_avail_at_input_rate =
+ cras_frames_at_rate(out_fmt.frame_rate,
+ (kBufferFrames / 4),
+ in_fmt.frame_rate);
- stream_area = (struct cras_audio_area*)calloc(1, sizeof(*area) +
- 2 * sizeof(*area->channels));
- stream_area->num_channels = 2;
- rstream_.audio_area = stream_area;
- int16_t *shm_samples = (int16_t *)rstream_.shm.area->samples;
- stream_area->channels[0].step_bytes = 4;
- stream_area->channels[0].buf = (uint8_t *)(shm_samples);
- stream_area->channels[1].step_bytes = 4;
- stream_area->channels[1].buf = (uint8_t *)(shm_samples + 1);
- rstream_.audio_area = stream_area;
-
- devstr.conv_area = (struct cras_audio_area*)calloc(1, sizeof(*area) +
- 2 * sizeof(*area->channels));
- devstr.conv_area->num_channels = 2;
- devstr.conv_area->channels[0].step_bytes = 4;
- devstr.conv_area->channels[0].buf = (uint8_t *)(devstr.conv_buffer->bytes);
- devstr.conv_area->channels[1].step_bytes = 4;
- devstr.conv_area->channels[1].buf =
- (uint8_t *)(devstr.conv_buffer->bytes + 1);
-
- 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, software_gain_scaler);
-
+ EXPECT_EQ(conv_buf_avail_at_input_rate, nread);
EXPECT_EQ((struct cras_fmt_conv *)0xdead, conv_frames_call.conv);
EXPECT_EQ((uint8_t *)cap_buf, conv_frames_call.in_buf);
EXPECT_EQ(devstr.conv_buffer->bytes, conv_frames_call.out_buf);
- EXPECT_EQ(kBufferFrames, conv_frames_call.in_frames);
+
+ EXPECT_EQ(conv_buf_avail_at_input_rate, conv_frames_call.in_frames);
+
+ // Expect number of output frames is limited by the size of converter buffer.
+ EXPECT_EQ(kBufferFrames / 4, conv_frames_call.out_frames);
+
+ 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(devstr.conv_area, copy_area_call.src);
+ EXPECT_EQ(software_gain_scaler, copy_area_call.software_gain_scaler);
+
+ free(devstr.conv_area);
+ byte_buffer_destroy(&devstr.conv_buffer);
+}
+
+TEST_F(CreateSuite, CaptureSRCLargeConverterBuffer) {
+ float software_gain_scaler = 10;
+ unsigned int stream_avail_at_input_rate;
+ int nread;
+
+ SetUpFmtConv(44100, 32000, kBufferFrames * 2);
+ nread = dev_stream_capture(&devstr, area, 0, software_gain_scaler);
+
+ // Available frames at stream side is bound by cb_threshold, which
+ // equals to kBufferFrames / 2.
+ stream_avail_at_input_rate =
+ cras_frames_at_rate(out_fmt.frame_rate,
+ (kBufferFrames / 2),
+ in_fmt.frame_rate);
+
+ EXPECT_EQ(stream_avail_at_input_rate, nread);
+ EXPECT_EQ((struct cras_fmt_conv *)0xdead, conv_frames_call.conv);
+ EXPECT_EQ((uint8_t *)cap_buf, conv_frames_call.in_buf);
+ EXPECT_EQ(devstr.conv_buffer->bytes, conv_frames_call.out_buf);
+
+ // Expect number of input frames is limited by |stream_avail_at_input_rate|
+ // at format conversion.
+ EXPECT_EQ(stream_avail_at_input_rate, conv_frames_call.in_frames);
+
+ // Expect number of output frames is limited by the size of converter buffer.
EXPECT_EQ(kBufferFrames * 2, conv_frames_call.out_frames);
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(devstr.conv_area, copy_area_call.src);
- 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);
+ byte_buffer_destroy(&devstr.conv_buffer);
}
TEST_F(CreateSuite, CreateSRC44to48) {
struct dev_stream *dev_stream;
rstream_.format = fmt_s16le_44_1;
- in_fmt.frame_rate = 44100;
- out_fmt.frame_rate = 48000;
+ in_fmt.frame_rate = 44100; // Input to converter is stream rate.
+ out_fmt.frame_rate = 48000; // Output from converter is device rate.
config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33);
dev_stream = dev_stream_create(&rstream_, 0, &fmt_s16le_48, (void *)0x55,
&cb_ts);
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,
- out_fmt.frame_rate),
- dev_stream->conv_buffer_size_frames);
+ // Converter tmp and output buffers are large enough for device output.
+ unsigned int device_frames =
+ cras_frames_at_rate(in_fmt.frame_rate, kBufferFrames,
+ out_fmt.frame_rate);
+ EXPECT_LE(kBufferFrames, device_frames); // Sanity check.
+ EXPECT_LE(device_frames, config_format_converter_frames);
+ EXPECT_LE(device_frames, dev_stream->conv_buffer_size_frames);
dev_stream_destroy(dev_stream);
}
-TEST_F(CreateSuite, CreateSRC44to48Input) {
+TEST_F(CreateSuite, CreateSRC44from48Input) {
struct dev_stream *dev_stream;
+ struct cras_audio_format processed_fmt = fmt_s16le_48;
+ processed_fmt.num_channels = 1;
rstream_.format = fmt_s16le_44_1;
rstream_.direction = CRAS_STREAM_INPUT;
- in_fmt.frame_rate = 48000;
- out_fmt.frame_rate = 44100;
+ in_fmt.frame_rate = 48000; // Input to converter is device rate.
+ out_fmt.frame_rate = 44100; // Output from converter is stream rate.
config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33);
+ cras_rstream_post_processing_format_val = &processed_fmt;
dev_stream = dev_stream_create(&rstream_, 0, &fmt_s16le_48, (void *)0x55,
&cb_ts);
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,
- out_fmt.frame_rate),
- dev_stream->conv_buffer_size_frames);
+ // Converter tmp and output buffers are large enough for device input.
+ unsigned int device_frames =
+ cras_frames_at_rate(out_fmt.frame_rate, kBufferFrames,
+ in_fmt.frame_rate);
+ EXPECT_LE(kBufferFrames, device_frames); // Sanity check.
+ EXPECT_LE(device_frames, config_format_converter_frames);
+ EXPECT_EQ(&processed_fmt, config_format_converter_from_fmt);
+ EXPECT_LE(device_frames, dev_stream->conv_buffer_size_frames);
dev_stream_destroy(dev_stream);
}
@@ -305,52 +357,130 @@
struct dev_stream *dev_stream;
rstream_.format = fmt_s16le_48;
- in_fmt.frame_rate = 48000;
- out_fmt.frame_rate = 44100;
+ in_fmt.frame_rate = 48000; // Stream rate.
+ out_fmt.frame_rate = 44100; // Device rate.
config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33);
dev_stream = dev_stream_create(&rstream_, 0, &fmt_s16le_44_1, (void *)0x55,
&cb_ts);
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,
- out_fmt.frame_rate),
- dev_stream->conv_buffer_size_frames);
+ // Converter tmp and output buffers are large enough for stream input.
+ EXPECT_LE(kBufferFrames, config_format_converter_frames);
+ EXPECT_LE(kBufferFrames, dev_stream->conv_buffer_size_frames);
dev_stream_destroy(dev_stream);
}
-TEST_F(CreateSuite, CreateSRC48to44Input) {
+TEST_F(CreateSuite, CreateSRC48from44Input) {
struct dev_stream *dev_stream;
rstream_.format = fmt_s16le_48;
rstream_.direction = CRAS_STREAM_INPUT;
- in_fmt.frame_rate = 44100;
- out_fmt.frame_rate = 48000;
+ in_fmt.frame_rate = 44100; // Device rate.
+ out_fmt.frame_rate = 48000; // Stream rate.
config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33);
dev_stream = dev_stream_create(&rstream_, 0, &fmt_s16le_44_1, (void *)0x55,
&cb_ts);
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,
- out_fmt.frame_rate),
- dev_stream->conv_buffer_size_frames);
+ // Converter tmp and output buffers are large enough for stream output.
+ EXPECT_LE(kBufferFrames, config_format_converter_frames);
+ EXPECT_LE(kBufferFrames, dev_stream->conv_buffer_size_frames);
dev_stream_destroy(dev_stream);
}
-TEST_F(CreateSuite, CreateSRC48to44StereoToMono) {
+TEST_F(CreateSuite, CreateSRC8to48) {
+ struct dev_stream *dev_stream;
+
+ rstream_.format = fmt_s16le_8;
+ in_fmt.frame_rate = 8000; // Stream rate.
+ out_fmt.frame_rate = 48000; // Device rate.
+ config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33);
+ dev_stream = dev_stream_create(&rstream_, 0, &fmt_s16le_48, (void *)0x55,
+ &cb_ts);
+ EXPECT_EQ(1, config_format_converter_called);
+ EXPECT_NE(static_cast<byte_buffer*>(NULL), dev_stream->conv_buffer);
+ // Converter tmp and output buffers are large enough for device output.
+ unsigned int device_frames =
+ cras_frames_at_rate(in_fmt.frame_rate, kBufferFrames,
+ out_fmt.frame_rate);
+ EXPECT_LE(kBufferFrames, device_frames); // Sanity check.
+ EXPECT_LE(device_frames, config_format_converter_frames);
+ EXPECT_LE(device_frames, dev_stream->conv_buffer_size_frames);
+ dev_stream_destroy(dev_stream);
+}
+
+TEST_F(CreateSuite, CreateSRC8from48Input) {
+ struct dev_stream *dev_stream;
+
+ rstream_.format = fmt_s16le_8;
+ rstream_.direction = CRAS_STREAM_INPUT;
+ in_fmt.frame_rate = 48000; // Device rate.
+ out_fmt.frame_rate = 8000; // Stream rate.
+ config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33);
+ dev_stream = dev_stream_create(&rstream_, 0, &fmt_s16le_48, (void *)0x55,
+ &cb_ts);
+ EXPECT_EQ(1, config_format_converter_called);
+ EXPECT_NE(static_cast<byte_buffer*>(NULL), dev_stream->conv_buffer);
+ // Converter tmp and output buffers are large enough for device input.
+ unsigned int device_frames =
+ cras_frames_at_rate(out_fmt.frame_rate, kBufferFrames,
+ in_fmt.frame_rate);
+ EXPECT_LE(kBufferFrames, device_frames); // Sanity check.
+ EXPECT_LE(device_frames, config_format_converter_frames);
+ EXPECT_LE(device_frames, dev_stream->conv_buffer_size_frames);
+ dev_stream_destroy(dev_stream);
+}
+
+TEST_F(CreateSuite, CreateSRC48to8) {
+ struct dev_stream *dev_stream;
+
+ rstream_.format = fmt_s16le_48;
+ in_fmt.frame_rate = 48000; // Stream rate.
+ out_fmt.frame_rate = 8000; // Device rate.
+ config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33);
+ dev_stream = dev_stream_create(&rstream_, 0, &fmt_s16le_8, (void *)0x55,
+ &cb_ts);
+ EXPECT_EQ(1, config_format_converter_called);
+ EXPECT_NE(static_cast<byte_buffer*>(NULL), dev_stream->conv_buffer);
+ // Converter tmp and output buffers are large enough for stream input.
+ EXPECT_LE(kBufferFrames, config_format_converter_frames);
+ EXPECT_LE(kBufferFrames, dev_stream->conv_buffer_size_frames);
+ dev_stream_destroy(dev_stream);
+}
+
+TEST_F(CreateSuite, CreateSRC48from8Input) {
+ struct dev_stream *dev_stream;
+
+ rstream_.format = fmt_s16le_48;
+ rstream_.direction = CRAS_STREAM_INPUT;
+ in_fmt.frame_rate = 8000; // Device rate.
+ out_fmt.frame_rate = 48000; // Stream rate.
+ config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33);
+ dev_stream = dev_stream_create(&rstream_, 0, &fmt_s16le_8, (void *)0x55,
+ &cb_ts);
+ EXPECT_EQ(1, config_format_converter_called);
+ EXPECT_NE(static_cast<byte_buffer*>(NULL), dev_stream->conv_buffer);
+ // Converter tmp and output buffers are large enough for stream output.
+ EXPECT_LE(kBufferFrames, config_format_converter_frames);
+ EXPECT_LE(kBufferFrames, dev_stream->conv_buffer_size_frames);
+ dev_stream_destroy(dev_stream);
+}
+
+TEST_F(CreateSuite, CreateSRC48MonoFrom44StereoInput) {
struct dev_stream *dev_stream;
rstream_.format = fmt_s16le_48_mono;
rstream_.direction = CRAS_STREAM_INPUT;
- in_fmt.frame_rate = 44100;
- out_fmt.frame_rate = 48000;
+ in_fmt.frame_rate = 44100; // Device rate.
+ out_fmt.frame_rate = 48000; // Stream rate.
config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33);
dev_stream = dev_stream_create(&rstream_, 0, &fmt_s16le_44_1, (void *)0x55,
&cb_ts);
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,
- out_fmt.frame_rate),
- dev_stream->conv_buffer_size_frames);
+ // Converter tmp and output buffers are large enough for stream output.
+ EXPECT_LE(kBufferFrames, config_format_converter_frames);
+ EXPECT_LE(kBufferFrames, dev_stream->conv_buffer_size_frames);
EXPECT_EQ(dev_stream->conv_buffer_size_frames * 4,
dev_stream->conv_buffer->max_size);
EXPECT_EQ(2, cras_audio_area_create_num_channels_val);
@@ -512,6 +642,35 @@
EXPECT_EQ(2, rstream_get_readable_call.num_called);
}
+TEST_F(CreateSuite, DevStreamFlushAudioMessages) {
+ struct dev_stream *dev_stream;
+ unsigned int dev_id = 9;
+
+ dev_stream = dev_stream_create(&rstream_, dev_id, &fmt_s16le_44_1,
+ (void *)0x55, &cb_ts);
+
+ dev_stream_flush_old_audio_messages(dev_stream);
+ EXPECT_EQ(1, cras_rstream_flush_old_audio_messages_called);
+ dev_stream_destroy(dev_stream);
+}
+
+TEST_F(CreateSuite, DevStreamIsPending) {
+ struct dev_stream *dev_stream;
+ unsigned int dev_id = 9;
+
+ dev_stream = dev_stream_create(&rstream_, dev_id, &fmt_s16le_44_1,
+ (void *)0x55, &cb_ts);
+
+ // dev_stream_is_pending_reply is only a wrapper.
+ cras_rstream_is_pending_reply_ret = 0;
+ EXPECT_EQ(0, dev_stream_is_pending_reply(dev_stream));
+
+ cras_rstream_is_pending_reply_ret = 1;
+ EXPECT_EQ(1, dev_stream_is_pending_reply(dev_stream));
+
+ dev_stream_destroy(dev_stream);
+}
+
TEST_F(CreateSuite, StreamCanFetch) {
struct dev_stream *dev_stream;
unsigned int dev_id = 9;
@@ -520,16 +679,17 @@
(void *)0x55, &cb_ts);
/* Verify stream cannot fetch when it's still pending. */
- cras_shm_set_callback_pending(&rstream_.shm, 1);
+ cras_rstream_is_pending_reply_ret = 1;
EXPECT_EQ(0, dev_stream_can_fetch(dev_stream));
/* Verify stream can fetch when buffer available. */
- cras_shm_set_callback_pending(&rstream_.shm, 0);
+ cras_rstream_is_pending_reply_ret = 0;
rstream_.shm.area->write_offset[0] = 0;
rstream_.shm.area->write_offset[1] = 0;
EXPECT_EQ(1, dev_stream_can_fetch(dev_stream));
/* Verify stream cannot fetch when there's still buffer. */
+ cras_rstream_is_pending_reply_ret = 0;
rstream_.shm.area->write_offset[0] = kBufferFrames;
rstream_.shm.area->write_offset[1] = kBufferFrames;
EXPECT_EQ(0, dev_stream_can_fetch(dev_stream));
@@ -707,6 +867,33 @@
dev_stream_destroy(dev_stream);
}
+TEST_F(CreateSuite, TriggerOnlyStreamSendOnlyOnce) {
+ struct dev_stream *dev_stream;
+ unsigned int dev_id = 9;
+
+ rstream_.direction = CRAS_STREAM_INPUT;
+ dev_stream = dev_stream_create(&rstream_, dev_id, &fmt_s16le_44_1,
+ (void *) 0x55, &cb_ts);
+ dev_stream->stream->flags = TRIGGER_ONLY;
+ dev_stream->stream->triggered = 0;
+
+ // Check first trigger callback called.
+ cras_shm_buffer_written(&rstream_.shm, rstream_.cb_threshold);
+ clock_gettime_retspec.tv_sec = 1;
+ clock_gettime_retspec.tv_nsec = 0;
+ dev_stream_capture_update_rstream(dev_stream);
+ EXPECT_EQ(1, cras_rstream_audio_ready_called);
+ EXPECT_EQ(1, dev_stream->stream->triggered);
+
+ // No future callback will be called for TRIGGER_ONLY streams.
+ cras_shm_buffer_written(&rstream_.shm, rstream_.cb_threshold);
+ clock_gettime_retspec.tv_sec = 2;
+ clock_gettime_retspec.tv_nsec = 0;
+ dev_stream_capture_update_rstream(dev_stream);
+ EXPECT_EQ(1, cras_rstream_audio_ready_called);
+ dev_stream_destroy(dev_stream);
+}
+
TEST_F(CreateSuite, InputDevStreamWakeTimeByNextCbTs) {
struct dev_stream *dev_stream;
unsigned int dev_id = 9;
@@ -729,12 +916,14 @@
cras_shm_buffer_written(&rstream_.shm, written_frames);
rc = dev_stream_wake_time(dev_stream, curr_level,
- &level_tstamp, &wake_time_out);
+ &level_tstamp, rstream_.cb_threshold, 0,
+ &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);
+ dev_stream_destroy(dev_stream);
}
TEST_F(CreateSuite, InputDevStreamWakeTimeByDevice) {
@@ -789,7 +978,8 @@
in_fmt.frame_rate = 48000;
rc = dev_stream_wake_time(dev_stream, curr_level,
- &level_tstamp, &wake_time_out);
+ &level_tstamp, rstream_.cb_threshold, 0,
+ &wake_time_out);
// The next wake up time is determined by needed time for device level
// to reach enough samples for one cb_threshold.
@@ -801,10 +991,12 @@
// 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);
+ &level_tstamp, rstream_.cb_threshold, 0,
+ &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);
+ dev_stream_destroy(dev_stream);
}
// Test set_playback_timestamp.
@@ -875,6 +1067,18 @@
EXPECT_LE(ts.tv_nsec, 250100000);
}
+TEST(MaxFramesForConverter, 8to48) {
+ EXPECT_EQ(481, max_frames_for_conversion(80, // Stream frames.
+ 8000, // Stream rate.
+ 48000)); // Device rate.
+}
+
+TEST(MaxFramesForConverter, 48to8) {
+ EXPECT_EQ(81, max_frames_for_conversion(80, // Stream frames.
+ 48000, // Stream rate.
+ 8000)); // Device rate.
+}
+
/* Stubs */
extern "C" {
@@ -940,17 +1144,24 @@
int cras_rstream_get_mute(const struct cras_rstream *rstream) {
return 0;
}
-
void cras_rstream_update_queued_frames(struct cras_rstream *rstream)
{
}
+struct cras_audio_format *cras_rstream_post_processing_format(
+ const struct cras_rstream *stream, void *dev_ptr)
+{
+ return cras_rstream_post_processing_format_val;
+}
+
int config_format_converter(struct cras_fmt_conv **conv,
enum CRAS_STREAM_DIRECTION dir,
const struct cras_audio_format *from,
const struct cras_audio_format *to,
unsigned int frames) {
config_format_converter_called++;
+ config_format_converter_from_fmt = from;
+ config_format_converter_frames = frames;
*conv = config_format_converter_conv;
return 0;
}
@@ -963,14 +1174,23 @@
uint8_t *out_buf,
unsigned int *in_frames,
unsigned int out_frames) {
+ unsigned int ret;
conv_frames_call.conv = conv;
conv_frames_call.in_buf = in_buf;
conv_frames_call.out_buf = out_buf;
conv_frames_call.in_frames = *in_frames;
- *in_frames = cras_fmt_conv_convert_frames_in_frames_val;
+ ret = cras_frames_at_rate(in_fmt.frame_rate,
+ *in_frames,
+ out_fmt.frame_rate);
conv_frames_call.out_frames = out_frames;
+ if (ret > out_frames) {
+ ret = out_frames;
+ *in_frames = cras_frames_at_rate(
+ out_fmt.frame_rate,
+ ret, in_fmt.frame_rate);
+ }
- return conv_frames_ret;
+ return ret;
}
void cras_mix_add(snd_pcm_format_t fmt, uint8_t *dst, uint8_t *src,
@@ -1055,6 +1275,17 @@
cras_fmt_conv_set_linear_resample_rates_called++;
}
+int cras_rstream_is_pending_reply(const struct cras_rstream *stream)
+{
+ return cras_rstream_is_pending_reply_ret;
+}
+
+int cras_rstream_flush_old_audio_messages(struct cras_rstream *stream)
+{
+ cras_rstream_flush_old_audio_messages_called++;
+ return 0;
+}
+
// From librt.
int clock_gettime(clockid_t clk_id, struct timespec *tp) {
tp->tv_sec = clock_gettime_retspec.tv_sec;
diff --git a/cras/src/tests/device_monitor_unittest.cc b/cras/src/tests/device_monitor_unittest.cc
index 31913be..cc9e0d1 100644
--- a/cras/src/tests/device_monitor_unittest.cc
+++ b/cras/src/tests/device_monitor_unittest.cc
@@ -132,7 +132,7 @@
enable_dev = dev;
}
-void cras_iodev_list_disable_dev(struct cras_iodev *dev) {
+void cras_iodev_list_disable_dev(struct cras_iodev *dev, bool force) {
disable_dev_called++;
disable_dev = dev;
}
diff --git a/cras/src/tests/dsp_core_unittest.cc b/cras/src/tests/dsp_core_unittest.cc
index 71b92eb..0957795 100644
--- a/cras/src/tests/dsp_core_unittest.cc
+++ b/cras/src/tests/dsp_core_unittest.cc
@@ -55,7 +55,8 @@
float output[SAMPLES];
float *out_ptr[] = {output, output + FRAMES};
- dsp_util_deinterleave(input, out_ptr, 2, FRAMES);
+ dsp_util_deinterleave((uint8_t *)input, out_ptr, 2,
+ SND_PCM_FORMAT_S16_LE, FRAMES);
for (int i = 0 ; i < SAMPLES; i++) {
EXPECT_EQ(answer[i], output[i]);
@@ -68,7 +69,8 @@
}
int16_t output2[SAMPLES];
- dsp_util_interleave(out_ptr, output2, 2, FRAMES);
+ dsp_util_interleave(out_ptr, (uint8_t *)output2, 2,
+ SND_PCM_FORMAT_S16_LE, FRAMES);
for (int i = 0 ; i < SAMPLES; i++) {
EXPECT_EQ(input[i], output2[i]);
}
diff --git a/cras/src/tests/dsp_unittest.cc b/cras/src/tests/dsp_unittest.cc
index 40fd9da..2202990 100644
--- a/cras/src/tests/dsp_unittest.cc
+++ b/cras/src/tests/dsp_unittest.cc
@@ -150,6 +150,10 @@
empty_init_module(module);
return module;
}
+void cras_dsp_module_set_sink_ext_module(struct dsp_module *module,
+ struct ext_dsp_module *ext_module)
+{
+}
} // extern "C"
int main(int argc, char **argv) {
diff --git a/cras/src/tests/empty_audio_stub.cc b/cras/src/tests/empty_audio_stub.cc
new file mode 100644
index 0000000..e15af00
--- /dev/null
+++ b/cras/src/tests/empty_audio_stub.cc
@@ -0,0 +1,24 @@
+/* Copyright 2018 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+extern "C" {
+#include "polled_interval_checker.h"
+
+struct polled_interval *pic_polled_interval_create(int interval_sec) {
+ return NULL;
+}
+
+int pic_interval_elapsed(const struct polled_interval *interval) {
+ return 0;
+}
+
+void pic_interval_reset(struct polled_interval *interval) {}
+
+void pic_polled_interval_destroy(struct polled_interval **interval) {}
+
+void pic_update_current_time() {}
+
+void cras_non_empty_audio_send_msg(int non_empty) {}
+}
diff --git a/cras/src/tests/float_buffer_unittest.cc b/cras/src/tests/float_buffer_unittest.cc
new file mode 100644
index 0000000..20d392e
--- /dev/null
+++ b/cras/src/tests/float_buffer_unittest.cc
@@ -0,0 +1,53 @@
+// Copyright 2018 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 "float_buffer.h"
+
+namespace {
+TEST(FloatBuffer, ReadWrite) {
+ unsigned int readable = 10;
+ struct float_buffer *b = float_buffer_create(10, 2);
+ EXPECT_EQ(10, float_buffer_writable(b));
+
+ // (w, r)=(8, 0)
+ float_buffer_written(b, 8);
+ EXPECT_EQ(8, float_buffer_level(b));
+
+ float_buffer_read_pointer(b, 0, &readable);
+ EXPECT_EQ(8, readable);
+ EXPECT_EQ(2, float_buffer_writable(b));
+
+ readable = 10;
+ float_buffer_read_pointer(b, 3, &readable);
+ EXPECT_EQ(5, readable);
+
+ // (w, r)=(8, 6)
+ float_buffer_read(b, 6);
+ EXPECT_EQ(2, float_buffer_writable(b));
+
+ // (w, r)=(0, 6)
+ float_buffer_written(b, 2);
+ EXPECT_EQ(6, float_buffer_writable(b));
+
+ // (w, r)=(3, 6)
+ readable = 10;
+ float_buffer_written(b, 3);
+ float_buffer_read_pointer(b, 0, &readable);
+ EXPECT_EQ(4, readable);
+
+ readable = 10;
+ float_buffer_read_pointer(b, 1, &readable);
+ EXPECT_EQ(3, readable);
+
+ float_buffer_destroy(&b);
+}
+
+} // 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 a0b6194..08bb243 100644
--- a/cras/src/tests/fmt_conv_unittest.cc
+++ b/cras/src/tests/fmt_conv_unittest.cc
@@ -16,8 +16,12 @@
{0, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1};
static int surround_channel_layout[CRAS_CH_MAX] =
{0, 1, 2, 3, 4, 5, -1, -1, -1, -1, -1};
+static int quad_channel_layout[CRAS_CH_MAX] =
+ {0, 1, 2, 3, -1, -1, -1, -1, -1, -1, -1};
static int linear_resampler_needed_val;
static double linear_resampler_ratio = 1.0;
+static unsigned int linear_resampler_num_channels;
+static unsigned int linear_resampler_format_bytes;
void ResetStub() {
linear_resampler_needed_val = 0;
@@ -40,6 +44,58 @@
layout[b] = tmp;
}
+TEST(FormatConverterTest, SmallFramesSRCWithLinearResampler) {
+ struct cras_audio_format in_fmt;
+ struct cras_audio_format out_fmt;
+ struct cras_fmt_conv *c;
+ int16_t *in_buf;
+ int16_t *out_buf;
+ unsigned int in_frames = 1;
+ unsigned int out_frames = 2;
+
+ ResetStub();
+ in_fmt.format = out_fmt.format = SND_PCM_FORMAT_S16_LE;
+ in_fmt.num_channels = out_fmt.num_channels = 1;
+ in_fmt.frame_rate = 16000;
+ out_fmt.frame_rate = 48000;
+ linear_resampler_needed_val = 1;
+
+ in_buf = (int16_t *)malloc(10 * 2 * 2);
+ out_buf = (int16_t *)malloc(10 * 2 * 2);
+
+ c = cras_fmt_conv_create(&in_fmt, &out_fmt, 10, 1);
+ EXPECT_NE((void *)NULL, c);
+
+ /* When process on small buffers doing SRC 16KHz -> 48KHz,
+ * speex does the work in two steps:
+ *
+ * (1) 0 -> 2 frames in output
+ * (2) 1 -> 1 frame in output
+ *
+ * Total result is 1 frame consumed in input and generated
+ * 3 frames in output.
+ */
+ out_frames = cras_fmt_conv_convert_frames(c, (uint8_t *)in_buf,
+ (uint8_t *)out_buf,
+ &in_frames,
+ out_frames);
+ EXPECT_EQ(2, out_frames);
+ EXPECT_EQ(0, in_frames);
+
+ in_frames = 1;
+ out_frames = 2;
+ out_frames = cras_fmt_conv_convert_frames(c, (uint8_t *)in_buf,
+ (uint8_t *)out_buf,
+ &in_frames,
+ out_frames);
+ EXPECT_EQ(1, out_frames);
+ EXPECT_EQ(1, in_frames);
+
+ cras_fmt_conv_destroy(&c);
+ free(in_buf);
+ free(out_buf);
+}
+
// Only support LE, BE should fail.
TEST(FormatConverterTest, InvalidParamsOnlyLE) {
struct cras_audio_format in_fmt;
@@ -98,7 +154,7 @@
}
}
- cras_fmt_conv_destroy(c);
+ cras_fmt_conv_destroy(&c);
free(in_buff);
free(out_buff);
}
@@ -149,7 +205,7 @@
EXPECT_EQ(1, out_buff[i]);
}
- cras_fmt_conv_destroy(c);
+ cras_fmt_conv_destroy(&c);
free(in_buff);
free(out_buff);
}
@@ -208,7 +264,7 @@
EXPECT_EQ(0x10000, out_buff[i]);
}
- cras_fmt_conv_destroy(c);
+ cras_fmt_conv_destroy(&c);
free(in_buff);
free(out_buff);
}
@@ -268,6 +324,7 @@
EXPECT_EQ(buf_size, out_frames);
for (i = 0; i < buf_size; i++)
EXPECT_LT(0, out_buff[i * 2]);
+ cras_fmt_conv_destroy(&c);
/* Swap channel to FR = 13450, RR = -100.
* Assert left channel is silent.
@@ -283,6 +340,7 @@
EXPECT_EQ(buf_size, out_frames);
for (i = 0; i < buf_size; i++)
EXPECT_LT(0, out_buff[i * 2 + 1]);
+ cras_fmt_conv_destroy(&c);
/* Swap channel to FC = 13450, LFE = -100.
* Assert output left and right has equal magnitude.
@@ -300,6 +358,7 @@
EXPECT_NE(0, out_buff[i * 2]);
EXPECT_EQ(out_buff[i * 2], out_buff[i * 2 + 1]);
}
+ cras_fmt_conv_destroy(&c);
/* Swap channel to FR = 13450, FL = -100.
* Assert output left is positive and right is negative. */
@@ -317,7 +376,123 @@
EXPECT_GT(0, out_buff[i * 2 + 1]);
}
- cras_fmt_conv_destroy(c);
+ cras_fmt_conv_destroy(&c);
+ free(in_buff);
+ free(out_buff);
+}
+
+// Test Quad to Stereo mix.
+TEST(FormatConverterTest, QuadToStereo) {
+ struct cras_fmt_conv *c;
+ struct cras_audio_format in_fmt;
+ struct cras_audio_format out_fmt;
+
+ size_t out_frames;
+ int16_t *in_buff;
+ int16_t *out_buff;
+ unsigned int i;
+ const size_t buf_size = 4096;
+ unsigned int in_buf_size = 4096;
+
+ ResetStub();
+ in_fmt.format = SND_PCM_FORMAT_S16_LE;
+ out_fmt.format = SND_PCM_FORMAT_S16_LE;
+ in_fmt.num_channels = 4;
+ out_fmt.num_channels = 2;
+ in_fmt.frame_rate = 48000;
+ out_fmt.frame_rate = 48000;
+ for (i = 0; i < CRAS_CH_MAX; i++)
+ in_fmt.channel_layout[i] = quad_channel_layout[i];
+
+ c = cras_fmt_conv_create(&in_fmt, &out_fmt, buf_size, 0);
+ ASSERT_NE(c, (void *)NULL);
+
+ out_frames = cras_fmt_conv_out_frames_to_in(c, buf_size);
+ EXPECT_EQ(buf_size, out_frames);
+
+ out_frames = cras_fmt_conv_in_frames_to_out(c, buf_size);
+ EXPECT_EQ(buf_size, out_frames);
+
+ in_buff = (int16_t *)malloc(buf_size * 2 * cras_get_format_bytes(&in_fmt));
+
+ /*
+ * Set left channel positive, right channel negative, assert values are
+ * copied and scaled as expected.
+ */
+ for (i = 0; i < buf_size; i++) {
+ in_buff[i * 4] = 800;
+ in_buff[i * 4 + 1] = -800;
+ in_buff[i * 4 + 2] = 80;
+ in_buff[i * 4 + 3] = -80;
+ }
+ out_buff = (int16_t *)malloc(buf_size * 2 * cras_get_format_bytes(&out_fmt));
+
+ out_frames = cras_fmt_conv_convert_frames(c,
+ (uint8_t *)in_buff,
+ (uint8_t *)out_buff,
+ &in_buf_size,
+ buf_size);
+ EXPECT_EQ(buf_size, out_frames);
+ for (i = 0; i < buf_size; i++) {
+ EXPECT_EQ(820, out_buff[i * 2]);
+ EXPECT_EQ(-820, out_buff[i * 2 + 1]);
+ }
+ cras_fmt_conv_destroy(&c);
+
+ /*
+ * Swap left and right channels, check channel map is respected.
+ */
+ swap_channel_layout(in_fmt.channel_layout, CRAS_CH_FL, CRAS_CH_FR);
+ swap_channel_layout(in_fmt.channel_layout, CRAS_CH_RL, CRAS_CH_RR);
+ c = cras_fmt_conv_create(&in_fmt, &out_fmt, buf_size, 0);
+ out_frames = cras_fmt_conv_convert_frames(c,
+ (uint8_t *)in_buff,
+ (uint8_t *)out_buff,
+ &in_buf_size,
+ buf_size);
+ EXPECT_EQ(buf_size, out_frames);
+ for (i = 0; i < buf_size; i++) {
+ EXPECT_EQ(-820, out_buff[i * 2]);
+ EXPECT_EQ(820, out_buff[i * 2 + 1]);
+ }
+ cras_fmt_conv_destroy(&c);
+
+ /*
+ * Swap front and rear, check channel map is respected.
+ */
+ swap_channel_layout(in_fmt.channel_layout, CRAS_CH_FR, CRAS_CH_RR);
+ swap_channel_layout(in_fmt.channel_layout, CRAS_CH_FL, CRAS_CH_RL);
+ c = cras_fmt_conv_create(&in_fmt, &out_fmt, buf_size, 0);
+ out_frames = cras_fmt_conv_convert_frames(c,
+ (uint8_t *)in_buff,
+ (uint8_t *)out_buff,
+ &in_buf_size,
+ buf_size);
+ EXPECT_EQ(buf_size, out_frames);
+ for (i = 0; i < buf_size; i++) {
+ EXPECT_EQ(-280, out_buff[i * 2]);
+ EXPECT_EQ(280, out_buff[i * 2 + 1]);
+ }
+ cras_fmt_conv_destroy(&c);
+
+ /*
+ * Empty channel map, check default behavior is applied.
+ */
+ for (i = 0; i < CRAS_CH_MAX; i++)
+ in_fmt.channel_layout[i] = -1;
+ c = cras_fmt_conv_create(&in_fmt, &out_fmt, buf_size, 0);
+ out_frames = cras_fmt_conv_convert_frames(c,
+ (uint8_t *)in_buff,
+ (uint8_t *)out_buff,
+ &in_buf_size,
+ buf_size);
+ EXPECT_EQ(buf_size, out_frames);
+ for (i = 0; i < buf_size; i++) {
+ EXPECT_EQ(820, out_buff[i * 2]);
+ EXPECT_EQ(-820, out_buff[i * 2 + 1]);
+ }
+ cras_fmt_conv_destroy(&c);
+
free(in_buff);
free(out_buff);
}
@@ -353,7 +528,7 @@
(uint8_t *)out_buff,
&in_buf_size,
buf_size / 2);
- cras_fmt_conv_destroy(c);
+ cras_fmt_conv_destroy(&c);
free(in_buff);
free(out_buff);
}
@@ -388,7 +563,7 @@
(uint8_t *)out_buff,
&in_buf_size,
buf_size * 2);
- cras_fmt_conv_destroy(c);
+ cras_fmt_conv_destroy(&c);
free(in_buff);
free(out_buff);
}
@@ -427,7 +602,7 @@
(uint8_t *)out_buff,
&in_buf_size,
buf_size * 2);
- cras_fmt_conv_destroy(c);
+ cras_fmt_conv_destroy(&c);
free(in_buff);
free(out_buff);
}
@@ -468,7 +643,7 @@
for (unsigned int i = 0; i < buf_size; i++)
EXPECT_EQ((int16_t)(in_buff[i] >> 16), out_buff[i]);
- cras_fmt_conv_destroy(c);
+ cras_fmt_conv_destroy(&c);
free(in_buff);
free(out_buff);
}
@@ -509,7 +684,7 @@
for (unsigned int i = 0; i < buf_size; i++)
EXPECT_EQ((int16_t)(in_buff[i] >> 8), out_buff[i]);
- cras_fmt_conv_destroy(c);
+ cras_fmt_conv_destroy(&c);
free(in_buff);
free(out_buff);
}
@@ -549,9 +724,10 @@
buf_size);
EXPECT_EQ(buf_size, out_frames);
for (unsigned int i = 0; i < buf_size; i++)
- EXPECT_EQ(((int16_t)(in_buff[i] - 128) << 8), out_buff[i]);
+ EXPECT_EQ((int16_t)((uint16_t)((int16_t)(in_buff[i]) - 128) << 8),
+ out_buff[i]);
- cras_fmt_conv_destroy(c);
+ cras_fmt_conv_destroy(&c);
free(in_buff);
free(out_buff);
}
@@ -590,9 +766,9 @@
buf_size);
EXPECT_EQ(buf_size, out_frames);
for (unsigned int i = 0; i < buf_size; i++)
- EXPECT_EQ(((int32_t)in_buff[i] << 16), out_buff[i]);
+ EXPECT_EQ((int32_t)((uint32_t)(int32_t)in_buff[i] << 16), out_buff[i]);
- cras_fmt_conv_destroy(c);
+ cras_fmt_conv_destroy(&c);
free(in_buff);
free(out_buff);
}
@@ -631,9 +807,9 @@
buf_size);
EXPECT_EQ(buf_size, out_frames);
for (unsigned int i = 0; i < buf_size; i++)
- EXPECT_EQ(((int32_t)in_buff[i] << 8), out_buff[i]);
+ EXPECT_EQ((int32_t)((uint32_t)(int32_t)in_buff[i] << 8), out_buff[i]);
- cras_fmt_conv_destroy(c);
+ cras_fmt_conv_destroy(&c);
free(in_buff);
free(out_buff);
}
@@ -675,7 +851,7 @@
for (unsigned int i = 0; i < buf_size; i++)
EXPECT_EQ((in_buff[i] >> 8) + 128, out_buff[i]);
- cras_fmt_conv_destroy(c);
+ cras_fmt_conv_destroy(&c);
free(in_buff);
free(out_buff);
}
@@ -718,7 +894,7 @@
buf_size);
EXPECT_EQ(buf_size, out_frames);
- cras_fmt_conv_destroy(c);
+ cras_fmt_conv_destroy(&c);
free(in_buff);
free(out_buff);
}
@@ -770,7 +946,7 @@
EXPECT_EQ(0, out_buff[6 * i + 5]);
}
- cras_fmt_conv_destroy(c);
+ cras_fmt_conv_destroy(&c);
free(in_buff);
free(out_buff);
}
@@ -822,7 +998,79 @@
EXPECT_EQ(0, out_buff[6 * i + 5]);
}
- cras_fmt_conv_destroy(c);
+ cras_fmt_conv_destroy(&c);
+ free(in_buff);
+ free(out_buff);
+}
+
+
+// Test 16 bit stereo to quad conversion.
+TEST(FormatConverterTest, ConvertS16LEToS16LEStereoToQuad) {
+ struct cras_fmt_conv *c;
+ struct cras_audio_format in_fmt;
+ struct cras_audio_format out_fmt;
+
+ size_t out_frames;
+ int16_t *in_buff;
+ int16_t *out_buff;
+ const size_t buf_size = 4096;
+ unsigned int in_buf_size = 4096;
+
+ ResetStub();
+ in_fmt.format = SND_PCM_FORMAT_S16_LE;
+ out_fmt.format = SND_PCM_FORMAT_S16_LE;
+ in_fmt.num_channels = 2;
+ out_fmt.num_channels = 4;
+ in_fmt.frame_rate = 48000;
+ out_fmt.frame_rate = 48000;
+ for (unsigned int i = 0; i < CRAS_CH_MAX; i++)
+ out_fmt.channel_layout[i] = quad_channel_layout[i];
+
+ c = cras_fmt_conv_create(&in_fmt, &out_fmt, buf_size, 0);
+ ASSERT_NE(c, (void *)NULL);
+
+ out_frames = cras_fmt_conv_in_frames_to_out(c, buf_size);
+ EXPECT_EQ(buf_size, out_frames);
+
+ in_buff = (int16_t *)malloc(buf_size * cras_get_format_bytes(&in_fmt));
+ for (unsigned int i = 0; i < in_buf_size; i++) {
+ in_buff[i * 2] = 40;
+ in_buff[i * 2 + 1] = 80;
+ }
+
+ out_buff = (int16_t *)malloc(buf_size * cras_get_format_bytes(&out_fmt));
+ out_frames = cras_fmt_conv_convert_frames(c,
+ (uint8_t *)in_buff,
+ (uint8_t *)out_buff,
+ &in_buf_size,
+ buf_size);
+ EXPECT_EQ(buf_size, out_frames);
+ for (unsigned int i = 0; i < buf_size; i++) {
+ EXPECT_EQ(40, out_buff[4 * i]);
+ EXPECT_EQ(80, out_buff[4 * i + 1]);
+ EXPECT_EQ(40, out_buff[4 * i + 2]);
+ EXPECT_EQ(80, out_buff[4 * i + 3]);
+ }
+ cras_fmt_conv_destroy(&c);
+
+ // Swap channels and check channel layout is respected.
+ swap_channel_layout(out_fmt.channel_layout, CRAS_CH_FL, CRAS_CH_RR);
+ swap_channel_layout(out_fmt.channel_layout, CRAS_CH_RL, CRAS_CH_FR);
+ c = cras_fmt_conv_create(&in_fmt, &out_fmt, buf_size, 0);
+ out_frames = cras_fmt_conv_convert_frames(c,
+ (uint8_t *)in_buff,
+ (uint8_t *)out_buff,
+ &in_buf_size,
+ buf_size);
+ EXPECT_EQ(buf_size, out_frames);
+ for (unsigned int i = 0; i < buf_size; i++) {
+ EXPECT_EQ(80, out_buff[4 * i]);
+ EXPECT_EQ(40, out_buff[4 * i + 1]);
+ EXPECT_EQ(80, out_buff[4 * i + 2]);
+ EXPECT_EQ(40, out_buff[4 * i + 3]);
+ }
+
+ cras_fmt_conv_destroy(&c);
free(in_buff);
free(out_buff);
}
@@ -865,7 +1113,7 @@
buf_size * 2);
EXPECT_EQ(buf_size * 2, out_frames);
- cras_fmt_conv_destroy(c);
+ cras_fmt_conv_destroy(&c);
free(in_buff);
free(out_buff);
}
@@ -908,7 +1156,7 @@
buf_size / 2);
EXPECT_EQ(buf_size / 2, out_frames);
- cras_fmt_conv_destroy(c);
+ cras_fmt_conv_destroy(&c);
free(in_buff);
free(out_buff);
}
@@ -952,7 +1200,7 @@
out_frames);
EXPECT_EQ(out_frames, ret_frames);
- cras_fmt_conv_destroy(c);
+ cras_fmt_conv_destroy(&c);
free(in_buff);
free(out_buff);
}
@@ -997,7 +1245,7 @@
out_frames - 1);
EXPECT_EQ(out_frames - 1, ret_frames);
- cras_fmt_conv_destroy(c);
+ cras_fmt_conv_destroy(&c);
free(in_buff);
free(out_buff);
}
@@ -1042,7 +1290,7 @@
out_frames - 2);
EXPECT_EQ(out_frames - 2, ret_frames);
- cras_fmt_conv_destroy(c);
+ cras_fmt_conv_destroy(&c);
free(in_buff);
free(out_buff);
}
@@ -1091,7 +1339,7 @@
out_frames);
EXPECT_EQ(expected_fr, out_frames);
- cras_fmt_conv_destroy(c);
+ cras_fmt_conv_destroy(&c);
free(in_buff);
free(out_buff);
}
@@ -1140,7 +1388,7 @@
buf_size);
EXPECT_EQ(expected_fr, out_frames);
- cras_fmt_conv_destroy(c);
+ cras_fmt_conv_destroy(&c);
free(in_buff);
free(out_buff);
}
@@ -1167,7 +1415,7 @@
config_format_converter(&c, CRAS_STREAM_OUTPUT, &in_fmt, &out_fmt, 4096);
ASSERT_NE(c, (void *)NULL);
- cras_fmt_conv_destroy(c);
+ cras_fmt_conv_destroy(&c);
}
// Test format converter not created when in/out format conversion is not
@@ -1193,6 +1441,7 @@
config_format_converter(&c, CRAS_STREAM_OUTPUT, &in_fmt, &out_fmt, 4096);
EXPECT_NE(c, (void *)NULL);
EXPECT_EQ(0, cras_fmt_conversion_needed(c));
+ cras_fmt_conv_destroy(&c);
}
// Test format converter not created for input when in/out format differs
@@ -1220,6 +1469,7 @@
config_format_converter(&c, CRAS_STREAM_INPUT, &in_fmt, &out_fmt, 4096);
EXPECT_NE(c, (void *)NULL);
EXPECT_EQ(0, cras_fmt_conversion_needed(c));
+ cras_fmt_conv_destroy(&c);
}
TEST(ChannelRemixTest, ChannelRemixAppliedOrNot) {
@@ -1252,7 +1502,9 @@
for (i = 0; i < 100; i++)
EXPECT_EQ(res[i], buf[i]);
- cras_fmt_conv_destroy(conv);
+ cras_fmt_conv_destroy(&conv);
+ free(buf);
+ free(res);
}
int main(int argc, char **argv) {
@@ -1288,6 +1540,8 @@
float src_rate,
float dst_rate)
{
+ linear_resampler_format_bytes = format_bytes;
+ linear_resampler_num_channels = num_channels;
return reinterpret_cast<struct linear_resampler*>(0x33);;
}
@@ -1327,6 +1581,10 @@
resampled_fr = dst_frames;
*src_frames = dst_frames / linear_resampler_ratio;
}
+ unsigned int resampled_bytes = resampled_fr * linear_resampler_format_bytes *
+ linear_resampler_num_channels;
+ for(size_t i = 0; i < resampled_bytes; i++)
+ dst[i] = (uint8_t)rand() & 0xff;
return resampled_fr;
}
diff --git a/cras/src/tests/hfp_iodev_unittest.cc b/cras/src/tests/hfp_iodev_unittest.cc
index 202bb13..3e1dc2d 100644
--- a/cras/src/tests/hfp_iodev_unittest.cc
+++ b/cras/src/tests/hfp_iodev_unittest.cc
@@ -23,6 +23,7 @@
static size_t cras_iodev_rm_node_called;
static size_t cras_iodev_set_active_node_called;
static size_t cras_iodev_free_format_called;
+static size_t cras_iodev_free_resources_called;
static size_t cras_bt_device_sco_connect_called;
static int cras_bt_transport_sco_connect_return_val;
static size_t hfp_info_add_iodev_called;
@@ -46,6 +47,7 @@
cras_iodev_rm_node_called = 0;
cras_iodev_set_active_node_called = 0;
cras_iodev_free_format_called = 0;
+ cras_iodev_free_resources_called = 0;
cras_bt_device_sco_connect_called = 0;
cras_bt_transport_sco_connect_return_val = 0;
hfp_info_add_iodev_called = 0;
@@ -71,8 +73,19 @@
namespace {
-TEST(HfpIodev, CreateHfpOutputIodev) {
- ResetStubData();
+class HfpIodev: public testing::Test {
+ protected:
+ virtual void SetUp() {
+ ResetStubData();
+ }
+
+ virtual void TearDown() {
+ free(dummy_audio_area);
+ dummy_audio_area = NULL;
+ }
+};
+
+TEST_F(HfpIodev, CreateHfpOutputIodev) {
iodev = hfp_iodev_create(CRAS_STREAM_OUTPUT, fake_device, fake_slc,
CRAS_BT_DEVICE_PROFILE_HFP_AUDIOGATEWAY,
fake_info);
@@ -86,10 +99,10 @@
ASSERT_EQ(1, cras_bt_device_rm_iodev_called);
ASSERT_EQ(1, cras_iodev_rm_node_called);
+ ASSERT_EQ(1, cras_iodev_free_resources_called);
}
-TEST(HfpIodev, CreateHfpInputIodev) {
- ResetStubData();
+TEST_F(HfpIodev, CreateHfpInputIodev) {
iodev = hfp_iodev_create(CRAS_STREAM_INPUT, fake_device, fake_slc,
CRAS_BT_DEVICE_PROFILE_HFP_AUDIOGATEWAY, fake_info);
@@ -104,11 +117,10 @@
ASSERT_EQ(1, cras_bt_device_rm_iodev_called);
ASSERT_EQ(1, cras_iodev_rm_node_called);
+ ASSERT_EQ(1, cras_iodev_free_resources_called);
}
-TEST(HfpIodev, OpenHfpIodev) {
- ResetStubData();
-
+TEST_F(HfpIodev, OpenHfpIodev) {
iodev = hfp_iodev_create(CRAS_STREAM_OUTPUT, fake_device, fake_slc,
CRAS_BT_DEVICE_PROFILE_HFP_AUDIOGATEWAY,
fake_info);
@@ -116,7 +128,7 @@
/* hfp_info not start yet */
hfp_info_running_return_val = 0;
- iodev->open_dev(iodev);
+ iodev->configure_dev(iodev);
ASSERT_EQ(1, cras_bt_device_sco_connect_called);
ASSERT_EQ(1, hfp_info_start_called);
@@ -126,14 +138,14 @@
hfp_info_running_return_val = 1;
iodev->close_dev(iodev);
+ hfp_iodev_destroy(iodev);
ASSERT_EQ(1, hfp_info_rm_iodev_called);
ASSERT_EQ(1, hfp_info_stop_called);
ASSERT_EQ(1, cras_iodev_free_format_called);
+ ASSERT_EQ(1, cras_iodev_free_resources_called);
}
-TEST(HfpIodev, OpenIodevWithHfpInfoAlreadyRunning) {
- ResetStubData();
-
+TEST_F(HfpIodev, OpenIodevWithHfpInfoAlreadyRunning) {
iodev = hfp_iodev_create(CRAS_STREAM_INPUT, fake_device, fake_slc,
CRAS_BT_DEVICE_PROFILE_HFP_AUDIOGATEWAY,
fake_info);
@@ -142,7 +154,7 @@
/* hfp_info already started by another device */
hfp_info_running_return_val = 1;
- iodev->open_dev(iodev);
+ iodev->configure_dev(iodev);
ASSERT_EQ(0, cras_bt_device_sco_connect_called);
ASSERT_EQ(0, hfp_info_start_called);
@@ -150,12 +162,14 @@
hfp_info_has_iodev_return_val = 1;
iodev->close_dev(iodev);
+ hfp_iodev_destroy(iodev);
ASSERT_EQ(1, hfp_info_rm_iodev_called);
ASSERT_EQ(0, hfp_info_stop_called);
ASSERT_EQ(1, cras_iodev_free_format_called);
+ ASSERT_EQ(1, cras_iodev_free_resources_called);
}
-TEST(HfpIodev, PutGetBuffer) {
+TEST_F(HfpIodev, PutGetBuffer) {
cras_audio_area *area;
unsigned frames;
@@ -164,7 +178,7 @@
CRAS_BT_DEVICE_PROFILE_HFP_AUDIOGATEWAY,
fake_info);
iodev->format = &fake_format;
- iodev->open_dev(iodev);
+ iodev->configure_dev(iodev);
hfp_buf_acquire_return_val = 100;
iodev->get_buffer(iodev, &area, &frames);
@@ -175,6 +189,8 @@
iodev->put_buffer(iodev, 40);
ASSERT_EQ(1, hfp_buf_release_called);
ASSERT_EQ(40, hfp_buf_release_nwritten_val);
+ hfp_iodev_destroy(iodev);
+ ASSERT_EQ(1, cras_iodev_free_resources_called);
}
} // namespace
@@ -332,6 +348,10 @@
void cras_iodev_free_audio_area(struct cras_iodev *iodev) {
}
+void cras_iodev_free_resources(struct cras_iodev *iodev) {
+ cras_iodev_free_resources_called++;
+}
+
void cras_audio_area_config_buf_pointers(struct cras_audio_area *area,
const struct cras_audio_format *fmt,
uint8_t *base_buffer)
diff --git a/cras/src/tests/iodev_list_unittest.cc b/cras/src/tests/iodev_list_unittest.cc
index 35b515d..26ebc8d 100644
--- a/cras/src/tests/iodev_list_unittest.cc
+++ b/cras/src/tests/iodev_list_unittest.cc
@@ -27,7 +27,6 @@
/* Data for stubs. */
static struct cras_observer_ops *observer_ops;
-static unsigned int cras_system_get_suspended_val;
static int add_stream_called;
static int rm_stream_called;
static unsigned int set_node_attr_called;
@@ -37,14 +36,18 @@
static cras_iodev *audio_thread_add_open_dev_dev;
static int audio_thread_add_open_dev_called;
static int audio_thread_rm_open_dev_called;
+static int audio_thread_is_dev_open_ret;
static struct audio_thread thread;
static struct cras_iodev loopback_input;
static int cras_iodev_close_called;
static struct cras_iodev *cras_iodev_close_dev;
+static struct cras_iodev dummy_hotword_iodev;
static struct cras_iodev dummy_empty_iodev[2];
static stream_callback *stream_add_cb;
static stream_callback *stream_rm_cb;
static struct cras_rstream *stream_list_get_ret;
+static int server_stream_create_called;
+static int server_stream_destroy_called;
static int audio_thread_drain_stream_return;
static int audio_thread_drain_stream_called;
static int cras_tm_create_timer_called;
@@ -57,8 +60,10 @@
static struct cras_iodev *device_disabled_dev;
static int device_disabled_count;
static void *device_enabled_cb_data;
+static void *device_disabled_cb_data;
static struct cras_rstream *audio_thread_add_stream_stream;
static struct cras_iodev *audio_thread_add_stream_dev;
+static struct cras_iodev *audio_thread_disconnect_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];
@@ -79,6 +84,9 @@
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 std::map<const struct cras_iodev*, int> cras_iodev_has_pinned_stream_ret;
+static struct cras_rstream *audio_thread_disconnect_stream_stream;
+static int audio_thread_disconnect_stream_called;
static int cras_iodev_is_zero_volume_ret;
void dummy_update_active_node(struct cras_iodev *iodev,
@@ -98,11 +106,18 @@
cras_iodev_close_called = 0;
stream_list_get_ret = 0;
+ server_stream_create_called = 0;
+ server_stream_destroy_called = 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;
+ audio_thread_disconnect_stream_called = 0;
+ audio_thread_disconnect_stream_stream = NULL;
+ audio_thread_is_dev_open_ret = 0;
+ cras_iodev_has_pinned_stream_ret.clear();
+
sample_rates_[0] = 44100;
sample_rates_[1] = 48000;
sample_rates_[2] = 0;
@@ -199,6 +214,13 @@
audio_thread_dev_start_ramp_req =
CRAS_IODEV_RAMP_REQUEST_UP_START_PLAYBACK;
cras_iodev_is_zero_volume_ret = 0;
+
+ dummy_empty_iodev[0].state = CRAS_IODEV_STATE_CLOSE;
+ dummy_empty_iodev[1].state = CRAS_IODEV_STATE_CLOSE;
+ }
+
+ virtual void TearDown() {
+ cras_iodev_list_reset();
}
static void set_volume_1(struct cras_iodev* iodev) {
@@ -274,14 +296,13 @@
stream_add_cb(&rstream2);
EXPECT_EQ(2, audio_thread_add_stream_called);
- cras_system_get_suspended_val = 1;
audio_thread_rm_open_dev_called = 0;
observer_ops->suspend_changed(NULL, 1);
EXPECT_EQ(1, audio_thread_rm_open_dev_called);
/* Test disable/enable dev won't cause add_stream to audio_thread. */
audio_thread_add_stream_called = 0;
- cras_iodev_list_disable_dev(&d1_);
+ cras_iodev_list_disable_dev(&d1_, false);
cras_iodev_list_enable_dev(&d1_);
EXPECT_EQ(0, audio_thread_add_stream_called);
@@ -298,7 +319,6 @@
audio_thread_add_open_dev_called = 0;
audio_thread_add_stream_called = 0;
- cras_system_get_suspended_val = 0;
stream_list_get_ret = stream_list;
observer_ops->suspend_changed(NULL, 0);
EXPECT_EQ(1, audio_thread_add_open_dev_called);
@@ -333,6 +353,54 @@
/* open dev called twice, one for fallback device. */
EXPECT_EQ(2, cras_iodev_open_called);
EXPECT_EQ(1, audio_thread_add_stream_called);
+ cras_iodev_list_deinit();
+}
+
+TEST_F(IoDevTestSuite, InitDevWithEchoRef) {
+ 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;
+ d1_.echo_reference_dev = &d2_;
+ rc = cras_iodev_list_add_output(&d1_);
+ ASSERT_EQ(0, rc);
+
+ d2_.direction = CRAS_STREAM_INPUT;
+ snprintf(d2_.active_node->name, CRAS_NODE_NAME_BUFFER_SIZE, "echo ref");
+ rc = cras_iodev_list_add_input(&d2_);
+ ASSERT_EQ(0, rc);
+
+ cras_iodev_list_select_node(CRAS_STREAM_OUTPUT,
+ cras_make_node_id(d1_.info.idx, 0));
+ /* No close call happened, because no stream exists. */
+ EXPECT_EQ(0, cras_iodev_close_called);
+
+ cras_iodev_open_ret[1] = 0;
+
+ DL_APPEND(stream_list, &rstream);
+ stream_list_get_ret = stream_list;
+ stream_add_cb(&rstream);
+
+ EXPECT_EQ(1, cras_iodev_open_called);
+ EXPECT_EQ(1, server_stream_create_called);
+ EXPECT_EQ(1, audio_thread_add_stream_called);
+
+ DL_DELETE(stream_list, &rstream);
+ stream_list_get_ret = stream_list;
+ stream_rm_cb(&rstream);
+
+ clock_gettime_retspec.tv_sec = 11;
+ clock_gettime_retspec.tv_nsec = 0;
+ cras_tm_timer_cb(NULL, NULL);
+
+ EXPECT_EQ(1, cras_iodev_close_called);
+ EXPECT_EQ(1, server_stream_destroy_called);
+
+ cras_iodev_list_deinit();
}
TEST_F(IoDevTestSuite, SelectNodeOpenFailShouldScheduleRetry) {
@@ -411,6 +479,7 @@
cras_iodev_list_select_node(CRAS_STREAM_OUTPUT,
cras_make_node_id(d2_.info.idx, 1));
EXPECT_EQ(1, cras_tm_cancel_timer_called);
+ cras_iodev_list_deinit();
}
TEST_F(IoDevTestSuite, InitDevFailShouldScheduleRetry) {
@@ -455,21 +524,59 @@
cras_iodev_list_rm_output(&d1_);
EXPECT_EQ(1, cras_tm_cancel_timer_called);
+ cras_iodev_list_deinit();
}
-static void device_enabled_cb(struct cras_iodev *dev, int enabled,
- void *cb_data)
+TEST_F(IoDevTestSuite, PinnedStreamInitFailShouldScheduleRetry) {
+ 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);
+
+ rstream.is_pinned = 1;
+ rstream.pinned_dev_idx = d1_.info.idx;
+
+ 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);
+ /* Init pinned dev fail, not proceed to add stream. */
+ EXPECT_EQ(1, cras_iodev_open_called);
+ EXPECT_EQ(0, audio_thread_add_stream_called);
+
+ EXPECT_NE((void *)NULL, cras_tm_timer_cb);
+ EXPECT_EQ(1, cras_tm_create_timer_called);
+
+ cras_tm_timer_cb(NULL, cras_tm_timer_cb_data);
+ EXPECT_EQ(2, cras_iodev_open_called);
+ EXPECT_EQ(1, audio_thread_add_stream_called);
+
+ cras_iodev_list_rm_output(&d1_);
+ cras_iodev_list_deinit();
+}
+
+static void device_enabled_cb(struct cras_iodev *dev, void *cb_data)
{
- if (enabled) {
- device_enabled_dev = dev;
- device_enabled_count++;
- } else {
- device_disabled_dev = dev;
- device_disabled_count++;
- }
+ device_enabled_dev = dev;
+ device_enabled_count++;
device_enabled_cb_data = cb_data;
}
+static void device_disabled_cb(struct cras_iodev *dev, void *cb_data)
+{
+ device_disabled_dev = dev;
+ device_disabled_count++;
+ device_disabled_cb_data = cb_data;
+}
+
TEST_F(IoDevTestSuite, SelectNode) {
struct cras_rstream rstream, rstream2;
int rc;
@@ -496,7 +603,7 @@
device_disabled_count = 0;
EXPECT_EQ(0, cras_iodev_list_set_device_enabled_callback(
- device_enabled_cb, (void *)0xABCD));
+ device_enabled_cb, device_disabled_cb, (void *)0xABCD));
cras_iodev_list_add_active_node(CRAS_STREAM_OUTPUT,
cras_make_node_id(d1_.info.idx, 1));
@@ -506,7 +613,8 @@
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);
+ // But no close call actually happened, because no stream exists.
+ EXPECT_EQ(0, audio_thread_rm_open_dev_called);
EXPECT_EQ(1, device_disabled_count);
EXPECT_NE(&d1_, device_disabled_dev);
@@ -529,14 +637,15 @@
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, 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));
+ EXPECT_EQ(0, cras_iodev_list_set_device_enabled_callback(NULL, NULL, NULL));
+ cras_iodev_list_deinit();
}
TEST_F(IoDevTestSuite, SelectPreviouslyEnabledNode) {
@@ -563,7 +672,7 @@
device_disabled_count = 0;
EXPECT_EQ(0, cras_iodev_list_set_device_enabled_callback(
- device_enabled_cb, (void *)0xABCD));
+ device_enabled_cb, device_disabled_cb, (void *)0xABCD));
// Add an active node.
cras_iodev_list_add_active_node(CRAS_STREAM_OUTPUT,
@@ -613,7 +722,8 @@
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));
+ EXPECT_EQ(0, cras_iodev_list_set_device_enabled_callback(NULL, NULL, NULL));
+ cras_iodev_list_deinit();
}
TEST_F(IoDevTestSuite, UpdateActiveNode) {
@@ -651,6 +761,7 @@
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);
+ cras_iodev_list_deinit();
}
TEST_F(IoDevTestSuite, SelectNonExistingNode) {
@@ -670,6 +781,7 @@
cras_make_node_id(2, 1));
EXPECT_EQ(0, d1_.is_enabled);
EXPECT_EQ(2, cras_observer_notify_active_node_called);
+ cras_iodev_list_deinit();
}
// Devices with the wrong direction should be rejected.
@@ -801,6 +913,7 @@
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_));
+ cras_iodev_list_deinit();
}
// Test output_mute_changed callback.
@@ -885,6 +998,7 @@
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_));
+ cras_iodev_list_deinit();
}
// Test enable/disable an iodev.
@@ -895,7 +1009,7 @@
EXPECT_EQ(0, cras_iodev_list_add_output(&d1_));
EXPECT_EQ(0, cras_iodev_list_set_device_enabled_callback(
- device_enabled_cb, (void *)0xABCD));
+ device_enabled_cb, device_disabled_cb, (void *)0xABCD));
// Enable a device.
cras_iodev_list_enable_dev(&d1_);
@@ -905,16 +1019,16 @@
EXPECT_EQ(&d1_, cras_iodev_list_get_first_enabled_iodev(CRAS_STREAM_OUTPUT));
// Disable a device.
- cras_iodev_list_disable_dev(&d1_);
+ cras_iodev_list_disable_dev(&d1_, false);
EXPECT_EQ(&d1_, device_disabled_dev);
EXPECT_EQ(1, device_disabled_count);
- EXPECT_EQ((void *)0xABCD, device_enabled_cb_data);
+ EXPECT_EQ((void *)0xABCD, device_disabled_cb_data);
- EXPECT_EQ(-EEXIST, cras_iodev_list_set_device_enabled_callback(
- device_enabled_cb, (void *)0xABCD));
+ EXPECT_EQ(0, cras_iodev_list_set_device_enabled_callback(
+ device_enabled_cb, device_disabled_cb, (void *)0xCDEF));
EXPECT_EQ(2, cras_observer_notify_active_node_called);
- EXPECT_EQ(0, cras_iodev_list_set_device_enabled_callback(NULL, NULL));
+ EXPECT_EQ(0, cras_iodev_list_set_device_enabled_callback(NULL, NULL, NULL));
}
// Test adding/removing an input dev to the list.
@@ -1121,6 +1235,7 @@
IONODE_ATTR_PLUGGED, 1);
EXPECT_EQ(rc, 0);
EXPECT_EQ(1, set_node_attr_called);
+ cras_iodev_list_deinit();
}
TEST_F(IoDevTestSuite, AddActiveNode) {
@@ -1174,6 +1289,7 @@
/* Assert active devices was set to default one, when selected device
* removed. */
cras_iodev_list_rm_output(&d1_);
+ cras_iodev_list_deinit();
}
TEST_F(IoDevTestSuite, DrainTimerCancel) {
@@ -1225,6 +1341,7 @@
clock_gettime_retspec.tv_sec += 30;
cras_tm_timer_cb(NULL, NULL);
EXPECT_EQ(1, audio_thread_rm_open_dev_called);
+ cras_iodev_list_deinit();
}
TEST_F(IoDevTestSuite, RemoveThenSelectActiveNode) {
@@ -1248,6 +1365,7 @@
cras_iodev_list_rm_active_node(CRAS_STREAM_OUTPUT, id);
ASSERT_EQ(audio_thread_rm_open_dev_called, 0);
+ cras_iodev_list_deinit();
}
TEST_F(IoDevTestSuite, AddRemovePinnedStream) {
@@ -1257,6 +1375,65 @@
// Add 2 output devices.
d1_.direction = CRAS_STREAM_OUTPUT;
+ d1_.info.idx = 1;
+ EXPECT_EQ(0, cras_iodev_list_add_output(&d1_));
+ cras_iodev_list_select_node(CRAS_STREAM_OUTPUT,
+ cras_make_node_id(d1_.info.idx, 0));
+ EXPECT_EQ(1, update_active_node_called);
+ EXPECT_EQ(&d1_, update_active_node_iodev_val[0]);
+
+ d2_.direction = CRAS_STREAM_OUTPUT;
+ d2_.info.idx = 2;
+ EXPECT_EQ(0, cras_iodev_list_add_output(&d2_));
+
+ // Setup pinned stream.
+ memset(&rstream, 0, sizeof(rstream));
+ rstream.is_pinned = 1;
+ rstream.pinned_dev_idx = d1_.info.idx;
+
+ // Add pinned stream to d1.
+ EXPECT_EQ(0, stream_add_cb(&rstream));
+ EXPECT_EQ(1, audio_thread_add_stream_called);
+ EXPECT_EQ(&d1_, audio_thread_add_stream_dev);
+ EXPECT_EQ(&rstream, audio_thread_add_stream_stream);
+ EXPECT_EQ(2, update_active_node_called);
+ // Init d1_ because of pinned stream
+ EXPECT_EQ(&d1_, update_active_node_iodev_val[1]);
+
+ // 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(4, update_active_node_called);
+ // Unselect d1_ and select to d2_
+ EXPECT_EQ(&d1_, update_active_node_iodev_val[2]);
+ EXPECT_EQ(&d2_, update_active_node_iodev_val[3]);
+
+ // 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(5, update_active_node_called);
+ // close pinned device
+ EXPECT_EQ(&d1_, update_active_node_iodev_val[4]);
+
+ // Assume dev is already opened, add pin stream should not trigger another
+ // update_active_node call, but will trigger audio_thread_add_stream.
+ audio_thread_is_dev_open_ret = 1;
+ EXPECT_EQ(0, stream_add_cb(&rstream));
+ EXPECT_EQ(5, update_active_node_called);
+ EXPECT_EQ(2, audio_thread_add_stream_called);
+
+ cras_iodev_list_deinit();
+}
+
+TEST_F(IoDevTestSuite, SuspendResumePinnedStream) {
+ struct cras_rstream rstream;
+
+ cras_iodev_list_init();
+
+ // Add 2 output devices.
+ d1_.direction = CRAS_STREAM_OUTPUT;
EXPECT_EQ(0, cras_iodev_list_add_output(&d1_));
d2_.direction = CRAS_STREAM_OUTPUT;
EXPECT_EQ(0, cras_iodev_list_add_output(&d2_));
@@ -1271,22 +1448,124 @@
EXPECT_EQ(1, audio_thread_add_stream_called);
EXPECT_EQ(&d1_, audio_thread_add_stream_dev);
EXPECT_EQ(&rstream, audio_thread_add_stream_stream);
- EXPECT_EQ(1, update_active_node_called);
- EXPECT_EQ(&d1_, update_active_node_iodev_val[0]);
- // 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]);
+ DL_APPEND(stream_list_get_ret, &rstream);
- // Remove pinned stream from d1, check d1 is closed after stream removed.
- EXPECT_EQ(0, stream_rm_cb(&rstream));
+ // Test for suspend
+
+ // Device state enters no_stream after stream is disconnected.
+ d1_.state = CRAS_IODEV_STATE_NO_STREAM_RUN;
+ // Device has no pinned stream now. But this pinned stream remains in stream_list.
+ cras_iodev_has_pinned_stream_ret[&d1_] = 0;
+
+ // Suspend
+ observer_ops->suspend_changed(NULL, 1);
+
+ // Verify that stream is disconnected and d1 is closed.
+ EXPECT_EQ(1, audio_thread_disconnect_stream_called);
+ EXPECT_EQ(&rstream, audio_thread_disconnect_stream_stream);
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]);
+
+ // Test for resume
+ cras_iodev_open_called = 0;
+ audio_thread_add_stream_called = 0;
+ audio_thread_add_stream_stream = NULL;
+ d1_.state = CRAS_IODEV_STATE_CLOSE;
+
+ // Resume
+ observer_ops->suspend_changed(NULL, 0);
+
+ // Verify that device is opened and stream is attached to the device.
+ EXPECT_EQ(1, cras_iodev_open_called);
+ EXPECT_EQ(1, audio_thread_add_stream_called);
+ EXPECT_EQ(&rstream, audio_thread_add_stream_stream);
+ cras_iodev_list_deinit();
+}
+
+TEST_F(IoDevTestSuite, HotwordStreamsAddedThenSuspendResume) {
+ struct cras_rstream rstream;
+ struct cras_rstream *stream_list = NULL;
+ cras_iodev_list_init();
+
+ node1.type = CRAS_NODE_TYPE_HOTWORD;
+ d1_.direction = CRAS_STREAM_INPUT;
+ EXPECT_EQ(0, cras_iodev_list_add_input(&d1_));
+
+ memset(&rstream, 0, sizeof(rstream));
+ rstream.is_pinned = 1;
+ rstream.pinned_dev_idx = d1_.info.idx;
+ rstream.flags = HOTWORD_STREAM;
+
+ /* Add a hotword stream. */
+ EXPECT_EQ(0, stream_add_cb(&rstream));
+ EXPECT_EQ(1, audio_thread_add_stream_called);
+ EXPECT_EQ(&d1_, audio_thread_add_stream_dev);
+ EXPECT_EQ(&rstream, audio_thread_add_stream_stream);
+
+ DL_APPEND(stream_list, &rstream);
+ stream_list_get_ret = stream_list;
+
+ /* Suspend hotword streams, verify the existing stream disconnects
+ * from the hotword device and connects to the empty iodev. */
+ EXPECT_EQ(0, cras_iodev_list_suspend_hotword_streams());
+ EXPECT_EQ(1, audio_thread_disconnect_stream_called);
+ EXPECT_EQ(&rstream, audio_thread_disconnect_stream_stream);
+ EXPECT_EQ(&d1_, audio_thread_disconnect_stream_dev);
+ EXPECT_EQ(2, audio_thread_add_stream_called);
+ EXPECT_EQ(&rstream, audio_thread_add_stream_stream);
+ EXPECT_EQ(&dummy_hotword_iodev, audio_thread_add_stream_dev);
+
+ /* Resume hotword streams, verify the stream disconnects from
+ * the empty iodev and connects back to the real hotword iodev. */
+ EXPECT_EQ(0, cras_iodev_list_resume_hotword_stream());
+ EXPECT_EQ(2, audio_thread_disconnect_stream_called);
+ EXPECT_EQ(&rstream, audio_thread_disconnect_stream_stream);
+ EXPECT_EQ(&dummy_hotword_iodev, audio_thread_disconnect_stream_dev);
+ EXPECT_EQ(3, audio_thread_add_stream_called);
+ EXPECT_EQ(&rstream, audio_thread_add_stream_stream);
+ EXPECT_EQ(&d1_, audio_thread_add_stream_dev);
+ cras_iodev_list_deinit();
+}
+
+TEST_F(IoDevTestSuite, HotwordStreamsAddedAfterSuspend) {
+ struct cras_rstream rstream;
+ struct cras_rstream *stream_list = NULL;
+ cras_iodev_list_init();
+
+ node1.type = CRAS_NODE_TYPE_HOTWORD;
+ d1_.direction = CRAS_STREAM_INPUT;
+ EXPECT_EQ(0, cras_iodev_list_add_input(&d1_));
+
+ memset(&rstream, 0, sizeof(rstream));
+ rstream.is_pinned = 1;
+ rstream.pinned_dev_idx = d1_.info.idx;
+ rstream.flags = HOTWORD_STREAM;
+
+ /* Suspends hotword streams before a stream connected. */
+ EXPECT_EQ(0, cras_iodev_list_suspend_hotword_streams());
+ EXPECT_EQ(0, audio_thread_disconnect_stream_called);
+ EXPECT_EQ(0, audio_thread_add_stream_called);
+
+ DL_APPEND(stream_list, &rstream);
+ stream_list_get_ret = stream_list;
+
+ /* Hotword stream connected, verify it is added to the empty iodev. */
+ EXPECT_EQ(0, stream_add_cb(&rstream));
+ EXPECT_EQ(1, audio_thread_add_stream_called);
+ EXPECT_EQ(&dummy_hotword_iodev, audio_thread_add_stream_dev);
+ EXPECT_EQ(&rstream, audio_thread_add_stream_stream);
+
+ /* Resume hotword streams, now the existing hotword stream should disconnect
+ * from the empty iodev and connect to the real hotword iodev. */
+ EXPECT_EQ(0, cras_iodev_list_resume_hotword_stream());
+ EXPECT_EQ(1, audio_thread_disconnect_stream_called);
+ EXPECT_EQ(&rstream, audio_thread_disconnect_stream_stream);
+ EXPECT_EQ(&dummy_hotword_iodev, audio_thread_disconnect_stream_dev);
+ EXPECT_EQ(2, audio_thread_add_stream_called);
+ EXPECT_EQ(&rstream, audio_thread_add_stream_stream);
+ EXPECT_EQ(&d1_, audio_thread_add_stream_dev);
+ cras_iodev_list_deinit();
}
} // namespace
@@ -1307,11 +1586,6 @@
void cras_system_state_update_complete() {
}
-int cras_system_get_suspended()
-{
- return cras_system_get_suspended_val;
-}
-
struct audio_thread *audio_thread_create() {
return &thread;
}
@@ -1350,6 +1624,12 @@
return 0;
}
+int audio_thread_is_dev_open(struct audio_thread *thread,
+ struct cras_iodev *dev)
+{
+ return audio_thread_is_dev_open_ret;
+}
+
int audio_thread_add_stream(struct audio_thread *thread,
struct cras_rstream *stream,
struct cras_iodev **devs,
@@ -1365,6 +1645,9 @@
struct cras_rstream *stream,
struct cras_iodev *iodev)
{
+ audio_thread_disconnect_stream_called++;
+ audio_thread_disconnect_stream_stream = stream;
+ audio_thread_disconnect_stream_dev = iodev;
return 0;
}
@@ -1413,14 +1696,29 @@
return 0;
}
-struct cras_iodev *empty_iodev_create(enum CRAS_STREAM_DIRECTION direction) {
- dummy_empty_iodev[direction].direction = direction;
- 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;
+struct cras_iodev *empty_iodev_create(enum CRAS_STREAM_DIRECTION direction,
+ enum CRAS_NODE_TYPE node_type) {
+ struct cras_iodev *dev;
+ if (node_type == CRAS_NODE_TYPE_HOTWORD) {
+ dev = &dummy_hotword_iodev;
+ } else {
+ dev = &dummy_empty_iodev[direction];
}
- return &dummy_empty_iodev[direction];
+ dev->direction = direction;
+ dev->update_active_node = dummy_update_active_node;
+ if (dev->active_node == NULL) {
+ struct cras_ionode *node = (struct cras_ionode *)calloc(1, sizeof(*node));
+ node->type = node_type;
+ dev->active_node = node;
+ }
+ return dev;
+}
+
+void empty_iodev_destroy(struct cras_iodev *iodev) {
+ if (iodev->active_node) {
+ free(iodev->active_node);
+ iodev->active_node = NULL;
+ }
}
struct cras_iodev *test_iodev_create(enum CRAS_STREAM_DIRECTION direction,
@@ -1441,7 +1739,8 @@
void loopback_iodev_destroy(struct cras_iodev *iodev) {
}
-int cras_iodev_open(struct cras_iodev *iodev, unsigned int cb_level)
+int cras_iodev_open(struct cras_iodev *iodev, unsigned int cb_level,
+ const struct cras_audio_format *fmt)
{
if (cras_iodev_open_ret[cras_iodev_open_called] == 0)
iodev->state = CRAS_IODEV_STATE_OPEN;
@@ -1476,6 +1775,11 @@
return cras_iodev_state_ret[iodev];
}
+int cras_iodev_has_pinned_stream(const struct cras_iodev *dev)
+{
+ return cras_iodev_has_pinned_stream_ret[dev];
+}
+
struct stream_list *stream_list_create(stream_callback *add_cb,
stream_callback *rm_cb,
stream_create_func *create_cb,
@@ -1492,6 +1796,16 @@
struct cras_rstream *stream_list_get(struct stream_list *list) {
return stream_list_get_ret;
}
+void server_stream_create(struct stream_list *stream_list,
+ unsigned int dev_idx)
+{
+ server_stream_create_called++;
+}
+void server_stream_destroy(struct stream_list *stream_list,
+ unsigned int dev_idx)
+{
+ server_stream_destroy_called++;
+}
int cras_rstream_create(struct cras_rstream_config *config,
struct cras_rstream **stream_out) {
@@ -1590,6 +1904,22 @@
return 0;
}
+#ifdef HAVE_WEBRTC_APM
+struct cras_apm *cras_apm_list_add(struct cras_apm_list *list,
+ void *dev_ptr,
+ const struct cras_audio_format *fmt)
+{
+ return NULL;
+}
+void cras_apm_list_remove(struct cras_apm_list *list, void *dev_ptr)
+{
+}
+int cras_apm_list_init(const char *device_config_dir)
+{
+ return 0;
+}
+#endif
+
// From librt.
int clock_gettime(clockid_t clk_id, struct timespec *tp) {
tp->tv_sec = clock_gettime_retspec.tv_sec;
diff --git a/cras/src/tests/iodev_stub.cc b/cras/src/tests/iodev_stub.cc
new file mode 100644
index 0000000..519c3f0
--- /dev/null
+++ b/cras/src/tests/iodev_stub.cc
@@ -0,0 +1,148 @@
+/* Copyright 2017 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <time.h>
+#include <unordered_map>
+
+extern "C" {
+#include "cras_iodev.h"
+}
+
+namespace {
+ struct cb_data {
+ int frames_queued_ret;
+ timespec frames_queued_ts;
+ };
+ std::unordered_map<cras_iodev*, cb_data> data_map;
+} // namespace
+
+void iodev_stub_reset() {
+ data_map.clear();
+}
+
+void iodev_stub_frames_queued(cras_iodev* iodev, int ret, timespec ts) {
+ cb_data data = { ret, ts };
+ data_map.insert({iodev, data});
+}
+
+extern "C" {
+
+double cras_iodev_get_est_rate_ratio(const struct cras_iodev *iodev) {
+ return 48000.0;
+}
+
+int cras_iodev_get_dsp_delay(const struct cras_iodev *iodev) {
+ return 0;
+}
+
+int cras_iodev_frames_queued(struct cras_iodev *iodev,
+ struct timespec *tstamp) {
+ auto elem = data_map.find(iodev);
+ if (elem != data_map.end()) {
+ *tstamp = elem->second.frames_queued_ts;
+ return elem->second.frames_queued_ret;
+ }
+ clock_gettime(CLOCK_MONOTONIC_RAW, tstamp);
+ return 0;
+}
+
+struct dev_stream *cras_iodev_rm_stream(struct cras_iodev *iodev,
+ const struct cras_rstream *stream) {
+ return NULL;
+}
+
+int cras_iodev_update_rate(struct cras_iodev *iodev, unsigned int level,
+ struct timespec *level_tstamp) {
+ return 0;
+}
+
+enum CRAS_IODEV_STATE cras_iodev_state(const struct cras_iodev *iodev) {
+ return CRAS_IODEV_STATE_OPEN;
+}
+
+unsigned int cras_iodev_all_streams_written(struct cras_iodev *iodev) {
+ return 0;
+}
+
+int cras_iodev_put_input_buffer(struct cras_iodev *iodev) {
+ return 0;
+}
+
+int cras_iodev_put_output_buffer(struct cras_iodev *iodev, uint8_t *frames,
+ unsigned int nframes, int* non_empty,
+ struct cras_fmt_conv *output_converter) {
+ return 0;
+}
+
+int cras_iodev_get_input_buffer(struct cras_iodev *iodev,
+ unsigned *frames) {
+ return 0;
+}
+
+int cras_iodev_get_output_buffer(struct cras_iodev *iodev,
+ struct cras_audio_area **area,
+ unsigned *frames) {
+ return 0;
+}
+
+float cras_iodev_get_software_gain_scaler(const struct cras_iodev *iodev) {
+ return 0.0;
+}
+
+void cras_iodev_stream_written(struct cras_iodev *iodev,
+ struct dev_stream *stream,
+ unsigned int nwritten) {
+}
+
+int cras_iodev_prepare_output_before_write_samples(struct cras_iodev *odev)
+{ return 0;
+}
+
+int cras_iodev_buffer_avail(struct cras_iodev *iodev, unsigned hw_level) {
+ return 0;
+}
+
+unsigned int cras_iodev_max_stream_offset(const struct cras_iodev *iodev) {
+ return 0;
+}
+
+int cras_iodev_odev_should_wake(const struct cras_iodev *odev)
+{
+ return 1;
+}
+
+int cras_iodev_output_underrun(struct cras_iodev *odev) {
+ return 0;
+}
+
+int cras_iodev_reset_request(struct cras_iodev* iodev) {
+ return 0;
+}
+
+unsigned int cras_iodev_stream_offset(struct cras_iodev *iodev,
+ struct dev_stream *stream) {
+ return 0;
+}
+
+unsigned int cras_iodev_get_num_underruns(const struct cras_iodev *iodev)
+{
+ return 0;
+}
+
+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;
+}
+
+void cras_iodev_update_highest_hw_level(struct cras_iodev *iodev,
+ unsigned int hw_level)
+{
+}
+
+} // extern "C"
diff --git a/cras/src/tests/iodev_stub.h b/cras/src/tests/iodev_stub.h
new file mode 100644
index 0000000..b1c4745
--- /dev/null
+++ b/cras/src/tests/iodev_stub.h
@@ -0,0 +1,15 @@
+/* Copyright 2017 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef IODEV_STUB_H_
+#define IODEV_STUB_H_
+
+#include <time.h>
+
+void iodev_stub_reset();
+
+void iodev_stub_frames_queued(cras_iodev* iodev, int ret, timespec ts);
+
+#endif // IODEV_STUB_H_
diff --git a/cras/src/tests/iodev_unittest.cc b/cras/src/tests/iodev_unittest.cc
index 29e6e22..f022164 100644
--- a/cras/src/tests/iodev_unittest.cc
+++ b/cras/src/tests/iodev_unittest.cc
@@ -13,6 +13,7 @@
#include "utlist.h"
#include "cras_audio_area.h"
#include "audio_thread_log.h"
+#include "input_data.h"
// Mock software volume scalers.
float softvol_scalers[101];
@@ -52,11 +53,13 @@
static float cras_dsp_pipeline_sink_buffer[2][DSP_BUFFER_SIZE];
static int cras_dsp_pipeline_get_delay_called;
static int cras_dsp_pipeline_apply_called;
+static int cras_dsp_pipeline_set_sink_ext_module_called;
static int cras_dsp_pipeline_apply_sample_count;
static unsigned int cras_mix_mute_count;
static unsigned int cras_dsp_num_input_channels_return;
static unsigned int cras_dsp_num_output_channels_return;
struct cras_dsp_context *cras_dsp_context_new_return;
+static unsigned int cras_dsp_load_dummy_pipeline_called;
static unsigned int rate_estimator_add_frames_num_frames;
static unsigned int rate_estimator_add_frames_called;
static int cras_system_get_mute_return;
@@ -103,6 +106,11 @@
static float cras_scale_buffer_increment_scaler;
static float cras_scale_buffer_increment_increment;
static int cras_scale_buffer_increment_channel;
+static struct cras_audio_format audio_fmt;
+static int buffer_share_add_id_called;
+static int buffer_share_get_new_write_point_ret;
+static int ext_mod_configure_called;
+static struct input_data *input_data_create_ret;
// Iodev callback
int update_channel_layout(struct cras_iodev *iodev) {
@@ -144,10 +152,12 @@
sizeof(cras_dsp_pipeline_sink_buffer));
cras_dsp_pipeline_get_delay_called = 0;
cras_dsp_pipeline_apply_called = 0;
+ cras_dsp_pipeline_set_sink_ext_module_called = 0;
cras_dsp_pipeline_apply_sample_count = 0;
cras_dsp_num_input_channels_return = 2;
cras_dsp_num_output_channels_return = 2;
cras_dsp_context_new_return = NULL;
+ cras_dsp_load_dummy_pipeline_called = 0;
rate_estimator_add_frames_num_frames = 0;
rate_estimator_add_frames_called = 0;
cras_system_get_mute_return = 0;
@@ -194,6 +204,11 @@
cras_scale_buffer_increment_scaler = 0;
cras_scale_buffer_increment_increment = 0;
cras_scale_buffer_increment_channel = 0;
+ audio_fmt.format = SND_PCM_FORMAT_S16_LE;
+ audio_fmt.frame_rate = 48000;
+ audio_fmt.num_channels = 2;
+ buffer_share_add_id_called = 0;
+ ext_mod_configure_called = 0;
}
namespace {
@@ -479,10 +494,11 @@
EXPECT_EQ(SND_PCM_FORMAT_S16_LE, iodev_.ext_format->format);
EXPECT_EQ(48000, iodev_.ext_format->frame_rate);
EXPECT_EQ(2, iodev_.ext_format->num_channels);
- EXPECT_EQ(2, cras_audio_format_set_channel_layout_called);
EXPECT_EQ(0, dsp_context_free_called);
- for (i = 0; i < CRAS_CH_MAX; i++)
+ for (i = 0; i < CRAS_CH_MAX; i++) {
EXPECT_EQ(iodev_.format->channel_layout[i], stereo_layout[i]);
+ EXPECT_EQ(iodev_.ext_format->channel_layout[i], stereo_layout[i]);
+ }
}
TEST_F(IoDevSetFormatTestSuite, UpdateChannelLayoutFail6ch) {
@@ -506,10 +522,11 @@
EXPECT_EQ(SND_PCM_FORMAT_S16_LE, iodev_.ext_format->format);
EXPECT_EQ(48000, iodev_.ext_format->frame_rate);
EXPECT_EQ(6, iodev_.ext_format->num_channels);
- EXPECT_EQ(2, cras_audio_format_set_channel_layout_called);
EXPECT_EQ(0, dsp_context_free_called);
- for (i = 0; i < CRAS_CH_MAX; i++)
+ for (i = 0; i < CRAS_CH_MAX; i++) {
EXPECT_EQ(iodev_.format->channel_layout[i], default_6ch_layout[i]);
+ EXPECT_EQ(iodev_.ext_format->channel_layout[i], default_6ch_layout[i]);
+ }
}
// Put buffer tests
@@ -589,7 +606,7 @@
iodev.format = &fmt;
iodev.put_buffer = put_buffer;
- rc = cras_iodev_put_output_buffer(&iodev, frames, 20);
+ rc = cras_iodev_put_output_buffer(&iodev, frames, 20, NULL, nullptr);
EXPECT_EQ(0, rc);
EXPECT_EQ(20, cras_mix_mute_count);
EXPECT_EQ(20, put_buffer_nframes);
@@ -651,7 +668,7 @@
iodev.format = &fmt;
iodev.put_buffer = put_buffer;
- rc = cras_iodev_put_output_buffer(&iodev, frames, 20);
+ rc = cras_iodev_put_output_buffer(&iodev, frames, 20, NULL, nullptr);
EXPECT_EQ(0, rc);
EXPECT_EQ(20, cras_mix_mute_count);
EXPECT_EQ(20, put_buffer_nframes);
@@ -680,7 +697,7 @@
// Assume ramping is done.
cras_ramp_get_current_action_ret.type = CRAS_RAMP_ACTION_NONE;
- rc = cras_iodev_put_output_buffer(&iodev, frames, 20);
+ rc = cras_iodev_put_output_buffer(&iodev, frames, 20, NULL, nullptr);
// Output should be muted.
EXPECT_EQ(0, rc);
EXPECT_EQ(20, cras_mix_mute_count);
@@ -690,7 +707,7 @@
// 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);
+ rc = cras_iodev_put_output_buffer(&iodev, frames, 20, NULL, nullptr);
// Output should not be muted.
EXPECT_EQ(0, rc);
@@ -729,7 +746,7 @@
// Assume ramping is done.
cras_ramp_get_current_action_ret.type = CRAS_RAMP_ACTION_NONE;
- rc = cras_iodev_put_output_buffer(&iodev, frames, 20);
+ rc = cras_iodev_put_output_buffer(&iodev, frames, 20, NULL, nullptr);
EXPECT_EQ(0, rc);
EXPECT_EQ(20, cras_mix_mute_count);
EXPECT_EQ(20, put_buffer_nframes);
@@ -738,7 +755,7 @@
// 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);
+ rc = cras_iodev_put_output_buffer(&iodev, frames, 20, NULL, nullptr);
// Output should not be muted.
EXPECT_EQ(0, rc);
@@ -770,7 +787,7 @@
iodev.format = &fmt;
iodev.put_buffer = put_buffer;
- rc = cras_iodev_put_output_buffer(&iodev, frames, 22);
+ rc = cras_iodev_put_output_buffer(&iodev, frames, 22, NULL, nullptr);
EXPECT_EQ(0, rc);
EXPECT_EQ(0, cras_mix_mute_count);
EXPECT_EQ(22, put_buffer_nframes);
@@ -796,7 +813,7 @@
cras_iodev_register_pre_dsp_hook(&iodev, pre_dsp_hook, (void *)0x1234);
cras_iodev_register_post_dsp_hook(&iodev, post_dsp_hook, (void *)0x5678);
- rc = cras_iodev_put_output_buffer(&iodev, frames, 32);
+ rc = cras_iodev_put_output_buffer(&iodev, frames, 32, NULL, nullptr);
EXPECT_EQ(0, rc);
EXPECT_EQ(0, cras_mix_mute_count);
EXPECT_EQ(1, pre_dsp_hook_called);
@@ -829,7 +846,7 @@
cras_system_get_volume_return = 13;
softvol_scalers[13] = 0.435;
- rc = cras_iodev_put_output_buffer(&iodev, frames, 53);
+ rc = cras_iodev_put_output_buffer(&iodev, frames, 53, NULL, nullptr);
EXPECT_EQ(0, rc);
EXPECT_EQ(0, cras_mix_mute_count);
EXPECT_EQ(53, put_buffer_nframes);
@@ -867,7 +884,7 @@
cras_system_get_volume_return = volume;
softvol_scalers[volume] = volume_scaler;
- rc = cras_iodev_put_output_buffer(&iodev, frames, n_frames);
+ rc = cras_iodev_put_output_buffer(&iodev, frames, n_frames, NULL, nullptr);
EXPECT_EQ(0, rc);
EXPECT_EQ(0, cras_mix_mute_count);
EXPECT_EQ(n_frames, put_buffer_nframes);
@@ -884,7 +901,7 @@
cras_system_get_volume_return = volume;
softvol_scalers[volume] = volume_scaler;
- rc = cras_iodev_put_output_buffer(&iodev, frames, n_frames);
+ rc = cras_iodev_put_output_buffer(&iodev, frames, n_frames, NULL, nullptr);
EXPECT_EQ(0, rc);
EXPECT_EQ(0, cras_mix_mute_count);
// cras_scale_buffer is not called.
@@ -932,7 +949,7 @@
// 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);
+ rc = cras_iodev_put_output_buffer(&iodev, frames, n_frames, NULL, nullptr);
EXPECT_EQ(0, rc);
EXPECT_EQ(0, cras_mix_mute_count);
// cras_scale_buffer is not called.
@@ -946,7 +963,7 @@
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);
+ rc = cras_iodev_put_output_buffer(&iodev, frames, n_frames, NULL, nullptr);
EXPECT_EQ(0, rc);
EXPECT_EQ(0, cras_mix_mute_count);
// cras_scale_buffer is not called.
@@ -983,7 +1000,7 @@
iodev.format = &fmt;
iodev.put_buffer = put_buffer;
- rc = cras_iodev_put_output_buffer(&iodev, frames, 53);
+ rc = cras_iodev_put_output_buffer(&iodev, frames, 53, NULL, nullptr);
EXPECT_EQ(0, rc);
EXPECT_EQ(0, cras_mix_mute_count);
EXPECT_EQ(53, put_buffer_nframes);
@@ -1227,7 +1244,7 @@
// 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
+ // 2400 * 0.01 dB is 15.848931
EXPECT_FLOAT_EQ(15.848931, cras_iodev_get_software_gain_scaler(&iodev));
EXPECT_FLOAT_EQ(3000, cras_iodev_maximum_software_gain(&iodev));
@@ -1269,10 +1286,10 @@
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));
+ EXPECT_EQ(-EINVAL, cras_iodev_get_input_buffer(&iodev, &frames));
}
-static int open_dev(struct cras_iodev *iodev) {
+static int configure_dev(struct cras_iodev *iodev) {
iodev->buffer_size = iodev_buffer_size;
return 0;
}
@@ -1281,14 +1298,15 @@
struct cras_iodev iodev;
memset(&iodev, 0, sizeof(iodev));
- iodev.open_dev = open_dev;
+ iodev.configure_dev = configure_dev;
iodev.direction = CRAS_STREAM_OUTPUT;
+ iodev.ext_format = &audio_fmt;
ResetStubData();
iodev.state = CRAS_IODEV_STATE_CLOSE;
iodev_buffer_size = 1024;
- cras_iodev_open(&iodev, 240);
+ cras_iodev_open(&iodev, 240, &audio_fmt);
EXPECT_EQ(0, iodev.max_cb_level);
EXPECT_EQ(240, iodev.min_cb_level);
@@ -1296,6 +1314,28 @@
EXPECT_EQ(CRAS_IODEV_STATE_NO_STREAM_RUN, iodev.state);
}
+TEST(IoDev, OpenOutputDeviceWithLowRateFmt) {
+ struct cras_iodev iodev;
+
+ memset(&iodev, 0, sizeof(iodev));
+ iodev.configure_dev = configure_dev;
+ iodev.direction = CRAS_STREAM_OUTPUT;
+ iodev.ext_format = &audio_fmt;
+ ResetStubData();
+
+ cras_audio_format low_rate_fmt = audio_fmt;
+ low_rate_fmt.frame_rate = 8000;
+ iodev.state = CRAS_IODEV_STATE_CLOSE;
+
+ iodev_buffer_size = 1024;
+ cras_iodev_open(&iodev, 40, &low_rate_fmt);
+ EXPECT_EQ(0, iodev.max_cb_level);
+
+ // Test that iodev min_cb_level should be set to
+ // 40 * 48000 / 8000 = 240
+ EXPECT_EQ(240, iodev.min_cb_level);
+}
+
int fake_start(const struct cras_iodev *iodev) {
return 0;
}
@@ -1304,15 +1344,16 @@
struct cras_iodev iodev;
memset(&iodev, 0, sizeof(iodev));
- iodev.open_dev = open_dev;
+ iodev.configure_dev = configure_dev;
iodev.direction = CRAS_STREAM_OUTPUT;
+ iodev.ext_format = &audio_fmt;
ResetStubData();
iodev.state = CRAS_IODEV_STATE_CLOSE;
iodev.start = fake_start;
iodev_buffer_size = 1024;
- cras_iodev_open(&iodev, 240);
+ cras_iodev_open(&iodev, 240, &audio_fmt);
EXPECT_EQ(0, iodev.max_cb_level);
EXPECT_EQ(240, iodev.min_cb_level);
@@ -1324,14 +1365,15 @@
struct cras_iodev iodev;
memset(&iodev, 0, sizeof(iodev));
- iodev.open_dev = open_dev;
+ iodev.configure_dev = configure_dev;
iodev.direction = CRAS_STREAM_INPUT;
+ iodev.ext_format = &audio_fmt;
ResetStubData();
iodev.state = CRAS_IODEV_STATE_CLOSE;
iodev_buffer_size = 1024;
- cras_iodev_open(&iodev, 240);
+ cras_iodev_open(&iodev, 240, &audio_fmt);
EXPECT_EQ(0, iodev.max_cb_level);
EXPECT_EQ(240, iodev.min_cb_level);
@@ -1343,15 +1385,16 @@
struct cras_iodev iodev;
memset(&iodev, 0, sizeof(iodev));
- iodev.open_dev = open_dev;
+ iodev.configure_dev = configure_dev;
iodev.direction = CRAS_STREAM_INPUT;
+ iodev.ext_format = &audio_fmt;
ResetStubData();
iodev.state = CRAS_IODEV_STATE_CLOSE;
iodev.start = fake_start;
iodev_buffer_size = 1024;
- cras_iodev_open(&iodev, 240);
+ cras_iodev_open(&iodev, 240, &audio_fmt);
EXPECT_EQ(0, iodev.max_cb_level);
EXPECT_EQ(240, iodev.min_cb_level);
@@ -1359,6 +1402,28 @@
EXPECT_EQ(CRAS_IODEV_STATE_NORMAL_RUN, iodev.state);
}
+TEST(IoDev, OpenInputDeviceWithLowRateFmt) {
+ struct cras_iodev iodev;
+
+ memset(&iodev, 0, sizeof(iodev));
+ iodev.configure_dev = configure_dev;
+ iodev.direction = CRAS_STREAM_INPUT;
+ iodev.ext_format = &audio_fmt;
+ ResetStubData();
+
+ cras_audio_format low_rate_fmt = audio_fmt;
+ low_rate_fmt.frame_rate = 8000;
+ iodev.state = CRAS_IODEV_STATE_CLOSE;
+
+ iodev_buffer_size = 1024;
+ cras_iodev_open(&iodev, 40, &low_rate_fmt);
+ EXPECT_EQ(0, iodev.max_cb_level);
+
+ // Test that iodev min_cb_level should be set to
+ // 40 * 48000 / 8000 = 240
+ EXPECT_EQ(240, iodev.min_cb_level);
+}
+
static int simple_no_stream(struct cras_iodev *dev, int enable)
{
simple_no_stream_enable = enable;
@@ -1372,8 +1437,11 @@
struct dev_stream stream1, stream2;
memset(&iodev, 0, sizeof(iodev));
- iodev.open_dev = open_dev;
+ memset(&rstream1, 0, sizeof(rstream1));
+ memset(&rstream2, 0, sizeof(rstream2));
+ iodev.configure_dev = configure_dev;
iodev.no_stream = simple_no_stream;
+ iodev.ext_format = &audio_fmt;
iodev.state = CRAS_IODEV_STATE_NORMAL_RUN;
rstream1.cb_threshold = 800;
stream1.stream = &rstream1;
@@ -1382,7 +1450,7 @@
ResetStubData();
iodev_buffer_size = 1024;
- cras_iodev_open(&iodev, rstream1.cb_threshold);
+ cras_iodev_open(&iodev, rstream1.cb_threshold, &audio_fmt);
EXPECT_EQ(0, iodev.max_cb_level);
EXPECT_EQ(512, iodev.min_cb_level);
@@ -1390,10 +1458,12 @@
cras_iodev_add_stream(&iodev, &stream1);
EXPECT_EQ(800, iodev.max_cb_level);
EXPECT_EQ(512, iodev.min_cb_level);
+ EXPECT_EQ(1, buffer_share_add_id_called);
cras_iodev_add_stream(&iodev, &stream2);
EXPECT_EQ(800, iodev.max_cb_level);
EXPECT_EQ(400, iodev.min_cb_level);
+ EXPECT_EQ(2, buffer_share_add_id_called);
cras_iodev_rm_stream(&iodev, &rstream1);
EXPECT_EQ(400, iodev.max_cb_level);
@@ -1403,7 +1473,28 @@
/* When all streams are removed, keep the last min_cb_level for draining. */
cras_iodev_rm_stream(&iodev, &rstream2);
EXPECT_EQ(0, iodev.max_cb_level);
- EXPECT_EQ(400, iodev.min_cb_level);
+ EXPECT_EQ(512, iodev.min_cb_level);
+}
+
+TEST(IoDev, TriggerOnlyStreamNoBufferShare) {
+ struct cras_iodev iodev;
+ struct cras_rstream rstream;
+ struct dev_stream stream;
+
+ memset(&iodev, 0, sizeof(iodev));
+ memset(&rstream, 0, sizeof(rstream));
+ iodev.configure_dev = configure_dev;
+ iodev.ext_format = &audio_fmt;
+ iodev.state = CRAS_IODEV_STATE_NORMAL_RUN;
+ rstream.cb_threshold = 800;
+ rstream.flags = TRIGGER_ONLY;
+ stream.stream = &rstream;
+ ResetStubData();
+
+ cras_iodev_open(&iodev, rstream.cb_threshold, &audio_fmt);
+ /* TRIGGER_ONLY streams shall not be added to buffer_share. */
+ cras_iodev_add_stream(&iodev, &stream);
+ EXPECT_EQ(0, buffer_share_add_id_called);
}
TEST(IoDev, FillZeros) {
@@ -1503,6 +1594,8 @@
struct dev_stream stream1;
struct cras_iodev_info info;
+ memset(&info, 0, sizeof(info));
+
ResetStubData();
rstream1.cb_threshold = min_cb_level;
@@ -1523,13 +1616,13 @@
iodev.direction = CRAS_STREAM_OUTPUT;
iodev.buffer_size = BUFFER_SIZE;
iodev.no_stream = no_stream;
- iodev.open_dev = open_dev;
+ iodev.configure_dev = configure_dev;
iodev.start = fake_start;
iodev.info = info;
iodev_buffer_size = BUFFER_SIZE;
// Open device.
- cras_iodev_open(&iodev, rstream1.cb_threshold);
+ cras_iodev_open(&iodev, rstream1.cb_threshold, &fmt);
// Add one stream to device.
cras_iodev_add_stream(&iodev, &stream1);
@@ -1825,30 +1918,51 @@
TEST(IoDev, FramesToPlayInSleep) {
struct cras_iodev iodev;
- unsigned int min_cb_level = 240, hw_level;
+ struct cras_audio_format fmt;
+ unsigned int min_cb_level = 512, hw_level;
unsigned int got_hw_level, got_frames;
struct timespec hw_tstamp;
memset(&iodev, 0, sizeof(iodev));
+ memset(&fmt, 0, sizeof(fmt));
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;
+ iodev.streams = reinterpret_cast<struct dev_stream *>(0x1);
+ iodev.state = CRAS_IODEV_STATE_NORMAL_RUN;
+ iodev.format = &fmt;
+ fmt.frame_rate = 48000;
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);
+ EXPECT_EQ(got_hw_level, hw_level);
+ EXPECT_EQ(got_frames, 514);
+
+ // Device is running. There is at least one stream for this device.
+ // hw_level is 2x greater than min_cb_level.
+ hw_level = 2 * 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(got_hw_level, hw_level);
+ EXPECT_EQ(got_frames, 1026);
+
+ // Device is running. There is at least one stream for this device.
+ // hw_level is less than min_cb_level.
+ hw_level = min_cb_level / 2;
+ fr_queued = hw_level;
+ got_frames = cras_iodev_frames_to_play_in_sleep(
+ &iodev, &got_hw_level, &hw_tstamp);
+ EXPECT_EQ(got_hw_level, hw_level);
+ EXPECT_EQ(got_frames, 208);
// Device is running. There is no stream for this device.
// hw_level is greater than min_cb_level.
@@ -1856,8 +1970,8 @@
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);
+ EXPECT_EQ(got_hw_level, hw_level);
+ EXPECT_EQ(got_frames, 0);
// Device is running. There is no stream for this device.
// hw_level is less than min_cb_level.
@@ -1867,8 +1981,8 @@
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);
+ EXPECT_EQ(got_hw_level, hw_level);
+ EXPECT_EQ(got_frames, 0);
}
static unsigned int get_num_underruns(const struct cras_iodev *iodev) {
@@ -1892,14 +2006,15 @@
ResetStubData();
- iodev.open_dev = open_dev;
+ iodev.configure_dev = configure_dev;
iodev.direction = CRAS_STREAM_OUTPUT;
+ iodev.ext_format = &audio_fmt;
iodev.state = CRAS_IODEV_STATE_CLOSE;
iodev_buffer_size = 1024;
// Open device.
- cras_iodev_open(&iodev, 240);
+ cras_iodev_open(&iodev, 240, &audio_fmt);
// The first reset request works.
EXPECT_EQ(0, cras_iodev_reset_request(&iodev));
@@ -1910,7 +2025,7 @@
EXPECT_EQ(1, device_monitor_reset_device_called);
// Assume device is opened again.
- cras_iodev_open(&iodev, 240);
+ cras_iodev_open(&iodev, 240, &audio_fmt);
// The reset request works.
EXPECT_EQ(0, cras_iodev_reset_request(&iodev));
@@ -1957,6 +2072,109 @@
EXPECT_EQ(1, output_underrun_called);
}
+static void ext_mod_configure(
+ struct ext_dsp_module *ext,
+ unsigned int buffer_size,
+ unsigned int num_channels,
+ unsigned int rate)
+{
+ ext_mod_configure_called++;
+}
+
+TEST(IoDev, SetExtDspMod) {
+ struct cras_iodev iodev;
+ struct cras_audio_format fmt;
+ struct ext_dsp_module ext;
+
+ ResetStubData();
+
+ memset(&iodev, 0, sizeof(iodev));
+ fmt.format = SND_PCM_FORMAT_S16_LE;
+ fmt.frame_rate = 48000;
+ fmt.num_channels = 2;
+ iodev.configure_dev = configure_dev;
+ iodev.ext_format = &fmt;
+ iodev.format = &fmt;
+ iodev.state = CRAS_IODEV_STATE_CLOSE;
+ ext.configure = ext_mod_configure;
+
+ iodev.dsp_context = reinterpret_cast<cras_dsp_context *>(0xf0f);
+ cras_dsp_get_pipeline_ret = 0x25;
+
+ cras_iodev_set_ext_dsp_module(&iodev, &ext);
+ EXPECT_EQ(0, ext_mod_configure_called);
+
+ cras_iodev_open(&iodev, 240, &fmt);
+ EXPECT_EQ(1, ext_mod_configure_called);
+ EXPECT_EQ(1, cras_dsp_get_pipeline_called);
+ EXPECT_EQ(1, cras_dsp_pipeline_set_sink_ext_module_called);
+
+ cras_iodev_set_ext_dsp_module(&iodev, NULL);
+ EXPECT_EQ(1, ext_mod_configure_called);
+
+ cras_iodev_set_ext_dsp_module(&iodev, &ext);
+ EXPECT_EQ(2, ext_mod_configure_called);
+ EXPECT_EQ(2, cras_dsp_get_pipeline_called);
+ EXPECT_EQ(2, cras_dsp_pipeline_set_sink_ext_module_called);
+
+ /* If pipeline doesn't exist, dummy pipeline should be loaded. */
+ cras_dsp_get_pipeline_ret = 0x0;
+ cras_iodev_set_ext_dsp_module(&iodev, &ext);
+ EXPECT_EQ(3, ext_mod_configure_called);
+ EXPECT_EQ(4, cras_dsp_get_pipeline_called);
+ EXPECT_EQ(1, cras_dsp_load_dummy_pipeline_called);
+ EXPECT_EQ(3, cras_dsp_pipeline_set_sink_ext_module_called);
+}
+
+TEST(IoDev, InputDspOffset) {
+ struct cras_iodev iodev;
+ struct cras_audio_format fmt;
+ struct cras_rstream rstream1;
+ struct dev_stream stream1;
+ struct input_data data;
+ unsigned int frames = 240;
+
+ ResetStubData();
+
+ rstream1.cb_threshold = 240;
+ rstream1.stream_id = 123;
+ stream1.stream = &rstream1;
+
+ memset(&iodev, 0, sizeof(iodev));
+ fmt.format = SND_PCM_FORMAT_S16_LE;
+ fmt.frame_rate = 48000;
+ fmt.num_channels = 2;
+ iodev.configure_dev = configure_dev;
+ iodev.ext_format = &fmt;
+ iodev.format = &fmt;
+ iodev.state = CRAS_IODEV_STATE_CLOSE;
+ iodev.get_buffer = get_buffer;
+ iodev.put_buffer = put_buffer;
+ iodev.direction = CRAS_STREAM_INPUT;
+ iodev.buffer_size = 480;
+
+ iodev.dsp_context = reinterpret_cast<cras_dsp_context *>(0xf0f);
+ cras_dsp_get_pipeline_ret = 0x25;
+ input_data_create_ret = &data;
+
+ cras_iodev_open(&iodev, 240, &fmt);
+
+ cras_iodev_add_stream(&iodev, &stream1);
+ cras_iodev_get_input_buffer(&iodev, &frames);
+
+ buffer_share_get_new_write_point_ret = 100;
+ cras_iodev_put_input_buffer(&iodev);
+ EXPECT_EQ(140, iodev.input_dsp_offset);
+
+ frames = 130;
+ cras_iodev_get_input_buffer(&iodev, &frames);
+ EXPECT_EQ(130, iodev.input_frames_read);
+
+ buffer_share_get_new_write_point_ret = 80;
+ cras_iodev_put_input_buffer(&iodev);
+ EXPECT_EQ(60, iodev.input_dsp_offset);
+}
+
extern "C" {
// From libpthread.
@@ -1969,12 +2187,6 @@
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,
@@ -1982,6 +2194,12 @@
{
}
+size_t cras_fmt_conv_in_frames_to_out(struct cras_fmt_conv *conv,
+ size_t in_frames)
+{
+ return in_frames;
+}
+
// From buffer_share
struct buffer_share *buffer_share_create(unsigned int buf_sz) {
return NULL;
@@ -1997,10 +2215,11 @@
}
unsigned int buffer_share_get_new_write_point(struct buffer_share *mix) {
- return 0;
+ return buffer_share_get_new_write_point_ret;
}
int buffer_share_add_id(struct buffer_share *mix, unsigned int id) {
+ buffer_share_add_id_called++;
return 0;
}
@@ -2038,6 +2257,11 @@
void cras_dsp_load_pipeline(struct cras_dsp_context *ctx)
{
}
+void cras_dsp_load_dummy_pipeline(struct cras_dsp_context *ctx,
+ unsigned int num_channels)
+{
+ cras_dsp_load_dummy_pipeline_called++;
+}
void cras_dsp_set_variable_string(struct cras_dsp_context *ctx, const char *key,
const char *value)
@@ -2080,11 +2304,13 @@
return 0;
}
-void cras_dsp_pipeline_apply(struct pipeline *pipeline,
- uint8_t *buf, unsigned int frames)
+int cras_dsp_pipeline_apply(struct pipeline *pipeline,
+ uint8_t *buf, snd_pcm_format_t format,
+ unsigned int frames)
{
cras_dsp_pipeline_apply_called++;
cras_dsp_pipeline_apply_sample_count = frames;
+ return 0;
}
void cras_dsp_pipeline_add_statistic(struct pipeline *pipeline,
@@ -2092,6 +2318,11 @@
int samples)
{
}
+void cras_dsp_pipeline_set_sink_ext_module(struct pipeline *pipeline,
+ struct ext_dsp_module *ext_module)
+{
+ cras_dsp_pipeline_set_sink_ext_module_called++;
+}
unsigned int cras_dsp_num_output_channels(const struct cras_dsp_context *ctx)
{
@@ -2307,6 +2538,45 @@
return 0;
}
+static void mod_run(struct ext_dsp_module *ext,
+ unsigned int nframes)
+{
+}
+
+static void mod_configure(struct ext_dsp_module *ext,
+ unsigned int buffer_size,
+ unsigned int num_channels,
+ unsigned int rate)
+{
+}
+
+struct input_data *input_data_create(void *dev_ptr)
+{
+ if (input_data_create_ret) {
+ input_data_create_ret->ext.run = mod_run;
+ input_data_create_ret->ext.configure = mod_configure;
+ }
+ return input_data_create_ret;
+}
+
+void input_data_destroy(struct input_data **data)
+{
+}
+void input_data_set_all_streams_read(struct input_data *data,
+ unsigned int nframes)
+{
+}
+
+int cras_audio_thread_severe_underrun()
+{
+ return 0;
+}
+
+int cras_audio_thread_underrun()
+{
+ return 0;
+}
+
} // extern "C"
} // namespace
diff --git a/cras/src/tests/linear_resampler_unittest.cc b/cras/src/tests/linear_resampler_unittest.cc
index e299648..6dbab94 100644
--- a/cras/src/tests/linear_resampler_unittest.cc
+++ b/cras/src/tests/linear_resampler_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2014 The Chromium OS Autholr. All rights reserved.
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -54,6 +54,7 @@
EXPECT_LE(*(int16_t *)(in_buf + 4 * i),
*(int16_t *)(out_buf + 4 * (i + 1)));
}
+ linear_resampler_destroy(lr);
}
TEST(LinearResampler, ResampleIntegerFractionToLarger) {
@@ -104,6 +105,7 @@
EXPECT_LE(*(int16_t *)(in_buf + 4 * i + 2),
*(int16_t *)(out_buf + 4 * i + 2));
}
+ linear_resampler_destroy(lr);
}
TEST(LinearResampler, ResampleIntegerFractionToLess) {
@@ -152,6 +154,7 @@
EXPECT_LE(*(int16_t *)(in_buf + 4 * i + 2),
*(int16_t *)(out_buf + 4 * i + 2));
}
+ linear_resampler_destroy(lr);
}
TEST(LinearResampler, ResampleIntegerNoSrcBuffer) {
@@ -170,6 +173,7 @@
out_buf, BUF_SIZE);
EXPECT_EQ(0, rc);
EXPECT_EQ(0, count);
+ linear_resampler_destroy(lr);
}
TEST(LinearResampler, ResampleIntegerNoDstBuffer) {
@@ -188,6 +192,7 @@
out_buf, 0);
EXPECT_EQ(0, rc);
EXPECT_EQ(0, count);
+ linear_resampler_destroy(lr);
}
extern "C" {
diff --git a/cras/src/tests/loopback_iodev_unittest.cc b/cras/src/tests/loopback_iodev_unittest.cc
index b0387bf..b5acb1a 100644
--- a/cras/src/tests/loopback_iodev_unittest.cc
+++ b/cras/src/tests/loopback_iodev_unittest.cc
@@ -14,6 +14,7 @@
#include "cras_shm.h"
#include "cras_types.h"
#include "dev_stream.h"
+#include "utlist.h"
}
namespace {
@@ -30,8 +31,9 @@
static unsigned int cras_iodev_list_add_input_called;
static unsigned int cras_iodev_list_rm_input_called;
static unsigned int cras_iodev_list_set_device_enabled_callback_called;
-static device_enabled_callback_t cras_iodev_list_set_device_enabled_callback_cb;
-static void *cras_iodev_list_set_device_enabled_callback_cb_data;
+static device_enabled_callback_t device_enabled_callback_cb;
+static device_disabled_callback_t device_disabled_callback_cb;
+static void *device_enabled_callback_cb_data;
class LoopBackTestSuite : public testing::Test{
protected:
@@ -58,7 +60,9 @@
virtual void TearDown() {
loopback_iodev_destroy(loop_in_);
EXPECT_EQ(1, cras_iodev_list_rm_input_called);
- EXPECT_EQ(NULL, cras_iodev_list_set_device_enabled_callback_cb);
+ EXPECT_EQ(NULL, device_enabled_callback_cb);
+ EXPECT_EQ(NULL, device_disabled_callback_cb);
+ free(dummy_audio_area);
}
uint8_t buf_[kBufferSize];
@@ -73,15 +77,15 @@
iodev.direction = CRAS_STREAM_OUTPUT;
iodev.format = &fmt_;
iodev.ext_format = &fmt_;
+ iodev.streams = NULL;
enabled_dev = &iodev;
// Open loopback devices.
- EXPECT_EQ(0, loop_in_->open_dev(loop_in_));
+ EXPECT_EQ(0, loop_in_->configure_dev(loop_in_));
EXPECT_EQ(1, cras_iodev_list_set_device_enabled_callback_called);
// Signal an output device is enabled.
- cras_iodev_list_set_device_enabled_callback_cb(&iodev, 1,
- cras_iodev_list_set_device_enabled_callback_cb_data);
+ device_enabled_callback_cb(&iodev, device_enabled_callback_cb_data);
// Expect that a hook was added to the iodev
ASSERT_NE(reinterpret_cast<loopback_hook_t>(NULL), loop_hook);
@@ -106,7 +110,7 @@
time_now.tv_sec = 100;
time_now.tv_nsec = 0;
- EXPECT_EQ(0, loop_in_->open_dev(loop_in_));
+ EXPECT_EQ(0, loop_in_->configure_dev(loop_in_));
EXPECT_EQ(1, cras_iodev_list_set_device_enabled_callback_called);
// Should be 480 samples after 480/frame rate seconds
@@ -139,7 +143,7 @@
iodev.streams = &stream;
enabled_dev = &iodev;
- loop_in_->open_dev(loop_in_);
+ loop_in_->configure_dev(loop_in_);
ASSERT_NE(reinterpret_cast<void *>(NULL), loop_hook);
// Loopback callback for the hook.
@@ -185,6 +189,7 @@
void cras_iodev_add_node(struct cras_iodev *iodev, struct cras_ionode *node)
{
+ DL_APPEND(iodev->nodes, node);
}
void cras_iodev_set_active_node(struct cras_iodev *iodev,
@@ -220,12 +225,15 @@
return 0;
}
-int cras_iodev_list_set_device_enabled_callback(device_enabled_callback_t cb,
- void *cb_data)
+int cras_iodev_list_set_device_enabled_callback(
+ device_enabled_callback_t enabled_cb,
+ device_disabled_callback_t disabled_cb,
+ void *cb_data)
{
cras_iodev_list_set_device_enabled_callback_called++;
- cras_iodev_list_set_device_enabled_callback_cb = cb;
- cras_iodev_list_set_device_enabled_callback_cb_data = cb_data;
+ device_enabled_callback_cb = enabled_cb;
+ device_disabled_callback_cb = disabled_cb;
+ device_enabled_callback_cb_data = cb_data;
return 0;
}
diff --git a/cras/src/tests/observer_unittest.cc b/cras/src/tests/observer_unittest.cc
index d45a6a4..899590f 100644
--- a/cras/src/tests/observer_unittest.cc
+++ b/cras/src/tests/observer_unittest.cc
@@ -196,7 +196,7 @@
ResetStubData();
rc = cras_observer_server_init();
ASSERT_EQ(0, rc);
- EXPECT_EQ(13, cras_alert_create_called);
+ EXPECT_EQ(15, 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),
@@ -230,6 +230,11 @@
CRAS_STREAM_POST_MIX_PRE_DSP]]);
EXPECT_EQ(reinterpret_cast<void *>(suspend_changed_alert),
cras_alert_add_callback_map[g_observer->alerts.suspend_changed]);
+ EXPECT_EQ(reinterpret_cast<void *>(hotword_triggered_alert),
+ cras_alert_add_callback_map[g_observer->alerts.hotword_triggered]);
+ EXPECT_EQ(reinterpret_cast<void *>(non_empty_audio_state_changed_alert),
+ cras_alert_add_callback_map[
+ g_observer->alerts.non_empty_audio_state_changed]);
cras_observer_get_ops(NULL, &ops1_);
EXPECT_NE(0, cras_observer_ops_are_empty(&ops1_));
@@ -243,7 +248,7 @@
virtual void TearDown() {
cras_observer_server_free();
- EXPECT_EQ(13, cras_alert_destroy_called);
+ EXPECT_EQ(15, cras_alert_destroy_called);
ResetStubData();
}
@@ -285,6 +290,7 @@
EXPECT_NE(0, cras_observer_ops_are_empty(&ops2_));
cras_observer_set_ops(client2_, &ops2_);
+ cras_observer_remove(client2_);
cb_context.clear();
alert(NULL, data);
// No callbacks executed.
@@ -567,6 +573,33 @@
DoObserverRemoveClear(num_active_streams_alert, data);
};
+TEST_F(ObserverTest, NotifyHotwordTriggered) {
+ struct cras_observer_alert_data_hotword_triggered *data;
+
+ cras_observer_notify_hotword_triggered(100, 200);
+ EXPECT_EQ(cras_alert_pending_alert_value,
+ g_observer->alerts.hotword_triggered);
+ 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_hotword_triggered *>(
+ cras_alert_pending_data_value);
+ EXPECT_EQ(data->tv_sec, 100);
+ EXPECT_EQ(data->tv_nsec, 200);
+}
+
+TEST_F(ObserverTest, NonEmpyAudioStateChanged) {
+ struct cras_observer_non_empty_audio_state *data;
+
+ cras_observer_notify_non_empty_audio_state_changed(1);
+ EXPECT_EQ(cras_alert_pending_alert_value,
+ g_observer->alerts.non_empty_audio_state_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_non_empty_audio_state *>(
+ cras_alert_pending_data_value);
+ EXPECT_EQ(data->non_empty, 1);
+}
+
// Stubs
extern "C" {
diff --git a/cras/src/tests/polled_interval_checker_unittest.cc b/cras/src/tests/polled_interval_checker_unittest.cc
new file mode 100644
index 0000000..c799387
--- /dev/null
+++ b/cras/src/tests/polled_interval_checker_unittest.cc
@@ -0,0 +1,155 @@
+// Copyright 2018 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 "polled_interval_checker.h"
+}
+
+static const int INTERVAL_DURATION = 5;
+
+static struct timespec time_now;
+
+static struct polled_interval* create_interval() {
+ struct polled_interval *interval =
+ pic_polled_interval_create(INTERVAL_DURATION);
+ EXPECT_NE(interval, static_cast<struct polled_interval*>(NULL));
+ return interval;
+}
+
+TEST(PolledIntervalCheckerTest, CreateDestroy) {
+ // Create an interval, checks it is non-null.
+ struct polled_interval *interval = create_interval();
+
+ pic_polled_interval_destroy(&interval);
+
+ // Check it's been set to null.
+ EXPECT_EQ(interval, static_cast<struct polled_interval*>(NULL));
+}
+
+TEST(PolledIntervalCheckerTest, BasicFlow) {
+ // Set initial time.
+ time_now.tv_sec = 1000;
+ time_now.tv_nsec = 0;
+ pic_update_current_time();
+
+ // Create interval starting at initial time.
+ struct polled_interval *interval = create_interval();
+
+ // Check it hasn't elapsed.
+ EXPECT_FALSE(pic_interval_elapsed(interval));
+
+ // Increment time by less than the interval duration.
+ time_now.tv_sec += INTERVAL_DURATION / 2;
+ pic_update_current_time();
+
+ // Check the interval hasn't elapsed yet.
+ EXPECT_FALSE(pic_interval_elapsed(interval));
+
+ // Increment time past the duration of the interval.
+ time_now.tv_sec += INTERVAL_DURATION;
+
+ // We haven't updated the current time, check the interval hasn't
+ // elapsed (that it isn't calling clock_gettime without us asking it to).
+ EXPECT_FALSE(pic_interval_elapsed(interval));
+
+ // Update time, check the interval has elapsed.
+ pic_update_current_time();
+ EXPECT_TRUE(pic_interval_elapsed(interval));
+ pic_polled_interval_destroy(&interval);
+}
+
+TEST(PolledIntervalCheckerTest, DoesNotResetAutomatically) {
+ // Set initial time.
+ time_now.tv_sec = 1000;
+ time_now.tv_nsec = 0;
+ pic_update_current_time();
+
+ struct polled_interval *interval = create_interval();
+
+ // Sanity check.
+ EXPECT_FALSE(pic_interval_elapsed(interval));
+
+ // Increment time so the interval elapses.
+ time_now.tv_sec += INTERVAL_DURATION;
+ pic_update_current_time();
+
+ // Check the interval has elapsed.
+ EXPECT_TRUE(pic_interval_elapsed(interval));
+
+ // Increment time further.
+ time_now.tv_sec += INTERVAL_DURATION * 2;
+ pic_update_current_time();
+
+ // Check the interval has still elapsed.
+ EXPECT_TRUE(pic_interval_elapsed(interval));
+
+ // Check repeated calls return true.
+ EXPECT_TRUE(pic_interval_elapsed(interval));
+ pic_polled_interval_destroy(&interval);
+}
+
+TEST(PolledIntervalCheckerTest, Reset) {
+ // Set initial time.
+ time_now.tv_sec = 1000;
+ time_now.tv_nsec = 0;
+ pic_update_current_time();
+
+ struct polled_interval *interval = create_interval();
+
+ // Sanity check.
+ EXPECT_FALSE(pic_interval_elapsed(interval));
+
+ // Increment time so the interval elapses.
+ time_now.tv_sec += INTERVAL_DURATION;
+ pic_update_current_time();
+
+ // Check the interval has elapsed.
+ EXPECT_TRUE(pic_interval_elapsed(interval));
+
+ // Increment time further.
+ time_now.tv_sec += INTERVAL_DURATION * 2;
+ pic_update_current_time();
+
+ // Check the interval has still elapsed.
+ EXPECT_TRUE(pic_interval_elapsed(interval));
+
+ // Reset the interval.
+ pic_interval_reset(interval);
+
+ // Check it's been reset.
+ EXPECT_FALSE(pic_interval_elapsed(interval));
+
+ // Increment time to just before it should elapse again.
+ time_now.tv_sec += INTERVAL_DURATION - 1;
+ pic_update_current_time();
+
+ // Check it still has not elapsed.
+ EXPECT_FALSE(pic_interval_elapsed(interval));
+
+ // Increment time to one duration after we reset it.
+ time_now.tv_sec += 1;
+ pic_update_current_time();
+
+ // Check the interval has elapsed now.
+ EXPECT_TRUE(pic_interval_elapsed(interval));
+ pic_polled_interval_destroy(&interval);
+}
+
+/* Stubs */
+extern "C" {
+
+int clock_gettime(clockid_t clk_id, struct timespec *tp) {
+ *tp = time_now;
+ return 0;
+}
+
+} // extern "C"
+
+int main(int argc, char **argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/cras/src/tests/rclient_unittest.cc b/cras/src/tests/rclient_unittest.cc
index e699c7e..0d32c2a 100644
--- a/cras/src/tests/rclient_unittest.cc
+++ b/cras/src/tests/rclient_unittest.cc
@@ -35,6 +35,7 @@
static int cras_system_set_capture_mute_called;
static size_t cras_system_set_capture_mute_locked_value;
static int cras_system_set_capture_mute_locked_called;
+static int cras_system_state_dump_snapshots_called;
static size_t cras_make_fd_nonblocking_called;
static audio_thread* iodev_get_thread_return;
static int stream_list_add_stream_return;
@@ -42,6 +43,7 @@
static unsigned int stream_list_disconnect_stream_called;
static unsigned int cras_iodev_list_rm_input_called;
static unsigned int cras_iodev_list_rm_output_called;
+static unsigned int cras_server_metrics_stream_config_called;
static struct cras_rstream dummy_rstream;
static size_t cras_observer_num_ops_registered;
static size_t cras_observer_register_notify_called;
@@ -59,6 +61,7 @@
cras_rstream_create_return = 0;
cras_rstream_create_stream_out = (struct cras_rstream *)NULL;
cras_iodev_attach_stream_retval = 0;
+ cras_server_metrics_stream_config_called = 0;
cras_system_set_volume_value = 0;
cras_system_set_volume_called = 0;
cras_system_set_capture_gain_value = 0;
@@ -73,6 +76,7 @@
cras_system_set_capture_mute_called = 0;
cras_system_set_capture_mute_locked_value = 0;
cras_system_set_capture_mute_locked_called = 0;
+ cras_system_state_dump_snapshots_called = 0;
cras_make_fd_nonblocking_called = 0;
iodev_get_thread_return = reinterpret_cast<audio_thread*>(0xad);
stream_list_add_stream_return = 0;
@@ -139,6 +143,7 @@
stream_id_ = 0x10002;
connect_msg_.header.id = CRAS_SERVER_CONNECT_STREAM;
connect_msg_.header.length = sizeof(connect_msg_);
+ connect_msg_.proto_version = CRAS_PROTO_VER;
connect_msg_.stream_type = CRAS_STREAM_TYPE_DEFAULT;
connect_msg_.direction = CRAS_STREAM_OUTPUT;
connect_msg_.stream_id = stream_id_;
@@ -187,6 +192,7 @@
EXPECT_EQ(0, cras_iodev_list_rm_output_called);
EXPECT_EQ(1, stream_list_add_stream_called);
EXPECT_EQ(0, stream_list_disconnect_stream_called);
+ EXPECT_EQ(0, cras_server_metrics_stream_config_called);
}
TEST_F(RClientMessagesSuite, ConnectMsgWithBadFd) {
@@ -202,6 +208,30 @@
EXPECT_NE(0, out_msg.err);
EXPECT_EQ(stream_list_add_stream_called,
stream_list_disconnect_stream_called);
+ EXPECT_EQ(0, cras_server_metrics_stream_config_called);
+}
+
+TEST_F(RClientMessagesSuite, ConnectMsgFromOldClient) {
+ struct cras_client_stream_connected_old out_msg;
+ int rc;
+
+ cras_rstream_create_stream_out = rstream_;
+ cras_iodev_attach_stream_retval = 0;
+
+ connect_msg_.header.length = sizeof(struct cras_connect_message_old);
+ connect_msg_.proto_version = CRAS_PROTO_VER - 1;
+
+ rc = cras_rclient_message_from_client(rclient_, &connect_msg_.header, 100);
+ EXPECT_EQ(0, rc);
+ EXPECT_EQ(1, cras_make_fd_nonblocking_called);
+
+ rc = read(pipe_fds_[0], &out_msg, sizeof(out_msg));
+ EXPECT_EQ(sizeof(out_msg), rc);
+ EXPECT_EQ(stream_id_, out_msg.stream_id);
+ EXPECT_EQ(0, out_msg.err);
+ EXPECT_EQ(1, stream_list_add_stream_called);
+ EXPECT_EQ(0, stream_list_disconnect_stream_called);
+ EXPECT_EQ(1, cras_server_metrics_stream_config_called);
}
TEST_F(RClientMessagesSuite, SuccessReply) {
@@ -221,6 +251,7 @@
EXPECT_EQ(0, out_msg.err);
EXPECT_EQ(1, stream_list_add_stream_called);
EXPECT_EQ(0, stream_list_disconnect_stream_called);
+ EXPECT_EQ(1, cras_server_metrics_stream_config_called);
}
TEST_F(RClientMessagesSuite, SuccessCreateThreadReply) {
@@ -240,6 +271,7 @@
EXPECT_EQ(0, out_msg.err);
EXPECT_EQ(1, stream_list_add_stream_called);
EXPECT_EQ(0, stream_list_disconnect_stream_called);
+ EXPECT_EQ(1, cras_server_metrics_stream_config_called);
}
TEST_F(RClientMessagesSuite, SetVolume) {
@@ -324,6 +356,15 @@
EXPECT_EQ(1, cras_system_set_capture_mute_locked_value);
}
+TEST_F(RClientMessagesSuite, DumpSnapshots) {
+ struct cras_dump_snapshots msg;
+ int rc;
+ cras_fill_dump_snapshots(&msg);
+ rc = cras_rclient_message_from_client(rclient_, &msg.header, -1);
+ EXPECT_EQ(0, rc);
+ EXPECT_EQ(1, cras_system_state_dump_snapshots_called);
+}
+
void RClientMessagesSuite::RegisterNotification(
enum CRAS_CLIENT_MESSAGE_ID msg_id,
void *callback, void **ops_address) {
@@ -361,8 +402,9 @@
}
if (!msg.do_register)
cras_observer_num_ops_registered--;
- if (cras_observer_num_ops_registered)
+ if (cras_observer_num_ops_registered) {
EXPECT_EQ(callback, *ops_address);
+ }
}
TEST_F(RClientMessagesSuite, RegisterStatusNotification) {
@@ -548,7 +590,7 @@
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);
+ EXPECT_EQ((uint64_t)msg->node_id, node_id);
}
TEST_F(RClientMessagesSuite, SendOutputNodeVolumeChanged) {
@@ -676,6 +718,20 @@
return 0;
}
+int audio_thread_set_aec_dump(struct audio_thread *thread,
+ cras_stream_id_t stream_id,
+ unsigned int start,
+ int fd)
+{
+ return 0;
+}
+
+#ifdef HAVE_WEBRTC_APM
+void cras_apm_list_reload_aec_config()
+{
+}
+#endif
+
const char *cras_config_get_socket_file_dir()
{
return CRAS_UT_TMPDIR;
@@ -688,6 +744,11 @@
return cras_rstream_create_return;
}
+unsigned int cras_rstream_get_effects(const struct cras_rstream *stream)
+{
+ return 0;
+}
+
int cras_iodev_move_stream_type(uint32_t type, uint32_t index)
{
return 0;
@@ -713,6 +774,11 @@
return 0;
}
+void cras_system_state_dump_snapshots()
+{
+ cras_system_state_dump_snapshots_called ++;
+}
+
void cras_system_set_volume(size_t volume)
{
cras_system_set_volume_value = volume;
@@ -894,4 +960,10 @@
cras_observer_remove_called++;
}
+int cras_server_metrics_stream_config(struct cras_rstream_config *config)
+{
+ cras_server_metrics_stream_config_called++;
+ return 0;
+}
+
} // extern "C"
diff --git a/cras/src/tests/rstream_stub.cc b/cras/src/tests/rstream_stub.cc
new file mode 100644
index 0000000..d0b7a0a
--- /dev/null
+++ b/cras/src/tests/rstream_stub.cc
@@ -0,0 +1,130 @@
+/* Copyright 2017 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <unordered_map>
+
+extern "C" {
+#include "cras_rstream.h"
+}
+
+namespace {
+
+struct cb_data {
+ std::unordered_map<unsigned int, unsigned int> dev_offset;
+ int pending_reply;
+};
+std::unordered_map<const cras_rstream*, cb_data> data_map;
+
+};
+
+void rstream_stub_reset() {
+ data_map.clear();
+}
+
+void rstream_stub_dev_offset(const cras_rstream* rstream,
+ unsigned int dev_id,
+ unsigned int offset) {
+ auto data = data_map.find(rstream);
+ if (data == data_map.end()) {
+ cb_data new_data;
+ new_data.dev_offset[dev_id] = offset;
+ data_map.insert({rstream, new_data});
+ } else {
+ data->second.dev_offset[dev_id] = offset;
+ }
+}
+
+void rstream_stub_pending_reply(const cras_rstream* rstream,
+ int ret_value) {
+ auto data = data_map.find(rstream);
+ if (data == data_map.end()) {
+ cb_data new_data;
+ new_data.pending_reply = ret_value;
+ data_map.insert({rstream, new_data});
+ } else {
+ data->second.pending_reply = ret_value;
+ }
+}
+
+extern "C" {
+
+void cras_rstream_record_fetch_interval(struct cras_rstream *rstream,
+ const struct timespec *now) {
+}
+
+void cras_rstream_dev_attach(struct cras_rstream *rstream,
+ unsigned int dev_id,
+ void *dev_ptr) {
+}
+
+void cras_rstream_dev_detach(struct cras_rstream *rstream, unsigned int dev_id) {
+}
+
+unsigned int cras_rstream_dev_offset(const struct cras_rstream *rstream,
+ unsigned int dev_id) {
+ auto elem = data_map.find(rstream);
+ if (elem != data_map.end())
+ return elem->second.dev_offset[dev_id];
+ return 0;
+}
+
+void cras_rstream_dev_offset_update(struct cras_rstream *rstream,
+ unsigned int frames,
+ unsigned int dev_id) {
+}
+
+unsigned int cras_rstream_playable_frames(struct cras_rstream *rstream,
+ unsigned int dev_id) {
+ return 0;
+}
+
+float cras_rstream_get_volume_scaler(struct cras_rstream *rstream) {
+ return 0.0;
+}
+
+int cras_rstream_get_mute(const struct cras_rstream *rstream) {
+ return 0;
+}
+
+uint8_t *cras_rstream_get_readable_frames(struct cras_rstream *rstream,
+ unsigned int offset,
+ size_t *frames) {
+ return NULL;
+}
+
+void cras_rstream_update_input_write_pointer(struct cras_rstream *rstream) {
+}
+
+void cras_rstream_update_output_read_pointer(struct cras_rstream *rstream) {
+}
+
+int cras_rstream_audio_ready(struct cras_rstream *stream, size_t count) {
+ cras_shm_buffer_write_complete(&stream->shm);
+ return 0;
+}
+
+int cras_rstream_request_audio(struct cras_rstream *stream,
+ const struct timespec *now) {
+ return 0;
+}
+
+void cras_rstream_update_queued_frames(struct cras_rstream *rstream)
+{
+}
+
+int cras_rstream_is_pending_reply(const struct cras_rstream *rstream)
+{
+ auto elem = data_map.find(rstream);
+ if (elem != data_map.end())
+ return elem->second.pending_reply;
+ return 0;
+}
+
+int cras_rstream_flush_old_audio_messages(struct cras_rstream *rstream)
+{
+ return 0;
+}
+
+} // extern "C"
diff --git a/cras/src/tests/rstream_stub.h b/cras/src/tests/rstream_stub.h
new file mode 100644
index 0000000..39a48b2
--- /dev/null
+++ b/cras/src/tests/rstream_stub.h
@@ -0,0 +1,21 @@
+/* Copyright 2017 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef RSTREAM_STUB_H_
+#define RSTREAM_STUB_H_
+
+#include <time.h>
+
+void rstream_stub_reset();
+
+void rstream_stub_dev_offset(const cras_rstream* rstream,
+ unsigned int dev_id,
+ unsigned int offset);
+
+// Stub that rstream is pending the reply from client or not.
+void rstream_stub_pending_reply(const cras_rstream* rstream,
+ int ret_value);
+
+#endif // RSTREAM_STUB_H_
diff --git a/cras/src/tests/rstream_unittest.cc b/cras/src/tests/rstream_unittest.cc
index 8034bfb..2261d47 100644
--- a/cras/src/tests/rstream_unittest.cc
+++ b/cras/src/tests/rstream_unittest.cc
@@ -5,6 +5,7 @@
#include <fcntl.h>
#include <stdio.h>
#include <sys/mman.h>
+#include <sys/socket.h>
#include <sys/types.h>
#include <gtest/gtest.h>
@@ -20,6 +21,9 @@
class RstreamTestSuite : public testing::Test {
protected:
virtual void SetUp() {
+ int rc;
+ int sock[2] = {-1, -1};
+
fmt_.format = SND_PCM_FORMAT_S16_LE;
fmt_.frame_rate = 48000;
fmt_.num_channels = 2;
@@ -32,18 +36,44 @@
config_.format = &fmt_;
config_.buffer_frames = 4096;
config_.cb_threshold = 2048;
- config_.audio_fd = 1;
+
+ // Create a socket pair because it will be used in rstream.
+ rc = socketpair(AF_UNIX, SOCK_STREAM, 0, sock);
+ ASSERT_EQ(0, rc);
+ config_.audio_fd = sock[1];
+ client_fd_ = sock[0];
+
config_.client = NULL;
}
+ virtual void TearDown() {
+ close(config_.audio_fd);
+ close(client_fd_);
+ }
+
static bool format_equal(cras_audio_format *fmt1, cras_audio_format *fmt2) {
return fmt1->format == fmt2->format &&
fmt1->frame_rate == fmt2->frame_rate &&
fmt1->num_channels == fmt2->num_channels;
}
+ void stub_client_reply(enum CRAS_AUDIO_MESSAGE_ID id, int frames, int err) {
+ int rc;
+ struct audio_message aud_msg;
+ // Create a message.
+ aud_msg.id = id;
+ aud_msg.frames = frames;
+ aud_msg.error = err;
+
+ // Use socket fd to stub message from client.
+ rc = write(client_fd_, &aud_msg, sizeof(aud_msg));
+ EXPECT_EQ(sizeof(aud_msg), rc);
+ return;
+ }
+
struct cras_audio_format fmt_;
struct cras_rstream_config config_;
+ int client_fd_;
};
TEST_F(RstreamTestSuite, InvalidDirection) {
@@ -193,6 +223,120 @@
cras_rstream_destroy(s);
}
+TEST_F(RstreamTestSuite, OutputStreamIsPendingReply) {
+ struct cras_rstream *s;
+ int rc;
+ struct timespec ts;
+
+ rc = cras_rstream_create(&config_, &s);
+ EXPECT_EQ(0, rc);
+
+ // Not pending reply.
+ rc = cras_rstream_is_pending_reply(s);
+ EXPECT_EQ(0, rc);
+
+ // Request some data from client.
+ rc = cras_rstream_request_audio(s, &ts);
+ EXPECT_GT(rc, 0);
+
+ // Pending reply.
+ rc = cras_rstream_is_pending_reply(s);
+ EXPECT_EQ(1, rc);
+
+ cras_rstream_destroy(s);
+}
+
+TEST_F(RstreamTestSuite, OutputStreamFlushMessages) {
+ struct cras_rstream *s;
+ int rc;
+ struct timespec ts;
+
+ rc = cras_rstream_create(&config_, &s);
+ EXPECT_EQ(0, rc);
+
+ // Not pending reply.
+ rc = cras_rstream_is_pending_reply(s);
+ EXPECT_EQ(0, rc);
+
+ // Request some data from client.
+ rc = cras_rstream_request_audio(s, &ts);
+ EXPECT_GT(rc, 0);
+
+ // Pending reply.
+ rc = cras_rstream_is_pending_reply(s);
+ EXPECT_EQ(1, rc);
+
+ // Client replies that data is ready.
+ stub_client_reply(AUDIO_MESSAGE_DATA_READY, 10, 0);
+
+ // Read messages.
+ cras_rstream_flush_old_audio_messages(s);
+
+ // NOT Pending reply.
+ rc = cras_rstream_is_pending_reply(s);
+ EXPECT_EQ(0, rc);
+
+ cras_rstream_destroy(s);
+}
+
+TEST_F(RstreamTestSuite, InputStreamIsPendingReply) {
+ struct cras_rstream *s;
+ int rc;
+
+ config_.direction = CRAS_STREAM_INPUT;
+
+ rc = cras_rstream_create(&config_, &s);
+ EXPECT_EQ(0, rc);
+
+ // Not pending reply.
+ rc = cras_rstream_is_pending_reply(s);
+ EXPECT_EQ(0, rc);
+
+ // Some data is ready. Sends it to client.
+ rc = cras_rstream_audio_ready(s, 10);
+ EXPECT_GT(rc, 0);
+
+ // Pending reply.
+ rc = cras_rstream_is_pending_reply(s);
+ EXPECT_EQ(1, rc);
+
+ cras_rstream_destroy(s);
+}
+
+TEST_F(RstreamTestSuite, InputStreamFlushMessages) {
+ struct cras_rstream *s;
+ int rc;
+
+ config_.direction = CRAS_STREAM_INPUT;
+
+ rc = cras_rstream_create(&config_, &s);
+ EXPECT_EQ(0, rc);
+
+ // Not pending reply.
+ rc = cras_rstream_is_pending_reply(s);
+ EXPECT_EQ(0, rc);
+
+ // Some data is ready. Sends it to client.
+ rc = cras_rstream_audio_ready(s, 10);
+ EXPECT_GT(rc, 0);
+
+ // Pending reply.
+ rc = cras_rstream_is_pending_reply(s);
+ EXPECT_EQ(1, rc);
+
+ // Client replies that data is captured.
+ stub_client_reply(AUDIO_MESSAGE_DATA_CAPTURED, 10, 0);
+
+ // Read messages.
+ cras_rstream_flush_old_audio_messages(s);
+
+ // NOT Pending reply.
+ rc = cras_rstream_is_pending_reply(s);
+ EXPECT_EQ(0, rc);
+
+ cras_rstream_destroy(s);
+}
+
} // namespace
int main(int argc, char **argv) {
@@ -249,5 +393,28 @@
void cras_system_state_stream_removed(enum CRAS_STREAM_DIRECTION direction) {
}
-
+#ifdef HAVE_WEBRTC_APM
+struct cras_apm_list *cras_apm_list_create(void *stream_ptr,
+ uint64_t effects)
+{
+ return NULL;
+}
+int cras_apm_list_destroy(struct cras_apm_list *list)
+{
+ return 0;
+}
+uint64_t cras_apm_list_get_effects(struct cras_apm_list *list)
+{
+ return APM_ECHO_CANCELLATION;
+}
+struct cras_apm *cras_apm_list_get(struct cras_apm_list *list,
+ void *dev_ptr)
+{
+ return NULL;
+}
+struct cras_audio_format *cras_apm_list_get_format(struct cras_apm *apm)
+{
+ return NULL;
+}
+#endif
}
diff --git a/cras/src/tests/server_metrics_unittest.cc b/cras/src/tests/server_metrics_unittest.cc
index db1b69f..c7d2339 100644
--- a/cras/src/tests/server_metrics_unittest.cc
+++ b/cras/src/tests/server_metrics_unittest.cc
@@ -8,6 +8,7 @@
extern "C" {
#include "cras_server_metrics.c"
#include "cras_main_message.h"
+#include "cras_rstream.h"
}
static enum CRAS_MAIN_MESSAGE_TYPE type_set;
@@ -27,7 +28,33 @@
EXPECT_EQ(type_set, CRAS_MAIN_METRICS);
}
-TEST(ServerMetricsTestSuite, SetMetrics) {
+TEST(ServerMetricsTestSuite, SetMetricHighestHardwareLevel) {
+ ResetStubData();
+ unsigned int hw_level = 1000;
+ sent_msg = (struct cras_server_metrics_message *)calloc(1, sizeof(*sent_msg));
+
+ cras_server_metrics_highest_hw_level(hw_level, CRAS_STREAM_INPUT);
+
+ EXPECT_EQ(sent_msg->header.type, CRAS_MAIN_METRICS);
+ EXPECT_EQ(sent_msg->header.length, sizeof(*sent_msg));
+ EXPECT_EQ(sent_msg->metrics_type, HIGHEST_INPUT_HW_LEVEL);
+ EXPECT_EQ(sent_msg->data.value, hw_level);
+
+ free(sent_msg);
+
+ sent_msg = (struct cras_server_metrics_message *)calloc(1, sizeof(*sent_msg));
+
+ cras_server_metrics_highest_hw_level(hw_level, CRAS_STREAM_OUTPUT);
+
+ EXPECT_EQ(sent_msg->header.type, CRAS_MAIN_METRICS);
+ EXPECT_EQ(sent_msg->header.length, sizeof(*sent_msg));
+ EXPECT_EQ(sent_msg->metrics_type, HIGHEST_OUTPUT_HW_LEVEL);
+ EXPECT_EQ(sent_msg->data.value, hw_level);
+
+ free(sent_msg);
+}
+
+TEST(ServerMetricsTestSuite, SetMetricsLongestFetchDelay) {
ResetStubData();
unsigned int delay = 100;
sent_msg = (struct cras_server_metrics_message *)calloc(1, sizeof(*sent_msg));
@@ -37,7 +64,47 @@
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);
+ EXPECT_EQ(sent_msg->data.value, delay);
+
+ free(sent_msg);
+}
+
+TEST(ServerMetricsTestSuite, SetMetricsNumUnderruns) {
+ ResetStubData();
+ unsigned int underrun = 10;
+ sent_msg = (struct cras_server_metrics_message *)calloc(1, sizeof(*sent_msg));
+
+ cras_server_metrics_num_underruns(underrun);
+
+ EXPECT_EQ(sent_msg->header.type, CRAS_MAIN_METRICS);
+ EXPECT_EQ(sent_msg->header.length, sizeof(*sent_msg));
+ EXPECT_EQ(sent_msg->metrics_type, NUM_UNDERRUNS);
+ EXPECT_EQ(sent_msg->data.value, underrun);
+
+ free(sent_msg);
+}
+
+TEST(ServerMetricsTestSuite, SetMetricsStreamConfig) {
+ ResetStubData();
+ struct cras_rstream_config config;
+ struct cras_audio_format format;
+ sent_msg = (struct cras_server_metrics_message *)calloc(1, sizeof(*sent_msg));
+
+ config.cb_threshold = 1024;
+ config.flags = BULK_AUDIO_OK;
+ format.format = SND_PCM_FORMAT_S16_LE;
+ format.frame_rate = 48000;
+
+ config.format = &format;
+ cras_server_metrics_stream_config(&config);
+
+ EXPECT_EQ(sent_msg->header.type, CRAS_MAIN_METRICS);
+ EXPECT_EQ(sent_msg->header.length, sizeof(*sent_msg));
+ EXPECT_EQ(sent_msg->metrics_type, STREAM_CONFIG);
+ EXPECT_EQ(sent_msg->data.stream_config.cb_threshold, 1024);
+ EXPECT_EQ(sent_msg->data.stream_config.flags, BULK_AUDIO_OK);
+ EXPECT_EQ(sent_msg->data.stream_config.format, SND_PCM_FORMAT_S16_LE);
+ EXPECT_EQ(sent_msg->data.stream_config.rate, 48000);
free(sent_msg);
}
@@ -55,6 +122,10 @@
int max, int nbuckets) {
}
+void cras_metrics_log_sparse_histogram(const char *name, int sample)
+{
+}
+
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));
diff --git a/cras/src/tests/shm_unittest.cc b/cras/src/tests/shm_unittest.cc
index c0bec89..9c36432 100644
--- a/cras/src/tests/shm_unittest.cc
+++ b/cras/src/tests/shm_unittest.cc
@@ -251,6 +251,32 @@
EXPECT_EQ(1, cras_shm_num_overruns(&shm_));
}
+TEST_F(ShmTestSuite, GetWritableFramesNeedToWrite) {
+ unsigned buffer_size = 480;
+ unsigned limit = 480;
+ unsigned written = 200;
+ unsigned frames;
+ shm_.area->write_buf_idx = 0;
+ shm_.config.used_size = buffer_size * shm_.config.frame_bytes;
+ shm_.area->write_offset[0] = written * shm_.config.frame_bytes;
+ buf_ = cras_shm_get_writeable_frames(&shm_, limit, &frames);
+ EXPECT_EQ(limit - written, frames);
+ EXPECT_EQ(shm_.area->samples + shm_.area->write_offset[0], buf_);
+}
+
+TEST_F(ShmTestSuite, GetWritableFramesNoNeedToWrite) {
+ unsigned buffer_size = 480;
+ unsigned limit = 240;
+ unsigned written = 300;
+ unsigned frames;
+ shm_.area->write_buf_idx = 0;
+ shm_.config.used_size = buffer_size * shm_.config.frame_bytes;
+ shm_.area->write_offset[0] = written * shm_.config.frame_bytes;
+ buf_ = cras_shm_get_writeable_frames(&shm_, limit, &frames);
+ EXPECT_EQ(0, frames);
+ EXPECT_EQ(shm_.area->samples + shm_.area->write_offset[0], buf_);
+}
+
} // namespace
int main(int argc, char **argv) {
diff --git a/cras/src/tests/system_state_unittest.cc b/cras/src/tests/system_state_unittest.cc
index 5816b77..d71d5f5 100644
--- a/cras/src/tests/system_state_unittest.cc
+++ b/cras/src/tests/system_state_unittest.cc
@@ -7,6 +7,7 @@
extern "C" {
#include "cras_alert.h"
+#include "cras_shm.h"
#include "cras_system_state.h"
#include "cras_types.h"
}
@@ -17,8 +18,10 @@
size_t cras_alsa_card_destroy_called;
static size_t add_stub_called;
static size_t rm_stub_called;
+static size_t add_task_stub_called;
static size_t callback_stub_called;
static void *select_data_value;
+static void *task_data_value;
static size_t add_callback_called;
static cras_alert_cb add_callback_cb;
static void *add_callback_arg;
@@ -41,11 +44,12 @@
kFakeAlsaCard = reinterpret_cast<struct cras_alsa_card*>(0x33);
add_stub_called = 0;
rm_stub_called = 0;
+ add_task_stub_called = 0;
callback_stub_called = 0;
add_callback_called = 0;
rm_callback_called = 0;
alert_pending_called = 0;
- device_config_dir = reinterpret_cast<char *>(3);
+ device_config_dir = NULL;
cras_alsa_card_config_dir = NULL;
cras_observer_notify_output_volume_called = 0;
cras_observer_notify_output_mute_called = 0;
@@ -67,12 +71,42 @@
select_data_value = select_data;
}
+static int add_task_stub(void (*cb)(void *data),
+ void *callback_data,
+ void *task_data)
+{
+ add_task_stub_called++;
+ task_data_value = task_data;
+ return 0;
+}
+
static void callback_stub(void *data) {
callback_stub_called++;
}
+static void do_sys_init() {
+ char *shm_name;
+ ASSERT_GT(asprintf(&shm_name, "/cras-%d", getpid()), 0);
+ int rw_shm_fd;
+ int ro_shm_fd;
+ struct cras_server_state *exp_state = (struct cras_server_state *)
+ cras_shm_setup(shm_name,
+ sizeof(*exp_state),
+ &rw_shm_fd,
+ &ro_shm_fd);
+ if (!exp_state)
+ exit(-1);
+ cras_system_state_init(device_config_dir,
+ shm_name,
+ rw_shm_fd,
+ ro_shm_fd,
+ exp_state,
+ sizeof(*exp_state));
+ free(shm_name);
+}
+
TEST(SystemStateSuite, DefaultVolume) {
- cras_system_state_init(device_config_dir);
+ do_sys_init();
EXPECT_EQ(100, cras_system_get_volume());
EXPECT_EQ(2000, cras_system_get_capture_gain());
EXPECT_EQ(0, cras_system_get_mute());
@@ -81,7 +115,7 @@
}
TEST(SystemStateSuite, SetVolume) {
- cras_system_state_init(device_config_dir);
+ do_sys_init();
cras_system_set_volume(0);
EXPECT_EQ(0, cras_system_get_volume());
cras_system_set_volume(50);
@@ -95,7 +129,7 @@
}
TEST(SystemStateSuite, SetMinMaxVolume) {
- cras_system_state_init(device_config_dir);
+ do_sys_init();
cras_system_set_volume_limits(-10000, -600);
EXPECT_EQ(-10000, cras_system_get_min_volume());
EXPECT_EQ(-600, cras_system_get_max_volume());
@@ -103,7 +137,7 @@
}
TEST(SystemStateSuite, SetCaptureVolume) {
- cras_system_state_init(device_config_dir);
+ do_sys_init();
cras_system_set_capture_gain(0);
EXPECT_EQ(0, cras_system_get_capture_gain());
cras_system_set_capture_gain(3000);
@@ -116,7 +150,7 @@
}
TEST(SystemStateSuite, SetCaptureVolumeStoreTarget) {
- cras_system_state_init(device_config_dir);
+ do_sys_init();
cras_system_set_capture_gain_limits(-2000, 2000);
cras_system_set_capture_gain(3000);
// Gain is within the limit.
@@ -132,7 +166,7 @@
}
TEST(SystemStateSuite, SetMinMaxCaptureGain) {
- cras_system_state_init(device_config_dir);
+ do_sys_init();
cras_system_set_capture_gain(3000);
cras_system_set_capture_gain_limits(-2000, 2000);
EXPECT_EQ(-2000, cras_system_get_min_capture_gain());
@@ -144,7 +178,7 @@
TEST(SystemStateSuite, SetUserMute) {
ResetStubData();
- cras_system_state_init(device_config_dir);
+ do_sys_init();
EXPECT_EQ(0, cras_system_get_mute());
@@ -165,7 +199,7 @@
TEST(SystemStateSuite, SetMute) {
ResetStubData();
- cras_system_state_init(device_config_dir);
+ do_sys_init();
EXPECT_EQ(0, cras_system_get_mute());
@@ -184,8 +218,54 @@
cras_system_state_deinit();
}
+TEST(SystemStateSuite, SetSystemMuteThenSwitchUserMute) {
+ ResetStubData();
+ do_sys_init();
+
+ EXPECT_EQ(0, cras_system_get_mute());
+
+ // Set system mute.
+ cras_system_set_mute(1);
+
+ // Switching user mute will not notify observer.
+ EXPECT_EQ(1, cras_observer_notify_output_mute_called);
+ cras_system_set_user_mute(1);
+ EXPECT_EQ(1, cras_observer_notify_output_mute_called);
+ cras_system_set_user_mute(0);
+ EXPECT_EQ(1, cras_observer_notify_output_mute_called);
+
+ // Unset system mute.
+ cras_system_set_mute(0);
+ EXPECT_EQ(2, cras_observer_notify_output_mute_called);
+
+ cras_system_state_deinit();
+}
+
+TEST(SystemStateSuite, SetUserMuteThenSwitchSystemMute) {
+ ResetStubData();
+ do_sys_init();
+
+ EXPECT_EQ(0, cras_system_get_mute());
+
+ // Set user mute.
+ cras_system_set_user_mute(1);
+
+ // Switching system mute will not notify observer.
+ EXPECT_EQ(1, cras_observer_notify_output_mute_called);
+ cras_system_set_mute(1);
+ EXPECT_EQ(1, cras_observer_notify_output_mute_called);
+ cras_system_set_mute(0);
+ EXPECT_EQ(1, cras_observer_notify_output_mute_called);
+
+ // Unset user mute.
+ cras_system_set_user_mute(0);
+ EXPECT_EQ(2, cras_observer_notify_output_mute_called);
+
+ cras_system_state_deinit();
+}
+
TEST(SystemStateSuite, CaptureMuteChangedCallbackMultiple) {
- cras_system_state_init(device_config_dir);
+ do_sys_init();
ResetStubData();
cras_system_set_capture_mute(1);
@@ -199,7 +279,7 @@
}
TEST(SystemStateSuite, MuteLocked) {
- cras_system_state_init(device_config_dir);
+ do_sys_init();
ResetStubData();
cras_system_set_mute(1);
@@ -211,7 +291,7 @@
cras_system_set_mute(0);
EXPECT_EQ(1, cras_system_get_mute());
EXPECT_EQ(1, cras_system_get_mute_locked());
- EXPECT_EQ(2, cras_observer_notify_output_mute_called);
+ EXPECT_EQ(1, cras_observer_notify_output_mute_called);
cras_system_set_capture_mute(1);
EXPECT_EQ(1, cras_system_get_capture_mute());
@@ -227,7 +307,7 @@
}
TEST(SystemStateSuite, Suspend) {
- cras_system_state_init(device_config_dir);
+ do_sys_init();
ResetStubData();
cras_system_set_suspended(1);
@@ -248,7 +328,7 @@
info.card_type = ALSA_CARD_TYPE_INTERNAL;
info.card_index = 0;
- cras_system_state_init(device_config_dir);
+ do_sys_init();
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);
@@ -261,7 +341,7 @@
info.card_type = ALSA_CARD_TYPE_INTERNAL;
info.card_index = 0;
- cras_system_state_init(device_config_dir);
+ do_sys_init();
EXPECT_EQ(0, cras_system_add_alsa_card(&info));
EXPECT_EQ(1, cras_alsa_card_create_called);
EXPECT_EQ(cras_alsa_card_config_dir, device_config_dir);
@@ -281,7 +361,7 @@
int rc;
ResetStubData();
- cras_system_state_init(device_config_dir);
+ do_sys_init();
rc = cras_system_add_select_fd(7, callback_stub, stub_data);
EXPECT_NE(0, rc);
EXPECT_EQ(0, add_stub_called);
@@ -305,9 +385,29 @@
cras_system_state_deinit();
}
+TEST(SystemSettingsAddTask, AddTask) {
+ void *stub_data = reinterpret_cast<void *>(44);
+ void *task_data = reinterpret_cast<void *>(33);
+ int rc;
+
+ do_sys_init();
+ rc = cras_system_add_task(callback_stub, stub_data);
+ EXPECT_NE(0, rc);
+ EXPECT_EQ(0, add_task_stub_called);
+ rc = cras_system_set_add_task_handler(add_task_stub, task_data);
+ EXPECT_EQ(0, rc);
+ EXPECT_EQ(0, add_task_stub_called);
+ rc = cras_system_add_task(callback_stub, stub_data);
+ EXPECT_EQ(0, rc);
+ EXPECT_EQ(1, add_task_stub_called);
+ EXPECT_EQ(task_data, task_data_value);
+
+ cras_system_state_deinit();
+}
+
TEST(SystemSettingsStreamCount, StreamCount) {
ResetStubData();
- cras_system_state_init(device_config_dir);
+ do_sys_init();
EXPECT_EQ(0, cras_system_state_get_active_streams());
cras_system_state_stream_added(CRAS_STREAM_OUTPUT);
@@ -324,7 +424,7 @@
TEST(SystemSettingsStreamCount, StreamCountByDirection) {
ResetStubData();
- cras_system_state_init(device_config_dir);
+ do_sys_init();
EXPECT_EQ(0, cras_system_state_get_active_streams());
cras_system_state_stream_added(CRAS_STREAM_OUTPUT);
diff --git a/cras/src/tests/timing_unittest.cc b/cras/src/tests/timing_unittest.cc
new file mode 100644
index 0000000..bae0fee
--- /dev/null
+++ b/cras/src/tests/timing_unittest.cc
@@ -0,0 +1,459 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+#include <stdint.h>
+#include <stdio.h>
+#include <time.h>
+
+#include <gtest/gtest.h>
+
+extern "C" {
+#include "dev_io.h" // tested
+#include "dev_stream.h" // tested
+#include "cras_rstream.h" // stubbed
+#include "cras_iodev.h" // stubbed
+#include "cras_shm.h"
+#include "cras_types.h"
+#include "utlist.h"
+
+struct audio_thread_event_log* atlog;
+}
+
+#include "dev_io_stubs.h"
+#include "iodev_stub.h"
+#include "rstream_stub.h"
+
+#define FAKE_POLL_FD 33
+
+namespace {
+
+class TimingSuite : public testing::Test{
+ protected:
+ virtual void SetUp() {
+ atlog = static_cast<audio_thread_event_log*>(calloc(1, sizeof(*atlog)));
+ iodev_stub_reset();
+ rstream_stub_reset();
+ }
+
+ virtual void TearDown() {
+ free(atlog);
+ }
+
+ timespec SingleInputDevNextWake(
+ size_t dev_cb_threshold,
+ size_t dev_level,
+ const timespec* level_timestamp,
+ cras_audio_format* dev_format,
+ const std::vector<StreamPtr>& streams,
+ CRAS_NODE_TYPE active_node_type = CRAS_NODE_TYPE_MIC) {
+ struct open_dev* dev_list_ = NULL;
+
+ DevicePtr dev = create_device(CRAS_STREAM_INPUT, dev_cb_threshold,
+ dev_format, active_node_type);
+ dev->dev->input_streaming = true;
+ DL_APPEND(dev_list_, dev->odev.get());
+
+ for (auto const& stream : streams) {
+ add_stream_to_dev(dev->dev, stream);
+ }
+
+ // Set response for frames_queued.
+ iodev_stub_frames_queued(dev->dev.get(), dev_level, *level_timestamp);
+
+ dev_io_send_captured_samples(dev_list_);
+
+ struct timespec dev_time;
+ dev_time.tv_sec = level_timestamp->tv_sec + 500; // Far in the future.
+ dev_io_next_input_wake(&dev_list_, &dev_time);
+ return dev_time;
+ }
+};
+
+// One device, one stream, write a callback of data and check the sleep time is
+// one more wakeup interval.
+TEST_F(TimingSuite, WaitAfterFill) {
+ const size_t cb_threshold = 480;
+
+ cras_audio_format format;
+ fill_audio_format(&format, 48000);
+
+ StreamPtr stream =
+ create_stream(1, 1, CRAS_STREAM_INPUT, cb_threshold, &format);
+ // rstream's next callback is now and there is enough data to fill.
+ struct timespec start;
+ clock_gettime(CLOCK_MONOTONIC_RAW, &start);
+ stream->rstream->next_cb_ts = start;
+ AddFakeDataToStream(stream.get(), 480);
+
+ std::vector<StreamPtr> streams;
+ streams.emplace_back(std::move(stream));
+ timespec dev_time = SingleInputDevNextWake(cb_threshold, 0, &start,
+ &format, streams);
+
+ // The next callback should be scheduled 10ms in the future.
+ // And the next wake up should reflect the only attached stream.
+ EXPECT_EQ(dev_time.tv_sec, streams[0]->rstream->next_cb_ts.tv_sec);
+ EXPECT_EQ(dev_time.tv_nsec, streams[0]->rstream->next_cb_ts.tv_nsec);
+}
+
+// One device with one stream which has block_size larger than the device buffer
+// level. If the device buffer level = 0, the input device wake time should be
+// set to (buffer_size / 2) / device_rate secs.
+TEST_F(TimingSuite, LargeCallbackStreamWithEmptyBuffer) {
+ const size_t cb_threshold = 3000;
+ const size_t dev_cb_threshold = 1200;
+ const size_t dev_level = 0;
+
+ cras_audio_format format;
+ fill_audio_format(&format, 48000);
+
+ StreamPtr stream =
+ create_stream(1, 1, CRAS_STREAM_INPUT, cb_threshold, &format);
+ struct timespec start;
+ clock_gettime(CLOCK_MONOTONIC_RAW, &start);
+ stream->rstream->next_cb_ts = start;
+
+ std::vector<StreamPtr> streams;
+ streams.emplace_back(std::move(stream));
+ timespec dev_time = SingleInputDevNextWake(
+ dev_cb_threshold, dev_level, &start, &format, streams);
+
+ struct timespec delta;
+ subtract_timespecs(&dev_time, &start, &delta);
+ // The next dev wake ts should be 25ms since the buffer level is empty and
+ // 1200 / 48000 = 0.025.
+ EXPECT_EQ(delta.tv_sec, 0);
+ EXPECT_LT(delta.tv_nsec, 25000000 + 5000 * 1000);
+ EXPECT_GT(delta.tv_nsec, 25000000 - 5000 * 1000);
+}
+
+// One device with one stream which has block_size larger than the device buffer
+// level. If the device buffer level = buffer_size / 2, the input device wake
+// time should be set to max(0, 5ms) = 5ms to prevent busy loop occurs.
+TEST_F(TimingSuite, LargeCallbackStreamWithHalfFullBuffer) {
+ const size_t cb_threshold = 3000;
+ const size_t dev_cb_threshold = 1200;
+ const size_t dev_level = 1200;
+
+ cras_audio_format format;
+ fill_audio_format(&format, 48000);
+
+ StreamPtr stream =
+ create_stream(1, 1, CRAS_STREAM_INPUT, cb_threshold, &format);
+ struct timespec start;
+ clock_gettime(CLOCK_MONOTONIC_RAW, &start);
+ stream->rstream->next_cb_ts = start;
+
+ std::vector<StreamPtr> streams;
+ streams.emplace_back(std::move(stream));
+ timespec dev_time = SingleInputDevNextWake(
+ dev_cb_threshold, dev_level, &start, &format, streams);
+
+ struct timespec delta;
+ subtract_timespecs(&dev_time, &start, &delta);
+ // The next dev wake ts should be 5ms since the buffer level is half full.
+ EXPECT_EQ(delta.tv_sec, 0);
+ EXPECT_LT(delta.tv_nsec, 5000000 + 5000 * 1000);
+ EXPECT_GT(delta.tv_nsec, 5000000 - 5000 * 1000);
+}
+
+// One device(48k), one stream(44.1k), write a callback of data and check that
+// the sleep time is correct when doing SRC.
+TEST_F(TimingSuite, WaitAfterFillSRC) {
+ cras_audio_format dev_format;
+ fill_audio_format(&dev_format, 48000);
+ cras_audio_format stream_format;
+ fill_audio_format(&stream_format, 44100);
+
+ StreamPtr stream =
+ create_stream(1, 1, CRAS_STREAM_INPUT, 441, &stream_format);
+ // rstream's next callback is now and there is enough data to fill.
+ struct timespec start;
+ clock_gettime(CLOCK_MONOTONIC_RAW, &start);
+ stream->rstream->next_cb_ts = start;
+ AddFakeDataToStream(stream.get(), 441);
+
+ std::vector<StreamPtr> streams;
+ streams.emplace_back(std::move(stream));
+ timespec dev_time = SingleInputDevNextWake(480, 0, &start,
+ &dev_format, streams);
+
+ // The next callback should be scheduled 10ms in the future.
+ struct timespec delta;
+ subtract_timespecs(&dev_time, &start, &delta);
+ EXPECT_LT(9900 * 1000, delta.tv_nsec);
+ EXPECT_GT(10100 * 1000, delta.tv_nsec);
+}
+
+// One device, two streams. One stream is ready the other still needs data.
+// Checks that the sleep interval is based on the time the device will take to
+// supply the needed samples for stream2.
+TEST_F(TimingSuite, WaitTwoStreamsSameFormat) {
+ const size_t cb_threshold = 480;
+
+ cras_audio_format format;
+ fill_audio_format(&format, 48000);
+
+ // stream1's next callback is now and there is enough data to fill.
+ StreamPtr stream1 =
+ create_stream(1, 1, CRAS_STREAM_INPUT, cb_threshold, &format);
+ struct timespec start;
+ clock_gettime(CLOCK_MONOTONIC_RAW, &start);
+ stream1->rstream->next_cb_ts = start;
+ AddFakeDataToStream(stream1.get(), cb_threshold);
+
+ // stream2 is only half full.
+ StreamPtr stream2 =
+ create_stream(1, 1, CRAS_STREAM_INPUT, cb_threshold, &format);
+ stream2->rstream->next_cb_ts = start;
+ AddFakeDataToStream(stream2.get(), 240);
+
+ std::vector<StreamPtr> streams;
+ streams.emplace_back(std::move(stream1));
+ streams.emplace_back(std::move(stream2));
+ timespec dev_time = SingleInputDevNextWake(cb_threshold, 0, &start,
+ &format, streams);
+
+ // Should wait for approximately 5 milliseconds for 240 samples at 48k.
+ struct timespec delta2;
+ subtract_timespecs(&dev_time, &start, &delta2);
+ EXPECT_LT(4900 * 1000, delta2.tv_nsec);
+ EXPECT_GT(5100 * 1000, delta2.tv_nsec);
+}
+
+// One device(44.1), two streams(44.1, 48). One stream is ready the other still
+// needs data. Checks that the sleep interval is based on the time the device
+// will take to supply the needed samples for stream2, stream2 is sample rate
+// converted from the 44.1k device to the 48k stream.
+TEST_F(TimingSuite, WaitTwoStreamsDifferentRates) {
+ cras_audio_format s1_format, s2_format;
+ fill_audio_format(&s1_format, 44100);
+ fill_audio_format(&s2_format, 48000);
+
+ // stream1's next callback is now and there is enough data to fill.
+ StreamPtr stream1 =
+ create_stream(1, 1, CRAS_STREAM_INPUT, 441, &s1_format);
+ struct timespec start;
+ clock_gettime(CLOCK_MONOTONIC_RAW, &start);
+ stream1->rstream->next_cb_ts = start;
+ AddFakeDataToStream(stream1.get(), 441);
+ // stream2's next callback is now but there is only half a callback of data.
+ StreamPtr stream2 =
+ create_stream(1, 1, CRAS_STREAM_INPUT, 480, &s2_format);
+ stream2->rstream->next_cb_ts = start;
+ AddFakeDataToStream(stream2.get(), 240);
+
+ std::vector<StreamPtr> streams;
+ streams.emplace_back(std::move(stream1));
+ streams.emplace_back(std::move(stream2));
+ timespec dev_time = SingleInputDevNextWake(441, 0, &start,
+ &s1_format, streams);
+
+ // Should wait for approximately 5 milliseconds for 240 48k samples from the
+ // 44.1k device.
+ struct timespec delta2;
+ subtract_timespecs(&dev_time, &start, &delta2);
+ EXPECT_LT(4900 * 1000, delta2.tv_nsec);
+ EXPECT_GT(5100 * 1000, delta2.tv_nsec);
+}
+
+// One device, two streams. Both streams get a full callback of data and the
+// device has enough samples for the next callback already. Checks that the
+// shorter of the two streams times is used for the next sleep interval.
+TEST_F(TimingSuite, WaitTwoStreamsDifferentWakeupTimes) {
+ cras_audio_format s1_format, s2_format;
+ fill_audio_format(&s1_format, 44100);
+ fill_audio_format(&s2_format, 48000);
+
+ struct timespec start;
+ clock_gettime(CLOCK_MONOTONIC_RAW, &start);
+
+ // stream1's next callback is in 3ms.
+ StreamPtr stream1 =
+ create_stream(1, 1, CRAS_STREAM_INPUT, 441, &s1_format);
+ stream1->rstream->next_cb_ts = start;
+ const timespec three_millis = { 0, 3 * 1000 * 1000 };
+ add_timespecs(&stream1->rstream->next_cb_ts, &three_millis);
+ AddFakeDataToStream(stream1.get(), 441);
+ // stream2 is also ready next cb in 5ms..
+ StreamPtr stream2 =
+ create_stream(1, 1, CRAS_STREAM_INPUT, 480, &s2_format);
+ stream2->rstream->next_cb_ts = start;
+ const timespec five_millis = { 0, 5 * 1000 * 1000 };
+ add_timespecs(&stream2->rstream->next_cb_ts, &five_millis);
+ AddFakeDataToStream(stream1.get(), 480);
+
+ std::vector<StreamPtr> streams;
+ streams.emplace_back(std::move(stream1));
+ streams.emplace_back(std::move(stream2));
+ timespec dev_time = SingleInputDevNextWake(441, 441, &start,
+ &s1_format, streams);
+
+ // Should wait for approximately 3 milliseconds for stream 1 first.
+ struct timespec delta2;
+ subtract_timespecs(&dev_time, &start, &delta2);
+ EXPECT_LT(2900 * 1000, delta2.tv_nsec);
+ EXPECT_GT(3100 * 1000, delta2.tv_nsec);
+}
+
+// One hotword stream attaches to hotword device. Input data has copied from
+// device to stream but total number is less than cb_threshold. Hotword stream
+// should be scheduled wake base on the samples needed to fill full shm.
+TEST_F(TimingSuite, HotwordStreamUseDevTiming) {
+ cras_audio_format fmt;
+ fill_audio_format(&fmt, 48000);
+
+ struct timespec start, delay;
+ clock_gettime(CLOCK_MONOTONIC_RAW, &start);
+
+ StreamPtr stream =
+ create_stream(1, 1, CRAS_STREAM_INPUT, 240, &fmt);
+ stream->rstream->flags = HOTWORD_STREAM;
+ stream->rstream->next_cb_ts = start;
+ delay.tv_sec = 0;
+ delay.tv_nsec = 3 * 1000 * 1000;
+ add_timespecs(&stream->rstream->next_cb_ts, &delay);
+
+ // Add fake data to stream and device so its slightly less than cb_threshold.
+ // Expect to wait for samples to fill the full buffer (480 - 192) frames
+ // instead of using the next_cb_ts.
+ AddFakeDataToStream(stream.get(), 192);
+ std::vector<StreamPtr> streams;
+ streams.emplace_back(std::move(stream));
+ timespec dev_time = SingleInputDevNextWake(4096, 0, &start,
+ &fmt, streams);
+ struct timespec delta;
+ subtract_timespecs(&dev_time, &start, &delta);
+ // 288 frames worth of time = 6 ms.
+ EXPECT_EQ(6 * 1000 * 1000, delta.tv_nsec);
+}
+
+// One hotword stream attaches to hotword device. Input data burst to a number
+// larger than cb_threshold. Also, stream is pending client reply.
+// In this case stream fd is used to poll for next wake.
+// And the dev wake time is unchanged from the default 20 seconds limit.
+TEST_F(TimingSuite, HotwordStreamBulkDataIsPending) {
+ int poll_fd = 0;
+ cras_audio_format fmt;
+ fill_audio_format(&fmt, 48000);
+
+ struct timespec start;
+ clock_gettime(CLOCK_MONOTONIC_RAW, &start);
+
+ StreamPtr stream =
+ create_stream(1, 1, CRAS_STREAM_INPUT, 240, &fmt);
+ stream->rstream->flags = HOTWORD_STREAM;
+ stream->rstream->next_cb_ts = start;
+
+ AddFakeDataToStream(stream.get(), 480);
+ std::vector<StreamPtr> streams;
+ streams.emplace_back(std::move(stream));
+ // Stream is pending the reply from client.
+ rstream_stub_pending_reply(streams[0]->rstream.get(), 1);
+
+ // There is more than 1 cb_threshold of data in device.
+ timespec dev_time = SingleInputDevNextWake(
+ 4096, 7000, &start, &fmt, streams, CRAS_NODE_TYPE_HOTWORD);
+
+ // Need to wait for stream fd in the next ppoll.
+ poll_fd = dev_stream_poll_stream_fd(streams[0]->dstream.get());
+ EXPECT_EQ(FAKE_POLL_FD, poll_fd);
+
+ struct timespec delta;
+ subtract_timespecs(&dev_time, &start, &delta);
+ // Wake up time should be default 20 seconds because audio thread
+ // depends on reply from client to wake it up.
+ EXPECT_LT(19, delta.tv_sec);
+ EXPECT_GT(21, delta.tv_sec);
+}
+
+// One hotword stream attaches to hotword device. Input data burst to a number
+// larger than cb_threshold. However, stream is not pending client reply.
+// This happens if there was no data during capture_to_stream.
+// In this case stream fd is NOT used to poll for next wake.
+// And the dev wake time is changed to a 0 instead of default 20 seconds.
+TEST_F(TimingSuite, HotwordStreamBulkDataIsNotPending) {
+ int poll_fd = 0;
+ cras_audio_format fmt;
+ fill_audio_format(&fmt, 48000);
+
+ struct timespec start;
+ clock_gettime(CLOCK_MONOTONIC_RAW, &start);
+
+ StreamPtr stream =
+ create_stream(1, 1, CRAS_STREAM_INPUT, 240, &fmt);
+ stream->rstream->flags = HOTWORD_STREAM;
+ stream->rstream->next_cb_ts = start;
+
+ AddFakeDataToStream(stream.get(), 480);
+ std::vector<StreamPtr> streams;
+ streams.emplace_back(std::move(stream));
+ // Stream is not pending the reply from client.
+ rstream_stub_pending_reply(streams[0]->rstream.get(), 0);
+
+ // There is more than 1 cb_threshold of data in device.
+ timespec dev_time = SingleInputDevNextWake(4096, 7000, &start,
+ &fmt, streams);
+
+ // Does not need to wait for stream fd in the next ppoll.
+ poll_fd = dev_stream_poll_stream_fd(streams[0]->dstream.get());
+ EXPECT_EQ(-1, poll_fd);
+
+ struct timespec delta;
+ subtract_timespecs(&dev_time, &start, &delta);
+ // Wake up time should be very small because there is enough
+ // data to be send to client.
+ EXPECT_LT(delta.tv_sec, 0.1);
+}
+
+/* Stubs */
+extern "C" {
+
+int cras_server_metrics_highest_hw_level(unsigned hw_level,
+ enum CRAS_STREAM_DIRECTION direction)
+{
+ return 0;
+}
+
+int cras_server_metrics_longest_fetch_delay(unsigned delay_msec)
+{
+ return 0;
+}
+
+int cras_server_metrics_num_underruns(unsigned num_underruns)
+{
+ return 0;
+}
+
+int input_data_get_for_stream(
+ struct input_data *data,
+ struct cras_rstream *stream,
+ struct buffer_share *offsets,
+ struct cras_audio_area **area,
+ unsigned int *offset)
+{
+ return 0;
+}
+
+int input_data_put_for_stream(struct input_data *data,
+ struct cras_rstream *stream,
+ struct buffer_share *offsets,
+ unsigned int frames)
+{
+ return 0;
+}
+struct cras_audio_format *cras_rstream_post_processing_format(
+ const struct cras_rstream *stream, void *dev_ptr)
+{
+ return NULL;
+}
+} // extern "C"
+
+} // namespace
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/init/cras.conf b/init/cras.conf
index eb1e62c..e7df799 100644
--- a/init/cras.conf
+++ b/init/cras.conf
@@ -5,7 +5,7 @@
# Installed by ADHD package.
# cras upstart job.
-description "ChromeOS audio server"
+description "Chrome OS audio server"
author "chromium-os-dev@chromium.org"
env CRAS_SOCKET_DIR=/run/cras
diff --git a/init/cras.sh b/init/cras.sh
index 2284243..cebf3a9 100644
--- a/init/cras.sh
+++ b/init/cras.sh
@@ -2,24 +2,53 @@
# 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"
+# Unified build config.
+device_config_dir="$(cros_config --abspath /audio/main cras-config-dir)"
+internal_ucm_suffix="$(cros_config /audio/main ucm-suffix)"
+
+# Handle legacy config.
+if [ -z "${device_config_dir}" ]; then
+ # Disable HSP/HFP on Google WiFi (Gale) with UART-HCI Bluetooth
+ # which is incapable of handling SCO audio.
+ platform_name="$(mosys platform name)"
+ if [ "$platform_name" = "Gale" ]; then
+ DISABLE_PROFILE="--disable_profile=hfp,hsp"
+ fi
+ # For boards that need a different device config, 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)"
+ fi
+ if [ -f /etc/cras/get_internal_ucm_suffix ]; then
+ internal_ucm_suffix="$(sh /etc/cras/get_internal_ucm_suffix)"
+ 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
- device_config_dir="$(sh /etc/cras/get_device_config_dir)"
+
+if [ -n "${device_config_dir}" ]; then
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)"
+if [ -n "${internal_ucm_suffix}" ]; then
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}
+
+# Leave cras in the init pid namespace as it uses its PID as an IPC identifier.
+exec minijail0 -u cras -g cras -G -n --uts -v -l \
+ -P /var/empty \
+ -b /,/ \
+ -k 'tmpfs,/run,tmpfs,MS_NODEV|MS_NOEXEC|MS_NOSUID,mode=755,size=10M' \
+ -b /run/cras,/run/cras,1 \
+ -b /run/dbus,/run/dbus,1 \
+ -b /run/udev,/run/udev \
+ -b /dev,/dev \
+ -b /dev/shm,/dev/shm,1 \
+ -k proc,/proc,proc \
+ -b /sys,/sys \
+ -k 'tmpfs,/var,tmpfs,MS_NODEV|MS_NOEXEC|MS_NOSUID,mode=755,size=10M' \
+ -b /var/lib/metrics/,/var/lib/metrics/,1 \
+ -S /usr/share/policy/cras-seccomp.policy \
+ -- \
+ /usr/bin/cras \
+ ${DSP_CONFIG} ${DEVICE_CONFIG_DIR} ${DISABLE_PROFILE} \
+ ${INTERNAL_UCM_SUFFIX}
diff --git a/scripts/audio_diagnostics b/scripts/audio_diagnostics
index 5ec96a9..83a380c 100755
--- a/scripts/audio_diagnostics
+++ b/scripts/audio_diagnostics
@@ -22,6 +22,9 @@
echo '=== cras_test_client --dump_audio_thread ==='
cras_test_client --dump_audio_thread
+echo '=== cras_test_client --dump_events ==='
+cras_test_client --dump_events
+
echo '=== aplay -l ==='
aplay -l
echo '=== arecord -l ==='
diff --git a/scripts/audio_thread_log_viewer/viewer_c3.py b/scripts/audio_thread_log_viewer/viewer_c3.py
index a6b11be..2f23dd5 100755
--- a/scripts/audio_thread_log_viewer/viewer_c3.py
+++ b/scripts/audio_thread_log_viewer/viewer_c3.py
@@ -18,7 +18,7 @@
<!-- 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://d3js.org/d3.v4.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 {
diff --git a/seccomp/cras-seccomp-amd64.policy b/seccomp/cras-seccomp-amd64.policy
new file mode 100644
index 0000000..41b5d2a
--- /dev/null
+++ b/seccomp/cras-seccomp-amd64.policy
@@ -0,0 +1,84 @@
+# Copyright 2018 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.
+
+clock_gettime: 1
+getuid: 1
+lstat: 1
+read: 1
+recvfrom: 1
+poll: 1
+ppoll: 1
+fstat: 1
+write: 1
+open: 1
+openat: 1
+close: 1
+readlinkat: 1
+getrandom: 1
+access: 1
+fcntl: 1
+getdents: 1
+sendmsg: 1
+stat: 1
+statfs: 1
+recvmsg: 1
+brk: 1
+mmap: 1
+getsockopt: 1
+accept: 1
+munmap: 1
+sendto: 1
+readlink: 1
+futex: 1
+lseek: 1
+rt_sigaction: 1
+socket: arg0 == AF_UNIX || arg0 == AF_BLUETOOTH || arg0 == AF_NETLINK
+unlink: 1
+nanosleep: 1
+pipe: 1
+ftruncate: 1
+mprotect: 1
+connect: 1
+getsockname: 1
+setsockopt: 1
+bind: 1
+restart_syscall: 1
+exit: 1
+exit_group: 1
+rt_sigreturn: 1
+chmod: 1
+geteuid: 1
+uname: 1
+clock_getres: 1
+gettid: 1
+rt_sigprocmask: 1
+fchmod: 1
+getrlimit: 1
+setrlimit: 1
+listen: 1
+clone: 1
+set_robust_list: 1
+getresuid: 1
+getresgid: 1
+sched_setscheduler: 1
+epoll_wait: 1
+getegid: 1
+getgid: 1
+prctl: arg0 == PR_SET_NAME
+epoll_create1: 1
+sched_get_priority_min: 1
+pipe2: 1
+epoll_ctl: 1
+sched_get_priority_max: 1
+lgetxattr: 1
+lsetxattr: 1
+madvise: 1
+sysinfo: 1
+flock: 1
+
+# Allow ioctl command of type 'A' and 'U' for SNDRV_PCM_IOCTL_* and
+# SNDRV_CTL_IOCTL_*, and EVIOCGSW(8), EVIOCGNAME(256), EVIOCGBIT(0x05, 8),
+# HCIGETDEVINFO
+ioctl: arg1 in 0xffff41ff && arg1 & 0x00004100 || arg1 in 0xffff55ff && arg1 & 0x00005500 || arg1 == 0x8008451b || arg1 == 0x81004506 || arg1 == 0x80084525 || arg1 == 0x800448d3
+getpid: 1
diff --git a/seccomp/cras-seccomp-arm.policy b/seccomp/cras-seccomp-arm.policy
new file mode 100644
index 0000000..74676a0
--- /dev/null
+++ b/seccomp/cras-seccomp-arm.policy
@@ -0,0 +1,90 @@
+# Copyright 2018 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.
+
+clock_gettime: 1
+poll: 1
+read: 1
+ppoll: 1
+write: 1
+recv: 1
+send: 1
+recvmsg: 1
+lstat64: 1
+fstat64: 1
+open: 1
+openat: 1
+close: 1
+fcntl64: 1
+readlinkat: 1
+sendmsg: 1
+access: 1
+getrandom: 1
+mmap2: 1
+epoll_wait: 1
+getsockopt: 1
+accept: 1
+stat64: 1
+mprotect: 1
+gettimeofday: 1
+getdents64: 1
+brk: 1
+statfs: 1
+readlink: 1
+munmap: 1
+rt_sigaction: 1
+lgetxattr: 1
+unlink: 1
+lsetxattr: 1
+rt_sigprocmask: 1
+ftruncate: 1
+futex: 1
+execve: 1
+set_robust_list: 1
+socket: arg0 == AF_UNIX || arg0 == AF_BLUETOOTH || arg0 == AF_NETLINK
+clone: 1
+setsockopt: 1
+geteuid32: 1
+ugetrlimit: 1
+uname: 1
+connect: 1
+bind: 1
+_llseek: 1
+getuid32: 1
+getgid32: 1
+getegid32: 1
+pipe: 1
+flock: 1
+# set_tls: 1
+set_tid_address: 1
+exit_group: 1
+getsockname: 1
+getdents: 1
+nanosleep: 1
+epoll_ctl: 1
+sched_setscheduler: 1
+restart_syscall: 1
+rt_sigreturn: 1
+getresuid32: 1
+exit: 1
+prctl: arg0 == PR_SET_NAME
+clock_getres: 1
+epoll_create1: 1
+fchmod: 1
+setrlimit: 1
+listen: 1
+gettid: 1
+sched_get_priority_min: 1
+chmod: 1
+madvise: 1
+getresgid32: 1
+pipe2: 1
+sched_get_priority_max: 1
+sysinfo: 1
+flock: 1
+
+# Allow ioctl command of type 'A' and 'U' for SNDRV_PCM_IOCTL_* and
+# SNDRV_CTL_IOCTL_*, and EVIOCGSW(8), EVIOCGNAME(256), EVIOCGBIT(0x05, 8),
+# HCIGETDEVINFO
+ioctl: arg1 in 0xffff41ff && arg1 & 0x00004100 || arg1 in 0xffff55ff && arg1 & 0x00005500 || arg1 == 0x8008451b || arg1 == 0x81004506 || arg1 == 0x80084525 || arg1 == 0x800448d3
+getpid: 1
diff --git a/seccomp/cras-seccomp-arm64.policy b/seccomp/cras-seccomp-arm64.policy
new file mode 100644
index 0000000..569c10c
--- /dev/null
+++ b/seccomp/cras-seccomp-arm64.policy
@@ -0,0 +1,82 @@
+# Copyright 2018 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.
+
+clock_gettime: 1
+# Allow ioctl command of type 'A' and 'U' for SNDRV_PCM_IOCTL_* and
+# SNDRV_CTL_IOCTL_*, and EVIOCGSW(8), EVIOCGNAME(256), EVIOCGBIT(0x05, 8),
+# HCIGETDEVINFO
+ioctl: arg1 in 0xffff41ff && arg1 & 0x00004100 || arg1 in 0xffff55ff && arg1 & 0x00005500 || arg1 == 0x8008451b || arg1 == 0x81004506 || arg1 == 0x80084525 || arg1 == 0x800448d3
+ppoll: 1
+read: 1
+write: 1
+newfstatat: 1
+fstat: 1
+openat: 1
+close: 1
+readlinkat: 1
+getrandom: 1
+faccessat: 1
+# Don't allow mmap or mprotect with both PROT_WRITE and PROT_EXEC
+mmap: arg2 in 0xfffffffb || arg2 in 0xfffffffd
+mprotect: arg2 in 0xfffffffb || arg2 in 0xfffffffd
+sendmsg: 1
+rt_sigaction: 1
+lseek: 1
+recvmsg: 1
+fcntl: 1
+getdents64: 1
+sendto: 1
+brk: 1
+munmap: 1
+socket: arg0 == AF_UNIX || arg0 == AF_BLUETOOTH || arg0 == AF_NETLINK
+statfs: 1
+getsockopt: 1
+accept: 1
+pipe2: 1
+prctl: arg0 == PR_SET_NAME
+futex: 1
+ftruncate: 1
+connect: 1
+bind: 1
+clock_getres: 1
+clone: 1
+epoll_create1: 1
+epoll_ctl: 1
+epoll_pwait: 1
+execve: 1
+exit: 1
+exit_group: 1
+fchmod: 1
+fchmodat: 1
+flock: 1
+flock: 1
+getegid: 1
+geteuid: 1
+getgid: 1
+getresgid: 1
+getresuid: 1
+getrlimit: 1
+getsockname: 1
+gettid: 1
+gettimeofday: 1
+getuid: 1
+lgetxattr: 1
+listen: 1
+lsetxattr: 1
+madvise: 1
+nanosleep: 1
+restart_syscall: 1
+rt_sigprocmask: 1
+rt_sigreturn: 1
+sched_get_priority_max: 1
+sched_get_priority_min: 1
+sched_setscheduler: 1
+setrlimit: 1
+set_robust_list: 1
+setsockopt: 1
+set_tid_address: 1
+sysinfo: 1
+uname: 1
+unlinkat: 1
+getpid: 1
diff --git a/ucm-config/cyan/sof-chtmax98090/HiFi.conf b/ucm-config/cyan/sof-chtmax98090/HiFi.conf
new file mode 100644
index 0000000..8b7aacd
--- /dev/null
+++ b/ucm-config/cyan/sof-chtmax98090/HiFi.conf
@@ -0,0 +1,185 @@
+# command-line sequence to switch playback/capture
+# alsaucm -c sof-chtmax98090 set _verb HiFi set _enadev Headphone
+# alsaucm -c sof-chtmax98090 set _verb HiFi set _enadev Speakers
+# alsaucm -c sof-chtmax98090 set _verb HiFi set _enadev HeadsetMic
+# alsaucm -c sof-chtmax98090 set _verb HiFi set _enadev InternalMic
+
+
+SectionVerb {
+
+ EnableSequence [
+ cdev "hw:sofchtmax98090"
+
+ 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='MIC2 Volume' 10"
+ cset "name='MIC2 Boost Volume' 0"
+
+ cset "name='ADCR Boost Volume' 4"
+ cset "name='ADCL Boost Volume' 4"
+ cset "name='ADCR Volume' 11"
+ cset "name='ADCL Volume' 11"
+
+ cset "name='Headphone Volume' 10"
+ cset "name='Speaker Volume' 10"
+
+ cset "name='Speaker Left Mixer Volume' 3"
+ cset "name='Speaker Right Mixer Volume' 3"
+ cset "name='Record Path DC Blocking' on"
+ cset "name='Playback Path DC Blocking' on"
+
+ cset "name='Headphone Left Switch' off"
+ cset "name='Headphone Right Switch' off"
+ cset "name='Headphone Switch' off"
+
+ cset "name='Speaker Left Switch' off"
+ cset "name='Speaker Right Switch' off"
+ cset "name='Ext Spk Switch' off"
+
+ cset "name='Headset Mic Switch' off"
+ cset "name='Int Mic Switch' off"
+ ]
+
+ DisableSequence [
+ ]
+
+ # ALSA PCM
+ Value {
+ # ALSA PCM device for HiFi
+ PlaybackPCM "hw:sofchtmax98090"
+ CapturePCM "hw:sofchtmax98090"
+ }
+}
+
+SectionDevice."Headphone" {
+ Comment "Headphone"
+
+ ConflictingDevice [
+ "Speakers"
+ ]
+
+ Value {
+ JackControl "Headphone Jack"
+ JackHWMute "Speakers"
+ }
+
+ EnableSequence [
+ cdev "hw:sofchtmax98090"
+
+ cset "name='Headphone Left Switch' on"
+ cset "name='Headphone Right Switch' on"
+ cset "name='Headphone Switch' on"
+ ]
+ DisableSequence [
+ cdev "hw:sofchtmax98090"
+
+ cset "name='Headphone Left Switch' off"
+ cset "name='Headphone Right Switch' off"
+ cset "name='Headphone Switch' off"
+ ]
+
+ Value {
+ PlaybackChannels 2
+ }
+}
+
+SectionDevice."Speakers" {
+ Comment "Speakers"
+
+ ConflictingDevice [
+ "Headphone"
+ ]
+
+ EnableSequence [
+ cdev "hw:sofchtmax98090"
+
+ cset "name='Speaker Left Switch' on"
+ cset "name='Speaker Right Switch' on"
+ cset "name='Ext Spk Switch' on"
+ ]
+ DisableSequence [
+ cdev "hw:sofchtmax98090"
+
+ cset "name='Speaker Left Switch' off"
+ cset "name='Speaker Right Switch' off"
+ cset "name='Ext Spk Switch' off"
+ ]
+
+ Value {
+ PlaybackChannels 2
+ }
+}
+
+SectionDevice."HeadsetMic" {
+ Comment "Headset Mic"
+
+ Value {
+ JackControl "Headset Mic Jack"
+ #FIXME CaptureControl "MIC2"
+ }
+
+ ConflictingDevice [
+ "InternalMic"
+ ]
+
+ EnableSequence [
+ cdev "hw:sofchtmax98090"
+
+ cset "name='Headset Mic Switch' on"
+ cset "name='DMIC Mux' ADC"
+ cset "name='Record Path DC Blocking' on"
+ ]
+
+ DisableSequence [
+ cdev "hw:sofchtmax98090"
+
+ cset "name='Headset Mic Switch' off"
+ cset "name='DMIC Mux' DMIC"
+ cset "name='Record Path DC Blocking' off"
+ ]
+
+ Value {
+ CaptureChannels 2
+ }
+}
+
+SectionDevice."InternalMic" {
+ Comment "Internal Mic"
+
+ Value {
+ #FIXME CaptureControl "MIC2"
+ }
+
+ ConflictingDevice [
+ "HeadsetMic"
+ ]
+
+ EnableSequence [
+ cdev "hw:sofchtmax98090"
+
+ cset "name='Int Mic Switch' on"
+ cset "name='DMIC Mux' DMIC"
+ cset "name='Record Path DC Blocking' off"
+ ]
+
+ DisableSequence [
+ cdev "hw:sofchtmax98090"
+
+ cset "name='Int Mic Switch' off"
+ cset "name='DMIC Mux' ADC"
+ cset "name='Record Path DC Blocking' on"
+ ]
+
+ Value {
+ CaptureChannels 2
+ }
+}
diff --git a/ucm-config/cyan/sof-chtmax98090/sof-chtmax98090.conf b/ucm-config/cyan/sof-chtmax98090/sof-chtmax98090.conf
new file mode 100644
index 0000000..6f73533
--- /dev/null
+++ b/ucm-config/cyan/sof-chtmax98090/sof-chtmax98090.conf
@@ -0,0 +1,6 @@
+Comment "sof-chtmax98090 internal card"
+
+SectionUseCase."HiFi" {
+ File "HiFi.conf"
+ Comment "Default"
+}
diff --git a/ucm-config/daisy/DAISY-I2S-98090/HiFi.conf b/ucm-config/daisy/DAISY-I2S-98090/HiFi.conf
index b583e31..bddfce6 100644
--- a/ucm-config/daisy/DAISY-I2S-98090/HiFi.conf
+++ b/ucm-config/daisy/DAISY-I2S-98090/HiFi.conf
@@ -42,7 +42,7 @@
Value {
JackName "DAISY-I2S-98090 HDMI Jack"
OutputDspName ""
- EDIDFile "/sys/devices/platform/exynos-drm/drm/card0/card0-HDMI-A-1/edid"
+ EDIDFile "/sys/devices/platform/exynos-drm/drm/card1/card1-HDMI-A-1/edid"
}
EnableSequence [
cdev "hw:DAISYI2S98090"
@@ -82,6 +82,7 @@
Value {
JackName "DAISY-I2S-98090 Mic Jack"
CaptureControl "MIC2"
+ DefaultNodeGain "-500"
}
EnableSequence [
diff --git a/ucm-config/daisy/DAISY-I2S/HiFi.conf b/ucm-config/daisy/DAISY-I2S/HiFi.conf
index 54d08fc..9031816 100644
--- a/ucm-config/daisy/DAISY-I2S/HiFi.conf
+++ b/ucm-config/daisy/DAISY-I2S/HiFi.conf
@@ -90,7 +90,7 @@
SectionDevice."HDMI".0 {
Value {
JackName "DAISY-I2S HDMI Jack"
- EDIDFile "/sys/devices/platform/exynos-drm/drm/card0/card0-HDMI-A-1/edid"
+ EDIDFile "/sys/devices/platform/exynos-drm/drm/card1/card1-HDMI-A-1/edid"
}
}
@@ -119,6 +119,7 @@
Value {
JackName "DAISY-I2S Mic Jack"
CaptureControl "MIC2"
+ DefaultNodeGain "-500"
}
EnableSequence [
diff --git a/ucm-config/daisy_skate/DAISY-I2S/HiFi.conf b/ucm-config/daisy_skate/DAISY-I2S/HiFi.conf
index 2516a96..ce1e45d 100644
--- a/ucm-config/daisy_skate/DAISY-I2S/HiFi.conf
+++ b/ucm-config/daisy_skate/DAISY-I2S/HiFi.conf
@@ -23,7 +23,7 @@
Value {
JackName "DAISY-I2S HDMI Jack"
OutputDspName ""
- EDIDFile "/sys/devices/platform/exynos-drm/drm/card0/card0-HDMI-A-1/edid"
+ EDIDFile "/sys/devices/platform/exynos-drm/drm/card1/card1-HDMI-A-1/edid"
}
}
diff --git a/ucm-config/daisy_spring/DAISY-I2S/HiFi.conf b/ucm-config/daisy_spring/DAISY-I2S/HiFi.conf
index 85fbc56..f6a1d31 100644
--- a/ucm-config/daisy_spring/DAISY-I2S/HiFi.conf
+++ b/ucm-config/daisy_spring/DAISY-I2S/HiFi.conf
@@ -48,7 +48,7 @@
Value {
JackName "DAISY-I2S HDMI Jack"
OutputDspName ""
- EDIDFile "/sys/devices/platform/exynos-drm/drm/card0/card0-HDMI-A-1/edid"
+ EDIDFile "/sys/devices/platform/exynos-drm/drm/card1/card1-HDMI-A-1/edid"
}
}
diff --git a/ucm-config/for_all_boards/Hangouts Meet speakermic/Hangouts Meet speakermic.conf b/ucm-config/for_all_boards/Hangouts Meet speakermic/Hangouts Meet speakermic.conf
new file mode 100644
index 0000000..c114430
--- /dev/null
+++ b/ucm-config/for_all_boards/Hangouts Meet speakermic/Hangouts Meet speakermic.conf
@@ -0,0 +1,6 @@
+Comment "Speakermic for Chrome for meeting"
+
+SectionUseCase."HiFi" {
+ File "HiFi.conf"
+ Comment "Default"
+}
diff --git a/ucm-config/for_all_boards/Hangouts Meet speakermic/HiFi.conf b/ucm-config/for_all_boards/Hangouts Meet speakermic/HiFi.conf
new file mode 100644
index 0000000..b84e696
--- /dev/null
+++ b/ucm-config/for_all_boards/Hangouts Meet speakermic/HiFi.conf
@@ -0,0 +1,8 @@
+SectionVerb {
+ Value {
+ MinBufferLevel "0"
+ }
+}
+
+SectionDevice."Headset".0 {
+}
diff --git a/ucm-config/for_all_boards/Plankton Captured HDMI Audio/HiFi.conf b/ucm-config/for_all_boards/Plankton Captured HDMI Audio/HiFi.conf
new file mode 100644
index 0000000..13db891
--- /dev/null
+++ b/ucm-config/for_all_boards/Plankton Captured HDMI Audio/HiFi.conf
@@ -0,0 +1,23 @@
+SectionVerb {
+ Value {
+ FullySpecifiedUCM "1"
+ }
+
+ EnableSequence [
+ ]
+
+ DisableSequence [
+ ]
+}
+
+SectionDevice."Plankton Captured HDMI Audio".0 {
+ Value {
+ CapturePCM "hw:Plankton Captured HDMI Audio,0"
+ }
+
+ EnableSequence [
+ ]
+
+ DisableSequence [
+ ]
+}
diff --git a/ucm-config/for_all_boards/Plankton Captured HDMI Audio/Plankton Captured HDMI Audio.conf b/ucm-config/for_all_boards/Plankton Captured HDMI Audio/Plankton Captured HDMI Audio.conf
new file mode 100644
index 0000000..6514aa1
--- /dev/null
+++ b/ucm-config/for_all_boards/Plankton Captured HDMI Audio/Plankton Captured HDMI Audio.conf
@@ -0,0 +1,6 @@
+Comment "Plankton Captured HDMI Audio"
+
+SectionUseCase."HiFi" {
+ File "HiFi.conf"
+ Comment "Default"
+}
diff --git a/ucm-config/glados/sklnau8825adi/HiFi.conf b/ucm-config/glados/sklnau8825adi/HiFi.conf
index 376e704..6a53caf 100644
--- a/ucm-config/glados/sklnau8825adi/HiFi.conf
+++ b/ucm-config/glados/sklnau8825adi/HiFi.conf
@@ -1,14 +1,17 @@
SectionVerb {
Value {
OutputDspName "speaker_eq"
+ InputDspName "extmic_eq"
}
EnableSequence [
cdev "hw:sklnau8825adi"
cset "name='codec1_out mo media0_in mi Switch' off"
cset "name='codec0_out mo media0_in mi Switch' on"
- cset "name='DAC Oversampling Rate' 128"
+ cset "name='DAC Oversampling Rate' 64"
cset "name='Headset Mic Switch' off"
+ cset "name='BIQ Path Select' ADC"
+ cset "name='BIQ Coefficients' 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
cset "name='codec0_iv_in Switch' 1"
cset "name='media0_out mo codec0_in mi Switch' off"
cset "name='media0_out mo dmic01_hifi_in mi Switch' on"
@@ -24,7 +27,8 @@
SectionDevice."Internal Mic".0 {
Value {
- MaxSoftwareGain "2000"
+ MaxSoftwareGain "2400"
+ InputDspName ""
}
EnableSequence [
@@ -99,11 +103,13 @@
Value {
JackName "sklnau8825adi Headset Jack"
CaptureControl "Mic"
+ DefaultNodeGain "800"
}
EnableSequence [
cdev "hw:sklnau8825adi"
cset "name='Headset Mic Switch' on"
+ cset "name='BIQ Coefficients' 0,155,0,6,255,102,0,0,255,179,0,0,0,154,0,6,255,179,128,0"
cset "name='media0_out mo codec0_in mi Switch' on"
cset "name='media0_out mo dmic01_hifi_in mi Switch' off"
]
@@ -111,6 +117,7 @@
DisableSequence [
cdev "hw:sklnau8825adi"
cset "name='Headset Mic Switch' off"
+ cset "name='BIQ Coefficients' 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
cset "name='media0_out mo codec0_in mi Switch' off"
cset "name='media0_out mo dmic01_hifi_in mi Switch' on"
]