summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJorge E. Moreira <jemoreira@google.com>2021-04-12 19:05:41 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2021-04-12 19:05:41 +0000
commitbcf1f249f11b6865cff3f0d3f0ae5801e67e0e7e (patch)
tree7e0e07b9d8d50fc5dd352d5b36d74fa3cb3427db
parent1a33fa04345d1d2599eae37bb34781783aa0d04e (diff)
parent072dce8ca50bdf87b113490cdf14bde14724914f (diff)
downloadadhd-bcf1f249f11b6865cff3f0d3f0ae5801e67e0e7e.tar.gz
Merge remote-tracking branch 'aosp/upstream-main' into master am: 072dce8ca5android-s-beta-4android-s-beta-3android-s-beta-4
Original change: https://android-review.googlesource.com/c/platform/external/adhd/+/1673705 Change-Id: I34df6886976fcb22f0bef53453a5cd0924f04f7b
-rw-r--r--.github/workflows/codeql-analysis.yml4
-rw-r--r--audio_streams/src/audio_streams.rs18
-rw-r--r--audio_streams/src/capture.rs5
-rw-r--r--audio_streams/src/shm_streams.rs12
-rw-r--r--cras/README.dbus-api4
-rw-r--r--cras/client/cras-sys/src/gen.rs88
-rw-r--r--cras/client/cras-sys/src/lib.rs15
-rw-r--r--cras/client/cras_tests/src/audio.rs2
-rw-r--r--cras/client/libcras/src/cras_stream.rs16
-rw-r--r--cras/client/libcras/src/libcras.rs2
-rw-r--r--cras/src/Makefile.am17
-rw-r--r--cras/src/common/cras_file_wait.c2
-rw-r--r--cras/src/common/cras_types.h13
-rw-r--r--cras/src/common/cras_util.h13
-rw-r--r--cras/src/dsp/drc.c2
-rw-r--r--cras/src/dsp/drc_kernel.c24
-rw-r--r--cras/src/dsp/drc_kernel.h2
-rw-r--r--cras/src/libcras/cras_client.c444
-rw-r--r--cras/src/libcras/cras_client.h693
-rw-r--r--cras/src/server/audio_thread.c3
-rw-r--r--cras/src/server/config/cras_board_config.c7
-rw-r--r--cras/src/server/config/cras_board_config.h1
-rw-r--r--cras/src/server/cras_a2dp_iodev.c6
-rw-r--r--cras/src/server/cras_alsa_io.c43
-rw-r--r--cras/src/server/cras_alsa_mixer.c2
-rw-r--r--cras/src/server/cras_alsa_mixer.h2
-rw-r--r--cras/src/server/cras_alsa_ucm.c155
-rw-r--r--cras/src/server/cras_alsa_ucm.h37
-rw-r--r--cras/src/server/cras_apm_list.c32
-rw-r--r--cras/src/server/cras_bt_battery_provider.c371
-rw-r--r--cras/src/server/cras_bt_battery_provider.h47
-rw-r--r--cras/src/server/cras_bt_constants.h10
-rw-r--r--cras/src/server/cras_bt_device.c19
-rw-r--r--cras/src/server/cras_bt_device.h4
-rw-r--r--cras/src/server/cras_bt_io.c5
-rw-r--r--cras/src/server/cras_bt_manager.c31
-rw-r--r--cras/src/server/cras_dbus_control.c65
-rw-r--r--cras/src/server/cras_device_monitor.c20
-rw-r--r--cras/src/server/cras_device_monitor.h4
-rw-r--r--cras/src/server/cras_fmt_conv.c15
-rw-r--r--cras/src/server/cras_fmt_conv_ops.c38
-rw-r--r--cras/src/server/cras_fmt_conv_ops.h7
-rw-r--r--cras/src/server/cras_hfp_ag_profile.c14
-rw-r--r--cras/src/server/cras_hfp_ag_profile.h4
-rw-r--r--cras/src/server/cras_hfp_alsa_iodev.c8
-rw-r--r--cras/src/server/cras_hfp_iodev.c11
-rw-r--r--cras/src/server/cras_hfp_slc.c101
-rw-r--r--cras/src/server/cras_hfp_slc.h5
-rw-r--r--cras/src/server/cras_iodev.c22
-rw-r--r--cras/src/server/cras_iodev.h14
-rw-r--r--cras/src/server/cras_iodev_list.c45
-rw-r--r--cras/src/server/cras_iodev_list.h5
-rw-r--r--cras/src/server/cras_rclient_util.c2
-rw-r--r--cras/src/server/cras_rstream.c29
-rw-r--r--cras/src/server/cras_rstream.h12
-rw-r--r--cras/src/server/cras_server_metrics.c229
-rw-r--r--cras/src/server/cras_server_metrics.h6
-rw-r--r--cras/src/server/cras_system_state.c30
-rw-r--r--cras/src/server/cras_system_state.h12
-rw-r--r--cras/src/server/dev_io.c274
-rw-r--r--cras/src/server/dev_io.h5
-rw-r--r--cras/src/server/dev_stream.c23
-rw-r--r--cras/src/server/dev_stream.h27
-rw-r--r--cras/src/server/server_stream.c3
-rw-r--r--cras/src/server/stream_list.c32
-rw-r--r--cras/src/server/stream_list.h11
-rw-r--r--cras/src/tests/a2dp_iodev_unittest.cc4
-rw-r--r--cras/src/tests/alsa_io_unittest.cc21
-rw-r--r--cras/src/tests/alsa_mixer_unittest.cc26
-rw-r--r--cras/src/tests/alsa_ucm_unittest.cc157
-rw-r--r--cras/src/tests/apm_list_unittest.cc61
-rw-r--r--cras/src/tests/audio_thread_unittest.cc14
-rw-r--r--cras/src/tests/bt_io_unittest.cc4
-rw-r--r--cras/src/tests/capture_rclient_unittest.cc5
-rw-r--r--cras/src/tests/control_rclient_unittest.cc7
-rw-r--r--cras/src/tests/cras_abi_unittest.cc139
-rw-r--r--cras/src/tests/dev_io_stubs.cc5
-rw-r--r--cras/src/tests/dev_io_unittest.cc94
-rw-r--r--cras/src/tests/dev_stream_unittest.cc76
-rw-r--r--cras/src/tests/fmt_conv_ops_unittest.cc33
-rw-r--r--cras/src/tests/hfp_alsa_iodev_unittest.cc8
-rw-r--r--cras/src/tests/hfp_iodev_unittest.cc4
-rw-r--r--cras/src/tests/iodev_list_unittest.cc113
-rw-r--r--cras/src/tests/iodev_stub.cc36
-rw-r--r--cras/src/tests/iodev_stub.h6
-rw-r--r--cras/src/tests/iodev_unittest.cc14
-rw-r--r--cras/src/tests/playback_rclient_unittest.cc5
-rw-r--r--cras/src/tests/rstream_unittest.cc1
-rw-r--r--cras/src/tests/server_metrics_unittest.cc37
-rw-r--r--cras/src/tests/stream_list_unittest.cc66
-rw-r--r--cras/src/tests/system_state_unittest.cc40
-rw-r--r--cras/src/tests/timing_unittest.cc78
-rw-r--r--cras/src/tools/cras_test_client/cras_test_client.c11
-rw-r--r--cros_alsa/src/card.rs24
-rw-r--r--cros_alsa/src/control_primitive.rs32
-rw-r--r--cros_alsa/src/control_tlv.rs313
-rw-r--r--cros_alsa/src/lib.rs3
-rw-r--r--init/cras.sh1
-rw-r--r--scripts/audio_tuning/frontend/audio.js8
-rw-r--r--seccomp/cras-seccomp-amd64.policy2
-rw-r--r--seccomp/cras-seccomp-arm.policy6
-rw-r--r--seccomp/cras-seccomp-arm64.policy1
-rw-r--r--sof_sys/.gitignore2
-rw-r--r--sof_sys/.rustfmt.toml2
-rw-r--r--sof_sys/Cargo.toml4
-rw-r--r--sof_sys/generator/.gitignore1
-rw-r--r--sof_sys/generator/Cargo.toml6
-rw-r--r--sof_sys/generator/README.md1
-rw-r--r--sof_sys/generator/src/main.rs42
-rw-r--r--sof_sys/generator/wrapper.h5
-rw-r--r--sof_sys/src/bindings.rs154
-rw-r--r--sof_sys/src/lib.rs12
-rw-r--r--sound_card_init/Cargo.lock122
-rw-r--r--sound_card_init/Cargo.toml12
-rw-r--r--sound_card_init/amp/Cargo.lock254
-rw-r--r--sound_card_init/amp/Cargo.toml (renamed from sound_card_init/utils/Cargo.toml)9
-rw-r--r--sound_card_init/amp/src/lib.rs58
-rw-r--r--sound_card_init/amp/src/max98373d/dsm_param.rs211
-rw-r--r--sound_card_init/amp/src/max98373d/mod.rs274
-rw-r--r--sound_card_init/amp/src/max98373d/settings.rs41
-rw-r--r--sound_card_init/amp/src/max98390d/mod.rs213
-rw-r--r--sound_card_init/amp/src/max98390d/settings.rs (renamed from sound_card_init/max98390d/src/settings.rs)47
-rw-r--r--sound_card_init/dsm/Cargo.lock (renamed from sound_card_init/max98390d/Cargo.lock)0
-rw-r--r--sound_card_init/dsm/Cargo.toml (renamed from sound_card_init/max98390d/Cargo.toml)5
-rw-r--r--sound_card_init/dsm/src/datastore.rs72
-rw-r--r--sound_card_init/dsm/src/error.rs (renamed from sound_card_init/max98390d/src/error.rs)72
-rw-r--r--sound_card_init/dsm/src/lib.rs335
-rw-r--r--sound_card_init/dsm/src/utils.rs (renamed from sound_card_init/utils/src/lib.rs)9
-rw-r--r--sound_card_init/dsm/src/vpd.rs (renamed from sound_card_init/max98390d/src/vpd.rs)15
-rw-r--r--sound_card_init/dsm/src/zero_player.rs209
-rw-r--r--sound_card_init/max98390d/src/amp_calibration.rs319
-rw-r--r--sound_card_init/max98390d/src/datastore.rs56
-rw-r--r--sound_card_init/max98390d/src/lib.rs153
-rw-r--r--sound_card_init/seccomp/sound_card_init-seccomp-amd64.policy125
-rw-r--r--sound_card_init/sound_card_init.conf88
-rw-r--r--sound_card_init/src/main.rs63
-rw-r--r--sound_card_init/utils/Cargo.lock109
-rw-r--r--sound_card_init/utils/src/error.rs39
-rw-r--r--ucm-config/for_all_boards/HUAWEI USB-C HEADSET/HUAWEI USB-C HEADSET.conf6
-rw-r--r--ucm-config/for_all_boards/HUAWEI USB-C HEADSET/HiFi.conf25
-rw-r--r--ucm-config/for_all_boards/Mi Dual Driver Earphones Type-C/HiFi.conf25
-rw-r--r--ucm-config/for_all_boards/Mi Dual Driver Earphones Type-C/Mi Dual Driver Earphones Type-C.conf6
-rw-r--r--ucm-config/for_all_boards/Scarlett 2i2 USB/HiFi.conf27
-rw-r--r--ucm-config/for_all_boards/Scarlett 2i2 USB/Scarlett 2i2 USB.conf5
-rw-r--r--ucm-config/for_all_boards/Scarlett 2i4 USB/HiFi.conf27
-rw-r--r--ucm-config/for_all_boards/Scarlett 2i4 USB/Scarlett 2i4 USB.conf5
-rw-r--r--unblocked_terms.txt1
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(&params->format, layout);
+}
+
+struct libcras_stream_params *libcras_stream_params_create()
+{
+ struct libcras_stream_params *params =
+ (struct libcras_stream_params *)calloc(
+ 1, sizeof(struct libcras_stream_params));
+ if (!params) {
+ syslog(LOG_ERR, "cras_client: calloc failed");
+ return NULL;
+ }
+ params->params_ = (struct cras_stream_params *)calloc(
+ 1, sizeof(struct cras_stream_params));
+ if (params->params_ == NULL) {
+ syslog(LOG_ERR, "cras_client: calloc failed");
+ free(params->params_);
+ return NULL;
+ }
+ params->api_version = CRAS_API_VERSION;
+ params->set = stream_params_set;
+ params->set_channel_layout = stream_params_set_channel_layout;
+ params->enable_aec = cras_client_stream_params_enable_aec;
+ return params;
+}
+
+void libcras_stream_params_destroy(struct libcras_stream_params *params)
+{
+ free(params->params_);
+ free(params);
+}
+
+struct cras_node_info {
+ uint64_t id;
+ uint32_t dev_idx;
+ uint32_t node_idx;
+ uint32_t max_supported_channels;
+ bool plugged;
+ bool active;
+ char type[CRAS_NODE_TYPE_BUFFER_SIZE];
+ char node_name[CRAS_NODE_NAME_BUFFER_SIZE];
+ char dev_name[CRAS_IODEV_NAME_BUFFER_SIZE];
+};
+
+int cras_node_info_get_id(struct cras_node_info *node, uint64_t *id)
+{
+ (*id) = node->id;
+ return 0;
+}
+
+int cras_node_info_get_dev_idx(struct cras_node_info *node, uint32_t *dev_idx)
+{
+ (*dev_idx) = node->dev_idx;
+ return 0;
+}
+
+int cras_node_info_get_node_idx(struct cras_node_info *node, uint32_t *node_idx)
+{
+ (*node_idx) = node->node_idx;
+ return 0;
+}
+
+int cras_node_info_get_max_supported_channels(struct cras_node_info *node,
+ uint32_t *max_supported_channels)
+{
+ (*max_supported_channels) = node->max_supported_channels;
+ return 0;
+}
+
+int cras_node_info_is_plugged(struct cras_node_info *node, bool *is_plugged)
+{
+ (*is_plugged) = node->plugged;
+ return 0;
+}
+
+int cras_node_info_is_active(struct cras_node_info *node, bool *is_active)
+{
+ (*is_active) = node->active;
+ return 0;
+}
+
+int cras_node_info_get_type(struct cras_node_info *node, char **type)
+{
+ (*type) = node->type;
+ return 0;
+}
+
+int cras_node_info_get_node_name(struct cras_node_info *node, char **node_name)
+{
+ (*node_name) = node->node_name;
+ return 0;
+}
+
+int cras_node_info_get_dev_name(struct cras_node_info *node, char **dev_name)
+{
+ (*dev_name) = node->dev_name;
+ return 0;
+}
+
+struct libcras_node_info *
+libcras_node_info_create(struct cras_iodev_info *iodev,
+ struct cras_ionode_info *ionode)
+{
+ struct libcras_node_info *node = (struct libcras_node_info *)calloc(
+ 1, sizeof(struct libcras_node_info));
+ if (!node) {
+ syslog(LOG_ERR, "cras_client: calloc failed");
+ return NULL;
+ }
+ node->node_ = (struct cras_node_info *)calloc(
+ 1, sizeof(struct cras_node_info));
+ if (node->node_ == NULL) {
+ syslog(LOG_ERR, "cras_client: calloc failed");
+ free(node);
+ return NULL;
+ }
+ node->api_version = CRAS_API_VERSION;
+ node->node_->id =
+ cras_make_node_id(ionode->iodev_idx, ionode->ionode_idx);
+ node->node_->dev_idx = ionode->iodev_idx;
+ node->node_->node_idx = ionode->ionode_idx;
+ node->node_->max_supported_channels = iodev->max_supported_channels;
+ node->node_->plugged = ionode->plugged;
+ node->node_->active = ionode->active;
+ strncpy(node->node_->type, ionode->type, CRAS_NODE_TYPE_BUFFER_SIZE);
+ node->node_->type[CRAS_NODE_TYPE_BUFFER_SIZE - 1] = '\0';
+ strncpy(node->node_->node_name, ionode->name,
+ CRAS_NODE_NAME_BUFFER_SIZE);
+ node->node_->node_name[CRAS_NODE_NAME_BUFFER_SIZE - 1] = '\0';
+ strncpy(node->node_->dev_name, iodev->name,
+ CRAS_IODEV_NAME_BUFFER_SIZE);
+ node->node_->dev_name[CRAS_IODEV_NAME_BUFFER_SIZE - 1] = '\0';
+ node->get_id = cras_node_info_get_id;
+ node->get_dev_idx = cras_node_info_get_dev_idx;
+ node->get_node_idx = cras_node_info_get_node_idx;
+ node->get_max_supported_channels =
+ cras_node_info_get_max_supported_channels;
+ node->is_plugged = cras_node_info_is_plugged;
+ node->is_active = cras_node_info_is_active;
+ node->get_type = cras_node_info_get_type;
+ node->get_node_name = cras_node_info_get_node_name;
+ node->get_dev_name = cras_node_info_get_dev_name;
+ return node;
+}
+
+void libcras_node_info_destroy(struct libcras_node_info *node)
+{
+ free(node->node_);
+ free(node);
+}
+
+void libcras_node_info_array_destroy(struct libcras_node_info **nodes,
+ size_t num)
+{
+ int i;
+ for (i = 0; i < num; i++)
+ libcras_node_info_destroy(nodes[i]);
+ free(nodes);
+}
diff --git a/cras/src/libcras/cras_client.h b/cras/src/libcras/cras_client.h
index 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 = &params;
+ params.stream_cb = get_stream_cb;
+ params.cb_threshold = 480;
+ params.user_data = (void*)0x321;
+ stream.shm = InitShm(960);
+ stream.shm->header->write_offset[0] = 960 * 4;
+ stream.shm->header->write_buf_idx = 0;
+ stream.shm->header->read_offset[0] = 0;
+ stream.shm->header->read_buf_idx = 0;
+ now.tv_sec = 100;
+ now.tv_nsec = 0;
+ stream.shm->header->ts.tv_sec = 90;
+ stream.shm->header->ts.tv_nsec = 0;
+
+ handle_capture_data_ready(&stream, 480);
+
+ EXPECT_EQ(1, get_stream_cb_called);
+ EXPECT_EQ(stream.id, cb_stream_id);
+ EXPECT_EQ(cras_shm_get_write_buffer_base(stream.shm), cb_buf);
+ EXPECT_EQ(480, cb_frames);
+ EXPECT_EQ(10, cb_latency.tv_sec);
+ EXPECT_EQ(0, cb_latency.tv_nsec);
+ EXPECT_EQ((void*)0x321, cb_usr_arg);
+
+ DestroyShm(stream.shm);
+}
+
+} // namespace
+
+extern "C" {
+
+int clock_gettime(clockid_t clk_id, struct timespec* tp) {
+ *tp = now;
+ return 0;
+}
+}
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ openlog(NULL, LOG_PERROR, LOG_USER);
+ return RUN_ALL_TESTS();
+}
diff --git a/cras/src/tests/dev_io_stubs.cc b/cras/src/tests/dev_io_stubs.cc
index 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