diff options
author | Jorge E. Moreira <jemoreira@google.com> | 2021-04-12 21:24:14 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2021-04-12 21:24:14 +0000 |
commit | 8cfb92904b40ff98c82dd5309698999a28c91388 (patch) | |
tree | 7e0e07b9d8d50fc5dd352d5b36d74fa3cb3427db | |
parent | 011384898c18c6b1119716aa6967e52b33c676a7 (diff) | |
parent | 94205e0ca5a9f1005e79f4682739fdce80a98dcc (diff) | |
download | adhd-8cfb92904b40ff98c82dd5309698999a28c91388.tar.gz |
Merge remote-tracking branch 'aosp/upstream-main' into master am: 072dce8ca5 am: bcf1f249f1 am: eebbf2e2b4 am: 94205e0ca5android-mainline-12.0.0_r99android-mainline-12.0.0_r98android-mainline-12.0.0_r77android-mainline-12.0.0_r70android-mainline-12.0.0_r69android-mainline-12.0.0_r63android-mainline-12.0.0_r59android-mainline-12.0.0_r56android-mainline-12.0.0_r5android-mainline-12.0.0_r49android-mainline-12.0.0_r42android-mainline-12.0.0_r39android-mainline-12.0.0_r22android-mainline-12.0.0_r19android-mainline-12.0.0_r122android-mainline-12.0.0_r115android-mainline-12.0.0_r113android-mainline-12.0.0_r100aml_tz3_314012070aml_tz3_314012050aml_tz3_314012010aml_tz3_313110000aml_tz3_312511020aml_tz3_312511010aml_tz3_312410020aml_tz3_312410010aml_tz3_311312010android12-mainline-tzdata3-releaseandroid12-mainline-networkstack-releaseandroid12-mainline-art-releaseaml_tz3_314012010
Original change: https://android-review.googlesource.com/c/platform/external/adhd/+/1673705
Change-Id: I164082a11bcb60d7c406b7f75aa3f190b58a1342
147 files changed, 6516 insertions, 1429 deletions
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index f8818dc5..e8af785b 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -7,10 +7,10 @@ name: "CodeQL" on: push: - branches: [master] + branches: [main] pull_request: # The branches below must be a subset of the branches above - branches: [master] + branches: [main] schedule: - cron: '0 11 * * 1' diff --git a/audio_streams/src/audio_streams.rs b/audio_streams/src/audio_streams.rs index 5290357e..e5fc83cb 100644 --- a/audio_streams/src/audio_streams.rs +++ b/audio_streams/src/audio_streams.rs @@ -341,8 +341,9 @@ impl NoopStream { impl PlaybackBufferStream for NoopStream { fn next_playback_buffer(&mut self) -> Result<PlaybackBuffer, BoxError> { if let Some(start_time) = self.start_time { - if start_time.elapsed() < self.next_frame { - std::thread::sleep(self.next_frame - start_time.elapsed()); + let elapsed = start_time.elapsed(); + if elapsed < self.next_frame { + std::thread::sleep(self.next_frame - elapsed); } self.next_frame += self.interval; } else { @@ -358,19 +359,6 @@ impl PlaybackBufferStream for NoopStream { } /// No-op control for `NoopStream`s. -/// Should be deprecated once all existing use of DummyStreamControl removed. -#[derive(Default)] -pub struct DummyStreamControl; - -impl DummyStreamControl { - pub fn new() -> Self { - DummyStreamControl {} - } -} - -impl StreamControl for DummyStreamControl {} - -/// No-op control for `NoopStream`s. #[derive(Default)] pub struct NoopStreamControl; diff --git a/audio_streams/src/capture.rs b/audio_streams/src/capture.rs index 930f1828..6a32cf1a 100644 --- a/audio_streams/src/capture.rs +++ b/audio_streams/src/capture.rs @@ -157,8 +157,9 @@ impl NoopCaptureStream { impl CaptureBufferStream for NoopCaptureStream { fn next_capture_buffer(&mut self) -> Result<CaptureBuffer, BoxError> { if let Some(start_time) = self.start_time { - if start_time.elapsed() < self.next_frame { - std::thread::sleep(self.next_frame - start_time.elapsed()); + let elapsed = start_time.elapsed(); + if elapsed < self.next_frame { + std::thread::sleep(self.next_frame - elapsed); } self.next_frame += self.interval; } else { diff --git a/audio_streams/src/shm_streams.rs b/audio_streams/src/shm_streams.rs index 13475cd8..b11626fd 100644 --- a/audio_streams/src/shm_streams.rs +++ b/audio_streams/src/shm_streams.rs @@ -271,10 +271,10 @@ impl ShmStream for NullShmStream { self.frame_rate } - fn wait_for_next_action_with_timeout<'a>( - &'a mut self, + fn wait_for_next_action_with_timeout( + &mut self, timeout: Duration, - ) -> GenericResult<Option<ServerRequest<'a>>> { + ) -> GenericResult<Option<ServerRequest>> { let elapsed = self.start_time.elapsed(); if elapsed < self.next_frame { if timeout < self.next_frame - elapsed { @@ -399,10 +399,10 @@ impl ShmStream for MockShmStream { self.frame_rate } - fn wait_for_next_action_with_timeout<'a>( - &'a mut self, + fn wait_for_next_action_with_timeout( + &mut self, timeout: Duration, - ) -> GenericResult<Option<ServerRequest<'a>>> { + ) -> GenericResult<Option<ServerRequest>> { { let start_time = Instant::now(); let &(ref lock, ref cvar) = &*self.request_notifier; 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", diff --git a/cros_alsa/src/card.rs b/cros_alsa/src/card.rs index fa88018f..42beef2c 100644 --- a/cros_alsa/src/card.rs +++ b/cros_alsa/src/card.rs @@ -10,6 +10,7 @@ use remain::sorted; use crate::control::{self, Control}; use crate::control_primitive; use crate::control_primitive::{Ctl, ElemId, ElemIface}; +use crate::control_tlv::{self, ControlTLV}; pub type Result<T> = std::result::Result<T, Error>; @@ -21,6 +22,8 @@ pub enum Error { AlsaControlAPI(control_primitive::Error), /// Error occurs in Control. Control(control::Error), + /// Error occurs in ControlTLV. + ControlTLV(control_tlv::Error), } impl error::Error for Error {} @@ -31,6 +34,12 @@ impl From<control::Error> for Error { } } +impl From<control_tlv::Error> for Error { + fn from(err: control_tlv::Error) -> Error { + Error::ControlTLV(err) + } +} + impl From<control_primitive::Error> for Error { fn from(err: control_primitive::Error) -> Error { Error::AlsaControlAPI(err) @@ -43,11 +52,13 @@ impl fmt::Display for Error { match self { AlsaControlAPI(e) => write!(f, "{}", e), Control(e) => write!(f, "{}", e), + ControlTLV(e) => write!(f, "{}", e), } } } /// `Card` represents a sound card. +#[derive(Debug)] pub struct Card { handle: Ctl, name: String, @@ -82,7 +93,7 @@ impl Card { /// # Errors /// /// * If control name is an invalid CString. - /// * If control dose not exist. + /// * If control does not exist. /// * If `Control` elem_type() mismatches the type of underlying mixer control. /// * If `Control` size() mismatches the number of value entries of underlying mixer control. pub fn control_by_name<'a, T: 'a>(&'a mut self, control_name: &str) -> Result<T> @@ -92,4 +103,15 @@ impl Card { let id = ElemId::new(ElemIface::Mixer, control_name)?; Ok(T::from(&mut self.handle, id)?) } + + /// Creates a `ControlTLV` from control name. + /// + /// # Errors + /// + /// * If control name is an invalid CString. + /// * If control does not exist. + pub fn control_tlv_by_name<'a>(&'a mut self, control_name: &str) -> Result<ControlTLV<'a>> { + let id = ElemId::new(ElemIface::Mixer, control_name)?; + Ok(ControlTLV::new(&mut self.handle, id)?) + } } diff --git a/cros_alsa/src/control_primitive.rs b/cros_alsa/src/control_primitive.rs index 4e1214d9..227aef27 100644 --- a/cros_alsa/src/control_primitive.rs +++ b/cros_alsa/src/control_primitive.rs @@ -16,9 +16,8 @@ use libc::strlen; use remain::sorted; pub type Result<T> = std::result::Result<T, Error>; -type BoxError = Box<dyn error::Error + Send + Sync>; -#[derive(Debug)] +#[derive(Debug, PartialEq)] /// Possible errors that can occur in FFI functions. pub enum FFIError { Rc(i32), @@ -36,7 +35,7 @@ impl fmt::Display for FFIError { } #[sorted] -#[derive(Debug)] +#[derive(Debug, PartialEq)] /// Possible errors that can occur in cros-alsa::control_primitive. pub enum Error { /// Control with the given name does not exist. @@ -51,10 +50,13 @@ pub enum Error { ElemInfoMallocFailed(FFIError), /// Failed to call snd_ctl_elem_value_malloc(). ElemValueMallocFailed(FFIError), - /// Input is not a valid CString. - InvalidCString(BoxError), + /// The slice used to create a CStr does not have one and only one null + /// byte positioned at the end. + FromBytesWithNulError(FromBytesWithNulError), /// Failed to convert to a valid ElemType. InvalidElemType(u32), + /// An error indicating that an interior nul byte was found. + NulError(NulError), /// Failed to call snd_strerror(). SndStrErrorFailed(i32), /// UTF-8 validation failed @@ -73,8 +75,9 @@ impl fmt::Display for Error { ElemIdMallocFailed(e) => write!(f, "snd_ctl_elem_id_malloc failed: {}", e), ElemInfoMallocFailed(e) => write!(f, "snd_ctl_elem_info_malloc failed: {}", e), ElemValueMallocFailed(e) => write!(f, "snd_ctl_elem_value_malloc failed: {}", e), - InvalidCString(e) => write!(f, "invalid CString: {}", e), + FromBytesWithNulError(e) => write!(f, "invalid CString: {}", e), InvalidElemType(v) => write!(f, "invalid ElemType: {}", v), + NulError(e) => write!(f, "invalid CString: {}", e), SndStrErrorFailed(e) => write!(f, "snd_strerror() failed: {}", e), Utf8Error(e) => write!(f, "{}", e), } @@ -95,13 +98,13 @@ impl From<str::Utf8Error> for Error { impl From<FromBytesWithNulError> for Error { fn from(err: FromBytesWithNulError) -> Error { - Error::InvalidCString(err.into()) + Error::FromBytesWithNulError(err) } } impl From<NulError> for Error { fn from(err: NulError) -> Error { - Error::InvalidCString(err.into()) + Error::NulError(err) } } @@ -324,9 +327,22 @@ impl ElemInfo { // Safe because self.0.as_ptr() is a valid snd_ctl_elem_info_t*. unsafe { snd_ctl_elem_info_get_count(self.0.as_ptr()) as usize } } + + /// Safe [snd_ctl_elem_info_is_tlv_readable](https://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html#gaac6bb412e5a9fffb5509e98a10de45b5) wrapper. + pub fn tlv_readable(&self) -> bool { + // Safe because self.0.as_ptr() is a valid snd_ctl_elem_info_t*. + unsafe { snd_ctl_elem_info_is_tlv_readable(self.0.as_ptr()) as usize == 1 } + } + + /// Safe [snd_ctl_elem_info_is_tlv_writable](https://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html#gacfbaae80d710b6feac682f8ba10a0341) wrapper. + pub fn tlv_writable(&self) -> bool { + // Safe because self.0.as_ptr() is a valid snd_ctl_elem_info_t*. + unsafe { snd_ctl_elem_info_is_tlv_writable(self.0.as_ptr()) as usize == 1 } + } } /// [snd_ctl_t](https://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html#ga06628f38def84a0fe3da74041db9d51f) wrapper. +#[derive(Debug)] pub struct Ctl(ptr::NonNull<snd_ctl_t>, PhantomData<snd_ctl_t>); impl Drop for Ctl { diff --git a/cros_alsa/src/control_tlv.rs b/cros_alsa/src/control_tlv.rs new file mode 100644 index 00000000..e1f35510 --- /dev/null +++ b/cros_alsa/src/control_tlv.rs @@ -0,0 +1,313 @@ +// Copyright 2020 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +//! `ControlTLV` supports read and write of the alsa TLV byte controls +//! Users can obtain a ControlTLV by Card::control_tlv_by_name(). +//! # Examples +//! This is an example of how to use a `TLV`. +//! +//! ``` +//! use std::assert_eq; +//! use std::convert::TryFrom; +//! use std::error::Error; +//! +//! use cros_alsa::{TLV, ControlTLVError}; +//! use cros_alsa::elem::Elem; +//! +//! type Result<T> = std::result::Result<T, ControlTLVError>; +//! +//! let mut tlv = TLV::new(0, vec![1,2,3,4]); +//! assert_eq!(4, tlv.len()); +//! assert_eq!(0, tlv.tlv_type()); +//! assert_eq!(2, tlv[1]); +//! tlv[1] = 8; +//! assert_eq!(vec![1,8,3,4], tlv.value().to_vec()); +//! assert_eq!(vec![0,16,1,8,3,4], Into::<Vec<u32>>::into(tlv)); +//! +//! ``` + +use std::{ + convert::TryFrom, + fmt, + ops::{Index, IndexMut}, + slice::SliceIndex, +}; +use std::{error, mem::size_of}; + +use remain::sorted; + +use crate::control_primitive::{self, Ctl, ElemId, ElemInfo, ElemType}; + +/// The Result type of cros-alsa::control. +pub type Result<T> = std::result::Result<T, Error>; + +#[sorted] +#[derive(Debug, PartialEq)] +/// Possible errors that can occur in cros-alsa::control. +pub enum Error { + /// Failed to call AlsaControlAPI. + AlsaControlAPI(control_primitive::Error), + /// Failed to convert buffer to TLV struct. + InvalidTLV, + /// ElemInfo::count() is not multiple of size_of::<u32>. + InvalidTLVSize(String, usize), + /// ElemInfo::elem_type() is not ElemType::Bytes. + InvalidTLVType(String, ElemType), + /// The control is not readable. + TLVNotReadable, + /// The control is not writeable. + TLVNotWritable, + /// Failed to call snd_ctl_elem_tlv_read. + TLVReadFailed(i32), + /// Failed to call snd_ctl_elem_tlv_write. + TVLWriteFailed(i32), +} + +impl error::Error for Error {} + +impl From<control_primitive::Error> for Error { + fn from(err: control_primitive::Error) -> Error { + Error::AlsaControlAPI(err) + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use Error::*; + match self { + AlsaControlAPI(e) => write!(f, "{}", e), + InvalidTLV => write!(f, "failed to convert to TLV"), + InvalidTLVSize(name, elem_size) => write!( + f, + "ElemInfo::size() of {} should be multiple of size_of::<u32>, get: {}", + name, elem_size + ), + InvalidTLVType(name, elem_type) => write!( + f, + "invalid ElemInfo::elem_type() of {}: expect: {}, get: {}", + name, + ElemType::Bytes, + elem_type + ), + TLVNotReadable => write!(f, "the control is not readable."), + TLVNotWritable => write!(f, "the control is not writable."), + TLVReadFailed(rc) => write!(f, "snd_ctl_elem_tlv_read failed: {}", rc), + TVLWriteFailed(rc) => write!(f, "snd_ctl_elem_tlv_write failed: {}", rc), + } + } +} + +/// TLV struct represents the TLV data to be read from +/// or write to an alsa TLV byte control. +#[derive(Debug)] +pub struct TLV { + /// data[Self::TYPE_OFFSET] contains the tlv type. + /// data[Self::LEN_OFFSET] contains the length of the value in bytes. + /// data[Self::VALUE_OFFSET..] contains the data. + data: Vec<u32>, +} + +impl TLV { + const TYPE_OFFSET: usize = 0; + const LEN_OFFSET: usize = 1; + const VALUE_OFFSET: usize = 2; + const TLV_HEADER_SIZE_BYTES: usize = Self::VALUE_OFFSET * size_of::<u32>(); + + /// Initializes a `TLV` by giving the tlv type and tlv value. + pub fn new(tlv_type: u32, tlv_value: Vec<u32>) -> Self { + let mut data = vec![0; 2]; + data[Self::TYPE_OFFSET] = tlv_type; + data[Self::LEN_OFFSET] = (tlv_value.len() * size_of::<u32>()) as u32; + data.extend(tlv_value.iter()); + Self { data } + } + + /// Returns the type of the tlv. + pub fn tlv_type(&self) -> u32 { + self.data[Self::TYPE_OFFSET] + } + + /// Returns the length of the tlv value in dword. + pub fn len(&self) -> usize { + self.data[Self::LEN_OFFSET] as usize / size_of::<u32>() + } + + /// Returns whether the tlv value is empty. + pub fn is_empty(&self) -> bool { + self.data[Self::LEN_OFFSET] == 0 + } + + /// Returns the tlv value in slice. + pub fn value(&self) -> &[u32] { + &self.data[Self::VALUE_OFFSET..] + } +} + +impl<I: SliceIndex<[u32]>> Index<I> for TLV { + type Output = I::Output; + #[inline] + fn index(&self, index: I) -> &Self::Output { + &self.data[Self::VALUE_OFFSET..][index] + } +} + +impl<I: SliceIndex<[u32]>> IndexMut<I> for TLV { + #[inline] + fn index_mut(&mut self, index: I) -> &mut Self::Output { + &mut self.data[Self::VALUE_OFFSET..][index] + } +} + +impl TryFrom<Vec<u32>> for TLV { + type Error = Error; + + /// Constructs a TLV from a vector with the following alsa tlv header validation: + /// 1 . tlv_buf[Self::LEN_OFFSET] should be multiple of size_of::<u32> + /// 2 . tlv_buf[Self::LEN_OFFSET] is the length of tlv value in byte and + /// should be less than the buffer length * size_of::<u32>. + fn try_from(data: Vec<u32>) -> Result<Self> { + if data.len() < 2 { + return Err(Error::InvalidTLV); + } + + if data[Self::LEN_OFFSET] % size_of::<u32>() as u32 != 0 { + return Err(Error::InvalidTLV); + } + + if data[Self::LEN_OFFSET] / size_of::<u32>() as u32 + > data[Self::VALUE_OFFSET..].len() as u32 + { + return Err(Error::InvalidTLV); + } + + Ok(Self { data }) + } +} + +impl Into<Vec<u32>> for TLV { + /// Returns the raw tlv data buffer (including the tlv header). + fn into(self) -> Vec<u32> { + self.data + } +} + +/// `ControlTLV` supports read and write of the alsa TLV byte controls. +pub struct ControlTLV<'a> { + handle: &'a mut Ctl, + id: ElemId, +} + +impl<'a> ControlTLV<'a> { + /// Called by `Card` to create a `ControlTLV`. + pub fn new(handle: &'a mut Ctl, id: ElemId) -> Result<Self> { + let info = ElemInfo::new(handle, &id)?; + if info.count() % size_of::<u32>() != 0 { + return Err(Error::InvalidTLVSize(id.name()?.to_owned(), info.count())); + } + match info.elem_type()? { + ElemType::Bytes => Ok(Self { handle, id }), + _ => Err(Error::InvalidTLVType( + id.name()?.to_owned(), + info.elem_type()?, + )), + } + } + + /// Reads data from the byte control by `snd_ctl_elem_tlv_read` + /// + /// # + /// # Errors + /// + /// * If it fails to read from the control. + pub fn load(&mut self) -> Result<TLV> { + if !ElemInfo::new(self.handle, &self.id)?.tlv_readable() { + return Err(Error::TLVNotReadable); + } + + let tlv_size = ElemInfo::new(self.handle, &self.id)?.count() + TLV::TLV_HEADER_SIZE_BYTES; + + let mut tlv_buf = vec![0; tlv_size / size_of::<u32>()]; + // Safe because handle.as_mut_ptr() is a valid *mut snd_ctl_t, id_as_ptr is valid and + // tlv_buf.as_mut_ptr() is also valid. + let rc = unsafe { + alsa_sys::snd_ctl_elem_tlv_read( + self.handle.as_mut_ptr(), + self.id.as_ptr(), + tlv_buf.as_mut_ptr(), + tlv_size as u32, + ) + }; + if rc < 0 { + return Err(Error::TLVReadFailed(rc)); + } + Ok(TLV::try_from(tlv_buf)?) + } + + /// Writes to the byte control by `snd_ctl_elem_tlv_write` + /// + /// # Results + /// + /// * `changed` - false on success. + /// - true on success when value was changed. + /// # + /// # Errors + /// + /// * If it fails to write to the control. + pub fn save(&mut self, tlv: TLV) -> Result<bool> { + if !ElemInfo::new(self.handle, &self.id)?.tlv_writable() { + return Err(Error::TLVNotReadable); + } + // Safe because handle.as_mut_ptr() is a valid *mut snd_ctl_t, id_as_ptr is valid and + // tlv.as_mut_ptr() is also valid. + let rc = unsafe { + alsa_sys::snd_ctl_elem_tlv_write( + self.handle.as_mut_ptr(), + self.id.as_ptr(), + Into::<Vec<u32>>::into(tlv).as_mut_ptr(), + ) + }; + if rc < 0 { + return Err(Error::TVLWriteFailed(rc)); + } + Ok(rc > 0) + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_tlv_try_from_raw_vec() { + let tlv_buf = vec![0, 12, 2, 3, 4]; + assert!(TLV::try_from(tlv_buf).is_ok()); + } + + #[test] + fn test_tlv_length_is_not_multiple_of_sizeof_int() { + // Invalid tlv length in data[Self::LEN_OFFSET]. + let tlv_buf = vec![0, 1, 2, 3, 4]; + assert_eq!(TLV::try_from(tlv_buf).unwrap_err(), Error::InvalidTLV); + } + + #[test] + fn test_tlv_length_larger_than_buff_size() { + // Invalid tlv length in data[Self::LEN_OFFSET]. + let tlv_buf = vec![0, 16, 2, 3, 4]; + assert_eq!(TLV::try_from(tlv_buf).unwrap_err(), Error::InvalidTLV); + } + + #[test] + fn test_tlv_length_less_than_two() { + // tlv buffer length < 2 + let tlv_buf = vec![0]; + assert_eq!(TLV::try_from(tlv_buf).unwrap_err(), Error::InvalidTLV); + } + + #[test] + fn test_tlv_length_equal_two() { + // tlv buffer size = 2. + let tlv_buf = vec![0, 0]; + assert!(TLV::try_from(tlv_buf).is_ok()); + } +} diff --git a/cros_alsa/src/lib.rs b/cros_alsa/src/lib.rs index aec3d52f..070d221a 100644 --- a/cros_alsa/src/lib.rs +++ b/cros_alsa/src/lib.rs @@ -44,14 +44,17 @@ mod card; mod control; mod control_primitive; +pub mod control_tlv; pub mod elem; pub use self::card::Card; pub use self::control::{Control, ControlOps, IntControl, StereoVolumeControl, SwitchControl}; pub use self::control_primitive::{Ctl, ElemId}; +pub use self::control_tlv::{ControlTLV, TLV}; pub use self::card::Error as CardError; pub use self::control::Error as ControlError; +pub use self::control_tlv::Error as ControlTLVError; pub use self::elem::Error as ElemError; #[allow(unused_imports)] diff --git a/init/cras.sh b/init/cras.sh index ca2d6b93..91114c00 100644 --- a/init/cras.sh +++ b/init/cras.sh @@ -48,7 +48,6 @@ exec minijail0 -u cras -g cras -G --uts -v -l \ -k 'tmpfs,/run,tmpfs,MS_NODEV|MS_NOEXEC|MS_NOSUID,mode=755,size=10M' \ -b /run/cras,/run/cras,1 \ -b /run/dbus,/run/dbus,1 \ - -b /run/systemd/journal \ -b /run/udev,/run/udev \ -b /dev,/dev \ -b /dev/shm,/dev/shm,1 \ diff --git a/scripts/audio_tuning/frontend/audio.js b/scripts/audio_tuning/frontend/audio.js index b90be953..98870cdd 100644 --- a/scripts/audio_tuning/frontend/audio.js +++ b/scripts/audio_tuning/frontend/audio.js @@ -1570,7 +1570,7 @@ function DrcDrawer(canvas) { var kneeThresholdDb; var kneeThreshold; var ykneeThresholdDb; - var masterLinearGain; + var mainLinearGain; var maxOutputDb = 6; var minOutputDb = -36; @@ -1662,10 +1662,10 @@ function DrcDrawer(canvas) { kneeThreshold = dBToLinear(kneeThresholdDb); ykneeThresholdDb = linearToDb(kneeCurve(kneeThreshold, curve_k)); - /* Calculate masterLinearGain */ + /* Calculate mainLinearGain */ var fullRangeGain = saturate(1, curve_k); var fullRangeMakeupGain = Math.pow(1 / fullRangeGain, 0.6); - masterLinearGain = dBToLinear(boost) * fullRangeMakeupGain; + mainLinearGain = dBToLinear(boost) * fullRangeMakeupGain; /* Clear canvas */ var width = canvas.width; @@ -1725,7 +1725,7 @@ function DrcDrawer(canvas) { var inputDb = xpixelToDb(x); var inputLinear = dBToLinear(inputDb); var outputLinear = saturate(inputLinear, curve_k); - outputLinear *= masterLinearGain; + outputLinear *= mainLinearGain; var outputDb = linearToDb(outputLinear); var y = dBToYPixel(outputDb); diff --git a/seccomp/cras-seccomp-amd64.policy b/seccomp/cras-seccomp-amd64.policy index 021ced19..afa6ace6 100644 --- a/seccomp/cras-seccomp-amd64.policy +++ b/seccomp/cras-seccomp-amd64.policy @@ -20,6 +20,7 @@ getrandom: 1 access: 1 fcntl: 1 getdents: 1 +getdents64: 1 sendmsg: 1 stat: 1 statfs: 1 @@ -38,6 +39,7 @@ socket: arg0 == AF_UNIX || arg0 == AF_BLUETOOTH || arg0 == AF_NETLINK socketpair: 1 unlink: 1 nanosleep: 1 +clock_nanosleep: 1 pipe: 1 ftruncate: 1 fallocate: 1 diff --git a/seccomp/cras-seccomp-arm.policy b/seccomp/cras-seccomp-arm.policy index e823244b..cbaa622b 100644 --- a/seccomp/cras-seccomp-arm.policy +++ b/seccomp/cras-seccomp-arm.policy @@ -3,9 +3,11 @@ # found in the LICENSE file. clock_gettime: 1 +clock_gettime64: 1 poll: 1 read: 1 ppoll: 1 +ppoll_time64: 1 write: 1 writev: 1 recv: 1 @@ -41,6 +43,7 @@ rt_sigprocmask: 1 ftruncate: 1 fallocate: 1 futex: 1 +futex_time64: 1 execve: 1 set_robust_list: 1 socket: arg0 == AF_UNIX || arg0 == AF_BLUETOOTH || arg0 == AF_NETLINK @@ -64,6 +67,8 @@ exit_group: 1 getsockname: 1 getdents: 1 nanosleep: 1 +clock_nanosleep: 1 +clock_nanosleep_time64: 1 epoll_ctl: 1 sched_setscheduler: 1 restart_syscall: 1 @@ -72,6 +77,7 @@ getresuid32: 1 exit: 1 prctl: arg0 == PR_SET_NAME clock_getres: 1 +clock_getres_time64: 1 epoll_create1: 1 fchmod: 1 setpriority: 1 diff --git a/seccomp/cras-seccomp-arm64.policy b/seccomp/cras-seccomp-arm64.policy index d06a7411..4b523552 100644 --- a/seccomp/cras-seccomp-arm64.policy +++ b/seccomp/cras-seccomp-arm64.policy @@ -68,6 +68,7 @@ listen: 1 lsetxattr: 1 madvise: 1 nanosleep: 1 +clock_nanosleep: 1 restart_syscall: 1 rt_sigprocmask: 1 rt_sigreturn: 1 diff --git a/sof_sys/.gitignore b/sof_sys/.gitignore new file mode 100644 index 00000000..fa8d85ac --- /dev/null +++ b/sof_sys/.gitignore @@ -0,0 +1,2 @@ +Cargo.lock +target diff --git a/sof_sys/.rustfmt.toml b/sof_sys/.rustfmt.toml new file mode 100644 index 00000000..a2db3012 --- /dev/null +++ b/sof_sys/.rustfmt.toml @@ -0,0 +1,2 @@ +use_field_init_shorthand = true +use_try_shorthand = true diff --git a/sof_sys/Cargo.toml b/sof_sys/Cargo.toml new file mode 100644 index 00000000..21934ee1 --- /dev/null +++ b/sof_sys/Cargo.toml @@ -0,0 +1,4 @@ +[package] +name = "sof_sys" +version = "0.1.0" +authors = ["The Chromium OS Authors"] diff --git a/sof_sys/generator/.gitignore b/sof_sys/generator/.gitignore new file mode 100644 index 00000000..03314f77 --- /dev/null +++ b/sof_sys/generator/.gitignore @@ -0,0 +1 @@ +Cargo.lock diff --git a/sof_sys/generator/Cargo.toml b/sof_sys/generator/Cargo.toml new file mode 100644 index 00000000..672d41da --- /dev/null +++ b/sof_sys/generator/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "generator" +version = "0.1.0" +authors = ["The Chromium OS Authors"] +[dependencies] +bindgen = "0.43.0" diff --git a/sof_sys/generator/README.md b/sof_sys/generator/README.md new file mode 100644 index 00000000..3e2ca771 --- /dev/null +++ b/sof_sys/generator/README.md @@ -0,0 +1 @@ +Use `cargo run` to generate rust bindings at sof_sys/src/bindings.rs diff --git a/sof_sys/generator/src/main.rs b/sof_sys/generator/src/main.rs new file mode 100644 index 00000000..60f0102d --- /dev/null +++ b/sof_sys/generator/src/main.rs @@ -0,0 +1,42 @@ +// Copyright 2020 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +extern crate bindgen; + +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + let bindings = bindgen::Builder::default() + .header("wrapper.h") + .derive_debug(false) + .clang_arg("-I../../../sound-open-firmware-private/src/include") + .whitelist_type("sof_abi_hdr") + .whitelist_type("sof_ipc_ctrl_cmd") + .generate() + .expect("Unable to generate bindings"); + + let header = b"// Copyright 2020 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +/* + * generated from files in sound-open-firmware-private/src/include: + * kernel/header.h + * ipc/control.h + */ + +"; + + // Write the bindings to the $OUT_DIR/bindings.rs file. + let out_path = PathBuf::from("../src").join("bindings.rs"); + + let mut output_file = + File::create(&out_path).expect(&format!("Couldn't create {:?}", out_path)); + output_file + .write_all(header) + .expect("Couldn't write header"); + output_file + .write_all(bindings.to_string().as_bytes()) + .expect("Couldn't write bindings"); +} diff --git a/sof_sys/generator/wrapper.h b/sof_sys/generator/wrapper.h new file mode 100644 index 00000000..5bac0f57 --- /dev/null +++ b/sof_sys/generator/wrapper.h @@ -0,0 +1,5 @@ +// Copyright 2021 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#include <kernel/header.h> +#include <ipc/control.h> diff --git a/sof_sys/src/bindings.rs b/sof_sys/src/bindings.rs new file mode 100644 index 00000000..7bed0dcf --- /dev/null +++ b/sof_sys/src/bindings.rs @@ -0,0 +1,154 @@ +// Copyright 2020 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +/* + * generated from files in sound-open-firmware-private/src/include: + * kernel/header.h + * ipc/control.h + */ + +/* automatically generated by rust-bindgen */ + +#[repr(C)] +#[derive(Default)] +pub struct __IncompleteArrayField<T>(::std::marker::PhantomData<T>); +impl<T> __IncompleteArrayField<T> { + #[inline] + pub fn new() -> Self { + __IncompleteArrayField(::std::marker::PhantomData) + } + #[inline] + pub unsafe fn as_ptr(&self) -> *const T { + ::std::mem::transmute(self) + } + #[inline] + pub unsafe fn as_mut_ptr(&mut self) -> *mut T { + ::std::mem::transmute(self) + } + #[inline] + pub unsafe fn as_slice(&self, len: usize) -> &[T] { + ::std::slice::from_raw_parts(self.as_ptr(), len) + } + #[inline] + pub unsafe fn as_mut_slice(&mut self, len: usize) -> &mut [T] { + ::std::slice::from_raw_parts_mut(self.as_mut_ptr(), len) + } +} +impl<T> ::std::fmt::Debug for __IncompleteArrayField<T> { + fn fmt(&self, fmt: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + fmt.write_str("__IncompleteArrayField") + } +} +impl<T> ::std::clone::Clone for __IncompleteArrayField<T> { + #[inline] + fn clone(&self) -> Self { + Self::new() + } +} +impl<T> ::std::marker::Copy for __IncompleteArrayField<T> {} +pub type __uint32_t = ::std::os::raw::c_uint; +/// \brief Header for all non IPC ABI data. +/// +/// Identifies data type, size and ABI. +/// Data header used for all component data structures and binary blobs sent to +/// firmware as runtime data. This data is typically sent by userspace +/// applications and tunnelled through any OS kernel (via binary kcontrol on +/// Linux) to the firmware. +#[repr(C, packed)] +pub struct sof_abi_hdr { + ///< 'S', 'O', 'F', '\0' + pub magic: u32, + ///< component specific type + pub type_: u32, + ///< size in bytes of data excl. this struct + pub size: u32, + ///< SOF ABI version + pub abi: u32, + ///< reserved for future use + pub reserved: [u32; 4usize], + ///< Component data - opaque to core + pub data: __IncompleteArrayField<u32>, +} +#[test] +fn bindgen_test_layout_sof_abi_hdr() { + assert_eq!( + ::std::mem::size_of::<sof_abi_hdr>(), + 32usize, + concat!("Size of: ", stringify!(sof_abi_hdr)) + ); + assert_eq!( + ::std::mem::align_of::<sof_abi_hdr>(), + 1usize, + concat!("Alignment of ", stringify!(sof_abi_hdr)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<sof_abi_hdr>())).magic as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sof_abi_hdr), + "::", + stringify!(magic) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<sof_abi_hdr>())).type_ as *const _ as usize }, + 4usize, + concat!( + "Offset of field: ", + stringify!(sof_abi_hdr), + "::", + stringify!(type_) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<sof_abi_hdr>())).size as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(sof_abi_hdr), + "::", + stringify!(size) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<sof_abi_hdr>())).abi as *const _ as usize }, + 12usize, + concat!( + "Offset of field: ", + stringify!(sof_abi_hdr), + "::", + stringify!(abi) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<sof_abi_hdr>())).reserved as *const _ as usize }, + 16usize, + concat!( + "Offset of field: ", + stringify!(sof_abi_hdr), + "::", + stringify!(reserved) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<sof_abi_hdr>())).data as *const _ as usize }, + 32usize, + concat!( + "Offset of field: ", + stringify!(sof_abi_hdr), + "::", + stringify!(data) + ) + ); +} +///< maps to ALSA volume style controls +pub const sof_ipc_ctrl_cmd_SOF_CTRL_CMD_VOLUME: sof_ipc_ctrl_cmd = 0; +///< maps to ALSA enum style controls +pub const sof_ipc_ctrl_cmd_SOF_CTRL_CMD_ENUM: sof_ipc_ctrl_cmd = 1; +///< maps to ALSA switch style controls +pub const sof_ipc_ctrl_cmd_SOF_CTRL_CMD_SWITCH: sof_ipc_ctrl_cmd = 2; +///< maps to ALSA binary style controls +pub const sof_ipc_ctrl_cmd_SOF_CTRL_CMD_BINARY: sof_ipc_ctrl_cmd = 3; +/// Control command type. +pub type sof_ipc_ctrl_cmd = u32; diff --git a/sof_sys/src/lib.rs b/sof_sys/src/lib.rs new file mode 100644 index 00000000..57119cfa --- /dev/null +++ b/sof_sys/src/lib.rs @@ -0,0 +1,12 @@ +// Copyright 2020 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#![allow(clippy::unreadable_literal)] +#![allow(clippy::cognitive_complexity)] +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] + +pub mod bindings; +#[allow(unused_imports)] +pub use bindings::sof_abi_hdr; diff --git a/sound_card_init/Cargo.lock b/sound_card_init/Cargo.lock index 06448e9d..c89ad1ec 100644 --- a/sound_card_init/Cargo.lock +++ b/sound_card_init/Cargo.lock @@ -11,6 +11,26 @@ dependencies = [ ] [[package]] +name = "amp" +version = "0.1.0" +dependencies = [ + "cros_alsa", + "dsm", + "libcras", + "remain", + "serde", + "serde_yaml", + "sof_sys", + "sys_util", +] + +[[package]] +name = "android_log-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85965b6739a430150bdd138e2374a98af0c3ee0d030b3bb7fc3bddff58d0102e" + +[[package]] name = "assertions" version = "0.1.0" @@ -58,10 +78,23 @@ dependencies = [ ] [[package]] +name = "dsm" +version = "0.1.0" +dependencies = [ + "audio_streams", + "cros_alsa", + "libcras", + "remain", + "serde", + "serde_yaml", + "sys_util", +] + +[[package]] name = "dtoa" -version = "0.4.5" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3" +checksum = "88d7ed2934d741c6b37e33e3832298e8850b53fd2d2bea03873375596c7cea4e" [[package]] name = "getopts" @@ -74,9 +107,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.71" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" +checksum = "89203f3fba0a3795506acaad8ebce3c80c0af93f994d5a1d7a0b1eeb23271929" [[package]] name = "libcras" @@ -91,29 +124,15 @@ dependencies = [ [[package]] name = "linked-hash-map" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" - -[[package]] -name = "max98390d" -version = "0.1.0" -dependencies = [ - "audio_streams", - "cros_alsa", - "libcras", - "remain", - "serde", - "serde_yaml", - "sys_util", - "utils", -] +checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" [[package]] name = "pkg-config" -version = "0.3.17" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" +checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" [[package]] name = "poll_token_derive" @@ -126,27 +145,27 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.18" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" dependencies = [ "unicode-xid", ] [[package]] name = "quote" -version = "1.0.6" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea" +checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df" dependencies = [ "proc-macro2", ] [[package]] name = "remain" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99c861227fc40c8da6fdaa3d58144ac84c0537080a43eb1d7d45c28f88dcb888" +checksum = "70ba1e78fa68412cb93ef642fd4d20b9a941be49ee9333875ebaf13112673ea7" dependencies = [ "proc-macro2", "quote", @@ -155,18 +174,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.111" +version = "1.0.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9124df5b40cbd380080b2cc6ab894c040a3070d995f5c9dc77e18c34a8ae37d" +checksum = "9bdd36f49e35b61d49efd8aa7fc068fd295961fd2286d0b2ee9a4c7a14e99cc3" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.111" +version = "1.0.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f2c3ac8e6ca1e9c80b8be1023940162bf81ae3cffbb1809474152f2ce1eb250" +checksum = "552954ce79a059ddd5fd68c271592374bd15cab2274970380c000118aeffe1cd" dependencies = [ "proc-macro2", "quote", @@ -175,9 +194,9 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.8.12" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16c7a592a1ec97c9c1c68d75b6e537dcbf60c7618e038e7841e00af1d9ccf0c4" +checksum = "971be8f6e4d4a47163b405a3df70d14359186f9ab0f3a3ec37df144ca1ce089f" dependencies = [ "dtoa", "linked-hash-map", @@ -186,26 +205,31 @@ dependencies = [ ] [[package]] +name = "sof_sys" +version = "0.1.0" + +[[package]] name = "sound_card_init" version = "0.1.0" dependencies = [ + "amp", "audio_streams", "cros_alsa", + "dsm", "getopts", "libcras", - "max98390d", "remain", "serde", "serde_yaml", + "sof_sys", "sys_util", - "utils", ] [[package]] name = "syn" -version = "1.0.30" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93a56fabc59dce20fe48b6c832cc249c713e7ed88fa28b0ee0a3bfcaae5fe4e2" +checksum = "cc60a3d73ea6594cd712d830cc1f0390fd71542d8c8cd24e70cc54cdfd5e05d5" dependencies = [ "proc-macro2", "quote", @@ -220,6 +244,7 @@ version = "0.1.0" name = "sys_util" version = "0.1.0" dependencies = [ + "android_log-sys", "data_model", "libc", "poll_token_derive", @@ -241,30 +266,21 @@ dependencies = [ [[package]] name = "unicode-width" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" [[package]] name = "unicode-xid" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" - -[[package]] -name = "utils" -version = "0.1.0" -dependencies = [ - "remain", - "serde", - "serde_yaml", -] +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" [[package]] name = "yaml-rust" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39f0c922f1a334134dc2f7a8b67dc5d25f0735263feec974345ff706bcf20b0d" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" dependencies = [ "linked-hash-map", ] diff --git a/sound_card_init/Cargo.toml b/sound_card_init/Cargo.toml index d4a2b376..9d5a107e 100644 --- a/sound_card_init/Cargo.toml +++ b/sound_card_init/Cargo.toml @@ -5,16 +5,23 @@ authors = ["The Chromium OS Authors"] edition = "2018" description = "Sound Card Initializer" +[workspace] +members = [ + "amp", + "dsm" +] + [dependencies] +amp = { path = "amp" } audio_streams = "*" cros_alsa = "*" +dsm = { path = "dsm" } getopts = "0.2" libcras = "*" remain = "0.2.1" -max98390d = { path = "max98390d" } -utils = { path = "utils" } serde = { version = "1.0", features = ["derive"] } serde_yaml = "0.8.11" +sof_sys = "*" sys_util = "*" [patch.crates-io] @@ -22,4 +29,5 @@ audio_streams = { path = "../audio_streams" } # ignored by ebuild cros_alsa = { path = "../cros_alsa" } # ignored by ebuild cros_alsa_derive = { path = "../cros_alsa/cros_alsa_derive" } # ignored by ebuild libcras = { path = "../cras/client/libcras" } # ignored by ebuild +sof_sys = { path = "../sof_sys" } # ignored by ebuild sys_util = { path = "../../../platform/crosvm/sys_util" } # ignored by ebuild diff --git a/sound_card_init/amp/Cargo.lock b/sound_card_init/amp/Cargo.lock new file mode 100644 index 00000000..679e60cd --- /dev/null +++ b/sound_card_init/amp/Cargo.lock @@ -0,0 +1,254 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "alsa-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "644d308f5822c2b39fba5a6d850f74c208bf73c61d1d2dfad62505d6960e4977" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "amp" +version = "0.1.0" +dependencies = [ + "cros_alsa", + "dsm", + "libcras", + "remain", + "serde", + "serde_yaml", + "sof_sys", + "sys_util", +] + +[[package]] +name = "android_log-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85965b6739a430150bdd138e2374a98af0c3ee0d030b3bb7fc3bddff58d0102e" + +[[package]] +name = "assertions" +version = "0.1.0" + +[[package]] +name = "audio_streams" +version = "0.1.0" +dependencies = [ + "sync", + "sys_util", +] + +[[package]] +name = "cras-sys" +version = "0.1.0" +dependencies = [ + "audio_streams", + "data_model", +] + +[[package]] +name = "cros_alsa" +version = "0.1.0" +dependencies = [ + "alsa-sys", + "cros_alsa_derive", + "libc", + "remain", +] + +[[package]] +name = "cros_alsa_derive" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "data_model" +version = "0.1.0" +dependencies = [ + "assertions", + "libc", +] + +[[package]] +name = "dsm" +version = "0.1.0" +dependencies = [ + "audio_streams", + "cros_alsa", + "libcras", + "remain", + "serde", + "serde_yaml", + "sys_util", +] + +[[package]] +name = "dtoa" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d7ed2934d741c6b37e33e3832298e8850b53fd2d2bea03873375596c7cea4e" + +[[package]] +name = "libc" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cca32fa0182e8c0989459524dc356b8f2b5c10f1b9eb521b7d182c03cf8c5ff" + +[[package]] +name = "libcras" +version = "0.1.0" +dependencies = [ + "audio_streams", + "cras-sys", + "data_model", + "libc", + "sys_util", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" + +[[package]] +name = "pkg-config" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" + +[[package]] +name = "poll_token_derive" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "remain" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ba1e78fa68412cb93ef642fd4d20b9a941be49ee9333875ebaf13112673ea7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde" +version = "1.0.123" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.123" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_yaml" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "971be8f6e4d4a47163b405a3df70d14359186f9ab0f3a3ec37df144ca1ce089f" +dependencies = [ + "dtoa", + "linked-hash-map", + "serde", + "yaml-rust", +] + +[[package]] +name = "sof_sys" +version = "0.1.0" + +[[package]] +name = "syn" +version = "1.0.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "sync" +version = "0.1.0" + +[[package]] +name = "sys_util" +version = "0.1.0" +dependencies = [ + "android_log-sys", + "data_model", + "libc", + "poll_token_derive", + "sync", + "syscall_defines", + "tempfile", +] + +[[package]] +name = "syscall_defines" +version = "0.1.0" + +[[package]] +name = "tempfile" +version = "3.0.7" +dependencies = [ + "libc", +] + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] diff --git a/sound_card_init/utils/Cargo.toml b/sound_card_init/amp/Cargo.toml index 8c688de3..62e63db7 100644 --- a/sound_card_init/utils/Cargo.toml +++ b/sound_card_init/amp/Cargo.toml @@ -1,11 +1,16 @@ [package] -name = "utils" +name = "amp" version = "0.1.0" authors = ["The Chromium OS Authors"] edition = "2018" -description = "Utils for sound_card_init" +description = "The boot time calibration logic for smart amp" [dependencies] +cros_alsa = "*" +libcras = "*" +dsm = { path = "../dsm" } remain = "0.2.1" serde = { version = "1.0", features = ["derive"]} serde_yaml = "0.8.11" +sof_sys = "*" +sys_util = "*" diff --git a/sound_card_init/amp/src/lib.rs b/sound_card_init/amp/src/lib.rs new file mode 100644 index 00000000..7114233d --- /dev/null +++ b/sound_card_init/amp/src/lib.rs @@ -0,0 +1,58 @@ +// Copyright 2021 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +//! `amp` crate provides `Amp` trait for amplifier initializations and `AmpBuilder` +//! to create `Amp` objects. +#![deny(missing_docs)] + +mod max98373d; +mod max98390d; +use std::path::PathBuf; + +use dsm::Error; + +use max98373d::Max98373; +use max98390d::Max98390; + +type Result<T> = std::result::Result<T, Error>; +const CONF_DIR: &str = "/etc/sound_card_init"; + +/// It creates `Amp` object based on the sound card name. +pub struct AmpBuilder<'a> { + sound_card_id: &'a str, + config_path: PathBuf, +} + +impl<'a> AmpBuilder<'a> { + /// Creates an `AmpBuilder`. + /// # Arguments + /// + /// * `card_name` - card name. + /// * `conf_file` - config file name. + pub fn new(sound_card_id: &'a str, conf_file: &'a str) -> Self { + let config_path = PathBuf::from(CONF_DIR).join(conf_file); + AmpBuilder { + sound_card_id, + config_path, + } + } + + /// Creates an `Amp` based on the sound card name. + pub fn build(&self) -> Result<Box<dyn Amp>> { + match self.sound_card_id { + "sofcmlmax98390d" => { + Ok(Box::new(Max98390::new(self.sound_card_id, &self.config_path)?) as Box<dyn Amp>) + } + "sofrt5682" => { + Ok(Box::new(Max98373::new(self.sound_card_id, &self.config_path)?) as Box<dyn Amp>) + } + _ => Err(Error::UnsupportedSoundCard(self.sound_card_id.to_owned())), + } + } +} + +/// It defines the required functions of amplifier objects. +pub trait Amp { + /// The amplifier boot time calibration flow. + fn boot_time_calibration(&mut self) -> Result<()>; +} diff --git a/sound_card_init/amp/src/max98373d/dsm_param.rs b/sound_card_init/amp/src/max98373d/dsm_param.rs new file mode 100644 index 00000000..d9257522 --- /dev/null +++ b/sound_card_init/amp/src/max98373d/dsm_param.rs @@ -0,0 +1,211 @@ +// Copyright 2020 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +use std::mem; + +use cros_alsa::{Card, TLV}; +use sof_sys::sof_abi_hdr; + +use dsm::{self, Error, Result}; + +/// Amp volume mode enumeration used by set_volume(). +#[derive(Copy, Clone, PartialEq)] +pub enum VolumeMode { + /// Low mode protects the speaker by limiting its output volume if the + /// calibration has not been completed successfully. + Low = 0x1009B9CF, + /// High mode removes the speaker output volume limitation after + /// having successfully completed the calibration. + High = 0x20000000, +} + +#[derive(Copy, Clone)] +/// Calibration mode enumeration. +pub enum CalibMode { + ON = 0x4, + OFF = 0x1, +} + +#[derive(Copy, Clone)] +/// Smart pilot signal mode mode enumeration. +pub enum SPTMode { + ON = 0x1, + OFF = 0x0, +} + +#[derive(Copy, Clone)] +/// DSM Parem field enumeration. +enum DsmAPI { + ParamCount = 0x0, + CalibMode = 0x1, + MakeupGain = 0x5, + DsmRdc = 0x6, + DsmAmbientTemp = 0x8, + AdaptiveRdc = 0x12, + SPTMode = 0x68, +} + +#[derive(Debug)] +/// It implements functions to access the `DSMParam` fields. +pub struct DSMParam { + param_count: usize, + num_channels: usize, + tlv: TLV, +} + +impl DSMParam { + const DWORD_PER_PARAM: usize = 2; + const VALUE_OFFSET: usize = 1; + const SOF_HEADER_SIZE: usize = mem::size_of::<sof_abi_hdr>() / mem::size_of::<i32>(); + + /// Creates an `DSMParam`. + /// # Arguments + /// + /// * `card` - `&Card`. + /// * `num_channels` - number of channels. + /// * `ctl_name` - the mixer control name to access the DSM param. + /// + /// # Results + /// + /// * `DSMParam` - It is initialized by the content of the given byte control . + /// + /// # Errors + /// + /// * If `Card` creation from sound card name fails. + pub fn new(card: &mut Card, num_channels: usize, ctl_name: &str) -> Result<Self> { + let tlv = card.control_tlv_by_name(ctl_name)?.load()?; + Self::try_from_tlv(tlv, num_channels) + } + + /// Sets DSMParam to the given calibration mode. + pub fn set_calibration_mode(&mut self, mode: CalibMode) { + for channel in 0..self.num_channels { + self.set(channel, DsmAPI::CalibMode, mode as i32); + } + } + + /// Sets DSMParam to the given smart pilot signal mode. + pub fn set_spt_mode(&mut self, mode: SPTMode) { + for channel in 0..self.num_channels { + self.set(channel, DsmAPI::SPTMode, mode as i32); + } + } + + /// Sets DSMParam to the given VolumeMode. + pub fn set_volume_mode(&mut self, mode: VolumeMode) { + for channel in 0..self.num_channels { + self.set(channel, DsmAPI::MakeupGain, mode as i32); + } + } + + /// Reads the calibrated rdc from DSMParam. + pub fn get_adaptive_rdc(&self) -> Vec<i32> { + self.get(DsmAPI::AdaptiveRdc) + } + + /// Sets DSMParam to the given the calibrated rdc. + pub fn set_rdc(&mut self, ch: usize, rdc: i32) { + self.set(ch, DsmAPI::DsmRdc, rdc); + } + + /// Sets DSMParam to the given calibrated temp. + pub fn set_ambient_temp(&mut self, ch: usize, temp: i32) { + self.set(ch, DsmAPI::DsmAmbientTemp, temp); + } + + /// Sets the `id` field to the given `val`. + fn set(&mut self, channel: usize, id: DsmAPI, val: i32) { + let pos = Self::value_pos(self.param_count, channel, id); + self.tlv[pos] = val as u32; + } + + /// Gets the val from the `id` field from all the channels. + fn get(&self, id: DsmAPI) -> Vec<i32> { + (0..self.num_channels) + .map(|channel| { + let pos = Self::value_pos(self.param_count, channel, id); + self.tlv[pos] as i32 + }) + .collect() + } + + fn try_from_tlv(tlv: TLV, num_channels: usize) -> Result<Self> { + let param_count_pos = Self::value_pos(0, 0, DsmAPI::ParamCount); + + if tlv.len() < param_count_pos { + return Err(Error::InvalidDSMParam); + } + + let param_count = tlv[param_count_pos] as usize; + + if tlv.len() != Self::SOF_HEADER_SIZE + param_count * num_channels * Self::DWORD_PER_PARAM { + return Err(Error::InvalidDSMParam); + } + + Ok(Self { + param_count, + num_channels, + tlv, + }) + } + + #[inline] + fn value_pos(param_count: usize, channel: usize, id: DsmAPI) -> usize { + Self::SOF_HEADER_SIZE + + (channel * param_count + id as usize) * Self::DWORD_PER_PARAM + + Self::VALUE_OFFSET + } +} + +impl Into<TLV> for DSMParam { + fn into(self) -> TLV { + self.tlv + } +} + +#[cfg(test)] +mod tests { + use super::*; + const PARAM_COUNT: usize = 138; + const CHANNEL_COUNT: usize = 2; + + #[test] + fn test_dsmparam_try_from_tlv_ok() { + let mut data = vec![ + 0u32; + DSMParam::SOF_HEADER_SIZE + + CHANNEL_COUNT * PARAM_COUNT * DSMParam::DWORD_PER_PARAM + ]; + data[DSMParam::value_pos(PARAM_COUNT, 0, DsmAPI::ParamCount)] = PARAM_COUNT as u32; + data[DSMParam::value_pos(PARAM_COUNT, 1, DsmAPI::ParamCount)] = PARAM_COUNT as u32; + + let tlv = TLV::new(0, data); + assert!(DSMParam::try_from_tlv(tlv, CHANNEL_COUNT).is_ok()); + } + + #[test] + fn test_dsmparam_try_from_invalid_len() { + let data = vec![0u32; DSMParam::SOF_HEADER_SIZE]; + + let tlv = TLV::new(0, data); + assert_eq!( + DSMParam::try_from_tlv(tlv, CHANNEL_COUNT).unwrap_err(), + Error::InvalidDSMParam + ); + } + + #[test] + fn test_dsmparam_try_from_param_count() { + let data = vec![ + 0u32; + DSMParam::SOF_HEADER_SIZE + + CHANNEL_COUNT * PARAM_COUNT * DSMParam::DWORD_PER_PARAM + ]; + + let tlv = TLV::new(0, data); + assert_eq!( + DSMParam::try_from_tlv(tlv, CHANNEL_COUNT).unwrap_err(), + Error::InvalidDSMParam + ); + } +} diff --git a/sound_card_init/amp/src/max98373d/mod.rs b/sound_card_init/amp/src/max98373d/mod.rs new file mode 100644 index 00000000..1ee29ceb --- /dev/null +++ b/sound_card_init/amp/src/max98373d/mod.rs @@ -0,0 +1,274 @@ +// Copyright 2020 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +//! `max98373d` module implements the required initialization workflows for sound +//! cards that use max98373d smart amp. +//! It currently supports boot time calibration for max98373d. +#![deny(missing_docs)] +mod dsm_param; +mod settings; + +use std::path::Path; +use std::time::Duration; +use std::{fs, thread}; + +use cros_alsa::{Card, IntControl}; +use dsm::{CalibData, Error, Result, SpeakerStatus, ZeroPlayer, DSM}; +use sys_util::info; + +use crate::Amp; +use dsm_param::*; +use settings::{AmpCalibSettings, DeviceSettings}; + +/// It implements the amplifier boot time calibration flow. +pub struct Max98373 { + card: Card, + setting: AmpCalibSettings, +} + +impl Amp for Max98373 { + /// Performs max98373d boot time calibration. + /// + /// # Errors + /// + /// If any amplifiers fail to complete the calibration. + fn boot_time_calibration(&mut self) -> Result<()> { + if !Path::new(&self.setting.dsm_param).exists() { + return Err(Error::MissingDSMParam); + } + + let num_channels = self.setting.num_channels(); + let dsm = DSM::new( + &self.card.name(), + num_channels, + Self::rdc_to_ohm, + Self::TEMP_UPPER_LIMIT_CELSIUS, + Self::TEMP_LOWER_LIMIT_CELSIUS, + ); + self.set_volume(VolumeMode::Low)?; + + let calib = if !self.setting.boot_time_calibration_enabled { + info!("skip boot time calibration and use vpd values"); + // Needs Rdc updates to be done after internal speaker is ready otherwise + // it would be overwritten by the DSM blob update. + dsm.wait_for_speakers_ready()?; + dsm.get_all_vpd_calibration_value()? + } else { + match dsm.check_speaker_over_heated_workflow()? { + SpeakerStatus::Hot(previous_calib) => previous_calib, + SpeakerStatus::Cold => { + let all_temp = self.get_ambient_temp()?; + let all_rdc = self.do_rdc_calibration()?; + all_rdc + .iter() + .zip(all_temp) + .enumerate() + .map(|(ch, (&rdc, temp))| { + dsm.decide_calibration_value_workflow(ch, CalibData { rdc, temp }) + }) + .collect::<Result<Vec<_>>>()? + } + } + }; + self.apply_calibration_value(&calib)?; + self.set_volume(VolumeMode::High)?; + Ok(()) + } +} + +impl Max98373 { + const TEMP_CALIB_WARM_UP_TIME: Duration = Duration::from_millis(10); + const RDC_CALIB_WARM_UP_TIME: Duration = Duration::from_millis(500); + const RDC_CALIB_INTERVAL: Duration = Duration::from_millis(200); + const CALIB_REPEAT_TIMES: usize = 5; + + const TEMP_UPPER_LIMIT_CELSIUS: f32 = 40.0; + const TEMP_LOWER_LIMIT_CELSIUS: f32 = 0.0; + + /// Creates an `Max98373`. + /// # Arguments + /// + /// * `card_name` - card_name. + /// * `config_path` - config file path. + /// + /// # Results + /// + /// * `Max98373` - It implements the Max98373 functions of boot time calibration. + /// + /// # Errors + /// + /// * If `Card` creation from sound card name fails. + pub fn new(card_name: &str, config_path: &Path) -> Result<Self> { + let conf = fs::read_to_string(config_path) + .map_err(|e| Error::FileIOFailed(config_path.to_path_buf(), e))?; + let settings = DeviceSettings::from_yaml_str(&conf)?; + Ok(Self { + card: Card::new(card_name)?, + setting: settings.amp_calibrations, + }) + } + + /// Triggers the amplifier calibration and reads the calibrated rdc. + /// To get accurate calibration results, the main thread calibrates the amplifier while + /// the `zero_player` starts another thread to play zeros to the speakers. + fn do_rdc_calibration(&mut self) -> Result<Vec<i32>> { + let mut zero_player: ZeroPlayer = Default::default(); + zero_player.start(Self::RDC_CALIB_WARM_UP_TIME)?; + // Playback of zeros is started for Self::RDC_CALIB_WARM_UP_TIME, and the main thread + // can start the calibration. + self.set_spt_mode(SPTMode::OFF)?; + self.set_calibration_mode(CalibMode::ON)?; + // Playback of zeros is started, and the main thread can start the calibration. + let mut avg_rdc = vec![0; self.setting.num_channels()]; + for _ in 0..Self::CALIB_REPEAT_TIMES { + let rdc = self.get_adaptive_rdc()?; + for i in 0..self.setting.num_channels() { + avg_rdc[i] += rdc[i]; + } + thread::sleep(Self::RDC_CALIB_INTERVAL); + } + self.set_spt_mode(SPTMode::ON)?; + self.set_calibration_mode(CalibMode::OFF)?; + zero_player.stop()?; + + avg_rdc = avg_rdc + .iter() + .map(|val| val / Self::CALIB_REPEAT_TIMES as i32) + .collect(); + Ok(avg_rdc) + } + + /// Sets the card volume control to the given VolumeMode. + fn set_volume(&mut self, mode: VolumeMode) -> Result<()> { + let mut dsm_param = DSMParam::new( + &mut self.card, + self.setting.num_channels(), + &self.setting.dsm_param_read_ctrl, + )?; + + dsm_param.set_volume_mode(mode); + + self.card + .control_tlv_by_name(&self.setting.dsm_param_write_ctrl)? + .save(dsm_param.into()) + .map_err(Error::DSMParamUpdateFailed)?; + Ok(()) + } + + /// Applies the calibration value to the amp. + fn apply_calibration_value(&mut self, calib: &[CalibData]) -> Result<()> { + let mut dsm_param = DSMParam::new( + &mut self.card, + self.setting.num_channels(), + &self.setting.dsm_param_read_ctrl, + )?; + for ch in 0..self.setting.num_channels() { + dsm_param.set_rdc(ch, calib[ch].rdc); + dsm_param.set_ambient_temp(ch, Self::celsius_to_dsm_unit(calib[ch].temp)); + } + self.card + .control_tlv_by_name(&self.setting.dsm_param_write_ctrl)? + .save(dsm_param.into()) + .map_err(Error::DSMParamUpdateFailed)?; + Ok(()) + } + + /// Rdc (ohm) = [ID:0x12] * 3.66 / 2^27 + #[inline] + fn rdc_to_ohm(x: i32) -> f32 { + (3.66 * x as f32) / (1 << 27) as f32 + } + + /// Returns the ambient temperature in celsius degree. + fn get_ambient_temp(&mut self) -> Result<Vec<f32>> { + let mut zero_player: ZeroPlayer = Default::default(); + zero_player.start(Self::TEMP_CALIB_WARM_UP_TIME)?; + let mut temps = Vec::new(); + for x in 0..self.setting.num_channels() as usize { + let temp = self + .card + .control_by_name::<IntControl>(&self.setting.temp_ctrl[x])? + .get()?; + let celsius = Self::measured_temp_to_celsius(temp); + temps.push(celsius); + } + zero_player.stop()?; + + Ok(temps) + } + + /// Converts the measured ambient temperature to celsius unit. + #[inline] + fn measured_temp_to_celsius(temp: i32) -> f32 { + // Measured Temperature (°C) = ([Mixer Val] * 1.28) - 29 + (temp as f32 * 1.28) - 29.0 + } + + /// Converts the ambient temperature from celsius to the DsmSetAPI::DsmAmbientTemp unit. + #[inline] + fn celsius_to_dsm_unit(celsius: f32) -> i32 { + // Temperature (℃) = [ID:0x12] / 2^19 + (celsius * (1 << 19) as f32) as i32 + } + + /// Sets the amp to the given smart pilot signal mode. + fn set_spt_mode(&mut self, mode: SPTMode) -> Result<()> { + let mut dsm_param = DSMParam::new( + &mut self.card, + self.setting.num_channels(), + &self.setting.dsm_param_read_ctrl, + )?; + dsm_param.set_spt_mode(mode); + self.card + .control_tlv_by_name(&self.setting.dsm_param_write_ctrl)? + .save(dsm_param.into()) + .map_err(Error::DSMParamUpdateFailed)?; + Ok(()) + } + + /// Sets the amp to the given the calibration mode. + fn set_calibration_mode(&mut self, mode: CalibMode) -> Result<()> { + let mut dsm_param = DSMParam::new( + &mut self.card, + self.setting.num_channels(), + &self.setting.dsm_param_read_ctrl, + )?; + dsm_param.set_calibration_mode(mode); + self.card + .control_tlv_by_name(&self.setting.dsm_param_write_ctrl)? + .save(dsm_param.into()) + .map_err(Error::DSMParamUpdateFailed)?; + Ok(()) + } + + /// Reads the calibrated rdc. + /// Must be called when the calibration mode in on. + fn get_adaptive_rdc(&mut self) -> Result<Vec<i32>> { + let dsm_param = DSMParam::new( + &mut self.card, + self.setting.num_channels(), + &self.setting.dsm_param_read_ctrl, + )?; + Ok(dsm_param.get_adaptive_rdc()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn celsius_to_dsm_unit() { + assert_eq!(Max98373::celsius_to_dsm_unit(37.0), 0x01280000); + assert_eq!(Max98373::celsius_to_dsm_unit(50.0), 0x01900000); + } + + #[test] + fn rdc_to_ohm() { + assert_eq!(Max98373::rdc_to_ohm(0x05cea0c7), 2.656767); + } + + #[test] + fn measured_temp_to_celsius() { + assert_eq!(Max98373::measured_temp_to_celsius(56), 42.68); + } +} diff --git a/sound_card_init/amp/src/max98373d/settings.rs b/sound_card_init/amp/src/max98373d/settings.rs new file mode 100644 index 00000000..1d6e64e5 --- /dev/null +++ b/sound_card_init/amp/src/max98373d/settings.rs @@ -0,0 +1,41 @@ +// Copyright 2020 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +use std::string::String; + +use dsm::{self, Error, Result}; +use serde::Deserialize; +/// `DeviceSettings` includes the settings of max98373. It currently includes: +/// * the settings of amplifier calibration. +/// * the path of dsm_param. +#[derive(Debug, Default, PartialEq, Deserialize, Clone)] +pub struct DeviceSettings { + pub amp_calibrations: AmpCalibSettings, +} + +/// `AmpCalibSettings` includes the settings needed for amplifier calibration. +#[derive(Debug, Default, PartialEq, Deserialize, Clone)] +pub struct AmpCalibSettings { + pub dsm_param_read_ctrl: String, + pub dsm_param_write_ctrl: String, + pub temp_ctrl: Vec<String>, + // Path of the dsm_param.bin file. + pub dsm_param: String, + pub boot_time_calibration_enabled: bool, +} + +impl AmpCalibSettings { + /// Returns the number of channels. + pub fn num_channels(&self) -> usize { + self.temp_ctrl.len() + } +} + +impl DeviceSettings { + /// Creates a `DeviceSettings` from a yaml str. + pub fn from_yaml_str(conf: &str) -> Result<DeviceSettings> { + let settings: DeviceSettings = serde_yaml::from_str(conf) + .map_err(|e| Error::DeserializationFailed("DeviceSettings".to_owned(), e))?; + Ok(settings) + } +} diff --git a/sound_card_init/amp/src/max98390d/mod.rs b/sound_card_init/amp/src/max98390d/mod.rs new file mode 100644 index 00000000..601165ec --- /dev/null +++ b/sound_card_init/amp/src/max98390d/mod.rs @@ -0,0 +1,213 @@ +// Copyright 2020 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +//! `max98390d` module implements the required initialization workflows for sound +//! cards that use max98390d smart amp. +//! It currently supports boot time calibration for max98390d. +#![deny(missing_docs)] +mod settings; + +use std::time::Duration; +use std::{fs, path::Path}; + +use cros_alsa::{Card, IntControl, SwitchControl}; +use dsm::{CalibData, Error, Result, SpeakerStatus, TempConverter, ZeroPlayer, DSM}; + +use crate::Amp; +use settings::{AmpCalibSettings, DeviceSettings}; + +/// Amp volume mode emulation used by set_volume(). +#[derive(PartialEq, Clone, Copy)] +enum VolumeMode { + /// Low mode protects the speaker by limiting its output volume if the + /// calibration has not been completed successfully. + Low = 138, + /// High mode removes the speaker output volume limitation after + /// having successfully completed the calibration. + High = 148, +} + +/// It implements the Max98390 functions of boot time calibration. +#[derive(Debug)] +pub struct Max98390 { + card: Card, + setting: AmpCalibSettings, +} + +impl Amp for Max98390 { + /// Performs max98390d boot time calibration. + /// + /// # Errors + /// + /// If the amplifier fails to complete the calibration. + fn boot_time_calibration(&mut self) -> Result<()> { + if !Path::new(&self.setting.dsm_param).exists() { + return Err(Error::MissingDSMParam); + } + + let mut dsm = DSM::new( + &self.card.name(), + self.setting.num_channels(), + Self::rdc_to_ohm, + Self::TEMP_UPPER_LIMIT_CELSIUS, + Self::TEMP_LOWER_LIMIT_CELSIUS, + ); + dsm.set_temp_converter(TempConverter::new( + Self::dsm_unit_to_celsius, + Self::celsius_to_dsm_unit, + )); + + self.set_volume(VolumeMode::Low)?; + let calib = match dsm.check_speaker_over_heated_workflow()? { + SpeakerStatus::Hot(previous_calib) => previous_calib, + SpeakerStatus::Cold => self + .do_calibration()? + .iter() + .enumerate() + .map(|(ch, calib_data)| dsm.decide_calibration_value_workflow(ch, *calib_data)) + .collect::<Result<Vec<_>>>()?, + }; + self.apply_calibration_value(calib)?; + self.set_volume(VolumeMode::High)?; + Ok(()) + } +} + +impl Max98390 { + const TEMP_UPPER_LIMIT_CELSIUS: f32 = 40.0; + const TEMP_LOWER_LIMIT_CELSIUS: f32 = 0.0; + const RDC_CALIB_WARM_UP_TIME: Duration = Duration::from_millis(300); + + /// Creates an `Max98390`. + /// # Arguments + /// + /// * `card_name` - card name. + /// * `config_path` - config file path. + /// + /// # Results + /// + /// * `Max98390` - It implements the Max98390 functions of boot time calibration. + /// + /// # Errors + /// + /// * If `Card` creation from sound card name fails. + pub fn new(card_name: &str, config_path: &Path) -> Result<Self> { + let conf = fs::read_to_string(config_path) + .map_err(|e| Error::FileIOFailed(config_path.to_path_buf(), e))?; + let settings = DeviceSettings::from_yaml_str(&conf)?; + Ok(Self { + card: Card::new(card_name)?, + setting: settings.amp_calibrations, + }) + } + + /// Sets the card volume control to given VolumeMode. + fn set_volume(&mut self, mode: VolumeMode) -> Result<()> { + for control in &self.setting.controls { + self.card + .control_by_name::<IntControl>(&control.volume_ctrl)? + .set(mode as i32)?; + } + Ok(()) + } + + /// Applies the calibration value to the amp. + fn apply_calibration_value(&mut self, calib: Vec<CalibData>) -> Result<()> { + for (ch, &CalibData { rdc, temp }) in calib.iter().enumerate() { + self.card + .control_by_name::<IntControl>(&self.setting.controls[ch].rdc_ctrl)? + .set(rdc)?; + self.card + .control_by_name::<IntControl>(&self.setting.controls[ch].temp_ctrl)? + .set(Self::celsius_to_dsm_unit(temp))?; + } + Ok(()) + } + + /// Triggers the amplifier calibration and reads the calibrated rdc and ambient_temp value + /// from the mixer control. + /// To get accurate calibration results, the main thread calibrates the amplifier while + /// the `zero_player` starts another thread to play zeros to the speakers. + fn do_calibration(&mut self) -> Result<Vec<CalibData>> { + let mut zero_player: ZeroPlayer = Default::default(); + zero_player.start(Self::RDC_CALIB_WARM_UP_TIME)?; + // Playback of zeros is started for Self::RDC_CALIB_WARM_UP_TIME, and the main thread + // can start the calibration. + let setting = &self.setting; + let card = &mut self.card; + let calib = setting + .controls + .iter() + .map(|control| { + card.control_by_name::<SwitchControl>(&control.calib_ctrl)? + .on()?; + let rdc = card + .control_by_name::<IntControl>(&control.rdc_ctrl)? + .get()?; + let temp = card + .control_by_name::<IntControl>(&control.temp_ctrl)? + .get()?; + card.control_by_name::<SwitchControl>(&control.calib_ctrl)? + .off()?; + Ok(CalibData { + rdc, + temp: Self::dsm_unit_to_celsius(temp), + }) + }) + .collect::<Result<Vec<CalibData>>>()?; + zero_player.stop()?; + Ok(calib) + } + + /// Converts the ambient temperature from celsius to the DSM unit. + #[inline] + fn celsius_to_dsm_unit(celsius: f32) -> i32 { + (celsius * ((1 << 12) as f32) / 100.0) as i32 + } + + /// Converts the ambient temperature from DSM unit to celsius. + #[inline] + fn dsm_unit_to_celsius(temp: i32) -> f32 { + temp as f32 * 100.0 / (1 << 12) as f32 + } + + /// Converts the calibrated value to real DC resistance in ohm unit. + #[inline] + fn rdc_to_ohm(x: i32) -> f32 { + 3.66 * (1 << 20) as f32 / x as f32 + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn celsius_to_dsm_unit() { + assert_eq!( + Max98390::celsius_to_dsm_unit(Max98390::TEMP_UPPER_LIMIT_CELSIUS), + 1638 + ); + assert_eq!( + Max98390::celsius_to_dsm_unit(Max98390::TEMP_LOWER_LIMIT_CELSIUS), + 0 + ); + } + + #[test] + fn dsm_unit_to_celsius() { + assert_eq!( + Max98390::dsm_unit_to_celsius(1638).round(), + Max98390::TEMP_UPPER_LIMIT_CELSIUS + ); + assert_eq!( + Max98390::dsm_unit_to_celsius(0), + Max98390::TEMP_LOWER_LIMIT_CELSIUS + ); + } + + #[test] + fn rdc_to_ohm() { + assert_eq!(Max98390::rdc_to_ohm(1123160), 3.416956); + assert_eq!(Max98390::rdc_to_ohm(1157049), 3.3168762); + } +} diff --git a/sound_card_init/max98390d/src/settings.rs b/sound_card_init/amp/src/max98390d/settings.rs index 32feefd9..316f25be 100644 --- a/sound_card_init/max98390d/src/settings.rs +++ b/sound_card_init/amp/src/max98390d/settings.rs @@ -3,35 +3,18 @@ // found in the LICENSE file. use std::string::String; +use dsm::{self, Error, Result}; use serde::Deserialize; -use crate::error::{Error, Result}; - /// `DeviceSettings` includes the settings of max98390. It currently includes: /// * the settings of amplifier calibration. /// * the path of dsm_param. #[derive(Debug, Default, PartialEq, Deserialize, Clone)] pub struct DeviceSettings { - pub amp_calibrations: Vec<AmpCalibSettings>, - pub dsm_param: String, + pub amp_calibrations: AmpCalibSettings, } - -/// `AmpCalibSettings` includes the settings needed for amplifier calibration. #[derive(Debug, Default, PartialEq, Deserialize, Clone)] -pub struct AmpCalibSettings { - /// `AmpSettings`. - pub amp: AmpSettings, - /// Vpd file of Rdc. - pub rdc_vpd: String, - /// Vpd file of ambient temperature. - pub temp_vpd: String, - /// File to store the boot time calibration values. - pub calib_file: String, -} - -/// `AmpSettings` represents mixer control names and amp params needed for amplifier calibration. -#[derive(Debug, Default, PartialEq, Deserialize, Clone)] -pub struct AmpSettings { +pub struct AmpCalibCtrl { // Mixer control to get/set rdc value. pub rdc_ctrl: String, // Mixer control to get/set ambient temperature value. @@ -40,14 +23,22 @@ pub struct AmpSettings { pub calib_ctrl: String, // Mixer control to adjust volume. pub volume_ctrl: String, - // The threshold to put volume into normal. - pub volume_high_limit: i32, - // The threshold to put volume into protected mode. - pub volume_low_limit: i32, - // The upper limit of a valid temperature value. - pub temp_upper_limit: i32, - // The lower limit of a valid temperature value. - pub temp_lower_limit: i32, +} + +/// `AmpCalibSettings` includes the settings needed for amplifier calibration. +#[derive(Debug, Default, PartialEq, Deserialize, Clone)] +pub struct AmpCalibSettings { + // Mixer control to get/set rdc value. + pub controls: Vec<AmpCalibCtrl>, + // Path of the dsm_param.bin file. + pub dsm_param: String, +} + +impl AmpCalibSettings { + /// Returns the number of channels. + pub fn num_channels(&self) -> usize { + self.controls.len() + } } impl DeviceSettings { diff --git a/sound_card_init/max98390d/Cargo.lock b/sound_card_init/dsm/Cargo.lock index 411c7527..411c7527 100644 --- a/sound_card_init/max98390d/Cargo.lock +++ b/sound_card_init/dsm/Cargo.lock diff --git a/sound_card_init/max98390d/Cargo.toml b/sound_card_init/dsm/Cargo.toml index f3bbae69..280896d2 100644 --- a/sound_card_init/max98390d/Cargo.toml +++ b/sound_card_init/dsm/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "max98390d" +name = "dsm" version = "0.1.0" authors = ["The Chromium OS Authors"] edition = "2018" @@ -12,5 +12,4 @@ libcras = "*" remain = "0.2.1" serde = { version = "1.0", features = ["derive"]} serde_yaml = "0.8.11" -sys_util = "*" -utils = { path = "../utils" }
\ No newline at end of file +sys_util = "*"
\ No newline at end of file diff --git a/sound_card_init/dsm/src/datastore.rs b/sound_card_init/dsm/src/datastore.rs new file mode 100644 index 00000000..f0180cc2 --- /dev/null +++ b/sound_card_init/dsm/src/datastore.rs @@ -0,0 +1,72 @@ +// Copyright 2020 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +use std::fs::{remove_file, File}; +use std::io::{prelude::*, BufReader, BufWriter}; +use std::path::PathBuf; + +use serde::{Deserialize, Serialize}; +use sys_util::info; + +use crate::error::{Error, Result}; + +/// `Datastore`, which stores and reads calibration values in yaml format. +#[derive(Debug, Deserialize, Serialize, Copy, Clone)] +pub enum Datastore { + /// Indicates using values in VPD. + UseVPD, + DSM { + rdc: i32, + temp: i32, + }, +} + +impl Datastore { + /// The dir of datastore. + pub const DATASTORE_DIR: &'static str = "/var/lib/sound_card_init"; + + /// Creates a `Datastore` and initializes its fields from the datastore file. + pub fn from_file(snd_card: &str, channel: usize) -> Result<Datastore> { + let path = Self::path(snd_card, channel); + let reader = + BufReader::new(File::open(&path).map_err(|e| Error::FileIOFailed(path.to_owned(), e))?); + let datastore: Datastore = + serde_yaml::from_reader(reader).map_err(|e| Error::SerdeError(path.to_owned(), e))?; + Ok(datastore) + } + + /// Saves a `Datastore` to file. + pub fn save(&self, snd_card: &str, channel: usize) -> Result<()> { + let path = Self::path(snd_card, channel); + + let mut writer = BufWriter::new( + File::create(&path).map_err(|e| Error::FileIOFailed(path.to_owned(), e))?, + ); + writer + .write( + serde_yaml::to_string(self) + .map_err(|e| Error::SerdeError(path.to_owned(), e))? + .as_bytes(), + ) + .map_err(|e| Error::FileIOFailed(path.to_owned(), e))?; + writer + .flush() + .map_err(|e| Error::FileIOFailed(path.to_owned(), e))?; + info!("update Datastore {}: {:?}", path.to_string_lossy(), self); + Ok(()) + } + + /// Deletes the datastore file. + pub fn delete(snd_card: &str, channel: usize) -> Result<()> { + let path = Self::path(snd_card, channel); + remove_file(&path).map_err(|e| Error::FileIOFailed(path.to_owned(), e))?; + info!("datastore: {:#?} is deleted.", path); + Ok(()) + } + + fn path(snd_card: &str, channel: usize) -> PathBuf { + PathBuf::from(Self::DATASTORE_DIR) + .join(snd_card) + .join(format!("calib_{}", channel)) + } +} diff --git a/sound_card_init/max98390d/src/error.rs b/sound_card_init/dsm/src/error.rs index 778ff961..4b6e8dc2 100644 --- a/sound_card_init/max98390d/src/error.rs +++ b/sound_card_init/dsm/src/error.rs @@ -1,15 +1,19 @@ // Copyright 2020 The Chromium OS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +use std::any::Any; use std::error; use std::fmt; use std::io; use std::num::ParseIntError; +use std::path::PathBuf; use std::sync::PoisonError; use std::time; use remain::sorted; +use crate::CalibData; + pub type Result<T> = std::result::Result<T, Error>; #[sorted] @@ -17,28 +21,41 @@ pub type Result<T> = std::result::Result<T, Error>; pub enum Error { AlsaCardError(cros_alsa::CardError), AlsaControlError(cros_alsa::ControlError), - CalibrationFailed, + AlsaControlTLVError(cros_alsa::ControlTLVError), CalibrationTimeout, CrasClientFailed(libcras::Error), DeserializationFailed(String, serde_yaml::Error), - FileIOFailed(String, io::Error), - HotSpeaker, + DSMParamUpdateFailed(cros_alsa::ControlTLVError), + FileIOFailed(PathBuf, io::Error), InternalSpeakerNotFound, InvalidDatastore, + InvalidDSMParam, InvalidShutDownTime, - InvalidTemperature(i32), - LargeCalibrationDiff(i32, i32), + InvalidTemperature(f32), + LargeCalibrationDiff(CalibData), MissingDSMParam, MutexPoisonError, NewPlayStreamFailed(libcras::BoxError), NextPlaybackBufferFailed(libcras::BoxError), PlaybackFailed(io::Error), - ReadTimestampFailed(utils::error::Error), - SerializationFailed(serde_yaml::Error), + SerdeError(PathBuf, serde_yaml::Error), StartPlaybackTimeout, SystemTimeError(time::SystemTimeError), + UnsupportedSoundCard(String), VPDParseFailed(String, ParseIntError), - WorkerPanics, + WorkerPanics(Box<dyn Any + Send + 'static>), + ZeroPlayerIsNotRunning, + ZeroPlayerIsRunning, +} + +impl PartialEq for Error { + // Implement eq for more Error when needed. + fn eq(&self, other: &Error) -> bool { + match (self, other) { + (Error::InvalidDSMParam, Error::InvalidDSMParam) => true, + _ => false, + } + } } impl From<cros_alsa::CardError> for Error { @@ -53,6 +70,12 @@ impl From<cros_alsa::ControlError> for Error { } } +impl From<cros_alsa::ControlTLVError> for Error { + fn from(err: cros_alsa::ControlTLVError) -> Error { + Error::AlsaControlTLVError(err) + } +} + impl<T> From<PoisonError<T>> for Error { fn from(_: PoisonError<T>) -> Error { Error::MutexPoisonError @@ -65,13 +88,16 @@ impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use Error::*; match self { - AlsaCardError(e) => write!(f, "{}", e), - AlsaControlError(e) => write!(f, "{}", e), - CalibrationFailed => write!(f, "amp calibration failed"), + AlsaCardError(e) => write!(f, "AlsaCardError: {}", e), + AlsaControlError(e) => write!(f, "AlsaControlError: {}", e), + AlsaControlTLVError(e) => write!(f, "AlsaControlTLVError: {}", e), CalibrationTimeout => write!(f, "calibration is not finished in time"), + DSMParamUpdateFailed(e) => write!(f, "failed to update DsmParam, err: {}", e), CrasClientFailed(e) => write!(f, "failed to create cras client: {}", e), - DeserializationFailed(file, e) => write!(f, "failed to parse {}: {}", file, e), - FileIOFailed(file, e) => write!(f, "{}: {}", file, e), + DeserializationFailed(file_path, e) => { + write!(f, "failed to parse {}: {}", file_path, e) + } + FileIOFailed(file_path, e) => write!(f, "{:#?}: {}", file_path, e), InvalidShutDownTime => write!(f, "invalid shutdown time"), InternalSpeakerNotFound => write!(f, "internal speaker is not found in cras"), InvalidTemperature(temp) => write!( @@ -80,23 +106,23 @@ impl fmt::Display for Error { temp ), InvalidDatastore => write!(f, "invalid datastore format"), - HotSpeaker => write!(f, "skip boot time calibration as the speakers may be hot"), - LargeCalibrationDiff(rdc, temp) => write!( - f, - "calibration difference is too large, rdc: {}, temp: {}", - rdc, temp - ), + InvalidDSMParam => write!(f, "invalid dsm param from kcontrol"), + LargeCalibrationDiff(calib) => { + write!(f, "calibration difference is too large, calib: {:?}", calib) + } MissingDSMParam => write!(f, "missing dsm_param.bin"), MutexPoisonError => write!(f, "mutex is poisoned"), NewPlayStreamFailed(e) => write!(f, "{}", e), NextPlaybackBufferFailed(e) => write!(f, "{}", e), PlaybackFailed(e) => write!(f, "{}", e), - ReadTimestampFailed(e) => write!(f, "{}", e), - SerializationFailed(e) => write!(f, "failed to serialize yaml: {}", e), + SerdeError(file_path, e) => write!(f, "{:?}: {}", file_path, e), StartPlaybackTimeout => write!(f, "playback is not started in time"), SystemTimeError(e) => write!(f, "{}", e), - VPDParseFailed(file, e) => write!(f, "failed to parse vpd {}: {}", file, e), - WorkerPanics => write!(f, "run_play_zero_worker panics"), + UnsupportedSoundCard(name) => write!(f, "unsupported sound card: {}", name), + VPDParseFailed(file_path, e) => write!(f, "failed to parse vpd {}: {}", file_path, e), + WorkerPanics(e) => write!(f, "run_play_zero_worker panics: {:#?}", e), + ZeroPlayerIsNotRunning => write!(f, "zero player is not running"), + ZeroPlayerIsRunning => write!(f, "zero player is running"), } } } diff --git a/sound_card_init/dsm/src/lib.rs b/sound_card_init/dsm/src/lib.rs new file mode 100644 index 00000000..0b3ec64c --- /dev/null +++ b/sound_card_init/dsm/src/lib.rs @@ -0,0 +1,335 @@ +// Copyright 2020 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +//! `dsm` crate implements the required initialization workflows for smart amps. + +mod datastore; +mod error; +pub mod utils; +mod vpd; +mod zero_player; + +use std::{ + thread, + time::{Duration, SystemTime, UNIX_EPOCH}, +}; + +use libcras::{CrasClient, CrasNodeType}; +use sys_util::{error, info}; + +use crate::datastore::Datastore; +pub use crate::error::{Error, Result}; +use crate::utils::{run_time, shutdown_time}; +use crate::vpd::VPD; +pub use crate::zero_player::ZeroPlayer; + +#[derive(Debug, Clone, Copy)] +/// `CalibData` represents the calibration data. +pub struct CalibData { + /// The DC resistance of the speaker is DSM unit. + pub rdc: i32, + /// The ambient temperature in celsius unit at which the rdc is measured. + pub temp: f32, +} + +/// `TempConverter` converts the temperature value between celsius and unit in VPD::dsm_calib_temp. +pub struct TempConverter { + vpd_to_celsius: fn(i32) -> f32, + celsius_to_vpd: fn(f32) -> i32, +} + +impl Default for TempConverter { + fn default() -> Self { + let vpd_to_celsius = |x: i32| x as f32; + let celsius_to_vpd = |x: f32| x.round() as i32; + Self { + vpd_to_celsius, + celsius_to_vpd, + } + } +} + +impl TempConverter { + /// Creates a `TempConverter` + /// + /// # Arguments + /// + /// * `vpd_to_celsius` - function to convert VPD::dsm_calib_temp to celsius unit` + /// * `celsius_to_vpd` - function to convert celsius unit to VPD::dsm_calib_temp` + /// # Results + /// + /// * `TempConverter` - it converts the temperature value between celsius and unit in VPD::dsm_calib_temp. + pub fn new(vpd_to_celsius: fn(i32) -> f32, celsius_to_vpd: fn(f32) -> i32) -> Self { + Self { + vpd_to_celsius, + celsius_to_vpd, + } + } +} + +/// `SpeakerStatus` are the possible return results of +/// DSM::check_speaker_over_heated_workflow. +pub enum SpeakerStatus { + ///`SpeakerStatus::Cold` means the speakers are not overheated and the Amp can + /// trigger the boot time calibration. + Cold, + /// `SpeakerStatus::Hot(Vec<CalibData>)` means the speakers may be too hot for calibration. + /// The boot time calibration should be skipped and the Amp should use the previous + /// calibration values returned by the enum. + Hot(Vec<CalibData>), +} + +/// `DSM`, which implements the required initialization workflows for smart amps. +pub struct DSM { + snd_card: String, + num_channels: usize, + temp_converter: TempConverter, + rdc_to_ohm: fn(i32) -> f32, + temp_upper_limit: f32, + temp_lower_limit: f32, +} + +impl DSM { + const SPEAKER_COOL_DOWN_TIME: Duration = Duration::from_secs(180); + const CALI_ERROR_UPPER_LIMIT: f32 = 0.3; + const CALI_ERROR_LOWER_LIMIT: f32 = 0.03; + + /// Creates a `DSM` + /// + /// # Arguments + /// + /// * `snd_card` - `sound card name`. + /// * `num_channels` - `number of channels`. + /// * `rdc_to_ohm` - `fn(rdc: i32) -> f32 to convert the CalibData::rdc to ohm unit`. + /// * `temp_upper_limit` - the high limit of the valid ambient temperature in dsm unit. + /// * `temp_lower_limit` - the low limit of the valid ambient temperature in dsm unit. + /// + /// # Results + /// + /// * `DSM` - It implements the required initialization workflows for smart amps. + pub fn new( + snd_card: &str, + num_channels: usize, + rdc_to_ohm: fn(i32) -> f32, + temp_upper_limit: f32, + temp_lower_limit: f32, + ) -> Self { + Self { + snd_card: snd_card.to_owned(), + num_channels, + rdc_to_ohm, + temp_converter: TempConverter::default(), + temp_upper_limit, + temp_lower_limit, + } + } + + /// Sets self.temp_converter to the given temp_converter. + /// + /// # Arguments + /// + /// * `temp_converter` - the convert function to use. + pub fn set_temp_converter(&mut self, temp_converter: TempConverter) { + self.temp_converter = temp_converter; + } + + /// Checks whether the speakers are overheated or not according to the previous shutdown time. + /// The boot time calibration should be skipped when the speakers may be too hot + /// and the Amp should use the previous calibration value returned by the + /// SpeakerStatus::Hot(Vec<CalibData>). + /// + /// # Results + /// + /// * `SpeakerStatus::Cold` - which means the speakers are not overheated and the Amp can + /// trigger the boot time calibration. + /// * `SpeakerStatus::Hot(Vec<CalibData>)` - when the speakers may be too hot. The boot + /// time calibration should be skipped and the Amp should use the previous calibration values + /// returned by the enum. + /// + /// # Errors + /// + /// * The speakers are overheated and there are no previous calibration values stored. + /// * Cannot determine whether the speakers are overheated as previous shutdown time record is + /// invalid. + pub fn check_speaker_over_heated_workflow(&self) -> Result<SpeakerStatus> { + if self.is_first_boot() { + return Ok(SpeakerStatus::Cold); + } + match self.is_speaker_over_heated() { + Ok(overheated) => { + if overheated { + let calib: Vec<CalibData> = (0..self.num_channels) + .map(|ch| -> Result<CalibData> { self.get_previous_calibration_value(ch) }) + .collect::<Result<Vec<CalibData>>>()?; + info!("the speakers are hot, the boot time calibration should be skipped"); + return Ok(SpeakerStatus::Hot(calib)); + } + Ok(SpeakerStatus::Cold) + } + Err(err) => { + // We cannot assume the speakers are not replaced or not overheated + // when the shutdown time file is invalid; therefore we can not use the datastore + // value anymore and we can not trigger boot time calibration. + for ch in 0..self.num_channels { + if let Err(e) = Datastore::delete(&self.snd_card, ch) { + error!("error delete datastore: {}", e); + } + } + Err(err) + } + } + } + + /// Decides a good calibration value and updates the stored value according to the following + /// logic: + /// * Returns the previous value if the ambient temperature is not within a valid range. + /// * Returns Error::LargeCalibrationDiff if rdc difference is larger than + /// `CALI_ERROR_UPPER_LIMIT`. + /// * Returns the previous value if the rdc difference is smaller than `CALI_ERROR_LOWER_LIMIT`. + /// * Returns the boot time calibration value and updates the datastore value if the rdc. + /// difference is between `CALI_ERROR_UPPER_LIMIT` and `CALI_ERROR_LOWER_LIMIT`. + /// + /// # Arguments + /// + /// * `card` - `&Card`. + /// * `channel` - `channel number`. + /// * `calib_data` - `boot time calibrated data`. + /// + /// # Results + /// + /// * `CalibData` - the calibration data to be applied according to the deciding logic. + /// + /// # Errors + /// + /// * VPD does not exist. + /// * rdc difference is larger than `CALI_ERROR_UPPER_LIMIT`. + /// * Failed to update Datastore. + pub fn decide_calibration_value_workflow( + &self, + channel: usize, + calib_data: CalibData, + ) -> Result<CalibData> { + if calib_data.temp < self.temp_lower_limit || calib_data.temp > self.temp_upper_limit { + info!("invalid temperature: {}.", calib_data.temp); + return self + .get_previous_calibration_value(channel) + .map_err(|_| Error::InvalidTemperature(calib_data.temp)); + } + let (datastore_exist, previous_calib) = match self.get_previous_calibration_value(channel) { + Ok(previous_calib) => (true, previous_calib), + Err(e) => { + info!("{}, use vpd as previous calibration value", e); + (false, self.get_vpd_calibration_value(channel)?) + } + }; + + let diff = { + let calib_rdc_ohm = (self.rdc_to_ohm)(calib_data.rdc); + let previous_rdc_ohm = (self.rdc_to_ohm)(previous_calib.rdc); + (calib_rdc_ohm - previous_rdc_ohm) / previous_rdc_ohm + }; + if diff > Self::CALI_ERROR_UPPER_LIMIT { + Err(Error::LargeCalibrationDiff(calib_data)) + } else if diff < Self::CALI_ERROR_LOWER_LIMIT { + if !datastore_exist { + Datastore::UseVPD.save(&self.snd_card, channel)?; + } + Ok(previous_calib) + } else { + Datastore::DSM { + rdc: calib_data.rdc, + temp: (self.temp_converter.celsius_to_vpd)(calib_data.temp), + } + .save(&self.snd_card, channel)?; + Ok(calib_data) + } + } + + /// Gets the calibration values from vpd. + /// + /// # Results + /// + /// * `Vec<CalibData>` - the calibration values in vpd. + /// + /// # Errors + /// + /// * Failed to read vpd. + pub fn get_all_vpd_calibration_value(&self) -> Result<Vec<CalibData>> { + (0..self.num_channels) + .map(|ch| self.get_vpd_calibration_value(ch)) + .collect::<Result<Vec<_>>>() + } + + /// Blocks until the internal speakers are ready. + /// + /// # Errors + /// + /// * Failed to wait the internal speakers to be ready. + pub fn wait_for_speakers_ready(&self) -> Result<()> { + let find_speaker = || -> Result<()> { + let cras_client = CrasClient::new().map_err(Error::CrasClientFailed)?; + let _node = cras_client + .output_nodes() + .find(|node| node.node_type == CrasNodeType::CRAS_NODE_TYPE_INTERNAL_SPEAKER) + .ok_or(Error::InternalSpeakerNotFound)?; + Ok(()) + }; + // TODO(b/155007305): Implement cras_client.wait_node_change and use it here. + const RETRY: usize = 3; + const RETRY_INTERVAL: Duration = Duration::from_millis(500); + for _ in 0..RETRY { + match find_speaker() { + Ok(_) => return Ok(()), + Err(e) => error!("retry on finding speaker: {}", e), + }; + thread::sleep(RETRY_INTERVAL); + } + Err(Error::InternalSpeakerNotFound) + } + + fn is_first_boot(&self) -> bool { + !run_time::exists(&self.snd_card) + } + + // If (Current time - the latest CRAS shutdown time) < cool_down_time, we assume that + // the speakers may be overheated. + fn is_speaker_over_heated(&self) -> Result<bool> { + let last_run = run_time::from_file(&self.snd_card)?; + let last_shutdown = shutdown_time::from_file()?; + if last_shutdown < last_run { + return Err(Error::InvalidShutDownTime); + } + + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .map_err(Error::SystemTimeError)?; + + let elapsed = now + .checked_sub(last_shutdown) + .ok_or(Error::InvalidShutDownTime)?; + + if elapsed < Self::SPEAKER_COOL_DOWN_TIME { + return Ok(true); + } + Ok(false) + } + + fn get_previous_calibration_value(&self, ch: usize) -> Result<CalibData> { + let sci_calib = Datastore::from_file(&self.snd_card, ch)?; + match sci_calib { + Datastore::UseVPD => self.get_vpd_calibration_value(ch), + Datastore::DSM { rdc, temp } => Ok(CalibData { + rdc, + temp: (self.temp_converter.vpd_to_celsius)(temp), + }), + } + } + + fn get_vpd_calibration_value(&self, channel: usize) -> Result<CalibData> { + let vpd = VPD::new(channel)?; + Ok(CalibData { + rdc: vpd.dsm_calib_r0, + temp: (self.temp_converter.vpd_to_celsius)(vpd.dsm_calib_temp), + }) + } +} diff --git a/sound_card_init/utils/src/lib.rs b/sound_card_init/dsm/src/utils.rs index e8edce77..64f6c972 100644 --- a/sound_card_init/utils/src/lib.rs +++ b/sound_card_init/dsm/src/utils.rs @@ -4,19 +4,14 @@ //! It contains common utils shared within sound_card_init. #![deny(missing_docs)] -//! The error definitions for utils. -pub mod error; - use std::fs::File; use std::io::{prelude::*, BufReader, BufWriter}; use std::path::PathBuf; use std::time::Duration; +use crate::datastore::Datastore; use crate::error::{Error, Result}; -/// The path of datastore. -pub const DATASTORE_DIR: &str = "/var/lib/sound_card_init"; - fn duration_from_file(path: &PathBuf) -> Result<Duration> { let reader = BufReader::new(File::open(&path).map_err(|e| Error::FileIOFailed(path.clone(), e))?); @@ -80,7 +75,7 @@ pub mod run_time { } fn run_time_file(snd_card: &str) -> PathBuf { - PathBuf::from(DATASTORE_DIR) + PathBuf::from(Datastore::DATASTORE_DIR) .join(snd_card) .join(RUN_TIME_FILE) } diff --git a/sound_card_init/max98390d/src/vpd.rs b/sound_card_init/dsm/src/vpd.rs index f5ac3221..b00864cc 100644 --- a/sound_card_init/max98390d/src/vpd.rs +++ b/sound_card_init/dsm/src/vpd.rs @@ -13,25 +13,26 @@ const VPD_DIR: &str = "/sys/firmware/vpd/ro/vpdfile"; /// `VPD`, which represents the amplifier factory calibration values. #[derive(Default, Debug)] pub struct VPD { - /// dsm_calib_r0 is (11 / 3) / actual_rdc * 2^20. pub dsm_calib_r0: i32, - /// dsm_calib_temp is actual_temp * 2^12 / 100. pub dsm_calib_temp: i32, } impl VPD { - /// Creates a `VPD` and initializes its fields from the given VPD files. - pub fn from_file(rdc_file: &str, temp_file: &str) -> Result<VPD> { + /// Creates a `VPD` and initializes its fields from VPD_DIR/dsm_calib_r0_{channel}. + /// # Arguments + /// + /// * `channel` - channel number. + pub fn new(channel: usize) -> Result<VPD> { let mut vpd: VPD = Default::default(); - vpd.dsm_calib_r0 = read_vpd_files(rdc_file)?; - vpd.dsm_calib_temp = read_vpd_files(temp_file)?; + vpd.dsm_calib_r0 = read_vpd_files(&format!("dsm_calib_r0_{}", channel))?; + vpd.dsm_calib_temp = read_vpd_files(&format!("dsm_calib_temp_{}", channel))?; Ok(vpd) } } fn read_vpd_files(file: &str) -> Result<i32> { let path = PathBuf::from(VPD_DIR).with_file_name(file); - let io_err = |e| Error::FileIOFailed(path.to_string_lossy().to_string(), e); + let io_err = |e| Error::FileIOFailed(path.to_owned(), e); let mut reader = BufReader::new(File::open(&path).map_err(io_err)?); let mut line = String::new(); reader.read_line(&mut line).map_err(io_err)?; diff --git a/sound_card_init/dsm/src/zero_player.rs b/sound_card_init/dsm/src/zero_player.rs new file mode 100644 index 00000000..441f7ffa --- /dev/null +++ b/sound_card_init/dsm/src/zero_player.rs @@ -0,0 +1,209 @@ +// Copyright 2020 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +use std::io::Write; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Condvar, Mutex}; +use std::thread; +use std::thread::JoinHandle; +use std::time::Duration; + +use audio_streams::SampleFormat; +use libcras::{CrasClient, CrasNodeType}; +use sys_util::error; + +use crate::error::{Error, Result}; + +/// `ZeroPlayer` provides the functionality to play zeros sample in the background thread. +#[derive(Default)] +pub struct ZeroPlayer { + thread_info: Option<PlayZeroWorkerInfo>, +} + +impl Drop for ZeroPlayer { + fn drop(&mut self) { + if self.thread_info.is_some() { + if let Err(e) = self.stop() { + error!("{}", e); + } + } + } +} + +impl ZeroPlayer { + /// It takes about 400 ms to get CRAS_NODE_TYPE_INTERNAL_SPEAKER during the boot time. + const TIMEOUT: Duration = Duration::from_millis(1000); + + /// Returns whether the ZeroPlayer is running. + pub fn running(&self) -> bool { + self.thread_info.is_some() + } + + /// Starts to play zeros for at most `max_playback_time`. + /// This function blocks and returns until playback has started for `min_playback_time`. + /// This function must be called when self.running() returns false. + /// + /// # Arguments + /// + /// * `min_playback_time` - It blocks and returns until playback has started for + /// `min_playback_time`. + /// + /// # Errors + /// + /// * If it's called when the `ZeroPlayer` is already running. + /// * Failed to find internal speakers. + /// * Failed to start the background thread. + pub fn start(&mut self, min_playback_time: Duration) -> Result<()> { + if self.running() { + return Err(Error::ZeroPlayerIsRunning); + } + self.thread_info = Some(PlayZeroWorkerInfo::new(min_playback_time)); + if let Some(thread_info) = &mut self.thread_info { + // Block until playback of zeros has started for min_playback_time or timeout. + let (lock, cvar) = &*(thread_info.ready); + let result = cvar.wait_timeout_while( + lock.lock()?, + min_playback_time + ZeroPlayer::TIMEOUT, + |&mut is_ready| !is_ready, + )?; + if result.1.timed_out() { + return Err(Error::StartPlaybackTimeout); + } + } + Ok(()) + } + + /// Stops playing zeros in the background thread. + /// This function must be called when self.running() returns true. + /// + /// # Errors + /// + /// * If it's called again when the `ZeroPlayer` is not running. + /// * Failed to play zeros to internal speakers via CRAS client. + /// * Failed to join the background thread. + pub fn stop(&mut self) -> Result<()> { + match self.thread_info.take() { + Some(mut thread_info) => Ok(thread_info.destroy()?), + None => Err(Error::ZeroPlayerIsNotRunning), + } + } +} + +// Audio thread book-keeping data +struct PlayZeroWorkerInfo { + thread: Option<JoinHandle<Result<()>>>, + // Uses `thread_run` to notify the background thread to stop. + thread_run: Arc<AtomicBool>, + // The background thread uses `ready` to notify the main thread that playback + // of zeros has started for min_playback_time. + ready: Arc<(Mutex<bool>, Condvar)>, +} + +impl Drop for PlayZeroWorkerInfo { + fn drop(&mut self) { + if let Err(e) = self.destroy() { + error!("{}", e); + } + } +} + +impl PlayZeroWorkerInfo { + // Spawns the PlayZeroWorker. + fn new(min_playback_time: Duration) -> Self { + let thread_run = Arc::new(AtomicBool::new(false)); + let ready = Arc::new((Mutex::new(false), Condvar::new())); + let mut worker = PlayZeroWorker::new(min_playback_time, thread_run.clone(), ready.clone()); + Self { + thread: Some(thread::spawn(move || -> Result<()> { + worker.run()?; + Ok(()) + })), + thread_run, + ready, + } + } + + // Joins the PlayZeroWorker. + fn destroy(&mut self) -> Result<()> { + self.thread_run.store(false, Ordering::Relaxed); + if let Some(handle) = self.thread.take() { + let res = handle.join().map_err(Error::WorkerPanics)?; + return match res { + Err(e) => Err(e), + Ok(_) => Ok(()), + }; + } + Ok(()) + } +} + +struct PlayZeroWorker { + min_playback_time: Duration, + // Uses `thread_run` to notify the background thread to stop. + thread_run: Arc<AtomicBool>, + // The background thread uses `ready` to notify the main thread that playback + // of zeros has started for min_playback_time. + ready: Arc<(Mutex<bool>, Condvar)>, +} + +impl PlayZeroWorker { + const FRAMES_PER_BUFFER: usize = 256; + const FRAME_RATE: u32 = 48000; + const NUM_CHANNELS: usize = 2; + const FORMAT: SampleFormat = SampleFormat::S16LE; + + fn new( + min_playback_time: Duration, + thread_run: Arc<AtomicBool>, + ready: Arc<(Mutex<bool>, Condvar)>, + ) -> Self { + Self { + min_playback_time, + thread_run, + ready, + } + } + + fn run(&mut self) -> Result<()> { + let mut cras_client = CrasClient::new().map_err(Error::CrasClientFailed)?; + // TODO(b/155007305): Implement cras_client.wait_node_change and use it here. + let node = cras_client + .output_nodes() + .find(|node| node.node_type == CrasNodeType::CRAS_NODE_TYPE_INTERNAL_SPEAKER) + .ok_or(Error::InternalSpeakerNotFound)?; + let local_buffer = + vec![0u8; Self::FRAMES_PER_BUFFER * Self::NUM_CHANNELS * Self::FORMAT.sample_bytes()]; + let min_playback_iterations = (Self::FRAME_RATE + * self.min_playback_time.as_millis() as u32) + / Self::FRAMES_PER_BUFFER as u32 + / 1000; + let (_control, mut stream) = cras_client + .new_pinned_playback_stream( + node.iodev_index, + Self::NUM_CHANNELS, + Self::FORMAT, + Self::FRAME_RATE, + Self::FRAMES_PER_BUFFER, + ) + .map_err(|e| Error::NewPlayStreamFailed(e))?; + + let mut iter = 0; + self.thread_run.store(true, Ordering::Relaxed); + while self.thread_run.load(Ordering::Relaxed) { + let mut buffer = stream + .next_playback_buffer() + .map_err(|e| Error::NextPlaybackBufferFailed(e))?; + let _write_frames = buffer.write(&local_buffer).map_err(Error::PlaybackFailed)?; + + // Notifies the main thread that playback of zeros has started for min_playback_time. + if iter == min_playback_iterations { + let (lock, cvar) = &*self.ready; + let mut is_ready = lock.lock()?; + *is_ready = true; + cvar.notify_one(); + } + iter += 1; + } + Ok(()) + } +} diff --git a/sound_card_init/max98390d/src/amp_calibration.rs b/sound_card_init/max98390d/src/amp_calibration.rs deleted file mode 100644 index 35340226..00000000 --- a/sound_card_init/max98390d/src/amp_calibration.rs +++ /dev/null @@ -1,319 +0,0 @@ -// Copyright 2020 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. -use std::fmt; -use std::io::Write; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{Arc, Condvar, Mutex}; -use std::thread; -use std::thread::JoinHandle; -use std::time::{Duration, Instant}; - -use audio_streams::SampleFormat; -use cros_alsa::{Card, IntControl, SwitchControl}; -use libcras::{CrasClient, CrasNodeType}; -use sys_util::{error, info}; - -use crate::{ - datastore::Datastore, - error::{Error, Result}, - settings::AmpCalibSettings, - vpd::VPD, -}; - -const CALI_ERROR_UPPER_LIMIT: f32 = 0.3; -const CALI_ERROR_LOWER_LIMIT: f32 = 0.03; - -const FRAMES_PER_BUFFER: usize = 256; -const FRAME_RATE: u32 = 48000; -const NUM_CHANNELS: usize = 2; -const FORMAT: SampleFormat = SampleFormat::S16LE; -const DURATION_MS: u32 = 1000; -const WARM_UP_DURATION_MS: u32 = 300; - -/// Amp volume mode emulation used by set_volume(). -#[derive(PartialEq)] -pub enum VolumeMode { - /// Low mode protects the speaker by limiting its output volume if the - /// calibration has not been completed successfully. - Low, - /// High mode removes the speaker output volume limitation after - /// having successfully completed the calibration. - High, -} - -/// It implements the amplifier boot time calibration flow. -pub struct AmpCalibration<'a> { - card: &'a mut Card, - setting: AmpCalibSettings, -} - -impl<'a> fmt::Debug for AmpCalibration<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("AmpCalibration") - .field("snd_card_id", &self.card.name()) - .field("amp_calib", &self.setting) - .finish() - } -} - -impl<'a> AmpCalibration<'a> { - /// Creates an `AmpCalibration`. - /// # Arguments - /// - /// * `card` - `&Card`. - /// * `setting` - `AmpCalibSettings`. - /// - /// # Results - /// - /// * `AmpCalibration` - It implements the amplifier boot time calibration flow. - /// - /// # Errors - /// - /// * If `Card` creation from sound card name fails. - pub fn new(card: &mut Card, setting: AmpCalibSettings) -> Result<AmpCalibration> { - let amp = AmpCalibration { card, setting }; - - Ok(amp) - } - - /// Sets card volume control to given VolumeMode. - pub fn set_volume(&mut self, mode: VolumeMode) -> Result<()> { - match mode { - VolumeMode::High => self - .card - .control_by_name::<IntControl>(&self.setting.amp.volume_ctrl)? - .set(self.setting.amp.volume_high_limit)?, - VolumeMode::Low => self - .card - .control_by_name::<IntControl>(&self.setting.amp.volume_ctrl)? - .set(self.setting.amp.volume_low_limit)?, - } - Ok(()) - } - - /// The implementation of max98390d boot time calibration logic. - /// - /// The boot time calibration logic includes the following steps: - /// * Gets results from `do_calibration`. - /// * Decides whether the new calibration result should replace the stored value. - /// * Applies a good calibration value. - pub fn run(&mut self) -> Result<()> { - let vpd = VPD::from_file(&self.setting.rdc_vpd, &self.setting.temp_vpd)?; - let (rdc_cali, temp_cali) = self.do_calibration()?; - let datastore = match Datastore::from_file(self.card.name(), &self.setting.calib_file) { - Ok(sci_calib) => Some(sci_calib), - Err(e) => { - info!("failure in Datastore::from_file: {}", e); - None - } - }; - - // Given that rdc_cali is the inverse of hardware real_rdc, the result of `rdc_diff` - // equals to transforming `rdc`s to `real_rdc`s and calculating the relative difference - // from the `real_rdc`s. - let rdc_diff = |x: i32, x_ref: i32| (x - x_ref).abs() as f32 / x as f32; - - let diff: f32 = match datastore { - None => rdc_diff(rdc_cali, vpd.dsm_calib_r0), - Some(d) => match d { - Datastore::UseVPD => rdc_diff(rdc_cali, vpd.dsm_calib_r0), - Datastore::DSM { rdc, .. } => rdc_diff(rdc_cali, rdc), - }, - }; - - if !self.validate_temperature(temp_cali) { - info!("invalid temperature: {}.", temp_cali); - return match datastore { - None => Err(Error::InvalidTemperature(temp_cali)), - Some(d) => self.apply_datastore(d), - }; - } - - if diff > CALI_ERROR_UPPER_LIMIT { - return Err(Error::LargeCalibrationDiff(rdc_cali, temp_cali)); - } else if diff < CALI_ERROR_LOWER_LIMIT { - match datastore { - None => Datastore::UseVPD.save(self.card.name(), &self.setting.calib_file)?, - Some(d) => self.apply_datastore(d)?, - } - } else { - info!("apply boot time calibration values."); - self.card - .control_by_name::<IntControl>(&self.setting.amp.rdc_ctrl)? - .set(rdc_cali)?; - self.card - .control_by_name::<IntControl>(&self.setting.amp.temp_ctrl)? - .set(temp_cali)?; - Datastore::DSM { - rdc: rdc_cali, - ambient_temp: temp_cali, - } - .save(self.card.name(), &self.setting.calib_file)?; - } - Ok(()) - } - - fn apply_datastore(&mut self, d: Datastore) -> Result<()> { - info!("apply datastore values."); - match d { - Datastore::UseVPD => Ok(()), - Datastore::DSM { rdc, ambient_temp } => { - self.card - .control_by_name::<IntControl>(&self.setting.amp.rdc_ctrl)? - .set(rdc)?; - self.card - .control_by_name::<IntControl>(&self.setting.amp.temp_ctrl)? - .set(ambient_temp)?; - Ok(()) - } - } - } - - fn validate_temperature(&self, temp: i32) -> bool { - temp < self.setting.amp.temp_upper_limit && temp > self.setting.amp.temp_lower_limit - } - - /// Triggers the amplifier calibration and reads the calibrated rdc and ambient_temp value - /// from the mixer control. - /// To get accurate calibration results, the main thread calibrates the amplifier while - /// the another thread plays zeros to the speakers. - fn do_calibration(&mut self) -> Result<(i32, i32)> { - // The playback worker uses `playback_started` to notify the main thread that playback - // of zeros has started. - let playback_started = Arc::new((Mutex::new(false), Condvar::new())); - // Shares `calib_finished` to the playback worker and uses it to notify the worker when - // the calibration is finished. - let calib_finished = Arc::new(AtomicBool::new(false)); - let handle = - AmpCalibration::run_play_zero_worker(playback_started.clone(), calib_finished.clone())?; - - // Waits until zero playback starts or timeout. - let mut timeout = Duration::from_millis(1000); - let (lock, cvar) = &*playback_started; - let mut started = lock.lock()?; - while !*started { - let start_time = Instant::now(); - started = cvar.wait_timeout(started, timeout)?.0; - if *started { - break; - } else { - let elapsed = start_time.elapsed(); - if elapsed > timeout { - return Err(Error::StartPlaybackTimeout); - } else { - // Spurious wakes. Decrements the sleep duration by the amount slept. - timeout -= start_time.elapsed(); - } - } - } - - // Playback of zeros is started, and the main thread can start the calibration. - self.card - .control_by_name::<SwitchControl>(&self.setting.amp.calib_ctrl)? - .on()?; - let rdc = self - .card - .control_by_name::<IntControl>(&self.setting.amp.rdc_ctrl)? - .get()?; - let temp = self - .card - .control_by_name::<IntControl>(&self.setting.amp.temp_ctrl)? - .get()?; - self.card - .control_by_name::<SwitchControl>(&self.setting.amp.calib_ctrl)? - .off()?; - // Notifies the play_zero_worker that the calibration is finished. - calib_finished.store(true, Ordering::Relaxed); - - // If play_zero_worker has error during the calibration, returns an error to keep the volume - // low to protect the speaker. - match handle.join() { - Ok(res) => { - if let Err(e) = res { - error!("run_play_zero_worker has error: {}", e); - return Err(e); - } - } - Err(e) => { - error!("run_play_zero_worker panics: {:?}", e); - return Err(Error::WorkerPanics); - } - } - - Ok((rdc, temp)) - } - - // Creates a thread to play zeros to the internal speakers. - fn run_play_zero_worker( - playback_started: Arc<(Mutex<bool>, Condvar)>, - calib_finished: Arc<AtomicBool>, - ) -> Result<JoinHandle<Result<()>>> { - let mut cras_client = CrasClient::new().map_err(Error::CrasClientFailed)?; - // TODO(b/155007305): Implement cras_client.wait_node_change and use it here. - let node = cras_client - .output_nodes() - .find(|node| node.node_type == CrasNodeType::CRAS_NODE_TYPE_INTERNAL_SPEAKER) - .ok_or(Error::InternalSpeakerNotFound)?; - - let handle = thread::spawn(move || -> Result<()> { - let local_buffer = [0u8; FRAMES_PER_BUFFER * NUM_CHANNELS * 2]; - let iterations = (FRAME_RATE * DURATION_MS) / FRAMES_PER_BUFFER as u32 / 1000; - let warm_up_iterations = - (FRAME_RATE * WARM_UP_DURATION_MS) / FRAMES_PER_BUFFER as u32 / 1000; - - let (_control, mut stream) = cras_client - .new_pinned_playback_stream( - node.iodev_index, - NUM_CHANNELS, - FORMAT, - FRAME_RATE, - FRAMES_PER_BUFFER, - ) - .map_err(|e| Error::NewPlayStreamFailed(e))?; - - // Plays zeros for at most DURATION_MS. - for i in 0..iterations { - if calib_finished.load(Ordering::Relaxed) { - break; - } - let mut buffer = stream - .next_playback_buffer() - .map_err(|e| Error::NextPlaybackBufferFailed(e))?; - let _write_frames = buffer.write(&local_buffer).map_err(Error::PlaybackFailed)?; - - // Notifies the main thread to start the calibration. - // The mute playing time need to be longer than WARM_UP_DURATION_MS to get rdc properly. - if i == warm_up_iterations { - let (lock, cvar) = &*playback_started; - let mut started = lock.lock()?; - *started = true; - cvar.notify_one(); - } - // The playback_started lock is unlocked here when `started` goes out of scope. - } - - // Returns an error if the calibration is not finished before playback stops. - if !calib_finished.load(Ordering::Relaxed) { - return Err(Error::CalibrationTimeout); - } - Ok(()) - }); - - Ok(handle) - } - - /// Skips max98390d boot time calibration when the speaker may be hot. - /// - /// If datastore exists, applies the stored value and sets volume to high. - /// If datastore does not exist, sets volume to low. - pub fn hot_speaker_workflow(&mut self) -> Result<()> { - if let Ok(sci_calib) = Datastore::from_file(self.card.name(), &self.setting.calib_file) { - self.apply_datastore(sci_calib)?; - self.set_volume(VolumeMode::High)?; - return Ok(()); - } - info!("no datastore, set volume low"); - self.set_volume(VolumeMode::Low) - } -} diff --git a/sound_card_init/max98390d/src/datastore.rs b/sound_card_init/max98390d/src/datastore.rs deleted file mode 100644 index 12ebff73..00000000 --- a/sound_card_init/max98390d/src/datastore.rs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2020 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. -use std::fs::File; -use std::io::{prelude::*, BufReader, BufWriter}; -use std::path::PathBuf; - -use serde::{Deserialize, Serialize}; -use sys_util::info; -use utils::DATASTORE_DIR; - -use crate::error::{Error, Result}; - -/// `Datastore`, which stores and reads calibration values in yaml format. -#[derive(Debug, Deserialize, Serialize, Copy, Clone)] -pub enum Datastore { - /// Indicates using values in VPD. - UseVPD, - /// rdc is (11 / 3) / actual_rdc * 2^20. - /// ambient_temp is actual_temp * 2^12 / 100. - DSM { rdc: i32, ambient_temp: i32 }, -} - -impl Datastore { - /// Creates a `Datastore` and initializes its fields from DATASTORE_DIR/<snd_card>/{file}. - pub fn from_file(snd_card: &str, file: &str) -> Result<Datastore> { - let path = PathBuf::from(DATASTORE_DIR).join(snd_card).join(file); - - let io_err = |e| Error::FileIOFailed(path.to_string_lossy().to_string(), e); - let parse_err = |e: serde_yaml::Error| { - Error::DeserializationFailed(path.to_string_lossy().to_string(), e) - }; - - let reader = BufReader::new(File::open(&path).map_err(io_err)?); - let datastore: Datastore = serde_yaml::from_reader(reader).map_err(parse_err)?; - Ok(datastore) - } - - /// Saves a `Datastore` to DATASTORE_DIR/<snd_card>/{file}. - pub fn save(&self, snd_card: &str, file: &str) -> Result<()> { - let path = PathBuf::from(DATASTORE_DIR).join(snd_card).join(file); - let io_err = |e| Error::FileIOFailed(path.to_string_lossy().to_string(), e); - - let mut writer = BufWriter::new(File::create(&path).map_err(io_err)?); - writer - .write( - serde_yaml::to_string(self) - .map_err(Error::SerializationFailed)? - .as_bytes(), - ) - .map_err(io_err)?; - writer.flush().map_err(io_err)?; - info!("update Datastore {}: {:?}", path.to_string_lossy(), self); - Ok(()) - } -} diff --git a/sound_card_init/max98390d/src/lib.rs b/sound_card_init/max98390d/src/lib.rs deleted file mode 100644 index 44b1c611..00000000 --- a/sound_card_init/max98390d/src/lib.rs +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright 2020 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. -//! `max98390d` crate implements the required initialization workflows. -//! It currently supports boot time calibration for max98390d. -#![deny(missing_docs)] -mod amp_calibration; -mod datastore; -mod error; -mod settings; -mod vpd; - -use std::fs; -use std::path::{Path, PathBuf}; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; - -use cros_alsa::Card; -use sys_util::error; -use utils::{run_time, shutdown_time, DATASTORE_DIR}; - -use crate::amp_calibration::{AmpCalibration, VolumeMode}; -use crate::error::{Error, Result}; -use crate::settings::DeviceSettings; - -const SPEAKER_COOL_DOWN_TIME: Duration = Duration::from_secs(180); - -/// Performs max98390d boot time calibration. -/// -/// -/// # Arguments -/// -/// * `snd_card` - The sound card name, ex: sofcmlmax98390d. -/// * `conf` - The `DeviceSettings` in yaml format. -/// -/// # Errors -/// -/// If any amplifiers fail to complete the calibration. -pub fn run_max98390d(snd_card: &str, conf: &str) -> Result<()> { - let settings = DeviceSettings::from_yaml_str(conf)?; - let mut card = Card::new(snd_card)?; - - if !Path::new(&settings.dsm_param).exists() { - set_all_volume_low(&mut card, &settings); - return Err(Error::MissingDSMParam); - } - - // Needs to check whether the speakers are over heated if it is not the first time boot. - if run_time::exists(snd_card) { - if let Err(err) = check_speaker_over_heated(snd_card, SPEAKER_COOL_DOWN_TIME) { - match err { - Error::HotSpeaker => run_all_hot_speaker_workflow(&mut card, &settings), - _ => { - // We cannot assume the speakers are not replaced or not over heated - // when the shutdown time file is invalid; therefore we can not use the datastore - // value anymore and we can not trigger boot time calibration. - del_all_datastore(snd_card, &settings); - set_all_volume_low(&mut card, &settings); - } - }; - return Err(err); - }; - } - - // If some error occurs during the calibration, the iteration will continue running the - // calibration for the next amp. - let results: Vec<Result<()>> = settings - .amp_calibrations - .into_iter() - .map(|s| { - let mut amp_calib = AmpCalibration::new(&mut card, s)?; - amp_calib.set_volume(VolumeMode::Low)?; - amp_calib.run()?; - amp_calib.set_volume(VolumeMode::High)?; - Ok(()) - }) - .filter_map(|res| res.err()) - .map(|e| { - error!("calibration error: {}. volume remains low.", e); - Err(e) - }) - .collect(); - - if !results.is_empty() { - return Err(Error::CalibrationFailed); - } - - Ok(()) -} - -fn del_all_datastore(snd_card: &str, settings: &DeviceSettings) { - for s in &settings.amp_calibrations { - if let Err(e) = fs::remove_file( - PathBuf::from(DATASTORE_DIR) - .join(snd_card) - .join(&s.calib_file), - ) { - error!("failed to remove datastore: {}.", e); - } - } -} - -fn run_all_hot_speaker_workflow(card: &mut Card, settings: &DeviceSettings) { - for s in &settings.amp_calibrations { - let mut amp_calib = match AmpCalibration::new(card, s.clone()) { - Ok(amp) => amp, - Err(e) => { - error!("{}.", e); - continue; - } - }; - if let Err(e) = amp_calib.hot_speaker_workflow() { - error!("failed to run hot_speaker_workflow: {}.", e); - } - } -} - -fn set_all_volume_low(card: &mut Card, settings: &DeviceSettings) { - for s in &settings.amp_calibrations { - let mut amp_calib = match AmpCalibration::new(card, s.clone()) { - Ok(amp) => amp, - Err(e) => { - error!("{}.", e); - continue; - } - }; - if let Err(e) = amp_calib.set_volume(VolumeMode::Low) { - error!("failed to set volume to low: {}.", e); - } - } -} - -// If (Current time - the latest CRAS shutdown time) < cool_down_time, we assume that -// the speakers may be over heated. -fn check_speaker_over_heated(snd_card: &str, cool_down_time: Duration) -> Result<()> { - let last_run = run_time::from_file(snd_card).map_err(Error::ReadTimestampFailed)?; - let last_shutdown = shutdown_time::from_file().map_err(Error::ReadTimestampFailed)?; - if last_shutdown < last_run { - return Err(Error::InvalidShutDownTime); - } - - let now = SystemTime::now() - .duration_since(UNIX_EPOCH) - .map_err(Error::SystemTimeError)?; - - let elapsed = now - .checked_sub(last_shutdown) - .ok_or(Error::InvalidShutDownTime)?; - - if elapsed < cool_down_time { - return Err(Error::HotSpeaker); - } - Ok(()) -} diff --git a/sound_card_init/seccomp/sound_card_init-seccomp-amd64.policy b/sound_card_init/seccomp/sound_card_init-seccomp-amd64.policy index b1bcd6b8..d06f225e 100644 --- a/sound_card_init/seccomp/sound_card_init-seccomp-amd64.policy +++ b/sound_card_init/seccomp/sound_card_init-seccomp-amd64.policy @@ -2,78 +2,81 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -lseek: 1 -read: 1 +access: 1 +arch_prctl: 1 +bind: 1 +brk: 1 +clone: 1 close: 1 -openat: 1 -mmap: arg2 in ~PROT_EXEC || arg2 in ~PROT_WRITE -fstat: 1 -rt_sigaction: 1 -prlimit64: 1 -mprotect: arg2 in ~PROT_EXEC || arg2 in ~PROT_WRITE -ioctl: arg1 == 0x5401 || arg1 == 0xc4c85512 || arg1 == 0x540f || arg1 == 0x80045500 || arg1 == 0xc4c85513 || arg1 == 0x81785501 || arg1 == 0x5413 || arg1 == 0xc1105511 -stat: 1 -munmap: 1 -write: 1 -setresuid: 1 -recvmsg: 1 -fcntl: 1 -futex: 1 +connect: 1 +dup2: 1 +dup: 1 epoll_create1: 1 epoll_ctl: 1 epoll_wait: 1 -setresgid: 1 -sigaltstack: 1 -rt_sigprocmask: 1 -access: 1 -getuid: 1 -brk: 1 -getpid: 1 -socket: arg0 == 0x10 || arg0 == 0x1 +execve: 1 +exit: 1 +exit_group: 1 +fcntl: 1 +fstat: 1 +futex: 1 +getcwd: 1 getdents: 1 -recvfrom: 1 -set_robust_list: 1 -umask: 1 -unlink: 1 -sendto: 1 -setgroups: 1 -connect: 1 +getdents64: 1 +getegid: 1 geteuid: 1 -prctl: arg0 == 0x3 || arg0 == 0x4 -clone: 1 -dup: 1 -sched_getaffinity: 1 -execve: 1 -arch_prctl: 1 -set_tid_address: 1 +getgid: 1 getgroups: 1 -getresuid: 1 +getpgid: 1 +getpgrp: 1 +getpid: 1 +getppid: 1 +getpriority: 1 +getrandom: 1 getresgid: 1 +getresuid: 1 +getsid: 1 +getsockname: 1 +getuid: 1 +ioctl: arg1 == 0x5401 || arg1 == 0xc4c85512 || arg1 == 0x540f || arg1 == 0x80045500 || arg1 == 0xc4c85513 || arg1 == 0x81785501 || arg1 == 0x5413 || arg1 == 0xc1105511 || arg1 == 0x81785501 || arg1 == 0x80045500 || arg1 == 0xc008551a || arg1 == 0xc4c85512 || arg1 == 0xc008551b || arg1 == 0xc1105511 +lseek: 1 +madvise: 1 +mmap: arg2 in ~PROT_EXEC || arg2 in ~PROT_WRITE +mprotect: arg2 in ~PROT_EXEC || arg2 in ~PROT_WRITE +munmap: 1 +nanosleep: 1 +clock_nanosleep: 1 +openat: 1 pipe2: 1 ppoll: 1 -statx: 1 -socketpair: 1 +prctl: arg0 == 0x3 || arg0 == 0x4 +prlimit64: 1 +read: 1 +recvfrom: 1 +recvmsg: 1 +restart_syscall: 1 +rt_sigaction: 1 +rt_sigprocmask: 1 +rt_sigreturn: 1 +sched_getaffinity: 1 +sched_yield: 1 sendmsg: 1 -madvise: 1 -exit: 1 -exit_group: 1 -getppid: 1 -getpgid: 1 -getsid: 1 -getgid: 1 -getegid: 1 -getcwd: 1 -uname: 1 -bind: 1 -getsockname: 1 -setuid: 1 +sendto: 1 +set_robust_list: 1 +set_tid_address: 1 setgid: 1 -getpriority: 1 +setgroups: 1 setpriority: 1 -getpgrp: 1 -dup2: 1 -getrandom: 1 -rt_sigreturn: 1 +setresgid: 1 +setresuid: 1 +setuid: 1 +sigaltstack: 1 +socket: arg0 == 0x10 || arg0 == 0x1 +socketpair: 1 +stat: 1 +statx: 1 +umask: 1 +uname: 1 +unlink: 1 wait4: 1 -restart_syscall: 1 -sched_yield: 1
\ No newline at end of file +write: 1 diff --git a/sound_card_init/sound_card_init.conf b/sound_card_init/sound_card_init.conf index 7ab0211e..40bc88f8 100644 --- a/sound_card_init/sound_card_init.conf +++ b/sound_card_init/sound_card_init.conf @@ -31,44 +31,50 @@ pre-start script fi end script -# Here (in order) are a list of the args added: -# --uts: Create and enter new UTS namespace (hostname/NIS domain name). -# -e: doesn't need network access. -# -l: process doesn't use SysV shared memory or IPC. -# -N: doesn't need to modify control groups settings. -# -v: run inside a new VFS namespace. -# -p -r: process doesn't need to access other processes in the system. -# -n: process doesn't need new privileges. -# -P: set /mnt/empty as the root fs. -# -b: bind / -# -k: Get a writeable and empty /run tmpfs path. -# -b: need /run/cras to connect cras. -# -b: /run/systemd/journal: needed for syslog. -# -b: need /dev to send ioctls to the system's block devices. -# -k: empty /sys tmpfs path. -# -b: need /sys/firmware/vpd/ro/ access to read the default calibration value in vpd. -# -k: get a writeable and empty /var tmpfs path. -# -b: need /var/lib/sound_card_init/$SOUND_CARD_ID writable access for datastore update. -# -b: need /var/lib/cras readable -exec minijail0 \ - --uts \ - -e \ - -l \ - -N \ - -v \ - -p -r \ - -n \ - -P /mnt/empty \ - -b / \ - -k 'tmpfs,/run,tmpfs,MS_NODEV|MS_NOEXEC|MS_NOSUID,mode=755,size=10M' \ - -b /run/cras \ - -b /run/systemd/journal \ - -b /dev \ - -k 'tmpfs,/sys,tmpfs,MS_NODEV|MS_NOEXEC|MS_NOSUID,mode=755,size=10M' \ - -b /sys/firmware/vpd/ro/ \ - -k 'tmpfs,/var,tmpfs,MS_NODEV|MS_NOEXEC|MS_NOSUID,mode=755,size=10M' \ - -b /var/lib/sound_card_init/"${SOUND_CARD_ID}"/,,1 \ - -b /var/lib/cras/ \ - -u sound_card_init -g sound_card_init -G \ - -S /usr/share/policy/sound_card_init-seccomp.policy \ - /usr/bin/sound_card_init "--id=${SOUND_CARD_ID}" + +script + CONFIG="$(cros_config /audio/main sound-card-init-conf)" + if [ -f /etc/sound_card_init/"${CONFIG}" ]; then + # Here (in order) are a list of the args added: + # --uts: Create and enter new UTS namespace (hostname/NIS domain name). + # -e: doesn't need network access. + # -l: process doesn't use SysV shared memory or IPC. + # -N: doesn't need to modify control groups settings. + # -v: run inside a new VFS namespace. + # -p -r: process doesn't need to access other processes in the system. + # -n: process doesn't need new privileges. + # -P: set /mnt/empty as the root fs. + # -b: bind / + # -k: Get a writeable and empty /run tmpfs path. + # -b: need /run/cras to connect cras. + # -b: need /dev to send ioctls to the system's block devices. + # -k: empty /sys tmpfs path. + # -b: need /sys/firmware/vpd/ro/ access to read the default calibration + # value in vpd. + # -k: get a writeable and empty /var tmpfs path. + # -b: need /var/lib/sound_card_init/$SOUND_CARD_ID writable access for + # datastore update. + # -b: need /var/lib/cras readable + exec minijail0 \ + --uts \ + -e \ + -l \ + -N \ + -v \ + -p -r \ + -n \ + -P /mnt/empty \ + -b / \ + -k 'tmpfs,/run,tmpfs,MS_NODEV|MS_NOEXEC|MS_NOSUID,mode=755,size=10M' \ + -b /run/cras \ + -b /dev \ + -k 'tmpfs,/sys,tmpfs,MS_NODEV|MS_NOEXEC|MS_NOSUID,mode=755,size=10M' \ + -b /sys/firmware/vpd/ro/ \ + -k 'tmpfs,/var,tmpfs,MS_NODEV|MS_NOEXEC|MS_NOSUID,mode=755,size=10M' \ + -b /var/lib/sound_card_init/"${SOUND_CARD_ID}"/,,1 \ + -b /var/lib/cras/ \ + -u sound_card_init -g sound_card_init -G \ + -S /usr/share/policy/sound_card_init-seccomp.policy \ + /usr/bin/sound_card_init "--id=${SOUND_CARD_ID}" "--conf=${CONFIG}" + fi +end script
\ No newline at end of file diff --git a/sound_card_init/src/main.rs b/sound_card_init/src/main.rs index 3f49ed97..806b7d53 100644 --- a/sound_card_init/src/main.rs +++ b/sound_card_init/src/main.rs @@ -14,9 +14,6 @@ use std::env; use std::error; use std::fmt; -use std::fs; -use std::io; -use std::path::PathBuf; use std::process; use std::string::String; @@ -24,24 +21,22 @@ use getopts::Options; use remain::sorted; use sys_util::{error, info, syslog}; -use max98390d::run_max98390d; -use utils::run_time; +use amp::AmpBuilder; +use dsm::utils::run_time; type Result<T> = std::result::Result<T, Error>; -const CONF_DIR: &str = "/etc/sound_card_init"; #[derive(Default)] struct Args { pub sound_card_id: String, + pub conf: String, } #[sorted] #[derive(Debug)] enum Error { MissingOption(String), - OpenConfigFailed(String, io::Error), ParseArgsFailed(getopts::Fail), - UnsupportedSoundCard(String), } impl error::Error for Error {} @@ -51,9 +46,7 @@ impl fmt::Display for Error { use Error::*; match self { MissingOption(option) => write!(f, "missing required option: {}", option), - OpenConfigFailed(file, e) => write!(f, "failed to open file {}: {}", file, e), ParseArgsFailed(e) => write!(f, "parse_args failed: {}", e), - UnsupportedSoundCard(name) => write!(f, "unsupported sound card: {}", name), } } } @@ -66,6 +59,12 @@ fn print_usage(opts: &Options) { fn parse_args() -> Result<Args> { let mut opts = Options::new(); opts.optopt("", "id", "sound card id", "ID"); + opts.optopt( + "", + "conf", + "the config file name. It should be $(cros_config /audio/main sound-card-init-conf)", + "CONFIG_NAME", + ); opts.optflag("h", "help", "print help menu"); let matches = opts .parse(&env::args().collect::<Vec<_>>()[1..]) @@ -87,31 +86,28 @@ fn parse_args() -> Result<Args> { e })?; - Ok(Args { sound_card_id }) -} - -fn get_config(args: &Args) -> Result<String> { - let config_path = PathBuf::from(CONF_DIR) - .join(&args.sound_card_id) - .with_extension("yaml"); + let conf = matches + .opt_str("conf") + .ok_or_else(|| Error::MissingOption("conf".to_owned())) + .map_err(|e| { + print_usage(&opts); + e + })?; - fs::read_to_string(&config_path) - .map_err(|e| Error::OpenConfigFailed(config_path.to_string_lossy().to_string(), e)) + Ok(Args { + sound_card_id, + conf, + }) } -/// Parses the CONF_DIR/<sound_card_id>.yaml and starts sound card initialization. +/// Parses the CONF_DIR/${args.conf}.yaml and starts the boot time calibration. fn sound_card_init(args: &Args) -> std::result::Result<(), Box<dyn error::Error>> { - info!("sound_card_id: {}", args.sound_card_id); - let conf = get_config(args)?; - - match args.sound_card_id.as_str() { - "sofcmlmax98390d" => { - run_max98390d(&args.sound_card_id, &conf)?; - info!("run_max98390d() finished successfully."); - Ok(()) - } - _ => Err(Error::UnsupportedSoundCard(args.sound_card_id.clone()).into()), - } + info!("sound_card_id: {}, conf:{}", args.sound_card_id, args.conf); + AmpBuilder::new(&args.sound_card_id, &args.conf) + .build()? + .boot_time_calibration()?; + + Ok(()) } fn main() { @@ -124,8 +120,9 @@ fn main() { } }; - if let Err(e) = sound_card_init(&args) { - error!("sound_card_init: {}", e); + match sound_card_init(&args) { + Ok(_) => info!("sound_card_init finished successfully."), + Err(e) => error!("sound_card_init: {}", e), } if let Err(e) = run_time::now_to_file(&args.sound_card_id) { diff --git a/sound_card_init/utils/Cargo.lock b/sound_card_init/utils/Cargo.lock deleted file mode 100644 index fd53fb0f..00000000 --- a/sound_card_init/utils/Cargo.lock +++ /dev/null @@ -1,109 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -[[package]] -name = "dtoa" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134951f4028bdadb9b84baf4232681efbf277da25144b9b0ad65df75946c422b" - -[[package]] -name = "linked-hash-map" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" - -[[package]] -name = "proc-macro2" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "quote" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "remain" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ba1e78fa68412cb93ef642fd4d20b9a941be49ee9333875ebaf13112673ea7" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde" -version = "1.0.116" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96fe57af81d28386a513cbc6858332abc6117cfdb5999647c6444b8f43a370a5" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.116" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f630a6370fd8e457873b4bd2ffdae75408bc291ba72be773772a4c2a065d9ae8" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_yaml" -version = "0.8.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae3e2dd40a7cdc18ca80db804b7f461a39bb721160a85c9a1fa30134bf3c02a5" -dependencies = [ - "dtoa", - "linked-hash-map", - "serde", - "yaml-rust", -] - -[[package]] -name = "syn" -version = "1.0.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e03e57e4fcbfe7749842d53e24ccb9aa12b7252dbe5e91d2acad31834c8b8fdd" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "unicode-xid" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" - -[[package]] -name = "utils" -version = "0.1.0" -dependencies = [ - "remain", - "serde", - "serde_yaml", -] - -[[package]] -name = "yaml-rust" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39f0c922f1a334134dc2f7a8b67dc5d25f0735263feec974345ff706bcf20b0d" -dependencies = [ - "linked-hash-map", -] diff --git a/sound_card_init/utils/src/error.rs b/sound_card_init/utils/src/error.rs deleted file mode 100644 index 93d53b9b..00000000 --- a/sound_card_init/utils/src/error.rs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2020 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -//! This mod contains all possible errors that can occur within utils. -use std::fmt; -use std::io; -use std::time; -use std::{error, path::PathBuf}; - -use remain::sorted; - -/// Alias for a `Result` with the error type `utils::Error`. -pub type Result<T> = std::result::Result<T, Error>; - -/// This type represents all possible errors that can occur within utils. -#[sorted] -#[derive(Debug)] -pub enum Error { - /// It wraps file path with the io::Error. - FileIOFailed(PathBuf, io::Error), - /// It wraps file path with the serde_yaml::Error. - SerdeError(PathBuf, serde_yaml::Error), - /// It wraps time::SystemTimeError. - SystemTimeError(time::SystemTimeError), -} - -impl error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use Error::*; - match self { - FileIOFailed(file, e) => write!(f, "{:?}: {}", file, e), - SerdeError(file, e) => write!(f, "{:?}: {}", file, e), - SystemTimeError(e) => write!(f, "{}", e), - } - } -} diff --git a/ucm-config/for_all_boards/HUAWEI USB-C HEADSET/HUAWEI USB-C HEADSET.conf b/ucm-config/for_all_boards/HUAWEI USB-C HEADSET/HUAWEI USB-C HEADSET.conf new file mode 100644 index 00000000..c1db5193 --- /dev/null +++ b/ucm-config/for_all_boards/HUAWEI USB-C HEADSET/HUAWEI USB-C HEADSET.conf @@ -0,0 +1,6 @@ +Comment "HUAWEI USB-C HEADSET" + +SectionUseCase."HiFi" { + File "HiFi.conf" + Comment "Default" +} diff --git a/ucm-config/for_all_boards/HUAWEI USB-C HEADSET/HiFi.conf b/ucm-config/for_all_boards/HUAWEI USB-C HEADSET/HiFi.conf new file mode 100644 index 00000000..d48942bf --- /dev/null +++ b/ucm-config/for_all_boards/HUAWEI USB-C HEADSET/HiFi.conf @@ -0,0 +1,25 @@ +SectionVerb { + Value { + FullySpecifiedUCM "1" + } + + EnableSequence [ + cdev "hw:HEADSET" + cset "name='PCM Playback Volume' 45" + ] + + DisableSequence [ + ] +} + +SectionDevice."HUAWEI USB-C HEADSET Output".0 { + Value { + PlaybackPCM "hw:HEADSET,0" + } +} + +SectionDevice."HUAWEI USB-C HEADSET Input".0 { + Value { + CapturePCM "hw:HEADSET,0" + } +} diff --git a/ucm-config/for_all_boards/Mi Dual Driver Earphones Type-C/HiFi.conf b/ucm-config/for_all_boards/Mi Dual Driver Earphones Type-C/HiFi.conf new file mode 100644 index 00000000..9d63e16e --- /dev/null +++ b/ucm-config/for_all_boards/Mi Dual Driver Earphones Type-C/HiFi.conf @@ -0,0 +1,25 @@ +SectionVerb { + Value { + FullySpecifiedUCM "1" + } + + EnableSequence [ + cdev "hw:TypeC" + cset "name='PCM Playback Volume' 45" + ] + + DisableSequence [ + ] +} + +SectionDevice."Mi Earphones Output".0 { + Value { + PlaybackPCM "hw:TypeC,0" + } +} + +SectionDevice."Mi Earphones Input".0 { + Value { + CapturePCM "hw:TypeC,0" + } +} diff --git a/ucm-config/for_all_boards/Mi Dual Driver Earphones Type-C/Mi Dual Driver Earphones Type-C.conf b/ucm-config/for_all_boards/Mi Dual Driver Earphones Type-C/Mi Dual Driver Earphones Type-C.conf new file mode 100644 index 00000000..df19b47b --- /dev/null +++ b/ucm-config/for_all_boards/Mi Dual Driver Earphones Type-C/Mi Dual Driver Earphones Type-C.conf @@ -0,0 +1,6 @@ +Comment "Mi Dual Driver Earphones Type-C" + +SectionUseCase."HiFi" { + File "HiFi.conf" + Comment "Default" +} diff --git a/ucm-config/for_all_boards/Scarlett 2i2 USB/HiFi.conf b/ucm-config/for_all_boards/Scarlett 2i2 USB/HiFi.conf new file mode 100644 index 00000000..f49d6816 --- /dev/null +++ b/ucm-config/for_all_boards/Scarlett 2i2 USB/HiFi.conf @@ -0,0 +1,27 @@ +SectionVerb { + Value { + FullySpecifiedUCM "1" + } + EnableSequence [ + cdev "hw:USB,0" + ] + DisableSequence [ + ] +} + +SectionDevice."Scarlett 2i2 USB Output".0 { + Comment "Scarlett 2i2 Output" + + Value { + PlaybackPCM "hw:USB,0" + PlaybackRate "48000" + } +} + +SectionDevice."Scarlett 2i2 USB Input".0 { + Comment "Scarlett 2i2 Input" + Value { + CapturePCM "hw:USB,0" + CaptureRate "48000" + } +} diff --git a/ucm-config/for_all_boards/Scarlett 2i2 USB/Scarlett 2i2 USB.conf b/ucm-config/for_all_boards/Scarlett 2i2 USB/Scarlett 2i2 USB.conf new file mode 100644 index 00000000..88bac6ba --- /dev/null +++ b/ucm-config/for_all_boards/Scarlett 2i2 USB/Scarlett 2i2 USB.conf @@ -0,0 +1,5 @@ +Comment "Scarlett 2i2 USB" +SectionUseCase."HiFi" { + File "HiFi.conf" + Comment "Default" +} diff --git a/ucm-config/for_all_boards/Scarlett 2i4 USB/HiFi.conf b/ucm-config/for_all_boards/Scarlett 2i4 USB/HiFi.conf new file mode 100644 index 00000000..894683cd --- /dev/null +++ b/ucm-config/for_all_boards/Scarlett 2i4 USB/HiFi.conf @@ -0,0 +1,27 @@ +SectionVerb { + Value { + FullySpecifiedUCM "1" + } + EnableSequence [ + cdev "hw:USB,0" + ] + DisableSequence [ + ] +} + +SectionDevice."Scarlett 2i4 USB Output".0 { + Comment "Scarlett 2i4 Output" + + Value { + PlaybackPCM "hw:USB,0" + PlaybackRate "48000" + } +} + +SectionDevice."Scarlett 2i4 USB Input".0 { + Comment "Scarlett 2i4 Input" + Value { + CapturePCM "hw:USB,0" + CaptureRate "48000" + } +} diff --git a/ucm-config/for_all_boards/Scarlett 2i4 USB/Scarlett 2i4 USB.conf b/ucm-config/for_all_boards/Scarlett 2i4 USB/Scarlett 2i4 USB.conf new file mode 100644 index 00000000..71ea1696 --- /dev/null +++ b/ucm-config/for_all_boards/Scarlett 2i4 USB/Scarlett 2i4 USB.conf @@ -0,0 +1,5 @@ +Comment "Scarlett 2i4 USB" +SectionUseCase."HiFi" { + File "HiFi.conf" + Comment "Default" +} diff --git a/unblocked_terms.txt b/unblocked_terms.txt index 674e3d33..cba7545a 100644 --- a/unblocked_terms.txt +++ b/unblocked_terms.txt @@ -5,7 +5,6 @@ # # See repohooks/README.md for more details. -dummy master \bnative white.?list |