diff options
Diffstat (limited to 'cras')
89 files changed, 3797 insertions, 453 deletions
diff --git a/cras/README.dbus-api b/cras/README.dbus-api index c55a8df6..f347358e 100644 --- a/cras/README.dbus-api +++ b/cras/README.dbus-api @@ -236,7 +236,3 @@ Signals OutputVolumeChanged(int32 volume) HotwordTriggered(int64 tv_sec, int64 tv_nsec) Indicates that hotword was triggered at the given timestamp. - - BluetoothBatteryChanged(string address, uint32 level) - - Indicates the battery level of a bluetooth device changed. diff --git a/cras/client/cras-sys/src/gen.rs b/cras/client/cras-sys/src/gen.rs index 0375a0bd..6fb4cdf8 100644 --- a/cras/client/cras-sys/src/gen.rs +++ b/cras/client/cras-sys/src/gen.rs @@ -748,7 +748,9 @@ pub enum CRAS_CONNECTION_TYPE { CRAS_CAPTURE = 2, CRAS_VMS_LEGACY = 3, CRAS_VMS_UNIFIED = 4, - CRAS_NUM_CONN_TYPE = 5, + CRAS_PLUGIN_PLAYBACK = 5, + CRAS_PLUGIN_UNIFIED = 6, + CRAS_NUM_CONN_TYPE = 7, } #[repr(u32)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] @@ -798,6 +800,9 @@ pub enum CRAS_CLIENT_TYPE { CRAS_CLIENT_TYPE_CROSVM = 6, CRAS_CLIENT_TYPE_SERVER_STREAM = 7, CRAS_CLIENT_TYPE_LACROS = 8, + CRAS_CLIENT_TYPE_PLUGIN = 9, + CRAS_CLIENT_TYPE_ARCVM = 10, + CRAS_NUM_CLIENT_TYPE = 11, } impl CRAS_STREAM_EFFECT { pub const APM_ECHO_CANCELLATION: CRAS_STREAM_EFFECT = CRAS_STREAM_EFFECT(1); @@ -914,24 +919,25 @@ pub enum CRAS_BT_LOG_EVENTS { BT_A2DP_START = 6, BT_A2DP_SUSPENDED = 7, BT_CODEC_SELECTION = 8, - BT_DEV_CONNECTED_CHANGE = 9, - BT_DEV_CONN_WATCH_CB = 10, - BT_DEV_SUSPEND_CB = 11, - BT_HFP_NEW_CONNECTION = 12, - BT_HFP_REQUEST_DISCONNECT = 13, - BT_HFP_SUPPORTED_FEATURES = 14, - BT_HFP_HF_INDICATOR = 15, - BT_HFP_SET_SPEAKER_GAIN = 16, - BT_HFP_UPDATE_SPEAKER_GAIN = 17, - BT_HSP_NEW_CONNECTION = 18, - BT_HSP_REQUEST_DISCONNECT = 19, - BT_NEW_AUDIO_PROFILE_AFTER_CONNECT = 20, - BT_RESET = 21, - BT_SCO_CONNECT = 22, - BT_TRANSPORT_ACQUIRE = 23, - BT_TRANSPORT_RELEASE = 24, - BT_TRANSPORT_SET_VOLUME = 25, - BT_TRANSPORT_UPDATE_VOLUME = 26, + BT_DEV_CONNECTED = 9, + BT_DEV_DISCONNECTED = 10, + BT_DEV_CONN_WATCH_CB = 11, + BT_DEV_SUSPEND_CB = 12, + BT_HFP_NEW_CONNECTION = 13, + BT_HFP_REQUEST_DISCONNECT = 14, + BT_HFP_SUPPORTED_FEATURES = 15, + BT_HFP_HF_INDICATOR = 16, + BT_HFP_SET_SPEAKER_GAIN = 17, + BT_HFP_UPDATE_SPEAKER_GAIN = 18, + BT_HSP_NEW_CONNECTION = 19, + BT_HSP_REQUEST_DISCONNECT = 20, + BT_NEW_AUDIO_PROFILE_AFTER_CONNECT = 21, + BT_RESET = 22, + BT_SCO_CONNECT = 23, + BT_TRANSPORT_ACQUIRE = 24, + BT_TRANSPORT_RELEASE = 25, + BT_TRANSPORT_SET_VOLUME = 26, + BT_TRANSPORT_UPDATE_VOLUME = 27, } #[repr(C, packed)] #[derive(Debug, Copy, Clone)] @@ -2117,12 +2123,15 @@ pub struct cras_server_state { pub bt_wbs_enabled: i32, pub deprioritize_bt_wbs_mic: i32, pub main_thread_debug_info: main_thread_debug_info, + pub num_input_streams_with_permission: [u32; 11usize], + pub noise_cancellation_enabled: i32, + pub hotword_pause_at_suspend: i32, } #[test] fn bindgen_test_layout_cras_server_state() { assert_eq!( ::std::mem::size_of::<cras_server_state>(), - 1414292usize, + 1414344usize, concat!("Size of: ", stringify!(cras_server_state)) ); assert_eq!( @@ -2520,6 +2529,45 @@ fn bindgen_test_layout_cras_server_state() { stringify!(main_thread_debug_info) ) ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<cras_server_state>())).num_input_streams_with_permission + as *const _ as usize + }, + 1414292usize, + concat!( + "Offset of field: ", + stringify!(cras_server_state), + "::", + stringify!(num_input_streams_with_permission) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<cras_server_state>())).noise_cancellation_enabled as *const _ + as usize + }, + 1414336usize, + concat!( + "Offset of field: ", + stringify!(cras_server_state), + "::", + stringify!(noise_cancellation_enabled) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<cras_server_state>())).hotword_pause_at_suspend as *const _ + as usize + }, + 1414340usize, + concat!( + "Offset of field: ", + stringify!(cras_server_state), + "::", + stringify!(hotword_pause_at_suspend) + ) + ); } pub const cras_notify_device_action_CRAS_DEVICE_ACTION_ADD: cras_notify_device_action = 0; pub const cras_notify_device_action_CRAS_DEVICE_ACTION_REMOVE: cras_notify_device_action = 1; diff --git a/cras/client/cras-sys/src/lib.rs b/cras/client/cras-sys/src/lib.rs index 8128575b..2b3d21e0 100644 --- a/cras/client/cras-sys/src/lib.rs +++ b/cras/client/cras-sys/src/lib.rs @@ -10,6 +10,7 @@ use std::error; use std::fmt; use std::iter::FromIterator; use std::os::raw::c_char; +use std::str::FromStr; use std::time::Duration; #[allow(dead_code)] @@ -47,6 +48,7 @@ unsafe impl data_model::DataInit for gen::cras_set_system_volume {} pub enum Error { InvalidChannel(i8), InvalidClientType(u32), + InvalidClientTypeStr, InvalidStreamType(u32), } @@ -68,6 +70,7 @@ impl fmt::Display for Error { t, CRAS_CLIENT_TYPE::CRAS_CLIENT_TYPE_SERVER_STREAM as u32 + 1 ), + InvalidClientTypeStr => write!(f, "Invalid client type string"), InvalidStreamType(t) => write!( f, "Stream type {} is not within valid range [0, {})", @@ -426,6 +429,18 @@ impl TryFrom<u32> for CRAS_CLIENT_TYPE { } } +impl FromStr for CRAS_CLIENT_TYPE { + type Err = Error; + fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { + use CRAS_CLIENT_TYPE::*; + match s { + "crosvm" => Ok(CRAS_CLIENT_TYPE_CROSVM), + "arcvm" => Ok(CRAS_CLIENT_TYPE_ARCVM), + _ => Err(Error::InvalidClientTypeStr), + } + } +} + impl Default for audio_stream_debug_info { fn default() -> Self { Self { diff --git a/cras/client/cras_tests/src/audio.rs b/cras/client/cras_tests/src/audio.rs index 5ab22474..23018fd7 100644 --- a/cras/client/cras_tests/src/audio.rs +++ b/cras/client/cras_tests/src/audio.rs @@ -59,7 +59,7 @@ type Result<T> = std::result::Result<T, Error>; static INTERRUPTED: AtomicBool = AtomicBool::new(false); -extern "C" fn sigint_handler() { +extern "C" fn sigint_handler(_: c_int) { // Check if we've already received one SIGINT. If we have, the program may // be misbehaving and not terminating, so to be safe we'll forcefully exit. if INTERRUPTED.load(Ordering::Acquire) { diff --git a/cras/client/libcras/src/cras_stream.rs b/cras/client/libcras/src/cras_stream.rs index 5914bfdd..f6004802 100644 --- a/cras/client/libcras/src/cras_stream.rs +++ b/cras/client/libcras/src/cras_stream.rs @@ -165,20 +165,20 @@ impl<'a, T: CrasStreamData<'a> + BufferDrop> CrasStream<'a, T> { fn wait_request_data(&mut self) -> Result<(), Error> { match self.controls.audio_sock_mut().read_audio_message()? { - AudioMessage::Success { id, .. } => match id { - CRAS_AUDIO_MESSAGE_ID::AUDIO_MESSAGE_REQUEST_DATA => Ok(()), - _ => Err(Error::MessageTypeError), - }, + AudioMessage::Success { + id: CRAS_AUDIO_MESSAGE_ID::AUDIO_MESSAGE_REQUEST_DATA, + .. + } => Ok(()), _ => Err(Error::MessageTypeError), } } fn wait_data_ready(&mut self) -> Result<u32, Error> { match self.controls.audio_sock_mut().read_audio_message()? { - AudioMessage::Success { id, frames } => match id { - CRAS_AUDIO_MESSAGE_ID::AUDIO_MESSAGE_DATA_READY => Ok(frames), - _ => Err(Error::MessageTypeError), - }, + AudioMessage::Success { + id: CRAS_AUDIO_MESSAGE_ID::AUDIO_MESSAGE_DATA_READY, + frames, + } => Ok(frames), _ => Err(Error::MessageTypeError), } } diff --git a/cras/client/libcras/src/libcras.rs b/cras/client/libcras/src/libcras.rs index 80d2cff7..402a4a27 100644 --- a/cras/client/libcras/src/libcras.rs +++ b/cras/client/libcras/src/libcras.rs @@ -136,7 +136,7 @@ pub use cras_sys::gen::{ CRAS_CLIENT_TYPE as CrasClientType, CRAS_NODE_TYPE as CrasNodeType, CRAS_STREAM_EFFECT as CrasStreamEffect, }; -pub use cras_sys::{AudioDebugInfo, CrasIodevInfo, CrasIonodeInfo}; +pub use cras_sys::{AudioDebugInfo, CrasIodevInfo, CrasIonodeInfo, Error as CrasSysError}; use sys_util::{PollContext, PollToken, SharedMemory}; mod audio_socket; diff --git a/cras/src/Makefile.am b/cras/src/Makefile.am index 69fea5ff..1e89f811 100644 --- a/cras/src/Makefile.am +++ b/cras/src/Makefile.am @@ -55,6 +55,7 @@ CRAS_DBUS_SOURCES = \ server/cras_bt_player.c \ server/cras_bt_io.c \ server/cras_bt_profile.c \ + server/cras_bt_battery_provider.c \ server/cras_dbus.c \ server/cras_dbus_util.c \ server/cras_dbus_control.c \ @@ -426,6 +427,7 @@ TESTS = \ byte_buffer_unittest \ card_config_unittest \ checksum_unittest \ + cras_abi_unittest \ cras_client_unittest \ cras_tm_unittest \ device_monitor_unittest \ @@ -613,7 +615,7 @@ a2dp_info_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/server \ -I$(top_srcdir)/src/common a2dp_info_unittest_LDADD = -lgtest -lpthread -a2dp_iodev_unittest_SOURCES = tests/a2dp_iodev_unittest.cc common/sfh.c +a2dp_iodev_unittest_SOURCES = tests/a2dp_iodev_unittest.cc a2dp_iodev_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/server \ -I$(top_srcdir)/src/common $(DBUS_CFLAGS) a2dp_iodev_unittest_LDADD = -lgtest -lpthread $(DBUS_LIBS) @@ -686,7 +688,7 @@ 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 \ - tests/metrics_stub.cc + tests/metrics_stub.cc common/sfh.c bt_device_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/server \ -I$(top_srcdir)/src/common $(DBUS_CFLAGS) bt_device_unittest_LDADD = -lgtest -lpthread $(DBUS_LIBS) @@ -718,6 +720,13 @@ checksum_unittest_SOURCES = tests/checksum_unittest.cc common/cras_checksum.c checksum_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common checksum_unittest_LDADD = -lgtest -lpthread +cras_abi_unittest_SOURCES = tests/cras_abi_unittest.cc \ + common/cras_config.c common/cras_shm.c common/cras_util.c \ + common/cras_file_wait.c common/cras_audio_format.c +cras_abi_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \ + -I$(top_srcdir)/src/libcras +cras_abi_unittest_LDADD = -lgtest -lpthread -lrt -lspeexdsp + cras_client_unittest_SOURCES = tests/cras_client_unittest.cc \ common/cras_config.c common/cras_shm.c common/cras_util.c \ common/cras_file_wait.c @@ -854,13 +863,13 @@ hfp_info_unittest_LDADD = -lgtest -lpthread if HAVE_DBUS hfp_iodev_unittest_SOURCES = tests/hfp_iodev_unittest.cc \ - server/cras_hfp_iodev.c common/sfh.c + server/cras_hfp_iodev.c hfp_iodev_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) -I$(top_srcdir)/src/common \ -I$(top_srcdir)/src/server $(DBUS_CFLAGS) hfp_iodev_unittest_LDADD = -lgtest -lpthread $(DBUS_LIBS) hfp_alsa_iodev_unittest_SOURCES = tests/hfp_alsa_iodev_unittest.cc \ - server/cras_hfp_alsa_iodev.c common/sfh.c + server/cras_hfp_alsa_iodev.c hfp_alsa_iodev_unittest_CPPFLAGS = $(COMMON_CPPFLAGS) \ -I$(top_srcdir)/src/common -I$(top_srcdir)/src/server $(DBUS_CFLAGS) hfp_alsa_iodev_unittest_LDADD = -lgtest -lpthread $(DBUS_LIBS) diff --git a/cras/src/common/cras_file_wait.c b/cras/src/common/cras_file_wait.c index 9ad94486..190a5e10 100644 --- a/cras/src/common/cras_file_wait.c +++ b/cras/src/common/cras_file_wait.c @@ -190,7 +190,7 @@ int cras_file_wait_dispatch(struct cras_file_wait *file_wait) strcpy(file_wait->watch_dir, file_wait->file_path); watch_dir_len = file_wait->file_path_len; - while (rc == -ENOENT) { + while (rc == -ENOENT || rc == -EACCES) { strcpy(file_wait->watch_path, file_wait->watch_dir); watch_path_len = watch_dir_len; diff --git a/cras/src/common/cras_types.h b/cras/src/common/cras_types.h index 90a04741..544ba02c 100644 --- a/cras/src/common/cras_types.h +++ b/cras/src/common/cras_types.h @@ -169,6 +169,7 @@ enum CRAS_CLIENT_TYPE { CRAS_CLIENT_TYPE_SERVER_STREAM, /* Server stream */ CRAS_CLIENT_TYPE_LACROS, /* LaCrOS */ CRAS_CLIENT_TYPE_PLUGIN, /* PluginVM */ + CRAS_CLIENT_TYPE_ARCVM, /* ARCVM */ CRAS_NUM_CLIENT_TYPE, /* numbers of CRAS_CLIENT_TYPE */ }; @@ -213,6 +214,7 @@ cras_client_type_str(enum CRAS_CLIENT_TYPE client_type) ENUM_STR(CRAS_CLIENT_TYPE_SERVER_STREAM) ENUM_STR(CRAS_CLIENT_TYPE_LACROS) ENUM_STR(CRAS_CLIENT_TYPE_PLUGIN) + ENUM_STR(CRAS_CLIENT_TYPE_ARCVM) default: return "INVALID_CLIENT_TYPE"; } @@ -368,7 +370,8 @@ enum CRAS_BT_LOG_EVENTS { BT_A2DP_START, BT_A2DP_SUSPENDED, BT_CODEC_SELECTION, - BT_DEV_CONNECTED_CHANGE, + BT_DEV_CONNECTED, + BT_DEV_DISCONNECTED, BT_DEV_CONN_WATCH_CB, BT_DEV_SUSPEND_CB, BT_HFP_NEW_CONNECTION, @@ -573,6 +576,12 @@ struct __attribute__((__packed__)) cras_audio_thread_snapshot_buffer { * main_thread_debug_info - ring buffer for storing main thread event logs. * num_input_streams_with_permission - An array containing numbers of input * streams with permission in each client type. + * noise_cancellation_enabled - Whether or not Noise Cancellation is enabled. + * hotword_pause_at_suspend - 1 = Pause hotword detection when the system + * suspends. Hotword detection is resumed after system resumes. + * 0 - Hotword detection is allowed to continue running after system + * suspends, so a detected hotword can wake up the device. + * */ #define CRAS_SERVER_STATE_VERSION 2 struct __attribute__((packed, aligned(4))) cras_server_state { @@ -612,6 +621,8 @@ struct __attribute__((packed, aligned(4))) cras_server_state { int32_t deprioritize_bt_wbs_mic; struct main_thread_debug_info main_thread_debug_info; uint32_t num_input_streams_with_permission[CRAS_NUM_CLIENT_TYPE]; + int32_t noise_cancellation_enabled; + int32_t hotword_pause_at_suspend; }; /* Actions for card add/remove/change. */ diff --git a/cras/src/common/cras_util.h b/cras/src/common/cras_util.h index ed476b7e..96985ab2 100644 --- a/cras/src/common/cras_util.h +++ b/cras/src/common/cras_util.h @@ -201,6 +201,19 @@ static inline uint64_t cras_frames_until_time(const struct timespec *end, return cras_time_to_frames(&time_until, rate); } +/* Returns true if the difference between a and b is shorter than t. */ +static inline bool timespec_diff_shorter_than(const struct timespec *a, + const struct timespec *b, + const struct timespec *t) +{ + struct timespec diff; + if (timespec_after(a, b)) + subtract_timespecs(a, b, &diff); + else + subtract_timespecs(b, a, &diff); + return timespec_after(t, &diff); +} + /* Poll on the given file descriptors. * * See ppoll(). This implementation changes the value of timeout to the diff --git a/cras/src/dsp/drc.c b/cras/src/dsp/drc.c index 1b2639a0..e6098419 100644 --- a/cras/src/dsp/drc.c +++ b/cras/src/dsp/drc.c @@ -104,7 +104,7 @@ static void set_default_parameters(struct drc *drc) param[PARAM_RELEASE_ZONE3] = 0.42f; param[PARAM_RELEASE_ZONE4] = 0.98f; - /* This is effectively a master volume on the compressed + /* This is effectively a main volume on the compressed * signal */ param[PARAM_POST_GAIN] = 0; /* dB */ param[PARAM_ENABLED] = 0; diff --git a/cras/src/dsp/drc_kernel.c b/cras/src/dsp/drc_kernel.c index c0eb100b..8c3404fc 100644 --- a/cras/src/dsp/drc_kernel.c +++ b/cras/src/dsp/drc_kernel.c @@ -257,7 +257,7 @@ void dk_set_parameters(struct drc_kernel *dk, float db_threshold, float db_knee, /* Empirical/perceptual tuning. */ full_range_makeup_gain = powf(full_range_makeup_gain, 0.6f); - dk->master_linear_gain = + dk->main_linear_gain = decibels_to_linear(db_post_gain) * full_range_makeup_gain; /* Attack parameters. */ @@ -566,7 +566,7 @@ static void dk_update_detector_average(struct drc_kernel *dk) #include <arm_neon.h> static void dk_compress_output(struct drc_kernel *dk) { - const float master_linear_gain = dk->master_linear_gain; + const float main_linear_gain = dk->main_linear_gain; const float envelope_rate = dk->envelope_rate; const float scaled_desired_gain = dk->scaled_desired_gain; const float compressor_gain = dk->compressor_gain; @@ -638,7 +638,7 @@ static void dk_compress_output(struct drc_kernel *dk) [A7]"w"(A7), [base]"w"(vdupq_n_f32(scaled_desired_gain)), [r4]"w"(vdupq_n_f32(r*r*r*r)), - [g]"w"(vdupq_n_f32(master_linear_gain)) + [g]"w"(vdupq_n_f32(main_linear_gain)) : /* clobber */ "memory", "cc"); // clang-format on @@ -698,7 +698,7 @@ static void dk_compress_output(struct drc_kernel *dk) [A7]"w"(A7), [one]"w"(vdupq_n_f32(1)), [r4]"w"(vdupq_n_f32(r*r*r*r)), - [g]"w"(vdupq_n_f32(master_linear_gain)) + [g]"w"(vdupq_n_f32(main_linear_gain)) : /* clobber */ "memory", "cc"); // clang-format on @@ -709,7 +709,7 @@ static void dk_compress_output(struct drc_kernel *dk) #include <emmintrin.h> static void dk_compress_output(struct drc_kernel *dk) { - const float master_linear_gain = dk->master_linear_gain; + const float main_linear_gain = dk->main_linear_gain; const float envelope_rate = dk->envelope_rate; const float scaled_desired_gain = dk->scaled_desired_gain; const float compressor_gain = dk->compressor_gain; @@ -789,7 +789,7 @@ static void dk_compress_output(struct drc_kernel *dk) [A7]"x"(A7), [base]"x"(_mm_set1_ps(scaled_desired_gain)), [r4]"x"(_mm_set1_ps(r*r*r*r)), - [g]"x"(_mm_set1_ps(master_linear_gain)) + [g]"x"(_mm_set1_ps(main_linear_gain)) : /* clobber */ "memory", "cc"); // clang-format on @@ -862,7 +862,7 @@ static void dk_compress_output(struct drc_kernel *dk) [A7]"x"(A7), [one]"x"(_mm_set1_ps(1)), [r4]"x"(_mm_set1_ps(r*r*r*r)), - [g]"x"(_mm_set1_ps(master_linear_gain)) + [g]"x"(_mm_set1_ps(main_linear_gain)) : /* clobber */ "memory", "cc"); // clang-format on @@ -872,7 +872,7 @@ static void dk_compress_output(struct drc_kernel *dk) #else static void dk_compress_output(struct drc_kernel *dk) { - const float master_linear_gain = dk->master_linear_gain; + const float main_linear_gain = dk->main_linear_gain; const float envelope_rate = dk->envelope_rate; const float scaled_desired_gain = dk->scaled_desired_gain; const float compressor_gain = dk->compressor_gain; @@ -902,8 +902,8 @@ static void dk_compress_output(struct drc_kernel *dk) float post_warp_compressor_gain = warp_sinf(x[j] + base); - /* Calculate total gain using master gain. */ - float total_gain = master_linear_gain * + /* Calculate total gain using main gain. */ + float total_gain = main_linear_gain * post_warp_compressor_gain; /* Apply final gain. */ @@ -936,8 +936,8 @@ static void dk_compress_output(struct drc_kernel *dk) float post_warp_compressor_gain = warp_sinf(x[j]); - /* Calculate total gain using master gain. */ - float total_gain = master_linear_gain * + /* Calculate total gain using main gain. */ + float total_gain = main_linear_gain * post_warp_compressor_gain; /* Apply final gain. */ diff --git a/cras/src/dsp/drc_kernel.h b/cras/src/dsp/drc_kernel.h index 1157f225..2ed9956e 100644 --- a/cras/src/dsp/drc_kernel.h +++ b/cras/src/dsp/drc_kernel.h @@ -67,7 +67,7 @@ struct drc_kernel { float kA, kB, kC, kD, kE; /* Calculated parameters */ - float master_linear_gain; + float main_linear_gain; float attack_frames; float sat_release_frames_inv_neg; float sat_release_rate_at_neg_two_db; diff --git a/cras/src/libcras/cras_client.c b/cras/src/libcras/cras_client.c index 3fe631d5..8420db1f 100644 --- a/cras/src/libcras/cras_client.c +++ b/cras/src/libcras/cras_client.c @@ -119,7 +119,8 @@ struct thread_state { }; /* Parameters used when setting up a capture or playback stream. See comment - * above cras_client_create_stream_params in the header for descriptions. */ + * above cras_client_stream_params_create or libcras_stream_params_set in the + * header for descriptions. */ struct cras_stream_params { enum CRAS_STREAM_DIRECTION direction; size_t buffer_frames; @@ -133,6 +134,7 @@ struct cras_stream_params { cras_unified_cb_t unified_cb; cras_error_cb_t err_cb; struct cras_audio_format format; + libcras_stream_cb_t stream_cb; }; /* Represents an attached audio stream. @@ -274,6 +276,92 @@ struct cras_hotword_handle { void *user_data; }; +struct cras_stream_cb_data { + cras_stream_id_t stream_id; + enum CRAS_STREAM_DIRECTION direction; + uint8_t *buf; + unsigned int frames; + struct timespec sample_ts; + void *user_arg; +}; + +int stream_cb_get_stream_id(struct cras_stream_cb_data *data, + cras_stream_id_t *id) +{ + *id = data->stream_id; + return 0; +} + +int stream_cb_get_buf(struct cras_stream_cb_data *data, uint8_t **buf) +{ + *buf = data->buf; + return 0; +} + +int stream_cb_get_frames(struct cras_stream_cb_data *data, unsigned int *frames) +{ + *frames = data->frames; + return 0; +} + +int stream_cb_get_latency(struct cras_stream_cb_data *data, + struct timespec *latency) +{ + if (data->direction == CRAS_STREAM_INPUT) + cras_client_calc_capture_latency(&data->sample_ts, latency); + else + cras_client_calc_playback_latency(&data->sample_ts, latency); + return 0; +} + +int stream_cb_get_user_arg(struct cras_stream_cb_data *data, void **user_arg) +{ + *user_arg = data->user_arg; + return 0; +} + +struct libcras_stream_cb_data * +libcras_stream_cb_data_create(cras_stream_id_t stream_id, + enum CRAS_STREAM_DIRECTION direction, + uint8_t *buf, unsigned int frames, + struct timespec sample_ts, void *user_arg) +{ + struct libcras_stream_cb_data *data = + (struct libcras_stream_cb_data *)calloc( + 1, sizeof(struct libcras_stream_cb_data)); + if (!data) { + syslog(LOG_ERR, "cras_client: calloc: %s", strerror(errno)); + return NULL; + } + data->data_ = (struct cras_stream_cb_data *)calloc( + 1, sizeof(struct cras_stream_cb_data)); + if (!data->data_) { + syslog(LOG_ERR, "cras_client: calloc: %s", strerror(errno)); + free(data); + return NULL; + } + data->api_version = CRAS_API_VERSION; + data->get_stream_id = stream_cb_get_stream_id; + data->get_buf = stream_cb_get_buf; + data->get_frames = stream_cb_get_frames; + data->get_latency = stream_cb_get_latency; + data->get_user_arg = stream_cb_get_user_arg; + data->data_->stream_id = stream_id; + data->data_->direction = direction; + data->data_->buf = buf; + data->data_->frames = frames; + data->data_->sample_ts = sample_ts; + data->data_->user_arg = user_arg; + return data; +} + +void libcras_stream_cb_data_destroy(struct libcras_stream_cb_data *data) +{ + if (data) + free(data->data_); + free(data); +} + /* * Local Helpers */ @@ -283,6 +371,10 @@ static int client_thread_rm_stream(struct cras_client *client, static int handle_message_from_server(struct cras_client *client); static int reregister_notifications(struct cras_client *client); +static struct libcras_node_info * +libcras_node_info_create(struct cras_iodev_info *iodev, + struct cras_ionode_info *ionode); + /* * Unlock the server_state_rwlock if lock_rc is 0. * @@ -1084,6 +1176,7 @@ static int handle_capture_data_ready(struct client_stream *stream, uint8_t *captured_frames; struct timespec ts; int rc = 0; + struct libcras_stream_cb_data *data; config = stream->config; /* If this message is for an output stream, log error and drop it. */ @@ -1098,14 +1191,24 @@ static int handle_capture_data_ready(struct client_stream *stream, cras_timespec_to_timespec(&ts, &stream->shm->header->ts); - if (config->unified_cb) + if (config->stream_cb) { + data = libcras_stream_cb_data_create( + stream->id, stream->direction, captured_frames, + num_frames, ts, config->user_data); + if (!data) + return -errno; + frames = config->stream_cb(data); + libcras_stream_cb_data_destroy(data); + data = NULL; + } else if (config->unified_cb) { frames = config->unified_cb(stream->client, stream->id, captured_frames, NULL, num_frames, &ts, NULL, config->user_data); - else + } else { frames = config->aud_cb(stream->client, stream->id, captured_frames, num_frames, &ts, config->user_data); + } if (frames < 0) { send_stream_message(stream, CLIENT_STREAM_EOF); rc = frames; @@ -1152,6 +1255,7 @@ static int handle_playback_request(struct client_stream *stream, struct cras_stream_params *config; struct cras_audio_shm *shm = stream->shm; struct timespec ts; + struct libcras_stream_cb_data *data; config = stream->config; @@ -1169,13 +1273,24 @@ static int handle_playback_request(struct client_stream *stream, cras_timespec_to_timespec(&ts, &shm->header->ts); /* Get samples from the user */ - if (config->unified_cb) + if (config->stream_cb) { + data = libcras_stream_cb_data_create(stream->id, + stream->direction, buf, + num_frames, ts, + config->user_data); + if (!data) + return -errno; + frames = config->stream_cb(data); + libcras_stream_cb_data_destroy(data); + data = NULL; + } else if (config->unified_cb) { frames = config->unified_cb(stream->client, stream->id, NULL, buf, num_frames, NULL, &ts, config->user_data); - else + } else { frames = config->aud_cb(stream->client, stream->id, buf, num_frames, &ts, config->user_data); + } if (frames < 0) { send_stream_message(stream, CLIENT_STREAM_EOF); rc = frames; @@ -2255,6 +2370,7 @@ struct cras_stream_params *cras_client_stream_params_create( params->user_data = user_data; params->aud_cb = aud_cb; params->unified_cb = 0; + params->stream_cb = 0; params->err_cb = err_cb; memcpy(&(params->format), format, sizeof(*format)); return params; @@ -2328,6 +2444,7 @@ struct cras_stream_params *cras_client_unified_params_create( params->user_data = user_data; params->aud_cb = 0; params->unified_cb = unified_cb; + params->stream_cb = 0; params->err_cb = err_cb; memcpy(&(params->format), format, sizeof(*format)); @@ -2350,7 +2467,8 @@ static inline int cras_client_send_add_stream_command_message( if (client == NULL || config == NULL || stream_id_out == NULL) return -EINVAL; - if (config->aud_cb == NULL && config->unified_cb == NULL) + if (config->stream_cb == NULL && config->aud_cb == NULL && + config->unified_cb == NULL) return -EINVAL; if (config->err_cb == NULL) @@ -3815,3 +3933,317 @@ int cras_client_disable_hotword_callback(struct cras_client *client, free(handle); return 0; } + +int get_nodes(struct cras_client *client, enum CRAS_STREAM_DIRECTION direction, + struct libcras_node_info ***nodes, size_t *num) +{ + struct cras_iodev_info iodevs[CRAS_MAX_IODEVS]; + struct cras_ionode_info ionodes[CRAS_MAX_IONODES]; + size_t num_devs = CRAS_MAX_IODEVS, num_nodes = CRAS_MAX_IONODES; + int rc, i, j; + + *num = 0; + if (direction == CRAS_STREAM_INPUT) { + rc = cras_client_get_input_devices(client, iodevs, ionodes, + &num_devs, &num_nodes); + } else { + rc = cras_client_get_output_devices(client, iodevs, ionodes, + &num_devs, &num_nodes); + } + + if (rc < 0) { + syslog(LOG_ERR, "Failed to get devices: %d", rc); + return rc; + } + + *nodes = (struct libcras_node_info **)calloc( + num_nodes, sizeof(struct libcras_node_info *)); + + for (i = 0; i < num_devs; i++) { + for (j = 0; j < num_nodes; j++) { + if (iodevs[i].idx != ionodes[j].iodev_idx) + continue; + (*nodes)[*num] = libcras_node_info_create(&iodevs[i], + &ionodes[j]); + if ((*nodes)[*num] == NULL) { + rc = -errno; + goto clean; + } + (*num)++; + } + } + return 0; +clean: + for (i = 0; i < *num; i++) + libcras_node_info_destroy((*nodes)[i]); + free(*nodes); + *nodes = NULL; + *num = 0; + return rc; +} + +int get_default_output_buffer_size(struct cras_client *client, int *size) +{ + int rc = cras_client_get_default_output_buffer_size(client); + if (rc < 0) + return rc; + *size = rc; + return 0; +} + +int get_aec_group_id(struct cras_client *client, int *id) +{ + int rc = cras_client_get_aec_group_id(client); + if (rc < 0) + return rc; + *id = rc; + return 0; +} + +int get_aec_supported(struct cras_client *client, int *supported) +{ + *supported = cras_client_get_aec_supported(client); + return 0; +} + +int get_system_muted(struct cras_client *client, int *muted) +{ + *muted = cras_client_get_system_muted(client); + return 0; +} + +int get_loopback_dev_idx(struct cras_client *client, int *idx) +{ + int rc = cras_client_get_first_dev_type_idx( + client, CRAS_NODE_TYPE_POST_MIX_PRE_DSP, CRAS_STREAM_INPUT); + if (rc < 0) + return rc; + *idx = rc; + return 0; +} + +struct libcras_client *libcras_client_create() +{ + struct libcras_client *client = (struct libcras_client *)calloc( + 1, sizeof(struct libcras_client)); + if (!client) { + syslog(LOG_ERR, "cras_client: calloc failed"); + return NULL; + } + if (cras_client_create(&client->client_)) { + libcras_client_destroy(client); + return NULL; + } + client->api_version = CRAS_API_VERSION; + client->connect = cras_client_connect; + client->connect_timeout = cras_client_connect_timeout; + client->connected_wait = cras_client_connected_wait; + client->run_thread = cras_client_run_thread; + client->stop = cras_client_stop; + client->add_pinned_stream = cras_client_add_pinned_stream; + client->rm_stream = cras_client_rm_stream; + client->set_stream_volume = cras_client_set_stream_volume; + client->get_nodes = get_nodes; + client->get_default_output_buffer_size = get_default_output_buffer_size; + client->get_aec_group_id = get_aec_group_id; + client->get_aec_supported = get_aec_supported; + client->get_system_muted = get_system_muted; + client->set_system_mute = cras_client_set_system_mute; + client->get_loopback_dev_idx = get_loopback_dev_idx; + return client; +} + +void libcras_client_destroy(struct libcras_client *client) +{ + cras_client_destroy(client->client_); + free(client); +} + +int stream_params_set(struct cras_stream_params *params, + enum CRAS_STREAM_DIRECTION direction, + size_t buffer_frames, size_t cb_threshold, + enum CRAS_STREAM_TYPE stream_type, + enum CRAS_CLIENT_TYPE client_type, uint32_t flags, + void *user_data, libcras_stream_cb_t stream_cb, + cras_error_cb_t err_cb, size_t rate, + snd_pcm_format_t format, size_t num_channels) +{ + params->direction = direction; + params->buffer_frames = buffer_frames; + params->cb_threshold = cb_threshold; + params->stream_type = stream_type; + params->client_type = client_type; + params->flags = flags; + params->user_data = user_data; + params->stream_cb = stream_cb; + params->err_cb = err_cb; + params->format.frame_rate = rate; + params->format.format = format; + params->format.num_channels = num_channels; + return 0; +} + +int stream_params_set_channel_layout(struct cras_stream_params *params, + int length, const int8_t *layout) +{ + if (length != CRAS_CH_MAX) + return -EINVAL; + return cras_audio_format_set_channel_layout(¶ms->format, layout); +} + +struct libcras_stream_params *libcras_stream_params_create() +{ + struct libcras_stream_params *params = + (struct libcras_stream_params *)calloc( + 1, sizeof(struct libcras_stream_params)); + if (!params) { + syslog(LOG_ERR, "cras_client: calloc failed"); + return NULL; + } + params->params_ = (struct cras_stream_params *)calloc( + 1, sizeof(struct cras_stream_params)); + if (params->params_ == NULL) { + syslog(LOG_ERR, "cras_client: calloc failed"); + free(params->params_); + return NULL; + } + params->api_version = CRAS_API_VERSION; + params->set = stream_params_set; + params->set_channel_layout = stream_params_set_channel_layout; + params->enable_aec = cras_client_stream_params_enable_aec; + return params; +} + +void libcras_stream_params_destroy(struct libcras_stream_params *params) +{ + free(params->params_); + free(params); +} + +struct cras_node_info { + uint64_t id; + uint32_t dev_idx; + uint32_t node_idx; + uint32_t max_supported_channels; + bool plugged; + bool active; + char type[CRAS_NODE_TYPE_BUFFER_SIZE]; + char node_name[CRAS_NODE_NAME_BUFFER_SIZE]; + char dev_name[CRAS_IODEV_NAME_BUFFER_SIZE]; +}; + +int cras_node_info_get_id(struct cras_node_info *node, uint64_t *id) +{ + (*id) = node->id; + return 0; +} + +int cras_node_info_get_dev_idx(struct cras_node_info *node, uint32_t *dev_idx) +{ + (*dev_idx) = node->dev_idx; + return 0; +} + +int cras_node_info_get_node_idx(struct cras_node_info *node, uint32_t *node_idx) +{ + (*node_idx) = node->node_idx; + return 0; +} + +int cras_node_info_get_max_supported_channels(struct cras_node_info *node, + uint32_t *max_supported_channels) +{ + (*max_supported_channels) = node->max_supported_channels; + return 0; +} + +int cras_node_info_is_plugged(struct cras_node_info *node, bool *is_plugged) +{ + (*is_plugged) = node->plugged; + return 0; +} + +int cras_node_info_is_active(struct cras_node_info *node, bool *is_active) +{ + (*is_active) = node->active; + return 0; +} + +int cras_node_info_get_type(struct cras_node_info *node, char **type) +{ + (*type) = node->type; + return 0; +} + +int cras_node_info_get_node_name(struct cras_node_info *node, char **node_name) +{ + (*node_name) = node->node_name; + return 0; +} + +int cras_node_info_get_dev_name(struct cras_node_info *node, char **dev_name) +{ + (*dev_name) = node->dev_name; + return 0; +} + +struct libcras_node_info * +libcras_node_info_create(struct cras_iodev_info *iodev, + struct cras_ionode_info *ionode) +{ + struct libcras_node_info *node = (struct libcras_node_info *)calloc( + 1, sizeof(struct libcras_node_info)); + if (!node) { + syslog(LOG_ERR, "cras_client: calloc failed"); + return NULL; + } + node->node_ = (struct cras_node_info *)calloc( + 1, sizeof(struct cras_node_info)); + if (node->node_ == NULL) { + syslog(LOG_ERR, "cras_client: calloc failed"); + free(node); + return NULL; + } + node->api_version = CRAS_API_VERSION; + node->node_->id = + cras_make_node_id(ionode->iodev_idx, ionode->ionode_idx); + node->node_->dev_idx = ionode->iodev_idx; + node->node_->node_idx = ionode->ionode_idx; + node->node_->max_supported_channels = iodev->max_supported_channels; + node->node_->plugged = ionode->plugged; + node->node_->active = ionode->active; + strncpy(node->node_->type, ionode->type, CRAS_NODE_TYPE_BUFFER_SIZE); + node->node_->type[CRAS_NODE_TYPE_BUFFER_SIZE - 1] = '\0'; + strncpy(node->node_->node_name, ionode->name, + CRAS_NODE_NAME_BUFFER_SIZE); + node->node_->node_name[CRAS_NODE_NAME_BUFFER_SIZE - 1] = '\0'; + strncpy(node->node_->dev_name, iodev->name, + CRAS_IODEV_NAME_BUFFER_SIZE); + node->node_->dev_name[CRAS_IODEV_NAME_BUFFER_SIZE - 1] = '\0'; + node->get_id = cras_node_info_get_id; + node->get_dev_idx = cras_node_info_get_dev_idx; + node->get_node_idx = cras_node_info_get_node_idx; + node->get_max_supported_channels = + cras_node_info_get_max_supported_channels; + node->is_plugged = cras_node_info_is_plugged; + node->is_active = cras_node_info_is_active; + node->get_type = cras_node_info_get_type; + node->get_node_name = cras_node_info_get_node_name; + node->get_dev_name = cras_node_info_get_dev_name; + return node; +} + +void libcras_node_info_destroy(struct libcras_node_info *node) +{ + free(node->node_); + free(node); +} + +void libcras_node_info_array_destroy(struct libcras_node_info **nodes, + size_t num) +{ + int i; + for (i = 0; i < num; i++) + libcras_node_info_destroy(nodes[i]); + free(nodes); +} diff --git a/cras/src/libcras/cras_client.h b/cras/src/libcras/cras_client.h index f7a18b5b..f26a0814 100644 --- a/cras/src/libcras/cras_client.h +++ b/cras/src/libcras/cras_client.h @@ -1308,6 +1308,699 @@ int cras_client_set_input_node_gain_changed_callback( int cras_client_set_num_active_streams_changed_callback( struct cras_client *client, cras_client_num_active_streams_changed_callback cb); + +/* + * The functions below prefixed with libcras wrap the original CRAS library + * They provide an interface that maps the pointers to the functions above. + * Please add a new function instead of modifying the existing function. + * Here are some rules about how to add a new function: + * 1. Increase the CRAS_API_VERSION by 1. + * 2. Write a new function in cras_client.c. + * 3. Append the corresponding pointer to the structure. Remeber DO NOT change + * the order of functions in the structs. + * 4. Assign the pointer to the new function in cras_client.c. + * 5. Create the inline function in cras_client.h, which is used by clients. + * Remember to add DISABLE_CFI_ICALL on the inline function. + * 6. Add CHECK_VERSION in the inline function. If the api_version is smaller + * than the supported version, this inline function will return -ENOSYS. + */ + +#define CRAS_API_VERSION 1 +#define CHECK_VERSION(object, version) \ + if (object->api_version < version) { \ + return -ENOSYS; \ + } + +/* + * The inline functions use the indirect function call. Therefore, they are + * incompatible with CFI-icall. + */ +#define DISABLE_CFI_ICALL __attribute__((no_sanitize("cfi-icall"))) + +struct libcras_node_info { + int api_version; + struct cras_node_info *node_; + int (*get_id)(struct cras_node_info *node, uint64_t *id); + int (*get_dev_idx)(struct cras_node_info *node, uint32_t *dev_idx); + int (*get_node_idx)(struct cras_node_info *node, uint32_t *node_idx); + int (*get_max_supported_channels)(struct cras_node_info *node, + uint32_t *max_supported_channels); + int (*is_plugged)(struct cras_node_info *node, bool *plugged); + int (*is_active)(struct cras_node_info *node, bool *active); + int (*get_type)(struct cras_node_info *node, char **name); + int (*get_node_name)(struct cras_node_info *node, char **name); + int (*get_dev_name)(struct cras_node_info *node, char **name); +}; + +struct libcras_client { + int api_version; + struct cras_client *client_; + int (*connect)(struct cras_client *client); + int (*connect_timeout)(struct cras_client *client, + unsigned int timeout_ms); + int (*connected_wait)(struct cras_client *client); + int (*run_thread)(struct cras_client *client); + int (*stop)(struct cras_client *client); + int (*add_pinned_stream)(struct cras_client *client, uint32_t dev_idx, + cras_stream_id_t *stream_id_out, + struct cras_stream_params *config); + int (*rm_stream)(struct cras_client *client, + cras_stream_id_t stream_id); + int (*set_stream_volume)(struct cras_client *client, + cras_stream_id_t stream_id, + float volume_scaler); + int (*get_nodes)(struct cras_client *client, + enum CRAS_STREAM_DIRECTION direction, + struct libcras_node_info ***nodes, size_t *num); + int (*get_default_output_buffer_size)(struct cras_client *client, + int *size); + int (*get_aec_group_id)(struct cras_client *client, int *id); + int (*get_aec_supported)(struct cras_client *client, int *supported); + int (*get_system_muted)(struct cras_client *client, int *muted); + int (*set_system_mute)(struct cras_client *client, int mute); + int (*get_loopback_dev_idx)(struct cras_client *client, int *idx); +}; + +struct cras_stream_cb_data; +struct libcras_stream_cb_data { + int api_version; + struct cras_stream_cb_data *data_; + int (*get_stream_id)(struct cras_stream_cb_data *data, + cras_stream_id_t *id); + int (*get_buf)(struct cras_stream_cb_data *data, uint8_t **buf); + int (*get_frames)(struct cras_stream_cb_data *data, + unsigned int *frames); + int (*get_latency)(struct cras_stream_cb_data *data, + struct timespec *latency); + int (*get_user_arg)(struct cras_stream_cb_data *data, void **user_arg); +}; +typedef int (*libcras_stream_cb_t)(struct libcras_stream_cb_data *data); + +struct libcras_stream_params { + int api_version; + struct cras_stream_params *params_; + int (*set)(struct cras_stream_params *params, + enum CRAS_STREAM_DIRECTION direction, size_t buffer_frames, + size_t cb_threshold, enum CRAS_STREAM_TYPE stream_type, + enum CRAS_CLIENT_TYPE client_type, uint32_t flags, + void *user_data, libcras_stream_cb_t stream_cb, + cras_error_cb_t err_cb, size_t rate, snd_pcm_format_t format, + size_t num_channels); + int (*set_channel_layout)(struct cras_stream_params *params, int length, + const int8_t *layout); + void (*enable_aec)(struct cras_stream_params *params); +}; + +/* + * Creates a new client. + * Returns: + * If success, return a valid libcras_client pointer. Otherwise, return + * NULL. + */ +struct libcras_client *libcras_client_create(); + +/* + * Destroys a client. + * Args: + * client - pointer returned from "libcras_client_create". + */ +void libcras_client_destroy(struct libcras_client *client); + +/* + * Connects a client to the running server. + * Waits forever (until interrupted or connected). + * Args: + * client - pointer returned from "libcras_client_create". + * Returns: + * 0 on success, or a negative error code on failure (from errno.h). + */ +DISABLE_CFI_ICALL +inline int libcras_client_connect(struct libcras_client *client) +{ + return client->connect(client->client_); +} + +/* + * Connects a client to the running server, retries until timeout. + * Args: + * client - pointer returned from "libcras_client_create". + * timeout_ms - timeout in milliseconds or negative to wait forever. + * Returns: + * 0 on success, or a negative error code on failure (from errno.h). + */ +DISABLE_CFI_ICALL +inline int libcras_client_connect_timeout(struct libcras_client *client, + unsigned int timeout_ms) +{ + return client->connect_timeout(client->client_, timeout_ms); +} + +/* + * Wait up to 1 second for the client thread to complete the server connection. + * + * After libcras_client_run_thread() is executed, this function can be + * used to ensure that the connection has been established with the server and + * ensure that any information about the server is up to date. If + * libcras_client_run_thread() has not yet been executed, or + * libcras_client_stop() was executed and thread isn't running, then this + * function returns -EINVAL. + * + * Args: + * client - pointer returned from "libcras_client_create". + * Returns: + * 0 on success, or a negative error code on failure (from errno.h). + */ +DISABLE_CFI_ICALL +inline int libcras_client_connected_wait(struct libcras_client *client) +{ + return client->connected_wait(client->client_); +} + +/* + * Begins running the client control thread. + * + * Required for stream operations and other operations noted below. + * + * Args: + * client - pointer returned from "libcras_client_create". + * Returns: + * 0 on success, or a negative error code on failure (from errno.h). + */ +DISABLE_CFI_ICALL +inline int libcras_client_run_thread(struct libcras_client *client) +{ + return client->run_thread(client->client_); +} + +/* + * Stops running a client. + * This function is executed automatically by cras_client_destroy(). + * Args: + * client - pointer returned from "libcras_client_create". + * Returns: + * 0 on success or if the thread was already stopped, -EINVAL if the client + * isn't valid. + */ +DISABLE_CFI_ICALL +inline int libcras_client_stop(struct libcras_client *client) +{ + return client->stop(client->client_); +} + +/* + * Creates a pinned stream and return the stream id or < 0 on error. + * + * Requires execution of libcras_client_run_thread(), and an active + * connection to the audio server. + * + * Args: + * client - pointer returned from "libcras_client_create". + * dev_idx - Index of the device to attach the newly created stream. + * stream_id_out - On success will be filled with the new stream id. + * Guaranteed to be set before any callbacks are made. + * params - The pointer specifying the parameters for the stream. + * (returned from libcras_stream_params_create) + * Returns: + * 0 on success, negative error code on failure (from errno.h). + */ +DISABLE_CFI_ICALL +inline int libcras_client_add_pinned_stream( + struct libcras_client *client, uint32_t dev_idx, + cras_stream_id_t *stream_id_out, struct libcras_stream_params *params) +{ + return client->add_pinned_stream(client->client_, dev_idx, + stream_id_out, params->params_); +} + +/* + * Removes a currently playing/capturing stream. + * + * Requires execution of libcras_client_run_thread(). + * + * Args: + * client - pointer returned from "libcras_client_create". + * stream_id - ID returned from libcras_client_add_stream to identify + * the stream to remove. + * Returns: + * 0 on success negative error code on failure (from errno.h). + */ +DISABLE_CFI_ICALL +inline int libcras_client_rm_stream(struct libcras_client *client, + cras_stream_id_t stream_id) +{ + return client->rm_stream(client->client_, stream_id); +} + +/* + * Sets the volume scaling factor for the given stream. + * + * Requires execution of cras_client_run_thread(). + * + * Args: + * client - pointer returned from "libcras_client_create". + * stream_id - ID returned from libcras_client_add_stream. + * volume_scaler - 0.0-1.0 the new value to scale this stream by. + * Returns: + * 0 on success negative error code on failure (from errno.h). + */ +DISABLE_CFI_ICALL +inline int libcras_client_set_stream_volume(struct libcras_client *client, + cras_stream_id_t stream_id, + float volume_scaler) +{ + return client->set_stream_volume(client->client_, stream_id, + volume_scaler); +} + +/* + * Gets the current list of audio nodes. + * + * Args: + * client - Pointer returned from "libcras_client_create". + * direction - Input or output. + * nodes - Array that will be filled with libcras_node_info pointers. + * num - Pointer to store the size of the array. + * Returns: + * 0 on success negative error code on failure (from errno.h). + * Remember to call libcras_node_info_array_destroy to free the array. + */ +DISABLE_CFI_ICALL +inline int libcras_client_get_nodes(struct libcras_client *client, + enum CRAS_STREAM_DIRECTION direction, + struct libcras_node_info ***nodes, + size_t *num) +{ + return client->get_nodes(client->client_, direction, nodes, num); +} + +/* + * Gets the default output buffer size. + * Args: + * client - Pointer returned from "libcras_client_create". + * size - The pointer to save the result. + * Returns: + * 0 on success negative error code on failure (from errno.h). + */ +DISABLE_CFI_ICALL +inline int +libcras_client_get_default_output_buffer_size(struct libcras_client *client, + int *size) +{ + return client->get_default_output_buffer_size(client->client_, size); +} + +/* + * Gets the AEC group ID. + * Args: + * client - Pointer returned from "libcras_client_create". + * id - The pointer to save the result. + * Returns: + * 0 on success negative error code on failure (from errno.h). + */ +DISABLE_CFI_ICALL +inline int libcras_client_get_aec_group_id(struct libcras_client *client, + int *id) +{ + return client->get_aec_group_id(client->client_, id); +} + +/* + * Gets whether AEC is supported. + * Args: + * client - Pointer returned from "libcras_client_create". + * supported - The pointer to save the result. + * Returns: + * 0 on success negative error code on failure (from errno.h). + */ +DISABLE_CFI_ICALL +inline int libcras_client_get_aec_supported(struct libcras_client *client, + int *supported) +{ + return client->get_aec_supported(client->client_, supported); +} + +/* + * Gets whether the system is muted. + * Args: + * client - Pointer returned from "libcras_client_create". + * muted - The pointer to save the result. + * Returns: + * 0 on success negative error code on failure (from errno.h). + */ +DISABLE_CFI_ICALL +inline int libcras_client_get_system_muted(struct libcras_client *client, + int *muted) +{ + return client->get_aec_group_id(client->client_, muted); +} + +/* + * Mutes or unmutes the system. + * Args: + * client - Pointer returned from "libcras_client_create". + * mute - 1 is to mute and 0 is to unmute. + * Returns: + * 0 on success negative error code on failure (from errno.h). + */ +DISABLE_CFI_ICALL +inline int libcras_client_set_system_mute(struct libcras_client *client, + int mute) +{ + return client->set_system_mute(client->client_, mute); +} + +/* + * Gets the index of the loopback device. + * Args: + * client - Pointer returned from "libcras_client_create". + * idx - The pointer to save the result. + * Returns: + * 0 on success negative error code on failure (from errno.h). + */ +DISABLE_CFI_ICALL +inline int libcras_client_get_loopback_dev_idx(struct libcras_client *client, + int *idx) +{ + return client->get_loopback_dev_idx(client->client_, idx); +} + +/* + * Creates a new struct to save stream params. + * Returns: + * If success, return a valid libcras_stream_params pointer. Otherwise, + * return NULL. + */ +struct libcras_stream_params *libcras_stream_params_create(); + +/* + * Destroys a stream params instance. + * Args: + * params - The pointer returned from libcras_stream_params_create. + */ +void libcras_stream_params_destroy(struct libcras_stream_params *params); + +/* + * Setup stream configuration parameters. + * Args: + * params - The pointer returned from libcras_stream_params_create. + * direction - Playback(CRAS_STREAM_OUTPUT) or capture(CRAS_STREAM_INPUT). + * buffer_frames - total number of audio frames to buffer (dictates latency). + * cb_threshold - For playback, call back for more data when the buffer + * reaches this level. For capture, this is ignored (Audio callback will + * be called when buffer_frames have been captured). + * stream_type - Media or talk (currently only support "default"). + * client_type - The client type, like Chrome or CrOSVM. + * flags - Currently only used for CRAS_INPUT_STREAM_FLAG. + * user_data - Pointer that will be passed to the callback. + * stream_cb - The audio callback. Called when audio is needed(playback) or + * ready(capture). + * err_cb - Called when there is an error with the stream. + * rate - The sample rate of the audio stream. + * format - The format of the audio stream. + * num_channels - The number of channels of the audio stream. + * Returns: + * 0 on success negative error code on failure (from errno.h). + */ +DISABLE_CFI_ICALL +inline int libcras_stream_params_set( + struct libcras_stream_params *params, + enum CRAS_STREAM_DIRECTION direction, size_t buffer_frames, + size_t cb_threshold, enum CRAS_STREAM_TYPE stream_type, + enum CRAS_CLIENT_TYPE client_type, uint32_t flags, void *user_data, + libcras_stream_cb_t stream_cb, cras_error_cb_t err_cb, size_t rate, + snd_pcm_format_t format, size_t num_channels) +{ + return params->set(params->params_, direction, buffer_frames, + cb_threshold, stream_type, client_type, flags, + user_data, stream_cb, err_cb, rate, format, + num_channels); +} + +/* + * Sets channel layout on given stream parameter. + * Args: + * params - The pointer returned from libcras_stream_params_create. + * length - The length of the array. + * layout - An integer array representing the position of each channel in + * enum CRAS_CHANNEL. + * Returns: + * 0 on success negative error code on failure (from errno.h). + */ +DISABLE_CFI_ICALL +inline int +libcras_stream_params_set_channel_layout(struct libcras_stream_params *params, + int length, const int8_t *layout) +{ + return params->set_channel_layout(params->params_, length, layout); +} + +/* + * Enables AEC on given stream parameter. + * Args: + * params - The pointer returned from libcras_stream_params_create. + * Returns: + * 0 on success negative error code on failure (from errno.h). + */ +DISABLE_CFI_ICALL +inline int +libcras_stream_params_enable_aec(struct libcras_stream_params *params) +{ + params->enable_aec(params->params_); + return 0; +} + +/* + * Gets stream id from the callback data. + * Args: + * data - The pointer passed to the callback function. + * id - The pointer to save the stream id. + * Returns: + * 0 on success negative error code on failure (from errno.h). + */ +DISABLE_CFI_ICALL +inline int +libcras_stream_cb_data_get_stream_id(struct libcras_stream_cb_data *data, + cras_stream_id_t *id) +{ + return data->get_stream_id(data->data_, id); +} + +/* + * Gets stream buf from the callback data. + * Args: + * data - The pointer passed to the callback function. + * buf - The pointer to save the stream buffer. + * Returns: + * 0 on success negative error code on failure (from errno.h). + */ +DISABLE_CFI_ICALL +inline int libcras_stream_cb_data_get_buf(struct libcras_stream_cb_data *data, + uint8_t **buf) +{ + return data->get_buf(data->data_, buf); +} + +/* + * Gets how many frames to read or play from the callback data. + * Args: + * data - The pointer passed to the callback function. + * frames - The pointer to save the number of frames. + * Returns: + * 0 on success negative error code on failure (from errno.h). + */ +DISABLE_CFI_ICALL +inline int +libcras_stream_cb_data_get_frames(struct libcras_stream_cb_data *data, + unsigned int *frames) +{ + return data->get_frames(data->data_, frames); +} + +/* + * Gets the latency from the callback data. + * Args: + * data - The pointer passed to the callback function. + * frames - The timespec pointer to save the latency. + * Returns: + * 0 on success negative error code on failure (from errno.h). + */ +DISABLE_CFI_ICALL +inline int +libcras_stream_cb_data_get_latency(struct libcras_stream_cb_data *data, + struct timespec *latency) +{ + return data->get_latency(data->data_, latency); +} + +/* + * Gets the user data from the callback data. + * Args: + * data - The pointer passed to the callback function. + * frames - The pointer to save the user data. + * Returns: + * 0 on success negative error code on failure (from errno.h). + */ +DISABLE_CFI_ICALL +inline int +libcras_stream_cb_data_get_usr_arg(struct libcras_stream_cb_data *data, + void **user_arg) +{ + return data->get_user_arg(data->data_, user_arg); +} + +/* + * Destroys a node info instance. + * Args: + * node - The libcras_node_info pointer to destroy. + */ +void libcras_node_info_destroy(struct libcras_node_info *node); + +/* + * Destroys a node info array. + * Args: + * nodes - The libcras_node_info pointer array to destroy. + * num - The size of the array. + */ +void libcras_node_info_array_destroy(struct libcras_node_info **nodes, + size_t num); + +/* + * Gets ID from the node info pointer. + * Args: + * node - The node info pointer. (Returned from libcras_client_get_nodes) + * id - The pointer to save ID. + * Returns: + * 0 on success negative error code on failure (from errno.h). + */ +DISABLE_CFI_ICALL +inline int libcras_node_info_get_id(struct libcras_node_info *node, + uint64_t *id) +{ + return node->get_id(node->node_, id); +} + +/* + * Gets device index from the node info pointer. + * Args: + * node - The node info pointer. (Returned from libcras_client_get_nodes) + * dev_idx - The pointer to the device index. + * Returns: + * 0 on success negative error code on failure (from errno.h). + */ +DISABLE_CFI_ICALL +inline int libcras_node_info_get_dev_idx(struct libcras_node_info *node, + uint32_t *dev_idx) +{ + return node->get_dev_idx(node->node_, dev_idx); +} + +/* + * Gets node index from the node info pointer. + * Args: + * node - The node info pointer. (Returned from libcras_client_get_nodes) + * node_idx - The pointer to save the node index. + * Returns: + * 0 on success negative error code on failure (from errno.h). + */ +DISABLE_CFI_ICALL +inline int libcras_node_info_get_node_idx(struct libcras_node_info *node, + uint32_t *node_idx) +{ + return node->get_node_idx(node->node_, node_idx); +} + +/* + * Gets the max supported channels from the node info pointer. + * Args: + * node - The node info pointer. (Returned from libcras_client_get_nodes) + * max_supported_channels - The pointer to save the result. + * Returns: + * 0 on success negative error code on failure (from errno.h). + */ +DISABLE_CFI_ICALL +inline int +libcras_node_info_get_max_supported_channels(struct libcras_node_info *node, + uint32_t *max_supported_channels) +{ + return node->get_max_supported_channels(node->node_, + max_supported_channels); +} + +/* + * Gets whether the node is plugged from the node info pointer. + * Args: + * node - The node info pointer. (Returned from libcras_client_get_nodes) + * plugged - The pointer to save the result. + * Returns: + * 0 on success negative error code on failure (from errno.h). + */ +DISABLE_CFI_ICALL +inline int libcras_node_info_is_plugged(struct libcras_node_info *node, + bool *plugged) +{ + return node->is_plugged(node->node_, plugged); +} + +/* + * Gets whether the node is active from the node info pointer. + * Args: + * node - The node info pointer. (Returned from libcras_client_get_nodes) + * active - The pointer to save the result. + * Returns: + * 0 on success negative error code on failure (from errno.h). + */ +DISABLE_CFI_ICALL +inline int libcras_node_info_is_active(struct libcras_node_info *node, + bool *active) +{ + return node->is_active(node->node_, active); +} + +/* + * Gets device type from the node info pointer. + * Args: + * node - The node info pointer. (Returned from libcras_client_get_nodes) + * type - The pointer to save the device type. + * Returns: + * 0 on success negative error code on failure (from errno.h). + */ +DISABLE_CFI_ICALL +inline int libcras_node_info_get_type(struct libcras_node_info *node, + char **type) +{ + return node->get_type(node->node_, type); +} + +/* + * Gets device name from the node info pointer. + * Args: + * node - The node info pointer. (Returned from libcras_client_get_nodes) + * name - The pointer to save the device name. + * Returns: + * 0 on success negative error code on failure (from errno.h). + */ +DISABLE_CFI_ICALL +inline int libcras_node_info_get_node_name(struct libcras_node_info *node, + char **name) +{ + return node->get_node_name(node->node_, name); +} + +/* + * Gets node name from the node info pointer. + * Args: + * node - The node info pointer. (Returned from libcras_client_get_nodes) + * name - The pointer to save the node name. + * Returns: + * 0 on success negative error code on failure (from errno.h). + */ +DISABLE_CFI_ICALL +inline int libcras_node_info_get_dev_name(struct libcras_node_info *node, + char **name) +{ + return node->get_dev_name(node->node_, name); +} + #ifdef __cplusplus } #endif diff --git a/cras/src/server/audio_thread.c b/cras/src/server/audio_thread.c index cd155e82..48bb0dc2 100644 --- a/cras/src/server/audio_thread.c +++ b/cras/src/server/audio_thread.c @@ -443,7 +443,8 @@ static int thread_add_stream(struct audio_thread *thread, { int rc; - rc = dev_io_append_stream(&thread->open_devs[stream->direction], stream, + rc = dev_io_append_stream(&thread->open_devs[CRAS_STREAM_OUTPUT], + &thread->open_devs[CRAS_STREAM_INPUT], stream, iodevs, num_iodevs); if (rc < 0) return rc; diff --git a/cras/src/server/config/cras_board_config.c b/cras/src/server/config/cras_board_config.c index 14d3fa0c..e36ea3cf 100644 --- a/cras/src/server/config/cras_board_config.c +++ b/cras/src/server/config/cras_board_config.c @@ -14,6 +14,7 @@ static const int32_t AEC_SUPPORTED_DEFAULT = 0; static const int32_t AEC_GROUP_ID_DEFAULT = -1; static const int32_t BLUETOOTH_WBS_ENABLED_INI_DEFAULT = 1; static const int32_t BLUETOOTH_DEPRIORITIZE_WBS_MIC_INI_DEFAULT = 0; +static const int32_t HOTWORD_PAUSE_AT_SUSPEND_DEFAULT = 0; #define CONFIG_NAME "board.ini" #define DEFAULT_OUTPUT_BUF_SIZE_INI_KEY "output:default_output_buffer_size" @@ -22,6 +23,7 @@ static const int32_t BLUETOOTH_DEPRIORITIZE_WBS_MIC_INI_DEFAULT = 0; #define BLUETOOTH_WBS_ENABLED_INI_KEY "bluetooth:wbs_enabled" #define BLUETOOTH_DEPRIORITIZE_WBS_MIC_INI_KEY "bluetooth:deprioritize_wbs_mic" #define UCM_IGNORE_SUFFIX_KEY "ucm:ignore_suffix" +#define HOTWORD_PAUSE_AT_SUSPEND "hotword:pause_at_suspend" void cras_board_config_get(const char *config_path, struct cras_board_config *board_config) @@ -85,6 +87,11 @@ void cras_board_config_get(const char *config_path, syslog(LOG_ERR, "Failed to call strdup: %d", errno); } + snprintf(ini_key, MAX_INI_KEY_LENGTH, HOTWORD_PAUSE_AT_SUSPEND); + ini_key[MAX_INI_KEY_LENGTH] = 0; + board_config->hotword_pause_at_suspend = iniparser_getint( + ini, ini_key, HOTWORD_PAUSE_AT_SUSPEND_DEFAULT); + iniparser_freedict(ini); syslog(LOG_DEBUG, "Loaded ini file %s", ini_name); } diff --git a/cras/src/server/config/cras_board_config.h b/cras/src/server/config/cras_board_config.h index 2ecde265..d4bd8496 100644 --- a/cras/src/server/config/cras_board_config.h +++ b/cras/src/server/config/cras_board_config.h @@ -15,6 +15,7 @@ struct cras_board_config { int32_t bt_wbs_enabled; int32_t deprioritize_bt_wbs_mic; char *ucm_ignore_suffix; + int32_t hotword_pause_at_suspend; }; /* Gets a configuration based on the config file specified. diff --git a/cras/src/server/cras_a2dp_iodev.c b/cras/src/server/cras_a2dp_iodev.c index 6c434758..b8a606e4 100644 --- a/cras/src/server/cras_a2dp_iodev.c +++ b/cras/src/server/cras_a2dp_iodev.c @@ -24,7 +24,6 @@ #include "cras_bt_device.h" #include "cras_iodev.h" #include "cras_util.h" -#include "sfh.h" #include "rtp.h" #include "utlist.h" @@ -644,10 +643,7 @@ struct cras_iodev *a2dp_iodev_create(struct cras_bt_transport *transport) snprintf(iodev->info.name, sizeof(iodev->info.name), "%s", name); iodev->info.name[ARRAY_SIZE(iodev->info.name) - 1] = '\0'; - iodev->info.stable_id = - SuperFastHash(cras_bt_device_object_path(device), - strlen(cras_bt_device_object_path(device)), - strlen(cras_bt_device_object_path(device))); + iodev->info.stable_id = cras_bt_device_get_stable_id(device); iodev->configure_dev = configure_dev; iodev->frames_queued = frames_queued; diff --git a/cras/src/server/cras_alsa_io.c b/cras/src/server/cras_alsa_io.c index da4ef630..275a6810 100644 --- a/cras/src/server/cras_alsa_io.c +++ b/cras/src/server/cras_alsa_io.c @@ -391,6 +391,7 @@ static int open_dev(struct cras_iodev *iodev) snd_pcm_t *handle; int rc; const char *pcm_name = NULL; + int enable_noise_cancellation; if (aio->base.direction == CRAS_STREAM_OUTPUT) { struct alsa_output_node *aout = @@ -412,6 +413,19 @@ static int open_dev(struct cras_iodev *iodev) aio->handle = handle; + /* Enable or disable noise cancellation if it supports. */ + if (aio->ucm && iodev->direction == CRAS_STREAM_INPUT && + ucm_node_noise_cancellation_exists(aio->ucm, + iodev->active_node->name)) { + enable_noise_cancellation = + cras_system_get_noise_cancellation_enabled(); + rc = ucm_enable_node_noise_cancellation( + aio->ucm, iodev->active_node->name, + enable_noise_cancellation); + if (rc < 0) + return rc; + } + return 0; } @@ -2021,6 +2035,17 @@ static int get_valid_frames(struct cras_iodev *odev, struct timespec *tstamp) return 0; } +static int support_noise_cancellation(const struct cras_iodev *iodev) +{ + struct alsa_io *aio = (struct alsa_io *)iodev; + + if (!aio->ucm || !iodev->active_node) + return 0; + + return ucm_node_noise_cancellation_exists(aio->ucm, + iodev->active_node->name); +} + /* * Exported Interface. */ @@ -2098,6 +2123,7 @@ alsa_iodev_create(size_t card_index, const char *card_name, size_t device_index, iodev->get_num_severe_underruns = get_num_severe_underruns; iodev->get_valid_frames = get_valid_frames; iodev->set_swap_mode_for_node = cras_iodev_dsp_set_swap_mode_for_node; + iodev->support_noise_cancellation = support_noise_cancellation; if (card_type == ALSA_CARD_TYPE_USB) iodev->min_buffer_level = USB_EXTRA_BUFFER_FRAMES; @@ -2418,12 +2444,10 @@ static int alsa_iodev_set_active_node(struct cras_iodev *iodev, unsigned dev_enabled) { struct alsa_io *aio = (struct alsa_io *)iodev; + int rc = 0; - if (iodev->active_node == ionode) { - enable_active_ucm(aio, dev_enabled); - init_device_settings(aio); - return 0; - } + if (iodev->active_node == ionode) + goto skip; /* Disable jack ucm before switching node. */ enable_active_ucm(aio, 0); @@ -2433,7 +2457,16 @@ static int alsa_iodev_set_active_node(struct cras_iodev *iodev, cras_iodev_set_active_node(iodev, ionode); aio->base.dsp_name = get_active_dsp_name(aio); cras_iodev_update_dsp(iodev); +skip: enable_active_ucm(aio, dev_enabled); + if (ionode->type == CRAS_NODE_TYPE_HOTWORD) { + if (dev_enabled) { + rc = ucm_enable_hotword_model(aio->ucm); + if (rc < 0) + return rc; + } else + ucm_disable_all_hotword_models(aio->ucm); + } /* Setting the volume will also unmute if the system isn't muted. */ init_device_settings(aio); return 0; diff --git a/cras/src/server/cras_alsa_mixer.c b/cras/src/server/cras_alsa_mixer.c index 10705573..3379d959 100644 --- a/cras/src/server/cras_alsa_mixer.c +++ b/cras/src/server/cras_alsa_mixer.c @@ -943,7 +943,7 @@ void cras_alsa_mixer_set_dBFS(struct cras_alsa_mixer *cras_mixer, long dBFS, assert(cras_mixer); /* dBFS is normally < 0 to specify the attenuation from max. max is the - * combined max of the master controls and the current output. + * combined max of the main controls and the current output. */ to_set = dBFS + cras_mixer->max_volume_dB; if (cras_alsa_mixer_has_volume(mixer_output)) diff --git a/cras/src/server/cras_alsa_mixer.h b/cras/src/server/cras_alsa_mixer.h index 3f730cf5..878fbe54 100644 --- a/cras/src/server/cras_alsa_mixer.h +++ b/cras/src/server/cras_alsa_mixer.h @@ -147,7 +147,7 @@ void cras_alsa_mixer_set_mute(struct cras_alsa_mixer *cras_mixer, int muted, * Args: * cras_mixer - Mixer to set the volume in. * muted - 1 if muted, 0 if not. - * mixer_input - The mixer input to mute if no master mute. + * mixer_input - The mixer input to mute if no card mute. */ void cras_alsa_mixer_set_capture_mute(struct cras_alsa_mixer *cras_mixer, int muted, diff --git a/cras/src/server/cras_alsa_ucm.c b/cras/src/server/cras_alsa_ucm.c index 9759a50f..3e46f6a9 100644 --- a/cras/src/server/cras_alsa_ucm.c +++ b/cras/src/server/cras_alsa_ucm.c @@ -22,7 +22,6 @@ static const char override_type_name_var[] = "OverrideNodeType"; static const char dsp_name_var[] = "DspName"; static const char playback_mixer_elem_var[] = "PlaybackMixerElem"; static const char capture_mixer_elem_var[] = "CaptureMixerElem"; -static const char swap_mode_suffix[] = "Swap Mode"; static const char min_buffer_level_var[] = "MinBufferLevel"; static const char dma_period_var[] = "DmaPeriodMicrosecs"; static const char disable_software_volume[] = "DisableSoftwareVolume"; @@ -38,6 +37,11 @@ static const char dependent_device_name_var[] = "DependentPCM"; static const char preempt_hotword_var[] = "PreemptHotword"; static const char echo_reference_dev_name_var[] = "EchoReferenceDev"; +/* SectionModifier prefixes and suffixes. */ +static const char hotword_model_prefix[] = "Hotword Model"; +static const char swap_mode_suffix[] = "Swap Mode"; +static const char noise_cancellation_suffix[] = "Noise Cancellation"; + /* * Set this value in a SectionDevice to specify the intrinsic sensitivity in * 0.01 dBFS/Pa. It currently only supports input devices. You should get the @@ -54,7 +58,6 @@ static const char intrinsic_sensitivity_var[] = "IntrinsicSensitivity"; * 0.01 dB. */ static const char default_node_gain[] = "DefaultNodeGain"; -static const char hotword_model_prefix[] = "Hotword Model"; static const char fully_specified_ucm_var[] = "FullySpecifiedUCM"; static const char main_volume_names[] = "MainVolumeNames"; @@ -64,6 +67,8 @@ static const char *use_case_verbs[] = { "Speech", "Pro Audio", "Accessibility", }; +static const size_t max_section_name_len = 100; + /* Represents a list of section names found in UCM. */ struct section_name { const char *name; @@ -72,9 +77,10 @@ struct section_name { struct cras_use_case_mgr { snd_use_case_mgr_t *mgr; - const char *name; + char *name; unsigned int avail_use_cases; enum CRAS_STREAM_TYPE use_case; + char *hotword_modifier; }; static inline const char *uc_verb(struct cras_use_case_mgr *mgr) @@ -376,6 +382,21 @@ static struct mixer_name *ucm_get_mixer_names(struct cras_use_case_mgr *mgr, return names; } +/* Gets the modifier name of Noise Cancellation for the given node_name. */ +static void ucm_get_node_noise_cancellation_name(const char *node_name, + char *mod_name) +{ + size_t len = + strlen(node_name) + 1 + strlen(noise_cancellation_suffix) + 1; + if (len > max_section_name_len) { + syslog(LOG_ERR, + "Length of the given section name is %zu > %zu(max)", + len, max_section_name_len); + len = max_section_name_len; + } + snprintf(mod_name, len, "%s %s", node_name, noise_cancellation_suffix); +} + /* Exported Interface */ struct cras_use_case_mgr *ucm_create(const char *name) @@ -394,6 +415,10 @@ struct cras_use_case_mgr *ucm_create(const char *name) if (!mgr) return NULL; + mgr->name = strdup(name); + if (!mgr->name) + goto cleanup; + rc = snd_use_case_mgr_open(&mgr->mgr, name); if (rc) { syslog(LOG_WARNING, "Can not open ucm for card %s, rc = %d", @@ -401,8 +426,8 @@ struct cras_use_case_mgr *ucm_create(const char *name) goto cleanup; } - mgr->name = name; mgr->avail_use_cases = 0; + mgr->hotword_modifier = NULL; num_verbs = snd_use_case_get_list(mgr->mgr, "_verbs", &list); for (i = 0; i < num_verbs; i += 2) { for (j = 0; j < CRAS_STREAM_NUM_TYPES; ++j) { @@ -424,6 +449,7 @@ struct cras_use_case_mgr *ucm_create(const char *name) cleanup_mgr: snd_use_case_mgr_close(mgr->mgr); cleanup: + free(mgr->name); free(mgr); return NULL; } @@ -431,6 +457,8 @@ cleanup: void ucm_destroy(struct cras_use_case_mgr *mgr) { snd_use_case_mgr_close(mgr->mgr); + free(mgr->hotword_modifier); + free(mgr->name); free(mgr); } @@ -487,6 +515,51 @@ int ucm_enable_swap_mode(struct cras_use_case_mgr *mgr, const char *node_name, return rc; } +int ucm_node_noise_cancellation_exists(struct cras_use_case_mgr *mgr, + const char *node_name) +{ + char *node_modifier_name = NULL; + int exists; + + node_modifier_name = (char *)malloc(max_section_name_len); + if (!node_modifier_name) + return 0; + ucm_get_node_noise_cancellation_name(node_name, node_modifier_name); + exists = ucm_mod_exists_with_name(mgr, node_modifier_name); + free((void *)node_modifier_name); + return exists; +} + +int ucm_enable_node_noise_cancellation(struct cras_use_case_mgr *mgr, + const char *node_name, int enable) +{ + char *node_modifier_name = NULL; + int rc; + + node_modifier_name = (char *)malloc(max_section_name_len); + if (!node_modifier_name) + return -ENOMEM; + ucm_get_node_noise_cancellation_name(node_name, node_modifier_name); + if (!ucm_mod_exists_with_name(mgr, node_modifier_name)) { + syslog(LOG_ERR, "Can not find modifier %s.", + node_modifier_name); + free((void *)node_modifier_name); + return -EPERM; + } + if (modifier_enabled(mgr, node_modifier_name) == !!enable) { + syslog(LOG_DEBUG, "Modifier %s is already %s.", + node_modifier_name, enable ? "enabled" : "disabled"); + free((void *)node_modifier_name); + return 0; + } + + syslog(LOG_DEBUG, "UCM %s Modifier %s", enable ? "enable" : "disable", + node_modifier_name); + rc = ucm_set_modifier_enabled(mgr, node_modifier_name, enable); + free((void *)node_modifier_name); + return rc; +} + int ucm_set_enabled(struct cras_use_case_mgr *mgr, const char *dev, int enable) { int rc; @@ -984,14 +1057,61 @@ char *ucm_get_hotword_models(struct cras_use_case_mgr *mgr) return models; } -int ucm_set_hotword_model(struct cras_use_case_mgr *mgr, const char *model) +void ucm_disable_all_hotword_models(struct cras_use_case_mgr *mgr) { const char **list; int num_enmods, mod_idx; - char *model_mod = NULL; + + if (!mgr) + return; + + /* Disable all currently enabled hotword model modifiers. */ + num_enmods = snd_use_case_get_list(mgr->mgr, "_enamods", &list); + if (num_enmods <= 0) + return; + + for (mod_idx = 0; mod_idx < num_enmods; mod_idx++) { + if (!strncmp(list[mod_idx], hotword_model_prefix, + strlen(hotword_model_prefix))) + ucm_set_modifier_enabled(mgr, list[mod_idx], 0); + } + snd_use_case_free_list(list, num_enmods); +} + +int ucm_enable_hotword_model(struct cras_use_case_mgr *mgr) +{ + if (mgr->hotword_modifier) + return ucm_set_modifier_enabled(mgr, mgr->hotword_modifier, 1); + return -EINVAL; +} + +static int ucm_is_modifier_enabled(struct cras_use_case_mgr *mgr, + char *modifier, long *value) +{ + int rc; + char *id; + size_t len = strlen(modifier) + 11 + 1; + + id = (char *)malloc(len); + + if (!id) + return -ENOMEM; + + snprintf(id, len, "_modstatus/%s", modifier); + rc = snd_use_case_geti(mgr->mgr, id, value); + free(id); + return rc; +} + +int ucm_set_hotword_model(struct cras_use_case_mgr *mgr, const char *model) +{ + char *model_mod; + long mod_status = 0; size_t model_mod_size = strlen(model) + 1 + strlen(hotword_model_prefix) + 1; + model_mod = (char *)malloc(model_mod_size); + if (!model_mod) return -ENOMEM; snprintf(model_mod, model_mod_size, "%s %s", hotword_model_prefix, @@ -1001,21 +1121,16 @@ int ucm_set_hotword_model(struct cras_use_case_mgr *mgr, const char *model) return -EINVAL; } - /* Disable all currently enabled horword model modifiers. */ - num_enmods = snd_use_case_get_list(mgr->mgr, "_enamods", &list); - if (num_enmods <= 0) - goto enable_mod; - - for (mod_idx = 0; mod_idx < num_enmods; mod_idx++) { - if (!strncmp(list[mod_idx], hotword_model_prefix, - strlen(hotword_model_prefix))) - ucm_set_modifier_enabled(mgr, list[mod_idx], 0); - } - snd_use_case_free_list(list, num_enmods); + /* If check failed, just move on, dont fail incoming model */ + if (mgr->hotword_modifier) + ucm_is_modifier_enabled(mgr, mgr->hotword_modifier, + &mod_status); -enable_mod: - ucm_set_modifier_enabled(mgr, model_mod, 1); - free((void *)model_mod); + ucm_disable_all_hotword_models(mgr); + free(mgr->hotword_modifier); + mgr->hotword_modifier = model_mod; + if (mod_status) + return ucm_enable_hotword_model(mgr); return 0; } diff --git a/cras/src/server/cras_alsa_ucm.h b/cras/src/server/cras_alsa_ucm.h index 99a8b440..55c3cf62 100644 --- a/cras/src/server/cras_alsa_ucm.h +++ b/cras/src/server/cras_alsa_ucm.h @@ -67,6 +67,28 @@ int ucm_swap_mode_exists(struct cras_use_case_mgr *mgr); int ucm_enable_swap_mode(struct cras_use_case_mgr *mgr, const char *node_name, int enable); +/* Checks if modifier of noise cancellation for given node_name exists in ucm. + * Args: + * mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create. + * node_name - The node name. + * Returns: + * 1 if it exists, 0 otherwise. + */ +int ucm_node_noise_cancellation_exists(struct cras_use_case_mgr *mgr, + const char *node_name); + +/* Enables or disables noise cancellation for the given node_name. First checks + * if the modifier is already enabled or disabled. + * Args: + * mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create. + * node_name - The node name. + * enable - Enable device if non-zero. + * Returns: + * 0 on success or negative error code on failure. + */ +int ucm_enable_node_noise_cancellation(struct cras_use_case_mgr *mgr, + const char *node_name, int enable); + /* Enables or disables a UCM device. First checks if the device is already * enabled or disabled. * Args: @@ -306,11 +328,26 @@ char *ucm_get_hotword_models(struct cras_use_case_mgr *mgr); /* Sets the desired hotword model. * Args: * mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create. + * model - locale for model * Returns: * 0 on success or negative error code on failure. */ int ucm_set_hotword_model(struct cras_use_case_mgr *mgr, const char *model); +/* Enable previously set hotword modifier + * Args: + * mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create. + * Returns: + * 0 on success or negative error code on failure. + */ +int ucm_enable_hotword_model(struct cras_use_case_mgr *mgr); + +/* Disable all hotword model modifiers + * Args: + * mgr - The cras_use_case_mgr pointer returned from alsa_ucm_create. + */ +void ucm_disable_all_hotword_models(struct cras_use_case_mgr *mgr); + /* Checks if this card has fully specified UCM config. * * Args: diff --git a/cras/src/server/cras_apm_list.c b/cras/src/server/cras_apm_list.c index ac57d86a..ab891137 100644 --- a/cras/src/server/cras_apm_list.c +++ b/cras/src/server/cras_apm_list.c @@ -61,9 +61,10 @@ * stream. * work_queue - A task queue instance created and destroyed by * libwebrtc_apm. - * use_tuned_settings - True if this APM uses settings tuned specifically - * for this hardware in AEC use case. Otherwise it uses the generic - * settings like run inside browser. + * is_aec_use_case - True if the input and output devices pair is in the + * typical AEC use case. This flag decides whether to use settings + * tuned specifically for this hardware if exists. Otherwise it uses + * the generic settings like run inside browser. */ struct cras_apm { webrtc_apm apm_ptr; @@ -74,7 +75,7 @@ struct cras_apm { struct cras_audio_format fmt; struct cras_audio_area *area; void *work_queue; - bool use_tuned_settings; + bool is_aec_use_case; struct cras_apm *prev, *next; }; @@ -239,13 +240,12 @@ 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. + /* Using the format from dev_fmt is dangerous because input device + * could have wild configurations like unuse the 1st channel and + * connects 2nd channel to the only mic. Data in the first channel + * is what APM cares about so always construct a new channel layout + * containing subset of original channels that matches either FL, FR, + * or FC. * TODO(hychao): extend the logic when we have a stream that wants * to record channels like RR(rear right). */ @@ -290,16 +290,16 @@ struct cras_apm *cras_apm_list_add_apm(struct cras_apm_list *list, /* Use tuned settings only when the forward dev(capture) and reverse * dev(playback) both are in typical AEC use case. */ - apm->use_tuned_settings = is_aec_use_case; + apm->is_aec_use_case = is_aec_use_case; if (rmodule->odev) { - apm->use_tuned_settings &= + apm->is_aec_use_case &= cras_iodev_is_aec_use_case(rmodule->odev->active_node); } /* Use the configs tuned specifically for internal device. Otherwise * just pass NULL so every other settings will be default. */ apm->apm_ptr = - apm->use_tuned_settings ? + apm->is_aec_use_case ? webrtc_apm_create(apm->fmt.num_channels, apm->fmt.frame_rate, aec_ini, apm_ini) : @@ -691,7 +691,9 @@ struct cras_audio_format *cras_apm_list_get_format(struct cras_apm *apm) bool cras_apm_list_get_use_tuned_settings(struct cras_apm *apm) { - return apm->use_tuned_settings; + /* If input and output devices in AEC use case, plus that a + * tuned setting is provided. */ + return apm->is_aec_use_case && (aec_ini || apm_ini); } void cras_apm_list_set_aec_dump(struct cras_apm_list *list, void *dev_ptr, diff --git a/cras/src/server/cras_bt_battery_provider.c b/cras/src/server/cras_bt_battery_provider.c new file mode 100644 index 00000000..13e6590f --- /dev/null +++ b/cras/src/server/cras_bt_battery_provider.c @@ -0,0 +1,371 @@ +/* Copyright 2020 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include <dbus/dbus.h> +#include <errno.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> + +#include "cras_bt_adapter.h" +#include "cras_bt_battery_provider.h" +#include "cras_bt_constants.h" +#include "cras_dbus_util.h" +#include "cras_observer.h" +#include "utlist.h" + +/* CRAS registers one battery provider to BlueZ, so we use a singleton. */ +static struct cras_bt_battery_provider battery_provider = { + .object_path = CRAS_DEFAULT_BATTERY_PROVIDER, + .interface = BLUEZ_INTERFACE_BATTERY_PROVIDER, + .conn = NULL, + .is_registered = false, + .observer = NULL, + .batteries = NULL, +}; + +static int cmp_battery_address(const struct cras_bt_battery *battery, + const char *address) +{ + return strcmp(battery->address, address); +} + +static void replace_colon_with_underscore(char *str) +{ + for (int i = 0; str[i]; i++) { + if (str[i] == ':') + str[i] = '_'; + } +} + +/* Converts address XX:XX:XX:XX:XX:XX to Battery Provider object path: + * /org/chromium/Cras/Bluetooth/BatteryProvider/XX_XX_XX_XX_XX_XX + */ +static char *address_to_battery_path(const char *address) +{ + char *object_path = malloc(strlen(CRAS_DEFAULT_BATTERY_PROVIDER) + + strlen(address) + 2); + + sprintf(object_path, "%s/%s", CRAS_DEFAULT_BATTERY_PROVIDER, address); + replace_colon_with_underscore(object_path); + + return object_path; +} + +/* Converts address XX:XX:XX:XX:XX:XX to device object path: + * /org/bluez/hci0/dev_XX_XX_XX_XX_XX_XX + */ +static char *address_to_device_path(const char *address) +{ + char *object_path = malloc(strlen(CRAS_DEFAULT_BATTERY_PREFIX) + + strlen(address) + 1); + + sprintf(object_path, "%s%s", CRAS_DEFAULT_BATTERY_PREFIX, address); + replace_colon_with_underscore(object_path); + + return object_path; +} + +static struct cras_bt_battery *battery_new(const char *address, uint32_t level) +{ + struct cras_bt_battery *battery; + + battery = calloc(1, sizeof(struct cras_bt_battery)); + battery->address = strdup(address); + battery->object_path = address_to_battery_path(address); + battery->device_path = address_to_device_path(address); + battery->level = level; + + return battery; +} + +static void battery_free(struct cras_bt_battery *battery) +{ + if (battery->address) + free(battery->address); + if (battery->object_path) + free(battery->object_path); + if (battery->device_path) + free(battery->device_path); + free(battery); +} + +static void populate_battery_properties(DBusMessageIter *iter, + const struct cras_bt_battery *battery) +{ + DBusMessageIter dict, entry, variant; + const char *property_percentage = "Percentage"; + const char *property_device = "Device"; + uint8_t level = battery->level; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{sv}", &dict); + + dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, + &entry); + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, + &property_percentage); + dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, + DBUS_TYPE_BYTE_AS_STRING, &variant); + dbus_message_iter_append_basic(&variant, DBUS_TYPE_BYTE, &level); + dbus_message_iter_close_container(&entry, &variant); + dbus_message_iter_close_container(&dict, &entry); + + dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, + &entry); + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, + &property_device); + dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, + DBUS_TYPE_OBJECT_PATH_AS_STRING, + &variant); + dbus_message_iter_append_basic(&variant, DBUS_TYPE_OBJECT_PATH, + &battery->device_path); + dbus_message_iter_close_container(&entry, &variant); + dbus_message_iter_close_container(&dict, &entry); + + dbus_message_iter_close_container(iter, &dict); +} + +/* Creates a new battery object and exposes it on D-Bus. */ +static struct cras_bt_battery * +get_or_create_battery(struct cras_bt_battery_provider *provider, + const char *address, uint32_t level) +{ + struct cras_bt_battery *battery; + DBusMessage *msg; + DBusMessageIter iter, dict, entry; + + LL_SEARCH(provider->batteries, battery, address, cmp_battery_address); + + if (battery) + return battery; + + syslog(LOG_DEBUG, "Creating new battery for %s", address); + + battery = battery_new(address, level); + LL_APPEND(provider->batteries, battery); + + msg = dbus_message_new_signal(CRAS_DEFAULT_BATTERY_PROVIDER, + DBUS_INTERFACE_OBJECT_MANAGER, + DBUS_SIGNAL_INTERFACES_ADDED); + + dbus_message_iter_init_append(msg, &iter); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, + &battery->object_path); + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sa{sv}}", + &dict); + dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, + &entry); + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, + &provider->interface); + populate_battery_properties(&entry, battery); + dbus_message_iter_close_container(&dict, &entry); + dbus_message_iter_close_container(&iter, &dict); + + if (!dbus_connection_send(provider->conn, msg, NULL)) { + syslog(LOG_ERR, + "Error sending " DBUS_SIGNAL_INTERFACES_ADDED " signal"); + } + + dbus_message_unref(msg); + + return battery; +} + +/* Updates the level of a battery object and signals it on D-Bus. */ +static void +update_battery_level(const struct cras_bt_battery_provider *provider, + struct cras_bt_battery *battery, uint32_t level) +{ + DBusMessage *msg; + DBusMessageIter iter; + + if (battery->level == level) + return; + + battery->level = level; + + msg = dbus_message_new_signal(battery->object_path, + DBUS_INTERFACE_PROPERTIES, + DBUS_SIGNAL_PROPERTIES_CHANGED); + + dbus_message_iter_init_append(msg, &iter); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, + &provider->interface); + populate_battery_properties(&iter, battery); + + if (!dbus_connection_send(provider->conn, msg, NULL)) { + syslog(LOG_ERR, "Error sending " DBUS_SIGNAL_PROPERTIES_CHANGED + " signal"); + } + + dbus_message_unref(msg); +} + +/* Invoked when HFP sends an alert about a battery value change. */ +static void on_bt_battery_changed(void *context, const char *address, + uint32_t level) +{ + struct cras_bt_battery_provider *provider = context; + + syslog(LOG_DEBUG, "Battery changed for address %s, level %d", address, + level); + + if (!provider->is_registered) { + syslog(LOG_WARNING, "Received battery level update while " + "battery provider is not registered"); + return; + } + + struct cras_bt_battery *battery = + get_or_create_battery(provider, address, level); + + update_battery_level(provider, battery, level); +} + +/* Invoked when we receive a D-Bus return of RegisterBatteryProvider from + * BlueZ. + */ +static void +cras_bt_on_battery_provider_registered(DBusPendingCall *pending_call, + void *data) +{ + DBusMessage *reply; + struct cras_bt_battery_provider *provider = data; + struct cras_observer_ops observer_ops; + + reply = dbus_pending_call_steal_reply(pending_call); + dbus_pending_call_unref(pending_call); + + if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) { + syslog(LOG_ERR, "RegisterBatteryProvider returned error: %s", + dbus_message_get_error_name(reply)); + dbus_message_unref(reply); + return; + } + + syslog(LOG_INFO, "RegisterBatteryProvider succeeded"); + + provider->is_registered = true; + + memset(&observer_ops, 0, sizeof(observer_ops)); + observer_ops.bt_battery_changed = on_bt_battery_changed; + provider->observer = cras_observer_add(&observer_ops, provider); + + dbus_message_unref(reply); +} + +int cras_bt_register_battery_provider(DBusConnection *conn, + const struct cras_bt_adapter *adapter) +{ + const char *adapter_path; + DBusMessage *method_call; + DBusMessageIter message_iter; + DBusPendingCall *pending_call; + + if (battery_provider.is_registered) { + syslog(LOG_ERR, "Battery Provider already registered"); + return -EBUSY; + } + + if (battery_provider.conn) + dbus_connection_unref(battery_provider.conn); + + battery_provider.conn = conn; + dbus_connection_ref(battery_provider.conn); + + adapter_path = cras_bt_adapter_object_path(adapter); + method_call = dbus_message_new_method_call( + BLUEZ_SERVICE, adapter_path, + BLUEZ_INTERFACE_BATTERY_PROVIDER_MANAGER, + "RegisterBatteryProvider"); + if (!method_call) + return -ENOMEM; + + dbus_message_iter_init_append(method_call, &message_iter); + dbus_message_iter_append_basic(&message_iter, DBUS_TYPE_OBJECT_PATH, + &battery_provider.object_path); + + if (!dbus_connection_send_with_reply(conn, method_call, &pending_call, + DBUS_TIMEOUT_USE_DEFAULT)) { + dbus_message_unref(method_call); + return -ENOMEM; + } + + dbus_message_unref(method_call); + + if (!pending_call) + return -EIO; + + if (!dbus_pending_call_set_notify( + pending_call, cras_bt_on_battery_provider_registered, + &battery_provider, NULL)) { + dbus_pending_call_cancel(pending_call); + dbus_pending_call_unref(pending_call); + return -ENOMEM; + } + + return 0; +} + +/* Removes a battery object and signals the removal on D-Bus as well. */ +static void cleanup_battery(struct cras_bt_battery_provider *provider, + struct cras_bt_battery *battery) +{ + DBusMessage *msg; + DBusMessageIter iter, entry; + + if (!battery) + return; + + LL_DELETE(provider->batteries, battery); + + msg = dbus_message_new_signal(CRAS_DEFAULT_BATTERY_PROVIDER, + DBUS_INTERFACE_OBJECT_MANAGER, + DBUS_SIGNAL_INTERFACES_REMOVED); + + dbus_message_iter_init_append(msg, &iter); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, + &battery->object_path); + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_STRING_AS_STRING, &entry); + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, + &provider->interface); + dbus_message_iter_close_container(&iter, &entry); + + if (!dbus_connection_send(provider->conn, msg, NULL)) { + syslog(LOG_ERR, "Error sending " DBUS_SIGNAL_INTERFACES_REMOVED + " signal"); + } + + dbus_message_unref(msg); + + battery_free(battery); +} + +void cras_bt_battery_provider_reset() +{ + struct cras_bt_battery *battery; + + syslog(LOG_INFO, "Resetting battery provider"); + + if (!battery_provider.is_registered) + return; + + battery_provider.is_registered = false; + + LL_FOREACH (battery_provider.batteries, battery) { + cleanup_battery(&battery_provider, battery); + } + + if (battery_provider.conn) { + dbus_connection_unref(battery_provider.conn); + battery_provider.conn = NULL; + } + + if (battery_provider.observer) { + cras_observer_remove(battery_provider.observer); + battery_provider.observer = NULL; + } +} diff --git a/cras/src/server/cras_bt_battery_provider.h b/cras/src/server/cras_bt_battery_provider.h new file mode 100644 index 00000000..1998cd78 --- /dev/null +++ b/cras/src/server/cras_bt_battery_provider.h @@ -0,0 +1,47 @@ +/* Copyright 2020 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef CRAS_BT_BATTERY_PROVIDER_H_ +#define CRAS_BT_BATTERY_PROVIDER_H_ + +#include <dbus/dbus.h> +#include <stdbool.h> + +#include "cras_bt_adapter.h" + +/* Object to represent a battery that is exposed to BlueZ. */ +struct cras_bt_battery { + char *address; + char *object_path; + char *device_path; + uint32_t level; + struct cras_bt_battery *next; +}; + +/* Object to register as battery provider so that bluetoothd will monitor + * battery objects that we expose. + */ +struct cras_bt_battery_provider { + const char *object_path; + const char *interface; + DBusConnection *conn; + bool is_registered; + struct cras_observer_client *observer; + struct cras_bt_battery *batteries; +}; + +/* Registers battery provider to bluetoothd. This is used when a Bluetooth + * adapter got enumerated. + * Args: + * conn - The D-Bus connection. + * adapter - The enumerated bluetooth adapter. + */ +int cras_bt_register_battery_provider(DBusConnection *conn, + const struct cras_bt_adapter *adapter); + +/* Resets internal state of battery provider. */ +void cras_bt_battery_provider_reset(); + +#endif /* CRAS_BT_BATTERY_PROVIDER_H_ */ diff --git a/cras/src/server/cras_bt_constants.h b/cras/src/server/cras_bt_constants.h index 618ac872..318aecab 100644 --- a/cras/src/server/cras_bt_constants.h +++ b/cras/src/server/cras_bt_constants.h @@ -9,6 +9,9 @@ #define BLUEZ_SERVICE "org.bluez" #define BLUEZ_INTERFACE_ADAPTER "org.bluez.Adapter1" +#define BLUEZ_INTERFACE_BATTERY_PROVIDER "org.bluez.BatteryProvider1" +#define BLUEZ_INTERFACE_BATTERY_PROVIDER_MANAGER \ + "org.bluez.BatteryProviderManager1" #define BLUEZ_INTERFACE_DEVICE "org.bluez.Device1" #define BLUEZ_INTERFACE_MEDIA "org.bluez.Media1" #define BLUEZ_INTERFACE_MEDIA_ENDPOINT "org.bluez.MediaEndpoint1" @@ -21,6 +24,9 @@ #ifndef DBUS_INTERFACE_OBJECT_MANAGER #define DBUS_INTERFACE_OBJECT_MANAGER "org.freedesktop.DBus.ObjectManager" #endif +#define DBUS_SIGNAL_INTERFACES_ADDED "InterfacesAdded" +#define DBUS_SIGNAL_INTERFACES_REMOVED "InterfacesRemoved" +#define DBUS_SIGNAL_PROPERTIES_CHANGED "PropertiesChanged" /* UUIDs taken from lib/uuid.h in the BlueZ source */ #define HSP_HS_UUID "00001108-0000-1000-8000-00805f9b34fb" @@ -49,6 +55,10 @@ #define CRAS_PLAYER_IDENTITY_DEFAULT "DefaultPlayer" #define CRAS_PLAYER_METADATA_SIZE_MAX 128 * sizeof(char) +#define CRAS_DEFAULT_BATTERY_PROVIDER \ + "/org/chromium/Cras/Bluetooth/BatteryProvider" +#define CRAS_DEFAULT_BATTERY_PREFIX "/org/bluez/hci0/dev_" + /* Instead of letting CRAS obtain the A2DP streaming packet size (a.k.a. AVDTP * MTU) from BlueZ Media Transport, force the packet size to the default L2CAP * packet size. This prevent the audio peripheral device to negotiate a larger diff --git a/cras/src/server/cras_bt_device.c b/cras/src/server/cras_bt_device.c index 70c87479..6b06dd13 100644 --- a/cras/src/server/cras_bt_device.c +++ b/cras/src/server/cras_bt_device.c @@ -34,6 +34,7 @@ #include "cras_server_metrics.h" #include "cras_system_state.h" #include "cras_tm.h" +#include "sfh.h" #include "utlist.h" /* @@ -91,6 +92,7 @@ static const unsigned int CRAS_SUPPORTED_PROFILES = * sco_fd - The file descriptor of the SCO connection. * sco_ref_count - The reference counts of the SCO connection. * suspend_reason - The reason code for why suspend is scheduled. + * stable_id - The unique and persistent id of this bt_device. */ struct cras_bt_device { DBusConnection *conn; @@ -115,6 +117,7 @@ struct cras_bt_device { int sco_fd; size_t sco_ref_count; enum cras_bt_device_suspend_reason suspend_reason; + unsigned int stable_id; struct cras_bt_device *prev, *next; }; @@ -174,6 +177,9 @@ struct cras_bt_device *cras_bt_device_create(DBusConnection *conn, free(device); return NULL; } + device->stable_id = + SuperFastHash(device->object_path, strlen(device->object_path), + strlen(device->object_path)); DL_APPEND(devices, device); @@ -343,6 +349,11 @@ const char *cras_bt_device_object_path(const struct cras_bt_device *device) return device->object_path; } +int cras_bt_device_get_stable_id(const struct cras_bt_device *device) +{ + return device->stable_id; +} + struct cras_bt_adapter * cras_bt_device_adapter(const struct cras_bt_device *device) { @@ -704,10 +715,14 @@ static void bt_device_cancel_suspend(struct cras_bt_device *device); void cras_bt_device_set_connected(struct cras_bt_device *device, int value) { struct cras_tm *tm = cras_system_state_get_tm(); - if (device->connected || value) - BTLOG(btlog, BT_DEV_CONNECTED_CHANGE, device->profiles, value); + if (!device->connected && value) { + BTLOG(btlog, BT_DEV_CONNECTED, device->profiles, + device->stable_id); + } if (device->connected && !value) { + BTLOG(btlog, BT_DEV_DISCONNECTED, device->profiles, + device->stable_id); cras_bt_profile_on_device_disconnected(device); /* Device is disconnected, resets connected profiles and the * suspend timer which scheduled earlier. */ diff --git a/cras/src/server/cras_bt_device.h b/cras/src/server/cras_bt_device.h index 4202bc93..9d3a2b9e 100644 --- a/cras/src/server/cras_bt_device.h +++ b/cras/src/server/cras_bt_device.h @@ -50,6 +50,10 @@ void cras_bt_device_reset(); struct cras_bt_device *cras_bt_device_get(const char *object_path); const char *cras_bt_device_object_path(const struct cras_bt_device *device); + +/* Gets the stable id of given cras_bt_device. */ +int cras_bt_device_get_stable_id(const struct cras_bt_device *device); + struct cras_bt_adapter * cras_bt_device_adapter(const struct cras_bt_device *device); const char *cras_bt_device_address(const struct cras_bt_device *device); diff --git a/cras/src/server/cras_bt_io.c b/cras/src/server/cras_bt_io.c index 9f5c2f79..acdca809 100644 --- a/cras/src/server/cras_bt_io.c +++ b/cras/src/server/cras_bt_io.c @@ -527,10 +527,7 @@ struct cras_iodev *cras_bt_io_create(struct cras_bt_device *device, active->base.idx = btio->next_node_id++; active->base.type = dev->active_node->type; active->base.volume = 100; - active->base.stable_id = - SuperFastHash(cras_bt_device_object_path(device), - strlen(cras_bt_device_object_path(device)), - strlen(cras_bt_device_object_path(device))); + active->base.stable_id = cras_bt_device_get_stable_id(device); active->base.ui_gain_scaler = 1.0f; /* * If the same headset is connected in wideband mode, we shall assign diff --git a/cras/src/server/cras_bt_manager.c b/cras/src/server/cras_bt_manager.c index 77e8079c..a7103406 100644 --- a/cras/src/server/cras_bt_manager.c +++ b/cras/src/server/cras_bt_manager.c @@ -19,6 +19,7 @@ #include "cras_bt_player.h" #include "cras_bt_profile.h" #include "cras_bt_transport.h" +#include "cras_bt_battery_provider.h" #include "utlist.h" struct cras_bt_event_log *btlog; @@ -120,6 +121,32 @@ static void cras_bt_interface_added(DBusConnection *conn, object_path); } } + } else if (strcmp(interface_name, + BLUEZ_INTERFACE_BATTERY_PROVIDER_MANAGER) == 0) { + struct cras_bt_adapter *adapter; + int ret; + + syslog(LOG_INFO, + "Bluetooth Battery Provider Manager available"); + + adapter = cras_bt_adapter_get(object_path); + if (adapter) { + syslog(LOG_INFO, + "Registering Battery Provider for adapter %s", + cras_bt_adapter_address(adapter)); + ret = cras_bt_register_battery_provider(conn, adapter); + if (ret != 0) { + syslog(LOG_ERR, + "Error registering Battery Provider " + "for adapter %s: %s", + cras_bt_adapter_address(adapter), + strerror(-ret)); + } + } else { + syslog(LOG_WARNING, + "Adapter not available when trying to create " + "Battery Provider"); + } } } @@ -158,6 +185,10 @@ static void cras_bt_interface_removed(DBusConnection *conn, cras_bt_transport_object_path(transport)); cras_bt_transport_remove(transport); } + } else if (strcmp(interface_name, + BLUEZ_INTERFACE_BATTERY_PROVIDER_MANAGER) == 0) { + syslog(LOG_INFO, "Bluetooth Battery Provider Manager removed"); + cras_bt_battery_provider_reset(); } } diff --git a/cras/src/server/cras_dbus_control.c b/cras/src/server/cras_dbus_control.c index 3479c3c6..b66e1276 100644 --- a/cras/src/server/cras_dbus_control.c +++ b/cras/src/server/cras_dbus_control.c @@ -125,6 +125,9 @@ " <method name=\"SetWbsEnabled\">\n" \ " <arg name=\"enabled\" type=\"b\" direction=\"in\"/>\n" \ " </method>\n" \ + " <method name=\"SetNoiseCancellationEnabled\">\n" \ + " <arg name=\"enabled\" type=\"b\" direction=\"in\"/>\n" \ + " </method>\n" \ " <method name=\"SetPlayerPlaybackStatus\">\n" \ " <arg name=\"status\" type=\"s\" direction=\"in\"/>\n" \ " </method>\n" \ @@ -137,8 +140,6 @@ " <method name=\"SetPlayerMetadata\">\n" \ " <arg name=\"metadata\" type=\"a{sv}\" direction=\"in\"/>\n" \ " </method>\n" \ - " <method name=\"ResendBluetoothBattery\">\n" \ - " </method>\n" \ " </interface>\n" \ " <interface name=\"" DBUS_INTERFACE_INTROSPECTABLE "\">\n" \ " <method name=\"Introspect\">\n" \ @@ -960,6 +961,24 @@ static DBusHandlerResult handle_set_wbs_enabled(DBusConnection *conn, return DBUS_HANDLER_RESULT_HANDLED; } +static DBusHandlerResult +handle_set_noise_cancellation_enabled(DBusConnection *conn, + DBusMessage *message, void *arg) +{ + int rc; + dbus_bool_t enabled; + + rc = get_single_arg(message, DBUS_TYPE_BOOLEAN, &enabled); + if (rc) + return rc; + + cras_system_set_noise_cancellation_enabled(enabled); + + send_empty_reply(conn, message); + + return DBUS_HANDLER_RESULT_HANDLED; +} + static DBusHandlerResult handle_set_player_playback_status(DBusConnection *conn, DBusMessage *message, void *arg) @@ -1060,17 +1079,6 @@ static DBusHandlerResult handle_set_player_metadata(DBusConnection *conn, return DBUS_HANDLER_RESULT_HANDLED; } -static DBusHandlerResult handle_resend_bluetooth_battery(DBusConnection *conn, - DBusMessage *message, - void *arg) -{ - cras_hfp_ag_resend_device_battery_level(); - - send_empty_reply(conn, message); - - return DBUS_HANDLER_RESULT_HANDLED; -} - /* Handle incoming messages. */ static DBusHandlerResult handle_control_message(DBusConnection *conn, DBusMessage *message, void *arg) @@ -1199,6 +1207,10 @@ static DBusHandlerResult handle_control_message(DBusConnection *conn, "SetWbsEnabled")) { return handle_set_wbs_enabled(conn, message, arg); } else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE, + "SetNoiseCancellationEnabled")) { + return handle_set_noise_cancellation_enabled(conn, message, + arg); + } else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE, "SetPlayerPlaybackStatus")) { return handle_set_player_playback_status(conn, message, arg); } else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE, @@ -1210,9 +1222,6 @@ static DBusHandlerResult handle_control_message(DBusConnection *conn, } else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE, "SetPlayerMetadata")) { return handle_set_player_metadata(conn, message, arg); - } else if (dbus_message_is_method_call(message, CRAS_CONTROL_INTERFACE, - "ResendBluetoothBattery")) { - return handle_resend_bluetooth_battery(conn, message, arg); } return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; @@ -1324,8 +1333,8 @@ static void signal_active_node_changed(void *context, dbus_uint32_t serial = 0; msg = create_dbus_message((dir == CRAS_STREAM_OUTPUT) ? - "ActiveOutputNodeChanged" : - "ActiveInputNodeChanged"); + "ActiveOutputNodeChanged" : + "ActiveInputNodeChanged"); if (!msg) return; dbus_message_append_args(msg, DBUS_TYPE_UINT64, &node_id, @@ -1463,25 +1472,6 @@ static void signal_non_empty_audio_state_changed(void *context, int non_empty) dbus_message_unref(msg); } -static void signal_bt_battery_changed(void *context, const char *address, - uint32_t level) -{ - struct cras_dbus_control *control = (struct cras_dbus_control *)context; - dbus_uint32_t serial = 0; - DBusMessage *msg; - - msg = create_dbus_message("BluetoothBatteryChanged"); - if (!msg) - return; - - dbus_message_append_args(msg, DBUS_TYPE_STRING, &address, - DBUS_TYPE_INVALID); - dbus_message_append_args(msg, DBUS_TYPE_UINT32, &level, - DBUS_TYPE_INVALID); - dbus_connection_send(control->conn, msg, &serial); - dbus_message_unref(msg); -} - /* Exported Interface */ void cras_dbus_control_start(DBusConnection *conn) @@ -1523,7 +1513,6 @@ void cras_dbus_control_start(DBusConnection *conn) observer_ops.hotword_triggered = signal_hotword_triggered; observer_ops.non_empty_audio_state_changed = signal_non_empty_audio_state_changed; - observer_ops.bt_battery_changed = signal_bt_battery_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 7dd0f5d7..e9730a0b 100644 --- a/cras/src/server/cras_device_monitor.c +++ b/cras/src/server/cras_device_monitor.c @@ -13,6 +13,7 @@ enum CRAS_DEVICE_MONITOR_MSG_TYPE { RESET_DEVICE, SET_MUTE_STATE, + ERROR_CLOSE, }; struct cras_device_monitor_message { @@ -62,6 +63,21 @@ int cras_device_monitor_set_device_mute_state(unsigned int dev_idx) return 0; } +int cras_device_monitor_error_close(unsigned int dev_idx) +{ + struct cras_device_monitor_message msg; + int err; + + init_device_msg(&msg, ERROR_CLOSE, dev_idx); + err = cras_main_message_send((struct cras_main_message *)&msg); + if (err < 0) { + syslog(LOG_ERR, "Failed to send device message %d", + ERROR_CLOSE); + return err; + } + return 0; +} + /* When device is in a bad state, e.g. severe underrun, * it might break how audio thread works and cause busy wake up loop. * Resetting the device can bring device back to normal state. @@ -84,6 +100,10 @@ static void handle_device_message(struct cras_main_message *msg, void *arg) case SET_MUTE_STATE: cras_iodev_list_set_dev_mute(device_msg->dev_idx); break; + case ERROR_CLOSE: + syslog(LOG_ERR, "Close erroneous device in main thread"); + cras_iodev_list_suspend_dev(device_msg->dev_idx); + break; default: syslog(LOG_ERR, "Unknown device message type %u", device_msg->message_type); diff --git a/cras/src/server/cras_device_monitor.h b/cras/src/server/cras_device_monitor.h index ac31adb9..eca2372b 100644 --- a/cras/src/server/cras_device_monitor.h +++ b/cras/src/server/cras_device_monitor.h @@ -15,4 +15,8 @@ int cras_device_monitor_set_device_mute_state(unsigned int dev_idx); /* Initializes device monitor and sets main thread callback. */ int cras_device_monitor_init(); +/* Asks main thread to close device because error has occured in audio + * thread. */ +int cras_device_monitor_error_close(unsigned int dev_idx); + #endif /* CRAS_DEVICE_MONITOR_H_ */ diff --git a/cras/src/server/cras_fmt_conv.c b/cras/src/server/cras_fmt_conv.c index 509db1eb..842529b9 100644 --- a/cras/src/server/cras_fmt_conv.c +++ b/cras/src/server/cras_fmt_conv.c @@ -216,6 +216,19 @@ static size_t stereo_to_51(struct cras_fmt_conv *conv, const uint8_t *in, return s16_stereo_to_51(left, right, center, in, in_frames, out); } +static size_t quad_to_51(struct cras_fmt_conv *conv, const uint8_t *in, + size_t in_frames, uint8_t *out) +{ + size_t fl, fr, rl, rr; + + fl = conv->out_fmt.channel_layout[CRAS_CH_FL]; + fr = conv->out_fmt.channel_layout[CRAS_CH_FR]; + rl = conv->out_fmt.channel_layout[CRAS_CH_RL]; + rr = conv->out_fmt.channel_layout[CRAS_CH_RR]; + + return s16_quad_to_51(fl, fr, rl, rr, in, in_frames, out); +} + static size_t _51_to_stereo(struct cras_fmt_conv *conv, const uint8_t *in, size_t in_frames, uint8_t *out) { @@ -398,6 +411,8 @@ struct cras_fmt_conv *cras_fmt_conv_create(const struct cras_audio_format *in, conv->channel_converter = quad_to_stereo; } else if (in->num_channels == 2 && out->num_channels == 6) { conv->channel_converter = stereo_to_51; + } else if (in->num_channels == 4 && out->num_channels == 6) { + conv->channel_converter = quad_to_51; } else if (in->num_channels == 6 && (out->num_channels == 2 || out->num_channels == 4)) { int in_channel_layout_set = 0; diff --git a/cras/src/server/cras_fmt_conv_ops.c b/cras/src/server/cras_fmt_conv_ops.c index a306d216..adc55215 100644 --- a/cras/src/server/cras_fmt_conv_ops.c +++ b/cras/src/server/cras_fmt_conv_ops.c @@ -223,6 +223,44 @@ size_t s16_stereo_to_51(size_t left, size_t right, size_t center, } /* + * Channel converter: quad to 5.1 surround. + * + * Fit the front left/right of input to the front left/right of output + * and rear left/right of input to the rear left/right of output + * respectively and fill others with zero. + */ +size_t s16_quad_to_51(size_t font_left, size_t front_right, size_t rear_left, + size_t rear_right, const uint8_t *_in, size_t in_frames, + uint8_t *_out) +{ + size_t i; + const int16_t *in = (const int16_t *)_in; + int16_t *out = (int16_t *)_out; + + memset(out, 0, sizeof(*out) * 6 * in_frames); + + if (font_left != -1 && front_right != -1 && rear_left != -1 && + rear_right != -1) + for (i = 0; i < in_frames; i++) { + out[6 * i + font_left] = in[4 * i]; + out[6 * i + front_right] = in[4 * i + 1]; + out[6 * i + rear_left] = in[4 * i + 2]; + out[6 * i + rear_right] = in[4 * i + 3]; + } + else + /* Use default 5.1 channel mapping for the conversion. + */ + for (i = 0; i < in_frames; i++) { + out[6 * i] = in[4 * i]; + out[6 * i + 1] = in[4 * i + 1]; + out[6 * i + 4] = in[4 * i + 2]; + out[6 * i + 5] = in[4 * i + 3]; + } + + return in_frames; +} + +/* * Channel converter: 5.1 surround to stereo. * * The out buffer can have room for just stereo samples. This convert function diff --git a/cras/src/server/cras_fmt_conv_ops.h b/cras/src/server/cras_fmt_conv_ops.h index a1a57487..0af7564b 100644 --- a/cras/src/server/cras_fmt_conv_ops.h +++ b/cras/src/server/cras_fmt_conv_ops.h @@ -46,6 +46,13 @@ size_t s16_stereo_to_51(size_t left, size_t right, size_t center, const uint8_t *in, size_t in_frames, uint8_t *out); /* + * Channel converter: quad to 5.1 surround. + */ +size_t s16_quad_to_51(size_t font_left, size_t front_right, size_t rear_left, + size_t rear_right, const uint8_t *in, size_t in_frames, + uint8_t *out); + +/* * Channel converter: 5.1 surround to stereo. */ size_t s16_51_to_stereo(const uint8_t *in, size_t in_frames, uint8_t *out); diff --git a/cras/src/server/cras_hfp_ag_profile.c b/cras/src/server/cras_hfp_ag_profile.c index 9d59d40e..b5fcecc3 100644 --- a/cras/src/server/cras_hfp_ag_profile.c +++ b/cras/src/server/cras_hfp_ag_profile.c @@ -20,7 +20,6 @@ #include "cras_server_metrics.h" #include "cras_system_state.h" #include "cras_iodev_list.h" -#include "cras_observer.h" #include "utlist.h" #include "packet_status_logger.h" @@ -461,19 +460,6 @@ struct packet_status_logger *cras_hfp_ag_get_wbs_logger() return &wbs_logger; } -void cras_hfp_ag_resend_device_battery_level() -{ - struct audio_gateway *ag; - int level; - DL_FOREACH (connected_ags, ag) { - level = hfp_slc_get_hf_battery_level(ag->slc_handle); - if (level >= 0 && level <= 100) - cras_observer_notify_bt_battery_changed( - cras_bt_device_address(ag->device), - (uint32_t)(level)); - } -} - int cras_hsp_ag_profile_create(DBusConnection *conn) { return cras_bt_add_profile(conn, &cras_hsp_ag_profile); diff --git a/cras/src/server/cras_hfp_ag_profile.h b/cras/src/server/cras_hfp_ag_profile.h index 50d27e05..3de56184 100644 --- a/cras/src/server/cras_hfp_ag_profile.h +++ b/cras/src/server/cras_hfp_ag_profile.h @@ -56,8 +56,4 @@ struct hfp_slc_handle *cras_hfp_ag_get_slc(struct cras_bt_device *device); /* Gets the logger for WBS packet status. */ struct packet_status_logger *cras_hfp_ag_get_wbs_logger(); -/* Iterate all possible AGs (theoratically only one) and signal its battery - * level */ -void cras_hfp_ag_resend_device_battery_level(); - #endif /* CRAS_HFP_AG_PROFILE_H_ */ diff --git a/cras/src/server/cras_hfp_alsa_iodev.c b/cras/src/server/cras_hfp_alsa_iodev.c index b80a88c7..c1b60b30 100644 --- a/cras/src/server/cras_hfp_alsa_iodev.c +++ b/cras/src/server/cras_hfp_alsa_iodev.c @@ -12,7 +12,6 @@ #include "cras_iodev.h" #include "cras_system_state.h" #include "cras_util.h" -#include "sfh.h" #include "utlist.h" #include "cras_bt_device.h" @@ -108,6 +107,7 @@ static int hfp_alsa_configure_dev(struct cras_iodev *iodev) return rc; } + hfp_set_call_status(hfp_alsa_io->slc, 1); iodev->buffer_size = aio->buffer_size; return 0; @@ -118,6 +118,7 @@ static int hfp_alsa_close_dev(struct cras_iodev *iodev) struct hfp_alsa_io *hfp_alsa_io = (struct hfp_alsa_io *)iodev; struct cras_iodev *aio = hfp_alsa_io->aio; + hfp_set_call_status(hfp_alsa_io->slc, 0); cras_bt_device_put_sco(hfp_alsa_io->device); cras_iodev_free_format(iodev); return aio->close_dev(aio); @@ -259,10 +260,7 @@ struct cras_iodev *hfp_alsa_iodev_create(struct cras_iodev *aio, name = cras_bt_device_object_path(device); snprintf(iodev->info.name, sizeof(iodev->info.name), "%s", name); iodev->info.name[ARRAY_SIZE(iodev->info.name) - 1] = 0; - iodev->info.stable_id = - SuperFastHash(cras_bt_device_object_path(device), - strlen(cras_bt_device_object_path(device)), - strlen(cras_bt_device_object_path(device))); + iodev->info.stable_id = cras_bt_device_get_stable_id(device); iodev->open_dev = hfp_alsa_open_dev; iodev->update_supported_formats = hfp_alsa_update_supported_formats; diff --git a/cras/src/server/cras_hfp_iodev.c b/cras/src/server/cras_hfp_iodev.c index 7cce3736..6a4ced04 100644 --- a/cras/src/server/cras_hfp_iodev.c +++ b/cras/src/server/cras_hfp_iodev.c @@ -17,7 +17,6 @@ #include "cras_iodev.h" #include "cras_system_state.h" #include "cras_util.h" -#include "sfh.h" #include "utlist.h" /* Implementation of bluetooth hands-free profile iodev. @@ -167,6 +166,7 @@ static int configure_dev(struct cras_iodev *iodev) hfpio->filled_zeros = 0; add_dev: hfp_info_add_iodev(hfpio->info, iodev->direction, iodev->format); + hfp_set_call_status(hfpio->slc, 1); iodev->buffer_size = hfp_buf_size(hfpio->info, iodev->direction); @@ -181,8 +181,10 @@ static int close_dev(struct cras_iodev *iodev) struct hfp_io *hfpio = (struct hfp_io *)iodev; hfp_info_rm_iodev(hfpio->info, iodev->direction); - if (hfp_info_running(hfpio->info) && !hfp_info_has_iodev(hfpio->info)) + if (hfp_info_running(hfpio->info) && !hfp_info_has_iodev(hfpio->info)) { hfp_info_stop(hfpio->info); + hfp_set_call_status(hfpio->slc, 0); + } cras_iodev_free_format(iodev); cras_iodev_free_audio_area(iodev); @@ -306,10 +308,7 @@ struct cras_iodev *hfp_iodev_create(enum CRAS_STREAM_DIRECTION dir, snprintf(iodev->info.name, sizeof(iodev->info.name), "%s", name); iodev->info.name[ARRAY_SIZE(iodev->info.name) - 1] = 0; - iodev->info.stable_id = - SuperFastHash(cras_bt_device_object_path(device), - strlen(cras_bt_device_object_path(device)), - strlen(cras_bt_device_object_path(device))); + iodev->info.stable_id = cras_bt_device_get_stable_id(device); iodev->configure_dev = configure_dev; iodev->frames_queued = frames_queued; diff --git a/cras/src/server/cras_hfp_slc.c b/cras/src/server/cras_hfp_slc.c index e4f0127d..28f73edc 100644 --- a/cras/src/server/cras_hfp_slc.c +++ b/cras/src/server/cras_hfp_slc.c @@ -441,12 +441,10 @@ static int available_codecs(struct hfp_slc_handle *handle, const char *cmd) id_str = strtok(NULL, ","); } - for (id = HFP_MAX_CODECS - 1; id > 0; id--) { - if (handle->hf_codec_supported[id]) { - handle->preferred_codec = id; - break; - } - } + if (hfp_slc_get_wideband_speech_supported(handle)) + handle->preferred_codec = HFP_CODEC_ID_MSBC; + else + handle->preferred_codec = HFP_CODEC_ID_CVSD; free(tokens); return hfp_send(handle, AT_CMD("OK")); @@ -609,6 +607,26 @@ static int operator_selection(struct hfp_slc_handle *handle, const char *buf) return hfp_send(handle, AT_CMD("OK")); } +/* The AT+CHLD command is used to control call hold, release, and multiparty + * states. + */ +static int call_hold(struct hfp_slc_handle *handle, const char *buf) +{ + int rc; + + // Chrome OS doesn't yet support CHLD features but we need to reply + // the query with an empty feature list rather than "ERROR" to increase + // interoperability with certain devices (b/172413440). + if (strlen(buf) > 8 && buf[7] == '=' && buf[8] == '?') { + rc = hfp_send(handle, AT_CMD("+CHLD:")); + if (rc) + return rc; + return hfp_send(handle, AT_CMD("OK")); + } + + return hfp_send(handle, AT_CMD("ERROR")); +} + /* AT+CIND command retrieves the supported indicator and its corresponding * range and order index or read current status of indicators. Mandatory * support per spec 4.2. @@ -938,6 +956,70 @@ static int terminate_call(struct hfp_slc_handle *handle, const char *cmd) return cras_telephony_event_terminate_call(); } +/* AT+XEVENT is defined by Android to support vendor specific features. + * Currently, the only known supported case for CrOS is the battery event sent + * by some Plantronics headsets. + */ +static int vendor_specific_features(struct hfp_slc_handle *handle, + const char *cmd) +{ + char *tokens, *event, *level_str, *num_of_level_str; + int level, num_of_level; + + tokens = strdup(cmd); + strtok(tokens, "="); + event = strtok(NULL, ","); + if (!event) + goto error_out; + + /* AT+XEVENT=BATTERY,Level,NumberOfLevel,MinutesOfTalkTime,IsCharging + * Level: The charge level with a zero-based integer. + * NumberOfLevel: How many charging levels there are. + * MinuteOfTalkTime: The estimated number of talk minutes remaining. + * IsCharging: A 0 or 1 value. + * + * We only support the battery level and thus only care about the first + * 3 arguments. + */ + if (!strncmp(event, "BATTERY", 7)) { + level_str = strtok(NULL, ","); + num_of_level_str = strtok(NULL, ","); + if (!level_str || !num_of_level_str) + goto error_out; + + level = atoi(level_str); + num_of_level = atoi(num_of_level_str); + if (level < 0 || num_of_level <= 1 || level >= num_of_level) + goto error_out; + + level = (int64_t)level * 100 / (num_of_level - 1); + if (handle->hf_battery != level) { + handle->hf_supports_battery_indicator |= + CRAS_HFP_BATTERY_INDICATOR_PLANTRONICS; + cras_server_metrics_hfp_battery_report( + CRAS_HFP_BATTERY_INDICATOR_PLANTRONICS); + handle->hf_battery = level; + cras_observer_notify_bt_battery_changed( + cras_bt_device_address(handle->device), + (uint32_t)(level)); + } + } + + free(tokens); + /* For Plantronic headsets, it is required to reply "OK" for the first + * AT+XEVENT=USER-AGENT... command to tell the headset our support of + * the xevent protocol. Otherwise, all following events including + * BATTERY won't be sent. + */ + return hfp_send(handle, AT_CMD("OK")); + +error_out: + syslog(LOG_ERR, "%s: malformed vendor specific command: '%s'", __func__, + cmd); + free(tokens); + return hfp_send(handle, AT_CMD("ERROR")); +} + /* AT commands to support in order to conform HFP specification. * * An initialized service level connection is the pre-condition for all @@ -999,6 +1081,8 @@ static struct at_command at_commands[] = { { "AT+VG", signal_gain_setting }, { "AT+VTS", dtmf_tone }, { "AT+XAPL", apple_supported_features }, + { "AT+XEVENT", vendor_specific_features }, + { "AT+CHLD", call_hold }, { 0 } }; @@ -1314,8 +1398,3 @@ int hfp_slc_get_hf_supports_battery_indicator(struct hfp_slc_handle *handle) { return handle->hf_supports_battery_indicator; } - -int hfp_slc_get_hf_battery_level(struct hfp_slc_handle *handle) -{ - return handle->hf_battery; -} diff --git a/cras/src/server/cras_hfp_slc.h b/cras/src/server/cras_hfp_slc.h index c3cdc117..99335eab 100644 --- a/cras/src/server/cras_hfp_slc.h +++ b/cras/src/server/cras_hfp_slc.h @@ -62,6 +62,7 @@ struct cras_bt_device; #define CRAS_HFP_BATTERY_INDICATOR_NONE 0x0 #define CRAS_HFP_BATTERY_INDICATOR_HFP 0x1 #define CRAS_HFP_BATTERY_INDICATOR_APPLE 0x2 +#define CRAS_HFP_BATTERY_INDICATOR_PLANTRONICS 0x4 /* Callback to call when service level connection initialized. */ typedef int (*hfp_slc_init_cb)(struct hfp_slc_handle *handle); @@ -145,10 +146,6 @@ int hfp_slc_get_ag_codec_negotiation_supported(struct hfp_slc_handle *handle); * Apple, HFP, none, or both. */ int hfp_slc_get_hf_supports_battery_indicator(struct hfp_slc_handle *handle); -/* Gets the battery level for the HF. The data ranges 0 ~ 100. Use -1 for no - * battery level reported.*/ -int hfp_slc_get_hf_battery_level(struct hfp_slc_handle *handle); - /* Init the codec negotiation process if needed. */ int hfp_slc_codec_connection_setup(struct hfp_slc_handle *handle); diff --git a/cras/src/server/cras_iodev.c b/cras/src/server/cras_iodev.c index fd1ce805..651cef71 100644 --- a/cras/src/server/cras_iodev.c +++ b/cras/src/server/cras_iodev.c @@ -732,6 +732,17 @@ bool cras_iodev_is_aec_use_case(const struct cras_ionode *node) return false; } +bool cras_iodev_is_on_internal_card(const struct cras_ionode *node) +{ + if (node->type == CRAS_NODE_TYPE_INTERNAL_SPEAKER) + return true; + if (node->type == CRAS_NODE_TYPE_HEADPHONE) + return true; + if (node->type == CRAS_NODE_TYPE_MIC) + return true; + return false; +} + float cras_iodev_get_software_volume_scaler(struct cras_iodev *iodev) { unsigned int volume; @@ -1010,6 +1021,7 @@ int cras_iodev_close(struct cras_iodev *iodev) if (iodev->active_node) { cras_server_metrics_device_runtime(iodev); + cras_server_metrics_device_gain(iodev); cras_server_metrics_device_volume(iodev); } @@ -1695,3 +1707,13 @@ int cras_iodev_drop_frames_by_time(struct cras_iodev *iodev, struct timespec ts) return rc; } + +bool cras_iodev_support_noise_cancellation(const struct cras_iodev *iodev) +{ + if (iodev->direction != CRAS_STREAM_INPUT) + return false; + + if (iodev->support_noise_cancellation) + return !!iodev->support_noise_cancellation(iodev); + return false; +} diff --git a/cras/src/server/cras_iodev.h b/cras/src/server/cras_iodev.h index db16a0f8..18a0962c 100644 --- a/cras/src/server/cras_iodev.h +++ b/cras/src/server/cras_iodev.h @@ -184,6 +184,8 @@ struct cras_ionode { * audio thread can sleep before serving this playback dev the next time. * Not implementing this ops means fall back to default behavior in * cras_iodev_default_frames_to_play_in_sleep(). + * support_noise_cancellation - (Optional) Checks if the device supports noise + * cancellation. * format - The audio format being rendered or captured to hardware. * rate_est - Rate estimator to estimate the actual device rate. * area - Information about how the samples are stored. @@ -274,6 +276,7 @@ struct cras_iodev { unsigned int (*frames_to_play_in_sleep)(struct cras_iodev *iodev, unsigned int *hw_level, struct timespec *hw_tstamp); + int (*support_noise_cancellation)(const struct cras_iodev *iodev); struct cras_audio_format *format; struct rate_estimator *rate_est; struct cras_audio_area *area; @@ -459,6 +462,9 @@ void cras_iodev_set_active_node(struct cras_iodev *iodev, /* Checks if the node is the typical playback or capture option for AEC usage. */ bool cras_iodev_is_aec_use_case(const struct cras_ionode *node); +/* Checks if the node is a playback or capture node on internal card. */ +bool cras_iodev_is_on_internal_card(const struct cras_ionode *node); + /* Adjust the system volume based on the volume of the given node. */ static inline unsigned int cras_iodev_adjust_node_volume(const struct cras_ionode *node, @@ -833,4 +839,12 @@ void cras_iodev_update_highest_hw_level(struct cras_iodev *iodev, int cras_iodev_drop_frames_by_time(struct cras_iodev *iodev, struct timespec ts); +/* Checks if an input device supports noise cancellation. + * Args: + * iodev - The device. + * Returns: + * True if device supports noise cancellation. False otherwise. + */ +bool cras_iodev_support_noise_cancellation(const struct cras_iodev *iodev); + #endif /* CRAS_IODEV_H_ */ diff --git a/cras/src/server/cras_iodev_list.c b/cras/src/server/cras_iodev_list.c index ada29719..b818c97b 100644 --- a/cras/src/server/cras_iodev_list.c +++ b/cras/src/server/cras_iodev_list.c @@ -91,6 +91,9 @@ static int stream_list_suspended = 0; static const unsigned int INIT_DEV_DELAY_MS = 1000; /* Flag to indicate that hotword streams are suspended. */ static int hotword_suspended = 0; +/* Flag to indicate that suspended hotword streams should be auto-resumed at + * system resume. */ +static int hotword_auto_resume = 0; static void idle_dev_check(struct cras_timer *timer, void *data); @@ -388,8 +391,9 @@ static void close_dev(struct cras_iodev *dev) MAINLOG(main_log, MAIN_THREAD_DEV_CLOSE, dev->info.idx, 0, 0); remove_all_streams_from_dev(dev); dev->idle_timeout.tv_sec = 0; - cras_iodev_close(dev); + /* close echo ref first to avoid underrun in hardware */ possibly_disable_echo_reference(dev); + cras_iodev_close(dev); } static void idle_dev_check(struct cras_timer *timer, void *data) @@ -490,6 +494,11 @@ static void suspend_devs() if (rstream->is_pinned) { struct cras_iodev *dev; + /* Skip closing hotword stream in the first pass. + * Closing an input device may resume hotword stream + * with its post_close_iodev_hook so we should deal + * with hotword stream in the second pass. + */ if ((rstream->flags & HOTWORD_STREAM) == HOTWORD_STREAM) continue; @@ -513,6 +522,14 @@ static void suspend_devs() DL_FOREACH (enabled_devs[CRAS_STREAM_INPUT], edev) { close_dev(edev->dev); } + + /* Doing this check after all the other enabled iodevs are closed to + * ensure preempted hotword streams obey the pause_at_suspend flag. + */ + if (cras_system_get_hotword_pause_at_suspend()) { + cras_iodev_list_suspend_hotword_streams(); + hotword_auto_resume = 1; + } } static int stream_added_cb(struct cras_rstream *rstream); @@ -527,6 +544,14 @@ static void resume_devs() MAINLOG(main_log, MAIN_THREAD_RESUME_DEVS, 0, 0, 0); + /* Auto-resume based on the local flag in case the system state flag has + * changed. + */ + if (hotword_auto_resume) { + cras_iodev_list_resume_hotword_stream(); + hotword_auto_resume = 0; + } + /* * To remove the short popped noise caused by applications that can not * stop playback "right away" after resume, we mute all output devices @@ -1856,6 +1881,24 @@ void cras_iodev_list_unregister_loopback(enum CRAS_LOOPBACK_TYPE type, } } +void cras_iodev_list_reset_for_noise_cancellation() +{ + struct cras_iodev *dev; + bool enabled = cras_system_get_noise_cancellation_enabled(); + + DL_FOREACH (devs[CRAS_STREAM_INPUT].iodevs, dev) { + if (!cras_iodev_is_open(dev) || + !cras_iodev_support_noise_cancellation(dev)) + continue; + syslog(LOG_INFO, "Re-open %s for %s noise cancellation", + dev->info.name, enabled ? "enabling" : "disabling"); + possibly_enable_fallback(CRAS_STREAM_INPUT, false); + cras_iodev_list_suspend_dev(dev->info.idx); + cras_iodev_list_resume_dev(dev->info.idx); + possibly_disable_fallback(CRAS_STREAM_INPUT); + } +} + void cras_iodev_list_reset() { struct enabled_dev *edev; diff --git a/cras/src/server/cras_iodev_list.h b/cras/src/server/cras_iodev_list.h index 61c3a182..d6e9ba54 100644 --- a/cras/src/server/cras_iodev_list.h +++ b/cras/src/server/cras_iodev_list.h @@ -274,6 +274,11 @@ int cras_iodev_list_suspend_hotword_streams(); /* Resumes all hotwording streams. */ int cras_iodev_list_resume_hotword_stream(); +/* Sets the state of noise cancellation for input devices which supports noise + * cancellation by suspend, enable/disable, then resume. + */ +void cras_iodev_list_reset_for_noise_cancellation(); + /* For unit test only. */ void cras_iodev_list_reset(); diff --git a/cras/src/server/cras_rclient_util.c b/cras/src/server/cras_rclient_util.c index def645e3..0af98863 100644 --- a/cras/src/server/cras_rclient_util.c +++ b/cras/src/server/cras_rclient_util.c @@ -170,6 +170,8 @@ int rclient_handle_client_stream_connect(struct cras_rclient *client, if (rc) goto cleanup_config; + detect_rtc_stream_pair(cras_iodev_list_get_stream_list(), stream); + /* Tell client about the stream setup. */ syslog(LOG_DEBUG, "Send connected for stream %x\n", msg->stream_id); diff --git a/cras/src/server/cras_rstream.c b/cras/src/server/cras_rstream.c index 94adcead..3c0a0ce3 100644 --- a/cras/src/server/cras_rstream.c +++ b/cras/src/server/cras_rstream.c @@ -167,6 +167,11 @@ static int verify_rstream_parameters(const struct cras_rstream_config *config, syslog(LOG_ERR, "rstream: Invalid stream type.\n"); return -EINVAL; } + if (config->client_type < CRAS_CLIENT_TYPE_UNKNOWN || + config->client_type >= CRAS_NUM_CLIENT_TYPE) { + syslog(LOG_ERR, "rstream: Invalid client type.\n"); + return -EINVAL; + } if ((config->client_shm_size > 0 && config->client_shm_fd < 0) || (config->client_shm_size == 0 && config->client_shm_fd >= 0)) { syslog(LOG_ERR, "rstream: invalid client-provided shm info\n"); @@ -287,8 +292,8 @@ int cras_rstream_create(struct cras_rstream_config *config, stream->cb_threshold = config->cb_threshold; stream->client = config->client; stream->shm = NULL; - stream->master_dev.dev_id = NO_DEVICE; - stream->master_dev.dev_ptr = NULL; + stream->main_dev.dev_id = NO_DEVICE; + stream->main_dev.dev_ptr = NULL; stream->num_missed_cb = 0; stream->is_pinned = (config->dev_idx != NO_DEVICE); stream->pinned_dev_idx = config->dev_idx; @@ -426,12 +431,12 @@ void cras_rstream_dev_attach(struct cras_rstream *rstream, unsigned int dev_id, if (buffer_share_add_id(rstream->buf_state, dev_id, dev_ptr) == 0) rstream->num_attached_devs++; - /* TODO(hychao): Handle master device assignment for complicated + /* TODO(hychao): Handle main device assignment for complicated * routing case. */ - if (rstream->master_dev.dev_id == NO_DEVICE) { - rstream->master_dev.dev_id = dev_id; - rstream->master_dev.dev_ptr = dev_ptr; + if (rstream->main_dev.dev_id == NO_DEVICE) { + rstream->main_dev.dev_id = dev_id; + rstream->main_dev.dev_ptr = dev_ptr; } } @@ -440,18 +445,18 @@ void cras_rstream_dev_detach(struct cras_rstream *rstream, unsigned int dev_id) if (buffer_share_rm_id(rstream->buf_state, dev_id) == 0) rstream->num_attached_devs--; - if (rstream->master_dev.dev_id == dev_id) { + if (rstream->main_dev.dev_id == dev_id) { int i; struct id_offset *o; - /* Choose the first device id as master. */ - rstream->master_dev.dev_id = NO_DEVICE; - rstream->master_dev.dev_ptr = NULL; + /* Choose the first device id as a main device. */ + rstream->main_dev.dev_id = NO_DEVICE; + rstream->main_dev.dev_ptr = NULL; for (i = 0; i < rstream->buf_state->id_sz; i++) { o = &rstream->buf_state->wr_idx[i]; if (o->used) { - rstream->master_dev.dev_id = o->id; - rstream->master_dev.dev_ptr = o->data; + rstream->main_dev.dev_id = o->id; + rstream->main_dev.dev_ptr = o->data; break; } } diff --git a/cras/src/server/cras_rstream.h b/cras/src/server/cras_rstream.h index 3bf7df0b..d57c13be 100644 --- a/cras/src/server/cras_rstream.h +++ b/cras/src/server/cras_rstream.h @@ -20,12 +20,12 @@ struct cras_connect_message; struct cras_rclient; struct dev_mix; -/* Holds informations about the master active device. +/* Holds informations about the main active device. * Members: - * dev_id - id of the master device. - * dev_ptr - pointer to the master device. + * dev_id - id of the main device. + * dev_ptr - pointer to the main device. */ -struct master_dev_info { +struct main_dev_info { int dev_id; void *dev_ptr; }; @@ -42,7 +42,7 @@ struct master_dev_info { * fd - Socket for requesting and sending audio buffer events. * buffer_frames - Buffer size in frames. * cb_threshold - Callback client when this much is left. - * master_dev_info - The info of the master device this stream attaches to. + * main_dev_info - The info of the main device this stream attaches to. * is_draining - The stream is draining and waiting to be removed. * client - The client who uses this stream. * shm - shared memory @@ -74,7 +74,7 @@ struct cras_rstream { size_t buffer_frames; size_t cb_threshold; int is_draining; - struct master_dev_info master_dev; + struct main_dev_info main_dev; struct cras_rclient *client; struct cras_audio_shm *shm; struct cras_audio_area *audio_area; diff --git a/cras/src/server/cras_server_metrics.c b/cras/src/server/cras_server_metrics.c index ef4011bd..7e487107 100644 --- a/cras/src/server/cras_server_metrics.c +++ b/cras/src/server/cras_server_metrics.c @@ -25,7 +25,9 @@ const char kBusyloop[] = "Cras.Busyloop"; const char kBusyloopLength[] = "Cras.BusyloopLength"; const char kDeviceTypeInput[] = "Cras.DeviceTypeInput"; const char kDeviceTypeOutput[] = "Cras.DeviceTypeOutput"; +const char kDeviceGain[] = "Cras.DeviceGain"; const char kDeviceVolume[] = "Cras.DeviceVolume"; +const char kFetchDelayMilliSeconds[] = "Cras.FetchDelayMilliSeconds"; const char kHighestDeviceDelayInput[] = "Cras.HighestDeviceDelayInput"; const char kHighestDeviceDelayOutput[] = "Cras.HighestDeviceDelayOutput"; const char kHighestInputHardwareLevel[] = "Cras.HighestInputHardwareLevel"; @@ -47,12 +49,12 @@ const char kMissedCallbackSecondTimeInput[] = const char kMissedCallbackSecondTimeOutput[] = "Cras.MissedCallbackSecondTimeOutput"; const char kNoCodecsFoundMetric[] = "Cras.NoCodecsFoundAtBoot"; -const char kStreamTimeoutMilliSeconds[] = "Cras.StreamTimeoutMilliSeconds"; const char kStreamCallbackThreshold[] = "Cras.StreamCallbackThreshold"; const char kStreamClientTypeInput[] = "Cras.StreamClientTypeInput"; const char kStreamClientTypeOutput[] = "Cras.StreamClientTypeOutput"; const char kStreamFlags[] = "Cras.StreamFlags"; const char kStreamEffects[] = "Cras.StreamEffects"; +const char kStreamRuntime[] = "Cras.StreamRuntime"; const char kStreamSamplingFormat[] = "Cras.StreamSamplingFormat"; const char kStreamSamplingRate[] = "Cras.StreamSamplingRate"; const char kUnderrunsPerDevice[] = "Cras.UnderrunsPerDevice"; @@ -93,6 +95,7 @@ enum CRAS_SERVER_METRICS_TYPE { BT_WIDEBAND_SELECTED_CODEC, BUSYLOOP, BUSYLOOP_LENGTH, + DEVICE_GAIN, DEVICE_RUNTIME, DEVICE_VOLUME, HIGHEST_DEVICE_DELAY_INPUT, @@ -163,7 +166,8 @@ struct cras_server_metrics_device_data { }; struct cras_server_metrics_stream_data { - enum CRAS_CLIENT_TYPE type; + enum CRAS_CLIENT_TYPE client_type; + enum CRAS_STREAM_TYPE stream_type; enum CRAS_STREAM_DIRECTION direction; struct timespec runtime; }; @@ -304,6 +308,31 @@ metrics_client_type_str(enum CRAS_CLIENT_TYPE client_type) return "ServerStream"; case CRAS_CLIENT_TYPE_LACROS: return "LaCrOS"; + case CRAS_CLIENT_TYPE_PLUGIN: + return "PluginVM"; + case CRAS_CLIENT_TYPE_ARCVM: + return "ARCVM"; + default: + return "InvalidType"; + } +} + +static inline const char * +metrics_stream_type_str(enum CRAS_STREAM_TYPE stream_type) +{ + switch (stream_type) { + case CRAS_STREAM_TYPE_DEFAULT: + return "Default"; + case CRAS_STREAM_TYPE_MULTIMEDIA: + return "Multimedia"; + case CRAS_STREAM_TYPE_VOICE_COMMUNICATION: + return "VoiceCommunication"; + case CRAS_STREAM_TYPE_SPEECH_RECOGNITION: + return "SpeechRecognition"; + case CRAS_STREAM_TYPE_PRO_AUDIO: + return "ProAudio"; + case CRAS_STREAM_TYPE_ACCESSIBILITY: + return "Accessibility"; default: return "InvalidType"; } @@ -394,6 +423,69 @@ get_metrics_device_type(struct cras_iodev *iodev) } } +/* + * Logs metrics for each group it belongs to. The UMA does not merge subgroups + * automatically so we need to log them separately. + * + * For example, if we call this function with argument (3, 48000, + * Cras.StreamSamplingRate, Input, Chrome), it will send 48000 to below + * metrics: + * Cras.StreamSamplingRate.Input.Chrome + * Cras.StreamSamplingRate.Input + * Cras.StreamSamplingRate + */ +static void log_sparse_histogram_each_level(int num, int sample, ...) +{ + char metrics_name[METRICS_NAME_BUFFER_SIZE] = {}; + va_list valist; + int i, len = 0; + + va_start(valist, sample); + + for (i = 0; i < num && len < METRICS_NAME_BUFFER_SIZE; i++) { + int metric_len = + snprintf(metrics_name + len, + METRICS_NAME_BUFFER_SIZE - len, "%s%s", + i ? "." : "", va_arg(valist, char *)); + // Exit early on error or running out of bufferspace. Avoids + // logging partial or corrupted strings. + if (metric_len < 0 || + metric_len > METRICS_NAME_BUFFER_SIZE - len) + break; + len += metric_len; + cras_metrics_log_sparse_histogram(metrics_name, sample); + } + + va_end(valist); +} + +static void log_histogram_each_level(int num, int sample, int min, int max, + int nbuckets, ...) +{ + char metrics_name[METRICS_NAME_BUFFER_SIZE] = {}; + va_list valist; + int i, len = 0; + + va_start(valist, nbuckets); + + for (i = 0; i < num && len < METRICS_NAME_BUFFER_SIZE; i++) { + int metric_len = + snprintf(metrics_name + len, + METRICS_NAME_BUFFER_SIZE - len, "%s%s", + i ? "." : "", va_arg(valist, char *)); + // Exit early on error or running out of bufferspace. Avoids + // logging partial or corrupted strings. + if (metric_len < 0 || + metric_len > METRICS_NAME_BUFFER_SIZE - len) + break; + len += metric_len; + cras_metrics_log_histogram(metrics_name, sample, min, max, + nbuckets); + } + + va_end(valist); +} + int cras_server_metrics_hfp_sco_connection_error( enum CRAS_METRICS_BT_SCO_ERROR_TYPE type) { @@ -536,6 +628,31 @@ int cras_server_metrics_device_runtime(struct cras_iodev *iodev) return 0; } +int cras_server_metrics_device_gain(struct cras_iodev *iodev) +{ + struct cras_server_metrics_message msg; + union cras_server_metrics_data data; + int err; + + if (iodev->direction == CRAS_STREAM_OUTPUT) + return 0; + + data.device_data.type = get_metrics_device_type(iodev); + data.device_data.value = + (unsigned)100 * iodev->active_node->ui_gain_scaler; + + init_server_metrics_msg(&msg, DEVICE_GAIN, data); + + err = cras_server_metrics_message_send( + (struct cras_main_message *)&msg); + if (err < 0) { + syslog(LOG_ERR, "Failed to send metrics message: DEVICE_GAIN"); + return err; + } + + return 0; +} + int cras_server_metrics_device_volume(struct cras_iodev *iodev) { struct cras_server_metrics_message msg; @@ -640,13 +757,31 @@ int cras_server_metrics_highest_hw_level(unsigned hw_level, return 0; } -int cras_server_metrics_longest_fetch_delay(unsigned delay_msec) +/* Logs longest fetch delay of a stream. */ +int cras_server_metrics_longest_fetch_delay(const struct cras_rstream *stream) { struct cras_server_metrics_message msg; union cras_server_metrics_data data; int err; - data.value = delay_msec; + data.stream_data.client_type = stream->client_type; + data.stream_data.stream_type = stream->stream_type; + data.stream_data.direction = stream->direction; + + /* + * There is no delay when the sleep_interval_ts larger than the + * longest_fetch_interval. + */ + if (!timespec_after(&stream->longest_fetch_interval, + &stream->sleep_interval_ts)) { + data.stream_data.runtime.tv_sec = 0; + data.stream_data.runtime.tv_nsec = 0; + } else { + subtract_timespecs(&stream->longest_fetch_interval, + &stream->sleep_interval_ts, + &data.stream_data.runtime); + } + init_server_metrics_msg(&msg, LONGEST_FETCH_DELAY, data); err = cras_server_metrics_message_send( (struct cras_main_message *)&msg); @@ -869,7 +1004,8 @@ int cras_server_metrics_stream_runtime(const struct cras_rstream *stream) struct timespec now; int err; - data.stream_data.type = stream->client_type; + data.stream_data.client_type = stream->client_type; + data.stream_data.stream_type = stream->stream_type; data.stream_data.direction = stream->direction; clock_gettime(CLOCK_MONOTONIC_RAW, &now); subtract_timespecs(&now, &stream->start_ts, &data.stream_data.runtime); @@ -899,7 +1035,9 @@ int cras_server_metrics_stream_destroy(const struct cras_rstream *stream) if (rc < 0) return rc; rc = cras_server_metrics_stream_runtime(stream); - return rc; + if (rc < 0) + return rc; + return cras_server_metrics_longest_fetch_delay(stream); } int cras_server_metrics_busyloop(struct timespec *ts, unsigned count) @@ -960,6 +1098,15 @@ static void metrics_device_runtime(struct cras_server_metrics_device_data data) cras_metrics_log_sparse_histogram(kDeviceTypeOutput, data.type); } +static void metrics_device_gain(struct cras_server_metrics_device_data data) +{ + char metrics_name[METRICS_NAME_BUFFER_SIZE]; + + snprintf(metrics_name, METRICS_NAME_BUFFER_SIZE, "%s.%s", kDeviceGain, + metrics_device_type_str(data.type)); + cras_metrics_log_histogram(metrics_name, data.value, 0, 2000, 20); +} + static void metrics_device_volume(struct cras_server_metrics_device_data data) { char metrics_name[METRICS_NAME_BUFFER_SIZE]; @@ -969,21 +1116,24 @@ static void metrics_device_volume(struct cras_server_metrics_device_data data) cras_metrics_log_histogram(metrics_name, data.value, 0, 100, 20); } -static void metrics_stream_runtime(struct cras_server_metrics_stream_data data) +static void +metrics_longest_fetch_delay(struct cras_server_metrics_stream_data data) { - char metrics_name[METRICS_NAME_BUFFER_SIZE]; - - snprintf(metrics_name, METRICS_NAME_BUFFER_SIZE, "Cras.%sStreamRuntime", - data.direction == CRAS_STREAM_INPUT ? "Input" : "Output"); - cras_metrics_log_histogram(metrics_name, (unsigned)data.runtime.tv_sec, - 0, 10000, 20); + int fetch_delay_msec = + data.runtime.tv_sec * 1000 + data.runtime.tv_nsec / 1000000; + log_histogram_each_level(3, fetch_delay_msec, 0, 10000, 20, + kFetchDelayMilliSeconds, + metrics_client_type_str(data.client_type), + metrics_stream_type_str(data.stream_type)); +} - snprintf(metrics_name, METRICS_NAME_BUFFER_SIZE, - "Cras.%sStreamRuntime.%s", - data.direction == CRAS_STREAM_INPUT ? "Input" : "Output", - metrics_client_type_str(data.type)); - cras_metrics_log_histogram(metrics_name, (unsigned)data.runtime.tv_sec, - 0, 10000, 20); +static void metrics_stream_runtime(struct cras_server_metrics_stream_data data) +{ + log_histogram_each_level( + 4, (int)data.runtime.tv_sec, 0, 10000, 20, kStreamRuntime, + data.direction == CRAS_STREAM_INPUT ? "Input" : "Output", + metrics_client_type_str(data.client_type), + metrics_stream_type_str(data.stream_type)); } static void metrics_busyloop(struct cras_server_metrics_timespec_data data) @@ -996,40 +1146,6 @@ static void metrics_busyloop(struct cras_server_metrics_timespec_data data) cras_metrics_log_histogram(metrics_name, data.count, 0, 1000, 20); } -/* - * Logs metrics for each group it belongs to. The UMA does not merge subgroups - * automatically so we need to log them separately. - * - * For example, if we call this function with argument (3, 48000, - * Cras.StreamSamplingRate, Input, Chrome), it will send 48000 to below - * metrics: - * Cras.StreamSamplingRate.Input.Chrome - * Cras.StreamSamplingRate.Input - * Cras.StreamSamplingRate - */ -static void log_sparse_histogram_each_level(int num, int sample, ...) -{ - char metrics_name[METRICS_NAME_BUFFER_SIZE] = {}; - va_list valist; - int i, len = 0; - - va_start(valist, sample); - - for (i = 0; i < num && len < METRICS_NAME_BUFFER_SIZE; i++) { - int metric_len = snprintf(metrics_name + len, - METRICS_NAME_BUFFER_SIZE - len, "%s%s", - i ? "." : "", va_arg(valist, char *)); - // Exit early on error or running out of bufferspace. Avoids - // logging partial or corrupted strings. - if (metric_len < 0 || metric_len > METRICS_NAME_BUFFER_SIZE - len) - break; - len += metric_len; - cras_metrics_log_sparse_histogram(metrics_name, sample); - } - - va_end(valist); -} - static void metrics_stream_config(struct cras_server_metrics_stream_config config) { @@ -1105,6 +1221,9 @@ static void handle_metrics_message(struct cras_main_message *msg, void *arg) kHfpWidebandSpeechSelectedCodec, metrics_msg->data.value); break; + case DEVICE_GAIN: + metrics_device_gain(metrics_msg->data.device_data); + break; case DEVICE_RUNTIME: metrics_device_runtime(metrics_msg->data.device_data); break; @@ -1132,9 +1251,7 @@ static void handle_metrics_message(struct cras_main_message *msg, void *arg) 20); break; case LONGEST_FETCH_DELAY: - cras_metrics_log_histogram(kStreamTimeoutMilliSeconds, - metrics_msg->data.value, 1, 20000, - 10); + metrics_longest_fetch_delay(metrics_msg->data.stream_data); break; case MISSED_CB_FIRST_TIME_INPUT: cras_metrics_log_histogram(kMissedCallbackFirstTimeInput, diff --git a/cras/src/server/cras_server_metrics.h b/cras/src/server/cras_server_metrics.h index 91f13c3c..e8458087 100644 --- a/cras/src/server/cras_server_metrics.h +++ b/cras/src/server/cras_server_metrics.h @@ -49,6 +49,9 @@ int cras_server_metrics_hfp_packet_loss(float packet_loss_ratio); /* Logs runtime of a device. */ int cras_server_metrics_device_runtime(struct cras_iodev *iodev); +/* Logs the gain of a device. */ +int cras_server_metrics_device_gain(struct cras_iodev *iodev); + /* Logs the volume of a device. */ int cras_server_metrics_device_volume(struct cras_iodev *iodev); @@ -61,9 +64,6 @@ int cras_server_metrics_highest_device_delay( int cras_server_metrics_highest_hw_level(unsigned hw_level, enum CRAS_STREAM_DIRECTION direction); -/* Logs the longest fetch delay of a stream in millisecond. */ -int cras_server_metrics_longest_fetch_delay(unsigned delay_msec); - /* Logs the number of underruns of a device. */ int cras_server_metrics_num_underruns(unsigned num_underruns); diff --git a/cras/src/server/cras_system_state.c b/cras/src/server/cras_system_state.c index 331ecb11..366afb5f 100644 --- a/cras/src/server/cras_system_state.c +++ b/cras/src/server/cras_system_state.c @@ -14,9 +14,11 @@ #include <syslog.h> #include "cras_alsa_card.h" +#include "cras_alert.h" #include "cras_board_config.h" #include "cras_config.h" #include "cras_device_blocklist.h" +#include "cras_iodev_list.h" #include "cras_observer.h" #include "cras_shm.h" #include "cras_system_state.h" @@ -158,6 +160,9 @@ void cras_system_state_init(const char *device_config_dir, const char *shm_name, exp_state->bt_wbs_enabled = board_config.bt_wbs_enabled; exp_state->deprioritize_bt_wbs_mic = board_config.deprioritize_bt_wbs_mic; + exp_state->noise_cancellation_enabled = 0; + exp_state->hotword_pause_at_suspend = + board_config.hotword_pause_at_suspend; if ((rc = pthread_mutex_init(&state.update_lock, 0) != 0)) { syslog(LOG_ERR, "Fatal: system state mutex init"); @@ -341,6 +346,7 @@ void cras_system_set_suspended(int suspended) { state.exp_state->suspended = suspended; cras_observer_notify_suspend_changed(suspended); + cras_alert_process_all_pending_alerts(); } void cras_system_set_volume_limits(long min, long max) @@ -399,6 +405,20 @@ bool cras_system_get_bt_fix_a2dp_packet_size_enabled() return state.bt_fix_a2dp_packet_size; } +void cras_system_set_noise_cancellation_enabled(bool enabled) +{ + /* When the flag is toggled, propagate to all iodevs immediately. */ + if (cras_system_get_noise_cancellation_enabled() != enabled) { + state.exp_state->noise_cancellation_enabled = enabled; + cras_iodev_list_reset_for_noise_cancellation(); + } +} + +bool cras_system_get_noise_cancellation_enabled() +{ + return !!state.exp_state->noise_cancellation_enabled; +} + bool cras_system_check_ignore_ucm_suffix(const char *card_name) { /* Check the general case: ALSA Loopback card "Loopback". */ @@ -414,6 +434,16 @@ bool cras_system_check_ignore_ucm_suffix(const char *card_name) return false; } +bool cras_system_get_hotword_pause_at_suspend() +{ + return !!state.exp_state->hotword_pause_at_suspend; +} + +void cras_system_set_hotword_pause_at_suspend(bool pause) +{ + state.exp_state->hotword_pause_at_suspend = pause; +} + int cras_system_add_alsa_card(struct cras_alsa_card_info *alsa_card_info) { struct card_list *card; diff --git a/cras/src/server/cras_system_state.h b/cras/src/server/cras_system_state.h index ff04606a..bd09395c 100644 --- a/cras/src/server/cras_system_state.h +++ b/cras/src/server/cras_system_state.h @@ -134,9 +134,21 @@ void cras_system_set_bt_fix_a2dp_packet_size_enabled(bool enabled); /* Gets the flag of Bluetooth fixed A2DP packet size. */ bool cras_system_get_bt_fix_a2dp_packet_size_enabled(); +/* Sets the flag to enable or disable Noise Cancellation. */ +void cras_system_set_noise_cancellation_enabled(bool enabled); + +/* Gets the flag of Noise Cancellation. */ +bool cras_system_get_noise_cancellation_enabled(); + /* Checks if the card ignores the ucm suffix. */ bool cras_system_check_ignore_ucm_suffix(const char *card_name); +/* Returns true if hotword detection is paused at system suspend. */ +bool cras_system_get_hotword_pause_at_suspend(); + +/* Sets whether to pause hotword detection at system suspend. */ +void cras_system_set_hotword_pause_at_suspend(bool pause); + /* Adds a card at the given index to the system. When a new card is found * (through a udev event notification) this will add the card to the system, * causing its devices to become available for playback/capture. diff --git a/cras/src/server/dev_io.c b/cras/src/server/dev_io.c index 42fe9558..b311b221 100644 --- a/cras/src/server/dev_io.c +++ b/cras/src/server/dev_io.c @@ -10,6 +10,7 @@ #include "audio_thread_log.h" #include "cras_audio_area.h" #include "cras_audio_thread_monitor.h" +#include "cras_device_monitor.h" #include "cras_iodev.h" #include "cras_non_empty_audio_handler.h" #include "cras_rstream.h" @@ -47,33 +48,78 @@ static const int DROP_FRAMES_THRESHOLD_MS = 50; /* The number of devices playing/capturing non-empty stream(s). */ static int non_empty_device_count = 0; -/* Gets the master device which the stream is attached to. */ -static inline struct cras_iodev *get_master_dev(const struct dev_stream *stream) +/* The timestamp of last EIO error time. */ +static struct timespec last_io_err_time = { 0, 0 }; + +/* The gap time to avoid repeated error close request to main thread. */ +static const int ERROR_CLOSE_GAP_TIME_SECS = 10; + +/* Gets the main device which the stream is attached to. */ +static inline struct cras_iodev *get_main_dev(const struct dev_stream *stream) { - return (struct cras_iodev *)stream->stream->master_dev.dev_ptr; + return (struct cras_iodev *)stream->stream->main_dev.dev_ptr; } /* Updates the estimated sample rate of open device to all attached * streams. */ -static void update_estimated_rate(struct open_dev *adev) +static void update_estimated_rate(struct open_dev *adev, + struct open_dev *odev_list, + bool self_rate_need_update) { - struct cras_iodev *master_dev; + struct cras_iodev *main_dev; struct cras_iodev *dev = adev->dev; + struct cras_iodev *tracked_dev = NULL; struct dev_stream *dev_stream; + double dev_rate_ratio; + double main_dev_rate_ratio; + + /* + * If there is an output device on the same sound card running with the same + * sampling rate, use the rate of that output device for this device. + */ + if (dev->direction == CRAS_STREAM_INPUT && + cras_iodev_is_on_internal_card(dev->active_node)) { + struct open_dev *odev; + DL_FOREACH (odev_list, odev) { + if (!cras_iodev_is_on_internal_card( + odev->dev->active_node)) + continue; + if (odev->dev->format->frame_rate != + dev->format->frame_rate) + continue; + tracked_dev = odev->dev; + break; + } + } + + /* + * Self-owned rate esimator does not need to udpate rate. There is no tracked + * output device. So there is no need to update. + */ + if (!self_rate_need_update && !tracked_dev) + return; DL_FOREACH (dev->streams, dev_stream) { - master_dev = get_master_dev(dev_stream); - if (master_dev == NULL) { - syslog(LOG_ERR, "Fail to find master open dev."); + main_dev = get_main_dev(dev_stream); + if (main_dev == NULL) { + syslog(LOG_ERR, "Fail to find main open dev."); continue; } - dev_stream_set_dev_rate( - dev_stream, dev->format->frame_rate, - cras_iodev_get_est_rate_ratio(dev), - cras_iodev_get_est_rate_ratio(master_dev), - adev->coarse_rate_adjust); + if (tracked_dev) { + dev_rate_ratio = + cras_iodev_get_est_rate_ratio(tracked_dev); + main_dev_rate_ratio = dev_rate_ratio; + } else { + dev_rate_ratio = cras_iodev_get_est_rate_ratio(dev); + main_dev_rate_ratio = + cras_iodev_get_est_rate_ratio(main_dev); + } + + dev_stream_set_dev_rate(dev_stream, dev->format->frame_rate, + dev_rate_ratio, main_dev_rate_ratio, + adev->coarse_rate_adjust); } } @@ -464,7 +510,7 @@ static int set_input_dev_wake_ts(struct open_dev *adev, bool *need_to_drop) * adev - The device to capture samples from. * Returns 0 on success. */ -static int capture_to_streams(struct open_dev *adev) +static int capture_to_streams(struct open_dev *adev, struct open_dev *odev_list) { struct cras_iodev *idev = adev->dev; snd_pcm_uframes_t remainder, hw_level, cap_limit; @@ -486,14 +532,29 @@ static int capture_to_streams(struct open_dev *adev) ATLOG(atlog, AUDIO_THREAD_READ_AUDIO_TSTAMP, idev->info.idx, hw_tstamp.tv_sec, hw_tstamp.tv_nsec); if (timespec_is_nonzero(&hw_tstamp)) { + bool self_rate_need_update; + if (hw_level < idev->min_cb_level / 2) adev->coarse_rate_adjust = 1; else if (hw_level > idev->max_cb_level * 2) adev->coarse_rate_adjust = -1; else adev->coarse_rate_adjust = 0; - if (cras_iodev_update_rate(idev, hw_level, &hw_tstamp)) - update_estimated_rate(adev); + + /* + * This values means whether the rate estimator in the device + * wants to update estimated rate. + */ + self_rate_need_update = + !!cras_iodev_update_rate(idev, hw_level, &hw_tstamp); + + /* + * Always calls update_estimated_rate so that new output rate + * has a chance to propagate to input. In update_estimated_rate, + * it will decide whether the new rate is from self rate estimator + * or from the tracked output device. + */ + update_estimated_rate(adev, odev_list, self_rate_need_update); } cap_limit = get_stream_limit(adev, hw_level, &cap_limit_stream); @@ -579,12 +640,13 @@ static int capture_to_streams(struct open_dev *adev) * write_limit - The maximum number of frames to write to dst. * * Returns: - * The number of frames rendered on success, a negative error code otherwise. + * The number of frames rendered on success. * This number of frames is the minimum of the amount of frames each stream * could provide which is the maximum that can currently be rendered. */ -static int write_streams(struct open_dev **odevs, struct open_dev *adev, - uint8_t *dst, size_t write_limit) +static unsigned int write_streams(struct open_dev **odevs, + struct open_dev *adev, uint8_t *dst, + size_t write_limit) { struct cras_iodev *odev = adev->dev; struct dev_stream *curr; @@ -746,7 +808,7 @@ int write_output_samples(struct open_dev **odevs, struct open_dev *adev, adev->coarse_rate_adjust = 0; if (cras_iodev_update_rate(odev, hw_level, &hw_tstamp)) - update_estimated_rate(adev); + update_estimated_rate(adev, NULL, true); } ATLOG(atlog, AUDIO_THREAD_FILL_AUDIO, adev->dev->info.idx, hw_level, odev->min_cb_level); @@ -768,9 +830,6 @@ int write_output_samples(struct open_dev **odevs, struct open_dev *adev, /* 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. */ @@ -934,27 +993,46 @@ int dev_io_send_captured_samples(struct open_dev *idev_list) static void handle_dev_err(int err_rc, struct open_dev **odevs, struct open_dev *adev) { + struct timespec diff, now; if (err_rc == -EPIPE) { /* Handle severe underrun. */ ATLOG(atlog, AUDIO_THREAD_SEVERE_UNDERRUN, adev->dev->info.idx, 0, 0); cras_iodev_reset_request(adev->dev); cras_audio_thread_event_severe_underrun(); + } else if (err_rc == -EIO) { + syslog(LOG_WARNING, "I/O err, reseting %s dev %s", + adev->dev->direction == CRAS_STREAM_OUTPUT ? "output" : + "input", + adev->dev->info.name); + clock_gettime(CLOCK_REALTIME, &now); + subtract_timespecs(&now, &last_io_err_time, &diff); + if ((last_io_err_time.tv_sec == 0 && + last_io_err_time.tv_nsec == 0) || + diff.tv_sec > ERROR_CLOSE_GAP_TIME_SECS) + cras_iodev_reset_request(adev->dev); + else + cras_device_monitor_error_close(adev->dev->info.idx); + + last_io_err_time = now; + } else { + syslog(LOG_ERR, "Dev %s err %d", adev->dev->info.name, err_rc); } /* Device error, remove it. */ dev_io_rm_open_dev(odevs, adev); } -int dev_io_capture(struct open_dev **list) +int dev_io_capture(struct open_dev **list, struct open_dev **olist) { struct open_dev *idev_list = *list; + struct open_dev *odev_list = *olist; struct open_dev *adev; int rc; DL_FOREACH (idev_list, adev) { if (!cras_iodev_is_open(adev->dev)) continue; - rc = capture_to_streams(adev); + rc = capture_to_streams(adev, odev_list); if (rc < 0) handle_dev_err(rc, list, adev); } @@ -1105,7 +1183,7 @@ void dev_io_run(struct open_dev **odevs, struct open_dev **idevs, update_longest_wake(*idevs, &now); dev_io_playback_fetch(*odevs); - dev_io_capture(idevs); + dev_io_capture(idevs, odevs); dev_io_send_captured_samples(*idevs); dev_io_playback_write(odevs, output_converter); } @@ -1259,14 +1337,61 @@ static void delete_stream_from_dev(struct cras_iodev *dev, dev_stream_destroy(out); } -int dev_io_append_stream(struct open_dev **dev_list, +/* + * Finds a matched input stream from open device list. + * The definition of the matched streams: Two streams having + * the same sampling rate and the same cb_threshold. + * This means their sleep time intervals should be very close + * if we neglect device estimated rate. + */ +static struct dev_stream * +find_matched_input_stream(const struct cras_rstream *out_stream, + struct open_dev *odev_list) +{ + struct open_dev *odev; + struct dev_stream *dev_stream; + size_t out_rate = out_stream->format.frame_rate; + size_t out_cb_threshold = cras_rstream_get_cb_threshold(out_stream); + + DL_FOREACH (odev_list, odev) { + DL_FOREACH (odev->dev->streams, dev_stream) { + if (dev_stream->stream->format.frame_rate != out_rate) + continue; + if (cras_rstream_get_cb_threshold(dev_stream->stream) != + out_cb_threshold) + continue; + return dev_stream; + } + } + return NULL; +} + +static bool +find_matched_input_stream_next_cb_ts(const struct cras_rstream *stream, + struct open_dev *odev_list, + const struct timespec **next_cb_ts, + const struct timespec **sleep_interval_ts) +{ + struct dev_stream *dev_stream = + find_matched_input_stream(stream, odev_list); + if (dev_stream) { + *next_cb_ts = dev_stream_next_cb_ts(dev_stream); + *sleep_interval_ts = dev_stream_sleep_interval_ts(dev_stream); + return *next_cb_ts != NULL; + } + return false; +} + +int dev_io_append_stream(struct open_dev **odevs, struct open_dev **idevs, struct cras_rstream *stream, struct cras_iodev **iodevs, unsigned int num_iodevs) { + struct open_dev **dev_list; struct open_dev *open_dev; struct cras_iodev *dev; struct dev_stream *out; struct timespec init_cb_ts; + const struct timespec *init_sleep_interval_ts = NULL; struct timespec extra_sleep; const struct timespec *stream_ts; unsigned int i; @@ -1274,6 +1399,11 @@ int dev_io_append_stream(struct open_dev **dev_list, int level; int rc = 0; + if (stream->direction == CRAS_STREAM_OUTPUT) + dev_list = odevs; + else + dev_list = idevs; + for (i = 0; i < num_iodevs; i++) { DL_SEARCH_SCALAR(*dev_list, open_dev, dev, iodevs[i]); if (!open_dev) @@ -1318,35 +1448,55 @@ int dev_io_append_stream(struct open_dev **dev_list, * may cause device buffer level stack up. */ if (stream->direction == CRAS_STREAM_OUTPUT) { - DL_FOREACH (dev->streams, out) { - stream_ts = dev_stream_next_cb_ts(out); - if (stream_ts && - (!cb_ts_set || - timespec_after(&init_cb_ts, stream_ts))) { - init_cb_ts = *stream_ts; - cb_ts_set = true; + /* + * If there is a matched input stream, find its next cb time. + * Use that as the initial cb time for this output stream. + */ + const struct timespec *in_stream_ts; + const struct timespec *in_stream_sleep_interval_ts; + bool found_matched_input; + found_matched_input = + find_matched_input_stream_next_cb_ts( + stream, *idevs, &in_stream_ts, + &in_stream_sleep_interval_ts); + if (found_matched_input) { + init_cb_ts = *in_stream_ts; + init_sleep_interval_ts = + in_stream_sleep_interval_ts; + } else { + DL_FOREACH (dev->streams, out) { + stream_ts = dev_stream_next_cb_ts(out); + if (stream_ts && + (!cb_ts_set || + timespec_after(&init_cb_ts, + stream_ts))) { + init_cb_ts = *stream_ts; + cb_ts_set = true; + } } - } - if (!cb_ts_set) { - level = cras_iodev_get_valid_frames( - dev, &init_cb_ts); - if (level < 0) { - syslog(LOG_ERR, - "Failed to set output init_cb_ts, rc = %d", - level); - rc = -EINVAL; - break; + if (!cb_ts_set) { + level = cras_iodev_get_valid_frames( + dev, &init_cb_ts); + if (level < 0) { + syslog(LOG_ERR, + "Failed to set output init_cb_ts, rc = %d", + level); + rc = -EINVAL; + break; + } + level -= cras_frames_at_rate( + stream->format.frame_rate, + cras_rstream_get_cb_threshold( + stream), + dev->format->frame_rate); + if (level < 0) + level = 0; + cras_frames_to_time( + level, dev->format->frame_rate, + &extra_sleep); + add_timespecs(&init_cb_ts, + &extra_sleep); } - level -= cras_frames_at_rate( - stream->format.frame_rate, - cras_rstream_get_cb_threshold(stream), - dev->format->frame_rate); - if (level < 0) - level = 0; - cras_frames_to_time(level, - dev->format->frame_rate, - &extra_sleep); - add_timespecs(&init_cb_ts, &extra_sleep); } } else { /* @@ -1365,7 +1515,7 @@ int dev_io_append_stream(struct open_dev **dev_list, } out = dev_stream_create(stream, dev->info.idx, dev->format, dev, - &init_cb_ts); + &init_cb_ts, init_sleep_interval_ts); if (!out) { rc = -EINVAL; break; @@ -1418,20 +1568,6 @@ 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); diff --git a/cras/src/server/dev_io.h b/cras/src/server/dev_io.h index 259bbabd..ca71a809 100644 --- a/cras/src/server/dev_io.h +++ b/cras/src/server/dev_io.h @@ -58,8 +58,9 @@ int write_output_samples(struct open_dev **odevs, struct open_dev *adev, * Captures samples from each device in the list. * list - Pointer to the list of input devices. Devices that fail to read * will be removed from the list. + * olist - Pointer to the list of output devices. */ -int dev_io_capture(struct open_dev **list); +int dev_io_capture(struct open_dev **list, struct open_dev **olist); /* * Send samples that have been captured to their streams. @@ -101,7 +102,7 @@ struct open_dev *dev_io_find_open_dev(struct open_dev *odev_list, unsigned int dev_idx); /* Append a new stream to a specified set of iodevs. */ -int dev_io_append_stream(struct open_dev **dev_list, +int dev_io_append_stream(struct open_dev **odevs, struct open_dev **idevs, struct cras_rstream *stream, struct cras_iodev **iodevs, unsigned int num_iodevs); diff --git a/cras/src/server/dev_stream.c b/cras/src/server/dev_stream.c index 025aeddd..be5a6dab 100644 --- a/cras/src/server/dev_stream.c +++ b/cras/src/server/dev_stream.c @@ -63,7 +63,8 @@ unsigned int max_frames_for_conversion(unsigned int stream_frames, struct dev_stream *dev_stream_create(struct cras_rstream *stream, unsigned int dev_id, const struct cras_audio_format *dev_fmt, - void *dev_ptr, struct timespec *cb_ts) + void *dev_ptr, struct timespec *cb_ts, + const struct timespec *sleep_interval_ts) { struct dev_stream *out; struct cras_audio_format *stream_fmt = &stream->format; @@ -122,8 +123,15 @@ struct dev_stream *dev_stream_create(struct cras_rstream *stream, out->conv_buffer = byte_buffer_create(buf_bytes); out->conv_area = cras_audio_area_create(ofmt->num_channels); - cras_frames_to_time(cras_rstream_get_cb_threshold(stream), - stream_fmt->frame_rate, &stream->sleep_interval_ts); + /* Use sleep interval hint from argument if it is provided */ + if (sleep_interval_ts) { + stream->sleep_interval_ts = *sleep_interval_ts; + } else { + cras_frames_to_time(cras_rstream_get_cb_threshold(stream), + stream_fmt->frame_rate, + &stream->sleep_interval_ts); + } + stream->next_cb_ts = *cb_ts; /* Sets up the stream & dev pair. */ @@ -149,9 +157,9 @@ void dev_stream_destroy(struct dev_stream *dev_stream) void dev_stream_set_dev_rate(struct dev_stream *dev_stream, unsigned int dev_rate, double dev_rate_ratio, - double master_rate_ratio, int coarse_rate_adjust) + double main_rate_ratio, int coarse_rate_adjust) { - if (dev_stream->dev_id == dev_stream->stream->master_dev.dev_id) { + if (dev_stream->dev_id == dev_stream->stream->main_dev.dev_id) { cras_fmt_conv_set_linear_resample_rates(dev_stream->conv, dev_rate, dev_rate); cras_frames_to_time_precise( @@ -159,9 +167,8 @@ void dev_stream_set_dev_rate(struct dev_stream *dev_stream, dev_stream->stream->format.frame_rate * dev_rate_ratio, &dev_stream->stream->sleep_interval_ts); } else { - double new_rate = - dev_rate * dev_rate_ratio / master_rate_ratio + - coarse_rate_adjust_step * coarse_rate_adjust; + double new_rate = dev_rate * dev_rate_ratio / main_rate_ratio + + coarse_rate_adjust_step * coarse_rate_adjust; cras_fmt_conv_set_linear_resample_rates(dev_stream->conv, dev_rate, new_rate); } diff --git a/cras/src/server/dev_stream.h b/cras/src/server/dev_stream.h index c39a8017..6b34d5d7 100644 --- a/cras/src/server/dev_stream.h +++ b/cras/src/server/dev_stream.h @@ -46,30 +46,47 @@ struct dev_stream { int is_running; }; +/* + * Creates a dev_stream. + * + * Args: + * stream - The associated rstream. + * dev_id - Index of the device. + * dev_fmt - The format of the device. + * dev_ptr - A pointer to the device + * cb_ts - A pointer to the initial callback time. + * sleep_interval_ts - A pointer to the initial sleep interval. + * Set to null to calculate the value from device rate and block size. + * Note that we need this argument so that output device sleep interval + * can use input device sleep interval in the beginning to have perfect + * alignment in WebRTC use case. + * Returns the pointer to the created dev_stream. + */ struct dev_stream *dev_stream_create(struct cras_rstream *stream, unsigned int dev_id, const struct cras_audio_format *dev_fmt, - void *dev_ptr, struct timespec *cb_ts); + void *dev_ptr, struct timespec *cb_ts, + const struct timespec *sleep_interval_ts); void dev_stream_destroy(struct dev_stream *dev_stream); /* * Update the estimated sample rate of the device. For multiple active * devices case, the linear resampler will be configured by the estimated - * rate ration of the master device and the current active device the + * rate ration of the main device and the current active device the * rstream attaches to. * * Args: * dev_stream - The structure holding the stream. * dev_rate - The sample rate device is using. * dev_rate_ratio - The ratio of estimated rate and used rate. - * master_rate_ratio - The ratio of estimated rate and used rate of - * master device. + * main_rate_ratio - The ratio of estimated rate and used rate of + * main device. * coarse_rate_adjust - The flag to indicate the direction device * sample rate should adjust to. */ void dev_stream_set_dev_rate(struct dev_stream *dev_stream, unsigned int dev_rate, double dev_rate_ratio, - double master_rate_ratio, int coarse_rate_adjust); + double main_rate_ratio, int coarse_rate_adjust); /* * Renders count frames from shm into dst. Updates count if anything is diff --git a/cras/src/server/server_stream.c b/cras/src/server/server_stream.c index 6644c469..36d5496e 100644 --- a/cras/src/server/server_stream.c +++ b/cras/src/server/server_stream.c @@ -83,6 +83,5 @@ void server_stream_destroy(struct stream_list *stream_list, syslog(LOG_ERR, "No server stream to destroy"); return; } - /* Schedule remove stream in next main thread loop. */ - cras_system_add_task(server_stream_rm_cb, stream_list); + server_stream_rm_cb(stream_list); } diff --git a/cras/src/server/stream_list.c b/cras/src/server/stream_list.c index 719608a4..04ef9fe1 100644 --- a/cras/src/server/stream_list.c +++ b/cras/src/server/stream_list.c @@ -3,12 +3,19 @@ * found in the LICENSE file. */ +#include <syslog.h> #include "cras_rstream.h" #include "cras_tm.h" #include "cras_types.h" #include "stream_list.h" #include "utlist.h" +/* + * If the time difference of two streams is short than 10s, they may be the RTC + * streams. + */ +static const struct timespec RTC_STREAM_THRESHOLD = { 10, 0 }; + struct stream_list { struct cras_rstream *streams; struct cras_rstream *streams_to_delete; @@ -154,3 +161,28 @@ bool stream_list_has_pinned_stream(struct stream_list *list, } return false; } + +void detect_rtc_stream_pair(struct stream_list *list, + struct cras_rstream *stream) +{ + struct cras_rstream *next_stream; + if (stream->cb_threshold != 480) + return; + if (stream->client_type != CRAS_CLIENT_TYPE_CHROME && + stream->client_type != CRAS_CLIENT_TYPE_LACROS) + return; + DL_FOREACH (list->streams, next_stream) { + if (next_stream->cb_threshold == 480 && + next_stream->direction != stream->direction && + next_stream->client_type == stream->client_type && + timespec_diff_shorter_than(&stream->start_ts, + &next_stream->start_ts, + &RTC_STREAM_THRESHOLD)) { + stream->stream_type = + CRAS_STREAM_TYPE_VOICE_COMMUNICATION; + next_stream->stream_type = + CRAS_STREAM_TYPE_VOICE_COMMUNICATION; + return; + } + } +} diff --git a/cras/src/server/stream_list.h b/cras/src/server/stream_list.h index 0a9b86a2..a527bc97 100644 --- a/cras/src/server/stream_list.h +++ b/cras/src/server/stream_list.h @@ -55,3 +55,14 @@ int stream_list_rm_all_client_streams(struct stream_list *list, */ bool stream_list_has_pinned_stream(struct stream_list *list, unsigned int dev_idx); + +/* + * Detects whether there is a RTC stream pair based on these rules: + * 1. The cb_threshold is 480. + * 2. The direction of two streams are opposite. + * 3. Two streams are from the same client. (Chrome or LaCrOS) + * 4. The start time of two streams are close enough. (shorter than 1s) + * If all rules are passed, set the stream type to the voice communication. + */ +void detect_rtc_stream_pair(struct stream_list *list, + struct cras_rstream *stream); diff --git a/cras/src/tests/a2dp_iodev_unittest.cc b/cras/src/tests/a2dp_iodev_unittest.cc index 523a62e4..06c1cd3c 100644 --- a/cras/src/tests/a2dp_iodev_unittest.cc +++ b/cras/src/tests/a2dp_iodev_unittest.cc @@ -803,6 +803,10 @@ const char* cras_bt_device_object_path(const struct cras_bt_device* device) { return "/org/bluez/hci0/dev_1A_2B_3C_4D_5E_6F"; } +int cras_bt_device_get_stable_id(const struct cras_bt_device* device) { + return 123; +} + void cras_bt_device_append_iodev(struct cras_bt_device* device, struct cras_iodev* iodev, enum cras_bt_device_profile profile) { diff --git a/cras/src/tests/alsa_io_unittest.cc b/cras/src/tests/alsa_io_unittest.cc index b3059a23..021b4789 100644 --- a/cras/src/tests/alsa_io_unittest.cc +++ b/cras/src/tests/alsa_io_unittest.cc @@ -2559,6 +2559,10 @@ void cras_system_set_volume_limits(long min, long max) { sys_set_volume_limits_called++; } +bool cras_system_get_noise_cancellation_enabled() { + return false; +} + // From cras_alsa_mixer. void cras_alsa_mixer_set_dBFS(struct cras_alsa_mixer* m, long dB_level, @@ -2807,6 +2811,17 @@ int ucm_get_channels_for_dev(struct cras_use_case_mgr* mgr, return -EINVAL; } +int ucm_node_noise_cancellation_exists(struct cras_use_case_mgr* mgr, + const char* node_name) { + return 0; +} + +int ucm_enable_node_noise_cancellation(struct cras_use_case_mgr* mgr, + const char* node_name, + int enable) { + return 0; +} + struct cras_volume_curve* cras_volume_curve_create_default() { return &default_curve; } @@ -2888,6 +2903,12 @@ const char* cras_alsa_jack_get_ucm_device(const struct cras_alsa_jack* jack) { return NULL; } +void ucm_disable_all_hotword_models(struct cras_use_case_mgr* mgr) {} + +int ucm_enable_hotword_model(struct cras_use_case_mgr* mgr) { + return 0; +} + int ucm_get_default_node_gain(struct cras_use_case_mgr* mgr, const char* dev, long* gain) { diff --git a/cras/src/tests/alsa_mixer_unittest.cc b/cras/src/tests/alsa_mixer_unittest.cc index edf61101..b3db9de5 100644 --- a/cras/src/tests/alsa_mixer_unittest.cc +++ b/cras/src/tests/alsa_mixer_unittest.cc @@ -381,7 +381,7 @@ TEST(AlsaMixer, CreateOneUnknownElementWithVolume) { mixer_control_destroy(mixer_output); } -TEST(AlsaMixer, CreateOneMasterElement) { +TEST(AlsaMixer, CreateOneMainElement) { struct cras_alsa_mixer* c; int element_playback_volume[] = { 1, @@ -419,10 +419,10 @@ TEST(AlsaMixer, CreateOneMasterElement) { EXPECT_EQ(3, snd_mixer_selem_get_name_called); EXPECT_EQ(1, snd_mixer_elem_next_called); - /* set mute should be called for Master. */ + /* set mute should be called for Main. */ cras_alsa_mixer_set_mute(c, 0, NULL); EXPECT_EQ(1, snd_mixer_selem_set_playback_switch_all_called); - /* set volume should be called for Master. */ + /* set volume should be called for Main. */ cras_alsa_mixer_set_dBFS(c, 0, NULL); EXPECT_EQ(1, snd_mixer_selem_set_playback_dB_all_called); @@ -515,15 +515,15 @@ TEST(AlsaMixer, CreateTwoMainVolumeElements) { EXPECT_EQ(5, snd_mixer_selem_get_name_called); EXPECT_EQ(3, snd_mixer_selem_has_playback_switch_called); - /* Set mute should be called for Master only. */ + /* Set mute should be called for Main only. */ cras_alsa_mixer_set_mute(c, 0, NULL); EXPECT_EQ(1, snd_mixer_selem_set_playback_switch_all_called); - /* Set volume should be called for Master and PCM. If Master doesn't set to + /* Set volume should be called for Main and PCM. If Main doesn't set to * anything but zero then the entire volume should be passed to the PCM * control.*/ - /* Set volume should be called for Master and PCM. (without mixer_output) */ + /* Set volume should be called for Main and PCM. (without mixer_output) */ snd_mixer_selem_get_playback_dB_return_values = get_dB_returns; snd_mixer_selem_get_playback_dB_return_values_length = ARRAY_SIZE(get_dB_returns); @@ -557,8 +557,8 @@ TEST(AlsaMixer, CreateTwoMainVolumeElements) { EXPECT_EQ(1, snd_mixer_selem_has_playback_switch_called); EXPECT_EQ(1, snd_mixer_selem_get_playback_dB_range_called); - /* Set volume should be called for Master, PCM, and the mixer_output passed - * in. If Master doesn't set to anything but zero then the entire volume + /* Set volume should be called for Main, PCM, and the mixer_output passed + * in. If Main doesn't set to anything but zero then the entire volume * should be passed to the PCM control.*/ cras_alsa_mixer_set_dBFS(c, -50, mixer_output); EXPECT_EQ(3, snd_mixer_selem_set_playback_dB_all_called); @@ -566,8 +566,8 @@ TEST(AlsaMixer, CreateTwoMainVolumeElements) { EXPECT_EQ(30, set_dB_values[0]); EXPECT_EQ(30, set_dB_values[1]); EXPECT_EQ(30, set_dB_values[2]); - /* Set volume should be called for Master and PCM. Since the controls were - * sorted, Master should get the volume remaining after PCM is set, in this + /* Set volume should be called for Main and PCM. Since the controls were + * sorted, Main should get the volume remaining after PCM is set, in this * case -50 - -24 = -26. */ long get_dB_returns2[] = { -25, @@ -584,7 +584,7 @@ TEST(AlsaMixer, CreateTwoMainVolumeElements) { cras_alsa_mixer_set_dBFS(c, -50, mixer_output); EXPECT_EQ(2, snd_mixer_selem_set_playback_dB_all_called); EXPECT_EQ(2, snd_mixer_selem_get_playback_dB_called); - EXPECT_EQ(54, set_dB_values[0]); // Master + EXPECT_EQ(54, set_dB_values[0]); // Main EXPECT_EQ(30, set_dB_values[1]); // PCM cras_alsa_mixer_destroy(c); @@ -639,7 +639,7 @@ TEST(AlsaMixer, CreateTwoMainCaptureElements) { EXPECT_EQ(5, snd_mixer_selem_get_name_called); EXPECT_EQ(3, snd_mixer_selem_has_capture_switch_called); - /* Set mute should be called for Master only. */ + /* Set mute should be called for Main only. */ cras_alsa_mixer_set_capture_mute(c, 0, NULL); EXPECT_EQ(1, snd_mixer_selem_set_capture_switch_all_called); /* Set volume should be called for Capture and Digital Capture. If Capture @@ -773,7 +773,7 @@ class AlsaMixerOutputs : public testing::Test { ResetStubData(); snd_mixer_first_elem_return_value = - reinterpret_cast<snd_mixer_elem_t*>(1); // Master + reinterpret_cast<snd_mixer_elem_t*>(1); // Main snd_mixer_elem_next_return_values = elements; snd_mixer_elem_next_return_values_length = ARRAY_SIZE(elements); snd_mixer_selem_has_playback_volume_return_values = element_playback_volume; diff --git a/cras/src/tests/alsa_ucm_unittest.cc b/cras/src/tests/alsa_ucm_unittest.cc index 44c35879..1b351ddf 100644 --- a/cras/src/tests/alsa_ucm_unittest.cc +++ b/cras/src/tests/alsa_ucm_unittest.cc @@ -28,11 +28,13 @@ static unsigned snd_use_case_get_called; static std::vector<std::string> snd_use_case_get_id; static int snd_use_case_set_return; static std::map<std::string, std::string> snd_use_case_get_value; +static std::map<std::string, unsigned> snd_use_case_geti_value; static unsigned snd_use_case_set_called; static std::vector<std::pair<std::string, std::string> > snd_use_case_set_param; static std::map<std::string, const char**> fake_list; static std::map<std::string, unsigned> fake_list_size; static unsigned snd_use_case_free_list_called; +static unsigned snd_use_case_geti_called; static std::vector<std::string> list_devices_callback_names; static std::vector<void*> list_devices_callback_args; static struct cras_use_case_mgr cras_ucm_mgr; @@ -45,10 +47,12 @@ static void ResetStubData() { snd_use_case_set_return = 0; snd_use_case_get_called = 0; snd_use_case_set_called = 0; + snd_use_case_geti_called = 0; snd_use_case_set_param.clear(); snd_use_case_free_list_called = 0; snd_use_case_get_id.clear(); snd_use_case_get_value.clear(); + snd_use_case_geti_value.clear(); fake_list.clear(); fake_list_size.clear(); fake_list["_verbs"] = avail_verbs; @@ -57,6 +61,7 @@ static void ResetStubData() { list_devices_callback_args.clear(); snd_use_case_mgr_open_mgr_ptr = reinterpret_cast<snd_use_case_mgr_t*>(0x55); cras_ucm_mgr.use_case = CRAS_STREAM_TYPE_DEFAULT; + cras_ucm_mgr.hotword_modifier = NULL; } static void list_devices_callback(const char* section_name, void* arg) { @@ -522,26 +527,85 @@ TEST(AlsaUcm, SetHotwordModel) { const char* modifiers[] = {"Hotword Model en", "Comment1", "Hotword Model jp", "Comment2", "Hotword Model de", "Comment3"}; - const char* enabled_mods[] = {"Hotword Model en"}; + const char* enabled_mods[] = {"Hotword Model jp"}; + int ret; + std::string id = "_modstatus/Hotword Model jp"; ResetStubData(); + snd_use_case_geti_value[id] = 1; fake_list["_modifiers/HiFi"] = modifiers; fake_list_size["_modifiers/HiFi"] = 6; EXPECT_EQ(-EINVAL, ucm_set_hotword_model(mgr, "zh")); EXPECT_EQ(0, snd_use_case_set_called); + ret = ucm_set_hotword_model(mgr, "jp"); + + EXPECT_EQ(0, ret); + EXPECT_EQ(0, snd_use_case_set_called); + EXPECT_EQ(0, strcmp(mgr->hotword_modifier, "Hotword Model jp")); + fake_list["_enamods"] = enabled_mods; fake_list_size["_enamods"] = 1; - ucm_set_hotword_model(mgr, "jp"); - + ret = ucm_set_hotword_model(mgr, "de"); + EXPECT_EQ(0, ret); EXPECT_EQ(2, snd_use_case_set_called); + EXPECT_EQ(1, snd_use_case_geti_called); EXPECT_EQ( snd_use_case_set_param[0], - std::make_pair(std::string("_dismod"), std::string("Hotword Model en"))); + std::make_pair(std::string("_dismod"), std::string("Hotword Model jp"))); EXPECT_EQ( snd_use_case_set_param[1], - std::make_pair(std::string("_enamod"), std::string("Hotword Model jp"))); + std::make_pair(std::string("_enamod"), std::string("Hotword Model de"))); + free(mgr->hotword_modifier); +} + +TEST(AlsaUcm, DisableAllHotwordModels) { + struct cras_use_case_mgr* mgr = &cras_ucm_mgr; + const char* modifiers[] = {"Hotword Model en", "Comment1", + "Hotword Model jp", "Comment2", + "Hotword Model de", "Comment3"}; + const char* enabled_mods[] = {"Hotword Model en"}; + ResetStubData(); + + fake_list["_modifiers/HiFi"] = modifiers; + fake_list_size["_modifiers/HiFi"] = 6; + fake_list["_enamods"] = enabled_mods; + fake_list_size["_enamods"] = 1; + + ucm_disable_all_hotword_models(mgr); + + EXPECT_EQ(1, snd_use_case_set_called); + EXPECT_EQ( + snd_use_case_set_param[0], + std::make_pair(std::string("_dismod"), std::string("Hotword Model en"))); +} + +TEST(AlsaUcm, EnableHotwordModel) { + struct cras_use_case_mgr* mgr = &cras_ucm_mgr; + const char* modifiers[] = {"Hotword Model en", "Comment1", + "Hotword Model jp", "Comment2", + "Hotword Model de", "Comment3"}; + const char* enabled_mods[] = {""}; + int ret; + ResetStubData(); + + fake_list["_modifiers/HiFi"] = modifiers; + fake_list_size["_modifiers/HiFi"] = 6; + fake_list["_enamods"] = enabled_mods; + fake_list_size["_enamods"] = 0; + + EXPECT_EQ(-EINVAL, ucm_enable_hotword_model(mgr)); + + mgr->hotword_modifier = strdup("Hotword Model de"); + ret = ucm_enable_hotword_model(mgr); + + EXPECT_EQ(0, ret); + EXPECT_EQ(1, snd_use_case_set_called); + EXPECT_EQ( + snd_use_case_set_param[0], + std::make_pair(std::string("_enamod"), std::string("Hotword Model de"))); + free(mgr->hotword_modifier); } TEST(AlsaUcm, SwapModeExists) { @@ -629,6 +693,76 @@ TEST(AlsaUcm, DisableSwapMode) { EXPECT_EQ(1, snd_use_case_set_called); } +TEST(AlsaUcm, NoiseCancellationExists) { + struct cras_use_case_mgr* mgr = &cras_ucm_mgr; + int rc; + const char* node = "Internal Mic"; + const char* modifiers_1[] = {"Internal Mic Noise Cancellation", "Comment"}; + const char* modifiers_2[] = {"Internal Mic Noise Augmentation", "Comment"}; + const char* modifiers_3[] = {"Microphone Noise Cancellation", "Comment"}; + + ResetStubData(); + + fake_list["_modifiers/HiFi"] = modifiers_1; + fake_list_size["_modifiers/HiFi"] = 2; + rc = ucm_node_noise_cancellation_exists(mgr, node); + EXPECT_EQ(1, rc); + + fake_list["_modifiers/HiFi"] = modifiers_2; + fake_list_size["_modifiers/HiFi"] = 2; + rc = ucm_node_noise_cancellation_exists(mgr, node); + EXPECT_EQ(0, rc); + + fake_list["_modifiers/HiFi"] = modifiers_3; + fake_list_size["_modifiers/HiFi"] = 2; + rc = ucm_node_noise_cancellation_exists(mgr, node); + EXPECT_EQ(0, rc); +} + +TEST(AlsaUcm, EnableDisableNoiseCancellation) { + struct cras_use_case_mgr* mgr = &cras_ucm_mgr; + int rc; + const char* modifiers[] = {"Internal Mic Noise Cancellation", "Comment1", + "Microphone Noise Cancellation", "Comment2"}; + const char* modifiers_enabled[] = {"Internal Mic Noise Cancellation"}; + + ResetStubData(); + + fake_list["_modifiers/HiFi"] = modifiers; + fake_list_size["_modifiers/HiFi"] = 4; + + fake_list["_enamods"] = modifiers_enabled; + fake_list_size["_enamods"] = 1; + + snd_use_case_set_return = 0; + + rc = ucm_enable_node_noise_cancellation(mgr, "Line In", 1); + EXPECT_EQ(-EPERM, rc); // Modifier is not existed + EXPECT_EQ(0, snd_use_case_set_called); + + rc = ucm_enable_node_noise_cancellation(mgr, "Line In", 0); + EXPECT_EQ(-EPERM, rc); // Modifier is not existed + EXPECT_EQ(0, snd_use_case_set_called); + + rc = ucm_enable_node_noise_cancellation(mgr, "Microphone", 0); + EXPECT_EQ(0, rc); // Modifier is already disabled + EXPECT_EQ(0, snd_use_case_set_called); + + rc = ucm_enable_node_noise_cancellation(mgr, "Microphone", 1); + EXPECT_EQ(0, rc); + EXPECT_EQ(1, snd_use_case_set_called); + + snd_use_case_set_called = 0; + + rc = ucm_enable_node_noise_cancellation(mgr, "Internal Mic", 1); + EXPECT_EQ(0, rc); // Modifier is already enabled + EXPECT_EQ(0, snd_use_case_set_called); + + rc = ucm_enable_node_noise_cancellation(mgr, "Internal Mic", 0); + EXPECT_EQ(0, rc); + EXPECT_EQ(1, snd_use_case_set_called); +} + TEST(AlsaFlag, GetFlag) { struct cras_use_case_mgr* mgr = &cras_ucm_mgr; char* flag_value; @@ -1406,6 +1540,19 @@ int snd_use_case_free_list(const char* list[], int items) { return 0; } +int snd_use_case_geti(snd_use_case_mgr_t* uc_mgr, + const char* identifier, + long* value) { + snd_use_case_geti_called++; + if (snd_use_case_geti_value.find(identifier) == + snd_use_case_geti_value.end()) { + *value = 0; + return -1; + } + *value = snd_use_case_geti_value[identifier]; + return 0; +} + } /* extern "C" */ } // namespace diff --git a/cras/src/tests/apm_list_unittest.cc b/cras/src/tests/apm_list_unittest.cc index 09c7b866..65e712fb 100644 --- a/cras/src/tests/apm_list_unittest.cc +++ b/cras/src/tests/apm_list_unittest.cc @@ -79,6 +79,64 @@ static void delete_tempdir(char* dir) { rmdir(dir); } +static void init_channel_layout(struct cras_audio_format* fmt) { + int i; + for (i = 0; i < CRAS_CH_MAX; i++) + fmt->channel_layout[i] = -1; +} + +TEST(ApmList, AddApmInputDevUnuseFirstChannel) { + struct cras_audio_format fmt; + struct cras_audio_format* val; + struct cras_apm* apm; + int ch; + const int num_test_casts = 9; + int test_layouts[num_test_casts][CRAS_CH_MAX] = { + {0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {0, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {0, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {3, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {3, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1}}; + int test_num_channels[num_test_casts] = {1, 2, 2, 2, 2, 3, 4, 4, 4}; + + fmt.frame_rate = 48000; + fmt.format = SND_PCM_FORMAT_S16_LE; + + cras_apm_list_init(""); + list = cras_apm_list_create(stream_ptr, APM_ECHO_CANCELLATION); + EXPECT_NE((void*)NULL, list); + + for (int i = 0; i < num_test_casts; i++) { + fmt.num_channels = test_num_channels[i]; + init_channel_layout(&fmt); + for (ch = 0; ch < CRAS_CH_MAX; ch++) + fmt.channel_layout[ch] = test_layouts[i][ch]; + + /* Input dev is of aec use case. */ + apm = cras_apm_list_add_apm(list, dev_ptr, &fmt, 1); + EXPECT_NE((void*)NULL, apm); + + /* Assert that the post-processing format never has an unset + * first channel in the layout. */ + bool first_channel_found_in_layout = 0; + val = cras_apm_list_get_format(apm); + for (ch = 0; ch < CRAS_CH_MAX; ch++) + if (0 == val->channel_layout[ch]) + first_channel_found_in_layout = 1; + + EXPECT_EQ(1, first_channel_found_in_layout); + + cras_apm_list_remove_apm(list, dev_ptr); + } + + cras_apm_list_destroy(list); + cras_apm_list_deinit(); +} + TEST(ApmList, AddRemoveApm) { struct cras_audio_format fmt; char* dir; @@ -170,6 +228,9 @@ TEST(ApmList, ApmProcessForwardBuffer) { fmt.num_channels = 2; fmt.frame_rate = 48000; fmt.format = SND_PCM_FORMAT_S16_LE; + init_channel_layout(&fmt); + fmt.channel_layout[CRAS_CH_FL] = 0; + fmt.channel_layout[CRAS_CH_FR] = 1; cras_apm_list_init(""); diff --git a/cras/src/tests/audio_thread_unittest.cc b/cras/src/tests/audio_thread_unittest.cc index 6b78c696..93045e0b 100644 --- a/cras/src/tests/audio_thread_unittest.cc +++ b/cras/src/tests/audio_thread_unittest.cc @@ -56,6 +56,7 @@ static struct cras_iodev* cras_iodev_start_ramp_odev; static enum CRAS_IODEV_RAMP_REQUEST cras_iodev_start_ramp_request; static struct timespec clock_gettime_retspec; static struct timespec init_cb_ts_; +static struct timespec sleep_interval_ts_; static std::map<const struct dev_stream*, struct timespec> dev_stream_wake_time_val; static int cras_device_monitor_set_device_mute_state_called; @@ -1225,10 +1226,12 @@ struct dev_stream* dev_stream_create(struct cras_rstream* stream, unsigned int dev_id, const struct cras_audio_format* dev_fmt, void* dev_ptr, - struct timespec* cb_ts) { + struct timespec* cb_ts, + const struct timespec* sleep_interval_ts) { struct dev_stream* out = static_cast<dev_stream*>(calloc(1, sizeof(*out))); out->stream = stream; init_cb_ts_ = *cb_ts; + sleep_interval_ts_ = *sleep_interval_ts; return out; } @@ -1268,7 +1271,7 @@ void dev_stream_set_delay(const struct dev_stream* dev_stream, void dev_stream_set_dev_rate(struct dev_stream* dev_stream, unsigned int dev_rate, double dev_rate_ratio, - double master_rate_ratio, + double main_rate_ratio, int coarse_rate_adjust) {} void dev_stream_update_frames(const struct dev_stream* dev_stream) {} @@ -1409,12 +1412,19 @@ int cras_device_monitor_set_device_mute_state(unsigned int dev_idx) { cras_device_monitor_set_device_mute_state_called++; return 0; } +int cras_device_monitor_error_close(unsigned int dev_idx) { + return 0; +} int cras_iodev_drop_frames_by_time(struct cras_iodev* iodev, struct timespec ts) { return 0; } +bool cras_iodev_is_on_internal_card(const struct cras_ionode* node) { + return 0; +} + // From librt. int clock_gettime(clockid_t clk_id, struct timespec* tp) { *tp = clock_gettime_retspec; diff --git a/cras/src/tests/bt_io_unittest.cc b/cras/src/tests/bt_io_unittest.cc index ee013cf3..dd02652f 100644 --- a/cras/src/tests/bt_io_unittest.cc +++ b/cras/src/tests/bt_io_unittest.cc @@ -458,6 +458,10 @@ const char* cras_bt_device_object_path(const struct cras_bt_device* device) { return "/fake/object/path"; } +int cras_bt_device_get_stable_id(const struct cras_bt_device* device) { + return 123; +} + int cras_bt_device_get_use_hardware_volume(struct cras_bt_device* device) { return 1; } diff --git a/cras/src/tests/capture_rclient_unittest.cc b/cras/src/tests/capture_rclient_unittest.cc index b749f1a5..446fddfa 100644 --- a/cras/src/tests/capture_rclient_unittest.cc +++ b/cras/src/tests/capture_rclient_unittest.cc @@ -270,4 +270,9 @@ bool cras_audio_format_valid(const struct cras_audio_format* fmt) { return true; } +void detect_rtc_stream_pair(struct stream_list* list, + struct cras_rstream* stream) { + return; +} + } // extern "C" diff --git a/cras/src/tests/control_rclient_unittest.cc b/cras/src/tests/control_rclient_unittest.cc index d6b63aab..63e3c8f0 100644 --- a/cras/src/tests/control_rclient_unittest.cc +++ b/cras/src/tests/control_rclient_unittest.cc @@ -967,4 +967,11 @@ struct packet_status_logger* cras_hfp_ag_get_wbs_logger() { return NULL; } +void detect_rtc_stream_pair(struct stream_list* list, + struct cras_rstream* stream) { + return; +} + +void cras_system_set_hotword_pause_at_suspend(bool pause) {} + } // extern "C" diff --git a/cras/src/tests/cras_abi_unittest.cc b/cras/src/tests/cras_abi_unittest.cc new file mode 100644 index 00000000..d566a9b7 --- /dev/null +++ b/cras/src/tests/cras_abi_unittest.cc @@ -0,0 +1,139 @@ +/* Copyright 2021 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <gtest/gtest.h> + +extern "C" { +#include "cras_client.c" +#include "cras_client.h" + +inline int libcras_unsupported_func(struct libcras_client* client) { + CHECK_VERSION(client, INT_MAX); + return 0; +} + +cras_stream_id_t cb_stream_id; +uint8_t* cb_buf; +unsigned int cb_frames; +struct timespec cb_latency; +void* cb_usr_arg; +int get_stream_cb_called; +struct timespec now; + +int get_stream_cb(struct libcras_stream_cb_data* data) { + get_stream_cb_called++; + EXPECT_NE((void*)NULL, data); + EXPECT_EQ(0, libcras_stream_cb_data_get_stream_id(data, &cb_stream_id)); + EXPECT_EQ(0, libcras_stream_cb_data_get_buf(data, &cb_buf)); + EXPECT_EQ(0, libcras_stream_cb_data_get_frames(data, &cb_frames)); + EXPECT_EQ(0, libcras_stream_cb_data_get_latency(data, &cb_latency)); + EXPECT_EQ(0, libcras_stream_cb_data_get_usr_arg(data, &cb_usr_arg)); + return 0; +} +} + +namespace { +class CrasAbiTestSuite : public testing::Test { + protected: + struct cras_audio_shm* InitShm(int frames) { + struct cras_audio_shm* shm = + static_cast<struct cras_audio_shm*>(calloc(1, sizeof(*shm))); + shm->header = + static_cast<cras_audio_shm_header*>(calloc(1, sizeof(*shm->header))); + cras_shm_set_frame_bytes(shm, 4); + uint32_t used_size = frames * 4; + cras_shm_set_used_size(shm, used_size); + shm->samples_info.length = used_size * 2; + memcpy(&shm->header->config, &shm->config, sizeof(shm->config)); + return shm; + } + + void DestroyShm(struct cras_audio_shm* shm) { + if (shm) + free(shm->header); + free(shm); + } + + virtual void SetUp() { get_stream_cb_called = 0; } +}; + +TEST_F(CrasAbiTestSuite, CheckUnsupportedFunction) { + auto* client = libcras_client_create(); + EXPECT_NE((void*)NULL, client); + EXPECT_EQ(-ENOSYS, libcras_unsupported_func(client)); + libcras_client_destroy(client); +} + +TEST_F(CrasAbiTestSuite, BasicStream) { + auto* client = libcras_client_create(); + EXPECT_NE((void*)NULL, client); + auto* stream = libcras_stream_params_create(); + EXPECT_NE((void*)NULL, stream); + /* Returns timeout because there is no real CRAS server in unittest. */ + EXPECT_EQ(-ETIMEDOUT, libcras_client_connect_timeout(client, 0)); + EXPECT_EQ(0, libcras_client_run_thread(client)); + EXPECT_EQ(0, libcras_stream_params_set(stream, CRAS_STREAM_INPUT, 480, 480, + CRAS_STREAM_TYPE_DEFAULT, + CRAS_CLIENT_TYPE_TEST, 0, NULL, NULL, + NULL, 48000, SND_PCM_FORMAT_S16, 2)); + cras_stream_id_t id; + /* Fails to add a stream because the stream callback is not set. */ + EXPECT_EQ(-EINVAL, libcras_client_add_pinned_stream(client, 0, &id, stream)); + /* Fails to set a stream volume because the stream is not added. */ + EXPECT_EQ(-EINVAL, libcras_client_set_stream_volume(client, id, 1.0)); + EXPECT_EQ(0, libcras_client_rm_stream(client, id)); + EXPECT_EQ(0, libcras_client_stop(client)); + libcras_stream_params_destroy(stream); + libcras_client_destroy(client); +} + +TEST_F(CrasAbiTestSuite, StreamCallback) { + struct client_stream stream; + struct cras_stream_params params; + stream.id = 0x123; + stream.direction = CRAS_STREAM_INPUT; + stream.flags = 0; + stream.config = ¶ms; + params.stream_cb = get_stream_cb; + params.cb_threshold = 480; + params.user_data = (void*)0x321; + stream.shm = InitShm(960); + stream.shm->header->write_offset[0] = 960 * 4; + stream.shm->header->write_buf_idx = 0; + stream.shm->header->read_offset[0] = 0; + stream.shm->header->read_buf_idx = 0; + now.tv_sec = 100; + now.tv_nsec = 0; + stream.shm->header->ts.tv_sec = 90; + stream.shm->header->ts.tv_nsec = 0; + + handle_capture_data_ready(&stream, 480); + + EXPECT_EQ(1, get_stream_cb_called); + EXPECT_EQ(stream.id, cb_stream_id); + EXPECT_EQ(cras_shm_get_write_buffer_base(stream.shm), cb_buf); + EXPECT_EQ(480, cb_frames); + EXPECT_EQ(10, cb_latency.tv_sec); + EXPECT_EQ(0, cb_latency.tv_nsec); + EXPECT_EQ((void*)0x321, cb_usr_arg); + + DestroyShm(stream.shm); +} + +} // namespace + +extern "C" { + +int clock_gettime(clockid_t clk_id, struct timespec* tp) { + *tp = now; + return 0; +} +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + openlog(NULL, LOG_PERROR, LOG_USER); + return RUN_ALL_TESTS(); +} diff --git a/cras/src/tests/dev_io_stubs.cc b/cras/src/tests/dev_io_stubs.cc index b74162b8..d97dde50 100644 --- a/cras/src/tests/dev_io_stubs.cc +++ b/cras/src/tests/dev_io_stubs.cc @@ -151,6 +151,11 @@ void add_stream_to_dev(IodevPtr& dev, const StreamPtr& stream) { static_cast<size_t>(dev->max_cb_level)); dev->largest_cb_level = std::max(stream->rstream->cb_threshold, static_cast<size_t>(dev->max_cb_level)); + + if (stream->rstream->main_dev.dev_id == NO_DEVICE) { + stream->rstream->main_dev.dev_id = dev->info.idx; + stream->rstream->main_dev.dev_ptr = dev.get(); + } } void fill_audio_format(cras_audio_format* format, unsigned int rate) { diff --git a/cras/src/tests/dev_io_unittest.cc b/cras/src/tests/dev_io_unittest.cc index 096e3ed3..2dbf344e 100644 --- a/cras/src/tests/dev_io_unittest.cc +++ b/cras/src/tests/dev_io_unittest.cc @@ -8,6 +8,7 @@ #include <time.h> #include <memory> +#include <unordered_map> extern "C" { #include "cras_iodev.h" // stubbed @@ -29,6 +30,13 @@ struct audio_thread_event_log* atlog; static float dev_stream_capture_software_gain_scaler_val; static float input_data_get_software_gain_scaler_val; static unsigned int dev_stream_capture_avail_ret = 480; +struct set_dev_rate_data { + unsigned int dev_rate; + double dev_rate_ratio; + double main_rate_ratio; + int coarse_rate_adjust; +}; +std::unordered_map<struct dev_stream*, set_dev_rate_data> set_dev_rate_map; namespace { @@ -39,6 +47,7 @@ class DevIoSuite : public testing::Test { iodev_stub_reset(); rstream_stub_reset(); fill_audio_format(&format, 48000); + set_dev_rate_map.clear(); stream = create_stream(1, 1, CRAS_STREAM_INPUT, cb_threshold, &format); } @@ -70,6 +79,7 @@ TEST_F(DevIoSuite, SendCapturedFails) { TEST_F(DevIoSuite, CaptureGain) { struct open_dev* dev_list = NULL; + struct open_dev* odev_list = NULL; struct timespec ts; DevicePtr dev = create_device(CRAS_STREAM_INPUT, cb_threshold, &format, CRAS_NODE_TYPE_MIC); @@ -82,20 +92,80 @@ TEST_F(DevIoSuite, CaptureGain) { /* The applied scaler gain should match what is reported by input_data. */ dev->dev->active_node->ui_gain_scaler = 1.0f; input_data_get_software_gain_scaler_val = 1.0f; - dev_io_capture(&dev_list); + dev_io_capture(&dev_list, &odev_list); EXPECT_EQ(1.0f, dev_stream_capture_software_gain_scaler_val); input_data_get_software_gain_scaler_val = 0.99f; - dev_io_capture(&dev_list); + dev_io_capture(&dev_list, &odev_list); EXPECT_EQ(0.99f, dev_stream_capture_software_gain_scaler_val); dev->dev->active_node->ui_gain_scaler = 0.6f; input_data_get_software_gain_scaler_val = 0.7f; - dev_io_capture(&dev_list); + dev_io_capture(&dev_list, &odev_list); EXPECT_FLOAT_EQ(0.42f, dev_stream_capture_software_gain_scaler_val); } /* + * When input and output devices are on the internal sound card, + * and their device rates are the same, use the estimated rate + * on the output device as the estimated rate of input device. + */ +TEST_F(DevIoSuite, CopyOutputEstimatedRate) { + struct open_dev* idev_list = NULL; + struct open_dev* odev_list = NULL; + struct timespec ts; + DevicePtr out_dev = create_device(CRAS_STREAM_OUTPUT, cb_threshold, &format, + CRAS_NODE_TYPE_INTERNAL_SPEAKER); + DevicePtr in_dev = create_device(CRAS_STREAM_INPUT, cb_threshold, &format, + CRAS_NODE_TYPE_MIC); + + in_dev->dev->state = CRAS_IODEV_STATE_NORMAL_RUN; + iodev_stub_frames_queued(in_dev->dev.get(), 20, ts); + DL_APPEND(idev_list, in_dev->odev.get()); + add_stream_to_dev(in_dev->dev, stream); + DL_APPEND(odev_list, out_dev->odev.get()); + iodev_stub_on_internal_card(out_dev->dev->active_node, 1); + iodev_stub_on_internal_card(in_dev->dev->active_node, 1); + + iodev_stub_est_rate_ratio(in_dev->dev.get(), 0.8f); + iodev_stub_est_rate_ratio(out_dev->dev.get(), 1.2f); + + dev_io_capture(&idev_list, &odev_list); + + EXPECT_FLOAT_EQ(1.2f, set_dev_rate_map[stream->dstream.get()].dev_rate_ratio); +} + +/* + * When input and output devices are not both on the internal sound card, + * estimated rates are independent. + */ +TEST_F(DevIoSuite, InputOutputIndependentEstimatedRate) { + struct open_dev* idev_list = NULL; + struct open_dev* odev_list = NULL; + struct timespec ts; + DevicePtr out_dev = create_device(CRAS_STREAM_OUTPUT, cb_threshold, &format, + CRAS_NODE_TYPE_INTERNAL_SPEAKER); + DevicePtr in_dev = create_device(CRAS_STREAM_INPUT, cb_threshold, &format, + CRAS_NODE_TYPE_USB); + + in_dev->dev->state = CRAS_IODEV_STATE_NORMAL_RUN; + iodev_stub_frames_queued(in_dev->dev.get(), 20, ts); + DL_APPEND(idev_list, in_dev->odev.get()); + add_stream_to_dev(in_dev->dev, stream); + DL_APPEND(odev_list, out_dev->odev.get()); + iodev_stub_on_internal_card(out_dev->dev->active_node, 1); + iodev_stub_on_internal_card(in_dev->dev->active_node, 0); + + iodev_stub_est_rate_ratio(in_dev->dev.get(), 0.8f); + iodev_stub_est_rate_ratio(out_dev->dev.get(), 1.2f); + iodev_stub_update_rate(in_dev->dev.get(), 1); + + dev_io_capture(&idev_list, &odev_list); + + EXPECT_FLOAT_EQ(0.8f, set_dev_rate_map[stream->dstream.get()].dev_rate_ratio); +} + +/* * If any hw_level is larger than 1.5 * largest_cb_level and * DROP_FRAMES_THRESHOLD_MS, reset all input devices. */ @@ -332,8 +402,16 @@ int dev_stream_mix(struct dev_stream* dev_stream, void dev_stream_set_dev_rate(struct dev_stream* dev_stream, unsigned int dev_rate, double dev_rate_ratio, - double master_rate_ratio, - int coarse_rate_adjust) {} + double main_rate_ratio, + int coarse_rate_adjust) { + set_dev_rate_data new_data; + new_data.dev_rate = dev_rate; + new_data.dev_rate_ratio = dev_rate_ratio; + new_data.main_rate_ratio = main_rate_ratio; + new_data.coarse_rate_adjust = coarse_rate_adjust; + + set_dev_rate_map[dev_stream] = new_data; +} int dev_stream_capture_update_rstream(struct dev_stream* dev_stream) { return 0; } @@ -373,7 +451,11 @@ struct dev_stream* dev_stream_create(struct cras_rstream* stream, unsigned int dev_id, const struct cras_audio_format* dev_fmt, void* dev_ptr, - struct timespec* cb_ts) { + struct timespec* cb_ts, + const struct timespec* sleep_interval_ts) { + return 0; +} +int cras_device_monitor_error_close(unsigned int dev_idx) { return 0; } } // extern "C" diff --git a/cras/src/tests/dev_stream_unittest.cc b/cras/src/tests/dev_stream_unittest.cc index 640ca932..700376fb 100644 --- a/cras/src/tests/dev_stream_unittest.cc +++ b/cras/src/tests/dev_stream_unittest.cc @@ -334,7 +334,7 @@ TEST_F(CreateSuite, CreateSRC44to48) { out_fmt.frame_rate = 48000; // Output from converter is device rate. config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33); dev_stream = - dev_stream_create(&rstream_, 0, &fmt_s16le_48, (void*)0x55, &cb_ts); + dev_stream_create(&rstream_, 0, &fmt_s16le_48, (void*)0x55, &cb_ts, NULL); EXPECT_EQ(1, config_format_converter_called); EXPECT_NE(static_cast<byte_buffer*>(NULL), dev_stream->conv_buffer); // Converter tmp and output buffers are large enough for device output. @@ -346,6 +346,24 @@ TEST_F(CreateSuite, CreateSRC44to48) { dev_stream_destroy(dev_stream); } +TEST_F(CreateSuite, CreateOutputWithSchedule) { + struct dev_stream* dev_stream; + unsigned int dev_id = 9; + // init_cb_ts and non-null init_sleep_ts will be used. + struct timespec init_cb_ts = {1, 2}; + struct timespec init_sleep_ts = {3, 4}; + + rstream_.direction = CRAS_STREAM_OUTPUT; + dev_stream = dev_stream_create(&rstream_, dev_id, &fmt_s16le_48, (void*)0x55, + &init_cb_ts, &init_sleep_ts); + + EXPECT_EQ(init_cb_ts.tv_sec, rstream_.next_cb_ts.tv_sec); + EXPECT_EQ(init_cb_ts.tv_nsec, rstream_.next_cb_ts.tv_nsec); + EXPECT_EQ(init_sleep_ts.tv_sec, rstream_.sleep_interval_ts.tv_sec); + EXPECT_EQ(init_sleep_ts.tv_nsec, rstream_.sleep_interval_ts.tv_nsec); + dev_stream_destroy(dev_stream); +} + TEST_F(CreateSuite, CreateSRC44from48Input) { struct dev_stream* dev_stream; struct cras_audio_format processed_fmt = fmt_s16le_48; @@ -358,7 +376,7 @@ TEST_F(CreateSuite, CreateSRC44from48Input) { config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33); cras_rstream_post_processing_format_val = &processed_fmt; dev_stream = - dev_stream_create(&rstream_, 0, &fmt_s16le_48, (void*)0x55, &cb_ts); + dev_stream_create(&rstream_, 0, &fmt_s16le_48, (void*)0x55, &cb_ts, NULL); EXPECT_EQ(1, config_format_converter_called); EXPECT_NE(static_cast<byte_buffer*>(NULL), dev_stream->conv_buffer); // Converter tmp and output buffers are large enough for device input. @@ -378,8 +396,8 @@ TEST_F(CreateSuite, CreateSRC48to44) { in_fmt.frame_rate = 48000; // Stream rate. out_fmt.frame_rate = 44100; // Device rate. config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33); - dev_stream = - dev_stream_create(&rstream_, 0, &fmt_s16le_44_1, (void*)0x55, &cb_ts); + dev_stream = dev_stream_create(&rstream_, 0, &fmt_s16le_44_1, (void*)0x55, + &cb_ts, NULL); EXPECT_EQ(1, config_format_converter_called); EXPECT_NE(static_cast<byte_buffer*>(NULL), dev_stream->conv_buffer); // Converter tmp and output buffers are large enough for stream input. @@ -396,8 +414,8 @@ TEST_F(CreateSuite, CreateSRC48from44Input) { in_fmt.frame_rate = 44100; // Device rate. out_fmt.frame_rate = 48000; // Stream rate. config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33); - dev_stream = - dev_stream_create(&rstream_, 0, &fmt_s16le_44_1, (void*)0x55, &cb_ts); + dev_stream = dev_stream_create(&rstream_, 0, &fmt_s16le_44_1, (void*)0x55, + &cb_ts, NULL); EXPECT_EQ(1, config_format_converter_called); EXPECT_NE(static_cast<byte_buffer*>(NULL), dev_stream->conv_buffer); // Converter tmp and output buffers are large enough for stream output. @@ -414,7 +432,7 @@ TEST_F(CreateSuite, CreateSRC8to48) { out_fmt.frame_rate = 48000; // Device rate. config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33); dev_stream = - dev_stream_create(&rstream_, 0, &fmt_s16le_48, (void*)0x55, &cb_ts); + dev_stream_create(&rstream_, 0, &fmt_s16le_48, (void*)0x55, &cb_ts, NULL); EXPECT_EQ(1, config_format_converter_called); EXPECT_NE(static_cast<byte_buffer*>(NULL), dev_stream->conv_buffer); // Converter tmp and output buffers are large enough for device output. @@ -435,7 +453,7 @@ TEST_F(CreateSuite, CreateSRC8from48Input) { out_fmt.frame_rate = 8000; // Stream rate. config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33); dev_stream = - dev_stream_create(&rstream_, 0, &fmt_s16le_48, (void*)0x55, &cb_ts); + dev_stream_create(&rstream_, 0, &fmt_s16le_48, (void*)0x55, &cb_ts, NULL); EXPECT_EQ(1, config_format_converter_called); EXPECT_NE(static_cast<byte_buffer*>(NULL), dev_stream->conv_buffer); // Converter tmp and output buffers are large enough for device input. @@ -455,7 +473,7 @@ TEST_F(CreateSuite, CreateSRC48to8) { out_fmt.frame_rate = 8000; // Device rate. config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33); dev_stream = - dev_stream_create(&rstream_, 0, &fmt_s16le_8, (void*)0x55, &cb_ts); + dev_stream_create(&rstream_, 0, &fmt_s16le_8, (void*)0x55, &cb_ts, NULL); EXPECT_EQ(1, config_format_converter_called); EXPECT_NE(static_cast<byte_buffer*>(NULL), dev_stream->conv_buffer); // Converter tmp and output buffers are large enough for stream input. @@ -473,7 +491,7 @@ TEST_F(CreateSuite, CreateSRC48from8Input) { out_fmt.frame_rate = 48000; // Stream rate. config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33); dev_stream = - dev_stream_create(&rstream_, 0, &fmt_s16le_8, (void*)0x55, &cb_ts); + dev_stream_create(&rstream_, 0, &fmt_s16le_8, (void*)0x55, &cb_ts, NULL); EXPECT_EQ(1, config_format_converter_called); EXPECT_NE(static_cast<byte_buffer*>(NULL), dev_stream->conv_buffer); // Converter tmp and output buffers are large enough for stream output. @@ -490,8 +508,8 @@ TEST_F(CreateSuite, CreateSRC48MonoFrom44StereoInput) { in_fmt.frame_rate = 44100; // Device rate. out_fmt.frame_rate = 48000; // Stream rate. config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33); - dev_stream = - dev_stream_create(&rstream_, 0, &fmt_s16le_44_1, (void*)0x55, &cb_ts); + dev_stream = dev_stream_create(&rstream_, 0, &fmt_s16le_44_1, (void*)0x55, + &cb_ts, NULL); EXPECT_EQ(1, config_format_converter_called); EXPECT_NE(static_cast<byte_buffer*>(NULL), dev_stream->conv_buffer); // Converter tmp and output buffers are large enough for stream output. @@ -510,8 +528,8 @@ TEST_F(CreateSuite, CaptureAvailConvBufHasSamples) { rstream_.format = fmt_s16le_48; rstream_.direction = CRAS_STREAM_INPUT; config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33); - dev_stream = - dev_stream_create(&rstream_, 0, &fmt_s16le_44_1, (void*)0x55, &cb_ts); + dev_stream = dev_stream_create(&rstream_, 0, &fmt_s16le_44_1, (void*)0x55, + &cb_ts, NULL); EXPECT_EQ(1, config_format_converter_called); EXPECT_NE(static_cast<byte_buffer*>(NULL), dev_stream->conv_buffer); EXPECT_LE( @@ -529,16 +547,16 @@ TEST_F(CreateSuite, CaptureAvailConvBufHasSamples) { dev_stream_destroy(dev_stream); } -TEST_F(CreateSuite, SetDevRateNotMasterDev) { +TEST_F(CreateSuite, SetDevRateNotMainDev) { struct dev_stream* dev_stream; unsigned int dev_id = 9; rstream_.format = fmt_s16le_48; rstream_.direction = CRAS_STREAM_INPUT; - rstream_.master_dev.dev_id = 4; + rstream_.main_dev.dev_id = 4; config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33); dev_stream = dev_stream_create(&rstream_, dev_id, &fmt_s16le_44_1, - (void*)0x55, &cb_ts); + (void*)0x55, &cb_ts, NULL); dev_stream_set_dev_rate(dev_stream, 44100, 1.01, 1.0, 0); EXPECT_EQ(1, cras_fmt_conv_set_linear_resample_rates_called); @@ -557,17 +575,17 @@ TEST_F(CreateSuite, SetDevRateNotMasterDev) { dev_stream_destroy(dev_stream); } -TEST_F(CreateSuite, SetDevRateMasterDev) { +TEST_F(CreateSuite, SetDevRateMainDev) { struct dev_stream* dev_stream; unsigned int dev_id = 9; unsigned int expected_ts_nsec; rstream_.format = fmt_s16le_48; rstream_.direction = CRAS_STREAM_INPUT; - rstream_.master_dev.dev_id = dev_id; + rstream_.main_dev.dev_id = dev_id; config_format_converter_conv = reinterpret_cast<struct cras_fmt_conv*>(0x33); dev_stream = dev_stream_create(&rstream_, dev_id, &fmt_s16le_44_1, - (void*)0x55, &cb_ts); + (void*)0x55, &cb_ts, NULL); dev_stream_set_dev_rate(dev_stream, 44100, 1.01, 1.0, 0); EXPECT_EQ(1, cras_fmt_conv_set_linear_resample_rates_called); @@ -661,7 +679,7 @@ TEST_F(CreateSuite, DevStreamFlushAudioMessages) { unsigned int dev_id = 9; dev_stream = dev_stream_create(&rstream_, dev_id, &fmt_s16le_44_1, - (void*)0x55, &cb_ts); + (void*)0x55, &cb_ts, NULL); dev_stream_flush_old_audio_messages(dev_stream); EXPECT_EQ(1, cras_rstream_flush_old_audio_messages_called); @@ -673,7 +691,7 @@ TEST_F(CreateSuite, DevStreamIsPending) { unsigned int dev_id = 9; dev_stream = dev_stream_create(&rstream_, dev_id, &fmt_s16le_44_1, - (void*)0x55, &cb_ts); + (void*)0x55, &cb_ts, NULL); // dev_stream_is_pending_reply is only a wrapper. cras_rstream_is_pending_reply_ret = 0; @@ -694,7 +712,7 @@ TEST_F(CreateSuite, StreamCanSend) { rstream_.direction = CRAS_STREAM_INPUT; dev_stream = dev_stream_create(&rstream_, dev_id, &fmt_s16le_44_1, - (void*)0x55, &cb_ts); + (void*)0x55, &cb_ts, NULL); // Assume there is a next_cb_ts on rstream. rstream_.next_cb_ts.tv_sec = 1; @@ -791,7 +809,7 @@ TEST_F(CreateSuite, StreamCanSendBulkAudio) { rstream_.direction = CRAS_STREAM_INPUT; rstream_.flags |= BULK_AUDIO_OK; dev_stream = dev_stream_create(&rstream_, dev_id, &fmt_s16le_44_1, - (void*)0x55, &cb_ts); + (void*)0x55, &cb_ts, NULL); // Assume there is a next_cb_ts on rstream. rstream_.next_cb_ts.tv_sec = 1; @@ -864,7 +882,7 @@ TEST_F(CreateSuite, TriggerOnlyStreamSendOnlyOnce) { rstream_.direction = CRAS_STREAM_INPUT; dev_stream = dev_stream_create(&rstream_, dev_id, &fmt_s16le_44_1, - (void*)0x55, &cb_ts); + (void*)0x55, &cb_ts, NULL); dev_stream->stream->flags = TRIGGER_ONLY; dev_stream->stream->triggered = 0; @@ -896,7 +914,7 @@ TEST_F(CreateSuite, InputDevStreamWakeTimeByNextCbTs) { rstream_.direction = CRAS_STREAM_INPUT; dev_stream = dev_stream_create(&rstream_, dev_id, &fmt_s16le_44_1, - (void*)0x55, &cb_ts); + (void*)0x55, &cb_ts, NULL); // Assume there is a next_cb_ts on rstream. rstream_.next_cb_ts.tv_sec = 1; @@ -929,8 +947,8 @@ TEST_F(CreateSuite, InputDevStreamWakeTimeByDevice) { int needed_frames_from_device = 0; rstream_.direction = CRAS_STREAM_INPUT; - dev_stream = - dev_stream_create(&rstream_, dev_id, &fmt_s16le_48, (void*)0x55, &cb_ts); + dev_stream = dev_stream_create(&rstream_, dev_id, &fmt_s16le_48, (void*)0x55, + &cb_ts, NULL); // Assume there is a next_cb_ts on rstream, that is, 1.005 seconds. rstream_.next_cb_ts.tv_sec = 1; @@ -994,7 +1012,7 @@ TEST_F(CreateSuite, UpdateNextWakeTime) { rstream_.direction = CRAS_STREAM_OUTPUT; dev_stream = dev_stream_create(&rstream_, dev_id, &fmt_s16le_44_1, - (void*)0x55, &cb_ts); + (void*)0x55, &cb_ts, NULL); // Case 1: The new next_cb_ts is greater than now. Do not need to reschedule. rstream_.next_cb_ts.tv_sec = 2; diff --git a/cras/src/tests/fmt_conv_ops_unittest.cc b/cras/src/tests/fmt_conv_ops_unittest.cc index ebe8b65d..0baf37b1 100644 --- a/cras/src/tests/fmt_conv_ops_unittest.cc +++ b/cras/src/tests/fmt_conv_ops_unittest.cc @@ -418,6 +418,39 @@ TEST(FormatConverterOpsTest, StereoTo51S16LECenter) { } } +// Test Quad to 5.1 conversion. S16_LE. +TEST(FormatConverterOpsTest, QuadTo51S16LE) { + const size_t frames = 4096; + const size_t in_ch = 4; + const size_t out_ch = 6; + const unsigned int fl_quad = 0; + const unsigned int fr_quad = 1; + const unsigned int rl_quad = 2; + const unsigned int rr_quad = 3; + + const unsigned int fl_51 = 0; + const unsigned int fr_51 = 1; + const unsigned int center_51 = 2; + const unsigned int lfe_51 = 3; + const unsigned int rl_51 = 4; + const unsigned int rr_51 = 5; + + S16LEPtr src = CreateS16LE(frames * in_ch); + S16LEPtr dst = CreateS16LE(frames * out_ch); + + size_t ret = s16_quad_to_51(fl_51, fr_51, rl_51, rr_51, (uint8_t*)src.get(), + frames, (uint8_t*)dst.get()); + EXPECT_EQ(ret, frames); + for (size_t i = 0; i < frames; ++i) { + EXPECT_EQ(0, dst[i * 6 + center_51]); + EXPECT_EQ(0, dst[i * 6 + lfe_51]); + EXPECT_EQ(src[i * 4 + fl_quad], dst[i * 6 + fl_51]); + EXPECT_EQ(src[i * 4 + fr_quad], dst[i * 6 + fr_51]); + EXPECT_EQ(src[i * 4 + rl_quad], dst[i * 6 + rl_51]); + EXPECT_EQ(src[i * 4 + rr_quad], dst[i * 6 + rr_51]); + } +} + // Test Stereo to 5.1 conversion. S16_LE, LeftRight. TEST(FormatConverterOpsTest, StereoTo51S16LELeftRight) { const size_t frames = 4096; diff --git a/cras/src/tests/hfp_alsa_iodev_unittest.cc b/cras/src/tests/hfp_alsa_iodev_unittest.cc index c5bd4e9a..8756c201 100644 --- a/cras/src/tests/hfp_alsa_iodev_unittest.cc +++ b/cras/src/tests/hfp_alsa_iodev_unittest.cc @@ -259,7 +259,7 @@ TEST_F(HfpAlsaIodev, ConfigureDev) { hfp_alsa_io->aio->format->channel_layout[i]); EXPECT_EQ(1, fake_configure_dev_called); - EXPECT_EQ(0, hfp_set_call_status_called); + EXPECT_EQ(1, hfp_set_call_status_called); EXPECT_EQ(buf_size, iodev->buffer_size); hfp_alsa_iodev_destroy(iodev); @@ -273,7 +273,7 @@ TEST_F(HfpAlsaIodev, CloseDev) { CRAS_BT_DEVICE_PROFILE_HFP_AUDIOGATEWAY); iodev->close_dev(iodev); - EXPECT_EQ(0, hfp_set_call_status_called); + EXPECT_EQ(1, hfp_set_call_status_called); EXPECT_EQ(1, cras_iodev_free_format_called); EXPECT_EQ(1, fake_close_dev_called); @@ -507,6 +507,10 @@ const char* cras_bt_device_object_path(const struct cras_bt_device* device) { return "/fake/object/path"; } +int cras_bt_device_get_stable_id(const struct cras_bt_device* device) { + return 123; +} + void cras_iodev_free_resources(struct cras_iodev* iodev) { cras_iodev_free_resources_called++; } diff --git a/cras/src/tests/hfp_iodev_unittest.cc b/cras/src/tests/hfp_iodev_unittest.cc index 18262bf9..1275ef2c 100644 --- a/cras/src/tests/hfp_iodev_unittest.cc +++ b/cras/src/tests/hfp_iodev_unittest.cc @@ -285,6 +285,10 @@ const char* cras_bt_device_object_path(const struct cras_bt_device* device) { return "/fake/object/path"; } +int cras_bt_device_get_stable_id(const struct cras_bt_device* device) { + return 123; +} + // From cras_hfp_info int hfp_info_add_iodev(struct hfp_info* info, enum CRAS_STREAM_DIRECTION direction, diff --git a/cras/src/tests/iodev_list_unittest.cc b/cras/src/tests/iodev_list_unittest.cc index 272537fc..8c71214a 100644 --- a/cras/src/tests/iodev_list_unittest.cc +++ b/cras/src/tests/iodev_list_unittest.cc @@ -95,6 +95,7 @@ static struct cras_rstream* audio_thread_disconnect_stream_stream; static int audio_thread_disconnect_stream_called; static struct cras_iodev fake_sco_in_dev, fake_sco_out_dev; static struct cras_ionode fake_sco_in_node, fake_sco_out_node; +static int server_state_hotword_pause_at_suspend; int dev_idx_in_vector(std::vector<unsigned int> v, unsigned int idx) { return std::find(v.begin(), v.end(), idx) != v.end(); @@ -238,6 +239,7 @@ class IoDevTestSuite : public testing::Test { mock_empty_iodev[1].state = CRAS_IODEV_STATE_CLOSE; mock_empty_iodev[1].update_active_node = update_active_node; mock_hotword_iodev.update_active_node = update_active_node; + server_state_hotword_pause_at_suspend = 0; } virtual void TearDown() { @@ -1942,6 +1944,105 @@ TEST_F(IoDevTestSuite, GetSCOPCMIodevs) { cras_iodev_list_deinit(); } +TEST_F(IoDevTestSuite, HotwordStreamsPausedAtSystemSuspend) { + struct cras_rstream rstream; + struct cras_rstream* stream_list = NULL; + cras_iodev_list_init(); + + node1.type = CRAS_NODE_TYPE_HOTWORD; + d1_.direction = CRAS_STREAM_INPUT; + EXPECT_EQ(0, cras_iodev_list_add_input(&d1_)); + + d1_.format = &fmt_; + + memset(&rstream, 0, sizeof(rstream)); + rstream.is_pinned = 1; + rstream.pinned_dev_idx = d1_.info.idx; + rstream.flags = HOTWORD_STREAM; + + /* Add a hotword stream. */ + EXPECT_EQ(0, stream_add_cb(&rstream)); + EXPECT_EQ(1, audio_thread_add_stream_called); + EXPECT_EQ(&d1_, audio_thread_add_stream_dev); + EXPECT_EQ(&rstream, audio_thread_add_stream_stream); + + DL_APPEND(stream_list, &rstream); + stream_list_get_ret = stream_list; + + server_state_hotword_pause_at_suspend = 1; + + /* Trigger system suspend. Verify hotword stream is moved to empty dev. */ + observer_ops->suspend_changed(NULL, 1); + EXPECT_EQ(1, audio_thread_disconnect_stream_called); + EXPECT_EQ(&rstream, audio_thread_disconnect_stream_stream); + EXPECT_EQ(&d1_, audio_thread_disconnect_stream_dev); + EXPECT_EQ(2, audio_thread_add_stream_called); + EXPECT_EQ(&rstream, audio_thread_add_stream_stream); + EXPECT_EQ(&mock_hotword_iodev, audio_thread_add_stream_dev); + + /* Trigger system resume. Verify hotword stream is moved to real dev.*/ + observer_ops->suspend_changed(NULL, 0); + EXPECT_EQ(2, audio_thread_disconnect_stream_called); + EXPECT_EQ(&rstream, audio_thread_disconnect_stream_stream); + EXPECT_EQ(&mock_hotword_iodev, audio_thread_disconnect_stream_dev); + EXPECT_EQ(3, audio_thread_add_stream_called); + EXPECT_EQ(&rstream, audio_thread_add_stream_stream); + EXPECT_EQ(&d1_, audio_thread_add_stream_dev); + + server_state_hotword_pause_at_suspend = 0; + audio_thread_disconnect_stream_called = 0; + audio_thread_add_stream_called = 0; + + /* Trigger system suspend. Verify hotword stream is not touched. */ + observer_ops->suspend_changed(NULL, 1); + EXPECT_EQ(0, audio_thread_disconnect_stream_called); + EXPECT_EQ(0, audio_thread_add_stream_called); + + /* Trigger system resume. Verify hotword stream is not touched.*/ + observer_ops->suspend_changed(NULL, 0); + EXPECT_EQ(0, audio_thread_disconnect_stream_called); + EXPECT_EQ(0, audio_thread_add_stream_called); + + cras_iodev_list_deinit(); +} + +TEST_F(IoDevTestSuite, SetNoiseCancellation) { + struct cras_rstream rstream; + struct cras_rstream* stream_list = NULL; + int rc; + + memset(&rstream, 0, sizeof(rstream)); + + cras_iodev_list_init(); + + d1_.direction = CRAS_STREAM_INPUT; + rc = cras_iodev_list_add_input(&d1_); + ASSERT_EQ(0, rc); + + d1_.format = &fmt_; + + rstream.direction = CRAS_STREAM_INPUT; + + audio_thread_add_open_dev_called = 0; + audio_thread_rm_open_dev_called = 0; + cras_iodev_list_add_active_node(CRAS_STREAM_INPUT, + cras_make_node_id(d1_.info.idx, 1)); + DL_APPEND(stream_list, &rstream); + stream_add_cb(&rstream); + stream_list_get_ret = stream_list; + EXPECT_EQ(1, audio_thread_add_stream_called); + EXPECT_EQ(1, audio_thread_add_open_dev_called); + + // reset_for_noise_cancellation causes device suspend & resume + // While suspending d1_: rm d1_, open fallback + // While resuming d1_: rm fallback, open d1_ + cras_iodev_list_reset_for_noise_cancellation(); + EXPECT_EQ(3, audio_thread_add_open_dev_called); + EXPECT_EQ(2, audio_thread_rm_open_dev_called); + + cras_iodev_list_deinit(); +} + } // namespace int main(int argc, char** argv) { @@ -1964,6 +2065,10 @@ int cras_system_get_mute() { return system_get_mute_return; } +bool cras_system_get_noise_cancellation_enabled() { + return false; +} + struct audio_thread* audio_thread_create() { return &thread; } @@ -2103,6 +2208,10 @@ void cras_iodev_set_node_plugged(struct cras_ionode* node, int plugged) { set_node_plugged_called++; } +bool cras_iodev_support_noise_cancellation(const struct cras_iodev* iodev) { + return true; +} + int cras_iodev_start_volume_ramp(struct cras_iodev* odev, unsigned int old_volume, unsigned int new_volume) { @@ -2246,4 +2355,8 @@ int clock_gettime(clockid_t clk_id, struct timespec* tp) { return 0; } +bool cras_system_get_hotword_pause_at_suspend() { + return !!server_state_hotword_pause_at_suspend; +} + } // extern "C" diff --git a/cras/src/tests/iodev_stub.cc b/cras/src/tests/iodev_stub.cc index 3dbb61d1..2e84faac 100644 --- a/cras/src/tests/iodev_stub.cc +++ b/cras/src/tests/iodev_stub.cc @@ -21,12 +21,30 @@ struct cb_data { std::unordered_map<cras_iodev*, cb_data> frames_queued_map; std::unordered_map<cras_iodev*, cb_data> valid_frames_map; std::unordered_map<cras_iodev*, timespec> drop_time_map; +std::unordered_map<const cras_iodev*, double> est_rate_ratio_map; +std::unordered_map<const cras_iodev*, int> update_rate_map; +std::unordered_map<const cras_ionode*, int> on_internal_card_map; } // namespace void iodev_stub_reset() { frames_queued_map.clear(); valid_frames_map.clear(); drop_time_map.clear(); + est_rate_ratio_map.clear(); + update_rate_map.clear(); + on_internal_card_map.clear(); +} + +void iodev_stub_est_rate_ratio(cras_iodev* iodev, double ratio) { + est_rate_ratio_map.insert({iodev, ratio}); +} + +void iodev_stub_update_rate(cras_iodev* iodev, int data) { + update_rate_map.insert({iodev, data}); +} + +void iodev_stub_on_internal_card(cras_ionode* node, int data) { + on_internal_card_map.insert({node, data}); } void iodev_stub_frames_queued(cras_iodev* iodev, int ret, timespec ts) { @@ -67,7 +85,11 @@ int cras_iodev_get_valid_frames(struct cras_iodev* iodev, } double cras_iodev_get_est_rate_ratio(const struct cras_iodev* iodev) { - return 1.0; + auto elem = est_rate_ratio_map.find(iodev); + if (elem != est_rate_ratio_map.end()) { + return elem->second; + } + return 1.0f; } int cras_iodev_get_dsp_delay(const struct cras_iodev* iodev) { @@ -93,6 +115,10 @@ struct dev_stream* cras_iodev_rm_stream(struct cras_iodev* iodev, int cras_iodev_update_rate(struct cras_iodev* iodev, unsigned int level, struct timespec* level_tstamp) { + auto elem = update_rate_map.find(iodev); + if (elem != update_rate_map.end()) { + return elem->second; + } return 0; } @@ -188,4 +214,12 @@ int cras_iodev_drop_frames_by_time(struct cras_iodev* iodev, drop_time_map.insert({iodev, ts}); return 0; } + +bool cras_iodev_is_on_internal_card(const struct cras_ionode* node) { + auto elem = on_internal_card_map.find(node); + if (elem != on_internal_card_map.end()) { + return elem->second; + } + return 1; +} } // extern "C" diff --git a/cras/src/tests/iodev_stub.h b/cras/src/tests/iodev_stub.h index dde1b9f4..e8016dd3 100644 --- a/cras/src/tests/iodev_stub.h +++ b/cras/src/tests/iodev_stub.h @@ -10,6 +10,12 @@ void iodev_stub_reset(); +void iodev_stub_est_rate_ratio(cras_iodev* iodev, double ratio); + +void iodev_stub_update_rate(cras_iodev* iodev, int data); + +void iodev_stub_on_internal_card(cras_ionode* node, int data); + void iodev_stub_frames_queued(cras_iodev* iodev, int ret, timespec ts); void iodev_stub_valid_frames(cras_iodev* iodev, int ret, timespec ts); diff --git a/cras/src/tests/iodev_unittest.cc b/cras/src/tests/iodev_unittest.cc index 21dc4d57..24b2b38d 100644 --- a/cras/src/tests/iodev_unittest.cc +++ b/cras/src/tests/iodev_unittest.cc @@ -2404,6 +2404,20 @@ TEST(IoDev, DeviceOverrun) { EXPECT_EQ(1, cras_audio_thread_event_dev_overrun_called); } +TEST(IoDev, OnInternalCard) { + static struct cras_ionode node; + node.type = CRAS_NODE_TYPE_INTERNAL_SPEAKER; + EXPECT_EQ(1, cras_iodev_is_on_internal_card(&node)); + node.type = CRAS_NODE_TYPE_HEADPHONE; + EXPECT_EQ(1, cras_iodev_is_on_internal_card(&node)); + node.type = CRAS_NODE_TYPE_MIC; + EXPECT_EQ(1, cras_iodev_is_on_internal_card(&node)); + node.type = CRAS_NODE_TYPE_USB; + EXPECT_EQ(0, cras_iodev_is_on_internal_card(&node)); + node.type = CRAS_NODE_TYPE_BLUETOOTH; + EXPECT_EQ(0, cras_iodev_is_on_internal_card(&node)); +} + extern "C" { struct main_thread_event_log* main_log; diff --git a/cras/src/tests/playback_rclient_unittest.cc b/cras/src/tests/playback_rclient_unittest.cc index 75cbe552..31ceda74 100644 --- a/cras/src/tests/playback_rclient_unittest.cc +++ b/cras/src/tests/playback_rclient_unittest.cc @@ -300,4 +300,9 @@ bool cras_audio_format_valid(const struct cras_audio_format* fmt) { return audio_format_valid; } +void detect_rtc_stream_pair(struct stream_list* list, + struct cras_rstream* stream) { + return; +} + } // extern "C" diff --git a/cras/src/tests/rstream_unittest.cc b/cras/src/tests/rstream_unittest.cc index 593c805d..d8dae24c 100644 --- a/cras/src/tests/rstream_unittest.cc +++ b/cras/src/tests/rstream_unittest.cc @@ -32,6 +32,7 @@ class RstreamTestSuite : public testing::Test { config_.stream_id = 555; config_.stream_type = CRAS_STREAM_TYPE_DEFAULT; + config_.client_type = CRAS_CLIENT_TYPE_UNKNOWN; config_.direction = CRAS_STREAM_OUTPUT; config_.dev_idx = NO_DEVICE; config_.flags = 0; diff --git a/cras/src/tests/server_metrics_unittest.cc b/cras/src/tests/server_metrics_unittest.cc index fe80e26f..e23906ec 100644 --- a/cras/src/tests/server_metrics_unittest.cc +++ b/cras/src/tests/server_metrics_unittest.cc @@ -132,20 +132,6 @@ TEST(ServerMetricsTestSuite, SetMetricHighestHardwareLevel) { EXPECT_EQ(sent_msgs[0].data.value, hw_level); } -TEST(ServerMetricsTestSuite, SetMetricsLongestFetchDelay) { - ResetStubData(); - unsigned int delay = 100; - - cras_server_metrics_longest_fetch_delay(delay); - - EXPECT_EQ(sent_msgs.size(), 1); - EXPECT_EQ(sent_msgs[0].header.type, CRAS_MAIN_METRICS); - EXPECT_EQ(sent_msgs[0].header.length, - sizeof(struct cras_server_metrics_message)); - EXPECT_EQ(sent_msgs[0].metrics_type, LONGEST_FETCH_DELAY); - EXPECT_EQ(sent_msgs[0].data.value, delay); -} - TEST(ServerMetricsTestSuite, SetMetricsNumUnderruns) { ResetStubData(); unsigned int underrun = 10; @@ -283,13 +269,18 @@ TEST(ServerMetricsTestSuite, SetMetricsStreamDestroy) { stream.num_missed_cb = 5; stream.first_missed_cb_ts.tv_sec = 100; stream.first_missed_cb_ts.tv_nsec = 0; + stream.longest_fetch_interval.tv_sec = 1; + stream.longest_fetch_interval.tv_nsec = 0; + stream.sleep_interval_ts.tv_sec = 0; + stream.sleep_interval_ts.tv_nsec = 5000000; stream.direction = CRAS_STREAM_INPUT; stream.client_type = CRAS_CLIENT_TYPE_TEST; + stream.stream_type = CRAS_STREAM_TYPE_DEFAULT; cras_server_metrics_stream_destroy(&stream); subtract_timespecs(&clock_gettime_retspec, &stream.start_ts, &diff_ts); - EXPECT_EQ(sent_msgs.size(), 3); + EXPECT_EQ(sent_msgs.size(), 4); // Log missed cb frequency. EXPECT_EQ(sent_msgs[0].header.type, CRAS_MAIN_METRICS); @@ -315,9 +306,23 @@ TEST(ServerMetricsTestSuite, SetMetricsStreamDestroy) { EXPECT_EQ(sent_msgs[2].header.length, sizeof(struct cras_server_metrics_message)); EXPECT_EQ(sent_msgs[2].metrics_type, STREAM_RUNTIME); - EXPECT_EQ(sent_msgs[2].data.stream_data.type, CRAS_CLIENT_TYPE_TEST); + EXPECT_EQ(sent_msgs[2].data.stream_data.client_type, CRAS_CLIENT_TYPE_TEST); + EXPECT_EQ(sent_msgs[2].data.stream_data.stream_type, + CRAS_STREAM_TYPE_DEFAULT); EXPECT_EQ(sent_msgs[2].data.stream_data.direction, CRAS_STREAM_INPUT); EXPECT_EQ(sent_msgs[2].data.stream_data.runtime.tv_sec, 1000); + + // Log longest fetch delay. + EXPECT_EQ(sent_msgs[3].header.type, CRAS_MAIN_METRICS); + EXPECT_EQ(sent_msgs[3].header.length, + sizeof(struct cras_server_metrics_message)); + EXPECT_EQ(sent_msgs[3].metrics_type, LONGEST_FETCH_DELAY); + EXPECT_EQ(sent_msgs[3].data.stream_data.client_type, CRAS_CLIENT_TYPE_TEST); + EXPECT_EQ(sent_msgs[3].data.stream_data.stream_type, + CRAS_STREAM_TYPE_DEFAULT); + EXPECT_EQ(sent_msgs[3].data.stream_data.direction, CRAS_STREAM_INPUT); + EXPECT_EQ(sent_msgs[3].data.stream_data.runtime.tv_sec, 0); + EXPECT_EQ(sent_msgs[3].data.stream_data.runtime.tv_nsec, 995000000); } TEST(ServerMetricsTestSuite, SetMetricsBusyloop) { diff --git a/cras/src/tests/stream_list_unittest.cc b/cras/src/tests/stream_list_unittest.cc index 40be35d0..500774f1 100644 --- a/cras/src/tests/stream_list_unittest.cc +++ b/cras/src/tests/stream_list_unittest.cc @@ -37,6 +37,10 @@ static int create_rstream_cb(struct cras_rstream_config* stream_config, (*stream)->direction = stream_config->direction; if (stream_config->format) (*stream)->format = *(stream_config->format); + (*stream)->cb_threshold = stream_config->cb_threshold; + (*stream)->client_type = stream_config->client_type; + (*stream)->stream_type = stream_config->stream_type; + clock_gettime(CLOCK_MONOTONIC_RAW, &(*stream)->start_ts); return 0; } @@ -129,6 +133,68 @@ TEST(StreamList, AddInDescendingOrderByChannels) { stream_list_destroy(l); } +TEST(StreamList, DetectRtcStreamPair) { + struct stream_list* l; + struct cras_rstream *s1, *s2, *s3, *s4; + struct cras_rstream_config s1_config, s2_config, s3_config, s4_config; + + s1_config.stream_id = 0x5001; + s1_config.direction = CRAS_STREAM_OUTPUT; + s1_config.cb_threshold = 480; + s1_config.client_type = CRAS_CLIENT_TYPE_CHROME; + s1_config.stream_type = CRAS_STREAM_TYPE_DEFAULT; + s1_config.format = NULL; + + s2_config.stream_id = 0x5002; + s2_config.direction = CRAS_STREAM_INPUT; + s2_config.cb_threshold = 480; + s2_config.client_type = CRAS_CLIENT_TYPE_CHROME; + s2_config.stream_type = CRAS_STREAM_TYPE_DEFAULT; + s2_config.format = NULL; + + // s3 is not a RTC stream because the cb threshold is not 480. + s3_config.stream_id = 0x5003; + s3_config.direction = CRAS_STREAM_INPUT; + s3_config.cb_threshold = 500; + s3_config.client_type = CRAS_CLIENT_TYPE_CHROME; + s3_config.stream_type = CRAS_STREAM_TYPE_DEFAULT; + s3_config.format = NULL; + + // s4 is not a RTC stream because it is not from the same client with s1. + s4_config.stream_id = 0x5004; + s4_config.direction = CRAS_STREAM_INPUT; + s4_config.cb_threshold = 480; + s4_config.client_type = CRAS_CLIENT_TYPE_LACROS; + s4_config.stream_type = CRAS_STREAM_TYPE_DEFAULT; + s4_config.format = NULL; + + reset_test_data(); + l = stream_list_create(added_cb, removed_cb, create_rstream_cb, + destroy_rstream_cb, NULL); + stream_list_add(l, &s1_config, &s1); + EXPECT_EQ(1, add_called); + EXPECT_EQ(1, create_called); + EXPECT_EQ(&s1_config, create_config); + + stream_list_add(l, &s2_config, &s2); + detect_rtc_stream_pair(l, s2); + stream_list_add(l, &s3_config, &s3); + detect_rtc_stream_pair(l, s3); + stream_list_add(l, &s4_config, &s4); + detect_rtc_stream_pair(l, s4); + + EXPECT_EQ(CRAS_STREAM_TYPE_VOICE_COMMUNICATION, s1->stream_type); + EXPECT_EQ(CRAS_STREAM_TYPE_VOICE_COMMUNICATION, s2->stream_type); + EXPECT_EQ(CRAS_STREAM_TYPE_DEFAULT, s3->stream_type); + EXPECT_EQ(CRAS_STREAM_TYPE_DEFAULT, s4->stream_type); + + EXPECT_EQ(0, stream_list_rm(l, 0x5001)); + EXPECT_EQ(0, stream_list_rm(l, 0x5002)); + EXPECT_EQ(0, stream_list_rm(l, 0x5003)); + EXPECT_EQ(0, stream_list_rm(l, 0x5004)); + stream_list_destroy(l); +} + extern "C" { struct cras_timer* cras_tm_create_timer(struct cras_tm* tm, diff --git a/cras/src/tests/system_state_unittest.cc b/cras/src/tests/system_state_unittest.cc index 0450df38..45224bc9 100644 --- a/cras/src/tests/system_state_unittest.cc +++ b/cras/src/tests/system_state_unittest.cc @@ -39,7 +39,9 @@ static size_t cras_observer_notify_capture_mute_called; static size_t cras_observer_notify_suspend_changed_called; static size_t cras_observer_notify_num_active_streams_called; static size_t cras_observer_notify_input_streams_with_permission_called; +static size_t cras_iodev_list_reset_for_noise_cancellation_called; static struct cras_board_config fake_board_config; +static size_t cras_alert_process_all_pending_alerts_called; static void ResetStubData() { cras_alsa_card_create_called = 0; @@ -60,6 +62,8 @@ static void ResetStubData() { cras_observer_notify_suspend_changed_called = 0; cras_observer_notify_num_active_streams_called = 0; cras_observer_notify_input_streams_with_permission_called = 0; + cras_alert_process_all_pending_alerts_called = 0; + cras_iodev_list_reset_for_noise_cancellation_called = 0; memset(&fake_board_config, 0, sizeof(fake_board_config)); } @@ -275,6 +279,7 @@ TEST(SystemStateSuite, Suspend) { cras_system_set_suspended(1); EXPECT_EQ(1, cras_observer_notify_suspend_changed_called); + EXPECT_EQ(1, cras_alert_process_all_pending_alerts_called); EXPECT_EQ(1, cras_system_get_suspended()); cras_system_set_suspended(0); @@ -431,6 +436,33 @@ TEST(SystemStateSuite, IgnoreUCMSuffix) { cras_system_state_deinit(); } +TEST(SystemStateSuite, SetNoiseCancellationEnabled) { + ResetStubData(); + do_sys_init(); + + EXPECT_EQ(0, cras_system_get_noise_cancellation_enabled()); + + cras_system_set_noise_cancellation_enabled(0); + EXPECT_EQ(0, cras_system_get_noise_cancellation_enabled()); + EXPECT_EQ(0, cras_iodev_list_reset_for_noise_cancellation_called); + + cras_system_set_noise_cancellation_enabled(1); + EXPECT_EQ(1, cras_system_get_noise_cancellation_enabled()); + EXPECT_EQ(1, cras_iodev_list_reset_for_noise_cancellation_called); + + cras_system_set_noise_cancellation_enabled(1); + EXPECT_EQ(1, cras_system_get_noise_cancellation_enabled()); + // cras_iodev_list_reset_for_noise_cancellation shouldn't be called if state + // is already enabled/disabled. + EXPECT_EQ(1, cras_iodev_list_reset_for_noise_cancellation_called); + + cras_system_set_noise_cancellation_enabled(0); + EXPECT_EQ(0, cras_system_get_noise_cancellation_enabled()); + EXPECT_EQ(2, cras_iodev_list_reset_for_noise_cancellation_called); + + cras_system_state_deinit(); +} + extern "C" { struct cras_alsa_card* cras_alsa_card_create( @@ -527,6 +559,14 @@ void cras_board_config_get(const char* config_path, *board_config = fake_board_config; } +void cras_alert_process_all_pending_alerts() { + cras_alert_process_all_pending_alerts_called++; +} + +void cras_iodev_list_reset_for_noise_cancellation() { + cras_iodev_list_reset_for_noise_cancellation_called++; +} + } // extern "C" } // namespace diff --git a/cras/src/tests/timing_unittest.cc b/cras/src/tests/timing_unittest.cc index 8a2de65f..964f30c3 100644 --- a/cras/src/tests/timing_unittest.cc +++ b/cras/src/tests/timing_unittest.cc @@ -111,20 +111,21 @@ int clock_gettime(clockid_t clk_id, struct timespec* tp) { // Add a new input stream, make sure the initial next_cb_ts is 0. TEST_F(TimingSuite, NewInputStreamInit) { - struct open_dev* dev_list_ = NULL; + struct open_dev* odev_list_ = NULL; + struct open_dev* idev_list_ = NULL; cras_audio_format format; fill_audio_format(&format, 48000); DevicePtr dev = create_device(CRAS_STREAM_INPUT, 1024, &format, CRAS_NODE_TYPE_MIC); - DL_APPEND(dev_list_, dev->odev.get()); + DL_APPEND(idev_list_, dev->odev.get()); struct cras_iodev* iodev = dev->odev->dev; ShmPtr shm = create_shm(480); RstreamPtr rstream = create_rstream(1, CRAS_STREAM_INPUT, 480, &format, shm.get()); - dev_io_append_stream(&dev_list_, rstream.get(), &iodev, 1); + dev_io_append_stream(&odev_list_, &idev_list_, rstream.get(), &iodev, 1); EXPECT_EQ(0, rstream->next_cb_ts.tv_sec); EXPECT_EQ(0, rstream->next_cb_ts.tv_nsec); @@ -806,23 +807,68 @@ TEST_F(TimingSuite, HotwordStreamBulkDataIsNotPending) { // When a new output stream is added, there are two rules to determine the // initial next_cb_ts. -// 1. If the device already has streams, the next_cb_ts will be the earliest +// 1. If there is a matched input stream, use the next_cb_ts and +// sleep_interval_ts from that input stream as the initial values. +// 2. If the device already has streams, the next_cb_ts will be the earliest // next callback time from these streams. -// 2. If there are no other streams, the next_cb_ts will be set to the time +// 3. If there are no other streams, the next_cb_ts will be set to the time // when the valid frames in device is lower than cb_threshold. (If it is // already lower than cb_threshold, set next_cb_ts to now.) // Test rule 1. +// There is a matched input stream. The next_cb_ts of the newly added output +// stream will use the next_cb_ts from the input stream. +TEST_F(TimingSuite, NewOutputStreamInitExistMatchedStream) { + struct open_dev* odev_list_ = NULL; + struct open_dev* idev_list_ = NULL; + + cras_audio_format format; + fill_audio_format(&format, 48000); + DevicePtr out_dev = create_device(CRAS_STREAM_OUTPUT, 1024, &format, + CRAS_NODE_TYPE_HEADPHONE); + DL_APPEND(odev_list_, out_dev->odev.get()); + struct cras_iodev* out_iodev = out_dev->odev->dev; + + DevicePtr in_dev = + create_device(CRAS_STREAM_INPUT, 1024, &format, CRAS_NODE_TYPE_MIC); + DL_APPEND(idev_list_, in_dev->odev.get()); + + StreamPtr in_stream = create_stream(1, 1, CRAS_STREAM_INPUT, 480, &format); + add_stream_to_dev(in_dev->dev, in_stream); + in_stream->rstream->next_cb_ts.tv_sec = 54321; + in_stream->rstream->next_cb_ts.tv_nsec = 12345; + in_stream->rstream->sleep_interval_ts.tv_sec = 321; + in_stream->rstream->sleep_interval_ts.tv_nsec = 123; + + ShmPtr shm = create_shm(480); + RstreamPtr rstream = + create_rstream(1, CRAS_STREAM_OUTPUT, 480, &format, shm.get()); + + dev_io_append_stream(&odev_list_, &idev_list_, rstream.get(), &out_iodev, 1); + + EXPECT_EQ(in_stream->rstream->next_cb_ts.tv_sec, rstream->next_cb_ts.tv_sec); + EXPECT_EQ(in_stream->rstream->next_cb_ts.tv_nsec, + rstream->next_cb_ts.tv_nsec); + EXPECT_EQ(in_stream->rstream->sleep_interval_ts.tv_sec, + rstream->sleep_interval_ts.tv_sec); + EXPECT_EQ(in_stream->rstream->sleep_interval_ts.tv_nsec, + rstream->sleep_interval_ts.tv_nsec); + + dev_stream_destroy(out_iodev->streams); +} + +// Test rule 2. // The device already has streams, the next_cb_ts will be the earliest // next_cb_ts from these streams. TEST_F(TimingSuite, NewOutputStreamInitStreamInDevice) { - struct open_dev* dev_list_ = NULL; + struct open_dev* odev_list_ = NULL; + struct open_dev* idev_list_ = NULL; cras_audio_format format; fill_audio_format(&format, 48000); DevicePtr dev = create_device(CRAS_STREAM_OUTPUT, 1024, &format, CRAS_NODE_TYPE_HEADPHONE); - DL_APPEND(dev_list_, dev->odev.get()); + DL_APPEND(odev_list_, dev->odev.get()); struct cras_iodev* iodev = dev->odev->dev; StreamPtr stream = create_stream(1, 1, CRAS_STREAM_OUTPUT, 480, &format); @@ -834,7 +880,7 @@ TEST_F(TimingSuite, NewOutputStreamInitStreamInDevice) { RstreamPtr rstream = create_rstream(1, CRAS_STREAM_OUTPUT, 480, &format, shm.get()); - dev_io_append_stream(&dev_list_, rstream.get(), &iodev, 1); + dev_io_append_stream(&odev_list_, &idev_list_, rstream.get(), &iodev, 1); EXPECT_EQ(stream->rstream->next_cb_ts.tv_sec, rstream->next_cb_ts.tv_sec); EXPECT_EQ(stream->rstream->next_cb_ts.tv_nsec, rstream->next_cb_ts.tv_nsec); @@ -842,17 +888,18 @@ TEST_F(TimingSuite, NewOutputStreamInitStreamInDevice) { dev_stream_destroy(iodev->streams->next); } -// Test rule 2. +// Test rule 3. // The there are no streams and no frames in device buffer. The next_cb_ts // will be set to now. TEST_F(TimingSuite, NewOutputStreamInitNoStreamNoFramesInDevice) { - struct open_dev* dev_list_ = NULL; + struct open_dev* odev_list_ = NULL; + struct open_dev* idev_list_ = NULL; cras_audio_format format; fill_audio_format(&format, 48000); DevicePtr dev = create_device(CRAS_STREAM_OUTPUT, 1024, &format, CRAS_NODE_TYPE_HEADPHONE); - DL_APPEND(dev_list_, dev->odev.get()); + DL_APPEND(odev_list_, dev->odev.get()); struct cras_iodev* iodev = dev->odev->dev; struct timespec start; @@ -862,7 +909,7 @@ TEST_F(TimingSuite, NewOutputStreamInitNoStreamNoFramesInDevice) { RstreamPtr rstream = create_rstream(1, CRAS_STREAM_OUTPUT, 480, &format, shm.get()); - dev_io_append_stream(&dev_list_, rstream.get(), &iodev, 1); + dev_io_append_stream(&odev_list_, &idev_list_, rstream.get(), &iodev, 1); EXPECT_EQ(start.tv_sec, rstream->next_cb_ts.tv_sec); EXPECT_EQ(start.tv_nsec, rstream->next_cb_ts.tv_nsec); @@ -875,13 +922,14 @@ TEST_F(TimingSuite, NewOutputStreamInitNoStreamNoFramesInDevice) { // next_cb_ts will be set to the time that valid frames in device is lower // than cb_threshold. TEST_F(TimingSuite, NewOutputStreamInitNoStreamSomeFramesInDevice) { - struct open_dev* dev_list_ = NULL; + struct open_dev* odev_list_ = NULL; + struct open_dev* idev_list_ = NULL; cras_audio_format format; fill_audio_format(&format, 48000); DevicePtr dev = create_device(CRAS_STREAM_OUTPUT, 1024, &format, CRAS_NODE_TYPE_HEADPHONE); - DL_APPEND(dev_list_, dev->odev.get()); + DL_APPEND(odev_list_, dev->odev.get()); struct cras_iodev* iodev = dev->odev->dev; struct timespec start; @@ -893,7 +941,7 @@ TEST_F(TimingSuite, NewOutputStreamInitNoStreamSomeFramesInDevice) { RstreamPtr rstream = create_rstream(1, CRAS_STREAM_OUTPUT, 480, &format, shm.get()); - dev_io_append_stream(&dev_list_, rstream.get(), &iodev, 1); + dev_io_append_stream(&odev_list_, &idev_list_, rstream.get(), &iodev, 1); // The next_cb_ts should be 10ms from now. At that time there are // only 480 valid frames in the device. diff --git a/cras/src/tools/cras_test_client/cras_test_client.c b/cras/src/tools/cras_test_client/cras_test_client.c index 5a7b3e06..7a851852 100644 --- a/cras/src/tools/cras_test_client/cras_test_client.c +++ b/cras/src/tools/cras_test_client/cras_test_client.c @@ -1017,10 +1017,13 @@ static void show_btlog_tag(const struct cras_bt_event_log *log, printf("%-30s dir %u codec id %u\n", "CODEC_SELECTION", data1, data2); break; - case BT_DEV_CONNECTED_CHANGE: - printf("%-30s supported profiles 0x%.2x now %s\n", - "DEV_CONNECTED_CHANGE", data1, - data2 ? "connected" : "disconnected"); + case BT_DEV_CONNECTED: + printf("%-30s supported profiles 0x%.2x stable_id 0x%08x\n", + "DEV_CONNECTED", data1, data2); + break; + case BT_DEV_DISCONNECTED: + printf("%-30s supported profiles 0x%.2x stable_id 0x%08x\n", + "DEV_DISCONNECTED", data1, data2); break; case BT_DEV_CONN_WATCH_CB: printf("%-30s %u retries left, supported profiles 0x%.2x\n", |