/* * Copyright (c) 2022-2023 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include "cap_internal.h" #include "ccid_internal.h" #include "csip_internal.h" #include "bap_endpoint.h" #include LOG_MODULE_REGISTER(bt_cap_commander, CONFIG_BT_CAP_COMMANDER_LOG_LEVEL); #include "common/bt_str.h" static const struct bt_cap_commander_cb *cap_cb; int bt_cap_commander_register_cb(const struct bt_cap_commander_cb *cb) { CHECKIF(cb == NULL) { LOG_DBG("cb is NULL"); return -EINVAL; } CHECKIF(cap_cb != NULL) { LOG_DBG("callbacks already registered"); return -EALREADY; } cap_cb = cb; return 0; } int bt_cap_commander_unregister_cb(const struct bt_cap_commander_cb *cb) { CHECKIF(cb == NULL) { LOG_DBG("cb is NULL"); return -EINVAL; } CHECKIF(cap_cb != cb) { LOG_DBG("cb is not registered"); return -EINVAL; } cap_cb = NULL; return 0; } static void cap_commander_discover_complete(struct bt_conn *conn, int err, const struct bt_csip_set_coordinator_csis_inst *csis_inst) { if (cap_cb && cap_cb->discovery_complete) { cap_cb->discovery_complete(conn, err, csis_inst); } } int bt_cap_commander_discover(struct bt_conn *conn) { CHECKIF(conn == NULL) { LOG_DBG("NULL conn"); return -EINVAL; } return bt_cap_common_discover(conn, cap_commander_discover_complete); } int bt_cap_commander_broadcast_reception_start( const struct bt_cap_commander_broadcast_reception_start_param *param) { return -ENOSYS; } int bt_cap_commander_broadcast_reception_stop( const struct bt_cap_commander_broadcast_reception_stop_param *param) { return -ENOSYS; } static void cap_commander_unicast_audio_proc_complete(void) { struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); enum bt_cap_common_proc_type proc_type; struct bt_conn *failed_conn; int err; failed_conn = active_proc->failed_conn; err = active_proc->err; proc_type = active_proc->proc_type; bt_cap_common_clear_active_proc(); if (cap_cb == NULL) { return; } switch (proc_type) { #if defined(CONFIG_BT_VCP_VOL_CTLR) case BT_CAP_COMMON_PROC_TYPE_VOLUME_CHANGE: if (cap_cb->volume_changed != NULL) { cap_cb->volume_changed(failed_conn, err); } break; case BT_CAP_COMMON_PROC_TYPE_VOLUME_MUTE_CHANGE: if (cap_cb->volume_changed != NULL) { cap_cb->volume_mute_changed(failed_conn, err); } break; #if defined(CONFIG_BT_VCP_VOL_CTLR_VOCS) case BT_CAP_COMMON_PROC_TYPE_VOLUME_OFFSET_CHANGE: if (cap_cb->volume_offset_changed != NULL) { cap_cb->volume_offset_changed(failed_conn, err); } break; #endif /* CONFIG_BT_VCP_VOL_CTLR_VOCS */ #endif /* CONFIG_BT_VCP_VOL_CTLR */ #if defined(CONFIG_BT_MICP_MIC_CTLR) case BT_CAP_COMMON_PROC_TYPE_MICROPHONE_MUTE_CHANGE: if (cap_cb->microphone_mute_changed != NULL) { cap_cb->microphone_mute_changed(failed_conn, err); } break; #if defined(CONFIG_BT_MICP_MIC_CTLR_AICS) case BT_CAP_COMMON_PROC_TYPE_MICROPHONE_GAIN_CHANGE: if (cap_cb->microphone_gain_changed != NULL) { cap_cb->microphone_gain_changed(failed_conn, err); } break; #endif /* CONFIG_BT_MICP_MIC_CTLR_AICS */ #endif /* CONFIG_BT_MICP_MIC_CTLR */ case BT_CAP_COMMON_PROC_TYPE_NONE: default: __ASSERT(false, "Invalid proc_type: %u", proc_type); } } int bt_cap_commander_cancel(void) { if (!bt_cap_common_proc_is_active() && !bt_cap_common_proc_is_aborted()) { LOG_DBG("No CAP procedure is in progress"); return -EALREADY; } bt_cap_common_abort_proc(NULL, -ECANCELED); cap_commander_unicast_audio_proc_complete(); return 0; } #if defined(CONFIG_BT_VCP_VOL_CTLR) static struct bt_vcp_vol_ctlr_cb vol_ctlr_cb; static bool vcp_cb_registered; static int cap_commander_register_vcp_cb(void) { int err; err = bt_vcp_vol_ctlr_cb_register(&vol_ctlr_cb); if (err != 0) { LOG_DBG("Failed to register VCP callbacks: %d", err); return -ENOEXEC; } vcp_cb_registered = true; return 0; } static bool valid_change_volume_param(const struct bt_cap_commander_change_volume_param *param) { CHECKIF(param == NULL) { LOG_DBG("param is NULL"); return false; } CHECKIF(param->count == 0) { LOG_DBG("Invalid param->count: %u", param->count); return false; } CHECKIF(param->members == NULL) { LOG_DBG("param->members is NULL"); return false; } CHECKIF(param->count > CONFIG_BT_MAX_CONN) { LOG_DBG("param->count (%zu) is larger than CONFIG_BT_MAX_CONN (%d)", param->count, CONFIG_BT_MAX_CONN); return false; } for (size_t i = 0U; i < param->count; i++) { const union bt_cap_set_member *member = ¶m->members[i]; const struct bt_cap_common_client *client = bt_cap_common_get_client(param->type, member); if (client == NULL) { LOG_DBG("Invalid param->members[%zu]", i); return false; } if (bt_vcp_vol_ctlr_get_by_conn(client->conn) == NULL) { LOG_DBG("Volume control not available for param->members[%zu]", i); return false; } for (size_t j = 0U; j < i; j++) { const union bt_cap_set_member *other = ¶m->members[j]; if (other == member) { LOG_DBG("param->members[%zu] (%p) is duplicated by " "param->members[%zu] (%p)", j, other, i, member); return false; } } } return true; } static void cap_commander_vcp_vol_set_cb(struct bt_vcp_vol_ctlr *vol_ctlr, int err) { struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); struct bt_conn *conn; int vcp_err; LOG_DBG("vol_ctlr %p", (void *)vol_ctlr); vcp_err = bt_vcp_vol_ctlr_conn_get(vol_ctlr, &conn); if (vcp_err != 0) { LOG_ERR("Failed to get conn by vol_ctrl: %d", vcp_err); return; } LOG_DBG("conn %p", (void *)conn); if (!bt_cap_common_conn_in_active_proc(conn)) { /* State change happened outside of a procedure; ignore */ return; } if (err != 0) { LOG_DBG("Failed to set volume: %d", err); bt_cap_common_abort_proc(conn, err); } else { active_proc->proc_done_cnt++; LOG_DBG("Conn %p volume updated (%zu/%zu streams done)", (void *)conn, active_proc->proc_done_cnt, active_proc->proc_cnt); } if (bt_cap_common_proc_is_aborted()) { LOG_DBG("Proc is aborted"); if (bt_cap_common_proc_all_handled()) { LOG_DBG("All handled"); cap_commander_unicast_audio_proc_complete(); } return; } if (!bt_cap_common_proc_is_done()) { const struct bt_cap_commander_proc_param *proc_param; proc_param = &active_proc->proc_param.commander[active_proc->proc_done_cnt]; conn = proc_param->conn; active_proc->proc_initiated_cnt++; err = bt_vcp_vol_ctlr_set_vol(bt_vcp_vol_ctlr_get_by_conn(conn), proc_param->change_volume.volume); if (err != 0) { LOG_DBG("Failed to set volume for conn %p: %d", (void *)conn, err); bt_cap_common_abort_proc(conn, err); cap_commander_unicast_audio_proc_complete(); } } else { cap_commander_unicast_audio_proc_complete(); } } int bt_cap_commander_change_volume(const struct bt_cap_commander_change_volume_param *param) { const struct bt_cap_commander_proc_param *proc_param; struct bt_cap_common_proc *active_proc; struct bt_conn *conn; int err; if (bt_cap_common_proc_is_active()) { LOG_DBG("A CAP procedure is already in progress"); return -EBUSY; } if (!valid_change_volume_param(param)) { return -EINVAL; } bt_cap_common_start_proc(BT_CAP_COMMON_PROC_TYPE_VOLUME_CHANGE, param->count); vol_ctlr_cb.vol_set = cap_commander_vcp_vol_set_cb; if (!vcp_cb_registered && cap_commander_register_vcp_cb() != 0) { LOG_DBG("Failed to register VCP callbacks"); return -ENOEXEC; } active_proc = bt_cap_common_get_active_proc(); for (size_t i = 0U; i < param->count; i++) { struct bt_conn *member_conn = bt_cap_common_get_member_conn(param->type, ¶m->members[i]); if (member_conn == NULL) { LOG_DBG("Invalid param->members[%zu]", i); return -EINVAL; } /* Store the necessary parameters as we cannot assume that the supplied parameters * are kept valid */ active_proc->proc_param.commander[i].conn = member_conn; active_proc->proc_param.commander[i].change_volume.volume = param->volume; } proc_param = &active_proc->proc_param.commander[0]; conn = proc_param->conn; active_proc->proc_initiated_cnt++; err = bt_vcp_vol_ctlr_set_vol(bt_vcp_vol_ctlr_get_by_conn(conn), proc_param->change_volume.volume); if (err != 0) { LOG_DBG("Failed to set volume for conn %p: %d", (void *)conn, err); return -ENOEXEC; } return 0; } static bool valid_change_volume_mute_state_param( const struct bt_cap_commander_change_volume_mute_state_param *param) { CHECKIF(param == NULL) { LOG_DBG("param is NULL"); return false; } CHECKIF(param->count == 0) { LOG_DBG("Invalid param->count: %u", param->count); return false; } CHECKIF(param->members == NULL) { LOG_DBG("param->members is NULL"); return false; } CHECKIF(param->count > CONFIG_BT_MAX_CONN) { LOG_DBG("param->count (%zu) is larger than CONFIG_BT_MAX_CONN (%d)", param->count, CONFIG_BT_MAX_CONN); return false; } for (size_t i = 0U; i < param->count; i++) { const union bt_cap_set_member *member = ¶m->members[i]; const struct bt_cap_common_client *client = bt_cap_common_get_client(param->type, member); CHECKIF(client == NULL) { LOG_DBG("Invalid param->members[%zu]", i); return false; } CHECKIF(bt_vcp_vol_ctlr_get_by_conn(client->conn) == NULL) { LOG_DBG("Volume control not available for param->members[%zu]", i); return false; } for (size_t j = 0U; j < i; j++) { const union bt_cap_set_member *other = ¶m->members[j]; CHECKIF(other == member) { LOG_DBG("param->members[%zu] (%p) is duplicated by " "param->members[%zu] (%p)", j, other, i, member); return false; } } } return true; } static void cap_commander_vcp_vol_mute_cb(struct bt_vcp_vol_ctlr *vol_ctlr, int err) { struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); struct bt_conn *conn; int vcp_err; LOG_DBG("vol_ctlr %p", (void *)vol_ctlr); vcp_err = bt_vcp_vol_ctlr_conn_get(vol_ctlr, &conn); if (vcp_err != 0) { LOG_ERR("Failed to get conn by vol_ctrl: %d", vcp_err); return; } LOG_DBG("conn %p", (void *)conn); if (!bt_cap_common_conn_in_active_proc(conn)) { /* State change happened outside of a procedure; ignore */ return; } if (err != 0) { LOG_DBG("Failed to set volume: %d", err); bt_cap_common_abort_proc(conn, err); } else { active_proc->proc_done_cnt++; LOG_DBG("Conn %p volume updated (%zu/%zu streams done)", (void *)conn, active_proc->proc_done_cnt, active_proc->proc_cnt); } if (bt_cap_common_proc_is_aborted()) { LOG_DBG("Proc is aborted"); if (bt_cap_common_proc_all_handled()) { LOG_DBG("All handled"); cap_commander_unicast_audio_proc_complete(); } return; } if (!bt_cap_common_proc_is_done()) { const struct bt_cap_commander_proc_param *proc_param; proc_param = &active_proc->proc_param.commander[active_proc->proc_done_cnt]; conn = proc_param->conn; active_proc->proc_initiated_cnt++; if (proc_param->change_vol_mute.mute) { err = bt_vcp_vol_ctlr_mute(bt_vcp_vol_ctlr_get_by_conn(conn)); } else { err = bt_vcp_vol_ctlr_unmute(bt_vcp_vol_ctlr_get_by_conn(conn)); } if (err != 0) { LOG_DBG("Failed to set volume for conn %p: %d", (void *)conn, err); bt_cap_common_abort_proc(conn, err); cap_commander_unicast_audio_proc_complete(); } } else { cap_commander_unicast_audio_proc_complete(); } } int bt_cap_commander_change_volume_mute_state( const struct bt_cap_commander_change_volume_mute_state_param *param) { const struct bt_cap_commander_proc_param *proc_param; struct bt_cap_common_proc *active_proc; struct bt_conn *conn; int err; if (bt_cap_common_proc_is_active()) { LOG_DBG("A CAP procedure is already in progress"); return -EBUSY; } if (!valid_change_volume_mute_state_param(param)) { return -EINVAL; } bt_cap_common_start_proc(BT_CAP_COMMON_PROC_TYPE_VOLUME_MUTE_CHANGE, param->count); vol_ctlr_cb.mute = cap_commander_vcp_vol_mute_cb; vol_ctlr_cb.unmute = cap_commander_vcp_vol_mute_cb; if (!vcp_cb_registered && cap_commander_register_vcp_cb() != 0) { LOG_DBG("Failed to register VCP callbacks"); return -ENOEXEC; } active_proc = bt_cap_common_get_active_proc(); for (size_t i = 0U; i < param->count; i++) { struct bt_conn *member_conn = bt_cap_common_get_member_conn(param->type, ¶m->members[i]); CHECKIF(member_conn == NULL) { LOG_DBG("Invalid param->members[%zu]", i); return -EINVAL; } /* Store the necessary parameters as we cannot assume that the supplied parameters * are kept valid */ active_proc->proc_param.commander[i].conn = member_conn; active_proc->proc_param.commander[i].change_vol_mute.mute = param->mute; } proc_param = &active_proc->proc_param.commander[0]; conn = proc_param->conn; active_proc->proc_initiated_cnt++; if (proc_param->change_vol_mute.mute) { err = bt_vcp_vol_ctlr_mute(bt_vcp_vol_ctlr_get_by_conn(conn)); } else { err = bt_vcp_vol_ctlr_unmute(bt_vcp_vol_ctlr_get_by_conn(conn)); } if (err != 0) { LOG_DBG("Failed to set volume mute state for conn %p: %d", (void *)conn, err); return -ENOEXEC; } return 0; } #if defined(CONFIG_BT_VCP_VOL_CTLR_VOCS) static bool valid_change_offset_param(const struct bt_cap_commander_change_volume_offset_param *param) { CHECKIF(param == NULL) { LOG_DBG("param is NULL"); return false; } CHECKIF(param->count == 0) { LOG_DBG("Invalid param->count: %u", param->count); return false; } CHECKIF(param->param == NULL) { LOG_DBG("param->param is NULL"); return false; } CHECKIF(param->count > CONFIG_BT_MAX_CONN) { LOG_DBG("param->count (%zu) is larger than CONFIG_BT_MAX_CONN (%d)", param->count, CONFIG_BT_MAX_CONN); return false; } for (size_t i = 0U; i < param->count; i++) { const struct bt_cap_commander_change_volume_offset_member_param *member_param = ¶m->param[i]; const union bt_cap_set_member *member = &member_param->member; const struct bt_cap_common_client *client = bt_cap_common_get_client(param->type, member); struct bt_vcp_vol_ctlr *vol_ctlr; struct bt_vcp_included included; int err; if (client == NULL) { LOG_DBG("Invalid param->param[%zu].member", i); return false; } vol_ctlr = bt_vcp_vol_ctlr_get_by_conn(client->conn); if (vol_ctlr == NULL) { LOG_DBG("Volume control not available for param->param[%zu].member", i); return false; } err = bt_vcp_vol_ctlr_included_get(vol_ctlr, &included); if (err != 0 || included.vocs_cnt == 0) { LOG_DBG("Volume offset control not available for param->param[%zu].member", i); return -ENOEXEC; } if (!IN_RANGE(member_param->offset, BT_VOCS_MIN_OFFSET, BT_VOCS_MAX_OFFSET)) { LOG_DBG("Invalid offset %d for param->param[%zu].offset", member_param->offset, i); return false; } for (size_t j = 0U; j < i; j++) { const union bt_cap_set_member *other = ¶m->param[j].member; if (other == member) { LOG_DBG("param->param[%zu].member (%p) is duplicated by " "param->param[%zu].member (%p)", j, other, i, member); return false; } } } return true; } static void cap_commander_vcp_set_offset_cb(struct bt_vocs *inst, int err) { struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); struct bt_conn *conn; int vocs_err; LOG_DBG("bt_vocs %p", (void *)inst); vocs_err = bt_vocs_client_conn_get(inst, &conn); if (vocs_err != 0) { LOG_ERR("Failed to get conn by inst: %d", vocs_err); return; } LOG_DBG("conn %p", (void *)conn); if (!bt_cap_common_conn_in_active_proc(conn)) { /* State change happened outside of a procedure; ignore */ return; } if (err != 0) { LOG_DBG("Failed to set offset: %d", err); bt_cap_common_abort_proc(conn, err); } else { active_proc->proc_done_cnt++; LOG_DBG("Conn %p offset updated (%zu/%zu streams done)", (void *)conn, active_proc->proc_done_cnt, active_proc->proc_cnt); } if (bt_cap_common_proc_is_aborted()) { LOG_DBG("Proc is aborted"); if (bt_cap_common_proc_all_handled()) { LOG_DBG("All handled"); cap_commander_unicast_audio_proc_complete(); } return; } if (!bt_cap_common_proc_is_done()) { const struct bt_cap_commander_proc_param *proc_param; proc_param = &active_proc->proc_param.commander[active_proc->proc_done_cnt]; conn = proc_param->conn; active_proc->proc_initiated_cnt++; err = bt_vocs_state_set(proc_param->change_offset.vocs, proc_param->change_offset.offset); if (err != 0) { LOG_DBG("Failed to set offset for conn %p: %d", (void *)conn, err); bt_cap_common_abort_proc(conn, err); cap_commander_unicast_audio_proc_complete(); } } else { cap_commander_unicast_audio_proc_complete(); } } int bt_cap_commander_change_volume_offset( const struct bt_cap_commander_change_volume_offset_param *param) { const struct bt_cap_commander_proc_param *proc_param; struct bt_cap_common_proc *active_proc; struct bt_vcp_vol_ctlr *vol_ctlr; struct bt_conn *conn; int err; if (bt_cap_common_proc_is_active()) { LOG_DBG("A CAP procedure is already in progress"); return -EBUSY; } if (!valid_change_offset_param(param)) { return -EINVAL; } bt_cap_common_start_proc(BT_CAP_COMMON_PROC_TYPE_VOLUME_OFFSET_CHANGE, param->count); vol_ctlr_cb.vocs_cb.set_offset = cap_commander_vcp_set_offset_cb; if (!vcp_cb_registered && cap_commander_register_vcp_cb() != 0) { LOG_DBG("Failed to register VCP callbacks"); return -ENOEXEC; } active_proc = bt_cap_common_get_active_proc(); for (size_t i = 0U; i < param->count; i++) { const struct bt_cap_commander_change_volume_offset_member_param *member_param = ¶m->param[i]; struct bt_conn *member_conn = bt_cap_common_get_member_conn(param->type, &member_param->member); struct bt_vcp_included included; if (member_conn == NULL) { LOG_DBG("Invalid param->members[%zu]", i); return -EINVAL; } vol_ctlr = bt_vcp_vol_ctlr_get_by_conn(member_conn); if (vol_ctlr == NULL) { LOG_DBG("Invalid param->members[%zu] vol_ctlr", i); return -EINVAL; } err = bt_vcp_vol_ctlr_included_get(vol_ctlr, &included); if (err != 0 || included.vocs_cnt == 0) { LOG_DBG("Invalid param->members[%zu] vocs", i); return -EINVAL; } /* Store the necessary parameters as we cannot assume that the supplied parameters * are kept valid */ active_proc->proc_param.commander[i].conn = member_conn; active_proc->proc_param.commander[i].change_offset.offset = member_param->offset; /* TODO: For now we just use the first VOCS instance * - How should we handle multiple? */ active_proc->proc_param.commander[i].change_offset.vocs = included.vocs[0]; } proc_param = &active_proc->proc_param.commander[0]; conn = proc_param->conn; active_proc->proc_initiated_cnt++; err = bt_vocs_state_set(proc_param->change_offset.vocs, proc_param->change_offset.offset); if (err != 0) { LOG_DBG("Failed to set volume for conn %p: %d", (void *)conn, err); return -ENOEXEC; } return 0; } #endif /* CONFIG_BT_VCP_VOL_CTLR_VOCS */ #endif /* CONFIG_BT_VCP_VOL_CTLR */ #if defined(CONFIG_BT_MICP_MIC_CTLR) static struct bt_micp_mic_ctlr_cb mic_ctlr_cb; static bool micp_callbacks_registered; static int cap_commander_register_micp_callbacks(void) { int err; err = bt_micp_mic_ctlr_cb_register(&mic_ctlr_cb); if (err != 0) { LOG_DBG("Failed to register MICP callbacks: %d", err); return -ENOEXEC; } micp_callbacks_registered = true; return 0; } static bool valid_change_microphone_mute_state_param( const struct bt_cap_commander_change_microphone_mute_state_param *param) { CHECKIF(param == NULL) { LOG_DBG("param is NULL"); return false; } CHECKIF(param->count == 0) { LOG_DBG("Invalid param->count: %u", param->count); return false; } CHECKIF(param->members == NULL) { LOG_DBG("param->members is NULL"); return false; } CHECKIF(param->count > CONFIG_BT_MAX_CONN) { LOG_DBG("param->count (%zu) is larger than CONFIG_BT_MAX_CONN (%d)", param->count, CONFIG_BT_MAX_CONN); return false; } for (size_t i = 0U; i < param->count; i++) { const union bt_cap_set_member *member = ¶m->members[i]; const struct bt_cap_common_client *client = bt_cap_common_get_client(param->type, member); CHECKIF(client == NULL) { LOG_DBG("Invalid param->members[%zu]", i); return false; } CHECKIF(bt_micp_mic_ctlr_get_by_conn(client->conn) == NULL) { LOG_DBG("Microphone control not available for param->members[%zu]", i); return false; } for (size_t j = 0U; j < i; j++) { const union bt_cap_set_member *other = ¶m->members[j]; CHECKIF(other == member) { LOG_DBG("param->members[%zu] (%p) is duplicated by " "param->members[%zu] (%p)", j, other, i, member); return false; } } } return true; } static void cap_commander_micp_mic_mute_cb(struct bt_micp_mic_ctlr *mic_ctlr, int err) { struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); struct bt_conn *conn; int micp_err; LOG_DBG("mic_ctlr %p", (void *)mic_ctlr); micp_err = bt_micp_mic_ctlr_conn_get(mic_ctlr, &conn); if (micp_err != 0) { LOG_ERR("Failed to get conn by mic_ctlr: %d", micp_err); return; } LOG_DBG("conn %p", (void *)conn); if (!bt_cap_common_conn_in_active_proc(conn)) { /* State change happened outside of a procedure; ignore */ return; } if (err != 0) { LOG_DBG("Failed to change microphone mute: %d", err); bt_cap_common_abort_proc(conn, err); } else { active_proc->proc_done_cnt++; LOG_DBG("Conn %p mute updated (%zu/%zu streams done)", (void *)conn, active_proc->proc_done_cnt, active_proc->proc_cnt); } if (bt_cap_common_proc_is_aborted()) { LOG_DBG("Proc is aborted"); if (bt_cap_common_proc_all_handled()) { LOG_DBG("All handled"); cap_commander_unicast_audio_proc_complete(); } return; } if (!bt_cap_common_proc_is_done()) { const struct bt_cap_commander_proc_param *proc_param; proc_param = &active_proc->proc_param.commander[active_proc->proc_done_cnt]; conn = proc_param->conn; active_proc->proc_initiated_cnt++; if (proc_param->change_mic_mute.mute) { err = bt_micp_mic_ctlr_mute(bt_micp_mic_ctlr_get_by_conn(conn)); } else { err = bt_micp_mic_ctlr_unmute(bt_micp_mic_ctlr_get_by_conn(conn)); } if (err != 0) { LOG_DBG("Failed to change mute for conn %p: %d", (void *)conn, err); bt_cap_common_abort_proc(conn, err); cap_commander_unicast_audio_proc_complete(); } } else { cap_commander_unicast_audio_proc_complete(); } } int bt_cap_commander_change_microphone_mute_state( const struct bt_cap_commander_change_microphone_mute_state_param *param) { const struct bt_cap_commander_proc_param *proc_param; struct bt_cap_common_proc *active_proc; struct bt_conn *conn; int err; if (bt_cap_common_proc_is_active()) { LOG_DBG("A CAP procedure is already in progress"); return -EBUSY; } if (!valid_change_microphone_mute_state_param(param)) { return -EINVAL; } bt_cap_common_start_proc(BT_CAP_COMMON_PROC_TYPE_MICROPHONE_MUTE_CHANGE, param->count); mic_ctlr_cb.mute_written = cap_commander_micp_mic_mute_cb; mic_ctlr_cb.unmute_written = cap_commander_micp_mic_mute_cb; if (!micp_callbacks_registered && cap_commander_register_micp_callbacks() != 0) { LOG_DBG("Failed to register MICP callbacks"); return -ENOEXEC; } active_proc = bt_cap_common_get_active_proc(); for (size_t i = 0U; i < param->count; i++) { struct bt_conn *member_conn = bt_cap_common_get_member_conn(param->type, ¶m->members[i]); CHECKIF(member_conn == NULL) { LOG_DBG("Invalid param->members[%zu]", i); return -EINVAL; } /* Store the necessary parameters as we cannot assume that the supplied parameters * are kept valid */ active_proc->proc_param.commander[i].conn = member_conn; active_proc->proc_param.commander[i].change_mic_mute.mute = param->mute; } proc_param = &active_proc->proc_param.commander[0]; conn = proc_param->conn; active_proc->proc_initiated_cnt++; if (proc_param->change_mic_mute.mute) { err = bt_micp_mic_ctlr_mute(bt_micp_mic_ctlr_get_by_conn(conn)); } else { err = bt_micp_mic_ctlr_unmute(bt_micp_mic_ctlr_get_by_conn(conn)); } if (err != 0) { LOG_DBG("Failed to set microphone mute state for conn %p: %d", (void *)conn, err); return -ENOEXEC; } return 0; } #if defined(CONFIG_BT_MICP_MIC_CTLR_AICS) static bool valid_change_microphone_gain_param( const struct bt_cap_commander_change_microphone_gain_setting_param *param) { CHECKIF(param == NULL) { LOG_DBG("param is NULL"); return false; } CHECKIF(param->count == 0) { LOG_DBG("Invalid param->count: %u", param->count); return false; } CHECKIF(param->param == NULL) { LOG_DBG("param->param is NULL"); return false; } CHECKIF(param->count > CONFIG_BT_MAX_CONN) { LOG_DBG("param->count (%zu) is larger than CONFIG_BT_MAX_CONN (%d)", param->count, CONFIG_BT_MAX_CONN); return false; } for (size_t i = 0U; i < param->count; i++) { const union bt_cap_set_member *member = ¶m->param[i].member; const struct bt_cap_common_client *client = bt_cap_common_get_client(param->type, member); struct bt_micp_mic_ctlr *mic_ctlr; struct bt_micp_included included; int err; if (client == NULL) { LOG_DBG("Invalid param->param[%zu].member", i); return false; } mic_ctlr = bt_micp_mic_ctlr_get_by_conn(client->conn); if (mic_ctlr == NULL) { LOG_DBG("Microphone control not available for param->param[%zu].member", i); return false; } err = bt_micp_mic_ctlr_included_get(mic_ctlr, &included); if (err != 0 || included.aics_cnt == 0) { LOG_DBG("Microphone audio input control not available for " "param->param[%zu].member", i); return -ENOEXEC; } for (size_t j = 0U; j < i; j++) { const union bt_cap_set_member *other = ¶m->param[j].member; if (other == member) { LOG_DBG("param->param[%zu].member (%p) is duplicated by " "param->param[%zu].member (%p)", j, other, i, member); return false; } } } return true; } static void cap_commander_micp_gain_set_cb(struct bt_aics *inst, int err) { struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); struct bt_conn *conn; int micp_err; LOG_DBG("bt_aics %p", (void *)inst); micp_err = bt_aics_client_conn_get(inst, &conn); if (micp_err != 0) { LOG_ERR("Failed to get conn by aics: %d", micp_err); return; } LOG_DBG("conn %p", (void *)conn); if (!bt_cap_common_conn_in_active_proc(conn)) { /* State change happened outside of a procedure; ignore */ return; } if (err != 0) { LOG_DBG("Failed to set gain: %d", err); bt_cap_common_abort_proc(conn, err); } else { active_proc->proc_done_cnt++; LOG_DBG("Conn %p gain updated (%zu/%zu streams done)", (void *)conn, active_proc->proc_done_cnt, active_proc->proc_cnt); } if (bt_cap_common_proc_is_aborted()) { LOG_DBG("Proc is aborted"); if (bt_cap_common_proc_all_handled()) { LOG_DBG("All handled"); cap_commander_unicast_audio_proc_complete(); } return; } if (!bt_cap_common_proc_is_done()) { const struct bt_cap_commander_proc_param *proc_param; proc_param = &active_proc->proc_param.commander[active_proc->proc_done_cnt]; conn = proc_param->conn; active_proc->proc_initiated_cnt++; err = bt_aics_gain_set(proc_param->change_gain.aics, proc_param->change_gain.gain); if (err != 0) { LOG_DBG("Failed to set gain for conn %p: %d", (void *)conn, err); bt_cap_common_abort_proc(conn, err); cap_commander_unicast_audio_proc_complete(); } } else { cap_commander_unicast_audio_proc_complete(); } } int bt_cap_commander_change_microphone_gain_setting( const struct bt_cap_commander_change_microphone_gain_setting_param *param) { const struct bt_cap_commander_proc_param *proc_param; struct bt_cap_common_proc *active_proc; struct bt_conn *conn; int err; if (bt_cap_common_proc_is_active()) { LOG_DBG("A CAP procedure is already in progress"); return -EBUSY; } if (!valid_change_microphone_gain_param(param)) { return -EINVAL; } bt_cap_common_start_proc(BT_CAP_COMMON_PROC_TYPE_MICROPHONE_GAIN_CHANGE, param->count); mic_ctlr_cb.aics_cb.set_gain = cap_commander_micp_gain_set_cb; if (!micp_callbacks_registered && cap_commander_register_micp_callbacks() != 0) { LOG_DBG("Failed to register MICP callbacks"); return -ENOEXEC; } active_proc = bt_cap_common_get_active_proc(); for (size_t i = 0U; i < param->count; i++) { const union bt_cap_set_member *member = ¶m->param[i].member; struct bt_conn *member_conn = bt_cap_common_get_member_conn(param->type, member); struct bt_micp_mic_ctlr *mic_ctlr; struct bt_micp_included included; if (member_conn == NULL) { LOG_DBG("Invalid param->param[%zu].member", i); return -EINVAL; } mic_ctlr = bt_micp_mic_ctlr_get_by_conn(member_conn); if (mic_ctlr == NULL) { LOG_DBG("Invalid param->param[%zu].member mic_ctlr", i); return -EINVAL; } err = bt_micp_mic_ctlr_included_get(mic_ctlr, &included); if (err != 0 || included.aics_cnt == 0) { LOG_DBG("Invalid param->param[%zu].member aics", i); return -EINVAL; } /* Store the necessary parameters as we cannot assume that the supplied parameters * are kept valid */ active_proc->proc_param.commander[i].conn = member_conn; active_proc->proc_param.commander[i].change_gain.gain = param->param[i].gain; /* TODO: For now we just use the first AICS instance * - How should we handle multiple? */ active_proc->proc_param.commander[i].change_gain.aics = included.aics[0]; } proc_param = &active_proc->proc_param.commander[0]; conn = proc_param->conn; active_proc->proc_initiated_cnt++; err = bt_aics_gain_set(proc_param->change_gain.aics, proc_param->change_gain.gain); if (err != 0) { LOG_DBG("Failed to set gain for conn %p: %d", (void *)conn, err); return -ENOEXEC; } return 0; } #endif /* CONFIG_BT_MICP_MIC_CTLR_AICS */ #endif /* CONFIG_BT_MICP_MIC_CTLR */