| /* |
| * Copyright 2021 HIMSA II K/S - www.himsa.com. Represented by EHIMA - |
| * www.ehima.com |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include <android_bluetooth_flags.h> |
| #include <base/functional/bind.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <lc3.h> |
| |
| #include <deque> |
| #include <map> |
| #include <mutex> |
| #include <optional> |
| |
| #include "audio_hal_client/audio_hal_client.h" |
| #include "audio_hal_interface/le_audio_software.h" |
| #include "bta/csis/csis_types.h" |
| #include "bta_gatt_api.h" |
| #include "bta_gatt_queue.h" |
| #include "bta_groups.h" |
| #include "bta_le_audio_api.h" |
| #include "btif/include/btif_profile_storage.h" |
| #include "btm_iso_api.h" |
| #include "client_parser.h" |
| #include "codec_interface.h" |
| #include "codec_manager.h" |
| #include "common/strings.h" |
| #include "common/time_util.h" |
| #include "content_control_id_keeper.h" |
| #include "device/include/controller.h" |
| #include "devices.h" |
| #include "include/check.h" |
| #include "internal_include/bt_trace.h" |
| #include "internal_include/stack_config.h" |
| #include "le_audio_health_status.h" |
| #include "le_audio_set_configuration_provider.h" |
| #include "le_audio_types.h" |
| #include "le_audio_utils.h" |
| #include "metrics_collector.h" |
| #include "os/log.h" |
| #include "osi/include/osi.h" |
| #include "osi/include/properties.h" |
| #include "stack/btm/btm_sec.h" |
| #include "stack/include/bt_types.h" |
| #include "stack/include/main_thread.h" |
| #include "state_machine.h" |
| #include "storage_helper.h" |
| |
| using base::Closure; |
| using bluetooth::Uuid; |
| using bluetooth::common::ToString; |
| using bluetooth::groups::DeviceGroups; |
| using bluetooth::groups::DeviceGroupsCallbacks; |
| using bluetooth::hci::IsoManager; |
| using bluetooth::hci::iso_manager::cig_create_cmpl_evt; |
| using bluetooth::hci::iso_manager::cig_remove_cmpl_evt; |
| using bluetooth::hci::iso_manager::CigCallbacks; |
| using bluetooth::le_audio::ConnectionState; |
| using bluetooth::le_audio::GroupNodeStatus; |
| using bluetooth::le_audio::GroupStatus; |
| using bluetooth::le_audio::GroupStreamStatus; |
| using bluetooth::le_audio::LeAudioHealthBasedAction; |
| using bluetooth::le_audio::UnicastMonitorModeStatus; |
| using le_audio::CodecManager; |
| using le_audio::ContentControlIdKeeper; |
| using le_audio::DeviceConnectState; |
| using le_audio::DsaMode; |
| using le_audio::DsaModes; |
| using le_audio::LeAudioCodecConfiguration; |
| using le_audio::LeAudioDevice; |
| using le_audio::LeAudioDeviceGroup; |
| using le_audio::LeAudioDeviceGroups; |
| using le_audio::LeAudioDevices; |
| using le_audio::LeAudioGroupStateMachine; |
| using le_audio::LeAudioHealthDeviceStatType; |
| using le_audio::LeAudioHealthGroupStatType; |
| using le_audio::LeAudioHealthStatus; |
| using le_audio::LeAudioRecommendationActionCb; |
| using le_audio::LeAudioSinkAudioHalClient; |
| using le_audio::LeAudioSourceAudioHalClient; |
| using le_audio::types::ase; |
| using le_audio::types::AseState; |
| using le_audio::types::AudioContexts; |
| using le_audio::types::AudioLocations; |
| using le_audio::types::BidirectionalPair; |
| using le_audio::types::DataPathState; |
| using le_audio::types::hdl_pair; |
| using le_audio::types::kDefaultScanDurationS; |
| using le_audio::types::kLeAudioContextAllBidir; |
| using le_audio::types::kLeAudioContextAllRemoteSinkOnly; |
| using le_audio::types::kLeAudioContextAllRemoteSource; |
| using le_audio::types::kLeAudioContextAllTypesArray; |
| using le_audio::types::LeAudioContextType; |
| using le_audio::utils::GetAudioContextsFromSinkMetadata; |
| using le_audio::utils::GetAudioContextsFromSourceMetadata; |
| |
| /* Enums */ |
| enum class AudioReconfigurationResult { |
| RECONFIGURATION_NEEDED = 0x00, |
| RECONFIGURATION_NOT_NEEDED, |
| RECONFIGURATION_NOT_POSSIBLE |
| }; |
| |
| enum class AudioState { |
| IDLE = 0x00, |
| READY_TO_START, |
| STARTED, |
| READY_TO_RELEASE, |
| RELEASING, |
| }; |
| |
| std::ostream& operator<<(std::ostream& os, |
| const AudioReconfigurationResult& state) { |
| switch (state) { |
| case AudioReconfigurationResult::RECONFIGURATION_NEEDED: |
| os << "RECONFIGURATION_NEEDED"; |
| break; |
| case AudioReconfigurationResult::RECONFIGURATION_NOT_NEEDED: |
| os << "RECONFIGURATION_NOT_NEEDED"; |
| break; |
| case AudioReconfigurationResult::RECONFIGURATION_NOT_POSSIBLE: |
| os << "RECONFIGRATION_NOT_POSSIBLE"; |
| break; |
| default: |
| os << "UNKNOWN"; |
| break; |
| } |
| return os; |
| } |
| |
| std::ostream& operator<<(std::ostream& os, const AudioState& audio_state) { |
| switch (audio_state) { |
| case AudioState::IDLE: |
| os << "IDLE"; |
| break; |
| case AudioState::READY_TO_START: |
| os << "READY_TO_START"; |
| break; |
| case AudioState::STARTED: |
| os << "STARTED"; |
| break; |
| case AudioState::READY_TO_RELEASE: |
| os << "READY_TO_RELEASE"; |
| break; |
| case AudioState::RELEASING: |
| os << "RELEASING"; |
| break; |
| default: |
| os << "UNKNOWN"; |
| break; |
| } |
| return os; |
| } |
| |
| namespace { |
| void le_audio_gattc_callback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data); |
| |
| static void le_audio_health_status_callback(const RawAddress& addr, |
| int group_id, |
| LeAudioHealthBasedAction action); |
| |
| class LeAudioClientImpl; |
| LeAudioClientImpl* instance; |
| std::mutex instance_mutex; |
| LeAudioSourceAudioHalClient::Callbacks* audioSinkReceiver; |
| LeAudioSinkAudioHalClient::Callbacks* audioSourceReceiver; |
| CigCallbacks* stateMachineHciCallbacks; |
| LeAudioGroupStateMachine::Callbacks* stateMachineCallbacks; |
| DeviceGroupsCallbacks* device_group_callbacks; |
| LeAudioIsoDataCallback* iso_data_callback; |
| |
| /* |
| * Coordinatet Set Identification Profile (CSIP) based on CSIP 1.0 |
| * and Coordinatet Set Identification Service (CSIS) 1.0 |
| * |
| * CSIP allows to organize audio servers into sets e.g. Stereo Set, 5.1 Set |
| * and speed up connecting it. |
| * |
| * Since leaudio has already grouping API it was decided to integrate here CSIS |
| * and allow it to group devices semi-automatically. |
| * |
| * Flow: |
| * If connected device contains CSIS services, and it is included into CAP |
| * service, implementation marks device as a set member and waits for the |
| * bta/csis to learn about groups and notify implementation about assigned |
| * group id. |
| * |
| */ |
| /* LeAudioClientImpl class represents main implementation class for le audio |
| * feature in stack. This class implements GATT, le audio and ISO related parts. |
| * |
| * This class is represented in single instance and manages a group of devices, |
| * and devices. All devices calls back static method from it and are dispatched |
| * to target receivers (e.g. ASEs, devices). |
| * |
| * This instance also implements a LeAudioClient which is a upper layer API. |
| * Also LeAudioClientCallbacks are callbacks for upper layer. |
| * |
| * This class may be bonded with Test socket which allows to drive an instance |
| * for test purposes. |
| */ |
| class LeAudioClientImpl : public LeAudioClient { |
| public: |
| ~LeAudioClientImpl() { |
| alarm_free(close_vbc_timeout_); |
| alarm_free(disable_timer_); |
| alarm_free(suspend_timeout_); |
| }; |
| |
| LeAudioClientImpl( |
| bluetooth::le_audio::LeAudioClientCallbacks* callbacks_, |
| LeAudioGroupStateMachine::Callbacks* state_machine_callbacks_, |
| base::Closure initCb) |
| : gatt_if_(0), |
| callbacks_(callbacks_), |
| active_group_id_(bluetooth::groups::kGroupUnknown), |
| configuration_context_type_(LeAudioContextType::UNINITIALIZED), |
| local_metadata_context_types_( |
| {.sink = AudioContexts(), .source = AudioContexts()}), |
| stream_setup_start_timestamp_(0), |
| stream_setup_end_timestamp_(0), |
| audio_receiver_state_(AudioState::IDLE), |
| audio_sender_state_(AudioState::IDLE), |
| in_call_(false), |
| in_voip_call_(false), |
| sink_monitor_mode_(false), |
| sink_monitor_notified_status_(std::nullopt), |
| source_monitor_mode_(false), |
| current_source_codec_config({0, 0, 0, 0}), |
| current_sink_codec_config({0, 0, 0, 0}), |
| le_audio_source_hal_client_(nullptr), |
| le_audio_sink_hal_client_(nullptr), |
| close_vbc_timeout_(alarm_new("LeAudioCloseVbcTimeout")), |
| suspend_timeout_(alarm_new("LeAudioSuspendTimeout")), |
| disable_timer_(alarm_new("LeAudioDisableTimer")) { |
| LeAudioGroupStateMachine::Initialize(state_machine_callbacks_); |
| groupStateMachine_ = LeAudioGroupStateMachine::Get(); |
| |
| if (bluetooth::common::InitFlags:: |
| IsTargetedAnnouncementReconnectionMode()) { |
| LOG_INFO(" Reconnection mode: TARGETED_ANNOUNCEMENTS"); |
| reconnection_mode_ = BTM_BLE_BKG_CONNECT_TARGETED_ANNOUNCEMENTS; |
| } else { |
| LOG_INFO(" Reconnection mode: ALLOW_LIST"); |
| reconnection_mode_ = BTM_BLE_BKG_CONNECT_ALLOW_LIST; |
| } |
| |
| if (IS_FLAG_ENABLED(leaudio_enable_health_based_actions)) { |
| LOG_INFO("Loading health status module"); |
| leAudioHealthStatus_ = LeAudioHealthStatus::Get(); |
| leAudioHealthStatus_->RegisterCallback( |
| base::BindRepeating(le_audio_health_status_callback)); |
| } |
| |
| BTA_GATTC_AppRegister( |
| le_audio_gattc_callback, |
| base::Bind( |
| [](base::Closure initCb, uint8_t client_id, uint8_t status) { |
| if (status != GATT_SUCCESS) { |
| LOG(ERROR) << "Can't start LeAudio profile - no gatt " |
| "clients left!"; |
| return; |
| } |
| instance->gatt_if_ = client_id; |
| initCb.Run(); |
| }, |
| initCb), |
| true); |
| |
| DeviceGroups::Get()->Initialize(device_group_callbacks); |
| } |
| |
| void ReconfigureAfterVbcClose() { |
| LOG_DEBUG("VBC close timeout"); |
| |
| if (IsInVoipCall()) { |
| SetInVoipCall(false); |
| } |
| |
| auto group = aseGroups_.FindById(active_group_id_); |
| if (!group) { |
| LOG_ERROR("Invalid group: %d", active_group_id_); |
| return; |
| } |
| |
| /* Reconfiguration to non requiring source scenario */ |
| if (sink_monitor_mode_) { |
| notifyAudioLocalSink(UnicastMonitorModeStatus::STREAMING_SUSPENDED); |
| } |
| |
| /* For sonification events we don't really need to reconfigure to HQ |
| * configuration, but if the previous configuration was for HQ Media, |
| * we might want to go back to that scenario. |
| */ |
| |
| if ((configuration_context_type_ != LeAudioContextType::MEDIA) && |
| (configuration_context_type_ != LeAudioContextType::GAME)) { |
| LOG_INFO( |
| "Keeping the old configuration as no HQ Media playback is needed " |
| "right now."); |
| return; |
| } |
| |
| /* Test the existing metadata against the recent availability */ |
| local_metadata_context_types_.source &= |
| group->GetAvailableContexts(le_audio::types::kLeAudioDirectionSink); |
| if (local_metadata_context_types_.source.none()) { |
| LOG_WARN("invalid/unknown context metadata, using 'MEDIA' instead"); |
| local_metadata_context_types_.source = |
| AudioContexts(LeAudioContextType::MEDIA); |
| } |
| |
| /* Choose the right configuration context */ |
| auto new_configuration_context = |
| ChooseConfigurationContextType(local_metadata_context_types_.source); |
| |
| LOG_DEBUG("new_configuration_context= %s", |
| ToString(new_configuration_context).c_str()); |
| ReconfigureOrUpdateMetadata(group, new_configuration_context, |
| {.sink = local_metadata_context_types_.source, |
| .source = local_metadata_context_types_.sink}); |
| } |
| |
| void StartVbcCloseTimeout() { |
| if (alarm_is_scheduled(close_vbc_timeout_)) { |
| StopVbcCloseTimeout(); |
| } |
| |
| static const uint64_t timeoutMs = 2000; |
| LOG_DEBUG("Start VBC close timeout with %lu ms", |
| static_cast<unsigned long>(timeoutMs)); |
| |
| alarm_set_on_mloop( |
| close_vbc_timeout_, timeoutMs, |
| [](void*) { |
| if (instance) instance->ReconfigureAfterVbcClose(); |
| }, |
| nullptr); |
| } |
| |
| void StopVbcCloseTimeout() { |
| if (alarm_is_scheduled(close_vbc_timeout_)) { |
| LOG_DEBUG("Cancel VBC close timeout"); |
| alarm_cancel(close_vbc_timeout_); |
| } |
| } |
| |
| void AseInitialStateReadRequest(LeAudioDevice* leAudioDevice) { |
| int ases_num = leAudioDevice->ases_.size(); |
| void* notify_flag_ptr = NULL; |
| |
| for (int i = 0; i < ases_num; i++) { |
| /* Last read ase characteristic should issue connected state callback |
| * to upper layer |
| */ |
| |
| if (leAudioDevice->notify_connected_after_read_ && |
| (i == (ases_num - 1))) { |
| notify_flag_ptr = |
| INT_TO_PTR(leAudioDevice->notify_connected_after_read_); |
| } |
| |
| BtaGattQueue::ReadCharacteristic(leAudioDevice->conn_id_, |
| leAudioDevice->ases_[i].hdls.val_hdl, |
| OnGattReadRspStatic, notify_flag_ptr); |
| } |
| } |
| |
| void OnGroupAddedCb(const RawAddress& address, const bluetooth::Uuid& uuid, |
| int group_id) { |
| LOG(INFO) << __func__ << " address: " << ADDRESS_TO_LOGGABLE_STR(address) |
| << " group uuid " << uuid |
| << " group_id: " << group_id; |
| |
| /* We are interested in the groups which are in the context of CAP */ |
| if (uuid != le_audio::uuid::kCapServiceUuid) return; |
| |
| LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address); |
| if (!leAudioDevice) return; |
| if (leAudioDevice->group_id_ != bluetooth::groups::kGroupUnknown) { |
| LOG(INFO) << __func__ |
| << " group already set: " << leAudioDevice->group_id_; |
| return; |
| } |
| |
| group_add_node(group_id, address); |
| } |
| |
| /* If device participates in streaming the group, it has to be stopped and |
| * group needs to be reconfigured if needed to new configuration without |
| * considering this removing device. |
| */ |
| void SetDeviceAsRemovePendingAndStopGroup(LeAudioDevice* leAudioDevice) { |
| LOG_INFO("device %s", ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_)); |
| leAudioDevice->SetConnectionState(DeviceConnectState::REMOVING); |
| leAudioDevice->closing_stream_for_disconnection_ = true; |
| GroupStop(leAudioDevice->group_id_); |
| } |
| |
| void OnGroupMemberAddedCb(const RawAddress& address, int group_id) { |
| LOG(INFO) << __func__ << " address: " << ADDRESS_TO_LOGGABLE_STR(address) |
| << " group_id: " << group_id; |
| |
| auto group = aseGroups_.FindById(group_id); |
| if (!group) { |
| LOG(ERROR) << __func__ << " Not interested in group id: " << group_id; |
| return; |
| } |
| |
| LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address); |
| if (!leAudioDevice) return; |
| if (leAudioDevice->group_id_ != bluetooth::groups::kGroupUnknown) { |
| LOG(INFO) << __func__ |
| << " group already set: " << leAudioDevice->group_id_; |
| return; |
| } |
| |
| if (leAudioHealthStatus_) { |
| leAudioHealthStatus_->AddStatisticForDevice( |
| leAudioDevice, LeAudioHealthDeviceStatType::VALID_CSIS); |
| } |
| |
| group_add_node(group_id, address); |
| } |
| |
| void OnGroupMemberRemovedCb(const RawAddress& address, int group_id) { |
| LOG(INFO) << __func__ << " address: " << ADDRESS_TO_LOGGABLE_STR(address) |
| << " group_id: " << group_id; |
| |
| LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address); |
| if (!leAudioDevice) return; |
| if (leAudioDevice->group_id_ != group_id) { |
| LOG_WARN("Device: %s not assigned to the group.", |
| ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_)); |
| return; |
| } |
| |
| LeAudioDeviceGroup* group = aseGroups_.FindById(group_id); |
| if (group == NULL) { |
| LOG(INFO) << __func__ |
| << " device not in the group: " |
| << ADDRESS_TO_LOGGABLE_STR(leAudioDevice->address_) |
| << ", " << group_id; |
| return; |
| } |
| |
| if (leAudioHealthStatus_) { |
| leAudioHealthStatus_->RemoveStatistics(address, group->group_id_); |
| } |
| |
| if (leAudioDevice->HaveActiveAse()) { |
| SetDeviceAsRemovePendingAndStopGroup(leAudioDevice); |
| return; |
| } |
| |
| group_remove_node(group, address); |
| } |
| |
| /* This callback happens if kLeAudioDeviceSetStateTimeoutMs timeout happens |
| * during transition from origin to target state |
| */ |
| void OnLeAudioDeviceSetStateTimeout(int group_id) { |
| LeAudioDeviceGroup* group = aseGroups_.FindById(group_id); |
| |
| if (!group) { |
| /* Group removed */ |
| return; |
| } |
| |
| bool check_if_recovery_needed = |
| group->GetTargetState() == AseState::BTA_LE_AUDIO_ASE_STATE_IDLE; |
| |
| if (leAudioHealthStatus_) { |
| leAudioHealthStatus_->AddStatisticForGroup( |
| group, LeAudioHealthGroupStatType::STREAM_CREATE_SIGNALING_FAILED); |
| } |
| |
| LOG_ERROR( |
| " State not achieved on time for group: group id %d, current state %s, " |
| "target state: %s, check_if_recovery_needed: %d", |
| group_id, ToString(group->GetState()).c_str(), |
| ToString(group->GetTargetState()).c_str(), check_if_recovery_needed); |
| group->SetTargetState(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE); |
| group->ClearAllCises(); |
| group->PrintDebugState(); |
| |
| /* There is an issue with a setting up stream or any other operation which |
| * are gatt operations. It means peer is not responsable. Lets close ACL |
| */ |
| CancelStreamingRequest(); |
| LeAudioDevice* leAudioDevice = group->GetFirstActiveDevice(); |
| if (leAudioDevice == nullptr) { |
| LOG_ERROR(" Shouldn't be called without an active device."); |
| leAudioDevice = group->GetFirstDevice(); |
| if (leAudioDevice == nullptr) { |
| LOG_ERROR(" Front device is null. Number of devices: %d", |
| group->Size()); |
| return; |
| } |
| } |
| |
| /* If Timeout happens on stream close and stream is closing just for the |
| * purpose of device disconnection, do not bother with recovery mode |
| */ |
| bool recovery = true; |
| if (check_if_recovery_needed) { |
| for (auto tmpDevice = leAudioDevice; tmpDevice != nullptr; |
| tmpDevice = group->GetNextActiveDevice(tmpDevice)) { |
| if (tmpDevice->closing_stream_for_disconnection_) { |
| recovery = false; |
| break; |
| } |
| } |
| } |
| |
| do { |
| DisconnectDevice(leAudioDevice, true, recovery); |
| leAudioDevice = group->GetNextActiveDevice(leAudioDevice); |
| } while (leAudioDevice); |
| |
| if (recovery) { |
| /* Both devices will be disconnected soon. Notify upper layer that group |
| * is inactive */ |
| groupSetAndNotifyInactive(); |
| } |
| } |
| |
| void OnDeviceAutonomousStateTransitionTimeout(LeAudioDevice* leAudioDevice) { |
| LOG_ERROR("Device %s, failed to complete autonomous transition", |
| ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_)); |
| DisconnectDevice(leAudioDevice, true); |
| } |
| |
| void UpdateLocationsAndContextsAvailability(LeAudioDeviceGroup* group) { |
| bool group_conf_changed = group->ReloadAudioLocations(); |
| group_conf_changed |= group->ReloadAudioDirections(); |
| group_conf_changed |= group->UpdateAudioContextAvailability(); |
| if (group_conf_changed) { |
| /* All the configurations should be recalculated for the new conditions */ |
| group->InvalidateCachedConfigurations(); |
| callbacks_->OnAudioConf(group->audio_directions_, group->group_id_, |
| group->snk_audio_locations_.to_ulong(), |
| group->src_audio_locations_.to_ulong(), |
| group->GetAvailableContexts().value()); |
| } |
| } |
| |
| void UpdateLocationsAndContextsAvailability(int group_id) { |
| LeAudioDeviceGroup* group = aseGroups_.FindById(group_id); |
| if (group) { |
| UpdateLocationsAndContextsAvailability(group); |
| } |
| } |
| |
| void SuspendedForReconfiguration() { |
| if (audio_sender_state_ > AudioState::IDLE) { |
| LeAudioLogHistory::Get()->AddLogHistory( |
| kLogBtCallAf, active_group_id_, RawAddress::kEmpty, |
| kLogAfSuspendForReconfig + "LocalSource", |
| "r_state: " + ToString(audio_receiver_state_) + |
| "s_state: " + ToString(audio_sender_state_)); |
| le_audio_source_hal_client_->SuspendedForReconfiguration(); |
| } |
| if (audio_receiver_state_ > AudioState::IDLE) { |
| LeAudioLogHistory::Get()->AddLogHistory( |
| kLogBtCallAf, active_group_id_, RawAddress::kEmpty, |
| kLogAfSuspendForReconfig + "LocalSink", |
| "r_state: " + ToString(audio_receiver_state_) + |
| "s_state: " + ToString(audio_sender_state_)); |
| le_audio_sink_hal_client_->SuspendedForReconfiguration(); |
| } |
| } |
| |
| void ReconfigurationComplete(uint8_t directions) { |
| if (directions & le_audio::types::kLeAudioDirectionSink) { |
| LeAudioLogHistory::Get()->AddLogHistory( |
| kLogBtCallAf, active_group_id_, RawAddress::kEmpty, |
| kLogAfReconfigComplete + "LocalSource", |
| "r_state: " + ToString(audio_receiver_state_) + |
| "s_state: " + ToString(audio_sender_state_)); |
| |
| le_audio_source_hal_client_->ReconfigurationComplete(); |
| } |
| if (directions & le_audio::types::kLeAudioDirectionSource) { |
| LeAudioLogHistory::Get()->AddLogHistory( |
| kLogBtCallAf, active_group_id_, RawAddress::kEmpty, |
| kLogAfReconfigComplete + "LocalSink", |
| "r_state: " + ToString(audio_receiver_state_) + |
| "s_state: " + ToString(audio_sender_state_)); |
| |
| le_audio_sink_hal_client_->ReconfigurationComplete(); |
| } |
| } |
| |
| void CancelLocalAudioSourceStreamingRequest() { |
| le_audio_source_hal_client_->CancelStreamingRequest(); |
| |
| LeAudioLogHistory::Get()->AddLogHistory( |
| kLogBtCallAf, active_group_id_, RawAddress::kEmpty, |
| kLogAfCancel + "LocalSource", |
| "s_state: " + ToString(audio_sender_state_)); |
| |
| audio_sender_state_ = AudioState::IDLE; |
| } |
| |
| void CancelLocalAudioSinkStreamingRequest() { |
| le_audio_sink_hal_client_->CancelStreamingRequest(); |
| |
| LeAudioLogHistory::Get()->AddLogHistory( |
| kLogBtCallAf, active_group_id_, RawAddress::kEmpty, |
| kLogAfCancel + "LocalSink", |
| "s_state: " + ToString(audio_receiver_state_)); |
| |
| audio_receiver_state_ = AudioState::IDLE; |
| } |
| |
| void CancelStreamingRequest() { |
| if (audio_sender_state_ >= AudioState::READY_TO_START) { |
| CancelLocalAudioSourceStreamingRequest(); |
| } |
| |
| if (audio_receiver_state_ >= AudioState::READY_TO_START) { |
| CancelLocalAudioSinkStreamingRequest(); |
| } |
| } |
| |
| void group_add_node(const int group_id, const RawAddress& address, |
| bool update_group_module = false) { |
| LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address); |
| LeAudioDeviceGroup* new_group; |
| LeAudioDeviceGroup* old_group = nullptr; |
| int old_group_id = bluetooth::groups::kGroupUnknown; |
| |
| if (!leAudioDevice) { |
| /* TODO This part possible to remove as this is to handle adding device to |
| * the group which is unknown and not connected. |
| */ |
| LOG(INFO) << __func__ << ", leAudioDevice unknown , address: " |
| << ADDRESS_TO_LOGGABLE_STR(address) |
| << " group: " << loghex(group_id); |
| |
| if (group_id == bluetooth::groups::kGroupUnknown) return; |
| |
| LOG(INFO) << __func__ << "Set member adding ..."; |
| leAudioDevices_.Add(address, DeviceConnectState::CONNECTING_BY_USER); |
| leAudioDevice = leAudioDevices_.FindByAddress(address); |
| } else { |
| if (leAudioDevice->group_id_ != bluetooth::groups::kGroupUnknown) { |
| old_group = aseGroups_.FindById(leAudioDevice->group_id_); |
| old_group_id = old_group->group_id_; |
| } |
| } |
| |
| auto id = DeviceGroups::Get()->GetGroupId(address, |
| le_audio::uuid::kCapServiceUuid); |
| if (group_id == bluetooth::groups::kGroupUnknown) { |
| if (id == bluetooth::groups::kGroupUnknown) { |
| DeviceGroups::Get()->AddDevice(address, |
| le_audio::uuid::kCapServiceUuid); |
| /* We will get back here when group will be created */ |
| return; |
| } |
| |
| new_group = aseGroups_.Add(id); |
| if (!new_group) { |
| LOG(ERROR) << __func__ |
| << ", can't create group - group is already there?"; |
| return; |
| } |
| } else { |
| ASSERT_LOG(id == group_id, |
| " group id missmatch? leaudio id: %d, groups module %d", |
| group_id, id); |
| new_group = aseGroups_.FindById(group_id); |
| if (!new_group) { |
| new_group = aseGroups_.Add(group_id); |
| } else { |
| if (new_group->IsDeviceInTheGroup(leAudioDevice)) return; |
| } |
| } |
| |
| LOG_DEBUG("New group %p, id: %d", new_group, new_group->group_id_); |
| |
| /* If device was in the group and it was not removed by the application, |
| * lets do it now |
| */ |
| if (old_group) group_remove_node(old_group, address, update_group_module); |
| |
| new_group->AddNode(leAudioDevices_.GetByAddress(address)); |
| |
| callbacks_->OnGroupNodeStatus(address, new_group->group_id_, |
| GroupNodeStatus::ADDED); |
| |
| /* If device is connected and added to the group, lets read ASE states */ |
| if (leAudioDevice->conn_id_ != GATT_INVALID_CONN_ID) |
| AseInitialStateReadRequest(leAudioDevice); |
| |
| /* Group may be destroyed once moved its last node to new group, so don't |
| * use `old_group` pointer anymore. |
| * Removing node from group requires updating group context availability */ |
| UpdateLocationsAndContextsAvailability(old_group_id); |
| |
| if (leAudioDevice->GetConnectionState() == DeviceConnectState::CONNECTED) { |
| UpdateLocationsAndContextsAvailability(new_group); |
| } |
| } |
| |
| void GroupAddNode(const int group_id, const RawAddress& address) override { |
| auto id = DeviceGroups::Get()->GetGroupId(address, |
| le_audio::uuid::kCapServiceUuid); |
| if (id == group_id) return; |
| |
| if (id != bluetooth::groups::kGroupUnknown) { |
| DeviceGroups::Get()->RemoveDevice(address, id); |
| } |
| |
| DeviceGroups::Get()->AddDevice(address, le_audio::uuid::kCapServiceUuid, |
| group_id); |
| } |
| |
| void remove_group_if_possible(LeAudioDeviceGroup* group) { |
| if (!group) { |
| LOG_DEBUG("group is null"); |
| return; |
| } |
| LOG_DEBUG("Group %p, id: %d, size: %d, is cig_state %s", group, |
| group->group_id_, group->Size(), |
| ToString(group->cig.GetState()).c_str()); |
| if (group->IsEmpty() && |
| (group->cig.GetState() == le_audio::types::CigState::NONE)) { |
| lastNotifiedGroupStreamStatusMap_.erase(group->group_id_); |
| aseGroups_.Remove(group->group_id_); |
| } |
| } |
| |
| void group_remove_node(LeAudioDeviceGroup* group, const RawAddress& address, |
| bool update_group_module = false) { |
| int group_id = group->group_id_; |
| group->RemoveNode(leAudioDevices_.GetByAddress(address)); |
| |
| if (update_group_module) { |
| int groups_group_id = DeviceGroups::Get()->GetGroupId( |
| address, le_audio::uuid::kCapServiceUuid); |
| if (groups_group_id == group_id) { |
| DeviceGroups::Get()->RemoveDevice(address, group_id); |
| } |
| } |
| |
| callbacks_->OnGroupNodeStatus(address, group_id, GroupNodeStatus::REMOVED); |
| |
| /* Remove group if this was the last leAudioDevice in this group */ |
| if (group->IsEmpty()) { |
| remove_group_if_possible(group); |
| return; |
| } |
| |
| /* Removing node from group requires updating group context availability */ |
| UpdateLocationsAndContextsAvailability(group); |
| } |
| |
| void GroupRemoveNode(const int group_id, const RawAddress& address) override { |
| LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address); |
| LeAudioDeviceGroup* group = aseGroups_.FindById(group_id); |
| |
| LOG(INFO) << __func__ << " group_id: " << group_id |
| << " address: " << ADDRESS_TO_LOGGABLE_STR(address); |
| |
| if (!leAudioDevice) { |
| LOG(ERROR) << __func__ |
| << ", Skipping unknown leAudioDevice, address: " |
| << ADDRESS_TO_LOGGABLE_STR(address); |
| return; |
| } |
| |
| if (leAudioDevice->group_id_ != group_id) { |
| LOG(ERROR) << __func__ << "Device is not in group_id: " << group_id |
| << ", but in group_id: " << leAudioDevice->group_id_; |
| return; |
| } |
| |
| if (group == NULL) { |
| LOG(ERROR) << __func__ << " device not in the group ?!"; |
| return; |
| } |
| |
| if (leAudioDevice->HaveActiveAse()) { |
| SetDeviceAsRemovePendingAndStopGroup(leAudioDevice); |
| return; |
| } |
| |
| group_remove_node(group, address, true); |
| } |
| |
| AudioContexts ChooseMetadataContextType(AudioContexts metadata_context_type) { |
| /* This function takes already filtered contexts which we are plannig to use |
| * in the Enable or UpdateMetadata command. |
| * Note we are not changing stream configuration here, but just the list of |
| * the contexts in the Metadata which will be provide to remote side. |
| * Ideally, we should send all the bits we have, but not all headsets like |
| * it. |
| */ |
| if (osi_property_get_bool(kAllowMultipleContextsInMetadata, true)) { |
| return metadata_context_type; |
| } |
| |
| LOG_DEBUG("Converting to single context type: %s", |
| metadata_context_type.to_string().c_str()); |
| |
| /* Mini policy */ |
| if (metadata_context_type.any()) { |
| LeAudioContextType context_priority_list[] = { |
| /* Highest priority first */ |
| LeAudioContextType::CONVERSATIONAL, |
| LeAudioContextType::RINGTONE, |
| LeAudioContextType::LIVE, |
| LeAudioContextType::VOICEASSISTANTS, |
| LeAudioContextType::GAME, |
| LeAudioContextType::MEDIA, |
| LeAudioContextType::EMERGENCYALARM, |
| LeAudioContextType::ALERTS, |
| LeAudioContextType::INSTRUCTIONAL, |
| LeAudioContextType::NOTIFICATIONS, |
| LeAudioContextType::SOUNDEFFECTS, |
| }; |
| for (auto ct : context_priority_list) { |
| if (metadata_context_type.test(ct)) { |
| LOG_DEBUG("Converted to single context type: %s", |
| ToString(ct).c_str()); |
| return AudioContexts(ct); |
| } |
| } |
| } |
| |
| /* Fallback to BAP mandated context type */ |
| LOG_WARN("Invalid/unknown context, using 'UNSPECIFIED'"); |
| return AudioContexts(LeAudioContextType::UNSPECIFIED); |
| } |
| |
| /* Return true if stream is started */ |
| bool GroupStream(int group_id, LeAudioContextType configuration_context_type, |
| BidirectionalPair<AudioContexts> remote_contexts) { |
| LeAudioDeviceGroup* group = aseGroups_.FindById(group_id); |
| |
| LOG_DEBUG("configuration_context_type= %s", |
| ToString(configuration_context_type).c_str()); |
| |
| DLOG(INFO) << __func__; |
| if (configuration_context_type >= LeAudioContextType::RFU) { |
| LOG(ERROR) << __func__ << ", stream context type is not supported: " |
| << ToHexString(configuration_context_type); |
| return false; |
| } |
| |
| if (!group) { |
| LOG(ERROR) << __func__ << ", unknown group id: " << group_id; |
| return false; |
| } |
| |
| LOG_DEBUG("group state=%s, target_state=%s", |
| ToString(group->GetState()).c_str(), |
| ToString(group->GetTargetState()).c_str()); |
| |
| if (!group->IsAnyDeviceConnected()) { |
| LOG(ERROR) << __func__ << ", group " << group_id << " is not connected "; |
| return false; |
| } |
| |
| /* Check if any group is in the transition state. If so, we don't allow to |
| * start new group to stream |
| */ |
| if (group->IsInTransition()) { |
| /* WARNING: Due to group state machine limitations, we should not |
| * interrupt any ongoing transition. We will check if another |
| * reconfiguration is needed once the group reaches streaming state. |
| */ |
| LOG_WARN( |
| "Group is already in the transition state. Waiting for the target " |
| "state to be reached."); |
| return false; |
| } |
| |
| /* Make sure we do not take the local sink metadata when only the local |
| * source scenario is about to be started (e.g. MEDIA). |
| */ |
| if (!kLeAudioContextAllBidir.test(configuration_context_type)) { |
| remote_contexts.source.clear(); |
| } |
| |
| /* Do not put the TBS CCID when not using Telecom for the VoIP calls. */ |
| auto ccid_contexts = remote_contexts; |
| if (IsInVoipCall() && !IsInCall()) { |
| ccid_contexts.sink.unset(LeAudioContextType::CONVERSATIONAL); |
| ccid_contexts.source.unset(LeAudioContextType::CONVERSATIONAL); |
| } |
| |
| BidirectionalPair<std::vector<uint8_t>> ccids = { |
| .sink = ContentControlIdKeeper::GetInstance()->GetAllCcids( |
| ccid_contexts.sink), |
| .source = ContentControlIdKeeper::GetInstance()->GetAllCcids( |
| ccid_contexts.source)}; |
| if (group->IsPendingConfiguration()) { |
| return groupStateMachine_->ConfigureStream( |
| group, configuration_context_type_, remote_contexts, ccids); |
| } else if (group->GetState() != |
| AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { |
| stream_setup_start_timestamp_ = |
| bluetooth::common::time_get_os_boottime_us(); |
| } |
| |
| /* If assistant have some connected delegators that needs to be informed |
| * when there would be request to stream unicast. |
| */ |
| if (IS_FLAG_ENABLED(leaudio_broadcast_audio_handover_policies) && |
| !sink_monitor_mode_ && source_monitor_mode_ && !group->IsStreaming()) { |
| callbacks_->OnUnicastMonitorModeStatus( |
| le_audio::types::kLeAudioDirectionSource, |
| UnicastMonitorModeStatus::STREAMING_REQUESTED); |
| } |
| |
| bool result = groupStateMachine_->StartStream( |
| group, configuration_context_type, remote_contexts, ccids); |
| |
| return result; |
| } |
| |
| void GroupStream(const int group_id, uint16_t context_type) override { |
| BidirectionalPair<AudioContexts> initial_contexts = { |
| AudioContexts(context_type), AudioContexts(context_type)}; |
| GroupStream(group_id, LeAudioContextType(context_type), initial_contexts); |
| } |
| |
| void GroupSuspend(const int group_id) override { |
| LeAudioDeviceGroup* group = aseGroups_.FindById(group_id); |
| |
| if (!group) { |
| LOG(ERROR) << __func__ << ", unknown group id: " << group_id; |
| return; |
| } |
| |
| if (!group->IsAnyDeviceConnected()) { |
| LOG(ERROR) << __func__ << ", group is not connected"; |
| return; |
| } |
| |
| if (group->IsInTransition()) { |
| LOG_INFO(", group is in transition from: %s to: %s", |
| ToString(group->GetState()).c_str(), |
| ToString(group->GetTargetState()).c_str()); |
| return; |
| } |
| |
| if (group->GetState() != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { |
| LOG_ERROR(", invalid current state of group: %s", |
| ToString(group->GetState()).c_str()); |
| return; |
| } |
| |
| groupStateMachine_->SuspendStream(group); |
| } |
| |
| void GroupStop(const int group_id) override { |
| LeAudioDeviceGroup* group = aseGroups_.FindById(group_id); |
| |
| if (!group) { |
| LOG(ERROR) << __func__ << ", unknown group id: " << group_id; |
| return; |
| } |
| |
| if (group->IsEmpty()) { |
| LOG(ERROR) << __func__ << ", group is empty"; |
| return; |
| } |
| |
| if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_IDLE) { |
| if (group->GetTargetState() != AseState::BTA_LE_AUDIO_ASE_STATE_IDLE) { |
| LOG_WARN(" group %d was about to stream, but got canceled: %s", |
| group_id, ToString(group->GetTargetState()).c_str()); |
| group->SetTargetState(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE); |
| } else { |
| LOG_WARN(", group %d already stopped: %s", group_id, |
| ToString(group->GetState()).c_str()); |
| } |
| return; |
| } |
| |
| groupStateMachine_->StopStream(group); |
| } |
| |
| void GroupDestroy(const int group_id) override { |
| LeAudioDeviceGroup* group = aseGroups_.FindById(group_id); |
| |
| if (!group) { |
| LOG(ERROR) << __func__ << ", unknown group id: " << group_id; |
| return; |
| } |
| |
| // Disconnect and remove each device within the group |
| auto* dev = group->GetFirstDevice(); |
| while (dev) { |
| auto* next_dev = group->GetNextDevice(dev); |
| RemoveDevice(dev->address_); |
| dev = next_dev; |
| } |
| } |
| |
| void SetCodecConfigPreference( |
| int group_id, |
| bluetooth::le_audio::btle_audio_codec_config_t input_codec_config, |
| bluetooth::le_audio::btle_audio_codec_config_t output_codec_config) |
| override { |
| // TODO Implement |
| } |
| |
| void SetCcidInformation(int ccid, int context_type) override { |
| LOG_DEBUG("Ccid: %d, context type %d", ccid, context_type); |
| |
| ContentControlIdKeeper::GetInstance()->SetCcid(AudioContexts(context_type), |
| ccid); |
| } |
| |
| void SetInCall(bool in_call) override { |
| LOG_DEBUG("in_call: %d", in_call); |
| in_call_ = in_call; |
| } |
| |
| bool IsInCall() override { return in_call_; } |
| |
| void SetInVoipCall(bool in_call) override { |
| LOG_DEBUG("in_voip_call: %d", in_call); |
| in_voip_call_ = in_call; |
| } |
| |
| bool IsInVoipCall() override { return in_voip_call_; } |
| |
| bool IsInStreaming() override { |
| return audio_sender_state_ == AudioState::STARTED || |
| audio_receiver_state_ == AudioState::STARTED; |
| } |
| |
| void SetUnicastMonitorMode(uint8_t direction, bool enable) override { |
| if (!IS_FLAG_ENABLED(leaudio_broadcast_audio_handover_policies)) { |
| LOG_WARN("Monitor mode is disabled, Set Unicast Monitor mode is ignored"); |
| return; |
| } |
| |
| if (direction == le_audio::types::kLeAudioDirectionSink) { |
| /* Cleanup Sink HAL client interface if listening mode is toggled off |
| * before group activation (active group context would take care of |
| * Sink HAL client cleanup). |
| */ |
| if (sink_monitor_mode_ && !enable && le_audio_sink_hal_client_ && |
| active_group_id_ == bluetooth::groups::kGroupUnknown) { |
| local_metadata_context_types_.sink.clear(); |
| le_audio_sink_hal_client_->Stop(); |
| le_audio_sink_hal_client_.reset(); |
| } |
| |
| LOG_DEBUG("enable: %d", enable); |
| sink_monitor_mode_ = enable; |
| } else if (direction == le_audio::types::kLeAudioDirectionSource) { |
| LOG_DEBUG("enable: %d", enable); |
| source_monitor_mode_ = enable; |
| |
| if (!enable) { |
| return; |
| } |
| |
| LeAudioDeviceGroup* group = aseGroups_.FindById(active_group_id_); |
| if (!group) { |
| callbacks_->OnUnicastMonitorModeStatus( |
| le_audio::types::kLeAudioDirectionSource, |
| UnicastMonitorModeStatus::STREAMING_SUSPENDED); |
| |
| return; |
| } |
| |
| if (group->IsStreaming()) { |
| callbacks_->OnUnicastMonitorModeStatus( |
| le_audio::types::kLeAudioDirectionSource, |
| UnicastMonitorModeStatus::STREAMING); |
| } else { |
| callbacks_->OnUnicastMonitorModeStatus( |
| le_audio::types::kLeAudioDirectionSource, |
| UnicastMonitorModeStatus::STREAMING_SUSPENDED); |
| } |
| } else { |
| LOG_ERROR("invalid direction: 0x%02x monitor mode set", direction); |
| } |
| } |
| |
| void SendAudioProfilePreferences( |
| const int group_id, bool is_output_preference_le_audio, |
| bool is_duplex_preference_le_audio) override { |
| LOG_INFO( |
| "group_id: %d, is_output_preference_le_audio: %d, " |
| "is_duplex_preference_le_audio: %d", |
| group_id, is_output_preference_le_audio, is_duplex_preference_le_audio); |
| if (group_id == bluetooth::groups::kGroupUnknown) { |
| LOG_WARN("Unknown group_id"); |
| return; |
| } |
| LeAudioDeviceGroup* group = aseGroups_.FindById(group_id); |
| if (!group) { |
| LOG_WARN("group_id %d does not exist", group_id); |
| return; |
| } |
| |
| group->is_output_preference_le_audio = is_output_preference_le_audio; |
| group->is_duplex_preference_le_audio = is_duplex_preference_le_audio; |
| } |
| |
| void StartAudioSession(LeAudioDeviceGroup* group, |
| const LeAudioCodecConfiguration* source_config, |
| const LeAudioCodecConfiguration* sink_config) { |
| /* This function is called when group is not yet set to active. |
| * This is why we don't have to check if session is started already. |
| * Just check if it is acquired. |
| */ |
| ASSERT_LOG(active_group_id_ == bluetooth::groups::kGroupUnknown, |
| "Active group is not set."); |
| ASSERT_LOG(le_audio_source_hal_client_, "Source session not acquired"); |
| ASSERT_LOG(le_audio_sink_hal_client_, "Sink session not acquired"); |
| |
| DsaModes dsa_modes = {DsaMode::DISABLED}; |
| if (IS_FLAG_ENABLED(leaudio_dynamic_spatial_audio)) { |
| dsa_modes = group->GetAllowedDsaModes(); |
| } |
| |
| /* We assume that peer device always use same frame duration */ |
| uint32_t frame_duration_us = 0; |
| if (!source_config->IsInvalid()) { |
| frame_duration_us = source_config->data_interval_us; |
| } else if (!sink_config->IsInvalid()) { |
| frame_duration_us = sink_config->data_interval_us; |
| } else { |
| ASSERT_LOG(true, "Both configs are invalid"); |
| } |
| |
| audio_framework_source_config.data_interval_us = frame_duration_us; |
| le_audio_source_hal_client_->Start(audio_framework_source_config, |
| audioSinkReceiver, dsa_modes); |
| |
| /* We use same frame duration for sink/source */ |
| audio_framework_sink_config.data_interval_us = frame_duration_us; |
| |
| /* If group supports more than 16kHz for the microphone in converstional |
| * case let's use that also for Audio Framework. |
| */ |
| std::optional<LeAudioCodecConfiguration> sink_configuration = |
| group->GetCodecConfigurationByDirection( |
| LeAudioContextType::CONVERSATIONAL, |
| le_audio::types::kLeAudioDirectionSource); |
| if (sink_configuration && |
| sink_configuration->sample_rate > |
| bluetooth::audio::le_audio::kSampleRate16000) { |
| audio_framework_sink_config.sample_rate = sink_configuration->sample_rate; |
| } |
| |
| le_audio_sink_hal_client_->Start(audio_framework_sink_config, |
| audioSourceReceiver, dsa_modes); |
| } |
| |
| bool isOutputPreferenceLeAudio(const RawAddress& address) { |
| LOG_INFO(" address: %s, active_group_id_: %d", |
| address.ToStringForLogging().c_str(), active_group_id_); |
| std::vector<RawAddress> active_leaudio_devices = |
| GetGroupDevices(active_group_id_); |
| if (std::find(active_leaudio_devices.begin(), active_leaudio_devices.end(), |
| address) == active_leaudio_devices.end()) { |
| LOG_INFO("Device %s is not active for LE Audio", |
| address.ToStringForLogging().c_str()); |
| return false; |
| } |
| |
| LeAudioDeviceGroup* group = aseGroups_.FindById(active_group_id_); |
| LOG_INFO(" active_group_id: %d, is_output_preference_le_audio_: %d", |
| group->group_id_, group->is_output_preference_le_audio); |
| return group->is_output_preference_le_audio; |
| } |
| |
| bool isDuplexPreferenceLeAudio(const RawAddress& address) { |
| LOG_INFO(" address: %s, active_group_id_: %d", |
| address.ToStringForLogging().c_str(), active_group_id_); |
| std::vector<RawAddress> active_leaudio_devices = |
| GetGroupDevices(active_group_id_); |
| if (std::find(active_leaudio_devices.begin(), active_leaudio_devices.end(), |
| address) == active_leaudio_devices.end()) { |
| LOG_INFO("Device %s is not active for LE Audio", |
| address.ToStringForLogging().c_str()); |
| return false; |
| } |
| |
| LeAudioDeviceGroup* group = aseGroups_.FindById(active_group_id_); |
| LOG_INFO(" active_group_id: %d, is_duplex_preference_le_audio: %d", |
| group->group_id_, group->is_duplex_preference_le_audio); |
| return group->is_duplex_preference_le_audio; |
| } |
| |
| void groupSetAndNotifyInactive(void) { |
| if (active_group_id_ == bluetooth::groups::kGroupUnknown) { |
| return; |
| } |
| auto group_id_to_close = active_group_id_; |
| active_group_id_ = bluetooth::groups::kGroupUnknown; |
| sink_monitor_notified_status_ = std::nullopt; |
| |
| LOG_INFO("Group id: %d", group_id_to_close); |
| if (alarm_is_scheduled(suspend_timeout_)) alarm_cancel(suspend_timeout_); |
| |
| StopAudio(); |
| ClientAudioInterfaceRelease(); |
| callbacks_->OnGroupStatus(group_id_to_close, GroupStatus::INACTIVE); |
| } |
| |
| void GroupSetActive(const int group_id) override { |
| LOG_INFO(" group_id: %d", group_id); |
| |
| if (group_id == bluetooth::groups::kGroupUnknown) { |
| if (active_group_id_ == bluetooth::groups::kGroupUnknown) { |
| /* Nothing to do */ |
| return; |
| } |
| |
| LOG_INFO("Active group_id changed %d -> %d", active_group_id_, group_id); |
| auto group_id_to_close = active_group_id_; |
| groupSetAndNotifyInactive(); |
| GroupStop(group_id_to_close); |
| |
| return; |
| } |
| |
| LeAudioDeviceGroup* group = aseGroups_.FindById(group_id); |
| if (!group) { |
| LOG(ERROR) << __func__ |
| << ", Invalid group: " << static_cast<int>(group_id); |
| return; |
| } |
| |
| if (active_group_id_ != bluetooth::groups::kGroupUnknown) { |
| if (active_group_id_ == group_id) { |
| LOG(INFO) << __func__ << ", Group is already active: " |
| << static_cast<int>(active_group_id_); |
| callbacks_->OnGroupStatus(active_group_id_, GroupStatus::ACTIVE); |
| return; |
| } |
| LOG(INFO) << __func__ << ", switching active group to: " << group_id; |
| } |
| |
| if (!le_audio_source_hal_client_) { |
| le_audio_source_hal_client_ = |
| LeAudioSourceAudioHalClient::AcquireUnicast(); |
| if (!le_audio_source_hal_client_) { |
| LOG(ERROR) << __func__ << ", could not acquire audio source interface"; |
| return; |
| } |
| } |
| |
| if (!le_audio_sink_hal_client_) { |
| le_audio_sink_hal_client_ = LeAudioSinkAudioHalClient::AcquireUnicast(); |
| if (!le_audio_sink_hal_client_) { |
| LOG(ERROR) << __func__ << ", could not acquire audio sink interface"; |
| return; |
| } |
| } |
| |
| /* Mini policy: Try configure audio HAL sessions with most recent context. |
| * If reconfiguration is not needed it means, context type is not supported. |
| * If most recent scenario is not supported, try to find first supported. |
| */ |
| LeAudioContextType default_context_type = configuration_context_type_; |
| if (!group->IsAudioSetConfigurationAvailable(default_context_type)) { |
| if (group->IsAudioSetConfigurationAvailable( |
| LeAudioContextType::UNSPECIFIED)) { |
| default_context_type = LeAudioContextType::UNSPECIFIED; |
| default_context_type = LeAudioContextType::UNSPECIFIED; |
| } else { |
| for (LeAudioContextType context_type : kLeAudioContextAllTypesArray) { |
| if (group->IsAudioSetConfigurationAvailable(context_type)) { |
| default_context_type = context_type; |
| break; |
| } |
| } |
| } |
| } |
| UpdateConfigAndCheckIfReconfigurationIsNeeded(group_id, |
| default_context_type); |
| if (current_source_codec_config.IsInvalid() && |
| current_sink_codec_config.IsInvalid()) { |
| LOG_ERROR("Unsupported device configurations"); |
| return; |
| } |
| |
| auto previous_active_group = active_group_id_; |
| LOG_INFO("Active group_id changed %d -> %d", previous_active_group, |
| group_id); |
| |
| if (previous_active_group == bluetooth::groups::kGroupUnknown) { |
| /* Expose audio sessions if there was no previous active group */ |
| StartAudioSession(group, ¤t_source_codec_config, |
| ¤t_sink_codec_config); |
| active_group_id_ = group_id; |
| } else { |
| /* In case there was an active group. Stop the stream, but before that, set |
| * the new group so the group change is correctly handled in OnStateMachineStatusReportCb |
| */ |
| active_group_id_ = group_id; |
| GroupStop(previous_active_group); |
| callbacks_->OnGroupStatus(previous_active_group, GroupStatus::INACTIVE); |
| } |
| |
| /* Reset sink listener notified status */ |
| sink_monitor_notified_status_ = std::nullopt; |
| if (IS_FLAG_ENABLED(leaudio_codec_config_callback_order_fix)) { |
| SendAudioGroupSelectableCodecConfigChanged(group); |
| callbacks_->OnGroupStatus(active_group_id_, GroupStatus::ACTIVE); |
| } else { |
| callbacks_->OnGroupStatus(active_group_id_, GroupStatus::ACTIVE); |
| SendAudioGroupSelectableCodecConfigChanged(group); |
| } |
| } |
| |
| void SetEnableState(const RawAddress& address, bool enabled) override { |
| LOG_INFO(" %s: %s", ADDRESS_TO_LOGGABLE_CSTR(address), |
| (enabled ? "enabled" : "disabled")); |
| auto leAudioDevice = leAudioDevices_.FindByAddress(address); |
| if (leAudioDevice == nullptr) { |
| LOG_WARN("%s is null", ADDRESS_TO_LOGGABLE_CSTR(address)); |
| return; |
| } |
| |
| auto group_id = leAudioDevice->group_id_; |
| auto group = aseGroups_.FindById(group_id); |
| if (group == nullptr) { |
| LOG_WARN("Group %d is not available", group_id); |
| return; |
| } |
| |
| if (enabled) { |
| group->Enable(gatt_if_, reconnection_mode_); |
| } else { |
| group->Disable(gatt_if_); |
| } |
| } |
| |
| void RemoveDevice(const RawAddress& address) override { |
| LOG_INFO(": %s ", ADDRESS_TO_LOGGABLE_CSTR(address)); |
| LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address); |
| if (!leAudioDevice) { |
| return; |
| } |
| |
| /* Remove device from the background connect if it is there */ |
| BTA_GATTC_CancelOpen(gatt_if_, address, false); |
| btif_storage_set_leaudio_autoconnect(address, false); |
| |
| LOG_INFO("%s, state: %s", ADDRESS_TO_LOGGABLE_CSTR(address), |
| bluetooth::common::ToString(leAudioDevice->GetConnectionState()) |
| .c_str()); |
| auto connection_state = leAudioDevice->GetConnectionState(); |
| switch (connection_state) { |
| case DeviceConnectState::REMOVING: |
| /* Just return, and let device disconnect */ |
| return; |
| case DeviceConnectState::CONNECTED: |
| case DeviceConnectState::CONNECTED_AUTOCONNECT_GETTING_READY: |
| case DeviceConnectState::CONNECTED_BY_USER_GETTING_READY: |
| /* ACL exist in this case, disconnect and mark as removing */ |
| Disconnect(address); |
| [[fallthrough]]; |
| case DeviceConnectState::DISCONNECTING: |
| case DeviceConnectState::DISCONNECTING_AND_RECOVER: |
| /* Device is disconnecting, just mark it shall be removed after all. */ |
| leAudioDevice->SetConnectionState(DeviceConnectState::REMOVING); |
| return; |
| case DeviceConnectState::CONNECTING_BY_USER: |
| BTA_GATTC_CancelOpen(gatt_if_, address, true); |
| [[fallthrough]]; |
| case DeviceConnectState::CONNECTING_AUTOCONNECT: |
| case DeviceConnectState::DISCONNECTED: |
| /* Do nothing, just remove device */ |
| break; |
| } |
| |
| /* Remove the group assignment if not yet removed. It might happen that the |
| * group module has already called the appropriate callback and we have |
| * already removed the group assignment. |
| */ |
| if (leAudioDevice->group_id_ != bluetooth::groups::kGroupUnknown) { |
| auto group = aseGroups_.FindById(leAudioDevice->group_id_); |
| group_remove_node(group, address, true); |
| } |
| |
| leAudioDevices_.Remove(address); |
| } |
| |
| void Connect(const RawAddress& address) override { |
| LOG_INFO(": %s ", ADDRESS_TO_LOGGABLE_CSTR(address)); |
| |
| LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address); |
| if (!leAudioDevice) { |
| leAudioDevices_.Add(address, DeviceConnectState::CONNECTING_BY_USER); |
| } else { |
| auto current_connect_state = leAudioDevice->GetConnectionState(); |
| if ((current_connect_state == DeviceConnectState::CONNECTED) || |
| (current_connect_state == DeviceConnectState::CONNECTING_BY_USER)) { |
| LOG_ERROR("Device %s is in invalid state: %s", |
| ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_), |
| bluetooth::common::ToString(current_connect_state).c_str()); |
| |
| return; |
| } |
| |
| if (leAudioDevice->group_id_ != bluetooth::groups::kGroupUnknown) { |
| auto group = GetGroupIfEnabled(leAudioDevice->group_id_); |
| if (!group) { |
| LOG_WARN(" %s, trying to connect to disabled group id %d", |
| ADDRESS_TO_LOGGABLE_CSTR(address), leAudioDevice->group_id_); |
| callbacks_->OnConnectionState(ConnectionState::DISCONNECTED, address); |
| return; |
| } |
| } |
| |
| leAudioDevice->SetConnectionState(DeviceConnectState::CONNECTING_BY_USER); |
| |
| le_audio::MetricsCollector::Get()->OnConnectionStateChanged( |
| leAudioDevice->group_id_, address, ConnectionState::CONNECTING, |
| le_audio::ConnectionStatus::SUCCESS); |
| } |
| |
| BTA_GATTC_Open(gatt_if_, address, BTM_BLE_DIRECT_CONNECTION, false); |
| } |
| |
| std::vector<RawAddress> GetGroupDevices(const int group_id) override { |
| LeAudioDeviceGroup* group = aseGroups_.FindById(group_id); |
| std::vector<RawAddress> all_group_device_addrs; |
| |
| if (group != nullptr) { |
| LeAudioDevice* leAudioDevice = group->GetFirstDevice(); |
| while (leAudioDevice) { |
| all_group_device_addrs.push_back(leAudioDevice->address_); |
| leAudioDevice = group->GetNextDevice(leAudioDevice); |
| }; |
| } |
| |
| return all_group_device_addrs; |
| } |
| |
| /* Restore paired device from storage to recreate groups */ |
| void AddFromStorage(const RawAddress& address, bool autoconnect, |
| int sink_audio_location, int source_audio_location, |
| int sink_supported_context_types, |
| int source_supported_context_types, |
| const std::vector<uint8_t>& handles, |
| const std::vector<uint8_t>& sink_pacs, |
| const std::vector<uint8_t>& source_pacs, |
| const std::vector<uint8_t>& ases) { |
| LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address); |
| |
| if (leAudioDevice) { |
| LOG_ERROR("Device is already loaded. Nothing to do."); |
| return; |
| } |
| |
| LOG_INFO( |
| "restoring: %s, autoconnect %d, sink_audio_location: %d, " |
| "source_audio_location: %d, sink_supported_context_types : 0x%04x, " |
| "source_supported_context_types 0x%04x ", |
| ADDRESS_TO_LOGGABLE_CSTR(address), autoconnect, sink_audio_location, |
| source_audio_location, sink_supported_context_types, |
| source_supported_context_types); |
| |
| leAudioDevices_.Add(address, DeviceConnectState::DISCONNECTED); |
| leAudioDevice = leAudioDevices_.FindByAddress(address); |
| |
| int group_id = DeviceGroups::Get()->GetGroupId( |
| address, le_audio::uuid::kCapServiceUuid); |
| if (group_id != bluetooth::groups::kGroupUnknown) { |
| group_add_node(group_id, address); |
| } |
| |
| leAudioDevice->snk_audio_locations_ = sink_audio_location; |
| if (sink_audio_location != 0) { |
| leAudioDevice->audio_directions_ |= |
| le_audio::types::kLeAudioDirectionSink; |
| } |
| |
| callbacks_->OnSinkAudioLocationAvailable( |
| leAudioDevice->address_, |
| leAudioDevice->snk_audio_locations_.to_ulong()); |
| |
| leAudioDevice->src_audio_locations_ = source_audio_location; |
| if (source_audio_location != 0) { |
| leAudioDevice->audio_directions_ |= |
| le_audio::types::kLeAudioDirectionSource; |
| } |
| |
| BidirectionalPair<AudioContexts> supported_contexts = { |
| .sink = AudioContexts(sink_supported_context_types), |
| .source = AudioContexts(source_supported_context_types), |
| }; |
| |
| leAudioDevice->SetSupportedContexts(supported_contexts); |
| |
| /* Use same as supported ones for now. */ |
| leAudioDevice->SetAvailableContexts(supported_contexts); |
| |
| if (!DeserializeHandles(leAudioDevice, handles)) { |
| LOG_WARN("Could not load Handles"); |
| } |
| |
| if (!DeserializeSinkPacs(leAudioDevice, sink_pacs)) { |
| /* If PACs are invalid, just say whole cache is invalid */ |
| leAudioDevice->known_service_handles_ = false; |
| LOG_WARN("Could not load sink pacs"); |
| } |
| |
| if (!DeserializeSourcePacs(leAudioDevice, source_pacs)) { |
| /* If PACs are invalid, just say whole cache is invalid */ |
| leAudioDevice->known_service_handles_ = false; |
| LOG_WARN("Could not load source pacs"); |
| } |
| |
| if (!DeserializeAses(leAudioDevice, ases)) { |
| /* If ASEs are invalid, just say whole cache is invalid */ |
| leAudioDevice->known_service_handles_ = false; |
| LOG_WARN("Could not load ases"); |
| } |
| |
| leAudioDevice->autoconnect_flag_ = autoconnect; |
| /* When adding from storage, make sure that autoconnect is used |
| * by all the devices in the group. |
| */ |
| leAudioDevices_.SetInitialGroupAutoconnectState( |
| group_id, gatt_if_, reconnection_mode_, autoconnect); |
| } |
| |
| bool GetHandlesForStorage(const RawAddress& addr, std::vector<uint8_t>& out) { |
| LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(addr); |
| return SerializeHandles(leAudioDevice, out); |
| } |
| |
| bool GetSinkPacsForStorage(const RawAddress& addr, |
| std::vector<uint8_t>& out) { |
| LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(addr); |
| return SerializeSinkPacs(leAudioDevice, out); |
| } |
| |
| bool GetSourcePacsForStorage(const RawAddress& addr, |
| std::vector<uint8_t>& out) { |
| LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(addr); |
| return SerializeSourcePacs(leAudioDevice, out); |
| } |
| |
| bool GetAsesForStorage(const RawAddress& addr, std::vector<uint8_t>& out) { |
| LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(addr); |
| |
| return SerializeAses(leAudioDevice, out); |
| } |
| |
| void BackgroundConnectIfNeeded(LeAudioDevice* leAudioDevice) { |
| if (!leAudioDevice->autoconnect_flag_) { |
| LOG_DEBUG("Device %s not in the background connect", |
| ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_)); |
| return; |
| } |
| AddToBackgroundConnectCheckGroupConnected(leAudioDevice); |
| } |
| |
| void Disconnect(const RawAddress& address) override { |
| LOG_INFO(": %s ", ADDRESS_TO_LOGGABLE_CSTR(address)); |
| LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address); |
| |
| if (!leAudioDevice) { |
| LOG_WARN("leAudioDevice not connected ( %s )", |
| ADDRESS_TO_LOGGABLE_CSTR(address)); |
| callbacks_->OnConnectionState(ConnectionState::DISCONNECTED, address); |
| return; |
| } |
| |
| auto connection_state = leAudioDevice->GetConnectionState(); |
| LOG_INFO("%s, state: %s", ADDRESS_TO_LOGGABLE_CSTR(address), |
| bluetooth::common::ToString(connection_state).c_str()); |
| |
| switch (connection_state) { |
| case DeviceConnectState::CONNECTING_BY_USER: |
| /* Timeout happen on the Java layer. Device probably not in the range. |
| * Cancel just direct connection and keep background if it is there. |
| */ |
| BTA_GATTC_CancelOpen(gatt_if_, address, true); |
| /* If this is a device which is a part of the group which is connected, |
| * lets start backgroup connect |
| */ |
| BackgroundConnectIfNeeded(leAudioDevice); |
| return; |
| case DeviceConnectState::CONNECTED: { |
| /* User is disconnecting the device, we shall remove the autoconnect |
| * flag for this device and all others if not TA is used |
| */ |
| /* If target announcement is used, do not remove autoconnect |
| */ |
| bool remove_from_autoconnect = |
| (reconnection_mode_ != BTM_BLE_BKG_CONNECT_TARGETED_ANNOUNCEMENTS); |
| |
| if (leAudioDevice->autoconnect_flag_ && remove_from_autoconnect) { |
| LOG_INFO("Removing autoconnect flag for group_id %d", |
| leAudioDevice->group_id_); |
| |
| /* Removes device from background connect */ |
| BTA_GATTC_CancelOpen(gatt_if_, address, false); |
| btif_storage_set_leaudio_autoconnect(address, false); |
| leAudioDevice->autoconnect_flag_ = false; |
| } |
| |
| /* Make sure ACL is disconnected to avoid reconnecting immediately |
| * when autoconnect with TA reconnection mechanism is used. |
| */ |
| bool force_acl_disconnect = leAudioDevice->autoconnect_flag_; |
| |
| auto group = aseGroups_.FindById(leAudioDevice->group_id_); |
| if (group) { |
| /* Remove devices from auto connect mode */ |
| for (auto dev = group->GetFirstDevice(); dev; |
| dev = group->GetNextDevice(dev)) { |
| if (remove_from_autoconnect && |
| (dev->GetConnectionState() == |
| DeviceConnectState::CONNECTING_AUTOCONNECT)) { |
| btif_storage_set_leaudio_autoconnect(dev->address_, false); |
| dev->autoconnect_flag_ = false; |
| BTA_GATTC_CancelOpen(gatt_if_, dev->address_, false); |
| dev->SetConnectionState(DeviceConnectState::DISCONNECTED); |
| } |
| } |
| if (group->IsStreaming() || !group->IsReleasingOrIdle()) { |
| leAudioDevice->closing_stream_for_disconnection_ = true; |
| groupStateMachine_->StopStream(group); |
| return; |
| } |
| force_acl_disconnect &= group->IsEnabled(); |
| } |
| |
| DisconnectDevice(leAudioDevice, force_acl_disconnect); |
| } |
| return; |
| case DeviceConnectState::CONNECTED_BY_USER_GETTING_READY: |
| /* Timeout happen on the Java layer before native got ready with the |
| * device */ |
| DisconnectDevice(leAudioDevice); |
| return; |
| case DeviceConnectState::CONNECTED_AUTOCONNECT_GETTING_READY: |
| /* Java is not aware about autoconnect actions, |
| * therefore this should not happen. |
| */ |
| LOG_WARN("Should not happen - disconnect device"); |
| DisconnectDevice(leAudioDevice); |
| return; |
| case DeviceConnectState::DISCONNECTED: |
| case DeviceConnectState::DISCONNECTING: |
| case DeviceConnectState::DISCONNECTING_AND_RECOVER: |
| case DeviceConnectState::CONNECTING_AUTOCONNECT: |
| case DeviceConnectState::REMOVING: |
| LOG_WARN("%s, invalid state %s", ADDRESS_TO_LOGGABLE_CSTR(address), |
| bluetooth::common::ToString(connection_state).c_str()); |
| return; |
| } |
| } |
| |
| void DisconnectDevice(LeAudioDevice* leAudioDevice, |
| bool acl_force_disconnect = false, |
| bool recover = false) { |
| if (leAudioDevice->conn_id_ == GATT_INVALID_CONN_ID) { |
| return; |
| } |
| |
| if (leAudioDevice->GetConnectionState() != DeviceConnectState::REMOVING) { |
| leAudioDevice->SetConnectionState(DeviceConnectState::DISCONNECTING); |
| } |
| |
| BtaGattQueue::Clean(leAudioDevice->conn_id_); |
| |
| /* Remote in bad state, force ACL Disconnection. */ |
| if (acl_force_disconnect) { |
| leAudioDevice->DisconnectAcl(); |
| if (recover) { |
| leAudioDevice->SetConnectionState( |
| DeviceConnectState::DISCONNECTING_AND_RECOVER); |
| } |
| } else { |
| BTA_GATTC_Close(leAudioDevice->conn_id_); |
| } |
| } |
| |
| void DeregisterNotifications(LeAudioDevice* leAudioDevice) { |
| /* GATTC will ommit not registered previously handles */ |
| for (auto pac_tuple : leAudioDevice->snk_pacs_) { |
| BTA_GATTC_DeregisterForNotifications(gatt_if_, leAudioDevice->address_, |
| std::get<0>(pac_tuple).val_hdl); |
| } |
| for (auto pac_tuple : leAudioDevice->src_pacs_) { |
| BTA_GATTC_DeregisterForNotifications(gatt_if_, leAudioDevice->address_, |
| std::get<0>(pac_tuple).val_hdl); |
| } |
| |
| if (leAudioDevice->snk_audio_locations_hdls_.val_hdl != 0) |
| BTA_GATTC_DeregisterForNotifications( |
| gatt_if_, leAudioDevice->address_, |
| leAudioDevice->snk_audio_locations_hdls_.val_hdl); |
| if (leAudioDevice->src_audio_locations_hdls_.val_hdl != 0) |
| BTA_GATTC_DeregisterForNotifications( |
| gatt_if_, leAudioDevice->address_, |
| leAudioDevice->src_audio_locations_hdls_.val_hdl); |
| if (leAudioDevice->audio_avail_hdls_.val_hdl != 0) |
| BTA_GATTC_DeregisterForNotifications( |
| gatt_if_, leAudioDevice->address_, |
| leAudioDevice->audio_avail_hdls_.val_hdl); |
| if (leAudioDevice->audio_supp_cont_hdls_.val_hdl != 0) |
| BTA_GATTC_DeregisterForNotifications( |
| gatt_if_, leAudioDevice->address_, |
| leAudioDevice->audio_supp_cont_hdls_.val_hdl); |
| if (leAudioDevice->ctp_hdls_.val_hdl != 0) |
| BTA_GATTC_DeregisterForNotifications(gatt_if_, leAudioDevice->address_, |
| leAudioDevice->ctp_hdls_.val_hdl); |
| |
| for (struct ase& ase : leAudioDevice->ases_) |
| BTA_GATTC_DeregisterForNotifications(gatt_if_, leAudioDevice->address_, |
| ase.hdls.val_hdl); |
| } |
| |
| /* This is a generic read/notify/indicate handler for gatt. Here messages |
| * are dispatched to correct elements e.g. ASEs, PACs, audio locations etc. |
| */ |
| void LeAudioCharValueHandle(uint16_t conn_id, uint16_t hdl, uint16_t len, |
| uint8_t* value, bool notify = false) { |
| LeAudioDevice* leAudioDevice = leAudioDevices_.FindByConnId(conn_id); |
| struct ase* ase; |
| |
| if (!leAudioDevice) { |
| LOG(ERROR) << __func__ << ", no leAudioDevice assigned to connection id: " |
| << static_cast<int>(conn_id); |
| return; |
| } |
| |
| ase = leAudioDevice->GetAseByValHandle(hdl); |
| |
| LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_); |
| if (ase) { |
| groupStateMachine_->ProcessGattNotifEvent(value, len, ase, leAudioDevice, |
| group); |
| |
| return; |
| } |
| |
| auto snk_pac_ent = std::find_if( |
| leAudioDevice->snk_pacs_.begin(), leAudioDevice->snk_pacs_.end(), |
| [&hdl](auto& pac_ent) { return std::get<0>(pac_ent).val_hdl == hdl; }); |
| if (snk_pac_ent != leAudioDevice->snk_pacs_.end()) { |
| std::vector<struct le_audio::types::acs_ac_record> pac_recs; |
| |
| /* Guard consistency of PAC records structure */ |
| if (!le_audio::client_parser::pacs::ParsePacs(pac_recs, len, value)) |
| return; |
| |
| LOG(INFO) << __func__ << ", Registering sink PACs"; |
| leAudioDevice->RegisterPACs(&std::get<1>(*snk_pac_ent), &pac_recs); |
| |
| /* Cached audio set configurations should be considered invalid when |
| * PACs are updated. |
| */ |
| if (group) { |
| group->InvalidateCachedConfigurations(); |
| } |
| if (notify) { |
| btif_storage_leaudio_update_pacs_bin(leAudioDevice->address_); |
| } |
| return; |
| } |
| |
| auto src_pac_ent = std::find_if( |
| leAudioDevice->src_pacs_.begin(), leAudioDevice->src_pacs_.end(), |
| [&hdl](auto& pac_ent) { return std::get<0>(pac_ent).val_hdl == hdl; }); |
| if (src_pac_ent != leAudioDevice->src_pacs_.end()) { |
| std::vector<struct le_audio::types::acs_ac_record> pac_recs; |
| |
| /* Guard consistency of PAC records structure */ |
| if (!le_audio::client_parser::pacs::ParsePacs(pac_recs, len, value)) |
| return; |
| |
| LOG(INFO) << __func__ << ", Registering source PACs"; |
| leAudioDevice->RegisterPACs(&std::get<1>(*src_pac_ent), &pac_recs); |
| |
| /* Cached audio set configurations should be considered invalid when |
| * PACs are updated. |
| */ |
| if (group) { |
| group->InvalidateCachedConfigurations(); |
| } |
| if (notify) { |
| btif_storage_leaudio_update_pacs_bin(leAudioDevice->address_); |
| } |
| return; |
| } |
| |
| if (hdl == leAudioDevice->snk_audio_locations_hdls_.val_hdl) { |
| AudioLocations snk_audio_locations; |
| |
| le_audio::client_parser::pacs::ParseAudioLocations(snk_audio_locations, |
| len, value); |
| |
| /* Value may not change */ |
| if ((leAudioDevice->audio_directions_ & |
| le_audio::types::kLeAudioDirectionSink) && |
| (leAudioDevice->snk_audio_locations_ ^ snk_audio_locations).none()) |
| return; |
| |
| /* Presence of PAC characteristic for source means support for source |
| * audio location. Value of 0x00000000 means mono/unspecified |
| */ |
| leAudioDevice->audio_directions_ |= |
| le_audio::types::kLeAudioDirectionSink; |
| leAudioDevice->snk_audio_locations_ = snk_audio_locations; |
| |
| callbacks_->OnSinkAudioLocationAvailable(leAudioDevice->address_, |
| snk_audio_locations.to_ulong()); |
| |
| if (notify) { |
| btif_storage_set_leaudio_audio_location( |
| leAudioDevice->address_, |
| leAudioDevice->snk_audio_locations_.to_ulong(), |
| leAudioDevice->src_audio_locations_.to_ulong()); |
| if (group && group->IsReleasingOrIdle()) { |
| UpdateLocationsAndContextsAvailability(leAudioDevice->group_id_); |
| } |
| } |
| } else if (hdl == leAudioDevice->src_audio_locations_hdls_.val_hdl) { |
| AudioLocations src_audio_locations; |
| |
| le_audio::client_parser::pacs::ParseAudioLocations(src_audio_locations, |
| len, value); |
| |
| /* Value may not change */ |
| if ((leAudioDevice->audio_directions_ & |
| le_audio::types::kLeAudioDirectionSource) && |
| (leAudioDevice->src_audio_locations_ ^ src_audio_locations).none()) |
| return; |
| |
| /* Presence of PAC characteristic for source means support for source |
| * audio location. Value of 0x00000000 means mono/unspecified |
| */ |
| leAudioDevice->audio_directions_ |= |
| le_audio::types::kLeAudioDirectionSource; |
| leAudioDevice->src_audio_locations_ = src_audio_locations; |
| |
| if (notify) { |
| btif_storage_set_leaudio_audio_location( |
| leAudioDevice->address_, |
| leAudioDevice->snk_audio_locations_.to_ulong(), |
| leAudioDevice->src_audio_locations_.to_ulong()); |
| if (group && group->IsReleasingOrIdle()) { |
| UpdateLocationsAndContextsAvailability(leAudioDevice->group_id_); |
| } |
| } |
| } else if (hdl == leAudioDevice->audio_avail_hdls_.val_hdl) { |
| BidirectionalPair<AudioContexts> contexts; |
| if (!le_audio::client_parser::pacs::ParseAvailableAudioContexts( |
| contexts, len, value)) { |
| return; |
| } |
| |
| leAudioDevice->SetAvailableContexts(contexts); |
| |
| if (!group) { |
| return; |
| } |
| |
| if (group->IsReleasingOrIdle()) { |
| /* Group is not streaming. Device does not have to be attach to the |
| * stream, and we can update context availability for the group |
| */ |
| UpdateLocationsAndContextsAvailability(group); |
| return; |
| } |
| |
| if (group->IsInTransition()) { |
| /* Group is in transition, do not take any actions now.*/ |
| return; |
| } |
| |
| if (leAudioDevice->HaveActiveAse()) { |
| /* Do nothing, device is streaming */ |
| return; |
| } |
| |
| if (leAudioDevice->GetConnectionState() != |
| DeviceConnectState::CONNECTED) { |
| /* Do nothing, wait until device is connected */ |
| return; |
| } |
| |
| AttachToStreamingGroupIfNeeded(leAudioDevice); |
| |
| } else if (hdl == leAudioDevice->audio_supp_cont_hdls_.val_hdl) { |
| BidirectionalPair<AudioContexts> supp_audio_contexts; |
| if (le_audio::client_parser::pacs::ParseSupportedAudioContexts( |
| supp_audio_contexts, len, value)) { |
| /* Just store if for now */ |
| leAudioDevice->SetSupportedContexts(supp_audio_contexts); |
| |
| btif_storage_set_leaudio_supported_context_types( |
| leAudioDevice->address_, supp_audio_contexts.sink.value(), |
| supp_audio_contexts.source.value()); |
| } |
| } else if (hdl == leAudioDevice->ctp_hdls_.val_hdl) { |
| groupStateMachine_->ProcessGattCtpNotification(group, value, len); |
| } else if (hdl == leAudioDevice->tmap_role_hdl_) { |
| le_audio::client_parser::tmap::ParseTmapRole(leAudioDevice->tmap_role_, |
| len, value); |
| } else { |
| LOG(ERROR) << __func__ << ", Unknown attribute read: " << loghex(hdl); |
| } |
| } |
| |
| void OnGattReadRsp(uint16_t conn_id, tGATT_STATUS status, uint16_t hdl, |
| uint16_t len, uint8_t* value, void* data) { |
| LeAudioCharValueHandle(conn_id, hdl, len, value); |
| } |
| |
| LeAudioDeviceGroup* GetGroupIfEnabled(int group_id) { |
| auto group = aseGroups_.FindById(group_id); |
| if (group == nullptr) { |
| LOG_INFO("Group %d does not exist", group_id); |
| return nullptr; |
| } |
| if (!group->IsEnabled()) { |
| LOG_INFO("Group %d is disabled", group_id); |
| return nullptr; |
| } |
| return group; |
| } |
| |
| void AddToBackgroundConnectCheckGroupConnected(LeAudioDevice* leAudioDevice) { |
| /* If device belongs to streaming group, add it on allow list */ |
| auto address = leAudioDevice->address_; |
| auto group = GetGroupIfEnabled(leAudioDevice->group_id_); |
| if (group == nullptr) { |
| LOG_INFO("Group %d is invalid or disabled ", leAudioDevice->group_id_); |
| return; |
| } |
| |
| leAudioDevice->SetConnectionState( |
| DeviceConnectState::CONNECTING_AUTOCONNECT); |
| |
| /* Cancel previous bakcground connect */ |
| BTA_GATTC_CancelOpen(gatt_if_, address, false); |
| if (group->IsAnyDeviceConnected()) { |
| LOG_INFO("Group %d in connected state. Adding %s to allow list ", |
| leAudioDevice->group_id_, ADDRESS_TO_LOGGABLE_CSTR(address)); |
| BTA_GATTC_Open(gatt_if_, address, BTM_BLE_BKG_CONNECT_ALLOW_LIST, false); |
| } else { |
| LOG_INFO( |
| "Adding %s to backgroud connect (default reconnection_mode " |
| "(0x%02x))", |
| ADDRESS_TO_LOGGABLE_CSTR(address), reconnection_mode_); |
| BTA_GATTC_Open(gatt_if_, address, reconnection_mode_, false); |
| } |
| } |
| |
| void OnGattConnected(tGATT_STATUS status, uint16_t conn_id, |
| tGATT_IF client_if, RawAddress address, |
| tBT_TRANSPORT transport, uint16_t mtu) { |
| LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address); |
| |
| LOG_INFO("%s, conn_id=0x%04x, transport=%s, status=%s (0x%02x)", |
| ADDRESS_TO_LOGGABLE_CSTR(address), conn_id, |
| bt_transport_text(transport).c_str(), |
| gatt_status_text(status).c_str(), status); |
| |
| if (transport != BT_TRANSPORT_LE) { |
| LOG_WARN("Only LE connection is allowed (transport %s)", |
| bt_transport_text(transport).c_str()); |
| BTA_GATTC_Close(conn_id); |
| return; |
| } |
| |
| if (!leAudioDevice) return; |
| |
| if (leAudioDevice->conn_id_ != GATT_INVALID_CONN_ID) { |
| LOG_DEBUG("Already connected %s, conn_id=0x%04x", |
| ADDRESS_TO_LOGGABLE_CSTR(address), leAudioDevice->conn_id_); |
| return; |
| } |
| |
| if (status != GATT_SUCCESS) { |
| /* Clear current connection request and let it be set again if needed */ |
| BTA_GATTC_CancelOpen(gatt_if_, address, false); |
| |
| /* autoconnect connection failed, that's ok */ |
| if (status != GATT_ILLEGAL_PARAMETER && |
| (leAudioDevice->GetConnectionState() == |
| DeviceConnectState::CONNECTING_AUTOCONNECT || |
| leAudioDevice->autoconnect_flag_)) { |
| LOG_INFO("Device not available now, do background connect."); |
| leAudioDevice->SetConnectionState(DeviceConnectState::DISCONNECTED); |
| AddToBackgroundConnectCheckGroupConnected(leAudioDevice); |
| return; |
| } |
| |
| leAudioDevice->SetConnectionState(DeviceConnectState::DISCONNECTED); |
| |
| LOG_ERROR("Failed to connect to LeAudio leAudioDevice, status: 0x%02x", |
| status); |
| callbacks_->OnConnectionState(ConnectionState::DISCONNECTED, address); |
| le_audio::MetricsCollector::Get()->OnConnectionStateChanged( |
| leAudioDevice->group_id_, address, ConnectionState::CONNECTED, |
| le_audio::ConnectionStatus::FAILED); |
| return; |
| } |
| |
| if (leAudioDevice->group_id_ != bluetooth::groups::kGroupUnknown) { |
| auto group = GetGroupIfEnabled(leAudioDevice->group_id_); |
| if (group == nullptr) { |
| BTA_GATTC_CancelOpen(gatt_if_, address, false); |
| |
| LOG_WARN( |
| "LeAudio profile is disabled for group_id: %d. %s is not connected", |
| leAudioDevice->group_id_, ADDRESS_TO_LOGGABLE_CSTR(address)); |
| return; |
| } |
| } |
| |
| leAudioDevice->conn_id_ = conn_id; |
| leAudioDevice->mtu_ = mtu; |
| |
| /* Remove device from the background connect (it might be either Allow list |
| * or TA) and add it again with reconnection_mode_. In case it is TA, we are |
| * sure that device will not be in the allow list for other applications |
| * which are using background connect. |
| */ |
| BTA_GATTC_CancelOpen(gatt_if_, address, false); |
| BTA_GATTC_Open(gatt_if_, address, reconnection_mode_, false); |
| |
| if (controller_get_interface()->SupportsBle2mPhy()) { |
| LOG(INFO) << ADDRESS_TO_LOGGABLE_STR(address) |
| << " set preferred PHY to 2M"; |
| BTM_BleSetPhy(address, PHY_LE_2M, PHY_LE_2M, 0); |
| } |
| |
| BTM_RequestPeerSCA(leAudioDevice->address_, transport); |
| |
| if (leAudioDevice->GetConnectionState() == |
| DeviceConnectState::CONNECTING_AUTOCONNECT) { |
| leAudioDevice->SetConnectionState( |
| DeviceConnectState::CONNECTED_AUTOCONNECT_GETTING_READY); |
| } else { |
| leAudioDevice->SetConnectionState( |
| DeviceConnectState::CONNECTED_BY_USER_GETTING_READY); |
| } |
| |
| /* Check if the device is in allow list and update the flag */ |
| leAudioDevice->UpdateDeviceAllowlistFlag(); |
| if (BTM_SecIsSecurityPending(address)) { |
| /* if security collision happened, wait for encryption done |
| * (BTA_GATTC_ENC_CMPL_CB_EVT) */ |
| return; |
| } |
| |
| /* verify bond */ |
| if (BTM_IsEncrypted(address, BT_TRANSPORT_LE)) { |
| /* if link has been encrypted */ |
| OnEncryptionComplete(address, BTM_SUCCESS); |
| return; |
| } |
| |
| int result = BTM_SetEncryption(address, BT_TRANSPORT_LE, nullptr, nullptr, |
| BTM_BLE_SEC_ENCRYPT); |
| |
| LOG_INFO("Encryption required for %s. Request result: 0x%02x", |
| ADDRESS_TO_LOGGABLE_CSTR(address), result); |
| |
| if (result == BTM_ERR_KEY_MISSING) { |
| LOG_ERROR("Link key unknown for %s, disconnect profile", |
| ADDRESS_TO_LOGGABLE_CSTR(address)); |
| le_audio::MetricsCollector::Get()->OnConnectionStateChanged( |
| leAudioDevice->group_id_, address, ConnectionState::CONNECTED, |
| le_audio::ConnectionStatus::FAILED); |
| |
| /* If link cannot be enctypted, disconnect profile */ |
| BTA_GATTC_Close(conn_id); |
| } |
| } |
| |
| void RegisterKnownNotifications(LeAudioDevice* leAudioDevice, |
| bool gatt_register, bool write_ccc) { |
| LOG(INFO) << __func__ << " device: " |
| << ADDRESS_TO_LOGGABLE_STR(leAudioDevice->address_); |
| |
| if (leAudioDevice->ctp_hdls_.val_hdl == 0) { |
| LOG_ERROR( |
| "Control point characteristic is mandatory - disconnecting device %s", |
| ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_)); |
| DisconnectDevice(leAudioDevice); |
| return; |
| } |
| |
| /* GATTC will ommit not registered previously handles */ |
| for (auto pac_tuple : leAudioDevice->snk_pacs_) { |
| subscribe_for_notification( |
| leAudioDevice->conn_id_, leAudioDevice->address_, |
| std::get<0>(pac_tuple), gatt_register, write_ccc); |
| } |
| for (auto pac_tuple : leAudioDevice->src_pacs_) { |
| subscribe_for_notification( |
| leAudioDevice->conn_id_, leAudioDevice->address_, |
| std::get<0>(pac_tuple), gatt_register, write_ccc); |
| } |
| |
| if (leAudioDevice->snk_audio_locations_hdls_.val_hdl != 0) |
| subscribe_for_notification( |
| leAudioDevice->conn_id_, leAudioDevice->address_, |
| leAudioDevice->snk_audio_locations_hdls_, gatt_register, write_ccc); |
| if (leAudioDevice->src_audio_locations_hdls_.val_hdl != 0) |
| subscribe_for_notification( |
| leAudioDevice->conn_id_, leAudioDevice->address_, |
| leAudioDevice->src_audio_locations_hdls_, gatt_register, write_ccc); |
| |
| if (leAudioDevice->audio_avail_hdls_.val_hdl != 0) |
| subscribe_for_notification( |
| leAudioDevice->conn_id_, leAudioDevice->address_, |
| leAudioDevice->audio_avail_hdls_, gatt_register, write_ccc); |
| |
| if (leAudioDevice->audio_supp_cont_hdls_.val_hdl != 0) |
| subscribe_for_notification( |
| leAudioDevice->conn_id_, leAudioDevice->address_, |
| leAudioDevice->audio_supp_cont_hdls_, gatt_register, write_ccc); |
| |
| for (struct ase& ase : leAudioDevice->ases_) |
| subscribe_for_notification(leAudioDevice->conn_id_, |
| leAudioDevice->address_, ase.hdls, |
| gatt_register, write_ccc); |
| |
| subscribe_for_notification(leAudioDevice->conn_id_, leAudioDevice->address_, |
| leAudioDevice->ctp_hdls_, gatt_register, |
| write_ccc); |
| } |
| |
| void changeMtuIfPossible(LeAudioDevice* leAudioDevice) { |
| if (leAudioDevice->mtu_ == GATT_DEF_BLE_MTU_SIZE) { |
| LOG(INFO) << __func__ << ", Configure MTU"; |
| /* Use here kBapMinimumAttMtu, because we know that GATT will request |
| * default ATT MTU anyways. We also know that GATT will use this |
| * kBapMinimumAttMtu as an input for Data Length Update procedure in the controller. |
| */ |
| BtaGattQueue::ConfigureMtu(leAudioDevice->conn_id_, kBapMinimumAttMtu); |
| } |
| } |
| |
| void OnEncryptionComplete(const RawAddress& address, uint8_t status) { |
| LOG_INFO("%s status 0x%02x ", ADDRESS_TO_LOGGABLE_CSTR(address), status); |
| LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address); |
| if (leAudioDevice == NULL || |
| (leAudioDevice->conn_id_ == GATT_INVALID_CONN_ID)) { |
| LOG_WARN("Skipping device which is %s", |
| (leAudioDevice ? " not connected by service." : " null")); |
| return; |
| } |
| |
| if (status != BTM_SUCCESS) { |
| LOG(ERROR) << "Encryption failed" |
| << " status: " << int{status}; |
| if (leAudioDevice->GetConnectionState() == |
| DeviceConnectState::CONNECTED_BY_USER_GETTING_READY) { |
| callbacks_->OnConnectionState(ConnectionState::DISCONNECTED, address); |
| le_audio::MetricsCollector::Get()->OnConnectionStateChanged( |
| leAudioDevice->group_id_, address, ConnectionState::CONNECTED, |
| le_audio::ConnectionStatus::FAILED); |
| } |
| |
| leAudioDevice->SetConnectionState(DeviceConnectState::DISCONNECTING); |
| |
| BTA_GATTC_Close(leAudioDevice->conn_id_); |
| return; |
| } |
| |
| if (leAudioDevice->encrypted_) { |
| LOG(INFO) << __func__ << " link already encrypted, nothing to do"; |
| return; |
| } |
| |
| changeMtuIfPossible(leAudioDevice); |
| |
| leAudioDevice->encrypted_ = true; |
| |
| /* If we know services, register for notifications */ |
| if (leAudioDevice->known_service_handles_) { |
| /* This registration will do subscribtion in local GATT as we |
| * assume remote device keeps bonded CCC values. |
| */ |
| RegisterKnownNotifications(leAudioDevice, true, false); |
| |
| /* Make sure remote keeps CCC values as per specification. |
| * We read only ctp_ccc value. If that one is good, we assume |
| * remote keeps CCC values correctly. |
| */ |
| BtaGattQueue::ReadCharacteristic(leAudioDevice->conn_id_, |
| leAudioDevice->ctp_hdls_.ccc_hdl, |
| OnGattCtpCccReadRspStatic, NULL); |
| } |
| |
| /* If we know services and read is not ongoing, this is reconnection and |
| * just notify connected */ |
| if (leAudioDevice->known_service_handles_ && |
| !leAudioDevice->notify_connected_after_read_) { |
| LOG_INFO("Wait for CCC registration and MTU change request"); |
| return; |
| } |
| |
| BTA_GATTC_ServiceSearchRequest( |
| leAudioDevice->conn_id_, |
| &le_audio::uuid::kPublishedAudioCapabilityServiceUuid); |
| } |
| |
| void checkGroupConnectionStateAfterMemberDisconnect(int group_id) { |
| /* This is fired t=kGroupConnectedWatchDelayMs after group member |
| * got disconencted while ather group members were connected. |
| * We want to check here if there is any group member connected. |
| * If so we should add other group members to allow list for better |
| * reconnection experiance. If all group members are disconnected |
| * i e.g. devices intentionally disconnected for other |
| * purposes like pairing with other device, then we do nothing here and |
| * device stay on the default reconnection policy (i.e. targeted |
| * announcements) |
| */ |
| auto group = aseGroups_.FindById(group_id); |
| if (group == nullptr) { |
| LOG_INFO("Group %d is destroyed.", group_id); |
| return; |
| } |
| |
| if (!group->IsAnyDeviceConnected()) { |
| LOG_INFO("Group %d is not connected", group_id); |
| /* Make sure all devices are in the default reconnection mode */ |
| group->ApplyReconnectionMode(gatt_if_, reconnection_mode_); |
| return; |
| } |
| |
| /* if group is still connected, make sure that other not connected |
| * set members are in the allow list for the quick reconnect. |
| * E.g. for the earbud case, probably one of the earbud is in the case now. |
| */ |
| group->AddToAllowListNotConnectedGroupMembers(gatt_if_); |
| } |
| |
| void scheduleGroupConnectedCheck(int group_id) { |
| LOG_INFO("Schedule group_id %d connected check.", group_id); |
| do_in_main_thread_delayed( |
| FROM_HERE, |
| base::BindOnce( |
| &LeAudioClientImpl::checkGroupConnectionStateAfterMemberDisconnect, |
| weak_factory_.GetWeakPtr(), group_id), |
| std::chrono::milliseconds(kGroupConnectedWatchDelayMs)); |
| } |
| |
| void autoConnect(RawAddress address) { |
| auto leAudioDevice = leAudioDevices_.FindByAddress(address); |
| if (leAudioDevice == nullptr) { |
| LOG_WARN("Device %s not valid anymore", |
| ADDRESS_TO_LOGGABLE_CSTR(address)); |
| return; |
| } |
| |
| BackgroundConnectIfNeeded(leAudioDevice); |
| } |
| |
| void scheduleAutoConnect(RawAddress& address) { |
| LOG_INFO("Schedule auto connect %s ", ADDRESS_TO_LOGGABLE_CSTR(address)); |
| do_in_main_thread_delayed( |
| FROM_HERE, |
| base::BindOnce(&LeAudioClientImpl::autoConnect, |
| weak_factory_.GetWeakPtr(), address), |
| std::chrono::milliseconds(kAutoConnectAfterOwnDisconnectDelayMs)); |
| } |
| |
| void recoveryReconnect(RawAddress address) { |
| LOG_INFO("Reconnecting to %s after timeout on state machine.", |
| ADDRESS_TO_LOGGABLE_CSTR(address)); |
| LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address); |
| |
| if (leAudioDevice == nullptr || |
| leAudioDevice->GetConnectionState() != |
| DeviceConnectState::DISCONNECTING_AND_RECOVER) { |
| LOG_WARN("Device %s, not interested in recovery connect anymore", |
| ADDRESS_TO_LOGGABLE_CSTR(address)); |
| return; |
| } |
| |
| auto group = GetGroupIfEnabled(leAudioDevice->group_id_); |
| |
| if (group != nullptr) { |
| leAudioDevice->SetConnectionState( |
| DeviceConnectState::CONNECTING_AUTOCONNECT); |
| BTA_GATTC_Open(gatt_if_, address, BTM_BLE_DIRECT_CONNECTION, false); |
| } else { |
| leAudioDevice->SetConnectionState(DeviceConnectState::DISCONNECTED); |
| } |
| } |
| |
| void scheduleRecoveryReconnect(RawAddress& address) { |
| LOG_INFO("Schedule reconnecting to %s after timeout on state machine.", |
| ADDRESS_TO_LOGGABLE_CSTR(address)); |
| do_in_main_thread_delayed( |
| FROM_HERE, |
| base::BindOnce(&LeAudioClientImpl::recoveryReconnect, |
| weak_factory_.GetWeakPtr(), address), |
| std::chrono::milliseconds(kRecoveryReconnectDelayMs)); |
| } |
| |
| void checkIfGroupMember(RawAddress address) { |
| LOG_INFO("checking being a group member: %s", |
| ADDRESS_TO_LOGGABLE_CSTR(address)); |
| LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address); |
| |
| if (leAudioDevice == nullptr) { |
| LOG_WARN("Device %s, probably removed", |
| ADDRESS_TO_LOGGABLE_CSTR(address)); |
| return; |
| } |
| |
| if (leAudioDevice->group_id_ == bluetooth::groups::kGroupUnknown) { |
| disconnectInvalidDevice(leAudioDevice, |
| ", device not a valid group member", |
| LeAudioHealthDeviceStatType::INVALID_CSIS); |
| return; |
| } |
| } |
| |
| /* This is called, when CSIS native module is about to add device to the |
| * group once the CSIS service will be verified on the remote side. |
| * After some time (kCsisGroupMemberDelayMs) a checkIfGroupMember will be |
| * called and will verify if the remote device has a group_id properly set. |
| * if not, it means there is something wrong with CSIS service on the remote |
| * side. |
| */ |
| void scheduleGuardForCsisAdd(RawAddress& address) { |
| LOG_INFO("Schedule reconnecting to %s after timeout on state machine.", |
| ADDRESS_TO_LOGGABLE_CSTR(address)); |
| do_in_main_thread_delayed( |
| FROM_HERE, |
| base::BindOnce(&LeAudioClientImpl::checkIfGroupMember, |
| weak_factory_.GetWeakPtr(), address), |
| std::chrono::milliseconds(kCsisGroupMemberDelayMs)); |
| } |
| |
| void OnGattDisconnected(uint16_t conn_id, tGATT_IF client_if, |
| RawAddress address, tGATT_DISCONN_REASON reason) { |
| LeAudioDevice* leAudioDevice = leAudioDevices_.FindByConnId(conn_id); |
| |
| if (!leAudioDevice) { |
| LOG(ERROR) << ", skipping unknown leAudioDevice, address: " |
| << ADDRESS_TO_LOGGABLE_STR(address); |
| return; |
| } |
| |
| BtaGattQueue::Clean(leAudioDevice->conn_id_); |
| LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_); |
| |
| DeregisterNotifications(leAudioDevice); |
| |
| callbacks_->OnConnectionState(ConnectionState::DISCONNECTED, address); |
| leAudioDevice->conn_id_ = GATT_INVALID_CONN_ID; |
| leAudioDevice->mtu_ = 0; |
| leAudioDevice->closing_stream_for_disconnection_ = false; |
| leAudioDevice->encrypted_ = false; |
| |
| groupStateMachine_->ProcessHciNotifAclDisconnected(group, leAudioDevice); |
| |
| le_audio::MetricsCollector::Get()->OnConnectionStateChanged( |
| leAudioDevice->group_id_, address, ConnectionState::DISCONNECTED, |
| le_audio::ConnectionStatus::SUCCESS); |
| |
| if (leAudioDevice->GetConnectionState() == DeviceConnectState::REMOVING) { |
| if (leAudioDevice->group_id_ != bluetooth::groups::kGroupUnknown) { |
| auto group = aseGroups_.FindById(leAudioDevice->group_id_); |
| group_remove_node(group, address, true); |
| } |
| leAudioDevices_.Remove(address); |
| return; |
| } |
| |
| auto connection_state = leAudioDevice->GetConnectionState(); |
| LOG_INFO("%s, autoconnect %d, reason 0x%02x, connection state %s", |
| ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_), |
| leAudioDevice->autoconnect_flag_, reason, |
| bluetooth::common::ToString(connection_state).c_str()); |
| |
| if (connection_state == DeviceConnectState::DISCONNECTING_AND_RECOVER) { |
| /* We are back after disconnecting device which was in a bad state. |
| * lets try to reconnected - 30 sec with direct connect and later fallback |
| * to default background reconnection mode. |
| * Since GATT notifies us before ACL was dropped, let's wait a bit |
| * before we do reconnect. |
| */ |
| scheduleRecoveryReconnect(address); |
| return; |
| } |
| |
| leAudioDevice->SetConnectionState(DeviceConnectState::DISCONNECTED); |
| |
| /* Attempt background re-connect if disconnect was not initiated locally |
| * or if autoconnect is set and device got disconnected because of some |
| * issues |
| */ |
| if (group == nullptr || !group->IsEnabled()) { |
| LOG_ERROR("Group id %d (%p) disabled or null", leAudioDevice->group_id_, |
| group); |
| return; |
| } |
| |
| if (reason == GATT_CONN_TERMINATE_LOCAL_HOST) { |
| if (leAudioDevice->autoconnect_flag_) { |
| /* In this case ACL might not yet been disconnected */ |
| scheduleAutoConnect(address); |
| } |
| return; |
| } |
| |
| /* Remote disconnects from us or Timeout happens */ |
| /* In this case ACL is disconnected */ |
| if (reason == GATT_CONN_TIMEOUT) { |
| leAudioDevice->SetConnectionState( |
| DeviceConnectState::CONNECTING_AUTOCONNECT); |
| |
| /* If timeout try to reconnect for 30 sec.*/ |
| BTA_GATTC_Open(gatt_if_, address, BTM_BLE_DIRECT_CONNECTION, false); |
| return; |
| } |
| |
| /* In other disconnect resons we act based on the autoconnect_flag_ */ |
| if (leAudioDevice->autoconnect_flag_) { |
| if (group->IsAnyDeviceConnected()) { |
| /* If all set is disconnecting, let's give it some time. |
| * If not all get disconnected, and there will be group member |
| * connected we want to put disconnected devices to allow list |
| */ |
| scheduleGroupConnectedCheck(leAudioDevice->group_id_); |
| } else { |
| group->ApplyReconnectionMode(gatt_if_, reconnection_mode_); |
| } |
| } |
| } |
| |
| bool subscribe_for_notification(uint16_t conn_id, const RawAddress& address, |
| struct le_audio::types::hdl_pair handle_pair, |
| bool gatt_register = true, |
| bool write_ccc = true) { |
| std::vector<uint8_t> value(2); |
| uint8_t* ptr = value.data(); |
| uint16_t handle = handle_pair.val_hdl; |
| uint16_t ccc_handle = handle_pair.ccc_hdl; |
| |
| LOG_INFO("conn id %d, gatt_register: %b, write_ccc: %b", conn_id, |
| gatt_register, write_ccc); |
| if (gatt_register && BTA_GATTC_RegisterForNotifications( |
| gatt_if_, address, handle) != GATT_SUCCESS) { |
| LOG(ERROR) << __func__ << ", cannot register for notification: " |
| << static_cast<int>(handle); |
| return false; |
| } |
| |
| if (write_ccc == false) { |
| LOG_VERBOSE("CCC is not written to %s (0x%04x), handle 0x%04x", |
| ADDRESS_TO_LOGGABLE_CSTR(address), conn_id, ccc_handle); |
| return true; |
| } |
| |
| UINT16_TO_STREAM(ptr, GATT_CHAR_CLIENT_CONFIG_NOTIFICATION); |
| |
| BtaGattQueue::WriteDescriptor( |
| conn_id, ccc_handle, std::move(value), GATT_WRITE, |
| [](uint16_t conn_id, tGATT_STATUS status, uint16_t handle, uint16_t len, |
| const uint8_t* value, void* data) { |
| if (instance) instance->OnGattWriteCcc(conn_id, status, handle, data); |
| }, |
| nullptr); |
| return true; |
| } |
| |
| /* Find the handle for the client characteristics configuration of a given |
| * characteristics. |
| */ |
| uint16_t find_ccc_handle(const gatt::Characteristic& charac) { |
| auto iter = std::find_if( |
| charac.descriptors.begin(), charac.descriptors.end(), |
| [](const auto& desc) { |
| return desc.uuid == Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG); |
| }); |
| |
| return iter == charac.descriptors.end() ? 0 : (*iter).handle; |
| } |
| |
| void ClearDeviceInformationAndStartSearch(LeAudioDevice* leAudioDevice) { |
| if (!leAudioDevice) { |
| LOG_WARN("leAudioDevice is null"); |
| return; |
| } |
| |
| LOG_INFO("%s", ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_)); |
| |
| if (leAudioDevice->known_service_handles_ == false) { |
| LOG_DEBUG("Database already invalidated"); |
| return; |
| } |
| |
| leAudioDevice->known_service_handles_ = false; |
| leAudioDevice->csis_member_ = false; |
| BtaGattQueue::Clean(leAudioDevice->conn_id_); |
| DeregisterNotifications(leAudioDevice); |
| |
| if (leAudioDevice->GetConnectionState() == DeviceConnectState::CONNECTED) { |
| leAudioDevice->SetConnectionState( |
| DeviceConnectState::CONNECTED_BY_USER_GETTING_READY); |
| } |
| |
| btif_storage_leaudio_clear_service_data(leAudioDevice->address_); |
| |
| BTA_GATTC_ServiceSearchRequest( |
| leAudioDevice->conn_id_, |
| &le_audio::uuid::kPublishedAudioCapabilityServiceUuid); |
| } |
| |
| void OnServiceChangeEvent(const RawAddress& address) { |
| LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address); |
| if (!leAudioDevice) { |
| LOG_WARN("Skipping unknown leAudioDevice %s (%p)", |
| ADDRESS_TO_LOGGABLE_CSTR(address), leAudioDevice); |
| return; |
| } |
| |
| if (leAudioDevice->conn_id_ != GATT_INVALID_CONN_ID) { |
| ClearDeviceInformationAndStartSearch(leAudioDevice); |
| return; |
| } |
| |
| /* If device is not connected, just clear the handle information and this |
| * will trigger service search onGattConnected */ |
| leAudioDevice->known_service_handles_ = false; |
| btif_storage_leaudio_clear_service_data(address); |
| } |
| |
| void OnMtuChanged(uint16_t conn_id, uint16_t mtu) { |
| LeAudioDevice* leAudioDevice = leAudioDevices_.FindByConnId(conn_id); |
| if (!leAudioDevice) { |
| LOG_DEBUG("Unknown connectect id %d", conn_id); |
| return; |
| } |
| |
| /** |
| * BAP 1.01. 3.6.1 |
| * ATT and EATT transport requirements |
| * The Unicast Client shall support a minimum ATT_MTU of 64 octets for one |
| * Unenhanced ATT bearer, or for at least one Enhanced ATT bearer if the |
| * Unicast Client supports Enhanced ATT bearers. |
| * |
| */ |
| if (mtu < 64) { |
| LOG_ERROR("Device %s MTU is too low (%d). Disconnecting from LE Audio", |
| ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_), mtu); |
| Disconnect(leAudioDevice->address_); |
| return; |
| } |
| |
| leAudioDevice->mtu_ = mtu; |
| } |
| |
| void OnGattServiceDiscoveryDone(const RawAddress& address) { |
| LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address); |
| if (!leAudioDevice || (leAudioDevice->conn_id_ == GATT_INVALID_CONN_ID)) { |
| LOG_VERBOSE("skipping unknown leAudioDevice, address %s (%p) ", |
| ADDRESS_TO_LOGGABLE_CSTR(address), leAudioDevice); |
| return; |
| } |
| |
| if (!leAudioDevice->encrypted_) { |
| LOG_DEBUG("Wait for device to be encrypted"); |
| return; |
| } |
| |
| if (!leAudioDevice->known_service_handles_) |
| BTA_GATTC_ServiceSearchRequest( |
| leAudioDevice->conn_id_, |
| &le_audio::uuid::kPublishedAudioCapabilityServiceUuid); |
| } |
| |
| void disconnectInvalidDevice(LeAudioDevice* leAudioDevice, |
| std::string error_string, |
| LeAudioHealthDeviceStatType stat) { |
| LOG_ERROR("%s, %s", ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_), |
| error_string.c_str()); |
| if (leAudioHealthStatus_) { |
| leAudioHealthStatus_->AddStatisticForDevice(leAudioDevice, stat); |
| } |
| DisconnectDevice(leAudioDevice); |
| } |
| |
| /* This method is called after connection beginning to identify and initialize |
| * a le audio device. Any missing mandatory attribute will result in reverting |
| * and cleaning up device. |
| */ |
| void OnServiceSearchComplete(uint16_t conn_id, tGATT_STATUS status) { |
| LeAudioDevice* leAudioDevice = leAudioDevices_.FindByConnId(conn_id); |
| |
| if (!leAudioDevice) { |
| DLOG(ERROR) << __func__ << ", skipping unknown leAudioDevice, conn_id: " |
| << loghex(conn_id); |
| return; |
| } |
| |
| LOG(INFO) << __func__ << " test csis_member " |
| << leAudioDevice->csis_member_; |
| |
| if (status != GATT_SUCCESS) { |
| /* close connection and report service discovery complete with error */ |
| LOG(ERROR) << "Service discovery failed"; |
| |
| DisconnectDevice(leAudioDevice); |
| return; |
| } |
| |
| if (!leAudioDevice->encrypted_) { |
| LOG_WARN("Device not yet bonded - waiting for encryption"); |
| return; |
| } |
| |
| const std::list<gatt::Service>* services = BTA_GATTC_GetServices(conn_id); |
| |
| const gatt::Service* pac_svc = nullptr; |
| const gatt::Service* ase_svc = nullptr; |
| const gatt::Service* tmas_svc = nullptr; |
| |
| std::vector<uint16_t> csis_primary_handles; |
| uint16_t cas_csis_included_handle = 0; |
| |
| for (const gatt::Service& tmp : *services) { |
| if (tmp.uuid == le_audio::uuid::kPublishedAudioCapabilityServiceUuid) { |
| LOG_INFO("Found Audio Capability service, handle: 0x%04x, device: %s", |
| tmp.handle, ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_)); |
| pac_svc = &tmp; |
| } else if (tmp.uuid == le_audio::uuid::kAudioStreamControlServiceUuid) { |
| LOG_INFO( |
| "Found Audio Stream Endpoint service, handle: 0x%04x, device: %s", |
| tmp.handle, ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_)); |
| ase_svc = &tmp; |
| } else if (tmp.uuid == bluetooth::csis::kCsisServiceUuid) { |
| LOG_INFO( |
| "Found CSIS service, handle: 0x%04x, is primary: %d, device: %s", |
| tmp.handle, tmp.is_primary, |
| ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_)); |
| if (tmp.is_primary) csis_primary_handles.push_back(tmp.handle); |
| } else if (tmp.uuid == le_audio::uuid::kCapServiceUuid) { |
| LOG_INFO("Found CAP service, handle: 0x%04x, device: %s", tmp.handle, |
| ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_)); |
| |
| /* Try to find context for CSIS instances */ |
| for (auto& included_srvc : tmp.included_services) { |
| if (included_srvc.uuid == bluetooth::csis::kCsisServiceUuid) { |
| LOG(INFO) << __func__ << " CSIS included into CAS"; |
| if (bluetooth::csis::CsisClient::IsCsisClientRunning()) |
| cas_csis_included_handle = included_srvc.start_handle; |
| |
| break; |
| } |
| } |
| } else if (tmp.uuid == le_audio::uuid::kTelephonyMediaAudioServiceUuid) { |
| LOG_INFO( |
| "Found Telephony and Media Audio service, handle: 0x%04x, device: " |
| "%s", |
| tmp.handle, ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_)); |
| tmas_svc = &tmp; |
| } |
| } |
| |
| /* Check if CAS includes primary CSIS service */ |
| if (!csis_primary_handles.empty() && cas_csis_included_handle) { |
| auto iter = |
| std::find(csis_primary_handles.begin(), csis_primary_handles.end(), |
| cas_csis_included_handle); |
| if (iter != csis_primary_handles.end()) |
| leAudioDevice->csis_member_ = true; |
| } |
| |
| if (!pac_svc || !ase_svc) { |
| disconnectInvalidDevice( |
| leAudioDevice, "No mandatory le audio services found (pacs or ascs)", |
| LeAudioHealthDeviceStatType::INVALID_DB); |
| return; |
| } |
| |
| /* Refresh PACs handles */ |
| leAudioDevice->ClearPACs(); |
| |
| for (const gatt::Characteristic& charac : pac_svc->characteristics) { |
| if (charac.uuid == |
| le_audio::uuid::kSinkPublishedAudioCapabilityCharacteristicUuid) { |
| struct hdl_pair hdl_pair; |
| hdl_pair.val_hdl = charac.value_handle; |
| hdl_pair.ccc_hdl = find_ccc_handle(charac); |
| |
| if (hdl_pair.ccc_hdl == 0) { |
| LOG_INFO(", Sink PACs ccc not available"); |
| } |
| |
| if (hdl_pair.ccc_hdl != 0 && |
| !subscribe_for_notification(conn_id, leAudioDevice->address_, |
| hdl_pair)) { |
| disconnectInvalidDevice(leAudioDevice, |
| ", cound not subscribe for snk pac char", |
| LeAudioHealthDeviceStatType::INVALID_DB); |
| return; |
| } |
| |
| /* Obtain initial state of sink PACs */ |
| BtaGattQueue::ReadCharacteristic(conn_id, hdl_pair.val_hdl, |
| OnGattReadRspStatic, NULL); |
| |
| leAudioDevice->snk_pacs_.push_back(std::make_tuple( |
| hdl_pair, std::vector<struct le_audio::types::acs_ac_record>())); |
| |
| LOG_INFO( |
| "Found Sink PAC characteristic, handle: 0x%04x, ccc handle: " |
| "0x%04x, addr: %s", |
| charac.value_handle, hdl_pair.ccc_hdl, |
| ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_)); |
| } else if (charac.uuid == |
| le_audio::uuid:: |
| kSourcePublishedAudioCapabilityCharacteristicUuid) { |
| struct hdl_pair hdl_pair; |
| hdl_pair.val_hdl = charac.value_handle; |
| hdl_pair.ccc_hdl = find_ccc_handle(charac); |
| |
| if (hdl_pair.ccc_hdl == 0) { |
| LOG_INFO(", Source PACs ccc not available"); |
| } |
| |
| if (hdl_pair.ccc_hdl != 0 && |
| !subscribe_for_notification(conn_id, leAudioDevice->address_, |
| hdl_pair)) { |
| disconnectInvalidDevice(leAudioDevice, |
| ", could not subscribe for src pac char", |
| LeAudioHealthDeviceStatType::INVALID_DB); |
| return; |
| } |
| |
| /* Obtain initial state of source PACs */ |
| BtaGattQueue::ReadCharacteristic(conn_id, hdl_pair.val_hdl, |
| OnGattReadRspStatic, NULL); |
| |
| leAudioDevice->src_pacs_.push_back(std::make_tuple( |
| hdl_pair, std::vector<struct le_audio::types::acs_ac_record>())); |
| |
| LOG_INFO( |
| "Found Source PAC characteristic, handle: 0x%04x, ccc handle: " |
| "0x%04x, addr: %s", |
| charac.value_handle, hdl_pair.ccc_hdl, |
| ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_)); |
| } else if (charac.uuid == |
| le_audio::uuid::kSinkAudioLocationCharacteristicUuid) { |
| leAudioDevice->snk_audio_locations_hdls_.val_hdl = charac.value_handle; |
| leAudioDevice->snk_audio_locations_hdls_.ccc_hdl = |
| find_ccc_handle(charac); |
| |
| if (leAudioDevice->snk_audio_locations_hdls_.ccc_hdl == 0) { |
| LOG_INFO(", snk audio locations char doesn't have ccc"); |
| } |
| |
| if (leAudioDevice->snk_audio_locations_hdls_.ccc_hdl != 0 && |
| !subscribe_for_notification( |
| conn_id, leAudioDevice->address_, |
| leAudioDevice->snk_audio_locations_hdls_)) { |
| disconnectInvalidDevice( |
| leAudioDevice, ", could not subscribe for snk locations char", |
| LeAudioHealthDeviceStatType::INVALID_DB); |
| return; |
| } |
| |
| /* Obtain initial state of sink audio locations */ |
| BtaGattQueue::ReadCharacteristic( |
| conn_id, leAudioDevice->snk_audio_locations_hdls_.val_hdl, |
| OnGattReadRspStatic, NULL); |
| |
| LOG_INFO( |
| "Found Sink audio locations characteristic, handle: 0x%04x, ccc " |
| "handle: 0x%04x, addr: %s", |
| charac.value_handle, |
| leAudioDevice->snk_audio_locations_hdls_.ccc_hdl, |
| ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_)); |
| } else if (charac.uuid == |
| le_audio::uuid::kSourceAudioLocationCharacteristicUuid) { |
| leAudioDevice->src_audio_locations_hdls_.val_hdl = charac.value_handle; |
| leAudioDevice->src_audio_locations_hdls_.ccc_hdl = |
| find_ccc_handle(charac); |
| |
| if (leAudioDevice->src_audio_locations_hdls_.ccc_hdl == 0) { |
| LOG_INFO(", src audio locations char doesn't have ccc"); |
| } |
| |
| if (leAudioDevice->src_audio_locations_hdls_.ccc_hdl != 0 && |
| !subscribe_for_notification( |
| conn_id, leAudioDevice->address_, |
| leAudioDevice->src_audio_locations_hdls_)) { |
| disconnectInvalidDevice( |
| leAudioDevice, ", could not subscribe for src locations char", |
| LeAudioHealthDeviceStatType::INVALID_DB); |
| return; |
| } |
| |
| /* Obtain initial state of source audio locations */ |
| BtaGattQueue::ReadCharacteristic( |
| conn_id, leAudioDevice->src_audio_locations_hdls_.val_hdl, |
| OnGattReadRspStatic, NULL); |
| |
| LOG_INFO( |
| "Found Source audio locations characteristic, handle: 0x%04x, ccc " |
| "handle: 0x%04x, addr: %s", |
| charac.value_handle, |
| leAudioDevice->src_audio_locations_hdls_.ccc_hdl, |
| ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_)); |
| } else if (charac.uuid == |
| le_audio::uuid::kAudioContextAvailabilityCharacteristicUuid) { |
| leAudioDevice->audio_avail_hdls_.val_hdl = charac.value_handle; |
| leAudioDevice->audio_avail_hdls_.ccc_hdl = find_ccc_handle(charac); |
| |
| if (leAudioDevice->audio_avail_hdls_.ccc_hdl == 0) { |
| disconnectInvalidDevice(leAudioDevice, |
| ", audio avails char doesn't have ccc", |
| LeAudioHealthDeviceStatType::INVALID_DB); |
| return; |
| } |
| |
| if (!subscribe_for_notification(conn_id, leAudioDevice->address_, |
| leAudioDevice->audio_avail_hdls_)) { |
| disconnectInvalidDevice(leAudioDevice, |
| ", could not subscribe for audio avails char", |
| LeAudioHealthDeviceStatType::INVALID_DB); |
| return; |
| } |
| |
| /* Obtain initial state */ |
| BtaGattQueue::ReadCharacteristic( |
| conn_id, leAudioDevice->audio_avail_hdls_.val_hdl, |
| OnGattReadRspStatic, NULL); |
| |
| LOG_INFO( |
| "Found Audio Availability Context characteristic, handle: 0x%04x, " |
| "ccc handle: 0x%04x, addr: %s", |
| charac.value_handle, leAudioDevice->audio_avail_hdls_.ccc_hdl, |
| ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_)); |
| } else if (charac.uuid == |
| le_audio::uuid::kAudioSupportedContextCharacteristicUuid) { |
| leAudioDevice->audio_supp_cont_hdls_.val_hdl = charac.value_handle; |
| leAudioDevice->audio_supp_cont_hdls_.ccc_hdl = find_ccc_handle(charac); |
| |
| if (leAudioDevice->audio_supp_cont_hdls_.ccc_hdl == 0) { |
| LOG_INFO(", audio supported char doesn't have ccc"); |
| } |
| |
| if (leAudioDevice->audio_supp_cont_hdls_.ccc_hdl != 0 && |
| !subscribe_for_notification(conn_id, leAudioDevice->address_, |
| leAudioDevice->audio_supp_cont_hdls_)) { |
| disconnectInvalidDevice( |
| leAudioDevice, |
| ", could not subscribe for audio supported ctx char", |
| LeAudioHealthDeviceStatType::INVALID_DB); |
| return; |
| } |
| |
| /* Obtain initial state */ |
| BtaGattQueue::ReadCharacteristic( |
| conn_id, leAudioDevice->audio_supp_cont_hdls_.val_hdl, |
| OnGattReadRspStatic, NULL); |
| |
| LOG_INFO( |
| "Found Audio Supported Context characteristic, handle: 0x%04x, ccc " |
| "handle: 0x%04x, addr: %s", |
| charac.value_handle, leAudioDevice->audio_supp_cont_hdls_.ccc_hdl, |
| ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_)); |
| } |
| } |
| |
| /* Refresh ASE handles */ |
| leAudioDevice->ases_.clear(); |
| |
| for (const gatt::Characteristic& charac : ase_svc->characteristics) { |
| LOG(INFO) << "Found characteristic, uuid: " << charac.uuid.ToString(); |
| if (charac.uuid == le_audio::uuid::kSinkAudioStreamEndpointUuid || |
| charac.uuid == le_audio::uuid::kSourceAudioStreamEndpointUuid) { |
| uint16_t ccc_handle = find_ccc_handle(charac); |
| if (ccc_handle == 0) { |
| disconnectInvalidDevice(leAudioDevice, ", ASE char doesn't have ccc", |
| LeAudioHealthDeviceStatType::INVALID_DB); |
| return; |
| } |
| struct le_audio::types::hdl_pair hdls(charac.value_handle, ccc_handle); |
| if (!subscribe_for_notification(conn_id, leAudioDevice->address_, |
| hdls)) { |
| disconnectInvalidDevice(leAudioDevice, |
| ", could not subscribe ASE char", |
| LeAudioHealthDeviceStatType::INVALID_DB); |
| return; |
| } |
| |
| int direction = |
| charac.uuid == le_audio::uuid::kSinkAudioStreamEndpointUuid |
| ? le_audio::types::kLeAudioDirectionSink |
| : le_audio::types::kLeAudioDirectionSource; |
| |
| leAudioDevice->ases_.emplace_back(charac.value_handle, ccc_handle, |
| direction); |
| |
| LOG_INFO( |
| "Found ASE characteristic, handle: 0x%04x, ccc handle: 0x%04x, " |
| "direction: %d, addr: %s", |
| charac.value_handle, ccc_handle, direction, |
| ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_)); |
| } else if (charac.uuid == |
| le_audio::uuid:: |
| kAudioStreamEndpointControlPointCharacteristicUuid) { |
| leAudioDevice->ctp_hdls_.val_hdl = charac.value_handle; |
| leAudioDevice->ctp_hdls_.ccc_hdl = find_ccc_handle(charac); |
| |
| if (leAudioDevice->ctp_hdls_.ccc_hdl == 0) { |
| disconnectInvalidDevice(leAudioDevice, ", ASE ctp doesn't have ccc", |
| LeAudioHealthDeviceStatType::INVALID_DB); |
| return; |
| } |
| |
| if (!subscribe_for_notification(conn_id, leAudioDevice->address_, |
| leAudioDevice->ctp_hdls_)) { |
| disconnectInvalidDevice(leAudioDevice, |
| ", could not subscribe ASE char", |
| LeAudioHealthDeviceStatType::INVALID_DB); |
| return; |
| } |
| |
| LOG_INFO( |
| "Found ASE Control Point characteristic, handle: 0x%04x, " |
| "ccc handle: 0x%04x, addr: %s", |
| charac.value_handle, leAudioDevice->ctp_hdls_.ccc_hdl, |
| ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_)); |
| } |
| } |
| |
| if (tmas_svc) { |
| for (const gatt::Characteristic& charac : tmas_svc->characteristics) { |
| if (charac.uuid == |
| le_audio::uuid::kTelephonyMediaAudioProfileRoleCharacteristicUuid) { |
| leAudioDevice->tmap_role_hdl_ = charac.value_handle; |
| |
| /* Obtain initial state of TMAP role */ |
| BtaGattQueue::ReadCharacteristic(conn_id, |
| leAudioDevice->tmap_role_hdl_, |
| OnGattReadRspStatic, NULL); |
| |
| LOG_INFO( |
| "Found Telephony and Media Profile characteristic, handle: " |
| "0x%04x, " |
| "device: %s", |
| leAudioDevice->tmap_role_hdl_, |
| ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_)); |
| } |
| } |
| } |
| |
| leAudioDevice->known_service_handles_ = true; |
| leAudioDevice->notify_connected_after_read_ = true; |
| if (leAudioHealthStatus_) { |
| leAudioHealthStatus_->AddStatisticForDevice( |
| leAudioDevice, LeAudioHealthDeviceStatType::VALID_DB); |
| } |
| |
| /* If already known group id */ |
| if (leAudioDevice->group_id_ != bluetooth::groups::kGroupUnknown) { |
| AseInitialStateReadRequest(leAudioDevice); |
| return; |
| } |
| |
| /* If device does not belong to any group yet we either add it to the |
| * group by our selfs now or wait for Csis to do it. In both cases, let's |
| * check if group is already assigned. |
| */ |
| int group_id = DeviceGroups::Get()->GetGroupId( |
| leAudioDevice->address_, le_audio::uuid::kCapServiceUuid); |
| if (group_id != bluetooth::groups::kGroupUnknown) { |
| instance->group_add_node(group_id, leAudioDevice->address_); |
| return; |
| } |
| |
| /* CSIS will trigger adding to group */ |
| if (leAudioDevice->csis_member_) { |
| LOG_INFO(" %s, waiting for CSIS to create group for device ", |
| ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_)); |
| scheduleGuardForCsisAdd(leAudioDevice->address_); |
| return; |
| } |
| |
| LOG_INFO("%s Not a CSIS member. Create group by our own", |
| ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_)); |
| |
| /* If there is no Csis just add device by our own */ |
| DeviceGroups::Get()->AddDevice(leAudioDevice->address_, |
| le_audio::uuid::kCapServiceUuid); |
| } |
| |
| void OnGattWriteCcc(uint16_t conn_id, tGATT_STATUS status, uint16_t hdl, |
| void* data) { |
| LeAudioDevice* leAudioDevice = leAudioDevices_.FindByConnId(conn_id); |
| std::vector<struct ase>::iterator ase_it; |
| |
| if (!leAudioDevice) { |
| LOG(ERROR) << __func__ << ", unknown conn_id=" << loghex(conn_id); |
| return; |
| } |
| |
| if (status == GATT_DATABASE_OUT_OF_SYNC) { |
| LOG_INFO("Database out of sync for %s, conn_id: 0x%04x", |
| ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_), conn_id); |
| ClearDeviceInformationAndStartSearch(leAudioDevice); |
| return; |
| } |
| |
| if (status == GATT_SUCCESS) { |
| LOG_INFO("Successfully registered on ccc: 0x%04x, device: %s", hdl, |
| ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_)); |
| |
| if (leAudioDevice->ctp_hdls_.ccc_hdl == hdl && |
| leAudioDevice->known_service_handles_ && |
| !leAudioDevice->notify_connected_after_read_) { |
| /* Reconnection case. Control point is the last CCC LeAudio is |
| * registering for on reconnection */ |
| connectionReady(leAudioDevice); |
| } |
| |
| return; |
| } |
| |
| LOG_ERROR( |
| "Failed to register for indications: 0x%04x, device: %s, status: " |
| "0x%02x", |
| hdl, ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_), status); |
| |
| ase_it = |
| std::find_if(leAudioDevice->ases_.begin(), leAudioDevice->ases_.end(), |
| [&hdl](const struct ase& ase) -> bool { |
| return ase.hdls.ccc_hdl == hdl; |
| }); |
| |
| if (ase_it == leAudioDevice->ases_.end()) { |
| LOG_ERROR("Unknown ccc handle: 0x%04x, device: %s", hdl, |
| ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_)); |
| return; |
| } |
| |
| BTA_GATTC_DeregisterForNotifications(gatt_if_, leAudioDevice->address_, |
| ase_it->hdls.val_hdl); |
| } |
| |
| void AttachToStreamingGroupIfNeeded(LeAudioDevice* leAudioDevice) { |
| if (leAudioDevice->group_id_ != active_group_id_) { |
| LOG(INFO) << __func__ << " group " << leAudioDevice->group_id_ |
| << " is not streaming. Nothing to do"; |
| return; |
| } |
| |
| LeAudioDeviceGroup* group = aseGroups_.FindById(active_group_id_); |
| |
| auto group_metadata_contexts = |
| get_bidirectional(group->GetMetadataContexts()); |
| auto device_available_contexts = leAudioDevice->GetAvailableContexts(); |
| if (!group_metadata_contexts.test_any(device_available_contexts)) { |
| LOG_INFO("%s does is not have required context type", |
| ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_)); |
| return; |
| } |
| |
| LOG_INFO("Attaching to group: %d", leAudioDevice->group_id_); |
| |
| /* Restore configuration */ |
| auto* stream_conf = &group->stream_conf; |
| |
| if (audio_sender_state_ == AudioState::IDLE && |
| audio_receiver_state_ == AudioState::IDLE) { |
| DLOG(INFO) << __func__ |
| << " Device not streaming but active - nothing to do"; |
| return; |
| } |
| |
| if (!stream_conf->conf) { |
| LOG_INFO("Configuration not yet set. Nothing to do now"); |
| return; |
| } |
| |
| auto num_of_devices = |
| get_num_of_devices_in_configuration(stream_conf->conf); |
| |
| if (num_of_devices < group->NumOfConnected() && |
| !group->IsAudioSetConfigurationSupported(leAudioDevice, |
| stream_conf->conf)) { |
| /* Reconfigure if newly connected member device cannot support current |
| * codec configuration */ |
| group->SetPendingConfiguration(); |
| groupStateMachine_->StopStream(group); |
| stream_setup_start_timestamp_ = |
| bluetooth::common::time_get_os_boottime_us(); |
| return; |
| } |
| |
| /* Do not put the TBS CCID when not using Telecom for the VoIP calls. */ |
| auto ccid_contexts = group->GetMetadataContexts(); |
| if (IsInVoipCall() && !IsInCall()) { |
| ccid_contexts.sink.unset(LeAudioContextType::CONVERSATIONAL); |
| ccid_contexts.source.unset(LeAudioContextType::CONVERSATIONAL); |
| } |
| BidirectionalPair<std::vector<uint8_t>> ccids = { |
| .sink = ContentControlIdKeeper::GetInstance()->GetAllCcids( |
| ccid_contexts.sink), |
| .source = ContentControlIdKeeper::GetInstance()->GetAllCcids( |
| ccid_contexts.source)}; |
| |
| if (!groupStateMachine_->AttachToStream(group, leAudioDevice, |
| std::move(ccids))) { |
| LOG_WARN("Could not add device %s to the group %d streaming. ", |
| ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_), |
| group->group_id_); |
| scheduleAttachDeviceToTheStream(leAudioDevice->address_); |
| } else { |
| stream_setup_start_timestamp_ = |
| bluetooth::common::time_get_os_boottime_us(); |
| } |
| } |
| |
| void restartAttachToTheStream(const RawAddress& addr) { |
| LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(addr); |
| if (leAudioDevice == nullptr || |
| leAudioDevice->conn_id_ == GATT_INVALID_CONN_ID) { |
| LOG_INFO("Device %s not available anymore", |
| ADDRESS_TO_LOGGABLE_CSTR(addr)); |
| return; |
| } |
| AttachToStreamingGroupIfNeeded(leAudioDevice); |
| } |
| |
| void scheduleAttachDeviceToTheStream(const RawAddress& addr) { |
| LOG_INFO("Device %s scheduler for stream ", ADDRESS_TO_LOGGABLE_CSTR(addr)); |
| do_in_main_thread_delayed( |
| FROM_HERE, |
| base::BindOnce(&LeAudioClientImpl::restartAttachToTheStream, |
| weak_factory_.GetWeakPtr(), addr), |
| std::chrono::milliseconds(kDeviceAttachDelayMs)); |
| } |
| |
| void SendAudioGroupSelectableCodecConfigChanged(LeAudioDeviceGroup* group) { |
| // This shall be called when device gets active |
| auto* stream_conf = &group->stream_conf; |
| if (stream_conf == nullptr) { |
| LOG_WARN("Stream configuration is not valid for group id %d", |
| group->group_id_); |
| return; |
| } |
| |
| auto leAudioDevice = group->GetFirstDevice(); |
| callbacks_->OnAudioGroupSelectableCodecConf( |
| group->group_id_, |
| le_audio::utils::GetRemoteBtLeAudioCodecConfigFromPac( |
| leAudioDevice->snk_pacs_), |
| le_audio::utils::GetRemoteBtLeAudioCodecConfigFromPac( |
| leAudioDevice->src_pacs_)); |
| } |
| |
| void SendAudioGroupCurrentCodecConfigChanged(LeAudioDeviceGroup* group) { |
| // This shall be called when configuration changes |
| auto* stream_conf = &group->stream_conf; |
| if (stream_conf == nullptr) { |
| LOG_WARN("Stream configuration is not valid for group id %d", |
| group->group_id_); |
| return; |
| } |
| |
| bluetooth::le_audio::btle_audio_codec_config_t input_config{}; |
| le_audio::utils::fillStreamParamsToBtLeAudioCodecConfig( |
| stream_conf->codec_id, &stream_conf->stream_params.source, |
| input_config); |
| |
| bluetooth::le_audio::btle_audio_codec_config_t output_config{}; |
| le_audio::utils::fillStreamParamsToBtLeAudioCodecConfig( |
| stream_conf->codec_id, &stream_conf->stream_params.sink, output_config); |
| |
| callbacks_->OnAudioGroupCurrentCodecConf(group->group_id_, input_config, |
| output_config); |
| } |
| |
| void connectionReady(LeAudioDevice* leAudioDevice) { |
| LOG_DEBUG("%s, %s", ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_), |
| bluetooth::common::ToString(leAudioDevice->GetConnectionState()) |
| .c_str()); |
| |
| if (IS_FLAG_ENABLED(le_audio_fast_bond_params)) { |
| L2CA_LockBleConnParamsForProfileConnection(leAudioDevice->address_, |
| false); |
| } |
| callbacks_->OnConnectionState(ConnectionState::CONNECTED, |
| leAudioDevice->address_); |
| |
| if (leAudioDevice->GetConnectionState() == |
| DeviceConnectState::CONNECTED_BY_USER_GETTING_READY && |
| (leAudioDevice->autoconnect_flag_ == false)) { |
| btif_storage_set_leaudio_autoconnect(leAudioDevice->address_, true); |
| leAudioDevice->autoconnect_flag_ = true; |
| } |
| |
| leAudioDevice->SetConnectionState(DeviceConnectState::CONNECTED); |
| le_audio::MetricsCollector::Get()->OnConnectionStateChanged( |
| leAudioDevice->group_id_, leAudioDevice->address_, |
| ConnectionState::CONNECTED, le_audio::ConnectionStatus::SUCCESS); |
| |
| if (leAudioDevice->group_id_ != bluetooth::groups::kGroupUnknown) { |
| LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_); |
| if (group) { |
| UpdateLocationsAndContextsAvailability(group); |
| } |
| AttachToStreamingGroupIfNeeded(leAudioDevice); |
| |
| if (reconnection_mode_ == BTM_BLE_BKG_CONNECT_TARGETED_ANNOUNCEMENTS) { |
| /* Add other devices to allow list if there are any not yet connected |
| * from the group |
| */ |
| group->AddToAllowListNotConnectedGroupMembers(gatt_if_); |
| } |
| } |
| } |
| |
| bool IsAseAcceptingAudioData(struct ase* ase) { |
| if (ase == nullptr) return false; |
| if (ase->state != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) return false; |
| if (ase->data_path_state != DataPathState::CONFIGURED) return false; |
| |
| return true; |
| } |
| |
| // mix stero signal into mono |
| std::vector<uint8_t> mono_blend(const std::vector<uint8_t>& buf, |
| int bytes_per_sample, size_t frames) { |
| std::vector<uint8_t> mono_out; |
| mono_out.resize(frames * bytes_per_sample); |
| |
| if (bytes_per_sample == 2) { |
| int16_t* out = (int16_t*)mono_out.data(); |
| const int16_t* in = (int16_t*)(buf.data()); |
| for (size_t i = 0; i < frames; ++i) { |
| int accum = 0; |
| accum += *in++; |
| accum += *in++; |
| accum /= 2; // round to 0 |
| *out++ = accum; |
| } |
| } else if (bytes_per_sample == 4) { |
| int32_t* out = (int32_t*)mono_out.data(); |
| const int32_t* in = (int32_t*)(buf.data()); |
| for (size_t i = 0; i < frames; ++i) { |
| int accum = 0; |
| accum += *in++; |
| accum += *in++; |
| accum /= 2; // round to 0 |
| *out++ = accum; |
| } |
| } else { |
| LOG_ERROR("Don't know how to mono blend that %d!", bytes_per_sample); |
| } |
| return mono_out; |
| } |
| |
| void PrepareAndSendToTwoCises( |
| const std::vector<uint8_t>& data, |
| const struct le_audio::stream_parameters& stream_params) { |
| uint16_t left_cis_handle = 0; |
| uint16_t right_cis_handle = 0; |
| |
| uint16_t number_of_required_samples_per_channel = |
| sw_enc_left->GetNumOfSamplesPerChannel(); |
| uint8_t bytes_per_sample = sw_enc_left->GetNumOfBytesPerSample(); |
| if (data.size() < bytes_per_sample * 2 /* channels */ * |
| number_of_required_samples_per_channel) { |
| LOG(ERROR) << __func__ << " Missing samples. Data size: " << +data.size() |
| << " expected: " |
| << bytes_per_sample * 2 * |
| number_of_required_samples_per_channel; |
| return; |
| } |
| |
| for (auto [cis_handle, audio_location] : stream_params.stream_locations) { |
| if (audio_location & le_audio::codec_spec_conf::kLeAudioLocationAnyLeft) |
| left_cis_handle = cis_handle; |
| if (audio_location & le_audio::codec_spec_conf::kLeAudioLocationAnyRight) |
| right_cis_handle = cis_handle; |
| } |
| |
| uint16_t byte_count = stream_params.octets_per_codec_frame; |
| bool mix_to_mono = (left_cis_handle == 0) || (right_cis_handle == 0); |
| if (mix_to_mono) { |
| std::vector<uint8_t> mono = mono_blend( |
| data, bytes_per_sample, number_of_required_samples_per_channel); |
| if (left_cis_handle) { |
| sw_enc_left->Encode(mono.data(), 1, byte_count); |
| } |
| |
| if (right_cis_handle) { |
| sw_enc_left->Encode(mono.data(), 1, byte_count); |
| } |
| } else { |
| sw_enc_left->Encode(data.data(), 2, byte_count); |
| sw_enc_right->Encode(data.data() + bytes_per_sample, 2, byte_count); |
| } |
| |
| DLOG(INFO) << __func__ << " left_cis_handle: " << +left_cis_handle |
| << " right_cis_handle: " << right_cis_handle; |
| /* Send data to the controller */ |
| if (left_cis_handle) |
| IsoManager::GetInstance()->SendIsoData( |
| left_cis_handle, |
| (const uint8_t*)sw_enc_left->GetDecodedSamples().data(), |
| sw_enc_left->GetDecodedSamples().size() * 2); |
| |
| if (right_cis_handle) |
| IsoManager::GetInstance()->SendIsoData( |
| right_cis_handle, |
| (const uint8_t*)sw_enc_right->GetDecodedSamples().data(), |
| sw_enc_right->GetDecodedSamples().size() * 2); |
| } |
| |
| void PrepareAndSendToSingleCis( |
| const std::vector<uint8_t>& data, |
| const struct le_audio::stream_parameters& stream_params) { |
| uint16_t num_channels = stream_params.num_of_channels; |
| uint16_t cis_handle = stream_params.stream_locations.front().first; |
| |
| uint16_t number_of_required_samples_per_channel = |
| sw_enc_left->GetNumOfSamplesPerChannel(); |
| uint8_t bytes_per_sample = sw_enc_left->GetNumOfBytesPerSample(); |
| if ((int)data.size() < (bytes_per_sample * num_channels * |
| number_of_required_samples_per_channel)) { |
| LOG(ERROR) << __func__ << "Missing samples"; |
| return; |
| } |
| |
| uint16_t byte_count = stream_params.octets_per_codec_frame; |
| bool mix_to_mono = (num_channels == 1); |
| if (mix_to_mono) { |
| /* Since we always get two channels from framework, lets make it mono here |
| */ |
| std::vector<uint8_t> mono = mono_blend( |
| data, bytes_per_sample, number_of_required_samples_per_channel); |
| sw_enc_left->Encode(mono.data(), 1, byte_count); |
| } else { |
| sw_enc_left->Encode((const uint8_t*)data.data(), 2, byte_count); |
| // Output to the left channel buffer with `byte_count` offset |
| sw_enc_right->Encode((const uint8_t*)data.data() + 2, 2, byte_count, |
| &sw_enc_left->GetDecodedSamples(), byte_count); |
| } |
| |
| IsoManager::GetInstance()->SendIsoData( |
| cis_handle, (const uint8_t*)sw_enc_left->GetDecodedSamples().data(), |
| sw_enc_left->GetDecodedSamples().size() * 2); |
| } |
| |
| const struct le_audio::stream_configuration* GetStreamSinkConfiguration( |
| LeAudioDeviceGroup* group) { |
| const struct le_audio::stream_configuration* stream_conf = |
| &group->stream_conf; |
| LOG_INFO("group_id: %d", group->group_id_); |
| if (stream_conf->stream_params.sink.stream_locations.size() == 0) { |
| return nullptr; |
| } |
| |
| LOG_INFO("configuration: %s", stream_conf->conf->name.c_str()); |
| return stream_conf; |
| } |
| |
| void OnAudioDataReady(const std::vector<uint8_t>& data) { |
| if ((active_group_id_ == bluetooth::groups::kGroupUnknown) || |
| (audio_sender_state_ != AudioState::STARTED)) |
| return; |
| |
| LeAudioDeviceGroup* group = aseGroups_.FindById(active_group_id_); |
| if (!group) { |
| LOG(ERROR) << __func__ << "There is no streaming group available"; |
| return; |
| } |
| |
| auto stream_conf = group->stream_conf; |
| if ((stream_conf.stream_params.sink.num_of_devices > 2) || |
| (stream_conf.stream_params.sink.num_of_devices == 0) || |
| stream_conf.stream_params.sink.stream_locations.empty()) { |
| LOG(ERROR) << __func__ << " Stream configufation is not valid."; |
| return; |
| } |
| |
| if ((stream_conf.stream_params.sink.num_of_devices == 2) || |
| (stream_conf.stream_params.sink.stream_locations.size() == 2)) { |
| /* Streaming to two devices or one device with 2 CISes */ |
| PrepareAndSendToTwoCises(data, stream_conf.stream_params.sink); |
| } else { |
| /* Streaming to one device and 1 CIS */ |
| PrepareAndSendToSingleCis(data, stream_conf.stream_params.sink); |
| } |
| } |
| |
| void CleanCachedMicrophoneData() { |
| cached_channel_timestamp_ = 0; |
| cached_channel_ = nullptr; |
| } |
| |
| /* Handles audio data packets coming from the controller */ |
| void HandleIncomingCisData(uint8_t* data, uint16_t size, |
| uint16_t cis_conn_hdl, uint32_t timestamp) { |
| /* Get only one channel for MONO microphone */ |
| /* Gather data for channel */ |
| if ((active_group_id_ == bluetooth::groups::kGroupUnknown) || |
| (audio_receiver_state_ != AudioState::STARTED)) |
| return; |
| |
| LeAudioDeviceGroup* group = aseGroups_.FindById(active_group_id_); |
| if (!group) { |
| LOG(ERROR) << __func__ << "There is no streaming group available"; |
| return; |
| } |
| |
| if (DsaDataConsume(group, cis_conn_hdl, data, size, timestamp)) { |
| return; |
| } |
| |
| uint16_t left_cis_handle = 0; |
| uint16_t right_cis_handle = 0; |
| for (auto [cis_handle, audio_location] : |
| group->stream_conf.stream_params.source.stream_locations) { |
| if (audio_location & le_audio::codec_spec_conf::kLeAudioLocationAnyLeft) { |
| left_cis_handle = cis_handle; |
| } |
| if (audio_location & |
| le_audio::codec_spec_conf::kLeAudioLocationAnyRight) { |
| right_cis_handle = cis_handle; |
| } |
| } |
| |
| auto decoder = sw_dec_left.get(); |
| if (cis_conn_hdl == left_cis_handle) { |
| decoder = sw_dec_left.get(); |
| } else if (cis_conn_hdl == right_cis_handle) { |
| decoder = sw_dec_right.get(); |
| } else { |
| LOG_ERROR("Received data for unknown handle: %04x", cis_conn_hdl); |
| return; |
| } |
| |
| if (!left_cis_handle || !right_cis_handle) { |
| /* mono or just one device connected */ |
| decoder->Decode(data, size); |
| SendAudioDataToAF(&decoder->GetDecodedSamples()); |
| return; |
| } |
| /* both devices are connected */ |
| |
| if (cached_channel_ == nullptr || |
| cached_channel_->GetDecodedSamples().empty()) { |
| /* First packet received, cache it. We need both channel data to send it |
| * to AF. */ |
| decoder->Decode(data, size); |
| cached_channel_timestamp_ = timestamp; |
| cached_channel_ = decoder; |
| return; |
| } |
| |
| /* We received either data for the other audio channel, or another |
| * packet for same channel */ |
| if (cached_channel_ != decoder) { |
| /* It's data for the 2nd channel */ |
| if (timestamp == cached_channel_timestamp_) { |
| /* Ready to mix data and send out to AF */ |
| decoder->Decode(data, size); |
| SendAudioDataToAF(&sw_dec_left->GetDecodedSamples(), |
| &sw_dec_right->GetDecodedSamples()); |
| |
| CleanCachedMicrophoneData(); |
| return; |
| } |
| |
| /* 2nd Channel is in the future compared to the cached data. |
| Send the cached data to AF, and keep the new channel data in cache. |
| This should happen only during stream setup */ |
| SendAudioDataToAF(&decoder->GetDecodedSamples()); |
| |
| decoder->Decode(data, size); |
| cached_channel_timestamp_ = timestamp; |
| cached_channel_ = decoder; |
| return; |
| } |
| |
| /* Data for same channel received. 2nd channel is down/not sending |
| * data */ |
| |
| /* Send the cached data out */ |
| SendAudioDataToAF(&decoder->GetDecodedSamples()); |
| |
| /* Cache the data in case 2nd channel connects */ |
| decoder->Decode(data, size); |
| cached_channel_timestamp_ = timestamp; |
| cached_channel_ = decoder; |
| } |
| |
| void SendAudioDataToAF(std::vector<int16_t>* left, |
| std::vector<int16_t>* right = nullptr) { |
| uint16_t to_write = 0; |
| uint16_t written = 0; |
| |
| bool af_is_stereo = (audio_framework_sink_config.num_channels == 2); |
| bool bt_got_stereo = (left != nullptr) & (right != nullptr); |
| |
| if (!af_is_stereo) { |
| if (!bt_got_stereo) { |
| std::vector<int16_t>* mono = left ? left : right; |
| /* mono audio over bluetooth, audio framework expects mono */ |
| to_write = sizeof(int16_t) * mono->size(); |
| written = le_audio_sink_hal_client_->SendData((uint8_t*)mono->data(), |
| to_write); |
| } else { |
| /* stereo audio over bluetooth, audio framework expects mono */ |
| for (size_t i = 0; i < left->size(); i++) { |
| (*left)[i] = ((*left)[i] + (*right)[i]) / 2; |
| } |
| to_write = sizeof(int16_t) * left->size(); |
| written = le_audio_sink_hal_client_->SendData((uint8_t*)left->data(), |
| to_write); |
| } |
| } else { |
| /* mono audio over bluetooth, audio framework expects stereo |
| * Here we handle stream without checking bt_got_stereo flag. |
| */ |
| const size_t mono_size = left ? left->size() : right->size(); |
| std::vector<uint16_t> mixed(mono_size * 2); |
| |
| for (size_t i = 0; i < mono_size; i++) { |
| mixed[2 * i] = left ? (*left)[i] : (*right)[i]; |
| mixed[2 * i + 1] = right ? (*right)[i] : (*left)[i]; |
| } |
| to_write = sizeof(int16_t) * mixed.size(); |
| written = |
| le_audio_sink_hal_client_->SendData((uint8_t*)mixed.data(), to_write); |
| } |
| |
| /* TODO: What to do if not all data sinked ? */ |
| if (written != to_write) LOG(ERROR) << __func__ << ", not all data sinked"; |
| } |
| |
| void ConfirmLocalAudioSourceStreamingRequest() { |
| le_audio_source_hal_client_->ConfirmStreamingRequest(); |
| |
| LeAudioLogHistory::Get()->AddLogHistory( |
| kLogBtCallAf, active_group_id_, RawAddress::kEmpty, |
| kLogAfResumeConfirm + "LocalSource", |
| "s_state: " + ToString(audio_sender_state_) + "-> STARTED"); |
| |
| audio_sender_state_ = AudioState::STARTED; |
| } |
| |
| void ConfirmLocalAudioSinkStreamingRequest() { |
| le_audio_sink_hal_client_->ConfirmStreamingRequest(); |
| |
| LeAudioLogHistory::Get()->AddLogHistory( |
| kLogBtCallAf, active_group_id_, RawAddress::kEmpty, |
| kLogAfResumeConfirm + "LocalSink", |
| "r_state: " + ToString(audio_receiver_state_) + "-> STARTED"); |
| |
| audio_receiver_state_ = AudioState::STARTED; |
| } |
| |
| void StartSendingAudio(int group_id) { |
| LOG(INFO) << __func__; |
| |
| LeAudioDeviceGroup* group = aseGroups_.FindById(group_id); |
| LeAudioDevice* device = group->GetFirstActiveDevice(); |
| LOG_ASSERT(device) << __func__ |
| << " Shouldn't be called without an active device."; |
| |
| /* Assume 2 ases max just for now. */ |
| auto* stream_conf = GetStreamSinkConfiguration(group); |
| if (stream_conf == nullptr) { |
| LOG(ERROR) << __func__ << " could not get sink configuration"; |
| groupStateMachine_->StopStream(group); |
| return; |
| } |
| |
| LOG_DEBUG("Sink stream config (#%d):\n", |
| static_cast<int>( |
| stream_conf->stream_params.sink.stream_locations.size())); |
| for (auto stream : stream_conf->stream_params.sink.stream_locations) { |
| LOG_DEBUG("Cis handle: 0x%02x, allocation 0x%04x\n", stream.first, |
| stream.second); |
| } |
| LOG_DEBUG("Source stream config (#%d):\n", |
| static_cast<int>( |
| stream_conf->stream_params.source.stream_locations.size())); |
| for (auto stream : stream_conf->stream_params.source.stream_locations) { |
| LOG_DEBUG("Cis handle: 0x%02x, allocation 0x%04x\n", stream.first, |
| stream.second); |
| } |
| |
| uint16_t remote_delay_ms = |
| group->GetRemoteDelay(le_audio::types::kLeAudioDirectionSink); |
| if (CodecManager::GetInstance()->GetCodecLocation() == |
| le_audio::types::CodecLocation::HOST) { |
| if (sw_enc_left || sw_enc_right) { |
| LOG(WARNING) |
| << " The encoder instance should have been already released."; |
| } |
| sw_enc_left = |
| le_audio::CodecInterface::CreateInstance(stream_conf->codec_id); |
| auto codec_status = sw_enc_left->InitEncoder( |
| audio_framework_source_config, current_source_codec_config); |
| if (codec_status != le_audio::CodecInterface::Status::STATUS_OK) { |
| LOG_ERROR("Left channel codec setup failed with err: %d", codec_status); |
| groupStateMachine_->StopStream(group); |
| return; |
| } |
| |
| sw_enc_right = |
| le_audio::CodecInterface::CreateInstance(stream_conf->codec_id); |
| codec_status = sw_enc_right->InitEncoder(audio_framework_source_config, |
| current_source_codec_config); |
| if (codec_status != le_audio::CodecInterface::Status::STATUS_OK) { |
| LOG_ERROR("Right channel codec setup failed with err: %d", |
| codec_status); |
| groupStateMachine_->StopStream(group); |
| return; |
| } |
| } |
| |
| le_audio_source_hal_client_->UpdateRemoteDelay(remote_delay_ms); |
| ConfirmLocalAudioSourceStreamingRequest(); |
| |
| if (!LeAudioHalVerifier::SupportsStreamActiveApi()) { |
| /* We update the target audio allocation before streamStarted so that the |
| * CodecManager would know how to configure the encoder. */ |
| BidirectionalPair<uint16_t> delays_pair = { |
| .sink = group->GetRemoteDelay(le_audio::types::kLeAudioDirectionSink), |
| .source = |
| group->GetRemoteDelay(le_audio::types::kLeAudioDirectionSource)}; |
| CodecManager::GetInstance()->UpdateActiveAudioConfig( |
| group->stream_conf.stream_params, delays_pair, |
| std::bind(&LeAudioClientImpl::UpdateAudioConfigToHal, |
| weak_factory_.GetWeakPtr(), std::placeholders::_1, |
| std::placeholders::_2)); |
| } |
| } |
| |
| const struct le_audio::stream_configuration* GetStreamSourceConfiguration( |
| LeAudioDeviceGroup* group) { |
| const struct le_audio::stream_configuration* stream_conf = |
| &group->stream_conf; |
| if (stream_conf->stream_params.source.stream_locations.size() == 0) { |
| return nullptr; |
| } |
| LOG_INFO("configuration: %s", stream_conf->conf->name.c_str()); |
| return stream_conf; |
| } |
| |
| void StartReceivingAudio(int group_id) { |
| LOG(INFO) << __func__; |
| |
| LeAudioDeviceGroup* group = aseGroups_.FindById(group_id); |
| |
| auto* stream_conf = GetStreamSourceConfiguration(group); |
| if (!stream_conf) { |
| LOG(WARNING) << " Could not get source configuration for group " |
| << active_group_id_ << " probably microphone not configured"; |
| groupStateMachine_->StopStream(group); |
| return; |
| } |
| |
| uint16_t remote_delay_ms = |
| group->GetRemoteDelay(le_audio::types::kLeAudioDirectionSource); |
| |
| CleanCachedMicrophoneData(); |
| |
| if (CodecManager::GetInstance()->GetCodecLocation() == |
| le_audio::types::CodecLocation::HOST) { |
| if (sw_dec_left.get() || sw_dec_right.get()) { |
| LOG(WARNING) |
| << " The decoder instance should have been already released."; |
| } |
| sw_dec_left = |
| le_audio::CodecInterface::CreateInstance(stream_conf->codec_id); |
| auto codec_status = sw_dec_left->InitDecoder(current_sink_codec_config, |
| audio_framework_sink_config); |
| if (codec_status != le_audio::CodecInterface::Status::STATUS_OK) { |
| LOG_ERROR("Left channel codec setup failed with err: %d", codec_status); |
| groupStateMachine_->StopStream(group); |
| return; |
| } |
| |
| sw_dec_right = |
| le_audio::CodecInterface::CreateInstance(stream_conf->codec_id); |
| codec_status = sw_dec_right->InitDecoder(current_sink_codec_config, |
| audio_framework_sink_config); |
| if (codec_status != le_audio::CodecInterface::Status::STATUS_OK) { |
| LOG_ERROR("Right channel codec setup failed with err: %d", |
| codec_status); |
| groupStateMachine_->StopStream(group); |
| return; |
| } |
| } |
| le_audio_sink_hal_client_->UpdateRemoteDelay(remote_delay_ms); |
| ConfirmLocalAudioSinkStreamingRequest(); |
| |
| if (!LeAudioHalVerifier::SupportsStreamActiveApi()) { |
| /* We update the target audio allocation before streamStarted so that the |
| * CodecManager would know how to configure the encoder. */ |
| BidirectionalPair<uint16_t> delays_pair = { |
| .sink = group->GetRemoteDelay(le_audio::types::kLeAudioDirectionSink), |
| .source = |
| group->GetRemoteDelay(le_audio::types::kLeAudioDirectionSource)}; |
| CodecManager::GetInstance()->UpdateActiveAudioConfig( |
| group->stream_conf.stream_params, delays_pair, |
| std::bind(&LeAudioClientImpl::UpdateAudioConfigToHal, |
| weak_factory_.GetWeakPtr(), std::placeholders::_1, |
| std::placeholders::_2)); |
| } |
| } |
| |
| void SuspendAudio(void) { |
| CancelStreamingRequest(); |
| |
| if (sw_enc_left) sw_enc_left.reset(); |
| if (sw_enc_right) sw_enc_right.reset(); |
| if (sw_dec_left) sw_dec_left.reset(); |
| if (sw_dec_right) sw_dec_right.reset(); |
| CleanCachedMicrophoneData(); |
| } |
| |
| void StopAudio(void) { SuspendAudio(); } |
| |
| void printSingleConfiguration(int fd, LeAudioCodecConfiguration* conf, |
| bool print_audio_state, bool sender = false) { |
| std::stringstream stream; |
| if (print_audio_state) { |
| if (sender) { |
| stream << "\taudio sender state: " << audio_sender_state_ << "\n"; |
| } else { |
| stream << "\taudio receiver state: " << audio_receiver_state_ << "\n"; |
| } |
| } |
| |
| stream << "\tsample rate: " << +conf->sample_rate |
| << ",\tchan: " << +conf->num_channels |
| << ",\tbits: " << +conf->bits_per_sample |
| << ",\tdata_interval_us: " << +conf->data_interval_us << "\n"; |
| |
| dprintf(fd, "%s", stream.str().c_str()); |
| } |
| |
| void printCurrentStreamConfiguration(int fd) { |
| auto conf = &audio_framework_source_config; |
| dprintf(fd, " Speaker codec config (audio framework) \n"); |
| if (conf) { |
| printSingleConfiguration(fd, conf, false); |
| } |
| |
| dprintf(fd, " Microphone codec config (audio framework) \n"); |
| conf = &audio_framework_sink_config; |
| if (conf) { |
| printSingleConfiguration(fd, conf, false); |
| } |
| |
| conf = ¤t_source_codec_config; |
| dprintf(fd, " Speaker codec config (Bluetooth)\n"); |
| if (conf) { |
| printSingleConfiguration(fd, conf, true, true); |
| } |
| |
| conf = ¤t_sink_codec_config; |
| dprintf(fd, " Microphone codec config (Bluetooth)\n"); |
| if (conf) { |
| printSingleConfiguration(fd, conf, true, false); |
| } |
| } |
| |
| void Dump(int fd) { |
| dprintf(fd, " APP ID: %d \n", gatt_if_); |
| dprintf(fd, " Active group: %d\n", active_group_id_); |
| dprintf(fd, " reconnection mode: %s \n", |
| (reconnection_mode_ == BTM_BLE_BKG_CONNECT_ALLOW_LIST |
| ? "Allow List" |
| : "Targeted Announcements")); |
| dprintf(fd, " configuration: %s (0x%08hx)\n", |
| bluetooth::common::ToString(configuration_context_type_).c_str(), |
| configuration_context_type_); |
| dprintf(fd, " local source metadata context type mask: %s\n", |
| local_metadata_context_types_.source.to_string().c_str()); |
| dprintf(fd, " local sink metadata context type mask: %s\n", |
| local_metadata_context_types_.sink.to_string().c_str()); |
| dprintf(fd, " TBS state: %s\n", in_call_ ? " In call" : "No calls"); |
| dprintf(fd, " Sink listening mode: %s\n", |
| sink_monitor_mode_ ? "true" : "false"); |
| if (sink_monitor_notified_status_) { |
| dprintf(fd, " Local sink notified state: %d\n", |
| sink_monitor_notified_status_.value()); |
| } |
| dprintf(fd, " Source monitor mode: %s\n", |
| source_monitor_mode_ ? "true" : "false"); |
| dprintf(fd, " Start time: "); |
| for (auto t : stream_start_history_queue_) { |
| dprintf(fd, ", %d ms", static_cast<int>(t)); |
| } |
| dprintf(fd, "\n"); |
| printCurrentStreamConfiguration(fd); |
| dprintf(fd, " ----------------\n "); |
| dprintf(fd, " LE Audio Groups:\n"); |
| aseGroups_.Dump(fd, active_group_id_); |
| dprintf(fd, "\n Not grouped devices:\n"); |
| leAudioDevices_.Dump(fd, bluetooth::groups::kGroupUnknown); |
| |
| if (leAudioHealthStatus_) { |
| leAudioHealthStatus_->DebugDump(fd); |
| } |
| } |
| |
| void Cleanup() { |
| StopVbcCloseTimeout(); |
| if (alarm_is_scheduled(suspend_timeout_)) alarm_cancel(suspend_timeout_); |
| |
| if (active_group_id_ != bluetooth::groups::kGroupUnknown) { |
| /* Bluetooth turned off while streaming */ |
| StopAudio(); |
| SetUnicastMonitorMode(le_audio::types::kLeAudioDirectionSink, false); |
| ClientAudioInterfaceRelease(); |
| } else { |
| /* There may be not stopped Sink HAL client due to set Listening mode */ |
| if (sink_monitor_mode_) { |
| SetUnicastMonitorMode(le_audio::types::kLeAudioDirectionSink, false); |
| } |
| } |
| groupStateMachine_->Cleanup(); |
| aseGroups_.Cleanup(); |
| lastNotifiedGroupStreamStatusMap_.clear(); |
| leAudioDevices_.Cleanup(gatt_if_); |
| if (gatt_if_) BTA_GATTC_AppDeregister(gatt_if_); |
| |
| if (leAudioHealthStatus_) { |
| leAudioHealthStatus_->Cleanup(); |
| } |
| } |
| |
| AudioReconfigurationResult UpdateConfigAndCheckIfReconfigurationIsNeeded( |
| int group_id, LeAudioContextType context_type) { |
| bool reconfiguration_needed = false; |
| bool sink_cfg_available = true; |
| bool source_cfg_available = true; |
| |
| LOG_DEBUG("Checking whether to reconfigure from %s to %s", |
| ToString(configuration_context_type_).c_str(), |
| ToString(context_type).c_str()); |
| |
| auto group = aseGroups_.FindById(group_id); |
| if (!group) { |
| LOG(ERROR) << __func__ |
| << ", Invalid group: " << static_cast<int>(group_id); |
| return AudioReconfigurationResult::RECONFIGURATION_NOT_NEEDED; |
| } |
| |
| std::optional<LeAudioCodecConfiguration> source_configuration = |
| group->GetCodecConfigurationByDirection( |
| context_type, le_audio::types::kLeAudioDirectionSink); |
| std::optional<LeAudioCodecConfiguration> sink_configuration = |
| group->GetCodecConfigurationByDirection( |
| context_type, le_audio::types::kLeAudioDirectionSource); |
| |
| if (source_configuration) { |
| if (*source_configuration != current_source_codec_config) { |
| current_source_codec_config = *source_configuration; |
| reconfiguration_needed = true; |
| } |
| } else { |
| if (!current_source_codec_config.IsInvalid()) { |
| current_source_codec_config = {0, 0, 0, 0}; |
| reconfiguration_needed = true; |
| } |
| source_cfg_available = false; |
| } |
| |
| if (sink_configuration) { |
| if (*sink_configuration != current_sink_codec_config) { |
| current_sink_codec_config = *sink_configuration; |
| reconfiguration_needed = true; |
| } |
| } else { |
| if (!current_sink_codec_config.IsInvalid()) { |
| current_sink_codec_config = {0, 0, 0, 0}; |
| reconfiguration_needed = true; |
| } |
| |
| sink_cfg_available = false; |
| } |
| |
| if (DsaReconfigureNeeded(group, context_type)) { |
| reconfiguration_needed = true; |
| } |
| |
| LOG_DEBUG( |
| " Context: %s Reconfiguration_needed = %d, sink_cfg_available = %d, " |
| "source_cfg_available = %d", |
| ToString(context_type).c_str(), reconfiguration_needed, |
| sink_cfg_available, source_cfg_available); |
| |
| if (!reconfiguration_needed) { |
| // Assign the new configuration context as it reprents the current |
| // use case even when it eventually ends up being the exact same |
| // codec and qos configuration. |
| if (configuration_context_type_ != context_type) { |
| configuration_context_type_ = context_type; |
| group->SetConfigurationContextType(context_type); |
| } |
| return AudioReconfigurationResult::RECONFIGURATION_NOT_NEEDED; |
| } |
| |
| if (!sink_cfg_available && !source_cfg_available) { |
| return AudioReconfigurationResult::RECONFIGURATION_NOT_POSSIBLE; |
| } |
| |
| LOG_INFO(" Session reconfiguration needed group: %d for context type: %s", |
| group->group_id_, ToHexString(context_type).c_str()); |
| |
| configuration_context_type_ = context_type; |
| return AudioReconfigurationResult::RECONFIGURATION_NEEDED; |
| } |
| |
| /* Returns true if stream is started */ |
| bool OnAudioResume(LeAudioDeviceGroup* group, int local_direction) { |
| auto remote_direction = |
| (local_direction == le_audio::types::kLeAudioDirectionSink |
| ? le_audio::types::kLeAudioDirectionSource |
| : le_audio::types::kLeAudioDirectionSink); |
| |
| auto remote_contexts = |
| DirectionalRealignMetadataAudioContexts(group, remote_direction); |
| ApplyRemoteMetadataAudioContextPolicy(group, remote_contexts, |
| remote_direction); |
| |
| if (!remote_contexts.sink.any() && !remote_contexts.source.any()) { |
| LOG_WARN("Requested context type not available on the remote side"); |
| if (leAudioHealthStatus_) { |
| leAudioHealthStatus_->AddStatisticForGroup( |
| group, LeAudioHealthGroupStatType::STREAM_CONTEXT_NOT_AVAILABLE); |
| } |
| return false; |
| } |
| |
| return GroupStream(active_group_id_, configuration_context_type_, |
| remote_contexts); |
| } |
| |
| void OnAudioSuspend() { |
| if (active_group_id_ == bluetooth::groups::kGroupUnknown) { |
| LOG(WARNING) << ", there is no longer active group"; |
| return; |
| } |
| |
| if (stack_config_get_interface() |
| ->get_pts_le_audio_disable_ases_before_stopping()) { |
| LOG_INFO("Stream disable_timer_ started"); |
| if (alarm_is_scheduled(disable_timer_)) alarm_cancel(disable_timer_); |
| |
| alarm_set_on_mloop( |
| disable_timer_, kAudioDisableTimeoutMs, |
| [](void* data) { |
| if (instance) instance->GroupSuspend(PTR_TO_INT(data)); |
| }, |
| INT_TO_PTR(active_group_id_)); |
| } |
| |
| /* Group should tie in time to get requested status */ |
| uint64_t timeoutMs = kAudioSuspentKeepIsoAliveTimeoutMs; |
| timeoutMs = osi_property_get_int32(kAudioSuspentKeepIsoAliveTimeoutMsProp, |
| timeoutMs); |
| |
| if (stack_config_get_interface() |
| ->get_pts_le_audio_disable_ases_before_stopping()) { |
| timeoutMs += kAudioDisableTimeoutMs; |
| } |
| |
| LOG_DEBUG("Stream suspend_timeout_ started: %d ms", |
| static_cast<int>(timeoutMs)); |
| if (alarm_is_scheduled(suspend_timeout_)) alarm_cancel(suspend_timeout_); |
| |
| alarm_set_on_mloop( |
| suspend_timeout_, timeoutMs, |
| [](void* data) { |
| if (instance) instance->GroupStop(PTR_TO_INT(data)); |
| }, |
| INT_TO_PTR(active_group_id_)); |
| } |
| |
| void OnLocalAudioSourceSuspend() { |
| LOG_INFO( |
| "active group_id: %d, IN: audio_receiver_state_: %s, " |
| "audio_sender_state_: %s", |
| active_group_id_, ToString(audio_receiver_state_).c_str(), |
| ToString(audio_sender_state_).c_str()); |
| LeAudioLogHistory::Get()->AddLogHistory( |
| kLogAfCallBt, active_group_id_, RawAddress::kEmpty, |
| kLogAfSuspend + "LocalSource", |
| "r_state: " + ToString(audio_receiver_state_) + |
| ", s_state: " + ToString(audio_sender_state_)); |
| |
| /* Note: This callback is from audio hal driver. |
| * Bluetooth peer is a Sink for Audio Framework. |
| * e.g. Peer is a speaker |
| */ |
| switch (audio_sender_state_) { |
| case AudioState::READY_TO_START: |
| case AudioState::STARTED: |
| audio_sender_state_ = AudioState::READY_TO_RELEASE; |
| break; |
| case AudioState::RELEASING: |
| return; |
| case AudioState::IDLE: |
| if (audio_receiver_state_ == AudioState::READY_TO_RELEASE) { |
| OnAudioSuspend(); |
| } |
| return; |
| case AudioState::READY_TO_RELEASE: |
| break; |
| } |
| |
| /* Last suspends group - triggers group stop */ |
| if ((audio_receiver_state_ == AudioState::IDLE) || |
| (audio_receiver_state_ == AudioState::READY_TO_RELEASE)) { |
| OnAudioSuspend(); |
| le_audio::MetricsCollector::Get()->OnStreamEnded(active_group_id_); |
| } |
| |
| LOG_INFO("OUT: audio_receiver_state_: %s, audio_sender_state_: %s", |
| ToString(audio_receiver_state_).c_str(), |
| ToString(audio_sender_state_).c_str()); |
| |
| LeAudioLogHistory::Get()->AddLogHistory( |
| kLogBtCallAf, active_group_id_, RawAddress::kEmpty, |
| kLogAfSuspendConfirm + "LocalSource", |
| "r_state: " + ToString(audio_receiver_state_) + |
| "s_state: " + ToString(audio_sender_state_)); |
| } |
| |
| void OnLocalAudioSourceResume() { |
| LOG_INFO( |
| "active group_id: %d, IN: audio_receiver_state_: %s, " |
| "audio_sender_state_: %s", |
| active_group_id_, ToString(audio_receiver_state_).c_str(), |
| ToString(audio_sender_state_).c_str()); |
| LeAudioLogHistory::Get()->AddLogHistory( |
| kLogAfCallBt, active_group_id_, RawAddress::kEmpty, |
| kLogAfResume + "LocalSource", |
| "r_state: " + ToString(audio_receiver_state_) + |
| ", s_state: " + ToString(audio_sender_state_)); |
| |
| /* Note: This callback is from audio hal driver. |
| * Bluetooth peer is a Sink for Audio Framework. |
| * e.g. Peer is a speaker |
| */ |
| auto group = aseGroups_.FindById(active_group_id_); |
| if (!group) { |
| LOG(ERROR) << __func__ |
| << ", Invalid group: " << static_cast<int>(active_group_id_); |
| return; |
| } |
| |
| /* Check if the device resume is allowed */ |
| if (!group->GetCodecConfigurationByDirection( |
| configuration_context_type_, |
| le_audio::types::kLeAudioDirectionSink)) { |
| LOG(ERROR) << __func__ << ", invalid resume request for context type: " |
| << ToHexString(configuration_context_type_); |
| CancelLocalAudioSourceStreamingRequest(); |
| return; |
| } |
| |
| DLOG(INFO) << __func__ << " active_group_id: " << active_group_id_ << "\n" |
| << " audio_receiver_state: " << audio_receiver_state_ << "\n" |
| << " audio_sender_state: " << audio_sender_state_ << "\n" |
| << " configuration_context_type_: " |
| << ToHexString(configuration_context_type_) << "\n" |
| << " group " << (group ? " exist " : " does not exist ") << "\n"; |
| |
| switch (audio_sender_state_) { |
| case AudioState::STARTED: |
| /* Looks like previous Confirm did not get to the Audio Framework*/ |
| ConfirmLocalAudioSourceStreamingRequest(); |
| break; |
| case AudioState::IDLE: |
| switch (audio_receiver_state_) { |
| case AudioState::IDLE: |
| /* Stream is not started. Try to do it.*/ |
| if (OnAudioResume(group, |
| le_audio::types::kLeAudioDirectionSource)) { |
| audio_sender_state_ = AudioState::READY_TO_START; |
| } else { |
| CancelLocalAudioSourceStreamingRequest(); |
| } |
| break; |
| case AudioState::READY_TO_START: |
| audio_sender_state_ = AudioState::READY_TO_START; |
| if (!IsDirectionAvailableForCurrentConfiguration( |
| group, le_audio::types::kLeAudioDirectionSink)) { |
| LOG_WARN( |
| " sink is not configured. \n audio_receiver_state: %s \n" |
| "audio_sender_state: %s \n isPendingConfiguration: %s \n " |
| "Reconfiguring to %s", |
| ToString(audio_receiver_state_).c_str(), |
| ToString(audio_sender_state_).c_str(), |
| (group->IsPendingConfiguration() ? "true" : "false"), |
| ToString(configuration_context_type_).c_str()); |
| group->PrintDebugState(); |
| SetConfigurationAndStopStreamWhenNeeded( |
| group, configuration_context_type_); |
| } |
| break; |
| case AudioState::STARTED: |
| audio_sender_state_ = AudioState::READY_TO_START; |
| /* If signalling part is completed trigger start sending audio |
| * here, otherwise it'll be called on group streaming state callback |
| */ |
| if (group->GetState() == |
| AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { |
| if (IsDirectionAvailableForCurrentConfiguration( |
| group, le_audio::types::kLeAudioDirectionSink)) { |
| StartSendingAudio(active_group_id_); |
| } else { |
| LOG_WARN( |
| " sink is not configured. \n audio_receiver_state: %s \n" |
| "audio_sender_state: %s \n isPendingConfiguration: %s \n " |
| "Reconfiguring to %s", |
| ToString(audio_receiver_state_).c_str(), |
| ToString(audio_sender_state_).c_str(), |
| (group->IsPendingConfiguration() ? "true" : "false"), |
| ToString(configuration_context_type_).c_str()); |
| group->PrintDebugState(); |
| SetConfigurationAndStopStreamWhenNeeded( |
| group, configuration_context_type_); |
| } |
| } else { |
| LOG_ERROR( |
| " called in wrong state. \n audio_receiver_state: %s \n" |
| "audio_sender_state: %s \n isPendingConfiguration: %s \n " |
| "Reconfiguring to %s", |
| ToString(audio_receiver_state_).c_str(), |
| ToString(audio_sender_state_).c_str(), |
| (group->IsPendingConfiguration() ? "true" : "false"), |
| ToString(configuration_context_type_).c_str()); |
| group->PrintDebugState(); |
| CancelStreamingRequest(); |
| } |
| break; |
| case AudioState::RELEASING: |
| /* Group is reconfiguring, reassing state and wait for |
| * the stream to be configured |
| */ |
| audio_sender_state_ = audio_receiver_state_; |
| break; |
| case AudioState::READY_TO_RELEASE: |
| /* If the other direction is streaming we can start sending audio */ |
| if (group->GetState() == |
| AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { |
| if (IsDirectionAvailableForCurrentConfiguration( |
| group, le_audio::types::kLeAudioDirectionSink)) { |
| StartSendingAudio(active_group_id_); |
| } else { |
| LOG_WARN( |
| " sink is not configured. \n audio_receiver_state: %s \n" |
| "audio_sender_state: %s \n isPendingConfiguration: %s \n " |
| "Reconfiguring to %s", |
| ToString(audio_receiver_state_).c_str(), |
| ToString(audio_sender_state_).c_str(), |
| (group->IsPendingConfiguration() ? "true" : "false"), |
| ToString(configuration_context_type_).c_str()); |
| group->PrintDebugState(); |
| SetConfigurationAndStopStreamWhenNeeded( |
| group, configuration_context_type_); |
| } |
| } else { |
| LOG_ERROR( |
| " called in wrong state. \n audio_receiver_state: %s \n" |
| "audio_sender_state: %s \n isPendingConfiguration: %s \n " |
| "Reconfiguring to %s", |
| ToString(audio_receiver_state_).c_str(), |
| ToString(audio_sender_state_).c_str(), |
| (group->IsPendingConfiguration() ? "true" : "false"), |
| ToString(configuration_context_type_).c_str()); |
| group->PrintDebugState(); |
| CancelStreamingRequest(); |
| } |
| break; |
| } |
| break; |
| case AudioState::READY_TO_START: |
| LOG_ERROR( |
| "called in wrong state, ignoring double start request. \n " |
| "audio_receiver_state: %s \n" |
| "audio_sender_state: %s \n isPendingConfiguration: %s \n " |
| "Reconfiguring to %s", |
| ToString(audio_receiver_state_).c_str(), |
| ToString(audio_sender_state_).c_str(), |
| (group->IsPendingConfiguration() ? "true" : "false"), |
| ToString(configuration_context_type_).c_str()); |
| group->PrintDebugState(); |
| break; |
| case AudioState::READY_TO_RELEASE: |
| switch (audio_receiver_state_) { |
| case AudioState::STARTED: |
| case AudioState::READY_TO_START: |
| case AudioState::IDLE: |
| case AudioState::READY_TO_RELEASE: |
| /* Stream is up just restore it */ |
| if (alarm_is_scheduled(suspend_timeout_)) |
| alarm_cancel(suspend_timeout_); |
| ConfirmLocalAudioSourceStreamingRequest(); |
| le_audio::MetricsCollector::Get()->OnStreamStarted( |
| active_group_id_, configuration_context_type_); |
| break; |
| case AudioState::RELEASING: |
| /* Keep wainting. After release is done, Audio Hal will be notified |
| */ |
| break; |
| } |
| break; |
| case AudioState::RELEASING: |
| /* Keep wainting. After release is done, Audio Hal will be notified */ |
| break; |
| } |
| } |
| |
| void OnLocalAudioSinkSuspend() { |
| LOG_INFO( |
| "active group_id: %d, IN: audio_receiver_state_: %s, " |
| "audio_sender_state_: %s", |
| active_group_id_, ToString(audio_receiver_state_).c_str(), |
| ToString(audio_sender_state_).c_str()); |
| LeAudioLogHistory::Get()->AddLogHistory( |
| kLogAfCallBt, active_group_id_, RawAddress::kEmpty, |
| kLogAfSuspend + "LocalSink", |
| "r_state: " + ToString(audio_receiver_state_) + |
| ", s_state: " + ToString(audio_sender_state_)); |
| |
| StartVbcCloseTimeout(); |
| |
| /* Note: This callback is from audio hal driver. |
| * Bluetooth peer is a Source for Audio Framework. |
| * e.g. Peer is microphone. |
| */ |
| switch (audio_receiver_state_) { |
| case AudioState::READY_TO_START: |
| case AudioState::STARTED: |
| audio_receiver_state_ = AudioState::READY_TO_RELEASE; |
| break; |
| case AudioState::RELEASING: |
| return; |
| case AudioState::IDLE: |
| if (audio_sender_state_ == AudioState::READY_TO_RELEASE) { |
| OnAudioSuspend(); |
| } |
| return; |
| case AudioState::READY_TO_RELEASE: |
| break; |
| } |
| |
| /* Last suspends group - triggers group stop */ |
| if ((audio_sender_state_ == AudioState::IDLE) || |
| (audio_sender_state_ == AudioState::READY_TO_RELEASE)) |
| OnAudioSuspend(); |
| |
| LOG_INFO("OUT: audio_receiver_state_: %s, audio_sender_state_: %s", |
| ToString(audio_receiver_state_).c_str(), |
| ToString(audio_sender_state_).c_str()); |
| |
| LeAudioLogHistory::Get()->AddLogHistory( |
| kLogBtCallAf, active_group_id_, RawAddress::kEmpty, |
| kLogAfSuspendConfirm + "LocalSink", |
| "r_state: " + ToString(audio_receiver_state_) + |
| "s_state: " + ToString(audio_sender_state_)); |
| } |
| |
| inline bool IsDirectionAvailableForCurrentConfiguration( |
| const LeAudioDeviceGroup* group, uint8_t direction) const { |
| return group |
| ->GetCachedCodecConfigurationByDirection(configuration_context_type_, |
| direction) |
| .has_value(); |
| } |
| |
| void notifyAudioLocalSink(UnicastMonitorModeStatus status) { |
| if (sink_monitor_notified_status_ != status) { |
| LOG_INFO("Stream monitoring status changed to: %d", |
| static_cast<int>(status)); |
| sink_monitor_notified_status_ = status; |
| callbacks_->OnUnicastMonitorModeStatus( |
| le_audio::types::kLeAudioDirectionSink, status); |
| } |
| } |
| |
| void OnLocalAudioSinkResume() { |
| LOG_INFO( |
| "active group_id: %d IN: audio_receiver_state_: %s, " |
| "audio_sender_state_: %s", |
| active_group_id_, ToString(audio_receiver_state_).c_str(), |
| ToString(audio_sender_state_).c_str()); |
| LeAudioLogHistory::Get()->AddLogHistory( |
| kLogAfCallBt, active_group_id_, RawAddress::kEmpty, |
| kLogAfResume + "LocalSink", |
| "r_state: " + ToString(audio_receiver_state_) + |
| ", s_state: " + ToString(audio_sender_state_)); |
| |
| if (sink_monitor_mode_ && |
| active_group_id_ == bluetooth::groups::kGroupUnknown) { |
| if (sink_monitor_notified_status_ != |
| UnicastMonitorModeStatus::STREAMING_REQUESTED) { |
| notifyAudioLocalSink(UnicastMonitorModeStatus::STREAMING_REQUESTED); |
| } |
| CancelLocalAudioSinkStreamingRequest(); |
| return; |
| } |
| |
| /* Stop the VBC close watchdog if needed */ |
| StopVbcCloseTimeout(); |
| |
| /* Note: This callback is from audio hal driver. |
| * Bluetooth peer is a Source for Audio Framework. |
| * e.g. Peer is microphone. |
| */ |
| auto group = aseGroups_.FindById(active_group_id_); |
| if (!group) { |
| LOG(ERROR) << __func__ |
| << ", Invalid group: " << static_cast<int>(active_group_id_); |
| return; |
| } |
| |
| /* We need new configuration_context_type_ to be selected before we go any |
| * further. |
| */ |
| if (audio_receiver_state_ == AudioState::IDLE) { |
| ReconfigureOrUpdateRemote(group, |
| le_audio::types::kLeAudioDirectionSource); |
| } |
| |
| /* Check if the device resume is allowed */ |
| if (!group->GetCodecConfigurationByDirection( |
| configuration_context_type_, |
| le_audio::types::kLeAudioDirectionSource)) { |
| LOG(ERROR) << __func__ << ", invalid resume request for context type: " |
| << ToHexString(configuration_context_type_); |
| CancelLocalAudioSinkStreamingRequest(); |
| return; |
| } |
| |
| DLOG(INFO) << __func__ << " active_group_id: " << active_group_id_ << "\n" |
| << " audio_receiver_state: " << audio_receiver_state_ << "\n" |
| << " audio_sender_state: " << audio_sender_state_ << "\n" |
| << " configuration_context_type_: " |
| << ToHexString(configuration_context_type_) << "\n" |
| << " group " << (group ? " exist " : " does not exist ") << "\n"; |
| |
| switch (audio_receiver_state_) { |
| case AudioState::STARTED: |
| ConfirmLocalAudioSinkStreamingRequest(); |
| break; |
| case AudioState::IDLE: |
| switch (audio_sender_state_) { |
| case AudioState::IDLE: |
| if (OnAudioResume(group, le_audio::types::kLeAudioDirectionSink)) { |
| audio_receiver_state_ = AudioState::READY_TO_START; |
| } else { |
| CancelLocalAudioSinkStreamingRequest(); |
| } |
| break; |
| case AudioState::READY_TO_START: |
| audio_receiver_state_ = AudioState::READY_TO_START; |
| if (!IsDirectionAvailableForCurrentConfiguration( |
| group, le_audio::types::kLeAudioDirectionSource)) { |
| LOG_WARN( |
| " source is not configured. \n audio_receiver_state: %s \n" |
| "audio_sender_state: %s \n isPendingConfiguration: %s \n " |
| "Reconfiguring to %s", |
| ToString(audio_receiver_state_).c_str(), |
| ToString(audio_sender_state_).c_str(), |
| (group->IsPendingConfiguration() ? "true" : "false"), |
| ToString(configuration_context_type_).c_str()); |
| group->PrintDebugState(); |
| SetConfigurationAndStopStreamWhenNeeded( |
| group, configuration_context_type_); |
| } |
| break; |
| case AudioState::STARTED: |
| audio_receiver_state_ = AudioState::READY_TO_START; |
| /* If signalling part is completed trigger start receiving audio |
| * here, otherwise it'll be called on group streaming state callback |
| */ |
| if (group->GetState() == |
| AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { |
| if (IsDirectionAvailableForCurrentConfiguration( |
| group, le_audio::types::kLeAudioDirectionSource)) { |
| StartReceivingAudio(active_group_id_); |
| } else { |
| LOG_WARN( |
| " source is not configured. \n audio_receiver_state: %s \n" |
| "audio_sender_state: %s \n isPendingConfiguration: %s \n " |
| "Reconfiguring to %s", |
| ToString(audio_receiver_state_).c_str(), |
| ToString(audio_sender_state_).c_str(), |
| (group->IsPendingConfiguration() ? "true" : "false"), |
| ToString(configuration_context_type_).c_str()); |
| group->PrintDebugState(); |
| SetConfigurationAndStopStreamWhenNeeded( |
| group, configuration_context_type_); |
| } |
| } else { |
| LOG_ERROR( |
| " called in wrong state. \n audio_receiver_state: %s \n" |
| "audio_sender_state: %s \n isPendingConfiguration: %s \n " |
| "Reconfiguring to %s", |
| ToString(audio_receiver_state_).c_str(), |
| ToString(audio_sender_state_).c_str(), |
| (group->IsPendingConfiguration() ? "true" : "false"), |
| ToString(configuration_context_type_).c_str()); |
| group->PrintDebugState(); |
| CancelStreamingRequest(); |
| } |
| break; |
| case AudioState::RELEASING: |
| /* Group is reconfiguring, reassing state and wait for |
| * the stream to be configured |
| */ |
| audio_receiver_state_ = audio_sender_state_; |
| break; |
| case AudioState::READY_TO_RELEASE: |
| /* If the other direction is streaming we can start receiving audio |
| */ |
| if (group->GetState() == |
| AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { |
| if (IsDirectionAvailableForCurrentConfiguration( |
| group, le_audio::types::kLeAudioDirectionSource)) { |
| StartReceivingAudio(active_group_id_); |
| } else { |
| LOG_WARN( |
| " source is not configured. \n audio_receiver_state: %s \n" |
| "audio_sender_state: %s \n isPendingConfiguration: %s \n " |
| "Reconfiguring to %s", |
| ToString(audio_receiver_state_).c_str(), |
| ToString(audio_sender_state_).c_str(), |
| (group->IsPendingConfiguration() ? "true" : "false"), |
| ToString(configuration_context_type_).c_str()); |
| group->PrintDebugState(); |
| SetConfigurationAndStopStreamWhenNeeded( |
| group, configuration_context_type_); |
| } |
| } else { |
| LOG_ERROR( |
| " called in wrong state. \n audio_receiver_state: %s \n" |
| "audio_sender_state: %s \n isPendingConfiguration: %s \n " |
| "Reconfiguring to %s", |
| ToString(audio_receiver_state_).c_str(), |
| ToString(audio_sender_state_).c_str(), |
| (group->IsPendingConfiguration() ? "true" : "false"), |
| ToString(configuration_context_type_).c_str()); |
| group->PrintDebugState(); |
| CancelStreamingRequest(); |
| } |
| break; |
| } |
| break; |
| case AudioState::READY_TO_START: |
| LOG_ERROR( |
| " Double resume request, just ignore it.. \n audio_receiver_state: " |
| "%s \n" |
| "audio_sender_state: %s \n isPendingConfiguration: %s \n " |
| "Reconfiguring to %s", |
| ToString(audio_receiver_state_).c_str(), |
| ToString(audio_sender_state_).c_str(), |
| (group->IsPendingConfiguration() ? "true" : "false"), |
| ToString(configuration_context_type_).c_str()); |
| group->PrintDebugState(); |
| break; |
| case AudioState::READY_TO_RELEASE: |
| switch (audio_sender_state_) { |
| case AudioState::STARTED: |
| case AudioState::IDLE: |
| case AudioState::READY_TO_START: |
| case AudioState::READY_TO_RELEASE: |
| /* Stream is up just restore it */ |
| if (alarm_is_scheduled(suspend_timeout_)) |
| alarm_cancel(suspend_timeout_); |
| ConfirmLocalAudioSinkStreamingRequest(); |
| break; |
| case AudioState::RELEASING: |
| /* Wait until releasing is completed */ |
| break; |
| } |
| |
| break; |
| case AudioState::RELEASING: |
| /* Wait until releasing is completed */ |
| break; |
| } |
| } |
| |
| /* Chooses a single context type to use as a key for selecting a single |
| * audio set configuration. Contexts used for the metadata can be different |
| * than this, but it's reasonable to select a configuration context from |
| * the metadata context types. |
| */ |
| LeAudioContextType ChooseConfigurationContextType( |
| AudioContexts available_remote_contexts) { |
| LOG_DEBUG("Got contexts=%s in config_context=%s", |
| bluetooth::common::ToString(available_remote_contexts).c_str(), |
| bluetooth::common::ToString(configuration_context_type_).c_str()); |
| |
| if (IsInCall()) { |
| LOG_DEBUG(" In Call preference used."); |
| return LeAudioContextType::CONVERSATIONAL; |
| } |
| |
| /* Mini policy - always prioritize sink+source configurations so that we are |
| * sure that for a mixed content we enable all the needed directions. |
| */ |
| if (available_remote_contexts.any()) { |
| LeAudioContextType context_priority_list[] = { |
| /* Highest priority first */ |
| LeAudioContextType::CONVERSATIONAL, |
| /* Handling RINGTONE will cause the ringtone volume slider to trigger |
| * reconfiguration. This will be fixed in b/283349711. |
| */ |
| LeAudioContextType::RINGTONE, |
| LeAudioContextType::LIVE, |
| LeAudioContextType::VOICEASSISTANTS, |
| LeAudioContextType::GAME, |
| LeAudioContextType::MEDIA, |
| LeAudioContextType::EMERGENCYALARM, |
| LeAudioContextType::ALERTS, |
| LeAudioContextType::INSTRUCTIONAL, |
| LeAudioContextType::NOTIFICATIONS, |
| LeAudioContextType::SOUNDEFFECTS, |
| }; |
| for (auto ct : context_priority_list) { |
| if (available_remote_contexts.test(ct)) { |
| LOG_DEBUG("Selecting configuration context type: %s", |
| ToString(ct).c_str()); |
| return ct; |
| } |
| } |
| } |
| |
| /* Use BAP mandated UNSPECIFIED only if we don't have any other valid |
| * configuration |
| */ |
| auto fallback_config = LeAudioContextType::UNSPECIFIED; |
| if (configuration_context_type_ != LeAudioContextType::UNINITIALIZED) { |
| fallback_config = configuration_context_type_; |
| } |
| |
| LOG_DEBUG("Selecting configuration context type: %s", |
| ToString(fallback_config).c_str()); |
| return fallback_config; |
| } |
| |
| bool SetConfigurationAndStopStreamWhenNeeded( |
| LeAudioDeviceGroup* group, LeAudioContextType new_context_type) { |
| auto reconfig_result = UpdateConfigAndCheckIfReconfigurationIsNeeded( |
| group->group_id_, new_context_type); |
| /* Even though the reconfiguration may not be needed, this has |
| * to be set here as it might be the initial configuration. |
| */ |
| configuration_context_type_ = new_context_type; |
| |
| LOG_INFO("group_id %d, context type %s (%s), %s", group->group_id_, |
| ToString(new_context_type).c_str(), |
| ToHexString(new_context_type).c_str(), |
| ToString(reconfig_result).c_str()); |
| if (reconfig_result == |
| AudioReconfigurationResult::RECONFIGURATION_NOT_NEEDED) { |
| return false; |
| } |
| |
| if (reconfig_result == |
| AudioReconfigurationResult::RECONFIGURATION_NOT_POSSIBLE) { |
| return false; |
| } |
| |
| if (group->GetState() != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { |
| DLOG(INFO) << __func__ << " Group is not streaming "; |
| return false; |
| } |
| |
| if (alarm_is_scheduled(suspend_timeout_)) alarm_cancel(suspend_timeout_); |
| |
| /* Need to reconfigure stream */ |
| group->SetPendingConfiguration(); |
| groupStateMachine_->StopStream(group); |
| return true; |
| } |
| |
| void OnLocalAudioSourceMetadataUpdate(source_metadata_v7 source_metadata, |
| DsaMode dsa_mode) { |
| if (active_group_id_ == bluetooth::groups::kGroupUnknown) { |
| LOG(WARNING) << ", cannot start streaming if no active group set"; |
| return; |
| } |
| |
| auto group = aseGroups_.FindById(active_group_id_); |
| if (!group) { |
| LOG(ERROR) << __func__ |
| << ", Invalid group: " << static_cast<int>(active_group_id_); |
| return; |
| } |
| |
| /* Stop the VBC close timeout timer, since we will reconfigure anyway if the |
| * VBC was suspended. |
| */ |
| StopVbcCloseTimeout(); |
| |
| LOG_INFO( |
| "group_id %d state=%s, target_state=%s, audio_receiver_state_: %s, " |
| "audio_sender_state_: %s, dsa_mode: %d", |
| group->group_id_, ToString(group->GetState()).c_str(), |
| ToString(group->GetTargetState()).c_str(), |
| ToString(audio_receiver_state_).c_str(), |
| ToString(audio_sender_state_).c_str(), static_cast<int>(dsa_mode)); |
| |
| group->dsa_.mode = dsa_mode; |
| |
| /* Set the remote sink metadata context from the playback tracks metadata */ |
| local_metadata_context_types_.source = |
| GetAudioContextsFromSourceMetadata(source_metadata); |
| |
| local_metadata_context_types_.sink = |
| ChooseMetadataContextType(local_metadata_context_types_.sink); |
| local_metadata_context_types_.source = |
| ChooseMetadataContextType(local_metadata_context_types_.source); |
| |
| ReconfigureOrUpdateRemote(group, le_audio::types::kLeAudioDirectionSink); |
| } |
| |
| /* Applies some predefined policy on the audio context metadata, including |
| * special handling of UNSPECIFIED context, which also involves checking |
| * context support and availability. |
| */ |
| void ApplyRemoteMetadataAudioContextPolicy( |
| LeAudioDeviceGroup* group, |
| BidirectionalPair<AudioContexts>& contexts_pair, int remote_dir) { |
| // We expect at least some context when this direction gets enabled |
| if (contexts_pair.get(remote_dir).none()) { |
| LOG_WARN( |
| "invalid/unknown %s context metadata, using 'UNSPECIFIED' instead", |
| (remote_dir == le_audio::types::kLeAudioDirectionSink) ? "sink" |
| : "source"); |
| contexts_pair.get(remote_dir) = |
| AudioContexts(LeAudioContextType::UNSPECIFIED); |
| } |
| |
| std::tuple<int, int, AudioState*> remote_directions[] = { |
| {le_audio::types::kLeAudioDirectionSink, |
| le_audio::types::kLeAudioDirectionSource, &audio_sender_state_}, |
| {le_audio::types::kLeAudioDirectionSource, |
| le_audio::types::kLeAudioDirectionSink, &audio_receiver_state_}, |
| }; |
| |
| /* Align with the context availability */ |
| for (auto entry : remote_directions) { |
| int dir, other_dir; |
| AudioState* local_hal_state; |
| std::tie(dir, other_dir, local_hal_state) = entry; |
| |
| /* When a certain context became unavailable while it was already in |
| * an active stream, it means that it is unavailable to other clients |
| * but we can keep using it. |
| */ |
| auto group_available_contexts = group->GetAvailableContexts(dir); |
| if ((*local_hal_state == AudioState::STARTED) || |
| (*local_hal_state == AudioState::READY_TO_START)) { |
| group_available_contexts |= group->GetMetadataContexts().get(dir); |
| } |
| |
| LOG_DEBUG("Checking contexts: %s, against the available contexts: %s", |
| ToString(contexts_pair.get(dir)).c_str(), |
| ToString(group_available_contexts).c_str()); |
| auto unavail_contexts = |
| contexts_pair.get(dir) & ~group_available_contexts; |
| if (unavail_contexts.none()) continue; |
| |
| contexts_pair.get(dir) &= group_available_contexts; |
| auto unavail_but_supported = |
| (unavail_contexts & group->GetSupportedContexts(dir)); |
| if (unavail_but_supported.none() && |
| group_available_contexts.test(LeAudioContextType::UNSPECIFIED)) { |
| LOG_DEBUG("Replaced the unsupported contexts: %s with UNSPECIFIED", |
| ToString(unavail_contexts).c_str()); |
| /* All unavailable are also unsupported - replace with UNSPECIFIED if |
| * available |
| */ |
| contexts_pair.get(dir).set(LeAudioContextType::UNSPECIFIED); |
| } else { |
| LOG_DEBUG("Some contexts are supported but currently unavailable: %s!", |
| ToString(unavail_but_supported).c_str()); |
| /* Some of the streamed contexts are support but not available and they |
| * were erased from the metadata. |
| * TODO: Either filter out these contexts from the stream or do not |
| * stream at all if the unavail_but_supported contexts are the only |
| * streamed contexts. |
| */ |
| } |
| } |
| |
| /* Don't mix UNSPECIFIED with any other context |
| * Note: This has to be in a separate loop - do not merge it with the above. |
| */ |
| for (auto entry : remote_directions) { |
| int dir, other_dir; |
| AudioState* local_hal_state; |
| std::tie(dir, other_dir, local_hal_state) = entry; |
| |
| if (contexts_pair.get(dir).test(LeAudioContextType::UNSPECIFIED)) { |
| /* Try to use the other direction context if not UNSPECIFIED and active |
| */ |
| if (contexts_pair.get(dir) == |
| AudioContexts(LeAudioContextType::UNSPECIFIED)) { |
| auto is_other_direction_streaming = |
| (*local_hal_state == AudioState::STARTED) || |
| (*local_hal_state == AudioState::READY_TO_START); |
| if (is_other_direction_streaming && |
| (contexts_pair.get(other_dir) != |
| AudioContexts(LeAudioContextType::UNSPECIFIED))) { |
| LOG_INFO( |
| "Other direction is streaming. Aligning other direction" |
| " metadata to match the current direciton context: %s", |
| ToString(contexts_pair.get(other_dir)).c_str()); |
| contexts_pair.get(dir) = contexts_pair.get(other_dir); |
| } |
| } else { |
| LOG_DEBUG("Removing UNSPECIFIED from the remote sink context: %s", |
| ToString(contexts_pair.get(other_dir)).c_str()); |
| contexts_pair.get(dir).unset(LeAudioContextType::UNSPECIFIED); |
| } |
| } |
| } |
| |
| contexts_pair.sink = ChooseMetadataContextType(contexts_pair.sink); |
| contexts_pair.source = ChooseMetadataContextType(contexts_pair.source); |
| |
| LOG_DEBUG("Aligned remote metadata audio context: sink=%s, source=%s", |
| ToString(contexts_pair.sink).c_str(), |
| ToString(contexts_pair.source).c_str()); |
| } |
| |
| void OnLocalAudioSinkMetadataUpdate(sink_metadata_v7 sink_metadata) { |
| if (active_group_id_ == bluetooth::groups::kGroupUnknown) { |
| LOG(WARNING) << ", cannot start streaming if no active group set"; |
| return; |
| } |
| |
| auto group = aseGroups_.FindById(active_group_id_); |
| if (!group) { |
| LOG(ERROR) << __func__ |
| << ", Invalid group: " << static_cast<int>(active_group_id_); |
| return; |
| } |
| |
| LOG_INFO( |
| "group_id %d state=%s, target_state=%s, audio_receiver_state_: %s, " |
| "audio_sender_state_: %s", |
| group->group_id_, ToString(group->GetState()).c_str(), |
| ToString(group->GetTargetState()).c_str(), |
| ToString(audio_receiver_state_).c_str(), |
| ToString(audio_sender_state_).c_str()); |
| |
| /* Set remote source metadata context from the recording tracks metadata */ |
| local_metadata_context_types_.sink = |
| GetAudioContextsFromSinkMetadata(sink_metadata); |
| |
| local_metadata_context_types_.sink = |
| ChooseMetadataContextType(local_metadata_context_types_.sink); |
| local_metadata_context_types_.source = |
| ChooseMetadataContextType(local_metadata_context_types_.source); |
| |
| /* Reconfigure or update only if the stream is already started |
| * otherwise wait for the local sink to resume. |
| */ |
| if (audio_receiver_state_ == AudioState::STARTED) { |
| ReconfigureOrUpdateRemote(group, |
| le_audio::types::kLeAudioDirectionSource); |
| } |
| } |
| |
| BidirectionalPair<AudioContexts> DirectionalRealignMetadataAudioContexts( |
| LeAudioDeviceGroup* group, int remote_direction) { |
| auto remote_other_direction = |
| (remote_direction == le_audio::types::kLeAudioDirectionSink |
| ? le_audio::types::kLeAudioDirectionSource |
| : le_audio::types::kLeAudioDirectionSink); |
| auto other_direction_hal = |
| (remote_other_direction == le_audio::types::kLeAudioDirectionSource |
| ? audio_receiver_state_ |
| : audio_sender_state_); |
| auto is_streaming_other_direction = |
| (other_direction_hal == AudioState::STARTED) || |
| (other_direction_hal == AudioState::READY_TO_START); |
| auto is_releasing_for_reconfiguration = |
| (((audio_receiver_state_ == AudioState::RELEASING) || |
| (audio_sender_state_ == AudioState::RELEASING)) && |
| group->IsPendingConfiguration() && |
| IsDirectionAvailableForCurrentConfiguration(group, |
| remote_other_direction)); |
| |
| // Inject conversational when ringtone is played - this is required for all |
| // the VoIP applications which are not using the telecom API. |
| constexpr AudioContexts possible_voip_contexts = |
| LeAudioContextType::RINGTONE | LeAudioContextType::CONVERSATIONAL; |
| if (local_metadata_context_types_.source.test_any(possible_voip_contexts) && |
| ((remote_direction == le_audio::types::kLeAudioDirectionSink) || |
| (remote_direction == le_audio::types::kLeAudioDirectionSource && |
| is_streaming_other_direction))) { |
| /* Simulate, we are already in the call. Sending RINGTONE when there is |
| * no incoming call to accept or reject on TBS could confuse the remote |
| * device and interrupt the stream establish procedure. |
| */ |
| if (!IsInCall()) { |
| SetInVoipCall(true); |
| } |
| } else if (IsInVoipCall()) { |
| SetInVoipCall(false); |
| } |
| |
| /* Make sure we have CONVERSATIONAL when in a call and it is not mixed |
| * with any other bidirectional context |
| */ |
| if (IsInCall() || IsInVoipCall()) { |
| LOG_DEBUG(" In Call preference used: %s, voip call: %s", |
| (IsInCall() ? "true" : "false"), |
| (IsInVoipCall() ? "true" : "false")); |
| local_metadata_context_types_.sink.unset_all(kLeAudioContextAllBidir); |
| local_metadata_context_types_.source.unset_all(kLeAudioContextAllBidir); |
| local_metadata_context_types_.sink.set( |
| LeAudioContextType::CONVERSATIONAL); |
| local_metadata_context_types_.source.set( |
| LeAudioContextType::CONVERSATIONAL); |
| } |
| |
| BidirectionalPair<AudioContexts> remote_metadata = { |
| .sink = local_metadata_context_types_.source, |
| .source = local_metadata_context_types_.sink}; |
| |
| if (IsInVoipCall()) { |
| LOG_DEBUG("Unsetting RINGTONE from remote sink "); |
| remote_metadata.sink.unset(LeAudioContextType::RINGTONE); |
| } |
| |
| auto is_ongoing_call_on_other_direction = |
| is_streaming_other_direction && (IsInVoipCall() || IsInCall()); |
| |
| LOG_DEBUG("local_metadata_context_types_.source= %s", |
| ToString(local_metadata_context_types_.source).c_str()); |
| LOG_DEBUG("local_metadata_context_types_.sink= %s", |
| ToString(local_metadata_context_types_.sink).c_str()); |
| LOG_DEBUG("remote_metadata.source= %s", |
| ToString(remote_metadata.source).c_str()); |
| LOG_DEBUG("remote_metadata.sink= %s", |
| ToString(remote_metadata.sink).c_str()); |
| LOG_DEBUG("remote_direction= %s", |
| (remote_direction == le_audio::types::kLeAudioDirectionSource |
| ? "Source" |
| : "Sink")); |
| LOG_DEBUG("is_streaming_other_direction= %s", |
| (is_streaming_other_direction ? "True" : "False")); |
| LOG_DEBUG("is_releasing_for_reconfiguration= %s", |
| (is_releasing_for_reconfiguration ? "True" : "False")); |
| LOG_DEBUG("is_ongoing_call_on_other_direction=%s", |
| (is_ongoing_call_on_other_direction ? "True" : "False")); |
| |
| if (remote_metadata.get(remote_other_direction) |
| .test_any(kLeAudioContextAllBidir) && |
| !is_streaming_other_direction) { |
| LOG_DEBUG( |
| "The other direction is not streaming bidirectional, ignore that " |
| "context."); |
| remote_metadata.get(remote_other_direction).clear(); |
| } |
| |
| /* Mixed contexts in the voiceback channel scenarios can confuse the remote |
| * on how to configure each channel. We should align the other direction |
| * metadata for the remote device. |
| */ |
| if (remote_metadata.get(remote_direction) |
| .test_any(kLeAudioContextAllBidir)) { |
| LOG_DEBUG( |
| "Aligning the other direction remote metadata to add this direction " |
| "context"); |
| |
| if (is_ongoing_call_on_other_direction) { |
| /* Other direction is streaming and is in call */ |
| remote_metadata.get(remote_direction) |
| .unset_all(kLeAudioContextAllBidir); |
| remote_metadata.get(remote_direction) |
| .set(LeAudioContextType::CONVERSATIONAL); |
| } else { |
| if (!is_streaming_other_direction) { |
| // Do not take the obsolete metadata |
| remote_metadata.get(remote_other_direction).clear(); |
| } |
| remote_metadata.get(remote_other_direction) |
| .unset_all(kLeAudioContextAllBidir); |
| remote_metadata.get(remote_other_direction) |
| .unset_all(kLeAudioContextAllRemoteSinkOnly); |
| remote_metadata.get(remote_other_direction) |
| .set_all(remote_metadata.get(remote_direction) & |
| ~kLeAudioContextAllRemoteSinkOnly); |
| } |
| } |
| LOG_DEBUG("remote_metadata.source= %s", |
| ToString(remote_metadata.source).c_str()); |
| LOG_DEBUG("remote_metadata.sink= %s", |
| ToString(remote_metadata.sink).c_str()); |
| |
| if (is_releasing_for_reconfiguration || is_streaming_other_direction) { |
| LOG_DEBUG("Other direction is streaming. Taking its contexts %s", |
| ToString(remote_metadata.get(remote_other_direction)).c_str()); |
| /* If current direction has no valid context or the other direction is |
| * bidirectional scenario, take the other direction context as well |
| */ |
| if ((remote_metadata.get(remote_direction).none() && |
| remote_metadata.get(remote_other_direction).any()) || |
| remote_metadata.get(remote_other_direction) |
| .test_any(kLeAudioContextAllBidir)) { |
| LOG_DEBUG( |
| "Aligning this direction remote metadata to add the other " |
| "direction context"); |
| /* Turn off bidirectional contexts on this direction to avoid mixing |
| * with the other direction bidirectional context |
| */ |
| remote_metadata.get(remote_direction) |
| .unset_all(kLeAudioContextAllBidir); |
| remote_metadata.get(remote_direction) |
| .set_all(remote_metadata.get(remote_other_direction)); |
| } |
| } |
| |
| /* Make sure that after alignment no sink only context leaks into the other |
| * direction. */ |
| remote_metadata.source.unset_all(kLeAudioContextAllRemoteSinkOnly); |
| |
| LOG_DEBUG("remote_metadata.source= %s", |
| ToString(remote_metadata.source).c_str()); |
| LOG_DEBUG("remote_metadata.sink= %s", |
| ToString(remote_metadata.sink).c_str()); |
| return remote_metadata; |
| } |
| |
| /* Return true if stream is started */ |
| bool ReconfigureOrUpdateRemote(LeAudioDeviceGroup* group, |
| int remote_direction) { |
| if (stack_config_get_interface() |
| ->get_pts_force_le_audio_multiple_contexts_metadata()) { |
| // Use common audio stream contexts exposed by the PTS |
| auto override_contexts = AudioContexts(0xFFFF); |
| for (auto device = group->GetFirstDevice(); device != nullptr; |
| device = group->GetNextDevice(device)) { |
| override_contexts &= device->GetAvailableContexts(); |
| } |
| if (override_contexts.value() == 0xFFFF) { |
| override_contexts = AudioContexts(LeAudioContextType::UNSPECIFIED); |
| } |
| LOG_WARN("Overriding local_metadata_context_types_: %s with: %s", |
| local_metadata_context_types_.source.to_string().c_str(), |
| override_contexts.to_string().c_str()); |
| |
| /* Choose the right configuration context */ |
| auto new_configuration_context = |
| ChooseConfigurationContextType(override_contexts); |
| |
| LOG_DEBUG("new_configuration_context= %s.", |
| ToString(new_configuration_context).c_str()); |
| BidirectionalPair<AudioContexts> remote_contexts = { |
| .sink = override_contexts, .source = override_contexts}; |
| return GroupStream(active_group_id_, new_configuration_context, |
| remote_contexts); |
| } |
| |
| /* When the local sink and source update their metadata, we need to come up |
| * with a coherent set of contexts for either one or both directions, |
| * especially when bidirectional scenarios can be triggered be either sink |
| * or source metadata update event. |
| */ |
| auto remote_metadata = |
| DirectionalRealignMetadataAudioContexts(group, remote_direction); |
| |
| /* Choose the right configuration context */ |
| auto config_context_candids = get_bidirectional(remote_metadata); |
| auto new_config_context = |
| ChooseConfigurationContextType(config_context_candids); |
| LOG_DEBUG("config_context_candids= %s, new_config_context= %s", |
| ToString(config_context_candids).c_str(), |
| ToString(new_config_context).c_str()); |
| |
| /* For the following contexts we don't actually need HQ audio: |
| * LeAudioContextType::NOTIFICATIONS |
| * LeAudioContextType::SOUNDEFFECTS |
| * LeAudioContextType::INSTRUCTIONAL |
| * LeAudioContextType::ALERTS |
| * LeAudioContextType::EMERGENCYALARM |
| * LeAudioContextType::UNSPECIFIED |
| * So do not reconfigure if the remote sink is already available at any |
| * quality and these are the only contributors to the current audio stream. |
| */ |
| auto no_reconfigure_contexts = |
| LeAudioContextType::NOTIFICATIONS | LeAudioContextType::SOUNDEFFECTS | |
| LeAudioContextType::INSTRUCTIONAL | LeAudioContextType::ALERTS | |
| LeAudioContextType::EMERGENCYALARM | LeAudioContextType::UNSPECIFIED; |
| if (config_context_candids.any() && |
| (config_context_candids & ~no_reconfigure_contexts).none() && |
| (configuration_context_type_ != LeAudioContextType::UNINITIALIZED) && |
| (configuration_context_type_ != LeAudioContextType::UNSPECIFIED) && |
| IsDirectionAvailableForCurrentConfiguration( |
| group, le_audio::types::kLeAudioDirectionSink)) { |
| LOG_INFO( |
| "There is no need to reconfigure for the sonification events, " |
| "staying with the existing configuration context of %s", |
| ToString(configuration_context_type_).c_str()); |
| new_config_context = configuration_context_type_; |
| } |
| |
| /* Do not configure the Voiceback channel if it is already configured. |
| * WARNING: This eliminates additional reconfigurations but can |
| * lead to unsatisfying audio quality when that direction was |
| * already configured with a lower quality. |
| */ |
| if (remote_direction == le_audio::types::kLeAudioDirectionSource) { |
| const auto has_audio_source_configured = |
| IsDirectionAvailableForCurrentConfiguration( |
| group, le_audio::types::kLeAudioDirectionSource) && |
| (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); |
| if (has_audio_source_configured) { |
| LOG_INFO( |
| "Audio source is already available in the current configuration " |
| "context in %s. Not switching to %s right now.", |
| ToString(configuration_context_type_).c_str(), |
| ToString(new_config_context).c_str()); |
| new_config_context = configuration_context_type_; |
| } |
| } |
| |
| /* Note that the remote device metadata was so far unfiltered when it comes |
| * to group context availability, or multiple contexts support flag, so that |
| * we could choose the correct configuration for the use case. Now we can |
| * align it to meet the metadata usage. |
| */ |
| ApplyRemoteMetadataAudioContextPolicy(group, remote_metadata, |
| remote_direction); |
| return ReconfigureOrUpdateMetadata(group, new_config_context, |
| remote_metadata); |
| } |
| |
| bool DsaReconfigureNeeded(LeAudioDeviceGroup* group, |
| LeAudioContextType context) { |
| if (!IS_FLAG_ENABLED(leaudio_dynamic_spatial_audio)) { |
| return false; |
| } |
| |
| // Reconfigure if DSA mode changed for media streaming |
| if (context != le_audio::types::LeAudioContextType::MEDIA) { |
| return false; |
| } |
| |
| if (group->dsa_.mode != DsaMode::ISO_SW && |
| group->dsa_.mode != DsaMode::ISO_HW) { |
| return false; |
| } |
| |
| if (group->dsa_.active) { |
| return false; |
| } |
| |
| LOG_INFO("DSA mode %d requested but not active", group->dsa_.mode); |
| return true; |
| } |
| |
| /* Return true if stream is started */ |
| bool ReconfigureOrUpdateMetadata( |
| LeAudioDeviceGroup* group, LeAudioContextType new_configuration_context, |
| BidirectionalPair<AudioContexts> remote_contexts) { |
| if (new_configuration_context != configuration_context_type_ || |
| DsaReconfigureNeeded(group, new_configuration_context)) { |
| LOG_INFO("Checking whether to change configuration context from %s to %s", |
| ToString(configuration_context_type_).c_str(), |
| ToString(new_configuration_context).c_str()); |
| |
| LeAudioLogHistory::Get()->AddLogHistory( |
| kLogAfCallBt, active_group_id_, RawAddress::kEmpty, |
| kLogAfMetadataUpdate + "Reconfigure", |
| ToString(configuration_context_type_) + "->" + |
| ToString(new_configuration_context)); |
| auto is_stopping = SetConfigurationAndStopStreamWhenNeeded( |
| group, new_configuration_context); |
| if (is_stopping) { |
| return false; |
| } |
| } |
| |
| if (group->GetTargetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { |
| LOG_INFO( |
| "The %s configuration did not change. Updating the metadata to " |
| "sink=%s, source=%s", |
| ToString(configuration_context_type_).c_str(), |
| ToString(remote_contexts.sink).c_str(), |
| ToString(remote_contexts.source).c_str()); |
| |
| LeAudioLogHistory::Get()->AddLogHistory( |
| kLogAfCallBt, active_group_id_, RawAddress::kEmpty, |
| kLogAfMetadataUpdate + "Updating...", |
| "Sink: " + ToString(remote_contexts.sink) + |
| "Source: " + ToString(remote_contexts.source)); |
| |
| return GroupStream(group->group_id_, configuration_context_type_, |
| remote_contexts); |
| } |
| return false; |
| } |
| |
| static void OnGattCtpCccReadRspStatic(uint16_t conn_id, tGATT_STATUS status, |
| uint16_t hdl, uint16_t len, |
| uint8_t* value, void* data) { |
| if (!instance) return; |
| |
| LOG_DEBUG("conn_id: 0x%04x, status: 0x%02x", conn_id, status); |
| |
| LeAudioDevice* leAudioDevice = |
| instance->leAudioDevices_.FindByConnId(conn_id); |
| |
| if (!leAudioDevice) { |
| LOG_ERROR("LeAudioDevice not found"); |
| return; |
| } |
| |
| if (status == GATT_DATABASE_OUT_OF_SYNC) { |
| LOG_INFO("Database out of sync for %s, re-discovering", |
| ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_)); |
| instance->ClearDeviceInformationAndStartSearch(leAudioDevice); |
| return; |
| } |
| |
| if (status != GATT_SUCCESS || len != 2) { |
| LOG_ERROR("Could not read CCC for %s, disconnecting", |
| ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_)); |
| instance->Disconnect(leAudioDevice->address_); |
| return; |
| } |
| |
| uint16_t val = *(uint16_t*)value; |
| if (val == 0) { |
| LOG_INFO("%s forgot CCC values. Re-subscribing", |
| ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_)); |
| instance->RegisterKnownNotifications(leAudioDevice, false, true); |
| } else { |
| instance->connectionReady(leAudioDevice); |
| } |
| } |
| |
| static void OnGattReadRspStatic(uint16_t conn_id, tGATT_STATUS status, |
| uint16_t hdl, uint16_t len, uint8_t* value, |
| void* data) { |
| if (!instance) return; |
| |
| LeAudioDevice* leAudioDevice = |
| instance->leAudioDevices_.FindByConnId(conn_id); |
| |
| if (status == GATT_SUCCESS) { |
| instance->LeAudioCharValueHandle(conn_id, hdl, len, value); |
| } else if (status == GATT_DATABASE_OUT_OF_SYNC) { |
| instance->ClearDeviceInformationAndStartSearch(leAudioDevice); |
| return; |
| } else { |
| LOG_ERROR("Failed to read attribute, hdl: 0x%04x, status: 0x%02x", hdl, |
| static_cast<int>(status)); |
| return; |
| } |
| |
| /* We use data to keep notify connected flag. */ |
| if (data && !!PTR_TO_INT(data)) { |
| leAudioDevice->notify_connected_after_read_ = false; |
| |
| /* Update handles, PACs and ASEs when all is read.*/ |
| btif_storage_leaudio_update_handles_bin(leAudioDevice->address_); |
| btif_storage_leaudio_update_pacs_bin(leAudioDevice->address_); |
| btif_storage_leaudio_update_ase_bin(leAudioDevice->address_); |
| |
| btif_storage_set_leaudio_audio_location( |
| leAudioDevice->address_, |
| leAudioDevice->snk_audio_locations_.to_ulong(), |
| leAudioDevice->src_audio_locations_.to_ulong()); |
| |
| instance->connectionReady(leAudioDevice); |
| } |
| } |
| |
| void LeAudioHealthSendRecommendation(const RawAddress& address, int group_id, |
| LeAudioHealthBasedAction action) { |
| LOG_DEBUG("%s, %d, %s", ADDRESS_TO_LOGGABLE_CSTR(address), group_id, |
| ToString(action).c_str()); |
| |
| if (address != RawAddress::kEmpty && |
| leAudioDevices_.FindByAddress(address)) { |
| callbacks_->OnHealthBasedRecommendationAction(address, action); |
| } |
| |
| if (group_id != bluetooth::groups::kGroupUnknown && |
| aseGroups_.FindById(group_id)) { |
| callbacks_->OnHealthBasedGroupRecommendationAction(group_id, action); |
| } |
| } |
| |
| void IsoCigEventsCb(uint16_t event_type, void* data) { |
| switch (event_type) { |
| case bluetooth::hci::iso_manager::kIsoEventCigOnCreateCmpl: { |
| auto* evt = static_cast<cig_create_cmpl_evt*>(data); |
| LeAudioDeviceGroup* group = aseGroups_.FindById(evt->cig_id); |
| ASSERT_LOG(group, "Group id: %d is null", evt->cig_id); |
| groupStateMachine_->ProcessHciNotifOnCigCreate( |
| group, evt->status, evt->cig_id, evt->conn_handles); |
| } break; |
| case bluetooth::hci::iso_manager::kIsoEventCigOnRemoveCmpl: { |
| auto* evt = static_cast<cig_remove_cmpl_evt*>(data); |
| LeAudioDeviceGroup* group = aseGroups_.FindById(evt->cig_id); |
| ASSERT_LOG(group, "Group id: %d is null", evt->cig_id); |
| groupStateMachine_->ProcessHciNotifOnCigRemove(evt->status, group); |
| remove_group_if_possible(group); |
| } break; |
| default: |
| LOG_ERROR("Invalid event %d", +event_type); |
| } |
| } |
| |
| void IsoCisEventsCb(uint16_t event_type, void* data) { |
| switch (event_type) { |
| case bluetooth::hci::iso_manager::kIsoEventCisDataAvailable: { |
| auto* event = |
| static_cast<bluetooth::hci::iso_manager::cis_data_evt*>(data); |
| |
| if (audio_receiver_state_ != AudioState::STARTED) { |
| LOG_ERROR("receiver state not ready, current state=%s", |
| ToString(audio_receiver_state_).c_str()); |
| break; |
| } |
| |
| HandleIncomingCisData(event->p_msg->data + event->p_msg->offset, |
| event->p_msg->len - event->p_msg->offset, |
| event->cis_conn_hdl, event->ts); |
| } break; |
| case bluetooth::hci::iso_manager::kIsoEventCisEstablishCmpl: { |
| auto* event = |
| static_cast<bluetooth::hci::iso_manager::cis_establish_cmpl_evt*>( |
| data); |
| |
| LeAudioDevice* leAudioDevice = leAudioDevices_.FindByCisConnHdl( |
| event->cig_id, event->cis_conn_hdl); |
| if (!leAudioDevice) { |
| LOG(ERROR) << __func__ << ", no bonded Le Audio Device with CIS: " |
| << +event->cis_conn_hdl; |
| break; |
| } |
| LeAudioDeviceGroup* group = |
| aseGroups_.FindById(leAudioDevice->group_id_); |
| |
| if (event->max_pdu_mtos > 0) |
| group->SetTransportLatency(le_audio::types::kLeAudioDirectionSink, |
| event->trans_lat_mtos); |
| if (event->max_pdu_stom > 0) |
| group->SetTransportLatency(le_audio::types::kLeAudioDirectionSource, |
| event->trans_lat_stom); |
| |
| if (leAudioHealthStatus_ && (event->status != HCI_SUCCESS)) { |
| leAudioHealthStatus_->AddStatisticForGroup( |
| group, LeAudioHealthGroupStatType::STREAM_CREATE_CIS_FAILED); |
| } |
| |
| groupStateMachine_->ProcessHciNotifCisEstablished(group, leAudioDevice, |
| event); |
| } break; |
| case bluetooth::hci::iso_manager::kIsoEventCisDisconnected: { |
| auto* event = |
| static_cast<bluetooth::hci::iso_manager::cis_disconnected_evt*>( |
| data); |
| |
| LeAudioDevice* leAudioDevice = leAudioDevices_.FindByCisConnHdl( |
| event->cig_id, event->cis_conn_hdl); |
| if (!leAudioDevice) { |
| LOG(ERROR) << __func__ << ", no bonded Le Audio Device with CIS: " |
| << +event->cis_conn_hdl; |
| break; |
| } |
| LeAudioDeviceGroup* group = |
| aseGroups_.FindById(leAudioDevice->group_id_); |
| |
| groupStateMachine_->ProcessHciNotifCisDisconnected(group, leAudioDevice, |
| event); |
| } break; |
| default: |
| LOG(INFO) << ", Not handeled ISO event"; |
| break; |
| } |
| } |
| |
| void IsoSetupIsoDataPathCb(uint8_t status, uint16_t conn_handle, |
| uint8_t cig_id) { |
| LeAudioDevice* leAudioDevice = |
| leAudioDevices_.FindByCisConnHdl(cig_id, conn_handle); |
| /* In case device has been disconnected before data path was setup */ |
| if (!leAudioDevice) { |
| LOG_WARN("Device for CIG %d and using cis_handle 0x%04x is disconnected.", |
| cig_id, conn_handle); |
| return; |
| } |
| LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_); |
| |
| instance->groupStateMachine_->ProcessHciNotifSetupIsoDataPath( |
| group, leAudioDevice, status, conn_handle); |
| } |
| |
| void IsoRemoveIsoDataPathCb(uint8_t status, uint16_t conn_handle, |
| uint8_t cig_id) { |
| LeAudioDevice* leAudioDevice = |
| leAudioDevices_.FindByCisConnHdl(cig_id, conn_handle); |
| |
| /* If CIS has been disconnected just before ACL being disconnected by the |
| * remote device, leAudioDevice might be already cleared i.e. has no |
| * information about conn_handle, when the data path remove compete arrives. |
| */ |
| if (!leAudioDevice) { |
| LOG_WARN("Device for CIG %d and using cis_handle 0x%04x is disconnected.", |
| cig_id, conn_handle); |
| return; |
| } |
| |
| LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_); |
| |
| instance->groupStateMachine_->ProcessHciNotifRemoveIsoDataPath( |
| group, leAudioDevice, status, conn_handle); |
| } |
| |
| void IsoLinkQualityReadCb( |
| uint8_t conn_handle, uint8_t cig_id, uint32_t txUnackedPackets, |
| uint32_t txFlushedPackets, uint32_t txLastSubeventPackets, |
| uint32_t retransmittedPackets, uint32_t crcErrorPackets, |
| uint32_t rxUnreceivedPackets, uint32_t duplicatePackets) { |
| LeAudioDevice* leAudioDevice = |
| leAudioDevices_.FindByCisConnHdl(cig_id, conn_handle); |
| if (!leAudioDevice) { |
| LOG(WARNING) << __func__ << ", device under connection handle: " |
| << loghex(conn_handle) |
| << ", has been disconnecected in meantime"; |
| return; |
| } |
| LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_); |
| |
| instance->groupStateMachine_->ProcessHciNotifIsoLinkQualityRead( |
| group, leAudioDevice, conn_handle, txUnackedPackets, txFlushedPackets, |
| txLastSubeventPackets, retransmittedPackets, crcErrorPackets, |
| rxUnreceivedPackets, duplicatePackets); |
| } |
| |
| void HandlePendingDeviceRemove(LeAudioDeviceGroup* group) { |
| for (auto device = group->GetFirstDevice(); device != nullptr; |
| device = group->GetNextDevice(device)) { |
| if (device->GetConnectionState() == DeviceConnectState::REMOVING) { |
| if (device->closing_stream_for_disconnection_) { |
| device->closing_stream_for_disconnection_ = false; |
| LOG_INFO("Disconnecting group id: %d, address: %s", group->group_id_, |
| ADDRESS_TO_LOGGABLE_CSTR(device->address_)); |
| bool force_acl_disconnect = |
| device->autoconnect_flag_ && group->IsEnabled(); |
| DisconnectDevice(device, force_acl_disconnect); |
| } |
| group_remove_node(group, device->address_, true); |
| } |
| } |
| } |
| |
| void HandlePendingDeviceDisconnection(LeAudioDeviceGroup* group) { |
| LOG_DEBUG(); |
| auto leAudioDevice = group->GetFirstDevice(); |
| while (leAudioDevice) { |
| if (leAudioDevice->closing_stream_for_disconnection_) { |
| leAudioDevice->closing_stream_for_disconnection_ = false; |
| LOG_DEBUG("Disconnecting group id: %d, address: %s", group->group_id_, |
| ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_)); |
| bool force_acl_disconnect = |
| leAudioDevice->autoconnect_flag_ && group->IsEnabled(); |
| DisconnectDevice(leAudioDevice, force_acl_disconnect); |
| } |
| leAudioDevice = group->GetNextDevice(leAudioDevice); |
| } |
| } |
| |
| void UpdateAudioConfigToHal(const ::le_audio::offload_config& config, |
| uint8_t remote_direction) { |
| if ((remote_direction & le_audio::types::kLeAudioDirectionSink) && |
| le_audio_source_hal_client_) { |
| le_audio_source_hal_client_->UpdateAudioConfigToHal(config); |
| } |
| if ((remote_direction & le_audio::types::kLeAudioDirectionSource) && |
| le_audio_sink_hal_client_) { |
| le_audio_sink_hal_client_->UpdateAudioConfigToHal(config); |
| } |
| } |
| |
| void NotifyUpperLayerGroupTurnedIdleDuringCall(int group_id) { |
| if (!osi_property_get_bool(kNotifyUpperLayerAboutGroupBeingInIdleDuringCall, |
| false)) { |
| return; |
| } |
| |
| /* If group is inactive, phone is in call and Group is not having CIS |
| * connected, notify upper layer about it, so it can decide to create SCO if |
| * it is in the handover case |
| */ |
| if ((IsInCall() || IsInVoipCall()) && |
| active_group_id_ == bluetooth::groups::kGroupUnknown) { |
| callbacks_->OnGroupStatus(group_id, GroupStatus::TURNED_IDLE_DURING_CALL); |
| } |
| } |
| |
| void take_stream_time(void) { |
| if (stream_setup_start_timestamp_ == 0) { |
| return; |
| } |
| |
| if (stream_start_history_queue_.size() == 10) { |
| stream_start_history_queue_.pop_back(); |
| } |
| |
| stream_setup_end_timestamp_ = bluetooth::common::time_get_os_boottime_us(); |
| stream_start_history_queue_.emplace_front( |
| (stream_setup_end_timestamp_ - stream_setup_start_timestamp_) / 1000); |
| |
| stream_setup_end_timestamp_ = 0; |
| stream_setup_start_timestamp_ = 0; |
| } |
| |
| void notifyGroupStreamStatus(int group_id, |
| GroupStreamStatus groupStreamStatus) { |
| if (!IS_FLAG_ENABLED(leaudio_callback_on_group_stream_status)) { |
| return; |
| } |
| |
| GroupStreamStatus newGroupStreamStatus = GroupStreamStatus::IDLE; |
| if (groupStreamStatus == GroupStreamStatus::STREAMING) { |
| newGroupStreamStatus = GroupStreamStatus::STREAMING; |
| } |
| |
| auto it = lastNotifiedGroupStreamStatusMap_.find(group_id); |
| |
| if (it != lastNotifiedGroupStreamStatusMap_.end()) { |
| if (it->second != newGroupStreamStatus) { |
| callbacks_->OnGroupStreamStatus(group_id, newGroupStreamStatus); |
| it->second = newGroupStreamStatus; |
| } |
| } else { |
| callbacks_->OnGroupStreamStatus(group_id, newGroupStreamStatus); |
| lastNotifiedGroupStreamStatusMap_.emplace(group_id, newGroupStreamStatus); |
| } |
| } |
| |
| void OnStateMachineStatusReportCb(int group_id, GroupStreamStatus status) { |
| LOG_INFO( |
| "status: %d , group_id: %d, audio_sender_state %s, " |
| "audio_receiver_state %s", |
| static_cast<int>(status), group_id, |
| bluetooth::common::ToString(audio_sender_state_).c_str(), |
| bluetooth::common::ToString(audio_receiver_state_).c_str()); |
| LeAudioDeviceGroup* group = aseGroups_.FindById(group_id); |
| |
| notifyGroupStreamStatus(group_id, status); |
| |
| switch (status) { |
| case GroupStreamStatus::STREAMING: { |
| ASSERT_LOG(group_id == active_group_id_, "invalid group id %d!=%d", |
| group_id, active_group_id_); |
| |
| take_stream_time(); |
| |
| le_audio::MetricsCollector::Get()->OnStreamStarted( |
| active_group_id_, configuration_context_type_); |
| |
| if (leAudioHealthStatus_) { |
| leAudioHealthStatus_->AddStatisticForGroup( |
| group, LeAudioHealthGroupStatType::STREAM_CREATE_SUCCESS); |
| } |
| |
| if (!group) { |
| LOG_ERROR("Group %d does not exist anymore. This shall not happen ", |
| group_id); |
| return; |
| } |
| |
| if ((audio_sender_state_ == AudioState::IDLE) && |
| (audio_receiver_state_ == AudioState::IDLE)) { |
| /* Audio Framework is not interested in the stream anymore. |
| * Just stop streaming |
| */ |
| LOG_WARN("Stopping stream for group %d as AF not interested.", |
| group_id); |
| groupStateMachine_->StopStream(group); |
| return; |
| } |
| |
| /* It might happen that the configuration has already changed, while |
| * the group was in the ongoing reconfiguration. We should stop the |
| * stream and reconfigure once again. |
| */ |
| if (group->GetConfigurationContextType() != |
| configuration_context_type_) { |
| LOG_DEBUG( |
| "The configuration %s is no longer valid. Stopping the stream to" |
| " reconfigure to %s", |
| ToString(group->GetConfigurationContextType()).c_str(), |
| ToString(configuration_context_type_).c_str()); |
| group->SetPendingConfiguration(); |
| groupStateMachine_->StopStream(group); |
| stream_setup_start_timestamp_ = |
| bluetooth::common::time_get_os_boottime_us(); |
| return; |
| } |
| |
| BidirectionalPair<uint16_t> delays_pair = { |
| .sink = |
| group->GetRemoteDelay(le_audio::types::kLeAudioDirectionSink), |
| .source = group->GetRemoteDelay( |
| le_audio::types::kLeAudioDirectionSource)}; |
| CodecManager::GetInstance()->UpdateActiveAudioConfig( |
| group->stream_conf.stream_params, delays_pair, |
| std::bind(&LeAudioClientImpl::UpdateAudioConfigToHal, |
| weak_factory_.GetWeakPtr(), std::placeholders::_1, |
| std::placeholders::_2)); |
| |
| if (audio_sender_state_ == AudioState::READY_TO_START) |
| StartSendingAudio(group_id); |
| if (audio_receiver_state_ == AudioState::READY_TO_START) |
| StartReceivingAudio(group_id); |
| |
| SendAudioGroupCurrentCodecConfigChanged(group); |
| break; |
| } |
| case GroupStreamStatus::SUSPENDED: |
| stream_setup_end_timestamp_ = 0; |
| stream_setup_start_timestamp_ = 0; |
| /** Stop Audio but don't release all the Audio resources */ |
| SuspendAudio(); |
| break; |
| case GroupStreamStatus::CONFIGURED_BY_USER: { |
| // Check which directions were suspended |
| uint8_t previously_active_directions = 0; |
| if (audio_sender_state_ >= AudioState::READY_TO_START) { |
| previously_active_directions |= |
| le_audio::types::kLeAudioDirectionSink; |
| } |
| if (audio_receiver_state_ >= AudioState::READY_TO_START) { |
| previously_active_directions |= |
| le_audio::types::kLeAudioDirectionSource; |
| } |
| |
| /* We are done with reconfiguration. |
| * Clean state and if Audio HAL is waiting, cancel the request |
| * so Audio HAL can Resume again. |
| */ |
| CancelStreamingRequest(); |
| ReconfigurationComplete(previously_active_directions); |
| } break; |
| case GroupStreamStatus::CONFIGURED_AUTONOMOUS: |
| /* This state is notified only when |
| * groups stays into CONFIGURED state after |
| * STREAMING. Peer device uses cache. For the moment |
| * it is handled same as IDLE |
| */ |
| case GroupStreamStatus::IDLE: { |
| if (sw_enc_left) sw_enc_left.reset(); |
| if (sw_enc_right) sw_enc_right.reset(); |
| if (sw_dec_left) sw_dec_left.reset(); |
| if (sw_dec_right) sw_dec_right.reset(); |
| CleanCachedMicrophoneData(); |
| |
| if (group) { |
| UpdateLocationsAndContextsAvailability(group->group_id_); |
| if (group->IsPendingConfiguration()) { |
| SuspendedForReconfiguration(); |
| auto remote_direction = |
| kLeAudioContextAllRemoteSource.test(configuration_context_type_) |
| ? le_audio::types::kLeAudioDirectionSource |
| : le_audio::types::kLeAudioDirectionSink; |
| |
| /* Reconfiguration to non requiring source scenario */ |
| if (sink_monitor_mode_ && |
| (remote_direction == le_audio::types::kLeAudioDirectionSink)) { |
| notifyAudioLocalSink( |
| UnicastMonitorModeStatus::STREAMING_SUSPENDED); |
| } |
| |
| auto remote_contexts = |
| DirectionalRealignMetadataAudioContexts(group, remote_direction); |
| ApplyRemoteMetadataAudioContextPolicy(group, remote_contexts, |
| remote_direction); |
| if (GroupStream(group->group_id_, configuration_context_type_, |
| remote_contexts)) { |
| /* If configuration succeed wait for new status. */ |
| return; |
| } |
| LOG_INFO("Clear pending configuration flag for group %d", |
| group->group_id_); |
| group->ClearPendingConfiguration(); |
| } else { |
| if (sink_monitor_mode_) { |
| notifyAudioLocalSink( |
| UnicastMonitorModeStatus::STREAMING_SUSPENDED); |
| } |
| |
| if (source_monitor_mode_) { |
| callbacks_->OnUnicastMonitorModeStatus( |
| le_audio::types::kLeAudioDirectionSource, |
| UnicastMonitorModeStatus::STREAMING_SUSPENDED); |
| } |
| } |
| } |
| |
| stream_setup_end_timestamp_ = 0; |
| stream_setup_start_timestamp_ = 0; |
| CancelStreamingRequest(); |
| |
| if (group) { |
| NotifyUpperLayerGroupTurnedIdleDuringCall(group->group_id_); |
| HandlePendingDeviceRemove(group); |
| HandlePendingDeviceDisconnection(group); |
| } |
| break; |
| } |
| case GroupStreamStatus::RELEASING: |
| case GroupStreamStatus::SUSPENDING: |
| if (active_group_id_ != bluetooth::groups::kGroupUnknown && |
| (active_group_id_ == group->group_id_) && |
| !group->IsPendingConfiguration() && |
| (audio_sender_state_ == AudioState::STARTED || |
| audio_receiver_state_ == AudioState::STARTED)) { |
| /* If releasing state is happening but it was not initiated either by |
| * reconfiguration or Audio Framework actions either by the Active group change, |
| * it means that it is some internal state machine error. This is very unlikely and |
| * for now just Inactivate the group. |
| */ |
| LOG_ERROR("Internal state machine error"); |
| group->PrintDebugState(); |
| groupSetAndNotifyInactive(); |
| } |
| |
| if (audio_sender_state_ != AudioState::IDLE) |
| audio_sender_state_ = AudioState::RELEASING; |
| |
| if (audio_receiver_state_ != AudioState::IDLE) |
| audio_receiver_state_ = AudioState::RELEASING; |
| |
| break; |
| default: |
| break; |
| } |
| } |
| |
| void OnUpdatedCisConfiguration(int group_id, uint8_t direction) { |
| LeAudioDeviceGroup* group = aseGroups_.FindById(group_id); |
| if (!group) { |
| LOG_ERROR("Invalid group_id: %d", group_id); |
| return; |
| } |
| group->UpdateCisConfiguration(direction); |
| } |
| |
| private: |
| tGATT_IF gatt_if_; |
| bluetooth::le_audio::LeAudioClientCallbacks* callbacks_; |
| LeAudioDevices leAudioDevices_; |
| LeAudioDeviceGroups aseGroups_; |
| LeAudioGroupStateMachine* groupStateMachine_; |
| int active_group_id_; |
| LeAudioContextType configuration_context_type_; |
| static constexpr char kAllowMultipleContextsInMetadata[] = |
| "persist.bluetooth.leaudio.allow.multiple.contexts"; |
| BidirectionalPair<AudioContexts> local_metadata_context_types_; |
| uint64_t stream_setup_start_timestamp_; |
| uint64_t stream_setup_end_timestamp_; |
| std::deque<uint64_t> stream_start_history_queue_; |
| |
| /* Microphone (s) */ |
| AudioState audio_receiver_state_; |
| /* Speaker(s) */ |
| AudioState audio_sender_state_; |
| /* Keep in call state. */ |
| bool in_call_; |
| bool in_voip_call_; |
| /* Listen for streaming status on Sink stream */ |
| bool sink_monitor_mode_; |
| /* Status which has been notified to Service */ |
| std::optional<UnicastMonitorModeStatus> sink_monitor_notified_status_; |
| /* Listen for streaming status on Source stream */ |
| bool source_monitor_mode_; |
| |
| /* Reconnection mode */ |
| tBTM_BLE_CONN_TYPE reconnection_mode_; |
| static constexpr uint64_t kGroupConnectedWatchDelayMs = 3000; |
| static constexpr uint64_t kRecoveryReconnectDelayMs = 2000; |
| static constexpr uint64_t kAutoConnectAfterOwnDisconnectDelayMs = 1000; |
| static constexpr uint64_t kCsisGroupMemberDelayMs = 5000; |
| |
| /* LeAudioHealthStatus */ |
| LeAudioHealthStatus* leAudioHealthStatus_ = nullptr; |
| |
| static constexpr char kNotifyUpperLayerAboutGroupBeingInIdleDuringCall[] = |
| "persist.bluetooth.leaudio.notify.idle.during.call"; |
| |
| static constexpr uint16_t kBapMinimumAttMtu = 64; |
| |
| /* Current stream configuration */ |
| LeAudioCodecConfiguration current_source_codec_config; |
| LeAudioCodecConfiguration current_sink_codec_config; |
| |
| /* Static Audio Framework session configuration. |
| * Resampling will be done inside the bt stack |
| */ |
| LeAudioCodecConfiguration audio_framework_source_config = { |
| .num_channels = 2, |
| .sample_rate = bluetooth::audio::le_audio::kSampleRate48000, |
| .bits_per_sample = bluetooth::audio::le_audio::kBitsPerSample16, |
| .data_interval_us = LeAudioCodecConfiguration::kInterval10000Us, |
| }; |
| |
| LeAudioCodecConfiguration audio_framework_sink_config = { |
| .num_channels = 2, |
| .sample_rate = bluetooth::audio::le_audio::kSampleRate16000, |
| .bits_per_sample = bluetooth::audio::le_audio::kBitsPerSample16, |
| .data_interval_us = LeAudioCodecConfiguration::kInterval10000Us, |
| }; |
| |
| std::unique_ptr<le_audio::CodecInterface> sw_enc_left; |
| std::unique_ptr<le_audio::CodecInterface> sw_enc_right; |
| |
| std::unique_ptr<le_audio::CodecInterface> sw_dec_left; |
| std::unique_ptr<le_audio::CodecInterface> sw_dec_right; |
| |
| std::vector<uint8_t> encoded_data; |
| std::unique_ptr<LeAudioSourceAudioHalClient> le_audio_source_hal_client_; |
| std::unique_ptr<LeAudioSinkAudioHalClient> le_audio_sink_hal_client_; |
| static constexpr uint64_t kAudioSuspentKeepIsoAliveTimeoutMs = 5000; |
| static constexpr uint64_t kAudioDisableTimeoutMs = 3000; |
| static constexpr char kAudioSuspentKeepIsoAliveTimeoutMsProp[] = |
| "persist.bluetooth.leaudio.audio.suspend.timeoutms"; |
| alarm_t* close_vbc_timeout_; |
| alarm_t* suspend_timeout_; |
| alarm_t* disable_timer_; |
| static constexpr uint64_t kDeviceAttachDelayMs = 500; |
| |
| uint32_t cached_channel_timestamp_ = 0; |
| le_audio::CodecInterface* cached_channel_ = nullptr; |
| |
| base::WeakPtrFactory<LeAudioClientImpl> weak_factory_{this}; |
| |
| std::map<int, GroupStreamStatus> lastNotifiedGroupStreamStatusMap_; |
| |
| void ClientAudioInterfaceRelease() { |
| if (le_audio_source_hal_client_) { |
| le_audio_source_hal_client_->Stop(); |
| le_audio_source_hal_client_.reset(); |
| } |
| |
| if (le_audio_sink_hal_client_) { |
| /* Keep session set up to monitor streaming request. This is required if |
| * there is another LE Audio device streaming (e.g. Broadcast) and via |
| * the session callbacks special action from this Module would be |
| * required e.g. to Unicast handover. |
| */ |
| if (!sink_monitor_mode_) { |
| local_metadata_context_types_.sink.clear(); |
| le_audio_sink_hal_client_->Stop(); |
| le_audio_sink_hal_client_.reset(); |
| } |
| } |
| local_metadata_context_types_.source.clear(); |
| configuration_context_type_ = LeAudioContextType::UNINITIALIZED; |
| |
| le_audio::MetricsCollector::Get()->OnStreamEnded(active_group_id_); |
| } |
| |
| bool DsaDataConsume(LeAudioDeviceGroup* group, uint16_t cis_conn_hdl, |
| uint8_t* data, uint16_t size, uint32_t timestamp) { |
| if (!IS_FLAG_ENABLED(leaudio_dynamic_spatial_audio)) { |
| return false; |
| } |
| |
| if (iso_data_callback == nullptr || !group->dsa_.active || |
| group->dsa_.mode != DsaMode::ISO_SW) { |
| return false; |
| } |
| |
| // Find LE Audio device |
| LeAudioDevice* leAudioDevice = group->GetFirstDevice(); |
| while (leAudioDevice != nullptr) { |
| if (leAudioDevice->GetDsaCisHandle() == cis_conn_hdl && |
| leAudioDevice->GetDsaDataPathState() == DataPathState::CONFIGURED) { |
| break; |
| } |
| leAudioDevice = group->GetNextDevice(leAudioDevice); |
| } |
| if (leAudioDevice == nullptr) { |
| LOG_WARN("No LE Audio device found for CIS handle: %d", cis_conn_hdl); |
| return false; |
| } |
| |
| bool consumed = iso_data_callback(leAudioDevice->address_, cis_conn_hdl, |
| data, size, timestamp); |
| if (consumed) { |
| return true; |
| } else { |
| LOG_VERBOSE("ISO data consumer not ready to accept data"); |
| return false; |
| } |
| } |
| }; |
| |
| static void le_audio_health_status_callback(const RawAddress& addr, |
| int group_id, |
| LeAudioHealthBasedAction action) { |
| if (instance) { |
| instance->LeAudioHealthSendRecommendation(addr, group_id, action); |
| } |
| } |
| |
| /* This is a generic callback method for gatt client which handles every client |
| * application events. |
| */ |
| void le_audio_gattc_callback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data) { |
| if (!p_data || !instance) return; |
| |
| LOG_INFO("event = %d", static_cast<int>(event)); |
| |
| switch (event) { |
| case BTA_GATTC_DEREG_EVT: |
| break; |
| |
| case BTA_GATTC_NOTIF_EVT: |
| instance->LeAudioCharValueHandle( |
| p_data->notify.conn_id, p_data->notify.handle, p_data->notify.len, |
| static_cast<uint8_t*>(p_data->notify.value), true); |
| |
| if (!p_data->notify.is_notify) |
| BTA_GATTC_SendIndConfirm(p_data->notify.conn_id, p_data->notify.handle); |
| |
| break; |
| |
| case BTA_GATTC_OPEN_EVT: |
| instance->OnGattConnected(p_data->open.status, p_data->open.conn_id, |
| p_data->open.client_if, p_data->open.remote_bda, |
| p_data->open.transport, p_data->open.mtu); |
| break; |
| |
| case BTA_GATTC_ENC_CMPL_CB_EVT: { |
| uint8_t encryption_status; |
| if (BTM_IsEncrypted(p_data->enc_cmpl.remote_bda, BT_TRANSPORT_LE)) { |
| encryption_status = BTM_SUCCESS; |
| } else { |
| encryption_status = BTM_FAILED_ON_SECURITY; |
| } |
| instance->OnEncryptionComplete(p_data->enc_cmpl.remote_bda, |
| encryption_status); |
| } break; |
| |
| case BTA_GATTC_CLOSE_EVT: |
| instance->OnGattDisconnected( |
| p_data->close.conn_id, p_data->close.client_if, |
| p_data->close.remote_bda, p_data->close.reason); |
| break; |
| |
| case BTA_GATTC_SEARCH_CMPL_EVT: |
| instance->OnServiceSearchComplete(p_data->search_cmpl.conn_id, |
| p_data->search_cmpl.status); |
| break; |
| |
| case BTA_GATTC_SRVC_DISC_DONE_EVT: |
| instance->OnGattServiceDiscoveryDone(p_data->service_changed.remote_bda); |
| break; |
| |
| case BTA_GATTC_SRVC_CHG_EVT: |
| instance->OnServiceChangeEvent(p_data->remote_bda); |
| break; |
| case BTA_GATTC_CFG_MTU_EVT: |
| instance->OnMtuChanged(p_data->cfg_mtu.conn_id, p_data->cfg_mtu.mtu); |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| class LeAudioStateMachineHciCallbacksImpl : public CigCallbacks { |
| public: |
| void OnCigEvent(uint8_t event, void* data) override { |
| if (instance) instance->IsoCigEventsCb(event, data); |
| } |
| |
| void OnCisEvent(uint8_t event, void* data) override { |
| if (instance) instance->IsoCisEventsCb(event, data); |
| } |
| |
| void OnSetupIsoDataPath(uint8_t status, uint16_t conn_handle, |
| uint8_t cig_id) override { |
| if (instance) instance->IsoSetupIsoDataPathCb(status, conn_handle, cig_id); |
| } |
| |
| void OnRemoveIsoDataPath(uint8_t status, uint16_t conn_handle, |
| uint8_t cig_id) override { |
| if (instance) instance->IsoRemoveIsoDataPathCb(status, conn_handle, cig_id); |
| } |
| |
| void OnIsoLinkQualityRead( |
| uint8_t conn_handle, uint8_t cig_id, uint32_t txUnackedPackets, |
| uint32_t txFlushedPackets, uint32_t txLastSubeventPackets, |
| uint32_t retransmittedPackets, uint32_t crcErrorPackets, |
| uint32_t rxUnreceivedPackets, uint32_t duplicatePackets) { |
| if (instance) |
| instance->IsoLinkQualityReadCb(conn_handle, cig_id, txUnackedPackets, |
| txFlushedPackets, txLastSubeventPackets, |
| retransmittedPackets, crcErrorPackets, |
| rxUnreceivedPackets, duplicatePackets); |
| } |
| }; |
| |
| LeAudioStateMachineHciCallbacksImpl stateMachineHciCallbacksImpl; |
| |
| class CallbacksImpl : public LeAudioGroupStateMachine::Callbacks { |
| public: |
| void StatusReportCb(int group_id, GroupStreamStatus status) override { |
| if (instance) instance->OnStateMachineStatusReportCb(group_id, status); |
| } |
| |
| void OnStateTransitionTimeout(int group_id) override { |
| if (instance) instance->OnLeAudioDeviceSetStateTimeout(group_id); |
| } |
| |
| void OnDeviceAutonomousStateTransitionTimeout( |
| LeAudioDevice* leAudioDevice) override { |
| if (instance) |
| instance->OnDeviceAutonomousStateTransitionTimeout(leAudioDevice); |
| } |
| |
| void OnUpdatedCisConfiguration(int group_id, uint8_t direction) { |
| if (instance) instance->OnUpdatedCisConfiguration(group_id, direction); |
| } |
| }; |
| |
| CallbacksImpl stateMachineCallbacksImpl; |
| |
| class SourceCallbacksImpl : public LeAudioSourceAudioHalClient::Callbacks { |
| public: |
| void OnAudioDataReady(const std::vector<uint8_t>& data) override { |
| if (instance) instance->OnAudioDataReady(data); |
| } |
| void OnAudioSuspend(void) override { |
| if (instance) instance->OnLocalAudioSourceSuspend(); |
| } |
| |
| void OnAudioResume(void) override { |
| if (instance) instance->OnLocalAudioSourceResume(); |
| } |
| |
| void OnAudioMetadataUpdate(source_metadata_v7 source_metadata, |
| DsaMode dsa_mode) override { |
| if (instance) |
| instance->OnLocalAudioSourceMetadataUpdate(std::move(source_metadata), |
| dsa_mode); |
| } |
| }; |
| |
| class SinkCallbacksImpl : public LeAudioSinkAudioHalClient::Callbacks { |
| public: |
| void OnAudioSuspend(void) override { |
| if (instance) instance->OnLocalAudioSinkSuspend(); |
| } |
| void OnAudioResume(void) override { |
| if (instance) instance->OnLocalAudioSinkResume(); |
| } |
| |
| void OnAudioMetadataUpdate(sink_metadata_v7 sink_metadata) override { |
| if (instance) |
| instance->OnLocalAudioSinkMetadataUpdate(std::move(sink_metadata)); |
| } |
| }; |
| |
| SourceCallbacksImpl audioSinkReceiverImpl; |
| SinkCallbacksImpl audioSourceReceiverImpl; |
| |
| class DeviceGroupsCallbacksImpl : public DeviceGroupsCallbacks { |
| public: |
| void OnGroupAdded(const RawAddress& address, const bluetooth::Uuid& uuid, |
| int group_id) override { |
| if (instance) instance->OnGroupAddedCb(address, uuid, group_id); |
| } |
| void OnGroupMemberAdded(const RawAddress& address, int group_id) override { |
| if (instance) instance->OnGroupMemberAddedCb(address, group_id); |
| } |
| void OnGroupMemberRemoved(const RawAddress& address, int group_id) override { |
| if (instance) instance->OnGroupMemberRemovedCb(address, group_id); |
| } |
| void OnGroupRemoved(const bluetooth::Uuid& uuid, int group_id) { |
| /* to implement if needed */ |
| } |
| void OnGroupAddFromStorage(const RawAddress& address, |
| const bluetooth::Uuid& uuid, int group_id) { |
| /* to implement if needed */ |
| } |
| }; |
| |
| class DeviceGroupsCallbacksImpl; |
| DeviceGroupsCallbacksImpl deviceGroupsCallbacksImpl; |
| |
| } // namespace |
| |
| void LeAudioClient::AddFromStorage( |
| const RawAddress& addr, bool autoconnect, int sink_audio_location, |
| int source_audio_location, int sink_supported_context_types, |
| int source_supported_context_types, const std::vector<uint8_t>& handles, |
| const std::vector<uint8_t>& sink_pacs, |
| const std::vector<uint8_t>& source_pacs, const std::vector<uint8_t>& ases) { |
| if (!instance) { |
| LOG(ERROR) << "Not initialized yet"; |
| return; |
| } |
| |
| instance->AddFromStorage(addr, autoconnect, sink_audio_location, |
| source_audio_location, sink_supported_context_types, |
| source_supported_context_types, handles, sink_pacs, |
| source_pacs, ases); |
| } |
| |
| bool LeAudioClient::GetHandlesForStorage(const RawAddress& addr, |
| std::vector<uint8_t>& out) { |
| if (!instance) { |
| LOG_ERROR("Not initialized yet"); |
| return false; |
| } |
| |
| return instance->GetHandlesForStorage(addr, out); |
| } |
| |
| bool LeAudioClient::GetSinkPacsForStorage(const RawAddress& addr, |
| std::vector<uint8_t>& out) { |
| if (!instance) { |
| LOG_ERROR("Not initialized yet"); |
| return false; |
| } |
| |
| return instance->GetSinkPacsForStorage(addr, out); |
| } |
| |
| bool LeAudioClient::GetSourcePacsForStorage(const RawAddress& addr, |
| std::vector<uint8_t>& out) { |
| if (!instance) { |
| LOG_ERROR("Not initialized yet"); |
| return false; |
| } |
| |
| return instance->GetSourcePacsForStorage(addr, out); |
| } |
| |
| bool LeAudioClient::GetAsesForStorage(const RawAddress& addr, |
| std::vector<uint8_t>& out) { |
| if (!instance) { |
| LOG_ERROR("Not initialized yet"); |
| return false; |
| } |
| |
| return instance->GetAsesForStorage(addr, out); |
| } |
| |
| bool LeAudioClient::IsLeAudioClientRunning(void) { return instance != nullptr; } |
| |
| bool LeAudioClient::IsLeAudioClientInStreaming(void) { |
| if (!instance) { |
| return false; |
| } |
| return instance->IsInStreaming(); |
| } |
| |
| LeAudioClient* LeAudioClient::Get() { |
| CHECK(instance); |
| return instance; |
| } |
| |
| /* Initializer of main le audio implementation class and its instance */ |
| void LeAudioClient::Initialize( |
| bluetooth::le_audio::LeAudioClientCallbacks* callbacks_, |
| base::Closure initCb, base::Callback<bool()> hal_2_1_verifier, |
| const std::vector<bluetooth::le_audio::btle_audio_codec_config_t>& |
| offloading_preference) { |
| std::scoped_lock<std::mutex> lock(instance_mutex); |
| if (instance) { |
| LOG(ERROR) << "Already initialized"; |
| return; |
| } |
| |
| if (!controller_get_interface() |
| ->SupportsBleConnectedIsochronousStreamCentral() && |
| !controller_get_interface() |
| ->SupportsBleConnectedIsochronousStreamPeripheral()) { |
| LOG(ERROR) << "Controller reports no ISO support." |
| " LeAudioClient Init aborted."; |
| return; |
| } |
| |
| LOG_ASSERT(std::move(hal_2_1_verifier).Run()) |
| << __func__ |
| << ", LE Audio Client requires Bluetooth Audio HAL V2.1 at least. Either " |
| "disable LE Audio Profile, or update your HAL"; |
| |
| IsoManager::GetInstance()->Start(); |
| |
| audioSinkReceiver = &audioSinkReceiverImpl; |
| audioSourceReceiver = &audioSourceReceiverImpl; |
| stateMachineHciCallbacks = &stateMachineHciCallbacksImpl; |
| stateMachineCallbacks = &stateMachineCallbacksImpl; |
| device_group_callbacks = &deviceGroupsCallbacksImpl; |
| instance = new LeAudioClientImpl(callbacks_, stateMachineCallbacks, initCb); |
| |
| IsoManager::GetInstance()->RegisterCigCallbacks(stateMachineHciCallbacks); |
| CodecManager::GetInstance()->Start(offloading_preference); |
| ContentControlIdKeeper::GetInstance()->Start(); |
| |
| callbacks_->OnInitialized(); |
| |
| auto cm = CodecManager::GetInstance(); |
| callbacks_->OnAudioLocalCodecCapabilities(cm->GetLocalAudioInputCodecCapa(), |
| cm->GetLocalAudioOutputCodecCapa()); |
| } |
| |
| void LeAudioClient::DebugDump(int fd) { |
| std::scoped_lock<std::mutex> lock(instance_mutex); |
| DeviceGroups::DebugDump(fd); |
| |
| dprintf(fd, "LeAudio Manager: \n"); |
| if (instance) |
| instance->Dump(fd); |
| else |
| dprintf(fd, " Not initialized \n"); |
| |
| LeAudioSinkAudioHalClient::DebugDump(fd); |
| LeAudioSourceAudioHalClient::DebugDump(fd); |
| le_audio::AudioSetConfigurationProvider::DebugDump(fd); |
| IsoManager::GetInstance()->Dump(fd); |
| LeAudioLogHistory::DebugDump(fd); |
| dprintf(fd, "\n"); |
| } |
| |
| void LeAudioClient::Cleanup(void) { |
| std::scoped_lock<std::mutex> lock(instance_mutex); |
| if (!instance) { |
| LOG(ERROR) << "Not initialized"; |
| return; |
| } |
| |
| LeAudioClientImpl* ptr = instance; |
| instance = nullptr; |
| ptr->Cleanup(); |
| delete ptr; |
| ptr = nullptr; |
| |
| CodecManager::GetInstance()->Stop(); |
| ContentControlIdKeeper::GetInstance()->Stop(); |
| LeAudioGroupStateMachine::Cleanup(); |
| IsoManager::GetInstance()->Stop(); |
| le_audio::MetricsCollector::Get()->Flush(); |
| } |
| |
| bool LeAudioClient::RegisterIsoDataConsumer(LeAudioIsoDataCallback callback) { |
| if (!IS_FLAG_ENABLED(leaudio_dynamic_spatial_audio)) { |
| return false; |
| } |
| |
| iso_data_callback = callback; |
| return true; |
| } |