| /* |
| * 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 <base/bind.h> |
| #include <base/strings/string_number_conversions.h> |
| |
| #include <deque> |
| |
| #include "advertise_data_parser.h" |
| #include "audio_hal_client/audio_hal_client.h" |
| #include "audio_hal_interface/le_audio_software.h" |
| #include "bta/csis/csis_types.h" |
| #include "bta_api.h" |
| #include "bta_gatt_api.h" |
| #include "bta_gatt_queue.h" |
| #include "bta_groups.h" |
| #include "bta_le_audio_api.h" |
| #include "btif_profile_storage.h" |
| #include "btm_iso_api.h" |
| #include "client_parser.h" |
| #include "codec_manager.h" |
| #include "common/time_util.h" |
| #include "content_control_id_keeper.h" |
| #include "device/include/controller.h" |
| #include "devices.h" |
| #include "embdrv/lc3/include/lc3.h" |
| #include "gatt/bta_gattc_int.h" |
| #include "gd/common/strings.h" |
| #include "internal_include/stack_config.h" |
| #include "le_audio_set_configuration_provider.h" |
| #include "le_audio_types.h" |
| #include "le_audio_utils.h" |
| #include "metrics_collector.h" |
| #include "osi/include/log.h" |
| #include "osi/include/osi.h" |
| #include "osi/include/properties.h" |
| #include "stack/btm/btm_sec.h" |
| #include "stack/include/btu.h" // do_in_main_thread |
| #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 le_audio::CodecManager; |
| using le_audio::ContentControlIdKeeper; |
| using le_audio::DeviceConnectState; |
| 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::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::AudioStreamDataPathState; |
| using le_audio::types::hdl_pair; |
| using le_audio::types::kDefaultScanDurationS; |
| using le_audio::types::LeAudioContextType; |
| using le_audio::utils::GetAllCcids; |
| using le_audio::utils::GetAllowedAudioContextsFromSourceMetadata; |
| |
| using le_audio::client_parser::ascs:: |
| kCtpResponseCodeInvalidConfigurationParameterValue; |
| using le_audio::client_parser::ascs::kCtpResponseCodeSuccess; |
| using le_audio::client_parser::ascs::kCtpResponseInvalidAseCisMapping; |
| using le_audio::client_parser::ascs::kCtpResponseNoReason; |
| |
| /* 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); |
| |
| inline uint8_t bits_to_bytes_per_sample(uint8_t bits_per_sample) { |
| // 24 bit audio stream is sent as unpacked, each sample takes 4 bytes. |
| if (bits_per_sample == 24) return 4; |
| |
| return bits_per_sample / 8; |
| } |
| |
| inline lc3_pcm_format bits_to_lc3_bits(uint8_t bits_per_sample) { |
| if (bits_per_sample == 16) return LC3_PCM_FORMAT_S16; |
| |
| if (bits_per_sample == 24) return LC3_PCM_FORMAT_S24; |
| |
| LOG_ALWAYS_FATAL("Encoder/decoder don't know how to handle %d", |
| bits_per_sample); |
| return LC3_PCM_FORMAT_S16; |
| } |
| |
| class LeAudioClientImpl; |
| LeAudioClientImpl* instance; |
| LeAudioSourceAudioHalClient::Callbacks* audioSinkReceiver; |
| LeAudioSinkAudioHalClient::Callbacks* audioSourceReceiver; |
| CigCallbacks* stateMachineHciCallbacks; |
| LeAudioGroupStateMachine::Callbacks* stateMachineCallbacks; |
| DeviceGroupsCallbacks* device_group_callbacks; |
| |
| /* |
| * 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(suspend_timeout_); |
| suspend_timeout_ = nullptr; |
| }; |
| |
| 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::MEDIA), |
| metadata_context_types_(AudioContexts(LeAudioContextType::MEDIA)), |
| stream_setup_start_timestamp_(0), |
| stream_setup_end_timestamp_(0), |
| audio_receiver_state_(AudioState::IDLE), |
| audio_sender_state_(AudioState::IDLE), |
| in_call_(false), |
| current_source_codec_config({0, 0, 0, 0}), |
| current_sink_codec_config({0, 0, 0, 0}), |
| lc3_encoder_left_mem(nullptr), |
| lc3_encoder_right_mem(nullptr), |
| lc3_decoder_left_mem(nullptr), |
| lc3_decoder_right_mem(nullptr), |
| lc3_decoder_left(nullptr), |
| lc3_decoder_right(nullptr), |
| le_audio_source_hal_client_(nullptr), |
| le_audio_sink_hal_client_(nullptr), |
| suspend_timeout_(alarm_new("LeAudioSuspendTimeout")), |
| disable_timer_(alarm_new("LeAudioDisableTimer")) { |
| LeAudioGroupStateMachine::Initialize(state_machine_callbacks_); |
| groupStateMachine_ = LeAudioGroupStateMachine::Get(); |
| |
| 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 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 << " 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); |
| } |
| |
| void OnGroupMemberAddedCb(const RawAddress& address, int group_id) { |
| LOG(INFO) << __func__ << " address: " << 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; |
| } |
| |
| group_add_node(group_id, address); |
| } |
| |
| void OnGroupMemberRemovedCb(const RawAddress& address, int group_id) { |
| LOG(INFO) << __func__ << " address: " << address |
| << " group_id: " << group_id; |
| |
| LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address); |
| if (!leAudioDevice) return; |
| if (leAudioDevice->group_id_ == bluetooth::groups::kGroupUnknown) { |
| LOG(INFO) << __func__ << " device already not assigned to the group."; |
| return; |
| } |
| |
| LeAudioDeviceGroup* group = aseGroups_.FindById(group_id); |
| if (group == NULL) { |
| LOG(INFO) << __func__ |
| << " device not in the group: " << leAudioDevice->address_ |
| << ", " << group_id; |
| 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; |
| } |
| |
| LOG_ERROR( |
| " State not achieved on time for group: group id %d, current state %s, " |
| "target state: %s", |
| group_id, ToString(group->GetState()).c_str(), |
| ToString(group->GetTargetState()).c_str()); |
| group->SetTargetState(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE); |
| |
| /* 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; |
| } |
| } |
| |
| do { |
| if (instance) instance->DisconnectDevice(leAudioDevice, true); |
| leAudioDevice = group->GetNextActiveDevice(leAudioDevice); |
| } while (leAudioDevice); |
| } |
| |
| void UpdateContextAndLocations(LeAudioDeviceGroup* group, |
| LeAudioDevice* leAudioDevice) { |
| /* Make sure location and direction are updated for the group. */ |
| auto location_update = group->ReloadAudioLocations(); |
| group->ReloadAudioDirections(); |
| |
| auto contexts_updated = group->UpdateAudioContextTypeAvailability( |
| leAudioDevice->GetAvailableContexts()); |
| |
| if (contexts_updated || location_update) { |
| callbacks_->OnAudioConf(group->audio_directions_, group->group_id_, |
| group->snk_audio_locations_.to_ulong(), |
| group->src_audio_locations_.to_ulong(), |
| group->GetAvailableContexts().value()); |
| } |
| } |
| |
| void SuspendedForReconfiguration() { |
| if (audio_sender_state_ > AudioState::IDLE) { |
| le_audio_source_hal_client_->SuspendedForReconfiguration(); |
| } |
| if (audio_receiver_state_ > AudioState::IDLE) { |
| le_audio_sink_hal_client_->SuspendedForReconfiguration(); |
| } |
| } |
| |
| void ReconfigurationComplete(uint8_t directions) { |
| if (directions & le_audio::types::kLeAudioDirectionSink) { |
| le_audio_source_hal_client_->ReconfigurationComplete(); |
| } |
| if (directions & le_audio::types::kLeAudioDirectionSource) { |
| le_audio_sink_hal_client_->ReconfigurationComplete(); |
| } |
| } |
| |
| void CancelStreamingRequest() { |
| if (audio_sender_state_ >= AudioState::READY_TO_START) { |
| le_audio_source_hal_client_->CancelStreamingRequest(); |
| audio_sender_state_ = AudioState::IDLE; |
| } |
| |
| if (audio_receiver_state_ >= AudioState::READY_TO_START) { |
| le_audio_sink_hal_client_->CancelStreamingRequest(); |
| audio_receiver_state_ = AudioState::IDLE; |
| } |
| } |
| |
| void ControlPointNotificationHandler( |
| struct le_audio::client_parser::ascs::ctp_ntf& ntf) { |
| for (auto& entry : ntf.entries) { |
| switch (entry.response_code) { |
| case kCtpResponseCodeInvalidConfigurationParameterValue: |
| switch (entry.reason) { |
| case kCtpResponseInvalidAseCisMapping: |
| CancelStreamingRequest(); |
| break; |
| case kCtpResponseNoReason: |
| default: |
| break; |
| } |
| break; |
| case kCtpResponseCodeSuccess: |
| FALLTHROUGH; |
| default: |
| break; |
| } |
| } |
| } |
| |
| 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 |
| << " 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 */ |
| if (aseGroups_.FindById(old_group_id) != nullptr) { |
| /* Removing node from group may touch its context integrity */ |
| auto contexts_updated = old_group->UpdateAudioContextTypeAvailability( |
| old_group->GetAvailableContexts()); |
| |
| bool group_conf_changed = old_group->ReloadAudioLocations(); |
| group_conf_changed |= old_group->ReloadAudioDirections(); |
| group_conf_changed |= contexts_updated; |
| |
| if (group_conf_changed) { |
| callbacks_->OnAudioConf(old_group->audio_directions_, old_group_id, |
| old_group->snk_audio_locations_.to_ulong(), |
| old_group->src_audio_locations_.to_ulong(), |
| old_group->GetAvailableContexts().value()); |
| } |
| } |
| |
| UpdateContextAndLocations(new_group, leAudioDevice); |
| } |
| |
| 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_state_).c_str()); |
| if (group->IsEmpty() && |
| (group->cig_state_ == le_audio::types::CigState::NONE)) { |
| 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 touch its context integrity */ |
| bool contexts_updated = group->UpdateAudioContextTypeAvailability( |
| group->GetAvailableContexts()); |
| |
| bool group_conf_changed = group->ReloadAudioLocations(); |
| group_conf_changed |= group->ReloadAudioDirections(); |
| group_conf_changed |= contexts_updated; |
| |
| if (group_conf_changed) |
| callbacks_->OnAudioConf(group->audio_directions_, group->group_id_, |
| group->snk_audio_locations_.to_ulong(), |
| group->src_audio_locations_.to_ulong(), |
| group->GetAvailableContexts().value()); |
| } |
| |
| 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; |
| |
| if (!leAudioDevice) { |
| LOG(ERROR) << __func__ |
| << ", Skipping unknown leAudioDevice, address: " << 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; |
| } |
| |
| 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::GAME, |
| LeAudioContextType::EMERGENCYALARM, |
| LeAudioContextType::ALERTS, |
| LeAudioContextType::RINGTONE, |
| LeAudioContextType::VOICEASSISTANTS, |
| LeAudioContextType::INSTRUCTIONAL, |
| LeAudioContextType::NOTIFICATIONS, |
| LeAudioContextType::LIVE, |
| LeAudioContextType::MEDIA, |
| }; |
| for (auto ct : context_priority_list) { |
| if (metadata_context_type.test(ct)) { |
| return AudioContexts(ct); |
| } |
| } |
| } |
| |
| return AudioContexts(LeAudioContextType::UNSPECIFIED); |
| } |
| |
| bool GroupStream(const int group_id, LeAudioContextType context_type, |
| AudioContexts metadata_context_type) { |
| LeAudioDeviceGroup* group = aseGroups_.FindById(group_id); |
| auto final_context_type = context_type; |
| |
| auto adjusted_metadata_context_type = |
| ChooseMetadataContextType(metadata_context_type); |
| DLOG(INFO) << __func__; |
| if (context_type >= LeAudioContextType::RFU) { |
| LOG(ERROR) << __func__ << ", stream context type is not supported: " |
| << ToHexString(context_type); |
| return false; |
| } |
| |
| if (!group) { |
| LOG(ERROR) << __func__ << ", unknown group id: " << group_id; |
| return false; |
| } |
| |
| if (!group->GetAvailableContexts().test(context_type)) { |
| LOG(ERROR) << " Unsupported context type by remote device: " |
| << ToHexString(context_type) << ". Switching to unspecified"; |
| final_context_type = LeAudioContextType::UNSPECIFIED; |
| } |
| |
| 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 (aseGroups_.IsAnyInTransition()) { |
| LOG(INFO) << __func__ << " some group is already in the transition state"; |
| return false; |
| } |
| |
| if (group->IsPendingConfiguration()) { |
| LOG_WARN("Group %d is reconfiguring right now. Drop the update", |
| group->group_id_); |
| return false; |
| } |
| |
| if (group->GetState() != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { |
| stream_setup_start_timestamp_ = |
| bluetooth::common::time_get_os_boottime_us(); |
| } |
| |
| bool result = groupStateMachine_->StartStream( |
| group, final_context_type, adjusted_metadata_context_type, |
| GetAllCcids(adjusted_metadata_context_type)); |
| |
| return result; |
| } |
| |
| void GroupStream(const int group_id, const uint16_t context_type) override { |
| GroupStream(group_id, LeAudioContextType(context_type), |
| AudioContexts(context_type)); |
| } |
| |
| 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) { |
| LOG_ERROR(", group already stopped: %s", |
| 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(context_type, ccid); |
| } |
| |
| void SetInCall(bool in_call) override { |
| LOG_DEBUG("in_call: %d", in_call); |
| in_call_ = in_call; |
| } |
| |
| void StartAudioSession(LeAudioDeviceGroup* group, |
| LeAudioCodecConfiguration* source_config, |
| 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"); |
| |
| /* 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); |
| |
| /* 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); |
| } |
| |
| void GroupSetActive(const int group_id) override { |
| DLOG(INFO) << __func__ << " group_id: " << group_id; |
| |
| if (group_id == bluetooth::groups::kGroupUnknown) { |
| if (active_group_id_ == bluetooth::groups::kGroupUnknown) { |
| /* Nothing to do */ |
| return; |
| } |
| |
| auto group_id_to_close = active_group_id_; |
| active_group_id_ = bluetooth::groups::kGroupUnknown; |
| |
| if (alarm_is_scheduled(suspend_timeout_)) alarm_cancel(suspend_timeout_); |
| |
| StopAudio(); |
| ClientAudioIntefraceRelease(); |
| |
| GroupStop(group_id_to_close); |
| callbacks_->OnGroupStatus(group_id_to_close, GroupStatus::INACTIVE); |
| 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 frequent context. |
| * If reconfiguration is not needed it means, context type is not supported. |
| * If most frequest scenario is not supported, try to find first supported. |
| */ |
| LeAudioContextType default_context_type = LeAudioContextType::UNSPECIFIED; |
| if (group->IsContextSupported(LeAudioContextType::MEDIA)) { |
| default_context_type = LeAudioContextType::MEDIA; |
| } else { |
| for (LeAudioContextType context_type : |
| le_audio::types::kLeAudioContextAllTypesArray) { |
| if (group->IsContextSupported(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(WARNING) << __func__ << ", unsupported device configurations"; |
| return; |
| } |
| |
| if (active_group_id_ == bluetooth::groups::kGroupUnknown) { |
| /* Expose audio sessions if there was no previous active group */ |
| StartAudioSession(group, ¤t_source_codec_config, |
| ¤t_sink_codec_config); |
| } else { |
| /* In case there was an active group. Stop the stream */ |
| GroupStop(active_group_id_); |
| callbacks_->OnGroupStatus(active_group_id_, GroupStatus::INACTIVE); |
| } |
| |
| active_group_id_ = group_id; |
| callbacks_->OnGroupStatus(active_group_id_, GroupStatus::ACTIVE); |
| } |
| |
| void RemoveDevice(const RawAddress& address) override { |
| LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address); |
| if (!leAudioDevice) { |
| return; |
| } |
| |
| if (leAudioDevice->conn_id_ != GATT_INVALID_CONN_ID) { |
| Disconnect(address); |
| leAudioDevice->SetConnectionState(DeviceConnectState::REMOVING); |
| return; |
| } |
| |
| /* 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 { |
| LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address); |
| if (!leAudioDevice) { |
| leAudioDevices_.Add(address, DeviceConnectState::CONNECTING_BY_USER); |
| |
| } else { |
| 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, true, 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; |
| } |
| |
| leAudioDevice->SetSupportedContexts( |
| AudioContexts(sink_supported_context_types), |
| AudioContexts(source_supported_context_types)); |
| |
| /* Use same as or supported ones for now. */ |
| leAudioDevice->SetAvailableContexts( |
| AudioContexts(sink_supported_context_types), |
| AudioContexts(source_supported_context_types)); |
| |
| if (!DeserializeHandles(leAudioDevice, handles)) { |
| LOG_WARN("Could not load Handles"); |
| } |
| |
| if (!DeserializeSinkPacs(leAudioDevice, sink_pacs)) { |
| LOG_WARN("Could not load sink pacs"); |
| } |
| |
| if (!DeserializeSourcePacs(leAudioDevice, source_pacs)) { |
| LOG_WARN("Could not load source pacs"); |
| } |
| |
| if (!DeserializeAses(leAudioDevice, ases)) { |
| LOG_WARN("Could not load ases"); |
| } |
| |
| if (autoconnect) { |
| leAudioDevice->SetConnectionState( |
| DeviceConnectState::CONNECTING_AUTOCONNECT); |
| leAudioDevice->autoconnect_flag_ = true; |
| BTA_GATTC_Open(gatt_if_, address, false, false); |
| } |
| } |
| |
| 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 BackgroundConnectIfGroupConnected(LeAudioDevice* leAudioDevice) { |
| DLOG(INFO) << __func__ << leAudioDevice->address_; |
| auto group = aseGroups_.FindById(leAudioDevice->group_id_); |
| if (!group) { |
| DLOG(INFO) << __func__ << " Device is not yet part of the group. "; |
| return; |
| } |
| |
| if (!group->IsAnyDeviceConnected()) { |
| DLOG(INFO) << __func__ << " group: " << leAudioDevice->group_id_ |
| << " is not connected"; |
| return; |
| } |
| |
| DLOG(INFO) << __func__ << "Add " << leAudioDevice->address_ |
| << " to background connect to connected group: " |
| << leAudioDevice->group_id_; |
| |
| leAudioDevice->SetConnectionState( |
| DeviceConnectState::CONNECTING_AUTOCONNECT); |
| BTA_GATTC_Open(gatt_if_, leAudioDevice->address_, false, false); |
| } |
| |
| void Disconnect(const RawAddress& address) override { |
| LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address); |
| |
| if (!leAudioDevice) { |
| LOG(ERROR) << __func__ << ", leAudioDevice not connected (" << address |
| << ")"; |
| return; |
| } |
| |
| /* cancel pending direct connect */ |
| if (leAudioDevice->GetConnectionState() == |
| DeviceConnectState::CONNECTING_BY_USER) { |
| BTA_GATTC_CancelOpen(gatt_if_, address, true); |
| } |
| |
| /* Removes all registrations for connection */ |
| BTA_GATTC_CancelOpen(0, address, false); |
| |
| if (leAudioDevice->conn_id_ != GATT_INVALID_CONN_ID) { |
| /* User is disconnecting the device, we shall remove the autoconnect flag |
| */ |
| btif_storage_set_leaudio_autoconnect(address, false); |
| leAudioDevice->autoconnect_flag_ = false; |
| |
| auto group = aseGroups_.FindById(leAudioDevice->group_id_); |
| if (group && |
| group->GetState() == |
| le_audio::types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { |
| leAudioDevice->closing_stream_for_disconnection_ = true; |
| groupStateMachine_->StopStream(group); |
| return; |
| } |
| DisconnectDevice(leAudioDevice); |
| return; |
| } |
| |
| /* If this is a device which is a part of the group which is connected, |
| * lets start backgroup connect |
| */ |
| BackgroundConnectIfGroupConnected(leAudioDevice); |
| } |
| |
| void DisconnectDevice(LeAudioDevice* leAudioDevice, |
| bool acl_force_disconnect = false) { |
| if (leAudioDevice->conn_id_ == GATT_INVALID_CONN_ID) { |
| return; |
| } |
| |
| leAudioDevice->SetConnectionState(DeviceConnectState::DISCONNECTING); |
| |
| if (acl_force_disconnect) { |
| leAudioDevice->DisconnectAcl(); |
| return; |
| } |
| |
| BtaGattQueue::Clean(leAudioDevice->conn_id_); |
| BTA_GATTC_Close(leAudioDevice->conn_id_); |
| leAudioDevice->conn_id_ = GATT_INVALID_CONN_ID; |
| leAudioDevice->mtu_ = 0; |
| } |
| |
| 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); |
| |
| if (ase) { |
| LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_); |
| 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); |
| |
| /* Update supported context types including internal capabilities */ |
| LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_); |
| |
| /* Available context map should be considered to be updated in response to |
| * PACs update. |
| * Read of available context during initial attribute discovery. |
| * Group would be assigned once service search is completed. |
| */ |
| if (group && group->UpdateAudioContextTypeAvailability( |
| leAudioDevice->GetAvailableContexts())) { |
| callbacks_->OnAudioConf(group->audio_directions_, group->group_id_, |
| group->snk_audio_locations_.to_ulong(), |
| group->src_audio_locations_.to_ulong(), |
| group->GetAvailableContexts().value()); |
| } |
| 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); |
| |
| /* Update supported context types including internal capabilities */ |
| LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_); |
| |
| /* Available context map should be considered to be updated in response to |
| * PACs update. |
| * Read of available context during initial attribute discovery. |
| * Group would be assigned once service search is completed. |
| */ |
| if (group && group->UpdateAudioContextTypeAvailability( |
| leAudioDevice->GetAvailableContexts())) { |
| callbacks_->OnAudioConf(group->audio_directions_, group->group_id_, |
| group->snk_audio_locations_.to_ulong(), |
| group->src_audio_locations_.to_ulong(), |
| group->GetAvailableContexts().value()); |
| } |
| |
| 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; |
| |
| LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_); |
| 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()); |
| } |
| |
| /* Read of source audio locations during initial attribute discovery. |
| * Group would be assigned once service search is completed. |
| */ |
| if (!group) return; |
| |
| bool group_conf_changed = group->ReloadAudioLocations(); |
| group_conf_changed |= group->ReloadAudioDirections(); |
| |
| if (group_conf_changed) { |
| callbacks_->OnAudioConf(group->audio_directions_, group->group_id_, |
| group->snk_audio_locations_.to_ulong(), |
| group->src_audio_locations_.to_ulong(), |
| group->GetAvailableContexts().value()); |
| } |
| } 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; |
| |
| LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_); |
| |
| if (notify) { |
| btif_storage_set_leaudio_audio_location( |
| leAudioDevice->address_, |
| leAudioDevice->snk_audio_locations_.to_ulong(), |
| leAudioDevice->src_audio_locations_.to_ulong()); |
| } |
| |
| /* Read of source audio locations during initial attribute discovery. |
| * Group would be assigned once service search is completed. |
| */ |
| if (!group) return; |
| |
| bool group_conf_changed = group->ReloadAudioLocations(); |
| group_conf_changed |= group->ReloadAudioDirections(); |
| |
| if (group_conf_changed) { |
| callbacks_->OnAudioConf(group->audio_directions_, group->group_id_, |
| group->snk_audio_locations_.to_ulong(), |
| group->src_audio_locations_.to_ulong(), |
| group->GetAvailableContexts().value()); |
| } |
| } else if (hdl == leAudioDevice->audio_avail_hdls_.val_hdl) { |
| le_audio::client_parser::pacs::acs_available_audio_contexts |
| avail_audio_contexts; |
| le_audio::client_parser::pacs::ParseAvailableAudioContexts( |
| avail_audio_contexts, len, value); |
| |
| auto updated_avail_contexts = leAudioDevice->SetAvailableContexts( |
| avail_audio_contexts.snk_avail_cont, |
| avail_audio_contexts.src_avail_cont); |
| |
| if (updated_avail_contexts.any()) { |
| /* Update scenario map considering changed available context types */ |
| LeAudioDeviceGroup* group = |
| aseGroups_.FindById(leAudioDevice->group_id_); |
| /* Read of available context during initial attribute discovery. |
| * Group would be assigned once service search is completed. |
| */ |
| if (group) { |
| /* Update of available context may happen during state transition |
| * or while streaming. Don't bother current transition or streaming |
| * process. Update configuration once group became idle. |
| */ |
| if (group->IsInTransition() || |
| (group->GetState() == |
| AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING)) { |
| group->SetPendingAvailableContextsChange(updated_avail_contexts); |
| return; |
| } |
| |
| auto contexts_updated = |
| group->UpdateAudioContextTypeAvailability(updated_avail_contexts); |
| if (contexts_updated) { |
| callbacks_->OnAudioConf(group->audio_directions_, group->group_id_, |
| group->snk_audio_locations_.to_ulong(), |
| group->src_audio_locations_.to_ulong(), |
| group->GetAvailableContexts().value()); |
| } |
| } |
| } |
| } else if (hdl == leAudioDevice->audio_supp_cont_hdls_.val_hdl) { |
| le_audio::client_parser::pacs::acs_supported_audio_contexts |
| supp_audio_contexts; |
| le_audio::client_parser::pacs::ParseSupportedAudioContexts( |
| supp_audio_contexts, len, value); |
| /* Just store if for now */ |
| leAudioDevice->SetSupportedContexts(supp_audio_contexts.snk_supp_cont, |
| supp_audio_contexts.src_supp_cont); |
| |
| btif_storage_set_leaudio_supported_context_types( |
| leAudioDevice->address_, supp_audio_contexts.snk_supp_cont.value(), |
| supp_audio_contexts.src_supp_cont.value()); |
| |
| } else if (hdl == leAudioDevice->ctp_hdls_.val_hdl) { |
| auto ntf = |
| std::make_unique<struct le_audio::client_parser::ascs::ctp_ntf>(); |
| |
| if (ParseAseCtpNotification(*ntf, len, value)) |
| ControlPointNotificationHandler(*ntf); |
| } 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); |
| } |
| |
| 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); |
| |
| if (!leAudioDevice) return; |
| |
| if (status != GATT_SUCCESS) { |
| /* autoconnect connection failed, that's ok */ |
| if (leAudioDevice->GetConnectionState() == |
| DeviceConnectState::CONNECTING_AUTOCONNECT) { |
| leAudioDevice->SetConnectionState(DeviceConnectState::DISCONNECTED); |
| return; |
| } |
| |
| leAudioDevice->SetConnectionState(DeviceConnectState::DISCONNECTED); |
| |
| LOG(ERROR) << "Failed to connect to LeAudio leAudioDevice, status: " |
| << +status; |
| callbacks_->OnConnectionState(ConnectionState::DISCONNECTED, address); |
| le_audio::MetricsCollector::Get()->OnConnectionStateChanged( |
| leAudioDevice->group_id_, address, ConnectionState::CONNECTED, |
| le_audio::ConnectionStatus::FAILED); |
| return; |
| } |
| |
| if (controller_get_interface()->supports_ble_2m_phy()) { |
| LOG(INFO) << 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); |
| } |
| |
| leAudioDevice->conn_id_ = conn_id; |
| leAudioDevice->mtu_ = mtu; |
| |
| 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; |
| } |
| |
| if (BTM_IsLinkKeyKnown(address, BT_TRANSPORT_LE)) { |
| int result = BTM_SetEncryption(address, BT_TRANSPORT_LE, nullptr, nullptr, |
| BTM_BLE_SEC_ENCRYPT); |
| |
| LOG(INFO) << __func__ |
| << "Encryption required. Request result: " << result; |
| return; |
| } |
| |
| LOG(ERROR) << __func__ << " Encryption error"; |
| le_audio::MetricsCollector::Get()->OnConnectionStateChanged( |
| leAudioDevice->group_id_, address, ConnectionState::CONNECTED, |
| le_audio::ConnectionStatus::FAILED); |
| } |
| |
| void RegisterKnownNotifications(LeAudioDevice* leAudioDevice) { |
| LOG(INFO) << __func__ << " device: " << 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)); |
| } |
| for (auto pac_tuple : leAudioDevice->src_pacs_) { |
| subscribe_for_notification(leAudioDevice->conn_id_, |
| leAudioDevice->address_, |
| std::get<0>(pac_tuple)); |
| } |
| |
| if (leAudioDevice->snk_audio_locations_hdls_.val_hdl != 0) |
| subscribe_for_notification(leAudioDevice->conn_id_, |
| leAudioDevice->address_, |
| leAudioDevice->snk_audio_locations_hdls_); |
| if (leAudioDevice->src_audio_locations_hdls_.val_hdl != 0) |
| subscribe_for_notification(leAudioDevice->conn_id_, |
| leAudioDevice->address_, |
| leAudioDevice->src_audio_locations_hdls_); |
| |
| if (leAudioDevice->audio_avail_hdls_.val_hdl != 0) |
| subscribe_for_notification(leAudioDevice->conn_id_, |
| leAudioDevice->address_, |
| leAudioDevice->audio_avail_hdls_); |
| |
| if (leAudioDevice->audio_supp_cont_hdls_.val_hdl != 0) |
| subscribe_for_notification(leAudioDevice->conn_id_, |
| leAudioDevice->address_, |
| leAudioDevice->audio_supp_cont_hdls_); |
| |
| for (struct ase& ase : leAudioDevice->ases_) |
| subscribe_for_notification(leAudioDevice->conn_id_, |
| leAudioDevice->address_, ase.hdls); |
| |
| subscribe_for_notification(leAudioDevice->conn_id_, leAudioDevice->address_, |
| leAudioDevice->ctp_hdls_); |
| } |
| |
| void changeMtuIfPossible(LeAudioDevice* leAudioDevice) { |
| if (leAudioDevice->mtu_ == GATT_DEF_BLE_MTU_SIZE) { |
| LOG(INFO) << __func__ << ", Configure MTU"; |
| BtaGattQueue::ConfigureMtu(leAudioDevice->conn_id_, GATT_MAX_MTU_SIZE); |
| } |
| } |
| |
| void OnEncryptionComplete(const RawAddress& address, uint8_t status) { |
| LOG(INFO) << __func__ << " " << address << "status: " << int{status}; |
| |
| LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address); |
| if (leAudioDevice == NULL) { |
| LOG(WARNING) << "Skipping unknown device" << address; |
| 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); |
| |
| /* If we know services, register for notifications */ |
| if (leAudioDevice->known_service_handles_) |
| RegisterKnownNotifications(leAudioDevice); |
| |
| leAudioDevice->encrypted_ = true; |
| |
| /* 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 OnGattDisconnected(uint16_t conn_id, tGATT_IF client_if, |
| RawAddress address, tGATT_DISCONN_REASON reason) { |
| LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address); |
| |
| if (!leAudioDevice) { |
| LOG(ERROR) << ", skipping unknown leAudioDevice, address: " << address; |
| return; |
| } |
| |
| BtaGattQueue::Clean(leAudioDevice->conn_id_); |
| LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_); |
| |
| groupStateMachine_->ProcessHciNotifAclDisconnected(group, leAudioDevice); |
| |
| 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; |
| |
| 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; |
| } |
| /* Attempt background re-connect if disconnect was not intended locally */ |
| if (reason != GATT_CONN_TERMINATE_LOCAL_HOST) { |
| leAudioDevice->SetConnectionState( |
| DeviceConnectState::CONNECTING_AUTOCONNECT); |
| BTA_GATTC_Open(gatt_if_, address, false, false); |
| } else { |
| leAudioDevice->SetConnectionState(DeviceConnectState::DISCONNECTED); |
| } |
| } |
| |
| bool subscribe_for_notification( |
| uint16_t conn_id, const RawAddress& address, |
| struct le_audio::types::hdl_pair handle_pair) { |
| 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", conn_id); |
| if (BTA_GATTC_RegisterForNotifications(gatt_if_, address, handle) != |
| GATT_SUCCESS) { |
| LOG(ERROR) << __func__ << ", cannot register for notification: " |
| << static_cast<int>(handle); |
| return false; |
| } |
| |
| 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 OnServiceChangeEvent(const RawAddress& address) { |
| LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address); |
| if (!leAudioDevice) { |
| DLOG(ERROR) << __func__ |
| << ", skipping unknown leAudioDevice, address: " << address; |
| return; |
| } |
| |
| LOG(INFO) << __func__ << ": address=" << address; |
| leAudioDevice->known_service_handles_ = false; |
| leAudioDevice->csis_member_ = false; |
| BtaGattQueue::Clean(leAudioDevice->conn_id_); |
| DeregisterNotifications(leAudioDevice); |
| } |
| |
| 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; |
| } |
| |
| leAudioDevice->mtu_ = mtu; |
| } |
| |
| void OnGattServiceDiscoveryDone(const RawAddress& address) { |
| LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address); |
| if (!leAudioDevice) { |
| DLOG(ERROR) << __func__ |
| << ", skipping unknown leAudioDevice, address: " << address; |
| 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); |
| } |
| /* 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; |
| } |
| |
| 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: " |
| << loghex(tmp.handle); |
| pac_svc = &tmp; |
| } else if (tmp.uuid == le_audio::uuid::kAudioStreamControlServiceUuid) { |
| LOG(INFO) << "Found Audio Stream Endpoint service, handle: " |
| << loghex(tmp.handle); |
| ase_svc = &tmp; |
| } else if (tmp.uuid == bluetooth::csis::kCsisServiceUuid) { |
| LOG(INFO) << "Found CSIS service, handle: " << loghex(tmp.handle) |
| << " is primary? " << tmp.is_primary; |
| 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: " << loghex(tmp.handle); |
| |
| /* 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: %04x", |
| tmp.handle); |
| 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) { |
| LOG(ERROR) << "No mandatory le audio services found"; |
| |
| DisconnectDevice(leAudioDevice); |
| 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(ERROR) << __func__ << ", snk pac char doesn't have ccc"; |
| |
| DisconnectDevice(leAudioDevice); |
| return; |
| } |
| |
| if (!subscribe_for_notification(conn_id, leAudioDevice->address_, |
| hdl_pair)) { |
| DisconnectDevice(leAudioDevice); |
| 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: " |
| << loghex(charac.value_handle) |
| << ", ccc handle: " << loghex(hdl_pair.ccc_hdl); |
| } 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(ERROR) << __func__ << ", src pac char doesn't have ccc"; |
| |
| DisconnectDevice(leAudioDevice); |
| return; |
| } |
| |
| if (!subscribe_for_notification(conn_id, leAudioDevice->address_, |
| hdl_pair)) { |
| DisconnectDevice(leAudioDevice); |
| 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: " |
| << loghex(charac.value_handle) |
| << ", ccc handle: " << loghex(hdl_pair.ccc_hdl); |
| } 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) << __func__ |
| << ", 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_)) { |
| DisconnectDevice(leAudioDevice); |
| 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: " |
| << loghex(charac.value_handle) << ", ccc handle: " |
| << loghex(leAudioDevice->snk_audio_locations_hdls_.ccc_hdl); |
| } 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) << __func__ |
| << ", snk 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_)) { |
| DisconnectDevice(leAudioDevice); |
| 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: " |
| << loghex(charac.value_handle) << ", ccc handle: " |
| << loghex(leAudioDevice->src_audio_locations_hdls_.ccc_hdl); |
| } 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) { |
| LOG(ERROR) << __func__ << ", audio avails char doesn't have ccc"; |
| |
| DisconnectDevice(leAudioDevice); |
| return; |
| } |
| |
| if (!subscribe_for_notification(conn_id, leAudioDevice->address_, |
| leAudioDevice->audio_avail_hdls_)) { |
| DisconnectDevice(leAudioDevice); |
| return; |
| } |
| |
| /* Obtain initial state */ |
| BtaGattQueue::ReadCharacteristic( |
| conn_id, leAudioDevice->audio_avail_hdls_.val_hdl, |
| OnGattReadRspStatic, NULL); |
| |
| LOG(INFO) << "Found Audio Availability Context characteristic, handle: " |
| << loghex(charac.value_handle) << ", ccc handle: " |
| << loghex(leAudioDevice->audio_avail_hdls_.ccc_hdl); |
| } 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) << __func__ << ", audio avails 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_)) { |
| DisconnectDevice(leAudioDevice); |
| 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: " |
| << loghex(charac.value_handle) << ", ccc handle: " |
| << loghex(leAudioDevice->audio_supp_cont_hdls_.ccc_hdl); |
| } |
| } |
| |
| /* 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) { |
| LOG(ERROR) << __func__ << ", audio avails char doesn't have ccc"; |
| |
| DisconnectDevice(leAudioDevice); |
| return; |
| } |
| struct le_audio::types::hdl_pair hdls(charac.value_handle, ccc_handle); |
| if (!subscribe_for_notification(conn_id, leAudioDevice->address_, |
| hdls)) { |
| DisconnectDevice(leAudioDevice); |
| 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: " |
| << loghex(charac.value_handle) |
| << ", ccc handle: " << loghex(ccc_handle) |
| << ", direction: " << direction; |
| } 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) { |
| LOG(ERROR) << __func__ << ", ase ctp doesn't have ccc"; |
| |
| DisconnectDevice(leAudioDevice); |
| return; |
| } |
| |
| if (!subscribe_for_notification(conn_id, leAudioDevice->address_, |
| leAudioDevice->ctp_hdls_)) { |
| DisconnectDevice(leAudioDevice); |
| return; |
| } |
| |
| LOG(INFO) << "Found ASE Control Point characteristic, handle: " |
| << loghex(charac.value_handle) << ", ccc handle: " |
| << loghex(leAudioDevice->ctp_hdls_.ccc_hdl); |
| } |
| } |
| |
| 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: %04x", |
| leAudioDevice->tmap_role_hdl_); |
| } |
| } |
| } |
| |
| leAudioDevice->known_service_handles_ = true; |
| btif_storage_leaudio_update_handles_bin(leAudioDevice->address_); |
| |
| leAudioDevice->notify_connected_after_read_ = true; |
| |
| /* 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) << __func__ << " waiting for CSIS to create group for device " |
| << leAudioDevice->address_; |
| return; |
| } |
| |
| /* 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_SUCCESS) { |
| LOG(INFO) << __func__ |
| << ", successfully registered on ccc: " << loghex(hdl); |
| |
| 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) << __func__ |
| << ", Failed to register for indications: " << loghex(hdl) |
| << ", status: " << loghex((int)(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) << __func__ |
| << ", unknown ccc handle: " << static_cast<int>(hdl); |
| 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; |
| } |
| |
| LOG_INFO("Attaching to group: %d", leAudioDevice->group_id_); |
| |
| /* Restore configuration */ |
| LeAudioDeviceGroup* group = aseGroups_.FindById(active_group_id_); |
| 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; |
| } |
| |
| auto num_of_devices = |
| get_num_of_devices_in_configuration(stream_conf->conf); |
| |
| if (num_of_devices < group->NumOfConnected() && |
| !group->IsConfigurationSupported(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; |
| } |
| |
| if (!groupStateMachine_->AttachToStream(group, leAudioDevice)) { |
| 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, |
| base::Unretained(this), addr), |
| #if BASE_VER < 931007 |
| base::TimeDelta::FromMilliseconds(kDeviceAttachDelayMs) |
| #else |
| base::Milliseconds(kDeviceAttachDelayMs) |
| #endif |
| ); |
| } |
| |
| void connectionReady(LeAudioDevice* leAudioDevice) { |
| LOG_DEBUG("%s, %s", leAudioDevice->address_.ToString().c_str(), |
| bluetooth::common::ToString(leAudioDevice->GetConnectionState()) |
| .c_str()); |
| 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_); |
| UpdateContextAndLocations(group, leAudioDevice); |
| AttachToStreamingGroupIfNeeded(leAudioDevice); |
| } |
| } |
| |
| 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 != AudioStreamDataPathState::DATA_PATH_ESTABLISHED) |
| 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, |
| struct le_audio::stream_configuration* stream_conf) { |
| uint16_t byte_count = stream_conf->sink_octets_per_codec_frame; |
| uint16_t left_cis_handle = 0; |
| uint16_t right_cis_handle = 0; |
| uint16_t number_of_required_samples_per_channel; |
| |
| int dt_us = current_source_codec_config.data_interval_us; |
| int af_hz = audio_framework_source_config.sample_rate; |
| number_of_required_samples_per_channel = lc3_frame_samples(dt_us, af_hz); |
| |
| lc3_pcm_format bits_per_sample = |
| bits_to_lc3_bits(audio_framework_source_config.bits_per_sample); |
| uint8_t bytes_per_sample = |
| bits_to_bytes_per_sample(audio_framework_source_config.bits_per_sample); |
| |
| for (auto [cis_handle, audio_location] : stream_conf->sink_streams) { |
| 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; |
| } |
| |
| 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; |
| } |
| |
| std::vector<uint8_t> chan_left_enc(byte_count, 0); |
| std::vector<uint8_t> chan_right_enc(byte_count, 0); |
| |
| bool mono = (left_cis_handle == 0) || (right_cis_handle == 0); |
| |
| if (!mono) { |
| lc3_encode(lc3_encoder_left, bits_per_sample, data.data(), 2, |
| chan_left_enc.size(), chan_left_enc.data()); |
| lc3_encode(lc3_encoder_right, bits_per_sample, |
| data.data() + bytes_per_sample, 2, chan_right_enc.size(), |
| chan_right_enc.data()); |
| } else { |
| std::vector<uint8_t> mono = mono_blend( |
| data, bytes_per_sample, number_of_required_samples_per_channel); |
| if (left_cis_handle) { |
| lc3_encode(lc3_encoder_left, bits_per_sample, mono.data(), 1, |
| chan_left_enc.size(), chan_left_enc.data()); |
| } |
| |
| if (right_cis_handle) { |
| lc3_encode(lc3_encoder_right, bits_per_sample, mono.data(), 1, |
| chan_right_enc.size(), chan_right_enc.data()); |
| } |
| } |
| |
| 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, chan_left_enc.data(), chan_left_enc.size()); |
| |
| if (right_cis_handle) |
| IsoManager::GetInstance()->SendIsoData( |
| right_cis_handle, chan_right_enc.data(), chan_right_enc.size()); |
| } |
| |
| void PrepareAndSendToSingleCis( |
| const std::vector<uint8_t>& data, |
| struct le_audio::stream_configuration* stream_conf) { |
| int num_channels = stream_conf->sink_num_of_channels; |
| uint16_t byte_count = stream_conf->sink_octets_per_codec_frame; |
| auto cis_handle = stream_conf->sink_streams.front().first; |
| uint16_t number_of_required_samples_per_channel; |
| |
| int dt_us = current_source_codec_config.data_interval_us; |
| int af_hz = audio_framework_source_config.sample_rate; |
| number_of_required_samples_per_channel = lc3_frame_samples(dt_us, af_hz); |
| lc3_pcm_format bits_per_sample = |
| bits_to_lc3_bits(audio_framework_source_config.bits_per_sample); |
| uint8_t bytes_per_sample = |
| bits_to_bytes_per_sample(audio_framework_source_config.bits_per_sample); |
| |
| if ((int)data.size() < (2 /* bytes per sample */ * num_channels * |
| number_of_required_samples_per_channel)) { |
| LOG(ERROR) << __func__ << "Missing samples"; |
| return; |
| } |
| std::vector<uint8_t> chan_encoded(num_channels * byte_count, 0); |
| |
| if (num_channels == 1) { |
| /* 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); |
| |
| auto err = lc3_encode(lc3_encoder_left, bits_per_sample, mono.data(), 1, |
| byte_count, chan_encoded.data()); |
| |
| if (err < 0) { |
| LOG(ERROR) << " error while encoding, error code: " << +err; |
| } |
| } else { |
| lc3_encode(lc3_encoder_left, bits_per_sample, (const int16_t*)data.data(), |
| 2, byte_count, chan_encoded.data()); |
| lc3_encode(lc3_encoder_right, bits_per_sample, |
| (const int16_t*)data.data() + 1, 2, byte_count, |
| chan_encoded.data() + byte_count); |
| } |
| |
| /* Send data to the controller */ |
| IsoManager::GetInstance()->SendIsoData(cis_handle, chan_encoded.data(), |
| chan_encoded.size()); |
| } |
| |
| 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->sink_streams.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.sink_num_of_devices > 2) || |
| (stream_conf.sink_num_of_devices == 0) || |
| stream_conf.sink_streams.empty()) { |
| LOG(ERROR) << __func__ << " Stream configufation is not valid."; |
| return; |
| } |
| |
| if (stream_conf.sink_num_of_devices == 2) { |
| PrepareAndSendToTwoCises(data, &stream_conf); |
| } else if (stream_conf.sink_streams.size() == 2) { |
| /* Streaming to one device but 2 CISes */ |
| PrepareAndSendToTwoCises(data, &stream_conf); |
| } else { |
| PrepareAndSendToSingleCis(data, &stream_conf); |
| } |
| } |
| |
| void CleanCachedMicrophoneData() { |
| cached_channel_data_.clear(); |
| cached_channel_timestamp_ = 0; |
| cached_channel_is_left_ = false; |
| } |
| |
| /* 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; |
| } |
| |
| auto stream_conf = group->stream_conf; |
| |
| uint16_t left_cis_handle = 0; |
| uint16_t right_cis_handle = 0; |
| for (auto [cis_handle, audio_location] : stream_conf.source_streams) { |
| 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; |
| } |
| } |
| |
| bool is_left = true; |
| if (cis_conn_hdl == left_cis_handle) { |
| is_left = true; |
| } else if (cis_conn_hdl == right_cis_handle) { |
| is_left = false; |
| } else { |
| LOG_ERROR("Received data for unknown handle: %04x", cis_conn_hdl); |
| return; |
| } |
| |
| uint16_t required_for_channel_byte_count = |
| stream_conf.source_octets_per_codec_frame; |
| |
| int dt_us = current_sink_codec_config.data_interval_us; |
| int af_hz = audio_framework_sink_config.sample_rate; |
| lc3_pcm_format bits_per_sample = |
| bits_to_lc3_bits(audio_framework_sink_config.bits_per_sample); |
| |
| int pcm_size; |
| if (dt_us == 10000) { |
| if (af_hz == 44100) |
| pcm_size = 480; |
| else |
| pcm_size = af_hz / 100; |
| } else if (dt_us == 7500) { |
| if (af_hz == 44100) |
| pcm_size = 360; |
| else |
| pcm_size = (af_hz * 3) / 400; |
| } else { |
| LOG(ERROR) << "BAD dt_us: " << dt_us; |
| return; |
| } |
| |
| std::vector<int16_t> pcm_data_decoded(pcm_size, 0); |
| |
| int err = 0; |
| |
| if (required_for_channel_byte_count != size) { |
| LOG(INFO) << "Insufficient data for decoding and send, required: " |
| << int(required_for_channel_byte_count) |
| << ", received: " << int(size) << ", will do PLC"; |
| size = 0; |
| data = nullptr; |
| } |
| |
| lc3_decoder_t decoder_to_use = |
| is_left ? lc3_decoder_left : lc3_decoder_right; |
| |
| err = lc3_decode(decoder_to_use, data, size, bits_per_sample, |
| pcm_data_decoded.data(), 1 /* pitch */); |
| |
| if (err < 0) { |
| LOG(ERROR) << " bad decoding parameters: " << static_cast<int>(err); |
| return; |
| } |
| |
| /* AF == Audio Framework */ |
| bool af_is_stereo = (audio_framework_sink_config.num_channels == 2); |
| |
| if (!left_cis_handle || !right_cis_handle) { |
| /* mono or just one device connected */ |
| SendAudioDataToAF(false /* bt_got_stereo */, af_is_stereo, |
| &pcm_data_decoded, nullptr); |
| return; |
| } |
| /* both devices are connected */ |
| |
| if (cached_channel_timestamp_ == 0 && cached_channel_data_.empty()) { |
| /* First packet received, cache it. We need both channel data to send it |
| * to AF. */ |
| cached_channel_data_ = pcm_data_decoded; |
| cached_channel_timestamp_ = timestamp; |
| cached_channel_is_left_ = is_left; |
| return; |
| } |
| |
| /* We received either data for the other audio channel, or another |
| * packet for same channel */ |
| |
| if (cached_channel_is_left_ != is_left) { |
| /* It's data for the 2nd channel */ |
| if (timestamp == cached_channel_timestamp_) { |
| /* Ready to mix data and send out to AF */ |
| if (is_left) { |
| SendAudioDataToAF(true /* bt_got_stereo */, af_is_stereo, |
| &cached_channel_data_, &pcm_data_decoded); |
| } else { |
| SendAudioDataToAF(true /* bt_got_stereo */, af_is_stereo, |
| &pcm_data_decoded, &cached_channel_data_); |
| } |
| |
| 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 */ |
| |
| if (cached_channel_is_left_) { |
| SendAudioDataToAF(false /* bt_got_stereo */, af_is_stereo, |
| &cached_channel_data_, nullptr); |
| } else { |
| SendAudioDataToAF(false /* bt_got_stereo */, af_is_stereo, nullptr, |
| &cached_channel_data_); |
| } |
| |
| cached_channel_data_ = pcm_data_decoded; |
| cached_channel_timestamp_ = timestamp; |
| cached_channel_is_left_ = is_left; |
| return; |
| } |
| |
| /* Data for same channel received. 2nd channel is down/not sending |
| * data */ |
| |
| /* Send the cached data out */ |
| if (cached_channel_is_left_) { |
| SendAudioDataToAF(false /* bt_got_stereo */, af_is_stereo, |
| &cached_channel_data_, nullptr); |
| } else { |
| SendAudioDataToAF(false /* bt_got_stereo */, af_is_stereo, nullptr, |
| &cached_channel_data_); |
| } |
| |
| /* Cache the data in case 2nd channel connects */ |
| cached_channel_data_ = pcm_data_decoded; |
| cached_channel_timestamp_ = timestamp; |
| cached_channel_is_left_ = is_left; |
| } |
| |
| void SendAudioDataToAF(bool bt_got_stereo, bool af_is_stereo, |
| std::vector<int16_t>* left, |
| std::vector<int16_t>* right) { |
| uint16_t to_write = 0; |
| uint16_t written = 0; |
| 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"; |
| } |
| |
| bool 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"; |
| return false; |
| } |
| |
| LOG_DEBUG("Sink stream config (#%d):\n", |
| static_cast<int>(stream_conf->sink_streams.size())); |
| for (auto stream : stream_conf->sink_streams) { |
| 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->source_streams.size())); |
| for (auto stream : stream_conf->source_streams) { |
| 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 (lc3_encoder_left_mem) { |
| LOG(WARNING) |
| << " The encoder instance should have been already released."; |
| free(lc3_encoder_left_mem); |
| lc3_encoder_left_mem = nullptr; |
| free(lc3_encoder_right_mem); |
| lc3_encoder_right_mem = nullptr; |
| } |
| int dt_us = current_source_codec_config.data_interval_us; |
| int sr_hz = current_source_codec_config.sample_rate; |
| int af_hz = audio_framework_source_config.sample_rate; |
| unsigned enc_size = lc3_encoder_size(dt_us, af_hz); |
| |
| lc3_encoder_left_mem = malloc(enc_size); |
| lc3_encoder_right_mem = malloc(enc_size); |
| |
| lc3_encoder_left = |
| lc3_setup_encoder(dt_us, sr_hz, af_hz, lc3_encoder_left_mem); |
| lc3_encoder_right = |
| lc3_setup_encoder(dt_us, sr_hz, af_hz, lc3_encoder_right_mem); |
| } |
| |
| le_audio_source_hal_client_->UpdateRemoteDelay(remote_delay_ms); |
| le_audio_source_hal_client_->ConfirmStreamingRequest(); |
| audio_sender_state_ = AudioState::STARTED; |
| /* We update the target audio allocation before streamStarted that the |
| * offloder would know how to configure offloader encoder. We should check |
| * if we need to update the current |
| * allocation here as the target allocation and the current allocation is |
| * different */ |
| updateOffloaderIfNeeded(group); |
| |
| return true; |
| } |
| |
| const struct le_audio::stream_configuration* GetStreamSourceConfiguration( |
| LeAudioDeviceGroup* group) { |
| const struct le_audio::stream_configuration* stream_conf = |
| &group->stream_conf; |
| if (stream_conf->source_streams.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"; |
| return; |
| } |
| |
| uint16_t remote_delay_ms = |
| group->GetRemoteDelay(le_audio::types::kLeAudioDirectionSource); |
| |
| CleanCachedMicrophoneData(); |
| |
| if (CodecManager::GetInstance()->GetCodecLocation() == |
| le_audio::types::CodecLocation::HOST) { |
| if (lc3_decoder_left_mem) { |
| LOG(WARNING) |
| << " The decoder instance should have been already released."; |
| free(lc3_decoder_left_mem); |
| lc3_decoder_left_mem = nullptr; |
| free(lc3_decoder_right_mem); |
| lc3_decoder_right_mem = nullptr; |
| } |
| |
| int dt_us = current_sink_codec_config.data_interval_us; |
| int sr_hz = current_sink_codec_config.sample_rate; |
| int af_hz = audio_framework_sink_config.sample_rate; |
| unsigned dec_size = lc3_decoder_size(dt_us, af_hz); |
| lc3_decoder_left_mem = malloc(dec_size); |
| lc3_decoder_right_mem = malloc(dec_size); |
| |
| lc3_decoder_left = |
| lc3_setup_decoder(dt_us, sr_hz, af_hz, lc3_decoder_left_mem); |
| lc3_decoder_right = |
| lc3_setup_decoder(dt_us, sr_hz, af_hz, lc3_decoder_right_mem); |
| } |
| le_audio_sink_hal_client_->UpdateRemoteDelay(remote_delay_ms); |
| le_audio_sink_hal_client_->ConfirmStreamingRequest(); |
| audio_receiver_state_ = AudioState::STARTED; |
| /* We update the target audio allocation before streamStarted that the |
| * offloder would know how to configure offloader decoder. We should check |
| * if we need to update the current |
| * allocation here as the target allocation and the current allocation is |
| * different */ |
| updateOffloaderIfNeeded(group); |
| } |
| |
| void SuspendAudio(void) { |
| CancelStreamingRequest(); |
| |
| if (lc3_encoder_left_mem) { |
| free(lc3_encoder_left_mem); |
| lc3_encoder_left_mem = nullptr; |
| free(lc3_encoder_right_mem); |
| lc3_encoder_right_mem = nullptr; |
| } |
| |
| if (lc3_decoder_left_mem) { |
| free(lc3_decoder_left_mem); |
| lc3_decoder_left_mem = nullptr; |
| free(lc3_decoder_right_mem); |
| lc3_decoder_right_mem = nullptr; |
| } |
| } |
| |
| 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, " Active group: %d\n", active_group_id_); |
| dprintf(fd, " configuration: %s (0x%08hx)\n", |
| bluetooth::common::ToString(configuration_context_type_).c_str(), |
| configuration_context_type_); |
| dprintf(fd, " metadata context type mask: %s\n", |
| metadata_context_types_.to_string().c_str()); |
| dprintf(fd, " TBS state: %s\n", in_call_ ? " In call" : "No calls"); |
| 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); |
| } |
| |
| void Cleanup(base::Callback<void()> cleanupCb) { |
| if (alarm_is_scheduled(suspend_timeout_)) alarm_cancel(suspend_timeout_); |
| |
| if (active_group_id_ != bluetooth::groups::kGroupUnknown) { |
| /* Bluetooth turned off while streaming */ |
| StopAudio(); |
| ClientAudioIntefraceRelease(); |
| } |
| groupStateMachine_->Cleanup(); |
| aseGroups_.Cleanup(); |
| leAudioDevices_.Cleanup(); |
| if (gatt_if_) BTA_GATTC_AppDeregister(gatt_if_); |
| |
| std::move(cleanupCb).Run(); |
| } |
| |
| AudioReconfigurationResult UpdateConfigAndCheckIfReconfigurationIsNeeded( |
| int group_id, LeAudioContextType context_type) { |
| bool reconfiguration_needed = false; |
| bool sink_cfg_available = true; |
| bool source_cfg_available = true; |
| |
| 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; |
| } |
| |
| LOG_DEBUG( |
| " Context: %s Reconfigufation_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) { |
| 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; |
| } |
| |
| bool OnAudioResume(LeAudioDeviceGroup* group) { |
| if (group->GetTargetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { |
| return true; |
| } |
| return GroupStream(active_group_id_, configuration_context_type_, |
| metadata_context_types_); |
| } |
| |
| 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("IN: audio_receiver_state_: %s, audio_sender_state_: %s", |
| ToString(audio_receiver_state_).c_str(), |
| ToString(audio_sender_state_).c_str()); |
| |
| /* 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()); |
| } |
| |
| void OnLocalAudioSourceResume() { |
| LOG_INFO("IN: audio_receiver_state_: %s, audio_sender_state_: %s", |
| ToString(audio_receiver_state_).c_str(), |
| ToString(audio_sender_state_).c_str()); |
| /* 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 expected */ |
| if (!group->GetCodecConfigurationByDirection( |
| configuration_context_type_, |
| le_audio::types::kLeAudioDirectionSink)) { |
| LOG(ERROR) << __func__ << ", invalid resume request for context type: " |
| << ToHexString(configuration_context_type_); |
| le_audio_source_hal_client_->CancelStreamingRequest(); |
| 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*/ |
| le_audio_source_hal_client_->ConfirmStreamingRequest(); |
| break; |
| case AudioState::IDLE: |
| switch (audio_receiver_state_) { |
| case AudioState::IDLE: |
| /* Stream is not started. Try to do it.*/ |
| if (OnAudioResume(group)) { |
| audio_sender_state_ = AudioState::READY_TO_START; |
| } else { |
| le_audio_source_hal_client_->CancelStreamingRequest(); |
| } |
| break; |
| case AudioState::READY_TO_START: |
| case AudioState::STARTED: |
| audio_sender_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) { |
| StartSendingAudio(active_group_id_); |
| } |
| 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: |
| LOG_WARN( |
| " called in wrong state. \n audio_receiver_state: %s \n" |
| "audio_sender_state: %s \n", |
| ToString(audio_receiver_state_).c_str(), |
| ToString(audio_sender_state_).c_str()); |
| CancelStreamingRequest(); |
| break; |
| } |
| break; |
| case AudioState::READY_TO_START: |
| LOG_WARN( |
| " called in wrong state. \n audio_receiver_state: %s \n" |
| "audio_sender_state: %s \n", |
| ToString(audio_receiver_state_).c_str(), |
| ToString(audio_sender_state_).c_str()); |
| CancelStreamingRequest(); |
| 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 */ |
| audio_sender_state_ = AudioState::STARTED; |
| if (alarm_is_scheduled(suspend_timeout_)) |
| alarm_cancel(suspend_timeout_); |
| le_audio_source_hal_client_->ConfirmStreamingRequest(); |
| 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("IN: audio_receiver_state_: %s, audio_sender_state_: %s", |
| ToString(audio_receiver_state_).c_str(), |
| ToString(audio_sender_state_).c_str()); |
| |
| /* 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()); |
| } |
| |
| bool IsAudioSourceAvailableForCurrentConfiguration() { |
| if (configuration_context_type_ == LeAudioContextType::CONVERSATIONAL || |
| configuration_context_type_ == LeAudioContextType::VOICEASSISTANTS) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void OnLocalAudioSinkResume() { |
| LOG_INFO("IN: audio_receiver_state_: %s, audio_sender_state_: %s", |
| ToString(audio_receiver_state_).c_str(), |
| ToString(audio_sender_state_).c_str()); |
| |
| /* 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; |
| } |
| |
| /* Check if the device resume is expected */ |
| if (!group->GetCodecConfigurationByDirection( |
| configuration_context_type_, |
| le_audio::types::kLeAudioDirectionSource)) { |
| LOG(ERROR) << __func__ << ", invalid resume request for context type: " |
| << ToHexString(configuration_context_type_); |
| le_audio_sink_hal_client_->CancelStreamingRequest(); |
| 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: |
| le_audio_sink_hal_client_->ConfirmStreamingRequest(); |
| break; |
| case AudioState::IDLE: |
| switch (audio_sender_state_) { |
| case AudioState::IDLE: |
| if (OnAudioResume(group)) { |
| audio_receiver_state_ = AudioState::READY_TO_START; |
| } else { |
| le_audio_sink_hal_client_->CancelStreamingRequest(); |
| } |
| break; |
| case AudioState::READY_TO_START: |
| case AudioState::STARTED: |
| audio_receiver_state_ = AudioState::READY_TO_START; |
| /* If signalling part is completed trigger start reveivin audio |
| * here, otherwise it'll be called on group streaming state callback |
| */ |
| if (group->GetState() == |
| AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { |
| if (!IsAudioSourceAvailableForCurrentConfiguration()) { |
| StopStreamIfNeeded(group, LeAudioContextType::VOICEASSISTANTS); |
| break; |
| } |
| |
| StartReceivingAudio(active_group_id_); |
| } |
| 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: |
| LOG_WARN( |
| " called in wrong state. \n audio_receiver_state: %s \n" |
| "audio_sender_state: %s \n", |
| ToString(audio_receiver_state_).c_str(), |
| ToString(audio_sender_state_).c_str()); |
| CancelStreamingRequest(); |
| break; |
| } |
| break; |
| case AudioState::READY_TO_START: |
| LOG_WARN( |
| " called in wrong state. \n audio_receiver_state: %s \n" |
| "audio_sender_state: %s \n", |
| ToString(audio_receiver_state_).c_str(), |
| ToString(audio_sender_state_).c_str()); |
| CancelStreamingRequest(); |
| 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 */ |
| audio_receiver_state_ = AudioState::STARTED; |
| if (alarm_is_scheduled(suspend_timeout_)) |
| alarm_cancel(suspend_timeout_); |
| le_audio_sink_hal_client_->ConfirmStreamingRequest(); |
| break; |
| case AudioState::RELEASING: |
| /* Wait until releasing is completed */ |
| break; |
| } |
| |
| break; |
| case AudioState::RELEASING: |
| /* Wait until releasing is completed */ |
| break; |
| } |
| } |
| |
| LeAudioContextType ChooseConfigurationContextType( |
| le_audio::types::AudioContexts available_contexts) { |
| if (in_call_) { |
| LOG_DEBUG(" In Call preference used."); |
| return LeAudioContextType::CONVERSATIONAL; |
| } |
| |
| /* Mini policy */ |
| if (available_contexts.any()) { |
| LeAudioContextType context_priority_list[] = { |
| /* Highest priority first */ |
| LeAudioContextType::CONVERSATIONAL, |
| LeAudioContextType::GAME, |
| /* Skip the RINGTONE to avoid reconfigurations when adjusting |
| * call volume slider while not in a call. |
| * LeAudioContextType::RINGTONE, |
| */ |
| LeAudioContextType::MEDIA, |
| LeAudioContextType::INSTRUCTIONAL, |
| LeAudioContextType::VOICEASSISTANTS, |
| LeAudioContextType::LIVE, |
| LeAudioContextType::NOTIFICATIONS, |
| LeAudioContextType::ALERTS, |
| LeAudioContextType::EMERGENCYALARM, |
| LeAudioContextType::UNSPECIFIED, |
| }; |
| for (auto ct : context_priority_list) { |
| if (available_contexts.test(ct)) { |
| return ct; |
| } |
| } |
| } |
| |
| /* Fallback to BAP mandated context type */ |
| LOG_WARN(" invalid/unknown context, using 'UNSPECIFIED'"); |
| return LeAudioContextType::UNSPECIFIED; |
| } |
| |
| bool StopStreamIfNeeded(LeAudioDeviceGroup* group, |
| LeAudioContextType new_context_type) { |
| auto reconfig_result = UpdateConfigAndCheckIfReconfigurationIsNeeded( |
| group->group_id_, 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( |
| std::vector<struct playback_track_metadata> source_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; |
| } |
| bool is_group_streaming = |
| (group->GetTargetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING); |
| |
| if (audio_receiver_state_ == AudioState::STARTED) { |
| /* If the receiver is started. Take into account current context type */ |
| metadata_context_types_ = |
| ChooseMetadataContextType(metadata_context_types_); |
| } else { |
| metadata_context_types_ = AudioContexts(); |
| } |
| |
| metadata_context_types_ |= GetAllowedAudioContextsFromSourceMetadata( |
| source_metadata, group->GetAvailableContexts()); |
| |
| if (stack_config_get_interface() |
| ->get_pts_force_le_audio_multiple_contexts_metadata()) { |
| // Use common audio stream contexts exposed by the PTS |
| metadata_context_types_ = AudioContexts(0xFFFF); |
| for (auto device = group->GetFirstDevice(); device != nullptr; |
| device = group->GetNextDevice(device)) { |
| metadata_context_types_ &= device->GetAvailableContexts(); |
| } |
| if (metadata_context_types_.value() == 0xFFFF) { |
| metadata_context_types_ = |
| AudioContexts(LeAudioContextType::UNSPECIFIED); |
| } |
| LOG_WARN("Overriding metadata_context_types_ with: %s", |
| metadata_context_types_.to_string().c_str()); |
| |
| /* Configuration is the same for new context, just will do update |
| * metadata of stream |
| */ |
| auto new_configuration_context = |
| ChooseConfigurationContextType(metadata_context_types_); |
| GroupStream(active_group_id_, new_configuration_context, |
| metadata_context_types_); |
| return; |
| } |
| |
| if (metadata_context_types_.none()) { |
| LOG_WARN( |
| " invalid/unknown context metadata, using 'UNSPECIFIED' instead"); |
| metadata_context_types_ = AudioContexts(LeAudioContextType::UNSPECIFIED); |
| } |
| |
| auto new_configuration_context = |
| ChooseConfigurationContextType(metadata_context_types_); |
| LOG_DEBUG("new_configuration_context_type: %s", |
| ToString(new_configuration_context).c_str()); |
| |
| if (new_configuration_context == configuration_context_type_) { |
| LOG_INFO("Context did not changed."); |
| |
| } else { |
| configuration_context_type_ = new_configuration_context; |
| if (StopStreamIfNeeded(group, new_configuration_context)) { |
| return; |
| } |
| } |
| |
| if (is_group_streaming) { |
| /* Configuration is the same for new context, just will do update |
| * metadata of stream |
| */ |
| GroupStream(active_group_id_, new_configuration_context, |
| metadata_context_types_); |
| } |
| } |
| |
| void OnLocalAudioSinkMetadataUpdate( |
| std::vector<struct record_track_metadata> sink_metadata) { |
| bool is_audio_source_invalid = true; |
| |
| for (auto& track : sink_metadata) { |
| LOG_INFO( |
| "%s: source=%d, gain=%f, destination device=%d, " |
| "destination device address=%.32s", |
| __func__, track.source, track.gain, track.dest_device, |
| track.dest_device_address); |
| |
| /* Don't differentiate source types, just check if it's valid */ |
| if (is_audio_source_invalid && track.source != AUDIO_SOURCE_INVALID) |
| is_audio_source_invalid = false; |
| } |
| |
| auto group = aseGroups_.FindById(active_group_id_); |
| if (!group) { |
| LOG(ERROR) << __func__ |
| << ", Invalid group: " << static_cast<int>(active_group_id_); |
| return; |
| } |
| |
| if (stack_config_get_interface() |
| ->get_pts_force_le_audio_multiple_contexts_metadata()) { |
| // Use common audio stream contexts exposed by the PTS |
| metadata_context_types_ = AudioContexts(0xFFFF); |
| for (auto device = group->GetFirstDevice(); device != nullptr; |
| device = group->GetNextDevice(device)) { |
| metadata_context_types_ &= device->GetAvailableContexts(); |
| } |
| if (metadata_context_types_.value() == 0xFFFF) { |
| metadata_context_types_ = |
| AudioContexts(LeAudioContextType::UNSPECIFIED); |
| } |
| metadata_context_types_.set(LeAudioContextType::VOICEASSISTANTS); |
| LOG_WARN("Overriding metadata_context_types_ with: %su", |
| metadata_context_types_.to_string().c_str()); |
| } |
| |
| /* Do nothing, since audio source is not valid and if voice assistant |
| * scenario is currently not supported by group |
| */ |
| if (is_audio_source_invalid || |
| !group->IsContextSupported(LeAudioContextType::VOICEASSISTANTS) || |
| IsAudioSourceAvailableForCurrentConfiguration()) { |
| return; |
| } |
| |
| auto new_context = LeAudioContextType::VOICEASSISTANTS; |
| |
| /* Add the new_context to the metadata */ |
| metadata_context_types_.set(new_context); |
| |
| if (StopStreamIfNeeded(group, new_context)) return; |
| |
| if (group->GetTargetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { |
| /* Add the new_context to the metadata */ |
| metadata_context_types_.set(new_context); |
| |
| /* Configuration is the same for new context, just will do update |
| * metadata of stream. Consider using separate metadata for each |
| * direction. |
| */ |
| GroupStream(active_group_id_, new_context, metadata_context_types_); |
| } |
| |
| /* Audio sessions are not resumed yet and not streaming, let's pick voice |
| * assistant as possible current context type. |
| */ |
| configuration_context_type_ = new_context; |
| } |
| |
| static void OnGattReadRspStatic(uint16_t conn_id, tGATT_STATUS status, |
| uint16_t hdl, uint16_t len, uint8_t* value, |
| void* data) { |
| if (!instance) return; |
| |
| if (status == GATT_SUCCESS) { |
| instance->LeAudioCharValueHandle(conn_id, hdl, len, value); |
| } |
| |
| /* We use data to keep notify connected flag. */ |
| if (data && !!PTR_TO_INT(data)) { |
| LeAudioDevice* leAudioDevice = |
| instance->leAudioDevices_.FindByConnId(conn_id); |
| leAudioDevice->notify_connected_after_read_ = false; |
| |
| /* Update PACs and ASEs when all is read.*/ |
| 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 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) << __func__ << " receiver state not ready "; |
| 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); |
| |
| 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 HandlePendingAvailableContextsChange(LeAudioDeviceGroup* group) { |
| if (!group) return; |
| |
| /* Update group configuration with pending available context change */ |
| auto contexts = group->GetPendingAvailableContextsChange(); |
| if (contexts.any()) { |
| auto success = group->UpdateAudioContextTypeAvailability(contexts); |
| if (success) { |
| callbacks_->OnAudioConf(group->audio_directions_, group->group_id_, |
| group->snk_audio_locations_.to_ulong(), |
| group->src_audio_locations_.to_ulong(), |
| group->GetAvailableContexts().value()); |
| } |
| group->ClearPendingAvailableContextsChange(); |
| } |
| } |
| |
| 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_)); |
| DisconnectDevice(leAudioDevice); |
| } |
| leAudioDevice = group->GetNextDevice(leAudioDevice); |
| } |
| } |
| |
| void updateOffloaderIfNeeded(LeAudioDeviceGroup* group) { |
| if (CodecManager::GetInstance()->GetCodecLocation() != |
| le_audio::types::CodecLocation::ADSP) { |
| return; |
| } |
| |
| LOG_INFO("Group %p, group_id %d", group, group->group_id_); |
| |
| const auto* stream_conf = &group->stream_conf; |
| |
| if (stream_conf->sink_offloader_changed || stream_conf->sink_is_initial) { |
| LOG_INFO("Update sink offloader streams"); |
| uint16_t remote_delay_ms = |
| group->GetRemoteDelay(le_audio::types::kLeAudioDirectionSink); |
| CodecManager::GetInstance()->UpdateActiveSourceAudioConfig( |
| *stream_conf, remote_delay_ms, |
| std::bind(&LeAudioSourceAudioHalClient::UpdateAudioConfigToHal, |
| le_audio_source_hal_client_.get(), std::placeholders::_1)); |
| group->StreamOffloaderUpdated(le_audio::types::kLeAudioDirectionSink); |
| } |
| |
| if (stream_conf->source_offloader_changed || |
| stream_conf->source_is_initial) { |
| LOG_INFO("Update source offloader streams"); |
| uint16_t remote_delay_ms = |
| group->GetRemoteDelay(le_audio::types::kLeAudioDirectionSource); |
| CodecManager::GetInstance()->UpdateActiveSinkAudioConfig( |
| *stream_conf, remote_delay_ms, |
| std::bind(&LeAudioSinkAudioHalClient::UpdateAudioConfigToHal, |
| le_audio_sink_hal_client_.get(), std::placeholders::_1)); |
| group->StreamOffloaderUpdated(le_audio::types::kLeAudioDirectionSource); |
| } |
| } |
| |
| 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 (in_call_ && 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 OnStateMachineStatusReportCb(int group_id, GroupStreamStatus status) { |
| LOG_INFO("status: %d , audio_sender_state %s, audio_receiver_state %s", |
| static_cast<int>(status), |
| bluetooth::common::ToString(audio_sender_state_).c_str(), |
| bluetooth::common::ToString(audio_receiver_state_).c_str()); |
| LeAudioDeviceGroup* group = aseGroups_.FindById(group_id); |
| switch (status) { |
| case GroupStreamStatus::STREAMING: |
| ASSERT_LOG(group_id == active_group_id_, "invalid group id %d!=%d", |
| group_id, active_group_id_); |
| |
| updateOffloaderIfNeeded(group); |
| |
| if (audio_sender_state_ == AudioState::READY_TO_START) |
| StartSendingAudio(group_id); |
| if (audio_receiver_state_ == AudioState::READY_TO_START) |
| StartReceivingAudio(group_id); |
| |
| take_stream_time(); |
| |
| le_audio::MetricsCollector::Get()->OnStreamStarted( |
| active_group_id_, configuration_context_type_); |
| 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(); |
| HandlePendingAvailableContextsChange(group); |
| 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 |
| */ |
| FALLTHROUGH; |
| case GroupStreamStatus::IDLE: { |
| if (group && group->IsPendingConfiguration()) { |
| SuspendedForReconfiguration(); |
| auto adjusted_metedata_context_type = |
| ChooseMetadataContextType(metadata_context_types_); |
| if (groupStateMachine_->ConfigureStream( |
| group, configuration_context_type_, |
| adjusted_metedata_context_type, |
| GetAllCcids(adjusted_metedata_context_type))) { |
| /* If configuration succeed wait for new status. */ |
| return; |
| } |
| } |
| stream_setup_end_timestamp_ = 0; |
| stream_setup_start_timestamp_ = 0; |
| CancelStreamingRequest(); |
| if (group) { |
| NotifyUpperLayerGroupTurnedIdleDuringCall(group->group_id_); |
| HandlePendingAvailableContextsChange(group); |
| HandlePendingDeviceDisconnection(group); |
| } |
| break; |
| } |
| case GroupStreamStatus::RELEASING: |
| case GroupStreamStatus::SUSPENDING: |
| 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; |
| } |
| } |
| |
| 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"; |
| AudioContexts 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_; |
| static constexpr char kNotifyUpperLayerAboutGroupBeingInIdleDuringCall[] = |
| "persist.bluetooth.leaudio.notify.idle.during.call"; |
| |
| /* 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, |
| }; |
| |
| void* lc3_encoder_left_mem; |
| void* lc3_encoder_right_mem; |
| |
| lc3_encoder_t lc3_encoder_left; |
| lc3_encoder_t lc3_encoder_right; |
| |
| void* lc3_decoder_left_mem; |
| void* lc3_decoder_right_mem; |
| |
| lc3_decoder_t lc3_decoder_left; |
| lc3_decoder_t lc3_decoder_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* suspend_timeout_; |
| alarm_t* disable_timer_; |
| static constexpr uint64_t kDeviceAttachDelayMs = 500; |
| |
| std::vector<int16_t> cached_channel_data_; |
| uint32_t cached_channel_timestamp_ = 0; |
| uint32_t cached_channel_is_left_; |
| |
| void ClientAudioIntefraceRelease() { |
| if (le_audio_source_hal_client_) { |
| le_audio_source_hal_client_->Stop(); |
| le_audio_source_hal_client_.reset(); |
| } |
| |
| if (le_audio_sink_hal_client_) { |
| le_audio_sink_hal_client_->Stop(); |
| le_audio_sink_hal_client_.reset(); |
| } |
| le_audio::MetricsCollector::Get()->OnStreamEnded(active_group_id_); |
| } |
| }; |
| |
| /* 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; |
| |
| DLOG(INFO) << __func__ << " event = " << +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); |
| } |
| }; |
| |
| CallbacksImpl stateMachineCallbacksImpl; |
| |
| class SourceCallbacksImpl : public LeAudioSourceAudioHalClient::Callbacks { |
| public: |
| void OnAudioDataReady(const std::vector<uint8_t>& data) override { |
| if (instance) instance->OnAudioDataReady(data); |
| } |
| void OnAudioSuspend(std::promise<void> do_suspend_promise) override { |
| if (instance) instance->OnLocalAudioSourceSuspend(); |
| do_suspend_promise.set_value(); |
| } |
| |
| void OnAudioResume(void) override { |
| if (instance) instance->OnLocalAudioSourceResume(); |
| } |
| |
| void OnAudioMetadataUpdate( |
| std::vector<struct playback_track_metadata> source_metadata) override { |
| if (instance) |
| instance->OnLocalAudioSourceMetadataUpdate(std::move(source_metadata)); |
| } |
| }; |
| |
| class SinkCallbacksImpl : public LeAudioSinkAudioHalClient::Callbacks { |
| public: |
| void OnAudioSuspend(std::promise<void> do_suspend_promise) override { |
| if (instance) instance->OnLocalAudioSinkSuspend(); |
| do_suspend_promise.set_value(); |
| } |
| void OnAudioResume(void) override { |
| if (instance) instance->OnLocalAudioSinkResume(); |
| } |
| |
| void OnAudioMetadataUpdate( |
| std::vector<struct record_track_metadata> 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; } |
| |
| 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) { |
| if (instance) { |
| LOG(ERROR) << "Already initialized"; |
| return; |
| } |
| |
| if (!controller_get_interface() |
| ->supports_ble_connected_isochronous_stream_central() && |
| !controller_get_interface() |
| ->supports_ble_connected_isochronous_stream_peripheral()) { |
| 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(); |
| } |
| |
| void LeAudioClient::DebugDump(int fd) { |
| 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); |
| dprintf(fd, "\n"); |
| } |
| |
| void LeAudioClient::Cleanup(base::Callback<void()> cleanupCb) { |
| if (!instance) { |
| LOG(ERROR) << "Not initialized"; |
| return; |
| } |
| |
| LeAudioClientImpl* ptr = instance; |
| instance = nullptr; |
| ptr->Cleanup(cleanupCb); |
| delete ptr; |
| ptr = nullptr; |
| |
| CodecManager::GetInstance()->Stop(); |
| ContentControlIdKeeper::GetInstance()->Stop(); |
| LeAudioGroupStateMachine::Cleanup(); |
| IsoManager::GetInstance()->Stop(); |
| le_audio::MetricsCollector::Get()->Flush(); |
| } |