| /* |
| * 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 <base/strings/string_number_conversions.h> |
| |
| #include <map> |
| |
| #include "bta_gatt_queue.h" |
| #include "bta_groups.h" |
| #include "bta_le_audio_api.h" |
| #include "btm_iso_api.h" |
| #include "btm_iso_api_types.h" |
| #include "client_audio.h" |
| #include "device/include/controller.h" |
| #include "stack/btm/btm_int.h" |
| |
| using bluetooth::hci::kIsoCigFramingFramed; |
| using bluetooth::hci::kIsoCigFramingUnframed; |
| using bluetooth::hci::kIsoCigPackingSequential; |
| using bluetooth::hci::kIsoCigPhy1M; |
| using bluetooth::hci::kIsoCigPhy2M; |
| using bluetooth::hci::iso_manager::kIsoSca0To20Ppm; |
| using le_audio::set_configurations::CodecCapabilitySetting; |
| 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::BidirectAsesPair; |
| using le_audio::types::LeAudioCodecId; |
| using le_audio::types::LeAudioContextType; |
| using le_audio::types::LeAudioLc3Config; |
| |
| namespace le_audio { |
| /* LeAudioDeviceGroup Class methods implementation */ |
| void LeAudioDeviceGroup::AddNode( |
| const std::shared_ptr<LeAudioDevice>& leAudioDevice) { |
| leAudioDevice->group_id_ = group_id_; |
| leAudioDevices_.push_back(std::weak_ptr<LeAudioDevice>(leAudioDevice)); |
| } |
| |
| void LeAudioDeviceGroup::RemoveNode( |
| const std::shared_ptr<LeAudioDevice>& leAudioDevice) { |
| leAudioDevice->group_id_ = bluetooth::groups::kGroupUnknown; |
| leAudioDevices_.erase( |
| std::remove_if( |
| leAudioDevices_.begin(), leAudioDevices_.end(), |
| [&leAudioDevice](auto& d) { return d.lock() == leAudioDevice; }), |
| leAudioDevices_.end()); |
| } |
| |
| bool LeAudioDeviceGroup::IsEmpty(void) { return leAudioDevices_.size() == 0; } |
| |
| bool LeAudioDeviceGroup::IsAnyDeviceConnected(void) { |
| return (NumOfConnected() != 0); |
| } |
| |
| int LeAudioDeviceGroup::Size(void) { return leAudioDevices_.size(); } |
| |
| int LeAudioDeviceGroup::NumOfConnected(types::LeAudioContextType context_type) { |
| if (leAudioDevices_.empty()) return 0; |
| |
| bool check_context_type = (context_type != LeAudioContextType::RFU); |
| AudioContexts type_set = static_cast<uint16_t>(context_type); |
| |
| /* return number of connected devices from the set*/ |
| return std::count_if( |
| leAudioDevices_.begin(), leAudioDevices_.end(), |
| [type_set, check_context_type](auto& iter) { |
| if (iter.expired()) return false; |
| if (iter.lock()->conn_id_ == GATT_INVALID_CONN_ID) return false; |
| |
| if (!check_context_type) return true; |
| |
| return (iter.lock()->GetAvailableContexts() & type_set).any(); |
| }); |
| } |
| |
| void LeAudioDeviceGroup::Cleanup(void) { leAudioDevices_.clear(); } |
| |
| void LeAudioDeviceGroup::Deactivate(void) { |
| for (auto* leAudioDevice = GetFirstActiveDevice(); leAudioDevice; |
| leAudioDevice = GetNextActiveDevice(leAudioDevice)) { |
| for (auto* ase = leAudioDevice->GetFirstActiveAse(); ase; |
| ase = leAudioDevice->GetNextActiveAse(ase)) { |
| ase->active = false; |
| } |
| } |
| } |
| |
| LeAudioDevice* LeAudioDeviceGroup::GetFirstDevice(void) { |
| return (leAudioDevices_.front().lock()).get(); |
| } |
| |
| LeAudioDevice* LeAudioDeviceGroup::GetFirstDeviceWithActiveContext( |
| types::LeAudioContextType context_type) { |
| AudioContexts type_set = static_cast<uint16_t>(context_type); |
| |
| auto iter = std::find_if( |
| leAudioDevices_.begin(), leAudioDevices_.end(), [&type_set](auto& iter) { |
| if (iter.expired()) return false; |
| return (iter.lock()->GetAvailableContexts() & type_set).any(); |
| }); |
| |
| if ((iter == leAudioDevices_.end()) || (iter->expired())) return nullptr; |
| |
| return (iter->lock()).get(); |
| } |
| |
| LeAudioDevice* LeAudioDeviceGroup::GetNextDevice(LeAudioDevice* leAudioDevice) { |
| auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), |
| [&leAudioDevice](auto& d) { |
| if (d.expired()) |
| return false; |
| else |
| return (d.lock()).get() == leAudioDevice; |
| }); |
| |
| /* If reference device not found */ |
| if (iter == leAudioDevices_.end()) return nullptr; |
| |
| std::advance(iter, 1); |
| /* If reference device is last in group */ |
| if (iter == leAudioDevices_.end()) return nullptr; |
| |
| if (iter->expired()) return nullptr; |
| |
| return (iter->lock()).get(); |
| } |
| |
| LeAudioDevice* LeAudioDeviceGroup::GetNextDeviceWithActiveContext( |
| LeAudioDevice* leAudioDevice, types::LeAudioContextType context_type) { |
| AudioContexts type_set = static_cast<uint16_t>(context_type); |
| |
| auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), |
| [&leAudioDevice](auto& d) { |
| if (d.expired()) |
| return false; |
| else |
| return (d.lock()).get() == leAudioDevice; |
| }); |
| |
| /* If reference device not found */ |
| if (iter == leAudioDevices_.end()) return nullptr; |
| |
| std::advance(iter, 1); |
| /* If reference device is last in group */ |
| if (iter == leAudioDevices_.end()) return nullptr; |
| |
| iter = std::find_if(iter, leAudioDevices_.end(), [&type_set](auto& d) { |
| if (d.expired()) |
| return false; |
| else |
| return (d.lock()->GetAvailableContexts() & type_set).any(); |
| ; |
| }); |
| |
| return (iter == leAudioDevices_.end()) ? nullptr : (iter->lock()).get(); |
| } |
| |
| bool LeAudioDeviceGroup::IsDeviceInTheGroup(LeAudioDevice* leAudioDevice) { |
| auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), |
| [&leAudioDevice](auto& d) { |
| if (d.expired()) |
| return false; |
| else |
| return (d.lock()).get() == leAudioDevice; |
| }); |
| |
| if ((iter == leAudioDevices_.end()) || (iter->expired())) return false; |
| |
| return true; |
| } |
| |
| bool LeAudioDeviceGroup::HaveAllActiveDevicesAsesTheSameState(AseState state) { |
| auto iter = std::find_if( |
| leAudioDevices_.begin(), leAudioDevices_.end(), [&state](auto& d) { |
| if (d.expired()) |
| return false; |
| else |
| return !(((d.lock()).get())->HaveAllActiveAsesSameState(state)); |
| }); |
| |
| return iter == leAudioDevices_.end(); |
| } |
| |
| LeAudioDevice* LeAudioDeviceGroup::GetFirstActiveDevice(void) { |
| auto iter = |
| std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), [](auto& d) { |
| if (d.expired()) |
| return false; |
| else |
| return ((d.lock()).get())->HaveActiveAse(); |
| }); |
| |
| if (iter == leAudioDevices_.end() || iter->expired()) return nullptr; |
| |
| return (iter->lock()).get(); |
| } |
| |
| LeAudioDevice* LeAudioDeviceGroup::GetNextActiveDevice( |
| LeAudioDevice* leAudioDevice) { |
| auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), |
| [&leAudioDevice](auto& d) { |
| if (d.expired()) |
| return false; |
| else |
| return (d.lock()).get() == leAudioDevice; |
| }); |
| |
| if (iter == leAudioDevices_.end() || |
| std::distance(iter, leAudioDevices_.end()) < 1) |
| return nullptr; |
| |
| iter = std::find_if(std::next(iter, 1), leAudioDevices_.end(), [](auto& d) { |
| if (d.expired()) |
| return false; |
| else |
| return ((d.lock()).get())->HaveActiveAse(); |
| }); |
| |
| return (iter == leAudioDevices_.end()) ? nullptr : (iter->lock()).get(); |
| } |
| |
| bool LeAudioDeviceGroup::SetContextType(LeAudioContextType context_type) { |
| /* XXX: group context policy ? / may it disallow to change type ?) */ |
| context_type_ = context_type; |
| |
| return true; |
| } |
| |
| LeAudioContextType LeAudioDeviceGroup::GetContextType(void) { |
| return context_type_; |
| } |
| |
| uint32_t LeAudioDeviceGroup::GetSduInterval(uint8_t direction) { |
| for (LeAudioDevice* leAudioDevice = GetFirstActiveDevice(); |
| leAudioDevice != nullptr; |
| leAudioDevice = GetNextActiveDevice(leAudioDevice)) { |
| struct ase* ase = leAudioDevice->GetFirstActiveAseByDirection(direction); |
| if (!ase) continue; |
| |
| return ase->codec_config.GetFrameDurationUs(); |
| } |
| |
| return 0; |
| } |
| |
| uint8_t LeAudioDeviceGroup::GetSCA(void) { |
| uint8_t sca = kIsoSca0To20Ppm; |
| |
| for (const auto& leAudioDevice : leAudioDevices_) { |
| uint8_t dev_sca = |
| BTM_GetPeerSCA(leAudioDevice.lock()->address_, BT_TRANSPORT_LE); |
| |
| /* If we could not read SCA from the peer device or sca is 0, |
| * then there is no reason to continue. |
| */ |
| if ((dev_sca == 0xFF) || (dev_sca == 0)) return 0; |
| |
| /* The Slaves_Clock_Accuracy parameter shall be the worst-case sleep clock |
| *accuracy of all the slaves that will participate in the CIG. |
| */ |
| if (dev_sca < sca) { |
| sca = dev_sca; |
| } |
| } |
| |
| return sca; |
| } |
| |
| uint8_t LeAudioDeviceGroup::GetPacking(void) { |
| /* TODO: Decide about packing */ |
| return kIsoCigPackingSequential; |
| } |
| |
| uint8_t LeAudioDeviceGroup::GetFraming(void) { |
| LeAudioDevice* leAudioDevice = GetFirstActiveDevice(); |
| LOG_ASSERT(leAudioDevice) |
| << __func__ << " Shouldn't be called without an active device."; |
| |
| do { |
| struct ase* ase = leAudioDevice->GetFirstActiveAse(); |
| if (!ase) continue; |
| |
| do { |
| if (ase->framing == types::kFramingUnframedPduUnsupported) |
| return kIsoCigFramingFramed; |
| } while ((ase = leAudioDevice->GetNextActiveAse(ase))); |
| } while ((leAudioDevice = GetNextActiveDevice(leAudioDevice))); |
| |
| return kIsoCigFramingUnframed; |
| } |
| |
| uint8_t LeAudioDeviceGroup::GetTargetLatency(void) { |
| /* TODO: Decide about target latency */ |
| return types::kTargetLatencyBalancedLatencyReliability; |
| } |
| |
| /* TODO: Preferred parameter may be other than minimum */ |
| static uint16_t find_max_transport_latency(LeAudioDeviceGroup* group, |
| uint8_t direction) { |
| uint16_t max_transport_latency = 0; |
| |
| for (LeAudioDevice* leAudioDevice = group->GetFirstActiveDevice(); |
| leAudioDevice != nullptr; |
| leAudioDevice = group->GetNextActiveDevice(leAudioDevice)) { |
| for (ase* ase = leAudioDevice->GetFirstActiveAseByDirection(direction); |
| ase != nullptr; |
| ase = leAudioDevice->GetNextActiveAseWithSameDirection(ase)) { |
| if (!ase) break; |
| |
| if (!max_transport_latency) |
| // first assignment |
| max_transport_latency = ase->max_transport_latency; |
| else if (ase->max_transport_latency < max_transport_latency) |
| max_transport_latency = ase->max_transport_latency; |
| } |
| } |
| |
| if (max_transport_latency < types::kMaxTransportLatencyMin) |
| max_transport_latency = types::kMaxTransportLatencyMin; |
| else if (max_transport_latency > types::kMaxTransportLatencyMax) |
| max_transport_latency = types::kMaxTransportLatencyMax; |
| |
| return max_transport_latency; |
| } |
| |
| uint16_t LeAudioDeviceGroup::GetMaxTransportLatencyStom(void) { |
| return find_max_transport_latency(this, types::kLeAudioDirectionSource); |
| } |
| |
| uint16_t LeAudioDeviceGroup::GetMaxTransportLatencyMtos(void) { |
| return find_max_transport_latency(this, types::kLeAudioDirectionSink); |
| } |
| |
| uint16_t LeAudioDeviceGroup::GetTransportLatency(uint8_t direction) { |
| if (direction == types::kLeAudioDirectionSink) { |
| return transport_latency_mtos_; |
| } else if (direction == types::kLeAudioDirectionSource) { |
| return transport_latency_stom_; |
| } else { |
| LOG(ERROR) << __func__ << ", invalid direction"; |
| return 0; |
| } |
| } |
| |
| void LeAudioDeviceGroup::SetTransportLatency(uint8_t direction, |
| uint16_t new_transport_latency) { |
| uint16_t* transport_latency; |
| |
| if (direction == types::kLeAudioDirectionSink) { |
| transport_latency = &transport_latency_mtos_; |
| } else if (direction == types::kLeAudioDirectionSource) { |
| transport_latency = &transport_latency_stom_; |
| } else { |
| LOG(ERROR) << __func__ << ", invalid direction"; |
| return; |
| } |
| |
| if (*transport_latency == new_transport_latency) return; |
| |
| if ((*transport_latency != 0) && |
| (*transport_latency != new_transport_latency)) { |
| LOG(WARNING) << __func__ << ", Different transport latency for group: " |
| << " old: " << static_cast<int>(*transport_latency) |
| << " [ms], new: " << static_cast<int>(new_transport_latency) |
| << " [ms]"; |
| return; |
| } |
| |
| LOG(INFO) << __func__ << ", updated group " << static_cast<int>(group_id_) |
| << " transport latency: " << static_cast<int>(new_transport_latency) |
| << " [ms]"; |
| *transport_latency = new_transport_latency; |
| } |
| |
| uint8_t LeAudioDeviceGroup::GetPhyBitmask(uint8_t direction) { |
| LeAudioDevice* leAudioDevice = GetFirstActiveDevice(); |
| LOG_ASSERT(leAudioDevice) |
| << __func__ << " Shouldn't be called without an active device."; |
| |
| // local supported PHY's |
| uint8_t phy_bitfield = kIsoCigPhy1M; |
| if (controller_get_interface()->supports_ble_2m_phy()) |
| phy_bitfield |= kIsoCigPhy2M; |
| |
| if (!leAudioDevice) { |
| LOG(ERROR) << "No active leaudio device for direction?: " << +direction; |
| return phy_bitfield; |
| } |
| |
| do { |
| struct ase* ase = leAudioDevice->GetFirstActiveAseByDirection(direction); |
| if (!ase) return phy_bitfield; |
| |
| do { |
| if (direction == ase->direction) { |
| phy_bitfield &= leAudioDevice->GetPhyBitmask(); |
| |
| // A value of 0x00 denotes no preference |
| if (ase->preferred_phy) phy_bitfield &= ase->preferred_phy; |
| } |
| } while ((ase = leAudioDevice->GetNextActiveAseWithSameDirection(ase))); |
| } while ((leAudioDevice = GetNextActiveDevice(leAudioDevice))); |
| |
| return phy_bitfield; |
| } |
| |
| uint8_t LeAudioDeviceGroup::GetTargetPhy(uint8_t direction) { |
| uint8_t phy_bitfield = GetPhyBitmask(direction); |
| |
| // prefer to use 2M if supported |
| if (phy_bitfield & kIsoCigPhy2M) |
| return types::kTargetPhy2M; |
| else if (phy_bitfield & kIsoCigPhy1M) |
| return types::kTargetPhy1M; |
| else |
| return 0; |
| } |
| |
| bool LeAudioDeviceGroup::GetPresentationDelay(uint32_t* delay, |
| uint8_t direction) { |
| uint32_t delay_min = 0; |
| uint32_t delay_max = UINT32_MAX; |
| uint32_t preferred_delay_min = delay_min; |
| uint32_t preferred_delay_max = delay_max; |
| |
| LeAudioDevice* leAudioDevice = GetFirstActiveDevice(); |
| LOG_ASSERT(leAudioDevice) |
| << __func__ << " Shouldn't be called without an active device."; |
| |
| do { |
| struct ase* ase = leAudioDevice->GetFirstActiveAseByDirection(direction); |
| if (!ase) continue; // device has no active ASEs in this direction |
| |
| do { |
| /* No common range check */ |
| if (ase->pres_delay_min > delay_max || ase->pres_delay_max < delay_min) |
| return false; |
| |
| if (ase->pres_delay_min > delay_min) delay_min = ase->pres_delay_min; |
| if (ase->pres_delay_max < delay_max) delay_max = ase->pres_delay_max; |
| if (ase->preferred_pres_delay_min > preferred_delay_min) |
| preferred_delay_min = ase->preferred_pres_delay_min; |
| if (ase->preferred_pres_delay_max < preferred_delay_max && |
| ase->preferred_pres_delay_max != types::kPresDelayNoPreference) |
| preferred_delay_max = ase->preferred_pres_delay_max; |
| } while ((ase = leAudioDevice->GetNextActiveAseWithSameDirection(ase))); |
| } while ((leAudioDevice = GetNextActiveDevice(leAudioDevice))); |
| |
| if (preferred_delay_min <= preferred_delay_max && |
| preferred_delay_min > delay_min && preferred_delay_min < delay_max) { |
| *delay = preferred_delay_min; |
| } else { |
| *delay = delay_min; |
| } |
| |
| return true; |
| } |
| |
| uint16_t LeAudioDeviceGroup::GetRemoteDelay(uint8_t direction) { |
| uint16_t remote_delay_ms = 0; |
| uint32_t presentation_delay; |
| |
| if (!GetPresentationDelay(&presentation_delay, direction)) { |
| /* This should never happens at stream request time but to be safe return |
| * some sample value to not break streaming |
| */ |
| return 100; |
| } |
| |
| /* us to ms */ |
| remote_delay_ms = presentation_delay / 1000; |
| remote_delay_ms += GetTransportLatency(direction) / 1000; |
| |
| return remote_delay_ms; |
| } |
| |
| /* This method returns AudioContext value if support for any type has changed */ |
| std::optional<AudioContexts> LeAudioDeviceGroup::UpdateActiveContextsMap(void) { |
| DLOG(INFO) << __func__ << " group id: " << group_id_ << " active contexts: " |
| << loghex(active_contexts_mask_.to_ulong()); |
| return UpdateActiveContextsMap(active_contexts_mask_); |
| } |
| |
| /* This method returns AudioContext value if support for any type has changed */ |
| std::optional<AudioContexts> LeAudioDeviceGroup::UpdateActiveContextsMap( |
| AudioContexts update_contexts) { |
| AudioContexts contexts = 0x0000; |
| bool active_contexts_has_been_modified = false; |
| |
| for (LeAudioContextType ctx_type : types::kLeAudioContextAllTypesArray) { |
| AudioContexts type_set = static_cast<uint16_t>(ctx_type); |
| |
| if ((type_set & update_contexts).none()) { |
| /* Fill context bitset for possible returned value if updated */ |
| if (active_context_to_configuration_map.count(ctx_type) > 0) |
| contexts |= type_set; |
| |
| continue; |
| } |
| |
| auto new_conf = FindFirstSupportedConfiguration(ctx_type); |
| |
| /* Check if support for context type has changed */ |
| if (active_context_to_configuration_map.count(ctx_type) == 0 || |
| active_context_to_configuration_map[ctx_type] == nullptr) { |
| /* Current configuration for context type is empty */ |
| if (new_conf == nullptr) { |
| /* Configuration remains empty */ |
| continue; |
| } else { |
| /* Configuration changes from empty to some */ |
| contexts |= type_set; |
| active_contexts_has_been_modified = true; |
| } |
| } else { |
| /* Current configuration for context type is not empty */ |
| if (new_conf == nullptr) { |
| /* Configuration changed to empty */ |
| contexts &= ~type_set; |
| active_contexts_has_been_modified = true; |
| } else if (new_conf != active_context_to_configuration_map[ctx_type]) { |
| /* Configuration changed to any other */ |
| contexts |= type_set; |
| active_contexts_has_been_modified = true; |
| } else { |
| /* Configuration is the same */ |
| contexts |= type_set; |
| continue; |
| } |
| } |
| |
| LOG(INFO) << __func__ << ", updated context: " << loghex(int(ctx_type)) |
| << ", " |
| << (active_context_to_configuration_map[ctx_type] != nullptr |
| ? active_context_to_configuration_map[ctx_type]->name |
| : "empty") |
| << " -> " << (new_conf != nullptr ? new_conf->name : "empty"); |
| active_context_to_configuration_map[ctx_type] = new_conf; |
| } |
| |
| /* Some contexts have changed, return new active context bitset */ |
| if (active_contexts_has_been_modified) { |
| active_contexts_mask_ = contexts; |
| return contexts; |
| } |
| |
| /* Nothing has changed */ |
| return std::nullopt; |
| } |
| |
| bool LeAudioDeviceGroup::ReloadAudioLocations(void) { |
| AudioLocations updated_snk_audio_locations_ = |
| codec_spec_conf::kLeAudioLocationMonoUnspecified; |
| AudioLocations updated_src_audio_locations_ = |
| codec_spec_conf::kLeAudioLocationMonoUnspecified; |
| |
| for (const auto& device : leAudioDevices_) { |
| if (device.expired()) continue; |
| updated_snk_audio_locations_ |= device.lock().get()->snk_audio_locations_; |
| updated_src_audio_locations_ |= device.lock().get()->src_audio_locations_; |
| } |
| |
| /* Nothing has changed */ |
| if ((updated_snk_audio_locations_ == snk_audio_locations_) && |
| (updated_src_audio_locations_ == src_audio_locations_)) |
| return false; |
| |
| snk_audio_locations_ = updated_snk_audio_locations_; |
| src_audio_locations_ = updated_src_audio_locations_; |
| |
| return true; |
| } |
| |
| bool LeAudioDeviceGroup::IsInTransition(void) { |
| return target_state_ != current_state_; |
| } |
| |
| bool LeAudioDeviceGroup::IsReleasing(void) { |
| return target_state_ == AseState::BTA_LE_AUDIO_ASE_STATE_IDLE; |
| } |
| |
| bool LeAudioDeviceGroup::IsGroupStreamReady(void) { |
| auto iter = |
| std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), [](auto& d) { |
| if (d.expired()) |
| return false; |
| else |
| return !(((d.lock()).get())->HaveAllActiveAsesCisEst()); |
| }); |
| |
| return iter == leAudioDevices_.end(); |
| } |
| |
| bool LeAudioDeviceGroup::HaveAllActiveDevicesCisDisc(void) { |
| auto iter = |
| std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), [](auto& d) { |
| if (d.expired()) |
| return false; |
| else |
| return !(((d.lock()).get())->HaveAllAsesCisDisc()); |
| }); |
| |
| return iter == leAudioDevices_.end(); |
| } |
| |
| uint8_t LeAudioDeviceGroup::GetFirstFreeCisId(void) { |
| for (uint8_t id = 0; id < UINT8_MAX; id++) { |
| auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), |
| [id](auto& d) { |
| if (d.expired()) |
| return false; |
| else |
| return ((d.lock()).get())->HasCisId(id); |
| }); |
| |
| if (iter == leAudioDevices_.end()) return id; |
| } |
| |
| return kInvalidCisId; |
| } |
| |
| bool CheckIfStrategySupported(types::LeAudioConfigurationStrategy strategy, |
| types::AudioLocations audio_locations, |
| uint8_t requested_channel_count, |
| uint8_t channel_count_mask) { |
| DLOG(INFO) << __func__ << " strategy: " << (int)strategy |
| << " locations: " << +audio_locations.to_ulong(); |
| |
| switch (strategy) { |
| case types::LeAudioConfigurationStrategy::MONO_ONE_CIS_PER_DEVICE: |
| return audio_locations.any(); |
| case types::LeAudioConfigurationStrategy::STEREO_TWO_CISES_PER_DEVICE: |
| if ((audio_locations.to_ulong() & |
| codec_spec_conf::kLeAudioLocationAnyLeft) && |
| (audio_locations.to_ulong() & |
| codec_spec_conf::kLeAudioLocationAnyRight)) |
| return true; |
| else |
| return false; |
| case types::LeAudioConfigurationStrategy::STEREO_ONE_CIS_PER_DEVICE: |
| if (!(audio_locations.to_ulong() & |
| codec_spec_conf::kLeAudioLocationAnyLeft) || |
| !(audio_locations.to_ulong() & |
| codec_spec_conf::kLeAudioLocationAnyRight)) |
| return false; |
| |
| DLOG(INFO) << __func__ << " requested chan cnt " |
| << +requested_channel_count |
| << " chan mask: " << loghex(channel_count_mask); |
| |
| /* Return true if requested channel count is set in the channel count |
| * mask. In the channel_count_mask, bit0 is set when 1 channel is |
| * supported. |
| */ |
| return ((1 << (requested_channel_count - 1)) & channel_count_mask); |
| default: |
| return false; |
| } |
| |
| return false; |
| } |
| |
| /* This method check if group support given audio configuration |
| * requirement for connected devices in the group and available ASEs |
| * (no matter on the ASE state) and for given context type |
| */ |
| bool LeAudioDeviceGroup::IsConfigurationSupported( |
| const set_configurations::AudioSetConfiguration* audio_set_conf, |
| types::LeAudioContextType context_type) { |
| if (!set_configurations::check_if_may_cover_scenario( |
| audio_set_conf, NumOfConnected(context_type))) { |
| DLOG(INFO) << __func__ << " cannot cover scenario " |
| << static_cast<int>(context_type) |
| << " size of for context type: " |
| << +NumOfConnected(context_type); |
| return false; |
| } |
| |
| /* TODO For now: set ase if matching with first pac. |
| * 1) We assume as well that devices will match requirements in order |
| * e.g. 1 Device - 1 Requirement, 2 Device - 2 Requirement etc. |
| * 2) ASEs should be active only if best (according to priority list) full |
| * scenarion will be covered. |
| * 3) ASEs should be filled according to performance profile. |
| */ |
| for (const auto& ent : (*audio_set_conf).confs) { |
| DLOG(INFO) << __func__ |
| << " Looking for configuration: " << audio_set_conf->name |
| << " - " |
| << (ent.direction == types::kLeAudioDirectionSink ? "snk" |
| : "src"); |
| |
| uint8_t required_device_cnt = ent.device_cnt; |
| uint8_t max_required_ase_per_dev = |
| ent.ase_cnt / ent.device_cnt + (ent.ase_cnt % ent.device_cnt); |
| uint8_t active_ase_num = 0; |
| auto strategy = ent.strategy; |
| |
| DLOG(INFO) << __func__ << " Number of devices: " << +required_device_cnt |
| << " number of ASEs: " << +ent.ase_cnt |
| << " Max ASE per device: " << +max_required_ase_per_dev |
| << " strategy: " << static_cast<int>(strategy); |
| |
| for (auto* device = GetFirstDeviceWithActiveContext(context_type); |
| device != nullptr && required_device_cnt > 0; |
| device = GetNextDeviceWithActiveContext(device, context_type)) { |
| /* Skip if device has ASE configured in this direction already */ |
| |
| if (device->ases_.empty()) continue; |
| |
| if (!device->IsCodecConfigurationSupported(ent.direction, ent.codec)) |
| continue; |
| |
| int needed_ase = std::min(static_cast<int>(max_required_ase_per_dev), |
| static_cast<int>(ent.ase_cnt - active_ase_num)); |
| |
| /* If we required more ASEs per device which means we would like to |
| * create more CISes to one device, we should also check the allocation |
| * if it allows us to do this. |
| */ |
| |
| types::AudioLocations audio_locations = 0; |
| /* Check direction and if audio location allows to create more cise */ |
| if (ent.direction == types::kLeAudioDirectionSink) |
| audio_locations = device->snk_audio_locations_; |
| else |
| audio_locations = device->src_audio_locations_; |
| |
| /* TODO Make it no Lc3 specific */ |
| if (!CheckIfStrategySupported( |
| strategy, audio_locations, |
| std::get<LeAudioLc3Config>(ent.codec.config).GetChannelCount(), |
| device->GetLc3SupportedChannelCount(ent.direction))) { |
| DLOG(INFO) << __func__ << " insufficient device audio allocation: " |
| << audio_locations; |
| continue; |
| } |
| |
| for (auto& ase : device->ases_) { |
| if (ase.direction != ent.direction) continue; |
| |
| active_ase_num++; |
| needed_ase--; |
| |
| if (needed_ase == 0) break; |
| } |
| |
| required_device_cnt--; |
| } |
| |
| if (required_device_cnt > 0) { |
| /* Don't left any active devices if requirements are not met */ |
| DLOG(INFO) << __func__ << " could not configure all the devices"; |
| return false; |
| } |
| } |
| |
| DLOG(INFO) << "Choosed ASE Configuration for group: " << this->group_id_ |
| << " configuration: " << audio_set_conf->name; |
| return true; |
| } |
| |
| uint32_t GetFirstLeft(const types::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; |
| |
| LOG_ASSERT(0) << __func__ << " shall not happen"; |
| return 0; |
| } |
| |
| uint32_t GetFirstRight(const types::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; |
| |
| LOG_ASSERT(0) << __func__ << " shall not happen"; |
| return 0; |
| } |
| |
| uint32_t PickAudioLocation(types::LeAudioConfigurationStrategy strategy, |
| types::AudioLocations audio_locations, |
| types::AudioLocations* group_audio_locations) { |
| DLOG(INFO) << __func__ << " strategy: " << (int)strategy |
| << " locations: " << +audio_locations.to_ulong() |
| << " group locations: " << +group_audio_locations->to_ulong(); |
| |
| switch (strategy) { |
| case types::LeAudioConfigurationStrategy::MONO_ONE_CIS_PER_DEVICE: |
| case types::LeAudioConfigurationStrategy::STEREO_TWO_CISES_PER_DEVICE: |
| if ((audio_locations.to_ulong() & |
| codec_spec_conf::kLeAudioLocationAnyLeft) && |
| !(group_audio_locations->to_ulong() & |
| codec_spec_conf::kLeAudioLocationAnyLeft)) { |
| uint32_t left_location = GetFirstLeft(audio_locations); |
| *group_audio_locations |= left_location; |
| return left_location; |
| } |
| |
| if ((audio_locations.to_ulong() & |
| codec_spec_conf::kLeAudioLocationAnyRight) && |
| !(group_audio_locations->to_ulong() & |
| codec_spec_conf::kLeAudioLocationAnyRight)) { |
| uint32_t right_location = GetFirstRight(audio_locations); |
| *group_audio_locations |= right_location; |
| return right_location; |
| } |
| break; |
| case types::LeAudioConfigurationStrategy::STEREO_ONE_CIS_PER_DEVICE: |
| if ((audio_locations.to_ulong() & |
| codec_spec_conf::kLeAudioLocationAnyLeft) && |
| (audio_locations.to_ulong() & |
| codec_spec_conf::kLeAudioLocationAnyRight)) { |
| uint32_t left_location = GetFirstLeft(audio_locations); |
| uint32_t right_location = GetFirstRight(audio_locations); |
| *group_audio_locations |= left_location | right_location; |
| return left_location | right_location; |
| } |
| break; |
| default: |
| LOG_ASSERT(0) << " Shall never happen "; |
| return 0; |
| } |
| |
| LOG_ASSERT(0) << " Shall never happen "; |
| return 0; |
| } |
| |
| bool LeAudioDevice::ConfigureAses( |
| const le_audio::set_configurations::SetConfiguration& ent, |
| types::LeAudioContextType context_type, |
| uint8_t* number_of_already_active_group_ase, |
| types::AudioLocations& group_snk_audio_locations, |
| types::AudioLocations& group_src_audio_locations, bool reconnect) { |
| struct ase* ase = GetFirstInactiveAse(ent.direction, reconnect); |
| if (!ase) return false; |
| |
| uint8_t active_ases = *number_of_already_active_group_ase; |
| uint8_t max_required_ase_per_dev = |
| ent.ase_cnt / ent.device_cnt + (ent.ase_cnt % ent.device_cnt); |
| le_audio::types::LeAudioConfigurationStrategy strategy = ent.strategy; |
| |
| bool is_codec_supported = |
| IsCodecConfigurationSupported(ent.direction, ent.codec); |
| if (!is_codec_supported) return false; |
| |
| int needed_ase = std::min((int)(max_required_ase_per_dev), |
| (int)(ent.ase_cnt - active_ases)); |
| |
| types::AudioLocations audio_locations = 0; |
| types::AudioLocations* group_audio_locations; |
| /* Check direction and if audio location allows to create more cise */ |
| if (ent.direction == types::kLeAudioDirectionSink) { |
| audio_locations = snk_audio_locations_; |
| group_audio_locations = &group_snk_audio_locations; |
| } else { |
| audio_locations = src_audio_locations_; |
| group_audio_locations = &group_src_audio_locations; |
| } |
| |
| for (; needed_ase && ase; needed_ase--) { |
| ase->active = true; |
| active_ases++; |
| |
| if (ase->state == AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED) |
| ase->reconfigure = true; |
| |
| ase->codec_id = ent.codec.id; |
| /* TODO: find better way to not use LC3 explicitly */ |
| ase->codec_config = std::get<LeAudioLc3Config>(ent.codec.config); |
| |
| /*Let's choose audio channel allocation if not set */ |
| ase->codec_config.audio_channel_allocation = |
| PickAudioLocation(strategy, audio_locations, group_audio_locations); |
| |
| ase->max_sdu_size = codec_spec_caps::GetAudioChannelCounts( |
| ase->codec_config.audio_channel_allocation) * |
| ase->codec_config.octets_per_codec_frame; |
| ase->metadata = GetMetadata(context_type); |
| |
| DLOG(INFO) << __func__ << " device=" << address_ |
| << ", activated ASE id=" << +ase->id |
| << ", direction=" << +ase->direction |
| << ", max_sdu_size=" << +ase->max_sdu_size |
| << ", cis_id=" << +ase->cis_id; |
| |
| ase = GetFirstInactiveAse(ent.direction, reconnect); |
| } |
| |
| *number_of_already_active_group_ase = active_ases; |
| return true; |
| } |
| |
| /* This method should choose aproperiate ASEs to be active and set a cached |
| * configuration for codec and qos. |
| */ |
| bool LeAudioDeviceGroup::ConfigureAses( |
| const set_configurations::AudioSetConfiguration* audio_set_conf, |
| types::LeAudioContextType context_type) { |
| if (!set_configurations::check_if_may_cover_scenario( |
| audio_set_conf, NumOfConnected(context_type))) |
| return false; |
| |
| /* TODO For now: set ase if matching with first pac. |
| * 1) We assume as well that devices will match requirements in order |
| * e.g. 1 Device - 1 Requirement, 2 Device - 2 Requirement etc. |
| * 2) ASEs should be active only if best (according to priority list) full |
| * scenarion will be covered. |
| * 3) ASEs should be filled according to performance profile. |
| */ |
| |
| types::AudioLocations group_snk_audio_locations = 0; |
| types::AudioLocations group_src_audio_locations = 0; |
| |
| for (const auto& ent : (*audio_set_conf).confs) { |
| DLOG(INFO) << __func__ |
| << " Looking for requirements: " << audio_set_conf->name << " - " |
| << (ent.direction == 1 ? "snk" : "src"); |
| |
| uint8_t required_device_cnt = ent.device_cnt; |
| uint8_t max_required_ase_per_dev = |
| ent.ase_cnt / ent.device_cnt + (ent.ase_cnt % ent.device_cnt); |
| uint8_t active_ase_num = 0; |
| le_audio::types::LeAudioConfigurationStrategy strategy = ent.strategy; |
| |
| DLOG(INFO) << __func__ << " Number of devices: " << +required_device_cnt |
| << " number of ASEs: " << +ent.ase_cnt |
| << " Max ASE per device: " << +max_required_ase_per_dev |
| << " strategy: " << (int)strategy; |
| |
| for (auto* device = GetFirstDeviceWithActiveContext(context_type); |
| device != nullptr && required_device_cnt > 0; |
| device = GetNextDeviceWithActiveContext(device, context_type)) { |
| /* Skip if device has ASE configured in this direction already */ |
| if (device->GetFirstActiveAseByDirection(ent.direction)) continue; |
| |
| if (!device->ConfigureAses(ent, context_type, &active_ase_num, |
| group_snk_audio_locations, |
| group_src_audio_locations)) |
| continue; |
| |
| required_device_cnt--; |
| } |
| |
| if (required_device_cnt > 0) { |
| /* Don't left any active devices if requirements are not met */ |
| LOG(ERROR) << __func__ << " could not configure all the devices"; |
| Deactivate(); |
| return false; |
| } |
| } |
| |
| LOG(INFO) << "Choosed ASE Configuration for group: " << this->group_id_ |
| << " configuration: " << audio_set_conf->name; |
| |
| active_context_type_ = context_type; |
| return true; |
| } |
| |
| const set_configurations::AudioSetConfiguration* |
| LeAudioDeviceGroup::GetActiveConfiguration(void) { |
| return active_context_to_configuration_map[active_context_type_]; |
| } |
| AudioContexts LeAudioDeviceGroup::GetActiveContexts(void) { |
| return active_contexts_mask_; |
| } |
| |
| std::optional<LeAudioCodecConfiguration> |
| LeAudioDeviceGroup::GetCodecConfigurationByDirection( |
| types::LeAudioContextType group_context_type, uint8_t direction) { |
| const set_configurations::AudioSetConfiguration* audio_set_conf = |
| active_context_to_configuration_map[group_context_type]; |
| LeAudioCodecConfiguration group_config = {0, 0, 0, 0}; |
| if (!audio_set_conf) return std::nullopt; |
| |
| for (const auto& conf : audio_set_conf->confs) { |
| if (conf.direction != direction) continue; |
| |
| if (group_config.sample_rate != 0 && |
| conf.codec.GetConfigSamplingFrequency() != group_config.sample_rate) { |
| LOG(WARNING) << __func__ |
| << ", stream configuration could not be " |
| "determined (sampling frequency differs) for direction: " |
| << loghex(direction); |
| return std::nullopt; |
| } |
| group_config.sample_rate = conf.codec.GetConfigSamplingFrequency(); |
| |
| if (group_config.data_interval_us != 0 && |
| conf.codec.GetConfigDataIntervalUs() != group_config.data_interval_us) { |
| LOG(WARNING) << __func__ |
| << ", stream configuration could not be " |
| "determined (data interval differs) for direction: " |
| << loghex(direction); |
| return std::nullopt; |
| } |
| group_config.data_interval_us = conf.codec.GetConfigDataIntervalUs(); |
| |
| if (group_config.bits_per_sample != 0 && |
| conf.codec.GetConfigBitsPerSample() != group_config.bits_per_sample) { |
| LOG(WARNING) << __func__ |
| << ", stream configuration could not be " |
| "determined (bits per sample differs) for direction: " |
| << loghex(direction); |
| return std::nullopt; |
| } |
| group_config.bits_per_sample = conf.codec.GetConfigBitsPerSample(); |
| |
| group_config.num_channels += |
| conf.codec.GetConfigChannelCount() * conf.device_cnt; |
| } |
| |
| if (group_config.IsInvalid()) return std::nullopt; |
| |
| return group_config; |
| } |
| |
| bool LeAudioDeviceGroup::IsMetadataChanged( |
| types::LeAudioContextType context_type) { |
| for (auto* leAudioDevice = GetFirstActiveDevice(); leAudioDevice; |
| leAudioDevice = GetNextActiveDevice(leAudioDevice)) { |
| if (leAudioDevice->IsMetadataChanged(context_type)) return true; |
| } |
| |
| return false; |
| } |
| |
| types::LeAudioContextType LeAudioDeviceGroup::GetCurrentContextType(void) { |
| return active_context_type_; |
| } |
| |
| const set_configurations::AudioSetConfiguration* |
| LeAudioDeviceGroup::FindFirstSupportedConfiguration( |
| LeAudioContextType context_type) { |
| const set_configurations::AudioSetConfigurations* confs = |
| set_configurations::get_confs_by_type(context_type); |
| |
| DLOG(INFO) << __func__ << " context type: " << (int)context_type |
| << " number of connected devices: " << NumOfConnected(); |
| |
| /* Filter out device set for all scenarios */ |
| if (!set_configurations::check_if_may_cover_scenario(confs, |
| NumOfConnected())) { |
| LOG(ERROR) << __func__ << ", group is unable to cover scenario"; |
| return nullptr; |
| } |
| |
| /* Filter out device set for each end every scenario */ |
| |
| for (const auto& conf : *confs) { |
| if (IsConfigurationSupported(conf, context_type)) { |
| DLOG(INFO) << __func__ << " found: " << conf->name; |
| return conf; |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| /* This method should choose aproperiate ASEs to be active and set a cached |
| * configuration for codec and qos. |
| */ |
| bool LeAudioDeviceGroup::Configure(LeAudioContextType context_type) { |
| const set_configurations::AudioSetConfiguration* conf = |
| active_context_to_configuration_map[context_type]; |
| |
| DLOG(INFO) << __func__; |
| |
| if (!conf) { |
| LOG(ERROR) << __func__ << ", requested context type: " |
| << loghex(static_cast<uint16_t>(context_type)) |
| << ", is in mismatch with cached active contexts"; |
| return false; |
| } |
| |
| DLOG(INFO) << __func__ << " setting context type: " << int(context_type); |
| |
| if (!ConfigureAses(conf, context_type)) { |
| LOG(ERROR) << __func__ << ", requested pick ASE config context type: " |
| << loghex(static_cast<uint16_t>(context_type)) |
| << ", is in mismatch with cached active contexts"; |
| return false; |
| } |
| return true; |
| } |
| |
| LeAudioDeviceGroup::~LeAudioDeviceGroup(void) { this->Cleanup(); } |
| void LeAudioDeviceGroup::Dump(int fd) { |
| std::stringstream stream; |
| auto* active_conf = GetActiveConfiguration(); |
| |
| stream << " == Group id: " << group_id_ << " == \n" |
| << " state: " << GetState() << "\n" |
| << " target state: " << GetTargetState() << "\n" |
| << " number of devices: " << Size() << "\n" |
| << " number of connected devices: " << NumOfConnected() << "\n" |
| << " active context types: " |
| << loghex(GetActiveContexts().to_ulong()) << "\n" |
| << " current context type: " |
| << static_cast<int>(GetCurrentContextType()) << "\n" |
| << " active stream configuration name: " |
| << (active_conf ? active_conf->name : " not set") << "\n" |
| << " Last used stream configuration: \n" |
| << " valid: " << (stream_conf.valid ? " Yes " : " No") << "\n" |
| << " codec id : " << +(stream_conf.id.coding_format) << "\n" |
| << " name: " |
| << (stream_conf.conf != nullptr ? stream_conf.conf->name : " null ") |
| << "\n" |
| << " number of sinks in the configuration " |
| << stream_conf.sink_num_of_devices << "\n" |
| << " number of sink_streams connected: " |
| << stream_conf.sink_streams.size() << "\n" |
| << " number of sources in the configuration " |
| << stream_conf.source_num_of_devices << "\n" |
| << " number of source_streams connected: " |
| << stream_conf.source_streams.size() << "\n" |
| << " === devices: ===\n"; |
| |
| dprintf(fd, "%s", stream.str().c_str()); |
| |
| for (const auto& device_iter : leAudioDevices_) { |
| device_iter.lock()->Dump(fd); |
| } |
| } |
| |
| /* LeAudioDevice Class methods implementation */ |
| void LeAudioDevice::ClearPACs(void) { |
| snk_pacs_.clear(); |
| src_pacs_.clear(); |
| } |
| |
| LeAudioDevice::~LeAudioDevice(void) { |
| alarm_free(link_quality_timer); |
| this->ClearPACs(); |
| } |
| |
| void LeAudioDevice::RegisterPACs( |
| std::vector<struct types::acs_ac_record>* pac_db, |
| std::vector<struct types::acs_ac_record>* pac_recs) { |
| /* Clear PAC database for characteristic in case if re-read, indicated */ |
| if (!pac_db->empty()) { |
| DLOG(INFO) << __func__ << ", upgrade PACs for characteristic"; |
| pac_db->clear(); |
| } |
| |
| /* TODO wrap this logging part with debug flag */ |
| for (const struct types::acs_ac_record& pac : *pac_recs) { |
| LOG(INFO) << "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" |
| << pac.codec_spec_caps.ToString() << "\n\tMetadata: " |
| << base::HexEncode(pac.metadata.data(), pac.metadata.size()); |
| } |
| |
| 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); |
| } |
| |
| struct ase* LeAudioDevice::GetFirstInactiveAseWithState(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::GetFirstActiveAseByDataPathState( |
| types::AudioStreamDataPathState state) { |
| auto iter = |
| std::find_if(ases_.begin(), ases_.end(), [state](const auto& ase) { |
| return (ase.active && (ase.data_path_state == state)); |
| }); |
| |
| return (iter == ases_.end()) ? nullptr : &(*iter); |
| } |
| |
| struct ase* LeAudioDevice::GetFirstInactiveAse(uint8_t direction, |
| bool reconnect) { |
| auto iter = std::find_if(ases_.begin(), ases_.end(), |
| [direction, reconnect](const auto& ase) { |
| if (ase.active || (ase.direction != direction)) |
| return false; |
| |
| if (!reconnect) return true; |
| |
| return (ase.cis_id != kInvalidCisId); |
| }); |
| |
| 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); |
| } |
| |
| BidirectAsesPair LeAudioDevice::GetAsesByCisConnHdl(uint16_t conn_hdl) { |
| BidirectAsesPair ases; |
| |
| 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; |
| } |
| |
| BidirectAsesPair LeAudioDevice::GetAsesByCisId(uint8_t cis_id) { |
| BidirectAsesPair ases; |
| |
| 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::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) { |
| auto iter = std::find_if( |
| ases_.begin(), ases_.end(), |
| [&state](const auto& ase) { return ase.active && (ase.state != state); }); |
| |
| return iter == ases_.end(); |
| } |
| |
| bool LeAudioDevice::IsReadyToCreateStream(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_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) { |
| auto iter = std::find_if(ases_.begin(), ases_.end(), [](const auto& ase) { |
| return ase.active && |
| (ase.data_path_state != AudioStreamDataPathState::CIS_ESTABLISHED); |
| }); |
| |
| return iter == ases_.end(); |
| } |
| |
| bool LeAudioDevice::HaveAllAsesCisDisc(void) { |
| auto iter = std::find_if(ases_.begin(), ases_.end(), [](const auto& ase) { |
| return ase.active && |
| (ase.data_path_state != AudioStreamDataPathState::CIS_ASSIGNED); |
| }); |
| |
| return iter == ases_.end(); |
| } |
| |
| bool LeAudioDevice::HasCisId(uint8_t id) { |
| struct ase* ase = GetFirstActiveAse(); |
| |
| while (ase) { |
| if (ase->cis_id == id) return true; |
| ase = GetNextActiveAse(ase); |
| } |
| |
| return false; |
| } |
| |
| uint8_t LeAudioDevice::GetMatchingBidirectionCisId( |
| const struct types::ase* base_ase) { |
| for (auto& ase : ases_) { |
| auto& cis = ase.cis_id; |
| if (!ase.active) continue; |
| |
| int num_cises = |
| std::count_if(ases_.begin(), ases_.end(), [&cis](const auto& iter_ase) { |
| return iter_ase.active && iter_ase.cis_id == cis; |
| }); |
| |
| /* |
| * If there is only one ASE for device with unique CIS ID and opposite to |
| * direction - it may be bi-directional/completive. |
| */ |
| if (num_cises == 1 && |
| ((base_ase->direction == types::kLeAudioDirectionSink && |
| ase.direction == types::kLeAudioDirectionSource) || |
| (base_ase->direction == types::kLeAudioDirectionSource && |
| ase.direction == types::kLeAudioDirectionSink))) { |
| return ase.cis_id; |
| } |
| } |
| |
| return kInvalidCisId; |
| } |
| |
| uint8_t LeAudioDevice::GetLc3SupportedChannelCount(uint8_t direction) { |
| auto& pacs = |
| direction == types::kLeAudioDirectionSink ? snk_pacs_ : src_pacs_; |
| |
| if (pacs.size() == 0) { |
| LOG(ERROR) << __func__ << " missing PAC for direction " << +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 (pac.codec_id.coding_format != types::kLeAudioCodingFormatLC3) |
| continue; |
| |
| auto supported_channel_count_ltv = pac.codec_spec_caps.Find( |
| codec_spec_caps::kLeAudioCodecLC3TypeAudioChannelCounts); |
| |
| return VEC_UINT8_TO_UINT8(supported_channel_count_ltv.value()); |
| }; |
| } |
| |
| return 0; |
| } |
| |
| bool LeAudioDevice::IsCodecConfigurationSupported( |
| uint8_t direction, const CodecCapabilitySetting& codec_capability_setting) { |
| auto& pacs = |
| direction == types::kLeAudioDirectionSink ? snk_pacs_ : src_pacs_; |
| |
| if (pacs.size() == 0) { |
| LOG(ERROR) << __func__ << " missing PAC for direction " << +direction; |
| return false; |
| } |
| |
| /* TODO: Validate channel locations */ |
| |
| 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 (!IsCodecCapabilitySettingSupported(pac, codec_capability_setting)) |
| continue; |
| |
| return true; |
| }; |
| } |
| |
| /* Doesn't match required configuration with any PAC */ |
| return false; |
| } |
| |
| /** |
| * Returns supported PHY's bitfield |
| */ |
| uint8_t LeAudioDevice::GetPhyBitmask(void) { |
| uint8_t phy_bitfield = kIsoCigPhy1M; |
| |
| if (BTM_IsPhy2mSupported(address_, BT_TRANSPORT_LE)) |
| phy_bitfield |= kIsoCigPhy2M; |
| |
| return phy_bitfield; |
| } |
| |
| void LeAudioDevice::SetSupportedContexts(AudioContexts snk_contexts, |
| AudioContexts src_contexts) { |
| supp_snk_context_ = snk_contexts; |
| supp_src_context_ = src_contexts; |
| } |
| |
| void LeAudioDevice::Dump(int fd) { |
| std::stringstream stream; |
| stream << " address: " << address_ << "\n" |
| << (conn_id_ == GATT_INVALID_CONN_ID ? " Not connected " |
| : " Connected conn_id =") |
| << (conn_id_ == GATT_INVALID_CONN_ID ? "" : std::to_string(conn_id_)) |
| << "\n" |
| << " set member: " << (csis_member_ ? " Yes" : " No") << "\n" |
| << " known_service_handles_: " << known_service_handles_ |
| << "\n" |
| << " notify_connected_after_read_: " |
| << notify_connected_after_read_ << "\n" |
| << " removing_device_: " << removing_device_ << "\n" |
| << " first_connection_: " << first_connection_ << "\n" |
| << " encrypted_: " << encrypted_ << "\n" |
| << " connecting_actively_: " << connecting_actively_ << "\n"; |
| |
| dprintf(fd, "%s", stream.str().c_str()); |
| } |
| |
| AudioContexts LeAudioDevice::GetAvailableContexts(void) { |
| return avail_snk_contexts_ | avail_src_contexts_; |
| } |
| |
| /* Returns XOR of updated sink and source bitset context types */ |
| AudioContexts LeAudioDevice::SetAvailableContexts(AudioContexts snk_contexts, |
| AudioContexts src_contexts) { |
| AudioContexts updated_contexts; |
| |
| updated_contexts = snk_contexts ^ avail_snk_contexts_; |
| updated_contexts |= src_contexts ^ avail_src_contexts_; |
| |
| DLOG(INFO) << __func__ |
| << "\n\t avail_snk_contexts_: " << avail_snk_contexts_.to_string() |
| << "\n\t avail_src_contexts_: " << avail_src_contexts_.to_string() |
| << "\n\t snk_contexts:" << snk_contexts.to_string() |
| << "\n\t src_contexts: " << src_contexts.to_string() |
| << "\n\t updated_contexts: " << updated_contexts.to_string(); |
| |
| avail_snk_contexts_ = snk_contexts; |
| avail_src_contexts_ = src_contexts; |
| |
| return updated_contexts; |
| } |
| |
| void LeAudioDevice::DeactivateAllAses(void) { |
| /* Just clear states and keep previous configuration for use |
| * in case device will get reconnected |
| */ |
| for (auto& ase : ases_) { |
| if (ase.active) { |
| ase.state = AseState::BTA_LE_AUDIO_ASE_STATE_IDLE; |
| ase.data_path_state = AudioStreamDataPathState::IDLE; |
| ase.active = false; |
| } |
| } |
| } |
| |
| std::vector<uint8_t> LeAudioDevice::GetMetadata( |
| LeAudioContextType context_type) { |
| std::vector<uint8_t> metadata; |
| |
| AppendMetadataLtvEntryForStreamingContext(metadata, context_type); |
| AppendMetadataLtvEntryForCcidList(metadata, context_type); |
| |
| return std::move(metadata); |
| } |
| |
| bool LeAudioDevice::IsMetadataChanged(types::LeAudioContextType context_type) { |
| for (auto* ase = this->GetFirstActiveAse(); ase; |
| ase = this->GetNextActiveAse(ase)) { |
| if (this->GetMetadata(context_type) != ase->metadata) return true; |
| } |
| |
| return false; |
| } |
| |
| LeAudioDeviceGroup* LeAudioDeviceGroups::Add(int group_id) { |
| /* Get first free group id */ |
| if (FindById(group_id)) { |
| LOG(ERROR) << __func__ |
| << ", group already exists, id: " << loghex(group_id); |
| return nullptr; |
| } |
| |
| return (groups_.emplace_back(std::make_unique<LeAudioDeviceGroup>(group_id))) |
| .get(); |
| } |
| |
| void LeAudioDeviceGroups::Remove(int group_id) { |
| auto iter = std::find_if( |
| groups_.begin(), groups_.end(), |
| [&group_id](auto const& group) { return group->group_id_ == group_id; }); |
| |
| if (iter == groups_.end()) { |
| LOG(ERROR) << __func__ << ", no such group_id: " << group_id; |
| return; |
| } |
| |
| groups_.erase(iter); |
| } |
| |
| LeAudioDeviceGroup* LeAudioDeviceGroups::FindById(int group_id) { |
| auto iter = std::find_if( |
| groups_.begin(), groups_.end(), |
| [&group_id](auto const& group) { return group->group_id_ == group_id; }); |
| |
| return (iter == groups_.end()) ? nullptr : iter->get(); |
| } |
| |
| void LeAudioDeviceGroups::Cleanup(void) { |
| for (auto& g : groups_) { |
| g->Cleanup(); |
| } |
| |
| groups_.clear(); |
| } |
| |
| void LeAudioDeviceGroups::Dump(int fd) { |
| for (auto& g : groups_) { |
| g->Dump(fd); |
| } |
| } |
| |
| bool LeAudioDeviceGroups::IsAnyInTransition(void) { |
| for (auto& g : groups_) { |
| if (g->IsInTransition()) { |
| DLOG(INFO) << __func__ << " group: " << g->group_id_ |
| << " is in transition"; |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| size_t LeAudioDeviceGroups::Size() { return (groups_.size()); } |
| |
| std::vector<int> LeAudioDeviceGroups::GetGroupsIds(void) { |
| std::vector<int> result; |
| |
| for (auto const& group : groups_) { |
| result.push_back(group->group_id_); |
| } |
| |
| return result; |
| } |
| |
| /* LeAudioDevices Class methods implementation */ |
| void LeAudioDevices::Add(const RawAddress& address, bool first_connection, |
| int group_id) { |
| auto device = FindByAddress(address); |
| if (device != nullptr) { |
| LOG(ERROR) << __func__ << ", address: " << address |
| << " is already assigned to group: " << device->group_id_; |
| return; |
| } |
| |
| leAudioDevices_.emplace_back( |
| std::make_shared<LeAudioDevice>(address, first_connection, 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) << __func__ << ", no such address: " << address; |
| return; |
| } |
| |
| leAudioDevices_.erase(iter); |
| } |
| |
| LeAudioDevice* LeAudioDevices::FindByAddress(const RawAddress& address) { |
| 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<LeAudioDevice> LeAudioDevices::GetByAddress( |
| const RawAddress& address) { |
| 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(uint16_t conn_id) { |
| 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(const uint16_t conn_hdl) { |
| auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), |
| [&conn_hdl](auto& d) { |
| LeAudioDevice* dev; |
| BidirectAsesPair ases; |
| |
| dev = d.get(); |
| ases = dev->GetAsesByCisConnHdl(conn_hdl); |
| if (ases.sink || ases.source) |
| return true; |
| else |
| return false; |
| }); |
| |
| if (iter == leAudioDevices_.end()) return nullptr; |
| |
| return iter->get(); |
| } |
| |
| size_t LeAudioDevices::Size() { return (leAudioDevices_.size()); } |
| |
| void LeAudioDevices::Dump(int fd, int group_id) { |
| for (auto const& device : leAudioDevices_) { |
| if (device->group_id_ == group_id) { |
| device->Dump(fd); |
| } |
| } |
| } |
| |
| void LeAudioDevices::Cleanup(void) { leAudioDevices_.clear(); } |
| |
| } // namespace le_audio |