/* * Copyright 2020 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 "devices.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "acl_api.h" #include "bta_gatt_api.h" #include "bta_gatt_queue.h" #include "btif/include/btif_storage.h" #include "btm_ble_api_types.h" #include "btm_iso_api_types.h" #include "common/strings.h" #include "gatt_api.h" #include "hardware/bluetooth.h" #include "hci/controller_interface.h" #include "hci_error_code.h" #include "hcidefs.h" #include "internal_include/bt_trace.h" #include "le_audio/codec_manager.h" #include "le_audio/le_audio_types.h" #include "le_audio_log_history.h" #include "le_audio_utils.h" #include "main/shim/entry.h" #include "os/logging/log_adapter.h" #include "osi/include/alarm.h" #include "osi/include/properties.h" #include "stack/include/btm_client_interface.h" #include "types/bt_transport.h" #include "types/raw_address.h" // TODO(b/369381361) Enfore -Wmissing-prototypes #pragma GCC diagnostic ignored "-Wmissing-prototypes" using bluetooth::hci::kIsoCigPhy1M; using bluetooth::hci::kIsoCigPhy2M; using bluetooth::le_audio::DeviceConnectState; using bluetooth::le_audio::types::ase; using bluetooth::le_audio::types::AseState; using bluetooth::le_audio::types::AudioContexts; using bluetooth::le_audio::types::AudioLocations; using bluetooth::le_audio::types::BidirectionalPair; using bluetooth::le_audio::types::CisState; using bluetooth::le_audio::types::DataPathState; using bluetooth::le_audio::types::LeAudioContextType; namespace bluetooth::le_audio { std::ostream& operator<<(std::ostream& os, const DeviceConnectState& state) { const char* char_value_ = "UNKNOWN"; switch (state) { case DeviceConnectState::CONNECTED: char_value_ = "CONNECTED"; break; case DeviceConnectState::DISCONNECTED: char_value_ = "DISCONNECTED"; break; case DeviceConnectState::REMOVING: char_value_ = "REMOVING"; break; case DeviceConnectState::DISCONNECTING: char_value_ = "DISCONNECTING"; break; case DeviceConnectState::DISCONNECTING_AND_RECOVER: char_value_ = "DISCONNECTING_AND_RECOVER"; break; case DeviceConnectState::CONNECTING_BY_USER: char_value_ = "CONNECTING_BY_USER"; break; case DeviceConnectState::CONNECTED_BY_USER_GETTING_READY: char_value_ = "CONNECTED_BY_USER_GETTING_READY"; break; case DeviceConnectState::CONNECTING_AUTOCONNECT: char_value_ = "CONNECTING_AUTOCONNECT"; break; case DeviceConnectState::CONNECTED_AUTOCONNECT_GETTING_READY: char_value_ = "CONNECTED_AUTOCONNECT_GETTING_READY"; break; } os << char_value_ << " (" << "0x" << std::setfill('0') << std::setw(2) << static_cast(state) << ")"; return os; } static uint32_t GetFirstLeft(const AudioLocations& audio_locations) { uint32_t audio_location_ulong = audio_locations.to_ulong(); if (audio_location_ulong & codec_spec_conf::kLeAudioLocationFrontLeft) { return codec_spec_conf::kLeAudioLocationFrontLeft; } if (audio_location_ulong & codec_spec_conf::kLeAudioLocationBackLeft) { return codec_spec_conf::kLeAudioLocationBackLeft; } if (audio_location_ulong & codec_spec_conf::kLeAudioLocationFrontLeftOfCenter) { return codec_spec_conf::kLeAudioLocationFrontLeftOfCenter; } if (audio_location_ulong & codec_spec_conf::kLeAudioLocationSideLeft) { return codec_spec_conf::kLeAudioLocationSideLeft; } if (audio_location_ulong & codec_spec_conf::kLeAudioLocationTopFrontLeft) { return codec_spec_conf::kLeAudioLocationTopFrontLeft; } if (audio_location_ulong & codec_spec_conf::kLeAudioLocationTopBackLeft) { return codec_spec_conf::kLeAudioLocationTopBackLeft; } if (audio_location_ulong & codec_spec_conf::kLeAudioLocationTopSideLeft) { return codec_spec_conf::kLeAudioLocationTopSideLeft; } if (audio_location_ulong & codec_spec_conf::kLeAudioLocationBottomFrontLeft) { return codec_spec_conf::kLeAudioLocationBottomFrontLeft; } if (audio_location_ulong & codec_spec_conf::kLeAudioLocationFrontLeftWide) { return codec_spec_conf::kLeAudioLocationFrontLeftWide; } if (audio_location_ulong & codec_spec_conf::kLeAudioLocationLeftSurround) { return codec_spec_conf::kLeAudioLocationLeftSurround; } return 0; } static uint32_t GetFirstRight(const AudioLocations& audio_locations) { uint32_t audio_location_ulong = audio_locations.to_ulong(); if (audio_location_ulong & codec_spec_conf::kLeAudioLocationFrontRight) { return codec_spec_conf::kLeAudioLocationFrontRight; } if (audio_location_ulong & codec_spec_conf::kLeAudioLocationBackRight) { return codec_spec_conf::kLeAudioLocationBackRight; } if (audio_location_ulong & codec_spec_conf::kLeAudioLocationFrontRightOfCenter) { return codec_spec_conf::kLeAudioLocationFrontRightOfCenter; } if (audio_location_ulong & codec_spec_conf::kLeAudioLocationSideRight) { return codec_spec_conf::kLeAudioLocationSideRight; } if (audio_location_ulong & codec_spec_conf::kLeAudioLocationTopFrontRight) { return codec_spec_conf::kLeAudioLocationTopFrontRight; } if (audio_location_ulong & codec_spec_conf::kLeAudioLocationTopBackRight) { return codec_spec_conf::kLeAudioLocationTopBackRight; } if (audio_location_ulong & codec_spec_conf::kLeAudioLocationTopSideRight) { return codec_spec_conf::kLeAudioLocationTopSideRight; } if (audio_location_ulong & codec_spec_conf::kLeAudioLocationBottomFrontRight) { return codec_spec_conf::kLeAudioLocationBottomFrontRight; } if (audio_location_ulong & codec_spec_conf::kLeAudioLocationFrontRightWide) { return codec_spec_conf::kLeAudioLocationFrontRightWide; } if (audio_location_ulong & codec_spec_conf::kLeAudioLocationRightSurround) { return codec_spec_conf::kLeAudioLocationRightSurround; } return 0; } uint32_t PickAudioLocation(types::LeAudioConfigurationStrategy strategy, const AudioLocations& device_locations, AudioLocations& group_locations) { log::debug("strategy: {}, locations: 0x{:x}, input group locations: 0x{:x}", (int)strategy, device_locations.to_ulong(), group_locations.to_ulong()); auto is_left_not_yet_assigned = !(group_locations.to_ulong() & codec_spec_conf::kLeAudioLocationAnyLeft); auto is_right_not_yet_assigned = !(group_locations.to_ulong() & codec_spec_conf::kLeAudioLocationAnyRight); uint32_t left_device_loc = GetFirstLeft(device_locations); uint32_t right_device_loc = GetFirstRight(device_locations); if (left_device_loc == 0 && right_device_loc == 0) { log::warn("Can't find device able to render left and right audio channel"); } switch (strategy) { case types::LeAudioConfigurationStrategy::MONO_ONE_CIS_PER_DEVICE: case types::LeAudioConfigurationStrategy::STEREO_TWO_CISES_PER_DEVICE: if (left_device_loc && is_left_not_yet_assigned) { group_locations |= left_device_loc; return left_device_loc; } if (right_device_loc && is_right_not_yet_assigned) { group_locations |= right_device_loc; return right_device_loc; } break; case types::LeAudioConfigurationStrategy::STEREO_ONE_CIS_PER_DEVICE: if (left_device_loc && right_device_loc) { group_locations |= left_device_loc | right_device_loc; return left_device_loc | right_device_loc; } break; default: log::fatal("Unknown strategy: {}", strategy); return 0; } log::error( "Can't find device for left/right channel. Strategy: {}, " "device_locations: {:x}, output group_locations: {:x}.", strategy, device_locations.to_ulong(), group_locations.to_ulong()); /* Return either any left or any right audio location. It might result with * multiple devices within the group having the same location. */ return left_device_loc ? left_device_loc : right_device_loc; } bool LeAudioDevice::IsAudioSetConfigurationSupported( const set_configurations::AudioSetConfiguration* audio_set_conf) const { for (auto direction : {le_audio::types::kLeAudioDirectionSink, le_audio::types::kLeAudioDirectionSource}) { const auto& confs = audio_set_conf->confs.get(direction); if (confs.size() == 0) { continue; } log::info("Looking for requirements: {} - {}", audio_set_conf->name, direction == 1 ? "snk" : "src"); auto const& pacs = (direction == types::kLeAudioDirectionSink) ? snk_pacs_ : src_pacs_; for (const auto& ent : confs) { if (!utils::GetConfigurationSupportedPac(pacs, ent.codec)) { log::info("Configuration is NOT supported by device {}", address_); return false; } } } log::info("Configuration is supported by device {}", address_); return true; } bool LeAudioDevice::ConfigureAses(const set_configurations::AudioSetConfiguration* audio_set_conf, uint8_t num_of_devices, uint8_t direction, LeAudioContextType context_type, uint8_t* number_of_already_active_group_ase, AudioLocations& group_audio_locations_memo, const AudioContexts& metadata_context_types, const std::vector& ccid_lists, bool reuse_cis_id) { auto direction_str = (direction == types::kLeAudioDirectionSink ? "Sink" : "Source"); /* First try to use the already configured ASE */ auto ase = GetFirstActiveAseByDirection(direction); if (ase) { log::info("{}, using an already active {} ASE id={}", address_, direction_str, ase->id); } else { ase = GetFirstInactiveAse(direction, reuse_cis_id); } if (!ase) { log::error("{}, unable to find a {} ASE to configure", address_, direction_str); PrintDebugState(); return false; } auto audio_locations = (direction == types::kLeAudioDirectionSink) ? snk_audio_locations_ : src_audio_locations_; auto const& group_ase_configs = audio_set_conf->confs.get(direction); std::vector ase_configs; std::copy_if(group_ase_configs.cbegin(), group_ase_configs.cend(), std::back_inserter(ase_configs), [&audio_locations](auto const& cfg) { /* Pass as matching if config has no allocation to match * (the legacy json config provider). Otherwise, with the codec * extensibility feature enabled, we receive ASE configurations * for the whole group and we should filter them by audio * allocations to match with the locations supported by a * particular device. */ auto config = cfg.codec.params.GetAsCoreCodecConfig(); if (!config.audio_channel_allocation.has_value()) { return true; } // No locations bits means mono audio if (audio_locations.none()) { return true; } // Filter-out not matching audio locations return (cfg.codec.params.GetAsCoreCodecConfig().audio_channel_allocation.value() & audio_locations.to_ulong()) != 0; }); auto const& pacs = (direction == types::kLeAudioDirectionSink) ? snk_pacs_ : src_pacs_; for (size_t i = 0; i < ase_configs.size() && ase; ++i) { auto const& ase_cfg = ase_configs.at(i); if (utils::IsCodecUsingLtvFormat(ase_cfg.codec.id) && !utils::GetConfigurationSupportedPac(pacs, ase_cfg.codec)) { log::error("{}, No {} PAC found matching codec: {}. Stop the activation.", address_, direction_str, common::ToString(ase_cfg.codec)); return false; } } /* The number_of_already_active_group_ase keeps all the active ases * in other devices in the group for the given direction. * This function counts active ases only for this device, and we count here * new active ases and already active ases which we want to reuse in the * scenario */ uint8_t active_ases = *number_of_already_active_group_ase; // Before we activate the ASEs, make sure we have the right configuration // Check for matching PACs only if we know that the LTV format is being used. uint8_t max_required_ase_per_dev = ase_configs.size() / num_of_devices + (ase_configs.size() % num_of_devices); int needed_ase = std::min((int)(max_required_ase_per_dev), (int)(ase_configs.size())); log::debug("{}, {} {} ASE(s) required for this configuration.", address_, needed_ase, direction_str); for (int i = 0; i < needed_ase; ++i) { auto const& ase_cfg = ase_configs.at(i); if (utils::IsCodecUsingLtvFormat(ase_cfg.codec.id) && !utils::GetConfigurationSupportedPac(pacs, ase_cfg.codec)) { log::error("{}, No {} PAC found matching codec: {}. Stop the activation.", address_, direction_str, common::ToString(ase_cfg.codec)); return false; } } auto strategy = utils::GetStrategyForAseConfig(group_ase_configs, num_of_devices); // Make sure we configure a single microphone if Dual Bidir SWB is not // supported. if (direction == types::kLeAudioDirectionSource && !CodecManager::GetInstance()->IsDualBiDirSwbSupported() && (active_ases != 0)) { if (CodecManager::GetInstance()->CheckCodecConfigIsDualBiDirSwb(*audio_set_conf)) { log::error( "{}, trying to configure the dual bidir SWB, but the feature is " "disabled. This should not happen! Skipping ASE activation.", address_); return true; } } for (int i = 0; i < needed_ase && ase; ++i) { auto const& ase_cfg = ase_configs.at(i); ase->active = true; ase->configured_for_context_type = context_type; ase->data_path_configuration = ase_cfg.data_path_configuration; active_ases++; /* In case of late connect, we could be here for STREAMING ase. * in such case, it is needed to mark ase as known active ase which * is important to validate scenario and is done already few lines above. * Nothing more to do is needed here. */ if (ase->state != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { if (ase->state == AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED) { ase->reconfigure = true; } ase->target_latency = ase_cfg.qos.target_latency; ase->codec_id = ase_cfg.codec.id; ase->codec_config = ase_cfg.codec.params; ase->vendor_codec_config = ase_cfg.codec.vendor_params; ase->channel_count = ase_cfg.codec.channel_count_per_iso_stream; /* Let's choose audio channel allocation if not set */ ase->codec_config.Add( codec_spec_conf::kLeAudioLtvTypeAudioChannelAllocation, PickAudioLocation(strategy, audio_locations, group_audio_locations_memo)); /* Get default value if no requirement for specific frame blocks per sdu */ if (utils::IsCodecUsingLtvFormat(ase->codec_id) && !ase->codec_config.Find(codec_spec_conf::kLeAudioLtvTypeCodecFrameBlocksPerSdu)) { ase->codec_config.Add(codec_spec_conf::kLeAudioLtvTypeCodecFrameBlocksPerSdu, GetMaxCodecFramesPerSduFromPac( utils::GetConfigurationSupportedPac(pacs, ase_cfg.codec))); } ase->qos_config.sdu_interval = ase_cfg.qos.sduIntervalUs; ase->qos_config.max_sdu_size = ase_cfg.qos.maxSdu; ase->qos_config.retrans_nb = ase_cfg.qos.retransmission_number; ase->qos_config.max_transport_latency = ase_cfg.qos.max_transport_latency; SetMetadataToAse(ase, metadata_context_types, ccid_lists); } log::debug( "device={}, activated ASE id={}, direction={}, max_sdu_size={}, " "cis_id={}, target_latency={}", address_, ase->id, direction_str, ase->qos_config.max_sdu_size, ase->cis_id, ase_cfg.qos.target_latency); /* Try to use the already active ASE */ ase = GetNextActiveAseWithSameDirection(ase); if (ase == nullptr) { ase = GetFirstInactiveAse(direction, reuse_cis_id); } } *number_of_already_active_group_ase = active_ases; return true; } /* LeAudioDevice Class methods implementation */ void LeAudioDevice::SetConnectionState(DeviceConnectState state) { log::debug("{}, {} --> {}", address_, bluetooth::common::ToString(connection_state_), bluetooth::common::ToString(state)); LeAudioLogHistory::Get()->AddLogHistory(kLogConnectionTag, group_id_, address_, bluetooth::common::ToString(connection_state_) + " -> ", "->" + bluetooth::common::ToString(state)); connection_state_ = state; } DeviceConnectState LeAudioDevice::GetConnectionState(void) { return connection_state_; } void LeAudioDevice::ClearPACs(void) { snk_pacs_.clear(); src_pacs_.clear(); } LeAudioDevice::~LeAudioDevice(void) { alarm_free(link_quality_timer); this->ClearPACs(); } void LeAudioDevice::ParseHeadtrackingCodec(const struct types::acs_ac_record& pac) { if (!com::android::bluetooth::flags::leaudio_dynamic_spatial_audio()) { return; } if (pac.codec_id == types::kLeAudioCodecHeadtracking) { log::info("Headtracking supported"); // Assume LE-ISO is supported if metadata is not available dsa_.modes = { DsaMode::DISABLED, DsaMode::ISO_SW, DsaMode::ISO_HW, }; if (!com::android::bluetooth::flags::headtracker_codec_capability()) { return; } /* * Android Headtracker Codec Metadata description * length: 5 * type: 0xFF * value: { * vendorId: 0x00E0 (Google) * vendorSpecificMetadata: { * length: 1 * type: 1 (Headtracker supported transports) * value: x * } * } */ std::vector ltv = pac.metadata; if (ltv.size() < 7) { log::info("{}, headtracker codec does not have metadata", address_); return; } if (ltv[0] < 5 || ltv[1] != types::kLeAudioMetadataTypeVendorSpecific || ltv[2] != (types::kLeAudioVendorCompanyIdGoogle & 0xFF) || ltv[3] != (types::kLeAudioVendorCompanyIdGoogle >> 8) || ltv[4] != types::kLeAudioMetadataHeadtrackerTransportLen || ltv[5] != types::kLeAudioMetadataHeadtrackerTransportVal) { log::warn("{}, headtracker codec metadata invalid", address_); return; } // Valid headtracker codec metadata available, so it must support reduced sdu size dsa_.reduced_sdu = true; uint8_t supported_transports = ltv[6]; DsaModes dsa_modes = {DsaMode::DISABLED}; if ((supported_transports & types::kLeAudioMetadataHeadtrackerTransportLeAcl) != 0) { log::debug("{}, headtracking supported over LE-ACL", address_); dsa_modes.push_back(DsaMode::ACL); } if ((supported_transports & types::kLeAudioMetadataHeadtrackerTransportLeIso) != 0) { log::debug("{}, headtracking supported over LE-ISO", address_); dsa_modes.push_back(DsaMode::ISO_SW); dsa_modes.push_back(DsaMode::ISO_HW); } dsa_.modes = dsa_modes; } } void LeAudioDevice::RegisterPACs(std::vector* pac_db, std::vector* pac_recs) { /* Clear PAC database for characteristic in case if re-read, indicated */ if (!pac_db->empty()) { log::debug("{}, upgrade PACs for characteristic", address_); pac_db->clear(); } dsa_.modes = {DsaMode::DISABLED}; /* TODO wrap this logging part with debug flag */ for (const struct types::acs_ac_record& pac : *pac_recs) { std::stringstream debug_str; debug_str << "Registering PAC" << "\n\tCoding format: " << loghex(pac.codec_id.coding_format) << "\n\tVendor codec company ID: " << loghex(pac.codec_id.vendor_company_id) << "\n\tVendor codec ID: " << loghex(pac.codec_id.vendor_codec_id) << "\n\tCodec spec caps:\n"; if (utils::IsCodecUsingLtvFormat(pac.codec_id) && !pac.codec_spec_caps.IsEmpty()) { debug_str << pac.codec_spec_caps.ToString("", types::CodecCapabilitiesLtvFormat); } else { debug_str << base::HexEncode(pac.codec_spec_caps_raw.data(), pac.codec_spec_caps_raw.size()); } debug_str << "\n\tMetadata: " << base::HexEncode(pac.metadata.data(), pac.metadata.size()); log::debug("{}", debug_str.str()); ParseHeadtrackingCodec(pac); } pac_db->insert(pac_db->begin(), pac_recs->begin(), pac_recs->end()); } struct ase* LeAudioDevice::GetAseByValHandle(uint16_t val_hdl) { auto iter = std::find_if(ases_.begin(), ases_.end(), [&val_hdl](const auto& ase) { return ase.hdls.val_hdl == val_hdl; }); return (iter == ases_.end()) ? nullptr : &(*iter); } int LeAudioDevice::GetAseCount(uint8_t direction) { return std::count_if(ases_.begin(), ases_.end(), [direction](const auto& a) { return a.direction == direction; }); } struct ase* LeAudioDevice::GetFirstAseWithState(uint8_t direction, AseState state) { auto iter = std::find_if(ases_.begin(), ases_.end(), [direction, state](const auto& ase) { return (ase.direction == direction) && (ase.state == state); }); return (iter == ases_.end()) ? nullptr : &(*iter); } struct ase* LeAudioDevice::GetFirstActiveAse(void) { auto iter = std::find_if(ases_.begin(), ases_.end(), [](const auto& ase) { return ase.active; }); return (iter == ases_.end()) ? nullptr : &(*iter); } struct ase* LeAudioDevice::GetFirstActiveAseByDirection(uint8_t direction) { auto iter = std::find_if(ases_.begin(), ases_.end(), [direction](const auto& ase) { return ase.active && (ase.direction == direction); }); return (iter == ases_.end()) ? nullptr : &(*iter); } struct ase* LeAudioDevice::GetNextActiveAseWithSameDirection(struct ase* base_ase) { auto iter = std::find_if(ases_.begin(), ases_.end(), [&base_ase](auto& ase) { return base_ase == &ase; }); /* Invalid ase given */ if (iter == ases_.end() || std::distance(iter, ases_.end()) < 1) { return nullptr; } iter = std::find_if(std::next(iter, 1), ases_.end(), [&iter](const auto& ase) { return ase.active && (*iter).direction == ase.direction; }); return (iter == ases_.end()) ? nullptr : &(*iter); } struct ase* LeAudioDevice::GetNextActiveAseWithDifferentDirection(struct ase* base_ase) { auto iter = std::find_if(ases_.begin(), ases_.end(), [&base_ase](auto& ase) { return base_ase == &ase; }); /* Invalid ase given */ if (std::distance(iter, ases_.end()) < 1) { log::debug("{}, ASE {} does not use bidirectional CIS", address_, base_ase->id); return nullptr; } iter = std::find_if(std::next(iter, 1), ases_.end(), [&iter](const auto& ase) { return ase.active && iter->direction != ase.direction; }); if (iter == ases_.end()) { return nullptr; } return &(*iter); } struct ase* LeAudioDevice::GetFirstActiveAseByCisAndDataPathState(CisState cis_state, DataPathState data_path_state) { auto iter = std::find_if(ases_.begin(), ases_.end(), [cis_state, data_path_state](const auto& ase) { return ase.active && (ase.data_path_state == data_path_state) && (ase.cis_state == cis_state); }); return (iter == ases_.end()) ? nullptr : &(*iter); } struct ase* LeAudioDevice::GetFirstInactiveAse(uint8_t direction, bool reuse_cis_id) { auto iter = std::find_if(ases_.begin(), ases_.end(), [direction, reuse_cis_id](const auto& ase) { if (ase.active || (ase.direction != direction)) { return false; } if (!reuse_cis_id) { return true; } return ase.cis_id != kInvalidCisId; }); /* If ASE is found, return it */ if (iter != ases_.end()) { return &(*iter); } /* If reuse was not set, that means there is no inactive ASE available. */ if (!reuse_cis_id) { return nullptr; } /* Since there is no ASE with assigned CIS ID, it means new configuration * needs more ASEs then it was configured before. * Let's find just inactive one */ iter = std::find_if(ases_.begin(), ases_.end(), [direction](const auto& ase) { if (ase.active || (ase.direction != direction)) { return false; } return true; }); return (iter == ases_.end()) ? nullptr : &(*iter); } struct ase* LeAudioDevice::GetNextActiveAse(struct ase* base_ase) { auto iter = std::find_if(ases_.begin(), ases_.end(), [&base_ase](auto& ase) { return base_ase == &ase; }); /* Invalid ase given */ if (iter == ases_.end() || std::distance(iter, ases_.end()) < 1) { return nullptr; } iter = std::find_if(std::next(iter, 1), ases_.end(), [](const auto& ase) { return ase.active; }); return (iter == ases_.end()) ? nullptr : &(*iter); } struct ase* LeAudioDevice::GetAseToMatchBidirectionCis(struct ase* base_ase) { auto iter = std::find_if(ases_.begin(), ases_.end(), [&base_ase](auto& ase) { return (base_ase->cis_conn_hdl == ase.cis_conn_hdl) && (base_ase->direction != ase.direction); }); return (iter == ases_.end()) ? nullptr : &(*iter); } BidirectionalPair LeAudioDevice::GetAsesByCisConnHdl(uint16_t conn_hdl) { BidirectionalPair ases = {nullptr, nullptr}; for (auto& ase : ases_) { if (ase.cis_conn_hdl == conn_hdl) { if (ase.direction == types::kLeAudioDirectionSink) { ases.sink = &ase; } else { ases.source = &ase; } } } return ases; } BidirectionalPair LeAudioDevice::GetAsesByCisId(uint8_t cis_id) { BidirectionalPair ases = {nullptr, nullptr}; for (auto& ase : ases_) { if (ase.cis_id == cis_id) { if (ase.direction == types::kLeAudioDirectionSink) { ases.sink = &ase; } else { ases.source = &ase; } } } return ases; } bool LeAudioDevice::HaveActiveAse(void) { auto iter = std::find_if(ases_.begin(), ases_.end(), [](const auto& ase) { return ase.active; }); return iter != ases_.end(); } bool LeAudioDevice::HaveAnyReleasingAse(void) { /* In configuring state when active in Idle or Configured and reconfigure */ auto iter = std::find_if(ases_.begin(), ases_.end(), [](const auto& ase) { if (!ase.active) { return false; } return ase.state == AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING; }); return iter != ases_.end(); } bool LeAudioDevice::HaveAnyStreamingAses(void) { /* In configuring state when active in Idle or Configured and reconfigure */ auto iter = std::find_if(ases_.begin(), ases_.end(), [](const auto& ase) { if (!ase.active) { return false; } if (ase.state == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) { return true; } return false; }); return iter != ases_.end(); } bool LeAudioDevice::HaveAnyUnconfiguredAses(void) { /* In configuring state when active in Idle or Configured and reconfigure */ auto iter = std::find_if(ases_.begin(), ases_.end(), [](const auto& ase) { if (!ase.active) { return false; } if (ase.state == AseState::BTA_LE_AUDIO_ASE_STATE_IDLE || ((ase.state == AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED) && ase.reconfigure)) { return true; } return false; }); return iter != ases_.end(); } bool LeAudioDevice::HaveAllActiveAsesSameState(AseState state) { log::verbose("{}", address_); auto iter = std::find_if(ases_.begin(), ases_.end(), [&state](const auto& ase) { log::verbose("ASE id: {}, active: {}, state: {}", ase.id, ase.active, bluetooth::common::ToString(ase.state)); return ase.active && (ase.state != state); }); return iter == ases_.end(); } bool LeAudioDevice::HaveAllActiveAsesSameDataPathState(types::DataPathState state) const { log::verbose("{}", address_); auto iter = std::find_if(ases_.begin(), ases_.end(), [&state](const auto& ase) { log::verbose("ASE id: {}, active: {}, state: {}", ase.id, ase.active, bluetooth::common::ToString(ase.data_path_state)); return ase.active && (ase.data_path_state != state); }); return iter == ases_.end(); } bool LeAudioDevice::IsReadyToCreateStream(void) { log::verbose("{}", address_); auto iter = std::find_if(ases_.begin(), ases_.end(), [](const auto& ase) { if (!ase.active) { return false; } log::verbose("ASE id: {}, state: {}, direction: {}", ase.id, bluetooth::common::ToString(ase.state), ase.direction); if (ase.direction == types::kLeAudioDirectionSink && (ase.state != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING && ase.state != AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING)) { return true; } if (ase.direction == types::kLeAudioDirectionSource && ase.state != AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING) { return true; } return false; }); return iter == ases_.end(); } bool LeAudioDevice::IsReadyToSuspendStream(void) { auto iter = std::find_if(ases_.begin(), ases_.end(), [](const auto& ase) { if (!ase.active) { return false; } if (ase.direction == types::kLeAudioDirectionSink && ase.state != AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED) { return true; } if (ase.direction == types::kLeAudioDirectionSource && ase.state != AseState::BTA_LE_AUDIO_ASE_STATE_DISABLING) { return true; } return false; }); return iter == ases_.end(); } bool LeAudioDevice::HaveAllActiveAsesCisEst(void) const { if (ases_.empty()) { log::warn("No ases for device {}", address_); /* If there is no ASEs at all, it means we are good here - meaning, it is * not waiting for any CIS to be established. */ return true; } log::verbose("{}", address_); bool has_active_ase = false; auto iter = std::find_if(ases_.begin(), ases_.end(), [&](const auto& ase) { if (!has_active_ase && ase.active) { has_active_ase = true; } log::verbose("ASE id: {}, cis_state: {}, direction: {}", ase.id, bluetooth::common::ToString(ase.cis_state), ase.direction); return ase.active && (ase.cis_state != CisState::CONNECTED); }); return iter == ases_.end() && has_active_ase; } bool LeAudioDevice::HaveAnyCisConnected(void) { /* Pending and Disconnecting is considered as connected in this function */ for (auto const ase : ases_) { if (ase.cis_state == CisState::CONNECTED || ase.cis_state == CisState::CONNECTING || ase.cis_state == CisState::DISCONNECTING) { return true; } } return false; } uint8_t LeAudioDevice::GetSupportedAudioChannelCounts(uint8_t direction) const { auto& pacs = direction == types::kLeAudioDirectionSink ? snk_pacs_ : src_pacs_; if (pacs.size() == 0) { log::error("{}, missing PAC for direction {}", address_, direction); return 0; } for (const auto& pac_tuple : pacs) { /* Get PAC records from tuple as second element from tuple */ auto& pac_recs = std::get<1>(pac_tuple); for (const auto pac : pac_recs) { if (!utils::IsCodecUsingLtvFormat(pac.codec_id)) { log::warn(" {} Unknown codec PAC record for codec: {}", address_, bluetooth::common::ToString(pac.codec_id)); continue; } log::assert_that(!pac.codec_spec_caps.IsEmpty(), "Codec specific capabilities are not parsed appropriately."); auto supported_channel_count_ltv = pac.codec_spec_caps.Find(codec_spec_caps::kLeAudioLtvTypeSupportedAudioChannelCounts); if (supported_channel_count_ltv == std::nullopt || supported_channel_count_ltv->size() == 0L) { return 1; } return VEC_UINT8_TO_UINT8(supported_channel_count_ltv.value()); }; } return 0; } /** * Returns supported PHY's bitfield */ uint8_t LeAudioDevice::GetPhyBitmask(void) const { uint8_t phy_bitfield = kIsoCigPhy1M; if (get_btm_client_interface().peer.BTM_IsPhy2mSupported(address_, BT_TRANSPORT_LE)) { phy_bitfield |= kIsoCigPhy2M; } return phy_bitfield; } void LeAudioDevice::PrintDebugState(void) { std::stringstream debug_str; debug_str << " Address: " << address_ << ", " << bluetooth::common::ToString(connection_state_) << ", conn_id: " << +conn_id_ << ", mtu: " << +mtu_ << ", num_of_ase: " << static_cast(ases_.size()); if (ases_.size() > 0) { debug_str << "\n == ASEs == "; for (auto& ase : ases_) { debug_str << " id: " << +ase.id << ", active: " << ase.active << ", dir: " << (ase.direction == types::kLeAudioDirectionSink ? "sink" : "source") << ", state: " << bluetooth::common::ToString(ase.state) << ", cis_id: " << +ase.cis_id << ", cis_handle: " << +ase.cis_conn_hdl << ", cis_state: " << bluetooth::common::ToString(ase.cis_state) << ", data_path_state: " << bluetooth::common::ToString(ase.data_path_state) << "\n ase max_latency: " << +ase.qos_config.max_transport_latency << ", rtn: " << +ase.qos_config.retrans_nb << ", max_sdu: " << +ase.qos_config.max_sdu_size << ", sdu_interval: " << +ase.qos_config.sdu_interval << ", presentation_delay: " << +ase.qos_config.presentation_delay << ", framing: " << +ase.qos_config.framing << ", phy: " << +ase.qos_config.phy << ", target latency: " << +ase.target_latency << ", reconfigure: " << ase.reconfigure << "\n\n"; } } log::info("{}", debug_str.str()); } uint8_t LeAudioDevice::GetPreferredPhyBitmask(uint8_t preferred_phy) const { // Start with full local phy support uint8_t phy_bitmask = bluetooth::hci::kIsoCigPhy1M; if (bluetooth::shim::GetController()->SupportsBle2mPhy()) { phy_bitmask |= bluetooth::hci::kIsoCigPhy2M; } if (bluetooth::shim::GetController()->SupportsBleCodedPhy()) { phy_bitmask |= bluetooth::hci::kIsoCigPhyC; } // Check against the remote device support phy_bitmask &= GetPhyBitmask(); // Take the preferences if possible if (preferred_phy && (phy_bitmask & preferred_phy)) { phy_bitmask &= preferred_phy; log::debug("{}, using ASE preferred phy 0x{:02x}", address_, static_cast(phy_bitmask)); } else { log::warn( " {}, ASE preferred 0x{:02x} has nothing common with phy_bitfield " "0x{:02x}", address_, static_cast(preferred_phy), static_cast(phy_bitmask)); } return phy_bitmask; } void LeAudioDevice::DumpPacsDebugState(std::stringstream& stream, types::PublishedAudioCapabilities pacs) { if (pacs.size() > 0) { for (auto& pac : pacs) { stream << "\t • Value handle: " << loghex(std::get<0>(pac).val_hdl) << ", CCC handle: " << loghex(std::get<0>(pac).ccc_hdl); for (auto& record : std::get<1>(pac)) { stream << "\n\t\t· CodecId (Coding format: " << loghex(record.codec_id.coding_format) << ", Vendor company ID: " << loghex(record.codec_id.vendor_company_id) << ", Vendor codec ID: " << loghex(record.codec_id.vendor_codec_id) << ")"; stream << "\n\t\t Codec specific capabilities:\n"; if (utils::IsCodecUsingLtvFormat(record.codec_id)) { stream << record.codec_spec_caps.ToString("\t\t\t", types::CodecCapabilitiesLtvFormat); } else { stream << "\t\t\t" << base::HexEncode(record.codec_spec_caps_raw.data(), record.codec_spec_caps_raw.size()) << "\n"; } stream << "\t\t Metadata: " << base::HexEncode(record.metadata.data(), record.metadata.size()); } stream << "\n"; } } } void LeAudioDevice::DumpPacsDebugState(std::stringstream& stream) { stream << " ● Device PACS, address: " << ADDRESS_TO_LOGGABLE_STR(address_) << "\n"; stream << "\t == Sink PACs:\n"; DumpPacsDebugState(stream, snk_pacs_); stream << "\t == Source PACs:\n"; DumpPacsDebugState(stream, src_pacs_); } static std::string locationToString(uint32_t location) { if (location & codec_spec_conf::kLeAudioLocationAnyLeft && location & codec_spec_conf::kLeAudioLocationAnyRight) { return "left/right"; } else if (location & codec_spec_conf::kLeAudioLocationAnyLeft) { return "left"; } else if (location & codec_spec_conf::kLeAudioLocationAnyRight) { return "right"; } else if (location == codec_spec_conf::kLeAudioLocationMonoAudio) { return "mono"; } return "unknown location"; } void LeAudioDevice::Dump(std::stringstream& stream) { uint16_t acl_handle = get_btm_client_interface().peer.BTM_GetHCIConnHandle(address_, BT_TRANSPORT_LE); std::string snk_location = locationToString(snk_audio_locations_.to_ulong()); std::string src_location = locationToString(src_audio_locations_.to_ulong()); stream << " ● Device address: " << ADDRESS_TO_LOGGABLE_STR(address_) << ", " << connection_state_ << ", conn_id: " << (conn_id_ == GATT_INVALID_CONN_ID ? "-1" : std::to_string(conn_id_)) << ", acl_handle: " << std::to_string(acl_handle) << ", snk_location: " << snk_location << ", src_location: " << src_location << ", mtu: " << std::to_string(mtu_) << ", " << (encrypted_ ? "Encrypted" : "Unecrypted") << "\n\t Sink avail. contexts: " << common::ToString(avail_contexts_.sink) << "\n\t Source avail. contexts: " << common::ToString(avail_contexts_.source) << "\n"; if (gmap_client_ != nullptr) { stream << "\t "; gmap_client_->DebugDump(stream); } else { stream << "\t "; stream << "GmapClient not initialized\n"; } if (ases_.size() > 0) { stream << "\t == ASEs (" << static_cast(ases_.size()) << "):\n"; stream << "\t id active dir cis_id cis_handle sdu latency rtn " "cis_state data_path_state\n"; for (auto& ase : ases_) { stream << std::setfill('\x20') << "\t " << std::left << std::setw(4) << static_cast(ase.id) << std::left << std::setw(7) << (ase.active ? "true" : "false") << std::left << std::setw(8) << (ase.direction == types::kLeAudioDirectionSink ? "sink" : "source") << std::left << std::setw(8) << static_cast(ase.cis_id) << std::left << std::setw(12) << ase.cis_conn_hdl << std::left << std::setw(5) << ase.qos_config.max_sdu_size << std::left << std::setw(8) << ase.qos_config.max_transport_latency << std::left << std::setw(5) << static_cast(ase.qos_config.retrans_nb) << std::left << std::setw(21) << bluetooth::common::ToString(ase.cis_state) << std::setw(19) << bluetooth::common::ToString(ase.data_path_state) << "\n"; } } } void LeAudioDevice::DisconnectAcl(void) { if (conn_id_ == GATT_INVALID_CONN_ID) { return; } uint16_t acl_handle = get_btm_client_interface().peer.BTM_GetHCIConnHandle(address_, BT_TRANSPORT_LE); if (acl_handle != HCI_INVALID_HANDLE) { acl_disconnect_from_handle(acl_handle, HCI_ERR_PEER_USER, "bta::bluetooth::le_audio::client disconnect"); } } void LeAudioDevice::SetAvailableContexts(BidirectionalPair contexts) { log::debug( "{}: \n\t previous_contexts_.sink: {} \n\t previous_contexts_.source: {} " " " "\n\t new_contexts.sink: {} \n\t new_contexts.source: {} \n\t", address_, avail_contexts_.sink.to_string(), avail_contexts_.source.to_string(), contexts.sink.to_string(), contexts.source.to_string()); avail_contexts_.sink = contexts.sink; avail_contexts_.source = contexts.source; } void LeAudioDevice::SetMetadataToAse(struct types::ase* ase, const AudioContexts& metadata_context_types, const std::vector& ccid_lists) { /* Filter multidirectional audio context for each ase direction */ auto directional_audio_context = metadata_context_types & GetAvailableContexts(ase->direction); if (directional_audio_context.any()) { ase->metadata = GetMetadata(directional_audio_context, ccid_lists); } else { ase->metadata = GetMetadata(AudioContexts(LeAudioContextType::UNSPECIFIED), std::vector()); } } bool LeAudioDevice::ActivateConfiguredAses( LeAudioContextType context_type, const BidirectionalPair& metadata_context_types, BidirectionalPair> ccid_lists) { if (conn_id_ == GATT_INVALID_CONN_ID) { log::warn("Device {} is not connected", address_); return false; } bool ret = false; log::info("Configuring device {}", address_); for (auto& ase : ases_) { if (ase.state == AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED && ase.configured_for_context_type == context_type) { log::info( "conn_id: {}, ase id {}, cis id {}, cis_handle 0x{:04x} is " "activated.", conn_id_, ase.id, ase.cis_id, ase.cis_conn_hdl); ase.active = true; ret = true; /* update metadata */ SetMetadataToAse(&ase, metadata_context_types.get(ase.direction), ccid_lists.get(ase.direction)); } } return ret; } void LeAudioDevice::DeactivateAllAses(void) { for (auto& ase : ases_) { if (ase.active == false && ase.cis_state != CisState::IDLE && ase.data_path_state != DataPathState::IDLE) { log::warn( "{}, ase_id: {}, ase.cis_id: {}, cis_handle: 0x{:02x}, " "ase.cis_state={}, ase.data_path_state={}", address_, ase.id, ase.cis_id, ase.cis_conn_hdl, bluetooth::common::ToString(ase.cis_state), bluetooth::common::ToString(ase.data_path_state)); } log::verbose("{}, ase_id {}", address_, ase.id); ase.state = AseState::BTA_LE_AUDIO_ASE_STATE_IDLE; ase.cis_state = CisState::IDLE; ase.data_path_state = DataPathState::IDLE; ase.active = false; ase.reconfigure = 0; ase.cis_id = bluetooth::le_audio::kInvalidCisId; ase.cis_conn_hdl = bluetooth::le_audio::kInvalidCisConnHandle; } } std::vector LeAudioDevice::GetMetadata(AudioContexts context_type, const std::vector& ccid_list) { std::vector metadata; AppendMetadataLtvEntryForStreamingContext(metadata, context_type); AppendMetadataLtvEntryForCcidList(metadata, ccid_list); return metadata; } bool LeAudioDevice::IsMetadataChanged(const BidirectionalPair& context_types, const BidirectionalPair>& ccid_lists) { for (auto* ase = this->GetFirstActiveAse(); ase; ase = this->GetNextActiveAse(ase)) { if (this->GetMetadata(context_types.get(ase->direction), ccid_lists.get(ase->direction)) != ase->metadata) { return true; } } return false; } void LeAudioDevice::GetDeviceModelName(void) { bt_property_t prop_name; bt_bdname_t prop_value = {0}; // Retrieve model name from storage BTIF_STORAGE_FILL_PROPERTY(&prop_name, BT_PROPERTY_REMOTE_MODEL_NUM, sizeof(bt_bdname_t), &prop_value); if (btif_storage_get_remote_device_property(&address_, &prop_name) == BT_STATUS_SUCCESS) { model_name_.assign((char*)prop_value.name); } } void LeAudioDevice::UpdateDeviceAllowlistFlag(void) { char allow_list[PROPERTY_VALUE_MAX] = {0}; GetDeviceModelName(); osi_property_get(kLeAudioDeviceAllowListProp, allow_list, ""); if (allow_list[0] == '\0' || model_name_ == "") { // if device allow list is empty or no remote model name available // return allowlist_flag_ as default false return; } std::istringstream stream(allow_list); std::string token; while (std::getline(stream, token, ',')) { if (token.compare(model_name_) == 0) { allowlist_flag_ = true; return; } } } DsaModes LeAudioDevice::GetDsaModes(void) { return dsa_.modes; } bool LeAudioDevice::DsaReducedSduSizeSupported() { return dsa_.reduced_sdu; } types::DataPathState LeAudioDevice::GetDsaDataPathState(void) { return dsa_.state; } void LeAudioDevice::SetDsaDataPathState(types::DataPathState state) { dsa_.state = state; } uint16_t LeAudioDevice::GetDsaCisHandle(void) { return dsa_.cis_handle; } void LeAudioDevice::SetDsaCisHandle(uint16_t cis_handle) { dsa_.cis_handle = cis_handle; } /* LeAudioDevices Class methods implementation */ void LeAudioDevices::Add(const RawAddress& address, DeviceConnectState state, int group_id) { auto device = FindByAddress(address); if (device != nullptr) { log::error("address: {} is already assigned to group: {}", address, device->group_id_); return; } leAudioDevices_.emplace_back(std::make_shared(address, state, group_id)); } void LeAudioDevices::Remove(const RawAddress& address) { auto iter = std::find_if( leAudioDevices_.begin(), leAudioDevices_.end(), [&address](auto const& leAudioDevice) { return leAudioDevice->address_ == address; }); if (iter == leAudioDevices_.end()) { log::error("no such address: {}", address); return; } leAudioDevices_.erase(iter); } LeAudioDevice* LeAudioDevices::FindByAddress(const RawAddress& address) const { auto iter = std::find_if( leAudioDevices_.begin(), leAudioDevices_.end(), [&address](auto const& leAudioDevice) { return leAudioDevice->address_ == address; }); return (iter == leAudioDevices_.end()) ? nullptr : iter->get(); } std::shared_ptr LeAudioDevices::GetByAddress(const RawAddress& address) const { auto iter = std::find_if( leAudioDevices_.begin(), leAudioDevices_.end(), [&address](auto const& leAudioDevice) { return leAudioDevice->address_ == address; }); return (iter == leAudioDevices_.end()) ? nullptr : *iter; } LeAudioDevice* LeAudioDevices::FindByConnId(tCONN_ID conn_id) const { auto iter = std::find_if( leAudioDevices_.begin(), leAudioDevices_.end(), [&conn_id](auto const& leAudioDevice) { return leAudioDevice->conn_id_ == conn_id; }); return (iter == leAudioDevices_.end()) ? nullptr : iter->get(); } LeAudioDevice* LeAudioDevices::FindByCisConnHdl(uint8_t cig_id, uint16_t conn_hdl) const { auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), [&conn_hdl, &cig_id](auto& d) { LeAudioDevice* dev; BidirectionalPair ases; dev = d.get(); if (dev->group_id_ != cig_id) { return false; } ases = dev->GetAsesByCisConnHdl(conn_hdl); if (ases.sink || ases.source) { return true; } else { return false; } }); if (iter == leAudioDevices_.end()) { return nullptr; } return iter->get(); } void LeAudioDevices::SetInitialGroupAutoconnectState(int group_id, int gatt_if, tBTM_BLE_CONN_TYPE /*reconnection_mode*/, bool current_dev_autoconnect_flag) { if (!current_dev_autoconnect_flag) { /* If current device autoconnect flag is false, check if there is other * device in the group which is in autoconnect mode. * If yes, assume whole group is in autoconnect. */ auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), [&group_id](auto& d) { LeAudioDevice* dev; dev = d.get(); if (dev->group_id_ != group_id) { return false; } return dev->autoconnect_flag_; }); current_dev_autoconnect_flag = !(iter == leAudioDevices_.end()); } if (!current_dev_autoconnect_flag) { return; } /* This function is called when bluetooth started, therefore here we will * try direct connection, if that failes, we fallback to background connection */ for (auto dev : leAudioDevices_) { if ((dev->group_id_ == group_id) && (dev->GetConnectionState() == DeviceConnectState::DISCONNECTED)) { dev->SetConnectionState(DeviceConnectState::CONNECTING_AUTOCONNECT); dev->autoconnect_flag_ = true; btif_storage_set_leaudio_autoconnect(dev->address_, true); BTA_GATTC_Open(gatt_if, dev->address_, BTM_BLE_DIRECT_CONNECTION, false); } } } size_t LeAudioDevices::Size() const { return leAudioDevices_.size(); } void LeAudioDevices::Dump(std::stringstream& stream, int group_id) const { for (auto const& device : leAudioDevices_) { if (device->group_id_ == group_id) { device->Dump(stream); stream << "\tAddress: " << device->address_ << "\n"; device->DumpPacsDebugState(stream); stream << "\n"; } } } void LeAudioDevices::Cleanup(tGATT_IF client_if) { for (auto const& device : leAudioDevices_) { auto connection_state = device->GetConnectionState(); if (connection_state == DeviceConnectState::DISCONNECTED || connection_state == DeviceConnectState::DISCONNECTING) { continue; } // For connecting or connected device always remove background connect BTA_GATTC_CancelOpen(client_if, device->address_, false); if (connection_state == DeviceConnectState::CONNECTING_BY_USER) { // When connecting by user, remove direct connect BTA_GATTC_CancelOpen(client_if, device->address_, true); } else if (connection_state != DeviceConnectState::CONNECTING_AUTOCONNECT) { // If connected, close the connection BtaGattQueue::Clean(device->conn_id_); BTA_GATTC_Close(device->conn_id_); device->DisconnectAcl(); } } leAudioDevices_.clear(); } } // namespace bluetooth::le_audio