diff --git a/bluetooth/audio/aidl/default/LeAudioOffloadAudioProvider.cpp b/bluetooth/audio/aidl/default/LeAudioOffloadAudioProvider.cpp index 5002a1aeda..6783c0f0b7 100644 --- a/bluetooth/audio/aidl/default/LeAudioOffloadAudioProvider.cpp +++ b/bluetooth/audio/aidl/default/LeAudioOffloadAudioProvider.cpp @@ -30,6 +30,8 @@ namespace audio { constexpr uint8_t kLeAudioDirectionSink = 0x01; constexpr uint8_t kLeAudioDirectionSource = 0x02; +constexpr uint8_t kIsoDataPathHci = 0x00; +constexpr uint8_t kIsoDataPathPlatformDefault = 0x01; const std::map freq_to_support_bitmask_map = { @@ -85,6 +87,7 @@ const std::map std::map sampling_freq_map = { {16000, CodecSpecificConfigurationLtv::SamplingFrequency::HZ16000}, + {24000, CodecSpecificConfigurationLtv::SamplingFrequency::HZ24000}, {48000, CodecSpecificConfigurationLtv::SamplingFrequency::HZ48000}, {96000, CodecSpecificConfigurationLtv::SamplingFrequency::HZ96000}, }; @@ -175,12 +178,13 @@ bool LeAudioOffloadAudioProvider::filterCapabilitiesMatchedContext( if (!metadata.has_value()) continue; if (metadata.value().getTag() == MetadataLtv::Tag::preferredAudioContexts) { // Check all pref audio context to see if anything matched - auto& context = metadata.value() - .get() - .values; - if (setting_context.bitmask & context.bitmask) { + auto& prefer_context = + metadata.value() + .get() + .values; + if (setting_context.bitmask & prefer_context.bitmask) { // New mask with matched capability - setting_context.bitmask &= context.bitmask; + setting_context.bitmask &= prefer_context.bitmask; return true; } } @@ -219,8 +223,9 @@ bool LeAudioOffloadAudioProvider::isMatchedAudioChannel( /*cfg_channel*/, CodecSpecificCapabilitiesLtv::SupportedAudioChannelCounts& /*capability_channel*/) { + // Simply ignore. + // Later can use additional capabilities to match requirement. bool isMatched = true; - // TODO: how to match? return isMatched; } @@ -317,22 +322,34 @@ bool LeAudioOffloadAudioProvider::isCapabilitiesMatchedCodecConfiguration( return true; } -bool LeAudioOffloadAudioProvider::isMatchedAseConfiguration( - LeAudioAseConfiguration setting_cfg, - LeAudioAseConfiguration requirement_cfg) { +bool isMonoConfig( + CodecSpecificConfigurationLtv::AudioChannelAllocation allocation) { + auto channel_count = std::bitset<32>(allocation.bitmask); + return (channel_count.count() <= 1); +} + +bool LeAudioOffloadAudioProvider::filterMatchedAseConfiguration( + LeAudioAseConfiguration& setting_cfg, + const LeAudioAseConfiguration& requirement_cfg) { // Check matching for codec configuration <=> requirement ASE codec // Also match if no CodecId requirement if (requirement_cfg.codecId.has_value()) { if (!setting_cfg.codecId.has_value()) return false; if (!isMatchedValidCodec(setting_cfg.codecId.value(), requirement_cfg.codecId.value())) { + LOG(WARNING) << __func__ << ": Doesn't match valid codec, cfg = " + << setting_cfg.codecId.value().toString() + << ", req = " << requirement_cfg.codecId.value().toString(); return false; } } - if (requirement_cfg.targetLatency == - LeAudioAseConfiguration::TargetLatency::UNDEFINED || + if (requirement_cfg.targetLatency != + LeAudioAseConfiguration::TargetLatency::UNDEFINED && setting_cfg.targetLatency != requirement_cfg.targetLatency) { + LOG(WARNING) << __func__ << ": Doesn't match target latency, cfg = " + << int(setting_cfg.targetLatency) + << ", req = " << int(requirement_cfg.targetLatency); return false; } // Ignore PHY requirement @@ -346,11 +363,24 @@ bool LeAudioOffloadAudioProvider::isMatchedAseConfiguration( for (auto requirement_cfg : requirement_cfg.codecConfiguration) { // Directly compare CodecSpecificConfigurationLtv auto cfg = cfg_tag_map.find(requirement_cfg.getTag()); + // Config not found for this requirement, cannot match if (cfg == cfg_tag_map.end()) { + LOG(WARNING) << __func__ << ": Config not found for the requirement " + << requirement_cfg.toString(); return false; } + // Ignore matching for audio channel allocation + // since the rule is complicated. Match outside instead + if (requirement_cfg.getTag() == + CodecSpecificConfigurationLtv::Tag::audioChannelAllocation) + continue; + if (cfg->second != requirement_cfg) { + LOG(WARNING) << __func__ + << ": Config doesn't match the requirement, cfg = " + << cfg->second.toString() + << ", req = " << requirement_cfg.toString(); return false; } } @@ -395,35 +425,133 @@ void LeAudioOffloadAudioProvider::filterCapabilitiesAseDirectionConfiguration( } } +int getLeAudioAseConfigurationAllocationBitmask(LeAudioAseConfiguration cfg) { + for (auto cfg_ltv : cfg.codecConfiguration) { + if (cfg_ltv.getTag() == + CodecSpecificConfigurationLtv::Tag::audioChannelAllocation) { + return cfg_ltv + .get() + .bitmask; + } + } + return 0; +} + +int getCountFromBitmask(int bitmask) { + return std::bitset<32>(bitmask).count(); +} + +std::optional findValidMonoConfig( + std::vector& valid_direction_configurations, + int bitmask) { + for (auto& cfg : valid_direction_configurations) { + int cfg_bitmask = + getLeAudioAseConfigurationAllocationBitmask(cfg.aseConfiguration); + if (getCountFromBitmask(cfg_bitmask) <= 1) { + // Modify the bitmask to be the same as the requirement + for (auto& codec_cfg : cfg.aseConfiguration.codecConfiguration) { + if (codec_cfg.getTag() == + CodecSpecificConfigurationLtv::Tag::audioChannelAllocation) { + codec_cfg + .get() + .bitmask = bitmask; + return cfg; + } + } + } + } + return std::nullopt; +} + +std::vector getValidConfigurationsFromAllocation( + int req_allocation_bitmask, + std::vector& valid_direction_configurations, + bool is_exact) { + // Prefer the same allocation_bitmask + int channel_count = getCountFromBitmask(req_allocation_bitmask); + + if (is_exact) { + for (auto& cfg : valid_direction_configurations) { + int cfg_bitmask = + getLeAudioAseConfigurationAllocationBitmask(cfg.aseConfiguration); + if (cfg_bitmask == req_allocation_bitmask) { + LOG(DEBUG) + << __func__ + << ": Found an exact match for the requirement allocation of " + << cfg_bitmask; + return {cfg}; + } + } + return {}; + } + // Not using exact match strategy + if (channel_count <= 1) { + // Mono requirement matched if cfg is a mono config + auto cfg = findValidMonoConfig(valid_direction_configurations, + req_allocation_bitmask); + if (cfg.has_value()) return {cfg.value()}; + } else { + // Stereo requirement returns 2 mono configs + // that has a combined bitmask equal to the stereo config + std::vector temp; + for (int bit = 0; bit < 32; ++bit) + if (req_allocation_bitmask & (1 << bit)) { + auto cfg = + findValidMonoConfig(valid_direction_configurations, (1 << bit)); + if (cfg.has_value()) temp.push_back(cfg.value()); + } + if (temp.size() == channel_count) return temp; + } + return {}; +} + void LeAudioOffloadAudioProvider::filterRequirementAseDirectionConfiguration( std::optional>>& direction_configurations, const std::vector>& requirements, std::optional>>& - valid_direction_configurations) { + valid_direction_configurations, + bool is_exact) { + // For every requirement, find the matched ase configuration + if (!direction_configurations.has_value()) return; + if (!valid_direction_configurations.has_value()) { valid_direction_configurations = std::vector>(); } - // For every requirement, find the matched ase configuration - if (!direction_configurations.has_value()) return; + for (auto& requirement : requirements) { if (!requirement.has_value()) continue; + auto req_allocation_bitmask = getLeAudioAseConfigurationAllocationBitmask( + requirement.value().aseConfiguration); + auto req_channel_count = getCountFromBitmask(req_allocation_bitmask); + + auto temp = std::vector(); + for (auto direction_configuration : direction_configurations.value()) { if (!direction_configuration.has_value()) continue; - if (!isMatchedAseConfiguration( + if (!filterMatchedAseConfiguration( direction_configuration.value().aseConfiguration, requirement.value().aseConfiguration)) continue; // Valid if match any requirement. - valid_direction_configurations.value().push_back(direction_configuration); - break; + temp.push_back(direction_configuration.value()); + } + + // Get the best matching config based on channel allocation + auto total_cfg_channel_count = 0; + auto req_valid_configs = getValidConfigurationsFromAllocation( + req_allocation_bitmask, temp, is_exact); + // Count and check required channel counts + for (auto& cfg : req_valid_configs) { + total_cfg_channel_count += getCountFromBitmask( + getLeAudioAseConfigurationAllocationBitmask(cfg.aseConfiguration)); + valid_direction_configurations.value().push_back(cfg); + } + if (total_cfg_channel_count != req_channel_count) { + valid_direction_configurations = std::nullopt; + return; } - } - // Ensure that each requirement will have one direction configurations - if (valid_direction_configurations.value().empty() || - (valid_direction_configurations.value().size() != requirements.size())) { - valid_direction_configurations = std::nullopt; } } @@ -444,10 +572,6 @@ LeAudioOffloadAudioProvider::getCapabilitiesMatchedAseConfigurationSettings( .flags = setting.flags, .packing = setting.packing, }; - // Try to match context in metadata. - if (!filterCapabilitiesMatchedContext(filtered_setting.audioContext, - capabilities)) - return std::nullopt; // Get a list of all matched AseDirectionConfiguration // for the input direction @@ -488,8 +612,8 @@ LeAudioOffloadAudioProvider::getCapabilitiesMatchedAseConfigurationSettings( std::optional LeAudioOffloadAudioProvider::getRequirementMatchedAseConfigurationSettings( IBluetoothAudioProvider::LeAudioAseConfigurationSetting& setting, - const IBluetoothAudioProvider::LeAudioConfigurationRequirement& - requirement) { + const IBluetoothAudioProvider::LeAudioConfigurationRequirement& requirement, + bool is_exact) { // Try to match context in metadata. if ((setting.audioContext.bitmask & requirement.audioContext.bitmask) != requirement.audioContext.bitmask) @@ -498,12 +622,6 @@ LeAudioOffloadAudioProvider::getRequirementMatchedAseConfigurationSettings( // Further filter setting's context setting.audioContext.bitmask &= requirement.audioContext.bitmask; - // Check requirement for the correct direction - const std::optional>>* - direction_requirement; - std::vector>* - direction_configuration; - // Create a new LeAudioAseConfigurationSetting to return LeAudioAseConfigurationSetting filtered_setting{ .audioContext = setting.audioContext, @@ -514,22 +632,63 @@ LeAudioOffloadAudioProvider::getRequirementMatchedAseConfigurationSettings( if (requirement.sinkAseRequirement.has_value()) { filterRequirementAseDirectionConfiguration( setting.sinkAseConfiguration, requirement.sinkAseRequirement.value(), - filtered_setting.sinkAseConfiguration); - if (!filtered_setting.sinkAseConfiguration.has_value()) return std::nullopt; + filtered_setting.sinkAseConfiguration, is_exact); + if (!filtered_setting.sinkAseConfiguration.has_value()) { + return std::nullopt; + } } if (requirement.sourceAseRequirement.has_value()) { filterRequirementAseDirectionConfiguration( setting.sourceAseConfiguration, requirement.sourceAseRequirement.value(), - filtered_setting.sourceAseConfiguration); - if (!filtered_setting.sourceAseConfiguration.has_value()) + filtered_setting.sourceAseConfiguration, is_exact); + if (!filtered_setting.sourceAseConfiguration.has_value()) { return std::nullopt; + } } return filtered_setting; } +std::vector +LeAudioOffloadAudioProvider::matchWithRequirement( + std::vector& + matched_ase_configuration_settings, + const std::vector& + in_requirements, + bool is_exact) { + // Each requirement will match with a valid setting + std::vector result; + for (auto& requirement : in_requirements) { + LOG(INFO) << __func__ << ": Trying to match for the requirement " + << requirement.toString(); + bool is_matched = false; + + for (auto& setting : matched_ase_configuration_settings) { + auto filtered_ase_configuration_setting = + getRequirementMatchedAseConfigurationSettings(setting, requirement, + is_exact); + if (filtered_ase_configuration_setting.has_value()) { + result.push_back(filtered_ase_configuration_setting.value()); + LOG(INFO) << __func__ << ": Result = " + << filtered_ase_configuration_setting.value().toString(); + // Found a matched setting, ignore other settings + is_matched = true; + break; + } + } + if (!is_matched) { + // If cannot satisfy this requirement, return an empty result + LOG(WARNING) << __func__ << ": Cannot match the requirement " + << requirement.toString(); + result.clear(); + break; + } + } + return result; +} + // For each requirement, a valid ASE configuration will satify: // - matched with any sink capability (if presented) // - OR matched with any source capability (if presented) @@ -555,31 +714,43 @@ ndk::ScopedAStatus LeAudioOffloadAudioProvider::getLeAudioAseConfiguration( return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); } - // Each setting consist of source and sink AseDirectionConfiguration vector - // Filter every sink capability + // Split out preferred and non-preferred settings based on context + // An example: preferred = MEDIA, available: MEDIA | CONVERSATION + // -> preferred list will have settings with MEDIA context + // -> non-preferred list will have settings with any context + // We want to match requirement with preferred context settings first std::vector matched_ase_configuration_settings; + // Matched ASE configuration with non-preferred audio context + std::vector + non_prefer_matched_ase_configuration_settings; - if (in_remoteSinkAudioCapabilities.has_value()) { + if (in_remoteSinkAudioCapabilities.has_value()) // Matching each setting with any remote capabilities - for (auto& setting : ase_configuration_settings) { + for (auto& setting : ase_configuration_settings) for (auto& capability : in_remoteSinkAudioCapabilities.value()) { if (!capability.has_value()) continue; auto filtered_ase_configuration_setting = getCapabilitiesMatchedAseConfigurationSettings( setting, capability.value(), kLeAudioDirectionSink); if (filtered_ase_configuration_setting.has_value()) { - matched_ase_configuration_settings.push_back( + // Push to non-prefer first for the broadest matching possible + non_prefer_matched_ase_configuration_settings.push_back( filtered_ase_configuration_setting.value()); + // Try to filter out prefer context to another vector. + if (filterCapabilitiesMatchedContext( + filtered_ase_configuration_setting.value().audioContext, + capability.value())) { + matched_ase_configuration_settings.push_back( + filtered_ase_configuration_setting.value()); + } } } - } - } // Combine filter every source capability - if (in_remoteSourceAudioCapabilities.has_value()) { + if (in_remoteSourceAudioCapabilities.has_value()) // Matching each setting with any remote capabilities - for (auto& setting : ase_configuration_settings) { + for (auto& setting : ase_configuration_settings) for (auto& capability : in_remoteSourceAudioCapabilities.value()) { if (!capability.has_value()) continue; auto filtered_ase_configuration_setting = @@ -588,45 +759,56 @@ ndk::ScopedAStatus LeAudioOffloadAudioProvider::getLeAudioAseConfiguration( if (filtered_ase_configuration_setting.has_value()) { // Put into the same list // possibly duplicated, filtered by requirement later - matched_ase_configuration_settings.push_back( + // Push to non-prefer first for the broadest matching possible + non_prefer_matched_ase_configuration_settings.push_back( filtered_ase_configuration_setting.value()); + // Try to filter out prefer context to another vector. + if (filterCapabilitiesMatchedContext( + filtered_ase_configuration_setting.value().audioContext, + capability.value())) { + matched_ase_configuration_settings.push_back( + filtered_ase_configuration_setting.value()); + } } } - } - } - if (matched_ase_configuration_settings.empty()) { - LOG(WARNING) << __func__ << ": No setting matched the capability"; - return ndk::ScopedAStatus::ok(); - } - // Each requirement will match with a valid setting - std::vector result; - for (auto& requirement : in_requirements) { - LOG(INFO) << __func__ << ": Trying to match for the requirement " - << requirement.toString(); - bool is_matched = false; + // Matching priority list: + // Preferred context - exact match with allocation + // Any context - exact match with allocation + // Preferred context - loose match with allocation + // Any context - loose match with allocation - for (auto& setting : matched_ase_configuration_settings) { - auto filtered_ase_configuration_setting = - getRequirementMatchedAseConfigurationSettings(setting, requirement); - if (filtered_ase_configuration_setting.has_value()) { - result.push_back(filtered_ase_configuration_setting.value()); - LOG(INFO) << __func__ << ": Result = " - << filtered_ase_configuration_setting.value().toString(); - // Found a matched setting, ignore other settings - is_matched = true; - break; - } - } - if (!is_matched) { - // If cannot satisfy this requirement, return an empty result - LOG(WARNING) << __func__ << ": Cannot match the requirement " - << requirement.toString(); - result.clear(); - break; - } + // A loose match will attempt to return 2 settings with the + // combined allocation bitmask equal the required allocation. + // For example, we can return 2 link (left link and right link) when + // the requirement required 1 (left + right) link. + auto result = matchWithRequirement(matched_ase_configuration_settings, + in_requirements, true); + if (result.empty()) { + LOG(WARNING) + << __func__ + << ": Cannot match with preferred context settings - exact match"; + result = matchWithRequirement(non_prefer_matched_ase_configuration_settings, + in_requirements, true); } - + if (result.empty()) { + LOG(WARNING) + << __func__ + << ": Cannot match with non-preferred context settings - exact match"; + result = matchWithRequirement(matched_ase_configuration_settings, + in_requirements, false); + } + if (result.empty()) { + LOG(WARNING) << __func__ + << ": Cannot match with preferred context settings - " + "non-exact match"; + result = matchWithRequirement(non_prefer_matched_ase_configuration_settings, + in_requirements, false); + } + if (result.empty()) + LOG(ERROR) << __func__ + << ": Cannot match with non preferred context settings - " + "non-exact match"; *_aidl_return = result; return ndk::ScopedAStatus::ok(); }; @@ -642,7 +824,6 @@ bool LeAudioOffloadAudioProvider::isMatchedQosRequirement( requirement_qos.maxTransportLatencyMs) { return false; } - // Ignore other parameters, as they are not populated in the setting_qos return true; } @@ -658,7 +839,8 @@ LeAudioOffloadAudioProvider::getDirectionQosConfiguration( uint8_t direction, const IBluetoothAudioProvider::LeAudioAseQosConfigurationRequirement& qosRequirement, - std::vector& ase_configuration_settings) { + std::vector& ase_configuration_settings, + bool is_exact) { std::optional direction_qos_requirement = std::nullopt; @@ -680,17 +862,26 @@ LeAudioOffloadAudioProvider::getDirectionQosConfiguration( // Get a list of all matched AseDirectionConfiguration // for the input direction - std::vector>* - direction_configuration = nullptr; + std::optional>> + direction_configuration = std::nullopt; if (direction == kLeAudioDirectionSink) { if (!setting.sinkAseConfiguration.has_value()) continue; - direction_configuration = &setting.sinkAseConfiguration.value(); + direction_configuration.emplace(setting.sinkAseConfiguration.value()); } else { if (!setting.sourceAseConfiguration.has_value()) continue; - direction_configuration = &setting.sourceAseConfiguration.value(); + direction_configuration.emplace(setting.sourceAseConfiguration.value()); } - for (auto cfg : *direction_configuration) { + if (!direction_configuration.has_value()) { + return std::nullopt; + } + + // Collect all valid cfg into a vector + // Then try to get the best match for audio allocation + + auto temp = std::vector(); + + for (auto& cfg : direction_configuration.value()) { if (!cfg.has_value()) continue; // If no requirement, return the first QoS if (!direction_qos_requirement.has_value()) { @@ -701,14 +892,30 @@ LeAudioOffloadAudioProvider::getDirectionQosConfiguration( // Try to match the ASE configuration // and QoS with requirement if (!cfg.value().qosConfiguration.has_value()) continue; - if (isMatchedAseConfiguration( + if (filterMatchedAseConfiguration( cfg.value().aseConfiguration, direction_qos_requirement.value().aseConfiguration) && isMatchedQosRequirement(cfg.value().qosConfiguration.value(), direction_qos_requirement.value())) { - return cfg.value().qosConfiguration; + temp.push_back(cfg.value()); } } + LOG(WARNING) << __func__ << ": Got " << temp.size() + << " configs, start matching allocation"; + + int qos_allocation_bitmask = getLeAudioAseConfigurationAllocationBitmask( + direction_qos_requirement.value().aseConfiguration); + // Get the best matching config based on channel allocation + auto req_valid_configs = getValidConfigurationsFromAllocation( + qos_allocation_bitmask, temp, is_exact); + if (req_valid_configs.empty()) { + LOG(WARNING) << __func__ + << ": Cannot find matching allocation for bitmask " + << qos_allocation_bitmask; + + } else { + return req_valid_configs[0].qosConfiguration; + } } return std::nullopt; @@ -730,15 +937,30 @@ ndk::ScopedAStatus LeAudioOffloadAudioProvider::getLeAudioAseQosConfiguration( if (in_qosRequirement.sinkAseQosRequirement.has_value()) { if (!isValidQosRequirement(in_qosRequirement.sinkAseQosRequirement.value())) return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); - result.sinkQosConfiguration = getDirectionQosConfiguration( - kLeAudioDirectionSink, in_qosRequirement, ase_configuration_settings); + { + // Try exact match first + result.sinkQosConfiguration = + getDirectionQosConfiguration(kLeAudioDirectionSink, in_qosRequirement, + ase_configuration_settings, true); + if (!result.sinkQosConfiguration.has_value()) { + result.sinkQosConfiguration = getDirectionQosConfiguration( + kLeAudioDirectionSink, in_qosRequirement, + ase_configuration_settings, false); + } + } } if (in_qosRequirement.sourceAseQosRequirement.has_value()) { if (!isValidQosRequirement( in_qosRequirement.sourceAseQosRequirement.value())) return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); - result.sourceQosConfiguration = getDirectionQosConfiguration( - kLeAudioDirectionSource, in_qosRequirement, ase_configuration_settings); + result.sourceQosConfiguration = + getDirectionQosConfiguration(kLeAudioDirectionSource, in_qosRequirement, + ase_configuration_settings, true); + if (!result.sourceQosConfiguration.has_value()) { + result.sourceQosConfiguration = getDirectionQosConfiguration( + kLeAudioDirectionSource, in_qosRequirement, + ase_configuration_settings, false); + } } *_aidl_return = result; @@ -763,28 +985,127 @@ ndk::ScopedAStatus LeAudioOffloadAudioProvider::onSourceAseMetadataChanged( return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); }; +LeAudioBroadcastConfigurationSetting getDefaultBroadcastSetting( + int context_bitmask, IBluetoothAudioProvider::BroadcastQuality quality) { + LeAudioBroadcastConfigurationSetting setting; + setting.retransmitionNum = 4; + setting.maxTransportLatencyMs = 60; + setting.sduIntervalUs = 10000; + setting.maxSduOctets = 40; + + if (quality == IBluetoothAudioProvider::BroadcastQuality::HIGH) { + LOG(INFO) << __func__ << ": High quality, returning high quality settings"; + setting.retransmitionNum = 4; + setting.maxTransportLatencyMs = 65; + setting.maxSduOctets = 200; + return setting; + } + + // Populate other settings base on context + // TODO: Populate with better design + if (context_bitmask & (AudioContext::LIVE_AUDIO | AudioContext::GAME)) { + setting.retransmitionNum = 2; + setting.maxTransportLatencyMs = 10; + setting.maxSduOctets = 120; + } else if (context_bitmask & (AudioContext::INSTRUCTIONAL)) { + setting.retransmitionNum = 2; + setting.maxTransportLatencyMs = 10; + setting.maxSduOctets = 40; + } else if (context_bitmask & + (AudioContext::SOUND_EFFECTS | AudioContext::UNSPECIFIED)) { + setting.retransmitionNum = 4; + setting.maxTransportLatencyMs = 60; + setting.maxSduOctets = 80; + } else if (context_bitmask & + (AudioContext::ALERTS | AudioContext::NOTIFICATIONS | + AudioContext::EMERGENCY_ALARM)) { + setting.retransmitionNum = 4; + setting.maxTransportLatencyMs = 60; + setting.maxSduOctets = 40; + } else if (context_bitmask & AudioContext::MEDIA) { + setting.retransmitionNum = 4; + setting.maxTransportLatencyMs = 60; + setting.maxSduOctets = 120; + } + + return setting; +} +void modifySubBISConfigAllocation( + IBluetoothAudioProvider::LeAudioSubgroupBisConfiguration& sub_bis_cfg, + int allocation_bitmask) { + for (auto& codec_cfg : sub_bis_cfg.bisConfiguration.codecConfiguration) { + if (codec_cfg.getTag() == + CodecSpecificConfigurationLtv::audioChannelAllocation) { + codec_cfg.get() + .bitmask = allocation_bitmask; + break; + } + } +} +void modifySubgroupConfiguration( + IBluetoothAudioProvider::LeAudioBroadcastSubgroupConfiguration& + subgroup_cfg, + int context_bitmask) { + // STEREO configs + // Split into 2 sub BIS config, each has numBis = 1 + if (context_bitmask & (AudioContext::LIVE_AUDIO | AudioContext::GAME | + AudioContext::SOUND_EFFECTS | + AudioContext::UNSPECIFIED | AudioContext::MEDIA)) { + if (subgroup_cfg.bisConfigurations.size() == 1) + subgroup_cfg.bisConfigurations.push_back( + subgroup_cfg.bisConfigurations[0]); + + subgroup_cfg.bisConfigurations[0].numBis = 1; + modifySubBISConfigAllocation( + subgroup_cfg.bisConfigurations[0], + CodecSpecificConfigurationLtv::AudioChannelAllocation::FRONT_LEFT); + + subgroup_cfg.bisConfigurations[1].numBis = 1; + modifySubBISConfigAllocation( + subgroup_cfg.bisConfigurations[1], + CodecSpecificConfigurationLtv::AudioChannelAllocation::FRONT_RIGHT); + return; + } + + // MONO configs + for (auto& sub_bis_cfg : subgroup_cfg.bisConfigurations) { + sub_bis_cfg.numBis = 1; + modifySubBISConfigAllocation( + sub_bis_cfg, + CodecSpecificConfigurationLtv::AudioChannelAllocation::FRONT_CENTER); + } +} + void LeAudioOffloadAudioProvider::getBroadcastSettings() { if (!broadcast_settings.empty()) return; - LOG(INFO) << __func__ << ": Loading broadcast settings from provider info"; + LOG(INFO) << __func__ + << ": Loading basic broadcast settings from provider info"; std::vector db_codec_info = BluetoothAudioCodecs::GetLeAudioOffloadCodecInfo( SessionType::LE_AUDIO_BROADCAST_HARDWARE_OFFLOAD_ENCODING_DATAPATH); + for (auto x : db_codec_info) { + LOG(INFO) << __func__ << ": codec info = " << x.toString(); + } broadcast_settings.clear(); - // Default value for unmapped fields + // Default value population CodecSpecificConfigurationLtv::AudioChannelAllocation default_allocation; default_allocation.bitmask = CodecSpecificConfigurationLtv::AudioChannelAllocation::FRONT_CENTER; CodecSpecificConfigurationLtv::CodecFrameBlocksPerSDU default_frame; - default_frame.value = 2; + default_frame.value = 1; for (auto& codec_info : db_codec_info) { if (codec_info.transport.getTag() != CodecInfo::Transport::leAudio) continue; auto& transport = codec_info.transport.get(); LeAudioBroadcastConfigurationSetting setting; + setting.retransmitionNum = 4; + setting.maxTransportLatencyMs = 60; + setting.sduIntervalUs = 10000; + setting.maxSduOctets = 40; // Default setting setting.numBis = 1; setting.phy = {Phy::TWO_M}; @@ -803,13 +1124,15 @@ void LeAudioOffloadAudioProvider::getBroadcastSettings() { default_frame, }; + // Ignore bis_cfg.metadata + // Add information to structure IBluetoothAudioProvider::LeAudioSubgroupBisConfiguration sub_bis_cfg; - sub_bis_cfg.numBis = 2; + sub_bis_cfg.numBis = 1; sub_bis_cfg.bisConfiguration = bis_cfg; IBluetoothAudioProvider::LeAudioBroadcastSubgroupConfiguration sub_cfg; // Populate the same sub config - sub_cfg.bisConfigurations = {sub_bis_cfg, sub_bis_cfg}; + sub_cfg.bisConfigurations = {sub_bis_cfg}; setting.subgroupsConfigurations = {sub_cfg}; broadcast_settings.push_back(setting); @@ -853,34 +1176,91 @@ LeAudioOffloadAudioProvider:: return filtered_setting; } +std::vector getCodecRequirementBasedOnContext( + int context_bitmask, IBluetoothAudioProvider::BroadcastQuality quality) { + // Default requirement: lc3_stereo_16_2 + std::vector requirement = { + CodecSpecificConfigurationLtv::SamplingFrequency::HZ16000, + CodecSpecificConfigurationLtv::FrameDuration::US10000, + }; + + if (quality == IBluetoothAudioProvider::BroadcastQuality::HIGH) { + LOG(INFO) << __func__ + << ": High quality, returning high quality requirement"; + requirement = { + CodecSpecificConfigurationLtv::SamplingFrequency::HZ48000, + CodecSpecificConfigurationLtv::FrameDuration::US10000, + }; + return requirement; + } + + if (context_bitmask & (AudioContext::LIVE_AUDIO | AudioContext::GAME)) { + // lc3_stereo_24_2_1 + requirement = { + CodecSpecificConfigurationLtv::SamplingFrequency::HZ24000, + CodecSpecificConfigurationLtv::FrameDuration::US10000, + }; + } else if (context_bitmask & (AudioContext::INSTRUCTIONAL)) { + // lc3_mono_16_2 + requirement = { + CodecSpecificConfigurationLtv::SamplingFrequency::HZ16000, + CodecSpecificConfigurationLtv::FrameDuration::US10000, + }; + } else if (context_bitmask & + (AudioContext::SOUND_EFFECTS | AudioContext::UNSPECIFIED)) { + // lc3_stereo_16_2 + requirement = { + CodecSpecificConfigurationLtv::SamplingFrequency::HZ16000, + CodecSpecificConfigurationLtv::FrameDuration::US10000, + }; + } else if (context_bitmask & + (AudioContext::ALERTS | AudioContext::NOTIFICATIONS | + AudioContext::EMERGENCY_ALARM)) { + // Default requirement: lc3_stereo_16_2 + requirement = { + CodecSpecificConfigurationLtv::SamplingFrequency::HZ16000, + CodecSpecificConfigurationLtv::FrameDuration::US10000, + }; + } else if (context_bitmask & AudioContext::MEDIA) { + // Default requirement: lc3_stereo_16_2 + // Return the 48k requirement + requirement = { + CodecSpecificConfigurationLtv::SamplingFrequency::HZ24000, + CodecSpecificConfigurationLtv::FrameDuration::US10000, + }; + } + return requirement; +} + bool LeAudioOffloadAudioProvider::isSubgroupConfigurationMatchedContext( - AudioContext setting_context, + AudioContext requirement_context, + IBluetoothAudioProvider::BroadcastQuality quality, LeAudioBroadcastSubgroupConfiguration configuration) { // Find any valid context metadata in the bisConfigurations // assuming the bis configuration in the same bis subgroup // will have the same context metadata std::optional config_context = std::nullopt; - for (auto& p : configuration.bisConfigurations) - if (p.bisConfiguration.metadata.has_value()) { - bool is_context_found = false; - for (auto& metadata : p.bisConfiguration.metadata.value()) { - if (!metadata.has_value()) continue; - if (metadata.value().getTag() == - MetadataLtv::Tag::preferredAudioContexts) { - config_context = metadata.value() - .get() - .values; - is_context_found = true; - break; - } - } - if (is_context_found) break; - } + auto codec_requirement = + getCodecRequirementBasedOnContext(requirement_context.bitmask, quality); + std::map + req_tag_map; + for (auto x : codec_requirement) req_tag_map[x.getTag()] = x; - // Not found context metadata in any of the bis configuration, assume matched - if (!config_context.has_value()) return true; - return (setting_context.bitmask & config_context.value().bitmask); + for (auto& bis_cfg : configuration.bisConfigurations) { + // Check every sub_bis_cfg to see which match + for (auto& x : bis_cfg.bisConfiguration.codecConfiguration) { + auto p = req_tag_map.find(x.getTag()); + if (p == req_tag_map.end()) continue; + if (p->second != x) { + LOG(WARNING) << __func__ << ": does not match for context " + << requirement_context.toString() + << ", cfg = " << x.toString(); + return false; + } + } + } + return true; } ndk::ScopedAStatus @@ -900,7 +1280,8 @@ LeAudioOffloadAudioProvider::getLeAudioBroadcastConfiguration( // We will allow empty capability input, match all settings with requirements. getBroadcastSettings(); std::vector filtered_settings; - if (!in_remoteSinkAudioCapabilities.has_value()) { + if (!in_remoteSinkAudioCapabilities.has_value() || + in_remoteSinkAudioCapabilities.value().empty()) { LOG(INFO) << __func__ << ": Empty capability, get all broadcast settings"; filtered_settings = broadcast_settings; } else { @@ -931,45 +1312,72 @@ LeAudioOffloadAudioProvider::getLeAudioBroadcastConfiguration( // Gather these suitable subgroup config in an array. // If the setting can satisfy all requirement, we can return the setting // with the filtered array. - for (auto& setting : filtered_settings) { - LeAudioBroadcastConfigurationSetting matched_setting(setting); - matched_setting.subgroupsConfigurations.clear(); - auto total_num_bis = 0; - bool matched_all_requirement = true; + auto context_bitmask = + in_requirement.subgroupConfigurationRequirements[0].audioContext.bitmask; + auto quality = in_requirement.subgroupConfigurationRequirements[0].quality; + LeAudioBroadcastConfigurationSetting return_setting = + getDefaultBroadcastSetting(context_bitmask, quality); + // Default setting + return_setting.numBis = 0; + return_setting.subgroupsConfigurations = {}; - for (auto& sub_req : in_requirement.subgroupConfigurationRequirements) { - bool is_matched = false; - for (auto& sub_cfg : setting.subgroupsConfigurations) { - // Match the context - if (!isSubgroupConfigurationMatchedContext(sub_req.audioContext, - sub_cfg)) - continue; - // Matching number of BIS - if (sub_req.bisNumPerSubgroup != sub_cfg.bisConfigurations.size()) - continue; - // Currently will ignore quality matching. - matched_setting.subgroupsConfigurations.push_back(sub_cfg); - total_num_bis += sub_cfg.bisConfigurations.size(); - is_matched = true; - break; + LeAudioDataPathConfiguration path; + path.isoDataPathConfiguration.isTransparent = true; + path.dataPathId = kIsoDataPathPlatformDefault; + + // Each subreq, find a setting that match + for (auto& sub_req : in_requirement.subgroupConfigurationRequirements) { + bool is_setting_matched = false; + for (auto setting : filtered_settings) { + bool is_matched = true; + // Check if every sub BIS config satisfy + for (auto& sub_group_config : setting.subgroupsConfigurations) { + if (!isSubgroupConfigurationMatchedContext( + sub_req.audioContext, sub_req.quality, sub_group_config)) { + is_matched = false; + break; + } + path.isoDataPathConfiguration.codecId = + sub_group_config.bisConfigurations[0].bisConfiguration.codecId; + // Also modify the subgroup config to match the context + modifySubgroupConfiguration(sub_group_config, context_bitmask); } - // There is an unmatched requirement, this setting cannot be used - if (!is_matched) { - matched_all_requirement = false; + + if (is_matched) { + is_setting_matched = true; + for (auto& sub_group_config : setting.subgroupsConfigurations) + return_setting.subgroupsConfigurations.push_back(sub_group_config); break; } } - if (!matched_all_requirement) continue; - - matched_setting.numBis = total_num_bis; - // Return the filtered setting if all requirement satified - *_aidl_return = matched_setting; - return ndk::ScopedAStatus::ok(); + if (!is_setting_matched) { + LOG(WARNING) << __func__ + << ": Cannot find a setting that match requirement " + << sub_req.toString(); + return ndk::ScopedAStatus::ok(); + } } - LOG(WARNING) << __func__ << ": Cannot match any requirement"; + // Populate all numBis + for (auto& sub_group_config : return_setting.subgroupsConfigurations) { + for (auto& sub_bis_config : sub_group_config.bisConfigurations) { + return_setting.numBis += sub_bis_config.numBis; + } + } + return_setting.phy = std::vector(return_setting.numBis, Phy::TWO_M); + // Populate data path config + return_setting.dataPathConfiguration = path; + // TODO: Workaround for STEREO configs maxSduOctets being doubled + if (context_bitmask & (AudioContext::LIVE_AUDIO | AudioContext::GAME | + AudioContext::SOUND_EFFECTS | + AudioContext::UNSPECIFIED | AudioContext::MEDIA)) { + return_setting.maxSduOctets /= 2; + } + LOG(INFO) << __func__ + << ": Combined setting that match: " << return_setting.toString(); + *_aidl_return = return_setting; return ndk::ScopedAStatus::ok(); }; diff --git a/bluetooth/audio/aidl/default/LeAudioOffloadAudioProvider.h b/bluetooth/audio/aidl/default/LeAudioOffloadAudioProvider.h index 06cd405fd7..043d923c83 100644 --- a/bluetooth/audio/aidl/default/LeAudioOffloadAudioProvider.h +++ b/bluetooth/audio/aidl/default/LeAudioOffloadAudioProvider.h @@ -122,8 +122,9 @@ class LeAudioOffloadAudioProvider : public BluetoothAudioProvider { bool isCapabilitiesMatchedCodecConfiguration( std::vector& codec_cfg, std::vector codec_capabilities); - bool isMatchedAseConfiguration(LeAudioAseConfiguration setting_cfg, - LeAudioAseConfiguration requirement_cfg); + bool filterMatchedAseConfiguration( + LeAudioAseConfiguration& setting_cfg, + const LeAudioAseConfiguration& requirement_cfg); bool isMatchedBISConfiguration( LeAudioBisConfiguration bis_cfg, const IBluetoothAudioProvider::LeAudioDeviceCapabilities& capabilities); @@ -138,7 +139,8 @@ class LeAudioOffloadAudioProvider : public BluetoothAudioProvider { direction_configurations, const std::vector>& requirements, std::optional>>& - valid_direction_configurations); + valid_direction_configurations, + bool is_exact); std::optional getCapabilitiesMatchedAseConfigurationSettings( IBluetoothAudioProvider::LeAudioAseConfigurationSetting& setting, @@ -148,7 +150,8 @@ class LeAudioOffloadAudioProvider : public BluetoothAudioProvider { getRequirementMatchedAseConfigurationSettings( IBluetoothAudioProvider::LeAudioAseConfigurationSetting& setting, const IBluetoothAudioProvider::LeAudioConfigurationRequirement& - requirement); + requirement, + bool is_exact); bool isMatchedQosRequirement(LeAudioAseQosConfiguration setting_qos, AseQosDirectionRequirement requirement_qos); std::optional @@ -160,10 +163,20 @@ class LeAudioOffloadAudioProvider : public BluetoothAudioProvider { uint8_t direction, const IBluetoothAudioProvider::LeAudioAseQosConfigurationRequirement& qosRequirement, - std::vector& ase_configuration_settings); + std::vector& ase_configuration_settings, + bool is_exact); bool isSubgroupConfigurationMatchedContext( AudioContext requirement_context, + IBluetoothAudioProvider::BroadcastQuality quality, LeAudioBroadcastSubgroupConfiguration configuration); + std::vector + matchWithRequirement( + std::vector& + matched_ase_configuration_settings, + const std::vector< + IBluetoothAudioProvider::LeAudioConfigurationRequirement>& + in_requirements, + bool is_exact); }; class LeAudioOffloadOutputAudioProvider : public LeAudioOffloadAudioProvider { diff --git a/bluetooth/audio/aidl/vts/VtsHalBluetoothAudioTargetTest.cpp b/bluetooth/audio/aidl/vts/VtsHalBluetoothAudioTargetTest.cpp index ec63831301..66cca0b128 100644 --- a/bluetooth/audio/aidl/vts/VtsHalBluetoothAudioTargetTest.cpp +++ b/bluetooth/audio/aidl/vts/VtsHalBluetoothAudioTargetTest.cpp @@ -4177,6 +4177,7 @@ class BluetoothAudioProviderLeAudioBroadcastHardwareAidl CodecSpecificCapabilitiesLtv::SupportedSamplingFrequencies(); sampling_rate.bitmask = CodecSpecificCapabilitiesLtv::SupportedSamplingFrequencies::HZ48000 | + CodecSpecificCapabilitiesLtv::SupportedSamplingFrequencies::HZ24000 | CodecSpecificCapabilitiesLtv::SupportedSamplingFrequencies::HZ16000; auto frame_duration = CodecSpecificCapabilitiesLtv::SupportedFrameDurations(); diff --git a/bluetooth/audio/utils/aidl_session/BluetoothLeAudioAseConfigurationSettingProvider.cpp b/bluetooth/audio/utils/aidl_session/BluetoothLeAudioAseConfigurationSettingProvider.cpp index 5429a8fca7..780154ad8a 100644 --- a/bluetooth/audio/utils/aidl_session/BluetoothLeAudioAseConfigurationSettingProvider.cpp +++ b/bluetooth/audio/utils/aidl_session/BluetoothLeAudioAseConfigurationSettingProvider.cpp @@ -267,7 +267,7 @@ void AudioSetConfigurationProviderJson:: ase_configuration_settings_.clear(); configurations_.clear(); auto loaded = LoadContent(kLeAudioSetConfigs, kLeAudioSetScenarios, - CodecLocation::HOST); + CodecLocation::ADSP); if (!loaded) LOG(ERROR) << ": Unable to load le audio set configuration files."; } else @@ -371,7 +371,6 @@ void AudioSetConfigurationProviderJson::populateConfigurationData( CodecSpecificConfigurationLtv::CodecFrameBlocksPerSDU(); frame_sdu_structure.value = codec_frames_blocks_per_sdu; ase.codecConfiguration.push_back(frame_sdu_structure); - // TODO: Channel count } void AudioSetConfigurationProviderJson::populateAseConfiguration( @@ -415,8 +414,53 @@ void AudioSetConfigurationProviderJson::populateAseConfiguration( } void AudioSetConfigurationProviderJson::populateAseQosConfiguration( - LeAudioAseQosConfiguration& qos, - const le_audio::QosConfiguration* qos_cfg) { + LeAudioAseQosConfiguration& qos, const le_audio::QosConfiguration* qos_cfg, + LeAudioAseConfiguration& ase) { + std::optional + frameBlock = std::nullopt; + std::optional frameDuration = + std::nullopt; + std::optional + allocation = std::nullopt; + std::optional octet = + std::nullopt; + + for (auto& cfg_ltv : ase.codecConfiguration) { + auto tag = cfg_ltv.getTag(); + if (tag == CodecSpecificConfigurationLtv::codecFrameBlocksPerSDU) { + frameBlock = + cfg_ltv.get(); + } else if (tag == CodecSpecificConfigurationLtv::frameDuration) { + frameDuration = + cfg_ltv.get(); + } else if (tag == CodecSpecificConfigurationLtv::audioChannelAllocation) { + allocation = + cfg_ltv.get(); + } else if (tag == CodecSpecificConfigurationLtv::octetsPerCodecFrame) { + octet = cfg_ltv.get(); + } + } + + int frameBlockValue = 1; + if (frameBlock.has_value()) frameBlockValue = frameBlock.value().value; + + // Populate maxSdu + if (allocation.has_value() && octet.has_value()) { + auto channel_count = std::bitset<32>(allocation.value().bitmask).count(); + qos.maxSdu = channel_count * octet.value().value * frameBlockValue; + } + // Populate sduIntervalUs + if (frameDuration.has_value()) { + switch (frameDuration.value()) { + case CodecSpecificConfigurationLtv::FrameDuration::US7500: + qos.sduIntervalUs = 7500; + break; + case CodecSpecificConfigurationLtv::FrameDuration::US10000: + qos.sduIntervalUs = 10000; + break; + } + qos.sduIntervalUs *= frameBlockValue; + } qos.maxTransportLatencyMs = qos_cfg->max_transport_latency(); qos.retransmissionNum = qos_cfg->retransmission_number(); } @@ -436,7 +480,7 @@ AudioSetConfigurationProviderJson::SetConfigurationFromFlatSubconfig( populateAseConfiguration(ase, flat_subconfig, qos_cfg); // Translate into LeAudioAseQosConfiguration - populateAseQosConfiguration(qos, qos_cfg); + populateAseQosConfiguration(qos, qos_cfg, ase); // Translate location to data path id switch (location) { @@ -453,6 +497,8 @@ AudioSetConfigurationProviderJson::SetConfigurationFromFlatSubconfig( path.dataPathId = kIsoDataPathPlatformDefault; break; } + // Move codecId to iso data path + path.isoDataPathConfiguration.codecId = ase.codecId.value(); direction_conf.aseConfiguration = ase; direction_conf.qosConfiguration = qos; @@ -678,7 +724,8 @@ bool AudioSetConfigurationProviderJson::LoadScenariosFromFiles( media_context.bitmask = (AudioContext::ALERTS | AudioContext::INSTRUCTIONAL | AudioContext::NOTIFICATIONS | AudioContext::EMERGENCY_ALARM | - AudioContext::UNSPECIFIED | AudioContext::MEDIA); + AudioContext::UNSPECIFIED | AudioContext::MEDIA | + AudioContext::SOUND_EFFECTS); AudioContext conversational_context = AudioContext(); conversational_context.bitmask = diff --git a/bluetooth/audio/utils/aidl_session/BluetoothLeAudioAseConfigurationSettingProvider.h b/bluetooth/audio/utils/aidl_session/BluetoothLeAudioAseConfigurationSettingProvider.h index ce91fca12b..6639009742 100644 --- a/bluetooth/audio/utils/aidl_session/BluetoothLeAudioAseConfigurationSettingProvider.h +++ b/bluetooth/audio/utils/aidl_session/BluetoothLeAudioAseConfigurationSettingProvider.h @@ -79,7 +79,7 @@ class AudioSetConfigurationProviderJson { static void populateAseQosConfiguration( LeAudioAseQosConfiguration& qos, - const le_audio::QosConfiguration* qos_cfg); + const le_audio::QosConfiguration* qos_cfg, LeAudioAseConfiguration& ase); static AseDirectionConfiguration SetConfigurationFromFlatSubconfig( const le_audio::AudioSetSubConfiguration* flat_subconfig,